1use std::hash::{Hash, Hasher};
2use std::sync::Arc;
3
4#[cfg(all(test, feature = "autodiff"))]
5use crate::ad::{ADRuleResult, PrimitiveTransposeInput};
6#[cfg(all(test, feature = "autodiff"))]
7use computegraph::types::{LocalValueId, OperationRole, ValueKey};
8use computegraph::GraphOperation;
9use num_complex::{Complex32, Complex64};
10
11use crate::dim_expr::DimExpr;
12use crate::ext_op::{ext_op_eq, hash_extension, ExtensionOp};
13use crate::input_key::TensorInputKey;
14use tenferro_tensor::{
15 CompareDir, DType, DotGeneralConfig, GatherConfig, PadConfig, ScatterConfig, SliceConfig,
16 TensorScalar,
17};
18
19pub trait ConstantScalar: TensorScalar + private::Sealed {
29 fn constant_bytes(self) -> Vec<u8>;
39}
40
41mod private {
42 pub trait Sealed {}
43
44 impl Sealed for f64 {}
45 impl Sealed for f32 {}
46 impl Sealed for i64 {}
47 impl Sealed for i32 {}
48 impl Sealed for bool {}
49 impl Sealed for num_complex::Complex64 {}
50 impl Sealed for num_complex::Complex32 {}
51}
52
53impl ConstantScalar for f64 {
54 fn constant_bytes(self) -> Vec<u8> {
55 self.to_le_bytes().to_vec()
56 }
57}
58
59impl ConstantScalar for f32 {
60 fn constant_bytes(self) -> Vec<u8> {
61 self.to_le_bytes().to_vec()
62 }
63}
64
65impl ConstantScalar for i64 {
66 fn constant_bytes(self) -> Vec<u8> {
67 self.to_le_bytes().to_vec()
68 }
69}
70
71impl ConstantScalar for i32 {
72 fn constant_bytes(self) -> Vec<u8> {
73 self.to_le_bytes().to_vec()
74 }
75}
76
77impl ConstantScalar for bool {
78 fn constant_bytes(self) -> Vec<u8> {
79 vec![u8::from(self)]
80 }
81}
82
83impl ConstantScalar for Complex64 {
84 fn constant_bytes(self) -> Vec<u8> {
85 let mut bytes = Vec::with_capacity(16);
86 bytes.extend_from_slice(&self.re.to_le_bytes());
87 bytes.extend_from_slice(&self.im.to_le_bytes());
88 bytes
89 }
90}
91
92impl ConstantScalar for Complex32 {
93 fn constant_bytes(self) -> Vec<u8> {
94 let mut bytes = Vec::with_capacity(8);
95 bytes.extend_from_slice(&self.re.to_le_bytes());
96 bytes.extend_from_slice(&self.im.to_le_bytes());
97 bytes
98 }
99}
100
101tenferro_core_ops::define_std_tensor_op!();
102
103impl StdTensorOp {
104 pub fn constant<T: ConstantScalar>(value: T) -> Self {
120 Self::Constant {
121 dtype: T::dtype(),
122 bytes: value.constant_bytes(),
123 }
124 }
125}
126
127impl PartialEq for StdTensorOp {
128 fn eq(&self, other: &Self) -> bool {
129 if std::mem::discriminant(self) != std::mem::discriminant(other) {
130 return false;
131 }
132 match (self, other) {
133 (Self::Add, Self::Add)
134 | (Self::Sub, Self::Sub)
135 | (Self::Mul, Self::Mul)
136 | (Self::Neg, Self::Neg)
137 | (Self::Conj, Self::Conj)
138 | (Self::Div, Self::Div)
139 | (Self::Rem, Self::Rem)
140 | (Self::Abs, Self::Abs)
141 | (Self::Sign, Self::Sign)
142 | (Self::Maximum, Self::Maximum)
143 | (Self::Minimum, Self::Minimum)
144 | (Self::Select, Self::Select)
145 | (Self::Clamp, Self::Clamp)
146 | (Self::Exp, Self::Exp)
147 | (Self::Log, Self::Log)
148 | (Self::Sin, Self::Sin)
149 | (Self::Cos, Self::Cos)
150 | (Self::Tanh, Self::Tanh)
151 | (Self::Sqrt, Self::Sqrt)
152 | (Self::Rsqrt, Self::Rsqrt)
153 | (Self::Pow, Self::Pow)
154 | (Self::Expm1, Self::Expm1)
155 | (Self::Log1p, Self::Log1p)
156 | (Self::DynamicUpdateSlice, Self::DynamicUpdateSlice) => true,
157 (Self::DotGeneral { config: a }, Self::DotGeneral { config: b }) => a == b,
158 (Self::Transpose { perm: a }, Self::Transpose { perm: b }) => a == b,
159 (Self::Reshape { to_shape: a }, Self::Reshape { to_shape: b }) => a == b,
160 (
161 Self::BroadcastInDim {
162 shape: sa,
163 dims: da,
164 },
165 Self::BroadcastInDim {
166 shape: sb,
167 dims: db,
168 },
169 ) => sa == sb && da == db,
170 (Self::Convert { from: fa, to: ta }, Self::Convert { from: fb, to: tb }) => {
171 fa == fb && ta == tb
172 }
173 (
174 Self::Constant {
175 dtype: da,
176 bytes: ba,
177 },
178 Self::Constant {
179 dtype: db,
180 bytes: bb,
181 },
182 ) => da == db && ba == bb,
183 (Self::ReduceSum { axes: a }, Self::ReduceSum { axes: b })
184 | (Self::ReduceSumSquares { axes: a }, Self::ReduceSumSquares { axes: b })
185 | (Self::ReduceProd { axes: a }, Self::ReduceProd { axes: b })
186 | (Self::ReduceMax { axes: a }, Self::ReduceMax { axes: b })
187 | (Self::ReduceMin { axes: a }, Self::ReduceMin { axes: b })
188 | (Self::Reverse { axes: a }, Self::Reverse { axes: b }) => a == b,
189 (Self::Compare(a), Self::Compare(b)) => a == b,
190 (
191 Self::ExtractDiag {
192 axis_a: aa,
193 axis_b: ba,
194 },
195 Self::ExtractDiag {
196 axis_a: ab,
197 axis_b: bb,
198 },
199 )
200 | (
201 Self::EmbedDiag {
202 axis_a: aa,
203 axis_b: ba,
204 },
205 Self::EmbedDiag {
206 axis_a: ab,
207 axis_b: bb,
208 },
209 ) => aa == ab && ba == bb,
210 (Self::Tril { k: a }, Self::Tril { k: b })
211 | (Self::Triu { k: a }, Self::Triu { k: b }) => a == b,
212 (Self::Gather(a), Self::Gather(b)) => a == b,
213 (
214 Self::GatherDynamicSliceSizes {
215 offset_dims: oa,
216 collapsed_slice_dims: ca,
217 start_index_map: sa,
218 index_vector_dim: ia,
219 slice_sizes: za,
220 },
221 Self::GatherDynamicSliceSizes {
222 offset_dims: ob,
223 collapsed_slice_dims: cb,
224 start_index_map: sb,
225 index_vector_dim: ib,
226 slice_sizes: zb,
227 },
228 ) => oa == ob && ca == cb && sa == sb && ia == ib && za == zb,
229 (Self::Scatter(a), Self::Scatter(b)) => a == b,
230 (Self::Slice(a), Self::Slice(b)) => a == b,
231 (Self::DynamicSlice { slice_sizes: a }, Self::DynamicSlice { slice_sizes: b }) => {
232 a == b
233 }
234 (Self::Pad(a), Self::Pad(b)) => a == b,
235 (
236 Self::Concatenate {
237 axis: a,
238 input_count: na,
239 },
240 Self::Concatenate {
241 axis: b,
242 input_count: nb,
243 },
244 ) => a == b && na == nb,
245 (Self::ShapeOf { axis: a }, Self::ShapeOf { axis: b })
246 | (Self::DynamicTruncate { axis: a }, Self::DynamicTruncate { axis: b })
247 | (Self::PadToMatch { axis: a }, Self::PadToMatch { axis: b }) => a == b,
248 (Self::Extension(a), Self::Extension(b)) => ext_op_eq(a.as_ref(), b.as_ref()),
249 _ => false,
250 }
251 }
252}
253
254impl Eq for StdTensorOp {}
255
256impl Hash for StdTensorOp {
257 fn hash<H: Hasher>(&self, state: &mut H) {
258 std::mem::discriminant(self).hash(state);
259 match self {
260 Self::Add
261 | Self::Sub
262 | Self::Mul
263 | Self::Neg
264 | Self::Conj
265 | Self::Div
266 | Self::Rem
267 | Self::Abs
268 | Self::Sign
269 | Self::Maximum
270 | Self::Minimum
271 | Self::Select
272 | Self::Clamp
273 | Self::Exp
274 | Self::Log
275 | Self::Sin
276 | Self::Cos
277 | Self::Tanh
278 | Self::Sqrt
279 | Self::Rsqrt
280 | Self::Pow
281 | Self::Expm1
282 | Self::Log1p => {}
283 Self::DotGeneral { config } => {
284 config.hash(state);
285 }
286 Self::Transpose { perm } => perm.hash(state),
287 Self::Reshape { to_shape } => {
288 to_shape.hash(state);
289 }
290 Self::BroadcastInDim { shape, dims } => {
291 shape.hash(state);
292 dims.hash(state);
293 }
294 Self::Convert { from, to } => {
295 from.hash(state);
296 to.hash(state);
297 }
298 Self::Constant { dtype, bytes } => {
299 dtype.hash(state);
300 bytes.hash(state);
301 }
302 Self::ReduceSum { axes } | Self::ReduceSumSquares { axes } => {
303 axes.hash(state);
304 }
305 Self::Compare(dir) => dir.hash(state),
306 Self::ExtractDiag { axis_a, axis_b } | Self::EmbedDiag { axis_a, axis_b } => {
307 axis_a.hash(state);
308 axis_b.hash(state);
309 }
310 Self::Tril { k } | Self::Triu { k } => k.hash(state),
311 Self::Gather(config) => config.hash(state),
312 Self::GatherDynamicSliceSizes {
313 offset_dims,
314 collapsed_slice_dims,
315 start_index_map,
316 index_vector_dim,
317 slice_sizes,
318 } => {
319 offset_dims.hash(state);
320 collapsed_slice_dims.hash(state);
321 start_index_map.hash(state);
322 index_vector_dim.hash(state);
323 slice_sizes.hash(state);
324 }
325 Self::Scatter(config) => config.hash(state),
326 Self::Slice(config) => config.hash(state),
327 Self::DynamicSlice { slice_sizes } => slice_sizes.hash(state),
328 Self::DynamicUpdateSlice => {}
329 Self::Pad(config) => config.hash(state),
330 Self::Concatenate { axis, input_count } => {
331 axis.hash(state);
332 input_count.hash(state);
333 }
334 Self::Reverse { axes } => axes.hash(state),
335 Self::ShapeOf { axis } | Self::DynamicTruncate { axis } | Self::PadToMatch { axis } => {
336 axis.hash(state)
337 }
338 Self::ReduceProd { axes } | Self::ReduceMax { axes } | Self::ReduceMin { axes } => {
339 axes.hash(state);
340 }
341 Self::Extension(op) => hash_extension(op.as_ref(), state),
342 }
343 }
344}
345
346fn n_inputs_from_dim_exprs(min_inputs: usize, exprs: &[&[DimExpr]]) -> usize {
347 let max_idx = exprs
348 .iter()
349 .flat_map(|exprs| exprs.iter())
350 .filter_map(DimExpr::max_input_idx)
351 .max()
352 .map_or(0, |max_idx| max_idx + 1);
353 max_idx.max(min_inputs)
354}
355
356impl GraphOperation for StdTensorOp {
357 type Operand = tenferro_tensor::Tensor;
358 type Context = ();
359 type InputKey = TensorInputKey;
360
361 fn input_count(&self) -> usize {
362 match self {
363 Self::Add | Self::Sub | Self::Mul | Self::DotGeneral { .. } | Self::Gather(_) => 2,
364 Self::GatherDynamicSliceSizes { slice_sizes, .. } => {
365 n_inputs_from_dim_exprs(2, &[slice_sizes])
366 }
367 Self::Neg
368 | Self::Conj
369 | Self::Transpose { .. }
370 | Self::Convert { .. }
371 | Self::ExtractDiag { .. }
372 | Self::EmbedDiag { .. }
373 | Self::Tril { .. }
374 | Self::Triu { .. }
375 | Self::Slice(_)
376 | Self::Pad(_)
377 | Self::Reverse { .. }
378 | Self::ShapeOf { .. } => 1,
379 Self::DynamicTruncate { .. } | Self::PadToMatch { .. } => 2,
380 Self::Reshape { to_shape } => n_inputs_from_dim_exprs(1, &[to_shape]),
381 Self::BroadcastInDim { shape, .. } => n_inputs_from_dim_exprs(1, &[shape]),
382 Self::ReduceSum { .. }
383 | Self::ReduceSumSquares { .. }
384 | Self::ReduceProd { .. }
385 | Self::ReduceMax { .. }
386 | Self::ReduceMin { .. } => 1,
387 Self::Div
388 | Self::Rem
389 | Self::Maximum
390 | Self::Minimum
391 | Self::Pow
392 | Self::DynamicSlice { .. } => 2,
393 Self::Constant { .. } => 0,
394 Self::Scatter(_) | Self::DynamicUpdateSlice => 3,
395 Self::Concatenate { input_count, .. } => *input_count,
396 Self::Abs
397 | Self::Sign
398 | Self::Exp
399 | Self::Log
400 | Self::Sin
401 | Self::Cos
402 | Self::Tanh
403 | Self::Sqrt
404 | Self::Rsqrt
405 | Self::Expm1
406 | Self::Log1p => 1,
407 Self::Select | Self::Clamp => 3,
408 Self::Compare(_) => 2,
409 Self::Extension(op) => ExtensionOp::input_count(op.as_ref()),
410 }
411 }
412
413 fn output_count(&self) -> usize {
414 match self {
415 Self::Add
416 | Self::Sub
417 | Self::Mul
418 | Self::Neg
419 | Self::Conj
420 | Self::DotGeneral { .. }
421 | Self::Transpose { .. }
422 | Self::Reshape { .. }
423 | Self::BroadcastInDim { .. }
424 | Self::Convert { .. }
425 | Self::ReduceSum { .. }
426 | Self::ReduceSumSquares { .. }
427 | Self::Div
428 | Self::Rem
429 | Self::Abs
430 | Self::Sign
431 | Self::Maximum
432 | Self::Minimum
433 | Self::Compare(_)
434 | Self::Select
435 | Self::Clamp
436 | Self::Constant { .. }
437 | Self::Exp
438 | Self::Log
439 | Self::Sin
440 | Self::Cos
441 | Self::Tanh
442 | Self::Sqrt
443 | Self::Rsqrt
444 | Self::Pow
445 | Self::Expm1
446 | Self::Log1p
447 | Self::ExtractDiag { .. }
448 | Self::EmbedDiag { .. }
449 | Self::Tril { .. }
450 | Self::Triu { .. }
451 | Self::Gather(_)
452 | Self::GatherDynamicSliceSizes { .. }
453 | Self::Scatter(_)
454 | Self::Slice(_)
455 | Self::DynamicSlice { .. }
456 | Self::DynamicUpdateSlice
457 | Self::Pad(_)
458 | Self::Reverse { .. }
459 | Self::ShapeOf { .. }
460 | Self::DynamicTruncate { .. }
461 | Self::PadToMatch { .. }
462 | Self::ReduceProd { .. }
463 | Self::ReduceMax { .. }
464 | Self::ReduceMin { .. } => 1,
465 Self::Concatenate { .. } => 1,
466 Self::Extension(op) => ExtensionOp::output_count(op.as_ref()),
467 }
468 }
469}
470
471#[cfg(all(test, feature = "autodiff"))]
472impl StdTensorOp {
473 pub(crate) fn jvp_rule(
474 &self,
475 builder: &mut computegraph::graph::GraphBuilder<Self>,
476 primal_in: &[ValueKey<Self>],
477 primal_out: &[ValueKey<Self>],
478 tangent_in: &[Option<LocalValueId>],
479 ctx: &mut crate::ad::context::ShapeGuardContext,
480 ) -> ADRuleResult<Vec<Option<LocalValueId>>> {
481 crate::ad::linearize(self, builder, primal_in, primal_out, tangent_in, ctx)
482 }
483
484 pub(crate) fn transpose_rule(
485 &self,
486 builder: &mut computegraph::graph::GraphBuilder<Self>,
487 cotangent_out: &[Option<LocalValueId>],
488 inputs: &[computegraph::ValueRef<Self>],
489 mode: &OperationRole,
490 ctx: &mut crate::ad::context::ShapeGuardContext,
491 ) -> ADRuleResult<Vec<Option<LocalValueId>>> {
492 let inputs = inputs
493 .iter()
494 .map(|input| match input {
495 computegraph::ValueRef::Local(local_id) => {
496 let key = builder.global_key(*local_id).clone();
497 PrimitiveTransposeInput::Residual(key)
498 }
499 computegraph::ValueRef::External(key) => {
500 PrimitiveTransposeInput::Residual(key.clone())
501 }
502 })
503 .collect::<Vec<_>>();
504 crate::ad::transpose_rule(self, builder, cotangent_out, inputs.as_slice(), mode, ctx)
505 }
506}