Skip to main content

ruda_tensor/backend/
contract.rs

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