burn_cubecl_fusion/optim/
base.rs1use crate::{CubeFusionHandle, FallbackOperation};
2use burn_fusion::stream::Context;
3use serde::{Deserialize, Serialize, de::DeserializeOwned};
4
5pub trait FusedOperation: Send + 'static {
14 const NAME: &'static str;
17
18 type State: Serialize + DeserializeOwned;
20
21 fn num_ops_fused(&self) -> usize;
23
24 fn max_relative_shape_id(&self) -> Option<usize> {
27 None
28 }
29
30 fn run(
34 &mut self,
35 context: &mut Context<CubeFusionHandle>,
36 fallback: &dyn Fn(usize) -> Box<dyn FallbackOperation>,
37 );
38
39 fn to_state(&self) -> Self::State;
42
43 fn from_state(device: &cubecl::Device, state: Self::State) -> Self;
45}
46
47pub struct CubeOptimization {
50 optimization: Box<dyn DynFusedOperation>,
51}
52
53impl CubeOptimization {
54 pub fn new(optimization: impl FusedOperation) -> Self {
56 Self {
57 optimization: Box::new(optimization),
58 }
59 }
60
61 pub fn name(&self) -> &'static str {
63 self.optimization.name()
64 }
65
66 pub fn num_ops_fused(&self) -> usize {
68 self.optimization.num_ops_fused()
69 }
70
71 pub fn run(
73 &mut self,
74 context: &mut Context<CubeFusionHandle>,
75 fallback: &dyn Fn(usize) -> Box<dyn FallbackOperation>,
76 ) {
77 self.optimization.run(context, fallback)
78 }
79
80 pub fn to_state(&self) -> CubeOptimizationState {
83 self.optimization.to_state()
84 }
85}
86
87impl core::fmt::Debug for CubeOptimization {
88 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
89 write!(f, "{} ({} ops)", self.name(), self.num_ops_fused())
90 }
91}
92
93impl burn_fusion::NumOperations for CubeOptimization {
94 fn len(&self) -> usize {
95 self.num_ops_fused()
96 }
97
98 fn max_relative_shape_id(&self) -> Option<usize> {
99 self.optimization.max_relative_shape_id()
100 }
101
102 fn name(&self) -> &'static str {
103 Self::name(self)
104 }
105}
106
107trait DynFusedOperation: Send {
111 fn name(&self) -> &'static str;
112 fn num_ops_fused(&self) -> usize;
113 fn max_relative_shape_id(&self) -> Option<usize>;
114 fn run(
115 &mut self,
116 context: &mut Context<CubeFusionHandle>,
117 fallback: &dyn Fn(usize) -> Box<dyn FallbackOperation>,
118 );
119 fn to_state(&self) -> CubeOptimizationState;
120}
121
122impl<O: FusedOperation> DynFusedOperation for O {
123 fn name(&self) -> &'static str {
124 O::NAME
125 }
126
127 fn num_ops_fused(&self) -> usize {
128 FusedOperation::num_ops_fused(self)
129 }
130
131 fn max_relative_shape_id(&self) -> Option<usize> {
132 FusedOperation::max_relative_shape_id(self)
133 }
134
135 fn run(
136 &mut self,
137 context: &mut Context<CubeFusionHandle>,
138 fallback: &dyn Fn(usize) -> Box<dyn FallbackOperation>,
139 ) {
140 FusedOperation::run(self, context, fallback)
141 }
142
143 fn to_state(&self) -> CubeOptimizationState {
144 CubeOptimizationState::new(O::NAME, &FusedOperation::to_state(self))
145 }
146}
147
148#[derive(Serialize, Deserialize, Debug)]
152pub struct CubeOptimizationState {
153 pub name: String,
156 state: Vec<u8>,
157}
158
159impl CubeOptimizationState {
160 pub fn new(name: &str, state: &impl Serialize) -> Self {
162 Self {
163 name: name.to_string(),
164 state: {
165 let mut bytes = Vec::new();
166 ciborium::into_writer(state, &mut bytes)
167 .expect("fusion optimization state must be serializable");
168 bytes
169 },
170 }
171 }
172
173 pub fn decode<T: DeserializeOwned>(&self) -> T {
180 ciborium::from_reader(self.state.as_slice()).unwrap_or_else(|err| {
181 panic!(
182 "state of fusion optimization `{}` failed to decode: {err}",
183 self.name
184 )
185 })
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn state_round_trips_through_bytes() {
195 let state = CubeOptimizationState::new("custom", &vec![3u32, 7]);
196
197 assert_eq!(state.name, "custom");
198 assert_eq!(state.decode::<Vec<u32>>(), vec![3, 7]);
199 }
200}