Skip to main content

burn_std/data/tensor/
base.rs

1use core::f32;
2
3use alloc::format;
4use alloc::string::String;
5use alloc::vec::Vec;
6use bytemuck::checked::CheckedCastError;
7use rand::Rng;
8use thiserror::Error;
9
10use crate::Scalar;
11use crate::distribution::Distribution;
12use crate::element::{Element, ElementConversion};
13use crate::tensor::DType;
14use crate::{
15    AccessError, BoolStore, Bytes, ExecutionError, QuantMode, QuantScheme, QuantValue,
16    QuantizedBytes, Reader, Shape, Writer, bf16, f16,
17};
18
19use serde::{Deserialize, Serialize};
20
21/// Errors that can occur while accessing or converting [`TensorData`].
22#[derive(Debug, Error, PartialEq, Eq)]
23pub enum DataError {
24    /// Host access to the underlying storage failed.
25    #[error("Failed to access TensorData storage: {0}")]
26    StorageAccess(#[from] AccessError),
27
28    /// The stored bytes aren't a valid representation of the requested element type.
29    #[error("TensorData storage is invalid for the requested element type: {0}")]
30    InvalidRepresentation(CheckedCastError),
31
32    /// The stored dtype doesn't match the requested dtype.
33    #[error("Expected data type {expected:?}, but got {actual:?}")]
34    DTypeMismatch {
35        /// The expected storage DType.
36        expected: DType,
37
38        /// The actual storage DType.
39        actual: DType,
40    },
41
42    /// Unsupported data conversion.
43    #[error("Unsupported data conversion from {from:?} to {to:?}")]
44    UnsupportedConversion {
45        /// The source DType.
46        from: DType,
47
48        /// The destination DType.
49        to: DType,
50    },
51
52    /// The byte storage doesn't match the number of elements described by the shape.
53    #[error("TensorData shape describes {expected} element(s), but storage contains {actual}")]
54    ElementCountMismatch {
55        /// The number of elements described by the shape.
56        expected: usize,
57
58        /// The number of elements present in storage.
59        actual: usize,
60    },
61}
62
63/// Errors that can occur while reading host data from a tensor.
64#[derive(Debug, Error)]
65pub enum TensorReadError {
66    /// Tensor execution failed while reading the data from the device.
67    #[error(transparent)]
68    Execution(#[from] ExecutionError),
69
70    /// The resulting [`TensorData`] could not satisfy the requested data operation.
71    #[error(transparent)]
72    Data(#[from] DataError),
73
74    /// The tensor shape does not satisfy the read operation's element-count requirement.
75    #[error("Expected {expected} tensor element(s), but got {actual}")]
76    InvalidShape {
77        /// The required number of elements.
78        expected: usize,
79
80        /// The actual number of elements.
81        actual: usize,
82    },
83}
84
85impl DataError {
86    /// Creates a [`DataError::DTypeMismatch`] for the requested element type `E`.
87    pub(super) fn dtype_mismatch_as<E: Element>(actual: DType) -> Self {
88        Self::DTypeMismatch {
89            expected: E::dtype(),
90            actual,
91        }
92    }
93}
94
95/// Data structure for tensors.
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97#[serde(try_from = "TensorDataDe")]
98pub struct TensorData {
99    /// The values of the tensor (as bytes).
100    pub bytes: Bytes,
101
102    /// The shape of the tensor.
103    #[serde(with = "shape_inner")]
104    pub shape: Shape,
105
106    /// The data type of the tensor.
107    pub dtype: DType,
108}
109
110/// Unvalidated wire form of [`TensorData`], used so that deserialization enforces the same
111/// byte-length invariant as the constructors instead of trusting the encoded shape.
112#[derive(Deserialize)]
113struct TensorDataDe {
114    bytes: Bytes,
115    #[serde(with = "shape_inner")]
116    shape: Shape,
117    dtype: DType,
118}
119
120impl TryFrom<TensorDataDe> for TensorData {
121    type Error = String;
122
123    fn try_from(data: TensorDataDe) -> Result<Self, Self::Error> {
124        // `dtype.size()` is not the stored width of quantized data (sub-byte values plus
125        // appended scales), so only the other dtypes have a shape-derived byte length. The
126        // product is checked as well: an encoded shape that overflows `usize` would otherwise
127        // wrap into a small element count that matches the payload.
128        let expected = data
129            .shape
130            .iter()
131            .try_fold(1usize, |numel, dim| numel.checked_mul(*dim))
132            .and_then(|numel| numel.checked_mul(data.dtype.size()));
133
134        if !matches!(data.dtype, DType::QFloat(_)) && expected != Some(data.bytes.len()) {
135            return Err(format!(
136                "Shape {:?} is invalid for input of size {:?} bytes",
137                data.shape,
138                data.bytes.len(),
139            ));
140        }
141
142        Ok(Self {
143            bytes: data.bytes,
144            shape: data.shape,
145            dtype: data.dtype,
146        })
147    }
148}
149
150// For backward compatibility with shape `Vec<usize>`
151mod shape_inner {
152    use crate::SmallVec;
153
154    use super::*;
155
156    pub fn serialize<S: serde::Serializer>(
157        shape: &Shape,
158        serializer: S,
159    ) -> Result<S::Ok, S::Error> {
160        shape.as_slice().serialize(serializer)
161    }
162
163    pub fn deserialize<'de, D: serde::Deserializer<'de>>(
164        deserializer: D,
165    ) -> Result<Shape, D::Error> {
166        let dims = SmallVec::<[usize; _]>::deserialize(deserializer)?;
167        Ok(Shape::new_raw(dims))
168    }
169}
170
171impl TensorData {
172    /// Creates a new tensor data structure.
173    pub fn new<E: Element, S: Into<Shape>>(value: Vec<E>, shape: S) -> Self {
174        // Ensure shape is valid
175        let shape = shape.into();
176        Self::check_data_len(&value, &shape);
177
178        Self {
179            bytes: Bytes::from_elems(value),
180            shape,
181            dtype: E::dtype(),
182        }
183    }
184
185    /// Creates a new quantized tensor data structure.
186    pub fn quantized<E: Element, S: Into<Shape>>(
187        value: Vec<E>,
188        shape: S,
189        scheme: QuantScheme,
190        qparams: &[f32],
191        global: Option<f32>,
192    ) -> Self {
193        let shape = shape.into();
194        Self::check_data_len(&value, &shape);
195
196        let q_bytes = QuantizedBytes::new(value, shape.clone(), scheme, qparams, global);
197
198        Self {
199            bytes: q_bytes.bytes,
200            shape,
201            dtype: DType::QFloat(q_bytes.scheme),
202        }
203    }
204
205    /// Creates a new tensor data structure from raw bytes.
206    pub fn from_bytes<S: Into<Shape>>(bytes: Bytes, shape: S, dtype: DType) -> Self {
207        Self {
208            bytes,
209            shape: shape.into(),
210            dtype,
211        }
212    }
213
214    /// Creates a new tensor data structure from raw bytes stored in a vector.
215    ///
216    /// Prefer [`TensorData::new`] or [`TensorData::quantized`] over this method unless you are
217    /// certain that the byte representation is valid.
218    pub fn from_bytes_vec<S: Into<Shape>>(bytes: Vec<u8>, shape: S, dtype: DType) -> Self {
219        Self {
220            bytes: Bytes::from_bytes_vec(bytes),
221            shape: shape.into(),
222            dtype,
223        }
224    }
225
226    // Check that the input vector contains a correct number of elements
227    fn check_data_len<E: Element>(data: &[E], shape: &Shape) {
228        let expected_data_len = numel(shape);
229        let num_data = data.len();
230        assert_eq!(
231            expected_data_len, num_data,
232            "Shape {shape:?} is invalid for input of size {num_data:?}",
233        );
234    }
235
236    /// Returns the immutable slice view of the tensor data.
237    ///
238    /// This materializes lazy storage into host-accessible memory when necessary.
239    ///
240    /// # Errors
241    ///
242    /// Returns an error if host access fails, the target element type doesn't match the stored
243    /// type, or the stored byte representation is invalid for `E`.
244    pub fn as_slice<E: Element>(&self) -> Result<&[E], DataError> {
245        if self.matches_target_dtype::<E>() {
246            bytemuck::checked::try_cast_slice(self.bytes.read(Reader::new())?)
247                .map_err(DataError::InvalidRepresentation)
248        } else {
249            Err(DataError::dtype_mismatch_as::<E>(self.dtype))
250        }
251    }
252
253    /// Returns the mutable slice view of the tensor data.
254    ///
255    /// This materializes lazy storage and performs copy-on-write when necessary.
256    ///
257    /// # Errors
258    ///
259    /// Returns an error if host access fails, the target element type doesn't match the stored
260    /// type, or the stored byte representation is invalid for `E`.
261    pub fn as_mut_slice<E: Element>(&mut self) -> Result<&mut [E], DataError> {
262        if self.matches_target_dtype::<E>() {
263            bytemuck::checked::try_cast_slice_mut(self.bytes.write(Writer::new())?)
264                .map_err(DataError::InvalidRepresentation)
265        } else {
266            Err(DataError::dtype_mismatch_as::<E>(self.dtype))
267        }
268    }
269
270    pub(super) fn matches_target_dtype<E: Element>(&self) -> bool {
271        let target_dtype = E::dtype();
272        match self.dtype {
273            DType::Bool(BoolStore::U8) => {
274                matches!(target_dtype, DType::U8 | DType::Bool(BoolStore::U8))
275            }
276            DType::Bool(BoolStore::U32) => {
277                matches!(target_dtype, DType::U32 | DType::Bool(BoolStore::U32))
278            }
279            dtype => dtype == target_dtype,
280        }
281    }
282
283    /// Returns the rank (the number of dimensions).
284    pub fn rank(&self) -> usize {
285        self.shape.len()
286    }
287
288    /// Returns the total number of elements of the tensor data.
289    pub fn num_elements(&self) -> usize {
290        numel(&self.shape)
291    }
292
293    /// Populates the data with random values.
294    pub fn random<E: Element, R: Rng, S: Into<Shape>>(
295        shape: S,
296        distribution: Distribution,
297        rng: &mut R,
298    ) -> Self {
299        let shape = shape.into();
300        let num_elements = numel(&shape);
301        let mut data = Vec::with_capacity(num_elements);
302
303        for _ in 0..num_elements {
304            data.push(E::random(distribution, rng));
305        }
306
307        TensorData::new(data, shape)
308    }
309
310    /// Populates the data with zeros.
311    pub fn zeros<E: Element, S: Into<Shape>>(shape: S) -> TensorData {
312        let shape = shape.into();
313        let num_elements = numel(&shape);
314        let mut data = Vec::<E>::with_capacity(num_elements);
315
316        for _ in 0..num_elements {
317            data.push(0.elem());
318        }
319
320        TensorData::new(data, shape)
321    }
322
323    /// Populates the data with ones.
324    pub fn ones<E: Element, S: Into<Shape>>(shape: S) -> TensorData {
325        let shape = shape.into();
326        let num_elements = numel(&shape);
327        let mut data = Vec::<E>::with_capacity(num_elements);
328
329        for _ in 0..num_elements {
330            data.push(1.elem());
331        }
332
333        TensorData::new(data, shape)
334    }
335
336    /// Populates the data with the given value
337    pub fn full<E: Element, S: Into<Shape>>(shape: S, fill_value: E) -> TensorData {
338        let shape = shape.into();
339        let num_elements = numel(&shape);
340        let mut data = Vec::<E>::with_capacity(num_elements);
341        for _ in 0..num_elements {
342            data.push(fill_value)
343        }
344
345        TensorData::new(data, shape)
346    }
347
348    /// Populates the data with the given value
349    pub fn full_dtype<E: Into<Scalar>, S: Into<Shape>>(
350        shape: S,
351        fill_value: E,
352        dtype: DType,
353    ) -> TensorData {
354        let fill_value = fill_value.into();
355        match dtype {
356            DType::F64 => Self::full::<f64, _>(shape, fill_value.elem()),
357            DType::F32 | DType::Flex32 => Self::full::<f32, _>(shape, fill_value.elem()),
358            DType::F16 => Self::full::<f16, _>(shape, fill_value.elem()),
359            DType::BF16 => Self::full::<bf16, _>(shape, fill_value.elem()),
360            DType::I64 => Self::full::<i64, _>(shape, fill_value.elem()),
361            DType::I32 => Self::full::<i32, _>(shape, fill_value.elem()),
362            DType::I16 => Self::full::<i16, _>(shape, fill_value.elem()),
363            DType::I8 => Self::full::<i8, _>(shape, fill_value.elem()),
364            DType::U64 => Self::full::<u64, _>(shape, fill_value.elem()),
365            DType::U32 => Self::full::<u32, _>(shape, fill_value.elem()),
366            DType::U16 => Self::full::<u16, _>(shape, fill_value.elem()),
367            DType::U8 => Self::full::<u8, _>(shape, fill_value.elem()),
368            DType::Bool(BoolStore::Native) => Self::full::<bool, _>(shape, fill_value.elem()),
369            DType::Bool(BoolStore::U8) => {
370                Self::full::<u8, _>(shape, fill_value.elem()).into_bool_u8()
371            }
372            DType::Bool(BoolStore::U32) => {
373                Self::full::<u32, _>(shape, fill_value.elem()).into_bool_u32()
374            }
375            DType::QFloat(_) => unreachable!(),
376        }
377    }
378
379    // Unchecked, used to overwrite the dtype
380    pub(super) fn into_bool_u8(mut self) -> Self {
381        self.dtype = DType::Bool(BoolStore::U8);
382        self
383    }
384
385    // Unchecked, used to overwrite the dtype
386    pub(super) fn into_bool_u32(mut self) -> Self {
387        self.dtype = DType::Bool(BoolStore::U32);
388        self
389    }
390
391    /// Returns the data as a slice of bytes.
392    pub fn as_bytes(&self) -> &[u8] {
393        &self.bytes
394    }
395
396    /// Returns the bytes representation of the data.
397    pub fn into_bytes(self) -> Bytes {
398        self.bytes
399    }
400}
401
402fn numel(shape: &[usize]) -> usize {
403    shape.iter().product()
404}
405
406impl<E: Element, const A: usize> From<[E; A]> for TensorData {
407    fn from(elems: [E; A]) -> Self {
408        TensorData::new(elems.to_vec(), [A])
409    }
410}
411
412impl<const A: usize> From<[usize; A]> for TensorData {
413    fn from(elems: [usize; A]) -> Self {
414        TensorData::new(elems.iter().map(|&e| e as i64).collect(), [A])
415    }
416}
417
418impl From<&[usize]> for TensorData {
419    fn from(elems: &[usize]) -> Self {
420        let mut data = Vec::with_capacity(elems.len());
421        for elem in elems.iter() {
422            data.push(*elem as i64);
423        }
424
425        TensorData::new(data, [elems.len()])
426    }
427}
428
429impl<E: Element> From<&[E]> for TensorData {
430    fn from(elems: &[E]) -> Self {
431        let mut data = Vec::with_capacity(elems.len());
432        for elem in elems.iter() {
433            data.push(*elem);
434        }
435
436        TensorData::new(data, [elems.len()])
437    }
438}
439
440impl<E: Element, const A: usize, const B: usize> From<[[E; B]; A]> for TensorData {
441    fn from(elems: [[E; B]; A]) -> Self {
442        let mut data = Vec::with_capacity(A * B);
443        for elem in elems.into_iter().take(A) {
444            for elem in elem.into_iter().take(B) {
445                data.push(elem);
446            }
447        }
448
449        TensorData::new(data, [A, B])
450    }
451}
452
453impl<E: Element, const A: usize, const B: usize, const C: usize> From<[[[E; C]; B]; A]>
454    for TensorData
455{
456    fn from(elems: [[[E; C]; B]; A]) -> Self {
457        let mut data = Vec::with_capacity(A * B * C);
458
459        for elem in elems.into_iter().take(A) {
460            for elem in elem.into_iter().take(B) {
461                for elem in elem.into_iter().take(C) {
462                    data.push(elem);
463                }
464            }
465        }
466
467        TensorData::new(data, [A, B, C])
468    }
469}
470
471impl<E: Element, const A: usize, const B: usize, const C: usize, const D: usize>
472    From<[[[[E; D]; C]; B]; A]> for TensorData
473{
474    fn from(elems: [[[[E; D]; C]; B]; A]) -> Self {
475        let mut data = Vec::with_capacity(A * B * C * D);
476
477        for elem in elems.into_iter().take(A) {
478            for elem in elem.into_iter().take(B) {
479                for elem in elem.into_iter().take(C) {
480                    for elem in elem.into_iter().take(D) {
481                        data.push(elem);
482                    }
483                }
484            }
485        }
486
487        TensorData::new(data, [A, B, C, D])
488    }
489}
490
491impl<Elem: Element, const A: usize, const B: usize, const C: usize, const D: usize, const E: usize>
492    From<[[[[[Elem; E]; D]; C]; B]; A]> for TensorData
493{
494    fn from(elems: [[[[[Elem; E]; D]; C]; B]; A]) -> Self {
495        let mut data = Vec::with_capacity(A * B * C * D * E);
496
497        for elem in elems.into_iter().take(A) {
498            for elem in elem.into_iter().take(B) {
499                for elem in elem.into_iter().take(C) {
500                    for elem in elem.into_iter().take(D) {
501                        for elem in elem.into_iter().take(E) {
502                            data.push(elem);
503                        }
504                    }
505                }
506            }
507        }
508
509        TensorData::new(data, [A, B, C, D, E])
510    }
511}
512impl core::fmt::Display for TensorData {
513    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
514        let fmt = match self.dtype {
515            DType::F64 => format!("{:?}", self.as_slice::<f64>().unwrap()),
516            DType::F32 | DType::Flex32 => format!("{:?}", self.as_slice::<f32>().unwrap()),
517            DType::F16 => format!("{:?}", self.as_slice::<f16>().unwrap()),
518            DType::BF16 => format!("{:?}", self.as_slice::<bf16>().unwrap()),
519            DType::I64 => format!("{:?}", self.as_slice::<i64>().unwrap()),
520            DType::I32 => format!("{:?}", self.as_slice::<i32>().unwrap()),
521            DType::I16 => format!("{:?}", self.as_slice::<i16>().unwrap()),
522            DType::I8 => format!("{:?}", self.as_slice::<i8>().unwrap()),
523            DType::U64 => format!("{:?}", self.as_slice::<u64>().unwrap()),
524            DType::U32 => format!("{:?}", self.as_slice::<u32>().unwrap()),
525            DType::U16 => format!("{:?}", self.as_slice::<u16>().unwrap()),
526            DType::U8 => format!("{:?}", self.as_slice::<u8>().unwrap()),
527            DType::Bool(BoolStore::Native) => format!("{:?}", self.as_slice::<bool>().unwrap()),
528            DType::Bool(BoolStore::U8) => format!("{:?}", self.as_slice::<u8>().unwrap()),
529            DType::Bool(BoolStore::U32) => format!("{:?}", self.as_slice::<u32>().unwrap()),
530            DType::QFloat(scheme) => match scheme {
531                QuantScheme {
532                    mode: QuantMode::Symmetric,
533                    value:
534                        QuantValue::Q8F
535                        | QuantValue::Q8S
536                        // Display sub-byte values as i8
537                        | QuantValue::Q4F
538                        | QuantValue::Q4S
539                        | QuantValue::Q2F
540                        | QuantValue::Q2S,
541                    ..
542                } => {
543                    format!("{:?} {scheme:?}", self.iter::<i8>().collect::<Vec<_>>())
544                },
545                QuantScheme {
546                        mode: QuantMode::Symmetric,
547                        value:
548                            QuantValue::E4M3 | QuantValue::E5M2 | QuantValue::E2M1,
549                        ..
550                    } => {
551                        unimplemented!("Can't format yet");
552                    }
553                QuantScheme {
554                    mode: QuantMode::Lookup,
555                    ..
556                } => {
557                    format!("<lookup-quantized> {scheme:?}")
558                }
559            },
560        };
561        f.write_str(fmt.as_str())
562    }
563}
564
565#[cfg(test)]
566mod tests {
567    use super::*;
568    use crate::*;
569    use ::rand::{
570        SeedableRng,
571        rngs::{StdRng, SysRng},
572    };
573    use alloc::string::ToString;
574    use alloc::vec;
575    use core::mem::{MaybeUninit, align_of, size_of};
576
577    #[test]
578    fn should_have_rank() {
579        let shape = [3, 5, 6];
580        let data = TensorData::random::<f32, _, _>(
581            shape,
582            Distribution::Default,
583            &mut StdRng::try_from_rng(&mut SysRng).unwrap(),
584        );
585
586        assert_eq!(data.rank(), 3);
587    }
588
589    #[test]
590    fn into_vec_should_yield_same_value_as_iter() {
591        let shape = [3, 5, 6];
592        let data = TensorData::random::<f32, _, _>(
593            shape,
594            Distribution::Default,
595            &mut StdRng::try_from_rng(&mut SysRng).unwrap(),
596        );
597
598        let expected = data.iter::<f32>().collect::<Vec<f32>>();
599        let actual = data.try_into_vec::<f32>().unwrap();
600
601        assert_eq!(expected, actual);
602    }
603
604    #[test]
605    #[should_panic]
606    fn into_vec_should_assert_wrong_dtype() {
607        let shape = [3, 5, 6];
608        let data = TensorData::random::<f32, _, _>(
609            shape,
610            Distribution::Default,
611            &mut StdRng::try_from_rng(&mut SysRng).unwrap(),
612        );
613
614        data.try_into_vec::<i32>().unwrap();
615    }
616
617    #[test]
618    fn should_have_right_num_elements() {
619        let shape = [3, 5, 6];
620        let num_elements: usize = shape.iter().product();
621        let data = TensorData::random::<f32, _, _>(
622            shape,
623            Distribution::Default,
624            &mut StdRng::try_from_rng(&mut SysRng).unwrap(),
625        );
626
627        assert_eq!(num_elements, data.bytes.len() / 4); // f32 stored as u8s
628        assert_eq!(num_elements, data.as_slice::<f32>().unwrap().len());
629    }
630
631    #[test]
632    fn should_have_right_shape() {
633        let data = TensorData::from([[3.0, 5.0, 6.0]]);
634        assert_eq!(data.shape, shape![1, 3]);
635
636        let data = TensorData::from([[4.0, 5.0, 8.0], [3.0, 5.0, 6.0]]);
637        assert_eq!(data.shape, shape![2, 3]);
638
639        let data = TensorData::from([3.0, 5.0, 6.0]);
640        assert_eq!(data.shape, shape![3]);
641    }
642
643    #[test]
644    fn should_convert_bytes_correctly() {
645        let mut vector: Vec<f32> = Vec::with_capacity(5);
646        vector.push(2.0);
647        vector.push(3.0);
648        let data1 = TensorData::new(vector, vec![2]);
649
650        let factor = size_of::<f32>() / size_of::<u8>();
651        assert_eq!(data1.bytes.len(), 2 * factor);
652        assert_eq!(data1.bytes.capacity(), 5 * factor);
653    }
654
655    #[test]
656    fn should_convert_bytes_correctly_inplace() {
657        fn test_precision<E: Element>() {
658            let data = TensorData::new((0..32).collect(), [32]);
659            let self1 = data.clone().convert::<E>();
660            for (i, val) in self1.try_into_vec::<E>().unwrap().into_iter().enumerate() {
661                assert_eq!(i as u32, val.elem::<u32>())
662            }
663        }
664        test_precision::<f32>();
665        test_precision::<f16>();
666        test_precision::<i64>();
667        test_precision::<i32>();
668    }
669
670    #[test]
671    fn should_convert_negative_values_to_bool_store() {
672        for store in [BoolStore::U8, BoolStore::U32, BoolStore::Native] {
673            let data = TensorData::from([-1i32, 0, 1, -12]).convert_dtype(DType::Bool(store));
674            assert_eq!(data.dtype, DType::Bool(store));
675            assert_eq!(
676                data.iter::<bool>().collect::<Vec<_>>(),
677                [true, false, true, true]
678            );
679
680            let data = TensorData::from([-1.5f32, 0.0, 0.5]).convert_dtype(DType::Bool(store));
681            assert_eq!(data.iter::<bool>().collect::<Vec<_>>(), [true, false, true]);
682        }
683    }
684
685    macro_rules! test_dtypes {
686    ($test_name:ident, $($dtype:ty),*) => {
687        $(
688            paste::paste! {
689                #[test]
690                fn [<$test_name _ $dtype:snake>]() {
691                    let full_dtype = TensorData::full_dtype([2, 16], 4, <$dtype>::dtype());
692                    let full = TensorData::full::<$dtype, _>([2, 16], 4.elem());
693                    assert_eq!(full_dtype, full);
694                }
695            }
696        )*
697    };
698}
699
700    test_dtypes!(
701        should_create_with_dtype,
702        bool,
703        i8,
704        i16,
705        i32,
706        i64,
707        u8,
708        u16,
709        u32,
710        u64,
711        f16,
712        bf16,
713        f32,
714        f64
715    );
716
717    #[test]
718    fn should_serialize_deserialize_tensor_data() {
719        let data = TensorData::new(vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3]);
720        assert_eq!(
721            data.as_bytes(),
722            [
723                0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 0, 0, 160, 64, 0, 0, 192,
724                64
725            ]
726        );
727        let serialized = serde_json::to_string(&data).unwrap();
728        let deserialized: TensorData = serde_json::from_str(&serialized).unwrap();
729        assert_eq!(data, deserialized);
730    }
731
732    #[test]
733    fn should_deserialize_tensor_data_with_shape_inner() {
734        // TensorData `shape` was previously a Vec<usize>.
735        let serialized = r#"{
736        "bytes": [0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 128, 64, 0, 0, 160, 64, 0, 0, 192, 64],
737        "shape": [2, 3],
738        "dtype": "F32"
739    }"#;
740
741        let data: TensorData = serde_json::from_str(serialized).unwrap();
742        assert_eq!(data.shape, shape![2, 3]);
743        assert_eq!(
744            data.as_slice::<f32>().unwrap(),
745            &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
746        );
747    }
748
749    #[test]
750    fn should_not_deserialize_tensor_data_with_shape_larger_than_bytes() {
751        // A crafted record whose shape claims 1000 f32 over a 4-byte payload must be rejected,
752        // otherwise the resulting tensor reads and writes out of bounds.
753        let serialized = r#"{"bytes": [0, 0, 128, 63], "shape": [1000], "dtype": "F32"}"#;
754
755        let err = serde_json::from_str::<TensorData>(serialized).unwrap_err();
756        assert!(
757            err.to_string().contains("is invalid for input of size"),
758            "unexpected error: {err}"
759        );
760    }
761
762    #[test]
763    fn should_not_deserialize_tensor_data_with_overflowing_shape() {
764        // The dimensions multiply to 2 with a wrapped product, matching the payload, so an
765        // unchecked product would accept a shape that claims 2^63 + 1 rows.
766        let serialized = r#"{"bytes": [0, 0, 128, 63, 0, 0, 0, 64], "shape": [9223372036854775809, 2], "dtype": "F32"}"#;
767
768        let err = serde_json::from_str::<TensorData>(serialized).unwrap_err();
769        assert!(
770            err.to_string().contains("is invalid for input of size"),
771            "unexpected error: {err}"
772        );
773    }
774
775    #[test]
776    fn should_serialize_shape_as_flat_array() {
777        // Ensure the new Shape serializes identically to how Vec<usize> used to,
778        // i.e. as a flat JSON array, not as an object like `{"dims": [2, 3]}`.
779        let data = TensorData::new(vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3]);
780        let serialized = serde_json::to_string(&data).unwrap();
781        let json: serde_json::Value = serde_json::from_str(&serialized).unwrap();
782        assert_eq!(json["shape"], serde_json::json!([2, 3]));
783    }
784
785    #[test]
786    fn test_tensor_data_try_view_dtype_mismatch() {
787        let data = TensorData::from([[1.0, 2.0], [3.0, 4.0]]);
788        let dtype = data.dtype;
789
790        assert_eq!(
791            data.try_view::<i32>().unwrap_err(),
792            DataError::DTypeMismatch {
793                expected: <i32 as Element>::dtype(),
794                actual: dtype,
795            }
796        );
797    }
798
799    #[test]
800    #[should_panic(expected = "Expected data type")]
801    fn test_tensor_data_expect_view_dtype_mismatch() {
802        let data = TensorData::from([[1.0, 2.0], [3.0, 4.0]]);
803        let _view = data.view::<i32>();
804    }
805
806    #[test]
807    fn test_tensor_data_try_mut_view_dtype_mismatch() {
808        let mut data = TensorData::from([[1.0, 2.0], [3.0, 4.0]]);
809
810        let result = data.try_mut_view::<i32>();
811        assert_eq!(
812            result.unwrap_err(),
813            DataError::DTypeMismatch {
814                actual: data.dtype,
815                expected: <i32 as Element>::dtype(),
816            }
817        );
818    }
819
820    #[test]
821    fn try_view_validates_storage() {
822        let invalid_representation = TensorData::from_bytes_vec(vec![0; 3], [1], DType::F32);
823        assert!(matches!(
824            invalid_representation.try_view::<f32>(),
825            Err(DataError::InvalidRepresentation(_))
826        ));
827
828        let invalid_count = TensorData::from_bytes_vec(vec![0; 8], [1], DType::F32);
829        assert!(matches!(
830            invalid_count.try_view::<f32>(),
831            Err(DataError::ElementCountMismatch {
832                expected: 1,
833                actual: 2
834            })
835        ));
836
837        let invalid_bool = TensorData::from_bytes_vec(vec![2], [1], DType::Bool(BoolStore::Native));
838        assert!(matches!(
839            invalid_bool.try_view::<bool>(),
840            Err(DataError::InvalidRepresentation(_))
841        ));
842    }
843
844    #[test]
845    fn try_view_propagates_storage_access_failure() {
846        use alloc::boxed::Box;
847
848        #[derive(Debug)]
849        struct FailingController;
850
851        impl AllocationController for FailingController {
852            fn alloc_align(&self) -> usize {
853                align_of::<f32>()
854            }
855
856            fn property(&self) -> AllocationProperty {
857                AllocationProperty::Other
858            }
859
860            fn capacity(&self) -> usize {
861                size_of::<f32>()
862            }
863
864            fn memory(&self, _policy: AccessPolicy) -> Result<&[MaybeUninit<u8>], AccessError> {
865                Err(AccessError::Read("test read failure".into()))
866            }
867
868            unsafe fn memory_mut(
869                &mut self,
870                _policy: AccessPolicy,
871            ) -> Result<&mut [MaybeUninit<u8>], AccessError> {
872                Err(AccessError::Read("test write failure".into()))
873            }
874        }
875
876        // SAFETY: The controller never exposes its inaccessible storage as initialized memory.
877        let bytes =
878            unsafe { Bytes::from_controller(Box::new(FailingController), size_of::<f32>()) };
879        let data = TensorData::from_bytes(bytes, [1], DType::F32);
880
881        assert!(matches!(
882            data.try_view::<f32>(),
883            Err(DataError::StorageAccess(AccessError::Read(reason))) if reason == "test read failure"
884        ));
885    }
886
887    #[test]
888    fn try_mut_view_validates_storage() {
889        let mut invalid_representation = TensorData::from_bytes_vec(vec![0; 3], [1], DType::F32);
890        assert!(matches!(
891            invalid_representation.try_mut_view::<f32>(),
892            Err(DataError::InvalidRepresentation(_))
893        ));
894    }
895
896    #[test]
897    fn try_cast_propagates_invalid_storage() {
898        let invalid_inplace = TensorData::from_bytes_vec(vec![0; 3], [1], DType::F32);
899        assert!(matches!(
900            invalid_inplace.try_cast(DType::I32),
901            Err(DataError::InvalidRepresentation(_))
902        ));
903
904        let invalid_clone = TensorData::from_bytes_vec(vec![0; 3], [1], DType::F32);
905        assert!(matches!(
906            invalid_clone.try_cast(DType::F64),
907            Err(DataError::InvalidRepresentation(_))
908        ));
909
910        let invalid_count = TensorData::from_bytes_vec(vec![0; 8], [1], DType::F32);
911        assert!(matches!(
912            invalid_count.try_cast(DType::F64),
913            Err(DataError::ElementCountMismatch {
914                expected: 1,
915                actual: 2
916            })
917        ));
918    }
919
920    #[test]
921    fn try_cast_reports_unsupported_quantized_conversion() {
922        let scheme = QuantScheme::default();
923        let target = DType::QFloat(scheme);
924
925        assert_eq!(
926            TensorData::from([1.0f32]).try_cast(target),
927            Err(DataError::UnsupportedConversion {
928                from: DType::F32,
929                to: target,
930            })
931        );
932
933        let quantized = TensorData::quantized(vec![0i8], [1], scheme, &[1.0], None);
934        assert_eq!(
935            quantized.try_cast(DType::F32),
936            Err(DataError::UnsupportedConversion {
937                from: target,
938                to: DType::F32,
939            })
940        );
941    }
942
943    #[test]
944    #[should_panic(expected = "Expected data type")]
945    fn test_tensor_data_expect_mut_view_dtype_mismatch() {
946        let mut data = TensorData::from([[1.0, 2.0], [3.0, 4.0]]);
947        let _view = data.mut_view::<i32>();
948    }
949
950    #[test]
951    fn test_tensor_data_index_view() {
952        let data = TensorData::from([[1.0, 2.0], [3.0, 4.0]]);
953        let view = data.view::<f64>();
954
955        assert_eq!(view.shape(), &data.shape);
956
957        assert_eq!(view[&[0, 0]], 1.0);
958        assert_eq!(view[&[0, 1]], 2.0);
959        assert_eq!(view[&[1, 0]], 3.0);
960        assert_eq!(view[&[1, 1]], 4.0);
961    }
962
963    #[test]
964    fn test_tensor_data_index_mut_view() {
965        let mut data = TensorData::from([[1.0, 2.0], [3.0, 4.0]]);
966        let shape = data.shape.clone();
967
968        let mut view = data.mut_view::<f64>();
969
970        assert_eq!(view.shape(), &shape);
971
972        assert_eq!(view[&[0, 0]], 1.0);
973        assert_eq!(view[&[0, 1]], 2.0);
974        assert_eq!(view[&[1, 0]], 3.0);
975        assert_eq!(view[&[1, 1]], 4.0);
976
977        view[&[0, 0]] = 10.0;
978        assert_eq!(view[&[0, 0]], 10.0);
979    }
980
981    #[test]
982    fn test_to_vec_as() {
983        let data = TensorData::from([0.0f32, 1.0, 2.5]);
984
985        // Same-dtype copy.
986        assert_eq!(data.try_to_vec_as::<f32>().unwrap(), vec![0.0f32, 1.0, 2.5]);
987
988        // Widening cast (different element size).
989        assert_eq!(data.try_to_vec_as::<f64>().unwrap(), vec![0.0f64, 1.0, 2.5]);
990
991        // Float to int cast (same element size) truncates.
992        assert_eq!(data.try_to_vec_as::<i32>().unwrap(), vec![0i32, 1, 2]);
993
994        // The source data is borrowed, not consumed.
995        data.assert_eq(&TensorData::from([0.0f32, 1.0, 2.5]), true);
996    }
997
998    #[test]
999    fn test_into_vec_as() {
1000        let data = TensorData::from([0i32, 1, 2, 3]);
1001
1002        // Same-dtype conversion.
1003        assert_eq!(
1004            data.clone().try_into_vec_as::<i32>().unwrap(),
1005            vec![0i32, 1, 2, 3]
1006        );
1007
1008        // Int to float cast.
1009        assert_eq!(
1010            data.clone().try_into_vec_as::<f32>().unwrap(),
1011            vec![0.0f32, 1.0, 2.0, 3.0]
1012        );
1013
1014        // Narrowing int cast.
1015        assert_eq!(data.try_into_vec_as::<u8>().unwrap(), vec![0u8, 1, 2, 3]);
1016    }
1017}