1use crate::op::{BackpropOp, Op};
2use crate::tensor::from_storage;
3#[cfg(feature = "rocm")]
4use crate::RocmStorage;
5#[cfg(feature = "vulkan")]
6use crate::VulkanStorage;
7#[cfg(feature = "wgpu")]
8use crate::WgpuStorage;
9use crate::{CpuStorage, CudaStorage, Layout, MetalStorage, Result, Shape, Tensor};
10use std::sync::Arc;
11
12#[cfg(feature = "vulkan")]
19fn log_vulkan_custom_op_bail(name: &str, l: &Layout) {
20 if std::env::var("VK_PROFILE").map(|v| v != "0").unwrap_or(false) {
21 eprintln!(
22 "[VK_PROFILE] custom-op bail op={name} shape={:?} (no vulkan_fwd; would round-trip/err)",
23 l.shape().dims()
24 );
25 }
26}
27
28pub trait CustomOp1 {
30 fn name(&self) -> &'static str;
32
33 fn cpu_fwd(&self, storage: &CpuStorage, layout: &Layout) -> Result<(CpuStorage, Shape)>;
36
37 fn cuda_fwd(&self, _storage: &CudaStorage, _layout: &Layout) -> Result<(CudaStorage, Shape)> {
40 Err(crate::Error::Cuda(
41 format!("no cuda implementation for {}", self.name()).into(),
42 ))
43 }
44
45 #[cfg(feature = "rocm")]
46 fn rocm_fwd(&self, _storage: &RocmStorage, _layout: &Layout) -> Result<(RocmStorage, Shape)> {
47 Err(crate::Error::Msg(format!(
48 "no rocm implementation for {}",
49 self.name()
50 )))
51 }
52 #[cfg(feature = "vulkan")]
53 fn vulkan_fwd(
54 &self,
55 _storage: &VulkanStorage,
56 _layout: &Layout,
57 ) -> Result<(VulkanStorage, Shape)> {
58 log_vulkan_custom_op_bail(self.name(), _layout);
59 Err(crate::Error::Msg(format!(
60 "no vulkan implementation for {}",
61 self.name()
62 )))
63 }
64 #[cfg(feature = "wgpu")]
65 fn wgpu_fwd(&self, _storage: &WgpuStorage, _layout: &Layout) -> Result<(WgpuStorage, Shape)> {
66 Err(crate::Error::Msg(format!(
67 "no wgpu implementation for {}",
68 self.name()
69 )))
70 }
71
72 fn metal_fwd(
75 &self,
76 _storage: &MetalStorage,
77 _layout: &Layout,
78 ) -> Result<(MetalStorage, Shape)> {
79 Err(crate::Error::Metal(
80 format!("no metal implementation for {}", self.name()).into(),
81 ))
82 }
83
84 fn bwd(&self, _arg: &Tensor, _res: &Tensor, _grad_res: &Tensor) -> Result<Option<Tensor>> {
88 Err(crate::Error::BackwardNotSupported { op: self.name() })
89 }
90}
91
92pub trait CustomOp2 {
93 fn name(&self) -> &'static str;
94
95 fn cpu_fwd(
98 &self,
99 s1: &CpuStorage,
100 l1: &Layout,
101 s2: &CpuStorage,
102 l2: &Layout,
103 ) -> Result<(CpuStorage, Shape)>;
104
105 fn cuda_fwd(
108 &self,
109 _: &CudaStorage,
110 _: &Layout,
111 _: &CudaStorage,
112 _: &Layout,
113 ) -> Result<(CudaStorage, Shape)> {
114 Err(crate::Error::Cuda(
115 format!("no cuda implementation for {}", self.name()).into(),
116 ))
117 }
118
119 #[cfg(feature = "rocm")]
120 fn rocm_fwd(
121 &self,
122 _: &RocmStorage,
123 _: &Layout,
124 _: &RocmStorage,
125 _: &Layout,
126 ) -> Result<(RocmStorage, Shape)> {
127 Err(crate::Error::Msg(format!(
128 "no rocm implementation for {}",
129 self.name()
130 )))
131 }
132 #[cfg(feature = "vulkan")]
133 fn vulkan_fwd(
134 &self,
135 _: &VulkanStorage,
136 l1: &Layout,
137 _: &VulkanStorage,
138 _: &Layout,
139 ) -> Result<(VulkanStorage, Shape)> {
140 log_vulkan_custom_op_bail(self.name(), l1);
141 Err(crate::Error::Msg(format!(
142 "no vulkan implementation for {}",
143 self.name()
144 )))
145 }
146 #[cfg(feature = "wgpu")]
147 fn wgpu_fwd(
148 &self,
149 _: &WgpuStorage,
150 _l1: &Layout,
151 _: &WgpuStorage,
152 _: &Layout,
153 ) -> Result<(WgpuStorage, Shape)> {
154 Err(crate::Error::Msg(format!(
155 "no wgpu implementation for {}",
156 self.name()
157 )))
158 }
159
160 fn metal_fwd(
163 &self,
164 _: &MetalStorage,
165 _: &Layout,
166 _: &MetalStorage,
167 _: &Layout,
168 ) -> Result<(MetalStorage, Shape)> {
169 Err(crate::Error::Metal(
170 format!("no metal implementation for {}", self.name()).into(),
171 ))
172 }
173
174 fn bwd(
175 &self,
176 _arg1: &Tensor,
177 _arg2: &Tensor,
178 _res: &Tensor,
179 _grad_res: &Tensor,
180 ) -> Result<(Option<Tensor>, Option<Tensor>)> {
181 Err(crate::Error::BackwardNotSupported { op: self.name() })
182 }
183}
184
185pub trait CustomOp3 {
186 fn name(&self) -> &'static str;
187
188 fn cpu_fwd(
191 &self,
192 s1: &CpuStorage,
193 l1: &Layout,
194 s2: &CpuStorage,
195 l2: &Layout,
196 s3: &CpuStorage,
197 l3: &Layout,
198 ) -> Result<(CpuStorage, Shape)>;
199
200 fn cuda_fwd(
203 &self,
204 _: &CudaStorage,
205 _: &Layout,
206 _: &CudaStorage,
207 _: &Layout,
208 _: &CudaStorage,
209 _: &Layout,
210 ) -> Result<(CudaStorage, Shape)> {
211 Err(crate::Error::Cuda(
212 format!("no cuda implementation for {}", self.name()).into(),
213 ))
214 }
215
216 #[cfg(feature = "rocm")]
217 fn rocm_fwd(
218 &self,
219 _: &RocmStorage,
220 _: &Layout,
221 _: &RocmStorage,
222 _: &Layout,
223 _: &RocmStorage,
224 _: &Layout,
225 ) -> Result<(RocmStorage, Shape)> {
226 Err(crate::Error::Msg(format!(
227 "no rocm implementation for {}",
228 self.name()
229 )))
230 }
231 #[cfg(feature = "vulkan")]
232 fn vulkan_fwd(
233 &self,
234 _: &VulkanStorage,
235 l1: &Layout,
236 _: &VulkanStorage,
237 _: &Layout,
238 _: &VulkanStorage,
239 _: &Layout,
240 ) -> Result<(VulkanStorage, Shape)> {
241 log_vulkan_custom_op_bail(self.name(), l1);
242 Err(crate::Error::Msg(format!(
243 "no vulkan implementation for {}",
244 self.name()
245 )))
246 }
247 #[cfg(feature = "wgpu")]
248 fn wgpu_fwd(
249 &self,
250 _: &WgpuStorage,
251 _l1: &Layout,
252 _: &WgpuStorage,
253 _: &Layout,
254 _: &WgpuStorage,
255 _: &Layout,
256 ) -> Result<(WgpuStorage, Shape)> {
257 Err(crate::Error::Msg(format!(
258 "no wgpu implementation for {}",
259 self.name()
260 )))
261 }
262
263 fn metal_fwd(
266 &self,
267 _: &MetalStorage,
268 _: &Layout,
269 _: &MetalStorage,
270 _: &Layout,
271 _: &MetalStorage,
272 _: &Layout,
273 ) -> Result<(MetalStorage, Shape)> {
274 Err(crate::Error::Metal(
275 format!("no metal implementation for {}", self.name()).into(),
276 ))
277 }
278
279 fn bwd(
280 &self,
281 _arg1: &Tensor,
282 _arg2: &Tensor,
283 _arg3: &Tensor,
284 _res: &Tensor,
285 _grad_res: &Tensor,
286 ) -> Result<(Option<Tensor>, Option<Tensor>, Option<Tensor>)> {
287 Err(crate::Error::BackwardNotSupported { op: self.name() })
288 }
289}
290
291impl Tensor {
292 pub fn apply_op1_no_bwd<C: CustomOp1>(&self, c: &C) -> Result<Self> {
294 let (storage, shape) = self.storage().apply_op1(self.layout(), c)?;
295 Ok(from_storage(storage, shape, BackpropOp::none(), false))
296 }
297
298 pub fn apply_op2_no_bwd<C: CustomOp2>(&self, rhs: &Self, c: &C) -> Result<Self> {
300 let (storage, shape) =
301 self.storage()
302 .apply_op2(self.layout(), &rhs.storage(), rhs.layout(), c)?;
303 Ok(from_storage(storage, shape, BackpropOp::none(), false))
304 }
305
306 pub fn apply_op3_no_bwd<C: CustomOp3>(&self, t2: &Self, t3: &Self, c: &C) -> Result<Self> {
308 let (storage, shape) = self.storage().apply_op3(
309 self.layout(),
310 &t2.storage(),
311 t2.layout(),
312 &t3.storage(),
313 t3.layout(),
314 c,
315 )?;
316 Ok(from_storage(storage, shape, BackpropOp::none(), false))
317 }
318
319 pub fn apply_op1_arc(&self, c: Arc<Box<dyn CustomOp1 + Send + Sync>>) -> Result<Self> {
321 let (storage, shape) = self
322 .storage()
323 .apply_op1(self.layout(), c.as_ref().as_ref())?;
324 let op = BackpropOp::new1(self, |s| Op::CustomOp1(s, c.clone()));
325 Ok(from_storage(storage, shape, op, false))
326 }
327
328 pub fn apply_op1<C: 'static + CustomOp1 + Send + Sync>(&self, c: C) -> Result<Self> {
329 self.apply_op1_arc(Arc::new(Box::new(c)))
330 }
331
332 pub fn apply_op2_arc(
334 &self,
335 rhs: &Self,
336 c: Arc<Box<dyn CustomOp2 + Send + Sync>>,
337 ) -> Result<Self> {
338 let (storage, shape) = self.storage().apply_op2(
339 self.layout(),
340 &rhs.storage(),
341 rhs.layout(),
342 c.as_ref().as_ref(),
343 )?;
344 let op = BackpropOp::new2(self, rhs, |t1, t2| Op::CustomOp2(t1, t2, c.clone()));
345 Ok(from_storage(storage, shape, op, false))
346 }
347
348 pub fn apply_op2<C: 'static + CustomOp2 + Send + Sync>(&self, r: &Self, c: C) -> Result<Self> {
349 self.apply_op2_arc(r, Arc::new(Box::new(c)))
350 }
351
352 pub fn apply_op3_arc(
354 &self,
355 t2: &Self,
356 t3: &Self,
357 c: Arc<Box<dyn CustomOp3 + Send + Sync>>,
358 ) -> Result<Self> {
359 let (storage, shape) = self.storage().apply_op3(
360 self.layout(),
361 &t2.storage(),
362 t2.layout(),
363 &t3.storage(),
364 t3.layout(),
365 c.as_ref().as_ref(),
366 )?;
367 let op = BackpropOp::new3(self, t2, t3, |t1, t2, t3| {
368 Op::CustomOp3(t1, t2, t3, c.clone())
369 });
370 Ok(from_storage(storage, shape, op, false))
371 }
372
373 pub fn apply_op3<C: 'static + CustomOp3 + Send + Sync>(
374 &self,
375 t2: &Self,
376 t3: &Self,
377 c: C,
378 ) -> Result<Self> {
379 self.apply_op3_arc(t2, t3, Arc::new(Box::new(c)))
380 }
381}
382
383pub trait InplaceOp1 {
388 fn name(&self) -> &'static str;
390
391 fn cpu_fwd(&self, storage: &mut CpuStorage, layout: &Layout) -> Result<()>;
394
395 fn cuda_fwd(&self, _storage: &mut CudaStorage, _layout: &Layout) -> Result<()> {
398 Err(crate::Error::Cuda(
399 format!("no cuda implementation for {}", self.name()).into(),
400 ))
401 }
402
403 #[cfg(feature = "rocm")]
404 fn rocm_fwd(&self, _storage: &mut RocmStorage, _layout: &Layout) -> Result<()> {
405 Err(crate::Error::Msg(format!(
406 "no rocm implementation for {}",
407 self.name()
408 )))
409 }
410 #[cfg(feature = "vulkan")]
411 fn vulkan_fwd(&self, _storage: &mut VulkanStorage, _layout: &Layout) -> Result<()> {
412 log_vulkan_custom_op_bail(self.name(), _layout);
413 Err(crate::Error::Msg(format!(
414 "no vulkan implementation for {}",
415 self.name()
416 )))
417 }
418 #[cfg(feature = "wgpu")]
419 fn wgpu_fwd(&self, _storage: &mut WgpuStorage, _layout: &Layout) -> Result<()> {
420 Err(crate::Error::Msg(format!(
421 "no wgpu implementation for {}",
422 self.name()
423 )))
424 }
425
426 fn metal_fwd(&self, _storage: &mut MetalStorage, _layout: &Layout) -> Result<()> {
429 Err(crate::Error::Metal(
430 format!("no metal implementation for {}", self.name()).into(),
431 ))
432 }
433}
434
435pub trait InplaceOp2 {
436 fn name(&self) -> &'static str;
437
438 fn cpu_fwd(&self, s1: &mut CpuStorage, l1: &Layout, s2: &CpuStorage, l2: &Layout)
441 -> Result<()>;
442
443 fn cuda_fwd(&self, _: &mut CudaStorage, _: &Layout, _: &CudaStorage, _: &Layout) -> Result<()> {
446 Err(crate::Error::Cuda(
447 format!("no cuda implementation for {}", self.name()).into(),
448 ))
449 }
450
451 #[cfg(feature = "rocm")]
452 fn rocm_fwd(&self, _: &mut RocmStorage, _: &Layout, _: &RocmStorage, _: &Layout) -> Result<()> {
453 Err(crate::Error::Msg(format!(
454 "no rocm implementation for {}",
455 self.name()
456 )))
457 }
458 #[cfg(feature = "vulkan")]
459 fn vulkan_fwd(
460 &self,
461 _: &mut VulkanStorage,
462 l1: &Layout,
463 _: &VulkanStorage,
464 _: &Layout,
465 ) -> Result<()> {
466 log_vulkan_custom_op_bail(self.name(), l1);
467 Err(crate::Error::Msg(format!(
468 "no vulkan implementation for {}",
469 self.name()
470 )))
471 }
472 #[cfg(feature = "wgpu")]
473 fn wgpu_fwd(
474 &self,
475 _: &mut WgpuStorage,
476 _l1: &Layout,
477 _: &WgpuStorage,
478 _: &Layout,
479 ) -> Result<()> {
480 Err(crate::Error::Msg(format!(
481 "no wgpu implementation for {}",
482 self.name()
483 )))
484 }
485
486 fn metal_fwd(
489 &self,
490 _: &mut MetalStorage,
491 _: &Layout,
492 _: &MetalStorage,
493 _: &Layout,
494 ) -> Result<()> {
495 Err(crate::Error::Metal(
496 format!("no metal implementation for {}", self.name()).into(),
497 ))
498 }
499}
500
501pub trait InplaceOp3 {
502 fn name(&self) -> &'static str;
503
504 fn cpu_fwd(
507 &self,
508 s1: &mut CpuStorage,
509 l1: &Layout,
510 s2: &CpuStorage,
511 l2: &Layout,
512 s3: &CpuStorage,
513 l3: &Layout,
514 ) -> Result<()>;
515
516 fn cuda_fwd(
519 &self,
520 _: &mut CudaStorage,
521 _: &Layout,
522 _: &CudaStorage,
523 _: &Layout,
524 _: &CudaStorage,
525 _: &Layout,
526 ) -> Result<()> {
527 Err(crate::Error::Cuda(
528 format!("no cuda implementation for {}", self.name()).into(),
529 ))
530 }
531
532 #[cfg(feature = "rocm")]
533 fn rocm_fwd(
534 &self,
535 _: &mut RocmStorage,
536 _: &Layout,
537 _: &RocmStorage,
538 _: &Layout,
539 _: &RocmStorage,
540 _: &Layout,
541 ) -> Result<()> {
542 Err(crate::Error::Msg(format!(
543 "no rocm implementation for {}",
544 self.name()
545 )))
546 }
547 #[cfg(feature = "vulkan")]
548 fn vulkan_fwd(
549 &self,
550 _: &mut VulkanStorage,
551 l1: &Layout,
552 _: &VulkanStorage,
553 _: &Layout,
554 _: &VulkanStorage,
555 _: &Layout,
556 ) -> Result<()> {
557 log_vulkan_custom_op_bail(self.name(), l1);
558 Err(crate::Error::Msg(format!(
559 "no vulkan implementation for {}",
560 self.name()
561 )))
562 }
563 #[cfg(feature = "wgpu")]
564 fn wgpu_fwd(
565 &self,
566 _: &mut WgpuStorage,
567 _l1: &Layout,
568 _: &WgpuStorage,
569 _: &Layout,
570 _: &WgpuStorage,
571 _: &Layout,
572 ) -> Result<()> {
573 Err(crate::Error::Msg(format!(
574 "no wgpu implementation for {}",
575 self.name()
576 )))
577 }
578
579 fn metal_fwd(
582 &self,
583 _: &mut MetalStorage,
584 _: &Layout,
585 _: &MetalStorage,
586 _: &Layout,
587 _: &MetalStorage,
588 _: &Layout,
589 ) -> Result<()> {
590 Err(crate::Error::Metal(
591 format!("no metal implementation for {}", self.name()).into(),
592 ))
593 }
594}
595
596impl Tensor {
597 pub fn inplace_op1<C: InplaceOp1>(&self, c: &C) -> Result<()> {
599 self.storage_mut().inplace_op1(self.layout(), c)
600 }
601
602 pub fn inplace_op2<C: InplaceOp2>(&self, rhs: &Self, c: &C) -> Result<()> {
604 self.storage_mut()
605 .inplace_op2(self.layout(), &rhs.storage(), rhs.layout(), c)
606 }
607
608 pub fn inplace_op3<C: InplaceOp3>(&self, t2: &Self, t3: &Self, c: &C) -> Result<()> {
610 self.storage_mut().inplace_op3(
611 self.layout(),
612 &t2.storage(),
613 t2.layout(),
614 &t3.storage(),
615 t3.layout(),
616 c,
617 )
618 }
619}
620
621#[cfg(feature = "ug")]
622pub struct UgIOp1 {
623 name: &'static str,
624 #[cfg(feature = "cuda")]
625 func: cudarc::driver::CudaFunction,
626 #[cfg(feature = "metal")]
627 func: hanzo_metal_kernels::metal::ComputePipeline,
628}
629
630#[cfg(feature = "ug")]
631impl UgIOp1 {
632 #[allow(unused)]
633 #[cfg(all(not(target_arch = "wasm32"), not(target_os = "ios")))]
634 pub fn new(
635 name: &'static str,
636 kernel: hanzo_ug::lang::ssa::Kernel,
637 device: &crate::Device,
638 ) -> Result<Self> {
639 #[cfg(feature = "cuda")]
640 {
641 let device = device.as_cuda_device()?;
642 let func = device.compile(name, kernel)?;
643 Ok(Self {
644 name,
645 func: func.into_cuda_function(),
646 })
647 }
648 #[cfg(feature = "metal")]
649 {
650 let device = device.as_metal_device()?;
651 let func = device.compile(name, kernel)?;
652 Ok(Self { name, func })
653 }
654 #[cfg(not(any(feature = "cuda", feature = "metal")))]
655 {
656 Ok(Self { name })
657 }
658 }
659}
660
661#[cfg(feature = "ug")]
662impl InplaceOp1 for UgIOp1 {
663 fn name(&self) -> &'static str {
664 self.name
665 }
666
667 fn cpu_fwd(&self, _: &mut CpuStorage, _: &Layout) -> Result<()> {
668 crate::bail!("ug ops are only supported on metal/cuda at the moment")
669 }
670
671 #[cfg(feature = "metal")]
672 fn metal_fwd(&self, sto: &mut MetalStorage, layout: &Layout) -> Result<()> {
673 use crate::backend::BackendStorage;
674 use objc2_metal;
675
676 let elem_count = layout.shape().elem_count();
677 if sto.dtype() != crate::DType::F32 {
678 crate::bail!("input is not a f32 tensor")
680 }
681 let device = sto.device();
682 let encoder = device.command_encoder()?;
683 encoder.set_compute_pipeline_state(&self.func);
684 let (g, b) = if elem_count.is_multiple_of(32) {
685 (elem_count / 32, 32)
686 } else {
687 (elem_count, 1)
688 };
689 let grid_dims = objc2_metal::MTLSize {
690 width: g,
691 height: 1,
692 depth: 1,
693 };
694 let group_dims = hanzo_metal_kernels::utils::get_block_dims(b, 1, 1);
695 encoder.set_output_buffer(0, Some(sto.buffer()), 0);
696 encoder.dispatch_threads(grid_dims, group_dims);
697
698 Ok(())
699 }
700
701 #[cfg(feature = "cuda")]
702 fn cuda_fwd(&self, sto: &mut CudaStorage, layout: &Layout) -> Result<()> {
703 use crate::cuda_backend::WrapErr;
704 use cudarc::driver::PushKernelArg;
705
706 let elem_count = layout.shape().elem_count();
707 let stream = sto.device.cuda_stream();
708 let sto = sto.as_cuda_slice::<f32>()?;
710 let sto = match layout.contiguous_offsets() {
711 None => crate::bail!("input has to be contiguous"),
712 Some((o1, o2)) => sto.slice(o1..o2),
713 };
714 let (g, b) = if elem_count % 32 == 0 {
715 (elem_count / 32, 32)
716 } else {
717 (elem_count, 1)
718 };
719 let cfg = cudarc::driver::LaunchConfig {
720 grid_dim: (g as u32, 1, 1),
721 block_dim: (b as u32, 1, 1),
722 shared_mem_bytes: 0,
723 };
724 let mut builder = stream.launch_builder(&self.func);
725 builder.arg(&sto);
726 unsafe { builder.launch(cfg) }.w()?;
727 Ok(())
728 }
729}