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}