1use std::collections::HashMap;
2use std::fmt;
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::sync::Arc;
5
6use computegraph::graph::{Graph, GraphBuilder};
7use computegraph::types::{OperationRole, ValueKey, ValueRef};
8use computegraph::LocalValueId;
9use num_complex::{Complex32, Complex64};
10use tenferro_ops::ad::context::GlobalMetadataScope;
11use tenferro_ops::broadcast::{broadcast_input_plan, broadcast_shape, broadcast_shapes};
12use tenferro_ops::dim_expr::DimExpr;
13use tenferro_ops::input_key::TensorInputKey;
14use tenferro_ops::std_tensor_op::StdTensorOp;
15use tenferro_tensor::{
16 CompareDir, DType, DotGeneralConfig, GatherConfig, PadConfig, ScatterConfig, SliceConfig,
17 Tensor, TensorScalar,
18};
19
20use super::error::{Error, Result};
21use super::sym_dim::SymDim;
22use crate::checkpoint::CheckpointNode;
23use crate::metadata::{
24 concrete_tensor_meta, register_scoped_graph_metadata, register_scoped_value_metadata,
25 symbolic_input_meta, tensor_meta, MetadataScopeChain,
26};
27use crate::scalar_semantics::{bool_from_real_for_op, round_real_to_i32_for_op, round_real_to_i64};
28
29static NEXT_INPUT_ID: AtomicU64 = AtomicU64::new(0);
30static NEXT_TRACED_ID: AtomicU64 = AtomicU64::new(0);
31
32pub type TracedTensorId = u64;
33
34pub(crate) fn next_input_key() -> TensorInputKey {
35 TensorInputKey::User {
36 id: NEXT_INPUT_ID.fetch_add(1, Ordering::Relaxed),
37 }
38}
39
40pub(crate) fn next_traced_id() -> TracedTensorId {
41 NEXT_TRACED_ID.fetch_add(1, Ordering::Relaxed)
42}
43
44type TracedInputMap = HashMap<TensorInputKey, Arc<Tensor>>;
45
46#[derive(Clone)]
47pub struct TracedTensor {
48 pub id: TracedTensorId,
49 pub rank: usize,
50 pub dtype: DType,
51 pub(crate) graph: Arc<Graph<StdTensorOp>>,
52 pub val: LocalValueId,
53 pub(crate) data: Option<Arc<Tensor>>,
54 pub(crate) shape_hint: Option<Vec<SymDim>>,
55 pub(crate) inputs_map: Arc<TracedInputMap>,
56 pub(crate) extra_roots: Vec<Arc<Graph<StdTensorOp>>>,
57 pub(crate) checkpoint_chain: Option<Arc<CheckpointNode>>,
58 pub(crate) metadata_scopes: MetadataScopeChain,
59}
60
61impl fmt::Debug for TracedTensor {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 f.debug_struct("TracedTensor")
64 .field("id", &self.id)
65 .field("rank", &self.rank)
66 .field("dtype", &self.dtype)
67 .field("val", &self.val)
68 .field("shape_hint", &self.shape_hint)
69 .field("has_data", &self.data.is_some())
70 .finish_non_exhaustive()
71 }
72}
73
74pub(crate) fn merge_traced_inputs_map<'a>(
75 inputs: impl IntoIterator<Item = &'a TracedTensor>,
76) -> Arc<TracedInputMap> {
77 let maps: Vec<_> = inputs
78 .into_iter()
79 .map(|input| &input.inputs_map)
80 .filter(|map| !map.is_empty())
81 .collect();
82 match maps.as_slice() {
83 [] => return Arc::new(HashMap::new()),
84 [single] => return Arc::clone(*single),
85 _ => {}
86 }
87
88 for &candidate in &maps {
89 if input_map_matches_ordered_merge(candidate.as_ref(), &maps) {
90 return Arc::clone(candidate);
91 }
92 }
93
94 let mut merged = (**maps[0]).clone();
95 for map in maps.iter().skip(1) {
96 merged.extend(
97 map.iter()
98 .map(|(key, tensor)| (key.clone(), tensor.clone())),
99 );
100 }
101 Arc::new(merged)
102}
103
104fn input_map_matches_ordered_merge(
105 candidate: &TracedInputMap,
106 maps: &[&Arc<TracedInputMap>],
107) -> bool {
108 for map in maps {
109 for key in map.keys() {
110 let Some(final_tensor) = maps.iter().rev().find_map(|source| source.get(key)) else {
111 return false;
112 };
113 let Some(candidate_tensor) = candidate.get(key) else {
114 return false;
115 };
116 if !Arc::ptr_eq(candidate_tensor, final_tensor) {
117 return false;
118 }
119 }
120 }
121 true
122}
123
124pub(crate) fn try_concrete_shape(tensor: &TracedTensor) -> Option<Vec<usize>> {
125 tensor
126 .shape_hint
127 .as_ref()?
128 .iter()
129 .map(SymDim::constant_value)
130 .collect()
131}
132
133pub(crate) fn concrete_shape(tensor: &TracedTensor) -> Result<Vec<usize>> {
134 tensor
135 .shape_hint
136 .as_ref()
137 .ok_or_else(|| Error::InvalidGraphBuild {
138 op: "TracedTensor::concrete_shape",
139 message: format!("missing shape hint for traced tensor {}", tensor.id),
140 })?
141 .iter()
142 .map(|dim| {
143 dim.constant_value()
144 .ok_or_else(|| Error::InvalidGraphBuild {
145 op: "TracedTensor::concrete_shape",
146 message: format!("symbolic dimension in shape hint for tensor {}", tensor.id),
147 })
148 })
149 .collect()
150}
151
152pub(crate) fn broadcast_to(tensor: &TracedTensor, target_shape: &[usize]) -> Result<TracedTensor> {
157 let tensor_shape = concrete_shape(tensor)?;
158 if tensor_shape == target_shape {
159 return Ok(tensor.clone());
160 }
161
162 let plan = broadcast_input_plan(&tensor_shape, target_shape).map_err(|err| {
163 Error::InvalidGraphBuild {
164 op: "broadcast_to",
165 message: err.to_string(),
166 }
167 })?;
168
169 let source = if plan.source_shape == tensor_shape {
170 tensor.clone()
171 } else {
172 tensor.reshape(&plan.source_shape)?
173 };
174 source.broadcast_in_dim(target_shape, &plan.dims)
175}
176
177pub(crate) fn broadcast_binary(
179 a: &TracedTensor,
180 b: &TracedTensor,
181) -> Result<(TracedTensor, TracedTensor)> {
182 if a.shape_hint == b.shape_hint && a.rank == b.rank {
183 return Ok((a.clone(), b.clone()));
184 }
185 if (try_concrete_shape(a).is_none() || try_concrete_shape(b).is_none()) && a.rank == b.rank {
186 return Ok((a.clone(), b.clone()));
187 }
188 let a_shape = concrete_shape(a)?;
189 let b_shape = concrete_shape(b)?;
190 let target = broadcast_shape(&a_shape, &b_shape).map_err(|err| Error::InvalidGraphBuild {
191 op: "broadcast_binary",
192 message: err.to_string(),
193 })?;
194 Ok((broadcast_to(a, &target)?, broadcast_to(b, &target)?))
195}
196
197pub(crate) fn broadcast_ternary(
198 a: &TracedTensor,
199 b: &TracedTensor,
200 c: &TracedTensor,
201) -> Result<(TracedTensor, TracedTensor, TracedTensor)> {
202 let a_shape = concrete_shape(a)?;
203 let b_shape = concrete_shape(b)?;
204 let c_shape = concrete_shape(c)?;
205 let target = broadcast_shapes([a_shape.as_slice(), b_shape.as_slice(), c_shape.as_slice()])
206 .map_err(|err| Error::InvalidGraphBuild {
207 op: "broadcast_ternary",
208 message: err.to_string(),
209 })?;
210 Ok((
211 broadcast_to(a, &target)?,
212 broadcast_to(b, &target)?,
213 broadcast_to(c, &target)?,
214 ))
215}
216
217fn scale_with_constant(input: &TracedTensor, op: StdTensorOp) -> Result<TracedTensor> {
218 let scalar = apply_nullary(op, 0, input.dtype, Some(vec![]))?;
219 apply_binary(
220 StdTensorOp::Mul,
221 input,
222 &scalar,
223 input.rank,
224 input.shape_hint.clone(),
225 )
226}
227
228fn dtype_inference_error(op: &StdTensorOp, context: &'static str, err: String) -> Error {
229 Error::InvalidGraphBuild {
230 op: context,
231 message: format!("built-in traced dtype inference failed for {op:?}: {err}"),
232 }
233}
234
235fn try_inferred_output_dtype(
236 op: &StdTensorOp,
237 inputs: &[DType],
238 context: &'static str,
239) -> Result<DType> {
240 crate::shape_infer::infer_output_dtype(op, inputs)
241 .map_err(|err| dtype_inference_error(op, context, err.to_string()))
242}
243
244fn inferred_output_dtype(op: &StdTensorOp, inputs: &[DType], context: &'static str) -> DType {
245 match crate::shape_infer::infer_output_dtype(op, inputs) {
246 Ok(dtype) => dtype,
247 Err(err) => {
248 panic!("{context}: built-in traced dtype inference failed for {op:?}: {err}");
249 }
250 }
251}
252
253fn checked_shape_product_for_graph_build(
254 shape: &[usize],
255 context: &'static str,
256 role: &'static str,
257) -> Result<usize> {
258 shape.iter().copied().try_fold(1usize, |acc, dim| {
259 acc.checked_mul(dim)
260 .ok_or_else(|| Error::InvalidGraphBuild {
261 op: context,
262 message: format!("{role} shape element count overflows usize"),
263 })
264 })
265}
266
267fn validate_concrete_reshape_shape(input: &TracedTensor, shape: &[usize]) -> Result<()> {
268 let to = checked_shape_product_for_graph_build(shape, "TracedTensor::reshape", "target")?;
269 let Some(input_shape) = try_concrete_shape(input) else {
270 return Ok(());
271 };
272 let from =
273 checked_shape_product_for_graph_build(&input_shape, "TracedTensor::reshape", "input")?;
274 if from != to {
275 return Err(Error::InvalidGraphBuild {
276 op: "TracedTensor::reshape",
277 message: format!("reshape element-count mismatch: from {from} to {to}"),
278 });
279 }
280 Ok(())
281}
282
283fn traced_input_shape_exprs(input_idx: usize, tensor: &TracedTensor) -> Vec<DimExpr> {
284 match tensor.shape_hint.as_ref() {
285 Some(shape) => shape
286 .iter()
287 .enumerate()
288 .map(|(axis, dim)| {
289 dim.constant_value()
290 .map_or(DimExpr::InputDim { input_idx, axis }, DimExpr::Const)
291 })
292 .collect(),
293 None => (0..tensor.rank)
294 .map(|axis| DimExpr::InputDim { input_idx, axis })
295 .collect(),
296 }
297}
298
299fn traced_input_sym_shape(tensor: &TracedTensor) -> Vec<SymDim> {
300 tensor.shape_hint.clone().unwrap_or_else(|| {
301 (0..tensor.rank)
302 .map(|axis| SymDim::tensor_axis(tensor.id, axis))
303 .collect()
304 })
305}
306
307pub(crate) fn infer_traced_single_output_shape(
308 op_name: &'static str,
309 op: &StdTensorOp,
310 inputs: &[&TracedTensor],
311) -> Result<(usize, Option<Vec<SymDim>>)> {
312 let input_shape_exprs: Vec<Vec<DimExpr>> = inputs
313 .iter()
314 .enumerate()
315 .map(|(input_idx, tensor)| traced_input_shape_exprs(input_idx, tensor))
316 .collect();
317 let input_shape_refs: Vec<&[DimExpr]> = input_shape_exprs.iter().map(Vec::as_slice).collect();
318 let output_shapes =
319 crate::shape_infer::infer_output_shapes(op, &input_shape_refs).map_err(|err| {
320 Error::InvalidGraphBuild {
321 op: op_name,
322 message: err.to_string(),
323 }
324 })?;
325 let output_shape = output_shapes
326 .first()
327 .ok_or_else(|| Error::InvalidGraphBuild {
328 op: op_name,
329 message: "shape inference returned no outputs".into(),
330 })?;
331 if output_shapes.len() != 1 {
332 return Err(Error::InvalidGraphBuild {
333 op: op_name,
334 message: format!(
335 "expected single-output shape inference, got {} outputs",
336 output_shapes.len()
337 ),
338 });
339 }
340
341 let input_sym_shapes: Vec<Vec<SymDim>> = inputs
342 .iter()
343 .map(|tensor| traced_input_sym_shape(tensor))
344 .collect();
345 let input_sym_refs: Vec<&[SymDim]> = input_sym_shapes.iter().map(Vec::as_slice).collect();
346 let out_shape_hint = output_shape
347 .iter()
348 .map(|dim| SymDim::from_dim_expr(dim, &input_sym_refs))
349 .collect();
350 Ok((output_shape.len(), Some(out_shape_hint)))
351}
352
353pub(crate) fn register_metadata_or_internal(
354 result: std::result::Result<GlobalMetadataScope, impl std::fmt::Display>,
355) -> Result<GlobalMetadataScope> {
356 result.map_err(|err| Error::Internal(format!("metadata registration failed: {err}")))
357}
358
359fn reduction_output_meta(
360 tensor: &TracedTensor,
361 axes: &[usize],
362 op: &'static str,
363) -> Result<(usize, Option<Vec<SymDim>>)> {
364 let mut seen = vec![false; tensor.rank];
365 for &axis in axes {
366 if axis >= tensor.rank {
367 return Err(Error::InvalidGraphBuild {
368 op,
369 message: format!("axis {axis} out of bounds for rank {}", tensor.rank),
370 });
371 }
372 if seen[axis] {
373 return Err(Error::InvalidGraphBuild {
374 op,
375 message: format!("duplicate reduction axis {axis}"),
376 });
377 }
378 seen[axis] = true;
379 }
380
381 let out_shape_hint = tensor.shape_hint.as_ref().map(|shape| {
382 (0..shape.len())
383 .filter(|d| !axes.contains(d))
384 .map(|d| shape[d].clone())
385 .collect()
386 });
387 Ok((tensor.rank - axes.len(), out_shape_hint))
388}
389
390fn validate_traced_axis(tensor: &TracedTensor, axis: usize, op: &'static str) -> Result<()> {
391 if axis >= tensor.rank {
392 return Err(Error::InvalidGraphBuild {
393 op,
394 message: format!("axis {axis} out of bounds for rank {}", tensor.rank),
395 });
396 }
397 Ok(())
398}
399
400fn validate_traced_axes(rank: usize, axes: &[usize], op: &'static str) -> Result<()> {
401 let mut seen = vec![false; rank];
402 for &axis in axes {
403 if axis >= rank {
404 return Err(Error::InvalidGraphBuild {
405 op,
406 message: format!("axis {axis} out of bounds for rank {rank}"),
407 });
408 }
409 if seen[axis] {
410 return Err(Error::InvalidGraphBuild {
411 op,
412 message: format!("duplicate axis {axis}"),
413 });
414 }
415 seen[axis] = true;
416 }
417 Ok(())
418}
419
420fn validate_traced_insert_axis(rank: usize, axis: usize, op: &'static str) -> Result<()> {
421 if axis > rank {
422 return Err(Error::InvalidGraphBuild {
423 op,
424 message: format!("axis {axis} out of bounds for rank {rank} insertion"),
425 });
426 }
427 Ok(())
428}
429
430fn validate_traced_perm(rank: usize, perm: &[usize], op: &'static str) -> Result<()> {
431 if perm.len() != rank {
432 return Err(Error::InvalidGraphBuild {
433 op,
434 message: format!(
435 "permutation length {} does not match rank {rank}",
436 perm.len()
437 ),
438 });
439 }
440 let mut seen = vec![false; rank];
441 for &axis in perm {
442 if axis >= rank {
443 return Err(Error::InvalidGraphBuild {
444 op,
445 message: format!("permutation axis {axis} out of bounds for rank {rank}"),
446 });
447 }
448 if seen[axis] {
449 return Err(Error::InvalidGraphBuild {
450 op,
451 message: format!("duplicate permutation axis {axis}"),
452 });
453 }
454 seen[axis] = true;
455 }
456 Ok(())
457}
458
459fn validate_broadcast_in_dim_args(
460 input: &TracedTensor,
461 output_shape: &[SymDim],
462 dims: &[usize],
463 op: &'static str,
464) -> Result<()> {
465 if dims.len() != input.rank {
466 return Err(Error::InvalidGraphBuild {
467 op,
468 message: format!(
469 "dims length {} must match input rank {}",
470 dims.len(),
471 input.rank
472 ),
473 });
474 }
475
476 let mut seen = vec![false; output_shape.len()];
477 for &dim in dims {
478 if dim >= output_shape.len() {
479 return Err(Error::InvalidGraphBuild {
480 op,
481 message: format!(
482 "broadcast dim {dim} out of bounds for output rank {}",
483 output_shape.len()
484 ),
485 });
486 }
487 if seen[dim] {
488 return Err(Error::InvalidGraphBuild {
489 op,
490 message: format!("duplicate broadcast dim {dim}"),
491 });
492 }
493 seen[dim] = true;
494 }
495
496 if let Some(input_shape) = input.shape_hint.as_ref() {
497 for (input_axis, &output_axis) in dims.iter().enumerate() {
498 let input_dim = &input_shape[input_axis];
499 let output_dim = &output_shape[output_axis];
500 if input_dim != output_dim && input_dim.constant_value() != Some(1) {
501 return Err(Error::InvalidGraphBuild {
502 op,
503 message: format!(
504 "input axis {input_axis} with dim {input_dim:?} cannot broadcast to \
505 output axis {output_axis} with dim {output_dim:?}"
506 ),
507 });
508 }
509 }
510 }
511
512 Ok(())
513}
514
515impl std::ops::Add for &TracedTensor {
516 type Output = Result<TracedTensor>;
517
518 fn add(self, rhs: &TracedTensor) -> Result<TracedTensor> {
519 TracedTensor::add(self, rhs)
520 }
521}
522
523impl std::ops::Sub for &TracedTensor {
524 type Output = Result<TracedTensor>;
525
526 fn sub(self, rhs: &TracedTensor) -> Result<TracedTensor> {
527 TracedTensor::sub(self, rhs)
528 }
529}
530
531impl std::ops::Mul for &TracedTensor {
532 type Output = Result<TracedTensor>;
533
534 fn mul(self, rhs: &TracedTensor) -> Result<TracedTensor> {
535 TracedTensor::mul(self, rhs)
536 }
537}
538
539impl std::ops::Mul<f64> for &TracedTensor {
540 type Output = Result<TracedTensor>;
541
542 fn mul(self, rhs: f64) -> Result<TracedTensor> {
543 self.scale_real(rhs)
544 }
545}
546
547impl std::ops::Mul<&TracedTensor> for f64 {
548 type Output = Result<TracedTensor>;
549
550 fn mul(self, rhs: &TracedTensor) -> Result<TracedTensor> {
551 rhs.scale_real(self)
552 }
553}
554
555impl std::ops::Neg for &TracedTensor {
556 type Output = Result<TracedTensor>;
557
558 fn neg(self) -> Self::Output {
559 TracedTensor::neg(self)
560 }
561}
562
563impl std::ops::Div for &TracedTensor {
564 type Output = Result<TracedTensor>;
565
566 fn div(self, rhs: &TracedTensor) -> Result<TracedTensor> {
567 TracedTensor::div(self, rhs)
568 }
569}
570
571impl std::ops::Rem for &TracedTensor {
572 type Output = Result<TracedTensor>;
573
574 fn rem(self, rhs: &TracedTensor) -> Result<TracedTensor> {
575 TracedTensor::rem(self, rhs)
576 }
577}
578
579impl TracedTensor {
580 pub fn graph(&self) -> &Arc<Graph<StdTensorOp>> {
591 &self.graph
592 }
593
594 pub fn attached_data(&self) -> Option<&Arc<Tensor>> {
612 self.data.as_ref()
613 }
614
615 pub fn from_tensor_concrete_shape(tensor: Tensor) -> Result<Self> {
636 let shape = tensor.shape().to_vec();
637 let rank = shape.len();
638 let dtype = tensor.dtype();
639 let key = next_input_key();
640 let id = next_traced_id();
641 let data = Arc::new(tensor);
642
643 let mut builder = GraphBuilder::new();
644 let val = builder.add_input(key.clone());
645 builder.set_outputs(vec![val]);
646 let graph = Arc::new(builder.build());
647 let metadata_scope = register_metadata_or_internal(register_scoped_value_metadata(
648 graph.values()[val].key.clone(),
649 concrete_tensor_meta(dtype, &shape),
650 ))?;
651
652 let mut map = HashMap::new();
653 map.insert(key, Arc::clone(&data));
654
655 Ok(Self {
656 id,
657 rank,
658 dtype,
659 graph,
660 val,
661 data: Some(data),
662 shape_hint: Some(shape.into_iter().map(SymDim::from).collect()),
663 inputs_map: Arc::new(map),
664 extra_roots: Vec::new(),
665 checkpoint_chain: None,
666 metadata_scopes: MetadataScopeChain::from_scope(metadata_scope),
667 })
668 }
669
670 pub fn from_tensor_symbolic_shape(tensor: Tensor) -> Result<Self> {
691 let rank = tensor.shape().len();
692 let dtype = tensor.dtype();
693 let key = next_input_key();
694 let id = next_traced_id();
695 let data = Arc::new(tensor);
696
697 let mut builder = GraphBuilder::new();
698 let val = builder.add_input(key.clone());
699 builder.set_outputs(vec![val]);
700 let graph = Arc::new(builder.build());
701 let metadata_scope = register_metadata_or_internal(register_scoped_value_metadata(
702 graph.values()[val].key.clone(),
703 symbolic_input_meta(dtype, id, rank),
704 ))?;
705
706 let mut map = HashMap::new();
707 map.insert(key, Arc::clone(&data));
708
709 Ok(Self {
710 id,
711 rank,
712 dtype,
713 graph,
714 val,
715 data: Some(data),
716 shape_hint: None,
717 inputs_map: Arc::new(map),
718 extra_roots: Vec::new(),
719 checkpoint_chain: None,
720 metadata_scopes: MetadataScopeChain::from_scope(metadata_scope),
721 })
722 }
723
724 pub fn input_concrete_shape(dtype: DType, shape: &[usize]) -> Result<Self> {
741 let shape = shape.to_vec();
742 let rank = shape.len();
743 let key = next_input_key();
744 let id = next_traced_id();
745
746 let mut builder = GraphBuilder::new();
747 let val = builder.add_input(key.clone());
748 builder.set_outputs(vec![val]);
749 let graph = Arc::new(builder.build());
750 let metadata_scope = register_metadata_or_internal(register_scoped_value_metadata(
751 graph.values()[val].key.clone(),
752 concrete_tensor_meta(dtype, &shape),
753 ))?;
754
755 Ok(Self {
756 id,
757 rank,
758 dtype,
759 graph,
760 val,
761 data: None,
762 shape_hint: Some(shape.into_iter().map(SymDim::from).collect()),
763 inputs_map: Arc::new(HashMap::new()),
764 extra_roots: Vec::new(),
765 checkpoint_chain: None,
766 metadata_scopes: MetadataScopeChain::from_scope(metadata_scope),
767 })
768 }
769
770 pub fn input_symbolic_shape(dtype: DType, rank: usize) -> Result<Self> {
787 let key = next_input_key();
788 let id = next_traced_id();
789
790 let mut builder = GraphBuilder::new();
791 let val = builder.add_input(key.clone());
792 builder.set_outputs(vec![val]);
793 let graph = Arc::new(builder.build());
794 let metadata_scope = register_metadata_or_internal(register_scoped_value_metadata(
795 graph.values()[val].key.clone(),
796 symbolic_input_meta(dtype, id, rank),
797 ))?;
798
799 Ok(Self {
800 id,
801 rank,
802 dtype,
803 graph,
804 val,
805 data: None,
806 shape_hint: None,
807 inputs_map: Arc::new(HashMap::new()),
808 extra_roots: Vec::new(),
809 checkpoint_chain: None,
810 metadata_scopes: MetadataScopeChain::from_scope(metadata_scope),
811 })
812 }
813
814 pub fn from_vec_col_major<T: TensorScalar>(shape: Vec<usize>, data: Vec<T>) -> Result<Self> {
832 Self::from_tensor_concrete_shape(Tensor::from_vec_col_major(shape, data)?)
833 }
834
835 pub fn is_concrete_shape(&self) -> bool {
850 try_concrete_shape(self).is_some()
851 }
852
853 pub fn try_concrete_shape(&self) -> Option<Vec<usize>> {
874 try_concrete_shape(self)
875 }
876
877 pub fn concrete_shape(&self) -> Result<Vec<usize>> {
883 concrete_shape(self)
884 }
885
886 pub fn input_key(&self) -> Option<TensorInputKey> {
889 match &self.graph.values()[self.val].key {
890 ValueKey::Input(key) => Some(key.clone()),
891 _ => None,
892 }
893 }
894
895 pub fn add(&self, other: &TracedTensor) -> Result<TracedTensor> {
909 let (lhs, rhs) = broadcast_binary(self, other)?;
910 apply_binary(
911 StdTensorOp::Add,
912 &lhs,
913 &rhs,
914 lhs.rank,
915 lhs.shape_hint.clone(),
916 )
917 }
918
919 pub fn sub(&self, other: &TracedTensor) -> Result<TracedTensor> {
923 let (lhs, rhs) = broadcast_binary(self, other)?;
924 apply_binary(
925 StdTensorOp::Sub,
926 &lhs,
927 &rhs,
928 lhs.rank,
929 lhs.shape_hint.clone(),
930 )
931 }
932
933 pub fn mul(&self, other: &TracedTensor) -> Result<TracedTensor> {
947 let (lhs, rhs) = broadcast_binary(self, other)?;
948 apply_binary(
949 StdTensorOp::Mul,
950 &lhs,
951 &rhs,
952 lhs.rank,
953 lhs.shape_hint.clone(),
954 )
955 }
956
957 pub fn div(&self, other: &TracedTensor) -> Result<TracedTensor> {
971 let (lhs, rhs) = broadcast_binary(self, other)?;
972 apply_binary(
973 StdTensorOp::Div,
974 &lhs,
975 &rhs,
976 lhs.rank,
977 lhs.shape_hint.clone(),
978 )
979 }
980
981 pub fn rem(&self, other: &TracedTensor) -> Result<TracedTensor> {
985 let (lhs, rhs) = broadcast_binary(self, other)?;
986 apply_binary(
987 StdTensorOp::Rem,
988 &lhs,
989 &rhs,
990 lhs.rank,
991 lhs.shape_hint.clone(),
992 )
993 }
994
995 pub fn compare(&self, other: &TracedTensor, dir: CompareDir) -> Result<TracedTensor> {
997 apply_broadcast_binary_op(StdTensorOp::Compare(dir), self, other)
998 }
999
1000 pub fn maximum(&self, other: &TracedTensor) -> Result<TracedTensor> {
1002 apply_broadcast_binary_op(StdTensorOp::Maximum, self, other)
1003 }
1004
1005 pub fn minimum(&self, other: &TracedTensor) -> Result<TracedTensor> {
1007 apply_broadcast_binary_op(StdTensorOp::Minimum, self, other)
1008 }
1009
1010 pub fn where_select(
1012 condition: &TracedTensor,
1013 on_true: &TracedTensor,
1014 on_false: &TracedTensor,
1015 ) -> Result<TracedTensor> {
1016 apply_broadcast_ternary_op(StdTensorOp::Select, condition, on_true, on_false)
1017 }
1018
1019 pub fn select(
1021 condition: &TracedTensor,
1022 on_true: &TracedTensor,
1023 on_false: &TracedTensor,
1024 ) -> Result<TracedTensor> {
1025 Self::where_select(condition, on_true, on_false)
1026 }
1027
1028 pub fn clamp(&self, lower: &TracedTensor, upper: &TracedTensor) -> Result<TracedTensor> {
1030 apply_broadcast_ternary_op(StdTensorOp::Clamp, self, lower, upper)
1031 }
1032
1033 fn apply_same_shape_unary(&self, op: StdTensorOp) -> Result<TracedTensor> {
1034 apply_unary(op, self, self.rank, self.shape_hint.clone())
1035 }
1036
1037 pub fn neg(&self) -> Result<TracedTensor> {
1050 self.apply_same_shape_unary(StdTensorOp::Neg)
1051 }
1052
1053 pub fn conj(&self) -> Result<TracedTensor> {
1068 self.apply_same_shape_unary(StdTensorOp::Conj)
1069 }
1070
1071 pub fn abs(&self) -> Result<TracedTensor> {
1083 self.apply_same_shape_unary(StdTensorOp::Abs)
1084 }
1085
1086 pub fn sign(&self) -> Result<TracedTensor> {
1096 self.apply_same_shape_unary(StdTensorOp::Sign)
1097 }
1098
1099 pub fn scale_real(&self, factor: f64) -> Result<TracedTensor> {
1110 let op = match self.dtype {
1111 DType::F64 => StdTensorOp::constant(factor),
1112 DType::F32 => StdTensorOp::constant(factor as f32),
1113 DType::I32 => StdTensorOp::constant(round_real_to_i32_for_op("scale_real", factor)?),
1114 DType::I64 => StdTensorOp::constant(round_real_to_i64(factor)?),
1115 DType::Bool => StdTensorOp::constant(bool_from_real_for_op("scale_real", factor)?),
1116 DType::C64 => StdTensorOp::constant(Complex64::new(factor, 0.0)),
1117 DType::C32 => StdTensorOp::constant(Complex32::new(factor as f32, 0.0)),
1118 };
1119 scale_with_constant(self, op)
1120 }
1121
1122 pub fn scale_complex(&self, factor: Complex64) -> Result<TracedTensor> {
1140 match self.dtype {
1141 DType::C64 => scale_with_constant(self, StdTensorOp::constant(factor)),
1142 DType::C32 => scale_with_constant(
1143 self,
1144 StdTensorOp::constant(Complex32::new(factor.re as f32, factor.im as f32)),
1145 ),
1146 DType::F32 | DType::F64 | DType::I32 | DType::I64 | DType::Bool => {
1147 Err(Error::InvalidGraphBuild {
1148 op: "scale_complex",
1149 message: format!("requires complex tensor dtype, got {:?}", self.dtype),
1150 })
1151 }
1152 }
1153 }
1154
1155 pub fn exp(&self) -> Result<TracedTensor> {
1165 self.apply_same_shape_unary(StdTensorOp::Exp)
1166 }
1167
1168 pub fn log(&self) -> Result<TracedTensor> {
1178 self.apply_same_shape_unary(StdTensorOp::Log)
1179 }
1180
1181 pub fn sin(&self) -> Result<TracedTensor> {
1191 self.apply_same_shape_unary(StdTensorOp::Sin)
1192 }
1193
1194 pub fn cos(&self) -> Result<TracedTensor> {
1204 self.apply_same_shape_unary(StdTensorOp::Cos)
1205 }
1206
1207 pub fn tanh(&self) -> Result<TracedTensor> {
1217 self.apply_same_shape_unary(StdTensorOp::Tanh)
1218 }
1219
1220 pub fn sqrt(&self) -> Result<TracedTensor> {
1230 self.apply_same_shape_unary(StdTensorOp::Sqrt)
1231 }
1232
1233 pub fn rsqrt(&self) -> Result<TracedTensor> {
1243 self.apply_same_shape_unary(StdTensorOp::Rsqrt)
1244 }
1245
1246 pub fn pow(&self, other: &TracedTensor) -> Result<TracedTensor> {
1257 let (lhs, rhs) = broadcast_binary(self, other)?;
1258 apply_binary(
1259 StdTensorOp::Pow,
1260 &lhs,
1261 &rhs,
1262 lhs.rank,
1263 lhs.shape_hint.clone(),
1264 )
1265 }
1266
1267 pub fn expm1(&self) -> Result<TracedTensor> {
1277 self.apply_same_shape_unary(StdTensorOp::Expm1)
1278 }
1279
1280 pub fn log1p(&self) -> Result<TracedTensor> {
1290 self.apply_same_shape_unary(StdTensorOp::Log1p)
1291 }
1292
1293 pub fn convert(&self, to: DType) -> Result<TracedTensor> {
1314 tenferro_tensor::validate::validate_convert_dtype("TracedTensor::convert", self.dtype, to)?;
1315 self.cast(to)
1316 }
1317
1318 pub fn cast(&self, to: DType) -> Result<TracedTensor> {
1334 if self.dtype == to {
1335 return Ok(self.clone());
1336 }
1337
1338 apply_unary_with_dtype(
1339 StdTensorOp::Convert {
1340 from: self.dtype,
1341 to,
1342 },
1343 self,
1344 self.rank,
1345 self.shape_hint.clone(),
1346 to,
1347 )
1348 }
1349
1350 pub fn dot_general(
1373 &self,
1374 other: &TracedTensor,
1375 config: DotGeneralConfig,
1376 ) -> Result<TracedTensor> {
1377 config
1378 .validate_dims_with_ranks(self.rank, other.rank)
1379 .map_err(|err| Error::InvalidGraphBuild {
1380 op: "dot_general",
1381 message: err.to_string(),
1382 })?;
1383 let lhs_free: Vec<usize> = (0..self.rank)
1384 .filter(|d| {
1385 !config.lhs_contracting_dims.contains(d) && !config.lhs_batch_dims.contains(d)
1386 })
1387 .collect();
1388 let rhs_free: Vec<usize> = (0..other.rank)
1389 .filter(|d| {
1390 !config.rhs_contracting_dims.contains(d) && !config.rhs_batch_dims.contains(d)
1391 })
1392 .collect();
1393 let out_rank = config.lhs_batch_dims.len() + lhs_free.len() + rhs_free.len();
1394 let out_shape_hint = match (&self.shape_hint, &other.shape_hint) {
1395 (Some(lhs_shape), Some(rhs_shape)) => {
1396 let mut out_shape = Vec::with_capacity(out_rank);
1397 for &d in &lhs_free {
1398 out_shape.push(lhs_shape[d].clone());
1399 }
1400 for &d in &rhs_free {
1401 out_shape.push(rhs_shape[d].clone());
1402 }
1403 for &d in &config.lhs_batch_dims {
1404 out_shape.push(lhs_shape[d].clone());
1405 }
1406 Some(out_shape)
1407 }
1408 _ => None,
1409 };
1410
1411 apply_binary(
1412 StdTensorOp::DotGeneral { config },
1413 self,
1414 other,
1415 out_rank,
1416 out_shape_hint,
1417 )
1418 }
1419
1420 pub fn matmul(&self, other: &TracedTensor) -> Result<TracedTensor> {
1422 if self.rank != 2 {
1423 return Err(Error::InvalidGraphBuild {
1424 op: "TracedTensor::matmul",
1425 message: format!("matmul requires rank-2 inputs, got lhs rank {}", self.rank),
1426 });
1427 }
1428 if other.rank != 2 {
1429 return Err(Error::InvalidGraphBuild {
1430 op: "TracedTensor::matmul",
1431 message: format!("matmul requires rank-2 inputs, got rhs rank {}", other.rank),
1432 });
1433 }
1434 if let (Some(lhs_shape), Some(rhs_shape)) = (&self.shape_hint, &other.shape_hint) {
1435 if let (Some(lhs_cols), Some(rhs_rows)) =
1436 (lhs_shape[1].constant_value(), rhs_shape[0].constant_value())
1437 {
1438 if lhs_cols != rhs_rows {
1439 return Err(Error::InvalidGraphBuild {
1440 op: "TracedTensor::matmul",
1441 message: format!(
1442 "matmul dimension mismatch: lhs columns {lhs_cols} != rhs rows {rhs_rows}"
1443 ),
1444 });
1445 }
1446 }
1447 }
1448 self.dot_general(
1449 other,
1450 DotGeneralConfig {
1451 lhs_contracting_dims: vec![1],
1452 rhs_contracting_dims: vec![0],
1453 lhs_batch_dims: vec![],
1454 rhs_batch_dims: vec![],
1455 },
1456 )
1457 }
1458
1459 pub fn reduce_sum(&self, axes: &[usize]) -> Result<TracedTensor> {
1475 let (out_rank, out_shape_hint) =
1476 reduction_output_meta(self, axes, "TracedTensor::reduce_sum")?;
1477 apply_unary(
1478 StdTensorOp::ReduceSum {
1479 axes: axes.to_vec(),
1480 },
1481 self,
1482 out_rank,
1483 out_shape_hint,
1484 )
1485 }
1486
1487 pub fn reduce_max(&self, axes: &[usize]) -> Result<TracedTensor> {
1505 let (out_rank, out_shape_hint) =
1506 reduction_output_meta(self, axes, "TracedTensor::reduce_max")?;
1507 try_apply_unary(
1508 StdTensorOp::ReduceMax {
1509 axes: axes.to_vec(),
1510 },
1511 self,
1512 out_rank,
1513 out_shape_hint,
1514 "TracedTensor::reduce_max",
1515 )
1516 }
1517
1518 pub fn reduce_min(&self, axes: &[usize]) -> Result<TracedTensor> {
1536 let (out_rank, out_shape_hint) =
1537 reduction_output_meta(self, axes, "TracedTensor::reduce_min")?;
1538 try_apply_unary(
1539 StdTensorOp::ReduceMin {
1540 axes: axes.to_vec(),
1541 },
1542 self,
1543 out_rank,
1544 out_shape_hint,
1545 "TracedTensor::reduce_min",
1546 )
1547 }
1548
1549 pub fn reduce_prod(&self, axes: &[usize]) -> Result<TracedTensor> {
1564 let (out_rank, out_shape_hint) =
1565 reduction_output_meta(self, axes, "TracedTensor::reduce_prod")?;
1566 apply_unary(
1567 StdTensorOp::ReduceProd {
1568 axes: axes.to_vec(),
1569 },
1570 self,
1571 out_rank,
1572 out_shape_hint,
1573 )
1574 }
1575
1576 pub fn reshape(&self, shape: &[usize]) -> Result<TracedTensor> {
1593 validate_concrete_reshape_shape(self, shape)?;
1594 apply_unary_with_dtype(
1595 StdTensorOp::Reshape {
1596 to_shape: DimExpr::from_concrete(shape),
1597 },
1598 self,
1599 shape.len(),
1600 Some(shape.iter().copied().map(SymDim::from).collect()),
1601 self.dtype,
1602 )
1603 }
1604
1605 pub fn sym_size(&self, axis: usize) -> Result<SymDim> {
1636 validate_traced_axis(self, axis, "TracedTensor::sym_size")?;
1637 Ok(self
1638 .shape_hint
1639 .as_ref()
1640 .and_then(|shape| shape.get(axis))
1641 .filter(|dim| dim.constant_value().is_none())
1642 .cloned()
1643 .unwrap_or_else(|| SymDim::tensor_axis(self.id, axis)))
1644 }
1645
1646 pub fn axis_sym_dim(&self, axis: usize) -> Result<SymDim> {
1675 validate_traced_axis(self, axis, "TracedTensor::axis_sym_dim")?;
1676 match self.shape_hint.as_ref().and_then(|shape| shape.get(axis)) {
1677 Some(dim) => Ok(dim.clone()),
1678 None => Ok(SymDim::tensor_axis(self.id, axis)),
1679 }
1680 }
1681
1682 pub fn sym_shape(&self) -> Option<&[SymDim]> {
1704 self.shape_hint.as_deref()
1705 }
1706
1707 pub fn reshape_sym(&self, shape: &[SymDim]) -> Result<TracedTensor> {
1720 let tensor_map = [(self.id, 0usize)];
1721 let to_shape = shape
1722 .iter()
1723 .map(|dim| dim.to_dim_expr(&tensor_map).map_err(Error::Internal))
1724 .collect::<Result<Vec<_>>>()?;
1725 let out_shape_hint = Some(shape.to_vec());
1726 apply_unary(
1727 StdTensorOp::Reshape { to_shape },
1728 self,
1729 shape.len(),
1730 out_shape_hint,
1731 )
1732 }
1733
1734 pub fn broadcast_in_dim(&self, shape: &[usize], dims: &[usize]) -> Result<TracedTensor> {
1751 let out_shape_hint: Vec<SymDim> = shape.iter().copied().map(SymDim::from).collect();
1752 validate_broadcast_in_dim_args(
1753 self,
1754 &out_shape_hint,
1755 dims,
1756 "TracedTensor::broadcast_in_dim",
1757 )?;
1758 apply_unary(
1759 StdTensorOp::BroadcastInDim {
1760 shape: DimExpr::from_concrete(shape),
1761 dims: dims.to_vec(),
1762 },
1763 self,
1764 shape.len(),
1765 Some(out_shape_hint),
1766 )
1767 }
1768
1769 pub fn broadcast_in_dim_sym(
1803 &self,
1804 shape: &[SymDim],
1805 dims: &[usize],
1806 shape_refs: &[&TracedTensor],
1807 ) -> Result<TracedTensor> {
1808 validate_broadcast_in_dim_args(self, shape, dims, "TracedTensor::broadcast_in_dim_sym")?;
1809
1810 let mut dedup_refs: Vec<&TracedTensor> = Vec::with_capacity(shape_refs.len());
1814 let mut tensor_map: Vec<(u64, usize)> = vec![(self.id, 0)];
1815 for &t in shape_refs {
1816 if !tensor_map.iter().any(|(id, _)| *id == t.id) {
1817 let idx = tensor_map.len();
1818 tensor_map.push((t.id, idx));
1819 dedup_refs.push(t);
1820 }
1821 }
1822
1823 let to_shape: Vec<DimExpr> = shape
1824 .iter()
1825 .map(|dim| {
1826 dim.to_dim_expr(&tensor_map)
1827 .map_err(|err| Error::InvalidGraphBuild {
1828 op: "broadcast_in_dim_sym",
1829 message: format!(
1830 "unresolved symbolic dimension: {err}; \
1831 pass every referenced tensor via `shape_refs`"
1832 ),
1833 })
1834 })
1835 .collect::<Result<Vec<_>>>()?;
1836
1837 let max_used_idx = DimExpr::max_input_idx_all(&to_shape).unwrap_or(0);
1844 let used_refs: Vec<&TracedTensor> = dedup_refs.into_iter().take(max_used_idx).collect();
1845
1846 let out_shape_hint = Some(shape.to_vec());
1847 apply_unary_with_shape_refs(
1848 StdTensorOp::BroadcastInDim {
1849 shape: to_shape,
1850 dims: dims.to_vec(),
1851 },
1852 self,
1853 &used_refs,
1854 shape.len(),
1855 out_shape_hint,
1856 )
1857 }
1858
1859 pub fn slice(&self, config: SliceConfig) -> Result<TracedTensor> {
1861 let op = StdTensorOp::Slice(config);
1862 let (out_rank, out_shape_hint) =
1863 infer_traced_single_output_shape("TracedTensor::slice", &op, &[self])?;
1864 apply_unary(op, self, out_rank, out_shape_hint)
1865 }
1866
1867 pub fn pad(&self, config: PadConfig) -> Result<TracedTensor> {
1869 let op = StdTensorOp::Pad(config);
1870 let (out_rank, out_shape_hint) =
1871 infer_traced_single_output_shape("TracedTensor::pad", &op, &[self])?;
1872 apply_unary(op, self, out_rank, out_shape_hint)
1873 }
1874
1875 pub fn reverse(&self, axes: &[usize]) -> Result<TracedTensor> {
1877 validate_traced_axes(self.rank, axes, "TracedTensor::reverse")?;
1878 apply_unary(
1879 StdTensorOp::Reverse {
1880 axes: axes.to_vec(),
1881 },
1882 self,
1883 self.rank,
1884 self.shape_hint.clone(),
1885 )
1886 }
1887
1888 pub fn gather(&self, indices: &TracedTensor, config: GatherConfig) -> Result<TracedTensor> {
1890 let op = StdTensorOp::Gather(config);
1891 let (out_rank, out_shape_hint) =
1892 infer_traced_single_output_shape("TracedTensor::gather", &op, &[self, indices])?;
1893 apply_binary_preserve_input_dtypes(op, self, indices, out_rank, out_shape_hint, self.dtype)
1894 }
1895
1896 pub fn scatter(
1898 &self,
1899 indices: &TracedTensor,
1900 updates: &TracedTensor,
1901 config: ScatterConfig,
1902 ) -> Result<TracedTensor> {
1903 let op = StdTensorOp::Scatter(config);
1904 let (out_rank, out_shape_hint) = infer_traced_single_output_shape(
1905 "TracedTensor::scatter",
1906 &op,
1907 &[self, indices, updates],
1908 )?;
1909 let out_dtype = crate::shape_infer::promote_dtype(self.dtype, updates.dtype);
1910 let operand = if self.dtype != out_dtype {
1911 self.cast(out_dtype)?
1912 } else {
1913 self.clone()
1914 };
1915 let updates = if updates.dtype != out_dtype {
1916 updates.cast(out_dtype)?
1917 } else {
1918 updates.clone()
1919 };
1920 apply_ternary_with_output_dtype(
1921 op,
1922 &operand,
1923 indices,
1924 &updates,
1925 out_rank,
1926 out_shape_hint,
1927 out_dtype,
1928 )
1929 }
1930
1931 pub fn dynamic_slice(&self, starts: &TracedTensor, sizes: &[usize]) -> Result<TracedTensor> {
1933 let op = StdTensorOp::DynamicSlice {
1934 slice_sizes: sizes.to_vec(),
1935 };
1936 let (out_rank, out_shape_hint) =
1937 infer_traced_single_output_shape("TracedTensor::dynamic_slice", &op, &[self, starts])?;
1938 apply_binary_preserve_input_dtypes(op, self, starts, out_rank, out_shape_hint, self.dtype)
1939 }
1940
1941 pub fn tril(&self, k: i64) -> Result<TracedTensor> {
1957 apply_unary(
1958 StdTensorOp::Tril { k },
1959 self,
1960 self.rank,
1961 self.shape_hint.clone(),
1962 )
1963 }
1964
1965 pub fn triu(&self, k: i64) -> Result<TracedTensor> {
1981 apply_unary(
1982 StdTensorOp::Triu { k },
1983 self,
1984 self.rank,
1985 self.shape_hint.clone(),
1986 )
1987 }
1988
1989 pub fn transpose(&self, perm: &[usize]) -> Result<TracedTensor> {
2005 validate_traced_perm(self.rank, perm, "TracedTensor::transpose")?;
2006 let out_shape_hint = self
2007 .shape_hint
2008 .as_ref()
2009 .map(|shape| perm.iter().map(|&p| shape[p].clone()).collect());
2010 apply_unary(
2011 StdTensorOp::Transpose {
2012 perm: perm.to_vec(),
2013 },
2014 self,
2015 self.rank,
2016 out_shape_hint,
2017 )
2018 }
2019
2020 pub fn extract_diag(&self, axis_a: usize, axis_b: usize) -> Result<TracedTensor> {
2036 validate_traced_axis(self, axis_a, "TracedTensor::extract_diag")?;
2037 validate_traced_axis(self, axis_b, "TracedTensor::extract_diag")?;
2038 if axis_a == axis_b {
2039 return Err(Error::InvalidGraphBuild {
2040 op: "TracedTensor::extract_diag",
2041 message: "diagonal axes must be distinct".into(),
2042 });
2043 }
2044 let op = StdTensorOp::ExtractDiag { axis_a, axis_b };
2045 let (out_rank, out_shape_hint) =
2046 infer_traced_single_output_shape("TracedTensor::extract_diag", &op, &[self])?;
2047 apply_unary(op, self, out_rank, out_shape_hint)
2048 }
2049
2050 pub fn embed_diag(&self, axis_a: usize, axis_b: usize) -> Result<TracedTensor> {
2066 validate_traced_axis(self, axis_a, "TracedTensor::embed_diag")?;
2067 validate_traced_insert_axis(self.rank, axis_b, "TracedTensor::embed_diag")?;
2068 let out_shape_hint = self.shape_hint.as_ref().map(|shape| {
2069 let mut out_shape = shape.clone();
2070 out_shape.insert(axis_b, shape[axis_a].clone());
2071 out_shape
2072 });
2073 apply_unary(
2074 StdTensorOp::EmbedDiag { axis_a, axis_b },
2075 self,
2076 self.rank + 1,
2077 out_shape_hint,
2078 )
2079 }
2080
2081 pub fn shape_of(&self, axis: usize) -> Result<TracedTensor> {
2104 validate_traced_axis(self, axis, "TracedTensor::shape_of")?;
2105 apply_unary_with_dtype(
2106 StdTensorOp::ShapeOf { axis },
2107 self,
2108 0,
2109 Some(vec![]),
2110 DType::F64,
2111 )
2112 }
2113
2114 pub fn dynamic_truncate(&self, size: &TracedTensor, axis: usize) -> Result<TracedTensor> {
2140 validate_traced_axis(self, axis, "TracedTensor::dynamic_truncate")?;
2141 if size.rank != 0 {
2142 return Err(Error::InvalidGraphBuild {
2143 op: "TracedTensor::dynamic_truncate",
2144 message: format!("size must be a scalar tensor, got rank {}", size.rank),
2145 });
2146 }
2147 apply_binary_preserve_input_dtypes(
2148 StdTensorOp::DynamicTruncate { axis },
2149 self,
2150 size,
2151 self.rank,
2152 None,
2153 self.dtype,
2154 )
2155 }
2156
2157 pub fn pad_to_match(&self, reference: &TracedTensor, axis: usize) -> Result<TracedTensor> {
2181 validate_traced_axis(self, axis, "TracedTensor::pad_to_match")?;
2182 validate_traced_axis(reference, axis, "TracedTensor::pad_to_match")?;
2183 let op = StdTensorOp::PadToMatch { axis };
2184 let (out_rank, out_shape_hint) = infer_traced_single_output_shape(
2185 "TracedTensor::pad_to_match",
2186 &op,
2187 &[self, reference],
2188 )?;
2189 apply_binary_preserve_input_dtypes(
2190 op,
2191 self,
2192 reference,
2193 out_rank,
2194 out_shape_hint,
2195 self.dtype,
2196 )
2197 }
2198}
2199
2200pub(crate) fn apply_unary(
2201 op: StdTensorOp,
2202 input: &TracedTensor,
2203 out_rank: usize,
2204 out_shape_hint: Option<Vec<SymDim>>,
2205) -> Result<TracedTensor> {
2206 let out_dtype = inferred_output_dtype(&op, &[input.dtype], "apply_unary");
2207 apply_unary_with_dtype(op, input, out_rank, out_shape_hint, out_dtype)
2208}
2209
2210fn try_apply_unary(
2211 op: StdTensorOp,
2212 input: &TracedTensor,
2213 out_rank: usize,
2214 out_shape_hint: Option<Vec<SymDim>>,
2215 context: &'static str,
2216) -> Result<TracedTensor> {
2217 let out_dtype = try_inferred_output_dtype(&op, &[input.dtype], context)?;
2218 apply_unary_with_dtype(op, input, out_rank, out_shape_hint, out_dtype)
2219}
2220
2221pub(crate) fn apply_unary_with_dtype(
2222 op: StdTensorOp,
2223 input: &TracedTensor,
2224 out_rank: usize,
2225 out_shape_hint: Option<Vec<SymDim>>,
2226 out_dtype: DType,
2227) -> Result<TracedTensor> {
2228 let mut builder = GraphBuilder::new();
2229 builder.add_parent(input.graph.clone());
2230 let input_ref = ValueRef::External(input.graph.values()[input.val].key.clone());
2231 let outputs = builder.add_operation(op, vec![input_ref], OperationRole::Primary);
2232 builder.set_outputs(outputs.clone());
2233 let graph = Arc::new(builder.build());
2234 let metadata_scope =
2235 register_single_output_metadata(graph.as_ref(), outputs[0], out_dtype, &out_shape_hint)?;
2236
2237 Ok(TracedTensor {
2238 id: next_traced_id(),
2239 rank: out_rank,
2240 dtype: out_dtype,
2241 graph,
2242 val: outputs[0],
2243 data: None,
2244 shape_hint: out_shape_hint,
2245 inputs_map: input.inputs_map.clone(),
2246 extra_roots: input.extra_roots.clone(),
2247 checkpoint_chain: input.checkpoint_chain.clone(),
2248 metadata_scopes: MetadataScopeChain::with_new(metadata_scope, [&input.metadata_scopes]),
2249 })
2250}
2251
2252pub(crate) fn apply_unary_with_shape_refs(
2261 op: StdTensorOp,
2262 input: &TracedTensor,
2263 shape_refs: &[&TracedTensor],
2264 out_rank: usize,
2265 out_shape_hint: Option<Vec<SymDim>>,
2266) -> Result<TracedTensor> {
2267 let mut builder = GraphBuilder::new();
2268 builder.add_parent(input.graph.clone());
2269 for t in shape_refs {
2270 builder.add_parent(t.graph.clone());
2271 }
2272 let mut op_inputs: Vec<ValueRef<StdTensorOp>> = Vec::with_capacity(1 + shape_refs.len());
2273 op_inputs.push(ValueRef::External(
2274 input.graph.values()[input.val].key.clone(),
2275 ));
2276 for t in shape_refs {
2277 op_inputs.push(ValueRef::External(t.graph.values()[t.val].key.clone()));
2278 }
2279 let outputs = builder.add_operation(op, op_inputs, OperationRole::Primary);
2280 builder.set_outputs(outputs.clone());
2281 let graph = Arc::new(builder.build());
2282 let metadata_scope =
2283 register_single_output_metadata(graph.as_ref(), outputs[0], input.dtype, &out_shape_hint)?;
2284
2285 let inputs_map =
2286 merge_traced_inputs_map(std::iter::once(input).chain(shape_refs.iter().copied()));
2287
2288 let mut extra_roots = input.extra_roots.clone();
2289 for t in shape_refs {
2290 extra_roots.extend(t.extra_roots.iter().cloned());
2291 }
2292
2293 let mut checkpoint_chain = input.checkpoint_chain.clone();
2294 for t in shape_refs {
2295 checkpoint_chain =
2296 CheckpointNode::merge_chains(checkpoint_chain, t.checkpoint_chain.clone());
2297 }
2298
2299 Ok(TracedTensor {
2300 id: next_traced_id(),
2301 rank: out_rank,
2302 dtype: input.dtype,
2303 graph,
2304 val: outputs[0],
2305 data: None,
2306 shape_hint: out_shape_hint,
2307 inputs_map,
2308 extra_roots,
2309 checkpoint_chain,
2310 metadata_scopes: MetadataScopeChain::with_new(
2311 metadata_scope,
2312 std::iter::once(&input.metadata_scopes)
2313 .chain(shape_refs.iter().map(|tensor| &tensor.metadata_scopes)),
2314 ),
2315 })
2316}
2317
2318pub(crate) fn apply_nullary(
2319 op: StdTensorOp,
2320 rank: usize,
2321 dtype: DType,
2322 shape_hint: Option<Vec<SymDim>>,
2323) -> Result<TracedTensor> {
2324 let mut builder = GraphBuilder::new();
2325 let outputs = builder.add_operation(op, vec![], OperationRole::Primary);
2326 builder.set_outputs(outputs.clone());
2327 let graph = Arc::new(builder.build());
2328 let metadata_scope =
2329 register_single_output_metadata(graph.as_ref(), outputs[0], dtype, &shape_hint)?;
2330
2331 Ok(TracedTensor {
2332 id: next_traced_id(),
2333 rank,
2334 dtype,
2335 graph,
2336 val: outputs[0],
2337 data: None,
2338 shape_hint,
2339 inputs_map: Arc::new(HashMap::new()),
2340 extra_roots: Vec::new(),
2341 checkpoint_chain: None,
2342 metadata_scopes: MetadataScopeChain::from_scope(metadata_scope),
2343 })
2344}
2345
2346pub(crate) fn apply_binary(
2347 op: StdTensorOp,
2348 lhs: &TracedTensor,
2349 rhs: &TracedTensor,
2350 out_rank: usize,
2351 out_shape_hint: Option<Vec<SymDim>>,
2352) -> Result<TracedTensor> {
2353 let input_dtype = crate::shape_infer::promote_dtype_for_binary_op(&op, lhs.dtype, rhs.dtype);
2354 let out_dtype = inferred_output_dtype(&op, &[lhs.dtype, rhs.dtype], "apply_binary");
2355
2356 let lhs = if lhs.dtype != input_dtype {
2358 lhs.cast(input_dtype)?
2359 } else {
2360 lhs.clone()
2361 };
2362 let rhs = if rhs.dtype != input_dtype {
2363 rhs.cast(input_dtype)?
2364 } else {
2365 rhs.clone()
2366 };
2367
2368 apply_binary_with_output_dtype(op, &lhs, &rhs, out_rank, out_shape_hint, out_dtype)
2369}
2370
2371fn try_apply_binary(
2372 op: StdTensorOp,
2373 lhs: &TracedTensor,
2374 rhs: &TracedTensor,
2375 out_rank: usize,
2376 out_shape_hint: Option<Vec<SymDim>>,
2377 context: &'static str,
2378) -> Result<TracedTensor> {
2379 let input_dtype = crate::shape_infer::promote_dtype_for_binary_op(&op, lhs.dtype, rhs.dtype);
2380 let out_dtype = try_inferred_output_dtype(&op, &[lhs.dtype, rhs.dtype], context)?;
2381
2382 let lhs = if lhs.dtype != input_dtype {
2383 lhs.cast(input_dtype)?
2384 } else {
2385 lhs.clone()
2386 };
2387 let rhs = if rhs.dtype != input_dtype {
2388 rhs.cast(input_dtype)?
2389 } else {
2390 rhs.clone()
2391 };
2392
2393 apply_binary_with_output_dtype(op, &lhs, &rhs, out_rank, out_shape_hint, out_dtype)
2394}
2395
2396pub(crate) fn apply_binary_preserve_input_dtypes(
2397 op: StdTensorOp,
2398 lhs: &TracedTensor,
2399 rhs: &TracedTensor,
2400 out_rank: usize,
2401 out_shape_hint: Option<Vec<SymDim>>,
2402 out_dtype: DType,
2403) -> Result<TracedTensor> {
2404 apply_binary_with_output_dtype(op, lhs, rhs, out_rank, out_shape_hint, out_dtype)
2405}
2406
2407pub(crate) fn apply_broadcast_binary_op(
2408 op: StdTensorOp,
2409 lhs: &TracedTensor,
2410 rhs: &TracedTensor,
2411) -> Result<TracedTensor> {
2412 let (lhs, rhs) = broadcast_binary(lhs, rhs)?;
2413 try_apply_binary(
2414 op,
2415 &lhs,
2416 &rhs,
2417 lhs.rank,
2418 lhs.shape_hint.clone(),
2419 "broadcast_binary",
2420 )
2421}
2422
2423pub(crate) fn apply_broadcast_ternary_op(
2424 op: StdTensorOp,
2425 first: &TracedTensor,
2426 second: &TracedTensor,
2427 third: &TracedTensor,
2428) -> Result<TracedTensor> {
2429 let (first, second, third) = broadcast_ternary(first, second, third)?;
2430 try_apply_ternary(
2431 op,
2432 &first,
2433 &second,
2434 &third,
2435 first.rank,
2436 first.shape_hint.clone(),
2437 "broadcast_ternary",
2438 )
2439}
2440
2441fn try_apply_ternary(
2442 op: StdTensorOp,
2443 first: &TracedTensor,
2444 second: &TracedTensor,
2445 third: &TracedTensor,
2446 out_rank: usize,
2447 out_shape_hint: Option<Vec<SymDim>>,
2448 context: &'static str,
2449) -> Result<TracedTensor> {
2450 let out_dtype =
2451 try_inferred_output_dtype(&op, &[first.dtype, second.dtype, third.dtype], context)?;
2452 let (first, second, third) = match op {
2453 StdTensorOp::Select => {
2454 let value_dtype = crate::shape_infer::promote_dtype(second.dtype, third.dtype);
2455 let second = if second.dtype != value_dtype {
2456 second.cast(value_dtype)?
2457 } else {
2458 second.clone()
2459 };
2460 let third = if third.dtype != value_dtype {
2461 third.cast(value_dtype)?
2462 } else {
2463 third.clone()
2464 };
2465 (first.clone(), second, third)
2466 }
2467 _ => {
2468 let input_dtype =
2469 crate::shape_infer::promote_dtypes([first.dtype, second.dtype, third.dtype]);
2470 let first = if first.dtype != input_dtype {
2471 first.cast(input_dtype)?
2472 } else {
2473 first.clone()
2474 };
2475 let second = if second.dtype != input_dtype {
2476 second.cast(input_dtype)?
2477 } else {
2478 second.clone()
2479 };
2480 let third = if third.dtype != input_dtype {
2481 third.cast(input_dtype)?
2482 } else {
2483 third.clone()
2484 };
2485 (first, second, third)
2486 }
2487 };
2488 apply_ternary_with_output_dtype(
2489 op,
2490 &first,
2491 &second,
2492 &third,
2493 out_rank,
2494 out_shape_hint,
2495 out_dtype,
2496 )
2497}
2498
2499fn apply_binary_with_output_dtype(
2500 op: StdTensorOp,
2501 lhs: &TracedTensor,
2502 rhs: &TracedTensor,
2503 out_rank: usize,
2504 out_shape_hint: Option<Vec<SymDim>>,
2505 out_dtype: DType,
2506) -> Result<TracedTensor> {
2507 let lhs_ref = ValueRef::External(lhs.graph.values()[lhs.val].key.clone());
2508 let rhs_ref = ValueRef::External(rhs.graph.values()[rhs.val].key.clone());
2509
2510 let mut builder = GraphBuilder::new();
2511 builder.add_parent(lhs.graph.clone());
2512 builder.add_parent(rhs.graph.clone());
2513 let outputs = builder.add_operation(op, vec![lhs_ref, rhs_ref], OperationRole::Primary);
2514 builder.set_outputs(outputs.clone());
2515 let graph = Arc::new(builder.build());
2516 let metadata_scope =
2517 register_single_output_metadata(graph.as_ref(), outputs[0], out_dtype, &out_shape_hint)?;
2518
2519 let mut extra_roots = lhs.extra_roots.clone();
2520 extra_roots.extend(rhs.extra_roots.iter().cloned());
2521
2522 Ok(TracedTensor {
2523 id: next_traced_id(),
2524 rank: out_rank,
2525 dtype: out_dtype,
2526 graph,
2527 val: outputs[0],
2528 data: None,
2529 shape_hint: out_shape_hint,
2530 inputs_map: merge_traced_inputs_map([lhs, rhs]),
2531 extra_roots,
2532 checkpoint_chain: CheckpointNode::merge_chains(
2533 lhs.checkpoint_chain.clone(),
2534 rhs.checkpoint_chain.clone(),
2535 ),
2536 metadata_scopes: MetadataScopeChain::with_new(
2537 metadata_scope,
2538 [&lhs.metadata_scopes, &rhs.metadata_scopes],
2539 ),
2540 })
2541}
2542
2543fn apply_ternary_with_output_dtype(
2544 op: StdTensorOp,
2545 first: &TracedTensor,
2546 second: &TracedTensor,
2547 third: &TracedTensor,
2548 out_rank: usize,
2549 out_shape_hint: Option<Vec<SymDim>>,
2550 out_dtype: DType,
2551) -> Result<TracedTensor> {
2552 let first_ref = ValueRef::External(first.graph.values()[first.val].key.clone());
2553 let second_ref = ValueRef::External(second.graph.values()[second.val].key.clone());
2554 let third_ref = ValueRef::External(third.graph.values()[third.val].key.clone());
2555
2556 let mut builder = GraphBuilder::new();
2557 builder.add_parent(first.graph.clone());
2558 builder.add_parent(second.graph.clone());
2559 builder.add_parent(third.graph.clone());
2560 let outputs = builder.add_operation(
2561 op,
2562 vec![first_ref, second_ref, third_ref],
2563 OperationRole::Primary,
2564 );
2565 builder.set_outputs(outputs.clone());
2566 let graph = Arc::new(builder.build());
2567 let metadata_scope =
2568 register_single_output_metadata(graph.as_ref(), outputs[0], out_dtype, &out_shape_hint)?;
2569
2570 let mut extra_roots = first.extra_roots.clone();
2571 extra_roots.extend(second.extra_roots.iter().cloned());
2572 extra_roots.extend(third.extra_roots.iter().cloned());
2573
2574 let checkpoint_chain = CheckpointNode::merge_chains(
2575 CheckpointNode::merge_chains(
2576 first.checkpoint_chain.clone(),
2577 second.checkpoint_chain.clone(),
2578 ),
2579 third.checkpoint_chain.clone(),
2580 );
2581
2582 Ok(TracedTensor {
2583 id: next_traced_id(),
2584 rank: out_rank,
2585 dtype: out_dtype,
2586 graph,
2587 val: outputs[0],
2588 data: None,
2589 shape_hint: out_shape_hint,
2590 inputs_map: merge_traced_inputs_map([first, second, third]),
2591 extra_roots,
2592 checkpoint_chain,
2593 metadata_scopes: MetadataScopeChain::with_new(
2594 metadata_scope,
2595 [
2596 &first.metadata_scopes,
2597 &second.metadata_scopes,
2598 &third.metadata_scopes,
2599 ],
2600 ),
2601 })
2602}
2603
2604fn register_single_output_metadata(
2605 graph: &Graph<StdTensorOp>,
2606 output: LocalValueId,
2607 dtype: DType,
2608 shape_hint: &Option<Vec<SymDim>>,
2609) -> Result<GlobalMetadataScope> {
2610 if let Some(shape) = shape_hint {
2611 register_metadata_or_internal(register_scoped_value_metadata(
2614 graph.values()[output].key.clone(),
2615 tensor_meta(dtype, shape.clone()),
2616 ))
2617 } else {
2618 register_metadata_or_internal(register_scoped_graph_metadata(graph, std::iter::empty()))
2621 }
2622}
2623
2624impl TracedTensor {
2625 pub(crate) fn resolve_roots(&self) -> Vec<Arc<Graph<StdTensorOp>>> {
2626 let mut roots = Vec::with_capacity(1 + self.extra_roots.len());
2627 roots.push(self.graph.clone());
2628 roots.extend(self.extra_roots.iter().cloned());
2629 roots
2630 }
2631}
2632
2633#[cfg(test)]
2634mod tests;