1use crate::layout::LayoutRelation;
2use crate::op::{BackpropOp, Op};
3use crate::tensor::from_storage;
4use crate::{bail, CpuStorage, CudaStorage, Layout, MetalStorage, Result, Shape, Storage, Tensor};
5use std::sync::Arc;
6
7pub trait CustomOp1 {
9 fn name(&self) -> &'static str;
11
12 fn cpu_fwd(&self, storage: &CpuStorage, layout: &Layout) -> Result<(CpuStorage, Shape)>;
15
16 fn cuda_fwd(&self, _storage: &CudaStorage, _layout: &Layout) -> Result<(CudaStorage, Shape)> {
19 Err(crate::Error::Cuda(
20 format!("no cuda implementation for {}", self.name()).into(),
21 ))
22 }
23
24 fn metal_fwd(
27 &self,
28 _storage: &MetalStorage,
29 _layout: &Layout,
30 ) -> Result<(MetalStorage, Shape)> {
31 Err(crate::Error::Metal(
32 format!("no metal implementation for {}", self.name()).into(),
33 ))
34 }
35
36 fn bwd(&self, _arg: &Tensor, _res: &Tensor, _grad_res: &Tensor) -> Result<Option<Tensor>> {
40 Err(crate::Error::BackwardNotSupported { op: self.name() })
41 }
42}
43
44pub trait CustomOp2 {
45 fn name(&self) -> &'static str;
46
47 fn cpu_fwd(
50 &self,
51 s1: &CpuStorage,
52 l1: &Layout,
53 s2: &CpuStorage,
54 l2: &Layout,
55 ) -> Result<(CpuStorage, Shape)>;
56
57 fn cuda_fwd(
60 &self,
61 _: &CudaStorage,
62 _: &Layout,
63 _: &CudaStorage,
64 _: &Layout,
65 ) -> Result<(CudaStorage, Shape)> {
66 Err(crate::Error::Cuda(
67 format!("no cuda implementation for {}", self.name()).into(),
68 ))
69 }
70
71 fn metal_fwd(
74 &self,
75 _: &MetalStorage,
76 _: &Layout,
77 _: &MetalStorage,
78 _: &Layout,
79 ) -> Result<(MetalStorage, Shape)> {
80 Err(crate::Error::Metal(
81 format!("no metal implementation for {}", self.name()).into(),
82 ))
83 }
84
85 fn bwd(
86 &self,
87 _arg1: &Tensor,
88 _arg2: &Tensor,
89 _res: &Tensor,
90 _grad_res: &Tensor,
91 ) -> Result<(Option<Tensor>, Option<Tensor>)> {
92 Err(crate::Error::BackwardNotSupported { op: self.name() })
93 }
94}
95
96pub trait CustomOp3 {
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 s3: &CpuStorage,
108 l3: &Layout,
109 ) -> Result<(CpuStorage, Shape)>;
110
111 fn cuda_fwd(
114 &self,
115 _: &CudaStorage,
116 _: &Layout,
117 _: &CudaStorage,
118 _: &Layout,
119 _: &CudaStorage,
120 _: &Layout,
121 ) -> Result<(CudaStorage, Shape)> {
122 Err(crate::Error::Cuda(
123 format!("no cuda implementation for {}", self.name()).into(),
124 ))
125 }
126
127 fn metal_fwd(
130 &self,
131 _: &MetalStorage,
132 _: &Layout,
133 _: &MetalStorage,
134 _: &Layout,
135 _: &MetalStorage,
136 _: &Layout,
137 ) -> Result<(MetalStorage, Shape)> {
138 Err(crate::Error::Metal(
139 format!("no metal implementation for {}", self.name()).into(),
140 ))
141 }
142
143 fn bwd(
144 &self,
145 _arg1: &Tensor,
146 _arg2: &Tensor,
147 _arg3: &Tensor,
148 _res: &Tensor,
149 _grad_res: &Tensor,
150 ) -> Result<(Option<Tensor>, Option<Tensor>, Option<Tensor>)> {
151 Err(crate::Error::BackwardNotSupported { op: self.name() })
152 }
153}
154
155impl Tensor {
156 pub fn apply_op1_no_bwd<C: CustomOp1>(&self, c: &C) -> Result<Self> {
158 let (storage, shape) = self.storage().apply_op1(self.layout(), c)?;
159 Ok(from_storage(storage, shape, BackpropOp::none(), false))
160 }
161
162 pub fn apply_op2_no_bwd<C: CustomOp2>(&self, rhs: &Self, c: &C) -> Result<Self> {
164 let (storage, shape) =
165 self.storage()
166 .apply_op2(self.layout(), &rhs.storage(), rhs.layout(), c)?;
167 Ok(from_storage(storage, shape, BackpropOp::none(), false))
168 }
169
170 pub fn apply_op3_no_bwd<C: CustomOp3>(&self, t2: &Self, t3: &Self, c: &C) -> Result<Self> {
172 let (storage, shape) = self.storage().apply_op3(
173 self.layout(),
174 &t2.storage(),
175 t2.layout(),
176 &t3.storage(),
177 t3.layout(),
178 c,
179 )?;
180 Ok(from_storage(storage, shape, BackpropOp::none(), false))
181 }
182
183 pub fn apply_op1_arc(&self, c: Arc<Box<dyn CustomOp1 + Send + Sync>>) -> Result<Self> {
185 let (storage, shape) = self
186 .storage()
187 .apply_op1(self.layout(), c.as_ref().as_ref())?;
188 let op = BackpropOp::new1(self, |s| Op::CustomOp1(s, c.clone()));
189 Ok(from_storage(storage, shape, op, false))
190 }
191
192 pub fn apply_op1<C: 'static + CustomOp1 + Send + Sync>(&self, c: C) -> Result<Self> {
193 self.apply_op1_arc(Arc::new(Box::new(c)))
194 }
195
196 pub fn apply_op2_arc(
198 &self,
199 rhs: &Self,
200 c: Arc<Box<dyn CustomOp2 + Send + Sync>>,
201 ) -> Result<Self> {
202 let (storage, shape) = self.storage().apply_op2(
203 self.layout(),
204 &rhs.storage(),
205 rhs.layout(),
206 c.as_ref().as_ref(),
207 )?;
208 let op = BackpropOp::new2(self, rhs, |t1, t2| Op::CustomOp2(t1, t2, c.clone()));
209 Ok(from_storage(storage, shape, op, false))
210 }
211
212 pub fn apply_op2<C: 'static + CustomOp2 + Send + Sync>(&self, r: &Self, c: C) -> Result<Self> {
213 self.apply_op2_arc(r, Arc::new(Box::new(c)))
214 }
215
216 pub fn apply_op3_arc(
218 &self,
219 t2: &Self,
220 t3: &Self,
221 c: Arc<Box<dyn CustomOp3 + Send + Sync>>,
222 ) -> Result<Self> {
223 let (storage, shape) = self.storage().apply_op3(
224 self.layout(),
225 &t2.storage(),
226 t2.layout(),
227 &t3.storage(),
228 t3.layout(),
229 c.as_ref().as_ref(),
230 )?;
231 let op = BackpropOp::new3(self, t2, t3, |t1, t2, t3| {
232 Op::CustomOp3(t1, t2, t3, c.clone())
233 });
234 Ok(from_storage(storage, shape, op, false))
235 }
236
237 pub fn apply_op3<C: 'static + CustomOp3 + Send + Sync>(
238 &self,
239 t2: &Self,
240 t3: &Self,
241 c: C,
242 ) -> Result<Self> {
243 self.apply_op3_arc(t2, t3, Arc::new(Box::new(c)))
244 }
245}
246
247pub trait InplaceOp1 {
252 fn name(&self) -> &'static str;
254
255 fn cpu_fwd(&self, storage: &mut CpuStorage, layout: &Layout) -> Result<()>;
258
259 fn cuda_fwd(&self, _storage: &mut CudaStorage, _layout: &Layout) -> Result<()> {
262 Err(crate::Error::Cuda(
263 format!("no cuda implementation for {}", self.name()).into(),
264 ))
265 }
266
267 fn metal_fwd(&self, _storage: &mut MetalStorage, _layout: &Layout) -> Result<()> {
270 Err(crate::Error::Metal(
271 format!("no metal implementation for {}", self.name()).into(),
272 ))
273 }
274}
275
276pub trait InplaceOpN<const N: usize> {
279 fn name(&self) -> &'static str;
280
281 fn src_access_pattern(&self) -> Option<AccessPattern> {
284 None
285 }
286
287 fn cpu_fwd(
288 &self,
289 dst: &mut CpuStorage,
290 dst_l: &Layout,
291 srcs: [(&CpuStorage, &Layout); N],
292 ) -> Result<()> {
293 let _ = (dst, dst_l, srcs);
294 bail!("no cpu implementation for {}", self.name())
295 }
296
297 fn cpu_fwd_aliased(
298 &self,
299 dst: &mut CpuStorage,
300 dst_l: &Layout,
301 srcs: [(Src<'_, CpuStorage>, &Layout); N],
302 ) -> Result<()> {
303 let _ = (dst, dst_l, srcs);
304 bail!("no aliased cpu implementation for {}", self.name())
305 }
306
307 fn cuda_fwd(
308 &self,
309 dst: &mut CudaStorage,
310 dst_l: &Layout,
311 srcs: [(&CudaStorage, &Layout); N],
312 ) -> Result<()> {
313 let _ = (dst, dst_l, srcs);
314 bail!("no cuda implementation for {}", self.name())
315 }
316
317 fn cuda_fwd_aliased(
318 &self,
319 dst: &mut CudaStorage,
320 dst_l: &Layout,
321 srcs: [(Src<'_, CudaStorage>, &Layout); N],
322 ) -> Result<()> {
323 let _ = (dst, dst_l, srcs);
324 bail!("no aliased cpu implementation for {}", self.name())
325 }
326
327 fn metal_fwd(
328 &self,
329 dst: &mut MetalStorage,
330 dst_l: &Layout,
331 srcs: [(&MetalStorage, &Layout); N],
332 ) -> Result<()> {
333 let _ = (dst, dst_l, srcs);
334 bail!("no metal implementation for {}", self.name())
335 }
336
337 fn metal_fwd_aliased(
338 &self,
339 dst: &mut MetalStorage,
340 dst_l: &Layout,
341 srcs: [(Src<'_, MetalStorage>, &Layout); N],
342 ) -> Result<()> {
343 let _ = (dst, dst_l, srcs);
344 bail!("no aliased metal implementation for {}", self.name())
345 }
346}
347
348#[derive(Debug)]
349pub enum Src<'a, S> {
350 Distinct(&'a S),
351 Aliased(LayoutRelation),
352}
353
354impl<S> Copy for Src<'_, S> {}
355
356impl<S> Clone for Src<'_, S> {
357 fn clone(&self) -> Self {
358 *self
359 }
360}
361
362pub(crate) fn all_distinct<'a, B, const N: usize>(
364 srcs: &[(Src<'a, B>, &'a Layout); N],
365) -> Option<[(&'a B, &'a Layout); N]> {
366 srcs.iter()
367 .all(|(s, _)| matches!(s, Src::Distinct(_)))
368 .then(|| {
369 std::array::from_fn(|i| match srcs[i] {
370 (Src::Distinct(s), l) => (s, l),
371 _ => unreachable!("checked immediately above"),
372 })
373 })
374}
375
376#[derive(Copy, Clone, PartialEq, Eq, Debug)]
382pub enum AccessPattern {
383 Elementwise,
385 Arbitrary,
387}
388
389impl AccessPattern {
390 fn supports(self, rel: LayoutRelation) -> bool {
391 matches!(
392 (self, rel),
393 (
394 AccessPattern::Elementwise,
395 LayoutRelation::Identical | LayoutRelation::Disjoint
396 ) | (AccessPattern::Arbitrary, LayoutRelation::Disjoint)
397 )
398 }
399}
400
401macro_rules! forward_op1 {
402 ($fwd:ident, $fwd_aliased:ident, $storage:ty) => {
403 fn $fwd(
404 &self,
405 dst: &mut $storage,
406 dl: &Layout,
407 _: [(&$storage, &Layout); 0],
408 ) -> Result<()> {
409 InplaceOp1::$fwd(self, dst, dl)
410 }
411
412 fn $fwd_aliased(
413 &self,
414 dst: &mut $storage,
415 dl: &Layout,
416 _: [(Src<'_, $storage>, &Layout); 0],
417 ) -> Result<()> {
418 InplaceOp1::$fwd(self, dst, dl)
419 }
420 };
421}
422
423impl<C: InplaceOp1> InplaceOpN<0> for C {
424 fn name(&self) -> &'static str {
425 InplaceOp1::name(self)
426 }
427
428 forward_op1!(cpu_fwd, cpu_fwd_aliased, CpuStorage);
429 forward_op1!(cuda_fwd, cuda_fwd_aliased, CudaStorage);
430 forward_op1!(metal_fwd, metal_fwd_aliased, MetalStorage);
431}
432
433macro_rules! forward_op2 {
434 ($fwd:ident, $fwd_aliased:ident, $storage:ty) => {
435 fn $fwd(
436 &self,
437 dst: &mut $storage,
438 dl: &Layout,
439 srcs: [(&$storage, &Layout); 1],
440 ) -> Result<()> {
441 let [(s, sl)] = srcs;
442 InplaceOp2::$fwd(self, dst, dl, s, sl)
443 }
444
445 fn $fwd_aliased(
446 &self,
447 dst: &mut $storage,
448 dl: &Layout,
449 srcs: [(Src<'_, $storage>, &Layout); 1],
450 ) -> Result<()> {
451 match srcs {
452 [(Src::Distinct(s), sl)] => InplaceOp2::$fwd(self, dst, dl, s, sl),
453 _ => bail!(
454 "{}: aliased input requires migrating to InplaceOpN",
455 self.name()
456 ),
457 }
458 }
459 };
460}
461
462impl<C: InplaceOp2> InplaceOpN<1> for C {
463 fn name(&self) -> &'static str {
464 InplaceOp2::name(self)
465 }
466
467 forward_op2!(cpu_fwd, cpu_fwd_aliased, CpuStorage);
468 forward_op2!(cuda_fwd, cuda_fwd_aliased, CudaStorage);
469 forward_op2!(metal_fwd, metal_fwd_aliased, MetalStorage);
470}
471
472pub trait InplaceOp2 {
473 fn name(&self) -> &'static str;
474
475 fn cpu_fwd(&self, s1: &mut CpuStorage, l1: &Layout, s2: &CpuStorage, l2: &Layout)
478 -> Result<()>;
479
480 fn cuda_fwd(
483 &self,
484 s1: &mut CudaStorage,
485 l1: &Layout,
486 s2: &CudaStorage,
487 l2: &Layout,
488 ) -> Result<()> {
489 _ = (s1, l1, s2, l2);
490 Err(crate::Error::Cuda(
491 format!("no cuda implementation for {}", self.name()).into(),
492 ))
493 }
494
495 fn metal_fwd(
498 &self,
499 s1: &mut MetalStorage,
500 l1: &Layout,
501 s2: &MetalStorage,
502 l2: &Layout,
503 ) -> Result<()> {
504 _ = (s1, l1, s2, l2);
505 Err(crate::Error::Metal(
506 format!("no metal implementation for {}", self.name()).into(),
507 ))
508 }
509}
510
511macro_rules! forward_op3 {
512 ($fwd:ident, $fwd_aliased:ident, $storage:ty) => {
513 fn $fwd(
514 &self,
515 dst: &mut $storage,
516 dl: &Layout,
517 srcs: [(&$storage, &Layout); 2],
518 ) -> Result<()> {
519 let [(s1, l1), (s2, l2)] = srcs;
520 InplaceOp3::$fwd(self, dst, dl, s1, l1, s2, l2)
521 }
522
523 fn $fwd_aliased(
524 &self,
525 dst: &mut $storage,
526 dl: &Layout,
527 srcs: [(Src<'_, $storage>, &Layout); 2],
528 ) -> Result<()> {
529 match srcs {
530 [(Src::Distinct(s1), l1), (Src::Distinct(s2), l2)] => {
531 InplaceOp3::$fwd(self, dst, dl, s1, l1, s2, l2)
532 }
533 _ => bail!(
534 "{}: aliased input requires migrating to InplaceOpN",
535 self.name()
536 ),
537 }
538 }
539 };
540}
541
542impl<C: InplaceOp3> InplaceOpN<2> for C {
543 fn name(&self) -> &'static str {
544 InplaceOp3::name(self)
545 }
546
547 forward_op3!(cpu_fwd, cpu_fwd_aliased, CpuStorage);
548 forward_op3!(cuda_fwd, cuda_fwd_aliased, CudaStorage);
549 forward_op3!(metal_fwd, metal_fwd_aliased, MetalStorage);
550}
551
552pub trait InplaceOp3 {
553 fn name(&self) -> &'static str;
554
555 fn cpu_fwd(
558 &self,
559 s1: &mut CpuStorage,
560 l1: &Layout,
561 s2: &CpuStorage,
562 l2: &Layout,
563 s3: &CpuStorage,
564 l3: &Layout,
565 ) -> Result<()>;
566
567 fn cuda_fwd(
570 &self,
571 _: &mut CudaStorage,
572 _: &Layout,
573 _: &CudaStorage,
574 _: &Layout,
575 _: &CudaStorage,
576 _: &Layout,
577 ) -> Result<()> {
578 Err(crate::Error::Cuda(
579 format!("no cuda implementation for {}", self.name()).into(),
580 ))
581 }
582
583 fn metal_fwd(
586 &self,
587 _: &mut MetalStorage,
588 _: &Layout,
589 _: &MetalStorage,
590 _: &Layout,
591 _: &MetalStorage,
592 _: &Layout,
593 ) -> Result<()> {
594 Err(crate::Error::Metal(
595 format!("no metal implementation for {}", self.name()).into(),
596 ))
597 }
598}
599
600impl Tensor {
601 fn inplace_op<const N: usize, C: InplaceOpN<N>>(&self, srcs: [&Self; N], c: &C) -> Result<()> {
606 let name = c.name();
607
608 if self.layout().has_internal_overlap() {
610 bail!("{name}: dst has repeated elements (zero-stride). Can not write in-place")
611 }
612
613 let access = c.src_access_pattern();
615 let mut rels: [Option<LayoutRelation>; N] = [None; N];
616 for i in 0..N {
617 if !self.same_storage(srcs[i]) {
618 continue;
619 }
620 let rel = Layout::relation(self.layout(), srcs[i].layout());
621 match access {
622 Some(a) if a.supports(rel) => rels[i] = Some(rel),
623 Some(a) => bail!(
624 "src {i} shares storage with dst ({rel:?}), which is not supported for the access pattern of `{name}` ({a:?})."
625 ),
626 None => bail!(
627 "src {i} shares storage with dst, and `{name}` does not support aliased operands."
628 ),
629 }
630 }
631
632 let dst_key = self.storage_key();
635
636 let mut order: [usize; N] = std::array::from_fn(|i| i);
637 order.sort_unstable_by_key(|&i| srcs[i].storage_key());
638
639 let mut guards: [Option<_>; N] = std::array::from_fn(|_| None);
640 let mut dst: Option<_> = None;
641
642 for &i in order.iter() {
643 if rels[i].is_some() {
644 continue; }
646 let key = srcs[i].storage_key();
647 if key > dst_key && dst.is_none() {
648 dst = Some(self.storage_mut());
649 }
650 guards[i] = Some(srcs[i].storage());
653 }
654 let mut dst = match dst {
656 Some(g) => g,
657 None => self.storage_mut(),
658 };
659
660 let operands: [(Src<'_, Storage>, &Layout); N] = std::array::from_fn(|i| {
661 let s = match (&guards[i], rels[i]) {
662 (Some(g), None) => Src::Distinct(&**g),
663 (None, Some(rel)) => Src::Aliased(rel),
664 _ => unreachable!(
665 "Source is either distinct or aliased. Other match patterns should be impossible"
666 ),
667 };
668 (s, srcs[i].layout())
669 });
670
671 dst.inplace_op(self.layout(), operands, c)
672 }
673
674 pub fn inplace_op1<C: InplaceOp1>(&self, c: &C) -> Result<()> {
676 self.inplace_op([], c)
677 }
678
679 pub fn inplace_op2<C: InplaceOpN<1>>(&self, rhs: &Self, c: &C) -> Result<()> {
681 self.inplace_op([rhs], c)
682 }
683
684 pub fn inplace_op3<C: InplaceOpN<2>>(&self, t2: &Self, t3: &Self, c: &C) -> Result<()> {
686 self.inplace_op([t2, t3], c)
687 }
688}