1use std::collections::VecDeque;
12
13use rlx_ir::hir::HirModule;
14use rlx_ir::{BindingManifest, DimBinding, ModelComponent};
15use rlx_opt::CompileResult;
16
17use crate::stages;
18use crate::{CompileOptions, CompiledGraph, Device};
19
20pub struct ModelCompilePipeline {
22 device: Device,
23 capacity: usize,
24 template: Option<CompileResult>,
25 entries: Vec<(u64, CompiledGraph)>,
26 order: VecDeque<u64>,
27}
28
29impl ModelCompilePipeline {
30 pub fn new(device: Device) -> Self {
31 Self::with_capacity(device, 8)
32 }
33
34 pub fn with_capacity(device: Device, capacity: usize) -> Self {
35 assert!(capacity > 0, "ModelCompilePipeline capacity must be ≥ 1");
36 Self {
37 device,
38 capacity,
39 template: None,
40 entries: Vec::new(),
41 order: VecDeque::new(),
42 }
43 }
44
45 pub fn device(&self) -> Device {
46 self.device
47 }
48
49 pub fn has_template(&self) -> bool {
50 self.template.is_some()
51 }
52
53 pub fn build_template<F>(
55 &mut self,
56 build_hir: F,
57 options: &CompileOptions,
58 ) -> Result<&CompileResult, rlx_ir::hir::LowerError>
59 where
60 F: FnOnce() -> HirModule,
61 {
62 if self.template.is_none() {
63 let pipe = stages::pipeline_for(self.device, options);
64 self.template = Some(pipe.compile_hir(build_hir())?);
65 }
66 Ok(self.template.as_ref().expect("template set"))
67 }
68
69 pub fn template_binding_manifest(&self) -> BindingManifest {
70 let template = self.template.as_ref().expect("call build_template first");
71 BindingManifest::from_lir(&template.lir)
72 }
73
74 pub fn specialize_template(
76 &self,
77 binding: &DimBinding,
78 options: &CompileOptions,
79 ) -> CompileResult {
80 let template = self
81 .template
82 .as_ref()
83 .expect("call build_template before specialize_template");
84 let pipe = stages::pipeline_for(self.device, options);
85 template.specialize(&pipe, binding)
86 }
87
88 pub fn compile_lir(
90 &self,
91 specialized: CompileResult,
92 options: &CompileOptions,
93 ) -> CompiledGraph {
94 let backend = crate::registry::backend_for(self.device).expect("backend registered");
95 let executable = backend.compile_lir(specialized.lir, options);
96 CompiledGraph::new(executable, self.device)
97 }
98
99 pub fn get_or_compile<F>(
101 &mut self,
102 key: u64,
103 binding: &DimBinding,
104 build_hir: F,
105 options: &CompileOptions,
106 ) -> Result<&mut CompiledGraph, rlx_ir::hir::LowerError>
107 where
108 F: FnOnce() -> HirModule,
109 {
110 if let Some(idx) = self.entries.iter().position(|(k, _)| *k == key) {
111 return Ok(&mut self.entries[idx].1);
112 }
113 let mut template_opts = options.clone();
114 template_opts.dim_binding = None;
115 self.build_template(build_hir, &template_opts)?;
116 let specialized = self.specialize_template(binding, &template_opts);
117 let mut compile_opts = options.clone();
118 compile_opts.dim_binding = None;
119 let compiled = self.compile_lir(specialized, &compile_opts);
120
121 if self.entries.len() >= self.capacity
122 && let Some(evict) = self.order.pop_front()
123 {
124 self.entries.retain(|(k, _)| *k != evict);
125 }
126 self.entries.push((key, compiled));
127 self.order.push_back(key);
128 Ok(&mut self.entries.last_mut().unwrap().1)
129 }
130
131 pub fn binding_manifest_for_binding(
133 &self,
134 binding: &DimBinding,
135 options: &CompileOptions,
136 ) -> BindingManifest {
137 let specialized = self.specialize_template(binding, options);
138 BindingManifest::from_lir(&specialized.lir)
139 }
140
141 pub fn binding_manifest_for_component(
143 &self,
144 component: &ModelComponent,
145 options: &CompileOptions,
146 ) -> BindingManifest {
147 self.binding_manifest_for_binding(&component.dim_binding(), options)
148 }
149
150 pub fn get_or_compile_component<F>(
152 &mut self,
153 component: &ModelComponent,
154 build_hir: F,
155 options: &CompileOptions,
156 ) -> Result<(&mut CompiledGraph, BindingManifest), rlx_ir::hir::LowerError>
157 where
158 F: FnOnce() -> HirModule,
159 {
160 let key = component.cache_key();
161 let binding = component.dim_binding();
162 let manifest = self.binding_manifest_for_component(component, options);
163 let compiled = self.get_or_compile(key, &binding, build_hir, options)?;
164 Ok((compiled, manifest))
165 }
166
167 pub fn contains(&self, key: u64) -> bool {
168 self.entries.iter().any(|(k, _)| *k == key)
169 }
170
171 pub fn len(&self) -> usize {
172 self.entries.len()
173 }
174
175 pub fn is_empty(&self) -> bool {
176 self.entries.is_empty()
177 }
178
179 pub fn template_result(&self) -> Option<&CompileResult> {
181 self.template.as_ref()
182 }
183
184 pub fn ensure_template<F: FnOnce() -> HirModule>(
186 &mut self,
187 build_hir: F,
188 options: &CompileOptions,
189 ) -> Result<&CompileResult, rlx_ir::hir::LowerError> {
190 self.build_template(build_hir, options)
191 }
192
193 pub fn get_or_specialize_aot<F: FnOnce() -> HirModule>(
195 &mut self,
196 aot: &crate::AotCache,
197 disk_base: &str,
198 key: u64,
199 binding: &DimBinding,
200 build_hir: F,
201 options: &CompileOptions,
202 ) -> Result<&mut CompiledGraph, crate::AotCacheError> {
203 if let Some(idx) = self.entries.iter().position(|(k, _)| *k == key) {
204 return Ok(&mut self.entries[idx].1);
205 }
206 let device = self.device;
207 let template = self.ensure_template(build_hir, options)?;
208 let compiled = aot.specialize_cached(disk_base, binding, device, template, options)?;
209 if self.entries.len() >= self.capacity
210 && let Some(evict_key) = self.order.pop_front()
211 {
212 self.entries.retain(|(k, _)| *k != evict_key);
213 }
214 self.entries.push((key, compiled));
215 self.order.push_back(key);
216 Ok(&mut self.entries.last_mut().unwrap().1)
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223 use rlx_ir::hir::HirMut;
224 use rlx_ir::{DType, HirModule, Shape};
225
226 #[test]
227 fn template_specialize_compile_smoke() {
228 let device = Device::Cpu;
229 let mut pipe = ModelCompilePipeline::new(device);
230 let opts = CompileOptions::new();
231
232 let build = || {
233 let mut hir = HirModule::new("dyn");
234 let mut gb = HirMut::new(&mut hir);
235 let x = gb.input("x", Shape::new(&[1, 8, 4], DType::F32));
236 let w = gb.param("w", Shape::new(&[4, 2], DType::F32));
237 let y = hir.linear(x, w, None, None, Shape::new(&[1, 8, 2], DType::F32));
238 hir.set_outputs(vec![y]);
239 hir
240 };
241
242 pipe.build_template(build, &opts).unwrap();
243 let binding = rlx_ir::DimBinding::new();
244 let spec = pipe.specialize_template(&binding, &opts);
245 let manifest = BindingManifest::from_lir(&spec.lir);
246 assert_eq!(manifest.params[0].name, "w");
247 let mut compiled = pipe.compile_lir(spec, &opts);
248 compiled.set_param("w", &[0.0f32; 8]);
249 let outs = compiled.run(&[("x", &[0.0f32; 32])]);
250 assert_eq!(outs.len(), 1);
251 }
252}