1use crate::{CubeDevice, tensor::CubeTensor};
2use burn_backend::cubecl::dtype_to_storage_type;
3use burn_backend::{
4 Backend, BackendGraph, BackendTypes, DTypeUsage, DTypeUsageSet, ExecutionError,
5 MemoryPoolUsage, ProfileDuration, ProfileOptions, ProfileToken, SlicedPoolReport, TensorData,
6 profile_with_tokens,
7};
8use burn_std::{BoolStore, DType, id::StreamId, quantization::quantizable};
9use cubecl::device::DeviceId;
10use cubecl::{
11 MemoryPoolKind, MemoryScope,
12 client::{Client, ProfileWindow},
13 features::{MmaConfig, TypeUsage},
14 ir::ElemType,
15 server::{ProfileError, ProfilingToken, ServerError},
16};
17
18#[cfg(not(feature = "fusion"))]
19use burn_backend::tensor::{BoolTensor, FloatTensor, IntTensor, QuantizedTensor};
20#[cfg(not(feature = "fusion"))]
21use burn_ir::{BackendIr, TensorHandle};
22
23fn qfloat_params_usable(client: &Client, dtype: DType) -> bool {
26 let DType::QFloat(scheme) = dtype else {
27 return true;
28 };
29
30 quantizable(&scheme)
31 && client
32 .properties()
33 .type_usage(ElemType::from_scale_dtype(scheme.scale_dtype()))
34 .is_superset(TypeUsage::Buffer | TypeUsage::Conversion)
35}
36
37pub(crate) fn server_err(err: ServerError) -> ExecutionError {
40 match err.is_device_poisoned() {
41 true => ExecutionError::device_poisoned(format!("{err}")),
42 false => ExecutionError::with_context(format!("{err}")),
43 }
44}
45
46fn graph_err(err: impl core::fmt::Display) -> ExecutionError {
48 ExecutionError::WithContext {
49 reason: format!("{err}"),
50 }
51}
52
53fn profile_err(err: ProfileError) -> ExecutionError {
55 ExecutionError::WithContext {
56 reason: format!("{err}"),
57 }
58}
59
60fn empty_window() -> ProfileDuration {
76 ProfileDuration::new_device_time_maybe(async move { None })
77}
78
79#[derive(Clone, Debug)]
86pub struct CubeGraph {
87 graph: cubecl::client::Graph,
88 device: CubeDevice,
89}
90
91#[derive(new)]
94pub struct CubeBackend;
95
96impl BackendTypes for CubeBackend {
97 type Device = CubeDevice;
98
99 type FloatTensorPrimitive = CubeTensor;
100 type IntTensorPrimitive = CubeTensor;
101 type BoolTensorPrimitive = CubeTensor;
102 type QuantizedTensorPrimitive = CubeTensor;
103
104 type GraphPrimitive = CubeGraph;
105}
106
107impl Backend for CubeBackend {
108 fn name(device: &Self::Device) -> String {
109 let client = device.client();
110 format!("cubecl<{}>", client.name())
111 }
112
113 fn seed(_device: &Self::Device, seed: u64) {
114 cubek::random::seed(seed);
115 }
116
117 fn ad_enabled(_device: &Self::Device) -> bool {
118 false
119 }
120
121 fn sync(device: &Self::Device) -> Result<(), ExecutionError> {
122 let client = device.client();
123 futures_lite::future::block_on(client.sync()).map_err(server_err)
129 }
130
131 fn profile<O: Send + 'static>(
132 device: &Self::Device,
133 options: ProfileOptions,
134 func: impl FnOnce() -> O + Send,
135 ) -> Result<(O, ProfileDuration), ExecutionError> {
136 profile_with_tokens::<Self, O>(device, options, func)
142 }
143
144 fn profile_start(device: &Self::Device) -> Result<Option<ProfileToken>, ExecutionError> {
145 let client = device.client();
146 client
147 .profile_start()
148 .map(|window| {
149 Some(ProfileToken {
150 id: window.token.id,
151 opened_on: window.stream_id.value,
152 })
153 })
154 .map_err(profile_err)
155 }
156
157 fn profile_end(
158 device: &Self::Device,
159 token: ProfileToken,
160 _options: ProfileOptions,
161 ) -> Result<ProfileDuration, ExecutionError> {
162 let client = device.client();
166 let window = ProfileWindow {
169 stream_id: StreamId {
170 value: token.opened_on,
171 },
172 token: ProfilingToken { id: token.id },
173 };
174 match client.profile_end(window) {
175 Ok(duration) => Ok(duration),
176 Err(ProfileError::NotMeasured { .. }) => Ok(empty_window()),
177 Err(err) => Err(profile_err(err)),
178 }
179 }
180
181 fn profile_abandon(device: &Self::Device, token: ProfileToken) {
184 let window = ProfileWindow {
185 stream_id: StreamId {
186 value: token.opened_on,
187 },
188 token: ProfilingToken { id: token.id },
189 };
190 device.client().profile_abandon(window);
191 }
192
193 fn graph_prepare(device: &Self::Device) -> Result<(), ExecutionError> {
194 let client = device.client();
195 client.graph_prepare().map_err(graph_err)
196 }
197
198 fn graph_start_capture(device: &Self::Device) -> Result<(), ExecutionError> {
199 let client = device.client();
200 client.start_capture().map_err(graph_err)
201 }
202
203 fn graph_stop_capture(device: &Self::Device) -> Result<BackendGraph<Self>, ExecutionError> {
204 let client = device.client();
205 let graph = client.stop_capture().map_err(graph_err)?;
206
207 Ok(CubeGraph {
208 graph,
209 device: device.clone(),
210 })
211 }
212
213 unsafe fn graph_replay(
214 device: &Self::Device,
215 graph: &BackendGraph<Self>,
216 ) -> Result<(), ExecutionError> {
217 if &graph.device != device {
220 return Err(ExecutionError::WithContext {
221 reason: format!(
222 "The graph was captured on {:?} and cannot replay on {device:?}",
223 graph.device
224 ),
225 });
226 }
227
228 unsafe { graph.graph.replay() }.map_err(graph_err)
235 }
236
237 fn memory_persistent_allocations<
238 Output: Send,
239 Input: Send,
240 Func: Fn(Input) -> Output + Send,
241 >(
242 device: &Self::Device,
243 input: Input,
244 func: Func,
245 ) -> Output {
246 let client = device.client();
247 client.memory_persistent_allocation(input, func)
248 }
249
250 fn memory_cleanup(device: &Self::Device) {
251 let client = device.client();
252 let _ = client.memory_cleanup();
255 }
256
257 fn memory_pool_report(device: &Self::Device) -> Option<Vec<SlicedPoolReport>> {
258 let report = device.client().memory_report(MemoryScope::Device);
259
260 Some(
265 report
266 .streams
267 .iter()
268 .flat_map(|stream| &stream.pools.dynamic)
269 .filter_map(|pool| {
270 let page_size = match pool.kind {
271 MemoryPoolKind::Sliced { page_size, .. } => page_size,
272 MemoryPoolKind::Adaptive { page_size, .. } => page_size,
273 _ => return None,
274 };
275 Some(SlicedPoolReport {
276 page_size,
277 pages: pool.pages,
278 pages_peak: pool.pages_peak,
279 largest_alloc: pool.largest_alloc,
280 })
281 })
282 .collect(),
283 )
284 }
285
286 fn memory_pool_usage(device: &Self::Device) -> Option<MemoryPoolUsage> {
287 let usage = device.client().memory_report(MemoryScope::Device).usage();
288
289 Some(MemoryPoolUsage {
290 number_allocs: usage.number_allocs,
291 bytes_in_use: usage.bytes_in_use,
292 bytes_padding: usage.bytes_padding,
293 bytes_reserved: usage.bytes_reserved,
294 })
295 }
296
297 fn staging<'a, Iter>(data: Iter, device: &Self::Device)
298 where
299 Iter: Iterator<Item = &'a mut TensorData>,
300 {
301 let client = device.client();
302 TensorData::with_bytes_mut(data, |bytes| client.staging(bytes.into_iter(), false));
303 }
304
305 fn supports_dtype(device: &Self::Device, dtype: DType) -> bool {
306 if let DType::Bool(BoolStore::Native) = dtype {
309 return false;
310 }
311 let client = device.client();
312
313 if !qfloat_params_usable(&client, dtype) {
314 return false;
315 }
316
317 let type_usage = client.properties().type_usage(dtype_to_storage_type(dtype));
318 type_usage.is_superset(
320 TypeUsage::Buffer
321 | TypeUsage::Conversion
322 | TypeUsage::Arithmetic
323 | TypeUsage::DotProduct,
324 )
325 }
326
327 fn dtype_usage(device: &Self::Device, dtype: DType) -> DTypeUsageSet {
328 if let DType::Bool(BoolStore::Native) = dtype {
331 return DTypeUsageSet::empty();
332 }
333 let client = device.client();
334
335 if !qfloat_params_usable(&client, dtype) {
336 return DTypeUsageSet::empty();
337 }
338
339 let props = client.properties();
340 let storage = dtype_to_storage_type(dtype);
341 let usage = props.type_usage(storage);
342
343 let mut out = DTypeUsageSet::new();
344
345 if usage.is_superset(TypeUsage::Buffer | TypeUsage::Conversion) {
346 out |= DTypeUsage::Storage;
347 }
348
349 if usage.contains(TypeUsage::Arithmetic) {
350 out |= DTypeUsage::Arithmetic;
351 }
352
353 let has_mma = |cfg: &MmaConfig| {
354 cfg.a_type == storage || cfg.b_type == storage || cfg.cd_type == storage
355 };
356 if props.features.matmul.cmma.iter().any(has_mma)
357 || props.features.matmul.mma.iter().any(has_mma)
358 {
359 out |= DTypeUsage::Accelerated;
360 }
361
362 out
363 }
364
365 fn device_count(type_id: u16) -> usize {
366 CubeDevice::enumerate(DeviceId::new(type_id, 0)).len()
367 }
368
369 fn flush(device: &Self::Device) -> Result<(), ExecutionError> {
370 let client = device.client();
371 client.flush().map_err(server_err)
372 }
373}
374
375impl core::fmt::Debug for CubeBackend {
376 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377 f.write_str("CubeCLBackend")
378 }
379}
380
381impl Clone for CubeBackend {
382 fn clone(&self) -> Self {
383 Self::new()
384 }
385}
386
387impl Default for CubeBackend {
388 fn default() -> Self {
389 Self::new()
390 }
391}
392
393#[cfg(not(feature = "fusion"))]
394impl BackendIr for CubeBackend {
395 type Handle = CubeTensor;
396
397 fn float_tensor(handle: TensorHandle<Self::Handle>) -> FloatTensor<Self> {
398 handle.handle
399 }
400
401 fn int_tensor(handle: TensorHandle<Self::Handle>) -> IntTensor<Self> {
402 handle.handle
403 }
404
405 fn bool_tensor(handle: TensorHandle<Self::Handle>) -> BoolTensor<Self> {
406 handle.handle
407 }
408
409 fn quantized_tensor(handle: TensorHandle<Self::Handle>) -> QuantizedTensor<Self> {
410 handle.handle
411 }
412
413 fn float_tensor_handle(tensor: FloatTensor<Self>) -> Self::Handle {
414 tensor
415 }
416
417 fn int_tensor_handle(tensor: IntTensor<Self>) -> Self::Handle {
418 tensor
419 }
420
421 fn bool_tensor_handle(tensor: BoolTensor<Self>) -> Self::Handle {
422 tensor
423 }
424
425 fn quantized_tensor_handle(tensor: QuantizedTensor<Self>) -> Self::Handle {
426 tensor
427 }
428}