Skip to main content

tract_data/
tensor.rs

1//! `Tensor`, tract main data object of interest.
2use crate::TVec;
3use crate::blob::Blob;
4use crate::datum::{ClampCast, Datum, DatumType, QParams, round_ties_to_even, scale_by};
5use crate::dim::TDim;
6use crate::internal::*;
7use half::f16;
8use itertools::{Itertools, izip};
9use ndarray::prelude::*;
10#[cfg(feature = "complex")]
11use num_complex::Complex;
12use num_traits::Float;
13use std::borrow::Cow;
14use std::fmt;
15use std::hash::Hash;
16use std::ops::Range;
17use std::sync::Arc;
18
19pub mod litteral;
20pub mod plain_view;
21pub mod storage;
22pub mod view;
23
24pub use plain_view::{PlainView, PlainViewMut};
25use storage::{PlainStorage, StorageKind, TensorStorage};
26
27#[derive(Copy, Clone, Default, Debug)]
28pub enum Approximation {
29    Exact,
30    #[default]
31    Close,
32    Approximate,
33    VeryApproximate,
34    SuperApproximate,
35    UltraApproximate,
36    Custom(f32, f32, f32),
37    /// Compare by integer ULP distance in the reference tensor's own float type,
38    /// accepting a distance up to the given bound.
39    ///
40    /// Unlike the tolerance-based variants this does not go through an f32 cast,
41    /// so an f16 comparison stays an f16 comparison. Use it to assert that two
42    /// implementations of a kernel agree to within a known number of rounding
43    /// steps.
44    Ulp(u64),
45}
46
47impl PartialEq for Approximation {
48    fn eq(&self, other: &Self) -> bool {
49        use Approximation::*;
50        match (self, other) {
51            (Custom(aa, ar, ao), Custom(ba, br, bo)) => aa == ba && ar == br && bo == ao,
52            (Ulp(a), Ulp(b)) => a == b,
53            _ => std::mem::discriminant(self) == std::mem::discriminant(other),
54        }
55    }
56}
57
58impl Eq for Approximation {}
59
60impl From<bool> for Approximation {
61    fn from(b: bool) -> Self {
62        if b { Self::Approximate } else { Self::Exact }
63    }
64}
65
66impl Approximation {
67    fn atol_rtol_outliers(&self, dt: &DatumType) -> (f64, f64, f64) {
68        use Approximation::*;
69        match (self, dt) {
70            (Exact, _) => (0.0, 0.0, 0.0),
71            (Close, DatumType::F16) => (1e-3, 1e-3, 0.0),
72            (Approximate, DatumType::F16) => (1e-3, 5e-3, 0.0),
73            (Approximate, qp) if qp.is_quantized() => (qp.zp_scale().1 as f64, 0., 0.0),
74            (Close, _) => (1e-7, 1e-7, 0.0),
75            (Approximate, _) => (1e-4, 5e-4, 0.0),
76            (VeryApproximate, _) => (5e-2, 1e-2, 0.0),
77            (SuperApproximate, _) => (0.1, 0.05, 0.0001),
78            (UltraApproximate, _) => (0.2, 0.1, 0.0005),
79            (Custom(atol, rtol, out), _) => (*atol as _, *rtol as _, *out as _),
80            // Handled by a dedicated path in `Tensor::close_enough`; these values
81            // are never consulted.
82            (Ulp(_), _) => (0.0, 0.0, 0.0),
83        }
84    }
85}
86
87/// Tensor is a concrete tensor in tract.
88pub struct Tensor {
89    dt: DatumType,
90    shape: TVec<usize>,
91    strides: TVec<isize>,
92    len: usize,
93    storage: StorageKind,
94}
95
96unsafe impl Send for Tensor {}
97unsafe impl Sync for Tensor {}
98
99impl Hash for Tensor {
100    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
101        use DatumType::*;
102        self.dt.hash(state);
103        self.shape.hash(state);
104        if let Some(plain) = self.storage.as_plain_ram() {
105            plain.layout().align().hash(state);
106            unsafe {
107                match self.dt {
108                    Bool => self.as_slice_unchecked::<bool>().hash(state),
109                    I8 => self.as_slice_unchecked::<i8>().hash(state),
110                    I16 => self.as_slice_unchecked::<i16>().hash(state),
111                    I32 => self.as_slice_unchecked::<i32>().hash(state),
112                    I64 => self.as_slice_unchecked::<i64>().hash(state),
113                    U8 => self.as_slice_unchecked::<u8>().hash(state),
114                    U16 => self.as_slice_unchecked::<u16>().hash(state),
115                    U32 => self.as_slice_unchecked::<u32>().hash(state),
116                    U64 => self.as_slice_unchecked::<u64>().hash(state),
117                    F16 => self.as_slice_unchecked::<i16>().hash(state),
118                    F32 => self.as_slice_unchecked::<i32>().hash(state),
119                    F64 => self.as_slice_unchecked::<i64>().hash(state),
120                    TDim => self.as_slice_unchecked::<crate::dim::TDim>().hash(state),
121                    String => self.as_slice_unchecked::<std::string::String>().hash(state),
122                    Blob => self.as_slice_unchecked::<crate::blob::Blob>().hash(state),
123                    QI8(_) => self.as_slice_unchecked::<i8>().hash(state),
124                    QU8(_) => self.as_slice_unchecked::<u8>().hash(state),
125                    QI32(_) => self.as_slice_unchecked::<i32>().hash(state),
126                    #[cfg(feature = "complex")]
127                    ComplexI16 => self.as_slice_unchecked::<Complex<i16>>().hash(state),
128                    #[cfg(feature = "complex")]
129                    ComplexI32 => self.as_slice_unchecked::<Complex<i32>>().hash(state),
130                    #[cfg(feature = "complex")]
131                    ComplexI64 => self.as_slice_unchecked::<Complex<i64>>().hash(state),
132                    #[cfg(feature = "complex")]
133                    ComplexF16 => self.as_slice_unchecked::<Complex<i16>>().hash(state),
134                    #[cfg(feature = "complex")]
135                    ComplexF32 => self.as_slice_unchecked::<Complex<i32>>().hash(state),
136                    #[cfg(feature = "complex")]
137                    ComplexF64 => self.as_slice_unchecked::<Complex<i64>>().hash(state),
138                }
139            }
140        } else {
141            self.storage.dyn_hash(state);
142        }
143    }
144}
145
146impl Clone for Tensor {
147    fn clone(&self) -> Tensor {
148        self.deep_clone()
149    }
150}
151
152impl Default for Tensor {
153    fn default() -> Tensor {
154        litteral::tensor0(0f32)
155    }
156}
157
158impl Drop for Tensor {
159    fn drop(&mut self) {
160        if self.as_plain_ram_storage().is_some() {
161            macro_rules! drop_in_place {
162                ($t: ty) => {
163                    if self.dt == <$t>::datum_type() {
164                        unsafe {
165                            let slice = self.as_slice_mut_unchecked::<$t>();
166                            std::ptr::drop_in_place(slice as *mut [$t]);
167                        }
168                    }
169                };
170            }
171            drop_in_place!(Blob);
172            drop_in_place!(String);
173            drop_in_place!(TDim);
174        }
175        // StorageKind::Exotic drops via Box<dyn TensorStorage> automatically
176    }
177}
178
179#[allow(unreachable_code)]
180pub fn vector_size() -> usize {
181    #[cfg(target_arch = "x86_64")]
182    {
183        return if is_x86_feature_detected!("avx512f") { 512 / 8 } else { 256 / 8 };
184    }
185    128 / 8
186}
187
188/// Copy `outer` blocks of `block` bytes from a contiguous source into a destination
189/// strided by `out_stride`, as `T`-sized items. Used for blocks too small for a
190/// `copy_nonoverlapping` call per block to pay for itself.
191///
192/// # Safety
193/// `block` and `out_stride` must be multiples of `size_of::<T>()`, both pointers must
194/// be `T`-aligned, and the two ranges must not overlap.
195#[inline]
196unsafe fn copy_blocks<T: Copy>(
197    src: *const u8,
198    dst: *mut u8,
199    outer: usize,
200    block: usize,
201    out_stride: usize,
202) {
203    unsafe {
204        let n = block / std::mem::size_of::<T>();
205        for o in 0..outer {
206            let s = src.add(o * block) as *const T;
207            let d = dst.add(o * out_stride) as *mut T;
208            for i in 0..n {
209                *d.add(i) = *s.add(i);
210            }
211        }
212    }
213}
214
215/// Transpose the `[i0, i1) × [m0, m1)` window of a `[a, mid]` matrix of N-byte
216/// cells into a `[mid, a]` matrix. `src` holds `a * mid` cells and `dst` `mid * a`
217/// cells, both densely packed.
218#[allow(clippy::too_many_arguments)]
219fn transpose_tile_cells<const N: usize>(
220    src: &[u8],
221    dst: &mut [u8],
222    i0: usize,
223    i1: usize,
224    m0: usize,
225    m1: usize,
226    a: usize,
227    mid: usize,
228) {
229    let (src, _) = src.as_chunks::<N>();
230    let (dst, _) = dst.as_chunks_mut::<N>();
231    if (a * N).is_multiple_of(4096) {
232        for (m, row) in dst.chunks_exact_mut(a).enumerate().take(m1).skip(m0) {
233            for (cell, i) in row[i0..i1].iter_mut().zip(i0..i1) {
234                *cell = src[i * mid + m];
235            }
236        }
237    } else {
238        let mut i = i0;
239        while i + 4 <= i1 {
240            let mut m = m0;
241            while m + 4 <= m1 {
242                let r0: &[[u8; N]; 4] = src[i * mid + m..i * mid + m + 4].try_into().unwrap();
243                let r1: &[[u8; N]; 4] =
244                    src[(i + 1) * mid + m..(i + 1) * mid + m + 4].try_into().unwrap();
245                let r2: &[[u8; N]; 4] =
246                    src[(i + 2) * mid + m..(i + 2) * mid + m + 4].try_into().unwrap();
247                let r3: &[[u8; N]; 4] =
248                    src[(i + 3) * mid + m..(i + 3) * mid + m + 4].try_into().unwrap();
249                for k in 0..4 {
250                    dst[(m + k) * a + i..(m + k) * a + i + 4]
251                        .copy_from_slice(&[r0[k], r1[k], r2[k], r3[k]]);
252                }
253                m += 4;
254            }
255            while m < m1 {
256                for ii in i..i + 4 {
257                    dst[m * a + ii] = src[ii * mid + m];
258                }
259                m += 1;
260            }
261            i += 4;
262        }
263        for i in i..i1 {
264            for (cell, m) in src[i * mid + m0..i * mid + m1].iter().zip(m0..m1) {
265                dst[m * a + i] = *cell;
266            }
267        }
268    }
269}
270
271/// Transpose the `[i0, i1) × [m0, m1)` window of a `[a, mid]` matrix of `item`-byte
272/// cells into a `[mid, a]` matrix. `src` holds `a * mid` cells and `dst` `mid * a`
273/// cells, both densely packed.
274#[allow(clippy::too_many_arguments)]
275fn transpose_tile(
276    src: &[u8],
277    dst: &mut [u8],
278    i0: usize,
279    i1: usize,
280    m0: usize,
281    m1: usize,
282    a: usize,
283    mid: usize,
284    item: usize,
285) {
286    match item {
287        1 => transpose_tile_cells::<1>(src, dst, i0, i1, m0, m1, a, mid),
288        2 => transpose_tile_cells::<2>(src, dst, i0, i1, m0, m1, a, mid),
289        4 => transpose_tile_cells::<4>(src, dst, i0, i1, m0, m1, a, mid),
290        8 => transpose_tile_cells::<8>(src, dst, i0, i1, m0, m1, a, mid),
291        16 => transpose_tile_cells::<16>(src, dst, i0, i1, m0, m1, a, mid),
292        _ => {
293            for i in i0..i1 {
294                for m in m0..m1 {
295                    let s = (i * mid + m) * item;
296                    let d = (m * a + i) * item;
297                    dst[d..d + item].copy_from_slice(&src[s..s + item]);
298                }
299            }
300        }
301    }
302}
303
304/// Transpose a `[a, mid]` matrix of `item`-byte cells into a `[mid, a]` matrix,
305/// visiting both in cache-sized tiles. `src` holds `a * mid` cells and `dst`
306/// `mid * a` cells, both densely packed.
307fn transpose_cells(src: &[u8], dst: &mut [u8], a: usize, mid: usize, item: usize) {
308    let tile = (64 / item).max(16);
309    let mut i0 = 0;
310    while i0 < a {
311        let i1 = (i0 + tile).min(a);
312        let mut m0 = 0;
313        while m0 < mid {
314            let m1 = (m0 + tile).min(mid);
315            transpose_tile(src, dst, i0, i1, m0, m1, a, mid, item);
316            m0 += tile;
317        }
318        i0 += tile;
319    }
320}
321
322/// Copy `src`, a `[outer, p, q, inner]` contiguous tensor of `elem_size`-byte
323/// elements, into `dst` laid out as `[outer, q, p, inner]` — the data movement
324/// performed by a single `move_axis` where `p` and `q` are the two extents the
325/// moved axis travels across. Both slices must cover `outer * p * q * inner *
326/// elem_size` bytes.
327fn move_axis_blocks(
328    src: &[u8],
329    dst: &mut [u8],
330    outer: usize,
331    p: usize,
332    q: usize,
333    inner: usize,
334    elem_size: usize,
335) {
336    let item = inner * elem_size;
337    let block = outer * p * q * item;
338    if p == 1 || q == 1 || inner == 0 {
339        dst[..block].copy_from_slice(&src[..block]);
340        return;
341    }
342    if item >= 64 {
343        for o in 0..outer {
344            for i in 0..p {
345                for m in 0..q {
346                    let s = ((o * p + i) * q + m) * item;
347                    let d = ((o * q + m) * p + i) * item;
348                    dst[d..d + item].copy_from_slice(&src[s..s + item]);
349                }
350            }
351        }
352    } else {
353        for o in 0..outer {
354            transpose_cells(
355                &src[o * p * q * item..(o + 1) * p * q * item],
356                &mut dst[o * p * q * item..(o + 1) * p * q * item],
357                p,
358                q,
359                item,
360            );
361        }
362    }
363}
364
365/// If `axes` is the identity permutation with one element moved — the shape of
366/// permutation `move_axis` produces — return the `(from, to)` of that move.
367fn move_axis_pair(axes: &[usize]) -> Option<(usize, usize)> {
368    let lo = (0..axes.len()).find(|&i| axes[i] != i)?;
369    let hi = (0..axes.len()).rev().find(|&i| axes[i] != i).unwrap();
370    if axes[hi] == lo && (lo..hi).all(|i| axes[i] == i + 1) {
371        return Some((lo, hi));
372    }
373    if axes[lo] == hi && (lo + 1..=hi).all(|i| axes[i] == i - 1) {
374        return Some((hi, lo));
375    }
376    None
377}
378
379impl Tensor {
380    /// Plain storage for this tensor's bytes, materializing it if the storage
381    /// keeps them elsewhere.
382    ///
383    /// Panics if the bytes cannot be produced, which is what the accessors
384    /// built on it (`as_bytes`, `as_ptr`, `as_slice_unchecked`) have always
385    /// done on non-plain storage. `try_as_plain_ram` is the fallible way in.
386    #[inline]
387    fn plain_ram_storage(&self) -> &PlainStorage {
388        self.storage.materialize_plain_ram().expect("Non-plain storage")
389    }
390
391    #[inline]
392    fn plain_ram_storage_mut(&mut self) -> &mut PlainStorage {
393        self.storage.as_plain_ram_mut().expect("Non-plain storage")
394    }
395
396    pub fn storage_as<T: TensorStorage>(&self) -> Option<&T> {
397        self.storage.as_storage().downcast_ref::<T>()
398    }
399
400    pub fn try_storage_as<T: TensorStorage>(&self) -> TractResult<&T> {
401        self.storage_as::<T>().context("Unexpected tensor storage type")
402    }
403
404    pub fn from_storage(
405        dt: DatumType,
406        shape: &[usize],
407        storage: impl TensorStorage + 'static,
408    ) -> Tensor {
409        let len = shape.iter().product::<usize>();
410        let strides = Self::natural_strides(shape);
411        Tensor {
412            dt,
413            shape: shape.into(),
414            strides,
415            len,
416            storage: StorageKind::Exotic(Box::new(storage)),
417        }
418    }
419
420    /// Returns an immutable [`PlainView`] if this tensor has plain storage.
421    #[inline]
422    pub fn as_plain_ram(&self) -> Option<PlainView<'_>> {
423        let storage = self.storage.as_plain_ram()?;
424        Some(PlainView::new(self, storage))
425    }
426
427    /// Returns an immutable [`PlainView`], or an error if storage is not plain.
428    ///
429    /// Unlike `as_plain_ram`, this materializes storage that holds its bytes
430    /// elsewhere, so it is the way to read a tensor whose storage may be
431    /// lazily host-backed.
432    #[inline]
433    pub fn try_as_plain_ram(&self) -> TractResult<PlainView<'_>> {
434        let storage = self.storage.materialize_plain_ram()?;
435        Ok(PlainView::new(self, storage))
436    }
437
438    /// Plain storage this tensor already holds in ram, if any. Never
439    /// materializes: the two axes at once, and the accessor to reach for
440    /// before a plain read.
441    #[inline]
442    pub fn as_plain_ram_storage(&self) -> Option<&PlainStorage> {
443        self.storage.as_plain_ram()
444    }
445
446    /// Mutable plain storage this tensor already holds in ram, if any. Never
447    /// materializes.
448    #[inline]
449    pub fn as_plain_ram_storage_mut(&mut self) -> Option<&mut PlainStorage> {
450        self.storage.as_plain_ram_mut()
451    }
452
453    /// Returns `true` if datum type and shape describe this tensor's layout on
454    /// their own.
455    ///
456    /// The layout axis, orthogonal to placement: a dense tensor is plain
457    /// wherever its bytes sit, block-quant weights are exotic wherever theirs
458    /// sit. Matches `TypedFact::is_plain`.
459    #[inline]
460    pub fn is_plain(&self) -> bool {
461        !self.storage.is_exotic()
462    }
463
464    /// Returns `true` if datum type and shape do not describe this tensor's
465    /// layout on their own, so a fact over it carries an `ExoticFact`.
466    #[inline]
467    pub fn is_exotic(&self) -> bool {
468        self.storage.is_exotic()
469    }
470
471    /// Returns `true` if this tensor's bytes can be read as plain host memory
472    /// right now: both axes at once, and what a caller gating an eager read or
473    /// an evaluation on cost is really asking.
474    #[inline]
475    pub fn is_plain_ram(&self) -> bool {
476        self.as_plain_ram_storage().is_some()
477    }
478
479    /// Returns `true` if this tensor's bytes are in host memory, readable
480    /// without a transfer.
481    ///
482    /// The placement axis, and a transient one: storage that leaves its bytes
483    /// on a device answers false until something materializes them, true
484    /// afterwards. It answers for the bytes in whatever layout the storage
485    /// keeps them, so it takes both axes -- `as_plain_ram_storage` -- for a
486    /// plain read to be sure to work.
487    #[inline]
488    pub fn in_ram(&self) -> bool {
489        self.storage.in_ram()
490    }
491
492    /// Build the `ExoticFact` matching this tensor's storage, or `None` for plain tensors.
493    pub fn exotic_fact(&self) -> TractResult<Option<Box<dyn crate::exotic::ExoticFact>>> {
494        self.storage.as_storage().exotic_fact(&self.shape)
495    }
496
497    /// Returns a mutable [`PlainViewMut`] if this tensor has plain storage.
498    #[inline]
499    pub fn as_plain_ram_mut(&mut self) -> Option<PlainViewMut<'_>> {
500        let storage = self.storage.as_plain_ram_mut()?;
501        Some(PlainViewMut::new(self.dt, &self.shape, &self.strides, self.len, storage))
502    }
503
504    /// Returns a mutable [`PlainViewMut`], or an error if storage is not plain.
505    #[inline]
506    pub fn try_as_plain_ram_mut(&mut self) -> TractResult<PlainViewMut<'_>> {
507        self.as_plain_ram_mut().context("Tensor storage is not plain")
508    }
509
510    /// Create an uninitialized tensor (dt as type paramater).
511    #[inline]
512    pub unsafe fn uninitialized<T: Datum>(shape: &[usize]) -> TractResult<Tensor> {
513        unsafe { Self::uninitialized_dt(T::datum_type(), shape) }
514    }
515
516    /// Create an uninitialized tensor (dt as regular parameter).
517    #[inline]
518    pub unsafe fn uninitialized_dt(dt: DatumType, shape: &[usize]) -> TractResult<Tensor> {
519        unsafe { Self::uninitialized_aligned_dt(dt, shape, vector_size()) }
520    }
521
522    /// Create an uninitialized tensor with a given alignment (in bytes).
523    #[inline]
524    pub unsafe fn uninitialized_aligned<T: Datum>(
525        shape: &[usize],
526        alignment: usize,
527    ) -> TractResult<Tensor> {
528        unsafe { Self::uninitialized_aligned_dt(T::datum_type(), shape, alignment) }
529    }
530
531    /// Create an uninitialized tensor with a given alignment (in bytes).
532    pub unsafe fn uninitialized_aligned_dt(
533        dt: DatumType,
534        shape: &[usize],
535        alignment: usize,
536    ) -> TractResult<Tensor> {
537        // `shape` and `dt` come from the model file. Computing the byte count
538        // with a plain product used to wrap around on overflow, silently
539        // allocating a buffer much smaller than the tensor claims (or asking
540        // the allocator for an absurd one). Check instead.
541        let bytes = shape
542            .iter()
543            .try_fold(dt.size_of(), |acc, &d| acc.checked_mul(d))
544            .filter(|&b| b <= isize::MAX as usize)
545            .ok_or_else(|| format_err!("tensor shape {shape:?} of {dt:?} is too large"))?;
546        let storage = StorageKind::Plain(PlainStorage::from(unsafe {
547            Blob::new_for_size_and_align(bytes, alignment)
548        }));
549        let mut tensor = Tensor { strides: tvec!(), dt, shape: shape.into(), storage, len: 0 };
550        if tensor.shape.len() == 0 {
551            tensor.len = 1;
552        } else {
553            tensor.update_strides_and_len();
554        }
555        if !tensor.storage.is_empty() {
556            unsafe fn write_defaults<T: Datum + Default>(tensor: &mut Tensor) {
557                unsafe {
558                    let len = tensor.len;
559                    let dst = tensor.as_slice_mut_unchecked::<T>().as_mut_ptr();
560                    for i in 0..len {
561                        std::ptr::write(dst.add(i), T::default());
562                    }
563                }
564            }
565            if dt == String::datum_type() {
566                unsafe { write_defaults::<String>(&mut tensor) }
567            } else if dt == Blob::datum_type() {
568                unsafe { write_defaults::<Blob>(&mut tensor) }
569            } else if dt == TDim::datum_type() {
570                unsafe { write_defaults::<TDim>(&mut tensor) }
571            } else if cfg!(debug_assertions) {
572                assert!(dt.is_copy());
573                if dt == DatumType::F32 {
574                    tensor.fill_t(f32::NAN).unwrap();
575                } else {
576                    // safe, non copy types have been dealt with
577                    tensor.as_bytes_mut().iter_mut().for_each(|x| *x = (-1i8) as u8);
578                }
579            }
580        }
581        Ok(tensor)
582    }
583
584    pub fn stack_tensors(
585        axis: usize,
586        tensors: &[impl std::borrow::Borrow<Tensor>],
587    ) -> TractResult<Tensor> {
588        ensure!(tensors.len() > 0);
589        let rank = tensors[0].borrow().rank();
590        ensure!(axis < rank);
591        ensure!(tensors.iter().all(|t| t.borrow().rank() == rank));
592        let dt = tensors[0].borrow().datum_type();
593        ensure!(tensors.iter().all(|t| t.borrow().datum_type() == dt));
594        let mut shape: TVec<usize> = tensors[0].borrow().shape().into();
595        for ax in 0..rank {
596            if ax != axis {
597                ensure!(tensors.iter().all(|t| t.borrow().shape()[ax] == shape[ax]));
598            }
599        }
600        shape[axis] = tensors.iter().map(|v| v.borrow().shape()[axis]).sum();
601        unsafe {
602            let mut result = Tensor::uninitialized_dt(dt, &shape)?;
603            // Every input keeps the same trailing block, so one outer stride walks
604            // them alongside the result and each contribution stays contiguous.
605            let outer: usize = shape[..axis].iter().product();
606            let out_stride = shape[axis..].iter().product::<usize>() * dt.size_of();
607            // Each contribution is `outer` blocks of `block` bytes, strided by
608            // `out_stride` in the result. At one f32 per block -- DTLN's
609            // [1, 2, 128, 2] axis-3 concat, FastEnhancer's [1, 256, 1, 2] -- a
610            // copy_nonoverlapping per block is `outer` calls to move four bytes
611            // each, and the generic strided assign below is no better. Copy those
612            // inline, typed, instead: no call, and the loop is a plain strided
613            // store LLVM can widen.
614            const SMALL_BLOCK_BYTES: usize = 64;
615            if dt.is_copy()
616                && outer > 0
617                && tensors.iter().all(|t| t.borrow().storage.as_plain_ram().is_some())
618            {
619                let out = result.plain_ram_storage_mut().as_mut_ptr();
620                let mut offset = 0isize;
621                for v in tensors {
622                    let v = v.borrow();
623                    let block = v.storage.byte_len() / outer;
624                    let src = v.plain_ram_storage().as_ptr();
625                    let dst = out.offset(offset);
626                    if outer == 1 {
627                        std::ptr::copy_nonoverlapping(src, dst, block);
628                    } else if block >= SMALL_BLOCK_BYTES {
629                        for o in 0..outer {
630                            std::ptr::copy_nonoverlapping(
631                                src.add(o * block),
632                                dst.add(o * out_stride),
633                                block,
634                            );
635                        }
636                    } else {
637                        // `block` and both pointers are multiples of the datum size,
638                        // so the typed copy stays aligned.
639                        match dt.size_of() {
640                            1 => copy_blocks::<u8>(src, dst, outer, block, out_stride),
641                            2 => copy_blocks::<u16>(src, dst, outer, block, out_stride),
642                            4 => copy_blocks::<u32>(src, dst, outer, block, out_stride),
643                            8 => copy_blocks::<u64>(src, dst, outer, block, out_stride),
644                            16 => copy_blocks::<u128>(src, dst, outer, block, out_stride),
645                            _ => {
646                                for o in 0..outer {
647                                    std::ptr::copy_nonoverlapping(
648                                        src.add(o * block),
649                                        dst.add(o * out_stride),
650                                        block,
651                                    );
652                                }
653                            }
654                        }
655                    }
656                    offset += block as isize;
657                }
658            } else {
659                let mut offset = 0;
660                for t in tensors {
661                    let t = t.borrow();
662                    let len = t.shape()[axis];
663                    result.assign_slice_from_resolved(
664                        &[],
665                        offset..offset + len,
666                        t,
667                        &[],
668                        0..len,
669                        axis,
670                    );
671                    offset += len;
672                }
673            }
674
675            Ok(result)
676        }
677    }
678
679    pub fn clear<T: Datum + num_traits::Zero + Clone>(&mut self) -> TractResult<()> {
680        self.fill_t(T::zero())
681    }
682
683    pub fn zero<T: Datum + num_traits::Zero>(shape: &[usize]) -> TractResult<Tensor> {
684        unsafe {
685            let mut t = Tensor::uninitialized::<T>(shape)?;
686            t.clear::<T>()?;
687            Ok(t)
688        }
689    }
690
691    pub fn zero_scalar<T: Datum + num_traits::Zero>() -> TractResult<Tensor> {
692        Tensor::zero::<T>(&[])
693    }
694
695    pub fn zero_scalar_dt(dt: DatumType) -> TractResult<Tensor> {
696        Tensor::zero_dt(dt, &[])
697    }
698
699    pub fn zero_dt(dt: DatumType, shape: &[usize]) -> TractResult<Tensor> {
700        Tensor::zero_aligned_dt(dt, shape, vector_size())
701    }
702
703    pub fn fill_t<T: Datum + Clone>(&mut self, value: T) -> TractResult<()> {
704        self.try_as_plain_ram_mut()?
705            .as_slice_mut::<T>()?
706            .iter_mut()
707            .for_each(|item| *item = value.clone());
708        Ok(())
709    }
710
711    pub fn zero_aligned_dt(
712        dt: DatumType,
713        shape: &[usize],
714        alignment: usize,
715    ) -> TractResult<Tensor> {
716        if shape.iter().product::<usize>() == 0 {
717            unsafe { return Tensor::uninitialized_dt(dt, shape) };
718        }
719        if dt.is_quantized() {
720            unsafe {
721                let mut t = Tensor::uninitialized_dt(dt, shape)?;
722                let zp = dt.zp_scale().0;
723                match dt.unquantized() {
724                    DatumType::I8 => t
725                        .try_as_plain_ram_mut()?
726                        .as_slice_mut::<i8>()?
727                        .iter_mut()
728                        .for_each(|item| *item = zp as _),
729                    DatumType::U8 => t
730                        .try_as_plain_ram_mut()?
731                        .as_slice_mut::<u8>()?
732                        .iter_mut()
733                        .for_each(|item| *item = zp as _),
734                    DatumType::I32 => t
735                        .try_as_plain_ram_mut()?
736                        .as_slice_mut::<i32>()?
737                        .iter_mut()
738                        .for_each(|item| *item = zp as _),
739                    _ => unreachable!(),
740                }
741                Ok(t)
742            }
743        } else if dt == DatumType::Bool {
744            let mut t = unsafe { Tensor::uninitialized_dt(dt, shape)? };
745            t.fill_t::<bool>(false)?;
746            Ok(t)
747        } else {
748            dispatch_zerolike!(Self::zero_aligned(dt)(shape, alignment))
749        }
750    }
751
752    pub fn zero_aligned<T: Datum + num_traits::Zero>(
753        shape: &[usize],
754        alignment: usize,
755    ) -> TractResult<Tensor> {
756        unsafe {
757            let mut tensor = Self::uninitialized_aligned::<T>(shape, alignment)?;
758            tensor.clear::<T>()?;
759            Ok(tensor)
760        }
761    }
762
763    /// Create a tensor with a given shape and a slice of elements.
764    /// The data is copied and aligned to size of T.
765    pub fn from_shape<T: Datum + Copy>(shape: &[usize], data: &[T]) -> TractResult<Tensor> {
766        Self::from_shape_align(shape, data, vector_size())
767    }
768
769    /// Create a tensor with a given shape and a slice of elements.
770    /// The data is copied and aligned to given alignment.
771    pub fn from_shape_align<T: Datum + Copy>(
772        shape: &[usize],
773        data: &[T],
774        align: usize,
775    ) -> TractResult<Tensor> {
776        ensure!(
777            data.len() == shape.iter().product::<usize>(),
778            "Shape product must be equal to data length"
779        );
780        unsafe {
781            let bytes = std::slice::from_raw_parts(
782                data.as_ptr() as *const u8,
783                data.len() * T::datum_type().size_of(),
784            );
785            let dt = T::datum_type();
786            Self::from_raw_dt_align(dt, shape, bytes, align)
787        }
788    }
789
790    /// Create a tensor from raw data.
791    ///
792    /// It copies the data, aligning it to the size of T.
793    pub unsafe fn from_raw<T: Datum>(shape: &[usize], content: &[u8]) -> TractResult<Tensor> {
794        unsafe { Tensor::from_raw_dt(T::datum_type(), shape, content) }
795    }
796
797    pub unsafe fn from_raw_aligned<T: Datum>(
798        shape: &[usize],
799        content: &[u8],
800        align: usize,
801    ) -> TractResult<Tensor> {
802        unsafe { Tensor::from_raw_dt_align(T::datum_type(), shape, content, align) }
803    }
804
805    pub unsafe fn from_raw_dt(
806        dt: DatumType,
807        shape: &[usize],
808        content: &[u8],
809    ) -> TractResult<Tensor> {
810        unsafe { Self::from_raw_dt_align(dt, shape, content, vector_size()) }
811    }
812
813    pub unsafe fn from_raw_dt_align(
814        dt: DatumType,
815        shape: &[usize],
816        content: &[u8],
817        align: usize,
818    ) -> TractResult<Tensor> {
819        // Check the declared shape against the payload *before* allocating.
820        // The shape is attacker-controlled, so allocating first lets a
821        // malformed model request an allocation of any size it likes, even
822        // when the payload that follows is a few bytes long.
823        let len = shape
824            .iter()
825            .try_fold(1usize, |acc, &d| acc.checked_mul(d))
826            .ok_or_else(|| format_err!("tensor shape {shape:?} overflows"))?;
827        let expected = len
828            .checked_mul(dt.size_of())
829            .ok_or_else(|| format_err!("tensor shape {shape:?} of {dt:?} is too large"))?;
830        ensure!(
831            content.len() == expected,
832            "Raw tensor data length ({}) does not match shape {:?} of {:?} ({} bytes)",
833            content.len(),
834            shape,
835            dt,
836            expected
837        );
838        let mut tensor = unsafe { Tensor::uninitialized_aligned_dt(dt, shape, align) }?;
839        tensor.as_bytes_mut().copy_from_slice(content);
840        Ok(tensor)
841    }
842
843    pub unsafe fn from_slice_align<T: Datum>(content: &[T], align: usize) -> TractResult<Tensor> {
844        let bytes = if content.len() == 0 {
845            &[]
846        } else {
847            unsafe {
848                std::slice::from_raw_parts(
849                    content.as_ptr() as *const u8,
850                    content.len() * T::datum_type().size_of(),
851                )
852            }
853        };
854        unsafe { Self::from_raw_dt_align(T::datum_type(), &[content.len()], bytes, align) }
855    }
856
857    /// Get the number of dimensions (or axes) of the tensor.
858    #[inline]
859    pub fn rank(&self) -> usize {
860        self.shape.len()
861    }
862
863    /// Get the shape of the tensor.
864    #[inline]
865    pub fn shape(&self) -> &[usize] {
866        &self.shape
867    }
868
869    /// Get the number of values in the tensor.
870    #[inline]
871    #[allow(clippy::len_without_is_empty)]
872    pub fn len(&self) -> usize {
873        self.len
874    }
875
876    /// Get the number of valeus in the tensor.
877    #[inline]
878    #[allow(clippy::len_without_is_empty)]
879    pub fn volume(&self) -> usize {
880        self.len
881    }
882
883    /// Get the shape of the tensor.
884    #[inline]
885    pub fn strides(&self) -> &[isize] {
886        &self.strides
887    }
888
889    fn update_strides_and_len(&mut self) {
890        self.strides.clear();
891        if self.shape.len() == 0 {
892            self.len = 1;
893            return;
894        }
895        compute_natural_stride_to(&mut self.strides, &self.shape);
896        self.len = unsafe { *self.strides.get_unchecked(0) as usize * self.shape.get_unchecked(0) };
897    }
898
899    /// Force the tensor shape, no consistency check.
900    pub unsafe fn set_shape_unchecked(&mut self, shape: &[usize]) {
901        if shape != &*self.shape {
902            self.shape.clear();
903            self.shape.extend_from_slice(shape);
904            self.update_strides_and_len();
905        }
906    }
907
908    /// Force the tensor shape and strides, no consistency check.
909    pub unsafe fn set_geometry_unchecked(&mut self, shape: &[usize], strides: &[isize]) {
910        self.shape.clear();
911        self.shape.extend_from_slice(shape);
912        self.strides.clear();
913        self.strides.extend_from_slice(strides);
914    }
915
916    /// Force the tensor shape.
917    pub fn set_shape(&mut self, shape: &[usize]) -> TractResult<()> {
918        if self.len() != shape.iter().product::<usize>() {
919            bail!("Invalid reshape {:?} to {:?}", self.shape, shape);
920        }
921        unsafe { self.set_shape_unchecked(shape) }
922        Ok(())
923    }
924
925    /// `true` when the tensor is backed by plain contiguous storage laid out
926    /// with the natural strides of its shape, so the block move applies.
927    #[inline]
928    fn can_move_axis_blocks(&self) -> bool {
929        self.dt.is_copy()
930            && self.has_plain_ram_storage()
931            && self.strides[..] == natural_strides(&self.shape)[..]
932    }
933
934    /// Return a new tensor with the axis `from` moved to position `to`, in fresh
935    /// plain storage. Data is copied once. Falls back to a clone plus the
936    /// ndarray path when the tensor is not plainly and contiguously laid out.
937    pub fn moved_axis(&self, from: usize, to: usize) -> TractResult<Tensor> {
938        ensure!(from < self.rank());
939        ensure!(to < self.rank());
940        let mut new_shape: TVec<usize> = self.shape.clone();
941        let moved = new_shape.remove(from);
942        new_shape.insert(to, moved);
943        if !self.can_move_axis_blocks() {
944            let mut permutation: Vec<usize> = (0..self.rank()).collect();
945            permutation.remove(from);
946            permutation.insert(to, from);
947            return self.clone().permute_axes(&permutation);
948        }
949        let shape = &*self.shape;
950        let (outer, p, q, inner) = if from < to {
951            (
952                shape[..from].iter().product(),
953                shape[from],
954                shape[from + 1..=to].iter().product(),
955                shape[to + 1..].iter().product(),
956            )
957        } else {
958            (
959                shape[..to].iter().product(),
960                shape[to..from].iter().product(),
961                shape[from],
962                shape[from + 1..].iter().product(),
963            )
964        };
965        let mut output = Tensor::zero_dt(self.dt, &new_shape)?;
966        move_axis_blocks(
967            self.as_bytes(),
968            output.as_bytes_mut(),
969            outer,
970            p,
971            q,
972            inner,
973            self.dt.size_of(),
974        );
975        Ok(output)
976    }
977
978    pub fn permute_axes(self, axes: &[usize]) -> TractResult<Tensor> {
979        ensure!(axes.iter().duplicates().next().is_none());
980        ensure!(axes.iter().all(|a| *a < self.rank()));
981        if axes.iter().enumerate().all(|(i, a)| i == *a) {
982            return Ok(self);
983        }
984        if let Some((from, to)) = move_axis_pair(axes)
985            && self.can_move_axis_blocks()
986        {
987            return self.moved_axis(from, to);
988        }
989        unsafe {
990            #[inline]
991            unsafe fn permute<T: Datum>(axes: &[usize], input: Tensor) -> Tensor {
992                unsafe { input.into_array_unchecked::<T>().permuted_axes(axes).into_tensor() }
993            }
994            let dt = self.datum_type();
995            let mut t = dispatch_datum_by_size!(permute(self.datum_type())(axes, self));
996            t.set_datum_type(dt);
997            Ok(t)
998        }
999    }
1000
1001    pub fn move_axis(self, from: usize, to: usize) -> TractResult<Tensor> {
1002        let mut permutation: Vec<usize> = (0..self.rank()).collect();
1003        permutation.remove(from);
1004        permutation.insert(to, from);
1005        self.permute_axes(&permutation)
1006    }
1007
1008    pub fn collapse_axis_with_next(mut self, axis: usize) -> Tensor {
1009        let removed = self.shape.remove(axis + 1);
1010        self.shape[axis] *= removed;
1011        self.update_strides_and_len();
1012        self
1013    }
1014
1015    pub fn split_axis(mut self, axis: usize, outer_dim: usize) -> TractResult<Tensor> {
1016        if !self.shape[axis].is_multiple_of(outer_dim) {
1017            bail!(
1018                "Invalid axis split, shape is {:?}, axis split at {}, outer {}",
1019                self.shape,
1020                axis,
1021                outer_dim
1022            );
1023        }
1024        self.shape.insert(axis + 1, self.shape[axis] / outer_dim);
1025        self.shape[axis] = outer_dim;
1026        self.update_strides_and_len();
1027        Ok(self)
1028    }
1029
1030    /// Reshape the tensor to `shape`.
1031    pub fn into_shape(mut self, shape: &[usize]) -> TractResult<Tensor> {
1032        self.set_shape(shape)?;
1033        Ok(self)
1034    }
1035
1036    pub fn insert_axis(&mut self, axis: usize) -> TractResult<()> {
1037        self.shape.insert(axis, 1);
1038        self.strides.insert(axis, self.strides.get(axis).copied().unwrap_or(1));
1039        Ok(())
1040    }
1041
1042    pub fn remove_axis(&mut self, axis: usize) -> TractResult<()> {
1043        ensure!(self.shape[axis] == 1, "Remove a non-1 axis: axis {} in {:?}", axis, self);
1044        self.shape.remove(axis);
1045        self.strides.remove(axis);
1046        Ok(())
1047    }
1048
1049    pub fn broadcast_into_rank(mut self, rank: usize) -> TractResult<Tensor> {
1050        self.broadcast_to_rank(rank)?;
1051        self.update_strides_and_len();
1052        Ok(self)
1053    }
1054
1055    pub fn broadcast_to_rank(&mut self, rank: usize) -> TractResult<()> {
1056        if rank < self.rank() {
1057            bail!("Can only broadcast to higher rank")
1058        }
1059        while self.shape.len() < rank {
1060            self.shape.insert(0, 1)
1061        }
1062        self.update_strides_and_len();
1063        Ok(())
1064    }
1065
1066    pub fn broadcast_scalar_to_shape(&self, shape: &[usize]) -> TractResult<Tensor> {
1067        if self.rank() > 0 {
1068            bail!("broadcast_scalar_to_shape called on {:?}, which is not a salar", self);
1069        }
1070        unsafe fn make<T: Datum>(src: &Tensor, dst: &mut Tensor) {
1071            unsafe {
1072                let value: &T = src.to_scalar_unchecked::<T>();
1073                dst.as_slice_mut_unchecked::<T>().iter_mut().for_each(|item| *item = value.clone())
1074            };
1075        }
1076        unsafe {
1077            let mut t = Tensor::uninitialized_dt(self.datum_type(), shape)?;
1078            dispatch_datum_by_size!(make(self.datum_type())(self, &mut t));
1079            Ok(t)
1080        }
1081    }
1082
1083    fn broadcast_to_shape_t<T: Datum>(&self, shape: &[usize]) -> TractResult<Tensor> {
1084        unsafe {
1085            let view = self.to_array_view_unchecked::<T>();
1086            let mut output = view
1087                .broadcast(shape)
1088                .with_context(|| format!("Broadcasting {view:?} to {shape:?}"))?
1089                .into_owned()
1090                .into_tensor();
1091            output.set_datum_type(self.datum_type());
1092            Ok(output)
1093        }
1094    }
1095
1096    pub fn broadcast_to_shape(&self, shape: &[usize]) -> TractResult<Tensor> {
1097        if !self.dt.is_copy() {
1098            return dispatch_datum!(Self::broadcast_to_shape_t(self.dt)(self, shape));
1099        }
1100        ensure!(
1101            self.rank() <= shape.len(),
1102            "Broadcasting {self:?} to {shape:?} would lose {} axes",
1103            self.rank() - shape.len()
1104        );
1105        let offset = shape.len() - self.rank();
1106        let mut src: TVec<usize> = tvec!(1; shape.len());
1107        src[offset..].copy_from_slice(self.shape());
1108        ensure!(
1109            izip!(&src, shape).all(|(s, d)| *s == 1 || s == d),
1110            "Broadcasting {self:?} to {shape:?}"
1111        );
1112        // The axes from the innermost one down to the first broadcast axis are
1113        // one contiguous run of the source, so only the axes above it need a
1114        // coordinate walk, with a null source stride wherever they broadcast.
1115        let mut split = shape.len();
1116        while split > 0 && src[split - 1] == shape[split - 1] {
1117            split -= 1;
1118        }
1119        let dt_size = self.dt.size_of();
1120        let run = shape[split..].iter().product::<usize>() * dt_size;
1121        let outer: usize = shape[..split].iter().product();
1122        let mut src_strides: TVec<usize> = tvec!(0; split);
1123        let mut acc = run;
1124        for ax in (0..split).rev() {
1125            src_strides[ax] = if src[ax] == 1 { 0 } else { acc };
1126            acc *= src[ax];
1127        }
1128        let mut output = unsafe { Tensor::uninitialized_dt(self.dt, shape)? };
1129        if run == 0 || outer == 0 {
1130            return Ok(output);
1131        }
1132        let source = self.as_bytes();
1133        let dst = output.as_bytes_mut();
1134        // When the innermost source axes broadcast, `run` collapses to a single element and
1135        // the block walk below copies `dt_size` bytes per output element -- measured at ~15
1136        // cycles each, which is 6.2 ms for a 6.5 MB `1x16x1x1` -> `1x16x320x320` bias
1137        // broadcast. Every output cell is then one contiguous run of a single value, so lay
1138        // the cell down once and double it instead of walking it. `run == dt_size` holds
1139        // exactly when the innermost source axis is broadcast.
1140        if run == dt_size {
1141            let mut bs = shape.len();
1142            while bs > 0 && src[bs - 1] == 1 {
1143                bs -= 1;
1144            }
1145            let cell: usize = shape[bs..].iter().product();
1146            let cells: usize = shape[..bs].iter().product();
1147            if cell > 0 && cells > 0 {
1148                let mut src_strides: TVec<usize> = tvec!(0; bs);
1149                let mut acc = 1;
1150                for ax in (0..bs).rev() {
1151                    src_strides[ax] = if src[ax] == 1 { 0 } else { acc };
1152                    acc *= src[ax];
1153                }
1154                let cell_bytes = cell * dt_size;
1155                let mut coords: TVec<usize> = tvec!(0; bs);
1156                for cell_ix in 0..cells {
1157                    let from: usize =
1158                        izip!(&coords, &src_strides).map(|(c, s)| c * s).sum::<usize>() * dt_size;
1159                    let out = &mut dst[cell_ix * cell_bytes..][..cell_bytes];
1160                    out[..dt_size].copy_from_slice(&source[from..][..dt_size]);
1161                    let mut filled = dt_size;
1162                    while filled < cell_bytes {
1163                        let n = filled.min(cell_bytes - filled);
1164                        out.copy_within(..n, filled);
1165                        filled += n;
1166                    }
1167                    for ax in (0..bs).rev() {
1168                        coords[ax] += 1;
1169                        if coords[ax] < shape[ax] {
1170                            break;
1171                        }
1172                        coords[ax] = 0;
1173                    }
1174                }
1175                return Ok(output);
1176            }
1177        }
1178        let mut coords: TVec<usize> = tvec!(0; split);
1179        for block in 0..outer {
1180            let from: usize = izip!(&coords, &src_strides).map(|(c, s)| c * s).sum();
1181            dst[block * run..][..run].copy_from_slice(&source[from..][..run]);
1182            for ax in (0..split).rev() {
1183                coords[ax] += 1;
1184                if coords[ax] < shape[ax] {
1185                    break;
1186                }
1187                coords[ax] = 0;
1188            }
1189        }
1190        Ok(output)
1191    }
1192
1193    pub fn broadcast_vector_to_shape(&self, shape: &[usize], axis: usize) -> TractResult<Tensor> {
1194        ensure!(self.rank() == 1);
1195        ensure!(shape[axis] == self.len());
1196        if !self.datum_type().is_copy() {
1197            let mut vec_shape = vec![1; shape.len()];
1198            vec_shape[axis] = self.len();
1199            return self.clone().into_shape(&vec_shape)?.broadcast_to_shape(shape);
1200        }
1201        unsafe {
1202            let mut output = Tensor::uninitialized_dt(self.datum_type(), shape)?;
1203            if output.len() == 0 {
1204                return Ok(output);
1205            }
1206            let inner_len = shape[axis + 1..].iter().product::<usize>();
1207
1208            unsafe fn splat<T>(input: &Tensor, output: &mut Tensor, inner_len: usize)
1209            where
1210                T: Datum + Copy,
1211            {
1212                unsafe {
1213                    for ix in 0..input.len() {
1214                        let value: T = input.as_slice_unchecked()[ix];
1215                        output.as_slice_mut_unchecked::<T>()[ix * inner_len..(ix + 1) * inner_len]
1216                            .iter_mut()
1217                            .for_each(|item| *item = value);
1218                    }
1219                }
1220            }
1221            dispatch_copy_by_size!(splat(self.datum_type())(&self, &mut output, inner_len));
1222
1223            let outer_len = shape[0..axis].iter().product::<usize>();
1224            let repeat_bytes_len = inner_len * self.as_bytes().len();
1225            let bytes = output.as_bytes_mut();
1226            for ix in 1..outer_len {
1227                bytes.copy_within(0..repeat_bytes_len, ix * repeat_bytes_len);
1228            }
1229
1230            Ok(output)
1231        }
1232    }
1233    pub fn assign_slice(
1234        &mut self,
1235        range: impl std::ops::RangeBounds<usize>,
1236        src: &Tensor,
1237        src_range: impl std::ops::RangeBounds<usize>,
1238        axis: usize,
1239    ) -> TractResult<()> {
1240        self.assign_slice_at_prefix(&[], range, src, &[], src_range, axis)
1241    }
1242
1243    /// Assign `src`'s `src_range` along `axis` into `range` along `axis`, each
1244    /// taken in the sub-tensor at its prefix. A prefix indexes the leading axes
1245    /// as [`Tensor::view_at_prefix`] does, and both prefixes cover the same
1246    /// axes, whose extents may then differ. `axis` stays an axis of the whole
1247    /// tensors, so it has to sit past the prefixes.
1248    pub fn assign_slice_at_prefix(
1249        &mut self,
1250        prefix: &[usize],
1251        range: impl std::ops::RangeBounds<usize>,
1252        src: &Tensor,
1253        src_prefix: &[usize],
1254        src_range: impl std::ops::RangeBounds<usize>,
1255        axis: usize,
1256    ) -> TractResult<()> {
1257        ensure!(self.rank() == src.rank());
1258        ensure!(axis < self.rank());
1259        let range = clip_range_bounds(self.shape[axis], range);
1260        let src_range = clip_range_bounds(src.shape[axis], src_range);
1261        ensure!(
1262            src.datum_type() == self.datum_type(),
1263            "Attempt to assign into {:?} from {:?}, datum type mismatch",
1264            self.datum_type(),
1265            src.datum_type()
1266        );
1267        ensure!(
1268            src_range.len() == range.len(),
1269            "Attempt to assign a range of {:?} from a range of {:?}",
1270            range,
1271            src_range,
1272        );
1273        ensure!(
1274            prefix.len() == src_prefix.len() && prefix.len() <= axis,
1275            "Attempt to assign axis {axis} at prefixes {prefix:?} and {src_prefix:?}"
1276        );
1277        ensure!(
1278            izip!(prefix, self.shape()).all(|(ix, dim)| ix < dim)
1279                && izip!(src_prefix, src.shape()).all(|(ix, dim)| ix < dim),
1280            "Attempt to assign into {self:?} at {prefix:?} from {src:?} at {src_prefix:?}"
1281        );
1282        ensure!(
1283            izip!(prefix.len().., &self.shape[prefix.len()..], &src.shape[prefix.len()..])
1284                .all(|(ix, dst, src)| ix == axis || src == dst),
1285            "Attempt to assign a {}-axis range of {:?} from a range of {:?}",
1286            axis,
1287            self,
1288            src
1289        );
1290        ensure!(
1291            src_range.end <= src.shape()[axis],
1292            "Assigning from invalid slice (axis {}, {:?}) of {:?}",
1293            axis,
1294            src_range,
1295            src
1296        );
1297        ensure!(
1298            range.end <= self.shape()[axis],
1299            "Assigning to invalid slice (axis {}, {:?}) of {:?}",
1300            axis,
1301            range,
1302            self
1303        );
1304        unsafe { self.assign_slice_from_resolved(prefix, range, src, src_prefix, src_range, axis) };
1305        Ok(())
1306    }
1307
1308    pub unsafe fn assign_slice_unchecked(
1309        &mut self,
1310        range: impl std::ops::RangeBounds<usize>,
1311        src: &Tensor,
1312        src_range: impl std::ops::RangeBounds<usize>,
1313        axis: usize,
1314    ) {
1315        let range = clip_range_bounds(self.shape[axis], range);
1316        let src_range = clip_range_bounds(src.shape[axis], src_range);
1317        unsafe { self.assign_slice_from_resolved(&[], range, src, &[], src_range, axis) };
1318    }
1319
1320    /// The byte offset of the sub-tensor at `prefix`, which indexes the leading
1321    /// axes as [`Tensor::view_at_prefix`] does.
1322    fn prefix_offset(&self, prefix: &[usize]) -> usize {
1323        izip!(prefix, &self.strides).map(|(ix, stride)| ix * *stride as usize).sum::<usize>()
1324            * self.datum_type().size_of()
1325    }
1326
1327    #[allow(clippy::ptr_eq)]
1328    unsafe fn assign_slice_from_resolved(
1329        &mut self,
1330        prefix: &[usize],
1331        range: std::ops::Range<usize>,
1332        src: &Tensor,
1333        src_prefix: &[usize],
1334        src_range: std::ops::Range<usize>,
1335        axis: usize,
1336    ) {
1337        unsafe {
1338            use ndarray::Slice;
1339            unsafe fn assign_slice_t<T: Datum>(
1340                to: &mut Tensor,
1341                to_prefix: &[usize],
1342                to_range: Range<usize>,
1343                from: &Tensor,
1344                from_prefix: &[usize],
1345                from_range: Range<usize>,
1346                axis: usize,
1347            ) {
1348                unsafe {
1349                    let mut to_view = to.to_array_view_mut_unchecked::<T>();
1350                    let mut from_view = from.to_array_view_unchecked::<T>();
1351                    for (ax, (to, from)) in izip!(to_prefix, from_prefix).enumerate() {
1352                        to_view.slice_axis_inplace(Axis(ax), Slice::from(*to..*to + 1));
1353                        from_view.slice_axis_inplace(Axis(ax), Slice::from(*from..*from + 1));
1354                    }
1355                    to_view
1356                        .slice_axis_mut(Axis(axis), Slice::from(to_range))
1357                        .assign(&from_view.slice_axis(Axis(axis), Slice::from(from_range)))
1358                }
1359            }
1360            if self.datum_type().is_copy() {
1361                // Tensors carry natural strides, so a range along `axis` is one
1362                // contiguous run per coordinate of the axes between the prefix
1363                // and it, and both sides share the run length and the trailing
1364                // block.
1365                let post = self.strides[axis] as usize * self.datum_type().size_of();
1366                let len = post * range.len();
1367                if len > 0 {
1368                    let outer: usize = self.shape[prefix.len()..axis].iter().product();
1369                    let dst_block = post * self.shape[axis];
1370                    let src_block = post * src.shape[axis];
1371                    let src_ptr = src
1372                        .plain_ram_storage()
1373                        .as_ptr()
1374                        .add(src.prefix_offset(src_prefix) + post * src_range.start);
1375                    let aliasing =
1376                        self.plain_ram_storage().as_ptr() == src.plain_ram_storage().as_ptr();
1377                    let dst_offset = self.prefix_offset(prefix) + post * range.start;
1378                    let dst_ptr = self.plain_ram_storage_mut().as_mut_ptr().add(dst_offset);
1379                    for run in 0..outer {
1380                        let from = src_ptr.add(run * src_block);
1381                        let to = dst_ptr.add(run * dst_block);
1382                        if aliasing {
1383                            std::ptr::copy(from, to, len);
1384                        } else {
1385                            std::ptr::copy_nonoverlapping(from, to, len);
1386                        }
1387                    }
1388                }
1389            } else {
1390                dispatch_datum!(assign_slice_t(self.datum_type())(
1391                    self, prefix, range, src, src_prefix, src_range, axis
1392                ));
1393            }
1394        }
1395    }
1396    /// Fill `range` along `axis` with `value`, a one-element tensor of this
1397    /// tensor's datum type.
1398    pub fn fill_slice(
1399        &mut self,
1400        range: impl std::ops::RangeBounds<usize>,
1401        value: &Tensor,
1402        axis: usize,
1403    ) -> TractResult<()> {
1404        self.fill_slice_at_prefix(&[], range, value, axis)
1405    }
1406
1407    /// Fill `range` along `axis` of the sub-tensor at `prefix`, which indexes
1408    /// the leading axes as [`Tensor::view_at_prefix`] does, with `value`, a
1409    /// one-element tensor of this tensor's datum type. `axis` stays an axis of
1410    /// the whole tensor, so it has to sit past `prefix`.
1411    pub fn fill_slice_at_prefix(
1412        &mut self,
1413        prefix: &[usize],
1414        range: impl std::ops::RangeBounds<usize>,
1415        value: &Tensor,
1416        axis: usize,
1417    ) -> TractResult<()> {
1418        ensure!(axis < self.rank(), "Filling axis {axis} of {self:?}");
1419        ensure!(
1420            prefix.len() <= axis,
1421            "Filling axis {axis} of {self:?} at prefix {prefix:?}, which reaches it"
1422        );
1423        ensure!(
1424            izip!(prefix, self.shape()).all(|(ix, dim)| ix < dim),
1425            "Filling {self:?} at prefix {prefix:?}"
1426        );
1427        ensure!(
1428            value.datum_type() == self.datum_type() && value.len() == 1,
1429            "Filling {:?} with {value:?}",
1430            self.datum_type()
1431        );
1432        let range = clip_range_bounds(self.shape[axis], range);
1433        ensure!(
1434            range.end <= self.shape[axis],
1435            "Filling invalid slice (axis {axis}, {range:?}) of {self:?}"
1436        );
1437        if !self.datum_type().is_copy() {
1438            return dispatch_datum!(Self::fill_slice_t(self.datum_type())(
1439                self, prefix, range, value, axis
1440            ));
1441        }
1442        // Tensors carry natural strides, so the range is one contiguous run per
1443        // coordinate of the axes between the prefix and `axis`, and a run of one
1444        // datum grows to its whole length in log2(len) copies of itself.
1445        let dt_size = self.datum_type().size_of();
1446        let post = self.strides[axis] as usize * dt_size;
1447        let len = post * range.len();
1448        if len == 0 {
1449            return Ok(());
1450        }
1451        let block = post * self.shape[axis];
1452        let runs: usize = self.shape[prefix.len()..axis].iter().product();
1453        let start = self.prefix_offset(prefix) + range.start * post;
1454        let value = &value.as_bytes()[..dt_size];
1455        let data = self.as_bytes_mut();
1456        for run in 0..runs {
1457            let run = &mut data[start + run * block..start + run * block + len];
1458            run[..dt_size].copy_from_slice(value);
1459            let mut written = dt_size;
1460            while written < len {
1461                let grow = written.min(len - written);
1462                run.copy_within(0..grow, written);
1463                written += grow;
1464            }
1465        }
1466        Ok(())
1467    }
1468
1469    fn fill_slice_t<T: Datum>(
1470        &mut self,
1471        prefix: &[usize],
1472        range: Range<usize>,
1473        value: &Tensor,
1474        axis: usize,
1475    ) -> TractResult<()> {
1476        let value = value.try_as_plain_ram()?.to_scalar::<T>()?.clone();
1477        let mut view = self.to_plain_array_view_mut::<T>()?;
1478        for (ax, ix) in prefix.iter().enumerate() {
1479            view.slice_axis_inplace(Axis(ax), (*ix..*ix + 1).into());
1480        }
1481        view.slice_axis_mut(Axis(axis), range.into()).fill(value);
1482        Ok(())
1483    }
1484
1485    /// Get the datum type of the tensor.
1486    #[inline]
1487    pub fn datum_type(&self) -> DatumType {
1488        self.dt
1489    }
1490
1491    /// Set the datum type of the tensor.
1492    #[inline]
1493    pub unsafe fn set_datum_type(&mut self, dt: DatumType) {
1494        self.dt = dt
1495    }
1496
1497    /// Dump the tensor in a human readable form.
1498    ///
1499    /// `force_full` will force the tensor to be dump in full even if it is big.
1500    pub fn dump(&self, force_full: bool) -> TractResult<String> {
1501        if self.as_plain_ram_storage().is_none() {
1502            return Ok(format!(
1503                "{},{:?} (non-plain storage)",
1504                self.shape.iter().join(","),
1505                self.dt,
1506            ));
1507        }
1508        unsafe fn dump_t<D: Datum>(tensor: &Tensor, n: usize) -> String {
1509            unsafe {
1510                if let Some(qp) = tensor.datum_type().qparams() {
1511                    let integers = tensor.cast_to::<i32>().unwrap();
1512                    integers.as_slice_unchecked::<i32>()[0..n]
1513                        .iter()
1514                        .map(|x| format!("[{}]({})", x, qp.dq(*x)))
1515                        .join(", ")
1516                } else {
1517                    tensor.as_slice_unchecked::<D>()[0..n].iter().join(", ")
1518                }
1519            }
1520        }
1521        unsafe {
1522            let trunc = self.len() > 12 && !force_full;
1523            let data = dispatch_datum!(dump_t(self.datum_type())(
1524                self,
1525                if trunc { 12 } else { self.len() }
1526            ));
1527            Ok(format!(
1528                "{},{:?} {}{}",
1529                self.shape.iter().join(","),
1530                self.dt,
1531                data,
1532                if trunc { "..." } else { "" }
1533            ))
1534        }
1535    }
1536
1537    /// Compare two tensors, allowing for rounding errors.
1538    pub fn close_enough(
1539        &self,
1540        other: &Self,
1541        approx: impl Into<Approximation> + std::fmt::Debug,
1542    ) -> TractResult<()> {
1543        let approx = approx.into();
1544        if self.shape() != other.shape() {
1545            bail!("Shape mismatch {:?} != {:?}", self.shape(), other.shape())
1546        }
1547        if let Approximation::Ulp(max_ulp) = approx {
1548            return self.ulp_close_enough(other, max_ulp);
1549        }
1550        let (atol, rtol, outliers) = approx.atol_rtol_outliers(&self.datum_type());
1551        let ma = self.cast_to::<f32>()?;
1552        let ma = ma.to_plain_array_view::<f32>()?;
1553        let mb = other.cast_to::<f32>()?;
1554        let mb = mb.to_plain_array_view::<f32>()?;
1555        let mut first_outlier = None;
1556        let mut outliers_count = 0;
1557        ndarray::indices_of(&ma).into_iter().for_each(|indices| {
1558            let a = ma[&indices];
1559            let b = mb[&indices];
1560            if !((a.is_nan() && b.is_nan())
1561                || (a.is_infinite() && b.is_infinite() && a.signum() == b.signum())
1562                || (a - b).abs() <= atol as f32 + rtol as f32 * b.abs())
1563            {
1564                if outliers_count == 0 {
1565                    first_outlier = Some(indices.as_array_view().to_vec());
1566                }
1567                outliers_count += 1;
1568            }
1569        });
1570        if self.volume() > 0 && outliers_count as f64 / self.volume() as f64 > outliers {
1571            let indices = first_outlier.unwrap();
1572            let a = ma[&*indices];
1573            let b = mb[&*indices];
1574            let ulp = self
1575                .max_ulp_distance(other)
1576                .map(|(d, _)| format!("{d}"))
1577                .unwrap_or_else(|_| "n/a".to_string());
1578            bail!(
1579                "Mismatch. First outlier: {:?} for {:?}) at {:?} {} != {}. Outliers: {} / {} = {:0.5} > {:0.5}. Max ULP ({:?}): {}.",
1580                approx,
1581                self.datum_type(),
1582                indices,
1583                a,
1584                b,
1585                outliers_count,
1586                self.volume(),
1587                outliers_count as f64 / self.volume() as f64,
1588                outliers,
1589                self.ulp_comparison_dt(),
1590                ulp,
1591            );
1592        }
1593        Ok(())
1594    }
1595
1596    /// The float type ULP distances against this tensor are measured in.
1597    ///
1598    /// Float tensors are compared in their own type, so an f16 comparison stays an
1599    /// f16 comparison. Anything else falls back to f32, matching what
1600    /// `close_enough` does for its tolerance check.
1601    pub fn ulp_comparison_dt(&self) -> DatumType {
1602        match self.datum_type() {
1603            dt @ (DatumType::F16 | DatumType::F32 | DatumType::F64) => dt,
1604            _ => DatumType::F32,
1605        }
1606    }
1607
1608    /// Largest integer ULP distance between `self` and `other`, and the flat index
1609    /// where it occurs.
1610    ///
1611    /// Comparison happens in [`Self::ulp_comparison_dt`]. See [`crate::ulp`] for
1612    /// the exact convention around signed zeros, infinities and NaN.
1613    pub fn max_ulp_distance(&self, other: &Self) -> TractResult<(u64, Option<usize>)> {
1614        if self.shape() != other.shape() {
1615            bail!("Shape mismatch {:?} != {:?}", self.shape(), other.shape())
1616        }
1617        let dt = self.ulp_comparison_dt();
1618        let a = self.cast_to_dt(dt)?;
1619        let b = other.cast_to_dt(dt)?;
1620        fn worst<D: Datum + crate::ulp::UlpFloat>(
1621            a: &Tensor,
1622            b: &Tensor,
1623        ) -> TractResult<(u64, Option<usize>)> {
1624            let a = a.to_plain_array_view::<D>()?;
1625            let b = b.to_plain_array_view::<D>()?;
1626            Ok(crate::ulp::max_ulp_distance(a.iter().copied(), b.iter().copied()))
1627        }
1628        match dt {
1629            DatumType::F16 => worst::<f16>(&a, &b),
1630            DatumType::F32 => worst::<f32>(&a, &b),
1631            DatumType::F64 => worst::<f64>(&a, &b),
1632            dt => bail!("No ULP comparison for {dt:?}"),
1633        }
1634    }
1635
1636    /// Compare two tensors by integer ULP distance, accepting a distance up to
1637    /// `max_ulp`.
1638    fn ulp_close_enough(&self, other: &Self, max_ulp: u64) -> TractResult<()> {
1639        let (worst, at) = self.max_ulp_distance(other)?;
1640        if worst <= max_ulp {
1641            return Ok(());
1642        }
1643        let dt = self.ulp_comparison_dt();
1644        let indices = at
1645            .map(|flat| {
1646                let mut rest = flat;
1647                let mut indices = vec![0; self.rank()];
1648                for (ix, dim) in self.shape().iter().enumerate().rev() {
1649                    indices[ix] = rest % dim;
1650                    rest /= dim;
1651                }
1652                format!("{indices:?}")
1653            })
1654            .unwrap_or_else(|| "?".to_string());
1655        let a = self.cast_to::<f64>()?;
1656        let b = other.cast_to::<f64>()?;
1657        let (a, b) = (a.to_plain_array_view::<f64>()?, b.to_plain_array_view::<f64>()?);
1658        let flat = at.unwrap_or(0);
1659        bail!(
1660            "Mismatch. Max ULP distance ({dt:?}): {} > {}, at {} ({} != {}).",
1661            worst,
1662            max_ulp,
1663            indices,
1664            a.iter().nth(flat).copied().unwrap_or(f64::NAN),
1665            b.iter().nth(flat).copied().unwrap_or(f64::NAN),
1666        );
1667    }
1668
1669    /// Transform the tensor into a `ndarray::Array`.
1670    pub fn into_plain_array<D: Datum>(self) -> TractResult<ArrayD<D>> {
1671        Ok(self.to_plain_array_view::<D>()?.to_owned())
1672    }
1673
1674    /// Transform the tensor into a `ndarray::Array`.
1675    pub unsafe fn into_array_unchecked<D: Datum>(self) -> ArrayD<D> {
1676        unsafe { self.to_array_view_unchecked::<D>().to_owned() }
1677    }
1678
1679    /// Returns a plain array view of the tensor.
1680    ///
1681    /// Errors if the storage is not plain or the datum type does not match `D`.
1682    #[inline]
1683    pub fn to_plain_array_view<D: Datum>(&self) -> TractResult<ArrayViewD<'_, D>> {
1684        self.try_as_plain_ram()?.to_array_view::<D>()
1685    }
1686
1687    /// Returns a mutable plain array view of the tensor.
1688    ///
1689    /// Errors if the storage is not plain or the datum type does not match `D`.
1690    #[inline]
1691    pub fn to_plain_array_view_mut<D: Datum>(&mut self) -> TractResult<ArrayViewMutD<'_, D>> {
1692        self.check_for_access::<D>()?;
1693        ensure!(self.storage.as_plain_ram_mut().is_some(), "Tensor storage is not plain");
1694        unsafe { Ok(self.to_array_view_mut_unchecked()) }
1695    }
1696
1697    fn check_for_access<D: Datum>(&self) -> TractResult<()> {
1698        ensure!(
1699            self.datum_type().unquantized() == D::datum_type().unquantized(),
1700            "Tensor datum type error: tensor is {:?}, accessed as {:?}",
1701            self.datum_type(),
1702            D::datum_type(),
1703        );
1704        Ok(())
1705    }
1706
1707    /// Transform the data as a `ndarray::Array`.
1708    pub unsafe fn to_array_view_unchecked<D: Datum>(&self) -> ArrayViewD<'_, D> {
1709        if self.len() != 0 {
1710            unsafe {
1711                ArrayViewD::from_shape_ptr(
1712                    &*self.shape,
1713                    self.plain_ram_storage().as_ptr() as *const D,
1714                )
1715            }
1716        } else {
1717            ArrayViewD::from_shape(&*self.shape, &[]).unwrap()
1718        }
1719    }
1720
1721    /// Transform the data as a mutable `ndarray::Array`.
1722    pub unsafe fn to_array_view_mut_unchecked<D: Datum>(&mut self) -> ArrayViewMutD<'_, D> {
1723        if self.len() != 0 {
1724            unsafe {
1725                let ptr = self.plain_ram_storage_mut().as_mut_ptr() as *mut D;
1726                ArrayViewMutD::from_shape_ptr(&*self.shape, ptr)
1727            }
1728        } else {
1729            ArrayViewMutD::from_shape(&*self.shape, &mut []).unwrap()
1730        }
1731    }
1732
1733    /// Access the data as a pointer.
1734    pub fn as_ptr<D: Datum>(&self) -> TractResult<*const D> {
1735        self.check_for_access::<D>()?;
1736        Ok(self.plain_ram_storage().as_ptr() as *const D)
1737    }
1738
1739    /// Access the data as a pointer.
1740    pub unsafe fn as_ptr_unchecked<D: Datum>(&self) -> *const D {
1741        self.plain_ram_storage().as_ptr() as *const D
1742    }
1743
1744    /// Access the data as a pointer.
1745    pub unsafe fn as_ptr_mut_unchecked<D: Datum>(&mut self) -> *mut D {
1746        self.plain_ram_storage_mut().as_mut_ptr() as *mut D
1747    }
1748
1749    /// Access the data as a mutable pointer.
1750    pub fn as_ptr_mut<D: Datum>(&mut self) -> TractResult<*mut D> {
1751        self.as_ptr::<D>().map(|p| p as *mut D)
1752    }
1753
1754    /// Access the data as a slice.
1755    pub unsafe fn as_slice_unchecked<D: Datum>(&self) -> &[D] {
1756        if self.storage.byte_len() == 0 {
1757            &[]
1758        } else {
1759            unsafe { std::slice::from_raw_parts::<D>(self.as_ptr_unchecked(), self.len()) }
1760        }
1761    }
1762
1763    /// Access the data as a mutable slice.
1764    pub unsafe fn as_slice_mut_unchecked<D: Datum>(&mut self) -> &mut [D] {
1765        if self.storage.byte_len() == 0 {
1766            &mut []
1767        } else {
1768            unsafe { std::slice::from_raw_parts_mut::<D>(self.as_ptr_mut_unchecked(), self.len()) }
1769        }
1770    }
1771
1772    /// Make the tensor a scalar tensor (assumes it contains a single value).
1773    pub fn to_scalar_tensor(&self) -> TractResult<Tensor> {
1774        fn to_scalar_tensor_t<D: Datum>(t: &Tensor) -> TractResult<Tensor> {
1775            Ok(litteral::tensor0(t.try_as_plain_ram()?.to_scalar::<D>()?.clone()))
1776        }
1777        dispatch_datum!(to_scalar_tensor_t(self.datum_type())(self))
1778    }
1779
1780    /// Access the data as a scalar.
1781    pub unsafe fn to_scalar_unchecked<D: Datum>(&self) -> &D {
1782        unsafe { &*(self.plain_ram_storage().as_ptr() as *const D) }
1783    }
1784
1785    /// Mutable access the data as a scalar.
1786    pub fn to_scalar_mut<D: Datum>(&mut self) -> TractResult<&mut D> {
1787        self.check_for_access::<D>()?;
1788        if self.len() == 0 {
1789            bail!("to_scalar_mut called on empty tensor ({:?})", self)
1790        }
1791        if self.len() > 1 {
1792            bail!("to_scalar called on a tensor with multiple values ({:?})", self)
1793        }
1794        unsafe { Ok(self.to_scalar_mut_unchecked()) }
1795    }
1796
1797    /// Mutable access the data as a scalar.
1798    pub unsafe fn to_scalar_mut_unchecked<D: Datum>(&mut self) -> &mut D {
1799        unsafe { &mut *(self.plain_ram_storage_mut().as_mut_ptr() as *mut D) }
1800    }
1801
1802    pub fn as_bytes(&self) -> &[u8] {
1803        self.plain_ram_storage().as_bytes()
1804    }
1805
1806    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
1807        self.plain_ram_storage_mut().as_bytes_mut()
1808    }
1809
1810    unsafe fn is_uniform_t<T: Datum>(&self) -> bool {
1811        let slice = unsafe { self.as_slice_unchecked::<T>() };
1812        slice[1..].iter().all(|x| x == &slice[0])
1813    }
1814
1815    pub fn is_uniform(&self) -> bool {
1816        if self.as_plain_ram_storage().is_none() {
1817            return false;
1818        }
1819        if self.len() <= 1 {
1820            return true;
1821        }
1822        unsafe { dispatch_datum!(Tensor::is_uniform_t(self.datum_type())(self)) }
1823    }
1824
1825    unsafe fn as_uniform_t<T: Datum>(&self) -> Tensor {
1826        let v: T = unsafe { self.as_slice_unchecked::<T>() }[0].clone();
1827        litteral::tensor0(v)
1828    }
1829
1830    pub fn as_uniform(&self) -> Option<Tensor> {
1831        if self.len() >= 1 && self.is_uniform() {
1832            unsafe {
1833                let mut t = dispatch_datum!(Tensor::as_uniform_t(self.datum_type())(self));
1834                t.set_datum_type(self.datum_type());
1835                Some(t)
1836            }
1837        } else {
1838            None
1839        }
1840    }
1841
1842    pub fn is_all_zero(&self) -> TractResult<bool> {
1843        Ok(self.len() == 0 || self.as_uniform().map(|t| t.is_zero().unwrap()).unwrap_or(false))
1844    }
1845
1846    pub fn is_zero(&self) -> TractResult<bool> {
1847        Ok(self == &Tensor::zero_scalar_dt(self.dt)?)
1848    }
1849
1850    unsafe fn natural_cast<
1851        Source: Datum + num_traits::AsPrimitive<Target>,
1852        Target: Datum + Copy,
1853    >(
1854        &self,
1855        other: &mut Tensor,
1856    ) {
1857        unsafe {
1858            self.as_slice_unchecked::<Source>()
1859                .iter()
1860                .zip(other.as_slice_mut_unchecked::<Target>().iter_mut())
1861                .for_each(|(s, d)| *d = s.as_())
1862        };
1863    }
1864
1865    unsafe fn cast_number_to_bool<Source: Datum + num_traits::Zero>(&self, other: &mut Tensor) {
1866        unsafe {
1867            self.as_slice_unchecked::<Source>()
1868                .iter()
1869                .zip(other.as_slice_mut_unchecked::<bool>().iter_mut())
1870                .for_each(|(s, d)| *d = !s.is_zero());
1871        }
1872    }
1873
1874    unsafe fn cast_from_string<Target: Datum + core::str::FromStr>(
1875        &self,
1876        other: &mut Tensor,
1877    ) -> TractResult<()> {
1878        unsafe {
1879            for (s, d) in self
1880                .as_slice_unchecked::<String>()
1881                .iter()
1882                .zip(other.as_slice_mut_unchecked::<Target>().iter_mut())
1883            {
1884                *d = s
1885                    .parse()
1886                    .map_err(|_| format_err!("Can not parse as {:?}", Target::datum_type()))?;
1887            }
1888            Ok(())
1889        }
1890    }
1891
1892    unsafe fn cast_to_string<Source: Datum>(&self, other: &mut Tensor) {
1893        unsafe {
1894            for (s, d) in self
1895                .as_slice_unchecked::<Source>()
1896                .iter()
1897                .zip(other.as_slice_mut_unchecked::<String>().iter_mut())
1898            {
1899                *d = s.to_string()
1900            }
1901        }
1902    }
1903
1904    /// Optionnaly convert data to a tensor for a new DatumType.
1905    pub fn cast_to<D: Datum>(&self) -> TractResult<Cow<'_, Tensor>> {
1906        self.cast_to_dt(D::datum_type())
1907    }
1908
1909    /// Optionnaly convert data to a tensor for a new DatumType.
1910    #[allow(clippy::redundant_closure_call)]
1911    pub fn cast_to_dt(&self, dst_dt: DatumType) -> TractResult<Cow<'_, Tensor>> {
1912        unsafe {
1913            if self.dt == dst_dt {
1914                return Ok(Cow::Borrowed(self));
1915            }
1916            if self.dt == TDim::datum_type() && (dst_dt.is_integer() || dst_dt.is_float()) {
1917                let slice = self.as_slice_unchecked::<TDim>();
1918                let mut ints = Self::uninitialized::<i64>(&self.shape)?;
1919                let ints_slice = ints.as_slice_mut_unchecked::<i64>();
1920                for i in 0..self.len() {
1921                    ints_slice[i] = slice[i].to_i64()?;
1922                }
1923                return Ok(Cow::Owned(ints.cast_to_dt(dst_dt)?.into_owned()));
1924            }
1925            if self.dt == bool::datum_type()
1926                && (dst_dt.is_integer() || dst_dt.is_float() || dst_dt == TDim::datum_type())
1927            {
1928                let slice = self.as_slice_unchecked::<bool>();
1929                let mut ints = Self::uninitialized::<i8>(&self.shape)?;
1930                let ints_slice = ints.as_slice_mut_unchecked::<i8>();
1931                for i in 0..self.len() {
1932                    ints_slice[i] = slice[i] as usize as i8;
1933                }
1934                return Ok(Cow::Owned(ints.cast_to_dt(dst_dt)?.into_owned()));
1935            }
1936            let mut result = Self::uninitialized_dt(dst_dt, &self.shape)?;
1937            if self.dt == DatumType::String {
1938                dispatch_numbers!(Self::cast_from_string(dst_dt)(self, &mut result))?;
1939                return Ok(Cow::Owned(result));
1940            }
1941            if dst_dt == DatumType::String {
1942                dispatch_datum!(Self::cast_to_string(self.dt)(self, &mut result));
1943                return Ok(Cow::Owned(result));
1944            }
1945            macro_rules! n {
1946                ($source:ty) => {
1947                    if <$source>::datum_type() == self.datum_type() {
1948                        match dst_dt {
1949                            DatumType::I8 => self.natural_cast::<$source, i8>(&mut result),
1950                            DatumType::I16 => self.natural_cast::<$source, i16>(&mut result),
1951                            DatumType::I32 => self.natural_cast::<$source, i32>(&mut result),
1952                            DatumType::I64 => self.natural_cast::<$source, i64>(&mut result),
1953                            DatumType::U8 => self.natural_cast::<$source, u8>(&mut result),
1954                            DatumType::U16 => self.natural_cast::<$source, u16>(&mut result),
1955                            DatumType::U32 => self.natural_cast::<$source, u32>(&mut result),
1956                            DatumType::U64 => self.natural_cast::<$source, u64>(&mut result),
1957                            DatumType::F16 => self.natural_cast::<$source, f16>(&mut result),
1958                            DatumType::F32 => self.natural_cast::<$source, f32>(&mut result),
1959                            DatumType::F64 => self.natural_cast::<$source, f64>(&mut result),
1960                            DatumType::TDim => {
1961                                let ints = self.cast_to::<i32>()?;
1962                                let slice = ints.as_slice_unchecked::<i32>();
1963                                let result = result.as_slice_mut_unchecked::<TDim>();
1964                                for i in 0..self.len() {
1965                                    result[i] = slice[i].into();
1966                                }
1967                            }
1968                            DatumType::Bool => self.cast_number_to_bool::<$source>(&mut result),
1969                            _ => todo!(),
1970                        }
1971                        return Ok(Cow::Owned(result));
1972                    };
1973                };
1974            }
1975            //If there is no quantization
1976            if !dst_dt.is_quantized() && !self.datum_type().is_quantized() {
1977                n!(u8);
1978                n!(u16);
1979                n!(u32);
1980                n!(u64);
1981                n!(i8);
1982                n!(i16);
1983                n!(i32);
1984                n!(i64);
1985                n!(f16);
1986                n!(f32);
1987                n!(f64);
1988            } else {
1989                let (s_zp, s_scale) = self.datum_type().zp_scale();
1990                let (d_zp, d_scale) = dst_dt.zp_scale();
1991                if self.datum_type().is_quantized() && dst_dt.is_float() {
1992                    macro_rules! q_to_fp {
1993                        ($source:ty, $dest:ty) => {
1994                            if <$source>::datum_type().unquantized()
1995                                == self.datum_type().unquantized()
1996                                && <$dest>::datum_type().unquantized() == dst_dt.unquantized()
1997                            {
1998                                self.as_slice_unchecked::<$source>()
1999                                    .iter()
2000                                    .zip(result.as_slice_mut_unchecked::<$dest>().iter_mut())
2001                                    .for_each(|(&s, d)| {
2002                                        *d = (s as $dest - s_zp as $dest) * s_scale as $dest;
2003                                    });
2004                                return Ok(Cow::Owned(result));
2005                            }
2006                        };
2007                    }
2008                    q_to_fp!(i8, f64);
2009                    q_to_fp!(i8, f32);
2010                    q_to_fp!(u8, f64);
2011                    q_to_fp!(u8, f32);
2012                }
2013                //TODO: optimize scale_by
2014                macro_rules! q8_to_q8 {
2015                    ($typ:ty) => {
2016                        if dst_dt.unquantized() == <$typ>::datum_type() {
2017                            self.as_slice_unchecked::<$typ>()
2018                                .iter()
2019                                .zip(result.as_slice_mut_unchecked::<$typ>().iter_mut())
2020                                .for_each(|(&s, d)| {
2021                                    *d = (d_zp as i32
2022                                        + scale_by(s as i32 - s_zp as i32, s_scale / d_scale))
2023                                    .clamp_cast()
2024                                });
2025                            return Ok(Cow::Owned(result));
2026                        }
2027                    };
2028                }
2029
2030                macro_rules! q_via_f32 {
2031                    ($source:ty, $dest:ty, $round:expr) => {
2032                        if <$source>::datum_type().unquantized() == self.datum_type().unquantized()
2033                            && <$dest>::datum_type().unquantized() == dst_dt.unquantized()
2034                        {
2035                            self.as_slice_unchecked::<$source>()
2036                                .iter()
2037                                .zip(result.as_slice_mut_unchecked::<$dest>().iter_mut())
2038                                .for_each(|(&s, d)| {
2039                                    let s_float = (s as f32 - s_zp as f32) * s_scale as f32;
2040                                    let d_float = s_float as f32 / d_scale as f32 + d_zp as f32;
2041                                    *d = $round(d_float);
2042                                });
2043                            return Ok(Cow::Owned(result));
2044                        }
2045                    };
2046                }
2047
2048                macro_rules! q_n {
2049                    (clamp $source:ty, $dest:ty) => {{
2050                        if <$source>::datum_type().unquantized() == self.datum_type().unquantized()
2051                            && <$dest>::datum_type().unquantized() == dst_dt.unquantized()
2052                        {
2053                            self.as_slice_unchecked::<$source>()
2054                                .iter()
2055                                .zip(result.as_slice_mut_unchecked::<$dest>().iter_mut())
2056                                .for_each(|(&s, d)| {
2057                                    *d = s.clamp_cast();
2058                                });
2059                            return Ok(Cow::Owned(result));
2060                        }
2061                    }};
2062                    ($source:ty, $dest:ty) => {{
2063                        if <$source>::datum_type().unquantized() == self.datum_type().unquantized()
2064                            && <$dest>::datum_type().unquantized() == dst_dt.unquantized()
2065                        {
2066                            self.as_slice_unchecked::<$source>()
2067                                .iter()
2068                                .zip(result.as_slice_mut_unchecked::<$dest>().iter_mut())
2069                                .for_each(|(&s, d)| {
2070                                    *d = s as $dest;
2071                                });
2072                            return Ok(Cow::Owned(result));
2073                        }
2074                    }};
2075                }
2076
2077                if dst_dt.unquantized() == self.datum_type().unquantized()
2078                    && dst_dt.is_quantized()
2079                    && self.datum_type().is_quantized()
2080                {
2081                    q8_to_q8!(i8);
2082                    q8_to_q8!(u8);
2083                }
2084
2085                q_via_f32!(f32, i8, |f| round_ties_to_even(f).clamp_cast());
2086                q_via_f32!(f32, u8, |f| round_ties_to_even(f).clamp_cast());
2087                q_via_f32!(f32, i32, |f| round_ties_to_even(f).clamp_cast());
2088                q_via_f32!(i8, f32, |f| f);
2089                q_via_f32!(u8, f32, |f| f);
2090                q_via_f32!(i32, f32, |f| f);
2091
2092                if dst_dt.is_quantized() && self.datum_type().is_quantized() {
2093                    q_via_f32!(u8, i8, |f| round_ties_to_even(f).clamp_cast());
2094                    q_via_f32!(i8, u8, |f| round_ties_to_even(f).clamp_cast());
2095                    q_via_f32!(i32, u8, |f| round_ties_to_even(f).clamp_cast());
2096                    q_via_f32!(i32, i8, |f| round_ties_to_even(f).clamp_cast());
2097                    q_via_f32!(u8, i32, |f| round_ties_to_even(f).clamp_cast());
2098                    q_via_f32!(i8, i32, |f| round_ties_to_even(f).clamp_cast());
2099
2100                    // ensure cast to different scale offset work
2101                    q_via_f32!(i8, i8, |f| round_ties_to_even(f).clamp_cast());
2102                    q_via_f32!(u8, u8, |f| round_ties_to_even(f).clamp_cast());
2103                }
2104
2105                q_n!(i8, i32);
2106                q_n!(i8, u32);
2107                q_n!(u8, i32);
2108                q_n!(u8, u32);
2109                q_n!(clamp i32, i8);
2110                q_n!(clamp i32, u8);
2111                q_n!(clamp u32, i8);
2112                q_n!(clamp u32, u8);
2113                q_n!(i8, i8);
2114                q_n!(u8, u8);
2115                q_n!(i32, i32);
2116                q_n!(u32, u32);
2117            }
2118
2119            bail!("Unsupported cast from {:?} to {:?}", self.dt, dst_dt)
2120        }
2121    }
2122
2123    /// Access the data as a scalar, after a cast.
2124    pub fn cast_to_scalar<D: Datum + Copy>(&self) -> TractResult<D> {
2125        let casted = self.cast_to::<D>()?;
2126        casted.try_as_plain_ram()?.to_scalar::<D>().copied()
2127    }
2128
2129    /// Access the nth element of the tensor, returned as a 0-rank Tensor
2130    pub fn nth(&self, nth: usize) -> TractResult<Tensor> {
2131        if nth >= self.len() {
2132            bail!(
2133                "nth called with {}th element on a tensor of len {} ({:?}",
2134                nth,
2135                self.len(),
2136                self
2137            );
2138        }
2139        unsafe fn nth_t<T: Datum>(me: &Tensor, nth: usize, output: &mut Tensor) {
2140            unsafe {
2141                let value = me.as_slice_unchecked::<T>()[nth].clone();
2142                std::ptr::write(output.as_slice_mut_unchecked::<T>().as_mut_ptr(), value);
2143            }
2144        }
2145        unsafe {
2146            let mut output = Tensor::uninitialized_dt(self.datum_type(), &[])?;
2147            dispatch_datum_by_size!(nth_t(self.datum_type())(self, nth, &mut output));
2148            Ok(output)
2149        }
2150    }
2151
2152    /// Strict equality test on tensors.
2153    fn eq_dt(&self, other: &Tensor) -> TractResult<bool> {
2154        unsafe fn eq_t<D: Datum>(me: &Tensor, other: &Tensor) -> TractResult<bool> {
2155            unsafe {
2156                if D::datum_type().is_float() {
2157                    return dispatch_floatlike!(float_eq_t(D::datum_type())(me, other));
2158                }
2159                Ok(izip!(me.as_slice_unchecked::<D>(), other.as_slice_unchecked::<D>())
2160                    .all(|(a, b)| a == b))
2161            }
2162        }
2163
2164        unsafe fn float_eq_t<D: Datum + Float>(me: &Tensor, other: &Tensor) -> TractResult<bool> {
2165            unsafe {
2166                Ok(izip!(me.as_slice_unchecked::<D>(), other.as_slice_unchecked::<D>())
2167                    .all(|(a, b)| (a.is_nan() && b.is_nan()) || a == b))
2168            }
2169        }
2170
2171        unsafe {
2172            Ok(self.datum_type() == other.datum_type()
2173                && self.shape() == other.shape()
2174                && dispatch_datum!(eq_t(self.dt)(self, other))?)
2175        }
2176    }
2177
2178    fn from_datum<T: Datum>(mut it: ArrayD<T>) -> Tensor {
2179        unsafe {
2180            let mut t = Self::uninitialized::<T>(it.shape()).unwrap();
2181            if let Some(slice) = it.as_slice_mut() {
2182                if t.datum_type().is_copy() {
2183                    std::ptr::copy_nonoverlapping(
2184                        slice.as_ptr() as *const i8,
2185                        t.as_ptr_mut_unchecked(),
2186                        t.plain_ram_storage().layout().size(),
2187                    );
2188                } else {
2189                    t.as_slice_mut_unchecked::<T>()
2190                        .iter_mut()
2191                        .zip(slice.iter_mut())
2192                        .for_each(|(t, s)| *t = std::mem::take(s));
2193                }
2194                return t;
2195            }
2196            if it.strides().iter().all(|&s| s > 0) && it.as_slice_memory_order().is_some() {
2197                let mut len_and_strides: TVec<(usize, usize)> = tvec!();
2198                for (len, stride) in itertools::izip!(it.shape(), it.strides(), t.strides())
2199                    .sorted_by_key(|(_, src, _)| *src)
2200                    .map(|(l, _, dst)| (*l as isize, *dst))
2201                {
2202                    if !len_and_strides.is_empty()
2203                        && len_and_strides.last().unwrap().1 * len_and_strides.last().unwrap().0
2204                            == stride as usize
2205                    {
2206                        len_and_strides.last_mut().unwrap().0 *= len as usize;
2207                    } else {
2208                        len_and_strides.push((len as usize, stride as usize));
2209                    }
2210                }
2211                len_and_strides.reverse();
2212                crate::scatter::scatter_contig_data(
2213                    it.as_ptr(),
2214                    t.as_ptr_mut_unchecked(),
2215                    &len_and_strides,
2216                );
2217                return t;
2218            }
2219            // finally use ndarray into_iter()
2220            t.as_slice_mut_unchecked().iter_mut().zip(it).for_each(|(t, a)| *t = a);
2221            t
2222        }
2223    }
2224
2225    pub fn deep_clone(&self) -> Tensor {
2226        if self.as_plain_ram_storage().is_none() {
2227            return Tensor {
2228                dt: self.dt,
2229                shape: self.shape.clone(),
2230                strides: self.strides.clone(),
2231                len: self.len,
2232                storage: self.storage.deep_clone(),
2233            };
2234        }
2235        unsafe {
2236            let mut tensor = Tensor::uninitialized_dt(self.datum_type(), self.shape()).unwrap();
2237            if self.len() > 0 {
2238                if self.dt.is_copy() {
2239                    self.plain_ram_storage().as_ptr().copy_to_nonoverlapping(
2240                        tensor.as_bytes_mut().as_mut_ptr(),
2241                        self.plain_ram_storage().layout().size(),
2242                    )
2243                } else if self.dt == DatumType::String {
2244                    tensor
2245                        .as_slice_mut_unchecked::<String>()
2246                        .clone_from_slice(self.as_slice_unchecked());
2247                } else if self.dt == DatumType::Blob {
2248                    tensor
2249                        .as_slice_mut_unchecked::<Blob>()
2250                        .clone_from_slice(self.as_slice_unchecked());
2251                } else if self.dt == DatumType::TDim {
2252                    tensor
2253                        .as_slice_mut_unchecked::<TDim>()
2254                        .clone_from_slice(self.as_slice_unchecked());
2255                }
2256            }
2257            tensor
2258        }
2259    }
2260
2261    pub fn slice(&self, axis: usize, start: usize, end: usize) -> TractResult<Tensor> {
2262        if axis >= self.rank() {
2263            bail!("Can not slice at axis {} tensor {:?}", axis, self);
2264        }
2265        if start > self.shape[axis] || end > self.shape[axis] || start >= end {
2266            bail!("Invalid slicing range {start}..{end} on axis {axis} for {self:?}");
2267        }
2268        // Storage gets first refusal: one that can serve the slice without a
2269        // copy does so, anything else falls through to the copy below.
2270        if let Some(sliced) =
2271            self.storage.as_storage().slice(self.dt, self.shape(), axis, start, end)?
2272        {
2273            return Ok(sliced);
2274        }
2275        let mut shape: TVec<usize> = self.shape().into();
2276        shape[axis] = end - start;
2277        unsafe {
2278            let mut tensor = Tensor::uninitialized_dt(self.datum_type(), &shape)?;
2279            tensor.assign_slice_from_resolved(&[], 0..end - start, self, &[], start..end, axis);
2280            Ok(tensor)
2281        }
2282    }
2283
2284    #[inline]
2285    pub fn view(&self) -> view::TensorView<'_> {
2286        unsafe { view::TensorView::view(self) }
2287    }
2288
2289    #[inline]
2290    pub fn view_at_prefix(&self, prefix: &[usize]) -> TractResult<view::TensorView<'_>> {
2291        view::TensorView::at_prefix(self, prefix)
2292    }
2293
2294    #[inline]
2295    pub fn view_offsetting(&self, coords: &[usize]) -> TractResult<view::TensorView<'_>> {
2296        view::TensorView::offsetting(self, coords)
2297    }
2298
2299    #[inline]
2300    pub unsafe fn view_offsetting_unchecked(&self, coords: &[usize]) -> view::TensorView<'_> {
2301        unsafe { view::TensorView::offsetting_unchecked(self, coords) }
2302    }
2303
2304    #[inline]
2305    pub fn view_mut(&mut self) -> view::TensorView<'_> {
2306        unsafe { view::TensorView::view(self) }
2307    }
2308
2309    #[inline]
2310    pub fn view_at_prefix_mut(&mut self, prefix: &[usize]) -> TractResult<view::TensorView<'_>> {
2311        view::TensorView::at_prefix(self, prefix)
2312    }
2313
2314    #[inline]
2315    pub fn view_offsetting_mut(&mut self, coords: &[usize]) -> TractResult<view::TensorView<'_>> {
2316        view::TensorView::offsetting(self, coords)
2317    }
2318
2319    /// Offsets the tensor as an i8 type if it's an u8 type, otherwise passes it unchanged.
2320    pub fn offset_u8_as_i8(self: &Arc<Self>) -> Arc<Self> {
2321        let mut t = if let DatumType::U8 = self.dt.unquantized() {
2322            self.try_as_plain_ram()
2323                .unwrap()
2324                .to_array_view::<u8>()
2325                .unwrap()
2326                .mapv(|v| v.wrapping_sub(128) as i8)
2327                .into_tensor()
2328        } else {
2329            return self.clone();
2330        };
2331
2332        if let DatumType::QU8(qp) = self.dt {
2333            if let QParams::ZpScale { zero_point, scale } = qp {
2334                t.dt = DatumType::QI8(QParams::ZpScale { zero_point: zero_point - 128, scale });
2335            } else {
2336                t.dt = DatumType::QI8(qp);
2337            }
2338        }
2339
2340        t.into_arc_tensor()
2341    }
2342
2343    /// Offsets the tensor as an u8 type if it's an i8 type, otherwise passes it unchanged.
2344    pub fn offset_i8_as_u8(self: &Arc<Self>) -> Arc<Self> {
2345        let mut t = if let DatumType::I8 = self.dt.unquantized() {
2346            self.try_as_plain_ram()
2347                .unwrap()
2348                .to_array_view::<i8>()
2349                .unwrap()
2350                .mapv(|v| (v as u8).wrapping_add(128))
2351                .into_tensor()
2352        } else {
2353            return self.clone();
2354        };
2355
2356        if let DatumType::QI8(qp) = self.dt {
2357            if let QParams::ZpScale { zero_point, scale } = qp {
2358                t.dt = DatumType::QU8(QParams::ZpScale { zero_point: zero_point + 128, scale });
2359            } else {
2360                t.dt = DatumType::QU8(qp);
2361            }
2362        }
2363        t.into_arc_tensor()
2364    }
2365
2366    pub fn to_aligned_default(&self) -> TractResult<Self> {
2367        if self.dt.is_copy() {
2368            unsafe {
2369                let mut t = Self::uninitialized_dt(self.dt, &self.shape)?;
2370                t.as_bytes_mut().copy_from_slice(self.as_bytes());
2371                Ok(t)
2372            }
2373        } else {
2374            let mut t = Self::zero_dt(self.dt, &self.shape)?;
2375            if self.dt == String::datum_type() {
2376                t.try_as_plain_ram_mut()?
2377                    .as_slice_mut::<String>()?
2378                    .clone_from_slice(self.try_as_plain_ram()?.as_slice()?);
2379            } else if self.dt == Blob::datum_type() {
2380                t.try_as_plain_ram_mut()?
2381                    .as_slice_mut::<Blob>()?
2382                    .clone_from_slice(self.try_as_plain_ram()?.as_slice()?);
2383            } else if self.dt == TDim::datum_type() {
2384                t.try_as_plain_ram_mut()?
2385                    .as_slice_mut::<TDim>()?
2386                    .clone_from_slice(self.try_as_plain_ram()?.as_slice()?);
2387            }
2388            Ok(t)
2389        }
2390    }
2391
2392    pub fn natural_strides(shape: &[usize]) -> TVec<isize> {
2393        let mut strides = tvec!();
2394        compute_natural_stride_to(&mut strides, shape);
2395        strides
2396    }
2397
2398    /// Returns `true` if this tensor owns plain ram storage outright, rather
2399    /// than storage that produces or holds some -- a device readback that has
2400    /// come back still keeps its device tensor.
2401    #[inline]
2402    pub fn has_plain_ram_storage(&self) -> bool {
2403        matches!(self.storage, StorageKind::Plain(_))
2404    }
2405
2406    /// This tensor backed by plain ram storage of its own: bytes left on a
2407    /// device come back here, and exotic storage, which has no plain form,
2408    /// errors.
2409    pub fn into_plain_ram(mut self) -> TractResult<Tensor> {
2410        if self.has_plain_ram_storage() {
2411            return Ok(self);
2412        }
2413        ensure!(self.dt.is_copy());
2414        let storage =
2415            std::mem::replace(&mut self.storage, StorageKind::Plain(PlainStorage::default()));
2416        let storage = storage.into_plain_ram().context("Storage can not produce plain bytes")?;
2417        Ok(Tensor {
2418            dt: self.dt,
2419            shape: self.shape.clone(),
2420            strides: self.strides.clone(),
2421            len: self.len,
2422            storage: StorageKind::Plain(storage),
2423        })
2424    }
2425
2426    pub fn into_blob(mut self) -> TractResult<Blob> {
2427        ensure!(self.dt.is_copy());
2428        let storage =
2429            std::mem::replace(&mut self.storage, StorageKind::Plain(PlainStorage::default()));
2430        Ok(storage.into_plain_ram().context("Storage is not plain")?.into_blob())
2431    }
2432}
2433
2434impl PartialEq for Tensor {
2435    fn eq(&self, other: &Tensor) -> bool {
2436        if self.dt != other.dt || self.shape != other.shape {
2437            return false;
2438        }
2439        match (self.storage.as_plain_ram(), other.storage.as_plain_ram()) {
2440            (Some(_), Some(_)) => self.eq_dt(other).unwrap_or(false),
2441            (None, None) => self.storage == other.storage,
2442            _ => false,
2443        }
2444    }
2445}
2446
2447impl Eq for Tensor {}
2448
2449impl fmt::Debug for Tensor {
2450    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
2451        let content = self.dump(false).unwrap_or_else(|e| format!("Error : {e:?}"));
2452        write!(formatter, "{content}")
2453    }
2454}
2455
2456#[cfg(feature = "complex")]
2457pub fn reinterpret_inner_dim_as_complex(mut t: Tensor) -> TractResult<Tensor> {
2458    ensure!(
2459        t.shape().last() == Some(&2),
2460        "The last dimension in the tensor shape {:?} must be 2",
2461        t.shape()
2462    );
2463    unsafe {
2464        t.shape.pop();
2465        t.set_datum_type(t.datum_type().complexify()?);
2466        t.update_strides_and_len();
2467        Ok(t)
2468    }
2469}
2470
2471#[cfg(feature = "complex")]
2472pub fn reinterpret_complex_as_inner_dim(mut t: Tensor) -> TractResult<Tensor> {
2473    unsafe {
2474        t.shape.push(2);
2475        t.set_datum_type(t.datum_type().decomplexify()?);
2476        t.update_strides_and_len();
2477        Ok(t)
2478    }
2479}
2480
2481pub fn clip_range_bounds(len: usize, range: impl std::ops::RangeBounds<usize>) -> Range<usize> {
2482    use std::ops::Bound;
2483    let start = match range.start_bound() {
2484        Bound::Included(ix) => *ix,
2485        Bound::Excluded(ix) => ix + 1,
2486        Bound::Unbounded => 0,
2487    };
2488    let end = match range.end_bound() {
2489        Bound::Included(ix) => *ix + 1,
2490        Bound::Excluded(ix) => *ix,
2491        Bound::Unbounded => len,
2492    };
2493    start..end
2494}
2495
2496pub fn natural_strides(shape: &[usize]) -> TVec<isize> {
2497    let mut strides = tvec!();
2498    compute_natural_stride_to(&mut strides, shape);
2499    strides
2500}
2501
2502fn compute_natural_stride_to(strides: &mut TVec<isize>, shape: &[usize]) {
2503    match shape.len() {
2504        0 => (),
2505        1 => strides.push(1),
2506        2 => strides.extend_from_slice(&[shape[1] as isize, 1]),
2507        3 => strides.extend_from_slice(&[(shape[1] * shape[2]) as isize, shape[2] as _, 1]),
2508        4 => strides.extend_from_slice(&[
2509            (shape[1] * shape[2] * shape[3]) as isize,
2510            (shape[2] * shape[3]) as _,
2511            shape[3] as _,
2512            1,
2513        ]),
2514        _ => {
2515            strides.push(1);
2516            for dim in shape.as_ref().iter().skip(1).rev() {
2517                let previous = *strides.last().unwrap();
2518                strides.push(previous * *dim as isize)
2519            }
2520            strides.reverse();
2521        }
2522    }
2523}
2524
2525impl<D: ::ndarray::Dimension, T: Datum> From<Array<T, D>> for Tensor {
2526    fn from(it: Array<T, D>) -> Tensor {
2527        Tensor::from_datum(it.into_dyn())
2528    }
2529}
2530
2531/// Convenient conversion to Tensor.
2532pub trait IntoTensor: Sized {
2533    /// Convert Self to a Tensor.
2534    ///
2535    /// May perform a copy
2536    fn into_tensor(self) -> Tensor;
2537}
2538
2539/// Convenient conversion to Arc<Tensor>.
2540pub trait IntoArcTensor: Sized {
2541    /// Convert Self to a Arc<Tensor>.
2542    ///
2543    /// May perform a copy
2544    fn into_arc_tensor(self) -> Arc<Tensor>;
2545}
2546
2547impl<D: ::ndarray::Dimension, T: Datum> IntoTensor for Array<T, D> {
2548    fn into_tensor(self) -> Tensor {
2549        Tensor::from(self)
2550    }
2551}
2552
2553impl<D: ::ndarray::Dimension, T: Datum> IntoArcTensor for Array<T, D> {
2554    fn into_arc_tensor(self) -> Arc<Tensor> {
2555        Arc::new(Tensor::from(self))
2556    }
2557}
2558
2559impl IntoTensor for Tensor {
2560    fn into_tensor(self) -> Tensor {
2561        self
2562    }
2563}
2564
2565impl IntoTensor for Arc<Tensor> {
2566    fn into_tensor(self) -> Tensor {
2567        Arc::try_unwrap(self).unwrap_or_else(|t| (*t).clone())
2568    }
2569}
2570
2571impl IntoArcTensor for Tensor {
2572    fn into_arc_tensor(self) -> Arc<Tensor> {
2573        Arc::new(self)
2574    }
2575}
2576
2577impl IntoArcTensor for Arc<Tensor> {
2578    fn into_arc_tensor(self) -> Arc<Tensor> {
2579        self
2580    }
2581}
2582
2583#[cfg(test)]
2584mod tests {
2585    use crate::dim::SymbolScope;
2586    use crate::prelude::tensor1;
2587
2588    use super::*;
2589    use litteral::tensor0;
2590    use proptest::collection::vec;
2591    use proptest::prelude::*;
2592
2593    // Regression for sonos/tract#2390: from_raw must reject a content length that
2594    // does not match the declared shape rather than panicking in copy_from_slice.
2595    #[test]
2596    fn from_raw_rejects_length_mismatch() {
2597        // shape [2, 3] of f32 needs 24 bytes; supply 12.
2598        let err = unsafe { Tensor::from_raw_dt(f32::datum_type(), &[2, 3], &[0u8; 12]) }
2599            .expect_err("from_raw must reject a short content buffer, not panic");
2600        assert!(err.to_string().contains("does not match shape"), "unexpected error: {err}");
2601        // Too-long content is rejected as well.
2602        assert!(unsafe { Tensor::from_raw_dt(f32::datum_type(), &[2, 3], &[0u8; 32]) }.is_err());
2603        // Exact match still succeeds.
2604        assert!(unsafe { Tensor::from_raw_dt(f32::datum_type(), &[2, 3], &[0u8; 24]) }.is_ok());
2605    }
2606
2607    #[derive(Debug)]
2608    struct PermuteAxisProblem {
2609        shape: Vec<usize>,
2610        permutation: Vec<usize>,
2611    }
2612
2613    impl Arbitrary for PermuteAxisProblem {
2614        type Strategy = BoxedStrategy<PermuteAxisProblem>;
2615        type Parameters = ();
2616
2617        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
2618            (0..8usize)
2619                .prop_flat_map(|rank| {
2620                    let permute: Vec<usize> = (0..rank).collect();
2621                    (proptest::collection::vec(1..5usize, rank), Just(permute).prop_shuffle())
2622                })
2623                .prop_map(|(shape, permutation)| PermuteAxisProblem { shape, permutation })
2624                .boxed()
2625        }
2626    }
2627
2628    impl PermuteAxisProblem {
2629        fn input(&self) -> ArrayD<i32> {
2630            let mut i = 0;
2631            ArrayD::from_shape_simple_fn(&*self.shape, || {
2632                i += 1;
2633                i
2634            })
2635            .permuted_axes(&*self.permutation)
2636        }
2637
2638        fn reference(&self) -> Tensor {
2639            let values: Vec<i32> = self.input().iter().copied().collect();
2640            let shape = self.permutation.iter().map(|ix| self.shape[*ix]).collect::<TVec<usize>>();
2641            super::litteral::tensor1(&values).into_shape(&shape).unwrap()
2642        }
2643
2644        fn tract(&self) -> Tensor {
2645            Tensor::from(self.input())
2646        }
2647
2648        fn check(&self) -> proptest::test_runner::TestCaseResult {
2649            prop_assert_eq!(self.tract(), self.reference());
2650            Ok(())
2651        }
2652    }
2653
2654    proptest::proptest! {
2655        #[test]
2656        fn prop(pb: PermuteAxisProblem) {
2657            pb.check().unwrap();
2658        }
2659    }
2660
2661    #[test]
2662    fn t_1_2() {
2663        PermuteAxisProblem { shape: vec![2, 1], permutation: vec![1, 0] }.check().unwrap();
2664    }
2665
2666    #[test]
2667    fn t_2_2() {
2668        PermuteAxisProblem { shape: vec![2, 2], permutation: vec![1, 0] }.check().unwrap();
2669    }
2670
2671    fn for_each_shape(rank: usize, max_dim: usize, mut f: impl FnMut(&[usize])) {
2672        let mut shape = vec![1usize; rank];
2673        loop {
2674            f(&shape);
2675            let mut i = rank;
2676            loop {
2677                if i == 0 {
2678                    return;
2679                }
2680                i -= 1;
2681                shape[i] += 1;
2682                if shape[i] <= max_dim {
2683                    break;
2684                }
2685                shape[i] = 1;
2686            }
2687        }
2688    }
2689
2690    fn check_moved_axis<T: Datum + num_traits::NumCast + Copy>(
2691        shape: &[usize],
2692        from: usize,
2693        to: usize,
2694    ) {
2695        let n: usize = shape.iter().product();
2696        let data: Vec<T> = (0..n).map(|i| T::from(i % 251).unwrap()).collect();
2697        let t = Tensor::from_shape::<T>(shape, &data).unwrap();
2698        let mut permutation: Vec<usize> = (0..shape.len()).collect();
2699        permutation.remove(from);
2700        permutation.insert(to, from);
2701        let reference = unsafe {
2702            t.clone().into_array_unchecked::<T>().permuted_axes(&*permutation).into_tensor()
2703        };
2704        let got = t.moved_axis(from, to).unwrap();
2705        assert_eq!(got.shape(), reference.shape(), "{shape:?} {from}->{to}");
2706        assert_eq!(got.as_bytes(), reference.as_bytes(), "{shape:?} {from}->{to}");
2707    }
2708
2709    #[test]
2710    fn moved_axis_matches_permuted_axes() {
2711        for (rank, max_dim) in [(2, 7), (3, 7), (4, 6), (5, 4)] {
2712            for_each_shape(rank, max_dim, |shape| {
2713                for from in 0..rank {
2714                    for to in 0..rank {
2715                        check_moved_axis::<u8>(shape, from, to);
2716                        check_moved_axis::<f16>(shape, from, to);
2717                        check_moved_axis::<f32>(shape, from, to);
2718                        check_moved_axis::<f64>(shape, from, to);
2719                    }
2720                }
2721            });
2722        }
2723    }
2724
2725    #[test]
2726    fn moved_axis_matches_permuted_axes_across_tiles() {
2727        // Past the 16- and 64-cell tile edges, and onto the page-aligned destination-stride rows.
2728        for shape in [
2729            vec![1030usize, 17],
2730            vec![1024, 5],
2731            vec![4096, 3],
2732            vec![2048, 64],
2733            vec![3, 1024, 7],
2734            vec![2, 70, 33],
2735            vec![1, 64, 100, 96],
2736        ] {
2737            for from in 0..shape.len() {
2738                for to in 0..shape.len() {
2739                    check_moved_axis::<u8>(&shape, from, to);
2740                    check_moved_axis::<f16>(&shape, from, to);
2741                    check_moved_axis::<f32>(&shape, from, to);
2742                    check_moved_axis::<f64>(&shape, from, to);
2743                }
2744            }
2745        }
2746    }
2747
2748    #[test]
2749    fn moved_axis_falls_back_on_non_contiguous() {
2750        let mut t =
2751            Tensor::from_shape::<f32>(&[4, 3], &(0..12).map(|i| i as f32).collect_vec()).unwrap();
2752        unsafe { t.set_geometry_unchecked(&[3, 4], &[1, 4]) };
2753        let got = t.moved_axis(0, 1).unwrap();
2754        let reference = unsafe {
2755            t.clone().into_array_unchecked::<f32>().permuted_axes(&[1, 0][..]).into_tensor()
2756        };
2757        assert_eq!(got.shape(), reference.shape());
2758        assert_eq!(got.as_bytes(), reference.as_bytes());
2759    }
2760
2761    #[test]
2762    fn permute_axes_scalar() {
2763        let t = tensor0(1f32);
2764        assert_eq!(t.clone().permute_axes(&[]).unwrap(), t);
2765    }
2766
2767    #[derive(Debug)]
2768    struct BroadcastVecToShape {
2769        vec: Vec<f32>,
2770        axis: usize,
2771        shape: TVec<usize>,
2772    }
2773
2774    impl BroadcastVecToShape {
2775        fn check(&self) -> proptest::test_runner::TestCaseResult {
2776            let input = tensor1(&self.vec);
2777            let mut intermediate = tvec![1usize; self.shape.len()];
2778            intermediate[self.axis] = self.vec.len();
2779            let reference = input
2780                .clone()
2781                .into_shape(&intermediate)
2782                .unwrap()
2783                .broadcast_to_shape(&self.shape)
2784                .unwrap();
2785            prop_assert_eq!(
2786                reference,
2787                input.broadcast_vector_to_shape(&self.shape, self.axis).unwrap()
2788            );
2789            Ok(())
2790        }
2791    }
2792
2793    impl Arbitrary for BroadcastVecToShape {
2794        type Strategy = BoxedStrategy<BroadcastVecToShape>;
2795        type Parameters = ();
2796
2797        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
2798            vec(0usize..5, 0usize..4)
2799                .prop_flat_map(|shape| {
2800                    (vec(-10f32..10f32, 0usize..5), Just(shape.clone()), 0..shape.len() + 1)
2801                })
2802                .prop_map(|(vec, mut shape, axis)| {
2803                    shape.insert(axis, vec.len());
2804                    BroadcastVecToShape { vec, shape: shape.into(), axis }
2805                })
2806                .boxed()
2807        }
2808    }
2809
2810    proptest::proptest! {
2811        #[test]
2812        fn broadcast_vector_to_shape_prop(pb: BroadcastVecToShape) {
2813            pb.check().unwrap()
2814        }
2815    }
2816
2817    #[test]
2818    #[cfg(feature = "complex")]
2819    fn test_reinterpret_inner_dim_as_complex() -> TractResult<()> {
2820        let input = crate::internal::tensor2(&[[1.0f32, 2.0], [3.0, 4.0], [5.0, 6.0]]);
2821        let cplx_input = reinterpret_inner_dim_as_complex(input)?;
2822        let expected = crate::internal::tensor1(&[
2823            Complex::new(1.0f32, 2.0),
2824            Complex::new(3.0, 4.0),
2825            Complex::new(5.0, 6.0),
2826        ]);
2827        assert_eq!(expected, cplx_input);
2828        Ok(())
2829    }
2830
2831    #[test]
2832    #[cfg(feature = "complex")]
2833    fn test_reinterpret_inner_dim_as_complex_2() -> TractResult<()> {
2834        let input =
2835            crate::internal::tensor3(&[[[1i32, 2], [1, 2]], [[3, 4], [3, 4]], [[5, 6], [5, 6]]]);
2836        let cplx_input = reinterpret_inner_dim_as_complex(input)?;
2837        let expected = crate::internal::tensor2(&[
2838            [Complex::new(1i32, 2), Complex::new(1, 2)],
2839            [Complex::new(3, 4), Complex::new(3, 4)],
2840            [Complex::new(5, 6), Complex::new(5, 6)],
2841        ]);
2842        assert_eq!(expected, cplx_input);
2843        Ok(())
2844    }
2845
2846    #[test]
2847    fn clone_tdim_tensor() {
2848        let symbols = SymbolScope::default();
2849        let a = symbols.sym("a");
2850        let t = tensor0(TDim::from(a));
2851        let _ = t.clone();
2852    }
2853
2854    #[test]
2855    fn ulp_approximation_accepts_within_bound() -> TractResult<()> {
2856        let a = tensor1(&[1.0f32, 2.0, 3.0]);
2857        let b = tensor1(&[
2858            f32::from_bits(1.0f32.to_bits() + 1),
2859            2.0,
2860            f32::from_bits(3.0f32.to_bits() + 2),
2861        ]);
2862        a.close_enough(&b, Approximation::Ulp(2))?;
2863        assert!(a.close_enough(&b, Approximation::Ulp(1)).is_err());
2864        assert_eq!(a.max_ulp_distance(&b)?, (2, Some(2)));
2865        Ok(())
2866    }
2867
2868    #[test]
2869    fn ulp_approximation_uses_the_tensor_own_float_type() -> TractResult<()> {
2870        // One f16 rounding step is ~8192 f32 steps. Measuring in f32 would make an
2871        // adjacent-f16 pair look wildly off, so the comparison must stay in f16.
2872        let one = f16::from_f32(1.0);
2873        let a = tensor1(&[one]);
2874        let b = tensor1(&[f16::from_bits(one.to_bits() + 1)]);
2875        assert_eq!(a.ulp_comparison_dt(), DatumType::F16);
2876        assert_eq!(a.max_ulp_distance(&b)?, (1, Some(0)));
2877        a.close_enough(&b, Approximation::Ulp(1))?;
2878        Ok(())
2879    }
2880
2881    #[test]
2882    fn ulp_approximation_is_scale_free() -> TractResult<()> {
2883        // The same relative error at wildly different magnitudes reads the same,
2884        // which a shared atol cannot do.
2885        let a = tensor1(&[1e-30f32, 1e30]);
2886        let b = tensor1(&[
2887            f32::from_bits(1e-30f32.to_bits() + 1),
2888            f32::from_bits(1e30f32.to_bits() + 1),
2889        ]);
2890        a.close_enough(&b, Approximation::Ulp(1))?;
2891        Ok(())
2892    }
2893
2894    #[test]
2895    fn ulp_approximation_rejects_shape_mismatch() {
2896        let a = tensor1(&[1.0f32, 2.0]);
2897        let b = tensor1(&[1.0f32]);
2898        assert!(a.close_enough(&b, Approximation::Ulp(1000)).is_err());
2899    }
2900
2901    // stack_tensors picks between three copy strategies by block size; they must
2902    // all agree with plain index arithmetic. Sizes 1/2/4/8 cover the typed-copy
2903    // dispatch, and a trailing axis of 1 gives the one-datum blocks that
2904    // FastEnhancer's [1, 256, 1, 2] and DTLN's [1, 2, 128, 2] concats produce.
2905    fn stack_reference<T: Datum + Copy + num_traits::Zero>(
2906        axis: usize,
2907        tensors: &[Tensor],
2908    ) -> Tensor {
2909        let mut shape: TVec<usize> = tensors[0].shape().into();
2910        shape[axis] = tensors.iter().map(|t| t.shape()[axis]).sum();
2911        let mut out = Tensor::zero::<T>(&shape).unwrap();
2912        let outer: usize = shape[..axis].iter().product();
2913        let inner: usize = shape[axis + 1..].iter().product();
2914        let mid = shape[axis];
2915        let ov = unsafe { out.as_slice_mut_unchecked::<T>() };
2916        let mut base = 0;
2917        for t in tensors {
2918            let m = t.shape()[axis];
2919            let tv = unsafe { t.as_slice_unchecked::<T>() };
2920            for o in 0..outer {
2921                for j in 0..m {
2922                    for i in 0..inner {
2923                        ov[(o * mid + base + j) * inner + i] = tv[(o * m + j) * inner + i];
2924                    }
2925                }
2926            }
2927            base += m;
2928        }
2929        out
2930    }
2931
2932    fn ramp<T: Datum + Copy + From<u8>>(shape: &[usize], seed: u8) -> Tensor {
2933        let n: usize = shape.iter().product();
2934        let v: Vec<T> = (0..n).map(|i| T::from(seed.wrapping_add(i as u8))).collect();
2935        Tensor::from_shape(shape, &v).unwrap()
2936    }
2937
2938    macro_rules! stack_agrees_for {
2939        ($name:ident, $t:ty) => {
2940            #[test]
2941            fn $name() {
2942                for shape in [
2943                    tvec!(1usize, 256, 1, 1),
2944                    tvec!(1usize, 2, 128, 1),
2945                    tvec!(1usize, 35, 35, 8),
2946                    tvec!(4usize, 3),
2947                    tvec!(7usize),
2948                ] {
2949                    for axis in 0..shape.len() {
2950                        let a: Tensor = ramp::<$t>(&shape, 1);
2951                        let b: Tensor = ramp::<$t>(&shape, 100);
2952                        let c: Tensor = ramp::<$t>(&shape, 200);
2953                        for n in 1..=3 {
2954                            let ins = [a.clone(), b.clone(), c.clone()][..n].to_vec();
2955                            let got = Tensor::stack_tensors(axis, &ins).unwrap();
2956                            let want = stack_reference::<$t>(axis, &ins);
2957                            assert_eq!(got, want, "shape {shape:?} axis {axis} n {n}");
2958                        }
2959                    }
2960                }
2961            }
2962        };
2963    }
2964
2965    stack_agrees_for!(stack_tensors_agrees_u8, u8);
2966    stack_agrees_for!(stack_tensors_agrees_u16, u16);
2967    stack_agrees_for!(stack_tensors_agrees_u32, u32);
2968    stack_agrees_for!(stack_tensors_agrees_u64, u64);
2969
2970    // A zero extent before the concatenated axis makes `outer` zero; the block
2971    // path divides by it, so it must not be taken.
2972    #[test]
2973    fn stack_tensors_tolerates_a_zero_outer_extent() {
2974        let a = Tensor::zero::<f32>(&[0, 2, 3]).unwrap();
2975        let stacked = Tensor::stack_tensors(2, &[a.clone(), a.clone()]).unwrap();
2976        assert_eq!(stacked.shape(), &[0, 2, 6]);
2977    }
2978
2979    // assign_slice copies one contiguous run per coordinate of the axes before
2980    // `axis`; the runs must agree with plain index arithmetic for every axis,
2981    // including the trailing one where each run is a single datum.
2982    fn assign_slice_reference<T: Datum + Copy>(
2983        dst: &Tensor,
2984        dst_range: Range<usize>,
2985        src: &Tensor,
2986        src_range: Range<usize>,
2987        axis: usize,
2988    ) -> Tensor {
2989        let mut out = dst.clone();
2990        let outer: usize = dst.shape()[..axis].iter().product();
2991        let inner: usize = dst.shape()[axis + 1..].iter().product();
2992        let dst_mid = dst.shape()[axis];
2993        let src_mid = src.shape()[axis];
2994        let sv = unsafe { src.as_slice_unchecked::<T>() };
2995        let ov = unsafe { out.as_slice_mut_unchecked::<T>() };
2996        for o in 0..outer {
2997            for j in 0..dst_range.len() {
2998                for i in 0..inner {
2999                    ov[(o * dst_mid + dst_range.start + j) * inner + i] =
3000                        sv[(o * src_mid + src_range.start + j) * inner + i];
3001                }
3002            }
3003        }
3004        out
3005    }
3006
3007    macro_rules! assign_slice_agrees_for {
3008        ($name:ident, $t:ty) => {
3009            #[test]
3010            fn $name() {
3011                for (shape, axis, dst_mid, src_mid, dst_start, len, src_start) in [
3012                    (tvec!(1usize, 56, 24), 2, 24, 8, 16, 8, 0),
3013                    (tvec!(1usize, 56, 24), 2, 24, 24, 0, 16, 8),
3014                    (tvec!(1usize, 32, 4, 128), 3, 128, 128, 0, 64, 64),
3015                    (tvec!(1usize, 8, 16, 64), 2, 16, 1, 3, 1, 0),
3016                    (tvec!(3usize, 5), 0, 3, 7, 1, 2, 4),
3017                    (tvec!(4usize, 3), 1, 3, 3, 0, 3, 0),
3018                    (tvec!(7usize), 0, 7, 7, 2, 0, 5),
3019                ] {
3020                    let mut dst_shape = shape.clone();
3021                    dst_shape[axis] = dst_mid;
3022                    let mut src_shape = shape.clone();
3023                    src_shape[axis] = src_mid;
3024                    let mut got: Tensor = ramp::<$t>(&dst_shape, 1);
3025                    let src: Tensor = ramp::<$t>(&src_shape, 100);
3026                    let want = assign_slice_reference::<$t>(
3027                        &got,
3028                        dst_start..dst_start + len,
3029                        &src,
3030                        src_start..src_start + len,
3031                        axis,
3032                    );
3033                    got.assign_slice(
3034                        dst_start..dst_start + len,
3035                        &src,
3036                        src_start..src_start + len,
3037                        axis,
3038                    )
3039                    .unwrap();
3040                    assert_eq!(got, want, "shape {dst_shape:?} axis {axis}");
3041                }
3042            }
3043        };
3044    }
3045
3046    assign_slice_agrees_for!(assign_slice_agrees_u8, u8);
3047    assign_slice_agrees_for!(assign_slice_agrees_u16, u16);
3048    assign_slice_agrees_for!(assign_slice_agrees_u32, u32);
3049    assign_slice_agrees_for!(assign_slice_agrees_u64, u64);
3050
3051    // The prefixed assign copies the same runs inside one sub-tensor of each
3052    // side, and the prefixed axes are free to differ in extent.
3053    macro_rules! assign_slice_at_prefix_agrees_for {
3054        ($name:ident, $t:ty) => {
3055            #[test]
3056            fn $name() {
3057                for (shape, prefix, src_lead, src_prefix, axis, start, len, src_start) in [
3058                    (tvec!(3usize, 5, 7), tvec!(2usize), 4, tvec!(3usize), 2, 3, 4, 0),
3059                    (tvec!(3usize, 5, 7), tvec!(0usize), 1, tvec!(0usize), 1, 1, 3, 2),
3060                    (tvec!(2usize, 4, 8, 3), tvec!(1usize, 2), 2, tvec!(0usize, 1), 3, 0, 3, 0),
3061                    (tvec!(4usize, 6), tvec!(), 4, tvec!(), 1, 2, 4, 2),
3062                ] {
3063                    let mut src_shape = shape.clone();
3064                    src_shape[0] = src_lead;
3065                    let mut got: Tensor = ramp::<$t>(&shape, 1);
3066                    let src: Tensor = ramp::<$t>(&src_shape, 100);
3067                    // A prefixed assign is the same assign between the two
3068                    // sub-tensors, which a slice materializes.
3069                    let mut want_sub = sub_tensor(&got, &prefix);
3070                    want_sub
3071                        .assign_slice(
3072                            start..start + len,
3073                            &sub_tensor(&src, &src_prefix),
3074                            src_start..src_start + len,
3075                            axis - prefix.len(),
3076                        )
3077                        .unwrap();
3078                    got.assign_slice_at_prefix(
3079                        &prefix,
3080                        start..start + len,
3081                        &src,
3082                        &src_prefix,
3083                        src_start..src_start + len,
3084                        axis,
3085                    )
3086                    .unwrap();
3087                    assert_eq!(
3088                        sub_tensor(&got, &prefix),
3089                        want_sub,
3090                        "shape {shape:?} prefix {prefix:?} axis {axis}"
3091                    );
3092                }
3093            }
3094        };
3095    }
3096
3097    fn sub_tensor(t: &Tensor, prefix: &[usize]) -> Tensor {
3098        let mut sub = t.clone();
3099        for ix in prefix {
3100            sub = sub.slice(0, *ix, ix + 1).unwrap();
3101            sub.remove_axis(0).unwrap();
3102        }
3103        sub
3104    }
3105
3106    assign_slice_at_prefix_agrees_for!(assign_slice_at_prefix_agrees_u8, u8);
3107    assign_slice_at_prefix_agrees_for!(assign_slice_at_prefix_agrees_u16, u16);
3108    assign_slice_at_prefix_agrees_for!(assign_slice_at_prefix_agrees_u32, u32);
3109    assign_slice_at_prefix_agrees_for!(assign_slice_at_prefix_agrees_u64, u64);
3110
3111    // fill_slice writes one contiguous run per coordinate of the axes between
3112    // the prefix and `axis`; the runs must agree with plain index arithmetic,
3113    // including on the trailing axis where each run is a single datum.
3114    fn fill_slice_reference<T: Datum + Copy>(
3115        data: &Tensor,
3116        prefix: &[usize],
3117        range: Range<usize>,
3118        value: T,
3119        axis: usize,
3120    ) -> Tensor {
3121        let mut out = data.clone();
3122        let shape = data.shape().to_vec();
3123        let inner: usize = shape[axis + 1..].iter().product();
3124        let mid = shape[axis];
3125        let outer: usize = shape[prefix.len()..axis].iter().product();
3126        let at: usize = izip!(prefix, data.strides()).map(|(ix, s)| ix * *s as usize).sum();
3127        let ov = unsafe { out.as_slice_mut_unchecked::<T>() };
3128        for o in 0..outer {
3129            for j in range.clone() {
3130                for i in 0..inner {
3131                    ov[at + (o * mid + j) * inner + i] = value;
3132                }
3133            }
3134        }
3135        out
3136    }
3137
3138    macro_rules! fill_slice_agrees_for {
3139        ($name:ident, $t:ty) => {
3140            #[test]
3141            fn $name() {
3142                for (shape, prefix, axis, start, len) in [
3143                    (tvec!(1usize, 56, 24), tvec!(), 2, 16, 8),
3144                    (tvec!(3usize, 5, 7), tvec!(), 1, 1, 3),
3145                    (tvec!(3usize, 5, 7), tvec!(2usize), 2, 3, 4),
3146                    (tvec!(3usize, 5, 7), tvec!(1usize, 4), 2, 0, 7),
3147                    (tvec!(4usize, 3), tvec!(), 0, 1, 2),
3148                    (tvec!(2usize, 8, 16, 64), tvec!(1usize), 2, 3, 1),
3149                    (tvec!(7usize), tvec!(), 0, 2, 0),
3150                ] {
3151                    let value: $t = 42 as $t;
3152                    let mut got: Tensor = ramp::<$t>(&shape, 1);
3153                    let want =
3154                        fill_slice_reference::<$t>(&got, &prefix, start..start + len, value, axis);
3155                    got.fill_slice_at_prefix(&prefix, start..start + len, &tensor0(value), axis)
3156                        .unwrap();
3157                    assert_eq!(got, want, "shape {shape:?} prefix {prefix:?} axis {axis}");
3158                }
3159            }
3160        };
3161    }
3162
3163    fill_slice_agrees_for!(fill_slice_agrees_u8, u8);
3164    fill_slice_agrees_for!(fill_slice_agrees_u16, u16);
3165    fill_slice_agrees_for!(fill_slice_agrees_u32, u32);
3166    fill_slice_agrees_for!(fill_slice_agrees_u64, u64);
3167
3168    #[test]
3169    fn fill_slice_carries_non_copy_data() {
3170        let strings = |v: [&str; 6]| {
3171            ndarray::Array2::from_shape_vec((2, 3), v.iter().map(|s| s.to_string()).collect())
3172                .unwrap()
3173                .into_tensor()
3174        };
3175        let mut data = strings(["a", "b", "c", "d", "e", "f"]);
3176        data.fill_slice(1..3, &tensor0("x".to_string()), 1).unwrap();
3177        assert_eq!(data, strings(["a", "x", "x", "d", "x", "x"]));
3178    }
3179
3180    #[test]
3181    fn assign_slice_carries_non_copy_data() {
3182        let strings = |v: [&str; 6]| {
3183            ndarray::Array2::from_shape_vec((2, 3), v.iter().map(|s| s.to_string()).collect())
3184                .unwrap()
3185                .into_tensor()
3186        };
3187        let mut dst = strings(["a", "b", "c", "d", "e", "f"]);
3188        let src = ndarray::Array2::from_shape_vec((2, 1), vec!["x".to_string(), "y".to_string()])
3189            .unwrap()
3190            .into_tensor();
3191        dst.assign_slice(1..2, &src, 0..1, 1).unwrap();
3192        assert_eq!(dst, strings(["a", "x", "c", "d", "y", "f"]));
3193    }
3194
3195    // The run-based broadcast must agree with the ndarray view it replaced, over
3196    // leading, middle and trailing broadcast axes and over ranks that grow.
3197    #[test]
3198    fn broadcast_to_shape_agrees_with_the_view() {
3199        for (src, dst) in [
3200            (tvec!(1usize, 8, 1, 7, 4), tvec!(1usize, 8, 4, 7, 4)),
3201            (tvec!(1usize, 1, 1, 7), tvec!(2usize, 3, 5, 7)),
3202            (tvec!(4usize), tvec!(2usize, 3, 5, 4)),
3203            (tvec!(1usize, 5, 3), tvec!(6usize, 5, 3)),
3204            (tvec!(2usize, 3), tvec!(2usize, 3)),
3205            (tvec!(1usize), tvec!(3usize, 1, 2)),
3206            (tvec!(3usize, 1), tvec!(3usize, 0)),
3207            // Innermost-axis broadcasts take the repeated-cell path: one contiguous run of
3208            // one value per cell, and cells that differ only on the axes below the walk.
3209            (tvec!(3usize, 1), tvec!(3usize, 5)),
3210            (tvec!(2usize, 1, 1), tvec!(2usize, 4, 3)),
3211            (tvec!(1usize, 16, 1, 1), tvec!(1usize, 16, 4, 4)),
3212            (tvec!(1usize, 1), tvec!(2usize, 3, 4)),
3213            (tvec!(1024usize, 1, 1), tvec!(1024usize, 4, 1)),
3214        ] {
3215            for dt in [f32::datum_type(), u8::datum_type(), i32::datum_type()] {
3216                let t = Tensor::zero_dt(dt, &src).unwrap().cast_to_dt(dt).unwrap().into_owned();
3217                let got = t.broadcast_to_shape(&dst).unwrap();
3218                let want = dispatch_datum!(Tensor::broadcast_to_shape_t(dt)(&t, &dst)).unwrap();
3219                assert_eq!(got.shape(), &*dst, "{src:?} -> {dst:?}");
3220                assert_eq!(got, want, "{src:?} -> {dst:?} {dt:?}");
3221            }
3222        }
3223    }
3224
3225    /// The repeated-cell path is only correct if every cell lands on the source element its
3226    /// coordinates name, so check it against the reference on values rather than on zeros.
3227    #[test]
3228    fn broadcast_to_shape_repeats_innermost_cells_with_the_right_value() {
3229        for (src, dst) in [
3230            (tvec!(3usize, 1), tvec!(3usize, 5)),
3231            (tvec!(2usize, 1, 1), tvec!(2usize, 4, 3)),
3232            (tvec!(1usize, 16, 1, 1), tvec!(1usize, 16, 4, 4)),
3233            (tvec!(1usize, 1), tvec!(2usize, 3, 4)),
3234            (tvec!(1024usize, 1, 1), tvec!(1024usize, 4, 1)),
3235            (tvec!(4usize, 1, 7), tvec!(4usize, 3, 7)),
3236            (tvec!(2usize, 3, 1, 1), tvec!(2usize, 3, 5, 6)),
3237        ] {
3238            let t = ramp::<u32>(&src, 0);
3239            let got = t.broadcast_to_shape(&dst).unwrap();
3240            let want = Tensor::broadcast_to_shape_t::<u32>(&t, &dst).unwrap();
3241            assert_eq!(got, want, "{src:?} -> {dst:?}");
3242        }
3243    }
3244
3245    #[test]
3246    fn broadcast_to_shape_carries_values_and_rejects_mismatches() {
3247        let t = tensor2(&[[1u8, 2, 3], [4, 5, 6]]);
3248        let got = t.clone().into_shape(&[2, 1, 3]).unwrap().broadcast_to_shape(&[2, 2, 3]).unwrap();
3249        assert_eq!(got, tensor3(&[[[1u8, 2, 3], [1, 2, 3]], [[4, 5, 6], [4, 5, 6]]]));
3250        assert!(t.broadcast_to_shape(&[3, 3]).is_err());
3251        assert!(t.broadcast_to_shape(&[3]).is_err());
3252    }
3253
3254    #[test]
3255    fn slice_keeps_the_datum_type_and_the_values() {
3256        let t = ramp::<u32>(&tvec!(2usize, 3, 4), 0);
3257        let got = t.slice(1, 1, 3).unwrap();
3258        let mut want = Tensor::zero::<u32>(&[2, 2, 4]).unwrap();
3259        want.assign_slice(0..2, &t, 1..3, 1).unwrap();
3260        assert_eq!(got, want);
3261        let quantized = Tensor::zero_dt(
3262            i8::datum_type().quantize(QParams::ZpScale { zero_point: 3, scale: 0.5 }),
3263            &[2, 4],
3264        )
3265        .unwrap();
3266        assert_eq!(quantized.slice(1, 0, 2).unwrap().datum_type(), quantized.datum_type());
3267    }
3268
3269    #[test]
3270    fn ulp_bounds_are_distinguished_by_equality() {
3271        assert_eq!(Approximation::Ulp(1), Approximation::Ulp(1));
3272        assert_ne!(Approximation::Ulp(1), Approximation::Ulp(2));
3273    }
3274
3275    /// Storage that keeps its bytes "elsewhere" until asked, standing in for a
3276    /// device-backed one so the seam can be tested without a GPU.
3277    #[derive(Debug)]
3278    struct LateStorage {
3279        elsewhere: Vec<u8>,
3280        here: std::sync::OnceLock<PlainStorage>,
3281        materializations: std::sync::atomic::AtomicUsize,
3282    }
3283
3284    impl LateStorage {
3285        fn new(bytes: &[u8]) -> LateStorage {
3286            LateStorage {
3287                elsewhere: bytes.to_vec(),
3288                here: std::sync::OnceLock::new(),
3289                materializations: std::sync::atomic::AtomicUsize::new(0),
3290            }
3291        }
3292        fn count(t: &Tensor) -> usize {
3293            t.storage_as::<LateStorage>()
3294                .unwrap()
3295                .materializations
3296                .load(std::sync::atomic::Ordering::Relaxed)
3297        }
3298    }
3299
3300    impl PartialEq for LateStorage {
3301        fn eq(&self, other: &Self) -> bool {
3302            self.elsewhere == other.elsewhere
3303        }
3304    }
3305    impl Eq for LateStorage {}
3306
3307    impl std::fmt::Display for LateStorage {
3308        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3309            write!(f, "LateStorage")
3310        }
3311    }
3312
3313    impl TensorStorage for LateStorage {
3314        fn byte_len(&self) -> usize {
3315            self.elsewhere.len()
3316        }
3317        fn is_empty(&self) -> bool {
3318            self.elsewhere.is_empty()
3319        }
3320        fn deep_clone(&self) -> Box<dyn TensorStorage> {
3321            Box::new(LateStorage::new(&self.elsewhere))
3322        }
3323        fn as_plain_ram(&self) -> Option<&PlainStorage> {
3324            self.here.get()
3325        }
3326        fn as_plain_ram_mut(&mut self) -> Option<&mut PlainStorage> {
3327            None
3328        }
3329        fn into_plain_ram(self: Box<Self>) -> Option<PlainStorage> {
3330            let me = *self;
3331            me.materialize_plain_ram().ok()?;
3332            me.here.into_inner()
3333        }
3334        fn dyn_hash(&self, _state: &mut dyn std::hash::Hasher) {}
3335        fn exotic_fact(&self, _shape: &[usize]) -> TractResult<Option<Box<dyn ExoticFact>>> {
3336            Ok(None)
3337        }
3338        fn is_exotic(&self) -> bool {
3339            false
3340        }
3341        fn in_ram(&self) -> bool {
3342            self.here.get().is_some()
3343        }
3344        fn materialize_plain_ram(&self) -> TractResult<&PlainStorage> {
3345            if let Some(here) = self.here.get() {
3346                return Ok(here);
3347            }
3348            self.materializations.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3349            let blob = Blob::from_bytes(&self.elsewhere)?;
3350            Ok(self.here.get_or_init(|| PlainStorage::from(blob)))
3351        }
3352        fn slice(
3353            &self,
3354            dt: DatumType,
3355            shape: &[usize],
3356            axis: usize,
3357            start: usize,
3358            end: usize,
3359        ) -> TractResult<Option<Tensor>> {
3360            // Only the outermost axis is a contiguous byte range here.
3361            if axis != 0 || shape[..axis].iter().product::<usize>() != 1 {
3362                return Ok(None);
3363            }
3364            let row = shape[1..].iter().product::<usize>() * dt.size_of();
3365            let mut sliced: TVec<usize> = shape.into();
3366            sliced[0] = end - start;
3367            Ok(Some(Tensor::from_storage(
3368                dt,
3369                &sliced,
3370                LateStorage::new(&self.elsewhere[start * row..end * row]),
3371            )))
3372        }
3373    }
3374
3375    fn late_tensor(shape: &[usize], values: &[f32]) -> Tensor {
3376        let host = Tensor::from_shape(shape, values).unwrap();
3377        Tensor::from_storage(f32::datum_type(), shape, LateStorage::new(host.as_bytes()))
3378    }
3379
3380    #[test]
3381    fn late_storage_stays_put_until_the_bytes_are_read() {
3382        let t = late_tensor(&[2, 3], &[1f32, 2., 3., 4., 5., 6.]);
3383        // Predicates must not drag the bytes back.
3384        assert!(!t.in_ram());
3385        assert!(t.as_plain_ram().is_none());
3386        assert_eq!(t.datum_type(), f32::datum_type());
3387        assert_eq!(t.shape(), &[2, 3]);
3388        assert_eq!(LateStorage::count(&t), 0);
3389        // Reading them does, once.
3390        assert_eq!(
3391            t.try_as_plain_ram().unwrap().as_slice::<f32>().unwrap(),
3392            &[1f32, 2., 3., 4., 5., 6.]
3393        );
3394        assert_eq!(LateStorage::count(&t), 1);
3395        assert_eq!(t.try_as_plain_ram().unwrap().as_slice::<f32>().unwrap()[0], 1f32);
3396        assert_eq!(LateStorage::count(&t), 1);
3397    }
3398
3399    #[test]
3400    fn late_storage_is_plain_but_not_in_ram_until_asked() {
3401        let t = late_tensor(&[2, 3], &[1f32, 2., 3., 4., 5., 6.]);
3402        assert!(t.is_plain());
3403        assert!(!t.in_ram());
3404        let t = t.into_plain_ram().unwrap();
3405        assert!(t.in_ram());
3406        assert!(t.has_plain_ram_storage());
3407        assert_eq!(
3408            t.try_as_plain_ram().unwrap().as_slice::<f32>().unwrap(),
3409            &[1f32, 2., 3., 4., 5., 6.]
3410        );
3411    }
3412
3413    #[test]
3414    fn late_storage_slices_without_materializing_when_it_can() {
3415        let t = late_tensor(&[2, 3], &[1f32, 2., 3., 4., 5., 6.]);
3416        let row = t.slice(0, 1, 2).unwrap();
3417        assert_eq!(LateStorage::count(&t), 0);
3418        assert!(row.storage_as::<LateStorage>().is_some());
3419        assert_eq!(row.shape(), &[1, 3]);
3420        assert_eq!(row.try_as_plain_ram().unwrap().as_slice::<f32>().unwrap(), &[4f32, 5., 6.]);
3421    }
3422
3423    #[test]
3424    fn late_storage_falls_back_to_a_copy_on_a_gappy_slice() {
3425        let t = late_tensor(&[2, 3], &[1f32, 2., 3., 4., 5., 6.]);
3426        let col = t.slice(1, 1, 3).unwrap();
3427        // Storage refused, so the generic path copied: material, and correct.
3428        assert!(col.in_ram());
3429        assert_eq!(col.shape(), &[2, 2]);
3430        assert_eq!(col.try_as_plain_ram().unwrap().as_slice::<f32>().unwrap(), &[2f32, 3., 5., 6.]);
3431        assert_eq!(LateStorage::count(&t), 1);
3432    }
3433}