1use alloc::vec::Vec;
2use core::fmt;
3
4use burn_backend::{DType, Element, TensorData, TensorMetadata};
5use burn_std::sync::Arc;
6use burn_std::{Bytes, Shape, bf16, f16};
7
8use crate::{FlexDevice, layout::Layout};
9
10#[derive(Clone)]
15pub struct FlexTensor {
16 data: Arc<Bytes>,
18 layout: Layout,
20 dtype: DType,
22}
23
24impl fmt::Debug for FlexTensor {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 f.debug_struct("FlexTensor")
27 .field("shape", self.layout.shape())
28 .field("dtype", &self.dtype)
29 .field("contiguous", &self.layout.is_contiguous())
30 .field("unique", &self.is_unique())
31 .finish()
32 }
33}
34
35impl FlexTensor {
36 pub fn new(data: Bytes, layout: Layout, dtype: DType) -> Self {
38 Self {
39 data: Arc::new(data),
40 layout,
41 dtype,
42 }
43 }
44
45 pub fn from_data(data: TensorData) -> Self {
47 let (bytes, shape, dtype) = data.into_parts();
48 let layout = Layout::contiguous(shape);
49 Self {
50 data: Arc::new(bytes),
51 layout,
52 dtype,
53 }
54 }
55
56 pub fn into_data(self) -> TensorData {
60 if self.layout.is_contiguous() && self.layout.start_offset() == 0 {
61 let expected_bytes = self.layout.num_elements() * dtype_size(self.dtype);
62 assert!(
63 expected_bytes <= self.data.len(),
64 "into_data: buffer ({} bytes) too small for {} elements of {:?}",
65 self.data.len(),
66 self.layout.num_elements(),
67 self.dtype
68 );
69 let bytes = if self.data.len() == expected_bytes {
70 match Arc::try_unwrap(self.data) {
72 Ok(bytes) => bytes,
73 Err(arc) => Bytes::from_bytes_vec((*arc)[..expected_bytes].to_vec()),
74 }
75 } else {
76 Bytes::from_bytes_vec(self.data[..expected_bytes].to_vec())
79 };
80 TensorData::from_bytes(bytes, self.layout.shape().clone(), self.dtype)
81 } else {
82 self.to_contiguous().into_data()
84 }
85 }
86
87 #[inline]
91 pub fn is_unique(&self) -> bool {
92 Arc::strong_count(&self.data) == 1
93 }
94
95 pub fn layout(&self) -> &Layout {
97 &self.layout
98 }
99
100 pub fn with_layout(self, layout: Layout) -> Self {
104 Self {
105 data: self.data,
106 layout,
107 dtype: self.dtype,
108 }
109 }
110
111 pub fn dtype(&self) -> DType {
113 self.dtype
114 }
115
116 pub fn is_contiguous(&self) -> bool {
118 self.layout.is_contiguous()
119 }
120
121 pub fn bytes(&self) -> &[u8] {
123 &self.data
124 }
125
126 pub fn data_arc(&self) -> Arc<Bytes> {
130 Arc::clone(&self.data)
131 }
132
133 pub fn from_arc(data: Arc<Bytes>, layout: Layout, dtype: DType) -> Self {
137 Self {
138 data,
139 layout,
140 dtype,
141 }
142 }
143
144 pub fn storage<E: Element + bytemuck::Pod>(&self) -> &[E] {
154 assert!(
155 E::dtype() == self.dtype
156 || (matches!(
157 self.dtype,
158 DType::Bool(burn_std::BoolStore::Native | burn_std::BoolStore::U8)
159 ) && E::dtype() == DType::U8),
160 "storage: dtype mismatch (expected {:?}, got {:?})",
161 self.dtype,
162 E::dtype()
163 );
164 bytemuck::cast_slice(&self.data)
165 }
166
167 pub fn storage_mut<E: Element + bytemuck::Pod>(&mut self) -> &mut [E] {
178 assert!(
179 E::dtype() == self.dtype
180 || (matches!(
181 self.dtype,
182 DType::Bool(burn_std::BoolStore::Native | burn_std::BoolStore::U8)
183 ) && E::dtype() == DType::U8),
184 "storage_mut: dtype mismatch (expected {:?}, got {:?})",
185 self.dtype,
186 E::dtype()
187 );
188 let bytes = Arc::make_mut(&mut self.data);
190 bytemuck::cast_slice_mut(bytes)
191 }
192
193 pub fn try_storage_mut<E: Element + bytemuck::Pod>(&mut self) -> Option<&mut [E]> {
200 assert!(
201 E::dtype() == self.dtype
202 || (matches!(
203 self.dtype,
204 DType::Bool(burn_std::BoolStore::Native | burn_std::BoolStore::U8)
205 ) && E::dtype() == DType::U8),
206 "try_storage_mut: dtype mismatch (expected {:?}, got {:?})",
207 self.dtype,
208 E::dtype()
209 );
210 if self.is_unique() {
211 let bytes = Arc::get_mut(&mut self.data)?;
213 Some(bytemuck::cast_slice_mut(bytes))
214 } else {
215 None
216 }
217 }
218
219 pub fn as_slice<E: Element + bytemuck::Pod>(&self) -> Option<&[E]> {
223 if E::dtype() != self.dtype {
224 return None;
225 }
226 let storage: &[E] = self.storage();
227 self.layout
228 .contiguous_offsets()
229 .map(|(start, end)| &storage[start..end])
230 }
231
232 pub fn empty(shape: Shape, dtype: DType) -> Self {
234 let num_elements = shape.num_elements();
235 let elem_size = dtype_size(dtype);
236 let bytes = Bytes::from_bytes_vec(alloc::vec![0u8; num_elements * elem_size]);
237 let layout = Layout::contiguous(shape);
238 Self {
239 data: Arc::new(bytes),
240 layout,
241 dtype,
242 }
243 }
244
245 pub fn zeros(shape: Shape, dtype: DType) -> Self {
247 Self::empty(shape, dtype)
248 }
249
250 pub fn filled_typed<E: bytemuck::Pod + Send + Sync>(
252 shape: Shape,
253 dtype: DType,
254 value: E,
255 ) -> Self {
256 assert_eq!(
257 dtype_size(dtype),
258 core::mem::size_of::<E>(),
259 "filled_typed: dtype size mismatch"
260 );
261 let n = shape.num_elements();
262 let data = alloc::vec![value; n];
263 let bytes = Bytes::from_elems(data);
264 Self {
265 data: Arc::new(bytes),
266 layout: Layout::contiguous(shape),
267 dtype,
268 }
269 }
270
271 pub(crate) fn into_contiguous(self) -> Self {
278 if self.is_contiguous()
284 && self.layout.start_offset() == 0
285 && self.data.len() == self.layout.num_elements() * dtype_size(self.dtype)
286 {
287 return self;
288 }
289
290 match self.dtype {
292 DType::F64 => self.copy_contiguous::<f64>(),
293 DType::F32 => self.copy_contiguous::<f32>(),
294 DType::F16 => self.copy_contiguous::<f16>(),
295 DType::BF16 => self.copy_contiguous::<bf16>(),
296 DType::I64 => self.copy_contiguous::<i64>(),
297 DType::I32 => self.copy_contiguous::<i32>(),
298 DType::I16 => self.copy_contiguous::<i16>(),
299 DType::I8 => self.copy_contiguous::<i8>(),
300 DType::U64 => self.copy_contiguous::<u64>(),
301 DType::U32 => self.copy_contiguous::<u32>(),
302 DType::U16 => self.copy_contiguous::<u16>(),
303 DType::U8 => self.copy_contiguous::<u8>(),
304 DType::Bool(burn_std::BoolStore::Native | burn_std::BoolStore::U8) => {
305 self.copy_contiguous::<u8>()
306 }
307 DType::Bool(burn_std::BoolStore::U32) => {
308 panic!("burn-flex: Bool(U32) storage is not yet supported")
309 }
310 _ => panic!("Unsupported dtype for contiguous copy: {:?}", self.dtype),
311 }
312 }
313
314 pub fn to_contiguous(&self) -> Self {
316 self.clone().into_contiguous()
317 }
318
319 fn copy_contiguous<E: Element + bytemuck::Pod>(&self) -> Self {
320 let src: &[E] = bytemuck::cast_slice(&self.data);
321 let n = self.layout.num_elements();
322 let mut dst = Vec::with_capacity(n);
323
324 let collapsed = collapse_for_copy(self.layout.shape(), self.layout.strides());
330 let (shape, strides) = collapsed.as_slices();
331 let offset = self.layout.start_offset() as isize;
332 let all_positive = strides.iter().all(|&s| s >= 0);
333
334 if n == 0 {
335 return Self::empty(self.layout.shape().clone(), self.dtype);
336 }
337
338 if shape.len() <= 1 {
339 let collapsed_numel = if shape.is_empty() { 1 } else { shape[0] };
341 debug_assert_eq!(n, collapsed_numel);
342 unsafe { dst.set_len(n) };
344 if shape.is_empty() {
345 if n > 0 {
346 dst[0] = src[offset as usize];
347 }
348 } else {
349 let len = shape[0];
350 let stride = strides[0];
351 if stride == 1 {
352 dst[..len].copy_from_slice(&src[offset as usize..offset as usize + len]);
353 } else if stride == -1 {
354 let start = (offset - (len as isize - 1)) as usize;
355 let src_slice = &src[start..start + len];
356 for (slot, &val) in dst[..len].iter_mut().zip(src_slice.iter().rev()) {
357 *slot = val;
358 }
359 } else {
360 for (i, slot) in dst.iter_mut().take(len).enumerate() {
361 let idx = (offset + i as isize * stride) as usize;
362 *slot = src[idx];
363 }
364 }
365 }
366 } else if shape.len() == 2 && all_positive {
367 debug_assert_eq!(shape[0] * shape[1], n, "2D strides must cover all elements");
372 unsafe { dst.set_len(n) };
375 copy_2d_tiled(
376 &mut dst, src, offset, shape[0], shape[1], strides[0], strides[1],
377 );
378 } else if shape.len() == 2 && !all_positive {
379 debug_assert_eq!(shape[0] * shape[1], n);
381 unsafe { dst.set_len(n) };
382 copy_2d_negative(
383 &mut dst, src, offset, shape[0], shape[1], strides[0], strides[1],
384 );
385 } else if shape.len() >= 3 && all_positive && is_transposed_2d(shape, strides) {
386 unsafe { dst.set_len(n) };
388 copy_batched_2d_tiled(&mut dst, src, offset, shape, strides);
389 } else if all_positive && strides.last().copied() == Some(1) {
390 unsafe { dst.set_len(n) };
393 copy_inner_contiguous_run(&mut dst, src, offset, shape, strides);
394 } else {
395 for idx in crate::strided_index::StridedIter::new(&self.layout) {
398 dst.push(src[idx]);
399 }
400 }
401
402 let bytes = Bytes::from_elems(dst);
403 let layout = Layout::contiguous(self.layout.shape().clone());
404 Self {
405 data: Arc::new(bytes),
406 layout,
407 dtype: self.dtype,
408 }
409 }
410
411 pub fn reshape(&self, new_shape: Shape) -> Self {
413 assert_eq!(
414 self.layout.num_elements(),
415 new_shape.num_elements(),
416 "reshape must preserve total elements"
417 );
418
419 if let Some(new_layout) = self.layout.reshape(new_shape.clone()) {
420 Self {
421 data: Arc::clone(&self.data),
422 layout: new_layout,
423 dtype: self.dtype,
424 }
425 } else {
426 self.to_contiguous().reshape(new_shape)
428 }
429 }
430
431 pub fn transpose(&self, dim1: usize, dim2: usize) -> Self {
433 Self {
434 data: Arc::clone(&self.data),
435 layout: self.layout.transpose(dim1, dim2),
436 dtype: self.dtype,
437 }
438 }
439
440 pub fn narrow(&self, dim: usize, start: usize, len: usize) -> Self {
442 Self {
443 data: Arc::clone(&self.data),
444 layout: self.layout.narrow(dim, start, len),
445 dtype: self.dtype,
446 }
447 }
448
449 pub fn permute(&self, axes: &[usize]) -> Self {
451 Self {
452 data: Arc::clone(&self.data),
453 layout: self.layout.permute(axes),
454 dtype: self.dtype,
455 }
456 }
457}
458
459impl TensorMetadata for FlexTensor {
460 type Device = FlexDevice;
461
462 fn dtype(&self) -> DType {
463 self.dtype
464 }
465
466 fn shape(&self) -> Shape {
467 self.layout.shape().clone()
468 }
469
470 fn rank(&self) -> usize {
471 self.layout.num_dims()
472 }
473
474 fn device(&self) -> Self::Device {
475 FlexDevice
476 }
477
478 fn can_mut(&self) -> bool {
479 self.is_unique()
480 }
481}
482
483const COLLAPSE_MAX_RANK: usize = 8;
486
487#[derive(Debug, Clone, Copy)]
491struct CollapsedLayout {
492 ndim: usize,
493 shape: [usize; COLLAPSE_MAX_RANK],
494 strides: [isize; COLLAPSE_MAX_RANK],
495}
496
497impl CollapsedLayout {
498 #[inline]
499 fn as_slices(&self) -> (&[usize], &[isize]) {
500 (&self.shape[..self.ndim], &self.strides[..self.ndim])
501 }
502}
503
504fn collapse_for_copy(shape: &[usize], strides: &[isize]) -> CollapsedLayout {
528 let mut out = CollapsedLayout {
529 ndim: 0,
530 shape: [0; COLLAPSE_MAX_RANK],
531 strides: [0; COLLAPSE_MAX_RANK],
532 };
533
534 if shape.len() > COLLAPSE_MAX_RANK {
539 out.ndim = shape.len().min(COLLAPSE_MAX_RANK);
540 return out;
541 }
542
543 for (&s, &st) in shape.iter().zip(strides.iter()) {
553 if s == 1 {
554 continue;
555 }
556 let merge = out.ndim > 0
557 && st > 0
558 && out.strides[out.ndim - 1] > 0
559 && (s as isize)
560 .checked_mul(st)
561 .is_some_and(|run| out.strides[out.ndim - 1] == run);
562 if merge {
563 out.shape[out.ndim - 1] *= s;
564 out.strides[out.ndim - 1] = st;
565 } else {
566 out.shape[out.ndim] = s;
567 out.strides[out.ndim] = st;
568 out.ndim += 1;
569 }
570 }
571
572 out
573}
574
575#[inline]
576fn is_transposed_2d(shape: &[usize], strides: &[isize]) -> bool {
577 let rank = shape.len();
578 if rank < 3 {
579 return false;
580 }
581 let r_st = strides[rank - 2];
582 let c_st = strides[rank - 1];
583 (r_st == 1 && c_st > 1) || (c_st == 1 && r_st > 1 && r_st != shape[rank - 1] as isize)
584}
585
586fn copy_batched_2d_tiled<E: Copy + Send + Sync>(
587 dst: &mut [E],
588 src: &[E],
589 offset: isize,
590 shape: &[usize],
591 strides: &[isize],
592) {
593 let rank = shape.len();
594 let rows = shape[rank - 2];
595 let cols = shape[rank - 1];
596 let r_st = strides[rank - 2];
597 let c_st = strides[rank - 1];
598 let slice_len = rows * cols;
599
600 let batch_shape = &shape[..rank - 2];
601 let batch_strides = &strides[..rank - 2];
602 #[cfg(feature = "rayon")]
603 {
604 let batch_count: usize = batch_shape.iter().product();
605 if batch_count * slice_len >= crate::ops::PARALLEL_THRESHOLD && batch_count > 1 {
606 use rayon::prelude::*;
607 dst.par_chunks_mut(slice_len)
608 .enumerate()
609 .for_each(|(b, slice_dst)| {
610 let mut remaining = b;
611 let mut batch_offset = offset;
612 for d in (0..batch_shape.len()).rev() {
613 let coord = remaining % batch_shape[d];
614 remaining /= batch_shape[d];
615 batch_offset += coord as isize * batch_strides[d];
616 }
617 copy_2d_tiled(slice_dst, src, batch_offset, rows, cols, r_st, c_st);
618 });
619 return;
620 }
621 }
622
623 for (b, slice_dst) in dst.chunks_mut(slice_len).enumerate() {
624 let mut remaining = b;
625 let mut batch_offset = offset;
626 for d in (0..batch_shape.len()).rev() {
627 let coord = remaining % batch_shape[d];
628 remaining /= batch_shape[d];
629 batch_offset += coord as isize * batch_strides[d];
630 }
631 copy_2d_tiled(slice_dst, src, batch_offset, rows, cols, r_st, c_st);
632 }
633}
634
635fn copy_2d_negative<E: Copy + Send + Sync>(
636 dst: &mut [E],
637 src: &[E],
638 offset: isize,
639 rows: usize,
640 cols: usize,
641 row_stride: isize,
642 col_stride: isize,
643) {
644 #[cfg(feature = "rayon")]
645 let n = rows * cols;
646 if row_stride < 0 && col_stride == 1 {
647 #[cfg(feature = "rayon")]
648 if n >= crate::ops::PARALLEL_THRESHOLD {
649 use rayon::prelude::*;
650 dst.par_chunks_mut(cols)
651 .enumerate()
652 .for_each(|(r, dst_row)| {
653 let row_src = (offset + r as isize * row_stride) as usize;
654 dst_row.copy_from_slice(&src[row_src..row_src + cols]);
655 });
656 return;
657 }
658 for r in 0..rows {
659 let row_src = (offset + r as isize * row_stride) as usize;
660 let dst_start = r * cols;
661 dst[dst_start..dst_start + cols].copy_from_slice(&src[row_src..row_src + cols]);
662 }
663 } else if col_stride == -1 {
664 #[cfg(feature = "rayon")]
665 if n >= crate::ops::PARALLEL_THRESHOLD {
666 use rayon::prelude::*;
667 dst.par_chunks_mut(cols)
668 .enumerate()
669 .for_each(|(r, dst_row)| {
670 let row_src_end = (offset + r as isize * row_stride) as usize;
671 let row_src_start = row_src_end + 1 - cols;
672 let src_slice = &src[row_src_start..=row_src_end];
673 for (slot, &val) in dst_row.iter_mut().zip(src_slice.iter().rev()) {
674 *slot = val;
675 }
676 });
677 return;
678 }
679 for r in 0..rows {
680 let row_src_end = (offset + r as isize * row_stride) as usize;
681 let row_src_start = row_src_end + 1 - cols;
682 let src_slice = &src[row_src_start..=row_src_end];
683 let dst_row = &mut dst[r * cols..(r + 1) * cols];
684 for (slot, &val) in dst_row.iter_mut().zip(src_slice.iter().rev()) {
685 *slot = val;
686 }
687 }
688 } else {
689 for r in 0..rows {
690 let row_base = offset + r as isize * row_stride;
691 let dst_row = &mut dst[r * cols..(r + 1) * cols];
692 for (c, slot) in dst_row.iter_mut().enumerate() {
693 let idx = (row_base + c as isize * col_stride) as usize;
694 *slot = src[idx];
695 }
696 }
697 }
698}
699
700#[inline]
705fn copy_inner_contiguous_run<E: Copy>(
706 dst: &mut [E],
707 src: &[E],
708 offset: isize,
709 shape: &[usize],
710 strides: &[isize],
711) {
712 debug_assert!(!shape.is_empty());
713 debug_assert_eq!(*strides.last().unwrap(), 1, "innermost stride must be 1");
714
715 let mut expected_stride: isize = 1;
719 let mut split = shape.len(); while split > 0 {
721 let dim_idx = split - 1;
722 if strides[dim_idx] == expected_stride {
723 expected_stride = expected_stride
724 .checked_mul(shape[dim_idx] as isize)
725 .expect("contiguous run length overflows isize");
726 split -= 1;
727 } else {
728 break;
729 }
730 }
731
732 let outer_shape = &shape[..split];
733 let outer_strides = &strides[..split];
734 let inner_run_length: usize = shape[split..].iter().product();
735
736 if outer_shape.is_empty() {
738 let src_index = offset as usize;
739 dst[..inner_run_length].copy_from_slice(&src[src_index..src_index + inner_run_length]);
740 return;
741 }
742
743 if inner_run_length == 0 || outer_shape.contains(&0) {
744 return;
745 }
746
747 let mut counter = [0usize; COLLAPSE_MAX_RANK];
756 let outer_dim_count = outer_shape.len();
757 let mut dst_position = 0usize;
758
759 loop {
760 let mut src_start = offset;
761 for dim_idx in 0..outer_dim_count {
762 src_start += counter[dim_idx] as isize * outer_strides[dim_idx];
763 }
764 let src_index = src_start as usize;
765 dst[dst_position..dst_position + inner_run_length]
766 .copy_from_slice(&src[src_index..src_index + inner_run_length]);
767 dst_position += inner_run_length;
768
769 let mut dim_idx = outer_dim_count;
770 loop {
771 if dim_idx == 0 {
772 return;
773 }
774 dim_idx -= 1;
775 counter[dim_idx] += 1;
776 if counter[dim_idx] < outer_shape[dim_idx] {
777 break;
778 }
779 counter[dim_idx] = 0;
780 }
781 }
782}
783
784#[cfg(any(feature = "rayon", test))]
787pub(crate) const COPY_2D_PARALLEL_THRESHOLD: usize = 1_048_576;
788
789#[inline]
794fn copy_2d_tiled<E: Copy + Send + Sync>(
795 dst: &mut [E],
796 src: &[E],
797 offset: isize,
798 rows: usize,
799 cols: usize,
800 row_stride: isize,
801 col_stride: isize,
802) {
803 const TILE: usize = 16;
804
805 #[cfg(feature = "rayon")]
806 if rows * cols >= COPY_2D_PARALLEL_THRESHOLD {
807 use rayon::prelude::*;
808 let total_tile_rows = rows.div_ceil(TILE);
809 let num_threads = rayon::current_num_threads();
810 let tile_rows_per_task = total_tile_rows.div_ceil(num_threads).max(1);
811 let chunk_len = tile_rows_per_task * TILE * cols;
812
813 dst.par_chunks_mut(chunk_len)
814 .enumerate()
815 .for_each(|(task_idx, dst_chunk)| {
816 let row_start = task_idx * tile_rows_per_task * TILE;
817 let chunk_rows = dst_chunk.len() / cols;
818 if row_stride <= col_stride {
819 for col_tile in (0..cols).step_by(TILE) {
821 let col_end = (col_tile + TILE).min(cols);
822 for r_tile in (0..chunk_rows).step_by(TILE) {
823 let r_end = (r_tile + TILE).min(chunk_rows);
824 for col in col_tile..col_end {
825 let col_base = offset + col as isize * col_stride;
826 for r in r_tile..r_end {
827 let row = row_start + r;
828 let idx = (col_base + row as isize * row_stride) as usize;
829 unsafe {
832 *dst_chunk.get_unchecked_mut(r * cols + col) = src[idx];
833 }
834 }
835 }
836 }
837 }
838 } else {
839 for r_tile in (0..chunk_rows).step_by(TILE) {
841 let r_end = (r_tile + TILE).min(chunk_rows);
842 for col_tile in (0..cols).step_by(TILE) {
843 let col_end = (col_tile + TILE).min(cols);
844 for r in r_tile..r_end {
845 let row = row_start + r;
846 let row_base = offset
847 + row as isize * row_stride
848 + col_tile as isize * col_stride;
849 let dst_base = r * cols + col_tile;
850 for c in 0..(col_end - col_tile) {
851 let idx = (row_base + c as isize * col_stride) as usize;
852 unsafe {
855 *dst_chunk.get_unchecked_mut(dst_base + c) = src[idx];
856 }
857 }
858 }
859 }
860 }
861 }
862 });
863 return;
864 }
865
866 if row_stride <= col_stride {
867 for col_tile in (0..cols).step_by(TILE) {
869 let col_end = (col_tile + TILE).min(cols);
870 for row_tile in (0..rows).step_by(TILE) {
871 let row_end = (row_tile + TILE).min(rows);
872 for col in col_tile..col_end {
873 let col_base = offset + col as isize * col_stride;
874 for row in row_tile..row_end {
875 let idx = (col_base + row as isize * row_stride) as usize;
876 unsafe {
879 *dst.get_unchecked_mut(row * cols + col) = src[idx];
880 }
881 }
882 }
883 }
884 }
885 } else {
886 for row_tile in (0..rows).step_by(TILE) {
888 let row_end = (row_tile + TILE).min(rows);
889 for col_tile in (0..cols).step_by(TILE) {
890 let col_end = (col_tile + TILE).min(cols);
891 for row in row_tile..row_end {
892 let row_base =
893 offset + row as isize * row_stride + col_tile as isize * col_stride;
894 let dst_base = row * cols + col_tile;
895 for c in 0..(col_end - col_tile) {
896 let idx = (row_base + c as isize * col_stride) as usize;
897 unsafe {
900 *dst.get_unchecked_mut(dst_base + c) = src[idx];
901 }
902 }
903 }
904 }
905 }
906 }
907}
908
909pub(crate) fn dtype_size(dtype: DType) -> usize {
925 let size = dtype.size();
927 assert!(
928 size > 0,
929 "burn-flex: dtype {:?} has zero-byte element size (sub-byte packed \
930 quantization is not yet supported)",
931 dtype
932 );
933 size
934}
935
936#[cfg(test)]
937mod tests {
938 use super::*;
939 use alloc::vec;
940
941 #[test]
942 fn test_from_data_roundtrip() {
943 let data = TensorData::from([1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0]);
944 let tensor = FlexTensor::from_data(data.clone());
945 let result = tensor.into_data();
946 assert_eq!(data.shape(), result.shape());
947 assert_eq!(data.dtype(), result.dtype());
948 }
949
950 #[test]
951 fn test_collapse_for_copy_squeezes_size1_and_merges_contig() {
952 let shape = vec![1, 244, 224, 48];
954 let strides = vec![2_623_488_isize, 224, 1, 54656];
955 let collapsed = collapse_for_copy(&shape, &strides);
956 let (s, st) = collapsed.as_slices();
957 assert_eq!(s, &[54656, 48]);
958 assert_eq!(st, &[1, 54656]);
959 }
960
961 #[test]
962 fn test_collapse_for_copy_already_contiguous_3d() {
963 let collapsed = collapse_for_copy(&[2, 3, 4], &[12, 4, 1]);
964 let (s, st) = collapsed.as_slices();
965 assert_eq!(s, &[24]);
966 assert_eq!(st, &[1]);
967 }
968
969 #[test]
970 fn test_collapse_for_copy_transpose_2d() {
971 let collapsed = collapse_for_copy(&[5, 3], &[1, 5]);
972 let (s, st) = collapsed.as_slices();
973 assert_eq!(s, &[5, 3]);
974 assert_eq!(st, &[1, 5]);
975 }
976
977 #[test]
978 fn test_collapse_for_copy_all_size1() {
979 let collapsed = collapse_for_copy(&[1, 1, 1], &[0, 0, 0]);
980 let (s, st) = collapsed.as_slices();
981 assert!(s.is_empty());
982 assert!(st.is_empty());
983 }
984
985 #[test]
992 fn test_to_contiguous_zero_sized_narrowed() {
993 let t = FlexTensor::from_data(TensorData::new(
994 (0..6).map(|i| i as f32).collect::<Vec<_>>(),
995 vec![6],
996 ));
997 let empty_view = t.narrow(0, 3, 0);
999 assert_eq!(empty_view.shape().to_vec(), vec![0]);
1000 assert_ne!(empty_view.layout().start_offset(), 0);
1001
1002 let contig = empty_view.to_contiguous();
1003 assert_eq!(contig.shape().to_vec(), vec![0]);
1004 assert_eq!(contig.layout().start_offset(), 0);
1005 assert_eq!(contig.into_data().bytes().len(), 0);
1006 }
1007
1008 #[test]
1009 fn test_to_contiguous_zero_sized_negative_stride() {
1010 let t = FlexTensor::from_data(TensorData::new(
1011 (0..6).map(|i| i as f32).collect::<Vec<_>>(),
1012 vec![6],
1013 ));
1014 let empty_neg = crate::ops::slice::slice(t, &[burn_std::Slice::new(3, Some(3), -1)]);
1015 assert_eq!(empty_neg.shape().to_vec(), vec![0]);
1016 let contig = empty_neg.to_contiguous();
1017 assert_eq!(contig.shape().to_vec(), vec![0]);
1018 assert_eq!(contig.into_data().bytes().len(), 0);
1019 }
1020
1021 #[test]
1028 fn test_to_contiguous_prefix_view_shrinks_buffer() {
1029 let data: Vec<f32> = (0..40).map(|i| i as f32).collect();
1030 let t = FlexTensor::from_data(TensorData::new(data, vec![8, 5]));
1031
1032 let prefix = t.narrow(0, 0, 5);
1033 assert_eq!(prefix.shape().to_vec(), vec![5, 5]);
1034 assert_eq!(prefix.layout().strides(), &[5, 1]);
1035 assert_eq!(prefix.layout().start_offset(), 0);
1036 assert!(prefix.is_contiguous());
1037 assert_eq!(prefix.storage::<f32>().len(), 40);
1038
1039 let contig = prefix.to_contiguous();
1040 assert_eq!(contig.storage::<f32>().len(), 25);
1041 assert_eq!(contig.layout().num_elements(), 25);
1042 assert_eq!(
1043 contig.storage::<f32>(),
1044 &(0..5)
1045 .flat_map(|r| (0..5).map(move |c| (r * 5 + c) as f32))
1046 .collect::<Vec<_>>()[..]
1047 );
1048 }
1049
1050 #[test]
1053 fn test_to_contiguous_4d_permuted_matches_naive() {
1054 let dims = [1, 48, 4, 5];
1055 let n: usize = dims.iter().product();
1056 let data: Vec<f32> = (0..n).map(|i| i as f32).collect();
1057 let t = FlexTensor::from_data(TensorData::new(data.clone(), dims.to_vec()));
1058 let permuted = t.permute(&[0, 2, 3, 1]);
1059 assert!(!permuted.is_contiguous());
1060
1061 let contig = permuted.to_contiguous();
1062 assert!(contig.is_contiguous());
1063 assert_eq!(contig.shape().to_vec(), vec![1, 4, 5, 48]);
1064
1065 let mut expected = Vec::with_capacity(n);
1067 for h in 0..4 {
1068 for w in 0..5 {
1069 for c in 0..48 {
1070 let idx = c * 20 + h * 5 + w;
1071 expected.push(data[idx]);
1072 }
1073 }
1074 }
1075
1076 let result_data = contig.into_data();
1077 let values = result_data.as_slice::<f32>().unwrap();
1078 assert_eq!(values, expected.as_slice());
1079 }
1080
1081 #[test]
1086 fn test_to_contiguous_3d_inner_stride1_matches_naive() {
1087 let dims = [1, 2, 4, 3, 3]; let n: usize = dims.iter().product();
1089 let data: Vec<f32> = (0..n).map(|i| i as f32).collect();
1090 let t = FlexTensor::from_data(TensorData::new(data.clone(), dims.to_vec()));
1091 let permuted = t.permute(&[0, 2, 1, 3, 4]); assert!(!permuted.is_contiguous());
1093
1094 let contiguous_data = permuted.to_contiguous();
1095 assert!(contiguous_data.is_contiguous());
1096 assert_eq!(contiguous_data.shape().to_vec(), vec![1, 4, 2, 3, 3]);
1097
1098 let mut expected = Vec::with_capacity(n);
1101 for c in 0..4 {
1102 for g in 0..2 {
1103 for h in 0..3 {
1104 for w in 0..3 {
1105 let idx = g * 36 + c * 9 + h * 3 + w;
1106 expected.push(data[idx]);
1107 }
1108 }
1109 }
1110 }
1111
1112 let result_data = contiguous_data.into_data();
1114 let values = result_data.as_slice::<f32>().unwrap();
1115 assert_eq!(values, expected.as_slice());
1116 }
1117
1118 #[test]
1121 fn test_to_contiguous_2d_row_stride_gt_col_stride() {
1122 let data: Vec<f32> = (0..18).map(|i| i as f32).collect();
1126 let t = FlexTensor::from_data(TensorData::new(data, vec![6, 3]));
1127 let stepped = crate::ops::slice::slice(
1128 t,
1129 &[
1130 burn_std::Slice::new(0, Some(6), 2),
1131 burn_std::Slice::new(0, None, 1),
1132 ],
1133 );
1134 assert_eq!(stepped.layout().shape().to_vec(), vec![3, 3]);
1136 assert_eq!(stepped.layout().strides(), &[6, 1]);
1137 assert!(!stepped.layout().is_contiguous());
1138
1139 let contig = stepped.to_contiguous();
1140 assert!(contig.is_contiguous());
1141 assert_eq!(contig.shape().to_vec(), vec![3, 3]);
1142
1143 let result_data = contig.into_data();
1144 let values = result_data.as_slice::<f32>().unwrap();
1145 let expected = vec![
1147 0.0f32, 1.0, 2.0, 6.0, 7.0, 8.0, 12.0, 13.0, 14.0, ];
1151 assert_eq!(values, expected.as_slice());
1152 }
1153
1154 #[test]
1157 fn test_to_contiguous_2d_row_stride_lt_col_stride() {
1158 let data: Vec<f32> = (0..18).map(|i| i as f32).collect();
1159 let t = FlexTensor::from_data(TensorData::new(data, vec![3, 6]));
1160 let transposed = t.transpose(0, 1);
1161 assert_eq!(transposed.layout().strides(), &[1, 6]);
1162 assert!(!transposed.layout().is_contiguous());
1163
1164 let contig = transposed.to_contiguous();
1165 assert!(contig.is_contiguous());
1166 assert_eq!(contig.shape().to_vec(), vec![6, 3]);
1167
1168 let result_data = contig.into_data();
1169 let values = result_data.as_slice::<f32>().unwrap();
1170 let mut expected = Vec::with_capacity(18);
1171 for c in 0..6 {
1172 for r in 0..3 {
1173 expected.push((r * 6 + c) as f32);
1174 }
1175 }
1176 assert_eq!(values, expected.as_slice());
1177 }
1178
1179 #[test]
1181 fn test_to_contiguous_2d_parallel_row_stride_le_col_stride() {
1182 let rows = 1024;
1183 let cols = 1024;
1184 let n = rows * cols;
1185 assert!(n >= COPY_2D_PARALLEL_THRESHOLD);
1186
1187 let data: Vec<f32> = (0..n).map(|i| (i % 997) as f32).collect();
1188 let t = FlexTensor::from_data(TensorData::new(data.clone(), vec![rows, cols]));
1189 let transposed = t.transpose(0, 1);
1190 assert_eq!(transposed.layout().strides(), &[1, rows as isize]);
1191 assert!(!transposed.layout().is_contiguous());
1192
1193 let contig = transposed.to_contiguous();
1194 assert!(contig.is_contiguous());
1195 assert_eq!(contig.shape().to_vec(), vec![cols, rows]);
1196
1197 let result_data = contig.into_data();
1198 let values = result_data.as_slice::<f32>().unwrap();
1199 for r in 0..cols {
1200 for c in 0..rows {
1201 let expected = data[c * cols + r];
1202 assert_eq!(values[r * rows + c], expected);
1203 }
1204 }
1205 }
1206
1207 #[test]
1209 fn test_to_contiguous_2d_parallel_row_stride_gt_col_stride() {
1210 let rows = 1024;
1211 let cols = 1024;
1212 let n = rows * cols;
1213 assert!(n >= COPY_2D_PARALLEL_THRESHOLD);
1214
1215 let full_cols = 2048;
1217 let data: Vec<f32> = (0..rows * full_cols).map(|i| (i % 997) as f32).collect();
1218 let t = FlexTensor::from_data(TensorData::new(data.clone(), vec![rows, full_cols]));
1219 let sliced = crate::ops::slice::slice(
1220 t,
1221 &[
1222 burn_std::Slice::new(0, Some(rows as isize), 1),
1223 burn_std::Slice::new(0, Some(cols as isize), 1),
1224 ],
1225 );
1226 assert_eq!(sliced.layout().strides(), &[full_cols as isize, 1]);
1227 assert!(!sliced.layout().is_contiguous());
1228
1229 let contig = sliced.to_contiguous();
1230 assert!(contig.is_contiguous());
1231 assert_eq!(contig.shape().to_vec(), vec![rows, cols]);
1232
1233 let result_data = contig.into_data();
1234 let values = result_data.as_slice::<f32>().unwrap();
1235 for r in 0..rows {
1236 for c in 0..cols {
1237 let expected = data[r * full_cols + c];
1238 assert_eq!(values[r * cols + c], expected);
1239 }
1240 }
1241 }
1242
1243 #[test]
1244 fn test_reshape() {
1245 let data = TensorData::new(vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]);
1246 let tensor = FlexTensor::from_data(data);
1247 let reshaped = tensor.reshape(Shape::from(vec![3, 2]));
1248 assert_eq!(reshaped.shape().to_vec(), vec![3, 2]);
1249 }
1250
1251 #[test]
1252 fn test_transpose() {
1253 let data = TensorData::new(vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]);
1254 let tensor = FlexTensor::from_data(data);
1255 let transposed = tensor.transpose(0, 1);
1256 assert_eq!(transposed.shape().to_vec(), vec![3, 2]);
1257 assert!(!transposed.is_contiguous());
1258 }
1259
1260 #[test]
1261 fn test_clone_is_cheap() {
1262 let data = TensorData::from([1.0f32, 2.0, 3.0, 4.0]);
1263 let tensor = FlexTensor::from_data(data);
1264
1265 assert!(tensor.is_unique());
1267
1268 let cloned = tensor.clone();
1270 assert!(!tensor.is_unique());
1271 assert!(!cloned.is_unique());
1272
1273 assert!(core::ptr::eq(
1275 tensor.bytes().as_ptr(),
1276 cloned.bytes().as_ptr(),
1277 ));
1278 }
1279
1280 #[test]
1281 fn test_cow_on_mutation() {
1282 let data = TensorData::from([1.0f32, 2.0, 3.0, 4.0]);
1283 let tensor = FlexTensor::from_data(data);
1284 let mut cloned = tensor.clone();
1285
1286 assert!(!tensor.is_unique());
1288 assert!(!cloned.is_unique());
1289
1290 let storage: &mut [f32] = cloned.storage_mut();
1292 storage[0] = 99.0;
1293
1294 assert!(tensor.is_unique());
1296 assert!(cloned.is_unique());
1297
1298 assert_ne!(tensor.bytes().as_ptr(), cloned.bytes().as_ptr());
1300 assert_eq!(tensor.storage::<f32>()[0], 1.0);
1301 assert_eq!(cloned.storage::<f32>()[0], 99.0);
1302 }
1303
1304 #[test]
1305 fn test_into_data_narrowed_at_offset_zero() {
1306 let data = TensorData::new(vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0], vec![2, 3]);
1308 let tensor = FlexTensor::from_data(data);
1309 let narrowed = tensor.narrow(0, 0, 1);
1311 assert!(narrowed.is_contiguous());
1312 assert_eq!(narrowed.layout().start_offset(), 0);
1313
1314 let result = narrowed.into_data();
1315 assert_eq!(result.shape().to_vec(), vec![1, 3]);
1316 assert_eq!(result.bytes().len(), 3 * core::mem::size_of::<f32>());
1318 let values: Vec<f32> = result.try_into_vec().unwrap();
1319 assert_eq!(values, vec![1.0, 2.0, 3.0]);
1320 }
1321
1322 #[test]
1323 fn test_reshape_preserves_offset_zero_copy() {
1324 let data: Vec<f32> = (0..16).map(|i| i as f32).collect();
1325 let tensor = FlexTensor::from_data(TensorData::new(data, vec![4, 4]));
1326 let sliced = tensor.narrow(0, 1, 2);
1328 assert!(sliced.is_contiguous());
1329 assert_eq!(sliced.layout().start_offset(), 4);
1330
1331 let reshaped = sliced.reshape(Shape::from(vec![8]));
1333 assert!(reshaped.is_contiguous());
1334 assert_eq!(reshaped.layout().start_offset(), 4);
1335 assert_eq!(reshaped.shape().to_vec(), vec![8]);
1336
1337 assert!(core::ptr::eq(
1339 sliced.bytes().as_ptr(),
1340 reshaped.bytes().as_ptr()
1341 ));
1342
1343 let values: Vec<f32> = reshaped.into_data().try_into_vec().unwrap();
1344 assert_eq!(values, vec![4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0]);
1345 }
1346
1347 #[test]
1348 fn test_transpose_unit_dim_zero_copy() {
1349 let data: Vec<f32> = (0..6).map(|i| i as f32).collect();
1352 let tensor = FlexTensor::from_data(TensorData::new(data, vec![6, 1]));
1353 let transposed = tensor.transpose(0, 1);
1354 assert_eq!(transposed.shape().to_vec(), vec![1, 6]);
1355 assert_eq!(transposed.layout().strides(), &[1, 1]);
1356 assert!(transposed.is_contiguous());
1357
1358 let contig = transposed.to_contiguous();
1360 assert!(core::ptr::eq(
1361 transposed.bytes().as_ptr(),
1362 contig.bytes().as_ptr()
1363 ));
1364 }
1365
1366 #[test]
1367 fn test_batched_transpose_parity() {
1368 let data: Vec<f32> = (0..24).map(|i| i as f32).collect();
1370 let tensor = FlexTensor::from_data(TensorData::new(data, vec![2, 3, 4]));
1371 let transposed = tensor.transpose(1, 2);
1372 assert_eq!(transposed.shape().to_vec(), vec![2, 4, 3]);
1373 assert!(!transposed.is_contiguous());
1374
1375 let contig = transposed.to_contiguous();
1376 assert!(contig.is_contiguous());
1377
1378 let values: Vec<f32> = contig.into_data().try_into_vec().unwrap();
1379 let mut expected = Vec::with_capacity(24);
1381 for b in 0..2 {
1382 for j in 0..4 {
1383 for i in 0..3 {
1384 expected.push((b * 12 + i * 4 + j) as f32);
1385 }
1386 }
1387 }
1388 assert_eq!(values, expected);
1389 }
1390
1391 #[test]
1392 fn test_negative_stride_flip_acceleration() {
1393 let data: Vec<f32> = (0..12).map(|i| i as f32).collect();
1394 let tensor = FlexTensor::from_data(TensorData::new(data, vec![3, 4]));
1395 let flipped = crate::ops::slice::slice(
1397 tensor,
1398 &[
1399 burn_std::Slice::new(0, None, -1),
1400 burn_std::Slice::new(0, None, 1),
1401 ],
1402 );
1403 assert_eq!(flipped.shape().to_vec(), vec![3, 4]);
1404 assert_eq!(flipped.layout().strides(), &[-4, 1]);
1405 assert!(!flipped.is_contiguous());
1406
1407 let contig = flipped.to_contiguous();
1408 assert!(contig.is_contiguous());
1409
1410 let values: Vec<f32> = contig.into_data().try_into_vec().unwrap();
1411 let expected = vec![
1412 8.0, 9.0, 10.0, 11.0, 4.0, 5.0, 6.0, 7.0, 0.0, 1.0, 2.0, 3.0, ];
1416 assert_eq!(values, expected);
1417 }
1418}