1use crate::config::{
2 CompareDir, DotGeneralConfig, GatherConfig, PadConfig, ScatterConfig, SliceConfig,
3};
4use crate::types::{
5 Buffer, TensorRank, TensorView, TensorViewMut, TypedTensor, TypedTensorView, TypedTensorViewMut,
6};
7use crate::validate::validate_convert_dtype;
8use crate::{DType, RuntimeCacheControl, Tensor, TensorRead, TensorValue, TensorWrite};
9use num_complex::{Complex32, Complex64};
10
11fn read_boundary_error(op: &'static str) -> crate::Error {
12 crate::Error::backend_failure(
13 op,
14 "backend does not accept borrowed tensor views at this execution boundary",
15 )
16}
17
18fn read_tensor<'a>(op: &'static str, input: TensorRead<'a>) -> crate::Result<&'a Tensor> {
19 input.as_tensor().ok_or_else(|| read_boundary_error(op))
20}
21
22fn validate_axis_list(
23 op: &'static str,
24 role: &'static str,
25 axes: &[usize],
26 rank: usize,
27) -> crate::Result<()> {
28 let mut seen = vec![false; rank];
29 for &axis in axes {
30 if axis >= rank {
31 return Err(crate::Error::AxisOutOfBounds { op, axis, rank });
32 }
33 if seen[axis] {
34 return Err(crate::Error::DuplicateAxis { op, axis, role });
35 }
36 seen[axis] = true;
37 }
38 Ok(())
39}
40
41fn validate_role_disjoint(
42 op: &'static str,
43 first_role: &'static str,
44 first_axes: &[usize],
45 second_role: &'static str,
46 second_axes: &[usize],
47) -> crate::Result<()> {
48 for &axis in first_axes {
49 if second_axes.contains(&axis) {
50 return Err(crate::Error::AxisRoleConflict {
51 op,
52 axis,
53 first_role,
54 second_role,
55 });
56 }
57 }
58 Ok(())
59}
60
61#[doc(hidden)]
63pub fn dot_general_output_shape(
64 lhs_shape: &[usize],
65 rhs_shape: &[usize],
66 config: &DotGeneralConfig,
67 op: &'static str,
68) -> crate::Result<Vec<usize>> {
69 if config.lhs_contracting_dims.len() != config.rhs_contracting_dims.len() {
70 return Err(crate::Error::InvalidConfig {
71 op,
72 message: "lhs/rhs contracting dim counts differ".into(),
73 });
74 }
75 if config.lhs_batch_dims.len() != config.rhs_batch_dims.len() {
76 return Err(crate::Error::InvalidConfig {
77 op,
78 message: "lhs/rhs batch dim counts differ".into(),
79 });
80 }
81
82 let lhs_rank = lhs_shape.len();
83 let rhs_rank = rhs_shape.len();
84 validate_axis_list(
85 op,
86 "lhs_contracting",
87 &config.lhs_contracting_dims,
88 lhs_rank,
89 )?;
90 validate_axis_list(
91 op,
92 "rhs_contracting",
93 &config.rhs_contracting_dims,
94 rhs_rank,
95 )?;
96 validate_axis_list(op, "lhs_batch", &config.lhs_batch_dims, lhs_rank)?;
97 validate_axis_list(op, "rhs_batch", &config.rhs_batch_dims, rhs_rank)?;
98 validate_role_disjoint(
99 op,
100 "lhs_contracting",
101 &config.lhs_contracting_dims,
102 "lhs_batch",
103 &config.lhs_batch_dims,
104 )?;
105 validate_role_disjoint(
106 op,
107 "rhs_contracting",
108 &config.rhs_contracting_dims,
109 "rhs_batch",
110 &config.rhs_batch_dims,
111 )?;
112
113 for (&lhs_axis, &rhs_axis) in config
114 .lhs_contracting_dims
115 .iter()
116 .zip(&config.rhs_contracting_dims)
117 {
118 if lhs_shape[lhs_axis] != rhs_shape[rhs_axis] {
119 return Err(crate::Error::ShapeMismatch {
120 op,
121 lhs: lhs_shape.to_vec(),
122 rhs: rhs_shape.to_vec(),
123 });
124 }
125 }
126 for (&lhs_axis, &rhs_axis) in config.lhs_batch_dims.iter().zip(&config.rhs_batch_dims) {
127 if lhs_shape[lhs_axis] != rhs_shape[rhs_axis] {
128 return Err(crate::Error::ShapeMismatch {
129 op,
130 lhs: lhs_shape.to_vec(),
131 rhs: rhs_shape.to_vec(),
132 });
133 }
134 }
135
136 let lhs_free = (0..lhs_rank)
137 .filter(|axis| {
138 !config.lhs_contracting_dims.contains(axis) && !config.lhs_batch_dims.contains(axis)
139 })
140 .map(|axis| lhs_shape[axis]);
141 let rhs_free = (0..rhs_rank)
142 .filter(|axis| {
143 !config.rhs_contracting_dims.contains(axis) && !config.rhs_batch_dims.contains(axis)
144 })
145 .map(|axis| rhs_shape[axis]);
146 let batch = config.lhs_batch_dims.iter().map(|&axis| lhs_shape[axis]);
147
148 Ok(lhs_free.chain(rhs_free).chain(batch).collect())
149}
150
151#[doc(hidden)]
153pub fn validate_dot_general_read_into(
154 lhs: &TensorRead<'_>,
155 rhs: &TensorRead<'_>,
156 config: &DotGeneralConfig,
157 out: &TensorWrite<'_>,
158 op: &'static str,
159) -> crate::Result<Vec<usize>> {
160 if lhs.dtype() != rhs.dtype() {
161 return Err(crate::Error::DTypeMismatch {
162 op,
163 lhs: lhs.dtype(),
164 rhs: rhs.dtype(),
165 });
166 }
167 if out.dtype() != lhs.dtype() {
168 return Err(crate::Error::DTypeMismatch {
169 op,
170 lhs: out.dtype(),
171 rhs: lhs.dtype(),
172 });
173 }
174 let expected = dot_general_output_shape(lhs.shape(), rhs.shape(), config, op)?;
175 if out.shape() != expected.as_slice() {
176 return Err(crate::Error::ShapeMismatch {
177 op,
178 lhs: out.shape().to_vec(),
179 rhs: expected.clone(),
180 });
181 }
182 Ok(expected)
183}
184
185#[derive(Clone, Copy, Debug, PartialEq)]
200pub enum ContractionScalar {
201 F32(f32),
202 F64(f64),
203 C32(Complex32),
204 C64(Complex64),
205}
206
207impl ContractionScalar {
208 pub fn dtype(self) -> DType {
218 match self {
219 Self::F32(_) => DType::F32,
220 Self::F64(_) => DType::F64,
221 Self::C32(_) => DType::C32,
222 Self::C64(_) => DType::C64,
223 }
224 }
225
226 pub fn one(dtype: DType) -> crate::Result<Self> {
237 match dtype {
238 DType::F32 => Ok(Self::F32(1.0)),
239 DType::F64 => Ok(Self::F64(1.0)),
240 DType::C32 => Ok(Self::C32(Complex32::new(1.0, 0.0))),
241 DType::C64 => Ok(Self::C64(Complex64::new(1.0, 0.0))),
242 DType::I32 | DType::I64 | DType::Bool => Err(crate::Error::DTypeMismatch {
243 op: "dot_general",
244 lhs: dtype,
245 rhs: DType::F32,
246 }),
247 }
248 }
249
250 pub fn zero(dtype: DType) -> crate::Result<Self> {
260 match dtype {
261 DType::F32 => Ok(Self::F32(0.0)),
262 DType::F64 => Ok(Self::F64(0.0)),
263 DType::C32 => Ok(Self::C32(Complex32::new(0.0, 0.0))),
264 DType::C64 => Ok(Self::C64(Complex64::new(0.0, 0.0))),
265 DType::I32 | DType::I64 | DType::Bool => Err(crate::Error::DTypeMismatch {
266 op: "dot_general",
267 lhs: dtype,
268 rhs: DType::F32,
269 }),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq)]
290pub struct DotGeneralAccumulation {
291 pub lhs_conj: bool,
292 pub rhs_conj: bool,
293 pub alpha: ContractionScalar,
294 pub beta: ContractionScalar,
295}
296
297#[doc(hidden)]
304#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
305pub struct GroupedGemmJob {
306 out_offset: usize,
307 lhs_offset: usize,
308 rhs_offset: usize,
309 rows: usize,
310 contracted: usize,
311 cols: usize,
312}
313
314impl GroupedGemmJob {
315 #[allow(clippy::too_many_arguments)]
316 pub fn new(
317 out_offset: usize,
318 lhs_offset: usize,
319 rhs_offset: usize,
320 rows: usize,
321 contracted: usize,
322 cols: usize,
323 ) -> Self {
324 Self {
325 out_offset,
326 lhs_offset,
327 rhs_offset,
328 rows,
329 contracted,
330 cols,
331 }
332 }
333
334 pub fn out_offset(&self) -> usize {
335 self.out_offset
336 }
337
338 pub fn lhs_offset(&self) -> usize {
339 self.lhs_offset
340 }
341
342 pub fn rhs_offset(&self) -> usize {
343 self.rhs_offset
344 }
345
346 pub fn rows(&self) -> usize {
347 self.rows
348 }
349
350 pub fn contracted(&self) -> usize {
351 self.contracted
352 }
353
354 pub fn cols(&self) -> usize {
355 self.cols
356 }
357}
358
359#[doc(hidden)]
361#[derive(Clone, Copy, Debug, PartialEq)]
362pub struct GroupedGemmConfig<'a> {
363 jobs: &'a [GroupedGemmJob],
364 accumulation: DotGeneralAccumulation,
365}
366
367impl<'a> GroupedGemmConfig<'a> {
368 pub fn new(jobs: &'a [GroupedGemmJob], accumulation: DotGeneralAccumulation) -> Self {
369 Self { jobs, accumulation }
370 }
371
372 pub fn jobs(&self) -> &'a [GroupedGemmJob] {
373 self.jobs
374 }
375
376 pub fn accumulation(&self) -> DotGeneralAccumulation {
377 self.accumulation
378 }
379}
380
381impl DotGeneralAccumulation {
382 pub fn overwrite(dtype: DType) -> crate::Result<Self> {
384 Ok(Self {
385 lhs_conj: false,
386 rhs_conj: false,
387 alpha: ContractionScalar::one(dtype)?,
388 beta: ContractionScalar::zero(dtype)?,
389 })
390 }
391
392 pub fn add_to(dtype: DType) -> crate::Result<Self> {
405 Ok(Self {
406 lhs_conj: false,
407 rhs_conj: false,
408 alpha: ContractionScalar::one(dtype)?,
409 beta: ContractionScalar::one(dtype)?,
410 })
411 }
412
413 pub fn scaled(alpha: ContractionScalar, beta: ContractionScalar) -> crate::Result<Self> {
428 if alpha.dtype() != beta.dtype() {
429 return Err(crate::Error::DTypeMismatch {
430 op: "dot_general",
431 lhs: alpha.dtype(),
432 rhs: beta.dtype(),
433 });
434 }
435 Ok(Self {
436 lhs_conj: false,
437 rhs_conj: false,
438 alpha,
439 beta,
440 })
441 }
442
443 fn validate_for_dtype(self, dtype: DType) -> crate::Result<()> {
444 for scalar in [self.alpha, self.beta] {
445 if scalar.dtype() != dtype {
446 return Err(crate::Error::DTypeMismatch {
447 op: "dot_general",
448 lhs: scalar.dtype(),
449 rhs: dtype,
450 });
451 }
452 }
453 Ok(())
454 }
455}
456
457#[doc(hidden)]
458pub fn validate_dot_general_accumulation(
459 lhs: &TensorRead<'_>,
460 rhs: &TensorRead<'_>,
461 config: &DotGeneralConfig,
462 accumulation: DotGeneralAccumulation,
463 out: &TensorWrite<'_>,
464 op: &'static str,
465) -> crate::Result<Vec<usize>> {
466 let shape = validate_dot_general_read_into(lhs, rhs, config, out, op)?;
467 accumulation.validate_for_dtype(lhs.dtype())?;
468 Ok(shape)
469}
470
471#[doc(hidden)]
472pub fn dot_general_accum_via_temp<B: TensorDot + ?Sized>(
473 backend: &mut B,
474 lhs: TensorRead<'_>,
475 rhs: TensorRead<'_>,
476 config: &DotGeneralConfig,
477 accumulation: DotGeneralAccumulation,
478 mut out: TensorWrite<'_>,
479) -> crate::Result<()> {
480 validate_dot_general_accumulation(&lhs, &rhs, config, accumulation, &out, "dot_general")?;
481 let dot = backend.dot_general_with_conj_read(
482 lhs,
483 rhs,
484 config,
485 accumulation.lhs_conj,
486 accumulation.rhs_conj,
487 )?;
488 accumulate_dot_result_into(&dot, accumulation, &mut out)
489}
490
491fn grouped_checked_product(
492 op: &'static str,
493 role: &'static str,
494 dims: &[usize],
495) -> crate::Result<usize> {
496 dims.iter().try_fold(1usize, |acc, &dim| {
497 acc.checked_mul(dim)
498 .ok_or_else(|| crate::Error::InvalidConfig {
499 op,
500 message: format!("{role} logical element count overflows usize for shape {dims:?}"),
501 })
502 })
503}
504
505fn checked_gemm_span(
506 op: &'static str,
507 role: &'static str,
508 offset: usize,
509 rows: usize,
510 cols: usize,
511) -> crate::Result<Option<std::ops::Range<usize>>> {
512 let len = rows
513 .checked_mul(cols)
514 .ok_or_else(|| crate::Error::InvalidConfig {
515 op,
516 message: format!(
517 "{role} matrix element count overflows usize: rows={rows} cols={cols}"
518 ),
519 })?;
520 if len == 0 {
521 return Ok(None);
522 }
523 let end = offset
524 .checked_add(len)
525 .ok_or_else(|| crate::Error::InvalidConfig {
526 op,
527 message: format!("{role} matrix range overflows usize: offset={offset} len={len}"),
528 })?;
529 Ok(Some(offset..end))
530}
531
532fn validate_grouped_gemm_range(
533 op: &'static str,
534 role: &'static str,
535 len: usize,
536 range: Option<std::ops::Range<usize>>,
537) -> crate::Result<()> {
538 let Some(range) = range else {
539 return Ok(());
540 };
541 if range.end > len {
542 return Err(crate::Error::InvalidConfig {
543 op,
544 message: format!(
545 "{role} matrix range {}..{} exceeds shared buffer logical length {len}",
546 range.start, range.end
547 ),
548 });
549 }
550 Ok(())
551}
552
553#[doc(hidden)]
554pub fn validate_grouped_gemm(
555 lhs: &TensorRead<'_>,
556 rhs: &TensorRead<'_>,
557 out: &TensorWrite<'_>,
558 config: &GroupedGemmConfig<'_>,
559 op: &'static str,
560) -> crate::Result<()> {
561 if lhs.dtype() != rhs.dtype() {
562 return Err(crate::Error::DTypeMismatch {
563 op,
564 lhs: lhs.dtype(),
565 rhs: rhs.dtype(),
566 });
567 }
568 if lhs.dtype() != out.dtype() {
569 return Err(crate::Error::DTypeMismatch {
570 op,
571 lhs: lhs.dtype(),
572 rhs: out.dtype(),
573 });
574 }
575 config.accumulation.validate_for_dtype(lhs.dtype())?;
576
577 let lhs_len = grouped_checked_product(op, "lhs", lhs.shape())?;
578 let rhs_len = grouped_checked_product(op, "rhs", rhs.shape())?;
579 let out_len = grouped_checked_product(op, "out", out.shape())?;
580 let mut out_ranges = Vec::<(usize, std::ops::Range<usize>)>::with_capacity(config.jobs.len());
585 for (idx, job) in config.jobs.iter().enumerate() {
586 validate_grouped_gemm_range(
587 op,
588 "lhs",
589 lhs_len,
590 checked_gemm_span(op, "lhs", job.lhs_offset, job.rows, job.contracted)?,
591 )?;
592 validate_grouped_gemm_range(
593 op,
594 "rhs",
595 rhs_len,
596 checked_gemm_span(op, "rhs", job.rhs_offset, job.contracted, job.cols)?,
597 )?;
598 let out_range = checked_gemm_span(op, "out", job.out_offset, job.rows, job.cols)?;
599 validate_grouped_gemm_range(op, "out", out_len, out_range.clone())?;
600 if let Some(out_range) = out_range {
601 out_ranges.push((idx, out_range));
602 }
603 }
604 out_ranges.sort_unstable_by_key(|(_, range)| range.start);
605 for pair in out_ranges.windows(2) {
606 let (prev_idx, previous) = &pair[0];
607 let (idx, current) = &pair[1];
608 if previous.end > current.start {
609 return Err(crate::Error::InvalidConfig {
610 op,
611 message: format!(
612 "grouped GEMM output range for job {idx} overlaps job {prev_idx} range {}..{}",
613 previous.start, previous.end
614 ),
615 });
616 }
617 }
618 Ok(())
619}
620
621fn add_element_offsets(
622 op: &'static str,
623 base: isize,
624 offset: usize,
625 role: &'static str,
626) -> crate::Result<isize> {
627 let offset = isize::try_from(offset).map_err(|_| crate::Error::InvalidConfig {
628 op,
629 message: format!("{role} offset {offset} does not fit in isize"),
630 })?;
631 base.checked_add(offset)
632 .ok_or_else(|| crate::Error::InvalidConfig {
633 op,
634 message: format!("{role} offset overflows isize: base={base} offset={offset}"),
635 })
636}
637
638fn dim_stride(op: &'static str, dim: usize, role: &'static str) -> crate::Result<isize> {
639 isize::try_from(dim).map_err(|_| crate::Error::InvalidConfig {
640 op,
641 message: format!("{role} leading dimension {dim} does not fit in isize"),
642 })
643}
644
645fn typed_read_storage<'a, T>(
646 tensor: &'a TypedTensor<T>,
647 op: &'static str,
648) -> crate::Result<(&'a [T], isize)> {
649 match tensor.buffer() {
650 Buffer::Host(data) => Ok((data, 0)),
651 Buffer::Backend(_) => Err(crate::Error::backend_failure(
652 op,
653 "grouped GEMM default path requires host-backed tensor storage",
654 )),
655 }
656}
657
658fn grouped_gemm_default_config() -> DotGeneralConfig {
659 DotGeneralConfig {
662 lhs_contracting_dims: vec![1],
663 rhs_contracting_dims: vec![0],
664 lhs_batch_dims: Vec::new(),
665 rhs_batch_dims: Vec::new(),
666 }
667}
668
669trait GroupedGemmDType<T> {
670 fn wrap_read(view: TypedTensorView<'_, T>) -> TensorView<'_>;
671 fn wrap_write(view: TypedTensorViewMut<'_, T>) -> TensorViewMut<'_>;
672}
673
674struct GroupedF32;
675struct GroupedF64;
676struct GroupedC32;
677struct GroupedC64;
678
679impl GroupedGemmDType<f32> for GroupedF32 {
680 fn wrap_read(view: TypedTensorView<'_, f32>) -> TensorView<'_> {
681 TensorView::F32(view)
682 }
683
684 fn wrap_write(view: TypedTensorViewMut<'_, f32>) -> TensorViewMut<'_> {
685 TensorViewMut::F32(view)
686 }
687}
688
689impl GroupedGemmDType<f64> for GroupedF64 {
690 fn wrap_read(view: TypedTensorView<'_, f64>) -> TensorView<'_> {
691 TensorView::F64(view)
692 }
693
694 fn wrap_write(view: TypedTensorViewMut<'_, f64>) -> TensorViewMut<'_> {
695 TensorViewMut::F64(view)
696 }
697}
698
699impl GroupedGemmDType<Complex32> for GroupedC32 {
700 fn wrap_read(view: TypedTensorView<'_, Complex32>) -> TensorView<'_> {
701 TensorView::C32(view)
702 }
703
704 fn wrap_write(view: TypedTensorViewMut<'_, Complex32>) -> TensorViewMut<'_> {
705 TensorViewMut::C32(view)
706 }
707}
708
709impl GroupedGemmDType<Complex64> for GroupedC64 {
710 fn wrap_read(view: TypedTensorView<'_, Complex64>) -> TensorView<'_> {
711 TensorView::C64(view)
712 }
713
714 fn wrap_write(view: TypedTensorViewMut<'_, Complex64>) -> TensorViewMut<'_> {
715 TensorViewMut::C64(view)
716 }
717}
718
719#[allow(clippy::too_many_arguments)]
720fn grouped_gemm_default_loop<B, T, V>(
721 backend: &mut B,
722 lhs_data: &[T],
723 lhs_base: isize,
724 rhs_data: &[T],
725 rhs_base: isize,
726 out_view: &mut TypedTensorViewMut<'_, T>,
727 config: &GroupedGemmConfig<'_>,
728) -> crate::Result<()>
729where
730 B: TensorDot + ?Sized,
731 T: 'static,
732 V: GroupedGemmDType<T>,
733{
734 let op = "grouped_gemm";
735 let dot_config = grouped_gemm_default_config();
736 for job in config.jobs {
737 let lhs_offset = add_element_offsets(op, lhs_base, job.lhs_offset, "lhs")?;
738 let rhs_offset = add_element_offsets(op, rhs_base, job.rhs_offset, "rhs")?;
739 let out_offset = add_element_offsets(op, out_view.offset(), job.out_offset, "out")?;
740 let lhs_rows = dim_stride(op, job.rows, "lhs")?;
741 let rhs_rows = dim_stride(op, job.contracted, "rhs")?;
742 let out_rows = dim_stride(op, job.rows, "out")?;
743 let lhs_matrix = TypedTensorView::from_slice(
747 vec![job.rows, job.contracted],
748 vec![1, lhs_rows],
749 lhs_offset,
750 lhs_data,
751 )?;
752 let rhs_matrix = TypedTensorView::from_slice(
753 vec![job.contracted, job.cols],
754 vec![1, rhs_rows],
755 rhs_offset,
756 rhs_data,
757 )?;
758 let out_storage = out_view.host_storage_mut()?;
759 let out_matrix = TypedTensorViewMut::from_slice(
760 vec![job.rows, job.cols],
761 vec![1, out_rows],
762 out_offset,
763 out_storage,
764 )?;
765 backend.dot_general_read_into_accum(
766 TensorRead::from_view(V::wrap_read(lhs_matrix)),
767 TensorRead::from_view(V::wrap_read(rhs_matrix)),
768 &dot_config,
769 config.accumulation,
770 TensorWrite::from_view(V::wrap_write(out_matrix)),
771 )?;
772 }
773 Ok(())
774}
775
776#[doc(hidden)]
777pub fn grouped_gemm_via_sequential<B>(
778 backend: &mut B,
779 lhs: TensorRead<'_>,
780 rhs: TensorRead<'_>,
781 config: &GroupedGemmConfig<'_>,
782 mut out: TensorWrite<'_>,
783) -> crate::Result<()>
784where
785 B: TensorDot + ?Sized,
786{
787 validate_grouped_gemm(&lhs, &rhs, &out, config, "grouped_gemm")?;
788 macro_rules! dispatch {
789 ($variant:ident, $wrapper:ty) => {
790 match (&lhs, &rhs, &mut out) {
791 (
792 TensorRead::Tensor(Tensor::$variant(a)),
793 TensorRead::Tensor(Tensor::$variant(b)),
794 TensorWrite::Tensor(Tensor::$variant(c)),
795 ) => {
796 let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
797 let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
798 let mut c_view = c.as_view_mut();
799 return grouped_gemm_default_loop::<_, _, $wrapper>(
800 backend,
801 a_data,
802 a_base,
803 b_data,
804 b_base,
805 &mut c_view,
806 config,
807 );
808 }
809 (
810 TensorRead::Tensor(Tensor::$variant(a)),
811 TensorRead::View(TensorView::$variant(b)),
812 TensorWrite::Tensor(Tensor::$variant(c)),
813 ) => {
814 let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
815 let mut c_view = c.as_view_mut();
816 return grouped_gemm_default_loop::<_, _, $wrapper>(
817 backend,
818 a_data,
819 a_base,
820 b.host_storage()?,
821 b.offset(),
822 &mut c_view,
823 config,
824 );
825 }
826 (
827 TensorRead::View(TensorView::$variant(a)),
828 TensorRead::Tensor(Tensor::$variant(b)),
829 TensorWrite::Tensor(Tensor::$variant(c)),
830 ) => {
831 let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
832 let mut c_view = c.as_view_mut();
833 return grouped_gemm_default_loop::<_, _, $wrapper>(
834 backend,
835 a.host_storage()?,
836 a.offset(),
837 b_data,
838 b_base,
839 &mut c_view,
840 config,
841 );
842 }
843 (
844 TensorRead::View(TensorView::$variant(a)),
845 TensorRead::View(TensorView::$variant(b)),
846 TensorWrite::Tensor(Tensor::$variant(c)),
847 ) => {
848 let mut c_view = c.as_view_mut();
849 return grouped_gemm_default_loop::<_, _, $wrapper>(
850 backend,
851 a.host_storage()?,
852 a.offset(),
853 b.host_storage()?,
854 b.offset(),
855 &mut c_view,
856 config,
857 );
858 }
859 (
860 TensorRead::Tensor(Tensor::$variant(a)),
861 TensorRead::Tensor(Tensor::$variant(b)),
862 TensorWrite::View(TensorViewMut::$variant(c)),
863 ) => {
864 let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
865 let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
866 return grouped_gemm_default_loop::<_, _, $wrapper>(
867 backend, a_data, a_base, b_data, b_base, c, config,
868 );
869 }
870 (
871 TensorRead::Tensor(Tensor::$variant(a)),
872 TensorRead::View(TensorView::$variant(b)),
873 TensorWrite::View(TensorViewMut::$variant(c)),
874 ) => {
875 let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
876 return grouped_gemm_default_loop::<_, _, $wrapper>(
877 backend,
878 a_data,
879 a_base,
880 b.host_storage()?,
881 b.offset(),
882 c,
883 config,
884 );
885 }
886 (
887 TensorRead::View(TensorView::$variant(a)),
888 TensorRead::Tensor(Tensor::$variant(b)),
889 TensorWrite::View(TensorViewMut::$variant(c)),
890 ) => {
891 let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
892 return grouped_gemm_default_loop::<_, _, $wrapper>(
893 backend,
894 a.host_storage()?,
895 a.offset(),
896 b_data,
897 b_base,
898 c,
899 config,
900 );
901 }
902 (
903 TensorRead::View(TensorView::$variant(a)),
904 TensorRead::View(TensorView::$variant(b)),
905 TensorWrite::View(TensorViewMut::$variant(c)),
906 ) => {
907 return grouped_gemm_default_loop::<_, _, $wrapper>(
908 backend,
909 a.host_storage()?,
910 a.offset(),
911 b.host_storage()?,
912 b.offset(),
913 c,
914 config,
915 );
916 }
917 _ => {}
918 }
919 };
920 }
921
922 dispatch!(F32, GroupedF32);
923 dispatch!(F64, GroupedF64);
924 dispatch!(C32, GroupedC32);
925 dispatch!(C64, GroupedC64);
926 Err(crate::Error::DTypeMismatch {
927 op: "grouped_gemm",
928 lhs: lhs.dtype(),
929 rhs: out.dtype(),
930 })
931}
932
933fn grouped_gemm_default<B>(
934 backend: &mut B,
935 lhs: TensorRead<'_>,
936 rhs: TensorRead<'_>,
937 config: &GroupedGemmConfig<'_>,
938 out: TensorWrite<'_>,
939) -> crate::Result<()>
940where
941 B: TensorDot + ?Sized,
942{
943 grouped_gemm_via_sequential(backend, lhs, rhs, config, out)
944}
945
946#[doc(hidden)]
947pub fn accumulate_dot_result_into(
948 dot: &Tensor,
949 accumulation: DotGeneralAccumulation,
950 out: &mut TensorWrite<'_>,
951) -> crate::Result<()> {
952 macro_rules! dispatch {
953 ($variant:ident, $ty:ty) => {
954 if let (
955 Tensor::$variant(dot),
956 ContractionScalar::$variant(alpha),
957 ContractionScalar::$variant(beta),
958 ) = (dot, accumulation.alpha, accumulation.beta)
959 {
960 match out {
961 TensorWrite::Tensor(Tensor::$variant(out)) => {
962 let mut out = out.as_view_mut();
963 accumulate_typed(dot.as_slice()?, alpha, beta, &mut out)?;
964 return Ok(());
965 }
966 TensorWrite::View(crate::TensorViewMut::$variant(out)) => {
967 accumulate_typed(dot.as_slice()?, alpha, beta, out)?;
968 return Ok(());
969 }
970 _ => {}
971 }
972 }
973 };
974 }
975
976 dispatch!(F32, f32);
977 dispatch!(F64, f64);
978 dispatch!(C32, Complex32);
979 dispatch!(C64, Complex64);
980
981 Err(crate::Error::DTypeMismatch {
982 op: "dot_general",
983 lhs: accumulation.alpha.dtype(),
984 rhs: dot.dtype(),
985 })
986}
987
988fn accumulate_typed<T>(
989 dot: &[T],
990 alpha: T,
991 beta: T,
992 out: &mut TypedTensorViewMut<'_, T>,
993) -> crate::Result<()>
994where
995 T: Copy
996 + PartialEq
997 + std::ops::Add<Output = T>
998 + std::ops::Mul<Output = T>
999 + num_traits::Zero
1000 + 'static,
1001{
1002 let beta_is_zero = beta == T::zero();
1003 for (linear, dot_value) in dot.iter().copied().enumerate() {
1004 let indices = flat_to_multi_for_shape(out.shape(), linear);
1005 let output = out
1006 .get_mut(&indices)
1007 .ok_or_else(|| crate::Error::InvalidConfig {
1008 op: "dot_general",
1009 message: format!("output index {indices:?} is outside accumulation target"),
1010 })?;
1011 *output = if beta_is_zero {
1015 alpha * dot_value
1016 } else {
1017 alpha * dot_value + beta * *output
1018 };
1019 }
1020 Ok(())
1021}
1022
1023fn flat_to_multi_for_shape(shape: &[usize], mut linear: usize) -> Vec<usize> {
1024 let mut indices = Vec::with_capacity(shape.len());
1025 for &dim in shape {
1026 if dim == 0 {
1027 indices.push(0);
1028 } else {
1029 indices.push(linear % dim);
1030 linear /= dim;
1031 }
1032 }
1033 indices
1034}
1035
1036#[doc(hidden)]
1038#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1039pub struct ElementwiseFusionPlan {
1040 dtype: crate::DType,
1041 input_count: usize,
1042 input_views: Vec<ElementwiseFusionInputView>,
1045 outputs: Vec<usize>,
1046 ops: Vec<ElementwiseFusionInst>,
1047}
1048
1049#[doc(hidden)]
1051#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1052pub enum ElementwiseFusionInputView {
1053 Identity,
1054 BroadcastInDim {
1055 shape: Vec<usize>,
1057 dims: Vec<usize>,
1058 },
1059}
1060
1061#[doc(hidden)]
1063#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1064pub struct ElementwiseFusionInst {
1065 op: ElementwiseFusionOp,
1066 inputs: Vec<usize>,
1067}
1068
1069tenferro_core_ops::define_elementwise_fusion_op!();
1070
1071impl ElementwiseFusionPlan {
1072 pub fn new(
1091 dtype: crate::DType,
1092 input_count: usize,
1093 outputs: Vec<usize>,
1094 ops: Vec<ElementwiseFusionInst>,
1095 ) -> Self {
1096 Self::with_input_views(
1097 dtype,
1098 vec![ElementwiseFusionInputView::Identity; input_count],
1099 outputs,
1100 ops,
1101 )
1102 }
1103
1104 pub fn with_input_views(
1124 dtype: crate::DType,
1125 input_views: impl IntoIterator<Item = ElementwiseFusionInputView>,
1126 outputs: Vec<usize>,
1127 ops: Vec<ElementwiseFusionInst>,
1128 ) -> Self {
1129 let input_views = input_views.into_iter().collect::<Vec<_>>();
1130 let input_count = input_views.len();
1131 Self {
1132 dtype,
1133 input_count,
1134 input_views,
1135 outputs,
1136 ops,
1137 }
1138 }
1139
1140 pub fn dtype(&self) -> crate::DType {
1152 self.dtype
1153 }
1154
1155 pub fn input_count(&self) -> usize {
1167 self.input_count
1168 }
1169
1170 pub fn input_views(&self) -> &[ElementwiseFusionInputView] {
1182 &self.input_views
1183 }
1184
1185 pub fn outputs(&self) -> &[usize] {
1197 &self.outputs
1198 }
1199
1200 pub fn ops(&self) -> &[ElementwiseFusionInst] {
1215 &self.ops
1216 }
1217}
1218
1219impl ElementwiseFusionInputView {
1220 pub fn broadcast_in_dim(
1231 shape: impl IntoIterator<Item = usize>,
1232 dims: impl IntoIterator<Item = usize>,
1233 ) -> Self {
1234 Self::BroadcastInDim {
1235 shape: shape.into_iter().collect(),
1236 dims: dims.into_iter().collect(),
1237 }
1238 }
1239
1240 pub fn is_identity(&self) -> bool {
1250 matches!(self, Self::Identity)
1251 }
1252}
1253
1254impl ElementwiseFusionInst {
1255 pub fn new(op: ElementwiseFusionOp, inputs: Vec<usize>) -> Self {
1266 Self { op, inputs }
1267 }
1268
1269 pub fn op(&self) -> ElementwiseFusionOp {
1280 self.op
1281 }
1282
1283 pub fn inputs(&self) -> &[usize] {
1294 &self.inputs
1295 }
1296}
1297
1298pub trait TensorElementwise {
1308 fn add(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1309
1310 fn add_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1330 self.add(read_tensor("add", lhs)?, read_tensor("add", rhs)?)
1331 }
1332
1333 fn add_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1353 self.add_read_into(
1354 TensorRead::from_tensor(lhs),
1355 TensorRead::from_tensor(rhs),
1356 out,
1357 )
1358 }
1359
1360 fn add_read_into(
1377 &mut self,
1378 lhs: TensorRead<'_>,
1379 rhs: TensorRead<'_>,
1380 mut out: TensorWrite<'_>,
1381 ) -> crate::Result<()> {
1382 let result = self.add_read(lhs, rhs)?;
1383 out.copy_from_tensor(&result)
1384 }
1385
1386 fn sub(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1387
1388 fn sub_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1404 self.sub(read_tensor("sub", lhs)?, read_tensor("sub", rhs)?)
1405 }
1406
1407 fn sub_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1409 self.sub_read_into(
1410 TensorRead::from_tensor(lhs),
1411 TensorRead::from_tensor(rhs),
1412 out,
1413 )
1414 }
1415
1416 fn sub_read_into(
1418 &mut self,
1419 lhs: TensorRead<'_>,
1420 rhs: TensorRead<'_>,
1421 mut out: TensorWrite<'_>,
1422 ) -> crate::Result<()> {
1423 let result = self.sub_read(lhs, rhs)?;
1424 out.copy_from_tensor(&result)
1425 }
1426
1427 fn mul(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1428 fn mul_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1429 self.mul(read_tensor("mul", lhs)?, read_tensor("mul", rhs)?)
1430 }
1431
1432 fn mul_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1434 self.mul_read_into(
1435 TensorRead::from_tensor(lhs),
1436 TensorRead::from_tensor(rhs),
1437 out,
1438 )
1439 }
1440
1441 fn mul_read_into(
1443 &mut self,
1444 lhs: TensorRead<'_>,
1445 rhs: TensorRead<'_>,
1446 mut out: TensorWrite<'_>,
1447 ) -> crate::Result<()> {
1448 let result = self.mul_read(lhs, rhs)?;
1449 out.copy_from_tensor(&result)
1450 }
1451
1452 fn neg(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1453 fn neg_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1454 self.neg(read_tensor("neg", input)?)
1455 }
1456
1457 fn neg_into(&mut self, input: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1459 self.neg_read_into(TensorRead::from_tensor(input), out)
1460 }
1461
1462 fn neg_read_into(
1464 &mut self,
1465 input: TensorRead<'_>,
1466 mut out: TensorWrite<'_>,
1467 ) -> crate::Result<()> {
1468 let result = self.neg_read(input)?;
1469 out.copy_from_tensor(&result)
1470 }
1471
1472 fn conj(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1473 fn conj_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1474 self.conj(read_tensor("conj", input)?)
1475 }
1476
1477 fn conj_into(&mut self, input: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1479 self.conj_read_into(TensorRead::from_tensor(input), out)
1480 }
1481
1482 fn conj_read_into(
1484 &mut self,
1485 input: TensorRead<'_>,
1486 mut out: TensorWrite<'_>,
1487 ) -> crate::Result<()> {
1488 let result = self.conj_read(input)?;
1489 out.copy_from_tensor(&result)
1490 }
1491
1492 fn div(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1493 fn div_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1494 self.div(read_tensor("div", lhs)?, read_tensor("div", rhs)?)
1495 }
1496
1497 fn div_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1499 self.div_read_into(
1500 TensorRead::from_tensor(lhs),
1501 TensorRead::from_tensor(rhs),
1502 out,
1503 )
1504 }
1505
1506 fn div_read_into(
1508 &mut self,
1509 lhs: TensorRead<'_>,
1510 rhs: TensorRead<'_>,
1511 mut out: TensorWrite<'_>,
1512 ) -> crate::Result<()> {
1513 let result = self.div_read(lhs, rhs)?;
1514 out.copy_from_tensor(&result)
1515 }
1516
1517 fn rem(&mut self, lhs: &Tensor, _rhs: &Tensor) -> crate::Result<Tensor> {
1536 Err(crate::Error::backend_failure(
1537 "rem",
1538 format!("backend does not implement rem for dtype {:?}", lhs.dtype()),
1539 ))
1540 }
1541
1542 fn rem_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1558 self.rem(read_tensor("rem", lhs)?, read_tensor("rem", rhs)?)
1559 }
1560
1561 fn abs(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1562 fn abs_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1563 self.abs(read_tensor("abs", input)?)
1564 }
1565
1566 fn sign(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1567 fn sign_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1568 self.sign(read_tensor("sign", input)?)
1569 }
1570
1571 fn maximum(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1572 fn maximum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1573 self.maximum(read_tensor("maximum", lhs)?, read_tensor("maximum", rhs)?)
1574 }
1575
1576 fn minimum(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1577 fn minimum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1578 self.minimum(read_tensor("minimum", lhs)?, read_tensor("minimum", rhs)?)
1579 }
1580
1581 fn compare(&mut self, lhs: &Tensor, rhs: &Tensor, dir: &CompareDir) -> crate::Result<Tensor>;
1582 fn compare_read(
1583 &mut self,
1584 lhs: TensorRead<'_>,
1585 rhs: TensorRead<'_>,
1586 dir: &CompareDir,
1587 ) -> crate::Result<Tensor> {
1588 self.compare(
1589 read_tensor("compare", lhs)?,
1590 read_tensor("compare", rhs)?,
1591 dir,
1592 )
1593 }
1594
1595 fn select(
1596 &mut self,
1597 pred: &Tensor,
1598 on_true: &Tensor,
1599 on_false: &Tensor,
1600 ) -> crate::Result<Tensor>;
1601 fn select_read(
1602 &mut self,
1603 pred: TensorRead<'_>,
1604 on_true: TensorRead<'_>,
1605 on_false: TensorRead<'_>,
1606 ) -> crate::Result<Tensor> {
1607 self.select(
1608 read_tensor("select", pred)?,
1609 read_tensor("select", on_true)?,
1610 read_tensor("select", on_false)?,
1611 )
1612 }
1613
1614 fn clamp(&mut self, input: &Tensor, lower: &Tensor, upper: &Tensor) -> crate::Result<Tensor>;
1615 fn clamp_read(
1616 &mut self,
1617 input: TensorRead<'_>,
1618 lower: TensorRead<'_>,
1619 upper: TensorRead<'_>,
1620 ) -> crate::Result<Tensor> {
1621 self.clamp(
1622 read_tensor("clamp", input)?,
1623 read_tensor("clamp", lower)?,
1624 read_tensor("clamp", upper)?,
1625 )
1626 }
1627}
1628
1629pub trait TensorAnalytic {
1639 fn exp(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1640 fn exp_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1641 self.exp(read_tensor("exp", input)?)
1642 }
1643
1644 fn log(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1645 fn log_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1646 self.log(read_tensor("log", input)?)
1647 }
1648
1649 fn sin(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1650 fn sin_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1651 self.sin(read_tensor("sin", input)?)
1652 }
1653
1654 fn cos(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1655 fn cos_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1656 self.cos(read_tensor("cos", input)?)
1657 }
1658
1659 fn tanh(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1660 fn tanh_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1661 self.tanh(read_tensor("tanh", input)?)
1662 }
1663
1664 fn sqrt(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1665 fn sqrt_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1666 self.sqrt(read_tensor("sqrt", input)?)
1667 }
1668
1669 fn rsqrt(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1670 fn rsqrt_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1671 self.rsqrt(read_tensor("rsqrt", input)?)
1672 }
1673
1674 fn pow(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1675 fn pow_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1676 self.pow(read_tensor("pow", lhs)?, read_tensor("pow", rhs)?)
1677 }
1678
1679 fn expm1(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1680 fn expm1_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1681 self.expm1(read_tensor("expm1", input)?)
1682 }
1683
1684 fn log1p(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1685 fn log1p_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1686 self.log1p(read_tensor("log1p", input)?)
1687 }
1688}
1689
1690pub trait TensorStructural {
1700 fn transpose(&mut self, input: &Tensor, perm: &[usize]) -> crate::Result<Tensor>;
1701 fn transpose_read(&mut self, input: TensorRead<'_>, perm: &[usize]) -> crate::Result<Tensor> {
1702 self.transpose(read_tensor("transpose", input)?, perm)
1703 }
1704
1705 fn reshape(&mut self, input: &Tensor, shape: &[usize]) -> crate::Result<Tensor>;
1706 fn reshape_read(&mut self, input: TensorRead<'_>, shape: &[usize]) -> crate::Result<Tensor> {
1707 self.reshape(read_tensor("reshape", input)?, shape)
1708 }
1709
1710 fn broadcast_in_dim(
1711 &mut self,
1712 input: &Tensor,
1713 shape: &[usize],
1714 dims: &[usize],
1715 ) -> crate::Result<Tensor>;
1716 fn broadcast_in_dim_read(
1717 &mut self,
1718 input: TensorRead<'_>,
1719 shape: &[usize],
1720 dims: &[usize],
1721 ) -> crate::Result<Tensor> {
1722 self.broadcast_in_dim(read_tensor("broadcast_in_dim", input)?, shape, dims)
1723 }
1724
1725 fn cast(&mut self, input: &Tensor, to: crate::DType) -> crate::Result<Tensor>;
1743
1744 fn convert(&mut self, input: &Tensor, to: crate::DType) -> crate::Result<Tensor> {
1762 validate_convert_dtype("convert", input.dtype(), to)?;
1763 self.cast(input, to)
1764 }
1765
1766 fn extract_diagonal(
1767 &mut self,
1768 input: &Tensor,
1769 axis_a: usize,
1770 axis_b: usize,
1771 ) -> crate::Result<Tensor>;
1772 fn embed_diagonal(
1773 &mut self,
1774 input: &Tensor,
1775 axis_a: usize,
1776 axis_b: usize,
1777 ) -> crate::Result<Tensor>;
1778 fn tril(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor>;
1779 fn triu(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor>;
1780}
1781
1782pub trait TensorReduction {
1796 fn reduce_sum(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
1797
1798 fn reduce_sum_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
1813 match input.as_tensor() {
1814 Some(input) => self.reduce_sum(input, axes),
1815 None => Err(crate::Error::backend_failure(
1816 "reduce_sum",
1817 "backend does not accept borrowed tensor views at this execution boundary",
1818 )),
1819 }
1820 }
1821
1822 fn reduce_prod(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
1823
1824 fn reduce_prod_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
1839 match input.as_tensor() {
1840 Some(input) => self.reduce_prod(input, axes),
1841 None => Err(crate::Error::backend_failure(
1842 "reduce_prod",
1843 "backend does not accept borrowed tensor views at this execution boundary",
1844 )),
1845 }
1846 }
1847
1848 fn reduce_max(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
1849
1850 fn reduce_max_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
1865 match input.as_tensor() {
1866 Some(input) => self.reduce_max(input, axes),
1867 None => Err(crate::Error::backend_failure(
1868 "reduce_max",
1869 "backend does not accept borrowed tensor views at this execution boundary",
1870 )),
1871 }
1872 }
1873
1874 fn reduce_min(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
1875
1876 fn reduce_min_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
1891 match input.as_tensor() {
1892 Some(input) => self.reduce_min(input, axes),
1893 None => Err(crate::Error::backend_failure(
1894 "reduce_min",
1895 "backend does not accept borrowed tensor views at this execution boundary",
1896 )),
1897 }
1898 }
1899}
1900
1901pub trait TensorDot: TensorElementwise {
1911 fn dot_general(
1912 &mut self,
1913 lhs: &Tensor,
1914 rhs: &Tensor,
1915 config: &DotGeneralConfig,
1916 ) -> crate::Result<Tensor>;
1917
1918 #[doc(hidden)]
1919 fn dot_general_read(
1920 &mut self,
1921 lhs: TensorRead<'_>,
1922 rhs: TensorRead<'_>,
1923 config: &DotGeneralConfig,
1924 ) -> crate::Result<Tensor> {
1925 match (lhs.as_tensor(), rhs.as_tensor()) {
1926 (Some(lhs), Some(rhs)) => self.dot_general(lhs, rhs, config),
1927 _ => {
1928 let lhs = lhs.to_tensor()?;
1929 let rhs = rhs.to_tensor()?;
1930 self.dot_general(&lhs, &rhs, config)
1931 }
1932 }
1933 }
1934
1935 fn dot_general_read_into(
1957 &mut self,
1958 lhs: TensorRead<'_>,
1959 rhs: TensorRead<'_>,
1960 config: &DotGeneralConfig,
1961 out: TensorWrite<'_>,
1962 ) -> crate::Result<()> {
1963 let accumulation = DotGeneralAccumulation::overwrite(lhs.dtype())?;
1964 self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
1965 }
1966
1967 #[doc(hidden)]
1968 fn dot_general_with_conj(
1969 &mut self,
1970 lhs: &Tensor,
1971 rhs: &Tensor,
1972 config: &DotGeneralConfig,
1973 lhs_conj: bool,
1974 rhs_conj: bool,
1975 ) -> crate::Result<Tensor> {
1976 if !lhs_conj && !rhs_conj {
1977 return self.dot_general(lhs, rhs, config);
1978 }
1979
1980 let lhs_tmp;
1981 let lhs_ref = if lhs_conj {
1982 lhs_tmp = self.conj(lhs)?;
1983 &lhs_tmp
1984 } else {
1985 lhs
1986 };
1987 let rhs_tmp;
1988 let rhs_ref = if rhs_conj {
1989 rhs_tmp = self.conj(rhs)?;
1990 &rhs_tmp
1991 } else {
1992 rhs
1993 };
1994 self.dot_general(lhs_ref, rhs_ref, config)
1995 }
1996
1997 #[allow(clippy::too_many_arguments)]
1998 #[doc(hidden)]
1999 fn dot_general_with_conj_read(
2000 &mut self,
2001 lhs: TensorRead<'_>,
2002 rhs: TensorRead<'_>,
2003 config: &DotGeneralConfig,
2004 lhs_conj: bool,
2005 rhs_conj: bool,
2006 ) -> crate::Result<Tensor> {
2007 if !lhs_conj && !rhs_conj {
2008 return self.dot_general_read(lhs, rhs, config);
2009 }
2010
2011 let lhs_tmp;
2012 let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
2013 tensor
2014 } else {
2015 lhs_tmp = lhs.to_tensor()?;
2016 &lhs_tmp
2017 };
2018 let rhs_tmp;
2019 let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
2020 tensor
2021 } else {
2022 rhs_tmp = rhs.to_tensor()?;
2023 &rhs_tmp
2024 };
2025 self.dot_general_with_conj(lhs_ref, rhs_ref, config, lhs_conj, rhs_conj)
2026 }
2027
2028 fn dot_general_read_into_accum(
2052 &mut self,
2053 lhs: TensorRead<'_>,
2054 rhs: TensorRead<'_>,
2055 config: &DotGeneralConfig,
2056 accumulation: DotGeneralAccumulation,
2057 out: TensorWrite<'_>,
2058 ) -> crate::Result<()> {
2059 dot_general_accum_via_temp(self, lhs, rhs, config, accumulation, out)
2060 }
2061}
2062
2063pub trait SessionCachedDot: TensorDot {
2073 #[doc(hidden)]
2074 fn dot_general_cached(
2075 &mut self,
2076 _cache_slot: Option<usize>,
2077 lhs: &Tensor,
2078 rhs: &Tensor,
2079 config: &DotGeneralConfig,
2080 ) -> crate::Result<Tensor> {
2081 self.dot_general(lhs, rhs, config)
2082 }
2083
2084 #[doc(hidden)]
2085 fn dot_general_read_cached(
2086 &mut self,
2087 cache_slot: Option<usize>,
2088 lhs: TensorRead<'_>,
2089 rhs: TensorRead<'_>,
2090 config: &DotGeneralConfig,
2091 ) -> crate::Result<Tensor> {
2092 match (lhs.as_tensor(), rhs.as_tensor()) {
2093 (Some(lhs), Some(rhs)) => self.dot_general_cached(cache_slot, lhs, rhs, config),
2094 _ => {
2095 let lhs = lhs.to_tensor()?;
2096 let rhs = rhs.to_tensor()?;
2097 self.dot_general_cached(cache_slot, &lhs, &rhs, config)
2098 }
2099 }
2100 }
2101
2102 #[allow(clippy::too_many_arguments)]
2104 #[doc(hidden)]
2105 fn dot_general_with_conj_cached(
2106 &mut self,
2107 _cache_slot: Option<usize>,
2108 lhs: &Tensor,
2109 rhs: &Tensor,
2110 config: &DotGeneralConfig,
2111 lhs_conj: bool,
2112 rhs_conj: bool,
2113 ) -> crate::Result<Tensor> {
2114 self.dot_general_with_conj(lhs, rhs, config, lhs_conj, rhs_conj)
2115 }
2116
2117 #[allow(clippy::too_many_arguments)]
2119 #[doc(hidden)]
2120 fn dot_general_with_conj_read_cached(
2121 &mut self,
2122 cache_slot: Option<usize>,
2123 lhs: TensorRead<'_>,
2124 rhs: TensorRead<'_>,
2125 config: &DotGeneralConfig,
2126 lhs_conj: bool,
2127 rhs_conj: bool,
2128 ) -> crate::Result<Tensor> {
2129 if !lhs_conj && !rhs_conj {
2130 return self.dot_general_read_cached(cache_slot, lhs, rhs, config);
2131 }
2132
2133 let lhs_tmp;
2134 let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
2135 tensor
2136 } else {
2137 lhs_tmp = lhs.to_tensor()?;
2138 &lhs_tmp
2139 };
2140 let rhs_tmp;
2141 let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
2142 tensor
2143 } else {
2144 rhs_tmp = rhs.to_tensor()?;
2145 &rhs_tmp
2146 };
2147 self.dot_general_with_conj_cached(cache_slot, lhs_ref, rhs_ref, config, lhs_conj, rhs_conj)
2148 }
2149
2150 fn dot_general_read_into_accum_cached(
2181 &mut self,
2182 _cache_slot: Option<usize>,
2183 lhs: TensorRead<'_>,
2184 rhs: TensorRead<'_>,
2185 config: &DotGeneralConfig,
2186 accumulation: DotGeneralAccumulation,
2187 out: TensorWrite<'_>,
2188 ) -> crate::Result<()> {
2189 self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
2190 }
2191
2192 #[doc(hidden)]
2193 fn grouped_gemm_cached(
2194 &mut self,
2195 _cache_slot: Option<usize>,
2196 lhs: TensorRead<'_>,
2197 rhs: TensorRead<'_>,
2198 config: &GroupedGemmConfig<'_>,
2199 out: TensorWrite<'_>,
2200 ) -> crate::Result<()> {
2201 grouped_gemm_default(self, lhs, rhs, config, out)
2202 }
2203}
2204
2205pub trait TensorIndexing {
2215 fn gather(
2216 &mut self,
2217 operand: &Tensor,
2218 start_indices: &Tensor,
2219 config: &GatherConfig,
2220 ) -> crate::Result<Tensor>;
2221 fn scatter(
2222 &mut self,
2223 operand: &Tensor,
2224 scatter_indices: &Tensor,
2225 updates: &Tensor,
2226 config: &ScatterConfig,
2227 ) -> crate::Result<Tensor>;
2228 fn slice(&mut self, input: &Tensor, config: &SliceConfig) -> crate::Result<Tensor>;
2229 fn dynamic_slice(
2230 &mut self,
2231 input: &Tensor,
2232 starts: &Tensor,
2233 slice_sizes: &[usize],
2234 ) -> crate::Result<Tensor>;
2235 fn dynamic_update_slice(
2236 &mut self,
2237 operand: &Tensor,
2238 update: &Tensor,
2239 starts: &Tensor,
2240 ) -> crate::Result<Tensor>;
2241 fn pad(&mut self, input: &Tensor, config: &PadConfig) -> crate::Result<Tensor>;
2242 fn concatenate(&mut self, inputs: &[&Tensor], axis: usize) -> crate::Result<Tensor>;
2243 fn reverse(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
2244}
2245
2246pub trait TensorViewCanonicalization<T: Clone + 'static, R: TensorRank> {
2270 fn to_contiguous(
2271 &mut self,
2272 view: &TypedTensorView<'_, T, R>,
2273 ) -> crate::Result<TypedTensor<T, R>>;
2274
2275 fn copy_from_contiguous(
2276 &mut self,
2277 src: &TypedTensor<T, R>,
2278 dst: &mut TypedTensorViewMut<'_, T, R>,
2279 ) -> crate::Result<()>;
2280}
2281
2282pub trait TensorFusion {
2292 #[doc(hidden)]
2293 fn execute_elementwise_fusion(
2294 &mut self,
2295 _inputs: &[&Tensor],
2296 _plan: &ElementwiseFusionPlan,
2297 ) -> crate::Result<Option<Vec<Tensor>>> {
2298 Ok(None)
2299 }
2300
2301 #[doc(hidden)]
2302 #[allow(clippy::too_many_arguments)]
2303 fn execute_broadcast_multiply(
2304 &mut self,
2305 _lhs: TensorRead<'_>,
2306 _lhs_shape: &[usize],
2307 _lhs_dims: &[usize],
2308 _rhs: TensorRead<'_>,
2309 _rhs_shape: &[usize],
2310 _rhs_dims: &[usize],
2311 ) -> crate::Result<Option<Tensor>> {
2312 Ok(None)
2313 }
2314
2315 #[doc(hidden)]
2316 #[allow(clippy::too_many_arguments)]
2317 fn execute_broadcast_multiply_value(
2318 &mut self,
2319 lhs: TensorRead<'_>,
2320 lhs_shape: &[usize],
2321 lhs_dims: &[usize],
2322 rhs: TensorRead<'_>,
2323 rhs_shape: &[usize],
2324 rhs_dims: &[usize],
2325 ) -> crate::Result<Option<TensorValue>> {
2326 self.execute_broadcast_multiply(lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims)
2327 .map(|tensor| tensor.map(TensorValue::from_tensor))
2328 }
2329}
2330
2331pub trait TensorBuffer {
2341 fn reclaim_buffer(&mut self, _tensor: Tensor) {}
2342}
2343
2344pub trait TensorDeviceTransfer {
2354 fn download_to_host(&mut self, tensor: &Tensor) -> crate::Result<Tensor> {
2355 Ok(tensor.clone())
2356 }
2357
2358 fn upload_host_tensor(&mut self, tensor: &Tensor) -> crate::Result<Tensor> {
2359 Ok(tensor.clone())
2360 }
2361}
2362
2363pub trait BackendRuntimeCache {
2373 #[doc(hidden)]
2374 type RuntimeCache: RuntimeCacheControl + Send + Sync + 'static;
2375}
2376
2377pub trait BackendCachedDot: BackendRuntimeCache + TensorDot {
2387 #[doc(hidden)]
2388 fn dot_general_cached(
2389 &mut self,
2390 _cache: &mut Self::RuntimeCache,
2391 _cache_slot: Option<usize>,
2392 lhs: &Tensor,
2393 rhs: &Tensor,
2394 config: &DotGeneralConfig,
2395 ) -> crate::Result<Tensor> {
2396 self.dot_general(lhs, rhs, config)
2397 }
2398
2399 #[doc(hidden)]
2400 fn dot_general_read_cached(
2401 &mut self,
2402 cache: &mut Self::RuntimeCache,
2403 cache_slot: Option<usize>,
2404 lhs: TensorRead<'_>,
2405 rhs: TensorRead<'_>,
2406 config: &DotGeneralConfig,
2407 ) -> crate::Result<Tensor> {
2408 match (lhs.as_tensor(), rhs.as_tensor()) {
2409 (Some(lhs), Some(rhs)) => self.dot_general_cached(cache, cache_slot, lhs, rhs, config),
2410 _ => {
2411 let lhs = lhs.to_tensor()?;
2412 let rhs = rhs.to_tensor()?;
2413 self.dot_general_cached(cache, cache_slot, &lhs, &rhs, config)
2414 }
2415 }
2416 }
2417
2418 #[allow(clippy::too_many_arguments)]
2420 #[doc(hidden)]
2421 fn dot_general_with_conj_cached(
2422 &mut self,
2423 _cache: &mut Self::RuntimeCache,
2424 _cache_slot: Option<usize>,
2425 lhs: &Tensor,
2426 rhs: &Tensor,
2427 config: &DotGeneralConfig,
2428 lhs_conj: bool,
2429 rhs_conj: bool,
2430 ) -> crate::Result<Tensor> {
2431 self.dot_general_with_conj(lhs, rhs, config, lhs_conj, rhs_conj)
2432 }
2433
2434 #[allow(clippy::too_many_arguments)]
2436 #[doc(hidden)]
2437 fn dot_general_with_conj_read_cached(
2438 &mut self,
2439 cache: &mut Self::RuntimeCache,
2440 cache_slot: Option<usize>,
2441 lhs: TensorRead<'_>,
2442 rhs: TensorRead<'_>,
2443 config: &DotGeneralConfig,
2444 lhs_conj: bool,
2445 rhs_conj: bool,
2446 ) -> crate::Result<Tensor> {
2447 if !lhs_conj && !rhs_conj {
2448 return self.dot_general_read_cached(cache, cache_slot, lhs, rhs, config);
2449 }
2450
2451 let lhs_tmp;
2452 let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
2453 tensor
2454 } else {
2455 lhs_tmp = lhs.to_tensor()?;
2456 &lhs_tmp
2457 };
2458 let rhs_tmp;
2459 let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
2460 tensor
2461 } else {
2462 rhs_tmp = rhs.to_tensor()?;
2463 &rhs_tmp
2464 };
2465 self.dot_general_with_conj_cached(
2466 cache, cache_slot, lhs_ref, rhs_ref, config, lhs_conj, rhs_conj,
2467 )
2468 }
2469
2470 #[allow(clippy::too_many_arguments)]
2507 fn dot_general_read_into_accum_cached(
2508 &mut self,
2509 _cache: &mut Self::RuntimeCache,
2510 _cache_slot: Option<usize>,
2511 lhs: TensorRead<'_>,
2512 rhs: TensorRead<'_>,
2513 config: &DotGeneralConfig,
2514 accumulation: DotGeneralAccumulation,
2515 out: TensorWrite<'_>,
2516 ) -> crate::Result<()> {
2517 self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
2518 }
2519
2520 #[doc(hidden)]
2521 fn grouped_gemm_cached(
2522 &mut self,
2523 _cache: &mut Self::RuntimeCache,
2524 _cache_slot: Option<usize>,
2525 lhs: TensorRead<'_>,
2526 rhs: TensorRead<'_>,
2527 config: &GroupedGemmConfig<'_>,
2528 out: TensorWrite<'_>,
2529 ) -> crate::Result<()> {
2530 grouped_gemm_default(self, lhs, rhs, config, out)
2531 }
2532}
2533
2534pub trait BackendSessionHost: BackendRuntimeCache {
2544 fn with_backend_session<R: Send>(
2545 &mut self,
2546 f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
2547 ) -> R
2548 where
2549 Self: TensorBackend + Sized,
2550 {
2551 default_backend_session(self, f)
2552 }
2553
2554 #[doc(hidden)]
2555 fn with_backend_session_cached<R: Send>(
2556 &mut self,
2557 _cache: &mut Self::RuntimeCache,
2558 f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
2559 ) -> R
2560 where
2561 Self: TensorBackend + Sized,
2562 {
2563 self.with_backend_session(f)
2564 }
2565}
2566
2567#[doc(hidden)]
2569pub trait TensorBackendOps:
2570 TensorElementwise
2571 + TensorAnalytic
2572 + TensorStructural
2573 + TensorReduction
2574 + TensorIndexing
2575 + TensorDot
2576 + TensorFusion
2577 + TensorBuffer
2578{
2579}
2580
2581impl<T> TensorBackendOps for T where
2582 T: TensorElementwise
2583 + TensorAnalytic
2584 + TensorStructural
2585 + TensorReduction
2586 + TensorIndexing
2587 + TensorDot
2588 + TensorFusion
2589 + TensorBuffer
2590 + ?Sized
2591{
2592}
2593
2594pub trait BackendSession: TensorBackendOps + SessionCachedDot {}
2617
2618impl<T> BackendSession for T where T: TensorBackendOps + SessionCachedDot + ?Sized {}
2619
2620pub trait TensorBackend:
2630 BackendRuntimeCache
2631 + TensorBackendOps
2632 + BackendCachedDot
2633 + TensorDeviceTransfer
2634 + BackendSessionHost
2635{
2636}
2637
2638impl<T> SessionCachedDot for T where T: TensorBackend + ?Sized {}
2639
2640pub fn default_backend_session<B: TensorBackend, R: Send>(
2655 backend: &mut B,
2656 f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
2657) -> R {
2658 f(backend)
2659}