use crate::CubeRuntime;
use burn_cubecl_fusion::optim::elemwise::{self, ElementWiseFuser, ElemwiseOptimization};
use burn_cubecl_fusion::optim::matmul::{self, MatmulFuser, MatmulOptimization};
use burn_cubecl_fusion::optim::nhwc_relayout::{self, NHWCRelayoutFuser, NHWCRelayoutOptimization};
use burn_cubecl_fusion::optim::reduce::{self, ReduceFuser, ReduceOptimization, ReduceSettings};
use burn_cubecl_fusion::optim::reduce_broadcasted::{
self, ReduceBroadcastedFuser, ReduceBroadcastedOptimization,
};
use burn_cubecl_fusion::optim::{CubeOptimization, CubeOptimizationState, FusedOperation};
use burn_fusion::OperationFuser;
use core::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
pub struct CubeFuser<R: CubeRuntime> {
fuser: Box<dyn OperationFuser<CubeOptimization<R>>>,
}
impl<R: CubeRuntime> CubeFuser<R> {
pub fn new(fuser: impl OperationFuser<CubeOptimization<R>> + 'static) -> Self {
Self {
fuser: Box::new(fuser),
}
}
}
pub trait OptimizationProvider<R: CubeRuntime>: Send + Sync + 'static {
type Operation: FusedOperation<R>;
fn name(&self) -> &str {
Self::Operation::NAME
}
fn fuser(&self, device: &R::Device) -> CubeFuser<R>;
fn restore(&self, device: &R::Device, state: &CubeOptimizationState) -> CubeOptimization<R> {
CubeOptimization::new(Self::Operation::from_state(device, state.decode()))
}
}
trait DynProvider<R: CubeRuntime>: Send + Sync {
fn fuser(&self, device: &R::Device) -> CubeFuser<R>;
fn restore(&self, device: &R::Device, state: &CubeOptimizationState) -> CubeOptimization<R>;
}
impl<R: CubeRuntime, P: OptimizationProvider<R>> DynProvider<R> for P {
fn fuser(&self, device: &R::Device) -> CubeFuser<R> {
OptimizationProvider::fuser(self, device)
}
fn restore(&self, device: &R::Device, state: &CubeOptimizationState) -> CubeOptimization<R> {
OptimizationProvider::restore(self, device, state)
}
}
struct ElemwiseProvider;
struct MatmulProvider;
struct ReduceProvider;
struct ReduceBroadcastedProvider;
struct NHWCRelayoutProvider;
impl<R: CubeRuntime> OptimizationProvider<R> for ElemwiseProvider {
type Operation = ElemwiseOptimization<R>;
fn fuser(&self, device: &R::Device) -> CubeFuser<R> {
CubeFuser::new(ElementWiseFuser::new(device.clone()))
}
}
impl<R: CubeRuntime> OptimizationProvider<R> for MatmulProvider {
type Operation = MatmulOptimization<R>;
fn fuser(&self, device: &R::Device) -> CubeFuser<R> {
CubeFuser::new(MatmulFuser::new(device.clone()))
}
}
impl<R: CubeRuntime> OptimizationProvider<R> for ReduceProvider {
type Operation = ReduceOptimization<R>;
fn fuser(&self, device: &R::Device) -> CubeFuser<R> {
CubeFuser::new(ReduceFuser::new(device.clone(), ReduceSettings::Always))
}
}
impl<R: CubeRuntime> OptimizationProvider<R> for ReduceBroadcastedProvider {
type Operation = ReduceBroadcastedOptimization<R>;
fn fuser(&self, device: &R::Device) -> CubeFuser<R> {
CubeFuser::new(ReduceBroadcastedFuser::new(device.clone()))
}
}
impl<R: CubeRuntime> OptimizationProvider<R> for NHWCRelayoutProvider {
type Operation = NHWCRelayoutOptimization<R>;
fn fuser(&self, device: &R::Device) -> CubeFuser<R> {
CubeFuser::new(NHWCRelayoutFuser::new(device.clone()))
}
}
pub const BUILTIN_NAMES: [&str; 5] = [
elemwise::NAME,
matmul::NAME,
reduce::NAME,
reduce_broadcasted::NAME,
nhwc_relayout::NAME,
];
fn builtins<R: CubeRuntime>() -> Vec<(String, Slot)> {
vec![
slot::<R>(ElemwiseProvider),
slot::<R>(MatmulProvider),
slot::<R>(ReduceProvider),
slot::<R>(ReduceBroadcastedProvider),
slot::<R>(NHWCRelayoutProvider),
]
}
fn slot<R: CubeRuntime>(provider: impl OptimizationProvider<R>) -> (String, Slot) {
let name = provider.name().to_string();
(name, Box::new(ProviderSlot::<R>(Box::new(provider))))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RegistryError {
ServiceRunning {
runtime: &'static str,
},
DuplicateOptimization {
name: String,
},
}
impl core::fmt::Display for RegistryError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ServiceRunning { runtime } => write!(
f,
"the fusion backend service for `{runtime}` is already running; \
register or remove fusion optimizations at the start of your program, \
before the first tensor operation on the fusion backend"
),
Self::DuplicateOptimization { name } => write!(
f,
"a fusion optimization named `{name}` is already registered"
),
}
}
}
impl std::error::Error for RegistryError {}
pub fn register<R: CubeRuntime>(
provider: impl OptimizationProvider<R>,
) -> Result<(), RegistryError> {
let (name, slot) = slot::<R>(provider);
let mut registry = registry().lock().unwrap();
entry_of::<R>(&mut registry).register(runtime_name::<R>(), name, slot)
}
pub fn remove<R: CubeRuntime>(name: &str) -> Result<(), RegistryError> {
let mut registry = registry().lock().unwrap();
entry_of::<R>(&mut registry).remove(runtime_name::<R>(), name)
}
pub(crate) fn restore<R: CubeRuntime>(
device: &R::Device,
state: CubeOptimizationState,
) -> CubeOptimization<R> {
let mut registry = registry().lock().unwrap();
entry_of::<R>(&mut registry)
.provider(&state.name)
.map(|slot| downcast::<R>(slot).restore(device, &state))
.unwrap_or_else(|| {
panic!(
"no fusion optimization named `{}` is registered for `{}`; register its \
provider before restoring serialized execution plans",
state.name,
runtime_name::<R>()
)
})
}
pub(crate) fn fusers<R: CubeRuntime>(
device: &R::Device,
) -> Vec<Box<dyn OperationFuser<CubeOptimization<R>>>> {
let mut registry = registry().lock().unwrap();
entry_of::<R>(&mut registry)
.start()
.providers
.iter()
.map(|(_, slot)| downcast::<R>(slot).fuser(device).fuser)
.collect()
}
fn registry() -> &'static Mutex<Registry> {
static REGISTRY: OnceLock<Mutex<Registry>> = OnceLock::new();
REGISTRY.get_or_init(Default::default)
}
fn runtime_name<R: CubeRuntime>() -> &'static str {
core::any::type_name::<R>()
}
fn entry_of<R: CubeRuntime>(registry: &mut Registry) -> &mut Entry {
registry.entry(TypeId::of::<R>(), builtins::<R>)
}
fn downcast<R: CubeRuntime>(slot: &Slot) -> &dyn DynProvider<R> {
slot.downcast_ref::<ProviderSlot<R>>()
.expect("registry entries are keyed by runtime type")
.0
.as_ref()
}
struct ProviderSlot<R: CubeRuntime>(Box<dyn DynProvider<R>>);
type Slot = Box<dyn Any + Send + Sync>;
#[derive(Default)]
struct Registry {
entries: HashMap<TypeId, Entry>,
}
impl Registry {
fn entry(
&mut self,
runtime: TypeId,
defaults: impl FnOnce() -> Vec<(String, Slot)>,
) -> &mut Entry {
self.entries.entry(runtime).or_insert_with(|| Entry {
providers: defaults(),
started: false,
})
}
}
struct Entry {
providers: Vec<(String, Slot)>,
started: bool,
}
impl Entry {
fn register(
&mut self,
runtime: &'static str,
name: String,
slot: Slot,
) -> Result<(), RegistryError> {
self.ensure_open(runtime)?;
if self.providers.iter().any(|(other, _)| *other == name) {
return Err(RegistryError::DuplicateOptimization { name });
}
self.providers.push((name, slot));
Ok(())
}
fn remove(&mut self, runtime: &'static str, name: &str) -> Result<(), RegistryError> {
self.ensure_open(runtime)?;
self.providers.retain(|(other, _)| other != name);
Ok(())
}
fn start(&mut self) -> &Self {
self.started = true;
self
}
fn provider(&self, name: &str) -> Option<&Slot> {
self.providers
.iter()
.find(|(other, _)| other == name)
.map(|(_, slot)| slot)
}
fn ensure_open(&self, runtime: &'static str) -> Result<(), RegistryError> {
if self.started {
return Err(RegistryError::ServiceRunning { runtime });
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
struct RuntimeA;
struct RuntimeB;
fn slot() -> Slot {
Box::new(())
}
fn empty() -> Entry {
Entry {
providers: Vec::new(),
started: false,
}
}
fn seeded() -> Vec<(String, Slot)> {
vec![("builtin".into(), slot())]
}
#[test]
fn register_then_remove_round_trips() {
let mut entry = empty();
entry
.register("A", "custom".into(), slot())
.expect("first registration succeeds");
entry.remove("A", "custom").expect("removal succeeds");
entry
.register("A", "custom".into(), slot())
.expect("re-registration after removal succeeds");
}
#[test]
fn duplicate_names_are_rejected() {
let mut entry = empty();
entry.register("A", "custom".into(), slot()).unwrap();
assert_eq!(
entry.register("A", "custom".into(), slot()),
Err(RegistryError::DuplicateOptimization {
name: "custom".into()
})
);
}
#[test]
fn started_entry_is_sealed() {
let mut entry = empty();
entry.start();
assert_eq!(
entry.register("A", "custom".into(), slot()),
Err(RegistryError::ServiceRunning { runtime: "A" })
);
assert_eq!(
entry.remove("A", "builtin"),
Err(RegistryError::ServiceRunning { runtime: "A" })
);
}
#[test]
fn provider_lookup_by_name() {
let mut entry = empty();
entry.register("A", "custom".into(), slot()).unwrap();
assert!(entry.provider("custom").is_some());
assert!(entry.provider("unknown").is_none());
}
#[test]
fn runtimes_are_independent() {
let mut registry = Registry::default();
registry.entry(TypeId::of::<RuntimeA>(), Vec::new).start();
registry
.entry(TypeId::of::<RuntimeB>(), Vec::new)
.register("B", "custom".into(), slot())
.expect("other runtime still accepts registrations");
}
#[test]
fn defaults_seed_the_entry_once() {
let mut registry = Registry::default();
let id = TypeId::of::<RuntimeA>();
assert!(registry.entry(id, seeded).provider("builtin").is_some());
assert_eq!(registry.entry(id, seeded).providers.len(), 1);
}
#[test]
fn defaults_are_removable_and_reserve_their_name() {
let mut registry = Registry::default();
let entry = registry.entry(TypeId::of::<RuntimeA>(), seeded);
assert_eq!(
entry.register("A", "builtin".into(), slot()),
Err(RegistryError::DuplicateOptimization {
name: "builtin".into()
})
);
entry.remove("A", "builtin").unwrap();
assert!(entry.provider("builtin").is_none());
entry
.register("A", "builtin".into(), slot())
.expect("the name is free after removal");
}
}