1use crate::backend::Backend;
8use crate::compiled::CompiledGraph;
9use crate::precision::Precision;
10use rlx_driver::Device;
11use rlx_ir::Graph;
12use rlx_ir::GraphModule;
13use rlx_ir::hir::HirModule;
14use rlx_opt::PrecisionPolicy;
15
16pub struct Session {
18 device: Device,
19 precision: Precision,
20 policy: Option<PrecisionPolicy>,
24}
25
26impl Session {
27 pub fn new(device: Device) -> Self {
32 Self::new_with_precision(device, Precision::F32)
33 }
34
35 pub fn new_with_precision(device: Device, precision: Precision) -> Self {
38 assert!(
42 crate::device_ext::is_available(device),
43 "device {} is not available — {}",
44 device,
45 if crate::device_ext::feature_compiled(device) {
46 format!(
47 "the `{}` feature is enabled but no such device/driver was found on this host",
48 feature_name(device)
49 )
50 } else {
51 format!("enable the `{}` Cargo feature", feature_name(device))
52 }
53 );
54 Self {
55 device,
56 precision,
57 policy: None,
58 }
59 }
60
61 pub fn with_policy(mut self, policy: PrecisionPolicy) -> Self {
65 self.policy = Some(policy);
66 self
67 }
68
69 pub fn device(&self) -> Device {
70 self.device
71 }
72 pub fn precision(&self) -> Precision {
73 self.precision
74 }
75 pub fn policy(&self) -> Option<&PrecisionPolicy> {
76 self.policy.as_ref()
77 }
78
79 pub fn compile(&self, graph: Graph) -> CompiledGraph {
91 if rlx_ir::dynamic::has_dynamic_dims(&graph) && !self.coreml_native_flex() {
92 return self.compile_deferred(graph, self.default_options());
93 }
94 let opts = self.default_options();
95 if opts.cache_param_invariant
96 && let Some(staged) = self.try_hoist_param_invariant(&graph, &opts)
97 {
98 return staged;
99 }
100 self.compile_module(GraphModule::from_graph(graph))
101 .expect("compile MIR graph through fusion pipeline")
102 }
103
104 fn try_hoist_param_invariant(
109 &self,
110 graph: &Graph,
111 options: &crate::CompileOptions,
112 ) -> Option<CompiledGraph> {
113 let split = rlx_compile::split_param_invariant(graph)?;
114 let mut sub = options.clone();
115 sub.cache_param_invariant = false;
116 let prepare = self.compile_with(split.prepare, &sub);
117 let main = self.compile_with(split.main, &sub);
118 Some(main.with_staging(
119 prepare,
120 split.boundary,
121 split.prepare_params,
122 split.main_params,
123 ))
124 }
125
126 fn compile_deferred(&self, graph: Graph, options: crate::CompileOptions) -> CompiledGraph {
128 let backend = self.create_backend();
129 let io_dtypes: Vec<(String, rlx_ir::DType)> = graph
130 .nodes()
131 .iter()
132 .filter_map(|n| match &n.op {
133 rlx_ir::Op::Input { name } | rlx_ir::Op::Param { name } => {
134 Some((name.clone(), n.shape.dtype()))
135 }
136 _ => None,
137 })
138 .collect();
139 let inner = Box::new(crate::deferred::DeferredExecutable::new(
140 graph,
141 backend,
142 options,
143 self.device,
144 ));
145 let mut compiled = CompiledGraph::new(inner, self.device);
146 compiled.set_io_dtypes(io_dtypes);
147 compiled
148 }
149
150 pub fn compile_graph(&self, graph: Graph) -> CompiledGraph {
152 self.compile(graph)
153 }
154
155 pub fn compile_with(&self, graph: Graph, options: &crate::CompileOptions) -> CompiledGraph {
160 let graph = match options.scaled_quant {
165 Some(cfg) => {
166 rlx_opt::rlx_compile::scaled_quant_insert::insert_scaled_matmul(graph, cfg)
167 }
168 None => graph,
169 };
170 if rlx_ir::dynamic::has_dynamic_dims(&graph) && !self.coreml_native_flex() {
171 return self.compile_deferred(graph, options.clone());
172 }
173 if options.cache_param_invariant
174 && let Some(staged) = self.try_hoist_param_invariant(&graph, options)
175 {
176 return staged;
177 }
178 self.compile_module_with(GraphModule::from_graph(graph), options)
179 .expect("compile MIR graph through fusion pipeline")
180 }
181
182 pub fn compile_hir(&self, hir: HirModule) -> Result<CompiledGraph, rlx_ir::hir::LowerError> {
184 self.compile_hir_with(hir, &self.default_options())
185 }
186
187 pub fn compile_hir_with(
189 &self,
190 hir: HirModule,
191 options: &crate::CompileOptions,
192 ) -> Result<CompiledGraph, rlx_ir::hir::LowerError> {
193 let backend = self.create_backend();
194 let executable = backend.compile_hir(hir, self.device, options)?;
195 Ok(CompiledGraph::new(executable, self.device))
196 }
197
198 pub fn compile_module(
200 &self,
201 module: GraphModule,
202 ) -> Result<CompiledGraph, rlx_ir::hir::LowerError> {
203 self.compile_module_with(module, &self.default_options())
204 }
205
206 pub fn compile_module_with(
208 &self,
209 module: GraphModule,
210 options: &crate::CompileOptions,
211 ) -> Result<CompiledGraph, rlx_ir::hir::LowerError> {
212 let backend = self.create_backend();
213 let io_dtypes = module.io_dtypes();
217 let executable = backend.compile_module(module, self.device, options)?;
218 let mut compiled = CompiledGraph::new(executable, self.device);
219 compiled.set_io_dtypes(io_dtypes);
220 Ok(compiled)
221 }
222
223 fn default_options(&self) -> crate::CompileOptions {
224 let opts = crate::CompileOptions::new().precision(self.precision);
225 match &self.policy {
226 Some(p) => opts.policy(p.clone()),
227 None => opts,
228 }
229 }
230
231 fn coreml_native_flex(&self) -> bool {
233 #[cfg(all(
234 feature = "coreml",
235 target_vendor = "apple",
236 not(target_os = "watchos")
237 ))]
238 {
239 self.device == Device::Ane
240 && rlx_ir::env::var("RLX_COREML_NATIVE_FLEX").as_deref() == Some("1")
241 }
242 #[cfg(not(all(
243 feature = "coreml",
244 target_vendor = "apple",
245 not(target_os = "watchos")
246 )))]
247 {
248 false
249 }
250 }
251
252 fn create_backend(&self) -> Box<dyn Backend> {
253 crate::registry::backend_for(self.device).unwrap_or_else(|| {
257 panic!(
258 "no backend registered for device {} — enable feature `{}` \
259 (or call `rlx_runtime::register_backend` for an external backend)",
260 self.device,
261 feature_name(self.device)
262 )
263 })
264 }
265}
266
267fn feature_name(device: Device) -> &'static str {
268 match device {
269 Device::Cpu => "cpu",
270 Device::Metal => "metal",
271 Device::Mlx => "mlx",
272 Device::Ane => "ane",
273 Device::Cuda => "cuda",
274 Device::Rocm => "rocm",
275 Device::Xdna => "xdna",
276 Device::Egpu => "egpu",
277 Device::OneApi => "oneapi",
278 Device::Tpu => "tpu",
279 Device::Hexagon => "qnn",
280 Device::Gpu => "gpu",
281 Device::Vulkan => "vulkan",
282 Device::OpenGl => "opengl",
283 Device::DirectX => "directx",
284 Device::WebGpu => "webgpu",
285 }
286}