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: crate::core_dtype(lhs.dtype()),
201 actual: crate::core_dtype(rhs.dtype()),
202 },
203 ));
204 }
205 if lhs.dtype() != out.dtype() {
206 return Err(validation(
207 op,
208 ValidationError::DTypeMismatch {
209 expected: crate::core_dtype(lhs.dtype()),
210 actual: crate::core_dtype(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 => Err(validation(
291 "ContractionScalar::one",
292 ValidationError::DTypeMismatch {
293 expected: crate::core_dtype(dtype),
294 actual: crate::core_dtype(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 => Err(validation(
321 "ContractionScalar::zero",
322 ValidationError::DTypeMismatch {
323 expected: crate::core_dtype(dtype),
324 actual: crate::core_dtype(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: crate::core_dtype(alpha.dtype()),
542 actual: crate::core_dtype(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: crate::core_dtype(scalar.dtype()),
561 actual: crate::core_dtype(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: crate::core_dtype(lhs.dtype()),
682 actual: crate::core_dtype(rhs.dtype()),
683 },
684 ));
685 }
686 if lhs.dtype() != out.dtype() {
687 return Err(validation(
688 op,
689 ValidationError::DTypeMismatch {
690 expected: crate::core_dtype(lhs.dtype()),
691 actual: crate::core_dtype(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, $wrapper:ty) => {
914 match (&lhs, &rhs, &mut out) {
915 (
916 TensorRead::Tensor(Tensor::$variant(a)),
917 TensorRead::Tensor(Tensor::$variant(b)),
918 TensorWrite::Tensor(Tensor::$variant(c)),
919 ) => {
920 let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
921 let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
922 let mut c_view = c.as_view_mut();
923 return grouped_gemm_default_loop::<_, _, $wrapper>(
924 backend,
925 a_data,
926 a_base,
927 b_data,
928 b_base,
929 &mut c_view,
930 config,
931 );
932 }
933 (
934 TensorRead::Tensor(Tensor::$variant(a)),
935 TensorRead::View(TensorView::$variant(b)),
936 TensorWrite::Tensor(Tensor::$variant(c)),
937 ) => {
938 let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
939 let mut c_view = c.as_view_mut();
940 return grouped_gemm_default_loop::<_, _, $wrapper>(
941 backend,
942 a_data,
943 a_base,
944 b.host_storage()?,
945 b.offset(),
946 &mut c_view,
947 config,
948 );
949 }
950 (
951 TensorRead::View(TensorView::$variant(a)),
952 TensorRead::Tensor(Tensor::$variant(b)),
953 TensorWrite::Tensor(Tensor::$variant(c)),
954 ) => {
955 let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
956 let mut c_view = c.as_view_mut();
957 return grouped_gemm_default_loop::<_, _, $wrapper>(
958 backend,
959 a.host_storage()?,
960 a.offset(),
961 b_data,
962 b_base,
963 &mut c_view,
964 config,
965 );
966 }
967 (
968 TensorRead::View(TensorView::$variant(a)),
969 TensorRead::View(TensorView::$variant(b)),
970 TensorWrite::Tensor(Tensor::$variant(c)),
971 ) => {
972 let mut c_view = c.as_view_mut();
973 return grouped_gemm_default_loop::<_, _, $wrapper>(
974 backend,
975 a.host_storage()?,
976 a.offset(),
977 b.host_storage()?,
978 b.offset(),
979 &mut c_view,
980 config,
981 );
982 }
983 (
984 TensorRead::Tensor(Tensor::$variant(a)),
985 TensorRead::Tensor(Tensor::$variant(b)),
986 TensorWrite::View(TensorViewMut::$variant(c)),
987 ) => {
988 let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
989 let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
990 return grouped_gemm_default_loop::<_, _, $wrapper>(
991 backend, a_data, a_base, b_data, b_base, c, config,
992 );
993 }
994 (
995 TensorRead::Tensor(Tensor::$variant(a)),
996 TensorRead::View(TensorView::$variant(b)),
997 TensorWrite::View(TensorViewMut::$variant(c)),
998 ) => {
999 let (a_data, a_base) = typed_read_storage(a, "grouped_gemm")?;
1000 return grouped_gemm_default_loop::<_, _, $wrapper>(
1001 backend,
1002 a_data,
1003 a_base,
1004 b.host_storage()?,
1005 b.offset(),
1006 c,
1007 config,
1008 );
1009 }
1010 (
1011 TensorRead::View(TensorView::$variant(a)),
1012 TensorRead::Tensor(Tensor::$variant(b)),
1013 TensorWrite::View(TensorViewMut::$variant(c)),
1014 ) => {
1015 let (b_data, b_base) = typed_read_storage(b, "grouped_gemm")?;
1016 return grouped_gemm_default_loop::<_, _, $wrapper>(
1017 backend,
1018 a.host_storage()?,
1019 a.offset(),
1020 b_data,
1021 b_base,
1022 c,
1023 config,
1024 );
1025 }
1026 (
1027 TensorRead::View(TensorView::$variant(a)),
1028 TensorRead::View(TensorView::$variant(b)),
1029 TensorWrite::View(TensorViewMut::$variant(c)),
1030 ) => {
1031 return grouped_gemm_default_loop::<_, _, $wrapper>(
1032 backend,
1033 a.host_storage()?,
1034 a.offset(),
1035 b.host_storage()?,
1036 b.offset(),
1037 c,
1038 config,
1039 );
1040 }
1041 _ => {}
1042 }
1043 };
1044 }
1045
1046 dispatch!(F32, GroupedF32);
1047 dispatch!(F64, GroupedF64);
1048 dispatch!(C32, GroupedC32);
1049 dispatch!(C64, GroupedC64);
1050 Err(validation(
1051 "grouped_gemm",
1052 ValidationError::DTypeMismatch {
1053 expected: crate::core_dtype(lhs.dtype()),
1054 actual: crate::core_dtype(out.dtype()),
1055 },
1056 ))
1057}
1058
1059fn grouped_gemm_default<B>(
1060 backend: &mut B,
1061 lhs: TensorRead<'_>,
1062 rhs: TensorRead<'_>,
1063 config: &GroupedGemmConfig<'_>,
1064 out: TensorWrite<'_>,
1065) -> crate::Result<()>
1066where
1067 B: TensorDot + ?Sized,
1068{
1069 grouped_gemm_via_sequential(backend, lhs, rhs, config, out)
1070}
1071
1072#[doc(hidden)]
1073pub fn accumulate_dot_result_into(
1074 dot: &Tensor,
1075 accumulation: DotGeneralAccumulation,
1076 out: &mut TensorWrite<'_>,
1077) -> crate::Result<()> {
1078 macro_rules! dispatch {
1079 ($variant:ident, $ty:ty) => {
1080 if let (
1081 Tensor::$variant(dot),
1082 ContractionScalar::$variant(alpha),
1083 ContractionScalar::$variant(beta),
1084 ) = (dot, accumulation.alpha, accumulation.beta)
1085 {
1086 match out {
1087 TensorWrite::Tensor(Tensor::$variant(out)) => {
1088 let mut out = out.as_view_mut();
1089 accumulate_typed(dot.as_slice()?, alpha, beta, &mut out)?;
1090 return Ok(());
1091 }
1092 TensorWrite::View(crate::TensorViewMut::$variant(out)) => {
1093 accumulate_typed(dot.as_slice()?, alpha, beta, out)?;
1094 return Ok(());
1095 }
1096 _ => {}
1097 }
1098 }
1099 };
1100 }
1101
1102 dispatch!(F32, f32);
1103 dispatch!(F64, f64);
1104 dispatch!(C32, Complex32);
1105 dispatch!(C64, Complex64);
1106
1107 Err(validation(
1108 "dot_general",
1109 ValidationError::DTypeMismatch {
1110 expected: crate::core_dtype(accumulation.alpha.dtype()),
1111 actual: crate::core_dtype(dot.dtype()),
1112 },
1113 ))
1114}
1115
1116fn accumulate_typed<T>(
1117 dot: &[T],
1118 alpha: T,
1119 beta: T,
1120 out: &mut TypedTensorViewMut<'_, T>,
1121) -> crate::Result<()>
1122where
1123 T: Copy
1124 + PartialEq
1125 + std::ops::Add<Output = T>
1126 + std::ops::Mul<Output = T>
1127 + num_traits::Zero
1128 + 'static,
1129{
1130 let beta_is_zero = beta == T::zero();
1131 if let Some(output) = compact_host_accumulation_slice(out, dot.len())? {
1132 for (output, dot_value) in output.iter_mut().zip(dot.iter().copied()) {
1133 // INVARIANT: beta == 0 follows BLAS GEMM semantics and does not read
1134 // the existing output element; beta != 0 requires an initialized
1135 // TensorWrite target and performs a read-modify-write update.
1136 *output = if beta_is_zero {
1137 alpha * dot_value
1138 } else {
1139 alpha * dot_value + beta * *output
1140 };
1141 }
1142 return Ok(());
1143 }
1144
1145 for (linear, dot_value) in dot.iter().copied().enumerate() {
1146 let indices = flat_to_multi_for_shape(out.shape(), linear);
1147 let output = out.get_mut(&indices).ok_or_else(|| {
1148 invalid_argument(
1149 "dot_general",
1150 "output",
1151 format!("index {indices:?} is outside accumulation target"),
1152 )
1153 })?;
1154 // INVARIANT: beta == 0 follows BLAS GEMM semantics and does not read
1155 // the existing output element; beta != 0 requires an initialized
1156 // TensorWrite target and performs a read-modify-write update.
1157 *output = if beta_is_zero {
1158 alpha * dot_value
1159 } else {
1160 alpha * dot_value + beta * *output
1161 };
1162 }
1163 Ok(())
1164}
1165
1166fn compact_host_accumulation_slice<'a, T: 'static>(
1167 out: &'a mut TypedTensorViewMut<'_, T>,
1168 expected_len: usize,
1169) -> crate::Result<Option<&'a mut [T]>> {
1170 if out.backend_buffer().is_some()
1171 || out.n_elements() != expected_len
1172 || !out.is_col_major_contiguous()?
1173 {
1174 return Ok(None);
1175 }
1176
1177 let start = usize::try_from(out.offset()).map_err(|_| {
1178 invalid_argument("dot_general", "output", "compact output offset is negative")
1179 })?;
1180 let end = start
1181 .checked_add(expected_len)
1182 .ok_or_else(|| validation("dot_general", ValidationError::IntegerOverflow))?;
1183 out.host_storage_mut()?
1184 .get_mut(start..end)
1185 .map(Some)
1186 .ok_or_else(|| {
1187 invalid_argument(
1188 "dot_general",
1189 "output",
1190 "compact output is outside its backing storage",
1191 )
1192 })
1193}
1194
1195fn flat_to_multi_for_shape(shape: &[usize], mut linear: usize) -> Vec<usize> {
1196 let mut indices = Vec::with_capacity(shape.len());
1197 for &dim in shape {
1198 if dim == 0 {
1199 indices.push(0);
1200 } else {
1201 indices.push(linear % dim);
1202 linear /= dim;
1203 }
1204 }
1205 indices
1206}
1207
1208/// Canonical elementwise fusion plan shared between segmented execution and backends.
1209#[doc(hidden)]
1210#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1211pub struct ElementwiseFusionPlan {
1212 dtype: crate::DType,
1213 input_count: usize,
1214 // Keep view metadata in Vecs. A/B benchmarking on the broadcast_mul
1215 // path showed SmallVec made this metadata path about 6-7% slower.
1216 input_views: Vec<ElementwiseFusionInputView>,
1217 outputs: Vec<usize>,
1218 ops: Vec<ElementwiseFusionInst>,
1219}
1220
1221/// Metadata-only view applied to one backend fusion input.
1222#[doc(hidden)]
1223#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1224pub enum ElementwiseFusionInputView {
1225 Identity,
1226 BroadcastInDim {
1227 // Vec is intentional here; see ElementwiseFusionPlan::input_views.
1228 shape: Vec<usize>,
1229 dims: Vec<usize>,
1230 },
1231}
1232
1233/// One node in a canonical elementwise fusion plan.
1234#[doc(hidden)]
1235#[derive(Clone, Debug, Hash, PartialEq, Eq)]
1236pub struct ElementwiseFusionInst {
1237 op: ElementwiseFusionOp,
1238 inputs: Vec<usize>,
1239}
1240
1241tenferro_core_ops::define_elementwise_fusion_op!();
1242
1243impl ElementwiseFusionPlan {
1244 /// Build a backend elementwise fusion plan.
1245 ///
1246 /// # Examples
1247 ///
1248 /// ```rust
1249 /// use tenferro_tensor::backend::{
1250 /// ElementwiseFusionInst, ElementwiseFusionOp, ElementwiseFusionPlan,
1251 /// };
1252 /// use tenferro_tensor::DType;
1253 ///
1254 /// let plan = ElementwiseFusionPlan::new(
1255 /// DType::F64,
1256 /// 2,
1257 /// vec![2],
1258 /// vec![ElementwiseFusionInst::new(ElementwiseFusionOp::Add, vec![0, 1])],
1259 /// );
1260 /// assert_eq!(plan.input_count(), 2);
1261 /// ```
1262 pub fn new(
1263 dtype: crate::DType,
1264 input_count: usize,
1265 outputs: Vec<usize>,
1266 ops: Vec<ElementwiseFusionInst>,
1267 ) -> Self {
1268 Self::with_input_views(
1269 dtype,
1270 vec![ElementwiseFusionInputView::Identity; input_count],
1271 outputs,
1272 ops,
1273 )
1274 }
1275
1276 /// Build a backend elementwise fusion plan with input view metadata.
1277 ///
1278 /// # Examples
1279 ///
1280 /// ```rust
1281 /// use tenferro_tensor::backend::{
1282 /// ElementwiseFusionInputView, ElementwiseFusionInst, ElementwiseFusionOp,
1283 /// ElementwiseFusionPlan,
1284 /// };
1285 /// use tenferro_tensor::DType;
1286 ///
1287 /// let plan = ElementwiseFusionPlan::with_input_views(
1288 /// DType::F64,
1289 /// vec![ElementwiseFusionInputView::broadcast_in_dim(vec![2, 3], vec![0])],
1290 /// vec![1],
1291 /// vec![ElementwiseFusionInst::new(ElementwiseFusionOp::Negate, vec![0])],
1292 /// );
1293 /// assert_eq!(plan.input_count(), 1);
1294 /// ```
1295 pub fn with_input_views(
1296 dtype: crate::DType,
1297 input_views: impl IntoIterator<Item = ElementwiseFusionInputView>,
1298 outputs: Vec<usize>,
1299 ops: Vec<ElementwiseFusionInst>,
1300 ) -> Self {
1301 let input_views = input_views.into_iter().collect::<Vec<_>>();
1302 let input_count = input_views.len();
1303 Self {
1304 dtype,
1305 input_count,
1306 input_views,
1307 outputs,
1308 ops,
1309 }
1310 }
1311
1312 /// Return the scalar dtype expected by this fusion plan.
1313 ///
1314 /// # Examples
1315 ///
1316 /// ```rust
1317 /// use tenferro_tensor::backend::ElementwiseFusionPlan;
1318 /// use tenferro_tensor::DType;
1319 ///
1320 /// let plan = ElementwiseFusionPlan::new(DType::F32, 0, Vec::new(), Vec::new());
1321 /// assert_eq!(plan.dtype(), DType::F32);
1322 /// ```
1323 pub fn dtype(&self) -> crate::DType {
1324 self.dtype
1325 }
1326
1327 /// Return the number of input tensors expected by this plan.
1328 ///
1329 /// # Examples
1330 ///
1331 /// ```rust
1332 /// use tenferro_tensor::backend::ElementwiseFusionPlan;
1333 /// use tenferro_tensor::DType;
1334 ///
1335 /// let plan = ElementwiseFusionPlan::new(DType::F64, 3, Vec::new(), Vec::new());
1336 /// assert_eq!(plan.input_count(), 3);
1337 /// ```
1338 pub fn input_count(&self) -> usize {
1339 self.input_count
1340 }
1341
1342 /// Return metadata views applied to fusion inputs before executing ops.
1343 ///
1344 /// # Examples
1345 ///
1346 /// ```rust
1347 /// use tenferro_tensor::backend::ElementwiseFusionPlan;
1348 /// use tenferro_tensor::DType;
1349 ///
1350 /// let plan = ElementwiseFusionPlan::new(DType::F64, 2, Vec::new(), Vec::new());
1351 /// assert_eq!(plan.input_views().len(), 2);
1352 /// ```
1353 pub fn input_views(&self) -> &[ElementwiseFusionInputView] {
1354 &self.input_views
1355 }
1356
1357 /// Return the value ids selected as fusion outputs.
1358 ///
1359 /// # Examples
1360 ///
1361 /// ```rust
1362 /// use tenferro_tensor::backend::ElementwiseFusionPlan;
1363 /// use tenferro_tensor::DType;
1364 ///
1365 /// let plan = ElementwiseFusionPlan::new(DType::F64, 0, vec![0], Vec::new());
1366 /// assert_eq!(plan.outputs(), &[0]);
1367 /// ```
1368 pub fn outputs(&self) -> &[usize] {
1369 &self.outputs
1370 }
1371
1372 /// Return the fused elementwise instruction sequence.
1373 ///
1374 /// # Examples
1375 ///
1376 /// ```rust
1377 /// use tenferro_tensor::backend::{
1378 /// ElementwiseFusionInst, ElementwiseFusionOp, ElementwiseFusionPlan,
1379 /// };
1380 /// use tenferro_tensor::DType;
1381 ///
1382 /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Negate, vec![0]);
1383 /// let plan = ElementwiseFusionPlan::new(DType::F64, 1, vec![1], vec![inst]);
1384 /// assert_eq!(plan.ops().len(), 1);
1385 /// ```
1386 pub fn ops(&self) -> &[ElementwiseFusionInst] {
1387 &self.ops
1388 }
1389}
1390
1391impl ElementwiseFusionInputView {
1392 /// Build metadata for a `BroadcastInDim` fusion input view.
1393 ///
1394 /// # Examples
1395 ///
1396 /// ```rust
1397 /// use tenferro_tensor::backend::ElementwiseFusionInputView;
1398 ///
1399 /// let view = ElementwiseFusionInputView::broadcast_in_dim(vec![2, 3], vec![0]);
1400 /// assert!(matches!(view, ElementwiseFusionInputView::BroadcastInDim { .. }));
1401 /// ```
1402 pub fn broadcast_in_dim(
1403 shape: impl IntoIterator<Item = usize>,
1404 dims: impl IntoIterator<Item = usize>,
1405 ) -> Self {
1406 Self::BroadcastInDim {
1407 shape: shape.into_iter().collect(),
1408 dims: dims.into_iter().collect(),
1409 }
1410 }
1411
1412 /// Return true when this fusion input is an identity view.
1413 ///
1414 /// # Examples
1415 ///
1416 /// ```rust
1417 /// use tenferro_tensor::backend::ElementwiseFusionInputView;
1418 ///
1419 /// assert!(ElementwiseFusionInputView::Identity.is_identity());
1420 /// ```
1421 pub fn is_identity(&self) -> bool {
1422 matches!(self, Self::Identity)
1423 }
1424}
1425
1426impl ElementwiseFusionInst {
1427 /// Build a backend elementwise fusion instruction.
1428 ///
1429 /// # Examples
1430 ///
1431 /// ```rust
1432 /// use tenferro_tensor::backend::{ElementwiseFusionInst, ElementwiseFusionOp};
1433 ///
1434 /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Add, vec![0, 1]);
1435 /// assert_eq!(inst.inputs(), &[0, 1]);
1436 /// ```
1437 pub fn new(op: ElementwiseFusionOp, inputs: Vec<usize>) -> Self {
1438 Self { op, inputs }
1439 }
1440
1441 /// Return the elementwise op executed by this instruction.
1442 ///
1443 /// # Examples
1444 ///
1445 /// ```rust
1446 /// use tenferro_tensor::backend::{ElementwiseFusionInst, ElementwiseFusionOp};
1447 ///
1448 /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Negate, vec![0]);
1449 /// assert_eq!(inst.op(), ElementwiseFusionOp::Negate);
1450 /// ```
1451 pub fn op(&self) -> ElementwiseFusionOp {
1452 self.op
1453 }
1454
1455 /// Return this instruction's input value ids.
1456 ///
1457 /// # Examples
1458 ///
1459 /// ```rust
1460 /// use tenferro_tensor::backend::{ElementwiseFusionInst, ElementwiseFusionOp};
1461 ///
1462 /// let inst = ElementwiseFusionInst::new(ElementwiseFusionOp::Multiply, vec![2, 0]);
1463 /// assert_eq!(inst.inputs(), &[2, 0]);
1464 /// ```
1465 pub fn inputs(&self) -> &[usize] {
1466 &self.inputs
1467 }
1468}
1469
1470/// Runtime operation selected by [`TensorElementwise::elementwise_read_into`].
1471#[non_exhaustive]
1472#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1473pub enum ElementwiseReadOp {
1474 /// Binary addition.
1475 Add,
1476 /// Binary subtraction.
1477 Subtract,
1478 /// Binary multiplication.
1479 Multiply,
1480 /// Unary negation.
1481 Negate,
1482 /// Unary conjugation.
1483 Conj,
1484 /// Binary division.
1485 Divide,
1486}
1487
1488impl ElementwiseReadOp {
1489 #[doc(hidden)]
1490 pub fn label(self) -> &'static str {
1491 match self {
1492 Self::Add => "add",
1493 Self::Subtract => "sub",
1494 Self::Multiply => "mul",
1495 Self::Negate => "neg",
1496 Self::Conj => "conj",
1497 Self::Divide => "div",
1498 }
1499 }
1500
1501 #[doc(hidden)]
1502 pub fn arity(self) -> usize {
1503 match self {
1504 Self::Negate | Self::Conj => 1,
1505 Self::Add | Self::Subtract | Self::Multiply | Self::Divide => 2,
1506 }
1507 }
1508}
1509
1510#[derive(Clone, Copy, Debug)]
1511enum StorageIdentity {
1512 Host {
1513 start: usize,
1514 end: usize,
1515 },
1516 Backend {
1517 domain: Option<AllocationDomainId>,
1518 allocation: Option<AllocationId>,
1519 family: &'static str,
1520 object: usize,
1521 },
1522}
1523
1524fn host_storage_identity<T>(data: &[T]) -> StorageIdentity {
1525 let start = data.as_ptr() as usize;
1526 let bytes = std::mem::size_of_val(data);
1527 StorageIdentity::Host {
1528 start,
1529 end: start.saturating_add(bytes),
1530 }
1531}
1532
1533fn backend_storage_identity<T: 'static>(buffer: &dyn crate::BackendStorage<T>) -> StorageIdentity {
1534 StorageIdentity::Backend {
1535 domain: buffer.allocation_domain(),
1536 allocation: buffer.allocation_id(),
1537 family: buffer.backend_family(),
1538 // INVARIANT: every backend buffer is borrowed from the single Box-owned
1539 // root allocation; the data pointer of this trait object is stable for
1540 // that owner and is used only as a fallback when provider identity is
1541 // unavailable.
1542 object: buffer as *const dyn crate::BackendStorage<T> as *const () as usize,
1543 }
1544}
1545
1546fn typed_tensor_storage_identity<T: crate::TensorScalar>(
1547 tensor: &TypedTensor<T>,
1548) -> crate::Result<StorageIdentity> {
1549 if tensor.backend_buffer().is_some() {
1550 let buffer = tensor.backend_buffer().ok_or_else(|| {
1551 crate::Error::runtime_state("typed_tensor_storage_identity", "backend buffer missing")
1552 })?;
1553 Ok(backend_storage_identity(buffer))
1554 } else {
1555 Ok(host_storage_identity(tensor.host_data()?))
1556 }
1557}
1558
1559fn typed_view_storage_identity<T: crate::TensorScalar + 'static>(
1560 view: &TypedTensorView<'_, T>,
1561) -> crate::Result<StorageIdentity> {
1562 match view.backend_buffer() {
1563 Some(buffer) => Ok(backend_storage_identity(buffer)),
1564 None => view.host_storage().map(host_storage_identity),
1565 }
1566}
1567
1568fn tensor_read_storage_identity(input: &TensorRead<'_>) -> crate::Result<StorageIdentity> {
1569 macro_rules! typed_identity {
1570 ($value:expr) => {
1571 match $value {
1572 Tensor::F32(value) => typed_tensor_storage_identity(value),
1573 Tensor::F64(value) => typed_tensor_storage_identity(value),
1574 Tensor::I32(value) => typed_tensor_storage_identity(value),
1575 Tensor::I64(value) => typed_tensor_storage_identity(value),
1576 Tensor::Bool(value) => typed_tensor_storage_identity(value),
1577 Tensor::C32(value) => typed_tensor_storage_identity(value),
1578 Tensor::C64(value) => typed_tensor_storage_identity(value),
1579 }
1580 };
1581 }
1582 macro_rules! view_identity {
1583 ($value:expr) => {
1584 match $value {
1585 TensorView::F32(value) => typed_view_storage_identity(value),
1586 TensorView::F64(value) => typed_view_storage_identity(value),
1587 TensorView::I32(value) => typed_view_storage_identity(value),
1588 TensorView::I64(value) => typed_view_storage_identity(value),
1589 TensorView::Bool(value) => typed_view_storage_identity(value),
1590 TensorView::C32(value) => typed_view_storage_identity(value),
1591 TensorView::C64(value) => typed_view_storage_identity(value),
1592 }
1593 };
1594 }
1595
1596 match input {
1597 TensorRead::Tensor(tensor) => typed_identity!(tensor),
1598 TensorRead::View(view) => view_identity!(view),
1599 }
1600}
1601
1602fn storage_overlaps(lhs: StorageIdentity, rhs: StorageIdentity) -> bool {
1603 match (lhs, rhs) {
1604 (
1605 StorageIdentity::Host {
1606 start: lhs_start,
1607 end: lhs_end,
1608 },
1609 StorageIdentity::Host {
1610 start: rhs_start,
1611 end: rhs_end,
1612 },
1613 ) => lhs_start < rhs_end && rhs_start < lhs_end,
1614 (
1615 StorageIdentity::Backend {
1616 domain: lhs_domain,
1617 allocation: lhs_allocation,
1618 family: lhs_family,
1619 object: lhs_object,
1620 },
1621 StorageIdentity::Backend {
1622 domain: rhs_domain,
1623 allocation: rhs_allocation,
1624 family: rhs_family,
1625 object: rhs_object,
1626 },
1627 ) => {
1628 lhs_object == rhs_object
1629 || matches!(
1630 (lhs_domain, rhs_domain, lhs_allocation, rhs_allocation),
1631 (Some(lhs_domain), Some(rhs_domain), Some(lhs), Some(rhs))
1632 if lhs_domain == rhs_domain && lhs == rhs
1633 )
1634 || matches!(
1635 (lhs_domain, rhs_domain, lhs_allocation, rhs_allocation),
1636 (None, None, Some(lhs), Some(rhs)) if lhs_family == rhs_family && lhs == rhs
1637 )
1638 }
1639 _ => false,
1640 }
1641}
1642
1643/// Validate that a caller-owned destination does not overlap any read input.
1644///
1645/// The check is intentionally conservative for host views: two views backed by
1646/// the same host allocation are treated as overlapping because the allocation
1647/// identity is the only stable boundary contract available to erased backend
1648/// code. Backend allocations use their domain/allocation identity when the
1649/// provider exposes it.
1650///
1651/// # Errors
1652///
1653/// Returns `tenferro_tensor_core::ValidationError::InvalidArgument` when the
1654/// destination storage overlaps an input, or `Error::RuntimeState` when
1655/// storage identity cannot be established safely.
1656///
1657/// # Examples
1658///
1659/// ```rust
1660/// use tenferro_tensor::{Tensor, TensorRead, TensorWrite};
1661/// use tenferro_tensor::backend::validate_read_into_destination;
1662///
1663/// let input = Tensor::from_vec_col_major(vec![1], vec![1.0_f64])?;
1664/// let mut output = Tensor::from_vec_col_major(vec![1], vec![0.0_f64])?;
1665/// validate_read_into_destination(
1666/// "example",
1667/// &[TensorRead::from_tensor(&input)],
1668/// &TensorWrite::from_tensor(&mut output),
1669/// )?;
1670/// # Ok::<(), tenferro_tensor::Error>(())
1671/// ```
1672pub fn validate_read_into_destination(
1673 op: &'static str,
1674 inputs: &[TensorRead<'_>],
1675 out: &TensorWrite<'_>,
1676) -> crate::Result<()> {
1677 let output_identity = tensor_read_storage_identity(&out.as_read())?;
1678 for (index, input) in inputs.iter().enumerate() {
1679 if storage_overlaps(tensor_read_storage_identity(input)?, output_identity) {
1680 return Err(Error::invalid_argument(
1681 op,
1682 "out",
1683 format!("destination storage overlaps input {index}"),
1684 ));
1685 }
1686 }
1687 Ok(())
1688}
1689
1690/// Execute a read-into elementwise operation through a backend's allocating operations.
1691///
1692/// Backend implementations use this helper when their device-native operation
1693/// APIs already provide the correct placement and copy semantics. It deliberately
1694/// does not inspect or transfer storage across devices.
1695///
1696/// # Errors
1697///
1698/// Returns [`Error::Validation`] for wrong arity or overlapping output storage,
1699/// and propagates the selected backend operation and copy errors.
1700#[doc(hidden)]
1701pub fn elementwise_read_into_via_allocating_ops<B: TensorElementwise + ?Sized>(
1702 backend: &mut B,
1703 op: ElementwiseReadOp,
1704 inputs: &[TensorRead<'_>],
1705 out: TensorWrite<'_>,
1706) -> crate::Result<()> {
1707 if inputs.len() != op.arity() {
1708 return Err(Error::invalid_argument(
1709 op.label(),
1710 "inputs",
1711 format!("expected {} inputs, got {}", op.arity(), inputs.len()),
1712 ));
1713 }
1714 validate_read_into_destination(op.label(), inputs, &out)?;
1715 let result = match op {
1716 ElementwiseReadOp::Add => backend.add_read(inputs[0].clone(), inputs[1].clone())?,
1717 ElementwiseReadOp::Subtract => backend.sub_read(inputs[0].clone(), inputs[1].clone())?,
1718 ElementwiseReadOp::Multiply => backend.mul_read(inputs[0].clone(), inputs[1].clone())?,
1719 ElementwiseReadOp::Negate => backend.neg_read(inputs[0].clone())?,
1720 ElementwiseReadOp::Conj => backend.conj_read(inputs[0].clone())?,
1721 ElementwiseReadOp::Divide => backend.div_read(inputs[0].clone(), inputs[1].clone())?,
1722 };
1723 backend.copy_read_into(TensorRead::from_tensor(&result), out)
1724}
1725
1726/// Elementwise tensor operations.
1727///
1728/// # Examples
1729///
1730/// ```rust
1731/// use tenferro_tensor::TensorElementwise;
1732///
1733/// fn accepts_elementwise<B: TensorElementwise>(_backend: &mut B) {}
1734/// ```
1735pub trait TensorElementwise: TensorStructural {
1736 /// Execute an elementwise operation into caller-owned storage.
1737 ///
1738 /// Every backend must implement this hook with its explicit execution
1739 /// context, placement checks, and output-storage policy. The hook is the
1740 /// backend boundary for borrowed inputs and caller-owned output; it must not
1741 /// silently transfer data between devices.
1742 ///
1743 /// # Examples
1744 ///
1745 /// ```rust
1746 /// use tenferro_tensor::{ElementwiseReadOp, TensorElementwise, TensorRead, TensorWrite};
1747 ///
1748 /// fn run_into<B: TensorElementwise>(
1749 /// backend: &mut B,
1750 /// input: TensorRead<'_>,
1751 /// out: TensorWrite<'_>,
1752 /// ) -> tenferro_tensor::Result<()> {
1753 /// backend.elementwise_read_into(ElementwiseReadOp::Conj, &[input], out)
1754 /// }
1755 /// ```
1756 ///
1757 /// # Errors
1758 ///
1759 /// Returns [`crate::Error::Validation`] for invalid arity, metadata, or
1760 /// overlapping storage. Backend-specific placement, unsupported-operation,
1761 /// and execution errors are returned unchanged by the implementation.
1762 fn elementwise_read_into(
1763 &mut self,
1764 op: ElementwiseReadOp,
1765 inputs: &[TensorRead<'_>],
1766 out: TensorWrite<'_>,
1767 ) -> crate::Result<()>;
1768
1769 /// # Errors
1770 ///
1771 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1772 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1773 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1774 /// backend execution or storage access cannot provide the requested result.
1775 fn add(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1776
1777 /// Elementwise addition accepting either owned tensors or borrowed views.
1778 ///
1779 /// Backends that implement this method must not silently move data across
1780 /// devices. A backend that cannot consume views should return an explicit
1781 /// backend error rather than materializing or transferring implicitly.
1782 ///
1783 /// # Examples
1784 ///
1785 /// ```rust
1786 /// use tenferro_tensor::{Tensor, TensorElementwise, TensorRead};
1787 ///
1788 /// fn add_owned<B: TensorElementwise>(
1789 /// backend: &mut B,
1790 /// lhs: &Tensor,
1791 /// rhs: &Tensor,
1792 /// ) -> tenferro_tensor::Result<Tensor> {
1793 /// backend.add_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
1794 /// }
1795 /// ```
1796 /// # Errors
1797 ///
1798 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1799 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1800 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1801 /// backend execution or storage access cannot provide the requested result.
1802 fn add_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1803 self.add(read_tensor("add", lhs)?, read_tensor("add", rhs)?)
1804 }
1805
1806 /// Overwrite caller-provided output with elementwise addition.
1807 ///
1808 /// `_into` methods never accumulate into the previous output value.
1809 ///
1810 /// # Examples
1811 ///
1812 /// ```rust
1813 /// use tenferro_tensor::{Tensor, TensorElementwise, TensorWrite};
1814 ///
1815 /// fn add_into<B: TensorElementwise>(
1816 /// backend: &mut B,
1817 /// lhs: &Tensor,
1818 /// rhs: &Tensor,
1819 /// mut out: Tensor,
1820 /// ) -> tenferro_tensor::Result<Tensor> {
1821 /// backend.add_into(lhs, rhs, TensorWrite::from_tensor(&mut out))?;
1822 /// Ok(out)
1823 /// }
1824 /// ```
1825 /// # Errors
1826 ///
1827 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1828 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1829 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1830 /// backend execution or storage access cannot provide the requested result.
1831 fn add_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1832 self.add_read_into(
1833 TensorRead::from_tensor(lhs),
1834 TensorRead::from_tensor(rhs),
1835 out,
1836 )
1837 }
1838
1839 /// Overwrite caller-provided output with elementwise addition from reads.
1840 ///
1841 /// # Examples
1842 ///
1843 /// ```rust
1844 /// use tenferro_tensor::{TensorElementwise, TensorRead, TensorWrite};
1845 ///
1846 /// fn add_read_into<B: TensorElementwise>(
1847 /// backend: &mut B,
1848 /// lhs: TensorRead<'_>,
1849 /// rhs: TensorRead<'_>,
1850 /// out: TensorWrite<'_>,
1851 /// ) -> tenferro_tensor::Result<()> {
1852 /// backend.add_read_into(lhs, rhs, out)
1853 /// }
1854 /// ```
1855 /// # Errors
1856 ///
1857 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1858 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1859 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1860 /// backend execution or storage access cannot provide the requested result.
1861 fn add_read_into(
1862 &mut self,
1863 lhs: TensorRead<'_>,
1864 rhs: TensorRead<'_>,
1865 out: TensorWrite<'_>,
1866 ) -> crate::Result<()> {
1867 self.elementwise_read_into(ElementwiseReadOp::Add, &[lhs, rhs], out)
1868 }
1869
1870 /// # Errors
1871 ///
1872 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1873 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1874 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1875 /// backend execution or storage access cannot provide the requested result.
1876 fn sub(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1877
1878 /// Elementwise subtraction accepting either owned tensors or borrowed views.
1879 ///
1880 /// # Examples
1881 ///
1882 /// ```rust
1883 /// use tenferro_tensor::{Tensor, TensorElementwise, TensorRead};
1884 ///
1885 /// fn sub_owned<B: TensorElementwise>(
1886 /// backend: &mut B,
1887 /// lhs: &Tensor,
1888 /// rhs: &Tensor,
1889 /// ) -> tenferro_tensor::Result<Tensor> {
1890 /// backend.sub_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
1891 /// }
1892 /// ```
1893 /// # Errors
1894 ///
1895 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1896 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1897 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1898 /// backend execution or storage access cannot provide the requested result.
1899 fn sub_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1900 self.sub(read_tensor("sub", lhs)?, read_tensor("sub", rhs)?)
1901 }
1902
1903 /// Overwrite caller-provided output with elementwise subtraction.
1904 /// # Errors
1905 ///
1906 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1907 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1908 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1909 /// backend execution or storage access cannot provide the requested result.
1910 fn sub_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1911 self.sub_read_into(
1912 TensorRead::from_tensor(lhs),
1913 TensorRead::from_tensor(rhs),
1914 out,
1915 )
1916 }
1917
1918 /// Overwrite caller-provided output with elementwise subtraction from reads.
1919 /// # Errors
1920 ///
1921 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1922 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1923 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1924 /// backend execution or storage access cannot provide the requested result.
1925 fn sub_read_into(
1926 &mut self,
1927 lhs: TensorRead<'_>,
1928 rhs: TensorRead<'_>,
1929 out: TensorWrite<'_>,
1930 ) -> crate::Result<()> {
1931 self.elementwise_read_into(ElementwiseReadOp::Subtract, &[lhs, rhs], out)
1932 }
1933
1934 /// # Errors
1935 ///
1936 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1937 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1938 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1939 /// backend execution or storage access cannot provide the requested result.
1940 fn mul(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
1941 /// # Errors
1942 ///
1943 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1944 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1945 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1946 /// backend execution or storage access cannot provide the requested result.
1947 fn mul_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
1948 self.mul(read_tensor("mul", lhs)?, read_tensor("mul", rhs)?)
1949 }
1950
1951 /// Overwrite caller-provided output with elementwise multiplication.
1952 /// # Errors
1953 ///
1954 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1955 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1956 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1957 /// backend execution or storage access cannot provide the requested result.
1958 fn mul_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
1959 self.mul_read_into(
1960 TensorRead::from_tensor(lhs),
1961 TensorRead::from_tensor(rhs),
1962 out,
1963 )
1964 }
1965
1966 /// Overwrite caller-provided output with elementwise multiplication from reads.
1967 /// # Errors
1968 ///
1969 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1970 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1971 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1972 /// backend execution or storage access cannot provide the requested result.
1973 fn mul_read_into(
1974 &mut self,
1975 lhs: TensorRead<'_>,
1976 rhs: TensorRead<'_>,
1977 out: TensorWrite<'_>,
1978 ) -> crate::Result<()> {
1979 self.elementwise_read_into(ElementwiseReadOp::Multiply, &[lhs, rhs], out)
1980 }
1981
1982 /// # Errors
1983 ///
1984 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1985 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1986 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1987 /// backend execution or storage access cannot provide the requested result.
1988 fn neg(&mut self, input: &Tensor) -> crate::Result<Tensor>;
1989 /// # Errors
1990 ///
1991 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
1992 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
1993 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
1994 /// backend execution or storage access cannot provide the requested result.
1995 fn neg_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
1996 self.neg(read_tensor("neg", input)?)
1997 }
1998
1999 /// Overwrite caller-provided output with elementwise negation.
2000 /// # Errors
2001 ///
2002 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2003 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2004 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2005 /// backend execution or storage access cannot provide the requested result.
2006 fn neg_into(&mut self, input: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
2007 self.neg_read_into(TensorRead::from_tensor(input), out)
2008 }
2009
2010 /// Overwrite caller-provided output with elementwise negation from a read.
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 neg_read_into(&mut self, input: TensorRead<'_>, out: TensorWrite<'_>) -> crate::Result<()> {
2018 self.elementwise_read_into(ElementwiseReadOp::Negate, &[input], out)
2019 }
2020
2021 /// # Errors
2022 ///
2023 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2024 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2025 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2026 /// backend execution or storage access cannot provide the requested result.
2027 fn conj(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2028 /// # Errors
2029 ///
2030 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2031 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2032 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2033 /// backend execution or storage access cannot provide the requested result.
2034 fn conj_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2035 self.conj(read_tensor("conj", input)?)
2036 }
2037
2038 /// Overwrite caller-provided output with elementwise conjugation.
2039 /// # Errors
2040 ///
2041 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2042 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2043 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2044 /// backend execution or storage access cannot provide the requested result.
2045 fn conj_into(&mut self, input: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
2046 self.conj_read_into(TensorRead::from_tensor(input), out)
2047 }
2048
2049 /// Overwrite caller-provided output with elementwise conjugation from a read.
2050 /// # Errors
2051 ///
2052 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2053 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2054 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2055 /// backend execution or storage access cannot provide the requested result.
2056 fn conj_read_into(&mut self, input: TensorRead<'_>, out: TensorWrite<'_>) -> crate::Result<()> {
2057 self.elementwise_read_into(ElementwiseReadOp::Conj, &[input], out)
2058 }
2059
2060 /// # Errors
2061 ///
2062 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2063 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2064 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2065 /// backend execution or storage access cannot provide the requested result.
2066 fn div(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2067 /// # Errors
2068 ///
2069 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2070 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2071 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2072 /// backend execution or storage access cannot provide the requested result.
2073 fn div_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2074 self.div(read_tensor("div", lhs)?, read_tensor("div", rhs)?)
2075 }
2076
2077 /// Overwrite caller-provided output with elementwise division.
2078 /// # Errors
2079 ///
2080 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2081 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2082 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2083 /// backend execution or storage access cannot provide the requested result.
2084 fn div_into(&mut self, lhs: &Tensor, rhs: &Tensor, out: TensorWrite<'_>) -> crate::Result<()> {
2085 self.div_read_into(
2086 TensorRead::from_tensor(lhs),
2087 TensorRead::from_tensor(rhs),
2088 out,
2089 )
2090 }
2091
2092 /// Overwrite caller-provided output with elementwise division from reads.
2093 /// # Errors
2094 ///
2095 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2096 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2097 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2098 /// backend execution or storage access cannot provide the requested result.
2099 fn div_read_into(
2100 &mut self,
2101 lhs: TensorRead<'_>,
2102 rhs: TensorRead<'_>,
2103 out: TensorWrite<'_>,
2104 ) -> crate::Result<()> {
2105 self.elementwise_read_into(ElementwiseReadOp::Divide, &[lhs, rhs], out)
2106 }
2107
2108 /// Elementwise remainder.
2109 ///
2110 /// The default is an explicit unsupported error so backend implementors can
2111 /// opt in without silent fallback.
2112 ///
2113 /// # Examples
2114 ///
2115 /// ```rust
2116 /// use tenferro_tensor::{Tensor, TensorElementwise};
2117 ///
2118 /// fn rem_owned<B: TensorElementwise>(
2119 /// backend: &mut B,
2120 /// lhs: &Tensor,
2121 /// rhs: &Tensor,
2122 /// ) -> tenferro_tensor::Result<Tensor> {
2123 /// backend.rem(lhs, rhs)
2124 /// }
2125 /// ```
2126 /// # Errors
2127 ///
2128 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2129 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2130 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2131 /// backend execution or storage access cannot provide the requested result.
2132 fn rem(&mut self, lhs: &Tensor, _rhs: &Tensor) -> crate::Result<Tensor> {
2133 Err(crate::Error::unsupported(
2134 "rem",
2135 format!("backend does not implement rem for dtype {:?}", lhs.dtype()),
2136 ))
2137 }
2138
2139 /// Elementwise remainder accepting owned tensors or borrowed views.
2140 ///
2141 /// # Examples
2142 ///
2143 /// ```rust
2144 /// use tenferro_tensor::{Tensor, TensorElementwise, TensorRead};
2145 ///
2146 /// fn rem_read<B: TensorElementwise>(
2147 /// backend: &mut B,
2148 /// lhs: &Tensor,
2149 /// rhs: &Tensor,
2150 /// ) -> tenferro_tensor::Result<Tensor> {
2151 /// backend.rem_read(TensorRead::from_tensor(lhs), TensorRead::from_tensor(rhs))
2152 /// }
2153 /// ```
2154 /// # Errors
2155 ///
2156 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2157 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2158 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2159 /// backend execution or storage access cannot provide the requested result.
2160 fn rem_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2161 self.rem(read_tensor("rem", lhs)?, read_tensor("rem", rhs)?)
2162 }
2163
2164 /// # Errors
2165 ///
2166 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2167 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2168 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2169 /// backend execution or storage access cannot provide the requested result.
2170 fn abs(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2171 /// # Errors
2172 ///
2173 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2174 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2175 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2176 /// backend execution or storage access cannot provide the requested result.
2177 fn abs_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2178 self.abs(read_tensor("abs", input)?)
2179 }
2180
2181 /// # Errors
2182 ///
2183 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2184 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2185 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2186 /// backend execution or storage access cannot provide the requested result.
2187 fn sign(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2188 /// # Errors
2189 ///
2190 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2191 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2192 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2193 /// backend execution or storage access cannot provide the requested result.
2194 fn sign_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2195 self.sign(read_tensor("sign", input)?)
2196 }
2197
2198 /// # Errors
2199 ///
2200 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2201 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2202 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2203 /// backend execution or storage access cannot provide the requested result.
2204 fn maximum(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2205 /// # Errors
2206 ///
2207 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2208 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2209 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2210 /// backend execution or storage access cannot provide the requested result.
2211 fn maximum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2212 self.maximum(read_tensor("maximum", lhs)?, read_tensor("maximum", rhs)?)
2213 }
2214
2215 /// # Errors
2216 ///
2217 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2218 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2219 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2220 /// backend execution or storage access cannot provide the requested result.
2221 fn minimum(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2222 /// # Errors
2223 ///
2224 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2225 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2226 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2227 /// backend execution or storage access cannot provide the requested result.
2228 fn minimum_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2229 self.minimum(read_tensor("minimum", lhs)?, read_tensor("minimum", rhs)?)
2230 }
2231
2232 /// # Errors
2233 ///
2234 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2235 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2236 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2237 /// backend execution or storage access cannot provide the requested result.
2238 fn compare(&mut self, lhs: &Tensor, rhs: &Tensor, dir: &CompareDir) -> crate::Result<Tensor>;
2239 /// # Errors
2240 ///
2241 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2242 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2243 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2244 /// backend execution or storage access cannot provide the requested result.
2245 fn compare_read(
2246 &mut self,
2247 lhs: TensorRead<'_>,
2248 rhs: TensorRead<'_>,
2249 dir: &CompareDir,
2250 ) -> crate::Result<Tensor> {
2251 self.compare(
2252 read_tensor("compare", lhs)?,
2253 read_tensor("compare", rhs)?,
2254 dir,
2255 )
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 select(
2265 &mut self,
2266 pred: &Tensor,
2267 on_true: &Tensor,
2268 on_false: &Tensor,
2269 ) -> crate::Result<Tensor>;
2270 /// # Errors
2271 ///
2272 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2273 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2274 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2275 /// backend execution or storage access cannot provide the requested result.
2276 fn select_read(
2277 &mut self,
2278 pred: TensorRead<'_>,
2279 on_true: TensorRead<'_>,
2280 on_false: TensorRead<'_>,
2281 ) -> crate::Result<Tensor> {
2282 self.select(
2283 read_tensor("select", pred)?,
2284 read_tensor("select", on_true)?,
2285 read_tensor("select", on_false)?,
2286 )
2287 }
2288
2289 /// # Errors
2290 ///
2291 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2292 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2293 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2294 /// backend execution or storage access cannot provide the requested result.
2295 fn clamp(&mut self, input: &Tensor, lower: &Tensor, upper: &Tensor) -> crate::Result<Tensor>;
2296 /// # Errors
2297 ///
2298 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2299 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2300 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2301 /// backend execution or storage access cannot provide the requested result.
2302 fn clamp_read(
2303 &mut self,
2304 input: TensorRead<'_>,
2305 lower: TensorRead<'_>,
2306 upper: TensorRead<'_>,
2307 ) -> crate::Result<Tensor> {
2308 self.clamp(
2309 read_tensor("clamp", input)?,
2310 read_tensor("clamp", lower)?,
2311 read_tensor("clamp", upper)?,
2312 )
2313 }
2314}
2315
2316/// Analytic unary and binary tensor operations.
2317///
2318/// # Examples
2319///
2320/// ```rust
2321/// use tenferro_tensor::TensorAnalytic;
2322///
2323/// fn accepts_analytic<B: TensorAnalytic>(_backend: &mut B) {}
2324/// ```
2325pub trait TensorAnalytic {
2326 /// # Errors
2327 ///
2328 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2329 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2330 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2331 /// backend execution or storage access cannot provide the requested result.
2332 fn exp(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2333 /// # Errors
2334 ///
2335 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2336 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2337 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2338 /// backend execution or storage access cannot provide the requested result.
2339 fn exp_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2340 self.exp(read_tensor("exp", input)?)
2341 }
2342
2343 /// # Errors
2344 ///
2345 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2346 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2347 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2348 /// backend execution or storage access cannot provide the requested result.
2349 fn log(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2350 /// # Errors
2351 ///
2352 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2353 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2354 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2355 /// backend execution or storage access cannot provide the requested result.
2356 fn log_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2357 self.log(read_tensor("log", input)?)
2358 }
2359
2360 /// # Errors
2361 ///
2362 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2363 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2364 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2365 /// backend execution or storage access cannot provide the requested result.
2366 fn sin(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2367 /// # Errors
2368 ///
2369 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2370 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2371 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2372 /// backend execution or storage access cannot provide the requested result.
2373 fn sin_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2374 self.sin(read_tensor("sin", input)?)
2375 }
2376
2377 /// # Errors
2378 ///
2379 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2380 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2381 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2382 /// backend execution or storage access cannot provide the requested result.
2383 fn cos(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2384 /// # Errors
2385 ///
2386 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2387 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2388 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2389 /// backend execution or storage access cannot provide the requested result.
2390 fn cos_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2391 self.cos(read_tensor("cos", input)?)
2392 }
2393
2394 /// # Errors
2395 ///
2396 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2397 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2398 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2399 /// backend execution or storage access cannot provide the requested result.
2400 fn tanh(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2401 /// # Errors
2402 ///
2403 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2404 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2405 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2406 /// backend execution or storage access cannot provide the requested result.
2407 fn tanh_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2408 self.tanh(read_tensor("tanh", input)?)
2409 }
2410
2411 /// # Errors
2412 ///
2413 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2414 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2415 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2416 /// backend execution or storage access cannot provide the requested result.
2417 fn sqrt(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2418 /// # Errors
2419 ///
2420 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2421 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2422 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2423 /// backend execution or storage access cannot provide the requested result.
2424 fn sqrt_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2425 self.sqrt(read_tensor("sqrt", input)?)
2426 }
2427
2428 /// # Errors
2429 ///
2430 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2431 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2432 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2433 /// backend execution or storage access cannot provide the requested result.
2434 fn rsqrt(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2435 /// # Errors
2436 ///
2437 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2438 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2439 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2440 /// backend execution or storage access cannot provide the requested result.
2441 fn rsqrt_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2442 self.rsqrt(read_tensor("rsqrt", input)?)
2443 }
2444
2445 /// # Errors
2446 ///
2447 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2448 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2449 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2450 /// backend execution or storage access cannot provide the requested result.
2451 fn pow(&mut self, lhs: &Tensor, rhs: &Tensor) -> crate::Result<Tensor>;
2452 /// # Errors
2453 ///
2454 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2455 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2456 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2457 /// backend execution or storage access cannot provide the requested result.
2458 fn pow_read(&mut self, lhs: TensorRead<'_>, rhs: TensorRead<'_>) -> crate::Result<Tensor> {
2459 self.pow(read_tensor("pow", lhs)?, read_tensor("pow", rhs)?)
2460 }
2461
2462 /// # Errors
2463 ///
2464 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2465 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2466 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2467 /// backend execution or storage access cannot provide the requested result.
2468 fn expm1(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2469 /// # Errors
2470 ///
2471 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2472 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2473 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2474 /// backend execution or storage access cannot provide the requested result.
2475 fn expm1_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2476 self.expm1(read_tensor("expm1", input)?)
2477 }
2478
2479 /// # Errors
2480 ///
2481 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2482 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2483 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2484 /// backend execution or storage access cannot provide the requested result.
2485 fn log1p(&mut self, input: &Tensor) -> crate::Result<Tensor>;
2486 /// # Errors
2487 ///
2488 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2489 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2490 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2491 /// backend execution or storage access cannot provide the requested result.
2492 fn log1p_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2493 self.log1p(read_tensor("log1p", input)?)
2494 }
2495}
2496
2497/// Shape, layout, and dtype transformation operations.
2498///
2499/// # Examples
2500///
2501/// ```rust
2502/// use tenferro_tensor::TensorStructural;
2503///
2504/// fn accepts_structural<B: TensorStructural>(_backend: &mut B) {}
2505/// ```
2506pub trait TensorStructural {
2507 /// Materialize an owned tensor or borrowed view into fresh compact storage.
2508 ///
2509 /// The result has the input's shape and dtype, uses compact column-major
2510 /// layout, and remains in the input's placement. This operation is a
2511 /// same-placement canonicalization boundary, never an implicit host/device
2512 /// transfer. The conservative default accepts only compact host-owned
2513 /// tensors and clones them; it rejects views, backend buffers, and device
2514 /// placement because only an owning backend can materialize those safely.
2515 ///
2516 /// Backend overrides may accept strided views. CUDA accepts numeric and
2517 /// complex views on its active device, including arbitrary valid strides,
2518 /// but currently reports an explicit unsupported-dtype error for `Bool`.
2519 ///
2520 /// # Examples
2521 ///
2522 /// ```rust
2523 /// use tenferro_tensor::{DType, Tensor, TensorRead, TensorStructural};
2524 ///
2525 /// struct HostDefaults;
2526 /// impl TensorStructural for HostDefaults {
2527 /// fn transpose(&mut self, _: &Tensor, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2528 /// fn reshape(&mut self, _: &Tensor, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2529 /// fn broadcast_in_dim(&mut self, _: &Tensor, _: &[usize], _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2530 /// fn cast(&mut self, _: &Tensor, _: DType) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2531 /// fn extract_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2532 /// fn embed_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2533 /// fn tril(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2534 /// fn triu(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2535 /// }
2536 ///
2537 /// let input = Tensor::from_vec_col_major(vec![2], vec![1_i32, 2])?;
2538 /// let mut backend = HostDefaults;
2539 /// let structural: &mut dyn TensorStructural = &mut backend;
2540 /// let output = structural.to_contiguous_read(TensorRead::from_tensor(&input))?;
2541 /// assert_eq!(output.shape(), &[2]);
2542 /// assert_eq!(output.as_slice::<i32>()?, &[1, 2]);
2543 /// # Ok::<(), tenferro_tensor::Error>(())
2544 /// ```
2545 /// # Errors
2546 ///
2547 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2548 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2549 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2550 /// backend execution or storage access cannot provide the requested result.
2551 fn to_contiguous_read(&mut self, input: TensorRead<'_>) -> crate::Result<Tensor> {
2552 match input {
2553 TensorRead::Tensor(input) => {
2554 if input.is_backend_buffer()
2555 || !matches!(
2556 input.placement().memory_kind,
2557 crate::MemoryKind::PinnedHost | crate::MemoryKind::UnpinnedHost
2558 )
2559 {
2560 return Err(crate::Error::runtime_state(
2561 "to_contiguous_read",
2562 "default materialization accepts only host-owned tensors; use the storage's owning backend",
2563 ));
2564 }
2565 input.duplicate()
2566 }
2567 TensorRead::View(view) => {
2568 if view.backend_family().is_some()
2569 || !matches!(
2570 view.placement().memory_kind,
2571 crate::MemoryKind::PinnedHost | crate::MemoryKind::UnpinnedHost
2572 )
2573 {
2574 return Err(crate::Error::runtime_state(
2575 "to_contiguous_read",
2576 "default materialization accepts only host-owned tensors; use the storage's owning backend",
2577 ));
2578 }
2579 view.duplicate()
2580 }
2581 }
2582 }
2583
2584 /// Overwrite caller-provided storage from a readable tensor or view.
2585 ///
2586 /// Source and destination must have identical dtype and shape and belong to
2587 /// the executing backend's placement. The destination is not resized, and
2588 /// every logical destination element is overwritten without reading its old
2589 /// value. Source and destination allocations must not alias. Implementations
2590 /// must not materialize through host memory or perform an implicit transfer.
2591 ///
2592 /// CPU accepts arbitrary valid source and destination strides and performs
2593 /// no tensor allocation. CUDA currently accepts only a compact column-major
2594 /// source with offset zero covering its full allocation; CUDA destinations
2595 /// may be arbitrary valid non-overlapping views. CUDA rejects aliased
2596 /// allocations and currently reports an explicit unsupported-dtype error
2597 /// for `Bool`. The conservative default is explicitly unsupported.
2598 ///
2599 /// # Examples
2600 ///
2601 /// ```rust
2602 /// use tenferro_tensor::{DType, Tensor, TensorRead, TensorStructural, TensorWrite};
2603 ///
2604 /// struct ConservativeDefaults;
2605 /// impl TensorStructural for ConservativeDefaults {
2606 /// fn transpose(&mut self, _: &Tensor, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2607 /// fn reshape(&mut self, _: &Tensor, _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2608 /// fn broadcast_in_dim(&mut self, _: &Tensor, _: &[usize], _: &[usize]) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2609 /// fn cast(&mut self, _: &Tensor, _: DType) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2610 /// fn extract_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2611 /// fn embed_diagonal(&mut self, _: &Tensor, _: usize, _: usize) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2612 /// fn tril(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2613 /// fn triu(&mut self, _: &Tensor, _: i64) -> tenferro_tensor::Result<Tensor> { unimplemented!() }
2614 /// }
2615 ///
2616 /// let src = Tensor::from_vec_col_major(vec![2], vec![1_i32, 2])?;
2617 /// let mut dst = Tensor::from_vec_col_major(vec![2], vec![0_i32, 0])?;
2618 /// let mut backend = ConservativeDefaults;
2619 /// let structural: &mut dyn TensorStructural = &mut backend;
2620 /// let error = structural.copy_read_into(
2621 /// TensorRead::from_tensor(&src),
2622 /// TensorWrite::from_tensor(&mut dst),
2623 /// ).unwrap_err();
2624 /// assert!(error.to_string().contains("unsupported"));
2625 /// assert_eq!(dst.as_slice::<i32>()?, &[0, 0]);
2626 /// # Ok::<(), tenferro_tensor::Error>(())
2627 /// ```
2628 /// # Errors
2629 ///
2630 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2631 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2632 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2633 /// backend execution or storage access cannot provide the requested result.
2634 fn copy_read_into(&mut self, _src: TensorRead<'_>, _dst: TensorWrite<'_>) -> crate::Result<()> {
2635 Err(crate::Error::unsupported(
2636 "copy_read_into",
2637 "backend-owned runtime copy is unsupported by this backend",
2638 ))
2639 }
2640
2641 /// # Errors
2642 ///
2643 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2644 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2645 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2646 /// backend execution or storage access cannot provide the requested result.
2647 fn transpose(&mut self, input: &Tensor, perm: &[usize]) -> crate::Result<Tensor>;
2648 /// # Errors
2649 ///
2650 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2651 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2652 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2653 /// backend execution or storage access cannot provide the requested result.
2654 fn transpose_read(&mut self, input: TensorRead<'_>, perm: &[usize]) -> crate::Result<Tensor> {
2655 self.transpose(read_tensor("transpose", input)?, perm)
2656 }
2657
2658 /// # Errors
2659 ///
2660 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2661 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2662 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2663 /// backend execution or storage access cannot provide the requested result.
2664 fn reshape(&mut self, input: &Tensor, shape: &[usize]) -> crate::Result<Tensor>;
2665 /// # Errors
2666 ///
2667 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2668 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2669 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2670 /// backend execution or storage access cannot provide the requested result.
2671 fn reshape_read(&mut self, input: TensorRead<'_>, shape: &[usize]) -> crate::Result<Tensor> {
2672 self.reshape(read_tensor("reshape", input)?, shape)
2673 }
2674
2675 /// # Errors
2676 ///
2677 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2678 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2679 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2680 /// backend execution or storage access cannot provide the requested result.
2681 fn broadcast_in_dim(
2682 &mut self,
2683 input: &Tensor,
2684 shape: &[usize],
2685 dims: &[usize],
2686 ) -> crate::Result<Tensor>;
2687 /// # Errors
2688 ///
2689 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2690 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2691 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2692 /// backend execution or storage access cannot provide the requested result.
2693 fn broadcast_in_dim_read(
2694 &mut self,
2695 input: TensorRead<'_>,
2696 shape: &[usize],
2697 dims: &[usize],
2698 ) -> crate::Result<Tensor> {
2699 self.broadcast_in_dim(read_tensor("broadcast_in_dim", input)?, shape, dims)
2700 }
2701
2702 /// Cast a tensor to another dtype using explicit dtype projection.
2703 ///
2704 /// Backends may truncate, narrow precision, project complex values, or use
2705 /// boolean truthiness according to their documented cast support.
2706 ///
2707 /// # Examples
2708 ///
2709 /// ```rust
2710 /// use tenferro_tensor::{DType, Tensor, TensorStructural};
2711 ///
2712 /// fn cast_to_i32<B: TensorStructural>(
2713 /// backend: &mut B,
2714 /// input: &Tensor,
2715 /// ) -> tenferro_tensor::Result<Tensor> {
2716 /// backend.cast(input, DType::I32)
2717 /// }
2718 /// ```
2719 /// # Errors
2720 ///
2721 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2722 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2723 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2724 /// backend execution or storage access cannot provide the requested result.
2725 fn cast(&mut self, input: &Tensor, to: crate::DType) -> crate::Result<Tensor>;
2726
2727 /// Convert a tensor to another dtype using checked dtype conversion.
2728 ///
2729 /// `convert` accepts only conversions allowed by tenferro's dtype-promotion
2730 /// lattice. Use [`TensorStructural::cast`] for explicit lossy projection.
2731 ///
2732 /// # Examples
2733 ///
2734 /// ```rust
2735 /// use tenferro_tensor::{DType, Tensor, TensorStructural};
2736 ///
2737 /// fn convert_to_f64<B: TensorStructural>(
2738 /// backend: &mut B,
2739 /// input: &Tensor,
2740 /// ) -> tenferro_tensor::Result<Tensor> {
2741 /// backend.convert(input, DType::F64)
2742 /// }
2743 /// ```
2744 /// # Errors
2745 ///
2746 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2747 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2748 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2749 /// backend execution or storage access cannot provide the requested result.
2750 fn convert(&mut self, input: &Tensor, to: crate::DType) -> crate::Result<Tensor> {
2751 validate_convert_dtype("convert", input.dtype(), to)?;
2752 self.cast(input, to)
2753 }
2754
2755 /// # Errors
2756 ///
2757 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2758 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2759 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2760 /// backend execution or storage access cannot provide the requested result.
2761 fn extract_diagonal(
2762 &mut self,
2763 input: &Tensor,
2764 axis_a: usize,
2765 axis_b: usize,
2766 ) -> crate::Result<Tensor>;
2767 /// # Errors
2768 ///
2769 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2770 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2771 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2772 /// backend execution or storage access cannot provide the requested result.
2773 fn embed_diagonal(
2774 &mut self,
2775 input: &Tensor,
2776 axis_a: usize,
2777 axis_b: usize,
2778 ) -> crate::Result<Tensor>;
2779 /// # Errors
2780 ///
2781 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2782 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2783 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2784 /// backend execution or storage access cannot provide the requested result.
2785 fn tril(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor>;
2786 /// # Errors
2787 ///
2788 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2789 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2790 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2791 /// backend execution or storage access cannot provide the requested result.
2792 fn triu(&mut self, input: &Tensor, k: i64) -> crate::Result<Tensor>;
2793}
2794
2795/// Reduction operations.
2796///
2797/// Reducing over an axis whose extent is zero returns an error for every
2798/// reduction operation. Passing an empty `axes` slice is a no-op for the public
2799/// reductions and returns the input values unchanged. Internal mapped
2800/// reductions document their own empty-axis semantics.
2801///
2802/// # Examples
2803///
2804/// ```rust
2805/// use tenferro_tensor::TensorReduction;
2806///
2807/// fn accepts_reduction<B: TensorReduction>(_backend: &mut B) {}
2808/// ```
2809pub trait TensorReduction {
2810 /// # Errors
2811 ///
2812 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2813 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2814 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2815 /// backend execution or storage access cannot provide the requested result.
2816 fn reduce_sum(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
2817
2818 /// Sum elements across axes from an owned tensor or borrowed view.
2819 ///
2820 /// # Examples
2821 ///
2822 /// ```rust
2823 /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2824 ///
2825 /// fn sum_owned<B: TensorReduction>(
2826 /// backend: &mut B,
2827 /// input: &Tensor,
2828 /// ) -> tenferro_tensor::Result<Tensor> {
2829 /// backend.reduce_sum_read(TensorRead::from_tensor(input), &[0])
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 reduce_sum_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
2839 match input.as_tensor() {
2840 Some(input) => self.reduce_sum(input, axes),
2841 None => Err(crate::Error::unsupported(
2842 "reduce_sum",
2843 "backend does not accept borrowed tensor views at this execution boundary",
2844 )),
2845 }
2846 }
2847
2848 /// Sum elementwise squares across axes.
2849 ///
2850 /// This execution hook is used by composite operations that avoid a
2851 /// materialized square. Empty axes produce an elementwise square. Backends
2852 /// that support this optimized path must override the hook directly.
2853 ///
2854 /// # Errors
2855 ///
2856 /// Returns the typed validation, unsupported, runtime-state, or backend
2857 /// error produced by multiplication or reduction.
2858 #[doc(hidden)]
2859 fn reduce_sum_squares_read(
2860 &mut self,
2861 _input: TensorRead<'_>,
2862 _axes: &[usize],
2863 ) -> crate::Result<Tensor> {
2864 Err(crate::Error::unsupported(
2865 "reduce_sum_squares",
2866 "backend does not implement fused sum-of-squares reduction",
2867 ))
2868 }
2869
2870 /// # Errors
2871 ///
2872 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2873 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2874 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2875 /// backend execution or storage access cannot provide the requested result.
2876 fn reduce_prod(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
2877
2878 /// Multiply elements across axes from an owned tensor or borrowed view.
2879 ///
2880 /// # Examples
2881 ///
2882 /// ```rust
2883 /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2884 ///
2885 /// fn prod_owned<B: TensorReduction>(
2886 /// backend: &mut B,
2887 /// input: &Tensor,
2888 /// ) -> tenferro_tensor::Result<Tensor> {
2889 /// backend.reduce_prod_read(TensorRead::from_tensor(input), &[0])
2890 /// }
2891 /// ```
2892 /// # Errors
2893 ///
2894 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2895 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2896 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2897 /// backend execution or storage access cannot provide the requested result.
2898 fn reduce_prod_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
2899 match input.as_tensor() {
2900 Some(input) => self.reduce_prod(input, axes),
2901 None => Err(crate::Error::unsupported(
2902 "reduce_prod",
2903 "backend does not accept borrowed tensor views at this execution boundary",
2904 )),
2905 }
2906 }
2907
2908 /// # Errors
2909 ///
2910 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2911 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2912 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2913 /// backend execution or storage access cannot provide the requested result.
2914 fn reduce_max(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
2915
2916 /// Take maximum values across axes from an owned tensor or borrowed view.
2917 ///
2918 /// # Examples
2919 ///
2920 /// ```rust
2921 /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2922 ///
2923 /// fn max_owned<B: TensorReduction>(
2924 /// backend: &mut B,
2925 /// input: &Tensor,
2926 /// ) -> tenferro_tensor::Result<Tensor> {
2927 /// backend.reduce_max_read(TensorRead::from_tensor(input), &[0])
2928 /// }
2929 /// ```
2930 /// # Errors
2931 ///
2932 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2933 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2934 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2935 /// backend execution or storage access cannot provide the requested result.
2936 fn reduce_max_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
2937 match input.as_tensor() {
2938 Some(input) => self.reduce_max(input, axes),
2939 None => Err(crate::Error::unsupported(
2940 "reduce_max",
2941 "backend does not accept borrowed tensor views at this execution boundary",
2942 )),
2943 }
2944 }
2945
2946 /// # Errors
2947 ///
2948 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2949 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2950 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2951 /// backend execution or storage access cannot provide the requested result.
2952 fn reduce_min(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
2953
2954 /// Take minimum values across axes from an owned tensor or borrowed view.
2955 ///
2956 /// # Examples
2957 ///
2958 /// ```rust
2959 /// use tenferro_tensor::{Tensor, TensorRead, TensorReduction};
2960 ///
2961 /// fn min_owned<B: TensorReduction>(
2962 /// backend: &mut B,
2963 /// input: &Tensor,
2964 /// ) -> tenferro_tensor::Result<Tensor> {
2965 /// backend.reduce_min_read(TensorRead::from_tensor(input), &[0])
2966 /// }
2967 /// ```
2968 /// # Errors
2969 ///
2970 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2971 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2972 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
2973 /// backend execution or storage access cannot provide the requested result.
2974 fn reduce_min_read(&mut self, input: TensorRead<'_>, axes: &[usize]) -> crate::Result<Tensor> {
2975 match input.as_tensor() {
2976 Some(input) => self.reduce_min(input, axes),
2977 None => Err(crate::Error::unsupported(
2978 "reduce_min",
2979 "backend does not accept borrowed tensor views at this execution boundary",
2980 )),
2981 }
2982 }
2983}
2984
2985/// Dot-general operations.
2986///
2987/// # Examples
2988///
2989/// ```rust
2990/// use tenferro_tensor::TensorDot;
2991///
2992/// fn accepts_dot<B: TensorDot>(_backend: &mut B) {}
2993/// ```
2994pub trait TensorDot: TensorElementwise {
2995 /// # Errors
2996 ///
2997 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
2998 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
2999 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3000 /// backend execution or storage access cannot provide the requested result.
3001 fn dot_general(
3002 &mut self,
3003 lhs: &Tensor,
3004 rhs: &Tensor,
3005 config: &DotGeneralConfig,
3006 ) -> crate::Result<Tensor>;
3007
3008 #[doc(hidden)]
3009 fn dot_general_read(
3010 &mut self,
3011 lhs: TensorRead<'_>,
3012 rhs: TensorRead<'_>,
3013 config: &DotGeneralConfig,
3014 ) -> crate::Result<Tensor> {
3015 match (lhs.as_tensor(), rhs.as_tensor()) {
3016 (Some(lhs), Some(rhs)) => self.dot_general(lhs, rhs, config),
3017 _ => {
3018 let lhs = self.to_contiguous_read(lhs)?;
3019 let rhs = self.to_contiguous_read(rhs)?;
3020 self.dot_general(&lhs, &rhs, config)
3021 }
3022 }
3023 }
3024
3025 /// Overwrite caller-provided output with dot-general from read inputs.
3026 ///
3027 /// This is the dot/GEMM spelling of `_into`: the previous output value is
3028 /// not read. Use [`TensorDot::dot_general_read_into_accum`] for explicit
3029 /// read-modify-write accumulation.
3030 ///
3031 /// # Examples
3032 ///
3033 /// ```rust
3034 /// use tenferro_tensor::{DotGeneralConfig, TensorDot, TensorRead, TensorWrite};
3035 ///
3036 /// fn dot_into<B: TensorDot>(
3037 /// backend: &mut B,
3038 /// lhs: TensorRead<'_>,
3039 /// rhs: TensorRead<'_>,
3040 /// config: &DotGeneralConfig,
3041 /// out: TensorWrite<'_>,
3042 /// ) -> tenferro_tensor::Result<()> {
3043 /// backend.dot_general_read_into(lhs, rhs, config, out)
3044 /// }
3045 /// ```
3046 /// # Errors
3047 ///
3048 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3049 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3050 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3051 /// backend execution or storage access cannot provide the requested result.
3052 fn dot_general_read_into(
3053 &mut self,
3054 lhs: TensorRead<'_>,
3055 rhs: TensorRead<'_>,
3056 config: &DotGeneralConfig,
3057 out: TensorWrite<'_>,
3058 ) -> crate::Result<()> {
3059 let accumulation = DotGeneralAccumulation::overwrite(lhs.dtype())?;
3060 self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
3061 }
3062
3063 #[doc(hidden)]
3064 fn dot_general_with_conj(
3065 &mut self,
3066 lhs: &Tensor,
3067 rhs: &Tensor,
3068 config: &DotGeneralConfig,
3069 lhs_conj: bool,
3070 rhs_conj: bool,
3071 ) -> crate::Result<Tensor> {
3072 if !lhs_conj && !rhs_conj {
3073 return self.dot_general(lhs, rhs, config);
3074 }
3075
3076 let lhs_tmp;
3077 let lhs_ref = if lhs_conj {
3078 lhs_tmp = self.conj(lhs)?;
3079 &lhs_tmp
3080 } else {
3081 lhs
3082 };
3083 let rhs_tmp;
3084 let rhs_ref = if rhs_conj {
3085 rhs_tmp = self.conj(rhs)?;
3086 &rhs_tmp
3087 } else {
3088 rhs
3089 };
3090 self.dot_general(lhs_ref, rhs_ref, config)
3091 }
3092
3093 #[allow(clippy::too_many_arguments)]
3094 #[doc(hidden)]
3095 fn dot_general_with_conj_read(
3096 &mut self,
3097 lhs: TensorRead<'_>,
3098 rhs: TensorRead<'_>,
3099 config: &DotGeneralConfig,
3100 lhs_conj: bool,
3101 rhs_conj: bool,
3102 ) -> crate::Result<Tensor> {
3103 if !lhs_conj && !rhs_conj {
3104 return self.dot_general_read(lhs, rhs, config);
3105 }
3106
3107 let lhs_tmp;
3108 let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
3109 tensor
3110 } else {
3111 lhs_tmp = self.to_contiguous_read(lhs)?;
3112 &lhs_tmp
3113 };
3114 let rhs_tmp;
3115 let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
3116 tensor
3117 } else {
3118 rhs_tmp = self.to_contiguous_read(rhs)?;
3119 &rhs_tmp
3120 };
3121 self.dot_general_with_conj(lhs_ref, rhs_ref, config, lhs_conj, rhs_conj)
3122 }
3123
3124 /// Apply scaled dot-general accumulation into caller-provided output.
3125 ///
3126 /// This is explicitly read-modify-write when `accumulation.beta` is nonzero:
3127 /// `out = alpha * dot_general(lhs, rhs) + beta * out`.
3128 ///
3129 /// # Examples
3130 ///
3131 /// ```rust
3132 /// use tenferro_tensor::{
3133 /// DotGeneralAccumulation, DotGeneralConfig, TensorDot, TensorRead, TensorWrite,
3134 /// };
3135 ///
3136 /// fn dot_add_to<B: TensorDot>(
3137 /// backend: &mut B,
3138 /// lhs: TensorRead<'_>,
3139 /// rhs: TensorRead<'_>,
3140 /// config: &DotGeneralConfig,
3141 /// out: TensorWrite<'_>,
3142 /// ) -> tenferro_tensor::Result<()> {
3143 /// let accumulation = DotGeneralAccumulation::add_to(lhs.dtype())?;
3144 /// backend.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
3145 /// }
3146 /// ```
3147 /// # Errors
3148 ///
3149 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3150 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3151 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3152 /// backend execution or storage access cannot provide the requested result.
3153 fn dot_general_read_into_accum(
3154 &mut self,
3155 lhs: TensorRead<'_>,
3156 rhs: TensorRead<'_>,
3157 config: &DotGeneralConfig,
3158 accumulation: DotGeneralAccumulation,
3159 out: TensorWrite<'_>,
3160 ) -> crate::Result<()> {
3161 dot_general_accum_via_temp(self, lhs, rhs, config, accumulation, out)
3162 }
3163}
3164
3165/// Session-scoped cached dot-general operations.
3166///
3167/// # Examples
3168///
3169/// ```rust
3170/// use tenferro_tensor::BackendSession;
3171///
3172/// fn accepts_session_dot<S: BackendSession + ?Sized>(_session: &mut S) {}
3173/// ```
3174pub trait SessionCachedDot: TensorDot {
3175 #[doc(hidden)]
3176 fn dot_general_cached(
3177 &mut self,
3178 _cache_slot: Option<usize>,
3179 lhs: &Tensor,
3180 rhs: &Tensor,
3181 config: &DotGeneralConfig,
3182 ) -> crate::Result<Tensor> {
3183 self.dot_general(lhs, rhs, config)
3184 }
3185
3186 #[doc(hidden)]
3187 fn dot_general_read_cached(
3188 &mut self,
3189 cache_slot: Option<usize>,
3190 lhs: TensorRead<'_>,
3191 rhs: TensorRead<'_>,
3192 config: &DotGeneralConfig,
3193 ) -> crate::Result<Tensor> {
3194 match (lhs.as_tensor(), rhs.as_tensor()) {
3195 (Some(lhs), Some(rhs)) => self.dot_general_cached(cache_slot, lhs, rhs, config),
3196 _ => {
3197 let lhs = self.to_contiguous_read(lhs)?;
3198 let rhs = self.to_contiguous_read(rhs)?;
3199 self.dot_general_cached(cache_slot, &lhs, &rhs, config)
3200 }
3201 }
3202 }
3203
3204 // Mirrors the dot-general signature plus runtime-cache metadata.
3205 #[allow(clippy::too_many_arguments)]
3206 #[doc(hidden)]
3207 fn dot_general_with_conj_cached(
3208 &mut self,
3209 _cache_slot: Option<usize>,
3210 lhs: &Tensor,
3211 rhs: &Tensor,
3212 config: &DotGeneralConfig,
3213 lhs_conj: bool,
3214 rhs_conj: bool,
3215 ) -> crate::Result<Tensor> {
3216 self.dot_general_with_conj(lhs, rhs, config, lhs_conj, rhs_conj)
3217 }
3218
3219 // Mirrors the dot-general read signature plus runtime-cache metadata.
3220 #[allow(clippy::too_many_arguments)]
3221 #[doc(hidden)]
3222 fn dot_general_with_conj_read_cached(
3223 &mut self,
3224 cache_slot: Option<usize>,
3225 lhs: TensorRead<'_>,
3226 rhs: TensorRead<'_>,
3227 config: &DotGeneralConfig,
3228 lhs_conj: bool,
3229 rhs_conj: bool,
3230 ) -> crate::Result<Tensor> {
3231 if !lhs_conj && !rhs_conj {
3232 return self.dot_general_read_cached(cache_slot, lhs, rhs, config);
3233 }
3234
3235 let lhs_tmp;
3236 let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
3237 tensor
3238 } else {
3239 lhs_tmp = self.to_contiguous_read(lhs)?;
3240 &lhs_tmp
3241 };
3242 let rhs_tmp;
3243 let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
3244 tensor
3245 } else {
3246 rhs_tmp = self.to_contiguous_read(rhs)?;
3247 &rhs_tmp
3248 };
3249 self.dot_general_with_conj_cached(cache_slot, lhs_ref, rhs_ref, config, lhs_conj, rhs_conj)
3250 }
3251
3252 /// Apply session-cached scaled dot-general accumulation into output.
3253 ///
3254 /// The cache slot is session-local metadata; `accumulation` still controls
3255 /// overwrite versus read-modify-write semantics.
3256 ///
3257 /// # Examples
3258 ///
3259 /// ```rust
3260 /// use tenferro_tensor::{
3261 /// DotGeneralAccumulation, DotGeneralConfig, SessionCachedDot, TensorRead, TensorWrite,
3262 /// };
3263 ///
3264 /// fn session_cached_dot_add_to<S: SessionCachedDot + ?Sized>(
3265 /// session: &mut S,
3266 /// lhs: TensorRead<'_>,
3267 /// rhs: TensorRead<'_>,
3268 /// config: &DotGeneralConfig,
3269 /// out: TensorWrite<'_>,
3270 /// ) -> tenferro_tensor::Result<()> {
3271 /// let accumulation = DotGeneralAccumulation::add_to(lhs.dtype())?;
3272 /// session.dot_general_read_into_accum_cached(
3273 /// Some(0),
3274 /// lhs,
3275 /// rhs,
3276 /// config,
3277 /// accumulation,
3278 /// out,
3279 /// )
3280 /// }
3281 /// ```
3282 /// # Errors
3283 ///
3284 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3285 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3286 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3287 /// backend execution or storage access cannot provide the requested result.
3288 fn dot_general_read_into_accum_cached(
3289 &mut self,
3290 _cache_slot: Option<usize>,
3291 lhs: TensorRead<'_>,
3292 rhs: TensorRead<'_>,
3293 config: &DotGeneralConfig,
3294 accumulation: DotGeneralAccumulation,
3295 out: TensorWrite<'_>,
3296 ) -> crate::Result<()> {
3297 self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
3298 }
3299
3300 #[doc(hidden)]
3301 fn grouped_gemm_cached(
3302 &mut self,
3303 _cache_slot: Option<usize>,
3304 lhs: TensorRead<'_>,
3305 rhs: TensorRead<'_>,
3306 config: &GroupedGemmConfig<'_>,
3307 out: TensorWrite<'_>,
3308 ) -> crate::Result<()> {
3309 grouped_gemm_default(self, lhs, rhs, config, out)
3310 }
3311}
3312
3313/// Indexing, slicing, and padding operations.
3314///
3315/// # Examples
3316///
3317/// ```rust
3318/// use tenferro_tensor::TensorIndexing;
3319///
3320/// fn accepts_indexing<B: TensorIndexing>(_backend: &mut B) {}
3321/// ```
3322pub trait TensorIndexing {
3323 /// # Errors
3324 ///
3325 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3326 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3327 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3328 /// backend execution or storage access cannot provide the requested result.
3329 fn gather(
3330 &mut self,
3331 operand: &Tensor,
3332 start_indices: &Tensor,
3333 config: &GatherConfig,
3334 ) -> crate::Result<Tensor>;
3335 /// # Errors
3336 ///
3337 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3338 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3339 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3340 /// backend execution or storage access cannot provide the requested result.
3341 fn scatter(
3342 &mut self,
3343 operand: &Tensor,
3344 scatter_indices: &Tensor,
3345 updates: &Tensor,
3346 config: &ScatterConfig,
3347 ) -> crate::Result<Tensor>;
3348 /// # Errors
3349 ///
3350 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3351 /// for invalid shapes, ranks, axes, dtypes, or output metadata. In
3352 /// particular, a limit greater than the corresponding input dimension is
3353 /// reported as [`crate::ValidationError::InvalidArgument`] with the
3354 /// `"configuration"` argument. It returns [`crate::Error::BackendFailure`]
3355 /// or [`crate::Error::BackendSource`] when backend execution or storage
3356 /// access cannot provide the requested result.
3357 fn slice(&mut self, input: &Tensor, config: &SliceConfig) -> crate::Result<Tensor>;
3358 /// # Errors
3359 ///
3360 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3361 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3362 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3363 /// backend execution or storage access cannot provide the requested result.
3364 fn dynamic_slice(
3365 &mut self,
3366 input: &Tensor,
3367 starts: &Tensor,
3368 slice_sizes: &[usize],
3369 ) -> crate::Result<Tensor>;
3370 /// # Errors
3371 ///
3372 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3373 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3374 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3375 /// backend execution or storage access cannot provide the requested result.
3376 fn dynamic_update_slice(
3377 &mut self,
3378 operand: &Tensor,
3379 update: &Tensor,
3380 starts: &Tensor,
3381 ) -> crate::Result<Tensor>;
3382 /// # Errors
3383 ///
3384 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3385 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3386 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3387 /// backend execution or storage access cannot provide the requested result.
3388 fn pad(&mut self, input: &Tensor, config: &PadConfig) -> crate::Result<Tensor>;
3389 /// # Errors
3390 ///
3391 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3392 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3393 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3394 /// backend execution or storage access cannot provide the requested result.
3395 fn concatenate(&mut self, inputs: &[&Tensor], axis: usize) -> crate::Result<Tensor>;
3396 /// # Errors
3397 ///
3398 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3399 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3400 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3401 /// backend execution or storage access cannot provide the requested result.
3402 fn reverse(&mut self, input: &Tensor, axes: &[usize]) -> crate::Result<Tensor>;
3403}
3404
3405/// Backend-owned canonicalization for typed tensor views.
3406///
3407/// Implementations must preserve the input placement family. CPU backends
3408/// canonicalize host views through explicit host copies and reject backend
3409/// buffers with a diagnostic that asks the caller to download first. GPU
3410/// backends canonicalize GPU-resident views on the same device and reject host
3411/// buffers with an upload hint.
3412///
3413/// [`TensorViewCanonicalization::copy_into`] requires source and destination
3414/// shapes, scalar dtypes, and placement families to match. The destination
3415/// view must be internally non-overlapping, and source and destination backing
3416/// allocations must not alias unless an implementation explicitly documents
3417/// and supports that case. Implementations may reject layouts their native
3418/// kernels cannot consume.
3419///
3420/// CUDA currently accepts only a compact column-major source view with offset
3421/// zero that covers its full allocation; arbitrary-stride destinations remain
3422/// supported. Canonicalization and copying are same-placement operations: they
3423/// must not perform hidden host/device transfers or silently materialize an
3424/// unsupported source layout.
3425///
3426/// This trait is intentionally separate from [`BackendSession`] so generic
3427/// typed methods do not change the object-safety contract of `dyn BackendSession`.
3428///
3429/// # Examples
3430///
3431/// ```rust
3432/// use tenferro_tensor::{DynRank, TensorViewCanonicalization, TypedTensor};
3433///
3434/// fn compact_i32<B: TensorViewCanonicalization<i32, DynRank>>(
3435/// backend: &mut B,
3436/// tensor: &TypedTensor<i32>,
3437/// ) -> tenferro_tensor::Result<TypedTensor<i32>> {
3438/// backend.to_contiguous(&tensor.as_view())
3439/// }
3440///
3441/// fn copy_i32<B: TensorViewCanonicalization<i32, DynRank>>(
3442/// backend: &mut B,
3443/// src: &TypedTensor<i32>,
3444/// dst: &mut TypedTensor<i32>,
3445/// ) -> tenferro_tensor::Result<()> {
3446/// backend.copy_into(&src.as_view(), &mut dst.as_view_mut())
3447/// }
3448/// ```
3449pub trait TensorViewCanonicalization<T: TensorScalar, R: TensorRank> {
3450 /// # Errors
3451 ///
3452 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3453 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3454 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3455 /// backend execution or storage access cannot provide the requested result.
3456 fn to_contiguous(
3457 &mut self,
3458 view: &TypedTensorView<'_, T, R>,
3459 ) -> crate::Result<TypedTensor<T, R>>;
3460
3461 /// # Errors
3462 ///
3463 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3464 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3465 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3466 /// backend execution or storage access cannot provide the requested result.
3467 fn copy_into(
3468 &mut self,
3469 src: &TypedTensorView<'_, T, R>,
3470 dst: &mut TypedTensorViewMut<'_, T, R>,
3471 ) -> crate::Result<()>;
3472}
3473
3474/// Optional elementwise fusion execution.
3475///
3476/// # Examples
3477///
3478/// ```rust
3479/// use tenferro_tensor::TensorFusion;
3480///
3481/// fn accepts_fusion<B: TensorFusion>(_backend: &mut B) {}
3482/// ```
3483pub trait TensorFusion {
3484 #[doc(hidden)]
3485 fn execute_elementwise_fusion(
3486 &mut self,
3487 _inputs: &[&Tensor],
3488 _plan: &ElementwiseFusionPlan,
3489 ) -> crate::Result<Option<Vec<Tensor>>> {
3490 Ok(None)
3491 }
3492
3493 #[doc(hidden)]
3494 #[allow(clippy::too_many_arguments)]
3495 fn execute_broadcast_multiply(
3496 &mut self,
3497 _lhs: TensorRead<'_>,
3498 _lhs_shape: &[usize],
3499 _lhs_dims: &[usize],
3500 _rhs: TensorRead<'_>,
3501 _rhs_shape: &[usize],
3502 _rhs_dims: &[usize],
3503 ) -> crate::Result<Option<Tensor>> {
3504 Ok(None)
3505 }
3506
3507 #[doc(hidden)]
3508 #[allow(clippy::too_many_arguments)]
3509 fn execute_broadcast_multiply_value(
3510 &mut self,
3511 lhs: TensorRead<'_>,
3512 lhs_shape: &[usize],
3513 lhs_dims: &[usize],
3514 rhs: TensorRead<'_>,
3515 rhs_shape: &[usize],
3516 rhs_dims: &[usize],
3517 ) -> crate::Result<Option<TensorValue>> {
3518 self.execute_broadcast_multiply(lhs, lhs_shape, lhs_dims, rhs, rhs_shape, rhs_dims)
3519 .map(|tensor| tensor.map(TensorValue::from_tensor))
3520 }
3521}
3522
3523/// Backend buffer lifecycle operations.
3524///
3525/// # Examples
3526///
3527/// ```rust
3528/// use tenferro_tensor::TensorBuffer;
3529///
3530/// fn accepts_buffer<B: TensorBuffer>(_backend: &mut B) {}
3531/// ```
3532pub trait TensorBuffer {
3533 fn reclaim_buffer(&mut self, _tensor: Tensor) {}
3534}
3535
3536/// Device transfer operations on backend boundaries.
3537///
3538/// # Examples
3539///
3540/// ```rust
3541/// use tenferro_tensor::TensorDeviceTransfer;
3542///
3543/// fn accepts_transfer<B: TensorDeviceTransfer>(_backend: &mut B) {}
3544/// ```
3545pub trait TensorDeviceTransfer {
3546 /// Explicitly copy a provider-owned read target into host storage.
3547 ///
3548 /// Implementations must not return the input unchanged or stage through an
3549 /// unrelated provider. A backend that cannot transfer the requested read
3550 /// target returns a typed unsupported error.
3551 ///
3552 /// # Errors
3553 ///
3554 /// Returns [`crate::Error::Unsupported`] when the implementation cannot
3555 /// perform the requested transfer, or a typed validation/backend error when
3556 /// the source cannot be read.
3557 fn download_to_host(&mut self, tensor: TensorRead<'_>) -> crate::Result<Tensor>;
3558
3559 /// Explicitly copy a host read target into provider storage.
3560 ///
3561 /// # Errors
3562 ///
3563 /// Returns [`crate::Error::Unsupported`] when the implementation cannot
3564 /// perform the requested transfer, or a typed validation/backend error when
3565 /// the source cannot be read.
3566 fn upload_host_tensor(&mut self, tensor: TensorRead<'_>) -> crate::Result<Tensor>;
3567}
3568
3569/// Runtime cache associated with a backend.
3570///
3571/// # Examples
3572///
3573/// ```rust
3574/// use tenferro_tensor::BackendRuntimeCache;
3575///
3576/// fn accepts_runtime_cache<B: BackendRuntimeCache>(_backend: &B) {}
3577/// ```
3578pub trait BackendRuntimeCache {
3579 #[doc(hidden)]
3580 type RuntimeCache: RuntimeCacheControl + Send + Sync + 'static;
3581}
3582
3583/// Backend-owned cached dot-general operations.
3584///
3585/// # Examples
3586///
3587/// ```rust
3588/// use tenferro_tensor::BackendCachedDot;
3589///
3590/// fn accepts_backend_cached_dot<B: BackendCachedDot>(_backend: &mut B) {}
3591/// ```
3592pub trait BackendCachedDot: BackendRuntimeCache + TensorDot {
3593 #[doc(hidden)]
3594 fn dot_general_cached(
3595 &mut self,
3596 _cache: &mut Self::RuntimeCache,
3597 _cache_slot: Option<usize>,
3598 lhs: &Tensor,
3599 rhs: &Tensor,
3600 config: &DotGeneralConfig,
3601 ) -> crate::Result<Tensor> {
3602 self.dot_general(lhs, rhs, config)
3603 }
3604
3605 #[doc(hidden)]
3606 fn dot_general_read_cached(
3607 &mut self,
3608 cache: &mut Self::RuntimeCache,
3609 cache_slot: Option<usize>,
3610 lhs: TensorRead<'_>,
3611 rhs: TensorRead<'_>,
3612 config: &DotGeneralConfig,
3613 ) -> crate::Result<Tensor> {
3614 match (lhs.as_tensor(), rhs.as_tensor()) {
3615 (Some(lhs), Some(rhs)) => self.dot_general_cached(cache, cache_slot, lhs, rhs, config),
3616 _ => {
3617 let lhs = self.to_contiguous_read(lhs)?;
3618 let rhs = self.to_contiguous_read(rhs)?;
3619 self.dot_general_cached(cache, cache_slot, &lhs, &rhs, config)
3620 }
3621 }
3622 }
3623
3624 // Mirrors the dot-general signature plus runtime-cache metadata.
3625 #[allow(clippy::too_many_arguments)]
3626 #[doc(hidden)]
3627 fn dot_general_with_conj_cached(
3628 &mut self,
3629 _cache: &mut Self::RuntimeCache,
3630 _cache_slot: Option<usize>,
3631 lhs: &Tensor,
3632 rhs: &Tensor,
3633 config: &DotGeneralConfig,
3634 lhs_conj: bool,
3635 rhs_conj: bool,
3636 ) -> crate::Result<Tensor> {
3637 self.dot_general_with_conj(lhs, rhs, config, lhs_conj, rhs_conj)
3638 }
3639
3640 // Mirrors the dot-general read signature plus runtime-cache metadata.
3641 #[allow(clippy::too_many_arguments)]
3642 #[doc(hidden)]
3643 fn dot_general_with_conj_read_cached(
3644 &mut self,
3645 cache: &mut Self::RuntimeCache,
3646 cache_slot: Option<usize>,
3647 lhs: TensorRead<'_>,
3648 rhs: TensorRead<'_>,
3649 config: &DotGeneralConfig,
3650 lhs_conj: bool,
3651 rhs_conj: bool,
3652 ) -> crate::Result<Tensor> {
3653 if !lhs_conj && !rhs_conj {
3654 return self.dot_general_read_cached(cache, cache_slot, lhs, rhs, config);
3655 }
3656
3657 let lhs_tmp;
3658 let lhs_ref = if let Some(tensor) = lhs.as_tensor() {
3659 tensor
3660 } else {
3661 lhs_tmp = self.to_contiguous_read(lhs)?;
3662 &lhs_tmp
3663 };
3664 let rhs_tmp;
3665 let rhs_ref = if let Some(tensor) = rhs.as_tensor() {
3666 tensor
3667 } else {
3668 rhs_tmp = self.to_contiguous_read(rhs)?;
3669 &rhs_tmp
3670 };
3671 self.dot_general_with_conj_cached(
3672 cache, cache_slot, lhs_ref, rhs_ref, config, lhs_conj, rhs_conj,
3673 )
3674 }
3675
3676 /// Apply cached scaled dot-general accumulation into caller-provided output.
3677 ///
3678 /// The cache slot identifies backend-local analysis metadata only; output
3679 /// semantics are still fully described by `accumulation`.
3680 ///
3681 /// # Examples
3682 ///
3683 /// ```rust
3684 /// use tenferro_tensor::{
3685 /// BackendCachedDot, BackendRuntimeCache, DotGeneralAccumulation, DotGeneralConfig,
3686 /// TensorRead, TensorWrite,
3687 /// };
3688 ///
3689 /// fn cached_dot_add_to<B: BackendCachedDot>(
3690 /// backend: &mut B,
3691 /// cache: &mut B::RuntimeCache,
3692 /// lhs: TensorRead<'_>,
3693 /// rhs: TensorRead<'_>,
3694 /// config: &DotGeneralConfig,
3695 /// out: TensorWrite<'_>,
3696 /// ) -> tenferro_tensor::Result<()>
3697 /// where
3698 /// B: BackendRuntimeCache,
3699 /// {
3700 /// let accumulation = DotGeneralAccumulation::add_to(lhs.dtype())?;
3701 /// backend.dot_general_read_into_accum_cached(
3702 /// cache,
3703 /// Some(0),
3704 /// lhs,
3705 /// rhs,
3706 /// config,
3707 /// accumulation,
3708 /// out,
3709 /// )
3710 /// }
3711 /// ```
3712 #[allow(clippy::too_many_arguments)]
3713 /// # Errors
3714 ///
3715 /// Returns [`crate::Error::Validation`] with a typed `ValidationError` source
3716 /// for invalid shapes, ranks, axes, dtypes, or output metadata. It returns
3717 /// [`crate::Error::BackendFailure`] or [`crate::Error::BackendSource`] when
3718 /// backend execution or storage access cannot provide the requested result.
3719 fn dot_general_read_into_accum_cached(
3720 &mut self,
3721 _cache: &mut Self::RuntimeCache,
3722 _cache_slot: Option<usize>,
3723 lhs: TensorRead<'_>,
3724 rhs: TensorRead<'_>,
3725 config: &DotGeneralConfig,
3726 accumulation: DotGeneralAccumulation,
3727 out: TensorWrite<'_>,
3728 ) -> crate::Result<()> {
3729 self.dot_general_read_into_accum(lhs, rhs, config, accumulation, out)
3730 }
3731
3732 #[doc(hidden)]
3733 fn grouped_gemm_cached(
3734 &mut self,
3735 _cache: &mut Self::RuntimeCache,
3736 _cache_slot: Option<usize>,
3737 lhs: TensorRead<'_>,
3738 rhs: TensorRead<'_>,
3739 config: &GroupedGemmConfig<'_>,
3740 out: TensorWrite<'_>,
3741 ) -> crate::Result<()> {
3742 grouped_gemm_default(self, lhs, rhs, config, out)
3743 }
3744}
3745
3746/// Backend execution-session entry points.
3747///
3748/// `with_backend_session` is the canonical user entry; one-shot concrete ops
3749/// delegate to the session form. `with_backend_session_cached` is the
3750/// runtime-cache-aware entry used by the runtime layer.
3751///
3752/// # Examples
3753///
3754/// ```rust
3755/// use tenferro_tensor::BackendSessionHost;
3756///
3757/// fn accepts_session_host<B: BackendSessionHost>(_backend: &mut B) {}
3758/// ```
3759pub trait BackendSessionHost: BackendRuntimeCache {
3760 fn with_backend_session<R: Send>(
3761 &mut self,
3762 f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
3763 ) -> R
3764 where
3765 Self: TensorBackend + Sized,
3766 {
3767 default_backend_session(self, f)
3768 }
3769
3770 #[doc(hidden)]
3771 fn with_backend_session_cached<R: Send>(
3772 &mut self,
3773 _cache: &mut Self::RuntimeCache,
3774 f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
3775 ) -> R
3776 where
3777 Self: TensorBackend + Sized,
3778 {
3779 self.with_backend_session(f)
3780 }
3781}
3782
3783/// Operation capabilities shared by backends and backend sessions.
3784#[doc(hidden)]
3785pub trait TensorBackendOps:
3786 TensorElementwise
3787 + TensorAnalytic
3788 + TensorStructural
3789 + TensorReduction
3790 + TensorIndexing
3791 + TensorDot
3792 + TensorFusion
3793 + TensorBuffer
3794{
3795}
3796
3797impl<T> TensorBackendOps for T where
3798 T: TensorElementwise
3799 + TensorAnalytic
3800 + TensorStructural
3801 + TensorReduction
3802 + TensorIndexing
3803 + TensorDot
3804 + TensorFusion
3805 + TensorBuffer
3806 + ?Sized
3807{
3808}
3809
3810/// Validate the shared input contract for [`BackendSession::vdot_read`].
3811#[doc(hidden)]
3812pub fn validate_vdot_read(lhs: &TensorRead<'_>, rhs: &TensorRead<'_>) -> crate::Result<()> {
3813 let op = "BackendSession::vdot_read";
3814 validate_supported_blas1_dtype(op, lhs.dtype())?;
3815 if rhs.dtype() != lhs.dtype() {
3816 return Err(Error::dtype_mismatch(op, lhs.dtype(), rhs.dtype()));
3817 }
3818 if lhs.shape() != rhs.shape() {
3819 return Err(Error::shape_mismatch(
3820 op,
3821 lhs.shape().to_vec(),
3822 rhs.shape().to_vec(),
3823 ));
3824 }
3825 validate_shape_product(op, lhs.shape())?;
3826 validate_compatible_placement(op, lhs, rhs)
3827}
3828
3829/// Validate the shared input contract for [`BackendSession::norm_squared_read`].
3830#[doc(hidden)]
3831pub fn validate_norm_squared_read(input: &TensorRead<'_>) -> crate::Result<()> {
3832 validate_supported_blas1_dtype("BackendSession::norm_squared_read", input.dtype())?;
3833 validate_shape_product("BackendSession::norm_squared_read", input.shape()).map(|_| ())
3834}
3835
3836/// Validate the shared input and destination contract for
3837/// [`BackendSession::axpby_read_into_accum`].
3838#[doc(hidden)]
3839pub fn validate_axpby_read_into_accum(
3840 alpha: ContractionScalar,
3841 x: &TensorRead<'_>,
3842 beta: ContractionScalar,
3843 y: &TensorWrite<'_>,
3844) -> crate::Result<()> {
3845 let op = "BackendSession::axpby_read_into_accum";
3846 validate_supported_blas1_dtype(op, x.dtype())?;
3847 if y.dtype() != x.dtype() {
3848 return Err(Error::dtype_mismatch(op, x.dtype(), y.dtype()));
3849 }
3850 if alpha.dtype() != x.dtype() {
3851 return Err(Error::dtype_mismatch(op, x.dtype(), alpha.dtype()));
3852 }
3853 if beta.dtype() != x.dtype() {
3854 return Err(Error::dtype_mismatch(op, x.dtype(), beta.dtype()));
3855 }
3856 if x.shape() != y.shape() {
3857 return Err(Error::shape_mismatch(
3858 op,
3859 x.shape().to_vec(),
3860 y.shape().to_vec(),
3861 ));
3862 }
3863 validate_shape_product(op, x.shape())?;
3864 if !y.as_read().is_col_major_contiguous()? {
3865 return Err(Error::invalid_argument(
3866 op,
3867 "y",
3868 "destination must be compact column-major and injective",
3869 ));
3870 }
3871 validate_compatible_placement(op, x, &y.as_read())?;
3872 validate_read_into_destination(op, std::slice::from_ref(x), y)
3873}
3874
3875/// # Errors
3876///
3877/// Returns [`Error::Unsupported`] when `dtype` is not floating or complex.
3878fn validate_supported_blas1_dtype(op: &'static str, dtype: DType) -> crate::Result<()> {
3879 if matches!(dtype, DType::F32 | DType::F64 | DType::C32 | DType::C64) {
3880 Ok(())
3881 } else {
3882 Err(Error::unsupported(
3883 op,
3884 format!("unsupported dtype {dtype:?}; supported dtypes: F32/F64/C32/C64"),
3885 ))
3886 }
3887}
3888
3889/// # Errors
3890///
3891/// Returns [`Error::Validation`] with [`ValidationError::IntegerOverflow`] when
3892/// the checked shape product overflows.
3893fn validate_shape_product(op: &'static str, shape: &[usize]) -> crate::Result<usize> {
3894 crate::validate::checked_shape_product(op, "shape", shape)
3895}
3896
3897/// # Errors
3898///
3899/// Returns [`Error::Validation`] with [`ValidationError::InvalidArgument`] when
3900/// placements differ.
3901fn validate_compatible_placement(
3902 op: &'static str,
3903 lhs: &TensorRead<'_>,
3904 rhs: &TensorRead<'_>,
3905) -> crate::Result<()> {
3906 if lhs.placement() != rhs.placement() {
3907 return Err(Error::invalid_argument(
3908 op,
3909 "placement",
3910 "tensor inputs must have compatible placement",
3911 ));
3912 }
3913 Ok(())
3914}
3915
3916/// Execution session surface for dense tensor backends.
3917///
3918/// All operations run within a backend-owned execution scope such as a CPU
3919/// thread policy or a GPU stream. Individual ops must not try to re-enter that
3920/// scope.
3921///
3922/// # Examples
3923///
3924/// ```rust
3925/// use tenferro_tensor::{BackendSessionHost, Tensor, TypedTensor};
3926///
3927/// fn add_in_session<B: BackendSessionHost>(
3928/// backend: &mut B,
3929/// a: &Tensor,
3930/// b: &Tensor,
3931/// ) -> tenferro_tensor::Result<Tensor>
3932/// where
3933/// B: tenferro_tensor::TensorBackend,
3934/// {
3935/// backend.with_backend_session(|exec| exec.add(a, b))
3936/// }
3937/// ```
3938pub trait BackendSession: TensorBackendOps + SessionCachedDot + TensorDeviceTransfer {
3939 /// Build-local identity for backend-extension session capability dispatch.
3940 #[doc(hidden)]
3941 fn session_type_id(&self) -> TypeId;
3942
3943 /// Compute the all-axis conjugating dot product without transferring either input.
3944 ///
3945 /// The result is a rank-0 tensor with the input dtype and has the value
3946 /// `sum(conj(lhs) * rhs)`. Borrowed views remain borrowed through the
3947 /// backend's existing same-placement planning boundary.
3948 ///
3949 /// # Examples
3950 ///
3951 /// ```rust
3952 /// use tenferro_tensor::{BackendSession, TensorRead};
3953 ///
3954 /// fn vdot(session: &mut dyn BackendSession, x: TensorRead<'_>, y: TensorRead<'_>)
3955 /// -> tenferro_tensor::Result<tenferro_tensor::Tensor>
3956 /// {
3957 /// session.vdot_read(x, y)
3958 /// }
3959 /// ```
3960 ///
3961 /// # Errors
3962 ///
3963 /// Returns [`Error::Unsupported`] when the backend does not override this
3964 /// capability or the dtype is unsupported; [`Error::Validation`] with
3965 /// `DTypeMismatch`, `ShapeMismatch`, or `InvalidArgument` when dtype, shape,
3966 /// or placement differs; [`Error::RuntimeState`] for inaccessible backend
3967 /// storage; or [`Error::BackendSource`] when provider execution fails.
3968 fn vdot_read(&mut self, _lhs: TensorRead<'_>, _rhs: TensorRead<'_>) -> crate::Result<Tensor> {
3969 Err(Error::unsupported(
3970 "BackendSession::vdot_read",
3971 "backend session does not implement vdot_read",
3972 ))
3973 }
3974
3975 /// Compute the all-axis sum of squared magnitudes without taking a square root.
3976 ///
3977 /// The result is rank 0 and is F32 for F32/C32 input or F64 for F64/C64
3978 /// input. No transfer or full-size algebra temporary is implied by this
3979 /// session contract.
3980 ///
3981 /// # Examples
3982 ///
3983 /// ```rust
3984 /// use tenferro_tensor::{BackendSession, TensorRead};
3985 ///
3986 /// fn norm_squared(session: &mut dyn BackendSession, x: TensorRead<'_>)
3987 /// -> tenferro_tensor::Result<tenferro_tensor::Tensor>
3988 /// {
3989 /// session.norm_squared_read(x)
3990 /// }
3991 /// ```
3992 ///
3993 /// # Errors
3994 ///
3995 /// Returns [`Error::Unsupported`] when the backend does not override this
3996 /// capability or the dtype is unsupported, [`Error::RuntimeState`] when
3997 /// backend storage is not host-accessible, or [`Error::BackendSource`] when
3998 /// reduction execution fails.
3999 fn norm_squared_read(&mut self, _input: TensorRead<'_>) -> crate::Result<Tensor> {
4000 Err(Error::unsupported(
4001 "BackendSession::norm_squared_read",
4002 "backend session does not implement norm_squared_read",
4003 ))
4004 }
4005
4006 /// Apply `y <- alpha * x + beta * y` in one pass into caller-owned storage.
4007 ///
4008 /// Scalars must have the exact tensor dtype; real coefficients for complex
4009 /// vectors are represented as complex values with zero imaginary part. The
4010 /// destination must be compact and injective, and any x/y storage overlap
4011 /// is rejected before mutation.
4012 ///
4013 /// # Examples
4014 ///
4015 /// ```rust
4016 /// use tenferro_tensor::{BackendSession, ContractionScalar, TensorRead, TensorWrite};
4017 ///
4018 /// fn axpby(session: &mut dyn BackendSession, x: TensorRead<'_>, y: TensorWrite<'_>)
4019 /// -> tenferro_tensor::Result<()>
4020 /// {
4021 /// session.axpby_read_into_accum(
4022 /// ContractionScalar::F64(1.0), x, ContractionScalar::F64(0.0), y,
4023 /// )
4024 /// }
4025 /// ```
4026 ///
4027 /// # Errors
4028 ///
4029 /// Returns [`Error::Unsupported`] when the backend does not override this
4030 /// capability or the dtype is unsupported; [`Error::Validation`] with
4031 /// `DTypeMismatch`, `ShapeMismatch`, or `InvalidArgument` for scalar/dtype,
4032 /// shape, placement, compactness, injectivity, or overlap failures; or
4033 /// [`Error::RuntimeState`] for inaccessible backend storage. Invalid
4034 /// requests leave the destination unchanged.
4035 fn axpby_read_into_accum(
4036 &mut self,
4037 _alpha: ContractionScalar,
4038 _x: TensorRead<'_>,
4039 _beta: ContractionScalar,
4040 _y: TensorWrite<'_>,
4041 ) -> crate::Result<()> {
4042 Err(Error::unsupported(
4043 "BackendSession::axpby_read_into_accum",
4044 "backend session does not implement axpby_read_into_accum",
4045 ))
4046 }
4047
4048 /// Erased pointer used only by backend leaf crates for a checked session
4049 /// capability bridge. The pointer is borrowed for the lifetime of `self`.
4050 ///
4051 /// # Safety
4052 ///
4053 /// The implementation must return a pointer to the same value represented
4054 /// by `self`, and that pointer must remain valid and uniquely borrowed for
4055 /// the duration of the `&mut self` borrow. Backend leaf crates may use this
4056 /// contract to recover a concrete session capability after checking
4057 /// [`Self::session_type_id`].
4058 #[doc(hidden)]
4059 unsafe fn session_data_mut(&mut self) -> *mut ();
4060}
4061
4062/// Standard runtime backend over dynamic [`Tensor`] values.
4063///
4064/// # Examples
4065///
4066/// ```rust
4067/// use tenferro_tensor::TensorBackend;
4068///
4069/// fn accepts_backend<B: TensorBackend>(_backend: &mut B) {}
4070/// ```
4071pub trait TensorBackend:
4072 BackendRuntimeCache
4073 + BackendSession
4074 + TensorBackendOps
4075 + BackendCachedDot
4076 + TensorDeviceTransfer
4077 + BackendSessionHost
4078{
4079}
4080
4081impl<T> SessionCachedDot for T where T: TensorBackend + ?Sized {}
4082
4083thread_local! {
4084 /// Tracks whether a session-entry closure is currently running on this
4085 /// thread, so nested session entry is caught in debug builds even for
4086 /// backends that do not override
4087 /// [`BackendSessionHost::with_backend_session`].
4088 static IN_SESSION: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
4089}
4090
4091/// Sets the in-session flag for the duration of a session closure and restores
4092/// it on exit, including on panic.
4093struct InSessionGuard;
4094
4095impl InSessionGuard {
4096 fn enter() -> Self {
4097 debug_assert!(
4098 !IN_SESSION.get(),
4099 "nested backend session entry: a session closure called \
4100 with_backend_session / default_backend_session again on this thread"
4101 );
4102 IN_SESSION.set(true);
4103 InSessionGuard
4104 }
4105}
4106
4107impl Drop for InSessionGuard {
4108 fn drop(&mut self) {
4109 IN_SESSION.set(false);
4110 }
4111}
4112
4113/// Run `f` with the thread-local in-session flag set, restoring it on exit
4114/// including on panic, and asserting (in debug builds) that the caller is not
4115/// already inside a session closure.
4116///
4117/// This is the portable nested-entry guard shared by every backend-session
4118/// entry point: [`default_backend_session`] and the GPU
4119/// `BackendSessionHost::with_backend_session` overrides. CPU keeps its own
4120/// release-mode `EXECUTION_OWNER` panic on top of this debug check.
4121#[doc(hidden)]
4122pub fn with_session_entry_guard<R>(f: impl FnOnce() -> R) -> R {
4123 let _guard = InSessionGuard::enter();
4124 f()
4125}
4126
4127/// Run a closure using the backend itself as a default execution session.
4128///
4129/// This is suitable for backends whose individual ops already manage their own
4130/// execution context.
4131///
4132/// # Examples
4133///
4134/// ```rust
4135/// use tenferro_tensor::{default_backend_session, TensorBackend};
4136///
4137/// fn run_with_default_session<B: TensorBackend>(backend: &mut B) -> usize {
4138/// default_backend_session(backend, |_exec| 1usize)
4139/// }
4140/// ```
4141pub fn default_backend_session<B: TensorBackend, R: Send>(
4142 backend: &mut B,
4143 f: impl FnOnce(&mut dyn BackendSession) -> R + Send,
4144) -> R {
4145 with_session_entry_guard(|| f(backend))
4146}