Skip to main content

tenferro_runtime/
typed_tensor.rs

1//! Typed tensor operation extension traits.
2//!
3//! Operation families that are no longer part of core, including einsum, live
4//! in their extension crates.
5
6use tenferro_ops::broadcast::{
7    broadcast_input_plan, broadcast_shape, broadcast_shapes, BroadcastError,
8};
9use tenferro_tensor::validate::matmul_config_for_shapes;
10use tenferro_tensor::{
11    BackendSession, CompareDir, Error, Result, Tensor, TensorRead, TensorScalar, ValidationError,
12};
13
14use crate::{TypedTensorMaskSessionOpsExt, TypedTensorSessionOpsExt};
15use tenferro_tensor::TypedTensor;
16
17impl<T: TensorScalar> TypedTensorSessionOpsExt<T> for TypedTensor<T> {
18    fn add(
19        &self,
20        rhs: &TypedTensor<T>,
21        session: &mut dyn BackendSession,
22    ) -> Result<TypedTensor<T>> {
23        let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
24        let out = session.add_read(lhs.tensor_read(), rhs.tensor_read())?;
25        into_typed_result("add", out)
26    }
27
28    fn mul(
29        &self,
30        rhs: &TypedTensor<T>,
31        session: &mut dyn BackendSession,
32    ) -> Result<TypedTensor<T>> {
33        let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
34        let out = session.mul_read(lhs.tensor_read(), rhs.tensor_read())?;
35        into_typed_result("mul", out)
36    }
37
38    fn exp(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
39        let out = session.exp_read(T::tensor_read(self))?;
40        into_typed_result("exp", out)
41    }
42
43    fn reduce_sum(
44        &self,
45        axes: &[usize],
46        session: &mut dyn BackendSession,
47    ) -> Result<TypedTensor<T>> {
48        let out = session.reduce_sum_read(T::tensor_read(self), axes)?;
49        into_typed_result("reduce_sum", out)
50    }
51
52    fn sub(
53        &self,
54        rhs: &TypedTensor<T>,
55        session: &mut dyn BackendSession,
56    ) -> Result<TypedTensor<T>> {
57        let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
58        let out = session.sub_read(lhs.tensor_read(), rhs.tensor_read())?;
59        into_typed_result("sub", out)
60    }
61
62    fn div(
63        &self,
64        rhs: &TypedTensor<T>,
65        session: &mut dyn BackendSession,
66    ) -> Result<TypedTensor<T>> {
67        let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
68        let out = session.div_read(lhs.tensor_read(), rhs.tensor_read())?;
69        into_typed_result("div", out)
70    }
71
72    fn rem(
73        &self,
74        rhs: &TypedTensor<T>,
75        session: &mut dyn BackendSession,
76    ) -> Result<TypedTensor<T>> {
77        let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
78        let out = session.rem_read(lhs.tensor_read(), rhs.tensor_read())?;
79        into_typed_result("rem", out)
80    }
81
82    fn pow(
83        &self,
84        rhs: &TypedTensor<T>,
85        session: &mut dyn BackendSession,
86    ) -> Result<TypedTensor<T>> {
87        let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
88        let out = session.pow_read(lhs.tensor_read(), rhs.tensor_read())?;
89        into_typed_result("pow", out)
90    }
91
92    fn maximum(
93        &self,
94        rhs: &TypedTensor<T>,
95        session: &mut dyn BackendSession,
96    ) -> Result<TypedTensor<T>> {
97        let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
98        let out = session.maximum_read(lhs.tensor_read(), rhs.tensor_read())?;
99        into_typed_result("maximum", out)
100    }
101
102    fn minimum(
103        &self,
104        rhs: &TypedTensor<T>,
105        session: &mut dyn BackendSession,
106    ) -> Result<TypedTensor<T>> {
107        let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
108        let out = session.minimum_read(lhs.tensor_read(), rhs.tensor_read())?;
109        into_typed_result("minimum", out)
110    }
111
112    fn neg(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
113        let out = session.neg_read(T::tensor_read(self))?;
114        into_typed_result("neg", out)
115    }
116
117    fn abs(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
118        let out = session.abs_read(T::tensor_read(self))?;
119        into_typed_result("abs", out)
120    }
121
122    fn sign(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
123        let out = session.sign_read(T::tensor_read(self))?;
124        into_typed_result("sign", out)
125    }
126
127    fn conj(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
128        let out = session.conj_read(T::tensor_read(self))?;
129        into_typed_result("conj", out)
130    }
131
132    fn log(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
133        let out = session.log_read(T::tensor_read(self))?;
134        into_typed_result("log", out)
135    }
136
137    fn expm1(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
138        let out = session.expm1_read(T::tensor_read(self))?;
139        into_typed_result("expm1", out)
140    }
141
142    fn log1p(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
143        let out = session.log1p_read(T::tensor_read(self))?;
144        into_typed_result("log1p", out)
145    }
146
147    fn sin(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
148        let out = session.sin_read(T::tensor_read(self))?;
149        into_typed_result("sin", out)
150    }
151
152    fn cos(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
153        let out = session.cos_read(T::tensor_read(self))?;
154        into_typed_result("cos", out)
155    }
156
157    fn tanh(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
158        let out = session.tanh_read(T::tensor_read(self))?;
159        into_typed_result("tanh", out)
160    }
161
162    fn sqrt(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
163        let out = session.sqrt_read(T::tensor_read(self))?;
164        into_typed_result("sqrt", out)
165    }
166
167    fn rsqrt(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
168        let out = session.rsqrt_read(T::tensor_read(self))?;
169        into_typed_result("rsqrt", out)
170    }
171
172    fn compare(
173        &self,
174        rhs: &TypedTensor<T>,
175        dir: CompareDir,
176        session: &mut dyn BackendSession,
177    ) -> Result<TypedTensor<bool>> {
178        let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
179        let out = session.compare_read(lhs.tensor_read(), rhs.tensor_read(), &dir)?;
180        into_typed_result("compare", out)
181    }
182
183    fn clamp(
184        &self,
185        lower: &TypedTensor<T>,
186        upper: &TypedTensor<T>,
187        session: &mut dyn BackendSession,
188    ) -> Result<TypedTensor<T>> {
189        let (input, lower, upper) = broadcast_ternary_in_read(self, lower, upper, session)?;
190        let out = session.clamp_read(
191            input.tensor_read(),
192            lower.tensor_read(),
193            upper.tensor_read(),
194        )?;
195        into_typed_result("clamp", out)
196    }
197
198    fn matmul(
199        &self,
200        rhs: &TypedTensor<T>,
201        session: &mut dyn BackendSession,
202    ) -> Result<TypedTensor<T>> {
203        let config = matmul_config_for_shapes("matmul", self.shape(), rhs.shape())?;
204        let out = session.dot_general_read(T::tensor_read(self), T::tensor_read(rhs), &config)?;
205        into_typed_result("matmul", out)
206    }
207
208    fn reshape(&self, shape: &[usize], session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
209        let out = session.reshape_read(T::tensor_read(self), shape)?;
210        into_typed_result("reshape", out)
211    }
212
213    fn transpose(
214        &self,
215        perm: &[usize],
216        session: &mut dyn BackendSession,
217    ) -> Result<TypedTensor<T>> {
218        let out = session.transpose_read(T::tensor_read(self), perm)?;
219        into_typed_result("transpose", out)
220    }
221
222    fn broadcast_in_dim(
223        &self,
224        shape: &[usize],
225        dims: &[usize],
226        session: &mut dyn BackendSession,
227    ) -> Result<TypedTensor<T>> {
228        let out = session.broadcast_in_dim_read(T::tensor_read(self), shape, dims)?;
229        into_typed_result("broadcast_in_dim", out)
230    }
231}
232
233impl TypedTensorMaskSessionOpsExt for TypedTensor<bool> {
234    fn where_select<U: TensorScalar>(
235        &self,
236        on_true: &TypedTensor<U>,
237        on_false: &TypedTensor<U>,
238        session: &mut dyn BackendSession,
239    ) -> Result<TypedTensor<U>> {
240        let (condition, on_true, on_false) =
241            broadcast_ternary_in_read(self, on_true, on_false, session)?;
242        let out = session.select_read(
243            condition.tensor_read(),
244            on_true.tensor_read(),
245            on_false.tensor_read(),
246        )?;
247        into_typed_result("where_select", out)
248    }
249}
250
251// INVARIANT: this private adapter keeps borrowed reads borrowed and owns only
252// the explicit fallback tensor; it is never exposed or cloned.
253#[allow(clippy::large_enum_variant)]
254pub(crate) enum ReadInput<'a> {
255    Borrowed(TensorRead<'a>),
256    Owned(Tensor),
257}
258
259impl ReadInput<'_> {
260    pub(crate) fn tensor_read(&self) -> TensorRead<'_> {
261        match self {
262            Self::Borrowed(read) => read.clone(),
263            Self::Owned(tensor) => TensorRead::from_tensor(tensor),
264        }
265    }
266}
267
268pub(crate) fn broadcast_to_in_read<'a>(
269    input: TensorRead<'a>,
270    target_shape: &[usize],
271    session: &mut dyn BackendSession,
272) -> Result<ReadInput<'a>> {
273    if input.shape() == target_shape {
274        return Ok(ReadInput::Borrowed(input));
275    }
276
277    let plan = broadcast_input_plan(input.shape(), target_shape).map_err(broadcast_error)?;
278    let source = if plan.source_shape == input.shape() {
279        ReadInput::Borrowed(input)
280    } else {
281        let reshaped = session.reshape_read(input, &plan.source_shape)?;
282        ReadInput::Owned(reshaped)
283    };
284    let out = session.broadcast_in_dim_read(source.tensor_read(), target_shape, &plan.dims)?;
285    Ok(ReadInput::Owned(out))
286}
287
288fn broadcast_binary_in_read<'a, T: TensorScalar>(
289    lhs: &'a TypedTensor<T>,
290    rhs: &'a TypedTensor<T>,
291    session: &mut dyn BackendSession,
292) -> Result<(ReadInput<'a>, ReadInput<'a>)> {
293    let shape = broadcast_shape(lhs.shape(), rhs.shape()).map_err(broadcast_error)?;
294    Ok((
295        broadcast_to_in_read(T::tensor_read(lhs), &shape, session)?,
296        broadcast_to_in_read(T::tensor_read(rhs), &shape, session)?,
297    ))
298}
299
300fn broadcast_ternary_in_read<'a, C: TensorScalar, T: TensorScalar>(
301    first: &'a TypedTensor<C>,
302    second: &'a TypedTensor<T>,
303    third: &'a TypedTensor<T>,
304    session: &mut dyn BackendSession,
305) -> Result<(ReadInput<'a>, ReadInput<'a>, ReadInput<'a>)> {
306    let shape = broadcast_shapes([first.shape(), second.shape(), third.shape()])
307        .map_err(broadcast_error)?;
308    Ok((
309        broadcast_to_in_read(C::tensor_read(first), &shape, session)?,
310        broadcast_to_in_read(T::tensor_read(second), &shape, session)?,
311        broadcast_to_in_read(T::tensor_read(third), &shape, session)?,
312    ))
313}
314
315fn broadcast_error(err: BroadcastError) -> Error {
316    match err {
317        BroadcastError::IncompatibleBinary { lhs, rhs } => {
318            Error::shape_mismatch("broadcast", lhs, rhs)
319        }
320        BroadcastError::IncompatibleInput { input, output } => {
321            Error::shape_mismatch("broadcast", input, output)
322        }
323        BroadcastError::RankTooLarge { input, output } => {
324            Error::rank_mismatch("broadcast", output.len(), input.len())
325        }
326    }
327}
328
329fn into_typed_result<T: TensorScalar>(op: &'static str, tensor: Tensor) -> Result<TypedTensor<T>> {
330    let actual = tensor.dtype();
331    T::into_typed(tensor).map_err(|_| {
332        Error::validation(
333            op,
334            ValidationError::DTypeMismatch {
335                expected: T::dtype(),
336                actual,
337            },
338        )
339    })
340}