1use crate::layout::LayoutRelation;
2use crate::op::{BackpropOp, Op};
3use crate::tensor::from_storage;
4#[cfg(feature = "rocm")]
5use crate::RocmStorage;
6#[cfg(feature = "vulkan")]
7use crate::VulkanStorage;
8#[cfg(feature = "wgpu")]
9use crate::WgpuStorage;
10use crate::{bail, CpuStorage, CudaStorage, Layout, MetalStorage, Result, Shape, Storage, Tensor};
11use std::sync::Arc;
12
13#[cfg(feature = "vulkan")]
20fn log_vulkan_custom_op_bail(name: &str, l: &Layout) {
21 if std::env::var("VK_PROFILE")
22 .map(|v| v != "0")
23 .unwrap_or(false)
24 {
25 eprintln!(
26 "[VK_PROFILE] custom-op bail op={name} shape={:?} (no vulkan_fwd; would round-trip/err)",
27 l.shape().dims()
28 );
29 }
30}
31
32pub trait CustomOp1 {
34 fn name(&self) -> &'static str;
36
37 fn cpu_fwd(&self, storage: &CpuStorage, layout: &Layout) -> Result<(CpuStorage, Shape)>;
40
41 fn cuda_fwd(&self, _storage: &CudaStorage, _layout: &Layout) -> Result<(CudaStorage, Shape)> {
44 Err(crate::Error::Cuda(
45 format!("no cuda implementation for {}", self.name()).into(),
46 ))
47 }
48
49 #[cfg(feature = "rocm")]
50 fn rocm_fwd(&self, _storage: &RocmStorage, _layout: &Layout) -> Result<(RocmStorage, Shape)> {
51 Err(crate::Error::Msg(format!(
52 "no rocm implementation for {}",
53 self.name()
54 )))
55 }
56 #[cfg(feature = "vulkan")]
57 fn vulkan_fwd(
58 &self,
59 _storage: &VulkanStorage,
60 _layout: &Layout,
61 ) -> Result<(VulkanStorage, Shape)> {
62 log_vulkan_custom_op_bail(self.name(), _layout);
63 Err(crate::Error::Msg(format!(
64 "no vulkan implementation for {}",
65 self.name()
66 )))
67 }
68 #[cfg(feature = "wgpu")]
69 fn wgpu_fwd(&self, _storage: &WgpuStorage, _layout: &Layout) -> Result<(WgpuStorage, Shape)> {
70 Err(crate::Error::Msg(format!(
71 "no wgpu implementation for {}",
72 self.name()
73 )))
74 }
75
76 fn metal_fwd(
79 &self,
80 _storage: &MetalStorage,
81 _layout: &Layout,
82 ) -> Result<(MetalStorage, Shape)> {
83 Err(crate::Error::Metal(
84 format!("no metal implementation for {}", self.name()).into(),
85 ))
86 }
87
88 fn bwd(&self, _arg: &Tensor, _res: &Tensor, _grad_res: &Tensor) -> Result<Option<Tensor>> {
92 Err(crate::Error::BackwardNotSupported { op: self.name() })
93 }
94}
95
96pub trait CustomOp2 {
97 fn name(&self) -> &'static str;
98
99 fn cpu_fwd(
102 &self,
103 s1: &CpuStorage,
104 l1: &Layout,
105 s2: &CpuStorage,
106 l2: &Layout,
107 ) -> Result<(CpuStorage, Shape)>;
108
109 fn cuda_fwd(
112 &self,
113 _: &CudaStorage,
114 _: &Layout,
115 _: &CudaStorage,
116 _: &Layout,
117 ) -> Result<(CudaStorage, Shape)> {
118 Err(crate::Error::Cuda(
119 format!("no cuda implementation for {}", self.name()).into(),
120 ))
121 }
122
123 #[cfg(feature = "rocm")]
124 fn rocm_fwd(
125 &self,
126 _: &RocmStorage,
127 _: &Layout,
128 _: &RocmStorage,
129 _: &Layout,
130 ) -> Result<(RocmStorage, Shape)> {
131 Err(crate::Error::Msg(format!(
132 "no rocm implementation for {}",
133 self.name()
134 )))
135 }
136 #[cfg(feature = "vulkan")]
137 fn vulkan_fwd(
138 &self,
139 _: &VulkanStorage,
140 l1: &Layout,
141 _: &VulkanStorage,
142 _: &Layout,
143 ) -> Result<(VulkanStorage, Shape)> {
144 log_vulkan_custom_op_bail(self.name(), l1);
145 Err(crate::Error::Msg(format!(
146 "no vulkan implementation for {}",
147 self.name()
148 )))
149 }
150 #[cfg(feature = "wgpu")]
151 fn wgpu_fwd(
152 &self,
153 _: &WgpuStorage,
154 _l1: &Layout,
155 _: &WgpuStorage,
156 _: &Layout,
157 ) -> Result<(WgpuStorage, Shape)> {
158 Err(crate::Error::Msg(format!(
159 "no wgpu implementation for {}",
160 self.name()
161 )))
162 }
163
164 fn metal_fwd(
167 &self,
168 _: &MetalStorage,
169 _: &Layout,
170 _: &MetalStorage,
171 _: &Layout,
172 ) -> Result<(MetalStorage, Shape)> {
173 Err(crate::Error::Metal(
174 format!("no metal implementation for {}", self.name()).into(),
175 ))
176 }
177
178 fn bwd(
179 &self,
180 _arg1: &Tensor,
181 _arg2: &Tensor,
182 _res: &Tensor,
183 _grad_res: &Tensor,
184 ) -> Result<(Option<Tensor>, Option<Tensor>)> {
185 Err(crate::Error::BackwardNotSupported { op: self.name() })
186 }
187}
188
189pub trait CustomOp3 {
190 fn name(&self) -> &'static str;
191
192 fn cpu_fwd(
195 &self,
196 s1: &CpuStorage,
197 l1: &Layout,
198 s2: &CpuStorage,
199 l2: &Layout,
200 s3: &CpuStorage,
201 l3: &Layout,
202 ) -> Result<(CpuStorage, Shape)>;
203
204 fn cuda_fwd(
207 &self,
208 _: &CudaStorage,
209 _: &Layout,
210 _: &CudaStorage,
211 _: &Layout,
212 _: &CudaStorage,
213 _: &Layout,
214 ) -> Result<(CudaStorage, Shape)> {
215 Err(crate::Error::Cuda(
216 format!("no cuda implementation for {}", self.name()).into(),
217 ))
218 }
219
220 #[cfg(feature = "rocm")]
221 fn rocm_fwd(
222 &self,
223 _: &RocmStorage,
224 _: &Layout,
225 _: &RocmStorage,
226 _: &Layout,
227 _: &RocmStorage,
228 _: &Layout,
229 ) -> Result<(RocmStorage, Shape)> {
230 Err(crate::Error::Msg(format!(
231 "no rocm implementation for {}",
232 self.name()
233 )))
234 }
235 #[cfg(feature = "vulkan")]
236 fn vulkan_fwd(
237 &self,
238 _: &VulkanStorage,
239 l1: &Layout,
240 _: &VulkanStorage,
241 _: &Layout,
242 _: &VulkanStorage,
243 _: &Layout,
244 ) -> Result<(VulkanStorage, Shape)> {
245 log_vulkan_custom_op_bail(self.name(), l1);
246 Err(crate::Error::Msg(format!(
247 "no vulkan implementation for {}",
248 self.name()
249 )))
250 }
251 #[cfg(feature = "wgpu")]
252 fn wgpu_fwd(
253 &self,
254 _: &WgpuStorage,
255 _l1: &Layout,
256 _: &WgpuStorage,
257 _: &Layout,
258 _: &WgpuStorage,
259 _: &Layout,
260 ) -> Result<(WgpuStorage, Shape)> {
261 Err(crate::Error::Msg(format!(
262 "no wgpu implementation for {}",
263 self.name()
264 )))
265 }
266
267 fn metal_fwd(
270 &self,
271 _: &MetalStorage,
272 _: &Layout,
273 _: &MetalStorage,
274 _: &Layout,
275 _: &MetalStorage,
276 _: &Layout,
277 ) -> Result<(MetalStorage, Shape)> {
278 Err(crate::Error::Metal(
279 format!("no metal implementation for {}", self.name()).into(),
280 ))
281 }
282
283 fn bwd(
284 &self,
285 _arg1: &Tensor,
286 _arg2: &Tensor,
287 _arg3: &Tensor,
288 _res: &Tensor,
289 _grad_res: &Tensor,
290 ) -> Result<(Option<Tensor>, Option<Tensor>, Option<Tensor>)> {
291 Err(crate::Error::BackwardNotSupported { op: self.name() })
292 }
293}
294
295impl Tensor {
296 pub fn apply_op1_no_bwd<C: CustomOp1>(&self, c: &C) -> Result<Self> {
298 let (storage, shape) = self.storage().apply_op1(self.layout(), c)?;
299 Ok(from_storage(storage, shape, BackpropOp::none(), false))
300 }
301
302 pub fn apply_op2_no_bwd<C: CustomOp2>(&self, rhs: &Self, c: &C) -> Result<Self> {
304 let (storage, shape) =
305 self.storage()
306 .apply_op2(self.layout(), &rhs.storage(), rhs.layout(), c)?;
307 Ok(from_storage(storage, shape, BackpropOp::none(), false))
308 }
309
310 pub fn apply_op3_no_bwd<C: CustomOp3>(&self, t2: &Self, t3: &Self, c: &C) -> Result<Self> {
312 let (storage, shape) = self.storage().apply_op3(
313 self.layout(),
314 &t2.storage(),
315 t2.layout(),
316 &t3.storage(),
317 t3.layout(),
318 c,
319 )?;
320 Ok(from_storage(storage, shape, BackpropOp::none(), false))
321 }
322
323 pub fn apply_op1_arc(&self, c: Arc<Box<dyn CustomOp1 + Send + Sync>>) -> Result<Self> {
325 let (storage, shape) = self
326 .storage()
327 .apply_op1(self.layout(), c.as_ref().as_ref())?;
328 let op = BackpropOp::new1(self, |s| Op::CustomOp1(s, c.clone()));
329 Ok(from_storage(storage, shape, op, false))
330 }
331
332 pub fn apply_op1<C: 'static + CustomOp1 + Send + Sync>(&self, c: C) -> Result<Self> {
333 self.apply_op1_arc(Arc::new(Box::new(c)))
334 }
335
336 pub fn apply_op2_arc(
338 &self,
339 rhs: &Self,
340 c: Arc<Box<dyn CustomOp2 + Send + Sync>>,
341 ) -> Result<Self> {
342 let (storage, shape) = self.storage().apply_op2(
343 self.layout(),
344 &rhs.storage(),
345 rhs.layout(),
346 c.as_ref().as_ref(),
347 )?;
348 let op = BackpropOp::new2(self, rhs, |t1, t2| Op::CustomOp2(t1, t2, c.clone()));
349 Ok(from_storage(storage, shape, op, false))
350 }
351
352 pub fn apply_op2<C: 'static + CustomOp2 + Send + Sync>(&self, r: &Self, c: C) -> Result<Self> {
353 self.apply_op2_arc(r, Arc::new(Box::new(c)))
354 }
355
356 pub fn apply_op3_arc(
358 &self,
359 t2: &Self,
360 t3: &Self,
361 c: Arc<Box<dyn CustomOp3 + Send + Sync>>,
362 ) -> Result<Self> {
363 let (storage, shape) = self.storage().apply_op3(
364 self.layout(),
365 &t2.storage(),
366 t2.layout(),
367 &t3.storage(),
368 t3.layout(),
369 c.as_ref().as_ref(),
370 )?;
371 let op = BackpropOp::new3(self, t2, t3, |t1, t2, t3| {
372 Op::CustomOp3(t1, t2, t3, c.clone())
373 });
374 Ok(from_storage(storage, shape, op, false))
375 }
376
377 pub fn apply_op3<C: 'static + CustomOp3 + Send + Sync>(
378 &self,
379 t2: &Self,
380 t3: &Self,
381 c: C,
382 ) -> Result<Self> {
383 self.apply_op3_arc(t2, t3, Arc::new(Box::new(c)))
384 }
385}
386
387pub trait InplaceOp1 {
392 fn name(&self) -> &'static str;
394
395 fn cpu_fwd(&self, storage: &mut CpuStorage, layout: &Layout) -> Result<()>;
398
399 fn cuda_fwd(&self, _storage: &mut CudaStorage, _layout: &Layout) -> Result<()> {
402 Err(crate::Error::Cuda(
403 format!("no cuda implementation for {}", self.name()).into(),
404 ))
405 }
406
407 #[cfg(feature = "rocm")]
408 fn rocm_fwd(&self, _storage: &mut RocmStorage, _layout: &Layout) -> Result<()> {
409 Err(crate::Error::Msg(format!(
410 "no rocm implementation for {}",
411 self.name()
412 )))
413 }
414 #[cfg(feature = "vulkan")]
415 fn vulkan_fwd(&self, _storage: &mut VulkanStorage, _layout: &Layout) -> Result<()> {
416 log_vulkan_custom_op_bail(self.name(), _layout);
417 Err(crate::Error::Msg(format!(
418 "no vulkan implementation for {}",
419 self.name()
420 )))
421 }
422 #[cfg(feature = "wgpu")]
423 fn wgpu_fwd(&self, _storage: &mut WgpuStorage, _layout: &Layout) -> Result<()> {
424 Err(crate::Error::Msg(format!(
425 "no wgpu implementation for {}",
426 self.name()
427 )))
428 }
429
430 fn metal_fwd(&self, _storage: &mut MetalStorage, _layout: &Layout) -> Result<()> {
433 Err(crate::Error::Metal(
434 format!("no metal implementation for {}", self.name()).into(),
435 ))
436 }
437}
438
439pub trait InplaceOpN<const N: usize> {
442 fn name(&self) -> &'static str;
443
444 fn src_access_pattern(&self) -> Option<AccessPattern> {
447 None
448 }
449
450 fn cpu_fwd(
451 &self,
452 dst: &mut CpuStorage,
453 dst_l: &Layout,
454 srcs: [(&CpuStorage, &Layout); N],
455 ) -> Result<()> {
456 let _ = (dst, dst_l, srcs);
457 bail!("no cpu implementation for {}", self.name())
458 }
459
460 fn cpu_fwd_aliased(
461 &self,
462 dst: &mut CpuStorage,
463 dst_l: &Layout,
464 srcs: [(Src<'_, CpuStorage>, &Layout); N],
465 ) -> Result<()> {
466 let _ = (dst, dst_l, srcs);
467 bail!("no aliased cpu implementation for {}", self.name())
468 }
469
470 fn cuda_fwd(
471 &self,
472 dst: &mut CudaStorage,
473 dst_l: &Layout,
474 srcs: [(&CudaStorage, &Layout); N],
475 ) -> Result<()> {
476 let _ = (dst, dst_l, srcs);
477 bail!("no cuda implementation for {}", self.name())
478 }
479
480 fn cuda_fwd_aliased(
481 &self,
482 dst: &mut CudaStorage,
483 dst_l: &Layout,
484 srcs: [(Src<'_, CudaStorage>, &Layout); N],
485 ) -> Result<()> {
486 let _ = (dst, dst_l, srcs);
487 bail!("no aliased cpu implementation for {}", self.name())
488 }
489
490 fn metal_fwd(
491 &self,
492 dst: &mut MetalStorage,
493 dst_l: &Layout,
494 srcs: [(&MetalStorage, &Layout); N],
495 ) -> Result<()> {
496 let _ = (dst, dst_l, srcs);
497 bail!("no metal implementation for {}", self.name())
498 }
499
500 fn metal_fwd_aliased(
501 &self,
502 dst: &mut MetalStorage,
503 dst_l: &Layout,
504 srcs: [(Src<'_, MetalStorage>, &Layout); N],
505 ) -> Result<()> {
506 let _ = (dst, dst_l, srcs);
507 bail!("no aliased metal implementation for {}", self.name())
508 }
509
510 #[cfg(feature = "rocm")]
511 fn rocm_fwd(
512 &self,
513 dst: &mut RocmStorage,
514 dst_l: &Layout,
515 srcs: [(&RocmStorage, &Layout); N],
516 ) -> Result<()> {
517 let _ = (dst, dst_l, srcs);
518 bail!("no rocm implementation for {}", self.name())
519 }
520
521 #[cfg(feature = "rocm")]
522 fn rocm_fwd_aliased(
523 &self,
524 dst: &mut RocmStorage,
525 dst_l: &Layout,
526 srcs: [(Src<'_, RocmStorage>, &Layout); N],
527 ) -> Result<()> {
528 let _ = (dst, dst_l, srcs);
529 bail!("no aliased rocm implementation for {}", self.name())
530 }
531
532 #[cfg(feature = "vulkan")]
533 fn vulkan_fwd(
534 &self,
535 dst: &mut VulkanStorage,
536 dst_l: &Layout,
537 srcs: [(&VulkanStorage, &Layout); N],
538 ) -> Result<()> {
539 log_vulkan_custom_op_bail(self.name(), dst_l);
540 let _ = (dst, dst_l, srcs);
541 bail!("no vulkan implementation for {}", self.name())
542 }
543
544 #[cfg(feature = "vulkan")]
545 fn vulkan_fwd_aliased(
546 &self,
547 dst: &mut VulkanStorage,
548 dst_l: &Layout,
549 srcs: [(Src<'_, VulkanStorage>, &Layout); N],
550 ) -> Result<()> {
551 log_vulkan_custom_op_bail(self.name(), dst_l);
552 let _ = (dst, dst_l, srcs);
553 bail!("no aliased vulkan implementation for {}", self.name())
554 }
555
556 #[cfg(feature = "wgpu")]
557 fn wgpu_fwd(
558 &self,
559 dst: &mut WgpuStorage,
560 dst_l: &Layout,
561 srcs: [(&WgpuStorage, &Layout); N],
562 ) -> Result<()> {
563 let _ = (dst, dst_l, srcs);
564 bail!("no wgpu implementation for {}", self.name())
565 }
566
567 #[cfg(feature = "wgpu")]
568 fn wgpu_fwd_aliased(
569 &self,
570 dst: &mut WgpuStorage,
571 dst_l: &Layout,
572 srcs: [(Src<'_, WgpuStorage>, &Layout); N],
573 ) -> Result<()> {
574 let _ = (dst, dst_l, srcs);
575 bail!("no aliased wgpu implementation for {}", self.name())
576 }
577}
578
579#[derive(Debug)]
580pub enum Src<'a, S> {
581 Distinct(&'a S),
582 Aliased(LayoutRelation),
583}
584
585impl<S> Copy for Src<'_, S> {}
586
587impl<S> Clone for Src<'_, S> {
588 fn clone(&self) -> Self {
589 *self
590 }
591}
592
593pub(crate) fn all_distinct<'a, B, const N: usize>(
595 srcs: &[(Src<'a, B>, &'a Layout); N],
596) -> Option<[(&'a B, &'a Layout); N]> {
597 srcs.iter()
598 .all(|(s, _)| matches!(s, Src::Distinct(_)))
599 .then(|| {
600 std::array::from_fn(|i| match srcs[i] {
601 (Src::Distinct(s), l) => (s, l),
602 _ => unreachable!("checked immediately above"),
603 })
604 })
605}
606
607#[derive(Copy, Clone, PartialEq, Eq, Debug)]
613pub enum AccessPattern {
614 Elementwise,
616 Arbitrary,
618}
619
620impl AccessPattern {
621 fn supports(self, rel: LayoutRelation) -> bool {
622 matches!(
623 (self, rel),
624 (
625 AccessPattern::Elementwise,
626 LayoutRelation::Identical | LayoutRelation::Disjoint
627 ) | (AccessPattern::Arbitrary, LayoutRelation::Disjoint)
628 )
629 }
630}
631
632macro_rules! forward_op1 {
633 ($fwd:ident, $fwd_aliased:ident, $storage:ty) => {
634 fn $fwd(
635 &self,
636 dst: &mut $storage,
637 dl: &Layout,
638 _: [(&$storage, &Layout); 0],
639 ) -> Result<()> {
640 InplaceOp1::$fwd(self, dst, dl)
641 }
642
643 fn $fwd_aliased(
644 &self,
645 dst: &mut $storage,
646 dl: &Layout,
647 _: [(Src<'_, $storage>, &Layout); 0],
648 ) -> Result<()> {
649 InplaceOp1::$fwd(self, dst, dl)
650 }
651 };
652}
653
654impl<C: InplaceOp1> InplaceOpN<0> for C {
655 fn name(&self) -> &'static str {
656 InplaceOp1::name(self)
657 }
658
659 forward_op1!(cpu_fwd, cpu_fwd_aliased, CpuStorage);
660 forward_op1!(cuda_fwd, cuda_fwd_aliased, CudaStorage);
661 forward_op1!(metal_fwd, metal_fwd_aliased, MetalStorage);
662 #[cfg(feature = "rocm")]
663 forward_op1!(rocm_fwd, rocm_fwd_aliased, RocmStorage);
664 #[cfg(feature = "vulkan")]
665 forward_op1!(vulkan_fwd, vulkan_fwd_aliased, VulkanStorage);
666 #[cfg(feature = "wgpu")]
667 forward_op1!(wgpu_fwd, wgpu_fwd_aliased, WgpuStorage);
668}
669
670macro_rules! forward_op2 {
671 ($fwd:ident, $fwd_aliased:ident, $storage:ty) => {
672 fn $fwd(
673 &self,
674 dst: &mut $storage,
675 dl: &Layout,
676 srcs: [(&$storage, &Layout); 1],
677 ) -> Result<()> {
678 let [(s, sl)] = srcs;
679 InplaceOp2::$fwd(self, dst, dl, s, sl)
680 }
681
682 fn $fwd_aliased(
683 &self,
684 dst: &mut $storage,
685 dl: &Layout,
686 srcs: [(Src<'_, $storage>, &Layout); 1],
687 ) -> Result<()> {
688 match srcs {
689 [(Src::Distinct(s), sl)] => InplaceOp2::$fwd(self, dst, dl, s, sl),
690 _ => bail!(
691 "{}: aliased input requires migrating to InplaceOpN",
692 self.name()
693 ),
694 }
695 }
696 };
697}
698
699impl<C: InplaceOp2> InplaceOpN<1> for C {
700 fn name(&self) -> &'static str {
701 InplaceOp2::name(self)
702 }
703
704 forward_op2!(cpu_fwd, cpu_fwd_aliased, CpuStorage);
705 forward_op2!(cuda_fwd, cuda_fwd_aliased, CudaStorage);
706 forward_op2!(metal_fwd, metal_fwd_aliased, MetalStorage);
707 #[cfg(feature = "rocm")]
708 forward_op2!(rocm_fwd, rocm_fwd_aliased, RocmStorage);
709 #[cfg(feature = "vulkan")]
710 forward_op2!(vulkan_fwd, vulkan_fwd_aliased, VulkanStorage);
711 #[cfg(feature = "wgpu")]
712 forward_op2!(wgpu_fwd, wgpu_fwd_aliased, WgpuStorage);
713}
714
715pub trait InplaceOp2 {
716 fn name(&self) -> &'static str;
717
718 fn cpu_fwd(&self, s1: &mut CpuStorage, l1: &Layout, s2: &CpuStorage, l2: &Layout)
721 -> Result<()>;
722
723 fn cuda_fwd(
726 &self,
727 s1: &mut CudaStorage,
728 l1: &Layout,
729 s2: &CudaStorage,
730 l2: &Layout,
731 ) -> Result<()> {
732 _ = (s1, l1, s2, l2);
733 Err(crate::Error::Cuda(
734 format!("no cuda implementation for {}", self.name()).into(),
735 ))
736 }
737
738 #[cfg(feature = "rocm")]
739 fn rocm_fwd(&self, _: &mut RocmStorage, _: &Layout, _: &RocmStorage, _: &Layout) -> Result<()> {
740 Err(crate::Error::Msg(format!(
741 "no rocm implementation for {}",
742 self.name()
743 )))
744 }
745 #[cfg(feature = "vulkan")]
746 fn vulkan_fwd(
747 &self,
748 _: &mut VulkanStorage,
749 l1: &Layout,
750 _: &VulkanStorage,
751 _: &Layout,
752 ) -> Result<()> {
753 log_vulkan_custom_op_bail(self.name(), l1);
754 Err(crate::Error::Msg(format!(
755 "no vulkan implementation for {}",
756 self.name()
757 )))
758 }
759 #[cfg(feature = "wgpu")]
760 fn wgpu_fwd(
761 &self,
762 _: &mut WgpuStorage,
763 _l1: &Layout,
764 _: &WgpuStorage,
765 _: &Layout,
766 ) -> Result<()> {
767 Err(crate::Error::Msg(format!(
768 "no wgpu implementation for {}",
769 self.name()
770 )))
771 }
772
773 fn metal_fwd(
776 &self,
777 s1: &mut MetalStorage,
778 l1: &Layout,
779 s2: &MetalStorage,
780 l2: &Layout,
781 ) -> Result<()> {
782 _ = (s1, l1, s2, l2);
783 Err(crate::Error::Metal(
784 format!("no metal implementation for {}", self.name()).into(),
785 ))
786 }
787}
788
789macro_rules! forward_op3 {
790 ($fwd:ident, $fwd_aliased:ident, $storage:ty) => {
791 fn $fwd(
792 &self,
793 dst: &mut $storage,
794 dl: &Layout,
795 srcs: [(&$storage, &Layout); 2],
796 ) -> Result<()> {
797 let [(s1, l1), (s2, l2)] = srcs;
798 InplaceOp3::$fwd(self, dst, dl, s1, l1, s2, l2)
799 }
800
801 fn $fwd_aliased(
802 &self,
803 dst: &mut $storage,
804 dl: &Layout,
805 srcs: [(Src<'_, $storage>, &Layout); 2],
806 ) -> Result<()> {
807 match srcs {
808 [(Src::Distinct(s1), l1), (Src::Distinct(s2), l2)] => {
809 InplaceOp3::$fwd(self, dst, dl, s1, l1, s2, l2)
810 }
811 _ => bail!(
812 "{}: aliased input requires migrating to InplaceOpN",
813 self.name()
814 ),
815 }
816 }
817 };
818}
819
820impl<C: InplaceOp3> InplaceOpN<2> for C {
821 fn name(&self) -> &'static str {
822 InplaceOp3::name(self)
823 }
824
825 forward_op3!(cpu_fwd, cpu_fwd_aliased, CpuStorage);
826 forward_op3!(cuda_fwd, cuda_fwd_aliased, CudaStorage);
827 forward_op3!(metal_fwd, metal_fwd_aliased, MetalStorage);
828 #[cfg(feature = "rocm")]
829 forward_op3!(rocm_fwd, rocm_fwd_aliased, RocmStorage);
830 #[cfg(feature = "vulkan")]
831 forward_op3!(vulkan_fwd, vulkan_fwd_aliased, VulkanStorage);
832 #[cfg(feature = "wgpu")]
833 forward_op3!(wgpu_fwd, wgpu_fwd_aliased, WgpuStorage);
834}
835
836pub trait InplaceOp3 {
837 fn name(&self) -> &'static str;
838
839 fn cpu_fwd(
842 &self,
843 s1: &mut CpuStorage,
844 l1: &Layout,
845 s2: &CpuStorage,
846 l2: &Layout,
847 s3: &CpuStorage,
848 l3: &Layout,
849 ) -> Result<()>;
850
851 fn cuda_fwd(
854 &self,
855 _: &mut CudaStorage,
856 _: &Layout,
857 _: &CudaStorage,
858 _: &Layout,
859 _: &CudaStorage,
860 _: &Layout,
861 ) -> Result<()> {
862 Err(crate::Error::Cuda(
863 format!("no cuda implementation for {}", self.name()).into(),
864 ))
865 }
866
867 #[cfg(feature = "rocm")]
868 fn rocm_fwd(
869 &self,
870 _: &mut RocmStorage,
871 _: &Layout,
872 _: &RocmStorage,
873 _: &Layout,
874 _: &RocmStorage,
875 _: &Layout,
876 ) -> Result<()> {
877 Err(crate::Error::Msg(format!(
878 "no rocm implementation for {}",
879 self.name()
880 )))
881 }
882 #[cfg(feature = "vulkan")]
883 fn vulkan_fwd(
884 &self,
885 _: &mut VulkanStorage,
886 l1: &Layout,
887 _: &VulkanStorage,
888 _: &Layout,
889 _: &VulkanStorage,
890 _: &Layout,
891 ) -> Result<()> {
892 log_vulkan_custom_op_bail(self.name(), l1);
893 Err(crate::Error::Msg(format!(
894 "no vulkan implementation for {}",
895 self.name()
896 )))
897 }
898 #[cfg(feature = "wgpu")]
899 fn wgpu_fwd(
900 &self,
901 _: &mut WgpuStorage,
902 _l1: &Layout,
903 _: &WgpuStorage,
904 _: &Layout,
905 _: &WgpuStorage,
906 _: &Layout,
907 ) -> Result<()> {
908 Err(crate::Error::Msg(format!(
909 "no wgpu implementation for {}",
910 self.name()
911 )))
912 }
913
914 fn metal_fwd(
917 &self,
918 _: &mut MetalStorage,
919 _: &Layout,
920 _: &MetalStorage,
921 _: &Layout,
922 _: &MetalStorage,
923 _: &Layout,
924 ) -> Result<()> {
925 Err(crate::Error::Metal(
926 format!("no metal implementation for {}", self.name()).into(),
927 ))
928 }
929}
930
931impl Tensor {
932 fn inplace_op<const N: usize, C: InplaceOpN<N>>(&self, srcs: [&Self; N], c: &C) -> Result<()> {
937 let name = c.name();
938
939 if self.layout().has_internal_overlap() {
941 bail!("{name}: dst has repeated elements (zero-stride). Can not write in-place")
942 }
943
944 let access = c.src_access_pattern();
946 let mut rels: [Option<LayoutRelation>; N] = [None; N];
947 for i in 0..N {
948 if !self.same_storage(srcs[i]) {
949 continue;
950 }
951 let rel = Layout::relation(self.layout(), srcs[i].layout());
952 match access {
953 Some(a) if a.supports(rel) => rels[i] = Some(rel),
954 Some(a) => bail!(
955 "src {i} shares storage with dst ({rel:?}), which is not supported for the access pattern of `{name}` ({a:?})."
956 ),
957 None => bail!(
958 "src {i} shares storage with dst, and `{name}` does not support aliased operands."
959 ),
960 }
961 }
962
963 let dst_key = self.storage_key();
966
967 let mut order: [usize; N] = std::array::from_fn(|i| i);
968 order.sort_unstable_by_key(|&i| srcs[i].storage_key());
969
970 let mut guards: [Option<_>; N] = std::array::from_fn(|_| None);
971 let mut dst: Option<_> = None;
972
973 for &i in order.iter() {
974 if rels[i].is_some() {
975 continue; }
977 let key = srcs[i].storage_key();
978 if key > dst_key && dst.is_none() {
979 dst = Some(self.storage_mut());
980 }
981 guards[i] = Some(srcs[i].storage());
984 }
985 let mut dst = match dst {
987 Some(g) => g,
988 None => self.storage_mut(),
989 };
990
991 let operands: [(Src<'_, Storage>, &Layout); N] = std::array::from_fn(|i| {
992 let s = match (&guards[i], rels[i]) {
993 (Some(g), None) => Src::Distinct(&**g),
994 (None, Some(rel)) => Src::Aliased(rel),
995 _ => unreachable!(
996 "Source is either distinct or aliased. Other match patterns should be impossible"
997 ),
998 };
999 (s, srcs[i].layout())
1000 });
1001
1002 dst.inplace_op(self.layout(), operands, c)
1003 }
1004
1005 pub fn inplace_op1<C: InplaceOp1>(&self, c: &C) -> Result<()> {
1007 self.inplace_op([], c)
1008 }
1009
1010 pub fn inplace_op2<C: InplaceOpN<1>>(&self, rhs: &Self, c: &C) -> Result<()> {
1012 self.inplace_op([rhs], c)
1013 }
1014
1015 pub fn inplace_op3<C: InplaceOpN<2>>(&self, t2: &Self, t3: &Self, c: &C) -> Result<()> {
1017 self.inplace_op([t2, t3], c)
1018 }
1019}