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