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