Skip to main content

burn_backend/backend/
base.rs

1use burn_std::DType;
2pub use burn_std::{ExecutionError, backtrace::BackTrace};
3
4use crate::distributed::DistributedOps;
5pub use crate::element::Element;
6use crate::ops::*;
7use crate::tensor::{BoolTensor, FloatTensor, IntTensor, QuantizedTensor};
8use crate::{TensorData, TensorMetadata};
9use alloc::string::String;
10use enumset::{EnumSet, EnumSetType};
11
12use crate::distributed::{DistributedParamId, DistributedParams};
13
14use super::DeviceOps;
15
16/// The mapping of types used by Backend and traits.
17pub trait BackendTypes: Clone + Send + Sync + core::fmt::Debug + 'static {
18    /// Device type.
19    type Device: DeviceOps;
20
21    /// Tensor primitive to be used for all float operations.
22    type FloatTensorPrimitive: TensorMetadata<Device = Self::Device> + 'static;
23
24    /// Tensor primitive to be used for all int operations.
25    type IntTensorPrimitive: TensorMetadata<Device = Self::Device> + 'static;
26
27    /// Tensor primitive to be used for all bool operations.
28    type BoolTensorPrimitive: TensorMetadata<Device = Self::Device> + 'static;
29
30    /// Tensor primitive to be used for all quantized operations.
31    type QuantizedTensorPrimitive: TensorMetadata<Device = Self::Device> + 'static;
32
33    /// Captured graph primitive returned by [`Backend::graph_stop_capture`] and
34    /// consumed by [`Backend::graph_replay`]: a backend-owned recording of a
35    /// launch sequence that replays as a single dispatch.
36    ///
37    /// Backends without graph-capture support use [`GraphUnsupported`], an
38    /// uninhabited type — their capture methods only ever error, so no value of
39    /// it can exist.
40    type GraphPrimitive: Clone + Send + Sync + core::fmt::Debug + 'static;
41}
42
43/// Captured graph primitive type used by the backend (see
44/// [`BackendTypes::GraphPrimitive`]).
45pub type BackendGraph<B> = <B as BackendTypes>::GraphPrimitive;
46
47/// Placeholder [graph primitive](BackendTypes::GraphPrimitive) for backends
48/// without graph-capture support.
49///
50/// Uninhabited: `graph_stop_capture` on such backends always errors, so a value
51/// of this type can never be constructed (and `graph_replay` can never be called).
52#[derive(Debug, Clone, Copy)]
53pub enum GraphUnsupported {}
54
55/// The error returned by the default (unsupported) graph-capture methods.
56fn graph_unsupported() -> ExecutionError {
57    ExecutionError::Generic {
58        reason: alloc::string::String::from("graph capture is not supported by this backend"),
59        backtrace: BackTrace::capture(),
60    }
61}
62
63/// This trait defines all types and functions needed for a backend to be used with burn.
64///
65/// ## Design
66///
67/// This trait aims to be as unopinionated as possible and allows implementations to define
68/// their own types and patterns. Therefore, there are few pre-defined abstractions baked
69/// into this trait.
70///
71/// Backends must define their own tensor types for each data type: `float`, `int`, and `bool`.
72/// Since we minimize assumptions, we chose to separate these types, as they are used in
73/// different contexts. However, some backends may have a generic tensor type that is used
74/// for all data types.
75///
76/// ### Eager Mode
77///
78/// Because burn supports dynamic graphs, the backend trait is designed around kernel
79/// implementations that can be called without any mutable context or graph. This may not be
80/// ideal for backends that want to configure their computational graphs and execute them
81/// multiple times.
82///
83/// To implement this kind of backend, channels could be used to communicate with a backend
84/// server thread to build the computation graphs and re-execute the ones that are repeated,
85/// with some form of cache. Once that pattern has matured, a graph mode backend trait could
86/// be extracted from it, allowing other backends of the same kind to be quickly integrated
87/// with burn. This pattern could also be used to create an operation fusion trait, which
88/// allows backends to define what kind of graph structures can be fused into one operation.
89///
90/// ### Multi-Threaded
91///
92/// Backend tensor types are all `Clone` + `Send`, which allows them to be safely
93/// sent between threads. It is recommended to wrap tensors with [Arc](alloc::sync::Arc),
94/// which avoids copying the tensor's buffer. Note that it is still possible to mutate and
95/// reuse tensors' buffer without locking; see the next section on the Mutable API.
96///
97/// ### Mutable API
98///
99/// There is no mutable or inplace operation API to implement, but that does not mean that
100/// backends cannot support them. Using [try_unwrap](alloc::sync::Arc::try_unwrap) and
101/// [get_mut](alloc::sync::Arc::get_mut) allows backends to have access to an owned or mutable
102/// reference to their tensor buffer data structure if the tensor is not shared. In that case,
103/// backends can dispatch to their owned inplace operations for better performance.
104///
105/// ## Documentation
106///
107/// Most of the documentation for each function can be found on the user API
108#[cfg_attr(doc, doc = crate::doc_tensor!())]
109#[cfg_attr(not(doc), doc = "`Tensor`")]
110/// struct in the `burn-tensor` crate.
111/// For modules, public functions are often created, which can be used by `burn-core` modules.
112pub trait Backend:
113    BackendTypes
114    + FloatTensorOps<Self>
115    + BoolTensorOps<Self>
116    + IntTensorOps<Self>
117    + ModuleOps<Self>
118    + ActivationOps<Self>
119    + QTensorOps<Self>
120    + TransactionOps<Self>
121    + DistributedOps<Self>
122    + Clone
123    + Default
124    + Sized
125    + Send
126    + Sync
127    + core::fmt::Debug
128    + 'static
129{
130    /// If autodiff is enabled.
131    fn ad_enabled(_device: &Self::Device) -> bool {
132        false
133    }
134
135    /// Sets the current allocation mode to persistent.
136    #[allow(unused_variables)]
137    fn memory_persistent_allocations<
138        Output: Send,
139        Input: Send,
140        Func: Fn(Input) -> Output + Send,
141    >(
142        device: &Self::Device,
143        input: Input,
144        func: Func,
145    ) -> Output {
146        func(input)
147    }
148
149    /// Manually triggers a memory cleanup on the given device.
150    #[allow(unused_variables)]
151    fn memory_cleanup(device: &Self::Device) {}
152
153    /// Name of the backend.
154    fn name(device: &Self::Device) -> String;
155
156    /// Seeds the backend on the specified device.
157    ///
158    /// There is no guarantee that only the specified device will be seeded, but it is guaranteed
159    /// that at least the specified device will be seeded.
160    ///
161    /// In all cases, this should ensure deterministic execution for a single-threaded program.
162    fn seed(device: &Self::Device, seed: u64);
163
164    /// Sync the backend, ensure that all computation are finished.
165    fn sync(_device: &Self::Device) -> Result<(), ExecutionError> {
166        Ok(())
167    }
168
169    /// Prepare `device` for an upcoming graph capture: route allocations into a
170    /// stable pool so every buffer allocated before graph_stop_capture can
171    /// be pinned. Call before the warmup run. No-op by default.
172    ///
173    /// See [`burn_graph`](crate) — the closure-based `capture` helper drives
174    /// this whole sequence.
175    fn graph_prepare(_device: &Self::Device) -> Result<(), ExecutionError> {
176        Ok(())
177    }
178
179    /// Begin recording launches on `device` into a graph (see
180    /// [`graph_stop_capture`](Backend::graph_stop_capture)). Errors on backends
181    /// without hardware graph support, so callers fall back to re-running.
182    fn graph_start_capture(_device: &Self::Device) -> Result<(), ExecutionError> {
183        Err(graph_unsupported())
184    }
185
186    /// Stop recording and return the captured [graph](BackendTypes::GraphPrimitive),
187    /// ready to [`graph_replay`](Backend::graph_replay).
188    fn graph_stop_capture(_device: &Self::Device) -> Result<BackendGraph<Self>, ExecutionError> {
189        Err(graph_unsupported())
190    }
191
192    /// Replay a captured [graph](BackendTypes::GraphPrimitive) — one dispatch
193    /// re-running the recorded launches against their original buffers.
194    ///
195    /// # Safety
196    ///
197    /// The replay dispatches raw device work against the exact buffers recorded
198    /// at capture time, with nothing tracking whether those buffers are still
199    /// valid. The caller must guarantee, for every tensor the captured closure
200    /// read or wrote:
201    ///
202    /// - its buffer is still alive — no tensor referenced by the graph has been
203    ///   freed (and its memory possibly reallocated) since capture;
204    /// - it is not concurrently read or written by work on another stream or
205    ///   thread while the replay executes;
206    /// - input refreshes and output reads are issued on the stream the graph
207    ///   was captured on, so they order correctly against the replay.
208    unsafe fn graph_replay(
209        _device: &Self::Device,
210        _graph: &BackendGraph<Self>,
211    ) -> Result<(), ExecutionError> {
212        Err(graph_unsupported())
213    }
214
215    /// Flush any pending operation of the backend.
216    fn flush(_device: &Self::Device);
217
218    /// Marks the given data as being used as a staging buffer for transfer between CPU and
219    /// accelerators like GPUs.
220    ///
221    /// The given data might be transferred to pinned memory or another format to improve data transfer
222    /// speed.
223    fn staging<'a, Iter>(_data: Iter, _device: &Self::Device)
224    where
225        Iter: Iterator<Item = &'a mut TensorData>,
226    {
227    }
228
229    /// Whether the type is fully supported by the specified device for general operations.
230    ///
231    /// A type is considered supported if it can be used for the full suite of tensor
232    /// operations, including storage, conversion, and basic arithmetic.
233    ///
234    /// Returning `false` does not necessarily mean the device cannot handle the type at all.
235    /// For instance, a device might support a type only for specialized hardware
236    /// acceleration (e.g., matrix multiplication) but lack general arithmetic support. Such
237    /// types should return `false` here as they are not globally supported.
238    fn supports_dtype(device: &Self::Device, dtype: DType) -> bool {
239        Self::dtype_usage(device, dtype).is_superset(DTypeUsage::general())
240    }
241
242    /// Returns the [DTypeUsageSet] for the given [DType] on the specified device.
243    fn dtype_usage(device: &Self::Device, dtype: DType) -> DTypeUsageSet;
244
245    /// Returns the number of devices available on this backend.
246    /// `device` is a reference device used to determine the underlying backend that should be queried.
247    /// A CUDA device will return all devices available to CUDA, a Vulkan device will return all
248    /// devices available to Vulkan, etc.
249    fn device_count(type_id: u16) -> usize;
250}
251
252/// Trait that allows a backend to support autodiff.
253pub trait AutodiffBackend: Backend {
254    /// The inner backend type.
255    type InnerBackend: Backend<Device = Self::Device>;
256
257    /// Gradients type.
258    type Gradients: Send;
259
260    /// Backward pass.
261    ///
262    /// # Arguments
263    ///
264    /// * `tensor` - The tensor is the last node of computational graph where the gradients are computed.
265    ///
266    /// # Returns
267    ///
268    /// The gradients.
269    fn backward(tensor: FloatTensor<Self>) -> Self::Gradients;
270
271    /// Returns the gradients of a tensor.
272    ///
273    /// # Arguments
274    ///
275    /// * `tensor` - The tensor to extract the gradients from.
276    ///
277    /// # Returns
278    ///
279    /// An optional tensor containing the gradient.
280    fn grad(
281        tensor: &FloatTensor<Self>,
282        grads: &Self::Gradients,
283    ) -> Option<FloatTensor<Self::InnerBackend>>;
284
285    /// Pops the gradients of a tensor and returns them.
286    ///
287    /// # Arguments
288    ///
289    /// * `tensor` - The tensor to pop the gradients from.
290    /// * `grads` - The gradients.
291    ///
292    /// # Returns
293    ///
294    /// An optional tensor containing the given gradients.
295    fn grad_remove(
296        tensor: &FloatTensor<Self>,
297        grads: &mut Self::Gradients,
298    ) -> Option<FloatTensor<Self::InnerBackend>>;
299
300    /// Replace the gradients of a tensor with the one provided.
301    ///
302    /// If no gradient existed for the provided tensor, register it.
303    ///
304    /// # Arguments
305    ///
306    /// * `tensor` - The tensor to pop the gradients from.
307    /// * `grads` - The gradients.
308    /// * `grad` - The updated grad tensor.
309    fn grad_replace(
310        tensor: &FloatTensor<Self>,
311        grads: &mut Self::Gradients,
312        grad: FloatTensor<Self::InnerBackend>,
313    );
314
315    /// Returns the tensor with inner backend type.
316    ///
317    /// # Arguments
318    ///
319    /// * `tensor` - The tensor to get the inner backend tensor for.
320    ///
321    /// # Returns
322    ///
323    /// The inner backend tensor.
324    fn inner(tensor: FloatTensor<Self>) -> FloatTensor<Self::InnerBackend>;
325
326    /// Returns the tensor with inner backend type.
327    ///
328    /// # Arguments
329    ///
330    /// * `tensor` - The tensor to get the inner backend tensor for.
331    ///
332    /// # Returns
333    ///
334    /// The inner backend tensor.
335    fn int_inner(tensor: IntTensor<Self>) -> IntTensor<Self::InnerBackend>;
336
337    /// Returns the tensor with inner backend type.
338    ///
339    /// # Arguments
340    ///
341    /// * `tensor` - The tensor to get the inner backend tensor for.
342    ///
343    /// # Returns
344    ///
345    /// The inner backend tensor.
346    fn bool_inner(tensor: BoolTensor<Self>) -> BoolTensor<Self::InnerBackend>;
347
348    /// Returns the tensor with inner backend type.
349    ///
350    /// # Arguments
351    ///
352    /// * `tensor` - The tensor to get the inner backend tensor for.
353    ///
354    /// # Returns
355    ///
356    /// The inner backend tensor.
357    fn q_inner(tensor: QuantizedTensor<Self>) -> QuantizedTensor<Self::InnerBackend>;
358
359    /// Converts the inner backend tensor to the autodiff backend tensor.
360    ///
361    /// # Arguments
362    ///
363    /// * `tensor` - The inner backend tensor to convert.
364    ///
365    ///
366    /// # Returns
367    ///
368    /// The autodiff backend tensor.
369    fn from_inner(tensor: FloatTensor<Self::InnerBackend>) -> FloatTensor<Self>;
370
371    /// Converts the inner backend tensor to the autodiff backend tensor.
372    ///
373    /// # Arguments
374    ///
375    /// * `tensor` - The inner backend tensor to convert.
376    ///
377    ///
378    /// # Returns
379    ///
380    /// The autodiff backend tensor.
381    fn int_from_inner(tensor: IntTensor<Self::InnerBackend>) -> IntTensor<Self>;
382
383    /// Converts the inner backend tensor to the autodiff backend tensor.
384    ///
385    /// # Arguments
386    ///
387    /// * `tensor` - The inner backend tensor to convert.
388    ///
389    ///
390    /// # Returns
391    ///
392    /// The autodiff backend tensor.
393    fn bool_from_inner(tensor: BoolTensor<Self::InnerBackend>) -> BoolTensor<Self>;
394
395    /// Converts the inner backend tensor to the autodiff backend tensor.
396    ///
397    /// # Arguments
398    ///
399    /// * `tensor` - The inner backend tensor to convert.
400    ///
401    ///
402    /// # Returns
403    ///
404    /// The autodiff backend tensor.
405    fn q_from_inner(tensor: QuantizedTensor<Self::InnerBackend>) -> QuantizedTensor<Self>;
406
407    /// Mark the tensor as distributed across multiple devices.
408    /// The gradients will be aggregated during the backward pass.
409    ///
410    /// This function does nothing when distributed training is not available.
411    fn set_distributed_params(
412        tensor: FloatTensor<Self>,
413        _param_id: DistributedParamId,
414    ) -> FloatTensor<Self> {
415        tensor
416    }
417
418    /// Returns the distributed parameters if the tensor was marked as distributed.
419    fn distributed_params(_tensor: &FloatTensor<Self>) -> Option<DistributedParams> {
420        None
421    }
422
423    /// Returns true if the tensor was marked as distributed.
424    fn is_distributed(_tensor: &FloatTensor<Self>) -> bool {
425        false
426    }
427}
428
429/// Describes how a data type can be used on a given device.
430///
431/// A data type may be supported for different classes of operations. Not all
432/// data types that appear in hardware or kernel implementations are suitable
433/// for general-purpose tensor operations.
434#[derive(Debug, EnumSetType)]
435pub enum DTypeUsage {
436    /// The type can be stored in device memory and converted to and from
437    /// other supported data types.
438    Storage,
439    /// The type supports general-purpose arithmetic and common tensor
440    /// operations (e.g. elementwise ops, reductions, etc.).
441    Arithmetic,
442    /// The type is supported by hardware-accelerated execution paths.
443    ///
444    /// This typically indicates support for accelerator-backed compute units (e.g., tensor
445    /// cores executing MMA instructions) for high-performance operations such as matrix
446    /// multiplication and operations that lower to it.
447    ///
448    /// # Notes
449    /// - A type can be both [`Arithmetic`](DTypeUsage::Arithmetic) and
450    ///   [`Accelerated`](DTypeUsage::Accelerated) if it supports general-purpose operations
451    ///   *and* accelerated paths.
452    /// - If a type is marked as `Accelerated` but not `Arithmetic`, it is not
453    ///   suitable for general-purpose tensor operations and may only be used
454    ///   in specific accelerated operations.
455    ///
456    /// `Accelerated` is a **flag**, not a detailed descriptor. It does not enumerate which
457    /// operations are accelerated or which accelerator features are available.
458    Accelerated,
459}
460
461/// A set of [DTypeUsage] representing the total capabilities of a data type on a device.
462pub type DTypeUsageSet = EnumSet<DTypeUsage>;
463
464impl DTypeUsage {
465    /// Returns the usage set required for general-purpose tensor support.
466    pub fn general() -> DTypeUsageSet {
467        DTypeUsage::Storage | DTypeUsage::Arithmetic
468    }
469}