Skip to main content

candle_core/
storage.rs

1use crate::backend::BackendStorage;
2use crate::custom_op::{all_distinct, InplaceOpN, Src};
3use crate::op::{self, CmpOp, ReduceOp};
4use crate::scalar::Scalar;
5use crate::{CpuStorage, CudaStorage, DType, Device, Error, Layout, MetalStorage, Result, Shape};
6use crate::{CustomOp1, CustomOp2, CustomOp3};
7use parking_lot::{RwLockReadGuard, RwLockWriteGuard};
8
9// We do not want to implement Clone on Storage as cloning may fail because of
10// out of memory. Instead try_clone should be used.
11#[derive(Debug)]
12pub enum Storage {
13    Cpu(CpuStorage),
14    Cuda(CudaStorage),
15    Metal(MetalStorage),
16}
17
18pub type StorageRef<'a> = RwLockReadGuard<'a, Storage>;
19pub type StorageMutRef<'a> = RwLockWriteGuard<'a, Storage>;
20
21impl Storage {
22    pub fn try_clone(&self, layout: &Layout) -> Result<Self> {
23        match self {
24            Self::Cpu(storage) => Ok(Self::Cpu(storage.clone())),
25            Self::Cuda(storage) => {
26                let storage = storage.try_clone(layout)?;
27                Ok(Self::Cuda(storage))
28            }
29            Self::Metal(storage) => {
30                let storage = storage.try_clone(layout)?;
31                Ok(Self::Metal(storage))
32            }
33        }
34    }
35
36    pub fn device(&self) -> Device {
37        match self {
38            Self::Cpu(_) => Device::Cpu,
39            Self::Cuda(storage) => Device::Cuda(storage.device().clone()),
40            Self::Metal(storage) => Device::Metal(storage.device().clone()),
41        }
42    }
43
44    pub fn dtype(&self) -> DType {
45        match self {
46            Self::Cpu(storage) => storage.dtype(),
47            Self::Cuda(storage) => storage.dtype(),
48            Self::Metal(storage) => storage.dtype(),
49        }
50    }
51
52    pub(crate) fn same_device(&self, rhs: &Self, op: &'static str) -> Result<()> {
53        let lhs_device = self.device();
54        let rhs_device = rhs.device();
55        let lhs = lhs_device.location();
56        let rhs = rhs_device.location();
57        let same_device = if self.device().is_metal() {
58            // On metal, we require the device to be exactly the same rather than
59            // having the same location. In cuda this is not necessary as all CudaDevice on the
60            // same GPU will use the same cuda stream.
61            lhs_device.same_device(&rhs_device)
62        } else {
63            lhs == rhs
64        };
65        if !same_device {
66            Err(Error::DeviceMismatchBinaryOp { lhs, rhs, op }.bt())
67        } else {
68            Ok(())
69        }
70    }
71
72    pub(crate) fn same_dtype(&self, rhs: &Self, op: &'static str) -> Result<()> {
73        let lhs = self.dtype();
74        let rhs = rhs.dtype();
75        if lhs != rhs {
76            Err(Error::DTypeMismatchBinaryOp { lhs, rhs, op }.bt())
77        } else {
78            Ok(())
79        }
80    }
81
82    pub(crate) fn const_set(&mut self, v: Scalar, l: &Layout) -> Result<()> {
83        match self {
84            Storage::Cpu(storage) => storage.const_set(v, l),
85            Storage::Cuda(storage) => storage.const_set(v, l),
86            Storage::Metal(storage) => storage.const_set(v, l),
87        }
88    }
89
90    pub(crate) fn affine(&self, layout: &Layout, mul: f64, add: f64) -> Result<Self> {
91        match self {
92            Storage::Cpu(storage) => {
93                let storage = storage.affine(layout, mul, add)?;
94                Ok(Self::Cpu(storage))
95            }
96            Self::Cuda(storage) => {
97                let storage = storage.affine(layout, mul, add)?;
98                Ok(Self::Cuda(storage))
99            }
100            Self::Metal(storage) => {
101                let storage = storage.affine(layout, mul, add)?;
102                Ok(Self::Metal(storage))
103            }
104        }
105    }
106
107    pub(crate) fn powf(&self, layout: &Layout, alpha: f64) -> Result<Self> {
108        match self {
109            Storage::Cpu(storage) => {
110                let storage = storage.powf(layout, alpha)?;
111                Ok(Self::Cpu(storage))
112            }
113            Self::Cuda(storage) => {
114                let storage = storage.powf(layout, alpha)?;
115                Ok(Self::Cuda(storage))
116            }
117            Self::Metal(storage) => {
118                let storage = storage.powf(layout, alpha)?;
119                Ok(Self::Metal(storage))
120            }
121        }
122    }
123
124    pub(crate) fn elu(&self, layout: &Layout, alpha: f64) -> Result<Self> {
125        match self {
126            Storage::Cpu(storage) => {
127                let storage = storage.elu(layout, alpha)?;
128                Ok(Self::Cpu(storage))
129            }
130            Self::Cuda(storage) => {
131                let storage = storage.elu(layout, alpha)?;
132                Ok(Self::Cuda(storage))
133            }
134            Self::Metal(storage) => {
135                let storage = storage.elu(layout, alpha)?;
136                Ok(Self::Metal(storage))
137            }
138        }
139    }
140
141    pub(crate) fn cmp(
142        &self,
143        op: CmpOp,
144        rhs: &Self,
145        lhs_layout: &Layout,
146        rhs_layout: &Layout,
147    ) -> Result<Self> {
148        self.same_device(rhs, "cmp")?;
149        self.same_dtype(rhs, "cmp")?;
150        match (self, rhs) {
151            (Storage::Cpu(lhs), Storage::Cpu(rhs)) => {
152                let storage = lhs.cmp(op, rhs, lhs_layout, rhs_layout)?;
153                Ok(Self::Cpu(storage))
154            }
155            (Self::Cuda(lhs), Self::Cuda(rhs)) => {
156                let storage = lhs.cmp(op, rhs, lhs_layout, rhs_layout)?;
157                Ok(Self::Cuda(storage))
158            }
159            (Self::Metal(lhs), Self::Metal(rhs)) => {
160                let storage = lhs.cmp(op, rhs, lhs_layout, rhs_layout)?;
161                Ok(Self::Metal(storage))
162            }
163            (lhs, rhs) => {
164                // Should not happen because of the same device check above but we're defensive
165                // anyway.
166                Err(Error::DeviceMismatchBinaryOp {
167                    lhs: lhs.device().location(),
168                    rhs: rhs.device().location(),
169                    op: "cmp",
170                }
171                .bt())
172            }
173        }
174    }
175
176    pub(crate) fn reduce_op(&self, op: ReduceOp, layout: &Layout, s: &[usize]) -> Result<Self> {
177        match self {
178            Storage::Cpu(storage) => {
179                let storage = storage.reduce_op(op, layout, s)?;
180                Ok(Self::Cpu(storage))
181            }
182            Self::Cuda(storage) => {
183                let storage = storage.reduce_op(op, layout, s)?;
184                Ok(Self::Cuda(storage))
185            }
186            Self::Metal(storage) => {
187                let storage = storage.reduce_op(op, layout, s)?;
188                Ok(Self::Metal(storage))
189            }
190        }
191    }
192
193    pub(crate) fn to_dtype(&self, layout: &Layout, dtype: DType) -> Result<Self> {
194        match self {
195            Storage::Cpu(storage) => {
196                let storage = storage.to_dtype(layout, dtype)?;
197                Ok(Self::Cpu(storage))
198            }
199            Self::Cuda(storage) => {
200                let storage = storage.to_dtype(layout, dtype)?;
201                Ok(Self::Cuda(storage))
202            }
203            Self::Metal(storage) => {
204                let storage = storage.to_dtype(layout, dtype)?;
205                Ok(Self::Metal(storage))
206            }
207        }
208    }
209
210    pub(crate) fn apply_op1(&self, l: &Layout, c: &dyn CustomOp1) -> Result<(Self, Shape)> {
211        match self {
212            Self::Cpu(storage) => {
213                let (storage, shape) = c.cpu_fwd(storage, l)?;
214                Ok((Self::Cpu(storage), shape))
215            }
216            Self::Cuda(storage) => {
217                let (storage, shape) = c.cuda_fwd(storage, l)?;
218                Ok((Self::Cuda(storage), shape))
219            }
220            Self::Metal(storage) => {
221                let (storage, shape) = c.metal_fwd(storage, l)?;
222                Ok((Self::Metal(storage), shape))
223            }
224        }
225    }
226
227    pub(crate) fn apply_op2(
228        &self,
229        l1: &Layout,
230        t2: &Self,
231        l2: &Layout,
232        c: &dyn CustomOp2,
233    ) -> Result<(Self, Shape)> {
234        self.same_device(t2, c.name())?;
235        match (self, t2) {
236            (Self::Cpu(s1), Self::Cpu(s2)) => {
237                let (s, shape) = c.cpu_fwd(s1, l1, s2, l2)?;
238                Ok((Self::Cpu(s), shape))
239            }
240            (Self::Cuda(s1), Self::Cuda(s2)) => {
241                let (s, shape) = c.cuda_fwd(s1, l1, s2, l2)?;
242                Ok((Self::Cuda(s), shape))
243            }
244            (Self::Metal(s1), Self::Metal(s2)) => {
245                let (s, shape) = c.metal_fwd(s1, l1, s2, l2)?;
246                Ok((Self::Metal(s), shape))
247            }
248            _ => unreachable!(),
249        }
250    }
251
252    pub(crate) fn apply_op3(
253        &self,
254        l1: &Layout,
255        t2: &Self,
256        l2: &Layout,
257        t3: &Self,
258        l3: &Layout,
259        c: &dyn CustomOp3,
260    ) -> Result<(Self, Shape)> {
261        self.same_device(t2, c.name())?;
262        self.same_device(t3, c.name())?;
263        match (self, t2, t3) {
264            (Self::Cpu(s1), Self::Cpu(s2), Self::Cpu(s3)) => {
265                let (s, shape) = c.cpu_fwd(s1, l1, s2, l2, s3, l3)?;
266                Ok((Self::Cpu(s), shape))
267            }
268            (Self::Cuda(s1), Self::Cuda(s2), Self::Cuda(s3)) => {
269                let (s, shape) = c.cuda_fwd(s1, l1, s2, l2, s3, l3)?;
270                Ok((Self::Cuda(s), shape))
271            }
272            (Self::Metal(s1), Self::Metal(s2), Self::Metal(s3)) => {
273                let (s, shape) = c.metal_fwd(s1, l1, s2, l2, s3, l3)?;
274                Ok((Self::Metal(s), shape))
275            }
276            _ => unreachable!(),
277        }
278    }
279
280    /// Applies an custom in-place op for the `self` tensor.
281    ///
282    /// [`Src::Aliased`] use the same underlying storage as `self`, while [`Src::Distinct`] does not.
283    /// If there are aliases present the aliased forward function on `InplaceOpN` is called. Otherwise
284    /// the normal forward function can be used.
285    pub(crate) fn inplace_op<const N: usize, C: InplaceOpN<N>>(
286        &mut self,
287        dst_l: &Layout,
288        srcs: [(Src<'_, Storage>, &Layout); N],
289        c: &C,
290    ) -> Result<()> {
291        // Aliased sources are `self`. Only distinct needs checking.
292        for (s, _) in srcs.iter() {
293            if let Src::Distinct(s) = s {
294                self.same_device(s, c.name())?;
295            }
296        }
297        macro_rules! inplace_dispatch {
298            ($dst:expr, $variant:ident, $fwd:ident, $fwd_aliased:ident) => {{
299                // Extract underlying storage variant on same device
300                let operands: [(Src<'_, _>, &Layout); N] = std::array::from_fn(|i| {
301                    let (s, l) = srcs[i];
302                    let s = match s {
303                        Src::Distinct(Storage::$variant(s)) => Src::Distinct(s),
304                        Src::Aliased(rel) => Src::Aliased(rel),
305                        Src::Distinct(_) => {
306                            unreachable!("same_device above rejects mismatched backends")
307                        }
308                    };
309                    (s, l)
310                });
311
312                match all_distinct(&operands) {
313                    Some(distinct) => c.$fwd($dst, dst_l, distinct),
314                    None => c.$fwd_aliased($dst, dst_l, operands),
315                }
316            }};
317        }
318
319        match self {
320            Storage::Cpu(dst) => inplace_dispatch!(dst, Cpu, cpu_fwd, cpu_fwd_aliased),
321            Storage::Cuda(dst) => inplace_dispatch!(dst, Cuda, cuda_fwd, cuda_fwd_aliased),
322            Storage::Metal(dst) => inplace_dispatch!(dst, Metal, metal_fwd, metal_fwd_aliased),
323        }
324    }
325
326    pub(crate) fn unary_impl<B: op::UnaryOpT>(&self, layout: &Layout) -> Result<Self> {
327        match self {
328            Storage::Cpu(storage) => {
329                let storage = storage.unary_impl::<B>(layout)?;
330                Ok(Self::Cpu(storage))
331            }
332            Self::Cuda(storage) => {
333                let storage = storage.unary_impl::<B>(layout)?;
334                Ok(Self::Cuda(storage))
335            }
336            Self::Metal(storage) => {
337                let storage = storage.unary_impl::<B>(layout)?;
338                Ok(Self::Metal(storage))
339            }
340        }
341    }
342
343    pub(crate) fn binary_impl<B: op::BinaryOpT>(
344        &self,
345        rhs: &Self,
346        lhs_layout: &Layout,
347        rhs_layout: &Layout,
348    ) -> Result<Self> {
349        self.same_device(rhs, B::NAME)?;
350        self.same_dtype(rhs, B::NAME)?;
351        match (self, rhs) {
352            (Storage::Cpu(lhs), Storage::Cpu(rhs)) => {
353                let storage = lhs.binary_impl::<B>(rhs, lhs_layout, rhs_layout)?;
354                Ok(Self::Cpu(storage))
355            }
356            (Self::Cuda(lhs), Self::Cuda(rhs)) => {
357                let storage = lhs.binary_impl::<B>(rhs, lhs_layout, rhs_layout)?;
358                Ok(Self::Cuda(storage))
359            }
360            (Self::Metal(lhs), Self::Metal(rhs)) => {
361                let storage = lhs.binary_impl::<B>(rhs, lhs_layout, rhs_layout)?;
362                Ok(Self::Metal(storage))
363            }
364            (lhs, rhs) => {
365                // Should not happen because of the same device check above but we're defensive
366                // anyway.
367                Err(Error::DeviceMismatchBinaryOp {
368                    lhs: lhs.device().location(),
369                    rhs: rhs.device().location(),
370                    op: B::NAME,
371                }
372                .bt())
373            }
374        }
375    }
376
377    pub(crate) fn conv1d(
378        &self,
379        l: &Layout,
380        kernel: &Self,
381        kernel_l: &Layout,
382        params: &crate::conv::ParamsConv1D,
383    ) -> Result<Self> {
384        self.same_device(kernel, "conv1d")?;
385        self.same_dtype(kernel, "conv1d")?;
386        match (self, &kernel) {
387            (Storage::Cpu(inp), Storage::Cpu(kernel)) => {
388                let s = inp.conv1d(l, kernel, kernel_l, params)?;
389                Ok(Self::Cpu(s))
390            }
391            (Storage::Cuda(inp), Storage::Cuda(kernel)) => {
392                let s = inp.conv1d(l, kernel, kernel_l, params)?;
393                Ok(Self::Cuda(s))
394            }
395            (Storage::Metal(inp), Storage::Metal(kernel)) => {
396                let s = inp.conv1d(l, kernel, kernel_l, params)?;
397                Ok(Self::Metal(s))
398            }
399            (lhs, rhs) => Err(Error::DeviceMismatchBinaryOp {
400                lhs: lhs.device().location(),
401                rhs: rhs.device().location(),
402                op: "conv1d",
403            }
404            .bt()),
405        }
406    }
407
408    pub(crate) fn conv_transpose1d(
409        &self,
410        l: &Layout,
411        kernel: &Self,
412        kernel_l: &Layout,
413        params: &crate::conv::ParamsConvTranspose1D,
414    ) -> Result<Self> {
415        self.same_device(kernel, "conv-transpose1d")?;
416        self.same_dtype(kernel, "conv-transpose1d")?;
417        match (self, &kernel) {
418            (Storage::Cpu(inp), Storage::Cpu(kernel)) => {
419                let s = inp.conv_transpose1d(l, kernel, kernel_l, params)?;
420                Ok(Self::Cpu(s))
421            }
422            (Storage::Cuda(inp), Storage::Cuda(kernel)) => {
423                let s = inp.conv_transpose1d(l, kernel, kernel_l, params)?;
424                Ok(Self::Cuda(s))
425            }
426            (Storage::Metal(inp), Storage::Metal(kernel)) => {
427                let s = inp.conv_transpose1d(l, kernel, kernel_l, params)?;
428                Ok(Self::Metal(s))
429            }
430            (lhs, rhs) => Err(Error::DeviceMismatchBinaryOp {
431                lhs: lhs.device().location(),
432                rhs: rhs.device().location(),
433                op: "conv-transpose1d",
434            }
435            .bt()),
436        }
437    }
438
439    pub(crate) fn conv2d(
440        &self,
441        l: &Layout,
442        kernel: &Self,
443        kernel_l: &Layout,
444        params: &crate::conv::ParamsConv2D,
445    ) -> Result<Self> {
446        self.same_device(kernel, "conv2d")?;
447        self.same_dtype(kernel, "conv2d")?;
448        match (self, &kernel) {
449            (Storage::Cpu(inp), Storage::Cpu(kernel)) => {
450                let s = inp.conv2d(l, kernel, kernel_l, params)?;
451                Ok(Self::Cpu(s))
452            }
453            (Storage::Cuda(inp), Storage::Cuda(kernel)) => {
454                let s = inp.conv2d(l, kernel, kernel_l, params)?;
455                Ok(Self::Cuda(s))
456            }
457            (Storage::Metal(inp), Storage::Metal(kernel)) => {
458                let s = inp.conv2d(l, kernel, kernel_l, params)?;
459                Ok(Self::Metal(s))
460            }
461            (lhs, rhs) => Err(Error::DeviceMismatchBinaryOp {
462                lhs: lhs.device().location(),
463                rhs: rhs.device().location(),
464                op: "conv2d",
465            }
466            .bt()),
467        }
468    }
469
470    pub(crate) fn conv_transpose2d(
471        &self,
472        l: &Layout,
473        kernel: &Self,
474        kernel_l: &Layout,
475        params: &crate::conv::ParamsConvTranspose2D,
476    ) -> Result<Self> {
477        self.same_device(kernel, "conv_transpose2d")?;
478        self.same_dtype(kernel, "conv_transpose2d")?;
479        match (self, &kernel) {
480            (Storage::Cpu(inp), Storage::Cpu(kernel)) => {
481                let s = inp.conv_transpose2d(l, kernel, kernel_l, params)?;
482                Ok(Self::Cpu(s))
483            }
484            (Storage::Cuda(inp), Storage::Cuda(kernel)) => {
485                let s = inp.conv_transpose2d(l, kernel, kernel_l, params)?;
486                Ok(Self::Cuda(s))
487            }
488            (Storage::Metal(inp), Storage::Metal(kernel)) => {
489                let s = inp.conv_transpose2d(l, kernel, kernel_l, params)?;
490                Ok(Self::Metal(s))
491            }
492            (lhs, rhs) => Err(Error::DeviceMismatchBinaryOp {
493                lhs: lhs.device().location(),
494                rhs: rhs.device().location(),
495                op: "conv_transpose2d",
496            }
497            .bt()),
498        }
499    }
500
501    pub(crate) fn avg_pool2d(
502        &self,
503        layout: &Layout,
504        kernel_size: (usize, usize),
505        stride: (usize, usize),
506    ) -> Result<Self> {
507        match self {
508            Storage::Cpu(storage) => {
509                let storage = storage.avg_pool2d(layout, kernel_size, stride)?;
510                Ok(Self::Cpu(storage))
511            }
512            Self::Cuda(storage) => {
513                let storage = storage.avg_pool2d(layout, kernel_size, stride)?;
514                Ok(Self::Cuda(storage))
515            }
516            Self::Metal(storage) => {
517                let storage = storage.avg_pool2d(layout, kernel_size, stride)?;
518                Ok(Self::Metal(storage))
519            }
520        }
521    }
522
523    pub(crate) fn max_pool2d(
524        &self,
525        layout: &Layout,
526        kernel_size: (usize, usize),
527        stride: (usize, usize),
528    ) -> Result<Self> {
529        match self {
530            Storage::Cpu(storage) => {
531                let storage = storage.max_pool2d(layout, kernel_size, stride)?;
532                Ok(Self::Cpu(storage))
533            }
534            Self::Cuda(storage) => {
535                let storage = storage.max_pool2d(layout, kernel_size, stride)?;
536                Ok(Self::Cuda(storage))
537            }
538            Self::Metal(storage) => {
539                let storage = storage.max_pool2d(layout, kernel_size, stride)?;
540                Ok(Self::Metal(storage))
541            }
542        }
543    }
544
545    pub(crate) fn upsample_nearest1d(&self, layout: &Layout, sz: usize) -> Result<Self> {
546        match self {
547            Storage::Cpu(storage) => {
548                let storage = storage.upsample_nearest1d(layout, sz)?;
549                Ok(Self::Cpu(storage))
550            }
551            Self::Cuda(storage) => {
552                let storage = storage.upsample_nearest1d(layout, sz)?;
553                Ok(Self::Cuda(storage))
554            }
555            Self::Metal(storage) => {
556                let storage = storage.upsample_nearest1d(layout, sz)?;
557                Ok(Self::Metal(storage))
558            }
559        }
560    }
561
562    pub(crate) fn upsample_nearest2d(&self, layout: &Layout, h: usize, w: usize) -> Result<Self> {
563        match self {
564            Storage::Cpu(storage) => {
565                let storage = storage.upsample_nearest2d(layout, h, w)?;
566                Ok(Self::Cpu(storage))
567            }
568            Self::Cuda(storage) => {
569                let storage = storage.upsample_nearest2d(layout, h, w)?;
570                Ok(Self::Cuda(storage))
571            }
572            Self::Metal(storage) => {
573                let storage = storage.upsample_nearest2d(layout, h, w)?;
574                Ok(Self::Metal(storage))
575            }
576        }
577    }
578
579    pub(crate) fn upsample_bilinear2d(
580        &self,
581        layout: &Layout,
582        h: usize,
583        w: usize,
584        align_corners: bool,
585        scale_h: Option<f64>,
586        scale_w: Option<f64>,
587    ) -> Result<Self> {
588        match self {
589            Storage::Cpu(storage) => {
590                let storage =
591                    storage.upsample_bilinear2d(layout, h, w, align_corners, scale_h, scale_w)?;
592                Ok(Self::Cpu(storage))
593            }
594            Self::Cuda(storage) => {
595                let storage =
596                    storage.upsample_bilinear2d(layout, h, w, align_corners, scale_h, scale_w)?;
597                Ok(Self::Cuda(storage))
598            }
599            Self::Metal(storage) => {
600                let storage =
601                    storage.upsample_bilinear2d(layout, h, w, align_corners, scale_h, scale_w)?;
602                Ok(Self::Metal(storage))
603            }
604        }
605    }
606
607    pub(crate) fn where_cond(
608        &self,
609        layout: &Layout,
610        t: &Self,
611        layout_t: &Layout,
612        f: &Self,
613        layout_f: &Layout,
614    ) -> Result<Self> {
615        self.same_device(t, "where")?;
616        self.same_device(f, "where")?;
617        t.same_dtype(f, "where")?;
618        match (self, t, f) {
619            (Storage::Cpu(cond), Storage::Cpu(t), Storage::Cpu(f)) => {
620                let storage = cond.where_cond(layout, t, layout_t, f, layout_f)?;
621                Ok(Self::Cpu(storage))
622            }
623            (Self::Cuda(cond), Self::Cuda(t), Self::Cuda(f)) => {
624                let storage = cond.where_cond(layout, t, layout_t, f, layout_f)?;
625                Ok(Self::Cuda(storage))
626            }
627            (Self::Metal(cond), Self::Metal(t), Self::Metal(f)) => {
628                let storage = cond.where_cond(layout, t, layout_t, f, layout_f)?;
629                Ok(Self::Metal(storage))
630            }
631            (_, lhs, rhs) => Err(Error::DeviceMismatchBinaryOp {
632                lhs: lhs.device().location(),
633                rhs: rhs.device().location(),
634                op: "where",
635            }
636            .bt()),
637        }
638    }
639
640    pub(crate) fn gather(
641        &self,
642        l: &Layout,
643        indexes: &Self,
644        indexes_l: &Layout,
645        d: usize,
646    ) -> Result<Self> {
647        self.same_device(indexes, "index-add")?;
648        match (self, indexes) {
649            (Self::Cpu(s), Self::Cpu(indexes)) => {
650                let storage = s.gather(l, indexes, indexes_l, d)?;
651                Ok(Self::Cpu(storage))
652            }
653            (Self::Cuda(s), Self::Cuda(indexes)) => {
654                let storage = s.gather(l, indexes, indexes_l, d)?;
655                Ok(Self::Cuda(storage))
656            }
657            (Self::Metal(s), Self::Metal(indexes)) => {
658                let storage = s.gather(l, indexes, indexes_l, d)?;
659                Ok(Self::Metal(storage))
660            }
661            _ => unreachable!(),
662        }
663    }
664
665    pub(crate) fn scatter_set(
666        &mut self,
667        l: &Layout,
668        indexes: &Self,
669        indexes_l: &Layout,
670        source: &Self,
671        source_l: &Layout,
672        d: usize,
673    ) -> Result<()> {
674        self.same_device(indexes, "scatter-set")?;
675        self.same_device(source, "scatter-set")?;
676        match (self, indexes, source) {
677            (Self::Cpu(s), Self::Cpu(indexes), Self::Cpu(source)) => {
678                s.scatter_set(l, indexes, indexes_l, source, source_l, d)?;
679            }
680            (Self::Cuda(s), Self::Cuda(indexes), Self::Cuda(source)) => {
681                s.scatter_set(l, indexes, indexes_l, source, source_l, d)?;
682            }
683            (Self::Metal(s), Self::Metal(indexes), Self::Metal(source)) => {
684                s.scatter_set(l, indexes, indexes_l, source, source_l, d)?;
685            }
686            _ => unreachable!(),
687        }
688        Ok(())
689    }
690
691    pub(crate) fn scatter_add(
692        &mut self,
693        l: &Layout,
694        indexes: &Self,
695        indexes_l: &Layout,
696        source: &Self,
697        source_l: &Layout,
698        d: usize,
699    ) -> Result<()> {
700        self.same_device(indexes, "scatter-add")?;
701        self.same_device(source, "scatter-add")?;
702        match (self, indexes, source) {
703            (Self::Cpu(s), Self::Cpu(indexes), Self::Cpu(source)) => {
704                s.scatter_add_set(l, indexes, indexes_l, source, source_l, d)?;
705            }
706            (Self::Cuda(s), Self::Cuda(indexes), Self::Cuda(source)) => {
707                s.scatter_add_set(l, indexes, indexes_l, source, source_l, d)?;
708            }
709            (Self::Metal(s), Self::Metal(indexes), Self::Metal(source)) => {
710                s.scatter_add_set(l, indexes, indexes_l, source, source_l, d)?;
711            }
712            _ => unreachable!(),
713        }
714        Ok(())
715    }
716
717    pub(crate) fn index_add(
718        &self,
719        l: &Layout,
720        indexes: &Self,
721        indexes_l: &Layout,
722        source: &Self,
723        source_l: &Layout,
724        d: usize,
725    ) -> Result<Self> {
726        self.same_device(indexes, "index-add")?;
727        self.same_device(source, "index-add")?;
728        match (self, indexes, source) {
729            (Self::Cpu(s), Self::Cpu(indexes), Self::Cpu(source)) => {
730                let storage = s.index_add(l, indexes, indexes_l, source, source_l, d)?;
731                Ok(Self::Cpu(storage))
732            }
733            (Self::Cuda(s), Self::Cuda(indexes), Self::Cuda(source)) => {
734                let storage = s.index_add(l, indexes, indexes_l, source, source_l, d)?;
735                Ok(Self::Cuda(storage))
736            }
737            (Self::Metal(s), Self::Metal(indexes), Self::Metal(source)) => {
738                let storage = s.index_add(l, indexes, indexes_l, source, source_l, d)?;
739                Ok(Self::Metal(storage))
740            }
741            _ => unreachable!(),
742        }
743    }
744
745    pub(crate) fn index_select(
746        &self,
747        rhs: &Self,
748        lhs_l: &Layout,
749        rhs_l: &Layout,
750        d: usize,
751    ) -> Result<Self> {
752        self.same_device(rhs, "index-select")?;
753        match (self, rhs) {
754            (Self::Cpu(lhs), Self::Cpu(rhs)) => {
755                let storage = lhs.index_select(rhs, lhs_l, rhs_l, d)?;
756                Ok(Self::Cpu(storage))
757            }
758            (Self::Cuda(lhs), Self::Cuda(rhs)) => {
759                let storage = lhs.index_select(rhs, lhs_l, rhs_l, d)?;
760                Ok(Self::Cuda(storage))
761            }
762            (Self::Metal(lhs), Self::Metal(rhs)) => {
763                let storage = lhs.index_select(rhs, lhs_l, rhs_l, d)?;
764                Ok(Self::Metal(storage))
765            }
766            (lhs, rhs) => Err(Error::DeviceMismatchBinaryOp {
767                lhs: lhs.device().location(),
768                rhs: rhs.device().location(),
769                op: "index-select",
770            }
771            .bt()),
772        }
773    }
774
775    pub(crate) fn matmul(
776        &self,
777        rhs: &Self,
778        bmnk: (usize, usize, usize, usize),
779        lhs_layout: &Layout,
780        rhs_layout: &Layout,
781    ) -> Result<Self> {
782        self.same_device(rhs, "matmul")?;
783        self.same_dtype(rhs, "matmul")?;
784        match (self, rhs) {
785            (Self::Cpu(lhs), Self::Cpu(rhs)) => {
786                let storage = lhs.matmul(rhs, bmnk, lhs_layout, rhs_layout)?;
787                Ok(Self::Cpu(storage))
788            }
789            (Self::Cuda(lhs), Self::Cuda(rhs)) => {
790                let storage = lhs.matmul(rhs, bmnk, lhs_layout, rhs_layout)?;
791                Ok(Self::Cuda(storage))
792            }
793            (Self::Metal(lhs), Self::Metal(rhs)) => {
794                let storage = lhs.matmul(rhs, bmnk, lhs_layout, rhs_layout)?;
795                Ok(Self::Metal(storage))
796            }
797            (lhs, rhs) => Err(Error::DeviceMismatchBinaryOp {
798                lhs: lhs.device().location(),
799                rhs: rhs.device().location(),
800                op: "matmul",
801            }
802            .bt()),
803        }
804    }
805
806    // self, the source can be strided whereas dst is contiguous.
807    pub(crate) fn copy_strided_src(
808        &self,
809        dst: &mut Self,
810        dst_offset: usize,
811        src_l: &Layout,
812    ) -> Result<()> {
813        match (self, dst) {
814            (Self::Cpu(src), Self::Cpu(dst)) => src.copy_strided_src(dst, dst_offset, src_l),
815            (Self::Cuda(src), Self::Cuda(dst)) => Ok(src.copy_strided_src(dst, dst_offset, src_l)?),
816            (Self::Metal(src), Self::Metal(dst)) => {
817                Ok(src.copy_strided_src(dst, dst_offset, src_l)?)
818            }
819            (lhs, rhs) => Err(Error::DeviceMismatchBinaryOp {
820                lhs: lhs.device().location(),
821                rhs: rhs.device().location(),
822                op: "copy",
823            }
824            .bt()),
825        }
826    }
827
828    #[allow(clippy::too_many_arguments)]
829    pub(crate) fn copy2d(
830        &self,
831        dst: &mut Self,
832        d1: usize,
833        d2: usize,
834        src_s: usize,
835        dst_s: usize,
836        src_o: usize,
837        dst_o: usize,
838    ) -> Result<()> {
839        match (self, dst) {
840            (Self::Cpu(src), Self::Cpu(dst)) => src.copy2d(dst, d1, d2, src_s, dst_s, src_o, dst_o),
841            (Self::Cuda(src), Self::Cuda(dst)) => {
842                Ok(src.copy2d(dst, d1, d2, src_s, dst_s, src_o, dst_o)?)
843            }
844            (Self::Metal(src), Self::Metal(dst)) => {
845                Ok(src.copy2d(dst, d1, d2, src_s, dst_s, src_o, dst_o)?)
846            }
847            (lhs, rhs) => Err(Error::DeviceMismatchBinaryOp {
848                lhs: lhs.device().location(),
849                rhs: rhs.device().location(),
850                op: "copy2d",
851            }
852            .bt()),
853        }
854    }
855}