Skip to main content

hanzo_ml/
tensor.rs

1//! Tensors are N-dimensional matrixes of elements using a single data type.
2#![allow(clippy::redundant_closure_call)]
3use crate::backend::{BackendDevice, BackendStorage};
4use crate::op::{BackpropOp, BinaryOp, CmpOp, Op, ReduceOp, UnaryOp};
5use crate::scalar::TensorOrScalar;
6use crate::shape::{Dim, Dims, ShapeWithOneHole};
7use crate::storage::{StorageMutRef, StorageRef};
8use crate::{bail, storage::Storage, DType, Device, Error, Layout, Result, Shape};
9use parking_lot::RwLock;
10use std::sync::Arc;
11
12/// Unique identifier for tensors.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14pub struct TensorId(usize);
15
16impl TensorId {
17    fn new() -> Self {
18        // https://users.rust-lang.org/t/idiomatic-rust-way-to-generate-unique-id/33805
19        use std::sync::atomic;
20        static COUNTER: atomic::AtomicUsize = atomic::AtomicUsize::new(1);
21        Self(COUNTER.fetch_add(1, atomic::Ordering::Relaxed))
22    }
23}
24
25pub struct Tensor_ {
26    id: TensorId,
27    // As we provide inner mutability on the tensor content, the alternatives are:
28    // - Using a mutex, this would have the highest cost when retrieving the storage but would
29    //   prevent errors when concurrent access takes place. Mutex would also be subject to
30    //   deadlocks for example using the current code if the same tensor is used twice by a single
31    //   binary op.
32    // - Using a refcell unsafe cell would have some intermediary cost, borrow checking would be
33    //   verified dynamically, but the resulting tensors would not be send or sync.
34    // - Using an unsafe cell would have the lowest cost but undefined behavior on concurrent
35    //   accesses.
36    // Ideally, we would use Arc<Storage> for tensors on which we don't plan on modifying the data
37    // and Arc<Mutex<Storage>> for tensors where the data could be modified, e.g. variables but
38    // that's tricky to encode in the current setup.
39    storage: Arc<RwLock<Storage>>,
40    layout: Layout,
41    op: BackpropOp,
42    is_variable: bool,
43    dtype: DType,
44    device: Device,
45}
46
47impl AsRef<Tensor> for Tensor {
48    fn as_ref(&self) -> &Tensor {
49        self
50    }
51}
52
53// Tensors are refcounted so that cloning is cheap when building the op graph.
54// Storages are also refcounted independently so that its possible to avoid
55// copying the storage for operations that only modify the shape or stride.
56#[derive(Clone)]
57/// The core struct for manipulating tensors.
58///
59/// ```rust
60/// use hanzo_ml::{Tensor, DType, Device};
61///
62/// let a = Tensor::arange(0f32, 6f32, &Device::Cpu)?.reshape((2, 3))?;
63/// let b = Tensor::arange(0f32, 12f32, &Device::Cpu)?.reshape((3, 4))?;
64///
65/// let c = a.matmul(&b)?;
66/// # Ok::<(), hanzo_ml::Error>(())
67/// ```
68///
69/// Tensors are reference counted with [`Arc`] so cloning them is cheap.
70pub struct Tensor(Arc<Tensor_>);
71
72impl std::ops::Deref for Tensor {
73    type Target = Tensor_;
74
75    fn deref(&self) -> &Self::Target {
76        self.0.as_ref()
77    }
78}
79
80macro_rules! unary_op {
81    ($fn_name:ident, $op_name:ident) => {
82        pub fn $fn_name(&self) -> Result<Self> {
83            let shape = self.shape();
84            if shape.elem_count() == 0 {
85                return Ok(self.clone());
86            }
87            let storage = self
88                .storage()
89                .unary_impl::<crate::op::$op_name>(self.layout())?;
90            let op = BackpropOp::new1(self, |s| Op::Unary(s, UnaryOp::$op_name));
91            Ok(from_storage(storage, shape.clone(), op, false))
92        }
93    };
94}
95
96macro_rules! binary_op {
97    ($fn_name:ident, $op_name:ident) => {
98        pub fn $fn_name(&self, rhs: &Self) -> Result<Self> {
99            let shape = self.same_shape_binary_op(rhs, stringify!($fn_name))?;
100            if shape.elem_count() == 0 {
101                return Ok(self.clone());
102            }
103            let storage = self.storage().binary_impl::<crate::op::$op_name>(
104                &*rhs.storage(),
105                self.layout(),
106                rhs.layout(),
107            )?;
108            let op = BackpropOp::new2(self, rhs, |t1, t2| Op::Binary(t1, t2, BinaryOp::$op_name));
109            Ok(from_storage(storage, shape.clone(), op, false))
110        }
111    };
112}
113
114macro_rules! binary_op_scalar {
115    ($fn_name:ident, $op_name:ident) => {
116        pub fn $fn_name<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
117            let rhs = match rhs.to_tensor_scalar()? {
118                crate::scalar::TensorScalar::Tensor(rhs) => rhs,
119                crate::scalar::TensorScalar::Scalar(rhs) => rhs
120                    .to_dtype(self.dtype())?
121                    .to_device(self.device())?
122                    .broadcast_as(self.shape())?,
123            };
124            let shape = self.same_shape_binary_op(&rhs, stringify!($fn_name))?;
125            if self.elem_count() == 0 {
126                return Ok(self.clone());
127            }
128            let storage = self.storage().binary_impl::<crate::op::$op_name>(
129                &*rhs.storage(),
130                self.layout(),
131                rhs.layout(),
132            )?;
133            let op = BackpropOp::new2(self, &rhs, |t1, t2| Op::Binary(t1, t2, BinaryOp::$op_name));
134            Ok(from_storage(storage, shape.clone(), op, false))
135        }
136    };
137}
138
139macro_rules! broadcast_binary_op {
140    ($fn_name:ident, $inner_fn_name:ident) => {
141        pub fn $fn_name(&self, rhs: &Self) -> Result<Self> {
142            let lhs = self;
143            let shape = lhs
144                .shape()
145                .broadcast_shape_binary_op(rhs.shape(), stringify!($fn_name))?;
146            let l_broadcast = shape != *lhs.shape();
147            let r_broadcast = shape != *rhs.shape();
148            match (l_broadcast, r_broadcast) {
149                (true, true) => lhs
150                    .broadcast_as(&shape)?
151                    .$inner_fn_name(&rhs.broadcast_as(&shape)?),
152                (false, true) => lhs.$inner_fn_name(&rhs.broadcast_as(&shape)?),
153                (true, false) => lhs.broadcast_as(&shape)?.$inner_fn_name(rhs),
154                (false, false) => lhs.$inner_fn_name(rhs),
155            }
156        }
157    };
158}
159
160/// Creates a fresh tensor structure based on a storage and a shape, this uses contiguous strides.
161pub(crate) fn from_storage<S: Into<Shape>>(
162    storage: Storage,
163    shape: S,
164    op: BackpropOp,
165    is_variable: bool,
166) -> Tensor {
167    let dtype = storage.dtype();
168    let device = storage.device();
169    let tensor_ = Tensor_ {
170        id: TensorId::new(),
171        storage: Arc::new(RwLock::new(storage)),
172        layout: Layout::contiguous(shape),
173        op,
174        is_variable,
175        dtype,
176        device,
177    };
178    Tensor(Arc::new(tensor_))
179}
180
181impl Tensor {
182    pub(crate) fn ones_impl<S: Into<Shape>>(
183        shape: S,
184        dtype: DType,
185        device: &Device,
186        is_variable: bool,
187    ) -> Result<Self> {
188        let none = BackpropOp::none();
189        let shape = shape.into();
190        let mut storage = unsafe { device.alloc_uninit(&shape, dtype)? };
191        let layout = Layout::contiguous(shape.clone());
192        storage.const_set(crate::scalar::Scalar::one(dtype), &layout)?;
193        Ok(from_storage(storage, shape, none, is_variable))
194    }
195
196    /// Creates a new tensor filled with ones.
197    ///
198    /// ```rust
199    /// use hanzo_ml::{Tensor, DType, Device};
200    /// let a = Tensor::ones((2, 3), DType::F32, &Device::Cpu)?;
201    /// let b = Tensor::from_slice(&[1.0f32, 1.0, 1.0, 1.0, 1.0, 1.0], (2, 3), &Device::Cpu)?;
202    /// // a == b
203    /// # Ok::<(), hanzo_ml::Error>(())
204    /// ```
205    pub fn ones<S: Into<Shape>>(shape: S, dtype: DType, device: &Device) -> Result<Self> {
206        Self::ones_impl(shape, dtype, device, false)
207    }
208
209    pub fn const_set(&self, value: crate::scalar::Scalar) -> Result<()> {
210        self.storage_mut().const_set(value, self.layout())
211    }
212
213    pub fn zero_set(&self) -> Result<()> {
214        self.const_set(crate::scalar::Scalar::zero(self.dtype()))
215    }
216
217    pub fn one_set(&self) -> Result<()> {
218        self.const_set(crate::scalar::Scalar::one(self.dtype()))
219    }
220
221    /// Creates a new tensor filled with ones with same shape, dtype, and device as the other tensor.
222    ///
223    /// ```rust
224    /// use hanzo_ml::{Tensor, DType, Device};
225    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
226    /// let b = a.ones_like()?;
227    /// // b == a + 1
228    /// # Ok::<(), hanzo_ml::Error>(())
229    /// ```
230    pub fn ones_like(&self) -> Result<Self> {
231        Tensor::ones(self.shape(), self.dtype(), self.device())
232    }
233
234    // Do not expose outside of the crate, the `is_variable=true` case should only be accessed from
235    // the variable module.
236    pub(crate) fn zeros_impl<S: Into<Shape>>(
237        shape: S,
238        dtype: DType,
239        device: &Device,
240        is_variable: bool,
241    ) -> Result<Self> {
242        let none = BackpropOp::none();
243        let shape = shape.into();
244        let storage = device.zeros(&shape, dtype)?;
245        Ok(from_storage(storage, shape, none, is_variable))
246    }
247
248    /// Creates a new tensor filled with zeros.
249    ///
250    /// ```rust
251    /// use hanzo_ml::{Tensor, DType, Device};
252    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
253    /// let b = Tensor::from_slice(&[0.0f32, 0.0, 0.0, 0.0, 0.0, 0.0], (2, 3), &Device::Cpu)?;
254    /// // a == b
255    /// # Ok::<(), hanzo_ml::Error>(())
256    /// ```
257    pub fn zeros<S: Into<Shape>>(shape: S, dtype: DType, device: &Device) -> Result<Self> {
258        Self::zeros_impl(shape, dtype, device, false)
259    }
260
261    /// Creates a new tensor filled with zeros with same shape, dtype, and device as the other
262    /// tensor.
263    ///
264    /// ```rust
265    /// use hanzo_ml::{Tensor, DType, Device};
266    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
267    /// let b = a.zeros_like()?;
268    /// // b is on CPU f32.
269    /// # Ok::<(), hanzo_ml::Error>(())
270    /// ```
271    pub fn zeros_like(&self) -> Result<Self> {
272        Tensor::zeros(self.shape(), self.dtype(), self.device())
273    }
274
275    // Do not expose outside of the crate, the `is_variable=true` case should only be accessed from
276    // the variable module.
277    pub(crate) unsafe fn empty_impl<S: Into<Shape>>(
278        shape: S,
279        dtype: DType,
280        device: &Device,
281        is_variable: bool,
282    ) -> Result<Self> {
283        let none = BackpropOp::none();
284        let shape = shape.into();
285        let storage = device.alloc_uninit(&shape, dtype)?;
286        Ok(from_storage(storage, shape, none, is_variable))
287    }
288
289    /// Creates a new tensor filled with uninitialized memory.
290    ///
291    /// # Safety
292    /// This returns uninitialized memory.
293    ///
294    /// ```rust
295    /// use hanzo_ml::{Tensor, DType, Device};
296    /// let a = unsafe { Tensor::empty((2, 3), DType::F32, &Device::Cpu)? };
297    /// // a == b
298    /// # Ok::<(), hanzo_ml::Error>(())
299    /// ```
300    pub unsafe fn empty<S: Into<Shape>>(shape: S, dtype: DType, device: &Device) -> Result<Self> {
301        Self::empty_impl(shape, dtype, device, false)
302    }
303
304    /// Creates a new tensor filled with uninitialized memory of the same shape, dtype, and device as the other
305    /// tensor.
306    ///
307    /// # Safety
308    /// This returns uninitialized memory.
309    ///
310    /// ```rust
311    /// use hanzo_ml::{Tensor, DType, Device};
312    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
313    /// let b = unsafe { a.empty_like()? };
314    /// # Ok::<(), hanzo_ml::Error>(())
315    /// ```
316    pub unsafe fn empty_like(&self) -> Result<Self> {
317        Tensor::empty(self.shape(), self.dtype(), self.device())
318    }
319
320    pub(crate) fn rand_impl<S: Into<Shape>, T: crate::FloatDType>(
321        lo: T,
322        up: T,
323        s: S,
324        device: &Device,
325        is_variable: bool,
326    ) -> Result<Self> {
327        let s = s.into();
328        let storage = device.rand_uniform(lo, up, &s)?;
329        let none = BackpropOp::none();
330        Ok(from_storage(storage, s, none, is_variable))
331    }
332
333    pub(crate) fn rand_f64_impl<S: Into<Shape>>(
334        lo: f64,
335        up: f64,
336        s: S,
337        dtype: DType,
338        device: &Device,
339        is_variable: bool,
340    ) -> Result<Self> {
341        let s = s.into();
342        let storage = device.rand_uniform_f64(lo, up, &s, dtype)?;
343        let none = BackpropOp::none();
344        Ok(from_storage(storage, s, none, is_variable))
345    }
346
347    /// Creates a new tensor initialized with values sampled uniformly between `lo` and `up`.
348    pub fn rand<S: Into<Shape>, T: crate::FloatDType>(
349        lo: T,
350        up: T,
351        s: S,
352        device: &Device,
353    ) -> Result<Self> {
354        Self::rand_impl(lo, up, s, device, false)
355    }
356
357    pub fn rand_like(&self, lo: f64, up: f64) -> Result<Self> {
358        Tensor::rand_f64_impl(lo, up, self.shape(), self.dtype(), self.device(), false)
359    }
360
361    pub(crate) fn randn_impl<S: Into<Shape>, T: crate::FloatDType>(
362        mean: T,
363        std: T,
364        s: S,
365        device: &Device,
366        is_variable: bool,
367    ) -> Result<Self> {
368        let s = s.into();
369        let storage = device.rand_normal(mean, std, &s)?;
370        let none = BackpropOp::none();
371        Ok(from_storage(storage, s, none, is_variable))
372    }
373
374    pub(crate) fn randn_f64_impl<S: Into<Shape>>(
375        mean: f64,
376        std: f64,
377        s: S,
378        dtype: DType,
379        device: &Device,
380        is_variable: bool,
381    ) -> Result<Self> {
382        let s = s.into();
383        let storage = device.rand_normal_f64(mean, std, &s, dtype)?;
384        let none = BackpropOp::none();
385        Ok(from_storage(storage, s, none, is_variable))
386    }
387
388    pub fn randn_like(&self, mean: f64, stdev: f64) -> Result<Self> {
389        Tensor::randn_f64_impl(
390            mean,
391            stdev,
392            self.shape(),
393            self.dtype(),
394            self.device(),
395            false,
396        )
397    }
398
399    /// Creates a new tensor initialized with values sampled from a normal distribution with the
400    /// specified `mean` and standard deviation `std`.
401    pub fn randn<S: Into<Shape>, T: crate::FloatDType>(
402        mean: T,
403        std: T,
404        s: S,
405        device: &Device,
406    ) -> Result<Self> {
407        Self::randn_impl(mean, std, s, device, false)
408    }
409
410    pub(crate) fn new_impl<A: crate::device::NdArray>(
411        array: A,
412        shape: Shape,
413        device: &Device,
414        is_variable: bool,
415    ) -> Result<Self> {
416        let n: usize = shape.elem_count();
417        let buffer_size: usize = array.shape()?.elem_count();
418        if buffer_size != n {
419            return Err(Error::ShapeMismatch { buffer_size, shape }.bt());
420        }
421        let storage = device.storage(array)?;
422        let none = BackpropOp::none();
423        Ok(from_storage(storage, shape, none, is_variable))
424    }
425
426    /// Creates a new tensor on the specified device using the content and shape of the input.
427    pub fn new<A: crate::device::NdArray>(array: A, device: &Device) -> Result<Self> {
428        let shape = array.shape()?;
429        Self::new_impl(array, shape, device, false)
430    }
431
432    /// Returns a new tensor with all the elements having the same specified value.
433    ///```rust
434    /// use hanzo_ml::{Tensor, Device};
435    /// let a = Tensor::full(3.5, (2, 4), &Device::Cpu)?;
436    ///
437    /// assert_eq!(a.to_vec2::<f64>()?, &[
438    ///     [3.5, 3.5, 3.5, 3.5],
439    ///     [3.5, 3.5, 3.5, 3.5],
440    /// ]);
441    /// # Ok::<(), hanzo_ml::Error>(())
442    pub fn full<D: crate::WithDType, S: Into<Shape>>(
443        value: D,
444        shape: S,
445        device: &Device,
446    ) -> Result<Self> {
447        let none = BackpropOp::none();
448        let shape = shape.into();
449        let mut storage = unsafe { device.alloc_uninit(&shape, D::DTYPE)? };
450        let layout = Layout::contiguous(shape.clone());
451        storage.const_set(value.to_scalar(), &layout)?;
452        Ok(from_storage(storage, shape, none, false))
453    }
454
455    /// Creates a new 1D tensor from an iterator.
456    ///```rust
457    /// use hanzo_ml::{Tensor, Device};
458    /// let a = Tensor::from_iter( [1.0, 2.0, 3.0, 4.0].into_iter(), &Device::Cpu)?;
459    ///
460    /// assert_eq!(a.to_vec1::<f64>()?, &[1.0, 2.0, 3.0, 4.0]);
461    /// # Ok::<(), hanzo_ml::Error>(())
462    /// ```
463    pub fn from_iter<D: crate::WithDType>(
464        iter: impl IntoIterator<Item = D>,
465        device: &Device,
466    ) -> Result<Self> {
467        let data = iter.into_iter().collect::<Vec<_>>();
468        let len = data.len();
469        Self::from_vec_impl(data, len, device, false)
470    }
471
472    /// Creates a new 1D tensor with values from the interval `[start, end)` taken with a common
473    /// difference `1` from `start`.
474    ///```rust
475    /// use hanzo_ml::{Tensor, Device};
476    /// let a = Tensor::arange(2., 5., &Device::Cpu)?;
477    ///
478    /// assert_eq!(a.to_vec1::<f64>()?, &[2., 3., 4.]);
479    /// # Ok::<(), hanzo_ml::Error>(())
480    /// ```
481    pub fn arange<D: crate::WithDType>(start: D, end: D, device: &Device) -> Result<Self> {
482        Self::arange_step(start, end, D::one(), device)
483    }
484
485    /// Creates a new 1D tensor with values from the interval `[start, end)` taken with a common
486    /// difference `step` from `start`.
487    ///```rust
488    /// use hanzo_ml::{Tensor, Device};
489    /// let a = Tensor::arange_step(2.0, 4.0, 0.5, &Device::Cpu)?;
490    ///
491    /// assert_eq!(a.to_vec1::<f64>()?, &[2.0, 2.5, 3.0, 3.5]);
492    /// # Ok::<(), hanzo_ml::Error>(())
493    /// ```
494    pub fn arange_step<D: crate::WithDType>(
495        start: D,
496        end: D,
497        step: D,
498        device: &Device,
499    ) -> Result<Self> {
500        if D::is_zero(&step) {
501            bail!("step cannot be zero")
502        }
503        let mut data = vec![];
504        let mut current = start;
505        if step >= D::zero() {
506            while current < end {
507                data.push(current);
508                current += step;
509            }
510        } else {
511            while current > end {
512                data.push(current);
513                current += step;
514            }
515        }
516        let len = data.len();
517        Self::from_vec_impl(data, len, device, false)
518    }
519
520    pub(crate) fn from_vec_impl<S: ShapeWithOneHole, D: crate::WithDType>(
521        data: Vec<D>,
522        shape: S,
523        device: &Device,
524        is_variable: bool,
525    ) -> Result<Self> {
526        let shape = shape.into_shape(data.len())?;
527        let storage = device.storage_owned(data)?;
528        let none = BackpropOp::none();
529        Ok(from_storage(storage, shape, none, is_variable))
530    }
531
532    /// Creates a new tensor initialized with values from the input vector. The number of elements
533    /// in this vector must be the same as the number of elements defined by the shape.
534    /// If the device is cpu, no data copy is made.
535    ///```rust
536    /// use hanzo_ml::{Tensor, Device};
537    /// let a = Tensor::from_vec(vec!{1., 2., 3., 4., 5., 6.}, (2, 3), &Device::Cpu)?;
538    ///
539    /// assert_eq!(a.to_vec2::<f64>()?, &[
540    ///     [1., 2., 3.],
541    ///     [4., 5., 6.]
542    /// ]);
543    /// # Ok::<(), hanzo_ml::Error>(())
544    /// ```
545    pub fn from_vec<S: ShapeWithOneHole, D: crate::WithDType>(
546        data: Vec<D>,
547        shape: S,
548        device: &Device,
549    ) -> Result<Self> {
550        Self::from_vec_impl(data, shape, device, false)
551    }
552
553    /// Creates a new tensor initialized with values from the input slice. The number of elements
554    /// in this vector must be the same as the number of elements defined by the shape.
555    ///```rust
556    /// use hanzo_ml::{Tensor, Device};
557    /// let values = vec![1., 2., 3., 4., 5., 6., 7., 8.];
558    /// let a = Tensor::from_slice(&values[1..7], (2, 3), &Device::Cpu)?;
559    ///
560    /// assert_eq!(a.to_vec2::<f64>()?, &[
561    ///     [2., 3., 4.],
562    ///     [5., 6., 7.]
563    /// ]);
564    /// # Ok::<(), hanzo_ml::Error>(())
565    /// ```
566    pub fn from_slice<S: ShapeWithOneHole, D: crate::WithDType>(
567        array: &[D],
568        shape: S,
569        device: &Device,
570    ) -> Result<Self> {
571        let shape = shape.into_shape(array.len())?;
572        let storage = device.storage_from_slice(array)?;
573        let none = BackpropOp::none();
574        Ok(from_storage(storage, shape, none, false))
575    }
576
577    pub(crate) fn same_shape_binary_op(&self, rhs: &Self, op: &'static str) -> Result<&Shape> {
578        let lhs = self.shape();
579        let rhs = rhs.shape();
580        if lhs != rhs {
581            Err(Error::ShapeMismatchBinaryOp {
582                lhs: lhs.clone(),
583                rhs: rhs.clone(),
584                op,
585            }
586            .bt())
587        } else {
588            Ok(lhs)
589        }
590    }
591
592    /// Returns true if the computation graph should track this op, that is if it is
593    /// a variable or if it has some variable as dependencies.
594    pub fn track_op(&self) -> bool {
595        self.is_variable || self.op.is_some()
596    }
597
598    /// Creates a fresh tensor structure based on a storage and a shape.
599    ///
600    /// # Note
601    /// - This uses contiguous strides
602    /// - Ensure the shape is compatible with the shape of the storage.
603    pub fn from_storage<S: Into<Shape>>(
604        storage: Storage,
605        shape: S,
606        op: BackpropOp,
607        is_variable: bool,
608    ) -> Tensor {
609        from_storage(storage, shape, op, is_variable)
610    }
611
612    // TODO: Also make an inplace version or a pre-allocated? This could be tricky
613    // if this can create cycles in the compute graph.
614    binary_op!(add, Add);
615    binary_op!(mul, Mul);
616    binary_op!(sub, Sub);
617    binary_op!(div, Div);
618    binary_op_scalar!(maximum, Maximum);
619    binary_op_scalar!(minimum, Minimum);
620    broadcast_binary_op!(broadcast_add, add);
621    broadcast_binary_op!(broadcast_mul, mul);
622    broadcast_binary_op!(broadcast_sub, sub);
623    broadcast_binary_op!(broadcast_div, div);
624    broadcast_binary_op!(broadcast_maximum, maximum);
625    broadcast_binary_op!(broadcast_minimum, minimum);
626    broadcast_binary_op!(broadcast_eq, eq);
627    broadcast_binary_op!(broadcast_ne, ne);
628    broadcast_binary_op!(broadcast_lt, lt);
629    broadcast_binary_op!(broadcast_le, le);
630    broadcast_binary_op!(broadcast_gt, gt);
631    broadcast_binary_op!(broadcast_ge, ge);
632
633    unary_op!(recip, Recip);
634    unary_op!(neg, Neg);
635    unary_op!(exp, Exp);
636    unary_op!(log, Log);
637    unary_op!(sin, Sin);
638    unary_op!(cos, Cos);
639    unary_op!(tanh, Tanh);
640    unary_op!(abs, Abs);
641    unary_op!(sqr, Sqr);
642    unary_op!(sqrt, Sqrt);
643    unary_op!(gelu, Gelu);
644    unary_op!(gelu_erf, GeluErf);
645    unary_op!(erf, Erf);
646    unary_op!(relu, Relu);
647    unary_op!(silu, Silu);
648    unary_op!(ceil, Ceil);
649    unary_op!(floor, Floor);
650    unary_op!(round, Round);
651    unary_op!(sign, Sign);
652
653    /// Round element of the input tensor to the nearest integer.
654    ///
655    /// If the number of decimals is negative, it specifies the number of positions to the left of
656    /// the decimal point.
657    pub fn round_to(&self, decimals: i32) -> Result<Self> {
658        let mult = 10f64.powi(decimals);
659        (self * mult)?.round()? * (1f64 / mult)
660    }
661
662    /// Retrieves the single scalar value hold in the tensor. If the tensor contains multiple
663    /// dimensions, an error is returned instead.
664    pub fn to_scalar<S: crate::WithDType>(&self) -> Result<S> {
665        if self.rank() != 0 {
666            Err(Error::UnexpectedNumberOfDims {
667                expected: 0,
668                got: self.rank(),
669                shape: self.shape().clone(),
670            }
671            .bt())?
672        }
673        let from_cpu_storage = |cpu_storage: &crate::CpuStorage| {
674            let data = S::cpu_storage_as_slice(cpu_storage)?;
675            Ok::<_, Error>(data[self.layout().start_offset()])
676        };
677        match &*self.storage() {
678            Storage::Cpu(cpu_storage) => from_cpu_storage(cpu_storage),
679            Storage::Cuda(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
680            Storage::Metal(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
681            #[cfg(feature = "rocm")]
682            Storage::Rocm(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
683            #[cfg(feature = "vulkan")]
684            Storage::Vulkan(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
685            #[cfg(feature = "wgpu")]
686            Storage::Wgpu(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
687        }
688    }
689
690    /// An alias for `to_scalar`.
691    pub fn to_vec0<S: crate::WithDType>(&self) -> Result<S> {
692        self.to_scalar::<S>()
693    }
694
695    /// Repeat this tensor along the specified dimensions.
696    pub fn repeat<S: Into<Shape>>(&self, shape: S) -> Result<Tensor> {
697        // Similar to PyTorch, we extend the number of dimensions of self if needed.
698        let repeats = shape.into();
699        let repeats = repeats.dims();
700        let mut inp = if self.rank() < repeats.len() {
701            let shape = [vec![1; repeats.len() - self.rank()], self.dims().to_vec()].concat();
702            self.reshape(shape)?
703        } else {
704            self.clone()
705        };
706        for (idx, &repeat) in repeats.iter().enumerate() {
707            inp = match repeat {
708                0 => inp.narrow(idx, 0, 0)?,
709                1 => inp,
710                repeat => Tensor::cat(&vec![&inp; repeat], idx)?,
711            };
712        }
713        Ok(inp)
714    }
715
716    /// Creates grids of coordinates specified by the 1D inputs.
717    ///
718    /// # Arguments
719    ///
720    /// * `args` - A slice of 1D tensors.
721    /// * `xy_indexing` - Whether to use xy indexing or ij indexing. If xy is selected, the
722    ///   first dimension corresponds to the cardinality of the second input and the second
723    ///   dimension corresponds to the cardinality of the first input. If ij is selected, the
724    ///   dimensions are in the same order as the cardinality of the inputs.
725    ///
726    /// # Examples
727    ///
728    /// ```rust
729    /// use hanzo_ml::{Tensor, Device, Shape};
730    /// let x = Tensor::new(&[1f32, 2., 3.], &Device::Cpu)?;
731    /// let y = Tensor::new(&[4f32, 5., 6.], &Device::Cpu)?;
732    ///
733    /// let grids_xy = Tensor::meshgrid(&[&x, &y], true)?;
734    ///
735    /// assert_eq!(grids_xy.len(), 2);
736    /// assert_eq!(grids_xy[0].dims(), &[3, 3]);
737    ///
738    /// assert_eq!(grids_xy[0].to_vec2::<f32>()?, &[[1., 2., 3.], [1., 2., 3.], [1., 2., 3.]]);
739    /// assert_eq!(grids_xy[1].to_vec2::<f32>()?, &[[4., 4., 4.], [5., 5., 5.], [6., 6., 6.]]);
740    ///
741    /// let grids_ij = Tensor::meshgrid(&[&x, &y], false)?;
742    ///
743    /// assert_eq!(grids_ij[0].to_vec2::<f32>()?, &[[1., 1., 1.], [2., 2., 2.], [3., 3., 3.]]);
744    /// assert_eq!(grids_ij[1].to_vec2::<f32>()?, &[[4., 5., 6.], [4., 5., 6.], [4., 5., 6.]]);
745    /// # Ok::<(), hanzo_ml::Error>(())
746    /// ```
747    ///
748    /// # Errors
749    ///
750    /// * Will return `Err` if `args` contains less than 2 tensors.
751    ///
752    pub fn meshgrid<A: AsRef<Tensor>>(args: &[A], xy_indexing: bool) -> Result<Vec<Self>> {
753        if args.len() <= 1 {
754            Err(Error::OpRequiresAtLeastTwoTensors { op: "meshgrid" }.bt())?
755        }
756        let args: Vec<_> = if xy_indexing {
757            args.iter().rev().collect()
758        } else {
759            args.iter().collect()
760        };
761
762        let mut shape = Vec::with_capacity(args.len());
763        for arg in args.iter() {
764            shape.push(arg.as_ref().dims1()?)
765        }
766
767        let mut grids = Vec::with_capacity(args.len());
768        for idx in 0..args.len() {
769            let mut ones = vec![1usize; args.len()];
770            ones[idx] = shape[idx];
771            let arg = args[idx].as_ref().reshape(ones)?;
772            let mut repeats = shape.clone();
773            repeats[idx] = 1;
774            let repeated_tensor = arg.repeat(repeats)?;
775            grids.push(repeated_tensor);
776        }
777        if xy_indexing {
778            grids.reverse();
779        }
780        Ok(grids)
781    }
782
783    /// This operation multiplies the input tensor by `mul` then adds `add` and return the result.
784    /// The input values `mul` and `add` are casted to the appropriate type so some rounding might
785    /// be performed.
786    ///
787    /// ```rust
788    /// use hanzo_ml::{Tensor, Device};
789    /// let a = Tensor::new(&[[0f32, 1.], [2., 3.]], &Device::Cpu)?;
790    /// let a = a.affine(4., -2.)?;
791    /// assert_eq!(a.to_vec2::<f32>()?, &[[-2.0, 2.0], [6.0, 10.0]]);
792    /// # Ok::<(), hanzo_ml::Error>(())
793    /// ```
794    pub fn affine(&self, mul: f64, add: f64) -> Result<Self> {
795        if self.elem_count() == 0 {
796            return Ok(self.clone());
797        }
798        let storage = self.storage().affine(self.layout(), mul, add)?;
799        let op = BackpropOp::new1(self, |arg| Op::Affine { arg, mul, add });
800        Ok(from_storage(storage, self.shape(), op, false))
801    }
802
803    /// Applies the Exponential Linear Unit (ELU) function on each element of the input tensor.
804    pub fn elu(&self, alpha: f64) -> Result<Self> {
805        if self.elem_count() == 0 {
806            return Ok(self.clone());
807        }
808        let storage = self.storage().elu(self.layout(), alpha)?;
809        let op = BackpropOp::new1(self, |t| Op::Elu(t, alpha));
810        Ok(from_storage(storage, self.shape(), op, false))
811    }
812
813    /// Raise the tensor to some float exponent `e`.
814    pub fn powf(&self, e: f64) -> Result<Self> {
815        if self.elem_count() == 0 {
816            return Ok(self.clone());
817        }
818        let storage = self.storage().powf(self.layout(), e)?;
819        let op = BackpropOp::new1(self, |t| Op::Powf(t, e));
820        Ok(from_storage(storage, self.shape(), op, false))
821    }
822
823    pub(crate) fn check_dim(&self, dim: usize, op: &'static str) -> Result<()> {
824        if dim >= self.dims().len() {
825            Err(Error::DimOutOfRange {
826                shape: self.shape().clone(),
827                dim: dim as i32,
828                op,
829            }
830            .bt())?
831        } else {
832            Ok(())
833        }
834    }
835
836    /// Split a tensor into the specified number of chunks, this may return less chunks than
837    /// specified.
838    pub fn chunk<D: Dim>(&self, chunks: usize, dim: D) -> Result<Vec<Self>> {
839        let dim = dim.to_index(self.shape(), "chunk")?;
840        let size = self.dim(dim)?;
841        if size < chunks {
842            (0..size).map(|i| self.narrow(dim, i, 1)).collect()
843        } else {
844            let chunk_size = size / chunks;
845            let cnt_additional = size % chunks;
846            let mut tensors = vec![];
847            let mut sum_chunk_size = 0;
848            for i in 0..chunks {
849                let chunk_size = if i < cnt_additional {
850                    chunk_size + 1
851                } else {
852                    chunk_size
853                };
854                let tensor = self.narrow(dim, sum_chunk_size, chunk_size)?;
855                tensors.push(tensor);
856                sum_chunk_size += chunk_size
857            }
858            Ok(tensors)
859        }
860    }
861
862    /// Returns a new tensor that is a narrowed version of the input, the dimension `dim`
863    /// ranges from `start` to `start + len`.
864    /// ```
865    /// use hanzo_ml::{Tensor, Device};
866    /// let a = Tensor::new(&[
867    ///     [0f32, 1., 2.],
868    ///     [3.  , 4., 5.],
869    ///     [6.  , 7., 8.]
870    /// ], &Device::Cpu)?;
871    ///
872    /// let b = a.narrow(0, 1, 2)?;
873    /// assert_eq!(b.shape().dims(), &[2, 3]);
874    /// assert_eq!(b.to_vec2::<f32>()?, &[
875    ///     [3., 4., 5.],
876    ///     [6., 7., 8.]
877    /// ]);
878    ///
879    /// let c = a.narrow(1, 1, 1)?;
880    /// assert_eq!(c.shape().dims(), &[3, 1]);
881    /// assert_eq!(c.to_vec2::<f32>()?, &[
882    ///     [1.],
883    ///     [4.],
884    ///     [7.]
885    /// ]);
886    /// # Ok::<(), hanzo_ml::Error>(())
887    /// ```
888    pub fn narrow<D: Dim>(&self, dim: D, start: usize, len: usize) -> Result<Self> {
889        let dims = self.dims();
890        let dim = dim.to_index(self.shape(), "narrow")?;
891        let err = |msg| {
892            Err::<(), _>(
893                Error::NarrowInvalidArgs {
894                    shape: self.shape().clone(),
895                    dim,
896                    start,
897                    len,
898                    msg,
899                }
900                .bt(),
901            )
902        };
903        if start > dims[dim] {
904            err("start > dim_len")?
905        }
906        if start.saturating_add(len) > dims[dim] {
907            err("start + len > dim_len")?
908        }
909        if start == 0 && dims[dim] == len {
910            Ok(self.clone())
911        } else {
912            let op = BackpropOp::new1(self, |t| Op::Narrow(t, dim, start, len));
913            let layout = self.layout().narrow(dim, start, len)?;
914            let tensor_ = Tensor_ {
915                id: TensorId::new(),
916                storage: self.storage.clone(),
917                layout,
918                op,
919                is_variable: false,
920                dtype: self.dtype,
921                device: self.device.clone(),
922            };
923            Ok(Tensor(Arc::new(tensor_)))
924        }
925    }
926
927    fn squeeze_dims(self, dims: &[usize]) -> Result<Self> {
928        match dims {
929            [] => Ok(self),
930            [i] => self.squeeze(*i),
931            dims => {
932                let dims = self
933                    .dims()
934                    .iter()
935                    .enumerate()
936                    .filter_map(|(dim_idx, &v)| {
937                        if dims.contains(&dim_idx) {
938                            None
939                        } else {
940                            Some(v)
941                        }
942                    })
943                    .collect::<Vec<_>>();
944                self.reshape(dims)
945            }
946        }
947    }
948
949    fn reduce_impl<D: Dim>(&self, dim: D, keepdim: bool, op: ReduceOp) -> Result<Self> {
950        let dim = dim.to_index(self.shape(), op.name())?;
951        let storage = self.storage().reduce_op(op, self.layout(), &[dim])?;
952        let mut dims = self.dims().to_vec();
953        dims[dim] = 1;
954        let op = match op {
955            ReduceOp::Sum | ReduceOp::Min | ReduceOp::Max => {
956                BackpropOp::new1(self, |arg| Op::Reduce(arg, op, dims.to_vec()))
957            }
958            ReduceOp::ArgMin | ReduceOp::ArgMax => BackpropOp::none(),
959        };
960        let res = from_storage(storage, dims, op, false);
961        if keepdim {
962            Ok(res)
963        } else {
964            res.squeeze_dims(&[dim])
965        }
966    }
967
968    fn sum_impl<D: Dims>(&self, sum_dims: D, keepdim: bool) -> Result<Self> {
969        let sum_dims = sum_dims.to_indexes(self.shape(), "sum")?;
970        let storage = self
971            .storage()
972            .reduce_op(ReduceOp::Sum, self.layout(), &sum_dims)?;
973        let mut dims = self.dims().to_vec();
974        for &sum_dim in sum_dims.iter() {
975            dims[sum_dim] = 1
976        }
977        let op = BackpropOp::new1(self, |a| Op::Reduce(a, ReduceOp::Sum, dims.to_vec()));
978        let sum = from_storage(storage, dims, op, false);
979        if keepdim {
980            Ok(sum)
981        } else {
982            sum.squeeze_dims(&sum_dims)
983        }
984    }
985
986    /// Roll the tensor input along the given dimension.
987    /// Elements that are shifted beyond the last position are re-introduced at the first position.
988    ///
989    /// ```rust
990    /// # use hanzo_ml::{Tensor, Device};
991    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
992    /// let tensor = tensor.roll(1, 0)?;
993    /// assert_eq!(tensor.to_vec2::<f32>()?, &[[4., 5.], [0., 1.], [2., 3.]]);
994    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
995    /// let tensor = tensor.roll(-1, 0)?;
996    /// assert_eq!(tensor.to_vec2::<f32>()?, &[[2., 3.], [4., 5.], [0., 1.]]);
997    /// # Ok::<(), hanzo_ml::Error>(())
998    /// ```
999    pub fn roll<D>(&self, shift: i32, dim: D) -> Result<Self>
1000    where
1001        D: Dim + Clone,
1002    {
1003        let dim = dim.to_index(self.shape(), "roll")?;
1004        let dim_size = self.dim(dim)?;
1005        let shift = shift.rem_euclid(dim_size as i32) as usize;
1006        if shift == 0 {
1007            Ok(self.clone())
1008        } else {
1009            let a = self.narrow(dim, 0, dim_size - shift)?;
1010            let b = self.narrow(dim, dim_size - shift, shift)?;
1011            Tensor::cat(&[&b, &a], dim)
1012        }
1013    }
1014
1015    /// Returns the sum of all elements in the input tensor. The sum is performed over all the
1016    /// input dimensions.
1017    ///
1018    /// The resulting tensor has a shape that is similar to the shape of the input tensor, except
1019    /// that the number of elements for each dimension index in `sum_dims` is 1.
1020    ///
1021    /// ```rust
1022    /// use hanzo_ml::{Tensor, Device};
1023    /// let a = Tensor::new(&[[0f32, 1.], [2., 3.]], &Device::Cpu)?;
1024    /// let s = a.sum_keepdim(0)?;
1025    /// assert_eq!(s.to_vec2::<f32>()?, &[[2., 4.]]);
1026    /// let s = a.sum_keepdim(1)?;
1027    /// assert_eq!(s.to_vec2::<f32>()?, &[[1.], [5.]]);
1028    /// let s = a.sum_keepdim((0, 1))?;
1029    /// assert_eq!(s.to_vec2::<f32>()?, &[[6.]]);
1030    /// # Ok::<(), hanzo_ml::Error>(())
1031    /// ```
1032    pub fn sum_keepdim<D: Dims>(&self, sum_dims: D) -> Result<Self> {
1033        self.sum_impl(sum_dims, true)
1034    }
1035
1036    /// Returns the sum of all elements in the input tensor. The sum is performed over all the
1037    /// input dimensions and compared to `sum_keepdim` these dimensions are squeezed rather than
1038    /// kept.
1039    pub fn sum<D: Dims>(&self, sum_dims: D) -> Result<Self> {
1040        self.sum_impl(sum_dims, false)
1041    }
1042
1043    /// Returns the mean of all elements in the input tensor. The mean is performed over all the
1044    /// input dimensions.
1045    ///
1046    /// The resulting tensor has a shape that is similar to the shape of the input tensor, except
1047    /// that the number of elements for each dimension index in `mean_dims` is 1.
1048    ///
1049    /// ```rust
1050    /// use hanzo_ml::{Tensor, Device};
1051    /// let a = Tensor::new(&[[0f32, 1.], [2., 3.]], &Device::Cpu)?;
1052    /// let s = a.mean_keepdim(0)?;
1053    /// assert_eq!(s.to_vec2::<f32>()?, &[[1., 2.]]);
1054    /// let s = a.mean_keepdim(1)?;
1055    /// assert_eq!(s.to_vec2::<f32>()?, &[[0.5], [2.5]]);
1056    /// let s = a.mean_keepdim((0, 1))?;
1057    /// assert_eq!(s.to_vec2::<f32>()?, &[[1.5]]);
1058    /// # Ok::<(), hanzo_ml::Error>(())
1059    /// ```
1060    pub fn mean_keepdim<D: Dims>(&self, mean_dims: D) -> Result<Self> {
1061        let mean_dims = mean_dims.to_indexes(self.shape(), "mean-keepdim")?;
1062        let reduced_dim: usize = mean_dims.iter().map(|i| self.dims()[*i]).product();
1063        let scale = 1f64 / (reduced_dim as f64);
1064        self.sum_impl(mean_dims, true)? * scale
1065    }
1066
1067    /// Returns the mean of all elements in the input tensor. The mean is performed over all the
1068    /// input dimensions and compared to `mean_keepdim` these dimensions are squeezed rather than
1069    /// kept.
1070    pub fn mean<D: Dims>(&self, mean_dims: D) -> Result<Self> {
1071        let mean_dims = mean_dims.to_indexes(self.shape(), "mean")?;
1072        let reduced_dim: usize = mean_dims.iter().map(|i| self.dims()[*i]).product();
1073        let scale = 1f64 / (reduced_dim as f64);
1074        self.sum_impl(mean_dims, false)? * scale
1075    }
1076
1077    /// Returns the unbiased variance over the selected dimension.
1078    pub fn var_keepdim<D: Dim>(&self, dim: D) -> Result<Self> {
1079        let dim = dim.to_index(self.shape(), "var")?;
1080        let mean = self.mean_keepdim(dim)?;
1081        let squares = self.broadcast_sub(&mean)?.sqr()?;
1082        squares.sum_impl(dim, true)? / (self.dim(dim)? - 1) as f64
1083    }
1084
1085    /// Returns the unbiased variance over the selected dimension.
1086    pub fn var<D: Dim>(&self, dim: D) -> Result<Self> {
1087        let dim = dim.to_index(self.shape(), "var")?;
1088        self.var_keepdim(dim)?.squeeze(dim)
1089    }
1090
1091    /// Gathers the maximum value across the selected dimension. The resulting shape has the same
1092    /// number of dimensions as the original tensor and the select dimension has a single element.
1093    pub fn max_keepdim<D: Dim>(&self, dim: D) -> Result<Self> {
1094        self.reduce_impl(dim, true, ReduceOp::Max)
1095    }
1096
1097    /// Similar to `max_keepdim` but the target dimension is squeezed.
1098    pub fn max<D: Dim>(&self, dim: D) -> Result<Self> {
1099        self.reduce_impl(dim, false, ReduceOp::Max)
1100    }
1101
1102    /// Gathers the minimum value across the selected dimension. The resulting shape has the same
1103    /// number of dimensions as the original tensor and the select dimension has a single element.
1104    pub fn min_keepdim<D: Dim>(&self, dim: D) -> Result<Self> {
1105        self.reduce_impl(dim, true, ReduceOp::Min)
1106    }
1107
1108    /// Similar to `min_keepdim` but the target dimension is squeezed.
1109    pub fn min<D: Dim>(&self, dim: D) -> Result<Self> {
1110        self.reduce_impl(dim, false, ReduceOp::Min)
1111    }
1112
1113    pub fn argmax_keepdim<D: Dim>(&self, dim: D) -> Result<Self> {
1114        self.reduce_impl(dim, true, ReduceOp::ArgMax)
1115    }
1116
1117    /// Similar to `argmax_keepdim` but the target dimension is squeezed.
1118    pub fn argmax<D: Dim>(&self, dim: D) -> Result<Self> {
1119        self.reduce_impl(dim, false, ReduceOp::ArgMax)
1120    }
1121
1122    pub fn argmin_keepdim<D: Dim>(&self, dim: D) -> Result<Self> {
1123        self.reduce_impl(dim, true, ReduceOp::ArgMin)
1124    }
1125
1126    /// Similar to `argmin_keepdim` but the target dimension is squeezed.
1127    pub fn argmin<D: Dim>(&self, dim: D) -> Result<Self> {
1128        self.reduce_impl(dim, false, ReduceOp::ArgMin)
1129    }
1130
1131    /// Element-wise comparison between two tensors, e.g. equality, greater than, ... The actual
1132    /// comparison operation is specified by the `op` argument.
1133    ///
1134    /// The returned tensor has the same shape as the original tensors and uses `u8` elements.
1135    pub fn cmp<T: TensorOrScalar>(&self, rhs: T, op: CmpOp) -> Result<Self> {
1136        let rhs = match rhs.to_tensor_scalar()? {
1137            crate::scalar::TensorScalar::Tensor(rhs) => rhs,
1138            crate::scalar::TensorScalar::Scalar(rhs) => rhs
1139                .to_dtype(self.dtype())?
1140                .to_device(self.device())?
1141                .broadcast_as(self.shape())?,
1142        };
1143        let shape = self.same_shape_binary_op(&rhs, "cmp")?;
1144        let storage = self
1145            .storage()
1146            .cmp(op, &rhs.storage(), self.layout(), rhs.layout())?;
1147        let op = BackpropOp::new1(self, |a| Op::Cmp(a, op));
1148        Ok(from_storage(storage, shape.dims(), op, false))
1149    }
1150
1151    /// Element-wise equality.
1152    pub fn eq<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
1153        self.cmp(rhs, CmpOp::Eq)
1154    }
1155
1156    /// Element-wise non-equality.
1157    pub fn ne<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
1158        self.cmp(rhs, CmpOp::Ne)
1159    }
1160
1161    /// Element-wise comparison with lower-than, the returned tensor uses value 1 where `self <
1162    /// rhs` and 0 otherwise.
1163    pub fn lt<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
1164        self.cmp(rhs, CmpOp::Lt)
1165    }
1166
1167    /// Element-wise comparison with greater-than, the returned tensor uses value 1 where `self >
1168    /// rhs` and 0 otherwise.
1169    pub fn gt<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
1170        self.cmp(rhs, CmpOp::Gt)
1171    }
1172
1173    /// Element-wise comparison with greater-equal, the returned tensor uses value 1 where `self >=
1174    /// rhs` and 0 otherwise.
1175    pub fn ge<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
1176        self.cmp(rhs, CmpOp::Ge)
1177    }
1178
1179    /// Element-wise comparison with lower-equal, the returned tensor uses value 1 where `self <=
1180    /// rhs` and 0 otherwise.
1181    pub fn le<T: TensorOrScalar>(&self, rhs: T) -> Result<Self> {
1182        self.cmp(rhs, CmpOp::Le)
1183    }
1184
1185    /// Clamp the tensor values to be between `min` and `max`.
1186    pub fn clamp<T1: TensorOrScalar, T2: TensorOrScalar>(&self, min: T1, max: T2) -> Result<Self> {
1187        self.maximum(min)?.minimum(max)
1188    }
1189
1190    /// Interpolate the input tensor to the `target_size` size, taking the value of the nearest element.
1191    ///
1192    /// The input tensor should have three dimensions, `(batch, channels, l)`, the returned
1193    /// tensor also has three dimensions, `(batch, channels, target_size)`.
1194    pub fn interpolate1d(&self, target_size: usize) -> Result<Self> {
1195        let (n, c, _l) = self.dims3()?;
1196        let op = BackpropOp::new1(self, |arg| Op::UpsampleNearest1D { arg, target_size });
1197        let storage = self
1198            .storage()
1199            .upsample_nearest1d(self.layout(), target_size)?;
1200        Ok(from_storage(storage, (n, c, target_size), op, false))
1201    }
1202
1203    /// Alias for `interpolate1d`.
1204    pub fn upsample_nearest1d(&self, target_size: usize) -> Result<Self> {
1205        self.interpolate1d(target_size)
1206    }
1207
1208    /// Interpolate the input tensor to the `(target_h, target_w)` size, taking the value of the
1209    /// nearest element.
1210    ///
1211    /// The input tensor should have four dimensions, `(batch, channels, h, w)`, the returned
1212    /// tensor also has four dimensions, `(batch, channels, target_h, target_w)`.
1213    pub fn interpolate2d(&self, target_h: usize, target_w: usize) -> Result<Self> {
1214        let (n, c, _h, _w) = self.dims4()?;
1215        let op = BackpropOp::new1(self, |arg| Op::UpsampleNearest2D {
1216            arg,
1217            target_h,
1218            target_w,
1219        });
1220        let storage = self
1221            .storage()
1222            .upsample_nearest2d(self.layout(), target_h, target_w)?;
1223        Ok(from_storage(storage, (n, c, target_h, target_w), op, false))
1224    }
1225
1226    /// Alias for `interpolate2d`.
1227    pub fn upsample_nearest2d(&self, target_h: usize, target_w: usize) -> Result<Self> {
1228        self.interpolate2d(target_h, target_w)
1229    }
1230
1231    /// Bilinear interpolation to resize the input tensor to the specified size.
1232    ///
1233    /// The input tensor should have four dimensions: `(batch, channels, h, w)`.
1234    /// The returned tensor also has four dimensions: `(batch, channels, target_h, target_w)`.
1235    ///
1236    /// # Arguments
1237    ///
1238    /// * `target_h` - Target height
1239    /// * `target_w` - Target width
1240    /// * `align_corners` - If true, corner pixels are aligned. If false (default),
1241    ///   pixels are treated as areas (matches PyTorch default behavior).
1242    ///
1243    /// # Example
1244    ///
1245    /// ```rust
1246    /// use hanzo_ml::{Tensor, Device};
1247    /// # fn main() -> hanzo_ml::Result<()> {
1248    /// let t = Tensor::arange(0f32, 16f32, &Device::Cpu)?.reshape((1, 1, 4, 4))?;
1249    /// let upsampled = t.upsample_bilinear2d(8, 8, false)?;
1250    /// assert_eq!(upsampled.dims(), &[1, 1, 8, 8]);
1251    /// # Ok(())
1252    /// # }
1253    /// ```
1254    pub fn upsample_bilinear2d(
1255        &self,
1256        target_h: usize,
1257        target_w: usize,
1258        align_corners: bool,
1259    ) -> Result<Self> {
1260        let (n, c, _h, _w) = self.dims4()?;
1261        let op = BackpropOp::new1(self, |arg| Op::UpsampleBilinear2D {
1262            arg,
1263            target_h,
1264            target_w,
1265            align_corners,
1266        });
1267        // Pass None for scale factors (size mode)
1268        let storage = self.storage().upsample_bilinear2d(
1269            self.layout(),
1270            target_h,
1271            target_w,
1272            align_corners,
1273            None,
1274            None,
1275        )?;
1276        Ok(from_storage(storage, (n, c, target_h, target_w), op, false))
1277    }
1278
1279    /// Bilinear interpolation using scale factors.
1280    ///
1281    /// Similar to `upsample_bilinear2d` but uses scale factors instead of absolute sizes.
1282    /// This matches PyTorch's `interpolate(scale_factor=...)` behavior.
1283    ///
1284    /// # Arguments
1285    ///
1286    /// * `scale_h` - Height scaling factor
1287    /// * `scale_w` - Width scaling factor
1288    /// * `align_corners` - If true, corner pixels are aligned
1289    ///
1290    /// # Example
1291    ///
1292    /// ```rust
1293    /// use hanzo_ml::{Tensor, Device};
1294    /// # fn main() -> hanzo_ml::Result<()> {
1295    /// let t = Tensor::arange(0f32, 16f32, &Device::Cpu)?.reshape((1, 1, 4, 4))?;
1296    /// // Scale by 2x in both dimensions
1297    /// let upsampled = t.upsample_bilinear2d_with_scale(2.0, 2.0, false)?;
1298    /// assert_eq!(upsampled.dims(), &[1, 1, 8, 8]);
1299    /// # Ok(())
1300    /// # }
1301    /// ```
1302    pub fn upsample_bilinear2d_with_scale(
1303        &self,
1304        scale_h: f64,
1305        scale_w: f64,
1306        align_corners: bool,
1307    ) -> Result<Self> {
1308        let (n, c, height_in, width_in) = self.dims4()?;
1309
1310        // Calculate output size (floor, matching PyTorch)
1311        let height_out = (height_in as f64 * scale_h).floor() as usize;
1312        let width_out = (width_in as f64 * scale_w).floor() as usize;
1313
1314        // Early return if size unchanged
1315        if height_in == height_out && width_in == width_out {
1316            return Ok(self.clone());
1317        }
1318
1319        let op = BackpropOp::new1(self, |arg| Op::UpsampleBilinear2D {
1320            arg,
1321            target_h: height_out,
1322            target_w: width_out,
1323            align_corners,
1324        });
1325
1326        // Pass original scale factors (scale_factor mode)
1327        // This ensures PyTorch-compatible scale calculation
1328        let storage = self.storage().upsample_bilinear2d(
1329            self.layout(),
1330            height_out,
1331            width_out,
1332            align_corners,
1333            Some(scale_h),
1334            Some(scale_w),
1335        )?;
1336        Ok(from_storage(
1337            storage,
1338            (n, c, height_out, width_out),
1339            op,
1340            false,
1341        ))
1342    }
1343
1344    /// 2D average pooling over an input tensor with multiple channels.
1345    ///
1346    /// The input tensor should have four dimensions, `(batch, channels, h, w)`, the returned
1347    /// tensor also has four dimensions, `(batch, channels, h', w')`. The pooling is performed on
1348    /// the two last dimensions using a kernel of size `sz`. The returned element is the average
1349    /// value over the kernel window.
1350    pub fn avg_pool2d<T: crate::ToUsize2>(&self, sz: T) -> Result<Self> {
1351        let sz = sz.to_usize2();
1352        self.avg_pool2d_with_stride(sz, sz)
1353    }
1354
1355    /// Same as `avg_pool2d` but with a `stride` that can be set to a value different from the
1356    /// kernel size.
1357    pub fn avg_pool2d_with_stride<T: crate::ToUsize2>(
1358        &self,
1359        kernel_size: T,
1360        stride: T,
1361    ) -> Result<Self> {
1362        let kernel_size = kernel_size.to_usize2();
1363        let stride = stride.to_usize2();
1364        let (n, c, h, w) = self.dims4()?;
1365        if h < kernel_size.0 || w < kernel_size.1 {
1366            bail!("kernel-size {kernel_size:?} is larger than the input size {h},{w}")
1367        }
1368        // https://pytorch.org/docs/stable/generated/torch.nn.AvgPool2d.html#torch.nn.AvgPool2d
1369        let h_out = (h - kernel_size.0) / stride.0 + 1;
1370        let w_out = (w - kernel_size.1) / stride.1 + 1;
1371        let op = BackpropOp::new1(self, |arg| Op::AvgPool2D {
1372            arg,
1373            kernel_size,
1374            stride,
1375        });
1376        let storage = self
1377            .storage()
1378            .avg_pool2d(self.layout(), kernel_size, stride)?;
1379        Ok(from_storage(storage, (n, c, h_out, w_out), op, false))
1380    }
1381
1382    /// 2D max pooling over an input tensor with multiple channels.
1383    ///
1384    /// The input tensor should have four dimensions, `(batch, channels, h, w)`, the returned
1385    /// tensor also has four dimensions, `(batch, channels, h', w')`. The pooling is performed on
1386    /// the two last dimensions using a kernel of size `sz`, the returned element is the maximum
1387    /// value over the kernel window.
1388    pub fn max_pool2d<T: crate::ToUsize2>(&self, sz: T) -> Result<Self> {
1389        let sz = sz.to_usize2();
1390        self.max_pool2d_with_stride(sz, sz)
1391    }
1392
1393    /// Same as `max_pool2d` but with a `stride` that can be set to a value different from the
1394    /// kernel size.
1395    pub fn max_pool2d_with_stride<T: crate::ToUsize2>(
1396        &self,
1397        kernel_size: T,
1398        stride: T,
1399    ) -> Result<Self> {
1400        let kernel_size = kernel_size.to_usize2();
1401        let stride = stride.to_usize2();
1402        let (n, c, h, w) = self.dims4()?;
1403        if h < kernel_size.0 || w < kernel_size.1 {
1404            bail!("kernel-size {kernel_size:?} is larger than the input size {h},{w}")
1405        }
1406        // https://pytorch.org/docs/stable/generated/torch.nn.MaxPool2d.html#torch.nn.MaxPool2d
1407        let h_out = (h - kernel_size.0) / stride.0 + 1;
1408        let w_out = (w - kernel_size.1) / stride.1 + 1;
1409        let op = BackpropOp::new1(self, |arg| Op::MaxPool2D {
1410            arg,
1411            kernel_size,
1412            stride,
1413        });
1414        let storage = self
1415            .storage()
1416            .max_pool2d(self.layout(), kernel_size, stride)?;
1417        Ok(from_storage(storage, (n, c, h_out, w_out), op, false))
1418    }
1419
1420    /// Computes the dot product of two 1D tensors.
1421    ///
1422    /// - If inputs are 1D vectors (`[n]`), returns their scalar dot product.
1423    /// - Panics if shapes are not compatible
1424    /// - Not supported for integer dtypes
1425    ///
1426    /// # Example (vectors)
1427    /// ```rust
1428    /// use hanzo_ml::{Tensor, Device};
1429    /// let t1 = Tensor::new(&[1.0, 2.0, 3.0], &Device::Cpu)?;
1430    /// let t2 = Tensor::new(&[4.0, 5.0, 6.0], &Device::Cpu)?;
1431    /// let res = t1.dot(&t2)?;
1432    /// assert_eq!(res.to_scalar::<f64>()?, 32.);
1433    /// # Ok::<(), hanzo_ml::Error>(())
1434    /// ```
1435    pub fn dot(&self, rhs: &Self) -> Result<Self> {
1436        if self.dims().len() != 1 || rhs.dims().len() != 1 {
1437            return Err(Error::ShapeMismatchBinaryOp {
1438                lhs: self.shape().clone(),
1439                rhs: rhs.shape().clone(),
1440                op: "dot",
1441            });
1442        }
1443
1444        (self * rhs).and_then(|ret| ret.sum_all())
1445    }
1446
1447    /// Computes the **Frobenius norm** (L2 norm of all elements) of the tensor.
1448    /// - Output is `sqrt(sum(x^2))`.
1449    /// - Always returns a scalar (`[]` shape).
1450    ///
1451    /// # Example
1452    /// ```rust
1453    /// use hanzo_ml::{Tensor, Device};
1454    /// let t = Tensor::new(&[[3., 4.], [0., 0.]], &Device::Cpu)?;
1455    /// let norm = t.norm()?;
1456    /// assert_eq!(norm.to_scalar::<f64>()?, 5.);
1457    /// # Ok::<(), hanzo_ml::Error>(())
1458    /// ```
1459    pub fn norm(&self) -> Result<Self> {
1460        if self.dtype().is_int() {
1461            bail!("norm not supported for integer dtypes");
1462        }
1463
1464        self.sqr().and_then(|x| x.sum_all()).and_then(|x| x.sqrt())
1465    }
1466
1467    /// Performs strict matrix-vector multiplication (`[m, n] * [n] = [m]`).
1468    ///
1469    /// - If `self` is a matrix (`[m, n]`) and `rhs` is a vector (`[n]`), returns a vector (`[m]`).
1470    /// - **No broadcasting**: Panics if `self` is not 2D or if `rhs` is not 1D with matching size.
1471    ///
1472    /// # Example
1473    /// ```rust
1474    /// use hanzo_ml::{Tensor, Device};
1475    /// let mat = Tensor::new(&[[1., 2., 3.], [4., 5., 6.]], &Device::Cpu)?;
1476    /// let vec = Tensor::new(&[1., 1., 1.], &Device::Cpu)?;
1477    /// let res = mat.mv(&vec)?;
1478    /// assert_eq!(res.to_vec1::<f64>()?, [6., 15.]);
1479    /// # Ok::<(), hanzo_ml::Error>(())
1480    /// ```
1481    pub fn mv(&self, rhs: &Self) -> Result<Self> {
1482        // Strict shape checks
1483        let lhs_dims = self.dims();
1484        let rhs_dims = rhs.dims();
1485        if lhs_dims.len() != 2 || rhs_dims.len() != 1 || lhs_dims[1] != rhs_dims[0] {
1486            return Err(Error::ShapeMismatchBinaryOp {
1487                lhs: self.shape().clone(),
1488                rhs: rhs.shape().clone(),
1489                op: "mv",
1490            });
1491        }
1492
1493        // Direct matmul after ensuring rhs is column vector
1494        self.matmul(&rhs.unsqueeze(1)?)?.squeeze(1)
1495    }
1496
1497    /// Returns the matrix-multiplication of the input tensor with the other provided tensor.
1498    ///
1499    /// # Arguments
1500    ///
1501    /// * `self` - A tensor with dimensions `b1, b2, ..., bi, m, k`.
1502    /// * `rhs` - A tensor with dimensions `b1, b2, ..., bi, k, n`.
1503    ///
1504    /// The resulting tensor has dimensions `b1, b2, ..., bi, m, n`.
1505    pub fn matmul(&self, rhs: &Self) -> Result<Self> {
1506        let a_dims = self.shape().dims();
1507        let b_dims = rhs.shape().dims();
1508
1509        let dim = a_dims.len();
1510
1511        if dim < 2 || b_dims.len() != dim {
1512            Err(Error::ShapeMismatchBinaryOp {
1513                lhs: self.shape().clone(),
1514                rhs: rhs.shape().clone(),
1515                op: "matmul",
1516            }
1517            .bt())?
1518        }
1519
1520        let m = a_dims[dim - 2];
1521        let k = a_dims[dim - 1];
1522        let k2 = b_dims[dim - 2];
1523        let n = b_dims[dim - 1];
1524
1525        let c_shape = Shape::from(&a_dims[..dim - 2]).extend(&[m, n]);
1526        let batching: usize = a_dims[..dim - 2].iter().product();
1527        let batching_b: usize = b_dims[..dim - 2].iter().product();
1528        if k != k2 || batching != batching_b {
1529            Err(Error::ShapeMismatchBinaryOp {
1530                lhs: self.shape().clone(),
1531                rhs: rhs.shape().clone(),
1532                op: "matmul",
1533            }
1534            .bt())?
1535        }
1536        if c_shape.elem_count() == 0 || k == 0 {
1537            {
1538                let lhs_storage = self.storage();
1539                let rhs_storage = rhs.storage();
1540                lhs_storage.same_device(&rhs_storage, "matmul")?;
1541                lhs_storage.same_dtype(&rhs_storage, "matmul")?;
1542            }
1543
1544            let storage = self.device().zeros(&c_shape, self.dtype())?;
1545            let op = BackpropOp::new2(self, rhs, Op::Matmul);
1546            return Ok(from_storage(storage, c_shape, op, false));
1547        }
1548
1549        let storage = self.storage().matmul(
1550            &rhs.storage(),
1551            (batching, m, n, k),
1552            self.layout(),
1553            rhs.layout(),
1554        )?;
1555        let op = BackpropOp::new2(self, rhs, Op::Matmul);
1556        Ok(from_storage(storage, c_shape, op, false))
1557    }
1558
1559    /// Matrix-multiplication with broadcasting support.
1560    ///
1561    /// Compared to `matmul` the two matrixes are allowed to have different dimensions as long as
1562    /// they are compatible for broadcast. E.g. if `self` has shape `(j, 1, n, k)` and `rhs` has
1563    /// shape `(l, k, m)`, the output will have shape `(j, l, n, m)`.
1564    pub fn broadcast_matmul(&self, rhs: &Self) -> Result<Self> {
1565        let lhs = self;
1566        let (l_shape, r_shape) = lhs.shape().broadcast_shape_matmul(rhs.shape())?;
1567        let l_broadcast = l_shape != *lhs.shape();
1568        let r_broadcast = r_shape != *rhs.shape();
1569        // TODO: Avoid concretising the broadcasted matrixes via contiguous.
1570        match (l_broadcast, r_broadcast) {
1571            (true, true) => lhs
1572                .broadcast_as(&l_shape)?
1573                .contiguous()?
1574                .matmul(&rhs.broadcast_as(&r_shape)?.contiguous()?),
1575            // A rank-2 rhs is only broadcast over the batch dimensions, so the whole product is
1576            // a single 2D matmul once the leading dims of lhs are folded into its row dimension.
1577            // Broadcasting the rhs instead would copy it `batch` times -- for an lm_head that is
1578            // the entire vocabulary matrix, per call. Same trick, and same contiguity guard, as
1579            // `hanzo_nn::Linear::forward`.
1580            (false, true) if rhs.rank() == 2 && lhs.is_contiguous() => {
1581                let (lhs_dims, rhs_dims) = (lhs.dims(), rhs.dims());
1582                let (m, k) = (lhs_dims[lhs.rank() - 2], lhs_dims[lhs.rank() - 1]);
1583                let n = rhs_dims[1];
1584                let batch: usize = lhs_dims[..lhs.rank() - 2].iter().product();
1585                let mut out_dims = lhs_dims.to_vec();
1586                out_dims.pop();
1587                out_dims.push(n);
1588                lhs.reshape((batch * m, k))?.matmul(rhs)?.reshape(out_dims)
1589            }
1590            (false, true) => lhs.matmul(&rhs.broadcast_as(&r_shape)?.contiguous()?),
1591            (true, false) => lhs.broadcast_as(&l_shape)?.contiguous()?.matmul(rhs),
1592            (false, false) => lhs.matmul(rhs),
1593        }
1594    }
1595
1596    /// Returns a tensor with the same shape as the input tensor, the values are taken from
1597    /// `on_true` if the input tensor value is not zero, and `on_false` at the positions where the
1598    /// input tensor is equal to zero.
1599    pub fn where_cond(&self, on_true: &Self, on_false: &Self) -> Result<Self> {
1600        let _shap = self.same_shape_binary_op(on_true, "where_cond")?;
1601        let shape = self.same_shape_binary_op(on_false, "where_cond")?;
1602        let storage = self.storage().where_cond(
1603            self.layout(),
1604            &on_true.storage(),
1605            on_true.layout(),
1606            &on_false.storage(),
1607            on_false.layout(),
1608        )?;
1609        let op = BackpropOp::new3(self, on_true, on_false, Op::WhereCond);
1610        Ok(from_storage(storage, shape, op, false))
1611    }
1612
1613    /// Returns a tensor with the values from the `self` tensor at the index corresponding to the
1614    /// values hold in the `ids` tensor.
1615    ///
1616    /// # Arguments
1617    ///
1618    /// * `self` - A tensor with dimensions `v, h`.
1619    /// * `ids` - A tensor with dimensions `s` and with integer values between 0 and v (exclusive).
1620    ///
1621    /// The resulting tensor has dimensions `s, h`. `s` is called the sequence length, `v` the
1622    /// vocabulary size, and `h` the hidden size.
1623    ///
1624    /// ```rust
1625    /// use hanzo_ml::{Tensor, Device};
1626    /// let values = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
1627    /// let ids = Tensor::new(&[2u32, 1u32, 2u32], &Device::Cpu)?;
1628    /// let emb = values.embedding(&ids)?;
1629    /// assert_eq!(emb.to_vec2::<f32>()?, &[[4., 5.], [2., 3.], [4., 5.]]);
1630    /// # Ok::<(), hanzo_ml::Error>(())
1631    /// ```
1632    pub fn embedding(&self, ids: &Self) -> Result<Self> {
1633        if self.rank() != 2 || ids.rank() != 1 {
1634            Err(Error::ShapeMismatchBinaryOp {
1635                lhs: self.shape().clone(),
1636                rhs: ids.shape().clone(),
1637                op: "embedding",
1638            }
1639            .bt())?
1640        }
1641        self.index_select(ids, 0)
1642    }
1643
1644    fn scatter_checks(&self, indexes: &Self, source: &Self, dim: usize) -> Result<()> {
1645        let source_dims = source.dims();
1646        let self_dims = self.dims();
1647        let mismatch = if source_dims.len() != self_dims.len() {
1648            true
1649        } else {
1650            let mut mismatch = false;
1651            for (i, (&d1, &d2)) in self_dims.iter().zip(source_dims.iter()).enumerate() {
1652                if i != dim && d1 != d2 {
1653                    mismatch = true;
1654                    break;
1655                }
1656            }
1657            mismatch
1658        };
1659        if mismatch {
1660            Err(Error::ShapeMismatchBinaryOp {
1661                op: "scatter (self, src)",
1662                lhs: self.shape().clone(),
1663                rhs: source.shape().clone(),
1664            }
1665            .bt())?
1666        }
1667        if indexes.dims() != source.dims() {
1668            Err(Error::ShapeMismatchBinaryOp {
1669                op: "scatter (indexes, src)",
1670                lhs: indexes.shape().clone(),
1671                rhs: source.shape().clone(),
1672            }
1673            .bt())?
1674        }
1675        Ok(())
1676    }
1677
1678    pub fn scatter<D: Dim>(&self, indexes: &Self, source: &Self, dim: D) -> Result<Self> {
1679        let dim = dim.to_index(self.shape(), "scatter")?;
1680        self.scatter_checks(indexes, source, dim)?;
1681        let shape = self.shape();
1682        let mut storage = unsafe { self.device().alloc_uninit(shape, self.dtype())? };
1683        self.storage()
1684            .copy_strided_src(&mut storage, 0, self.layout())?;
1685        let layout = Layout::contiguous(shape);
1686        storage.scatter_set(
1687            &layout,
1688            &indexes.storage(),
1689            indexes.layout(),
1690            &source.storage(),
1691            source.layout(),
1692            dim,
1693        )?;
1694        let op = BackpropOp::new3(self, indexes, source, |t1, t2, t3| {
1695            Op::Scatter(t1, t2, t3, dim)
1696        });
1697        Ok(from_storage(storage, self.shape(), op, false))
1698    }
1699
1700    pub fn scatter_set<D: Dim>(&self, indexes: &Self, source: &Self, dim: D) -> Result<()> {
1701        if self.same_storage(source) {
1702            crate::bail!("cannot use slice_set when self and src share their storage")
1703        }
1704        let dim = dim.to_index(self.shape(), "scatter-set")?;
1705        self.scatter_checks(indexes, source, dim)?;
1706        self.storage_mut().scatter_set(
1707            self.layout(),
1708            &indexes.storage(),
1709            indexes.layout(),
1710            &source.storage(),
1711            source.layout(),
1712            dim,
1713        )?;
1714        Ok(())
1715    }
1716
1717    pub fn scatter_add<D: Dim>(&self, indexes: &Self, source: &Self, dim: D) -> Result<Self> {
1718        let dim = dim.to_index(self.shape(), "scatter-add")?;
1719        self.scatter_checks(indexes, source, dim)?;
1720        let shape = self.shape();
1721        let mut storage = unsafe { self.device().alloc_uninit(shape, self.dtype())? };
1722        self.storage()
1723            .copy_strided_src(&mut storage, 0, self.layout())?;
1724        let layout = Layout::contiguous(shape);
1725        storage.scatter_add(
1726            &layout,
1727            &indexes.storage(),
1728            indexes.layout(),
1729            &source.storage(),
1730            source.layout(),
1731            dim,
1732        )?;
1733        let op = BackpropOp::new3(self, indexes, source, |t1, t2, t3| {
1734            Op::ScatterAdd(t1, t2, t3, dim)
1735        });
1736        Ok(from_storage(storage, self.shape(), op, false))
1737    }
1738
1739    pub fn scatter_add_set<D: Dim>(&self, indexes: &Self, source: &Self, dim: D) -> Result<()> {
1740        if self.same_storage(source) {
1741            crate::bail!("cannot use slice_set when self and src share their storage")
1742        }
1743        let dim = dim.to_index(self.shape(), "scatter-add-set")?;
1744        self.scatter_checks(indexes, source, dim)?;
1745        self.storage_mut().scatter_add(
1746            self.layout(),
1747            &indexes.storage(),
1748            indexes.layout(),
1749            &source.storage(),
1750            source.layout(),
1751            dim,
1752        )?;
1753        Ok(())
1754    }
1755
1756    /// Embeds the values of the `src` tensor into the `self` tensor on the specified dimension.
1757    pub fn slice_scatter<D: Dim>(&self, src: &Self, dim: D, start: usize) -> Result<Self> {
1758        let dim = dim.to_index(self.shape(), "slice-scatter")?;
1759        if dim == 0 {
1760            self.slice_scatter0(src, start)
1761        } else {
1762            // TODO: Maybe we want to add a more efficient implementation at some point.
1763            self.transpose(0, dim)?
1764                .slice_scatter0(&src.transpose(0, dim)?, start)?
1765                .transpose(0, dim)
1766        }
1767    }
1768
1769    /// Embeds the values of the `src` tensor into the `self` tensor on the first dimension.
1770    pub fn slice_scatter0(&self, src: &Self, start: usize) -> Result<Self> {
1771        if self.dtype() != src.dtype() {
1772            Err(Error::DTypeMismatchBinaryOp {
1773                lhs: self.dtype(),
1774                rhs: src.dtype(),
1775                op: "slice-scatter",
1776            }
1777            .bt())?
1778        }
1779        if self.device().location() != src.device.location() {
1780            Err(Error::DeviceMismatchBinaryOp {
1781                lhs: self.device().location(),
1782                rhs: src.device().location(),
1783                op: "slice-scatter",
1784            }
1785            .bt())?
1786        }
1787        if self.rank() != src.rank() {
1788            Err(Error::UnexpectedNumberOfDims {
1789                expected: self.rank(),
1790                got: src.rank(),
1791                shape: src.shape().clone(),
1792            }
1793            .bt())?
1794        }
1795        let shape_ok =
1796            self.dims()
1797                .iter()
1798                .zip(src.dims().iter())
1799                .enumerate()
1800                .all(|(dim_idx, (&d1, &d2))| {
1801                    if 0 == dim_idx {
1802                        d2 + start <= d1
1803                    } else {
1804                        d1 == d2
1805                    }
1806                });
1807        if !shape_ok {
1808            Err(Error::ShapeMismatchBinaryOp {
1809                op: "slice-scatter (self, src)",
1810                lhs: self.shape().clone(),
1811                rhs: src.shape().clone(),
1812            }
1813            .bt())?
1814        }
1815        let mut storage = unsafe { self.device().alloc_uninit(self.shape(), self.dtype())? };
1816        self.storage()
1817            .copy_strided_src(&mut storage, 0, self.layout())?;
1818        let offset = start * src.dims()[1..].iter().product::<usize>();
1819        src.storage()
1820            .copy_strided_src(&mut storage, offset, src.layout())?;
1821        let op = BackpropOp::new2(self, src, |t1, t2| Op::SliceScatter0(t1, t2, start));
1822        Ok(from_storage(storage, self.shape(), op, false))
1823    }
1824
1825    /// Accumulate element from `source` at indexes `indexes` and add them to `self`.
1826    pub fn index_add<D: Dim>(&self, indexes: &Self, source: &Self, dim: D) -> Result<Self> {
1827        let dim = dim.to_index(self.shape(), "index-add")?;
1828        let source_dims = source.dims();
1829        let self_dims = self.dims();
1830        let mismatch = if source_dims.len() != self_dims.len() {
1831            true
1832        } else {
1833            let mut mismatch = false;
1834            for (i, (&d1, &d2)) in self_dims.iter().zip(source_dims.iter()).enumerate() {
1835                if i != dim && d1 != d2 {
1836                    mismatch = true;
1837                    break;
1838                }
1839            }
1840            mismatch
1841        };
1842        if mismatch {
1843            Err(Error::ShapeMismatchBinaryOp {
1844                op: "index-add (self, source)",
1845                lhs: self.shape().clone(),
1846                rhs: source.shape().clone(),
1847            }
1848            .bt())?
1849        }
1850        // The number of element in indexes must match the dimension on which the add is
1851        // performed on the source tensor (and the index values from `indexes` are taken from
1852        // the target tensor self)
1853        let indexes_len = indexes.dims1()?;
1854        if source_dims[dim] != indexes_len {
1855            Err(Error::ShapeMismatchBinaryOp {
1856                op: "index-add (ids, source))",
1857                lhs: indexes.shape().clone(),
1858                rhs: source.shape().clone(),
1859            }
1860            .bt())?
1861        }
1862        let storage = self.storage().index_add(
1863            self.layout(),
1864            &indexes.storage(),
1865            indexes.layout(),
1866            &source.storage(),
1867            source.layout(),
1868            dim,
1869        )?;
1870        let op = BackpropOp::new3(self, indexes, source, |t1, t2, t3| {
1871            Op::IndexAdd(t1, t2, t3, dim)
1872        });
1873        Ok(from_storage(storage, self.shape(), op, false))
1874    }
1875
1876    /// Gather values across the target dimension.
1877    ///
1878    /// # Arguments
1879    ///
1880    /// * `self` - The input tensor.
1881    /// * `indexes` - The indices of elements to gather, this should have same number of dimensions as `self`
1882    ///   and indexes.dims()[d] <= self.dims()[d] for all dimensions d != dim
1883    /// * `dim` - the target dimension.
1884    ///
1885    /// The resulting tensor has the same shape as `indexes` and use values from `self` indexed on
1886    /// dimension `dim` by the values in `indexes`.
1887    pub fn gather<D: Dim>(&self, indexes: &Self, dim: D) -> Result<Self> {
1888        let dim = dim.to_index(self.shape(), "gather")?;
1889
1890        let self_dims = self.dims();
1891        let indexes_dims = indexes.dims();
1892        let mismatch = if indexes_dims.len() != self_dims.len() {
1893            true
1894        } else {
1895            let mut mismatch = false;
1896            for (i, (&d1, &d2)) in self_dims.iter().zip(indexes_dims.iter()).enumerate() {
1897                if i != dim && d1 < d2 {
1898                    mismatch = true;
1899                    break;
1900                }
1901            }
1902            mismatch
1903        };
1904        if mismatch {
1905            Err(Error::ShapeMismatchBinaryOp {
1906                op: "gather",
1907                lhs: self.shape().clone(),
1908                rhs: indexes.shape().clone(),
1909            }
1910            .bt())?
1911        }
1912        let storage =
1913            self.storage()
1914                .gather(self.layout(), &indexes.storage(), indexes.layout(), dim)?;
1915        let op = BackpropOp::new2(self, indexes, |t1, t2| Op::Gather(t1, t2, dim));
1916        Ok(from_storage(storage, indexes.shape(), op, false))
1917    }
1918
1919    /// Select values for the input tensor at the target indexes across the specified dimension.
1920    ///
1921    /// The `indexes` is argument is an int tensor with a single dimension.
1922    /// The output has the same number of dimension as the `self` input. The target dimension of
1923    /// the output has length the length of `indexes` and the values are taken from `self` using
1924    /// the index from `indexes`. Other dimensions have the same number of elements as the input
1925    /// tensor.
1926    pub fn index_select<D: Dim>(&self, indexes: &Self, dim: D) -> Result<Self> {
1927        let dim = dim.to_index(self.shape(), "index-select")?;
1928        let indexes_len = match indexes.dims() {
1929            [l] => *l,
1930            _ => Err(Error::ShapeMismatchBinaryOp {
1931                lhs: self.shape().clone(),
1932                rhs: indexes.shape().clone(),
1933                op: "index-select",
1934            }
1935            .bt())?,
1936        };
1937        let storage = self.storage().index_select(
1938            &indexes.storage(),
1939            self.layout(),
1940            indexes.layout(),
1941            dim,
1942        )?;
1943        let mut dims = self.dims().to_vec();
1944        dims[dim] = indexes_len;
1945        let op = BackpropOp::new2(self, indexes, |t1, t2| Op::IndexSelect(t1, t2, dim));
1946        Ok(from_storage(storage, dims, op, false))
1947    }
1948
1949    /// Returns an iterator over position of the elements in the storage when ranging over the
1950    /// index tuples in lexicographic order.
1951    pub fn strided_index(&self) -> crate::StridedIndex<'_> {
1952        self.layout.strided_index()
1953    }
1954
1955    /// Similar to `strided_index` but returns the position of the start of each contiguous block
1956    /// as well as the length of the contiguous blocks. For a contiguous tensor, the index iterator
1957    /// will only return the start offset and the size would be the number of elements in the
1958    /// tensor.
1959    pub fn strided_blocks(&self) -> crate::StridedBlocks<'_> {
1960        self.layout.strided_blocks()
1961    }
1962
1963    /// Returns the data contained in a 1D tensor as a vector of scalar values.
1964    pub fn to_vec1<S: crate::WithDType>(&self) -> Result<Vec<S>> {
1965        if self.rank() != 1 {
1966            Err(Error::UnexpectedNumberOfDims {
1967                expected: 1,
1968                got: self.rank(),
1969                shape: self.shape().clone(),
1970            }
1971            .bt())?
1972        }
1973        let from_cpu_storage = |cpu_storage: &crate::CpuStorage| {
1974            let data = S::cpu_storage_as_slice(cpu_storage)?;
1975            let data = match self.layout.contiguous_offsets() {
1976                Some((o1, o2)) => data[o1..o2].to_vec(),
1977                None => self.strided_index().map(|i| data[i]).collect(),
1978            };
1979            Ok::<Vec<_>, Error>(data)
1980        };
1981        match &*self.storage() {
1982            Storage::Cpu(storage) => from_cpu_storage(storage),
1983            Storage::Cuda(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1984            Storage::Metal(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1985            #[cfg(feature = "rocm")]
1986            Storage::Rocm(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1987            #[cfg(feature = "vulkan")]
1988            Storage::Vulkan(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1989            #[cfg(feature = "wgpu")]
1990            Storage::Wgpu(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
1991        }
1992    }
1993
1994    /// Returns the data contained in a 2D tensor as a vector of vector of scalar values.
1995    pub fn to_vec2<S: crate::WithDType>(&self) -> Result<Vec<Vec<S>>> {
1996        let (dim1, dim2) = self.dims2()?;
1997        let from_cpu_storage = |cpu_storage: &crate::CpuStorage| {
1998            let data = S::cpu_storage_as_slice(cpu_storage)?;
1999            let mut rows = vec![];
2000            match self.layout.contiguous_offsets() {
2001                Some((o1, o2)) => {
2002                    let data = &data[o1..o2];
2003                    for idx_row in 0..dim1 {
2004                        rows.push(data[idx_row * dim2..(idx_row + 1) * dim2].to_vec())
2005                    }
2006                }
2007                None => {
2008                    let mut src_index = self.strided_index();
2009                    for _idx_row in 0..dim1 {
2010                        let row = (0..dim2).map(|_| data[src_index.next().unwrap()]).collect();
2011                        rows.push(row)
2012                    }
2013                    assert!(src_index.next().is_none());
2014                }
2015            }
2016            Ok(rows)
2017        };
2018        match &*self.storage() {
2019            Storage::Cpu(storage) => from_cpu_storage(storage),
2020            Storage::Cuda(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2021            Storage::Metal(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2022            #[cfg(feature = "rocm")]
2023            Storage::Rocm(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2024            #[cfg(feature = "vulkan")]
2025            Storage::Vulkan(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2026            #[cfg(feature = "wgpu")]
2027            Storage::Wgpu(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2028        }
2029    }
2030
2031    /// Returns the data contained in a 3D tensor.
2032    pub fn to_vec3<S: crate::WithDType>(&self) -> Result<Vec<Vec<Vec<S>>>> {
2033        let (dim1, dim2, dim3) = self.dims3()?;
2034        let from_cpu_storage = |cpu_storage: &crate::CpuStorage| {
2035            let data = S::cpu_storage_as_slice(cpu_storage)?;
2036            let mut top_rows = vec![];
2037            match self.layout.contiguous_offsets() {
2038                Some((o1, o2)) => {
2039                    let data = &data[o1..o2];
2040                    let dim23 = dim2 * dim3;
2041                    for idx1 in 0..dim1 {
2042                        let data = &data[idx1 * dim23..(idx1 + 1) * dim23];
2043                        let mut rows = vec![];
2044                        for idx2 in 0..dim2 {
2045                            rows.push(data[idx2 * dim3..(idx2 + 1) * dim3].to_vec())
2046                        }
2047                        top_rows.push(rows);
2048                    }
2049                }
2050                None => {
2051                    let mut src_index = self.strided_index();
2052                    for _idx in 0..dim1 {
2053                        let mut rows = vec![];
2054                        for _jdx in 0..dim2 {
2055                            let row = (0..dim3).map(|_| data[src_index.next().unwrap()]).collect();
2056                            rows.push(row)
2057                        }
2058                        top_rows.push(rows);
2059                    }
2060                    assert!(src_index.next().is_none());
2061                }
2062            }
2063            Ok(top_rows)
2064        };
2065        match &*self.storage() {
2066            Storage::Cpu(storage) => from_cpu_storage(storage),
2067            Storage::Cuda(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2068            Storage::Metal(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2069            #[cfg(feature = "rocm")]
2070            Storage::Rocm(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2071            #[cfg(feature = "vulkan")]
2072            Storage::Vulkan(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2073            #[cfg(feature = "wgpu")]
2074            Storage::Wgpu(storage) => from_cpu_storage(&storage.to_cpu_storage()?),
2075        }
2076    }
2077
2078    /// The dtype for the elements stored in the input tensor.
2079    pub fn dtype(&self) -> DType {
2080        self.dtype
2081    }
2082
2083    /// The device on which the input tensor is located.
2084    pub fn device(&self) -> &Device {
2085        &self.device
2086    }
2087
2088    /// The tensor shape, i.e. dimension sizes on each axis.
2089    pub fn shape(&self) -> &Shape {
2090        self.layout().shape()
2091    }
2092
2093    /// The dimension size for this tensor on each axis.
2094    pub fn dims(&self) -> &[usize] {
2095        self.shape().dims()
2096    }
2097
2098    /// The dimension size for a specified dimension index.
2099    pub fn dim<D: Dim>(&self, dim: D) -> Result<usize> {
2100        let dim = dim.to_index(self.shape(), "dim")?;
2101        Ok(self.dims()[dim])
2102    }
2103
2104    /// The layout of the input tensor, this stores both the shape of the tensor as well as the
2105    /// strides and the start offset to apply to the underlying storage.
2106    pub fn layout(&self) -> &Layout {
2107        &self.layout
2108    }
2109
2110    pub fn stride(&self) -> &[usize] {
2111        self.layout.stride()
2112    }
2113
2114    /// The number of dimensions for this tensor, 0 for a scalar tensor, 1 for a 1D tensor, etc.
2115    pub fn rank(&self) -> usize {
2116        self.shape().rank()
2117    }
2118
2119    /// The number of elements stored in this tensor.
2120    pub fn elem_count(&self) -> usize {
2121        self.shape().elem_count()
2122    }
2123
2124    /// The unique identifier for this tensor.
2125    pub fn id(&self) -> TensorId {
2126        self.id
2127    }
2128
2129    /// Whether this tensor is a variable or not. A variable is a tensor for which gradient is
2130    /// tracked and on which backpropagation can be performed.
2131    pub fn is_variable(&self) -> bool {
2132        self.is_variable
2133    }
2134
2135    pub(crate) fn op(&self) -> &Option<Op> {
2136        &self.op
2137    }
2138
2139    /// Computes the max of all the elements in this tensor and returns a tensor holding this
2140    /// scalar with zero dimensions.
2141    ///
2142    /// ```rust
2143    /// use hanzo_ml::{Tensor, Device};
2144    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2145    /// let tensor = tensor.max_all()?;
2146    /// assert_eq!(tensor.to_scalar::<f32>()?, 5.);
2147    /// # Ok::<(), hanzo_ml::Error>(())
2148    /// ```
2149    pub fn max_all(&self) -> Result<Tensor> {
2150        if self.rank() == 0 {
2151            Ok(self.clone())
2152        } else {
2153            self.flatten_all()?.max(0)
2154        }
2155    }
2156
2157    /// Computes the min of all the elements in this tensor and returns a tensor holding this
2158    /// scalar with zero dimensions.
2159    ///
2160    /// ```rust
2161    /// use hanzo_ml::{Tensor, Device};
2162    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2163    /// let tensor = tensor.min_all()?;
2164    /// assert_eq!(tensor.to_scalar::<f32>()?, 0.);
2165    /// # Ok::<(), hanzo_ml::Error>(())
2166    /// ```
2167    pub fn min_all(&self) -> Result<Tensor> {
2168        if self.rank() == 0 {
2169            Ok(self.clone())
2170        } else {
2171            self.flatten_all()?.min(0)
2172        }
2173    }
2174
2175    /// Computes the sum of all the elements in this tensor and returns a tensor holding this
2176    /// scalar with zero dimensions.
2177    ///
2178    /// ```rust
2179    /// use hanzo_ml::{Tensor, Device};
2180    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2181    /// let tensor = tensor.sum_all()?;
2182    /// assert_eq!(tensor.to_scalar::<f32>()?, 15.);
2183    /// # Ok::<(), hanzo_ml::Error>(())
2184    /// ```
2185    pub fn sum_all(&self) -> Result<Tensor> {
2186        let dims: Vec<_> = (0..self.rank()).collect();
2187        self.sum(dims)
2188    }
2189
2190    pub fn mean_all(&self) -> Result<Tensor> {
2191        self.sum_all()? / self.elem_count() as f64
2192    }
2193
2194    fn flatten_<D1: Dim, D2: Dim>(
2195        &self,
2196        start_dim: Option<D1>,
2197        end_dim: Option<D2>,
2198    ) -> Result<Tensor> {
2199        if self.rank() == 0 {
2200            self.reshape(1)
2201        } else {
2202            let start_dim = match start_dim {
2203                None => 0,
2204                Some(dim) => dim.to_index(self.shape(), "flatten")?,
2205            };
2206            let end_dim = match end_dim {
2207                None => self.rank() - 1,
2208                Some(dim) => dim.to_index(self.shape(), "flatten")?,
2209            };
2210            if start_dim < end_dim {
2211                let dims = self.dims();
2212                let mut dst_dims = dims[..start_dim].to_vec();
2213                dst_dims.push(dims[start_dim..end_dim + 1].iter().product::<usize>());
2214                if end_dim + 1 < dims.len() {
2215                    dst_dims.extend(&dims[end_dim + 1..]);
2216                }
2217                self.reshape(dst_dims)
2218            } else {
2219                Ok(self.clone())
2220            }
2221        }
2222    }
2223
2224    /// Flattens the input tensor on the dimension indexes from `start_dim` to `end_dim` (both
2225    /// inclusive).
2226    pub fn flatten<D1: Dim, D2: Dim>(&self, start_dim: D1, end_dim: D2) -> Result<Tensor> {
2227        self.flatten_(Some(start_dim), Some(end_dim))
2228    }
2229
2230    /// Flattens the input tensor on the dimension indexes from `0` to `end_dim` (inclusive).
2231    pub fn flatten_to<D: Dim>(&self, end_dim: D) -> Result<Tensor> {
2232        self.flatten_(None::<usize>, Some(end_dim))
2233    }
2234
2235    /// Flattens the input tensor on the dimension indexes from `start_dim` (inclusive) to the last
2236    /// dimension.
2237    pub fn flatten_from<D: Dim>(&self, start_dim: D) -> Result<Tensor> {
2238        self.flatten_(Some(start_dim), None::<usize>)
2239    }
2240
2241    /// Flattens the input tensor by reshaping it into a one dimension tensor.
2242    ///
2243    /// ```rust
2244    /// use hanzo_ml::{Tensor, Device};
2245    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2246    /// let tensor = tensor.flatten_all()?;
2247    /// assert_eq!(tensor.to_vec1::<f32>()?, &[0., 1., 2., 3., 4., 5.]);
2248    /// # Ok::<(), hanzo_ml::Error>(())
2249    /// ```
2250    pub fn flatten_all(&self) -> Result<Tensor> {
2251        self.flatten_(None::<usize>, None::<usize>)
2252    }
2253
2254    /// Returns the sub-tensor fixing the index at `i` on the first dimension.
2255    ///
2256    /// ```rust
2257    /// use hanzo_ml::{Tensor, Device};
2258    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2259    /// let t = tensor.get(0)?;
2260    /// assert_eq!(t.to_vec1::<f32>()?, &[0., 1.]);
2261    /// let t = tensor.get(1)?;
2262    /// assert_eq!(t.to_vec1::<f32>()?, &[2., 3.]);
2263    /// # Ok::<(), hanzo_ml::Error>(())
2264    /// ```
2265    pub fn get(&self, i: usize) -> Result<Tensor> {
2266        let dims = self.dims();
2267        if dims.is_empty() {
2268            Ok(self.clone())
2269        } else {
2270            self.narrow(0, i, 1)?.reshape(&dims[1..])
2271        }
2272    }
2273
2274    /// Returns the sub-tensor fixing the index at `index` on the dimension `dim`.
2275    ///
2276    /// ```rust
2277    /// use hanzo_ml::{Tensor, Device};
2278    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2279    /// let t = tensor.get_on_dim(1, 0)?;
2280    /// assert_eq!(t.to_vec1::<f32>()?, &[0., 2., 4.]);
2281    /// let t = tensor.get_on_dim(1, 1)?;
2282    /// assert_eq!(t.to_vec1::<f32>()?, &[1., 3., 5.]);
2283    /// let t = tensor.get_on_dim(0, 1)?;
2284    /// assert_eq!(t.to_vec1::<f32>()?, &[2., 3.]);
2285    /// # Ok::<(), hanzo_ml::Error>(())
2286    /// ```
2287    pub fn get_on_dim<D: Dim>(&self, dim: D, index: usize) -> Result<Tensor> {
2288        let dim = dim.to_index(self.shape(), "get_on_dim")?;
2289        self.narrow(dim, index, 1)?.squeeze(dim)
2290    }
2291
2292    /// Returns a tensor that is a transposed version of the input, the two last dimensions of the
2293    /// input are swapped.
2294    ///
2295    /// ```rust
2296    /// use hanzo_ml::{Tensor, Device};
2297    /// let tensor = Tensor::new(&[[0f32, 1.], [2., 3.], [4., 5.]], &Device::Cpu)?;
2298    /// let tensor = tensor.t()?;
2299    /// assert_eq!(tensor.to_vec2::<f32>()?, &[[0.0, 2.0, 4.0], [1.0, 3.0, 5.0]]);
2300    /// # Ok::<(), hanzo_ml::Error>(())
2301    /// ```
2302    pub fn t(&self) -> Result<Tensor> {
2303        let rank = self.rank();
2304        if rank < 2 {
2305            Err(Error::UnexpectedNumberOfDims {
2306                expected: 2,
2307                got: rank,
2308                shape: self.shape().clone(),
2309            }
2310            .bt())?
2311        }
2312        self.transpose(rank - 2, rank - 1)
2313    }
2314
2315    /// Returns a tensor that is a transposed version of the input, the given dimensions are
2316    /// swapped.
2317    pub fn transpose<D1: Dim, D2: Dim>(&self, dim1: D1, dim2: D2) -> Result<Tensor> {
2318        let dim1 = dim1.to_index(self.shape(), "transpose")?;
2319        let dim2 = dim2.to_index(self.shape(), "transpose")?;
2320        if dim1 == dim2 {
2321            return Ok(self.clone());
2322        }
2323        let op = BackpropOp::new1(self, |t| Op::Transpose(t, dim1, dim2));
2324        let tensor_ = Tensor_ {
2325            id: TensorId::new(),
2326            storage: self.storage.clone(),
2327            layout: self.layout.transpose(dim1, dim2)?,
2328            op,
2329            is_variable: false,
2330            dtype: self.dtype,
2331            device: self.device.clone(),
2332        };
2333        Ok(Tensor(Arc::new(tensor_)))
2334    }
2335
2336    /// Returns a tensor with the same data as the input where the dimensions have been permuted.
2337    /// dims must be a permutation, i.e. include each dimension index exactly once.
2338    ///
2339    /// ```rust
2340    /// use hanzo_ml::{Tensor, Device};
2341    /// let tensor = Tensor::arange(0u32, 120u32, &Device::Cpu)?.reshape((2, 3, 4, 5))?;
2342    /// assert_eq!(tensor.dims(), &[2, 3, 4, 5]);
2343    /// let tensor = tensor.permute((2, 3, 1, 0))?;
2344    /// assert_eq!(tensor.dims(), &[4, 5, 3, 2]);
2345    /// # Ok::<(), hanzo_ml::Error>(())
2346    /// ```
2347    pub fn permute<D: Dims>(&self, dims: D) -> Result<Tensor> {
2348        let dims = dims.to_indexes(self.shape(), "permute")?;
2349        // O(n^2) permutation check but these arrays are small.
2350        let is_permutation =
2351            dims.len() == self.rank() && (0..dims.len()).all(|i| dims.contains(&i));
2352        if !is_permutation {
2353            bail!(
2354                "dimension mismatch in permute, tensor {:?}, dims: {:?}",
2355                self.dims(),
2356                dims
2357            )
2358        }
2359        let op = BackpropOp::new1(self, |t| Op::Permute(t, dims.clone()));
2360        let tensor_ = Tensor_ {
2361            id: TensorId::new(),
2362            storage: self.storage.clone(),
2363            layout: self.layout.permute(&dims)?,
2364            op,
2365            is_variable: false,
2366            dtype: self.dtype,
2367            device: self.device.clone(),
2368        };
2369        Ok(Tensor(Arc::new(tensor_)))
2370    }
2371
2372    /// Returns true if the data is stored in a C contiguous (aka row major) way.
2373    pub fn is_contiguous(&self) -> bool {
2374        self.layout.is_contiguous()
2375    }
2376
2377    /// Returns true if the data is stored in a Fortran contiguous (aka column major) way.
2378    pub fn is_fortran_contiguous(&self) -> bool {
2379        self.layout.is_fortran_contiguous()
2380    }
2381
2382    /// Compared to clone, this copies the actual storage but may fail because of running out of
2383    /// memory.
2384    pub fn copy(&self) -> Result<Tensor> {
2385        let op = BackpropOp::new1(self, Op::Copy);
2386        let tensor_ = Tensor_ {
2387            id: TensorId::new(),
2388            storage: Arc::new(RwLock::new(self.storage().try_clone(self.layout())?)),
2389            layout: self.layout.clone(),
2390            op,
2391            is_variable: false,
2392            dtype: self.dtype,
2393            device: self.device.clone(),
2394        };
2395        Ok(Tensor(Arc::new(tensor_)))
2396    }
2397
2398    /// Returns a new tensor detached from the current graph, gradient are not propagated through
2399    /// this new node. The storage of this tensor is shared with the initial tensor.
2400    ///
2401    /// If the tensor is already detached from the computation graph, the same tensor is returned.
2402    pub fn detach(&self) -> Tensor {
2403        if self.op.is_none() && !self.is_variable {
2404            self.clone()
2405        } else {
2406            let tensor_ = Tensor_ {
2407                id: TensorId::new(),
2408                storage: self.storage.clone(),
2409                layout: self.layout.clone(),
2410                op: BackpropOp::none(),
2411                is_variable: false,
2412                dtype: self.dtype,
2413                device: self.device.clone(),
2414            };
2415            Tensor(Arc::new(tensor_))
2416        }
2417    }
2418
2419    /// If the target device is the same as the tensor device, only a shallow copy is performed.
2420    pub fn to_device(&self, device: &Device) -> Result<Tensor> {
2421        if self.device().same_device(device) {
2422            Ok(self.clone())
2423        } else {
2424            let storage = match (&*self.storage(), device) {
2425                (Storage::Cpu(storage), Device::Cuda(cuda)) => {
2426                    Storage::Cuda(cuda.storage_from_cpu_storage(storage)?)
2427                }
2428                (Storage::Cpu(storage), Device::Metal(metal)) => {
2429                    Storage::Metal(metal.storage_from_cpu_storage(storage)?)
2430                }
2431                (Storage::Cuda(storage), Device::Cpu) => Storage::Cpu(storage.to_cpu_storage()?),
2432                (Storage::Metal(storage), Device::Cpu) => Storage::Cpu(storage.to_cpu_storage()?),
2433                #[cfg(feature = "rocm")]
2434                (Storage::Rocm(storage), Device::Cpu) => Storage::Cpu(storage.to_cpu_storage()?),
2435                #[cfg(feature = "vulkan")]
2436                (Storage::Vulkan(storage), Device::Cpu) => Storage::Cpu(storage.to_cpu_storage()?),
2437                #[cfg(feature = "wgpu")]
2438                (Storage::Wgpu(storage), Device::Cpu) => Storage::Cpu(storage.to_cpu_storage()?),
2439                #[cfg(feature = "rocm")]
2440                (Storage::Cpu(storage), Device::Rocm(rocm)) => {
2441                    Storage::Rocm(rocm.storage_from_cpu_storage(storage)?)
2442                }
2443                #[cfg(feature = "vulkan")]
2444                (Storage::Cpu(storage), Device::Vulkan(vulkan)) => {
2445                    Storage::Vulkan(vulkan.storage_from_cpu_storage(storage)?)
2446                }
2447                #[cfg(feature = "wgpu")]
2448                (Storage::Cpu(storage), Device::Wgpu(wgpu)) => {
2449                    Storage::Wgpu(wgpu.storage_from_cpu_storage(storage)?)
2450                }
2451                #[cfg(feature = "rocm")]
2452                (Storage::Rocm(storage), Device::Rocm(rocm)) => {
2453                    let cpu_storage = storage.to_cpu_storage()?;
2454                    Storage::Rocm(rocm.storage_from_cpu_storage(&cpu_storage)?)
2455                }
2456                #[cfg(feature = "vulkan")]
2457                (Storage::Vulkan(storage), Device::Vulkan(vulkan)) => {
2458                    let cpu_storage = storage.to_cpu_storage()?;
2459                    Storage::Vulkan(vulkan.storage_from_cpu_storage(&cpu_storage)?)
2460                }
2461                #[cfg(feature = "wgpu")]
2462                (Storage::Wgpu(storage), Device::Wgpu(wgpu)) => {
2463                    let cpu_storage = storage.to_cpu_storage()?;
2464                    Storage::Wgpu(wgpu.storage_from_cpu_storage(&cpu_storage)?)
2465                }
2466                (Storage::Cuda(storage), Device::Cuda(cuda)) => {
2467                    // TODO: Avoid passing through the cpu storage here, especially if the gpu ids
2468                    // are the same.
2469                    let cpu_storage = storage.to_cpu_storage()?;
2470                    Storage::Cuda(cuda.storage_from_cpu_storage(&cpu_storage)?)
2471                }
2472                (Storage::Cpu(storage), Device::Cpu) => Storage::Cpu(storage.clone()),
2473                _ => {
2474                    bail!(
2475                        "not implemented yet, self.device: {:?}, device: {:?}",
2476                        self.device(),
2477                        device
2478                    )
2479                }
2480            };
2481            let op = BackpropOp::new1(self, Op::ToDevice);
2482            let tensor_ = Tensor_ {
2483                id: TensorId::new(),
2484                storage: Arc::new(RwLock::new(storage)),
2485                layout: self.layout.clone(),
2486                op,
2487                is_variable: false,
2488                dtype: self.dtype,
2489                device: device.clone(),
2490            };
2491            Ok(Tensor(Arc::new(tensor_)))
2492        }
2493    }
2494
2495    /// Returns a new tensor duplicating data from the original tensor. New dimensions are inserted
2496    /// on the left.
2497    pub fn broadcast_left<S: Into<Shape>>(&self, left_shape: S) -> Result<Self> {
2498        let left_shape = left_shape.into();
2499        let mut dims = left_shape.into_dims();
2500        dims.extend(self.dims());
2501        self.broadcast_as(dims)
2502    }
2503
2504    /// Broadcast the input tensor to the target shape. This returns an error if the input shape is
2505    /// not compatible with the target shape.
2506    ///
2507    /// If the input shape is `i_1, i_2, ... i_k`, the target shape has to have `k` dimensions or
2508    /// more and shape `j_1, ..., j_l, t_1, t_2, ..., t_k`. The dimensions `j_1` to `j_l` can have
2509    /// any value, the dimension `t_a` must be equal to `i_a` if `i_a` is different from 1. If
2510    /// `i_a` is equal to 1, any value can be used.
2511    pub fn broadcast_as<S: Into<Shape>>(&self, shape: S) -> Result<Self> {
2512        let tensor_ = Tensor_ {
2513            id: TensorId::new(),
2514            storage: self.storage.clone(),
2515            layout: self.layout.broadcast_as(shape)?,
2516            op: BackpropOp::new1(self, Op::Broadcast),
2517            is_variable: false,
2518            dtype: self.dtype,
2519            device: self.device.clone(),
2520        };
2521        Ok(Tensor(Arc::new(tensor_)))
2522    }
2523
2524    /// An alias for broadcast_as.
2525    pub fn expand<S: Into<Shape>>(&self, shape: S) -> Result<Self> {
2526        self.broadcast_as(shape)
2527    }
2528
2529    /// Casts the input tensor to the target `dtype`.
2530    ///
2531    /// ```rust
2532    /// use hanzo_ml::{Tensor, Device};
2533    /// let tensor = Tensor::new(3.14159265358979f64, &Device::Cpu)?;
2534    /// assert_eq!(tensor.to_scalar::<f64>()?, 3.14159265358979);
2535    /// let tensor = tensor.to_dtype(hanzo_ml::DType::F32)?;
2536    /// assert_eq!(tensor.to_scalar::<f32>()?, 3.1415927);
2537    /// # Ok::<(), hanzo_ml::Error>(())
2538    /// ```
2539    pub fn to_dtype(&self, dtype: DType) -> Result<Self> {
2540        if self.dtype() == dtype {
2541            Ok(self.clone())
2542        } else {
2543            let shape = self.shape();
2544            let storage = self.storage().to_dtype(self.layout(), dtype)?;
2545            let op = BackpropOp::new1(self, Op::ToDType);
2546            Ok(from_storage(storage, shape.clone(), op, false))
2547        }
2548    }
2549
2550    /// Returns a tensor that is in row major order. This is the same as the original tensor if it
2551    /// was already contiguous, otherwise a copy is triggered.
2552    pub fn contiguous(&self) -> Result<Tensor> {
2553        if self.is_contiguous() {
2554            Ok(self.clone())
2555        } else {
2556            let shape = self.shape();
2557            let mut storage = unsafe { self.device().alloc_uninit(shape, self.dtype())? };
2558            self.storage()
2559                .copy_strided_src(&mut storage, 0, self.layout())?;
2560            let op = BackpropOp::new1(self, Op::Copy);
2561            Ok(from_storage(storage, shape.clone(), op, false))
2562        }
2563    }
2564
2565    /// Returns a tensor that is in row major order. This always makes a copy.
2566    pub fn force_contiguous(&self) -> Result<Tensor> {
2567        let shape = self.shape();
2568        let mut storage = unsafe { self.device().alloc_uninit(shape, self.dtype())? };
2569        self.storage()
2570            .copy_strided_src(&mut storage, 0, self.layout())?;
2571        let op = BackpropOp::new1(self, Op::Copy);
2572        Ok(from_storage(storage, shape.clone(), op, false))
2573    }
2574
2575    /// Create a variable based on the values currently stored in a tensor. The storage is always
2576    /// copied.
2577    pub(crate) fn make_var(&self) -> Result<Tensor> {
2578        let shape = self.shape().clone();
2579        let mut storage = unsafe { self.device().alloc_uninit(&shape, self.dtype())? };
2580        self.storage()
2581            .copy_strided_src(&mut storage, 0, self.layout())?;
2582        Ok(from_storage(storage, shape, BackpropOp::none(), true))
2583    }
2584
2585    /// Reshape returns a tensor with the target shape provided that the number of elements of the
2586    /// original tensor is the same.
2587    /// If the input tensor is contiguous, this is a view on the original data. Otherwise this uses
2588    /// a new storage and copies the data over, the returned tensor is always contiguous.
2589    ///
2590    /// The shape can be specified using a tuple of `usize` and at most one `()` in which case
2591    /// the behavior is the same as when using `-1` in PyTorch: this dimension size is adjusted so
2592    /// as to match the number of elements in the tensor.
2593    ///
2594    /// ```rust
2595    /// # use hanzo_ml::{Tensor, DType, Device, D};
2596    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
2597    ///
2598    /// let c = a.reshape((1, 6))?;
2599    /// assert_eq!(c.shape().dims(), &[1, 6]);
2600    ///
2601    /// let c = a.reshape((3, 2))?;
2602    /// assert_eq!(c.shape().dims(), &[3, 2]);
2603    ///
2604    /// let c = a.reshape((2, (), 1))?;
2605    /// assert_eq!(c.shape().dims(), &[2, 3, 1]);
2606    ///
2607    /// # Ok::<(), hanzo_ml::Error>(())
2608    /// ```
2609    pub fn reshape<S: ShapeWithOneHole>(&self, s: S) -> Result<Tensor> {
2610        let shape = s.into_shape(self.elem_count())?;
2611        if shape.elem_count() != self.elem_count() {
2612            return Err(Error::ShapeMismatchBinaryOp {
2613                lhs: self.shape().clone(),
2614                rhs: shape,
2615                op: "reshape",
2616            }
2617            .bt());
2618        }
2619        let op = BackpropOp::new1(self, Op::Reshape);
2620        if self.is_contiguous() {
2621            let tensor_ = Tensor_ {
2622                id: TensorId::new(),
2623                storage: self.storage.clone(),
2624                layout: Layout::contiguous_with_offset(shape, self.layout.start_offset()),
2625                op,
2626                is_variable: false,
2627                dtype: self.dtype,
2628                device: self.device.clone(),
2629            };
2630            Ok(Tensor(Arc::new(tensor_)))
2631        } else {
2632            let mut storage = unsafe { self.device().alloc_uninit(&shape, self.dtype())? };
2633            self.storage()
2634                .copy_strided_src(&mut storage, 0, self.layout())?;
2635            Ok(from_storage(storage, shape, op, false))
2636        }
2637    }
2638
2639    /// Creates a new tensor with the specified dimension removed if its size was one.
2640    ///
2641    /// ```rust
2642    /// # use hanzo_ml::{Tensor, DType, Device, D};
2643    /// let a = Tensor::zeros((2, 3, 1), DType::F32, &Device::Cpu)?;
2644    ///
2645    /// let c = a.squeeze(2)?;
2646    /// assert_eq!(c.shape().dims(), &[2, 3]);
2647    ///
2648    /// let c = a.squeeze(D::Minus1)?;
2649    /// assert_eq!(c.shape().dims(), &[2, 3]);
2650    /// # Ok::<(), hanzo_ml::Error>(())
2651    /// ```
2652    pub fn squeeze<D: Dim>(&self, dim: D) -> Result<Self> {
2653        // The PyTorch semantics are to return the same tensor if the target dimension
2654        // does not have a size of 1.
2655        let dims = self.dims();
2656        let dim = dim.to_index(self.shape(), "squeeze")?;
2657        if dims[dim] == 1 {
2658            let mut dims = dims.to_vec();
2659            let mut strides = self.stride().to_vec();
2660            dims.remove(dim);
2661            strides.remove(dim);
2662            let tensor_ = Tensor_ {
2663                id: TensorId::new(),
2664                storage: self.storage.clone(),
2665                layout: Layout::new(dims.into(), strides, self.layout.start_offset()),
2666                op: BackpropOp::new1(self, Op::Reshape),
2667                is_variable: false,
2668                dtype: self.dtype,
2669                device: self.device.clone(),
2670            };
2671            Ok(Tensor(Arc::new(tensor_)))
2672        } else {
2673            Ok(self.clone())
2674        }
2675    }
2676
2677    /// Creates a new tensor with a dimension of size one inserted at the specified position.
2678    ///
2679    /// ```rust
2680    /// # use hanzo_ml::{Tensor, DType, Device, D};
2681    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
2682    ///
2683    /// let c = a.unsqueeze(0)?;
2684    /// assert_eq!(c.shape().dims(), &[1, 2, 3]);
2685    ///
2686    /// let c = a.unsqueeze(D::Minus1)?;
2687    /// assert_eq!(c.shape().dims(), &[2, 3, 1]);
2688    /// # Ok::<(), hanzo_ml::Error>(())
2689    /// ```
2690    pub fn unsqueeze<D: Dim>(&self, dim: D) -> Result<Self> {
2691        let mut dims = self.dims().to_vec();
2692        let mut strides = self.stride().to_vec();
2693        let dim = dim.to_index_plus_one(self.shape(), "unsqueeze")?;
2694        // Cannot panic because to_index_plus_one already checks dimensions
2695        dims.insert(dim, 1);
2696        // Any stride would work here, but we pick one so as to maximize the probability to remain
2697        // C contiguous.
2698        let stride = if dim < strides.len() { strides[dim] } else { 1 };
2699        strides.insert(dim, stride);
2700        let tensor_ = Tensor_ {
2701            id: TensorId::new(),
2702            storage: self.storage.clone(),
2703            layout: Layout::new(dims.into(), strides, self.layout.start_offset()),
2704            op: BackpropOp::new1(self, Op::Reshape),
2705            is_variable: false,
2706            dtype: self.dtype,
2707            device: self.device.clone(),
2708        };
2709        Ok(Tensor(Arc::new(tensor_)))
2710    }
2711
2712    /// Stacks two or more tensors along a particular dimension.
2713    ///
2714    /// All tensors must have the same rank, and the output has one additional rank
2715    ///
2716    /// ```rust
2717    /// # use hanzo_ml::{Tensor, DType, Device};
2718    /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
2719    /// let b = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?;
2720    ///
2721    /// let c = Tensor::stack(&[&a, &b], 0)?;
2722    /// assert_eq!(c.shape().dims(), &[2, 2, 3]);
2723    ///
2724    /// let c = Tensor::stack(&[&a, &b], 2)?;
2725    /// assert_eq!(c.shape().dims(), &[2, 3, 2]);
2726    /// # Ok::<(), hanzo_ml::Error>(())
2727    /// ```
2728    pub fn stack<A: AsRef<Tensor>, D: Dim>(args: &[A], dim: D) -> Result<Self> {
2729        if args.is_empty() {
2730            Err(Error::OpRequiresAtLeastOneTensor { op: "stack" }.bt())?
2731        }
2732        let dim = dim.to_index_plus_one(args[0].as_ref().shape(), "stack")?;
2733        let args = args
2734            .iter()
2735            .map(|t| t.as_ref().unsqueeze(dim))
2736            .collect::<Result<Vec<_>>>()?;
2737        Self::cat(&args, dim)
2738    }
2739
2740    /// Pad the input tensor using 0s along dimension `dim`. This adds `left` elements before the
2741    /// input tensor values and `right` elements after.
2742    pub fn pad_with_zeros<D: Dim>(&self, dim: D, left: usize, right: usize) -> Result<Self> {
2743        if left == 0 && right == 0 {
2744            Ok(self.clone())
2745        } else if left == 0 {
2746            let dim = dim.to_index(self.shape(), "pad_with_zeros")?;
2747            let mut dims = self.dims().to_vec();
2748            dims[dim] = right;
2749            let right = Tensor::zeros(dims.as_slice(), self.dtype, self.device())?;
2750            Tensor::cat(&[self, &right], dim)
2751        } else if right == 0 {
2752            let dim = dim.to_index(self.shape(), "pad_with_zeros")?;
2753            let mut dims = self.dims().to_vec();
2754            dims[dim] = left;
2755            let left = Tensor::zeros(dims.as_slice(), self.dtype, self.device())?;
2756            Tensor::cat(&[&left, self], dim)
2757        } else {
2758            let dim = dim.to_index(self.shape(), "pad_with_zeros")?;
2759            let mut dims = self.dims().to_vec();
2760            dims[dim] = left;
2761            let left = Tensor::zeros(dims.as_slice(), self.dtype, self.device())?;
2762            dims[dim] = right;
2763            let right = Tensor::zeros(dims.as_slice(), self.dtype, self.device())?;
2764            Tensor::cat(&[&left, self, &right], dim)
2765        }
2766    }
2767
2768    /// Pad the input tensor using same values along dimension `dim`. This adds `left` elements before the
2769    /// input tensor values and `right` elements after.
2770    pub fn pad_with_same<D: Dim>(&self, dim: D, left: usize, right: usize) -> Result<Self> {
2771        if left == 0 && right == 0 {
2772            Ok(self.clone())
2773        } else if self.elem_count() == 0 {
2774            bail!("cannot use pad_with_same on an empty tensor")
2775        } else if left == 0 {
2776            let dim = dim.to_index(self.shape(), "pad_with_same")?;
2777            let r = self.narrow(dim, self.dim(dim)? - 1, 1)?;
2778            let mut v = vec![self];
2779            for _ in 0..right {
2780                v.push(&r)
2781            }
2782            Tensor::cat(&v, dim)
2783        } else if right == 0 {
2784            let dim = dim.to_index(self.shape(), "pad_with_same")?;
2785            let l = self.narrow(dim, 0, 1)?;
2786            let mut v = vec![];
2787            for _ in 0..left {
2788                v.push(&l)
2789            }
2790            v.push(self);
2791            Tensor::cat(&v, dim)
2792        } else {
2793            let dim = dim.to_index(self.shape(), "pad_with_same")?;
2794            let l = self.narrow(dim, 0, 1)?;
2795            let r = self.narrow(dim, self.dim(dim)? - 1, 1)?;
2796            let mut v = vec![];
2797            for _ in 0..left {
2798                v.push(&l)
2799            }
2800            v.push(self);
2801            for _ in 0..right {
2802                v.push(&r)
2803            }
2804            Tensor::cat(&v, dim)
2805        }
2806    }
2807
2808    /// Run the `forward` method of `m` on `self`.
2809    pub fn apply<M: crate::Module>(&self, m: &M) -> Result<Self> {
2810        m.forward(self)
2811    }
2812
2813    /// Run the `forward` method of `m` on `self`.
2814    pub fn apply_t<M: crate::ModuleT>(&self, m: &M, train: bool) -> Result<Self> {
2815        m.forward_t(self, train)
2816    }
2817
2818    /// Acquire read lock on storage and returns guard.
2819    /// `read_recursive` allows for shared read access.
2820    pub(crate) fn storage(&self) -> StorageRef<'_> {
2821        self.storage.read_recursive()
2822    }
2823
2824    /// Acquire write lock on storage and returns guard.
2825    pub(crate) fn storage_mut(&self) -> StorageMutRef<'_> {
2826        self.storage.write()
2827    }
2828
2829    // If we extend the visibility of this function to be usable outside of this crate, we should
2830    // make it unsafe.
2831    pub(crate) fn storage_mut_and_layout(&self) -> (StorageMutRef<'_>, &Layout) {
2832        let storage = self.storage.write();
2833        (storage, &self.layout)
2834    }
2835
2836    /// The storage used by this tensor, together with the layout to use to access it safely.
2837    pub fn storage_and_layout(&self) -> (StorageRef<'_>, &Layout) {
2838        let storage = self.storage.read();
2839        (storage, &self.layout)
2840    }
2841
2842    /// Unique key for this tensor's storage. Equal keys mean the tensors share the same allocation.
2843    #[inline]
2844    pub(crate) fn storage_key(&self) -> usize {
2845        let lock: &RwLock<Storage> = self.storage.as_ref();
2846        std::ptr::from_ref(lock).addr()
2847    }
2848
2849    /// Check if two tensors share the same underlying allocation.
2850    #[inline]
2851    pub(crate) fn same_storage(&self, rhs: &Self) -> bool {
2852        self.storage_key() == rhs.storage_key()
2853    }
2854
2855    /// Normalize a 'relative' axis value: positive values are kept, negative
2856    /// values means counting the dimensions from the back.
2857    pub fn normalize_axis(&self, axis: i64) -> Result<usize> {
2858        let rank = self.rank() as i64;
2859        if rank <= axis {
2860            bail!("axis {axis} is too large, tensor rank {rank}")
2861        } else if 0 <= axis {
2862            Ok(axis as usize)
2863        } else {
2864            let naxis = rank + axis;
2865            if naxis < 0 {
2866                bail!("axis {axis} is too small, tensor rank {rank}")
2867            }
2868            Ok(naxis as usize)
2869        }
2870    }
2871
2872    /// Returns a lower triangular matrix of ones of size n by n.
2873    pub fn tril2(n: usize, dtype: DType, device: &Device) -> Result<Self> {
2874        let t = Tensor::arange(0u32, n as u32, device)?;
2875        let t1 = t.reshape((1, n))?.broadcast_as((n, n))?;
2876        let t2 = t.reshape((n, 1))?.broadcast_as((n, n))?;
2877        t1.le(&t2)?.to_dtype(dtype)
2878    }
2879
2880    /// Returns an upper triangular matrix of ones of size n by n.
2881    pub fn triu2(n: usize, dtype: DType, device: &Device) -> Result<Self> {
2882        let t = Tensor::arange(0u32, n as u32, device)?;
2883        let t1 = t.reshape((1, n))?.broadcast_as((n, n))?;
2884        let t2 = t.reshape((n, 1))?.broadcast_as((n, n))?;
2885        t1.ge(&t2)?.to_dtype(dtype)
2886    }
2887
2888    /// Returns a matrix with a diagonal of ones of size n by n.
2889    pub fn eye(n: usize, dtype: DType, device: &Device) -> Result<Self> {
2890        let t = Tensor::arange(0u32, n as u32, device)?;
2891        let t1 = t.reshape((1, n))?.broadcast_as((n, n))?;
2892        let t2 = t.reshape((n, 1))?.broadcast_as((n, n))?;
2893        t1.eq(&t2)?.to_dtype(dtype)
2894    }
2895
2896    /// Returns the cumulative sum of elements of the input tensor summed over the specified
2897    /// dimension.
2898    ///
2899    /// This operation is most efficient when dim is the last dimension of the tensor.
2900    pub fn cumsum<D: Dim>(&self, dim: D) -> Result<Self> {
2901        let dim = dim.to_index(self.shape(), "cumsum")?;
2902        let rank = self.rank();
2903        if rank == 0 {
2904            return Ok(self.clone());
2905        }
2906        let n_axis = self.dim(dim)?;
2907        let triu = Tensor::triu2(n_axis, self.dtype(), self.device())?;
2908        if rank == 1 {
2909            self.unsqueeze(0)?.matmul(&triu)?.squeeze(0)
2910        } else {
2911            let last = rank - 1;
2912            let t = self.transpose(dim, last)?;
2913            let t = t.broadcast_matmul(&triu)?;
2914            t.transpose(dim, last)
2915        }
2916    }
2917
2918    /// Returns a copy of `self` where the values within `ranges` have been replaced with the
2919    /// content of `src`.
2920    pub fn slice_assign<D: std::ops::RangeBounds<usize>>(
2921        &self,
2922        ranges: &[D],
2923        src: &Tensor,
2924    ) -> Result<Self> {
2925        let src_dims = src.dims();
2926        let self_dims = self.dims();
2927        if self_dims.len() != src_dims.len() {
2928            bail!(
2929                "slice-assign requires input with the same rank {} <> {}",
2930                self_dims.len(),
2931                src_dims.len()
2932            )
2933        }
2934        if self_dims.len() != ranges.len() {
2935            bail!(
2936                "slice-assign requires input with the same rank as there are ranges {} <> {}",
2937                self_dims.len(),
2938                ranges.len()
2939            )
2940        }
2941        let mut src = src.clone();
2942        let mut mask = Self::ones(src.shape(), DType::U8, src.device())?;
2943        for (i, range) in ranges.iter().enumerate() {
2944            let start_included = match range.start_bound() {
2945                std::ops::Bound::Unbounded => 0,
2946                std::ops::Bound::Included(v) => *v,
2947                std::ops::Bound::Excluded(v) => *v + 1,
2948            };
2949            let end_excluded = match range.end_bound() {
2950                std::ops::Bound::Unbounded => self_dims[i],
2951                std::ops::Bound::Included(v) => *v + 1,
2952                std::ops::Bound::Excluded(v) => *v,
2953            };
2954            if end_excluded <= start_included {
2955                bail!("slice-assign: empty range for dim {i}, {start_included} {end_excluded}")
2956            }
2957            if self_dims[i] < end_excluded {
2958                bail!(
2959                    "slice-assign: upper bound is out of range for dim {i}, {end_excluded} {}",
2960                    self_dims[i]
2961                )
2962            }
2963            if end_excluded - start_included != src_dims[i] {
2964                bail!(
2965                    "slice-assign: the range for dim {i} ({start_included}..{end_excluded}) does not match the size of src {}", src_dims[i]
2966                )
2967            }
2968            src = src.pad_with_zeros(i, start_included, self_dims[i] - end_excluded)?;
2969            mask = mask.pad_with_zeros(i, start_included, self_dims[i] - end_excluded)?
2970        }
2971        mask.where_cond(/* on_true= */ &src, /* on_false= */ self)
2972    }
2973
2974    /// Returns log(sum(exp(tensor), dim)).
2975    pub fn log_sum_exp<D: Dims>(&self, sum_dims: D) -> Result<Self> {
2976        let sum_dims = sum_dims.to_indexes(self.shape(), "log-sum-exp")?;
2977        if sum_dims.is_empty() {
2978            return Ok(self.clone());
2979        }
2980        let max = sum_dims[1..]
2981            .iter()
2982            .try_fold(self.max_keepdim(sum_dims[0])?, |max, &dim| {
2983                max.max_keepdim(dim)
2984            })?;
2985        let exp = self.broadcast_sub(&max)?.exp()?;
2986        let sum = exp.sum(sum_dims.clone())?;
2987
2988        sum.log()? + max.squeeze_dims(&sum_dims)
2989    }
2990
2991    /// Pointwise pow operation.
2992    pub fn pow(&self, rhs: &Tensor) -> Result<Self> {
2993        rhs.mul(&self.log()?)?.exp()
2994    }
2995
2996    /// Broadcasting version of `pow`.
2997    pub fn broadcast_pow(&self, rhs: &Tensor) -> Result<Self> {
2998        rhs.broadcast_mul(&self.log()?)?.exp()
2999    }
3000
3001    /// Returns a new tensor with the order of elements reversed along the specified dimensions.
3002    /// This function makes a copy of the tensor’s data.
3003    ///
3004    /// ```rust
3005    /// # use hanzo_ml::{Tensor, Device};
3006    /// let t = Tensor::arange(0., 6., &Device::Cpu)?.reshape((2, 3))?;
3007    /// assert_eq!(t.to_vec2::<f64>()?, &[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0]]);
3008    /// let t_flipped = t.flip(&[0])?;
3009    /// assert_eq!(t_flipped.to_vec2::<f64>()?, &[[3.0, 4.0, 5.0], [0.0, 1.0, 2.0]]);
3010    /// # Ok::<(), hanzo_ml::Error>(())
3011    /// ```
3012    pub fn flip(&self, dims: &[usize]) -> Result<Tensor> {
3013        let mut result = self.clone();
3014        for &dim in dims.iter() {
3015            let size = result.dim(dim)?;
3016            let indices: Vec<i64> = (0..size).rev().map(|x| x as i64).collect();
3017            let indices_tensor = Tensor::from_vec(indices, (size,), result.device())?;
3018            result = result.index_select(&indices_tensor, dim)?;
3019        }
3020        Ok(result)
3021    }
3022
3023    /// Returns a view of which contains all slices of size `size` from self tensor in the dimension
3024    /// `dim` and stepped by `step`.
3025    pub fn unfold<D: Dim>(&self, dim: D, size: usize, step: usize) -> Result<Self> {
3026        // https://github.com/pytorch/pytorch/blob/75b0720a97ac5d82e8a7a1a6ae7c5f7a87d7183d/aten/src/ATen/native/TensorShape.cpp#L3785-L3804
3027        let mut sizes = self.dims().to_vec();
3028        let mut strides = self.stride().to_vec();
3029
3030        let dim = dim.to_index(self.shape(), "unfold")?;
3031
3032        let max_len = if self.dims().is_empty() {
3033            1
3034        } else {
3035            sizes[dim]
3036        };
3037        if size > max_len {
3038            bail!(
3039                "unsqueeze: maximum size for tensor at dimension {dim} is {max_len} but size is {size}"
3040            )
3041        }
3042        sizes.push(size);
3043        strides.push(if self.dims().is_empty() {
3044            1
3045        } else {
3046            strides[dim]
3047        });
3048
3049        if !self.dims().is_empty() {
3050            sizes[dim] = ((sizes[dim] as f32 - size as f32) / step as f32 + 1.) as usize;
3051            strides[dim] *= step;
3052        }
3053
3054        let tensor_ = Tensor_ {
3055            id: TensorId::new(),
3056            storage: self.storage.clone(),
3057            layout: Layout::new(sizes.into(), strides, self.layout.start_offset()),
3058            op: BackpropOp::new1(self, Op::Reshape),
3059            is_variable: false,
3060            dtype: self.dtype,
3061            device: self.device.clone(),
3062        };
3063        Ok(Tensor(Arc::new(tensor_)))
3064    }
3065}
3066
3067macro_rules! bin_trait {
3068    ($trait:ident, $fn1:ident, $mul:expr, $add:expr) => {
3069        impl<B: std::borrow::Borrow<Tensor>> std::ops::$trait<B> for Tensor {
3070            type Output = Result<Tensor>;
3071
3072            fn $fn1(self, rhs: B) -> Self::Output {
3073                Tensor::$fn1(&self, rhs.borrow())
3074            }
3075        }
3076
3077        impl<B: std::borrow::Borrow<Tensor>> std::ops::$trait<B> for &Tensor {
3078            type Output = Result<Tensor>;
3079
3080            fn $fn1(self, rhs: B) -> Self::Output {
3081                Tensor::$fn1(&self, rhs.borrow())
3082            }
3083        }
3084
3085        impl<B: std::borrow::Borrow<Tensor>> std::ops::$trait<Tensor> for Result<B> {
3086            type Output = Result<Tensor>;
3087
3088            fn $fn1(self, rhs: Tensor) -> Self::Output {
3089                Tensor::$fn1(self?.borrow(), &rhs)
3090            }
3091        }
3092
3093        impl<B: std::borrow::Borrow<Tensor>> std::ops::$trait<&Tensor> for Result<B> {
3094            type Output = Result<Tensor>;
3095
3096            fn $fn1(self, rhs: &Tensor) -> Self::Output {
3097                Tensor::$fn1(self?.borrow(), rhs)
3098            }
3099        }
3100
3101        impl<B: std::borrow::Borrow<Tensor>> std::ops::$trait<Result<B>> for Tensor {
3102            type Output = Result<Tensor>;
3103
3104            fn $fn1(self, rhs: Result<B>) -> Self::Output {
3105                Tensor::$fn1(&self, rhs?.borrow())
3106            }
3107        }
3108
3109        impl<B: std::borrow::Borrow<Tensor>> std::ops::$trait<Result<B>> for &Tensor {
3110            type Output = Result<Tensor>;
3111
3112            fn $fn1(self, rhs: Result<B>) -> Self::Output {
3113                Tensor::$fn1(&self, rhs?.borrow())
3114            }
3115        }
3116
3117        impl std::ops::$trait<f64> for Tensor {
3118            type Output = Result<Tensor>;
3119
3120            fn $fn1(self, rhs: f64) -> Self::Output {
3121                self.affine($mul(rhs), $add(rhs))
3122            }
3123        }
3124
3125        impl std::ops::$trait<f64> for &Tensor {
3126            type Output = Result<Tensor>;
3127
3128            fn $fn1(self, rhs: f64) -> Self::Output {
3129                self.affine($mul(rhs), $add(rhs))
3130            }
3131        }
3132    };
3133}
3134
3135bin_trait!(Add, add, |_| 1., |v| v);
3136bin_trait!(Sub, sub, |_| 1., |v: f64| -v);
3137bin_trait!(Mul, mul, |v| v, |_| 0.);
3138bin_trait!(Div, div, |v| 1. / v, |_| 0.);
3139
3140impl std::ops::Add<Tensor> for f64 {
3141    type Output = Result<Tensor>;
3142
3143    fn add(self, rhs: Tensor) -> Self::Output {
3144        rhs + self
3145    }
3146}
3147
3148impl std::ops::Add<&Tensor> for f64 {
3149    type Output = Result<Tensor>;
3150
3151    fn add(self, rhs: &Tensor) -> Self::Output {
3152        rhs + self
3153    }
3154}
3155
3156impl std::ops::Mul<Tensor> for f64 {
3157    type Output = Result<Tensor>;
3158
3159    fn mul(self, rhs: Tensor) -> Self::Output {
3160        rhs * self
3161    }
3162}
3163
3164impl std::ops::Mul<&Tensor> for f64 {
3165    type Output = Result<Tensor>;
3166
3167    fn mul(self, rhs: &Tensor) -> Self::Output {
3168        rhs * self
3169    }
3170}
3171
3172impl std::ops::Sub<Tensor> for f64 {
3173    type Output = Result<Tensor>;
3174
3175    fn sub(self, rhs: Tensor) -> Self::Output {
3176        rhs.affine(-1., self)
3177    }
3178}
3179
3180impl std::ops::Sub<&Tensor> for f64 {
3181    type Output = Result<Tensor>;
3182
3183    fn sub(self, rhs: &Tensor) -> Self::Output {
3184        rhs.affine(-1., self)
3185    }
3186}
3187
3188impl std::ops::Div<Tensor> for f64 {
3189    type Output = Result<Tensor>;
3190
3191    #[allow(clippy::suspicious_arithmetic_impl)]
3192    fn div(self, rhs: Tensor) -> Self::Output {
3193        rhs.recip()? * self
3194    }
3195}
3196
3197impl std::ops::Div<&Tensor> for f64 {
3198    type Output = Result<Tensor>;
3199
3200    #[allow(clippy::suspicious_arithmetic_impl)]
3201    fn div(self, rhs: &Tensor) -> Self::Output {
3202        rhs.recip()? * self
3203    }
3204}
3205
3206impl<S: Into<Shape>> From<(Storage, S)> for Tensor {
3207    fn from((storage, shape): (Storage, S)) -> Self {
3208        from_storage(storage, shape, BackpropOp::none(), false)
3209    }
3210}