1use burn_backend::{
2 AllocationProperty, DType, Element, Shape, TensorData, TensorMetadata,
3 quantization::{QParams, QuantLevel, QuantMode, QuantScheme, QuantValue},
4};
5use burn_std::BoolStore;
6
7use crate::ops::quantization::{QuantizationStrategy, SymmetricQuantization};
8use crate::{NdArrayDevice, NdArrayStorage};
9use alloc::vec::Vec;
10use ndarray::{ArcArray, ArrayD, IxDyn};
11
12pub type SharedArray<E> = ArcArray<E, IxDyn>;
14
15#[derive(Debug, Clone)]
21#[allow(missing_docs)]
22pub enum NdArrayTensor {
23 F64(NdArrayStorage<f64>),
24 F32(NdArrayStorage<f32>),
25 I64(NdArrayStorage<i64>),
26 I32(NdArrayStorage<i32>),
27 I16(NdArrayStorage<i16>),
28 I8(NdArrayStorage<i8>),
29 U64(NdArrayStorage<u64>),
30 U32(NdArrayStorage<u32>),
31 U16(NdArrayStorage<u16>),
32 U8(NdArrayStorage<u8>),
33 Bool(NdArrayStorage<bool>),
34}
35
36impl NdArrayTensor {
37 pub(crate) fn bool(self) -> SharedArray<bool> {
39 match self {
40 NdArrayTensor::Bool(storage) => storage.into_shared(),
41 _ => unimplemented!("Expected bool tensor, got {:?}", self.dtype()),
42 }
43 }
44
45 #[inline]
47 pub fn is_borrowed(&self) -> bool {
48 macro_rules! check {
49 ($($variant:ident),*) => {
50 match self {
51 $(NdArrayTensor::$variant(s) => s.is_borrowed(),)*
52 }
53 };
54 }
55 check!(F64, F32, I64, I32, I16, I8, U64, U32, U16, U8, Bool)
56 }
57}
58
59pub(crate) fn cast_to_dtype<E1: Element>(array: SharedArray<E1>, dtype: DType) -> NdArrayTensor
60where
61 NdArrayTensor: From<SharedArray<E1>>,
62{
63 fn cast<E1: Element, E2: Element>(array: SharedArray<E1>) -> SharedArray<E2> {
64 array.mapv(|a| a.elem()).into_shared()
65 }
66
67 if E1::dtype() == dtype {
68 return array.into();
69 }
70
71 match dtype {
72 DType::F64 => cast::<E1, f64>(array).into(),
73 DType::F32 => cast::<E1, f32>(array).into(),
74 DType::Flex32 => cast::<E1, f32>(array).into(),
75 DType::I64 => cast::<E1, i64>(array).into(),
76 DType::I32 => cast::<E1, i32>(array).into(),
77 DType::I16 => cast::<E1, i16>(array).into(),
78 DType::I8 => cast::<E1, i8>(array).into(),
79 DType::U64 => cast::<E1, u64>(array).into(),
80 DType::U32 => cast::<E1, u32>(array).into(),
81 DType::U16 => cast::<E1, u16>(array).into(),
82 DType::U8 => cast::<E1, u8>(array).into(),
83 DType::Bool(BoolStore::Native) => cast::<E1, bool>(array).into(),
84 dtype => panic!("Unsupported dtype: {dtype:?}"),
85 }
86}
87
88macro_rules! impl_from {
89 ($($ty: ty => $dtype: ident),*) => {
90 $(impl From<SharedArray<$ty>> for NdArrayTensor {
92 fn from(value: SharedArray<$ty>) -> NdArrayTensor {
93 NdArrayTensor::$dtype(NdArrayStorage::from_owned(value))
94 }
95 })*
96
97 $(impl From<NdArrayStorage<$ty>> for NdArrayTensor {
99 fn from(value: NdArrayStorage<$ty>) -> NdArrayTensor {
100 NdArrayTensor::$dtype(value)
101 }
102 })*
103 };
104}
105
106impl_from!(
107 f64 => F64, f32 => F32,
108 i64 => I64, i32 => I32, i16 => I16, i8 => I8,
109 u64 => U64, u32 => U32, u16 => U16, u8 => U8,
110 bool => Bool
111);
112
113#[macro_export]
121macro_rules! execute_with_dtype {
122 (($lhs:expr, $rhs:expr),$element:ident, $op:expr, [$($dtype: ident => $ty: ty),*]) => {{
123 let lhs_dtype = burn_backend::TensorMetadata::dtype(&$lhs);
124 let rhs_dtype = burn_backend::TensorMetadata::dtype(&$rhs);
125 match ($lhs, $rhs) {
126 $(
127 ($crate::NdArrayTensor::$dtype(lhs), $crate::NdArrayTensor::$dtype(rhs)) => {
128 #[allow(unused)]
129 type $element = $ty;
130 $op(lhs.into_shared(), rhs.into_shared()).into()
132 }
133 )*
134 _ => panic!(
135 "Data type mismatch (lhs: {:?}, rhs: {:?})",
136 lhs_dtype, rhs_dtype
137 ),
138 }
139 }};
140 (($lhs:expr, $rhs:expr), $op:expr) => {{
142 $crate::execute_with_dtype!(($lhs, $rhs), E, $op)
143 }};
144
145 (($lhs:expr, $rhs:expr), $element:ident, $op:expr) => {{
147 $crate::execute_with_dtype!(($lhs, $rhs), $element, $op, [
148 F64 => f64, F32 => f32,
149 I64 => i64, I32 => i32, I16 => i16, I8 => i8,
150 U64 => u64, U32 => u32, U16 => u16, U8 => u8,
151 Bool => bool
152 ])
153 }};
154
155 ($tensor:expr, $element:ident, $op:expr, [$($dtype: ident => $ty: ty),*]) => {{
156 match $tensor {
157 $(
158 $crate::NdArrayTensor::$dtype(storage) => {
159 #[allow(unused)]
160 type $element = $ty;
161 $op(storage.into_shared()).into()
163 }
164 )*
165 #[allow(unreachable_patterns)]
166 other => unimplemented!("unsupported dtype: {:?}", other.dtype())
167 }
168 }};
169 ($tensor:expr, $op:expr) => {{
171 $crate::execute_with_dtype!($tensor, E, $op)
172 }};
173
174 ($tensor:expr, $element:ident, $op:expr) => {{
176 $crate::execute_with_dtype!($tensor, $element, $op, [
177 F64 => f64, F32 => f32,
178 I64 => i64, I32 => i32, I16 => i16, I8 => i8,
179 U64 => u64, U32 => u32, U16 => u16, U8 => u8,
180 Bool => bool
181 ])
182 }};
183}
184
185#[macro_export]
192macro_rules! execute_with_float_dtype {
193 (($lhs:expr, $rhs:expr), $op:expr) => {{
195 $crate::execute_with_float_dtype!(($lhs, $rhs), E, $op)
196 }};
197
198 (($lhs:expr, $rhs:expr), $element:ident, $op:expr) => {{
200 $crate::execute_with_dtype!(($lhs, $rhs), $element, $op, [
201 F64 => f64, F32 => f32
202 ])
203 }};
204
205 ($tensor:expr, $op:expr) => {{
207 $crate::execute_with_float_dtype!($tensor, E, $op)
208 }};
209
210 ($tensor:expr, $element:ident, $op:expr) => {{
212 $crate::execute_with_dtype!($tensor, $element, $op, [
213 F64 => f64, F32 => f32
214 ])
215 }};
216}
217
218#[macro_export]
225macro_rules! execute_with_int_dtype {
226 (($lhs:expr, $rhs:expr), $op:expr) => {{
228 $crate::execute_with_int_dtype!(($lhs, $rhs), E, $op)
229 }};
230
231 (($lhs:expr, $rhs:expr), $element:ident, $op:expr) => {{
233 $crate::execute_with_dtype!(($lhs, $rhs), $element, $op, [
234 I64 => i64, I32 => i32, I16 => i16, I8 => i8,
235 U64 => u64, U32 => u32, U16 => u16, U8 => u8
236 ])
237 }};
238
239 ($tensor:expr, $op:expr) => {{
241 $crate::execute_with_int_dtype!($tensor, E, $op)
242 }};
243
244 ($tensor:expr, $element:ident, $op:expr) => {{
246 $crate::execute_with_dtype!($tensor, $element, $op, [
247 I64 => i64, I32 => i32, I16 => i16, I8 => i8,
248 U64 => u64, U32 => u32, U16 => u16, U8 => u8
249 ])
250 }};
251}
252
253#[macro_export]
260macro_rules! execute_with_numeric_dtype {
261 (($lhs:expr, $rhs:expr), $op:expr) => {{
263 $crate::execute_with_numeric_dtype!(($lhs, $rhs), E, $op)
264 }};
265
266 (($lhs:expr, $rhs:expr), $element:ident, $op:expr) => {{
268 $crate::execute_with_dtype!(($lhs, $rhs), $element, $op, [
269 F64 => f64, F32 => f32,
270 I64 => i64, I32 => i32, I16 => i16, I8 => i8,
271 U64 => u64, U32 => u32, U16 => u16, U8 => u8
272 ])
273 }};
274
275 ($tensor:expr, $op:expr) => {{
277 $crate::execute_with_numeric_dtype!($tensor, E, $op)
278 }};
279
280 ($tensor:expr, $element:ident, $op:expr) => {{
282 $crate::execute_with_dtype!($tensor, $element, $op, [
283 F64 => f64, F32 => f32,
284 I64 => i64, I32 => i32, I16 => i16, I8 => i8,
285 U64 => u64, U32 => u32, U16 => u16, U8 => u8
286 ])
287 }};
288}
289
290#[macro_export]
298macro_rules! cat_with_dtype {
299 ($tensors: expr, $dim: expr, [$($dtype: ident),*]) => {
300 match &$tensors[0] {
301 $(NdArrayTensor::$dtype(_) => {
302 let tensors = $tensors
303 .iter()
304 .map(|t| {
305 if let NdArrayTensor::$dtype(storage) = t {
306 storage.view()
308 } else {
309 panic!("Concatenate data type mismatch (expected {:?}, got {:?})", $tensors[0].dtype(), t.dtype())
310 }
311 })
312 .collect::<Vec<_>>();
313 NdArrayOps::concatenate(&tensors, $dim).into()
314 })*
315 _ => panic!("Unsupported dtype: {:?}", $tensors[0].dtype())
316 }
317 };
318}
319
320#[macro_export]
322macro_rules! execute_with_float_out_dtype {
323 ($out_dtype:expr, $element:ident, $op:expr, [$($dtype: ident => $ty: ty),*]) => {{
324 match $out_dtype {
325 $(
326 burn_std::FloatDType::$dtype => {
327 #[allow(unused)]
328 type $element = $ty;
329 $op
330 }
331 )*
332 #[allow(unreachable_patterns)]
333 other => unimplemented!("unsupported dtype: {other:?}")
334 }
335 }};
336 ($out_dtype:expr, $op:expr) => {{
338 $crate::execute_with_float_out_dtype!($out_dtype, E, $op)
339 }};
340
341 ($out_dtype:expr, $element:ident, $op:expr) => {{
343 $crate::execute_with_float_out_dtype!($out_dtype, $element, $op, [
344 F64 => f64, F32 => f32
345 ])
346 }};
347}
348
349#[macro_export]
351macro_rules! execute_with_int_out_dtype {
352 ($out_dtype:expr, $element:ident, $op:expr, [$($dtype: ident => $ty: ty),*]) => {{
353 match $out_dtype {
354 $(
355 burn_std::IntDType::$dtype => {
356 #[allow(unused)]
357 type $element = $ty;
358 $op
359 }
360 )*
361 #[allow(unreachable_patterns)]
362 other => unimplemented!("unsupported dtype: {other:?}")
363 }
364 }};
365 ($out_dtype:expr, $op:expr) => {{
367 $crate::execute_with_int_out_dtype!($out_dtype, E, $op)
368 }};
369
370 ($out_dtype:expr, $element:ident, $op:expr) => {{
372 $crate::execute_with_int_out_dtype!($out_dtype, $element, $op, [
373 I64 => i64, I32 => i32, I16 => i16, I8 => i8,
374 U64 => u64, U32 => u32, U16 => u16, U8 => u8
375 ])
376 }};
377}
378
379impl TensorMetadata for NdArrayTensor {
380 type Device = NdArrayDevice;
381 fn dtype(&self) -> DType {
382 match self {
383 NdArrayTensor::F64(_) => DType::F64,
384 NdArrayTensor::F32(_) => DType::F32,
385 NdArrayTensor::I64(_) => DType::I64,
386 NdArrayTensor::I32(_) => DType::I32,
387 NdArrayTensor::I16(_) => DType::I16,
388 NdArrayTensor::I8(_) => DType::I8,
389 NdArrayTensor::U64(_) => DType::U64,
390 NdArrayTensor::U32(_) => DType::U32,
391 NdArrayTensor::U16(_) => DType::U16,
392 NdArrayTensor::U8(_) => DType::U8,
393 NdArrayTensor::Bool(_) => DType::Bool(BoolStore::Native),
394 }
395 }
396
397 fn shape(&self) -> Shape {
398 macro_rules! get_shape {
400 ($($variant:ident),*) => {
401 match self {
402 $(NdArrayTensor::$variant(storage) => Shape::from(storage.shape().to_vec()),)*
403 }
404 };
405 }
406 get_shape!(F64, F32, I64, I32, I16, I8, U64, U32, U16, U8, Bool)
407 }
408
409 fn rank(&self) -> usize {
410 self.shape().num_dims()
411 }
412
413 fn device(&self) -> NdArrayDevice {
414 NdArrayDevice::Cpu
415 }
416
417 fn can_mut(&self) -> bool {
418 false
422 }
423}
424
425pub(crate) trait ShapeOps {
426 fn num_dims(self) -> usize;
427 fn num_elements(self) -> usize;
428 fn dims<const N: usize>(self) -> [usize; N];
429 fn into_shape(self) -> Shape;
430}
431
432impl ShapeOps for &[usize] {
433 fn num_dims(self) -> usize {
434 self.len()
435 }
436
437 fn num_elements(self) -> usize {
438 self.iter().product()
439 }
440
441 fn dims<const N: usize>(self) -> [usize; N] {
442 self.try_into().unwrap()
443 }
444
445 fn into_shape(self) -> Shape {
446 Shape::from(self)
447 }
448}
449
450mod utils {
451 use burn_std::tensor::is_contiguous;
452
453 use super::*;
454
455 impl NdArrayTensor {
456 pub(crate) fn into_data(self) -> TensorData {
457 let shape = self.shape();
458 let contiguous = self.is_contiguous();
459
460 fn inner<E: Element>(
461 shape: Shape,
462 is_contiguous: bool,
463 array: ArcArray<E, IxDyn>,
464 ) -> TensorData {
465 let vec = if is_contiguous {
466 match array.try_into_owned_nocopy() {
467 Ok(owned) => {
468 let (mut vec, offset) = owned.into_raw_vec_and_offset();
469 if let Some(offset) = offset {
470 vec.drain(..offset);
471 }
472 if vec.len() > shape.num_elements() {
473 vec.drain(shape.num_elements()..vec.len());
474 }
475 vec
476 }
477 Err(array) => array.into_iter().collect(),
478 }
479 } else {
480 array.into_iter().collect()
481 };
482
483 TensorData::new(vec, shape)
484 }
485
486 execute_with_dtype!(self, |arr| inner(shape, contiguous, arr))
488 }
489
490 pub(crate) fn is_contiguous(&self) -> bool {
491 macro_rules! check_contiguous {
494 ($($variant:ident),*) => {
495 match self {
496 $(NdArrayTensor::$variant(storage) => {
497 match storage {
498 NdArrayStorage::Borrowed { .. } => {
499 true
502 }
503 NdArrayStorage::Owned(array) => {
504 let shape = array.shape();
505 let mut strides = Vec::with_capacity(array.strides().len());
506 for &stride in array.strides() {
507 if stride <= 0 {
508 return false;
509 }
510 strides.push(stride as usize);
511 }
512 is_contiguous(shape, &strides)
513 }
514 }
515 })*
516 }
517 };
518 }
519 check_contiguous!(F64, F32, I64, I32, I16, I8, U64, U32, U16, U8, Bool)
520 }
521 }
522}
523
524#[macro_export(local_inner_macros)]
526macro_rules! to_typed_dims {
527 (
528 $n:expr,
529 $dims:expr,
530 justdim
531 ) => {{
532 let mut dims = [0; $n];
533 for i in 0..$n {
534 dims[i] = $dims[i];
535 }
536 let dim: Dim<[usize; $n]> = Dim(dims);
537 dim
538 }};
539}
540
541#[macro_export(local_inner_macros)]
543macro_rules! reshape {
544 (
545 ty $ty:ty,
546 n $n:expr,
547 shape $shape:expr,
548 array $array:expr
549 ) => {{
550 let dim = $crate::to_typed_dims!($n, $shape, justdim);
551 let array = match $array.is_standard_layout() {
552 true => {
558 match $array.into_shape_with_order(dim) {
559 Ok(val) => val,
560 Err(err) => {
561 core::panic!("Shape should be compatible shape={dim:?}: {err:?}");
562 }
563 }
564 },
565 false => $array.to_shape(dim).unwrap().as_standard_layout().into_shared(),
566 };
567 array.into_dyn()
568 }};
569 (
570 ty $ty:ty,
571 shape $shape:expr,
572 array $array:expr,
573 d $D:expr
574 ) => {{
575 match $D {
576 1 => reshape!(ty $ty, n 1, shape $shape, array $array),
577 2 => reshape!(ty $ty, n 2, shape $shape, array $array),
578 3 => reshape!(ty $ty, n 3, shape $shape, array $array),
579 4 => reshape!(ty $ty, n 4, shape $shape, array $array),
580 5 => reshape!(ty $ty, n 5, shape $shape, array $array),
581 6 => reshape!(ty $ty, n 6, shape $shape, array $array),
582 _ => core::panic!("NdArray supports arrays up to 6 dimensions, received: {}", $D),
583 }
584 }};
585}
586
587#[macro_export]
589macro_rules! slice {
590 ($tensor:expr, $slices:expr) => {
591 slice!($tensor, $slices, F64, F32, I64, I32, I16, I8, U64, U32, U16, U8, Bool)
592 };
593 ($tensor:expr, $slices:expr, $($variant:ident),*) => {
594 match $tensor {
595 $(NdArrayTensor::$variant(s) => { NdArrayOps::slice(s.view(), $slices).into() })*
596 }
597 };
598}
599
600impl NdArrayTensor {
601 pub fn from_data(data: TensorData) -> NdArrayTensor {
611 if data.bytes.property() != AllocationProperty::Native {
616 match Self::try_from_data_borrowed(data) {
617 Ok(tensor) => return tensor,
618 Err(data) => return Self::from_data_owned(data),
619 }
620 }
621 Self::from_data_owned(data)
622 }
623
624 fn try_from_data_borrowed(data: TensorData) -> Result<NdArrayTensor, TensorData> {
631 let TensorData {
632 bytes,
633 shape,
634 dtype,
635 } = data;
636
637 macro_rules! try_borrow {
638 ($ty:ty, $variant:ident, $bytes:expr, $shape:expr) => {
639 match NdArrayStorage::<$ty>::from_borrowed($bytes, $shape) {
640 Ok(storage) => return Ok(NdArrayTensor::$variant(storage)),
641 Err((bytes, shape)) => (bytes, shape),
642 }
643 };
644 }
645
646 let (bytes, shape) = match dtype {
648 DType::F64 => try_borrow!(f64, F64, bytes, shape),
649 DType::F32 => try_borrow!(f32, F32, bytes, shape),
650 DType::I64 => try_borrow!(i64, I64, bytes, shape),
651 DType::I32 => try_borrow!(i32, I32, bytes, shape),
652 DType::I16 => try_borrow!(i16, I16, bytes, shape),
653 DType::I8 => try_borrow!(i8, I8, bytes, shape),
654 DType::U64 => try_borrow!(u64, U64, bytes, shape),
655 DType::U32 => try_borrow!(u32, U32, bytes, shape),
656 DType::U16 => try_borrow!(u16, U16, bytes, shape),
657 DType::U8 => try_borrow!(u8, U8, bytes, shape),
658 DType::Bool(BoolStore::Native) => try_borrow!(bool, Bool, bytes, shape),
659 _ => (bytes, shape), };
661
662 Err(TensorData {
663 bytes,
664 shape,
665 dtype,
666 })
667 }
668
669 fn from_data_owned(data: TensorData) -> NdArrayTensor {
675 let shape = data.shape.to_vec(); macro_rules! execute {
678 ($data: expr, [$($dtype: pat => $ty: ty),*]) => {
679 match $data.dtype {
680 $( $dtype => {
681 match data.into_vec::<$ty>() {
682 Ok(vec) => unsafe { ArrayD::from_shape_vec_unchecked(shape, vec) }.into_shared(),
683 Err(err) => panic!("Data should have the same element type as the tensor {err:?}"),
684 }.into()
685 }, )*
686 other => unimplemented!("Unsupported dtype {other:?}"),
687 }
688 };
689 }
690
691 execute!(data, [
692 DType::F64 => f64, DType::F32 => f32,
693 DType::I64 => i64, DType::I32 => i32, DType::I16 => i16, DType::I8 => i8,
694 DType::U64 => u64, DType::U32 => u32, DType::U16 => u16, DType::U8 => u8,
695 DType::Bool(BoolStore::Native) => bool
696 ])
697 }
698}
699
700#[derive(Clone, Debug)]
702pub struct NdArrayQTensor {
703 pub qtensor: NdArrayTensor,
705 pub scheme: QuantScheme,
707 pub qparams: Vec<QParams<f32>>,
709}
710
711impl NdArrayQTensor {
712 pub fn strategy(&self) -> QuantizationStrategy {
714 match self.scheme {
715 QuantScheme {
716 level: QuantLevel::Tensor,
717 mode: QuantMode::Symmetric,
718 value:
719 QuantValue::Q8F
720 | QuantValue::Q8S
721 | QuantValue::E4M3
722 | QuantValue::E5M2
723 | QuantValue::Q4F
724 | QuantValue::Q4S
725 | QuantValue::E2M1
726 | QuantValue::Q2F
727 | QuantValue::Q2S,
728 ..
729 } => QuantizationStrategy::PerTensorSymmetric(SymmetricQuantization::init(
730 self.qparams[0].scales,
731 self.scheme.value,
732 )),
733 QuantScheme {
734 level: QuantLevel::Block(block_size),
735 mode: QuantMode::Symmetric,
736 value:
737 QuantValue::Q8F
738 | QuantValue::Q8S
739 | QuantValue::E4M3
740 | QuantValue::E5M2
741 | QuantValue::Q4F
742 | QuantValue::Q4S
743 | QuantValue::E2M1
744 | QuantValue::Q2F
745 | QuantValue::Q2S,
746 ..
747 } => QuantizationStrategy::PerBlockSymmetric(
748 self.qparams
749 .iter()
750 .map(|q| SymmetricQuantization::init(q.scales, self.scheme.value))
751 .collect(),
752 block_size,
753 ),
754 }
755 }
756}
757
758impl TensorMetadata for NdArrayQTensor {
759 type Device = NdArrayDevice;
760 fn dtype(&self) -> DType {
761 DType::QFloat(self.scheme)
762 }
763
764 fn shape(&self) -> Shape {
765 self.qtensor.shape()
766 }
767
768 fn rank(&self) -> usize {
769 self.shape().num_dims()
770 }
771
772 fn device(&self) -> Self::Device {
773 NdArrayDevice::Cpu
774 }
775
776 fn can_mut(&self) -> bool {
777 self.qtensor.can_mut()
778 }
779}
780
781#[cfg(test)]
782mod tests {
783 use crate::NdArray;
784 use alloc::vec;
785
786 use super::*;
787 use burn_backend::{
788 Distribution,
789 ops::{FloatTensorOps, QTensorOps},
790 quantization::{QuantStore, QuantizationParametersPrimitive},
791 };
792 use burn_std::rand::get_seeded_rng;
793
794 #[test]
795 fn should_support_into_and_from_data_1d() {
796 let data_expected = TensorData::random::<f32, _, _>(
797 Shape::new([3]),
798 Distribution::Default,
799 &mut get_seeded_rng(),
800 );
801 let tensor = NdArrayTensor::from_data(data_expected.clone());
802
803 let data_actual = tensor.into_data();
804
805 assert_eq!(data_expected, data_actual);
806 }
807
808 #[test]
809 fn should_support_into_and_from_data_2d() {
810 let data_expected = TensorData::random::<f32, _, _>(
811 Shape::new([2, 3]),
812 Distribution::Default,
813 &mut get_seeded_rng(),
814 );
815 let tensor = NdArrayTensor::from_data(data_expected.clone());
816
817 let data_actual = tensor.into_data();
818
819 assert_eq!(data_expected, data_actual);
820 }
821
822 #[test]
823 fn should_support_into_and_from_data_3d() {
824 let data_expected = TensorData::random::<f32, _, _>(
825 Shape::new([2, 3, 4]),
826 Distribution::Default,
827 &mut get_seeded_rng(),
828 );
829 let tensor = NdArrayTensor::from_data(data_expected.clone());
830
831 let data_actual = tensor.into_data();
832
833 assert_eq!(data_expected, data_actual);
834 }
835
836 #[test]
837 fn should_support_into_and_from_data_4d() {
838 let data_expected = TensorData::random::<f32, _, _>(
839 Shape::new([2, 3, 4, 2]),
840 Distribution::Default,
841 &mut get_seeded_rng(),
842 );
843 let tensor = NdArrayTensor::from_data(data_expected.clone());
844
845 let data_actual = tensor.into_data();
846
847 assert_eq!(data_expected, data_actual);
848 }
849
850 #[test]
851 fn should_support_qtensor_strategy() {
852 type B = NdArray;
853 let scale: f32 = 0.009_019_608;
854 let device = Default::default();
855
856 let tensor = B::float_from_data(TensorData::from([-1.8f32, -1.0, 0.0, 0.5]), &device);
857 let scheme = QuantScheme::default()
858 .with_value(QuantValue::Q8S)
859 .with_store(QuantStore::Native);
860 let qparams = QuantizationParametersPrimitive {
861 scales: B::float_from_data(TensorData::from([scale]), &device),
862 };
863 let qtensor: NdArrayQTensor = B::quantize(tensor, &scheme, qparams);
864
865 assert_eq!(qtensor.scheme(), scheme);
866 assert_eq!(
867 qtensor.strategy(),
868 QuantizationStrategy::PerTensorSymmetric(SymmetricQuantization::init(
869 scale,
870 QuantValue::Q8S
871 ))
872 );
873 }
874
875 #[test]
881 fn zero_copy_creates_borrowed_storage_for_non_native() {
882 use burn_backend::AllocationProperty;
886 use burn_std::Bytes;
887
888 let data: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
889 let bytes = Bytes::from_elems(data);
890 let non_native_bytes = Bytes::from_shared(
892 bytes::Bytes::copy_from_slice(&bytes),
893 AllocationProperty::Other,
894 );
895 let tensor_data = TensorData::from_bytes(non_native_bytes, Shape::new([2, 2]), DType::F32);
896
897 let tensor = NdArrayTensor::from_data(tensor_data);
898
899 match &tensor {
900 NdArrayTensor::F32(storage) => {
901 assert!(
902 storage.is_borrowed(),
903 "ZERO-COPY REGRESSION: from_data should create borrowed storage \
904 for non-native (e.g. burnpack) TensorData"
905 );
906 assert!(
907 !storage.is_unique(),
908 "ZERO-COPY REGRESSION: borrowed storage must report is_unique() == false"
909 );
910 }
911 _ => panic!("Expected F32 tensor"),
912 }
913 }
914
915 #[test]
916 fn native_alloc_creates_owned_storage() {
917 use burn_std::Bytes;
919
920 let data: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
921 let bytes = Bytes::from_elems(data); let tensor_data = TensorData::from_bytes(bytes, Shape::new([2, 2]), DType::F32);
923
924 let tensor = NdArrayTensor::from_data(tensor_data);
925
926 match &tensor {
927 NdArrayTensor::F32(storage) => {
928 assert!(
929 !storage.is_borrowed(),
930 "PERF REGRESSION: from_data must NOT create borrowed storage \
931 for native TensorData"
932 );
933 }
934 _ => panic!("Expected F32 tensor"),
935 }
936 }
937
938 #[test]
939 fn zero_copy_data_integrity() {
940 use burn_std::Bytes;
942
943 let data: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0];
944 let bytes = Bytes::from_elems(data);
945 let tensor_data = TensorData::from_bytes(bytes, Shape::new([2, 2]), DType::F32);
946
947 let tensor = NdArrayTensor::from_data(tensor_data);
948
949 match &tensor {
950 NdArrayTensor::F32(storage) => {
951 let view = storage.view();
952 assert_eq!(view[[0, 0]], 1.0);
953 assert_eq!(view[[0, 1]], 2.0);
954 assert_eq!(view[[1, 0]], 3.0);
955 assert_eq!(view[[1, 1]], 4.0);
956 }
957 _ => panic!("Expected F32 tensor"),
958 }
959 }
960
961 #[test]
962 fn zero_copy_fallback_when_bytes_owned() {
963 let data = TensorData::from([1.0f32, 2.0, 3.0, 4.0]);
966 let tensor = NdArrayTensor::from_data(data.clone());
967 let result = tensor.into_data();
968
969 assert_eq!(data, result, "Data should round-trip correctly");
970 }
971}