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