Skip to main content

trustformers_core/tensor/
mod.rs

1//! Core tensor abstraction for TrustformeRS.
2//!
3//! This module provides the fundamental `Tensor` type that serves as the backbone
4//! for all numerical computations in TrustformeRS. It offers a unified interface
5//! over different backend implementations (ndarray, PyTorch, Candle) while
6//! maintaining high performance through SIMD optimizations.
7//!
8//! # Overview
9//!
10//! The `Tensor` enum provides:
11//! - Multi-backend support (CPU via ndarray, GPU via PyTorch/Candle)
12//! - Common tensor operations (matmul, add, softmax, etc.)
13//! - Broadcasting and shape manipulation
14//! - Gradient-related operations for training
15//! - Serialization support for model persistence
16//!
17//! # Example
18//!
19//! ```no_run
20//! use trustformers_core::tensor::Tensor;
21//!
22//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
23//! // Create tensors
24//! let a = Tensor::randn(&[2, 3])?;
25//! let b = Tensor::randn(&[3, 4])?;
26//!
27//! // Perform operations
28//! let c = a.matmul(&b)?;  // Matrix multiplication
29//! let d = c.relu()?;       // ReLU activation
30//! let e = d.softmax(-1)?;  // Softmax along last dimension
31//! # Ok(())
32//! # }
33//! ```
34//!
35//! # Performance Notes
36//!
37//! - SIMD operations are used where available for better performance
38//! - Tensor operations are optimized for common transformer patterns
39//! - GPU operations are available when compiled with appropriate features
40
41mod activations;
42mod complex;
43pub mod constructors;
44mod conversions;
45mod expression;
46mod math_ops;
47mod sparse;
48pub mod transformations;
49mod utils;
50
51#[cfg(test)]
52mod complex_tests;
53#[cfg(test)]
54mod constructors_tests;
55#[cfg(test)]
56mod property_tests;
57
58use crate::errors::Result;
59use scirs2_core::ndarray::{ArrayBase, ArrayD, Dim, IxDynImpl, OwnedRepr};
60use scirs2_core::Complex;
61use scirs2_core::{Complex32, Complex64};
62use serde::{Deserialize, Serialize};
63
64/// Data types supported by tensors
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
66pub enum DType {
67    /// 32-bit floating point
68    F32,
69    /// 16-bit floating point
70    F16,
71    /// Brain floating point 16
72    BF16,
73    /// 64-bit floating point
74    F64,
75    /// 32-bit complex number (two 32-bit floats)
76    C32,
77    /// 64-bit complex number (two 64-bit floats)
78    C64,
79    /// 16-bit complex number (two 16-bit floats)
80    CF16,
81    /// Brain floating point 16 complex number (two BF16 floats)
82    CBF16,
83    /// 8-bit unsigned integer
84    U8,
85    /// 16-bit unsigned integer
86    U16,
87    /// 32-bit unsigned integer
88    U32,
89    /// 64-bit unsigned integer
90    U64,
91    /// 8-bit signed integer
92    I8,
93    /// 16-bit signed integer
94    I16,
95    /// 32-bit signed integer
96    I32,
97    /// 64-bit signed integer
98    I64,
99    /// Boolean
100    Bool,
101}
102
103impl DType {
104    /// Returns the size in bytes of an element of this data type
105    pub fn size_in_bytes(&self) -> usize {
106        match self {
107            DType::F32 => 4,
108            DType::F16 => 2,
109            DType::BF16 => 2,
110            DType::F64 => 8,
111            DType::C32 => 8,   // Two 32-bit floats
112            DType::C64 => 16,  // Two 64-bit floats
113            DType::CF16 => 4,  // Two 16-bit floats
114            DType::CBF16 => 4, // Two BF16 floats
115            DType::U8 => 1,
116            DType::U16 => 2,
117            DType::U32 => 4,
118            DType::U64 => 8,
119            DType::I8 => 1,
120            DType::I16 => 2,
121            DType::I32 => 4,
122            DType::I64 => 8,
123            DType::Bool => 1,
124        }
125    }
126}
127
128/// Multi-backend tensor representation.
129///
130/// The `Tensor` enum provides a unified interface over different tensor backends,
131/// allowing seamless switching between CPU and GPU computations based on availability
132/// and requirements.
133///
134/// # Variants
135///
136/// - `F32`: 32-bit floating point tensors (most common for neural networks)
137/// - `F64`: 64-bit floating point tensors (for high precision requirements)
138/// - `I64`: 64-bit integer tensors (for indices and discrete values)
139/// - `Candle`: Candle backend (requires `candle` feature)
140///
141/// # Backend Selection
142///
143/// The default backend is ndarray (CPU), which provides good performance for
144/// small to medium models. For larger models or when GPU acceleration is needed,
145/// enable the `candle` feature.
146///
147/// # Example
148///
149/// ```no_run
150/// use trustformers_core::tensor::Tensor;
151///
152/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
153/// // Create a tensor with default backend
154/// let tensor = Tensor::zeros(&[2, 3, 4])?;
155/// assert_eq!(tensor.shape(), vec![2, 3, 4]);
156/// # Ok(())
157/// # }
158/// ```
159/// Metal GPU buffer wrapper for GPU-resident tensors
160#[cfg(all(target_os = "macos", feature = "metal"))]
161#[derive(Debug)]
162pub struct MetalTensorData {
163    pub buffer_id: crate::gpu_ops::metal::BufferId,
164    pub shape: Vec<usize>,
165    pub dtype: DType,
166}
167
168#[cfg(all(target_os = "macos", feature = "metal"))]
169impl Clone for MetalTensorData {
170    fn clone(&self) -> Self {
171        // Note: This creates a reference to the same GPU buffer
172        // Actual data is not copied - buffer is reference counted
173        Self {
174            buffer_id: self.buffer_id,
175            shape: self.shape.clone(),
176            dtype: self.dtype,
177        }
178    }
179}
180
181/// CUDA GPU buffer wrapper for GPU-resident tensors.
182///
183/// Holds a reference-counted [`BufferHandle`](crate::gpu_ops::cuda::BufferHandle) rather
184/// than a raw buffer id: cloning shares the same device allocation (refcount increment
185/// only), and when the last clone drops the buffer is removed from the backend cache and
186/// its device memory freed. The handle also carries the CUDA device ordinal the buffer
187/// lives on, so downstream ops address the correct device on multi-GPU machines.
188#[cfg(feature = "cuda")]
189#[derive(Debug, Clone)]
190pub struct CudaTensorData {
191    /// Lifecycle-managed reference to the GPU-resident buffer (id + device ordinal).
192    pub buffer: crate::gpu_ops::cuda::BufferHandle,
193    pub shape: Vec<usize>,
194    pub dtype: DType,
195}
196
197#[cfg(feature = "cuda")]
198impl CudaTensorData {
199    /// Wrap a freshly minted resident buffer id (owned by the backend for `device_id`)
200    /// into a lifecycle-managed CUDA tensor payload. Each raw id must be wrapped at most
201    /// once; all sharing then goes through `clone()`.
202    pub fn new(
203        buffer_id: crate::gpu_ops::cuda::BufferId,
204        device_id: usize,
205        shape: Vec<usize>,
206        dtype: DType,
207    ) -> Self {
208        Self {
209            buffer: crate::gpu_ops::cuda::BufferHandle::new(buffer_id, device_id),
210            shape,
211            dtype,
212        }
213    }
214
215    /// Build from an existing handle (test seam for mocked release callbacks).
216    #[cfg(test)]
217    pub(crate) fn from_handle(
218        buffer: crate::gpu_ops::cuda::BufferHandle,
219        shape: Vec<usize>,
220        dtype: DType,
221    ) -> Self {
222        Self {
223            buffer,
224            shape,
225            dtype,
226        }
227    }
228
229    /// The resident buffer id backing this tensor.
230    #[inline]
231    pub fn buffer_id(&self) -> crate::gpu_ops::cuda::BufferId {
232        self.buffer.id()
233    }
234
235    /// The CUDA device ordinal this tensor's buffer lives on.
236    #[inline]
237    pub fn device_id(&self) -> usize {
238        self.buffer.device_id()
239    }
240}
241
242pub enum Tensor {
243    // Standard ndarray types
244    F32(ArrayD<f32>),
245    F64(ArrayD<f64>),
246    F16(ArrayD<half::f16>),
247    BF16(ArrayD<half::bf16>),
248    I64(ArrayD<i64>),
249    // Complex number types
250    C32(ArrayD<Complex32>),
251    C64(ArrayD<Complex64>),
252    CF16(ArrayD<Complex<half::f16>>),
253    CBF16(ArrayD<Complex<half::bf16>>),
254    // Sparse tensor variant
255    Sparse(crate::sparse_tensor::SparseTensor),
256    // GPU support available via hardware acceleration module (CUDA, ROCm, Intel OneAPI, Vulkan, Metal)
257    // and backend-specific implementations (Candle)
258    #[cfg(feature = "candle")]
259    Candle(candle_core::Tensor),
260    // Metal GPU-resident tensor (data lives on GPU)
261    #[cfg(all(target_os = "macos", feature = "metal"))]
262    Metal(MetalTensorData),
263    // CUDA GPU-resident tensor (data lives on GPU)
264    #[cfg(feature = "cuda")]
265    CUDA(CudaTensorData),
266}
267
268// Manual Clone implementation because some backend tensor types require custom clone semantics
269impl Clone for Tensor {
270    fn clone(&self) -> Self {
271        match self {
272            Tensor::F32(arr) => Tensor::F32(arr.clone()),
273            Tensor::F64(arr) => Tensor::F64(arr.clone()),
274            Tensor::F16(arr) => Tensor::F16(arr.clone()),
275            Tensor::BF16(arr) => Tensor::BF16(arr.clone()),
276            Tensor::I64(arr) => Tensor::I64(arr.clone()),
277            Tensor::C32(arr) => Tensor::C32(arr.clone()),
278            Tensor::C64(arr) => Tensor::C64(arr.clone()),
279            Tensor::CF16(arr) => Tensor::CF16(arr.clone()),
280            Tensor::CBF16(arr) => Tensor::CBF16(arr.clone()),
281            Tensor::Sparse(s) => Tensor::Sparse(s.clone()),
282            #[cfg(feature = "candle")]
283            Tensor::Candle(t) => Tensor::Candle(t.clone()),
284            #[cfg(all(target_os = "macos", feature = "metal"))]
285            Tensor::Metal(data) => Tensor::Metal(data.clone()),
286            #[cfg(feature = "cuda")]
287            Tensor::CUDA(data) => Tensor::CUDA(data.clone()),
288        }
289    }
290}
291
292// Manual Debug implementation for Tensor
293impl std::fmt::Debug for Tensor {
294    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295        match self {
296            Tensor::F32(_) => write!(f, "Tensor::F32(shape: {:?}, dtype: F32)", self.shape()),
297            Tensor::F64(_) => write!(f, "Tensor::F64(shape: {:?}, dtype: F64)", self.shape()),
298            Tensor::F16(_) => write!(f, "Tensor::F16(shape: {:?}, dtype: F16)", self.shape()),
299            Tensor::BF16(_) => write!(f, "Tensor::BF16(shape: {:?}, dtype: BF16)", self.shape()),
300            Tensor::I64(_) => write!(f, "Tensor::I64(shape: {:?}, dtype: I64)", self.shape()),
301            Tensor::C32(_) => write!(f, "Tensor::C32(shape: {:?}, dtype: C32)", self.shape()),
302            Tensor::C64(_) => write!(f, "Tensor::C64(shape: {:?}, dtype: C64)", self.shape()),
303            Tensor::CF16(_) => write!(f, "Tensor::CF16(shape: {:?}, dtype: CF16)", self.shape()),
304            Tensor::CBF16(_) => write!(f, "Tensor::CBF16(shape: {:?}, dtype: CBF16)", self.shape()),
305            Tensor::Sparse(s) => write!(f, "Tensor::Sparse({:?})", s),
306            #[cfg(feature = "candle")]
307            Tensor::Candle(_) => write!(f, "Tensor::Candle(shape: {:?})", self.shape()),
308            #[cfg(all(target_os = "macos", feature = "metal"))]
309            Tensor::Metal(data) => write!(
310                f,
311                "Tensor::Metal(shape: {:?}, dtype: {:?}, buffer_id: {:?})",
312                data.shape, data.dtype, data.buffer_id
313            ),
314            #[cfg(feature = "cuda")]
315            Tensor::CUDA(data) => write!(
316                f,
317                "Tensor::CUDA(shape: {:?}, dtype: {:?}, buffer: {:?})",
318                data.shape, data.dtype, data.buffer
319            ),
320        }
321    }
322}
323
324// Safety: The Candle backend is internally thread-safe:
325// - Candle: Tensors are designed to be thread-safe with reference-counted storage.
326// Multiple threads can safely hold references to the same tensor.
327#[cfg(feature = "candle")]
328unsafe impl Sync for Tensor {}
329
330// The implementations are in separate modules but the methods are part of the Tensor impl blocks
331
332impl From<ArrayBase<OwnedRepr<f32>, Dim<IxDynImpl>>> for Tensor {
333    fn from(arr: ArrayD<f32>) -> Self {
334        Tensor::F32(arr)
335    }
336}
337
338impl From<ArrayBase<OwnedRepr<f64>, Dim<IxDynImpl>>> for Tensor {
339    fn from(arr: ArrayD<f64>) -> Self {
340        Tensor::F64(arr)
341    }
342}
343
344// Additional math operations for trait compatibility
345impl std::ops::Add for Tensor {
346    type Output = Result<Tensor>;
347
348    fn add(self, other: Tensor) -> Self::Output {
349        Tensor::add(&self, &other)
350    }
351}
352
353impl std::ops::Add for &Tensor {
354    type Output = Result<Tensor>;
355
356    fn add(self, other: &Tensor) -> Self::Output {
357        Tensor::add(self, other)
358    }
359}
360
361impl std::ops::Add<&&Tensor> for &Tensor {
362    type Output = Result<Tensor>;
363
364    fn add(self, other: &&Tensor) -> Self::Output {
365        Tensor::add(self, other)
366    }
367}
368
369impl std::ops::Add<&Tensor> for &&Tensor {
370    type Output = Result<Tensor>;
371
372    fn add(self, other: &Tensor) -> Self::Output {
373        Tensor::add(self, other)
374    }
375}
376
377impl std::ops::Sub for Tensor {
378    type Output = Result<Tensor>;
379
380    fn sub(self, other: Tensor) -> Self::Output {
381        Tensor::sub(&self, &other)
382    }
383}
384
385// Scalar multiplication operators
386impl std::ops::Mul<f32> for Tensor {
387    type Output = Result<Tensor>;
388
389    fn mul(self, scalar: f32) -> Self::Output {
390        self.scalar_mul(scalar)
391    }
392}
393
394impl std::ops::Mul<f32> for &Tensor {
395    type Output = Result<Tensor>;
396
397    fn mul(self, scalar: f32) -> Self::Output {
398        self.scalar_mul(scalar)
399    }
400}
401
402impl std::ops::Mul<f64> for Tensor {
403    type Output = Result<Tensor>;
404
405    fn mul(self, scalar: f64) -> Self::Output {
406        self.scalar_mul(scalar as f32)
407    }
408}
409
410impl std::ops::Mul<f64> for &Tensor {
411    type Output = Result<Tensor>;
412
413    fn mul(self, scalar: f64) -> Self::Output {
414        self.scalar_mul(scalar as f32)
415    }
416}
417
418// Element-wise multiplication with another tensor
419impl std::ops::Mul<&Tensor> for &Tensor {
420    type Output = Result<Tensor>;
421
422    fn mul(self, other: &Tensor) -> Self::Output {
423        Tensor::mul(self, other)
424    }
425}
426
427impl std::ops::Mul<Tensor> for &Tensor {
428    type Output = Result<Tensor>;
429
430    fn mul(self, other: Tensor) -> Self::Output {
431        Tensor::mul(self, &other)
432    }
433}
434
435impl std::ops::Mul<&Tensor> for Tensor {
436    type Output = Result<Tensor>;
437
438    fn mul(self, other: &Tensor) -> Self::Output {
439        Tensor::mul(&self, other)
440    }
441}
442
443// Scalar division operators
444impl std::ops::Div<f32> for Tensor {
445    type Output = Result<Tensor>;
446
447    fn div(self, scalar: f32) -> Self::Output {
448        self.scalar_div(scalar)
449    }
450}
451
452impl std::ops::Div<f32> for &Tensor {
453    type Output = Result<Tensor>;
454
455    fn div(self, scalar: f32) -> Self::Output {
456        self.scalar_div(scalar)
457    }
458}
459
460impl std::ops::Div<f64> for Tensor {
461    type Output = Result<Tensor>;
462
463    fn div(self, scalar: f64) -> Self::Output {
464        self.scalar_div(scalar as f32)
465    }
466}
467
468impl std::ops::Div<f64> for &Tensor {
469    type Output = Result<Tensor>;
470
471    fn div(self, scalar: f64) -> Self::Output {
472        self.scalar_div(scalar as f32)
473    }
474}
475
476impl std::ops::Div<f64> for &&Tensor {
477    type Output = Result<Tensor>;
478
479    fn div(self, scalar: f64) -> Self::Output {
480        (*self).scalar_div(scalar as f32)
481    }
482}
483
484// Tensor subtraction operators
485impl std::ops::Sub for &Tensor {
486    type Output = Result<Tensor>;
487
488    fn sub(self, other: &Tensor) -> Self::Output {
489        Tensor::sub(self, other)
490    }
491}
492
493// Type alias for backward compatibility
494pub type TensorType = DType;
495
496// Re-export expression template types
497pub use expression::{EvalContext, ExprNode, OpType, OptimizationHints, TensorExpr};
498
499// Re-export gradient tracking utilities
500pub use utils::{clear_gradients, disable_grad, enable_grad, is_grad_enabled};