1use crate::CubeDevice;
2use burn_cubecl_fusion::optim::elemwise::{self, ElementWiseFuser, ElemwiseOptimization};
3use burn_cubecl_fusion::optim::matmul::{self, MatmulFuser, MatmulOptimization};
4use burn_cubecl_fusion::optim::nhwc_relayout::{self, NHWCRelayoutFuser, NHWCRelayoutOptimization};
5use burn_cubecl_fusion::optim::reduce::{self, ReduceFuser, ReduceOptimization, ReduceSettings};
6use burn_cubecl_fusion::optim::reduce_broadcasted::{
7 self, ReduceBroadcastedFuser, ReduceBroadcastedOptimization,
8};
9use burn_cubecl_fusion::optim::{CubeOptimization, CubeOptimizationState, FusedOperation};
10use burn_fusion::OperationFuser;
11use core::any::Any;
12use std::sync::{Mutex, OnceLock};
13
14pub struct CubeFuser {
18 fuser: Box<dyn OperationFuser<CubeOptimization>>,
19}
20
21impl CubeFuser {
22 pub fn new(fuser: impl OperationFuser<CubeOptimization> + 'static) -> Self {
24 Self {
25 fuser: Box::new(fuser),
26 }
27 }
28}
29
30pub trait OptimizationProvider: Send + Sync + 'static {
39 type Operation: FusedOperation;
41
42 fn name(&self) -> &str {
45 Self::Operation::NAME
46 }
47
48 fn fuser(&self, device: &CubeDevice) -> CubeFuser;
50
51 fn restore(&self, device: &CubeDevice, state: &CubeOptimizationState) -> CubeOptimization {
54 CubeOptimization::new(Self::Operation::from_state(device, state.decode()))
55 }
56}
57
58trait DynProvider: Send + Sync {
62 fn fuser(&self, device: &CubeDevice) -> CubeFuser;
63 fn restore(&self, device: &CubeDevice, state: &CubeOptimizationState) -> CubeOptimization;
64}
65
66impl<P: OptimizationProvider> DynProvider for P {
67 fn fuser(&self, device: &CubeDevice) -> CubeFuser {
68 OptimizationProvider::fuser(self, device)
69 }
70
71 fn restore(&self, device: &CubeDevice, state: &CubeOptimizationState) -> CubeOptimization {
72 OptimizationProvider::restore(self, device, state)
73 }
74}
75
76struct ElemwiseProvider;
80struct MatmulProvider;
81struct ReduceProvider;
82struct ReduceBroadcastedProvider;
83struct NHWCRelayoutProvider;
84
85impl OptimizationProvider for ElemwiseProvider {
86 type Operation = ElemwiseOptimization;
87
88 fn fuser(&self, device: &CubeDevice) -> CubeFuser {
89 CubeFuser::new(ElementWiseFuser::new(device.clone()))
90 }
91}
92
93impl OptimizationProvider for MatmulProvider {
94 type Operation = MatmulOptimization;
95
96 fn fuser(&self, device: &CubeDevice) -> CubeFuser {
97 CubeFuser::new(MatmulFuser::new(device.clone()))
98 }
99}
100
101impl OptimizationProvider for ReduceProvider {
102 type Operation = ReduceOptimization;
103
104 fn fuser(&self, device: &CubeDevice) -> CubeFuser {
105 CubeFuser::new(ReduceFuser::new(device.clone(), ReduceSettings::Always))
106 }
107}
108
109impl OptimizationProvider for ReduceBroadcastedProvider {
110 type Operation = ReduceBroadcastedOptimization;
111
112 fn fuser(&self, device: &CubeDevice) -> CubeFuser {
113 CubeFuser::new(ReduceBroadcastedFuser::new(device.clone()))
114 }
115}
116
117impl OptimizationProvider for NHWCRelayoutProvider {
118 type Operation = NHWCRelayoutOptimization;
119
120 fn fuser(&self, device: &CubeDevice) -> CubeFuser {
121 CubeFuser::new(NHWCRelayoutFuser::new(device.clone()))
122 }
123}
124
125pub const BUILTIN_NAMES: [&str; 5] = [
128 elemwise::NAME,
129 matmul::NAME,
130 reduce::NAME,
131 reduce_broadcasted::NAME,
132 nhwc_relayout::NAME,
133];
134
135fn builtins() -> Vec<(String, Slot)> {
137 vec![
138 slot(ElemwiseProvider),
139 slot(MatmulProvider),
140 slot(ReduceProvider),
141 slot(ReduceBroadcastedProvider),
142 slot(NHWCRelayoutProvider),
143 ]
144}
145
146fn slot(provider: impl OptimizationProvider) -> (String, Slot) {
147 let name = provider.name().to_string();
148 (name, Box::new(ProviderSlot(Box::new(provider))))
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
153pub enum RegistryError {
154 ServiceRunning,
157 DuplicateOptimization {
159 name: String,
161 },
162}
163
164impl core::fmt::Display for RegistryError {
165 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
166 match self {
167 Self::ServiceRunning => write!(
168 f,
169 "the fusion backend service is already running; \
170 register or remove fusion optimizations at the start of your program, \
171 before the first tensor operation on the fusion backend"
172 ),
173 Self::DuplicateOptimization { name } => write!(
174 f,
175 "a fusion optimization named `{name}` is already registered"
176 ),
177 }
178 }
179}
180
181impl std::error::Error for RegistryError {}
182
183pub fn register(provider: impl OptimizationProvider) -> Result<(), RegistryError> {
197 let (name, slot) = slot(provider);
198 registry().lock().unwrap().register(name, slot)
199}
200
201pub fn remove(name: &str) -> Result<(), RegistryError> {
212 registry().lock().unwrap().remove(name)
213}
214
215pub(crate) fn restore(device: &CubeDevice, state: CubeOptimizationState) -> CubeOptimization {
218 let registry = registry().lock().unwrap();
219 registry
220 .provider(&state.name)
221 .map(|slot| downcast(slot).restore(device, &state))
222 .unwrap_or_else(|| {
223 panic!(
224 "no fusion optimization named `{}` is registered; register its \
225 provider before restoring serialized execution plans",
226 state.name,
227 )
228 })
229}
230
231pub(crate) fn fusers(device: &CubeDevice) -> Vec<Box<dyn OperationFuser<CubeOptimization>>> {
236 let mut registry = registry().lock().unwrap();
237 registry
238 .start()
239 .providers
240 .iter()
241 .map(|(_, slot)| downcast(slot).fuser(device).fuser)
242 .collect()
243}
244
245fn registry() -> &'static Mutex<Registry> {
246 static REGISTRY: OnceLock<Mutex<Registry>> = OnceLock::new();
247 REGISTRY.get_or_init(|| Mutex::new(Registry::seeded(builtins)))
248}
249
250fn downcast(slot: &Slot) -> &dyn DynProvider {
252 slot.downcast_ref::<ProviderSlot>()
253 .expect("every slot in the registry holds a provider")
254 .0
255 .as_ref()
256}
257
258struct ProviderSlot(Box<dyn DynProvider>);
260
261type Slot = Box<dyn Any + Send + Sync>;
263
264struct Registry {
267 providers: Vec<(String, Slot)>,
268 started: bool,
269}
270
271impl Registry {
272 fn seeded(defaults: impl FnOnce() -> Vec<(String, Slot)>) -> Self {
273 Self {
274 providers: defaults(),
275 started: false,
276 }
277 }
278
279 fn register(&mut self, name: String, slot: Slot) -> Result<(), RegistryError> {
280 self.ensure_open()?;
281 if self.providers.iter().any(|(other, _)| *other == name) {
282 return Err(RegistryError::DuplicateOptimization { name });
283 }
284 self.providers.push((name, slot));
285 Ok(())
286 }
287
288 fn remove(&mut self, name: &str) -> Result<(), RegistryError> {
289 self.ensure_open()?;
290 self.providers.retain(|(other, _)| other != name);
291 Ok(())
292 }
293
294 fn start(&mut self) -> &Self {
295 self.started = true;
296 self
297 }
298
299 fn provider(&self, name: &str) -> Option<&Slot> {
300 self.providers
301 .iter()
302 .find(|(other, _)| other == name)
303 .map(|(_, slot)| slot)
304 }
305
306 fn ensure_open(&self) -> Result<(), RegistryError> {
308 if self.started {
309 return Err(RegistryError::ServiceRunning);
310 }
311 Ok(())
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 fn slot() -> Slot {
320 Box::new(())
321 }
322
323 fn empty() -> Registry {
324 Registry::seeded(Vec::new)
325 }
326
327 fn seeded() -> Vec<(String, Slot)> {
328 vec![("builtin".into(), slot())]
329 }
330
331 #[test]
332 fn register_then_remove_round_trips() {
333 let mut registry = empty();
334
335 registry
336 .register("custom".into(), slot())
337 .expect("first registration succeeds");
338 registry.remove("custom").expect("removal succeeds");
339
340 registry
342 .register("custom".into(), slot())
343 .expect("re-registration after removal succeeds");
344 }
345
346 #[test]
347 fn duplicate_names_are_rejected() {
348 let mut registry = empty();
349
350 registry.register("custom".into(), slot()).unwrap();
351 assert_eq!(
352 registry.register("custom".into(), slot()),
353 Err(RegistryError::DuplicateOptimization {
354 name: "custom".into()
355 })
356 );
357 }
358
359 #[test]
360 fn a_started_registry_is_sealed() {
361 let mut registry = empty();
362 registry.start();
363
364 assert_eq!(
365 registry.register("custom".into(), slot()),
366 Err(RegistryError::ServiceRunning)
367 );
368 assert_eq!(
369 registry.remove("builtin"),
370 Err(RegistryError::ServiceRunning)
371 );
372 }
373
374 #[test]
375 fn provider_lookup_by_name() {
376 let mut registry = empty();
377 registry.register("custom".into(), slot()).unwrap();
378
379 assert!(registry.provider("custom").is_some());
380 assert!(registry.provider("unknown").is_none());
381 }
382
383 #[test]
384 fn defaults_seed_the_registry() {
385 let registry = Registry::seeded(seeded);
386
387 assert!(registry.provider("builtin").is_some());
388 }
389
390 #[test]
391 fn defaults_are_removable_and_reserve_their_name() {
392 let mut registry = Registry::seeded(seeded);
393
394 assert_eq!(
396 registry.register("builtin".into(), slot()),
397 Err(RegistryError::DuplicateOptimization {
398 name: "builtin".into()
399 })
400 );
401
402 registry.remove("builtin").unwrap();
404 assert!(registry.provider("builtin").is_none());
405 registry
406 .register("builtin".into(), slot())
407 .expect("the name is free after removal");
408 }
409}