Skip to main content

burn_cubecl/
backend.rs

1use crate::{CubeRuntime, tensor::CubeTensor};
2use burn_backend::cubecl::dtype_to_storage_type;
3use burn_backend::{
4    Backend, BackendGraph, BackendTypes, DTypeUsage, DTypeUsageSet, DeviceOps, ExecutionError,
5    TensorData,
6};
7use burn_std::{BoolStore, DType};
8use cubecl::{
9    features::{MmaConfig, TypeUsage},
10    server::ComputeServer,
11};
12use std::marker::PhantomData;
13
14#[cfg(not(feature = "fusion"))]
15use burn_backend::tensor::{BoolTensor, FloatTensor, IntTensor, QuantizedTensor};
16#[cfg(not(feature = "fusion"))]
17use burn_ir::{BackendIr, TensorHandle};
18
19/// Turn a cubecl graph-capture error into a backend [`ExecutionError`].
20fn graph_err(err: impl core::fmt::Display) -> ExecutionError {
21    ExecutionError::WithContext {
22        reason: format!("{err}"),
23    }
24}
25
26/// Generic tensor backend that can be compiled just-in-time to any shader runtime
27#[derive(new)]
28pub struct CubeBackend<R: CubeRuntime> {
29    _runtime: PhantomData<R>,
30}
31
32impl<R> BackendTypes for CubeBackend<R>
33where
34    R: CubeRuntime,
35    R::Server: ComputeServer,
36    R::Device: DeviceOps,
37{
38    type Device = R::Device;
39
40    type FloatTensorPrimitive = CubeTensor<R>;
41    type IntTensorPrimitive = CubeTensor<R>;
42    type BoolTensorPrimitive = CubeTensor<R>;
43    type QuantizedTensorPrimitive = CubeTensor<R>;
44
45    type GraphPrimitive = cubecl::client::Graph<R>;
46}
47
48impl<R> Backend for CubeBackend<R>
49where
50    R: CubeRuntime,
51    R::Server: ComputeServer,
52    R::Device: DeviceOps,
53{
54    fn name(device: &Self::Device) -> String {
55        let client = R::client(device);
56        format!("cubecl<{}>", R::name(&client))
57    }
58
59    fn seed(_device: &Self::Device, seed: u64) {
60        cubek::random::seed(seed);
61    }
62
63    fn ad_enabled(_device: &Self::Device) -> bool {
64        false
65    }
66
67    fn sync(device: &Self::Device) -> Result<(), ExecutionError> {
68        let client = R::client(device);
69        futures_lite::future::block_on(client.sync()).map_err(|err| ExecutionError::WithContext {
70            reason: format!("{err}"),
71        })
72    }
73
74    fn graph_prepare(device: &Self::Device) -> Result<(), ExecutionError> {
75        let client = R::client(device);
76        client.graph_prepare().map_err(graph_err)
77    }
78
79    fn graph_start_capture(device: &Self::Device) -> Result<(), ExecutionError> {
80        let client = R::client(device);
81        client.start_capture().map_err(graph_err)
82    }
83
84    fn graph_stop_capture(device: &Self::Device) -> Result<BackendGraph<Self>, ExecutionError> {
85        let client = R::client(device);
86        client.stop_capture().map_err(graph_err)
87    }
88
89    unsafe fn graph_replay(
90        _device: &Self::Device,
91        graph: &BackendGraph<Self>,
92    ) -> Result<(), ExecutionError> {
93        // cubecl's `Graph::replay` is fire-and-forget: it enqueues the dispatch
94        // and returns immediately, so a replay failure is not reported here — it
95        // lands in the stream's error queue and surfaces on the next sync/flush.
96        //
97        // Safety: the buffer-liveness and stream-ordering obligations are the
98        // caller's, forwarded verbatim from this method's own contract.
99        unsafe { graph.replay() };
100        Ok(())
101    }
102
103    fn memory_persistent_allocations<
104        Output: Send,
105        Input: Send,
106        Func: Fn(Input) -> Output + Send,
107    >(
108        device: &Self::Device,
109        input: Input,
110        func: Func,
111    ) -> Output {
112        let client = R::client(device);
113        client.memory_persistent_allocation(input, func).unwrap()
114    }
115
116    fn memory_cleanup(device: &Self::Device) {
117        let client = R::client(device);
118        client.memory_cleanup();
119    }
120
121    fn staging<'a, Iter>(data: Iter, device: &Self::Device)
122    where
123        Iter: Iterator<Item = &'a mut TensorData>,
124    {
125        let client = R::client(device);
126        client.staging(data.map(|td| &mut td.bytes), false);
127    }
128
129    fn supports_dtype(device: &Self::Device, dtype: DType) -> bool {
130        // Right now no cubecl backend actually works with native bool, even if
131        // the `TypeUsage` might indicate otherwise.
132        if let DType::Bool(BoolStore::Native) = dtype {
133            return false;
134        }
135
136        let client = R::client(device);
137
138        let type_usage = client.properties().type_usage(dtype_to_storage_type(dtype));
139        // Same as `TypeUsage::all_scalar()`, but we make the usage explicit here
140        type_usage.is_superset(
141            TypeUsage::Buffer
142                | TypeUsage::Conversion
143                | TypeUsage::Arithmetic
144                | TypeUsage::DotProduct,
145        )
146    }
147
148    fn dtype_usage(device: &Self::Device, dtype: DType) -> DTypeUsageSet {
149        // Right now no cubecl backend actually works with native bool, even if
150        // the `TypeUsage` might indicate otherwise.
151        if let DType::Bool(BoolStore::Native) = dtype {
152            return DTypeUsageSet::empty();
153        }
154
155        let client = R::client(device);
156
157        let props = client.properties();
158        let storage = dtype_to_storage_type(dtype);
159        let usage = props.type_usage(storage);
160
161        let mut out = DTypeUsageSet::new();
162
163        if usage.is_superset(TypeUsage::Buffer | TypeUsage::Conversion) {
164            out |= DTypeUsage::Storage;
165        }
166
167        if usage.contains(TypeUsage::Arithmetic) {
168            out |= DTypeUsage::Arithmetic;
169        }
170
171        let has_mma = |cfg: &MmaConfig| {
172            cfg.a_type == storage || cfg.b_type == storage || cfg.cd_type == storage
173        };
174        if props.features.matmul.cmma.iter().any(has_mma)
175            || props.features.matmul.mma.iter().any(has_mma)
176        {
177            out |= DTypeUsage::Accelerated;
178        }
179
180        out
181    }
182
183    fn device_count(type_id: u16) -> usize {
184        let client = R::client(&Default::default());
185        client.device_count(type_id)
186    }
187
188    fn flush(device: &Self::Device) {
189        let client = R::client(device);
190        client.flush().unwrap();
191    }
192}
193
194impl<R: CubeRuntime> core::fmt::Debug for CubeBackend<R> {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        f.write_str("CubeCLBackend")
197    }
198}
199
200impl<R: CubeRuntime> Clone for CubeBackend<R> {
201    fn clone(&self) -> Self {
202        Self::new()
203    }
204}
205
206impl<R: CubeRuntime> Default for CubeBackend<R> {
207    fn default() -> Self {
208        Self::new()
209    }
210}
211
212impl<R: cubecl::Runtime> CubeRuntime for R
213where
214    R::Device: DeviceOps,
215{
216    type CubeDevice = R::Device;
217    type CubeServer = R::Server;
218}
219
220#[cfg(not(feature = "fusion"))]
221impl<R: CubeRuntime> BackendIr for CubeBackend<R> {
222    type Handle = CubeTensor<R>;
223
224    fn float_tensor(handle: TensorHandle<Self::Handle>) -> FloatTensor<Self> {
225        handle.handle
226    }
227
228    fn int_tensor(handle: TensorHandle<Self::Handle>) -> IntTensor<Self> {
229        handle.handle
230    }
231
232    fn bool_tensor(handle: TensorHandle<Self::Handle>) -> BoolTensor<Self> {
233        handle.handle
234    }
235
236    fn quantized_tensor(handle: TensorHandle<Self::Handle>) -> QuantizedTensor<Self> {
237        handle.handle
238    }
239
240    fn float_tensor_handle(tensor: FloatTensor<Self>) -> Self::Handle {
241        tensor
242    }
243
244    fn int_tensor_handle(tensor: IntTensor<Self>) -> Self::Handle {
245        tensor
246    }
247
248    fn bool_tensor_handle(tensor: BoolTensor<Self>) -> Self::Handle {
249        tensor
250    }
251
252    fn quantized_tensor_handle(tensor: QuantizedTensor<Self>) -> Self::Handle {
253        tensor
254    }
255}