tenferro_tensor/backend.rs
1use crate::config::{
2 CompareDir, DotGeneralConfig, GatherConfig, PadConfig, ScatterConfig, SliceConfig,
3};
4use crate::types::{
5 TensorRank, TensorScalar, TensorView, TensorViewMut, TypedTensor, TypedTensorView,
6 TypedTensorViewMut,
7};
8use crate::validate::validate_convert_dtype;
9use crate::{
10 AllocationDomainId, AllocationId, DType, Error, RuntimeCacheControl, ShapeMismatch, Tensor,
11 TensorRead, TensorValue, TensorWrite, ValidationError,
12};
13use num_complex::{Complex32, Complex64};
14use std::any::TypeId;
15
16#[cfg(test)]
17mod tests;
18
19fn read_boundary_error(op: &'static str) -> crate::Error {
20 crate::Error::unsupported(
21 op,
22 "backend does not accept borrowed tensor views at this execution boundary",
23 )
24}
25
26fn validation(op: &'static str, source: ValidationError) -> crate::Error {
27 Error::validation(op, source)
28}
29
30fn invalid_argument(op: &'static str, argument: &'static str, message: impl Into<String>) -> Error {
31 Error::invalid_argument(op, argument, message)
32}
33
34fn read_tensor<'a>(op: &'static str, input: TensorRead<'a>) -> crate::Result<&'a Tensor> {
35 input.as_tensor().ok_or_else(|| read_boundary_error(op))
36}
37
38fn validate_axis_list(
39 op: &'static str,
40 role: &'static str,
41 axes: &[usize],
42 rank: usize,
43) -> crate::Result<()> {
44 let mut seen = vec![false; rank];
45 for &axis in axes {
46 if axis >= rank {
47 return Err(validation(
48 op,
49 ValidationError::AxisOutOfBounds { axis, rank },
50 ));
51 }
52 if seen[axis] {
53 return Err(validation(
54 op,
55 ValidationError::DuplicateAxis { axis, role },
56 ));
57 }
58 seen[axis] = true;
59 }
60 Ok(())
61}
62
63fn validate_role_disjoint(
64 op: &'static str,
65 first_role: &'static str,
66 first_axes: &[usize],
67 second_role: &'static str,
68 second_axes: &[usize],
69) -> crate::Result<()> {
70 for &axis in first_axes {
71 if second_axes.contains(&axis) {
72 return Err(validation(
73 op,
74 ValidationError::AxisRoleConflict {
75 axis,
76 first_role,
77 second_role,
78 },
79 ));
80 }
81 }
82 Ok(())
83}
84
85/// Infer the output shape for a validated dot-general operation.
86#[doc(hidden)]
87pub fn dot_general_output_shape(
88 lhs_shape: &[usize],
89 rhs_shape: &[usize],
90 config: &DotGeneralConfig,
91 op: &'static str,
92) -> crate::Result<Vec<usize>> {
93 if config.lhs_contracting_dims.len() != config.rhs_contracting_dims.len() {
94 return Err(invalid_argument(
95 op,
96 "contracting_dims",
97 "lhs/rhs contracting dim counts differ",
98 ));
99 }
100 if config.lhs_batch_dims.len() != config.rhs_batch_dims.len() {
101 return Err(invalid_argument(
102 op,
103 "batch_dims",
104 "lhs/rhs batch dim counts differ",
105 ));
106 }
107
108 let lhs_rank = lhs_shape.len();
109 let rhs_rank = rhs_shape.len();
110 validate_axis_list(
111 op,
112 "lhs_contracting",
113 &config.lhs_contracting_dims,
114 lhs_rank,
115 )?;
116 validate_axis_list(
117 op,
118 "rhs_contracting",
119 &config.rhs_contracting_dims,
120 rhs_rank,
121 )?;
122 validate_axis_list(op, "lhs_batch", &config.lhs_batch_dims, lhs_rank)?;
123 validate_axis_list(op, "rhs_batch", &config.rhs_batch_dims, rhs_rank)?;
124 validate_role_disjoint(
125 op,
126 "lhs_contracting",
127 &config.lhs_contracting_dims,
128 "lhs_batch",
129 &config.lhs_batch_dims,
130 )?;
131 validate_role_disjoint(
132 op,
133 "rhs_contracting",
134 &config.rhs_contracting_dims,
135 "rhs_batch",
136 &config.rhs_batch_dims,
137 )?;
138
139 for (&lhs_axis, &rhs_axis) in config
140 .lhs_contracting_dims
141 .iter()
142 .zip(&config.rhs_contracting_dims)
143 {
144 if lhs_shape[lhs_axis] != rhs_shape[rhs_axis] {
145 return Err(validation(
146 op,
147 ShapeMismatch::ContractedDimensions {
148 lhs_axis,
149 lhs_size: lhs_shape[lhs_axis],
150 rhs_axis,
151 rhs_size: rhs_shape[rhs_axis],
152 }
153 .into(),
154 ));
155 }
156 }
157 for (&lhs_axis, &rhs_axis) in config.lhs_batch_dims.iter().zip(&config.rhs_batch_dims) {
158 if lhs_shape[lhs_axis] != rhs_shape[rhs_axis] {
159 return Err(validation(
160 op,
161 ShapeMismatch::ContractedDimensions {
162 lhs_axis,
163 lhs_size: lhs_shape[lhs_axis],
164 rhs_axis,
165 rhs_size: rhs_shape[rhs_axis],
166 }
167 .into(),
168 ));
169 }
170 }
171
172 let lhs_free = (0..lhs_rank)
173 .filter(|axis| {
174 !config.lhs_contracting_dims.contains(axis) && !config.lhs_batch_dims.contains(axis)
175 })
176 .map(|axis| lhs_shape[axis]);
177 let rhs_free = (0..rhs_rank)
178 .filter(|axis| {
179 !config.rhs_contracting_dims.contains(axis) && !config.rhs_batch_dims.contains(axis)
180 })
181 .map(|axis| rhs_shape[axis]);
182 let batch = config.lhs_batch_dims.iter().map(|&axis| lhs_shape[axis]);
183
184 Ok(lhs_free.chain(rhs_free).chain(batch).collect())
185}
186
187/// Validate output dtype and shape for dot-general read-into dispatch.
188#[doc(hidden)]
189pub fn validate_dot_general_read_into(
190 lhs: &TensorRead<'_>,
191 rhs: &TensorRead<'_>,
192 config: &DotGeneralConfig,
193 out: &TensorWrite<'_>,
194 op: &'static str,
195) -> crate::Result<Vec<usize>> {
196 if lhs.dtype() != rhs.dtype() {
197 return Err(validation(
198 op,
199 ValidationError::DTypeMismatch {
200 expected: lhs.dtype(),
201 actual: rhs.dtype(),
202 },
203 ));
204 }
205 if lhs.dtype() != out.dtype() {
206 return Err(validation(
207 op,
208 ValidationError::DTypeMismatch {
209 expected: lhs.dtype(),
210 actual: out.dtype(),
211 },
212 ));
213 }
214 let expected = dot_general_output_shape(lhs.shape(), rhs.shape(), config, op)?;
215 if out.shape() != expected.as_slice() {
216 return Err(validation(
217 op,
218 ShapeMismatch::ExpectedActual {
219 expected: expected.clone().into(),
220 actual: out.shape().to_vec().into(),
221 }
222 .into(),
223 ));
224 }
225 Ok(expected)
226}
227
228/// Scalar coefficient accepted by contraction accumulation backends.
229///
230/// `ContractionScalar` is intentionally narrower than [`crate::TensorScalar`]:
231/// dot-general accumulation is only defined for floating and complex tensor
232/// dtypes.
233///
234/// # Examples
235///
236/// ```rust
237/// use tenferro_tensor::{ContractionScalar, DType};
238///
239/// let alpha = ContractionScalar::F64(2.0);
240/// assert_eq!(alpha.dtype(), DType::F64);
241/// ```
242#[derive(Clone, Copy, Debug, PartialEq)]
243pub enum ContractionScalar {
244 F32(f32),
245 F64(f64),
246 C32(Complex32),
247 C64(Complex64),
248}
249
250impl ContractionScalar {
251 /// Return this scalar's tensor dtype.
252 ///
253 /// # Examples
254 ///
255 /// ```rust
256 /// use tenferro_tensor::{ContractionScalar, DType};
257 ///
258 /// assert_eq!(ContractionScalar::F32(1.0).dtype(), DType::F32);
259 /// ```
260 pub fn dtype(self) -> DType {
261 match self {
262 Self::F32(_) => DType::F32,
263 Self::F64(_) => DType::F64,
264 Self::C32(_) => DType::C32,
265 Self::C64(_) => DType::C64,
266 }
267 }
268
269 /// Return the multiplicative identity for a supported contraction dtype.
270 ///
271 /// # Examples
272 ///
273 /// ```rust
274 /// use tenferro_tensor::{ContractionScalar, DType};
275 ///
276 /// assert_eq!(ContractionScalar::one(DType::F64).unwrap(), ContractionScalar::F64(1.0));
277 /// assert!(ContractionScalar::one(DType::I32).is_err());
278 /// ```
279 /// # Errors
280 ///
281 /// Returns [`crate::Error::Validation`] with a
282 /// [`crate::ValidationError::DTypeMismatch`] source when `dtype` is `I32`,
283 /// `I64`, or `Bool`, which do not support contraction scalar identities.
284 pub fn one(dtype: DType) -> crate::Result<Self> {
285 match dtype {
286 DType::F32 => Ok(Self::F32(1.0)),
287 DType::F64 => Ok(Self::F64(1.0)),
288 DType::C32 => Ok(Self::C32(Complex32::new(1.0, 0.0))),
289 DType::C64 => Ok(Self::C64(Complex64::new(1.0, 0.0))),
290 DType::I32 | DType::I64 | DType::Bool | DType::External(_) => Err(validation(
291 "ContractionScalar::one",
292 ValidationError::DTypeMismatch {
293 expected: dtype,
294 actual: DType::F32,
295 },
296 )),
297 }
298 }
299
300 /// Return the additive identity for a supported contraction dtype.
301 ///
302 /// # Examples
303 ///
304 /// ```rust
305 /// use tenferro_tensor::{ContractionScalar, DType};
306 ///
307 /// assert_eq!(ContractionScalar::zero(DType::F64).unwrap(), ContractionScalar::F64(0.0));
308 /// ```
309 /// # Errors
310 ///
311 /// Returns [`crate::Error::Validation`] with a
312 /// [`crate::ValidationError::DTypeMismatch`] source when `dtype` is `I32`,
313 /// `I64`, or `Bool`, which do not support contraction scalar identities.
314 pub fn zero(dtype: DType) -> crate::Result<Self> {
315 match dtype {
316 DType::F32 => Ok(Self::F32(0.0)),
317 DType::F64 => Ok(Self::F64(0.0)),
318 DType::C32 => Ok(Self::C32(Complex32::new(0.0, 0.0))),
319 DType::C64 => Ok(Self::C64(Complex64::new(0.0, 0.0))),
320 DType::I32 | DType::I64 | DType::Bool | DType::External(_) => Err(validation(
321 "ContractionScalar::zero",
322 ValidationError::DTypeMismatch {
323 expected: dtype,
324 actual: DType::F32,
325 },
326 )),
327 }
328 }
329}
330
331/// Output-update semantics for dot-general accumulation.
332///
333/// This keeps contraction axes in [`DotGeneralConfig`] and output update
334/// semantics here, so cached and non-cached backend traits can share the same
335/// accumulation contract.
336///
337/// # Examples
338///
339/// ```rust
340/// use tenferro_tensor::{ContractionScalar, DotGeneralAccumulation, DType};
341///
342/// let accum = DotGeneralAccumulation::overwrite(DType::F64).unwrap();
343/// assert_eq!(accum.alpha, ContractionScalar::F64(1.0));
344/// assert_eq!(accum.beta, ContractionScalar::F64(0.0));
345/// ```
346#[derive(Clone, Copy, Debug, PartialEq)]
347pub struct DotGeneralAccumulation {
348 pub lhs_conj: bool,
349 pub rhs_conj: bool,
350 pub alpha: ContractionScalar,
351 pub beta: ContractionScalar,
352}
353
354/// One matrix multiply in a grouped GEMM over shared flat buffers.
355///
356/// Offsets are element offsets into the corresponding shared lhs, rhs, and
357/// output buffers. Each job computes a column-major `rows x cols` output block
358/// from a column-major `rows x contracted` lhs block and a column-major
359/// `contracted x cols` rhs block.
360///
361/// Provider implementations receive these descriptors through the public
362/// grouped-GEMM request accessor. The engine validates ranges and pairwise
363/// output disjointness before provider entry.
364#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
365pub struct GroupedGemmJob {
366 out_offset: usize,
367 lhs_offset: usize,
368 rhs_offset: usize,
369 rows: usize,
370 contracted: usize,
371 cols: usize,
372}
373
374impl GroupedGemmJob {
375 /// Construct a column-major grouped-GEMM job over shared flat buffers.
376 #[allow(clippy::too_many_arguments)]
377 pub fn new(
378 out_offset: usize,
379 lhs_offset: usize,
380 rhs_offset: usize,
381 rows: usize,
382 contracted: usize,
383 cols: usize,
384 ) -> Self {
385 Self {
386 out_offset,
387 lhs_offset,
388 rhs_offset,
389 rows,
390 contracted,
391 cols,
392 }
393 }
394
395 /// Return the output element offset.
396 pub fn out_offset(&self) -> usize {
397 self.out_offset
398 }
399
400 /// Return the left-input element offset.
401 pub fn lhs_offset(&self) -> usize {
402 self.lhs_offset
403 }
404
405 /// Return the right-input element offset.
406 pub fn rhs_offset(&self) -> usize {
407 self.rhs_offset
408 }
409
410 /// Return the output row count.
411 pub fn rows(&self) -> usize {
412 self.rows
413 }
414
415 /// Return the contracted dimension.
416 pub fn contracted(&self) -> usize {
417 self.contracted
418 }
419
420 /// Return the output column count.
421 pub fn cols(&self) -> usize {
422 self.cols
423 }
424}
425
426/// Shared scalar/update metadata for grouped GEMM execution.
427#[doc(hidden)]
428#[derive(Clone, Copy, Debug, PartialEq)]
429pub struct GroupedGemmConfig<'a> {
430 jobs: &'a [GroupedGemmJob],
431 accumulation: DotGeneralAccumulation,
432}
433
434impl<'a> GroupedGemmConfig<'a> {
435 pub fn new(jobs: &'a [GroupedGemmJob], accumulation: DotGeneralAccumulation) -> Self {
436 Self { jobs, accumulation }
437 }
438
439 pub fn jobs(&self) -> &'a [GroupedGemmJob] {
440 self.jobs
441 }
442
443 pub fn accumulation(&self) -> DotGeneralAccumulation {
444 self.accumulation
445 }
446}
447
448impl DotGeneralAccumulation {
449 fn identity(
450 op: &'static str,
451 dtype: DType,
452 multiplicative: bool,
453 ) -> crate::Result<ContractionScalar> {
454 let result = if multiplicative {
455 ContractionScalar::one(dtype)
456 } else {
457 ContractionScalar::zero(dtype)
458 };
459 result.map_err(|error| match error {
460 Error::Validation { source, .. } => validation(op, source),
461 error => error,
462 })
463 }
464
465 /// Return overwrite semantics, `out = lhs dot rhs`, for `dtype`.
466 ///
467 /// # Examples
468 ///
469 /// ```rust
470 /// use tenferro_tensor::{ContractionScalar, DotGeneralAccumulation, DType};
471 ///
472 /// let accum = DotGeneralAccumulation::overwrite(DType::F64).unwrap();
473 /// assert_eq!(accum.alpha, ContractionScalar::F64(1.0));
474 /// assert_eq!(accum.beta, ContractionScalar::F64(0.0));
475 /// ```
476 ///
477 /// # Errors
478 ///
479 /// Returns [`crate::Error::Validation`] with a
480 /// [`crate::ValidationError::DTypeMismatch`] source when `dtype` does not
481 /// support contraction scalar identities.
482 pub fn overwrite(dtype: DType) -> crate::Result<Self> {
483 Ok(Self {
484 lhs_conj: false,
485 rhs_conj: false,
486 alpha: Self::identity("DotGeneralAccumulation::overwrite", dtype, true)?,
487 beta: Self::identity("DotGeneralAccumulation::overwrite", dtype, false)?,
488 })
489 }
490
491 /// Return additive update semantics, `out += lhs dot rhs`, for `dtype`.
492 ///
493 /// # Examples
494 ///
495 /// ```rust
496 /// use tenferro_tensor::{ContractionScalar, DType, DotGeneralAccumulation};
497 ///
498 /// let accum = DotGeneralAccumulation::add_to(DType::F64)?;
499 /// assert_eq!(accum.alpha, ContractionScalar::F64(1.0));
500 /// assert_eq!(accum.beta, ContractionScalar::F64(1.0));
501 /// # Ok::<(), tenferro_tensor::Error>(())
502 /// ```
503 /// # Errors
504 ///
505 /// Returns [`crate::Error::Validation`] with a
506 /// [`crate::ValidationError::DTypeMismatch`] source when `dtype` does not
507 /// support contraction scalar identities.
508 pub fn add_to(dtype: DType) -> crate::Result<Self> {
509 Ok(Self {
510 lhs_conj: false,
511 rhs_conj: false,
512 alpha: Self::identity("DotGeneralAccumulation::add_to", dtype, true)?,
513 beta: Self::identity("DotGeneralAccumulation::add_to", dtype, true)?,
514 })
515 }
516
517 /// Return scaled update semantics, `out = alpha * lhs dot rhs + beta * out`.
518 ///
519 /// # Examples
520 ///
521 /// ```rust
522 /// use tenferro_tensor::{ContractionScalar, DotGeneralAccumulation};
523 ///
524 /// let accum = DotGeneralAccumulation::scaled(
525 /// ContractionScalar::F32(0.5),
526 /// ContractionScalar::F32(2.0),
527 /// )?;
528 /// assert_eq!(accum.alpha, ContractionScalar::F32(0.5));
529 /// # Ok::<(), tenferro_tensor::Error>(())
530 /// ```
531 /// # Errors
532 ///
533 /// Returns [`crate::Error::Validation`] with a
534 /// [`crate::ValidationError::DTypeMismatch`] source when `alpha` and `beta`
535 /// have different dtypes.
536 pub fn scaled(alpha: ContractionScalar, beta: ContractionScalar) -> crate::Result<Self> {
537 if alpha.dtype() != beta.dtype() {
538 return Err(validation(
539 "DotGeneralAccumulation::scaled",
540 ValidationError::DTypeMismatch {
541 expected: alpha.dtype(),
542 actual: beta.dtype(),
543 },
544 ));
545 }
546 Ok(Self {
547 lhs_conj: false,
548 rhs_conj: false,
549 alpha,
550 beta,
551 })
552 }
553
554 fn validate_for_dtype(self, dtype: DType) -> crate::Result<()> {
555 for scalar in [self.alpha, self.beta] {
556 if scalar.dtype() != dtype {
557 return Err(validation(
558 "dot_general",
559 ValidationError::DTypeMismatch {
560 expected: scalar.dtype(),
561 actual: dtype,
562 },
563 ));
564 }
565 }
566 Ok(())
567 }
568}
569
570#[doc(hidden)]
571pub fn validate_dot_general_accumulation(
572 lhs: &TensorRead<'_>,
573 rhs: &TensorRead<'_>,
574 config: &DotGeneralConfig,
575 accumulation: DotGeneralAccumulation,
576 out: &TensorWrite<'_>,
577 op: &'static str,
578) -> crate::Result<Vec<usize>> {
579 let shape = validate_dot_general_read_into(lhs, rhs, config, out, op)?;
580 accumulation.validate_for_dtype(lhs.dtype())?;
581 Ok(shape)
582}
583
584#[doc(hidden)]
585pub fn dot_general_accum_via_temp<B: TensorDot + ?Sized>(
586 backend: &mut B,
587 lhs: TensorRead<'_>,
588 rhs: TensorRead<'_>,
589 config: &DotGeneralConfig,
590 accumulation: DotGeneralAccumulation,
591 mut out: TensorWrite<'_>,
592) -> crate::Result<()> {
593 validate_dot_general_accumulation(&lhs, &rhs, config, accumulation, &out, "dot_general")?;
594 let dot = backend.dot_general_with_conj_read(
595 lhs,
596 rhs,
597 config,
598 accumulation.lhs_conj,
599 accumulation.rhs_conj,
600 )?;
601 accumulate_dot_result_into(&dot, accumulation, &mut out)
602}
603
604fn grouped_checked_product(
605 op: &'static str,
606 role: &'static str,
607 dims: &[usize],
608) -> crate::Result<usize> {
609 dims.iter().try_fold(1usize, |acc, &dim| {
610 acc.checked_mul(dim).ok_or_else(|| {
611 invalid_argument(
612 op,
613 role,
614 format!("logical element count overflows usize for shape {dims:?}"),
615 )
616 })
617 })
618}
619
620fn checked_gemm_span(
621 op: &'static str,
622 role: &'static str,
623 offset: usize,
624 rows: usize,
625 cols: usize,
626) -> crate::Result<Option<std::ops::Range<usize>>> {
627 let len = rows.checked_mul(cols).ok_or_else(|| {
628 invalid_argument(
629 op,
630 role,
631 format!("matrix element count overflows usize: rows={rows} cols={cols}"),
632 )
633 })?;
634 if len == 0 {
635 return Ok(None);
636 }
637 let end = offset.checked_add(len).ok_or_else(|| {
638 invalid_argument(
639 op,
640 role,
641 format!("matrix range overflows usize: offset={offset} len={len}"),
642 )
643 })?;
644 Ok(Some(offset..end))
645}
646
647fn validate_grouped_gemm_range(
648 op: &'static str,
649 role: &'static str,
650 len: usize,
651 range: Option<std::ops::Range<usize>>,
652) -> crate::Result<()> {
653 let Some(range) = range else {
654 return Ok(());
655 };
656 if range.end > len {
657 return Err(invalid_argument(
658 op,
659 role,
660 format!(
661 "matrix range {}..{} exceeds shared buffer logical length {len}",
662 range.start, range.end
663 ),
664 ));
665 }
666 Ok(())
667}
668
669#[doc(hidden)]
670pub fn validate_grouped_gemm(
671 lhs: &TensorRead<'_>,
672 rhs: &TensorRead<'_>,
673 out: &TensorWrite<'_>,
674 config: &GroupedGemmConfig<'_>,
675 op: &'static str,
676) -> crate::Result<()> {
677 if lhs.dtype() != rhs.dtype() {
678 return Err(validation(
679 op,
680 ValidationError::DTypeMismatch {
681 expected: lhs.dtype(),
682 actual: rhs.dtype(),
683 },
684 ));
685 }
686 if lhs.dtype() != out.dtype() {
687 return Err(validation(
688 op,
689 ValidationError::DTypeMismatch {
690 expected: lhs.dtype(),
691 actual: out.dtype(),
692 },
693 ));
694 }
695 config.accumulation.validate_for_dtype(lhs.dtype())?;
696
697 let lhs_len = grouped_checked_product(op, "lhs", lhs.shape())?;
698 let rhs_len = grouped_checked_product(op, "rhs", rhs.shape())?;
699 let out_len = grouped_checked_product(op, "out", out.shape())?;
700 // Grouped GEMM job count is runtime-controlled and can be large. Keep the
701 // validation ranges in a reserved Vec, not SmallVec, so arbitrary batches
702 // avoid inline-capacity tuning and can be sorted for O(n log n) overlap
703 // validation.
704 let mut out_ranges = Vec::<(usize, std::ops::Range<usize>)>::with_capacity(config.jobs.len());
705 for (idx, job) in config.jobs.iter().enumerate() {
706 validate_grouped_gemm_range(
707 op,
708 "lhs",
709 lhs_len,
710 checked_gemm_span(op, "lhs", job.lhs_offset, job.rows, job.contracted)?,
711 )?;
712 validate_grouped_gemm_range(
713 op,
714 "rhs",
715 rhs_len,
716 checked_gemm_span(op, "rhs", job.rhs_offset, job.contracted, job.cols)?,
717 )?;
718 let out_range = checked_gemm_span(op, "out", job.out_offset, job.rows, job.cols)?;
719 validate_grouped_gemm_range(op, "out", out_len, out_range.clone())?;
720 if let Some(out_range) = out_range {
721 out_ranges.push((idx, out_range));
722 }
723 }
724 out_ranges.sort_unstable_by_key(|(_, range)| range.start);
725 for pair in out_ranges.windows(2) {
726 let (prev_idx, previous) = &pair[0];
727 let (idx, current) = &pair[1];
728 if previous.end > current.start {
729 return Err(invalid_argument(
730 op,
731 "jobs",
732 format!(
733 "grouped GEMM output range for job {idx} overlaps job {prev_idx} range {}..{}",
734 previous.start, previous.end
735 ),
736 ));
737 }
738 }
739 Ok(())
740}
741
742fn add_element_offsets(
743 op: &'static str,
744 base: isize,
745 offset: usize,
746 role: &'static str,
747) -> crate::Result<isize> {
748 let offset = isize::try_from(offset).map_err(|_| {
749 invalid_argument(op, role, format!("offset {offset} does not fit in isize"))
750 })?;
751 base.checked_add(offset).ok_or_else(|| {
752 invalid_argument(
753 op,
754 role,
755 format!("offset overflows isize: base={base} offset={offset}"),
756 )
757 })
758}
759
760fn dim_stride(op: &'static str, dim: usize, role: &'static str) -> crate::Result<isize> {
761 isize::try_from(dim).map_err(|_| {
762 invalid_argument(
763 op,
764 role,
765 format!("leading dimension {dim} does not fit in isize"),
766 )
767 })
768}
769
770fn typed_read_storage<'a, T: crate::TensorScalar>(
771 tensor: &'a TypedTensor<T>,
772 op: &'static str,
773) -> crate::Result<(&'a [T], isize)> {
774 tensor.host_data().map(|data| (data, 0)).map_err(|_| {
775 crate::Error::runtime_state(
776 op,
777 "grouped GEMM default path requires host-backed tensor storage",
778 )
779 })
780}
781
782fn grouped_gemm_default_config() -> DotGeneralConfig {
783 // DotGeneralConfig owns Vec fields, so this rank-2 fallback config follows
784 // that API boundary rather than introducing SmallVec locally.
785 DotGeneralConfig {
786 lhs_contracting_dims: vec![1],
787 rhs_contracting_dims: vec![0],
788 lhs_batch_dims: Vec::new(),
789 rhs_batch_dims: Vec::new(),
790 }
791}
792
793trait GroupedGemmDType<T> {
794 fn wrap_read(view: TypedTensorView<'_, T>) -> TensorView<'_>;
795 fn wrap_write(view: TypedTensorViewMut<'_, T>) -> TensorViewMut<'_>;
796}
797
798struct GroupedF32;
799struct GroupedF64;
800struct GroupedC32;
801struct GroupedC64;
802
803impl GroupedGemmDType<f32> for GroupedF32 {
804 fn wrap_read(view: TypedTensorView<'_, f32>) -> TensorView<'_> {
805 TensorView::F32(view)
806 }
807
808 fn wrap_write(view: TypedTensorViewMut<'_, f32>) -> TensorViewMut<'_> {
809 TensorViewMut::F32(view)
810 }
811}
812
813impl GroupedGemmDType<f64> for GroupedF64 {
814 fn wrap_read(view: TypedTensorView<'_, f64>) -> TensorView<'_> {
815 TensorView::F64(view)
816 }
817
818 fn wrap_write(view: TypedTensorViewMut<'_, f64>) -> TensorViewMut<'_> {
819 TensorViewMut::F64(view)
820 }
821}
822
823impl GroupedGemmDType<Complex32> for GroupedC32 {
824 fn wrap_read(view: TypedTensorView<'_, Complex32>) -> TensorView<'_> {
825 TensorView::C32(view)
826 }
827
828 fn wrap_write(view: TypedTensorViewMut<'_, Complex32>) -> TensorViewMut<'_> {
829 TensorViewMut::C32(view)
830 }
831}
832
833impl GroupedGemmDType<Complex64> for GroupedC64 {
834 fn wrap_read(view: TypedTensorView<'_, Complex64>) -> TensorView<'_> {
835 TensorView::C64(view)
836 }
837
838 fn wrap_write(view: TypedTensorViewMut<'_, Complex64>) -> TensorViewMut<'_> {
839 TensorViewMut::C64(view)
840 }
841}
842
843#[allow(clippy::too_many_arguments)]
844fn grouped_gemm_default_loop<B, T, V>(
845 backend: &mut B,
846 lhs_data: &[T],
847 lhs_base: isize,
848 rhs_data: &[T],
849 rhs_base: isize,
850 out_view: &mut TypedTensorViewMut<'_, T>,
851 config: &GroupedGemmConfig<'_>,
852) -> crate::Result<()>
853where
854 B: TensorDot + ?Sized,
855 T: 'static,
856 V: GroupedGemmDType<T>,
857{
858 let op = "grouped_gemm";
859 let dot_config = grouped_gemm_default_config();
860 for job in config.jobs {
861 let lhs_offset = add_element_offsets(op, lhs_base, job.lhs_offset, "lhs")?;
862 let rhs_offset = add_element_offsets(op, rhs_base, job.rhs_offset, "rhs")?;
863 let out_offset = add_element_offsets(op, out_view.offset(), job.out_offset, "out")?;
864 let lhs_rows = dim_stride(op, job.rows, "lhs")?;
865 let rhs_rows = dim_stride(op, job.contracted, "rhs")?;
866 let out_rows = dim_stride(op, job.rows, "out")?;
867 // TypedTensorView constructors own Vec shape/stride metadata. These
868 // fallback rank-2 views are short-lived, but SmallVec is not usable
869 // without changing the view API.
870 let lhs_matrix = TypedTensorView::from_slice(
871 vec![job.rows, job.contracted],
872 vec![1, lhs_rows],
873 lhs_offset,
874 lhs_data,
875 )?;
876 let rhs_matrix = TypedTensorView::from_slice(
877 vec![job.contracted, job.cols],
878 vec![1, rhs_rows],
879 rhs_offset,
880 rhs_data,
881 )?;
882 let out_storage = out_view.host_storage_mut()?;
883 let out_matrix = TypedTensorViewMut::from_slice(
884 vec![job.rows, job.cols],
885 vec![1, out_rows],
886 out_offset,
887 out_storage,
888 )?;
889 backend.dot_general_read_into_accum(
890 TensorRead::from_view(V::wrap_read(lhs_matrix)),
891 TensorRead::from_view(V::wrap_read(rhs_matrix)),
892 &dot_config,
893 config.accumulation,
894 TensorWrite::from_view(V::wrap_write(out_matrix)),
895 )?;
896 }
897 Ok(())
898}
899
900#[doc(hidden)]
901pub fn grouped_gemm_via_sequential<B>(
902 backend: &mut B,
903 lhs: TensorRead<'_>,
904 rhs: TensorRead<'_>,
905 config: &GroupedGemmConfig<'_>,
906 mut out: TensorWrite<'_>,
907) -> crate::Result<()>
908where
909 B: TensorDot + ?Sized,
910{
911 validate_grouped_gemm(&lhs, &rhs, &out, config, "grouped_gemm")?;
912 macro_rules! dispatch {
913 ($variant:ident, $scalar:ty, $wrapper:ty) => {
914 match (&lhs, &rhs, &mut out) {
915 (TensorRead::Tensor(a), TensorRead::Tensor(b), TensorWrite::Tensor(c))
916 if a.dtype() == <$scalar as crate::TensorScalar>::dtype()
917 && b.dtype() == <$scalar as crate::TensorScalar>::dtype()
918 && c.dtype() == <$scalar as crate::TensorScalar>::dtype() =>
919 {
920 let a = a
921 .as_typed::<$scalar>()
922 .expect("the dtype guard selects this arm");
923 let b = b
924 .as_typed::<$scalar>()
925 .expect("the dtype guard selects this arm");
926 let c = c
927 .as_typed_mut::<$scalar>()
928 .expect("the dtype guard selects this arm");
929 let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
930 let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
931 let mut c_view = c.as_view_mut();
932 return grouped_gemm_default_loop::<_, _, $wrapper>(
933 backend,
934 a_data,
935 a_base,
936 b_data,
937 b_base,
938 &mut c_view,
939 config,
940 );
941 }
942 (
943 TensorRead::Tensor(a),
944 TensorRead::View(TensorView::$variant(b)),
945 TensorWrite::Tensor(c),
946 ) if a.dtype() == <$scalar as crate::TensorScalar>::dtype()
947 && c.dtype() == <$scalar as crate::TensorScalar>::dtype() =>
948 {
949 let a = a
950 .as_typed::<$scalar>()
951 .expect("the dtype guard selects this arm");
952 let c = c
953 .as_typed_mut::<$scalar>()
954 .expect("the dtype guard selects this arm");
955 let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
956 let mut c_view = c.as_view_mut();
957 return grouped_gemm_default_loop::<_, _, $wrapper>(
958 backend,
959 a_data,
960 a_base,
961 b.host_storage()?,
962 b.offset(),
963 &mut c_view,
964 config,
965 );
966 }
967 (
968 TensorRead::View(TensorView::$variant(a)),
969 TensorRead::Tensor(b),
970 TensorWrite::Tensor(c),
971 ) if b.dtype() == <$scalar as crate::TensorScalar>::dtype()
972 && c.dtype() == <$scalar as crate::TensorScalar>::dtype() =>
973 {
974 let b = b
975 .as_typed::<$scalar>()
976 .expect("the dtype guard selects this arm");
977 let c = c
978 .as_typed_mut::<$scalar>()
979 .expect("the dtype guard selects this arm");
980 let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
981 let mut c_view = c.as_view_mut();
982 return grouped_gemm_default_loop::<_, _, $wrapper>(
983 backend,
984 a.host_storage()?,
985 a.offset(),
986 b_data,
987 b_base,
988 &mut c_view,
989 config,
990 );
991 }
992 (
993 TensorRead::View(TensorView::$variant(a)),
994 TensorRead::View(TensorView::$variant(b)),
995 TensorWrite::Tensor(c),
996 ) if c.dtype() == <$scalar as crate::TensorScalar>::dtype() => {
997 let c = c
998 .as_typed_mut::<$scalar>()
999 .expect("the dtype guard selects this arm");
1000 let mut c_view = c.as_view_mut();
1001 return grouped_gemm_default_loop::<_, _, $wrapper>(
1002 backend,
1003 a.host_storage()?,
1004 a.offset(),
1005 b.host_storage()?,
1006 b.offset(),
1007 &mut c_view,
1008 config,
1009 );
1010 }
1011 (
1012 TensorRead::Tensor(a),
1013 TensorRead::Tensor(b),
1014 TensorWrite::View(TensorViewMut::$variant(c)),
1015 ) if a.dtype() == <$scalar as crate::TensorScalar>::dtype()
1016 && b.dtype() == <$scalar as crate::TensorScalar>::dtype() =>
1017 {
1018 let a = a
1019 .as_typed::<$scalar>()
1020 .expect("the dtype guard selects this arm");
1021 let b = b
1022 .as_typed::<$scalar>()
1023 .expect("the dtype guard selects this arm");
1024 let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
1025 let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
1026 return grouped_gemm_default_loop::<_, _, $wrapper>(
1027 backend, a_data, a_base, b_data, b_base, c, config,
1028 );
1029 }
1030 (
1031 TensorRead::Tensor(a),
1032 TensorRead::View(TensorView::$variant(b)),
1033 TensorWrite::View(TensorViewMut::$variant(c)),
1034 ) if a.dtype() == <$scalar as crate::TensorScalar>::dtype() => {
1035 let a = a
1036 .as_typed::<$scalar>()
1037 .expect("the dtype guard selects this arm");
1038 let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
1039 return grouped_gemm_default_loop::<_, _, $wrapper>(
1040 backend,
1041 a_data,
1042 a_base,
1043 b.host_storage()?,
1044 b.offset(),
1045 c,
1046 config,
1047 );
1048 }
1049 (
1050 TensorRead::View(TensorView::$variant(a)),
1051 TensorRead::Tensor(b),
1052 TensorWrite::View(TensorViewMut::$variant(c)),
1053 ) if b.dtype() == <$scalar as crate::TensorScalar>::dtype() => {
1054 let b = b
1055 .as_typed::<$scalar>()
1056 .expect("the dtype guard selects this arm");
1057 let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
1058 return grouped_gemm_default_loop::<_, _, $wrapper>(
1059 backend,
1060 a.host_storage()?,
1061 a.offset(),
1062 b_data,
1063 b_base,
1064 c,
1065 config,
1066 );
1067 }
1068 (
1069 TensorRead::View(TensorView::$variant(a)),
1070 TensorRead::View(TensorView::$variant(b)),
1071 TensorWrite::View(TensorViewMut::$variant(c)),
1072 ) => {
1073 return grouped_gemm_default_loop::<_, _, $wrapper>(
1074 backend,
1075 a.host_storage()?,
1076 a.offset(),
1077 b.host_storage()?,
1078 b.offset(),
1079 c,
1080 config,
1081 );
1082 }
1083 _ => {}
1084 }
1085 };
1086 }
1087
1088 dispatch!(F32, f32, GroupedF32);
1089 dispatch!(F64, f64, GroupedF64);
1090 dispatch!(C32, Complex32, GroupedC32);
1091 dispatch!(C64, Complex64, GroupedC64);
1092 Err(validation(
1093 "grouped_gemm",
1094 ValidationError::DTypeMismatch {
1095 expected: lhs.dtype(),
1096 actual: out.dtype(),
1097 },
1098 ))
1099}
1100
1101fn grouped_gemm_default<B>(
1102 backend: &mut B,
1103 lhs: TensorRead<'_>,
1104 rhs: TensorRead<'_>,
1105 config: &GroupedGemmConfig<'_>,
1106 out: TensorWrite<'_>,
1107) -> crate::Result<()>
1108where
1109 B: TensorDot + ?Sized,
1110{
1111 grouped_gemm_via_sequential(backend, lhs, rhs, config, out)
1112}
1113
1114#[doc(hidden)]
1115pub fn accumulate_dot_result_into(
1116 dot: &Tensor,
1117 accumulation: DotGeneralAccumulation,
1118 out: &mut TensorWrite<'_>,
1119) -> crate::Result<()> {
1120 macro_rules! dispatch {
1121 ($variant:ident, $ty:ty) => {
1122 if dot.dtype() == <$ty as crate::TensorScalar>::dtype() {
1123 let (ContractionScalar::$variant(alpha), ContractionScalar::$variant(beta)) =
1124 (accumulation.alpha, accumulation.beta)
1125 else {
1126 return Err(validation(
1127 "dot_general",
1128 ValidationError::DTypeMismatch {
1129 expected: dot.dtype(),
1130 actual: accumulation.alpha.dtype(),
1131 },
1132 ));
1133 };
1134 let dot = dot
1135 .as_typed::<$ty>()
1136 .expect("the dtype guard selects this arm");
1137 match out {
1138 TensorWrite::Tensor(out) => {
1139 let out = out
1140 .as_typed_mut::<$ty>()
1141 .expect("the dtype guard selects this arm");
1142 let mut out = out.as_view_mut();
1143 accumulate_typed(dot.as_slice()?, alpha, beta, &mut out)?;
1144 return Ok(());
1145 }
1146 TensorWrite::View(crate::TensorViewMut::$variant(out)) => {
1147 accumulate_typed(dot.as_slice()?, alpha, beta, out)?;
1148 return Ok(());
1149 }
1150 _ => {}
1151 }
1152 }
1153 };
1154 }
1155
1156 dispatch!(F32, f32);
1157 dispatch!(F64, f64);
1158 dispatch!(C32, Complex32);
1159 dispatch!(C64, Complex64);
1160
1161 Err(validation(
1162 "dot_general",
1163 ValidationError::DTypeMismatch {
1164 expected: accumulation.alpha.dtype(),
1165 actual: dot.dtype(),
1166 },
1167 ))
1168}
1169
1170fn accumulate_typed<T>(
1171 dot: &[T],
1172 alpha: T,
1173 beta: T,
1174 out: &mut TypedTensorViewMut<'_, T>,
1175) -> crate::Result<()>
1176where
1177 T: Copy
1178 + PartialEq
1179 + std::ops::Add<Output = T>
1180 + std::ops::Mul<Output = T>
1181 + num_traits::Zero
1182 + 'static,
1183{
1184 let beta_is_zero = beta == T::zero();
1185 if let Some(output) = compact_host_accumulation_slice(out, dot.len())? {
1186 for (output, dot_value) in output.iter_mut().zip(dot.iter().copied()) {
1187 // INVARIANT: beta == 0 follows BLAS GEMM semantics and does not read
1188 // the existing output element; beta != 0 requires an initialized
1189 // TensorWrite target and performs a read-modify-write update.
1190 *output = if beta_is_zero {
1191 alpha * dot_value
1192 } else {
1193 alpha * dot_value + beta * *output
1194 };
1195 }
1196 return Ok(());
1197 }
1198
1199 for (linear, dot_value) in dot.iter().copied().enumerate() {
1200 let indices = flat_to_multi_for_shape(out.shape(), linear);
1201 let output = out.get_mut(&indices).ok_or_else(|| {
1202 invalid_argument(
1203 "dot_general",
1204 "output",
1205 format!("index {indices:?} is outside accumulation target"),
1206 )
1207 })?;
1208 // INVARIANT: beta == 0 follows BLAS GEMM semantics and does not read
1209 // the existing output element; beta != 0 requires an initialized
1210 // TensorWrite target and performs a read-modify-write update.
1211 *output = if beta_is_zero {
1212 alpha * dot_value
1213 } else {
1214 alpha * dot_value + beta * *output
1215 };
1216 }
1217 Ok(())
1218}
1219
1220fn compact_host_accumulation_slice<'a, T: 'static>(
1221 out: &'a mut TypedTensorViewMut<'_, T>,
1222 expected_len: usize,
1223) -> crate::Result<Option<&'a mut [T]>> {
1224 if out.backend_buffer().is_some()
1225 || out.n_elements() != expected_len
1226 || !out.is_col_major_contiguous()?
1227 {
1228 return Ok(None);
1229 }
1230
1231 let start = usize::try_from(out.offset()).map_err(|_| {
1232 invalid_argument("dot_general", "output", "compact output offset is negative")
1233 })?;
1234 let end = start
1235 .checked_add(expected_len)
1236 .ok_or_else(|| validation("dot_general", ValidationError::IntegerOverflow))?;
1237 out.host_storage_mut()?
1238 .get_mut(start..end)
1239 .map(Some)
1240 .ok_or_else(|| {
1241 invalid_argument(
1242 "dot_general",
1243 "output",
1244 "compact output is outside its backing storage",
1245 )
1246 })
1247}
1248
1249fn flat_to_multi_for_shape(shape: &[usize], mut linear: usize) -> Vec<usize> {
1250 let mut indices = Vec::with_capacity(shape.len());
1251 for &dim in shape {
1252 if dim == 0 {
1253 indices.push(0);
1254 } else {
1255 indices.push(linear % dim);
1256 linear /= dim;
1257 }
1258 }
1259 indices
1260}
1261
1262/// Canonical elementwise fusion plan shared between segmented execution and backends.
1263#[doc(hidden)]
1264#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1265pub struct ElementwiseFusionPlan {
1266 dtype: crate::DType,
1267 input_count: usize,
1268 // Keep view metadata in Vecs. A/B benchmarking on the broadcast_mul
1269 // path showed SmallVec made this metadata path about 6-7% slower.
1270 input_views: Vec<ElementwiseFusionInputView>,
1271 outputs: Vec<usize>,
1272 ops: Vec<ElementwiseFusionInst>,
1273}
1274
1275/// Metadata-only view applied to one backend fusion input.
1276#[doc(hidden)]
1277#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1278pub enum ElementwiseFusionInputView {
1279 Identity,
1280 BroadcastInDim {
1281 // Vec is intentional here; see ElementwiseFusionPlan::input_views.
1282 shape: Vec<usize>,
1283 dims: Vec<usize>,
1284 },
1285}
1286
1287/// One node in a canonical elementwise fusion plan.
1288#[doc(hidden)]
1289#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1290pub struct ElementwiseFusionInst {
1291 op: ElementwiseFusionOp,
1292 inputs: Vec<usize>,
1293}
1294
1295tenferro_core_ops::define_elementwise_fusion_op!();
1296
1297impl ElementwiseFusionPlan {
1298 /// Build a backend elementwise fusion plan.
1299 ///
1300 /// # Examples
1301 ///
1302 /// ```rust
1303 /// use tenferro_tensor::backend::{
1304 /// ElementwiseFusionInst, ElementwiseFusionOp, ElementwiseFusionPlan,
1305 /// };
1306 /// use tenferro_tensor::DType;
1307 ///
1308 /// let plan = ElementwiseFusionPlan::new(
1309 /// DType::F64,
1310 /// 2,
1311 /// vec![2],
1312 /// vec![ElementwiseFusionInst::new(ElementwiseFusionOp::Add, vec![0, 1])],
1313 /// );
1314 /// assert_eq!(plan.input_count(), 2);
1315 /// ```
1316 pub fn new(
1317 dtype: crate::DType,
1318 input_count: usize,
1319 outputs: Vec<usize>,
1320 ops: Vec<ElementwiseFusionInst>,
1321 ) -> Self {
1322 Self::with_input_views(
1323 dtype,
1324 vec![ElementwiseFusionInputView::Identity; input_count],
1325 outputs,
1326 ops,
1327 )
1328 }
1329
1330 /// Build a backend elementwise fusion plan with input view metadata.
1331 ///
1332 /// # Examples
1333 ///
1334 /// ```rust
1335 /// use tenferro_tensor::backend::{
1336 /// ElementwiseFusionInputView, ElementwiseFusionInst, ElementwiseFusionOp,
1337 /// ElementwiseFusionPlan,
1338 /// };
1339 /// use tenferro_tensor::DType;
1340 ///
1341 /// let plan = ElementwiseFusionPlan::with_input_views(
1342 /// DType::F64,
1343 /// vec![ElementwiseFusionInputView::broadcast_in_dim(vec![2, 3], vec![0])],
1344 /// vec![1],
1345 /// vec![ElementwiseFusionInst::new(ElementwiseFusionOp::Negate, vec![0])],
1346 /// );
1347 /// assert_eq!(plan.input_count(), 1);
1348 /// ```
1349 pub fn with_input_views(
1350 dtype: crate::DType,
1351 input_views: impl IntoIterator<Item = ElementwiseFusionInputView>,
1352 outputs: Vec<usize>,
1353 ops: Vec<ElementwiseFusionInst>,
1354 ) -> Self {
1355 let input_views = input_views.into_iter().collect::<Vec<_>>();
1356 let input_count = input_views.len();
1357 Self {
1358 dtype,
1359 input_count,
1360 input_views,
1361 outputs,
1362 ops,
1363 }
1364 }
1365
1366 /// Return the scalar dtype expected by this fusion plan.
1367 ///
1368 /// # Examples
1369 ///
1370 /// ```rust
1371 /// use tenferro_tensor::backend::ElementwiseFusionPlan;
1372 /// use tenferro_tensor::DType;
1373 ///
1374 /// let plan = ElementwiseFusionPlan::new(DType::F32, 0, Vec::new(), Vec::new());
1375 /// assert_eq!(plan.dtype(), DType::F32);
1376 /// ```
1377 pub fn dtype(&self) -> crate::DType {
1378 self.dtype
1379 }
1380
1381 /// Return the number of input tensors expected by this plan.
1382 ///
1383 /// # Examples
1384 ///
1385 /// ```rust
1386 /// use tenferro_tensor::backend::ElementwiseFusionPlan;
1387 /// use tenferro_tensor::DType;
1388 ///
1389 /// let plan = ElementwiseFusionPlan::new(DType::F64, 3, Vec::new(), Vec::new());
1390 /// assert_eq!(plan.input_count(), 3);
1391 /// ```
1392 pub fn input_count(&self) -> usize {
1393 self.input_count
1394 }
1395
1396 /// Return metadata views applied to fusion inputs before executing ops.
1397 ///
1398 /// # Examples
1399 ///
1400 /// ```rust
1401 /// use tenferro_tensor::backend::ElementwiseFusionPlan;
1402 /// use tenferro_tensor::DType;
1403 ///
1404 /// let plan = ElementwiseFusionPlan::new(DType::F64, 2, Vec::new(), Vec::new());
1405 /// assert_eq!(plan.input_views().len(), 2);
1406 /// ```
1407 pub fn input_views(&self) -> &[ElementwiseFusionInputView] {
1408 &self.input_views
1409 }
1410
1411 /// Return the value ids selected as fusion outputs.
1412 ///
1413 /// # Examples
1414 ///
1415 /// ```rust
1416 /// use tenferro_tensor::backend::ElementwiseFusionPlan;
1417 /// use tenferro_tensor::DType;
1418 ///
1419 /// let plan = ElementwiseFusionPlan::new(DType::F64, 0, vec![0], Vec::new());
1420 /// assert_eq!(plan.outputs(), &[0]);
1421 /// ```
1422 pub fn outputs(&self) -> &[usize] {
1423 &self.outputs
1424 }
1425
1426 /// Return the fused elementwise instruction sequence.
1427 ///
1428 /// # Examples
1429 ///
1430 /// ```rust
1431 /// use tenferro_tensor::backend::{
1432 /// ElementwiseFusionInst, ElementwiseFusionOp, ElementwiseFusionPlan,
1433 /// };
1434 /// use tenferro_tensor::DType;
1435 ///
1436 /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Negate, vec![0]);
1437 /// let plan = ElementwiseFusionPlan::new(DType::F64, 1, vec![1], vec![inst]);
1438 /// assert_eq!(plan.ops().len(), 1);
1439 /// ```
1440 pub fn ops(&self) -> &[ElementwiseFusionInst] {
1441 &self.ops
1442 }
1443}
1444
1445impl ElementwiseFusionInputView {
1446 /// Build metadata for a `BroadcastInDim` fusion input view.
1447 ///
1448 /// # Examples
1449 ///
1450 /// ```rust
1451 /// use tenferro_tensor::backend::ElementwiseFusionInputView;
1452 ///
1453 /// let view = ElementwiseFusionInputView::broadcast_in_dim(vec![2, 3], vec![0]);
1454 /// assert!(matches!(view, ElementwiseFusionInputView::BroadcastInDim { .. }));
1455 /// ```
1456 pub fn broadcast_in_dim(
1457 shape: impl IntoIterator<Item = usize>,
1458 dims: impl IntoIterator<Item = usize>,
1459 ) -> Self {
1460 Self::BroadcastInDim {
1461 shape: shape.into_iter().collect(),
1462 dims: dims.into_iter().collect(),
1463 }
1464 }
1465
1466 /// Return true when this fusion input is an identity view.
1467 ///
1468 /// # Examples
1469 ///
1470 /// ```rust
1471 /// use tenferro_tensor::backend::ElementwiseFusionInputView;
1472 ///
1473 /// assert!(ElementwiseFusionInputView::Identity.is_identity());
1474 /// ```
1475 pub fn is_identity(&self) -> bool {
1476 matches!(self, Self::Identity)
1477 }
1478}
1479
1480impl ElementwiseFusionInst {
1481 /// Build a backend elementwise fusion instruction.
1482 ///
1483 /// # Examples
1484 ///
1485 /// ```rust
1486 /// use tenferro_tensor::backend::{ElementwiseFusionInst, ElementwiseFusionOp};
1487 ///
1488 /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Add, vec![0, 1]);
1489 /// assert_eq!(inst.inputs(), &[0, 1]);
1490 /// ```
1491 pub fn new(op: ElementwiseFusionOp, inputs: Vec<usize>) -> Self {
1492 Self { op, inputs }
1493 }
1494
1495 /// Return the elementwise op executed by this instruction.
1496 ///
1497 /// # Examples
1498 ///
1499 /// ```rust
1500 /// use tenferro_tensor::backend::{ElementwiseFusionInst, ElementwiseFusionOp};
1501 ///
1502 /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Negate, vec![0]);
1503 /// assert_eq!(inst.op(), ElementwiseFusionOp::Negate);
1504 /// ```
1505 pub fn op(&self) -> ElementwiseFusionOp {
1506 self.op
1507 }
1508
1509 /// Return this instruction's input value ids.
1510 ///
1511 /// # Examples
1512 ///
1513 /// ```rust
1514 /// use tenferro_tensor::backend::{ElementwiseFusionInst, ElementwiseFusionOp};
1515 ///
1516 /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Multiply, vec![2, 0]);
1517 /// assert_eq!(inst.inputs(), &[2, 0]);
1518 /// ```
1519 pub fn inputs(&self) -> &[usize] {
1520 &self.inputs
1521 }
1522}
1523
1524/// Runtime operation selected by [`TensorElementwise::elementwise_read_into`].
1525#[non_exhaustive]
1526#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1527pub enum ElementwiseReadOp {
1528 /// Binary addition.
1529 Add,
1530 /// Binary subtraction.
1531 Subtract,
1532 /// Binary multiplication.
1533 Multiply,
1534 /// Unary negation.
1535 Negate,
1536 /// Unary conjugation.
1537 Conj,
1538 /// Binary division.
1539 Divide,
1540}
1541
1542impl ElementwiseReadOp {
1543 #[doc(hidden)]
1544 pub fn label(self) -> &'static str {
1545 match self {
1546 Self::Add => "add",
1547 Self::Subtract => "sub",
1548 Self::Multiply => "mul",
1549 Self::Negate => "neg",
1550 Self::Conj => "conj",
1551 Self::Divide => "div",
1552 }
1553 }
1554
1555 #[doc(hidden)]
1556 pub fn arity(self) -> usize {
1557 match self {
1558 Self::Negate | Self::Conj => 1,
1559 Self::Add | Self::Subtract | Self::Multiply | Self::Divide => 2,
1560 }
1561 }
1562}
1563
1564#[derive(Clone, Copy, Debug)]
1565enum StorageIdentity {
1566 Host {
1567 start: usize,
1568 end: usize,
1569 },
1570 Backend {
1571 domain: Option<AllocationDomainId>,
1572 allocation: Option<AllocationId>,
1573 family: &'static str,
1574 object: usize,
1575 },
1576}
1577
1578fn host_storage_identity<T>(data: &[T]) -> StorageIdentity {
1579 let start = data.as_ptr() as usize;
1580 let bytes = std::mem::size_of_val(data);
1581 StorageIdentity::Host {
1582 start,
1583 end: start.saturating_add(bytes),
1584 }
1585}
1586
1587fn backend_storage_identity<T: 'static>(buffer: &dyn crate::BackendStorage<T>) -> StorageIdentity {
1588 StorageIdentity::Backend {
1589 domain: buffer.allocation_domain(),
1590 allocation: buffer.allocation_id(),
1591 family: buffer.backend_family(),
1592 // INVARIANT: every backend buffer is borrowed from the single Box-owned
1593 // root allocation; the data pointer of this trait object is stable for
1594 // that owner and is used only as a fallback when provider identity is
1595 // unavailable.
1596 object: buffer as *const dyn crate::BackendStorage<T> as *const () as usize,
1597 }
1598}
1599
1600fn typed_tensor_storage_identity<T: crate::TensorScalar>(
1601 tensor: &TypedTensor<T>,
1602) -> crate::Result<StorageIdentity> {
1603 if tensor.backend_buffer().is_some() {
1604 let buffer = tensor.backend_buffer().ok_or_else(|| {
1605 crate::Error::runtime_state("typed_tensor_storage_identity", "backend buffer missing")
1606 })?;
1607 Ok(backend_storage_identity(buffer))
1608 } else {
1609 Ok(host_storage_identity(tensor.host_data()?))
1610 }
1611}
1612
1613fn typed_view_storage_identity<T: crate::TensorScalar + 'static>(
1614 view: &TypedTensorView<'_, T>,
1615) -> crate::Result<StorageIdentity> {
1616 match view.backend_buffer() {
1617 Some(buffer) => Ok(backend_storage_identity(buffer)),
1618 None => view.host_storage().map(host_storage_identity),
1619 }
1620}
1621
1622/// The typed tensor behind `value`, or the refusal this module reports for one.
1623///
1624/// Callers reach this from a match on the dtype, so `None` means the tag and the runtime dtype
1625/// disagree rather than a caller mistake.
1626fn identity_operand<T: TensorScalar>(value: &Tensor) -> crate::Result<&TypedTensor<T>> {
1627 value.as_typed::<T>().ok_or_else(|| {
1628 crate::Error::unsupported_dtype(
1629 "storage_identity",
1630 value.dtype(),
1631 "an externally defined payload has no storage identity",
1632 )
1633 })
1634}
1635
1636fn tensor_read_storage_identity(input: &TensorRead<'_>) -> crate::Result<StorageIdentity> {
1637 macro_rules! typed_identity {
1638 ($value:expr) => {
1639 match $value.dtype() {
1640 DType::F32 => typed_tensor_storage_identity(identity_operand::<f32>($value)?),
1641 DType::F64 => typed_tensor_storage_identity(identity_operand::<f64>($value)?),
1642 DType::I32 => typed_tensor_storage_identity(identity_operand::<i32>($value)?),
1643 DType::I64 => typed_tensor_storage_identity(identity_operand::<i64>($value)?),
1644 DType::Bool => typed_tensor_storage_identity(identity_operand::<bool>($value)?),
1645 DType::C32 => typed_tensor_storage_identity(identity_operand::<Complex32>($value)?),
1646 DType::C64 => typed_tensor_storage_identity(identity_operand::<Complex64>($value)?),
1647 // A caller-owned payload has no allocation identity, so it cannot
1648 // take part in an aliasing check.
1649 DType::External(_) => {
1650 return Err(crate::Error::unsupported_dtype(
1651 "storage_identity",
1652 $value.dtype(),
1653 "an externally defined payload has no storage identity",
1654 ));
1655 }
1656 }
1657 };
1658 }
1659 macro_rules! view_identity {
1660 ($value:expr) => {
1661 match $value {
1662 TensorView::F32(value) => typed_view_storage_identity(value),
1663 TensorView::F64(value) => typed_view_storage_identity(value),
1664 TensorView::I32(value) => typed_view_storage_identity(value),
1665 TensorView::I64(value) => typed_view_storage_identity(value),
1666 TensorView::Bool(value) => typed_view_storage_identity(value),
1667 TensorView::C32(value) => typed_view_storage_identity(value),
1668 TensorView::C64(value) => typed_view_storage_identity(value),
1669 }
1670 };
1671 }
1672
1673 match input {
1674 TensorRead::Tensor(tensor) => typed_identity!(tensor),
1675 TensorRead::View(view) => view_identity!(view),
1676 }
1677}
1678
1679fn storage_overlaps(lhs: StorageIdentity, rhs: StorageIdentity) -> bool {
1680 match (lhs, rhs) {
1681 (
1682 StorageIdentity::Host {
1683 start: lhs_start,
1684 end: lhs_end,
1685 },
1686 StorageIdentity::Host {
1687 start: rhs_start,
1688 end: rhs_end,
1689 },
1690 ) => lhs_start < rhs_end && rhs_start < lhs_end,
1691 (
1692 StorageIdentity::Backend {
1693 domain: lhs_domain,
1694 allocation: lhs_allocation,
1695 family: lhs_family,
1696 object: lhs_object,
1697 },
1698 StorageIdentity::Backend {
1699 domain: rhs_domain,
1700 allocation: rhs_allocation,
1701 family: rhs_family,
1702 object: rhs_object,
1703 },
1704 ) => {
1705 lhs_object == rhs_object
1706 || matches!(
1707 (lhs_domain, rhs_domain, lhs_allocation, rhs_allocation),
1708 (Some(lhs_domain), Some(rhs_domain), Some(lhs), Some(rhs))
1709 if lhs_domain == rhs_domain && lhs == rhs
1710 )
1711 || matches!(
1712 (lhs_domain, rhs_domain, lhs_allocation, rhs_allocation),
1713 (None, None, Some(lhs), Some(rhs)) if lhs_family == rhs_family && lhs == rhs
1714 )
1715 }
1716 _ => false,
1717 }
1718}
1719
1720/// Validate that a caller-owned destination does not overlap any read input.
1721///
1722/// The check is intentionally conservative for host views: two views backed by
1723/// the same host allocation are treated as overlapping because the allocation
1724/// identity is the only stable boundary contract available to erased backend
1725/// code. Backend allocations use their domain/allocation identity when the
1726/// provider exposes it.
1727///
1728/// # Errors
1729///
1730/// Returns `tenferro_tensor_core::ValidationError::InvalidArgument` when the
1731/// destination storage overlaps an input, or `Error::RuntimeState` when
1732/// storage identity cannot be established safely.
1733///
1734/// # Examples
1735///
1736/// ```rust
1737/// use tenferro_tensor::{Tensor, TensorRead, TensorWrite};
1738/// use tenferro_tensor::backend::validate_read_into_destination;
1739///
1740/// let input = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
1741/// let mut output = Tensor::from_vec_col_major(vec![1], vec![0.0_f64])?;
1742/// validate_read_into_destination(
1743/// "example",
1744/// &[TensorRead::from_tensor(&input)],
1745/// &TensorWrite::from_tensor(&mut output),
1746/// )?;
1747/// # Ok::<(), tenferro_tensor::Error>(())
1748/// ```
1749pub fn validate_read_into_destination(
1750 op: &'static str,
1751 inputs: &[TensorRead<'_>],
1752 out: &TensorWrite<'_>,
1753) -> crate::Result<()> {
1754 let output_identity = tensor_read_storage_identity(&out.as_read())?;
1755 for (index, input) in inputs.iter().enumerate() {
1756 if storage_overlaps(tensor_read_storage_identity(input)?, output_identity) {
1757 return Err(Error::invalid_argument(
1758 op,
1759 "out",
1760 format!("destination storage overlaps input {index}"),
1761 ));
1762 }
1763 }
1764 Ok(())
1765}
1766
1767/// Execute a read-into elementwise operation through a backend's allocating operations.
1768///
1769/// Backend implementations use this helper when their device-native operation
1770/// APIs already provide the correct placement and copy semantics. It deliberately
1771/// does not inspect or transfer storage across devices.
1772///
1773/// # Errors
1774///
1775/// Returns [`Error::Validation`] for wrong arity or overlapping output storage,
1776/// and propagates the selected backend operation and copy errors.
1777#[doc(hidden)]
1778pub fn elementwise_read_into_via_allocating_ops<B: TensorElementwise + ?Sized>(
1779 backend: &mut B,
1780 op: ElementwiseReadOp,
1781 inputs: &[TensorRead<'_>],
1782 out: TensorWrite<'_>,
1783) -> crate::Result<()> {
1784 if inputs.len() != op.arity() {
1785 return Err(Error::invalid_argument(
1786 op.label(),
1787 "inputs",
1788 format!("expected {} inputs, got {}", op.arity(), inputs.len()),
1789 ));
1790 }
1791 validate_read_into_destination(op.label(), inputs, &out)?;
1792 let result = match op {
1793 ElementwiseReadOp::Add => backend.add_read(inputs[0].clone(), inputs[1].clone())?,
1794 ElementwiseReadOp::Subtract => backend.sub_read(inputs[0].clone(), inputs[1].clone())?,
1795 ElementwiseReadOp::Multiply => backend.mul_read(inputs[0].clone(), inputs[1].clone())?,
1796 ElementwiseReadOp::Negate => backend.neg_read(inputs[0].clone())?,
1797 ElementwiseReadOp::Conj => backend.conj_read(inputs[0].clone())?,
1798 ElementwiseReadOp::Divide => backend.div_read(inputs[0].clone(), inputs[1].clone())?,
1799 };
1800 backend.copy_read_into(TensorRead::from_tensor(&result), out)
1801}
1802
1803/// Elementwise tensor operations.
1804///
1805/// # Examples
1806///
1807/// ```rust
1808/// use tenferro_tensor::TensorElementwise;
1809///
1810/// fn accepts_elementwise<B: TensorElementwise>(_backend: &mut B) {}
1811/// ```
1812pub trait TensorElementwise: TensorStructural {
1813 /// Execute an elementwise operation into caller-owned storage.
1814 ///
1815 /// Every backend must implement this hook with its explicit execution
1816 /// context, placement checks, and output-storage policy. The hook is the
1817 /// backend boundary for borrowed inputs and caller-owned output; it must not
1818 /// silently transfer data between devices.
1819 ///
1820 /// # Examples
1821 ///
1822 /// ```rust
1823 /// use tenferro_tensor::{ElementwiseReadOp, TensorElementwise, TensorRead, TensorWrite};
1824 ///
1825 /// fn run_into<B: TensorElementwise>(
1826 /// backend: &mut B,
1827 /// input: TensorRead<'_>,
1828 /// out: TensorWrite<'_>,
1829 /// ) -> tenferro_tensor::Result<()> {
1830 /// backend.elementwise_read_into(ElementwiseReadOp::Conj, &[input], out)
1831 /// }
1832 /// ```
1833 ///
1834 /// # Errors
1835 ///
1836 /// Returns [`crate::Error::Validation`] for invalid arity, metadata, or
1837 /// overlapping storage. Backend-specific placement, unsupported-operation,
1838 /// and execution errors are returned unchanged by the implementation.
1839 fn elementwise_read_into(
1840 &mut self,
1841 op: ElementwiseReadOp,
1842 inputs: &[TensorRead<'_>],
1843 out: TensorWrite<'_>,
1844 ) -> crate::Result<()>;
1845
1846 /// # Errors
1847 ///
1848 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1849 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1850 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1851 /// backend execution or storage access cannot provide the requested result.
1852 fn add(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1853
1854 /// Elementwise addition accepting either owned tensors or borrowed views.
1855 ///
1856 /// Backends that implement this method must not silently move data across
1857 /// devices. A backend that cannot consume views should return an explicit
1858 /// backend error rather than materializing or transferring implicitly.
1859 ///
1860 /// # Examples
1861 ///
1862 /// ```rust
1863 /// use tenferro_tensor::{Tensor, TensorElementwise, TensorRead};
1864 ///
1865 /// fn add_owned<B: TensorElementwise>(
1866 /// backend: &mut B,
1867 /// lhs: &Tensor,
1868 /// rhs: &Tensor,
1869 /// ) -> tenferro_tensor::Result<Tensor> {
1870 /// backend.add_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
1871 /// }
1872 /// ```
1873 /// # Errors
1874 ///
1875 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1876 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1877 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1878 /// backend execution or storage access cannot provide the requested result.
1879 fn add_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1880 self.add(read_tensor("add", lhs)?, read_tensor("add", rhs)?)
1881 }
1882
1883 /// Overwrite caller-provided output with elementwise addition.
1884 ///
1885 /// `_into` methods never accumulate into the previous output value.
1886 ///
1887 /// # Examples
1888 ///
1889 /// ```rust
1890 /// use tenferro_tensor::{Tensor, TensorElementwise, TensorWrite};
1891 ///
1892 /// fn add_into<B: TensorElementwise>(
1893 /// backend: &mut B,
1894 /// lhs: &Tensor,
1895 /// rhs: &Tensor,
1896 /// mut out: Tensor,
1897 /// ) -> tenferro_tensor::Result<Tensor> {
1898 /// backend.add_into(lhs, rhs, TensorWrite::from_tensor(&mut out))?;
1899 /// Ok(out)
1900 /// }
1901 /// ```
1902 /// # Errors
1903 ///
1904 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1905 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1906 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1907 /// backend execution or storage access cannot provide the requested result.
1908 fn add_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1909 self.add_read_into(
1910 TensorRead::from_tensor(lhs),
1911 TensorRead::from_tensor(rhs),
1912 out,
1913 )
1914 }
1915
1916 /// Overwrite caller-provided output with elementwise addition from reads.
1917 ///
1918 /// # Examples
1919 ///
1920 /// ```rust
1921 /// use tenferro_tensor::{TensorElementwise, TensorRead, TensorWrite};
1922 ///
1923 /// fn add_read_into<B: TensorElementwise>(
1924 /// backend: &mut B,
1925 /// lhs: TensorRead<'_>,
1926 /// rhs: TensorRead<'_>,
1927 /// out: TensorWrite<'_>,
1928 /// ) -> tenferro_tensor::Result<()> {
1929 /// backend.add_read_into(lhs, rhs, out)
1930 /// }
1931 /// ```
1932 /// # Errors
1933 ///
1934 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1935 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1936 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1937 /// backend execution or storage access cannot provide the requested result.
1938 fn add_read_into(
1939 &mut self,
1940 lhs: TensorRead<'_>,
1941 rhs: TensorRead<'_>,
1942 out: TensorWrite<'_>,
1943 ) -> crate::Result<()> {
1944 self.elementwise_read_into(ElementwiseReadOp::Add, &[lhs, rhs], out)
1945 }
1946
1947 /// # Errors
1948 ///
1949 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1950 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1951 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1952 /// backend execution or storage access cannot provide the requested result.
1953 fn sub(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1954
1955 /// Elementwise subtraction accepting either owned tensors or borrowed views.
1956 ///
1957 /// # Examples
1958 ///
1959 /// ```rust
1960 /// use tenferro_tensor::{Tensor, TensorElementwise, TensorRead};
1961 ///
1962 /// fn sub_owned<B: TensorElementwise>(
1963 /// backend: &mut B,
1964 /// lhs: &Tensor,
1965 /// rhs: &Tensor,
1966 /// ) -> tenferro_tensor::Result<Tensor> {
1967 /// backend.sub_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
1968 /// }
1969 /// ```
1970 /// # Errors
1971 ///
1972 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1973 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1974 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1975 /// backend execution or storage access cannot provide the requested result.
1976 fn sub_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1977 self.sub(read_tensor("sub", lhs)?, read_tensor("sub", rhs)?)
1978 }
1979
1980 /// Overwrite caller-provided output with elementwise subtraction.
1981 /// # Errors
1982 ///
1983 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1984 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1985 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1986 /// backend execution or storage access cannot provide the requested result.
1987 fn sub_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1988 self.sub_read_into(
1989 TensorRead::from_tensor(lhs),
1990 TensorRead::from_tensor(rhs),
1991 out,
1992 )
1993 }
1994
1995 /// Overwrite caller-provided output with elementwise subtraction from reads.
1996 /// # Errors
1997 ///
1998 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1999 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2000 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2001 /// backend execution or storage access cannot provide the requested result.
2002 fn sub_read_into(
2003 &mut self,
2004 lhs: TensorRead<'_>,
2005 rhs: TensorRead<'_>,
2006 out: TensorWrite<'_>,
2007 ) -> crate::Result<()> {
2008 self.elementwise_read_into(ElementwiseReadOp::Subtract, &[lhs, rhs], out)
2009 }
2010
2011 /// # Errors
2012 ///
2013 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2014 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2015 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2016 /// backend execution or storage access cannot provide the requested result.
2017 fn mul(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2018 /// # Errors
2019 ///
2020 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2021 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2022 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2023 /// backend execution or storage access cannot provide the requested result.
2024 fn mul_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2025 self.mul(read_tensor("mul", lhs)?, read_tensor("mul", rhs)?)
2026 }
2027
2028 /// Overwrite caller-provided output with elementwise multiplication.
2029 /// # Errors
2030 ///
2031 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2032 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2033 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2034 /// backend execution or storage access cannot provide the requested result.
2035 fn mul_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
2036 self.mul_read_into(
2037 TensorRead::from_tensor(lhs),
2038 TensorRead::from_tensor(rhs),
2039 out,
2040 )
2041 }
2042
2043 /// Overwrite caller-provided output with elementwise multiplication from reads.
2044 /// # Errors
2045 ///
2046 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2047 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2048 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2049 /// backend execution or storage access cannot provide the requested result.
2050 fn mul_read_into(
2051 &mut self,
2052 lhs: TensorRead<'_>,
2053 rhs: TensorRead<'_>,
2054 out: TensorWrite<'_>,
2055 ) -> crate::Result<()> {
2056 self.elementwise_read_into(ElementwiseReadOp::Multiply, &[lhs, rhs], out)
2057 }
2058
2059 /// # Errors
2060 ///
2061 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2062 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2063 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2064 /// backend execution or storage access cannot provide the requested result.
2065 fn neg(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2066 /// # Errors
2067 ///
2068 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2069 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2070 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2071 /// backend execution or storage access cannot provide the requested result.
2072 fn neg_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2073 self.neg(read_tensor("neg", input)?)
2074 }
2075
2076 /// Overwrite caller-provided output with elementwise negation.
2077 /// # Errors
2078 ///
2079 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2080 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2081 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2082 /// backend execution or storage access cannot provide the requested result.
2083 fn neg_into(&mut self, input: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
2084 self.neg_read_into(TensorRead::from_tensor(input), out)
2085 }
2086
2087 /// Overwrite caller-provided output with elementwise negation from a read.
2088 /// # Errors
2089 ///
2090 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2091 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2092 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2093 /// backend execution or storage access cannot provide the requested result.
2094 fn neg_read_into(&mut self, input: TensorRead<'_>, out: TensorWrite<'_>) -> crate::Result<()> {
2095 self.elementwise_read_into(ElementwiseReadOp::Negate, &[input], out)
2096 }
2097
2098 /// # Errors
2099 ///
2100 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2101 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2102 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2103 /// backend execution or storage access cannot provide the requested result.
2104 fn conj(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2105 /// # Errors
2106 ///
2107 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2108 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2109 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2110 /// backend execution or storage access cannot provide the requested result.
2111 fn conj_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2112 self.conj(read_tensor("conj", input)?)
2113 }
2114
2115 /// Overwrite caller-provided output with elementwise conjugation.
2116 /// # Errors
2117 ///
2118 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2119 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2120 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2121 /// backend execution or storage access cannot provide the requested result.
2122 fn conj_into(&mut self, input: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
2123 self.conj_read_into(TensorRead::from_tensor(input), out)
2124 }
2125
2126 /// Overwrite caller-provided output with elementwise conjugation from a read.
2127 /// # Errors
2128 ///
2129 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2130 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2131 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2132 /// backend execution or storage access cannot provide the requested result.
2133 fn conj_read_into(&mut self, input: TensorRead<'_>, out: TensorWrite<'_>) -> crate::Result<()> {
2134 self.elementwise_read_into(ElementwiseReadOp::Conj, &[input], out)
2135 }
2136
2137 /// # Errors
2138 ///
2139 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2140 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2141 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2142 /// backend execution or storage access cannot provide the requested result.
2143 fn div(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2144 /// # Errors
2145 ///
2146 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2147 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2148 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2149 /// backend execution or storage access cannot provide the requested result.
2150 fn div_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2151 self.div(read_tensor("div", lhs)?, read_tensor("div", rhs)?)
2152 }
2153
2154 /// Overwrite caller-provided output with elementwise division.
2155 /// # Errors
2156 ///
2157 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2158 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2159 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2160 /// backend execution or storage access cannot provide the requested result.
2161 fn div_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
2162 self.div_read_into(
2163 TensorRead::from_tensor(lhs),
2164 TensorRead::from_tensor(rhs),
2165 out,
2166 )
2167 }
2168
2169 /// Overwrite caller-provided output with elementwise division from reads.
2170 /// # Errors
2171 ///
2172 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2173 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2174 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2175 /// backend execution or storage access cannot provide the requested result.
2176 fn div_read_into(
2177 &mut self,
2178 lhs: TensorRead<'_>,
2179 rhs: TensorRead<'_>,
2180 out: TensorWrite<'_>,
2181 ) -> crate::Result<()> {
2182 self.elementwise_read_into(ElementwiseReadOp::Divide, &[lhs, rhs], out)
2183 }
2184
2185 /// Elementwise remainder.
2186 ///
2187 /// The default is an explicit unsupported error so backend implementors can
2188 /// opt in without silent fallback.
2189 ///
2190 /// # Examples
2191 ///
2192 /// ```rust
2193 /// use tenferro_tensor::{Tensor, TensorElementwise};
2194 ///
2195 /// fn rem_owned<B: TensorElementwise>(
2196 /// backend: &mut B,
2197 /// lhs: &Tensor,
2198 /// rhs: &Tensor,
2199 /// ) -> tenferro_tensor::Result<Tensor> {
2200 /// backend.rem(lhs, rhs)
2201 /// }
2202 /// ```
2203 /// # Errors
2204 ///
2205 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2206 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2207 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2208 /// backend execution or storage access cannot provide the requested result.
2209 fn rem(&mut self, lhs: &Tensor, _rhs: &Tensor) -> crate::Result<Tensor> {
2210 Err(crate::Error::unsupported(
2211 "rem",
2212 format!("backend does not implement rem for dtype {:?}", lhs.dtype()),
2213 ))
2214 }
2215
2216 /// Elementwise remainder accepting owned tensors or borrowed views.
2217 ///
2218 /// # Examples
2219 ///
2220 /// ```rust
2221 /// use tenferro_tensor::{Tensor, TensorElementwise, TensorRead};
2222 ///
2223 /// fn rem_read<B: TensorElementwise>(
2224 /// backend: &mut B,
2225 /// lhs: &Tensor,
2226 /// rhs: &Tensor,
2227 /// ) -> tenferro_tensor::Result<Tensor> {
2228 /// backend.rem_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
2229 /// }
2230 /// ```
2231 /// # Errors
2232 ///
2233 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2234 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2235 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2236 /// backend execution or storage access cannot provide the requested result.
2237 fn rem_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2238 self.rem(read_tensor("rem", lhs)?, read_tensor("rem", rhs)?)
2239 }
2240
2241 /// # Errors
2242 ///
2243 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2244 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2245 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2246 /// backend execution or storage access cannot provide the requested result.
2247 fn abs(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2248 /// # Errors
2249 ///
2250 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2251 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2252 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2253 /// backend execution or storage access cannot provide the requested result.
2254 fn abs_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2255 self.abs(read_tensor("abs", input)?)
2256 }
2257
2258 /// # Errors
2259 ///
2260 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2261 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2262 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2263 /// backend execution or storage access cannot provide the requested result.
2264 fn sign(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2265 /// # Errors
2266 ///
2267 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2268 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2269 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2270 /// backend execution or storage access cannot provide the requested result.
2271 fn sign_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2272 self.sign(read_tensor("sign", input)?)
2273 }
2274
2275 /// # Errors
2276 ///
2277 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2278 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2279 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2280 /// backend execution or storage access cannot provide the requested result.
2281 fn maximum(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2282 /// # Errors
2283 ///
2284 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2285 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2286 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2287 /// backend execution or storage access cannot provide the requested result.
2288 fn maximum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2289 self.maximum(read_tensor("maximum", lhs)?, read_tensor("maximum", rhs)?)
2290 }
2291
2292 /// # Errors
2293 ///
2294 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2295 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2296 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2297 /// backend execution or storage access cannot provide the requested result.
2298 fn minimum(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2299 /// # Errors
2300 ///
2301 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2302 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2303 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2304 /// backend execution or storage access cannot provide the requested result.
2305 fn minimum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2306 self.minimum(read_tensor("minimum", lhs)?, read_tensor("minimum", rhs)?)
2307 }
2308
2309 /// # Errors
2310 ///
2311 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2312 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2313 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2314 /// backend execution or storage access cannot provide the requested result.
2315 fn compare(&mut self, lhs: &Tensor, rhs: &Tensor, dir: &CompareDir) -> crate::Result<Tensor>;
2316 /// # Errors
2317 ///
2318 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2319 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2320 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2321 /// backend execution or storage access cannot provide the requested result.
2322 fn compare_read(
2323 &mut self,
2324 lhs: TensorRead<'_>,
2325 rhs: TensorRead<'_>,
2326 dir: &CompareDir,
2327 ) -> crate::Result<Tensor> {
2328 self.compare(
2329 read_tensor("compare", lhs)?,
2330 read_tensor("compare", rhs)?,
2331 dir,
2332 )
2333 }
2334
2335 /// # Errors
2336 ///
2337 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2338 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2339 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2340 /// backend execution or storage access cannot provide the requested result.
2341 fn select(
2342 &mut self,
2343 pred: &Tensor,
2344 on_true: &Tensor,
2345 on_false: &Tensor,
2346 ) -> crate::Result<Tensor>;
2347 /// # Errors
2348 ///
2349 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2350 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2351 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2352 /// backend execution or storage access cannot provide the requested result.
2353 fn select_read(
2354 &mut self,
2355 pred: TensorRead<'_>,
2356 on_true: TensorRead<'_>,
2357 on_false: TensorRead<'_>,
2358 ) -> crate::Result<Tensor> {
2359 self.select(
2360 read_tensor("select", pred)?,
2361 read_tensor("select", on_true)?,
2362 read_tensor("select", on_false)?,
2363 )
2364 }
2365
2366 /// # Errors
2367 ///
2368 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2369 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2370 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2371 /// backend execution or storage access cannot provide the requested result.
2372 fn clamp(&mut self, input: &Tensor, lower: &Tensor, upper: &Tensor) -> crate::Result<Tensor>;
2373 /// # Errors
2374 ///
2375 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2376 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2377 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2378 /// backend execution or storage access cannot provide the requested result.
2379 fn clamp_read(
2380 &mut self,
2381 input: TensorRead<'_>,
2382 lower: TensorRead<'_>,
2383 upper: TensorRead<'_>,
2384 ) -> crate::Result<Tensor> {
2385 self.clamp(
2386 read_tensor("clamp", input)?,
2387 read_tensor("clamp", lower)?,
2388 read_tensor("clamp", upper)?,
2389 )
2390 }
2391}
2392
2393/// Analytic unary and binary tensor operations.
2394///
2395/// # Examples
2396///
2397/// ```rust
2398/// use tenferro_tensor::TensorAnalytic;
2399///
2400/// fn accepts_analytic<B: TensorAnalytic>(_backend: &mut B) {}
2401/// ```
2402pub trait TensorAnalytic {
2403 /// # Errors
2404 ///
2405 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2406 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2407 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2408 /// backend execution or storage access cannot provide the requested result.
2409 fn exp(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2410 /// # Errors
2411 ///
2412 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2413 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2414 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2415 /// backend execution or storage access cannot provide the requested result.
2416 fn exp_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2417 self.exp(read_tensor("exp", input)?)
2418 }
2419
2420 /// # Errors
2421 ///
2422 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2423 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2424 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2425 /// backend execution or storage access cannot provide the requested result.
2426 fn log(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2427 /// # Errors
2428 ///
2429 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2430 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2431 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2432 /// backend execution or storage access cannot provide the requested result.
2433 fn log_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2434 self.log(read_tensor("log", input)?)
2435 }
2436
2437 /// # Errors
2438 ///
2439 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2440 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2441 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2442 /// backend execution or storage access cannot provide the requested result.
2443 fn sin(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2444 /// # Errors
2445 ///
2446 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2447 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2448 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2449 /// backend execution or storage access cannot provide the requested result.
2450 fn sin_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2451 self.sin(read_tensor("sin", input)?)
2452 }
2453
2454 /// # Errors
2455 ///
2456 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2457 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2458 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2459 /// backend execution or storage access cannot provide the requested result.
2460 fn cos(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2461 /// # Errors
2462 ///
2463 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2464 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2465 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2466 /// backend execution or storage access cannot provide the requested result.
2467 fn cos_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2468 self.cos(read_tensor("cos", input)?)
2469 }
2470
2471 /// # Errors
2472 ///
2473 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2474 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2475 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2476 /// backend execution or storage access cannot provide the requested result.
2477 fn tanh(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2478 /// # Errors
2479 ///
2480 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2481 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2482 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2483 /// backend execution or storage access cannot provide the requested result.
2484 fn tanh_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2485 self.tanh(read_tensor("tanh", input)?)
2486 }
2487
2488 /// # Errors
2489 ///
2490 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2491 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2492 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2493 /// backend execution or storage access cannot provide the requested result.
2494 fn sqrt(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2495 /// # Errors
2496 ///
2497 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2498 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2499 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2500 /// backend execution or storage access cannot provide the requested result.
2501 fn sqrt_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2502 self.sqrt(read_tensor("sqrt", input)?)
2503 }
2504
2505 /// # Errors
2506 ///
2507 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2508 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2509 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2510 /// backend execution or storage access cannot provide the requested result.
2511 fn rsqrt(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2512 /// # Errors
2513 ///
2514 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2515 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2516 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2517 /// backend execution or storage access cannot provide the requested result.
2518 fn rsqrt_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2519 self.rsqrt(read_tensor("rsqrt", input)?)
2520 }
2521
2522 /// # Errors
2523 ///
2524 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2525 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2526 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2527 /// backend execution or storage access cannot provide the requested result.
2528 fn pow(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2529 /// # Errors
2530 ///
2531 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2532 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2533 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2534 /// backend execution or storage access cannot provide the requested result.
2535 fn pow_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2536 self.pow(read_tensor("pow", lhs)?, read_tensor("pow", rhs)?)
2537 }
2538
2539 /// # Errors
2540 ///
2541 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2542 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2543 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2544 /// backend execution or storage access cannot provide the requested result.
2545 fn expm1(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2546 /// # Errors
2547 ///
2548 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2549 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2550 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2551 /// backend execution or storage access cannot provide the requested result.
2552 fn expm1_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2553 self.expm1(read_tensor("expm1", input)?)
2554 }
2555
2556 /// # Errors
2557 ///
2558 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2559 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2560 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2561 /// backend execution or storage access cannot provide the requested result.
2562 fn log1p(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2563 /// # Errors
2564 ///
2565 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2566 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2567 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2568 /// backend execution or storage access cannot provide the requested result.
2569 fn log1p_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2570 self.log1p(read_tensor("log1p", input)?)
2571 }
2572}
2573
2574/// Shape, layout, and dtype transformation operations.
2575///
2576/// # Examples
2577///
2578/// ```rust
2579/// use tenferro_tensor::TensorStructural;
2580///
2581/// fn accepts_structural<B: TensorStructural>(_backend: &mut B) {}
2582/// ```
2583pub trait TensorStructural {
2584 /// Materialize an owned tensor or borrowed view into fresh compact storage.
2585 ///
2586 /// The result has the input's shape and dtype, uses compact column-major
2587 /// layout, and remains in the input's placement. This operation is a
2588 /// same-placement canonicalization boundary, never an implicit host/device
2589 /// transfer. The conservative default accepts only compact host-owned
2590 /// tensors and clones them; it rejects views, backend buffers, and device
2591 /// placement because only an owning backend can materialize those safely.
2592 ///
2593 /// Backend overrides may accept strided views. CUDA accepts numeric and
2594 /// complex views on its active device, including arbitrary valid strides,
2595 /// but currently reports an explicit unsupported-dtype error for `Bool`.
2596 ///
2597 /// # Examples
2598 ///
2599 /// ```rust
2600 /// use tenferro_tensor::{DType, Tensor, TensorRead, TensorStructural};
2601 ///
2602 /// struct HostDefaults;
2603 /// impl TensorStructural for HostDefaults {
2604 /// fn transpose(&mut self, _: &Tensor, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2605 /// fn reshape(&mut self, _: &Tensor, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2606 /// fn broadcast_in_dim(&mut self, _: &Tensor, _: &[usize], _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2607 /// fn cast(&mut self, _: &Tensor, _: DType) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2608 /// fn extract_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2609 /// fn embed_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2610 /// fn tril(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2611 /// fn triu(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2612 /// }
2613 ///
2614 /// let input = Tensor::from_vec_col_major(vec![2], vec![1_i32, 2])?;
2615 /// let mut backend = HostDefaults;
2616 /// let structural: &mut dyn TensorStructural = &mut backend;
2617 /// let output = structural.to_contiguous_read(TensorRead::from_tensor(&input))?;
2618 /// assert_eq!(output.shape(), &[2]);
2619 /// assert_eq!(output.as_slice::<i32>()?, &[1, 2]);
2620 /// # Ok::<(), tenferro_tensor::Error>(())
2621 /// ```
2622 /// # Errors
2623 ///
2624 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2625 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2626 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2627 /// backend execution or storage access cannot provide the requested result.
2628 fn to_contiguous_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2629 match input {
2630 TensorRead::Tensor(input) => {
2631 if input.is_backend_buffer()
2632 || !matches!(
2633 input.placement().memory_kind,
2634 crate::MemoryKind::PinnedHost | crate::MemoryKind::UnpinnedHost
2635 )
2636 {
2637 return Err(crate::Error::runtime_state(
2638 "to_contiguous_read",
2639 "default materialization accepts only host-owned tensors; use the storage's owning backend",
2640 ));
2641 }
2642 input.duplicate()
2643 }
2644 TensorRead::View(view) => {
2645 if view.backend_family().is_some()
2646 || !matches!(
2647 view.placement().memory_kind,
2648 crate::MemoryKind::PinnedHost | crate::MemoryKind::UnpinnedHost
2649 )
2650 {
2651 return Err(crate::Error::runtime_state(
2652 "to_contiguous_read",
2653 "default materialization accepts only host-owned tensors; use the storage's owning backend",
2654 ));
2655 }
2656 view.duplicate()
2657 }
2658 }
2659 }
2660
2661 /// Overwrite caller-provided storage from a readable tensor or view.
2662 ///
2663 /// Source and destination must have identical dtype and shape and belong to
2664 /// the executing backend's placement. The destination is not resized, and
2665 /// every logical destination element is overwritten without reading its old
2666 /// value. Source and destination allocations must not alias. Implementations
2667 /// must not materialize through host memory or perform an implicit transfer.
2668 ///
2669 /// CPU accepts arbitrary valid source and destination strides and performs
2670 /// no tensor allocation. CUDA currently accepts only a compact column-major
2671 /// source with offset zero covering its full allocation; CUDA destinations
2672 /// may be arbitrary valid non-overlapping views. CUDA rejects aliased
2673 /// allocations and currently reports an explicit unsupported-dtype error
2674 /// for `Bool`. The conservative default is explicitly unsupported.
2675 ///
2676 /// # Examples
2677 ///
2678 /// ```rust
2679 /// use tenferro_tensor::{DType, Tensor, TensorRead, TensorStructural, TensorWrite};
2680 ///
2681 /// struct ConservativeDefaults;
2682 /// impl TensorStructural for ConservativeDefaults {
2683 /// fn transpose(&mut self, _: &Tensor, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2684 /// fn reshape(&mut self, _: &Tensor, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2685 /// fn broadcast_in_dim(&mut self, _: &Tensor, _: &[usize], _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2686 /// fn cast(&mut self, _: &Tensor, _: DType) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2687 /// fn extract_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2688 /// fn embed_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2689 /// fn tril(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2690 /// fn triu(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2691 /// }
2692 ///
2693 /// let src = Tensor::from_vec_col_major(vec![2], vec![1_i32, 2])?;
2694 /// let mut dst = Tensor::from_vec_col_major(vec![2], vec![0_i32, 0])?;
2695 /// let mut backend = ConservativeDefaults;
2696 /// let structural: &mut dyn TensorStructural = &mut backend;
2697 /// let error = structural.copy_read_into(
2698 /// TensorRead::from_tensor(&src),
2699 /// TensorWrite::from_tensor(&mut dst),
2700 /// ).unwrap_err();
2701 /// assert!(error.to_string().contains("unsupported"));
2702 /// assert_eq!(dst.as_slice::<i32>()?, &[0, 0]);
2703 /// # Ok::<(), tenferro_tensor::Error>(())
2704 /// ```
2705 /// # Errors
2706 ///
2707 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2708 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2709 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2710 /// backend execution or storage access cannot provide the requested result.
2711 fn copy_read_into(&mut self, _src: TensorRead<'_>, _dst: TensorWrite<'_>) -> crate::Result<()> {
2712 Err(crate::Error::unsupported(
2713 "copy_read_into",
2714 "backend-owned runtime copy is unsupported by this backend",
2715 ))
2716 }
2717
2718 /// # Errors
2719 ///
2720 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2721 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2722 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2723 /// backend execution or storage access cannot provide the requested result.
2724 fn transpose(&mut self, input: &Tensor, perm: &[usize]) -> crate::Result<Tensor>;
2725 /// # Errors
2726 ///
2727 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2728 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2729 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2730 /// backend execution or storage access cannot provide the requested result.
2731 fn transpose_read(&mut self, input: TensorRead<'_>, perm: &[usize]) -> crate::Result<Tensor> {
2732 self.transpose(read_tensor("transpose", input)?, perm)
2733 }
2734
2735 /// # Errors
2736 ///
2737 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2738 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2739 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2740 /// backend execution or storage access cannot provide the requested result.
2741 fn reshape(&mut self, input: &Tensor, shape: &[usize]) -> crate::Result<Tensor>;
2742 /// # Errors
2743 ///
2744 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2745 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2746 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2747 /// backend execution or storage access cannot provide the requested result.
2748 fn reshape_read(&mut self, input: TensorRead<'_>, shape: &[usize]) -> crate::Result<Tensor> {
2749 self.reshape(read_tensor("reshape", input)?, shape)
2750 }
2751
2752 /// # Errors
2753 ///
2754 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2755 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2756 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2757 /// backend execution or storage access cannot provide the requested result.
2758 fn broadcast_in_dim(
2759 &mut self,
2760 input: &Tensor,
2761 shape: &[usize],
2762 dims: &[usize],
2763 ) -> crate::Result<Tensor>;
2764 /// # Errors
2765 ///
2766 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2767 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2768 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2769 /// backend execution or storage access cannot provide the requested result.
2770 fn broadcast_in_dim_read(
2771 &mut self,
2772 input: TensorRead<'_>,
2773 shape: &[usize],
2774 dims: &[usize],
2775 ) -> crate::Result<Tensor> {
2776 self.broadcast_in_dim(read_tensor("broadcast_in_dim", input)?, shape, dims)
2777 }
2778
2779 /// Cast a tensor to another dtype using explicit dtype projection.
2780 ///
2781 /// Backends may truncate, narrow precision, project complex values, or use
2782 /// boolean truthiness according to their documented cast support.
2783 ///
2784 /// # Examples
2785 ///
2786 /// ```rust
2787 /// use tenferro_tensor::{DType, Tensor, TensorStructural};
2788 ///
2789 /// fn cast_to_i32<B: TensorStructural>(
2790 /// backend: &mut B,
2791 /// input: &Tensor,
2792 /// ) -> tenferro_tensor::Result<Tensor> {
2793 /// backend.cast(input, DType::I32)
2794 /// }
2795 /// ```
2796 /// # Errors
2797 ///
2798 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2799 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2800 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2801 /// backend execution or storage access cannot provide the requested result.
2802 fn cast(&mut self, input: &Tensor, to: crate::DType) -> crate::Result<Tensor>;
2803
2804 /// Convert a tensor to another dtype using checked dtype conversion.
2805 ///
2806 /// `convert` accepts only conversions allowed by tenferro's dtype-promotion
2807 /// lattice. Use [`TensorStructural::cast`] for explicit lossy projection.
2808 ///
2809 /// # Examples
2810 ///
2811 /// ```rust
2812 /// use tenferro_tensor::{DType, Tensor, TensorStructural};
2813 ///
2814 /// fn convert_to_f64<B: TensorStructural>(
2815 /// backend: &mut B,
2816 /// input: &Tensor,
2817 /// ) -> tenferro_tensor::Result<Tensor> {
2818 /// backend.convert(input, DType::F64)
2819 /// }
2820 /// ```
2821 /// # Errors
2822 ///
2823 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2824 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2825 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2826 /// backend execution or storage access cannot provide the requested result.
2827 fn convert(&mut self, input: &Tensor, to: crate::DType) -> crate::Result<Tensor> {
2828 validate_convert_dtype("convert", input.dtype(), to)?;
2829 self.cast(input, to)
2830 }
2831
2832 /// # Errors
2833 ///
2834 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2835 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2836 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2837 /// backend execution or storage access cannot provide the requested result.
2838 fn extract_diagonal(
2839 &mut self,
2840 input: &Tensor,
2841 axis_a: usize,
2842 axis_b: usize,
2843 ) -> crate::Result<Tensor>;
2844 /// # Errors
2845 ///
2846 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2847 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2848 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2849 /// backend execution or storage access cannot provide the requested result.
2850 fn embed_diagonal(
2851 &mut self,
2852 input: &Tensor,
2853 axis_a: usize,
2854 axis_b: usize,
2855 ) -> crate::Result<Tensor>;
2856 /// # Errors
2857 ///
2858 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2859 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2860 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2861 /// backend execution or storage access cannot provide the requested result.
2862 fn tril(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor>;
2863 /// # Errors
2864 ///
2865 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2866 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2867 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2868 /// backend execution or storage access cannot provide the requested result.
2869 fn triu(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor>;
2870}
2871
2872/// Reduction operations.
2873///
2874/// Reducing over an axis whose extent is zero returns an error for every
2875/// reduction operation. Passing an empty `axes` slice is a no-op for the public
2876/// reductions and returns the input values unchanged. Internal mapped
2877/// reductions document their own empty-axis semantics.
2878///
2879/// # Examples
2880///
2881/// ```rust
2882/// use tenferro_tensor::TensorReduction;
2883///
2884/// fn accepts_reduction<B: TensorReduction>(_backend: &mut B) {}
2885/// ```
2886pub trait TensorReduction {
2887 /// # Errors
2888 ///
2889 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2890 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2891 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2892 /// backend execution or storage access cannot provide the requested result.
2893 fn reduce_sum(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
2894
2895 /// Sum elements across axes from an owned tensor or borrowed view.
2896 ///
2897 /// # Examples
2898 ///
2899 /// ```rust
2900 /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2901 ///
2902 /// fn sum_owned<B: TensorReduction>(
2903 /// backend: &mut B,
2904 /// input: &Tensor,
2905 /// ) -> tenferro_tensor::Result<Tensor> {
2906 /// backend.reduce_sum_read(TensorRead::from_tensor(input), &[0])
2907 /// }
2908 /// ```
2909 /// # Errors
2910 ///
2911 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2912 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2913 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2914 /// backend execution or storage access cannot provide the requested result.
2915 fn reduce_sum_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
2916 match input.as_tensor() {
2917 Some(input) => self.reduce_sum(input, axes),
2918 None => Err(crate::Error::unsupported(
2919 "reduce_sum",
2920 "backend does not accept borrowed tensor views at this execution boundary",
2921 )),
2922 }
2923 }
2924
2925 /// Sum elementwise squares across axes.
2926 ///
2927 /// This execution hook is used by composite operations that avoid a
2928 /// materialized square. Empty axes produce an elementwise square. Backends
2929 /// that support this optimized path must override the hook directly.
2930 ///
2931 /// # Errors
2932 ///
2933 /// Returns the typed validation, unsupported, runtime-state, or backend
2934 /// error produced by multiplication or reduction.
2935 #[doc(hidden)]
2936 fn reduce_sum_squares_read(
2937 &mut self,
2938 _input: TensorRead<'_>,
2939 _axes: &[usize],
2940 ) -> crate::Result<Tensor> {
2941 Err(crate::Error::unsupported(
2942 "reduce_sum_squares",
2943 "backend does not implement fused sum-of-squares reduction",
2944 ))
2945 }
2946
2947 /// # Errors
2948 ///
2949 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2950 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2951 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2952 /// backend execution or storage access cannot provide the requested result.
2953 fn reduce_prod(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
2954
2955 /// Multiply elements across axes from an owned tensor or borrowed view.
2956 ///
2957 /// # Examples
2958 ///
2959 /// ```rust
2960 /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2961 ///
2962 /// fn prod_owned<B: TensorReduction>(
2963 /// backend: &mut B,
2964 /// input: &Tensor,
2965 /// ) -> tenferro_tensor::Result<Tensor> {
2966 /// backend.reduce_prod_read(TensorRead::from_tensor(input), &[0])
2967 /// }
2968 /// ```
2969 /// # Errors
2970 ///
2971 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2972 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2973 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2974 /// backend execution or storage access cannot provide the requested result.
2975 fn reduce_prod_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
2976 match input.as_tensor() {
2977 Some(input) => self.reduce_prod(input, axes),
2978 None => Err(crate::Error::unsupported(
2979 "reduce_prod",
2980 "backend does not accept borrowed tensor views at this execution boundary",
2981 )),
2982 }
2983 }
2984
2985 /// # Errors
2986 ///
2987 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2988 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2989 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2990 /// backend execution or storage access cannot provide the requested result.
2991 fn reduce_max(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
2992
2993 /// Take maximum values across axes from an owned tensor or borrowed view.
2994 ///
2995 /// # Examples
2996 ///
2997 /// ```rust
2998 /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2999 ///
3000 /// fn max_owned<B: TensorReduction>(
3001 /// backend: &mut B,
3002 /// input: &Tensor,
3003 /// ) -> tenferro_tensor::Result<Tensor> {
3004 /// backend.reduce_max_read(TensorRead::from_tensor(input), &[0])
3005 /// }
3006 /// ```
3007 /// # Errors
3008 ///
3009 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3010 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3011 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3012 /// backend execution or storage access cannot provide the requested result.
3013 fn reduce_max_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
3014 match input.as_tensor() {
3015 Some(input) => self.reduce_max(input, axes),
3016 None => Err(crate::Error::unsupported(
3017 "reduce_max",
3018 "backend does not accept borrowed tensor views at this execution boundary",
3019 )),
3020 }
3021 }
3022
3023 /// # Errors
3024 ///
3025 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3026 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3027 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3028 /// backend execution or storage access cannot provide the requested result.
3029 fn reduce_min(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
3030
3031 /// Take minimum values across axes from an owned tensor or borrowed view.
3032 ///
3033 /// # Examples
3034 ///
3035 /// ```rust
3036 /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
3037 ///
3038 /// fn min_owned<B: TensorReduction>(
3039 /// backend: &mut B,
3040 /// input: &Tensor,
3041 /// ) -> tenferro_tensor::Result<Tensor> {
3042 /// backend.reduce_min_read(TensorRead::from_tensor(input), &[0])
3043 /// }
3044 /// ```
3045 /// # Errors
3046 ///
3047 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3048 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3049 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3050 /// backend execution or storage access cannot provide the requested result.
3051 fn reduce_min_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
3052 match input.as_tensor() {
3053 Some(input) => self.reduce_min(input, axes),
3054 None => Err(crate::Error::unsupported(
3055 "reduce_min",
3056 "backend does not accept borrowed tensor views at this execution boundary",
3057 )),
3058 }
3059 }
3060}
3061
3062/// Dot-general operations.
3063///
3064/// # Examples
3065///
3066/// ```rust
3067/// use tenferro_tensor::TensorDot;
3068///
3069/// fn accepts_dot<B: TensorDot>(_backend: &mut B) {}
3070/// ```
3071pub trait TensorDot: TensorElementwise {
3072 /// # Errors
3073 ///
3074 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3075 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3076 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3077 /// backend execution or storage access cannot provide the requested result.
3078 fn dot_general(
3079 &mut self,
3080 lhs: &Tensor,
3081 rhs: &Tensor,
3082 config: &DotGeneralConfig,
3083 ) -> crate::Result<Tensor>;
3084
3085 #[doc(hidden)]
3086 fn dot_general_read(
3087 &mut self,
3088 lhs: TensorRead<'_>,
3089 rhs: TensorRead<'_>,
3090 config: &DotGeneralConfig,
3091 ) -> crate::Result<Tensor> {
3092 match (lhs.as_tensor(), rhs.as_tensor()) {
3093 (Some(lhs), Some(rhs)) => self.dot_general(lhs, rhs, config),
3094 _ => {
3095 let lhs = self.to_contiguous_read(lhs)?;
3096 let rhs = self.to_contiguous_read(rhs)?;
3097 self.dot_general(&lhs, &rhs, config)
3098 }
3099 }
3100 }
3101
3102 /// Overwrite caller-provided output with dot-general from read inputs.
3103 ///
3104 /// This is the dot/GEMM spelling of `_into`: the previous output value is
3105 /// not read. Use [`TensorDot::dot_general_read_into_accum`] for explicit
3106 /// read-modify-write accumulation.
3107 ///
3108 /// # Examples
3109 ///
3110 /// ```rust
3111 /// use tenferro_tensor::{DotGeneralConfig, TensorDot, TensorRead, TensorWrite};
3112 ///
3113 /// fn dot_into<B: TensorDot>(
3114 /// backend: &mut B,
3115 /// lhs: TensorRead<'_>,
3116 /// rhs: TensorRead<'_>,
3117 /// config: &DotGeneralConfig,
3118 /// out: TensorWrite<'_>,
3119 /// ) -> tenferro_tensor::Result<()> {
3120 /// backend.dot_general_read_into(lhs, rhs, config, out)
3121 /// }
3122 /// ```
3123 /// # Errors
3124 ///
3125 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3126 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3127 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3128 /// backend execution or storage access cannot provide the requested result.
3129 fn dot_general_read_into(
3130 &mut self,
3131 lhs: TensorRead<'_>,
3132 rhs: TensorRead<'_>,
3133 config: &DotGeneralConfig,
3134 out: TensorWrite<'_>,
3135 ) -> crate::Result<()> {
3136 let accumulation = DotGeneralAccumulation::overwrite(lhs.dtype())?;
3137 self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
3138 }
3139
3140 #[doc(hidden)]
3141 fn dot_general_with_conj(
3142 &mut self,
3143 lhs: &Tensor,
3144 rhs: &Tensor,
3145 config: &DotGeneralConfig,
3146 lhs_conj: bool,
3147 rhs_conj: bool,
3148 ) -> crate::Result<Tensor> {
3149 if !lhs_conj && !rhs_conj {
3150 return self.dot_general(lhs, rhs, config);
3151 }
3152
3153 let lhs_tmp;
3154 let lhs_ref = if lhs_conj {
3155 lhs_tmp = self.conj(lhs)?;
3156 &lhs_tmp
3157 } else {
3158 lhs
3159 };
3160 let rhs_tmp;
3161 let rhs_ref = if rhs_conj {
3162 rhs_tmp = self.conj(rhs)?;
3163 &rhs_tmp
3164 } else {
3165 rhs
3166 };
3167 self.dot_general(lhs_ref, rhs_ref, config)
3168 }
3169
3170 #[allow(clippy::too_many_arguments)]
3171 #[doc(hidden)]
3172 fn dot_general_with_conj_read(
3173 &mut self,
3174 lhs: TensorRead<'_>,
3175 rhs: TensorRead<'_>,
3176 config: &DotGeneralConfig,
3177 lhs_conj: bool,
3178 rhs_conj: bool,
3179 ) -> crate::Result<Tensor> {
3180 if !lhs_conj && !rhs_conj {
3181 return self.dot_general_read(lhs, rhs, config);
3182 }
3183
3184 let lhs_tmp;
3185 let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
3186 tensor
3187 } else {
3188 lhs_tmp = self.to_contiguous_read(lhs)?;
3189 &lhs_tmp
3190 };
3191 let rhs_tmp;
3192 let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
3193 tensor
3194 } else {
3195 rhs_tmp = self.to_contiguous_read(rhs)?;
3196 &rhs_tmp
3197 };
3198 self.dot_general_with_conj(lhs_ref, rhs_ref, config, lhs_conj, rhs_conj)
3199 }
3200
3201 /// Apply scaled dot-general accumulation into caller-provided output.
3202 ///
3203 /// This is explicitly read-modify-write when `accumulation.beta` is nonzero:
3204 /// `out = alpha * dot_general(lhs, rhs) + beta * out`.
3205 ///
3206 /// # Examples
3207 ///
3208 /// ```rust
3209 /// use tenferro_tensor::{
3210 /// DotGeneralAccumulation, DotGeneralConfig, TensorDot, TensorRead, TensorWrite,
3211 /// };
3212 ///
3213 /// fn dot_add_to<B: TensorDot>(
3214 /// backend: &mut B,
3215 /// lhs: TensorRead<'_>,
3216 /// rhs: TensorRead<'_>,
3217 /// config: &DotGeneralConfig,
3218 /// out: TensorWrite<'_>,
3219 /// ) -> tenferro_tensor::Result<()> {
3220 /// let accumulation = DotGeneralAccumulation::add_to(lhs.dtype())?;
3221 /// backend.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
3222 /// }
3223 /// ```
3224 /// # Errors
3225 ///
3226 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3227 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3228 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3229 /// backend execution or storage access cannot provide the requested result.
3230 fn dot_general_read_into_accum(
3231 &mut self,
3232 lhs: TensorRead<'_>,
3233 rhs: TensorRead<'_>,
3234 config: &DotGeneralConfig,
3235 accumulation: DotGeneralAccumulation,
3236 out: TensorWrite<'_>,
3237 ) -> crate::Result<()> {
3238 dot_general_accum_via_temp(self, lhs, rhs, config, accumulation, out)
3239 }
3240}
3241
3242/// Session-scoped cached dot-general operations.
3243///
3244/// # Examples
3245///
3246/// ```rust
3247/// use tenferro_tensor::BackendSession;
3248///
3249/// fn accepts_session_dot<S: BackendSession + ?Sized>(_session: &mut S) {}
3250/// ```
3251pub trait SessionCachedDot: TensorDot {
3252 #[doc(hidden)]
3253 fn dot_general_cached(
3254 &mut self,
3255 _cache_slot: Option<usize>,
3256 lhs: &Tensor,
3257 rhs: &Tensor,
3258 config: &DotGeneralConfig,
3259 ) -> crate::Result<Tensor> {
3260 self.dot_general(lhs, rhs, config)
3261 }
3262
3263 #[doc(hidden)]
3264 fn dot_general_read_cached(
3265 &mut self,
3266 cache_slot: Option<usize>,
3267 lhs: TensorRead<'_>,
3268 rhs: TensorRead<'_>,
3269 config: &DotGeneralConfig,
3270 ) -> crate::Result<Tensor> {
3271 match (lhs.as_tensor(), rhs.as_tensor()) {
3272 (Some(lhs), Some(rhs)) => self.dot_general_cached(cache_slot, lhs, rhs, config),
3273 _ => {
3274 let lhs = self.to_contiguous_read(lhs)?;
3275 let rhs = self.to_contiguous_read(rhs)?;
3276 self.dot_general_cached(cache_slot, &lhs, &rhs, config)
3277 }
3278 }
3279 }
3280
3281 // Mirrors the dot-general signature plus runtime-cache metadata.
3282 #[allow(clippy::too_many_arguments)]
3283 #[doc(hidden)]
3284 fn dot_general_with_conj_cached(
3285 &mut self,
3286 _cache_slot: Option<usize>,
3287 lhs: &Tensor,
3288 rhs: &Tensor,
3289 config: &DotGeneralConfig,
3290 lhs_conj: bool,
3291 rhs_conj: bool,
3292 ) -> crate::Result<Tensor> {
3293 self.dot_general_with_conj(lhs, rhs, config, lhs_conj, rhs_conj)
3294 }
3295
3296 // Mirrors the dot-general read signature plus runtime-cache metadata.
3297 #[allow(clippy::too_many_arguments)]
3298 #[doc(hidden)]
3299 fn dot_general_with_conj_read_cached(
3300 &mut self,
3301 cache_slot: Option<usize>,
3302 lhs: TensorRead<'_>,
3303 rhs: TensorRead<'_>,
3304 config: &DotGeneralConfig,
3305 lhs_conj: bool,
3306 rhs_conj: bool,
3307 ) -> crate::Result<Tensor> {
3308 if !lhs_conj && !rhs_conj {
3309 return self.dot_general_read_cached(cache_slot, lhs, rhs, config);
3310 }
3311
3312 let lhs_tmp;
3313 let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
3314 tensor
3315 } else {
3316 lhs_tmp = self.to_contiguous_read(lhs)?;
3317 &lhs_tmp
3318 };
3319 let rhs_tmp;
3320 let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
3321 tensor
3322 } else {
3323 rhs_tmp = self.to_contiguous_read(rhs)?;
3324 &rhs_tmp
3325 };
3326 self.dot_general_with_conj_cached(cache_slot, lhs_ref, rhs_ref, config, lhs_conj, rhs_conj)
3327 }
3328
3329 /// Apply session-cached scaled dot-general accumulation into output.
3330 ///
3331 /// The cache slot is session-local metadata; `accumulation` still controls
3332 /// overwrite versus read-modify-write semantics.
3333 ///
3334 /// # Examples
3335 ///
3336 /// ```rust
3337 /// use tenferro_tensor::{
3338 /// DotGeneralAccumulation, DotGeneralConfig, SessionCachedDot, TensorRead, TensorWrite,
3339 /// };
3340 ///
3341 /// fn session_cached_dot_add_to<S: SessionCachedDot + ?Sized>(
3342 /// session: &mut S,
3343 /// lhs: TensorRead<'_>,
3344 /// rhs: TensorRead<'_>,
3345 /// config: &DotGeneralConfig,
3346 /// out: TensorWrite<'_>,
3347 /// ) -> tenferro_tensor::Result<()> {
3348 /// let accumulation = DotGeneralAccumulation::add_to(lhs.dtype())?;
3349 /// session.dot_general_read_into_accum_cached(
3350 /// Some(0),
3351 /// lhs,
3352 /// rhs,
3353 /// config,
3354 /// accumulation,
3355 /// out,
3356 /// )
3357 /// }
3358 /// ```
3359 /// # Errors
3360 ///
3361 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3362 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3363 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3364 /// backend execution or storage access cannot provide the requested result.
3365 fn dot_general_read_into_accum_cached(
3366 &mut self,
3367 _cache_slot: Option<usize>,
3368 lhs: TensorRead<'_>,
3369 rhs: TensorRead<'_>,
3370 config: &DotGeneralConfig,
3371 accumulation: DotGeneralAccumulation,
3372 out: TensorWrite<'_>,
3373 ) -> crate::Result<()> {
3374 self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
3375 }
3376
3377 #[doc(hidden)]
3378 fn grouped_gemm_cached(
3379 &mut self,
3380 _cache_slot: Option<usize>,
3381 lhs: TensorRead<'_>,
3382 rhs: TensorRead<'_>,
3383 config: &GroupedGemmConfig<'_>,
3384 out: TensorWrite<'_>,
3385 ) -> crate::Result<()> {
3386 grouped_gemm_default(self, lhs, rhs, config, out)
3387 }
3388}
3389
3390/// Indexing, slicing, and padding operations.
3391///
3392/// # Examples
3393///
3394/// ```rust
3395/// use tenferro_tensor::TensorIndexing;
3396///
3397/// fn accepts_indexing<B: TensorIndexing>(_backend: &mut B) {}
3398/// ```
3399pub trait TensorIndexing {
3400 /// # Errors
3401 ///
3402 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3403 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3404 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3405 /// backend execution or storage access cannot provide the requested result.
3406 fn gather(
3407 &mut self,
3408 operand: &Tensor,
3409 start_indices: &Tensor,
3410 config: &GatherConfig,
3411 ) -> crate::Result<Tensor>;
3412 /// # Errors
3413 ///
3414 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3415 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3416 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3417 /// backend execution or storage access cannot provide the requested result.
3418 fn scatter(
3419 &mut self,
3420 operand: &Tensor,
3421 scatter_indices: &Tensor,
3422 updates: &Tensor,
3423 config: &ScatterConfig,
3424 ) -> crate::Result<Tensor>;
3425 /// # Errors
3426 ///
3427 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3428 /// for invalid shapes, ranks, axes, dtypes, or output metadata. In
3429 /// particular, a limit greater than the corresponding input dimension is
3430 /// reported as [`crate::ValidationError::InvalidArgument`] with the
3431 /// `"configuration"` argument. It returns [`crate::Error::BackendFailure`]
3432 /// or [`crate::Error::BackendSource`] when backend execution or storage
3433 /// access cannot provide the requested result.
3434 fn slice(&mut self, input: &Tensor, config: &SliceConfig) -> crate::Result<Tensor>;
3435 /// # Errors
3436 ///
3437 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3438 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3439 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3440 /// backend execution or storage access cannot provide the requested result.
3441 fn dynamic_slice(
3442 &mut self,
3443 input: &Tensor,
3444 starts: &Tensor,
3445 slice_sizes: &[usize],
3446 ) -> crate::Result<Tensor>;
3447 /// # Errors
3448 ///
3449 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3450 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3451 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3452 /// backend execution or storage access cannot provide the requested result.
3453 fn dynamic_update_slice(
3454 &mut self,
3455 operand: &Tensor,
3456 update: &Tensor,
3457 starts: &Tensor,
3458 ) -> crate::Result<Tensor>;
3459 /// # Errors
3460 ///
3461 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3462 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3463 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3464 /// backend execution or storage access cannot provide the requested result.
3465 fn pad(&mut self, input: &Tensor, config: &PadConfig) -> crate::Result<Tensor>;
3466 /// # Errors
3467 ///
3468 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3469 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3470 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3471 /// backend execution or storage access cannot provide the requested result.
3472 fn concatenate(&mut self, inputs: &[&Tensor], axis: usize) -> crate::Result<Tensor>;
3473 /// # Errors
3474 ///
3475 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3476 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3477 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3478 /// backend execution or storage access cannot provide the requested result.
3479 fn reverse(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
3480}
3481
3482/// Backend-owned canonicalization for typed tensor views.
3483///
3484/// Implementations must preserve the input placement family. CPU backends
3485/// canonicalize host views through explicit host copies and reject backend
3486/// buffers with a diagnostic that asks the caller to download first. GPU
3487/// backends canonicalize GPU-resident views on the same device and reject host
3488/// buffers with an upload hint.
3489///
3490/// [`TensorViewCanonicalization::copy_into`] requires source and destination
3491/// shapes, scalar dtypes, and placement families to match. The destination
3492/// view must be internally non-overlapping, and source and destination backing
3493/// allocations must not alias unless an implementation explicitly documents
3494/// and supports that case. Implementations may reject layouts their native
3495/// kernels cannot consume.
3496///
3497/// CUDA currently accepts only a compact column-major source view with offset
3498/// zero that covers its full allocation; arbitrary-stride destinations remain
3499/// supported. Canonicalization and copying are same-placement operations: they
3500/// must not perform hidden host/device transfers or silently materialize an
3501/// unsupported source layout.
3502///
3503/// This trait is intentionally separate from [`BackendSession`] so generic
3504/// typed methods do not change the object-safety contract of `dyn BackendSession`.
3505///
3506/// # Examples
3507///
3508/// ```rust
3509/// use tenferro_tensor::{DynRank, TensorViewCanonicalization, TypedTensor};
3510///
3511/// fn compact_i32<B: TensorViewCanonicalization<i32, DynRank>>(
3512/// backend: &mut B,
3513/// tensor: &TypedTensor<i32>,
3514/// ) -> tenferro_tensor::Result<TypedTensor<i32>> {
3515/// backend.to_contiguous(&tensor.as_view())
3516/// }
3517///
3518/// fn copy_i32<B: TensorViewCanonicalization<i32, DynRank>>(
3519/// backend: &mut B,
3520/// src: &TypedTensor<i32>,
3521/// dst: &mut TypedTensor<i32>,
3522/// ) -> tenferro_tensor::Result<()> {
3523/// backend.copy_into(&src.as_view(), &mut dst.as_view_mut())
3524/// }
3525/// ```
3526pub trait TensorViewCanonicalization<T: TensorScalar, R: TensorRank> {
3527 /// # Errors
3528 ///
3529 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3530 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3531 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3532 /// backend execution or storage access cannot provide the requested result.
3533 fn to_contiguous(
3534 &mut self,
3535 view: &TypedTensorView<'_, T, R>,
3536 ) -> crate::Result<TypedTensor<T, R>>;
3537
3538 /// # Errors
3539 ///
3540 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3541 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3542 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3543 /// backend execution or storage access cannot provide the requested result.
3544 fn copy_into(
3545 &mut self,
3546 src: &TypedTensorView<'_, T, R>,
3547 dst: &mut TypedTensorViewMut<'_, T, R>,
3548 ) -> crate::Result<()>;
3549}
3550
3551/// Optional elementwise fusion execution.
3552///
3553/// # Examples
3554///
3555/// ```rust
3556/// use tenferro_tensor::TensorFusion;
3557///
3558/// fn accepts_fusion<B: TensorFusion>(_backend: &mut B) {}
3559/// ```
3560pub trait TensorFusion {
3561 #[doc(hidden)]
3562 fn execute_elementwise_fusion(
3563 &mut self,
3564 _inputs: &[&Tensor],
3565 _plan: &ElementwiseFusionPlan,
3566 ) -> crate::Result<Option<Vec<Tensor>>> {
3567 Ok(None)
3568 }
3569
3570 #[doc(hidden)]
3571 #[allow(clippy::too_many_arguments)]
3572 fn execute_broadcast_multiply(
3573 &mut self,
3574 _lhs: TensorRead<'_>,
3575 _lhs_shape: &[usize],
3576 _lhs_dims: &[usize],
3577 _rhs: TensorRead<'_>,
3578 _rhs_shape: &[usize],
3579 _rhs_dims: &[usize],
3580 ) -> crate::Result<Option<Tensor>> {
3581 Ok(None)
3582 }
3583
3584 #[doc(hidden)]
3585 #[allow(clippy::too_many_arguments)]
3586 fn execute_broadcast_multiply_value(
3587 &mut self,
3588 lhs: TensorRead<'_>,
3589 lhs_shape: &[usize],
3590 lhs_dims: &[usize],
3591 rhs: TensorRead<'_>,
3592 rhs_shape: &[usize],
3593 rhs_dims: &[usize],
3594 ) -> crate::Result<Option<TensorValue>> {
3595 self.execute_broadcast_multiply(lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims)
3596 .map(|tensor| tensor.map(TensorValue::from_tensor))
3597 }
3598}
3599
3600/// Backend buffer lifecycle operations.
3601///
3602/// # Examples
3603///
3604/// ```rust
3605/// use tenferro_tensor::TensorBuffer;
3606///
3607/// fn accepts_buffer<B: TensorBuffer>(_backend: &mut B) {}
3608/// ```
3609pub trait TensorBuffer {
3610 fn reclaim_buffer(&mut self, _tensor: Tensor) {}
3611}
3612
3613/// Device transfer operations on backend boundaries.
3614///
3615/// # Examples
3616///
3617/// ```rust
3618/// use tenferro_tensor::TensorDeviceTransfer;
3619///
3620/// fn accepts_transfer<B: TensorDeviceTransfer>(_backend: &mut B) {}
3621/// ```
3622pub trait TensorDeviceTransfer {
3623 /// Explicitly copy a provider-owned read target into host storage.
3624 ///
3625 /// Implementations must not return the input unchanged or stage through an
3626 /// unrelated provider. A backend that cannot transfer the requested read
3627 /// target returns a typed unsupported error.
3628 ///
3629 /// # Errors
3630 ///
3631 /// Returns [`crate::Error::Unsupported`] when the implementation cannot
3632 /// perform the requested transfer, or a typed validation/backend error when
3633 /// the source cannot be read.
3634 fn download_to_host(&mut self, tensor: TensorRead<'_>) -> crate::Result<Tensor>;
3635
3636 /// Explicitly copy a host read target into provider storage.
3637 ///
3638 /// # Errors
3639 ///
3640 /// Returns [`crate::Error::Unsupported`] when the implementation cannot
3641 /// perform the requested transfer, or a typed validation/backend error when
3642 /// the source cannot be read.
3643 fn upload_host_tensor(&mut self, tensor: TensorRead<'_>) -> crate::Result<Tensor>;
3644}
3645
3646/// Runtime cache associated with a backend.
3647///
3648/// # Examples
3649///
3650/// ```rust
3651/// use tenferro_tensor::BackendRuntimeCache;
3652///
3653/// fn accepts_runtime_cache<B: BackendRuntimeCache>(_backend: &B) {}
3654/// ```
3655pub trait BackendRuntimeCache {
3656 #[doc(hidden)]
3657 type RuntimeCache: RuntimeCacheControl + Send + Sync + 'static;
3658}
3659
3660/// Backend-owned cached dot-general operations.
3661///
3662/// # Examples
3663///
3664/// ```rust
3665/// use tenferro_tensor::BackendCachedDot;
3666///
3667/// fn accepts_backend_cached_dot<B: BackendCachedDot>(_backend: &mut B) {}
3668/// ```
3669pub trait BackendCachedDot: BackendRuntimeCache + TensorDot {
3670 #[doc(hidden)]
3671 fn dot_general_cached(
3672 &mut self,
3673 _cache: &mut Self::RuntimeCache,
3674 _cache_slot: Option<usize>,
3675 lhs: &Tensor,
3676 rhs: &Tensor,
3677 config: &DotGeneralConfig,
3678 ) -> crate::Result<Tensor> {
3679 self.dot_general(lhs, rhs, config)
3680 }
3681
3682 #[doc(hidden)]
3683 fn dot_general_read_cached(
3684 &mut self,
3685 cache: &mut Self::RuntimeCache,
3686 cache_slot: Option<usize>,
3687 lhs: TensorRead<'_>,
3688 rhs: TensorRead<'_>,
3689 config: &DotGeneralConfig,
3690 ) -> crate::Result<Tensor> {
3691 match (lhs.as_tensor(), rhs.as_tensor()) {
3692 (Some(lhs), Some(rhs)) => self.dot_general_cached(cache, cache_slot, lhs, rhs, config),
3693 _ => {
3694 let lhs = self.to_contiguous_read(lhs)?;
3695 let rhs = self.to_contiguous_read(rhs)?;
3696 self.dot_general_cached(cache, cache_slot, &lhs, &rhs, config)
3697 }
3698 }
3699 }
3700
3701 // Mirrors the dot-general signature plus runtime-cache metadata.
3702 #[allow(clippy::too_many_arguments)]
3703 #[doc(hidden)]
3704 fn dot_general_with_conj_cached(
3705 &mut self,
3706 _cache: &mut Self::RuntimeCache,
3707 _cache_slot: Option<usize>,
3708 lhs: &Tensor,
3709 rhs: &Tensor,
3710 config: &DotGeneralConfig,
3711 lhs_conj: bool,
3712 rhs_conj: bool,
3713 ) -> crate::Result<Tensor> {
3714 self.dot_general_with_conj(lhs, rhs, config, lhs_conj, rhs_conj)
3715 }
3716
3717 // Mirrors the dot-general read signature plus runtime-cache metadata.
3718 #[allow(clippy::too_many_arguments)]
3719 #[doc(hidden)]
3720 fn dot_general_with_conj_read_cached(
3721 &mut self,
3722 cache: &mut Self::RuntimeCache,
3723 cache_slot: Option<usize>,
3724 lhs: TensorRead<'_>,
3725 rhs: TensorRead<'_>,
3726 config: &DotGeneralConfig,
3727 lhs_conj: bool,
3728 rhs_conj: bool,
3729 ) -> crate::Result<Tensor> {
3730 if !lhs_conj && !rhs_conj {
3731 return self.dot_general_read_cached(cache, cache_slot, lhs, rhs, config);
3732 }
3733
3734 let lhs_tmp;
3735 let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
3736 tensor
3737 } else {
3738 lhs_tmp = self.to_contiguous_read(lhs)?;
3739 &lhs_tmp
3740 };
3741 let rhs_tmp;
3742 let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
3743 tensor
3744 } else {
3745 rhs_tmp = self.to_contiguous_read(rhs)?;
3746 &rhs_tmp
3747 };
3748 self.dot_general_with_conj_cached(
3749 cache, cache_slot, lhs_ref, rhs_ref, config, lhs_conj, rhs_conj,
3750 )
3751 }
3752
3753 /// Apply cached scaled dot-general accumulation into caller-provided output.
3754 ///
3755 /// The cache slot identifies backend-local analysis metadata only; output
3756 /// semantics are still fully described by `accumulation`.
3757 ///
3758 /// # Examples
3759 ///
3760 /// ```rust
3761 /// use tenferro_tensor::{
3762 /// BackendCachedDot, BackendRuntimeCache, DotGeneralAccumulation, DotGeneralConfig,
3763 /// TensorRead, TensorWrite,
3764 /// };
3765 ///
3766 /// fn cached_dot_add_to<B: BackendCachedDot>(
3767 /// backend: &mut B,
3768 /// cache: &mut B::RuntimeCache,
3769 /// lhs: TensorRead<'_>,
3770 /// rhs: TensorRead<'_>,
3771 /// config: &DotGeneralConfig,
3772 /// out: TensorWrite<'_>,
3773 /// ) -> tenferro_tensor::Result<()>
3774 /// where
3775 /// B: BackendRuntimeCache,
3776 /// {
3777 /// let accumulation = DotGeneralAccumulation::add_to(lhs.dtype())?;
3778 /// backend.dot_general_read_into_accum_cached(
3779 /// cache,
3780 /// Some(0),
3781 /// lhs,
3782 /// rhs,
3783 /// config,
3784 /// accumulation,
3785 /// out,
3786 /// )
3787 /// }
3788 /// ```
3789 #[allow(clippy::too_many_arguments)]
3790 /// # Errors
3791 ///
3792 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3793 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3794 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3795 /// backend execution or storage access cannot provide the requested result.
3796 fn dot_general_read_into_accum_cached(
3797 &mut self,
3798 _cache: &mut Self::RuntimeCache,
3799 _cache_slot: Option<usize>,
3800 lhs: TensorRead<'_>,
3801 rhs: TensorRead<'_>,
3802 config: &DotGeneralConfig,
3803 accumulation: DotGeneralAccumulation,
3804 out: TensorWrite<'_>,
3805 ) -> crate::Result<()> {
3806 self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
3807 }
3808
3809 #[doc(hidden)]
3810 fn grouped_gemm_cached(
3811 &mut self,
3812 _cache: &mut Self::RuntimeCache,
3813 _cache_slot: Option<usize>,
3814 lhs: TensorRead<'_>,
3815 rhs: TensorRead<'_>,
3816 config: &GroupedGemmConfig<'_>,
3817 out: TensorWrite<'_>,
3818 ) -> crate::Result<()> {
3819 grouped_gemm_default(self, lhs, rhs, config, out)
3820 }
3821}
3822
3823/// Backend execution-session entry points.
3824///
3825/// `with_backend_session` is the canonical user entry; one-shot concrete ops
3826/// delegate to the session form. `with_backend_session_cached` is the
3827/// runtime-cache-aware entry used by the runtime layer.
3828///
3829/// # Examples
3830///
3831/// ```rust
3832/// use tenferro_tensor::BackendSessionHost;
3833///
3834/// fn accepts_session_host<B: BackendSessionHost>(_backend: &mut B) {}
3835/// ```
3836pub trait BackendSessionHost: BackendRuntimeCache {
3837 fn with_backend_session<R: Send>(
3838 &mut self,
3839 f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
3840 ) -> R
3841 where
3842 Self: TensorBackend + Sized,
3843 {
3844 default_backend_session(self, f)
3845 }
3846
3847 #[doc(hidden)]
3848 fn with_backend_session_cached<R: Send>(
3849 &mut self,
3850 _cache: &mut Self::RuntimeCache,
3851 f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
3852 ) -> R
3853 where
3854 Self: TensorBackend + Sized,
3855 {
3856 self.with_backend_session(f)
3857 }
3858}
3859
3860/// Operation capabilities shared by backends and backend sessions.
3861#[doc(hidden)]
3862pub trait TensorBackendOps:
3863 TensorElementwise
3864 + TensorAnalytic
3865 + TensorStructural
3866 + TensorReduction
3867 + TensorIndexing
3868 + TensorDot
3869 + TensorFusion
3870 + TensorBuffer
3871{
3872}
3873
3874impl<T> TensorBackendOps for T where
3875 T: TensorElementwise
3876 + TensorAnalytic
3877 + TensorStructural
3878 + TensorReduction
3879 + TensorIndexing
3880 + TensorDot
3881 + TensorFusion
3882 + TensorBuffer
3883 + ?Sized
3884{
3885}
3886
3887/// Validate the shared input contract for [`BackendSession::vdot_read`].
3888#[doc(hidden)]
3889pub fn validate_vdot_read(lhs: &TensorRead<'_>, rhs: &TensorRead<'_>) -> crate::Result<()> {
3890 let op = "BackendSession::vdot_read";
3891 validate_supported_blas1_dtype(op, lhs.dtype())?;
3892 if rhs.dtype() != lhs.dtype() {
3893 return Err(Error::dtype_mismatch(op, lhs.dtype(), rhs.dtype()));
3894 }
3895 if lhs.shape() != rhs.shape() {
3896 return Err(Error::shape_mismatch(
3897 op,
3898 lhs.shape().to_vec(),
3899 rhs.shape().to_vec(),
3900 ));
3901 }
3902 validate_shape_product(op, lhs.shape())?;
3903 validate_compatible_placement(op, lhs, rhs)
3904}
3905
3906/// Validate the shared input contract for [`BackendSession::norm_squared_read`].
3907#[doc(hidden)]
3908pub fn validate_norm_squared_read(input: &TensorRead<'_>) -> crate::Result<()> {
3909 validate_supported_blas1_dtype("BackendSession::norm_squared_read", input.dtype())?;
3910 validate_shape_product("BackendSession::norm_squared_read", input.shape()).map(|_| ())
3911}
3912
3913/// Validate the shared input and destination contract for
3914/// [`BackendSession::axpby_read_into_accum`].
3915#[doc(hidden)]
3916pub fn validate_axpby_read_into_accum(
3917 alpha: ContractionScalar,
3918 x: &TensorRead<'_>,
3919 beta: ContractionScalar,
3920 y: &TensorWrite<'_>,
3921) -> crate::Result<()> {
3922 let op = "BackendSession::axpby_read_into_accum";
3923 validate_supported_blas1_dtype(op, x.dtype())?;
3924 if y.dtype() != x.dtype() {
3925 return Err(Error::dtype_mismatch(op, x.dtype(), y.dtype()));
3926 }
3927 if alpha.dtype() != x.dtype() {
3928 return Err(Error::dtype_mismatch(op, x.dtype(), alpha.dtype()));
3929 }
3930 if beta.dtype() != x.dtype() {
3931 return Err(Error::dtype_mismatch(op, x.dtype(), beta.dtype()));
3932 }
3933 if x.shape() != y.shape() {
3934 return Err(Error::shape_mismatch(
3935 op,
3936 x.shape().to_vec(),
3937 y.shape().to_vec(),
3938 ));
3939 }
3940 validate_shape_product(op, x.shape())?;
3941 if !y.as_read().is_col_major_contiguous()? {
3942 return Err(Error::invalid_argument(
3943 op,
3944 "y",
3945 "destination must be compact column-major and injective",
3946 ));
3947 }
3948 validate_compatible_placement(op, x, &y.as_read())?;
3949 validate_read_into_destination(op, std::slice::from_ref(x), y)
3950}
3951
3952/// # Errors
3953///
3954/// Returns [`Error::Unsupported`] when `dtype` is not floating or complex.
3955fn validate_supported_blas1_dtype(op: &'static str, dtype: DType) -> crate::Result<()> {
3956 if matches!(dtype, DType::F32 | DType::F64 | DType::C32 | DType::C64) {
3957 Ok(())
3958 } else {
3959 Err(Error::unsupported(
3960 op,
3961 format!("unsupported dtype {dtype:?}; supported dtypes: F32/F64/C32/C64"),
3962 ))
3963 }
3964}
3965
3966/// # Errors
3967///
3968/// Returns [`Error::Validation`] with [`ValidationError::IntegerOverflow`] when
3969/// the checked shape product overflows.
3970fn validate_shape_product(op: &'static str, shape: &[usize]) -> crate::Result<usize> {
3971 crate::validate::checked_shape_product(op, "shape", shape)
3972}
3973
3974/// # Errors
3975///
3976/// Returns [`Error::Validation`] with [`ValidationError::InvalidArgument`] when
3977/// placements differ.
3978fn validate_compatible_placement(
3979 op: &'static str,
3980 lhs: &TensorRead<'_>,
3981 rhs: &TensorRead<'_>,
3982) -> crate::Result<()> {
3983 if lhs.placement() != rhs.placement() {
3984 return Err(Error::invalid_argument(
3985 op,
3986 "placement",
3987 "tensor inputs must have compatible placement",
3988 ));
3989 }
3990 Ok(())
3991}
3992
3993/// Execution session surface for dense tensor backends.
3994///
3995/// All operations run within a backend-owned execution scope such as a CPU
3996/// thread policy or a GPU stream. Individual ops must not try to re-enter that
3997/// scope.
3998///
3999/// # Examples
4000///
4001/// ```rust
4002/// use tenferro_tensor::{BackendSessionHost, Tensor, TypedTensor};
4003///
4004/// fn add_in_session<B: BackendSessionHost>(
4005/// backend: &mut B,
4006/// a: &Tensor,
4007/// b: &Tensor,
4008/// ) -> tenferro_tensor::Result<Tensor>
4009/// where
4010/// B: tenferro_tensor::TensorBackend,
4011/// {
4012/// backend.with_backend_session(|exec| exec.add(a, b))
4013/// }
4014/// ```
4015pub trait BackendSession: TensorBackendOps + SessionCachedDot + TensorDeviceTransfer {
4016 /// Build-local identity for backend-extension session capability dispatch.
4017 #[doc(hidden)]
4018 fn session_type_id(&self) -> TypeId;
4019
4020 /// Compute the all-axis conjugating dot product without transferring either input.
4021 ///
4022 /// The result is a rank-0 tensor with the input dtype and has the value
4023 /// `sum(conj(lhs) * rhs)`. Borrowed views remain borrowed through the
4024 /// backend's existing same-placement planning boundary.
4025 ///
4026 /// # Examples
4027 ///
4028 /// ```rust
4029 /// use tenferro_tensor::{BackendSession, TensorRead};
4030 ///
4031 /// fn vdot(session: &mut dyn BackendSession, x: TensorRead<'_>, y: TensorRead<'_>)
4032 /// -> tenferro_tensor::Result<tenferro_tensor::Tensor>
4033 /// {
4034 /// session.vdot_read(x, y)
4035 /// }
4036 /// ```
4037 ///
4038 /// # Errors
4039 ///
4040 /// Returns [`Error::Unsupported`] when the backend does not override this
4041 /// capability or the dtype is unsupported; [`Error::Validation`] with
4042 /// `DTypeMismatch`, `ShapeMismatch`, or `InvalidArgument` when dtype, shape,
4043 /// or placement differs; [`Error::RuntimeState`] for inaccessible backend
4044 /// storage; or [`Error::BackendSource`] when provider execution fails.
4045 fn vdot_read(&mut self, _lhs: TensorRead<'_>, _rhs: TensorRead<'_>) -> crate::Result<Tensor> {
4046 Err(Error::unsupported(
4047 "BackendSession::vdot_read",
4048 "backend session does not implement vdot_read",
4049 ))
4050 }
4051
4052 /// Compute the all-axis sum of squared magnitudes without taking a square root.
4053 ///
4054 /// The result is rank 0 and is F32 for F32/C32 input or F64 for F64/C64
4055 /// input. No transfer or full-size algebra temporary is implied by this
4056 /// session contract.
4057 ///
4058 /// # Examples
4059 ///
4060 /// ```rust
4061 /// use tenferro_tensor::{BackendSession, TensorRead};
4062 ///
4063 /// fn norm_squared(session: &mut dyn BackendSession, x: TensorRead<'_>)
4064 /// -> tenferro_tensor::Result<tenferro_tensor::Tensor>
4065 /// {
4066 /// session.norm_squared_read(x)
4067 /// }
4068 /// ```
4069 ///
4070 /// # Errors
4071 ///
4072 /// Returns [`Error::Unsupported`] when the backend does not override this
4073 /// capability or the dtype is unsupported, [`Error::RuntimeState`] when
4074 /// backend storage is not host-accessible, or [`Error::BackendSource`] when
4075 /// reduction execution fails.
4076 fn norm_squared_read(&mut self, _input: TensorRead<'_>) -> crate::Result<Tensor> {
4077 Err(Error::unsupported(
4078 "BackendSession::norm_squared_read",
4079 "backend session does not implement norm_squared_read",
4080 ))
4081 }
4082
4083 /// Apply `y <- alpha * x + beta * y` in one pass into caller-owned storage.
4084 ///
4085 /// Scalars must have the exact tensor dtype; real coefficients for complex
4086 /// vectors are represented as complex values with zero imaginary part. The
4087 /// destination must be compact and injective, and any x/y storage overlap
4088 /// is rejected before mutation.
4089 ///
4090 /// # Examples
4091 ///
4092 /// ```rust
4093 /// use tenferro_tensor::{BackendSession, ContractionScalar, TensorRead, TensorWrite};
4094 ///
4095 /// fn axpby(session: &mut dyn BackendSession, x: TensorRead<'_>, y: TensorWrite<'_>)
4096 /// -> tenferro_tensor::Result<()>
4097 /// {
4098 /// session.axpby_read_into_accum(
4099 /// ContractionScalar::F64(1.0), x, ContractionScalar::F64(0.0), y,
4100 /// )
4101 /// }
4102 /// ```
4103 ///
4104 /// # Errors
4105 ///
4106 /// Returns [`Error::Unsupported`] when the backend does not override this
4107 /// capability or the dtype is unsupported; [`Error::Validation`] with
4108 /// `DTypeMismatch`, `ShapeMismatch`, or `InvalidArgument` for scalar/dtype,
4109 /// shape, placement, compactness, injectivity, or overlap failures; or
4110 /// [`Error::RuntimeState`] for inaccessible backend storage. Invalid
4111 /// requests leave the destination unchanged.
4112 fn axpby_read_into_accum(
4113 &mut self,
4114 _alpha: ContractionScalar,
4115 _x: TensorRead<'_>,
4116 _beta: ContractionScalar,
4117 _y: TensorWrite<'_>,
4118 ) -> crate::Result<()> {
4119 Err(Error::unsupported(
4120 "BackendSession::axpby_read_into_accum",
4121 "backend session does not implement axpby_read_into_accum",
4122 ))
4123 }
4124
4125 /// Erased pointer used only by backend leaf crates for a checked session
4126 /// capability bridge. The pointer is borrowed for the lifetime of `self`.
4127 ///
4128 /// # Safety
4129 ///
4130 /// The implementation must return a pointer to the same value represented
4131 /// by `self`, and that pointer must remain valid and uniquely borrowed for
4132 /// the duration of the `&mut self` borrow. Backend leaf crates may use this
4133 /// contract to recover a concrete session capability after checking
4134 /// [`Self::session_type_id`].
4135 #[doc(hidden)]
4136 unsafe fn session_data_mut(&mut self) -> *mut ();
4137}
4138
4139/// Standard runtime backend over dynamic [`Tensor`] values.
4140///
4141/// # Examples
4142///
4143/// ```rust
4144/// use tenferro_tensor::TensorBackend;
4145///
4146/// fn accepts_backend<B: TensorBackend>(_backend: &mut B) {}
4147/// ```
4148pub trait TensorBackend:
4149 BackendRuntimeCache
4150 + BackendSession
4151 + TensorBackendOps
4152 + BackendCachedDot
4153 + TensorDeviceTransfer
4154 + BackendSessionHost
4155{
4156}
4157
4158impl<T> SessionCachedDot for T where T: TensorBackend + ?Sized {}
4159
4160thread_local! {
4161 /// Tracks whether a session-entry closure is currently running on this
4162 /// thread, so nested session entry is caught in debug builds even for
4163 /// backends that do not override
4164 /// [`BackendSessionHost::with_backend_session`].
4165 static IN_SESSION: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
4166}
4167
4168/// Sets the in-session flag for the duration of a session closure and restores
4169/// it on exit, including on panic.
4170struct InSessionGuard;
4171
4172impl InSessionGuard {
4173 fn enter() -> Self {
4174 debug_assert!(
4175 !IN_SESSION.get(),
4176 "nested backend session entry: a session closure called \
4177 with_backend_session / default_backend_session again on this thread"
4178 );
4179 IN_SESSION.set(true);
4180 InSessionGuard
4181 }
4182}
4183
4184impl Drop for InSessionGuard {
4185 fn drop(&mut self) {
4186 IN_SESSION.set(false);
4187 }
4188}
4189
4190/// Run `f` with the thread-local in-session flag set, restoring it on exit
4191/// including on panic, and asserting (in debug builds) that the caller is not
4192/// already inside a session closure.
4193///
4194/// This is the portable nested-entry guard shared by every backend-session
4195/// entry point: [`default_backend_session`] and the GPU
4196/// `BackendSessionHost::with_backend_session` overrides. CPU keeps its own
4197/// release-mode `EXECUTION_OWNER` panic on top of this debug check.
4198#[doc(hidden)]
4199pub fn with_session_entry_guard<R>(f: impl FnOnce() -> R) -> R {
4200 let _guard = InSessionGuard::enter();
4201 f()
4202}
4203
4204/// Run a closure using the backend itself as a default execution session.
4205///
4206/// This is suitable for backends whose individual ops already manage their own
4207/// execution context.
4208///
4209/// # Examples
4210///
4211/// ```rust
4212/// use tenferro_tensor::{default_backend_session, TensorBackend};
4213///
4214/// fn run_with_default_session<B: TensorBackend>(backend: &mut B) -> usize {
4215/// default_backend_session(backend, |_exec| 1usize)
4216/// }
4217/// ```
4218pub fn default_backend_session<B: TensorBackend, R: Send>(
4219 backend: &mut B,
4220 f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
4221) -> R {
4222 with_session_entry_guard(|| f(backend))
4223}