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, metadata_scopes_for_scope, metadata_scopes_with_new, push_metadata_scope,
25 register_scoped_graph_metadata, register_scoped_value_metadata, symbolic_input_meta,
26 tensor_meta,
27};
28use crate::scalar_semantics::{bool_from_real_for_op, round_real_to_i32_for_op, round_real_to_i64};
29
30static NEXT_INPUT_ID: AtomicU64 = AtomicU64::new(0);
31static NEXT_TRACED_ID: AtomicU64 = AtomicU64::new(0);
32
33pub type TracedTensorId = u64;
34
35pub(crate) fn next_input_key() -> TensorInputKey {
36 TensorInputKey::User {
37 id: NEXT_INPUT_ID.fetch_add(1, Ordering::Relaxed),
38 }
39}
40
41pub(crate) fn next_traced_id() -> TracedTensorId {
42 NEXT_TRACED_ID.fetch_add(1, Ordering::Relaxed)
43}
44
45#[derive(Clone)]
46pub struct TracedTensor {
47 pub id: TracedTensorId,
48 pub rank: usize,
49 pub dtype: DType,
50 pub(crate) graph: Arc<Graph<StdTensorOp>>,
51 pub val: LocalValueId,
52 pub(crate) data: Option<Arc<Tensor>>,
53 pub(crate) shape_hint: Option<Vec<SymDim>>,
54 pub(crate) inputs_map: Arc<HashMap<TensorInputKey, Arc<Tensor>>>,
55 pub(crate) extra_roots: Vec<Arc<Graph<StdTensorOp>>>,
56 pub(crate) checkpoint_chain: Option<Arc<CheckpointNode>>,
57 pub(crate) metadata_scopes: Vec<Arc<GlobalMetadataScope>>,
58}
59
60impl fmt::Debug for TracedTensor {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 f.debug_struct("TracedTensor")
63 .field("id", &self.id)
64 .field("rank", &self.rank)
65 .field("dtype", &self.dtype)
66 .field("val", &self.val)
67 .field("shape_hint", &self.shape_hint)
68 .field("has_data", &self.data.is_some())
69 .finish_non_exhaustive()
70 }
71}
72
73pub(crate) fn try_concrete_shape(tensor: &TracedTensor) -> Option<Vec<usize>> {
74 tensor
75 .shape_hint
76 .as_ref()?
77 .iter()
78 .map(SymDim::constant_value)
79 .collect()
80}
81
82pub(crate) fn concrete_shape(tensor: &TracedTensor) -> Result<Vec<usize>> {
83 tensor
84 .shape_hint
85 .as_ref()
86 .ok_or_else(|| Error::InvalidGraphBuild {
87 op: "TracedTensor::concrete_shape",
88 message: format!("missing shape hint for traced tensor {}", tensor.id),
89 })?
90 .iter()
91 .map(|dim| {
92 dim.constant_value()
93 .ok_or_else(|| Error::InvalidGraphBuild {
94 op: "TracedTensor::concrete_shape",
95 message: format!("symbolic dimension in shape hint for tensor {}", tensor.id),
96 })
97 })
98 .collect()
99}
100
101pub(crate) fn broadcast_to(tensor: &TracedTensor, target_shape: &[usize]) -> Result<TracedTensor> {
106 let tensor_shape = concrete_shape(tensor)?;
107 if tensor_shape == target_shape {
108 return Ok(tensor.clone());
109 }
110
111 let plan = broadcast_input_plan(&tensor_shape, target_shape).map_err(|err| {
112 Error::InvalidGraphBuild {
113 op: "broadcast_to",
114 message: err.to_string(),
115 }
116 })?;
117
118 let source = if plan.source_shape == tensor_shape {
119 tensor.clone()
120 } else {
121 tensor.reshape(&plan.source_shape)
122 };
123 source.broadcast_in_dim(target_shape, &plan.dims)
124}
125
126pub(crate) fn broadcast_binary(
128 a: &TracedTensor,
129 b: &TracedTensor,
130) -> Result<(TracedTensor, TracedTensor)> {
131 if a.shape_hint == b.shape_hint && a.rank == b.rank {
132 return Ok((a.clone(), b.clone()));
133 }
134 if (try_concrete_shape(a).is_none() || try_concrete_shape(b).is_none()) && a.rank == b.rank {
135 return Ok((a.clone(), b.clone()));
136 }
137 let a_shape = concrete_shape(a)?;
138 let b_shape = concrete_shape(b)?;
139 let target = broadcast_shape(&a_shape, &b_shape).map_err(|err| Error::InvalidGraphBuild {
140 op: "broadcast_binary",
141 message: err.to_string(),
142 })?;
143 Ok((broadcast_to(a, &target)?, broadcast_to(b, &target)?))
144}
145
146pub(crate) fn broadcast_ternary(
147 a: &TracedTensor,
148 b: &TracedTensor,
149 c: &TracedTensor,
150) -> Result<(TracedTensor, TracedTensor, TracedTensor)> {
151 let a_shape = concrete_shape(a)?;
152 let b_shape = concrete_shape(b)?;
153 let c_shape = concrete_shape(c)?;
154 let target = broadcast_shapes([a_shape.as_slice(), b_shape.as_slice(), c_shape.as_slice()])
155 .map_err(|err| Error::InvalidGraphBuild {
156 op: "broadcast_ternary",
157 message: err.to_string(),
158 })?;
159 Ok((
160 broadcast_to(a, &target)?,
161 broadcast_to(b, &target)?,
162 broadcast_to(c, &target)?,
163 ))
164}
165
166fn scale_with_constant(input: &TracedTensor, op: StdTensorOp) -> TracedTensor {
167 let scalar = apply_nullary(op, 0, input.dtype, Some(vec![]));
168 apply_binary(
169 StdTensorOp::Mul,
170 input,
171 &scalar,
172 input.rank,
173 input.shape_hint.clone(),
174 )
175}
176
177fn inferred_output_dtype(op: &StdTensorOp, inputs: &[DType], context: &'static str) -> DType {
178 match crate::shape_infer::infer_output_dtype(op, inputs) {
179 Ok(dtype) => dtype,
180 Err(err) => {
181 panic!("{context}: built-in traced dtype inference failed for {op:?}: {err}");
182 }
183 }
184}
185
186fn traced_input_shape_exprs(input_idx: usize, tensor: &TracedTensor) -> Vec<DimExpr> {
187 match tensor.shape_hint.as_ref() {
188 Some(shape) => shape
189 .iter()
190 .enumerate()
191 .map(|(axis, dim)| {
192 dim.constant_value()
193 .map_or(DimExpr::InputDim { input_idx, axis }, DimExpr::Const)
194 })
195 .collect(),
196 None => (0..tensor.rank)
197 .map(|axis| DimExpr::InputDim { input_idx, axis })
198 .collect(),
199 }
200}
201
202fn traced_input_sym_shape(tensor: &TracedTensor) -> Vec<SymDim> {
203 tensor.shape_hint.clone().unwrap_or_else(|| {
204 (0..tensor.rank)
205 .map(|axis| SymDim::tensor_axis(tensor.id, axis))
206 .collect()
207 })
208}
209
210pub(crate) fn infer_traced_single_output_shape(
211 op_name: &'static str,
212 op: &StdTensorOp,
213 inputs: &[&TracedTensor],
214) -> Result<(usize, Option<Vec<SymDim>>)> {
215 let input_shape_exprs: Vec<Vec<DimExpr>> = inputs
216 .iter()
217 .enumerate()
218 .map(|(input_idx, tensor)| traced_input_shape_exprs(input_idx, tensor))
219 .collect();
220 let input_shape_refs: Vec<&[DimExpr]> = input_shape_exprs.iter().map(Vec::as_slice).collect();
221 let output_shapes =
222 crate::shape_infer::infer_output_shapes(op, &input_shape_refs).map_err(|err| {
223 Error::InvalidGraphBuild {
224 op: op_name,
225 message: err.to_string(),
226 }
227 })?;
228 let output_shape = output_shapes
229 .first()
230 .ok_or_else(|| Error::InvalidGraphBuild {
231 op: op_name,
232 message: "shape inference returned no outputs".into(),
233 })?;
234 if output_shapes.len() != 1 {
235 return Err(Error::InvalidGraphBuild {
236 op: op_name,
237 message: format!(
238 "expected single-output shape inference, got {} outputs",
239 output_shapes.len()
240 ),
241 });
242 }
243
244 let input_sym_shapes: Vec<Vec<SymDim>> = inputs
245 .iter()
246 .map(|tensor| traced_input_sym_shape(tensor))
247 .collect();
248 let input_sym_refs: Vec<&[SymDim]> = input_sym_shapes.iter().map(Vec::as_slice).collect();
249 let out_shape_hint = output_shape
250 .iter()
251 .map(|dim| SymDim::from_dim_expr(dim, &input_sym_refs))
252 .collect();
253 Ok((output_shape.len(), Some(out_shape_hint)))
254}
255
256fn register_metadata_or_internal(
257 result: std::result::Result<GlobalMetadataScope, impl std::fmt::Display>,
258) -> Result<GlobalMetadataScope> {
259 result.map_err(|err| Error::Internal(format!("metadata registration failed: {err}")))
260}
261
262fn reduction_output_meta(
263 tensor: &TracedTensor,
264 axes: &[usize],
265 op: &'static str,
266) -> Result<(usize, Option<Vec<SymDim>>)> {
267 let mut seen = vec![false; tensor.rank];
268 for &axis in axes {
269 if axis >= tensor.rank {
270 return Err(Error::InvalidGraphBuild {
271 op,
272 message: format!("axis {axis} out of bounds for rank {}", tensor.rank),
273 });
274 }
275 if seen[axis] {
276 return Err(Error::InvalidGraphBuild {
277 op,
278 message: format!("duplicate reduction axis {axis}"),
279 });
280 }
281 seen[axis] = true;
282 }
283
284 let out_shape_hint = tensor.shape_hint.as_ref().map(|shape| {
285 (0..shape.len())
286 .filter(|d| !axes.contains(d))
287 .map(|d| shape[d].clone())
288 .collect()
289 });
290 Ok((tensor.rank - axes.len(), out_shape_hint))
291}
292
293fn validate_traced_axis(tensor: &TracedTensor, axis: usize, op: &'static str) -> Result<()> {
294 if axis >= tensor.rank {
295 return Err(Error::InvalidGraphBuild {
296 op,
297 message: format!("axis {axis} out of bounds for rank {}", tensor.rank),
298 });
299 }
300 Ok(())
301}
302
303fn validate_traced_axes(rank: usize, axes: &[usize], op: &'static str) -> Result<()> {
304 let mut seen = vec![false; rank];
305 for &axis in axes {
306 if axis >= rank {
307 return Err(Error::InvalidGraphBuild {
308 op,
309 message: format!("axis {axis} out of bounds for rank {rank}"),
310 });
311 }
312 if seen[axis] {
313 return Err(Error::InvalidGraphBuild {
314 op,
315 message: format!("duplicate axis {axis}"),
316 });
317 }
318 seen[axis] = true;
319 }
320 Ok(())
321}
322
323fn validate_traced_insert_axis(rank: usize, axis: usize, op: &'static str) -> Result<()> {
324 if axis > rank {
325 return Err(Error::InvalidGraphBuild {
326 op,
327 message: format!("axis {axis} out of bounds for rank {rank} insertion"),
328 });
329 }
330 Ok(())
331}
332
333fn validate_traced_perm(rank: usize, perm: &[usize], op: &'static str) -> Result<()> {
334 if perm.len() != rank {
335 return Err(Error::InvalidGraphBuild {
336 op,
337 message: format!(
338 "permutation length {} does not match rank {rank}",
339 perm.len()
340 ),
341 });
342 }
343 let mut seen = vec![false; rank];
344 for &axis in perm {
345 if axis >= rank {
346 return Err(Error::InvalidGraphBuild {
347 op,
348 message: format!("permutation axis {axis} out of bounds for rank {rank}"),
349 });
350 }
351 if seen[axis] {
352 return Err(Error::InvalidGraphBuild {
353 op,
354 message: format!("duplicate permutation axis {axis}"),
355 });
356 }
357 seen[axis] = true;
358 }
359 Ok(())
360}
361
362fn validate_broadcast_in_dim_args(
363 input: &TracedTensor,
364 output_shape: &[SymDim],
365 dims: &[usize],
366 op: &'static str,
367) -> Result<()> {
368 if dims.len() != input.rank {
369 return Err(Error::InvalidGraphBuild {
370 op,
371 message: format!(
372 "dims length {} must match input rank {}",
373 dims.len(),
374 input.rank
375 ),
376 });
377 }
378
379 let mut seen = vec![false; output_shape.len()];
380 for &dim in dims {
381 if dim >= output_shape.len() {
382 return Err(Error::InvalidGraphBuild {
383 op,
384 message: format!(
385 "broadcast dim {dim} out of bounds for output rank {}",
386 output_shape.len()
387 ),
388 });
389 }
390 if seen[dim] {
391 return Err(Error::InvalidGraphBuild {
392 op,
393 message: format!("duplicate broadcast dim {dim}"),
394 });
395 }
396 seen[dim] = true;
397 }
398
399 if let Some(input_shape) = input.shape_hint.as_ref() {
400 for (input_axis, &output_axis) in dims.iter().enumerate() {
401 let input_dim = &input_shape[input_axis];
402 let output_dim = &output_shape[output_axis];
403 if input_dim != output_dim && input_dim.constant_value() != Some(1) {
404 return Err(Error::InvalidGraphBuild {
405 op,
406 message: format!(
407 "input axis {input_axis} with dim {input_dim:?} cannot broadcast to \
408 output axis {output_axis} with dim {output_dim:?}"
409 ),
410 });
411 }
412 }
413 }
414
415 Ok(())
416}
417
418impl std::ops::Add for &TracedTensor {
419 type Output = Result<TracedTensor>;
420
421 fn add(self, rhs: &TracedTensor) -> Result<TracedTensor> {
422 TracedTensor::add(self, rhs)
423 }
424}
425
426impl std::ops::Sub for &TracedTensor {
427 type Output = Result<TracedTensor>;
428
429 fn sub(self, rhs: &TracedTensor) -> Result<TracedTensor> {
430 TracedTensor::sub(self, rhs)
431 }
432}
433
434impl std::ops::Mul for &TracedTensor {
435 type Output = Result<TracedTensor>;
436
437 fn mul(self, rhs: &TracedTensor) -> Result<TracedTensor> {
438 TracedTensor::mul(self, rhs)
439 }
440}
441
442impl std::ops::Mul<f64> for &TracedTensor {
443 type Output = Result<TracedTensor>;
444
445 fn mul(self, rhs: f64) -> Result<TracedTensor> {
446 self.scale_real(rhs)
447 }
448}
449
450impl std::ops::Mul<&TracedTensor> for f64 {
451 type Output = Result<TracedTensor>;
452
453 fn mul(self, rhs: &TracedTensor) -> Result<TracedTensor> {
454 rhs.scale_real(self)
455 }
456}
457
458impl std::ops::Neg for &TracedTensor {
459 type Output = TracedTensor;
460
461 fn neg(self) -> TracedTensor {
462 TracedTensor::neg(self)
463 }
464}
465
466impl std::ops::Div for &TracedTensor {
467 type Output = Result<TracedTensor>;
468
469 fn div(self, rhs: &TracedTensor) -> Result<TracedTensor> {
470 TracedTensor::div(self, rhs)
471 }
472}
473
474impl TracedTensor {
475 pub fn graph(&self) -> &Arc<Graph<StdTensorOp>> {
486 &self.graph
487 }
488
489 pub fn attached_data(&self) -> Option<&Arc<Tensor>> {
507 self.data.as_ref()
508 }
509
510 pub fn from_tensor_concrete_shape(tensor: Tensor) -> Result<Self> {
531 let shape = tensor.shape().to_vec();
532 let rank = shape.len();
533 let dtype = tensor.dtype();
534 let key = next_input_key();
535 let id = next_traced_id();
536 let data = Arc::new(tensor);
537
538 let mut builder = GraphBuilder::new();
539 let val = builder.add_input(key.clone());
540 builder.set_outputs(vec![val]);
541 let graph = Arc::new(builder.build());
542 let metadata_scope = register_metadata_or_internal(register_scoped_value_metadata(
543 graph.values()[val].key.clone(),
544 concrete_tensor_meta(dtype, &shape),
545 ))?;
546
547 let mut map = HashMap::new();
548 map.insert(key, Arc::clone(&data));
549
550 Ok(Self {
551 id,
552 rank,
553 dtype,
554 graph,
555 val,
556 data: Some(data),
557 shape_hint: Some(shape.into_iter().map(SymDim::from).collect()),
558 inputs_map: Arc::new(map),
559 extra_roots: Vec::new(),
560 checkpoint_chain: None,
561 metadata_scopes: metadata_scopes_for_scope(metadata_scope),
562 })
563 }
564
565 pub fn from_tensor_symbolic_shape(tensor: Tensor) -> Result<Self> {
586 let rank = tensor.shape().len();
587 let dtype = tensor.dtype();
588 let key = next_input_key();
589 let id = next_traced_id();
590 let data = Arc::new(tensor);
591
592 let mut builder = GraphBuilder::new();
593 let val = builder.add_input(key.clone());
594 builder.set_outputs(vec![val]);
595 let graph = Arc::new(builder.build());
596 let metadata_scope = register_metadata_or_internal(register_scoped_value_metadata(
597 graph.values()[val].key.clone(),
598 symbolic_input_meta(dtype, id, rank),
599 ))?;
600
601 let mut map = HashMap::new();
602 map.insert(key, Arc::clone(&data));
603
604 Ok(Self {
605 id,
606 rank,
607 dtype,
608 graph,
609 val,
610 data: Some(data),
611 shape_hint: None,
612 inputs_map: Arc::new(map),
613 extra_roots: Vec::new(),
614 checkpoint_chain: None,
615 metadata_scopes: metadata_scopes_for_scope(metadata_scope),
616 })
617 }
618
619 pub fn input_concrete_shape(dtype: DType, shape: &[usize]) -> Result<Self> {
636 let shape = shape.to_vec();
637 let rank = shape.len();
638 let key = next_input_key();
639 let id = next_traced_id();
640
641 let mut builder = GraphBuilder::new();
642 let val = builder.add_input(key.clone());
643 builder.set_outputs(vec![val]);
644 let graph = Arc::new(builder.build());
645 let metadata_scope = register_metadata_or_internal(register_scoped_value_metadata(
646 graph.values()[val].key.clone(),
647 concrete_tensor_meta(dtype, &shape),
648 ))?;
649
650 Ok(Self {
651 id,
652 rank,
653 dtype,
654 graph,
655 val,
656 data: None,
657 shape_hint: Some(shape.into_iter().map(SymDim::from).collect()),
658 inputs_map: Arc::new(HashMap::new()),
659 extra_roots: Vec::new(),
660 checkpoint_chain: None,
661 metadata_scopes: metadata_scopes_for_scope(metadata_scope),
662 })
663 }
664
665 pub fn input_symbolic_shape(dtype: DType, rank: usize) -> Result<Self> {
682 let key = next_input_key();
683 let id = next_traced_id();
684
685 let mut builder = GraphBuilder::new();
686 let val = builder.add_input(key.clone());
687 builder.set_outputs(vec![val]);
688 let graph = Arc::new(builder.build());
689 let metadata_scope = register_metadata_or_internal(register_scoped_value_metadata(
690 graph.values()[val].key.clone(),
691 symbolic_input_meta(dtype, id, rank),
692 ))?;
693
694 Ok(Self {
695 id,
696 rank,
697 dtype,
698 graph,
699 val,
700 data: None,
701 shape_hint: None,
702 inputs_map: Arc::new(HashMap::new()),
703 extra_roots: Vec::new(),
704 checkpoint_chain: None,
705 metadata_scopes: metadata_scopes_for_scope(metadata_scope),
706 })
707 }
708
709 pub fn from_vec_col_major<T: TensorScalar>(shape: Vec<usize>, data: Vec<T>) -> Result<Self> {
727 Self::from_tensor_concrete_shape(Tensor::from_vec_col_major(shape, data)?)
728 }
729
730 pub fn is_concrete_shape(&self) -> bool {
745 try_concrete_shape(self).is_some()
746 }
747
748 pub fn try_concrete_shape(&self) -> Option<Vec<usize>> {
769 try_concrete_shape(self)
770 }
771
772 pub fn concrete_shape(&self) -> Result<Vec<usize>> {
778 concrete_shape(self)
779 }
780
781 pub fn input_key(&self) -> Option<TensorInputKey> {
784 match &self.graph.values()[self.val].key {
785 ValueKey::Input(key) => Some(key.clone()),
786 _ => None,
787 }
788 }
789
790 pub fn add(&self, other: &TracedTensor) -> Result<TracedTensor> {
804 let (lhs, rhs) = broadcast_binary(self, other)?;
805 Ok(apply_binary(
806 StdTensorOp::Add,
807 &lhs,
808 &rhs,
809 lhs.rank,
810 lhs.shape_hint.clone(),
811 ))
812 }
813
814 pub fn sub(&self, other: &TracedTensor) -> Result<TracedTensor> {
818 let (lhs, rhs) = broadcast_binary(self, other)?;
819 let rhs = rhs.neg();
820 Ok(apply_binary(
821 StdTensorOp::Add,
822 &lhs,
823 &rhs,
824 lhs.rank,
825 lhs.shape_hint.clone(),
826 ))
827 }
828
829 pub fn mul(&self, other: &TracedTensor) -> Result<TracedTensor> {
843 let (lhs, rhs) = broadcast_binary(self, other)?;
844 Ok(apply_binary(
845 StdTensorOp::Mul,
846 &lhs,
847 &rhs,
848 lhs.rank,
849 lhs.shape_hint.clone(),
850 ))
851 }
852
853 pub fn div(&self, other: &TracedTensor) -> Result<TracedTensor> {
867 let (lhs, rhs) = broadcast_binary(self, other)?;
868 Ok(apply_binary(
869 StdTensorOp::Div,
870 &lhs,
871 &rhs,
872 lhs.rank,
873 lhs.shape_hint.clone(),
874 ))
875 }
876
877 pub fn compare(&self, other: &TracedTensor, dir: CompareDir) -> Result<TracedTensor> {
879 let (lhs, rhs) = broadcast_binary(self, other)?;
880 Ok(apply_binary(
881 StdTensorOp::Compare(dir),
882 &lhs,
883 &rhs,
884 lhs.rank,
885 lhs.shape_hint.clone(),
886 ))
887 }
888
889 pub fn maximum(&self, other: &TracedTensor) -> Result<TracedTensor> {
891 apply_broadcast_binary_op(StdTensorOp::Maximum, self, other)
892 }
893
894 pub fn minimum(&self, other: &TracedTensor) -> Result<TracedTensor> {
896 apply_broadcast_binary_op(StdTensorOp::Minimum, self, other)
897 }
898
899 pub fn where_select(
901 condition: &TracedTensor,
902 on_true: &TracedTensor,
903 on_false: &TracedTensor,
904 ) -> Result<TracedTensor> {
905 apply_broadcast_ternary_op(StdTensorOp::Select, condition, on_true, on_false)
906 }
907
908 pub fn select(
910 condition: &TracedTensor,
911 on_true: &TracedTensor,
912 on_false: &TracedTensor,
913 ) -> Result<TracedTensor> {
914 Self::where_select(condition, on_true, on_false)
915 }
916
917 pub fn clamp(&self, lower: &TracedTensor, upper: &TracedTensor) -> Result<TracedTensor> {
919 apply_broadcast_ternary_op(StdTensorOp::Clamp, self, lower, upper)
920 }
921
922 fn apply_same_shape_unary(&self, op: StdTensorOp) -> TracedTensor {
923 apply_unary(op, self, self.rank, self.shape_hint.clone())
924 }
925
926 pub fn neg(&self) -> TracedTensor {
939 self.apply_same_shape_unary(StdTensorOp::Neg)
940 }
941
942 pub fn conj(&self) -> TracedTensor {
957 self.apply_same_shape_unary(StdTensorOp::Conj)
958 }
959
960 pub fn abs(&self) -> TracedTensor {
972 self.apply_same_shape_unary(StdTensorOp::Abs)
973 }
974
975 pub fn sign(&self) -> TracedTensor {
985 self.apply_same_shape_unary(StdTensorOp::Sign)
986 }
987
988 pub fn scale_real(&self, factor: f64) -> Result<TracedTensor> {
999 let op = match self.dtype {
1000 DType::F64 => StdTensorOp::constant(factor),
1001 DType::F32 => StdTensorOp::constant(factor as f32),
1002 DType::I32 => StdTensorOp::constant(round_real_to_i32_for_op("scale_real", factor)?),
1003 DType::I64 => StdTensorOp::constant(round_real_to_i64(factor)?),
1004 DType::Bool => StdTensorOp::constant(bool_from_real_for_op("scale_real", factor)?),
1005 DType::C64 => StdTensorOp::constant(Complex64::new(factor, 0.0)),
1006 DType::C32 => StdTensorOp::constant(Complex32::new(factor as f32, 0.0)),
1007 };
1008 Ok(scale_with_constant(self, op))
1009 }
1010
1011 pub fn scale_complex(&self, factor: Complex64) -> Result<TracedTensor> {
1029 match self.dtype {
1030 DType::C64 => Ok(scale_with_constant(self, StdTensorOp::constant(factor))),
1031 DType::C32 => Ok(scale_with_constant(
1032 self,
1033 StdTensorOp::constant(Complex32::new(factor.re as f32, factor.im as f32)),
1034 )),
1035 DType::F32 | DType::F64 | DType::I32 | DType::I64 | DType::Bool => {
1036 Err(Error::InvalidGraphBuild {
1037 op: "scale_complex",
1038 message: format!("requires complex tensor dtype, got {:?}", self.dtype),
1039 })
1040 }
1041 }
1042 }
1043
1044 pub fn exp(&self) -> TracedTensor {
1054 self.apply_same_shape_unary(StdTensorOp::Exp)
1055 }
1056
1057 pub fn log(&self) -> TracedTensor {
1067 self.apply_same_shape_unary(StdTensorOp::Log)
1068 }
1069
1070 pub fn sin(&self) -> TracedTensor {
1080 self.apply_same_shape_unary(StdTensorOp::Sin)
1081 }
1082
1083 pub fn cos(&self) -> TracedTensor {
1093 self.apply_same_shape_unary(StdTensorOp::Cos)
1094 }
1095
1096 pub fn tanh(&self) -> TracedTensor {
1106 self.apply_same_shape_unary(StdTensorOp::Tanh)
1107 }
1108
1109 pub fn sqrt(&self) -> TracedTensor {
1119 self.apply_same_shape_unary(StdTensorOp::Sqrt)
1120 }
1121
1122 pub fn rsqrt(&self) -> TracedTensor {
1132 self.apply_same_shape_unary(StdTensorOp::Rsqrt)
1133 }
1134
1135 pub fn pow(&self, other: &TracedTensor) -> Result<TracedTensor> {
1146 let (lhs, rhs) = broadcast_binary(self, other)?;
1147 Ok(apply_binary(
1148 StdTensorOp::Pow,
1149 &lhs,
1150 &rhs,
1151 lhs.rank,
1152 lhs.shape_hint.clone(),
1153 ))
1154 }
1155
1156 pub fn expm1(&self) -> TracedTensor {
1166 self.apply_same_shape_unary(StdTensorOp::Expm1)
1167 }
1168
1169 pub fn log1p(&self) -> TracedTensor {
1179 self.apply_same_shape_unary(StdTensorOp::Log1p)
1180 }
1181
1182 pub fn convert(&self, to: DType) -> Result<TracedTensor> {
1203 tenferro_tensor::validate::validate_convert_dtype("TracedTensor::convert", self.dtype, to)?;
1204 Ok(self.cast(to))
1205 }
1206
1207 pub fn cast(&self, to: DType) -> TracedTensor {
1223 if self.dtype == to {
1224 return self.clone();
1225 }
1226
1227 apply_unary_with_dtype(
1228 StdTensorOp::Convert {
1229 from: self.dtype,
1230 to,
1231 },
1232 self,
1233 self.rank,
1234 self.shape_hint.clone(),
1235 to,
1236 )
1237 }
1238
1239 pub fn dot_general(
1262 &self,
1263 other: &TracedTensor,
1264 config: DotGeneralConfig,
1265 ) -> Result<TracedTensor> {
1266 config
1267 .validate_dims_with_ranks(self.rank, other.rank)
1268 .map_err(|err| Error::InvalidGraphBuild {
1269 op: "dot_general",
1270 message: err.to_string(),
1271 })?;
1272 let lhs_free: Vec<usize> = (0..self.rank)
1273 .filter(|d| {
1274 !config.lhs_contracting_dims.contains(d) && !config.lhs_batch_dims.contains(d)
1275 })
1276 .collect();
1277 let rhs_free: Vec<usize> = (0..other.rank)
1278 .filter(|d| {
1279 !config.rhs_contracting_dims.contains(d) && !config.rhs_batch_dims.contains(d)
1280 })
1281 .collect();
1282 let out_rank = config.lhs_batch_dims.len() + lhs_free.len() + rhs_free.len();
1283 let out_shape_hint = match (&self.shape_hint, &other.shape_hint) {
1284 (Some(lhs_shape), Some(rhs_shape)) => {
1285 let mut out_shape = Vec::with_capacity(out_rank);
1286 for &d in &lhs_free {
1287 out_shape.push(lhs_shape[d].clone());
1288 }
1289 for &d in &rhs_free {
1290 out_shape.push(rhs_shape[d].clone());
1291 }
1292 for &d in &config.lhs_batch_dims {
1293 out_shape.push(lhs_shape[d].clone());
1294 }
1295 Some(out_shape)
1296 }
1297 _ => None,
1298 };
1299
1300 Ok(apply_binary(
1301 StdTensorOp::DotGeneral { config },
1302 self,
1303 other,
1304 out_rank,
1305 out_shape_hint,
1306 ))
1307 }
1308
1309 pub fn matmul(&self, other: &TracedTensor) -> Result<TracedTensor> {
1311 if self.rank != 2 {
1312 return Err(Error::InvalidGraphBuild {
1313 op: "TracedTensor::matmul",
1314 message: format!("matmul requires rank-2 inputs, got lhs rank {}", self.rank),
1315 });
1316 }
1317 if other.rank != 2 {
1318 return Err(Error::InvalidGraphBuild {
1319 op: "TracedTensor::matmul",
1320 message: format!("matmul requires rank-2 inputs, got rhs rank {}", other.rank),
1321 });
1322 }
1323 if let (Some(lhs_shape), Some(rhs_shape)) = (&self.shape_hint, &other.shape_hint) {
1324 if let (Some(lhs_cols), Some(rhs_rows)) =
1325 (lhs_shape[1].constant_value(), rhs_shape[0].constant_value())
1326 {
1327 if lhs_cols != rhs_rows {
1328 return Err(Error::InvalidGraphBuild {
1329 op: "TracedTensor::matmul",
1330 message: format!(
1331 "matmul dimension mismatch: lhs columns {lhs_cols} != rhs rows {rhs_rows}"
1332 ),
1333 });
1334 }
1335 }
1336 }
1337 self.dot_general(
1338 other,
1339 DotGeneralConfig {
1340 lhs_contracting_dims: vec![1],
1341 rhs_contracting_dims: vec![0],
1342 lhs_batch_dims: vec![],
1343 rhs_batch_dims: vec![],
1344 },
1345 )
1346 }
1347
1348 pub fn reduce_sum(&self, axes: &[usize]) -> Result<TracedTensor> {
1364 let (out_rank, out_shape_hint) =
1365 reduction_output_meta(self, axes, "TracedTensor::reduce_sum")?;
1366 Ok(apply_unary(
1367 StdTensorOp::ReduceSum {
1368 axes: axes.to_vec(),
1369 },
1370 self,
1371 out_rank,
1372 out_shape_hint,
1373 ))
1374 }
1375
1376 pub fn reduce_max(&self, axes: &[usize]) -> Result<TracedTensor> {
1394 let (out_rank, out_shape_hint) =
1395 reduction_output_meta(self, axes, "TracedTensor::reduce_max")?;
1396 Ok(apply_unary(
1397 StdTensorOp::ReduceMax {
1398 axes: axes.to_vec(),
1399 },
1400 self,
1401 out_rank,
1402 out_shape_hint,
1403 ))
1404 }
1405
1406 pub fn reduce_min(&self, axes: &[usize]) -> Result<TracedTensor> {
1424 let (out_rank, out_shape_hint) =
1425 reduction_output_meta(self, axes, "TracedTensor::reduce_min")?;
1426 Ok(apply_unary(
1427 StdTensorOp::ReduceMin {
1428 axes: axes.to_vec(),
1429 },
1430 self,
1431 out_rank,
1432 out_shape_hint,
1433 ))
1434 }
1435
1436 pub fn reduce_prod(&self, axes: &[usize]) -> Result<TracedTensor> {
1451 let (out_rank, out_shape_hint) =
1452 reduction_output_meta(self, axes, "TracedTensor::reduce_prod")?;
1453 Ok(apply_unary(
1454 StdTensorOp::ReduceProd {
1455 axes: axes.to_vec(),
1456 },
1457 self,
1458 out_rank,
1459 out_shape_hint,
1460 ))
1461 }
1462
1463 pub fn reshape(&self, shape: &[usize]) -> TracedTensor {
1473 apply_unary(
1474 StdTensorOp::Reshape {
1475 to_shape: DimExpr::from_concrete(shape),
1476 },
1477 self,
1478 shape.len(),
1479 Some(shape.iter().copied().map(SymDim::from).collect()),
1480 )
1481 }
1482
1483 pub fn sym_size(&self, axis: usize) -> Result<SymDim> {
1514 validate_traced_axis(self, axis, "TracedTensor::sym_size")?;
1515 Ok(self
1516 .shape_hint
1517 .as_ref()
1518 .and_then(|shape| shape.get(axis))
1519 .filter(|dim| dim.constant_value().is_none())
1520 .cloned()
1521 .unwrap_or_else(|| SymDim::tensor_axis(self.id, axis)))
1522 }
1523
1524 pub fn axis_sym_dim(&self, axis: usize) -> Result<SymDim> {
1553 validate_traced_axis(self, axis, "TracedTensor::axis_sym_dim")?;
1554 match self.shape_hint.as_ref().and_then(|shape| shape.get(axis)) {
1555 Some(dim) => Ok(dim.clone()),
1556 None => Ok(SymDim::tensor_axis(self.id, axis)),
1557 }
1558 }
1559
1560 pub fn sym_shape(&self) -> Option<&[SymDim]> {
1582 self.shape_hint.as_deref()
1583 }
1584
1585 pub fn reshape_sym(&self, shape: &[SymDim]) -> Result<TracedTensor> {
1598 let tensor_map = [(self.id, 0usize)];
1599 let to_shape = shape
1600 .iter()
1601 .map(|dim| dim.to_dim_expr(&tensor_map).map_err(Error::Internal))
1602 .collect::<Result<Vec<_>>>()?;
1603 let out_shape_hint = Some(shape.to_vec());
1604 Ok(apply_unary(
1605 StdTensorOp::Reshape { to_shape },
1606 self,
1607 shape.len(),
1608 out_shape_hint,
1609 ))
1610 }
1611
1612 pub fn broadcast_in_dim(&self, shape: &[usize], dims: &[usize]) -> Result<TracedTensor> {
1629 let out_shape_hint: Vec<SymDim> = shape.iter().copied().map(SymDim::from).collect();
1630 validate_broadcast_in_dim_args(
1631 self,
1632 &out_shape_hint,
1633 dims,
1634 "TracedTensor::broadcast_in_dim",
1635 )?;
1636 Ok(apply_unary(
1637 StdTensorOp::BroadcastInDim {
1638 shape: DimExpr::from_concrete(shape),
1639 dims: dims.to_vec(),
1640 },
1641 self,
1642 shape.len(),
1643 Some(out_shape_hint),
1644 ))
1645 }
1646
1647 pub fn broadcast_in_dim_sym(
1681 &self,
1682 shape: &[SymDim],
1683 dims: &[usize],
1684 shape_refs: &[&TracedTensor],
1685 ) -> Result<TracedTensor> {
1686 validate_broadcast_in_dim_args(self, shape, dims, "TracedTensor::broadcast_in_dim_sym")?;
1687
1688 let mut dedup_refs: Vec<&TracedTensor> = Vec::with_capacity(shape_refs.len());
1692 let mut tensor_map: Vec<(u64, usize)> = vec![(self.id, 0)];
1693 for &t in shape_refs {
1694 if !tensor_map.iter().any(|(id, _)| *id == t.id) {
1695 let idx = tensor_map.len();
1696 tensor_map.push((t.id, idx));
1697 dedup_refs.push(t);
1698 }
1699 }
1700
1701 let to_shape: Vec<DimExpr> = shape
1702 .iter()
1703 .map(|dim| {
1704 dim.to_dim_expr(&tensor_map)
1705 .map_err(|err| Error::InvalidGraphBuild {
1706 op: "broadcast_in_dim_sym",
1707 message: format!(
1708 "unresolved symbolic dimension: {err}; \
1709 pass every referenced tensor via `shape_refs`"
1710 ),
1711 })
1712 })
1713 .collect::<Result<Vec<_>>>()?;
1714
1715 let max_used_idx = DimExpr::max_input_idx_all(&to_shape).unwrap_or(0);
1722 let used_refs: Vec<&TracedTensor> = dedup_refs.into_iter().take(max_used_idx).collect();
1723
1724 let out_shape_hint = Some(shape.to_vec());
1725 Ok(apply_unary_with_shape_refs(
1726 StdTensorOp::BroadcastInDim {
1727 shape: to_shape,
1728 dims: dims.to_vec(),
1729 },
1730 self,
1731 &used_refs,
1732 shape.len(),
1733 out_shape_hint,
1734 ))
1735 }
1736
1737 pub fn slice(&self, config: SliceConfig) -> Result<TracedTensor> {
1739 let op = StdTensorOp::Slice(config);
1740 let (out_rank, out_shape_hint) =
1741 infer_traced_single_output_shape("TracedTensor::slice", &op, &[self])?;
1742 Ok(apply_unary(op, self, out_rank, out_shape_hint))
1743 }
1744
1745 pub fn pad(&self, config: PadConfig) -> Result<TracedTensor> {
1747 let op = StdTensorOp::Pad(config);
1748 let (out_rank, out_shape_hint) =
1749 infer_traced_single_output_shape("TracedTensor::pad", &op, &[self])?;
1750 Ok(apply_unary(op, self, out_rank, out_shape_hint))
1751 }
1752
1753 pub fn reverse(&self, axes: &[usize]) -> Result<TracedTensor> {
1755 validate_traced_axes(self.rank, axes, "TracedTensor::reverse")?;
1756 Ok(apply_unary(
1757 StdTensorOp::Reverse {
1758 axes: axes.to_vec(),
1759 },
1760 self,
1761 self.rank,
1762 self.shape_hint.clone(),
1763 ))
1764 }
1765
1766 pub fn gather(&self, indices: &TracedTensor, config: GatherConfig) -> Result<TracedTensor> {
1768 let op = StdTensorOp::Gather(config);
1769 let (out_rank, out_shape_hint) =
1770 infer_traced_single_output_shape("TracedTensor::gather", &op, &[self, indices])?;
1771 Ok(apply_binary_preserve_input_dtypes(
1772 op,
1773 self,
1774 indices,
1775 out_rank,
1776 out_shape_hint,
1777 self.dtype,
1778 ))
1779 }
1780
1781 pub fn scatter(
1783 &self,
1784 indices: &TracedTensor,
1785 updates: &TracedTensor,
1786 config: ScatterConfig,
1787 ) -> Result<TracedTensor> {
1788 let op = StdTensorOp::Scatter(config);
1789 let (out_rank, out_shape_hint) = infer_traced_single_output_shape(
1790 "TracedTensor::scatter",
1791 &op,
1792 &[self, indices, updates],
1793 )?;
1794 let out_dtype = crate::shape_infer::promote_dtype(self.dtype, updates.dtype);
1795 let operand = if self.dtype != out_dtype {
1796 self.cast(out_dtype)
1797 } else {
1798 self.clone()
1799 };
1800 let updates = if updates.dtype != out_dtype {
1801 updates.cast(out_dtype)
1802 } else {
1803 updates.clone()
1804 };
1805 Ok(apply_ternary_with_output_dtype(
1806 op,
1807 &operand,
1808 indices,
1809 &updates,
1810 out_rank,
1811 out_shape_hint,
1812 out_dtype,
1813 ))
1814 }
1815
1816 pub fn dynamic_slice(&self, starts: &TracedTensor, sizes: &[usize]) -> Result<TracedTensor> {
1818 let op = StdTensorOp::DynamicSlice {
1819 slice_sizes: sizes.to_vec(),
1820 };
1821 let (out_rank, out_shape_hint) =
1822 infer_traced_single_output_shape("TracedTensor::dynamic_slice", &op, &[self, starts])?;
1823 Ok(apply_binary_preserve_input_dtypes(
1824 op,
1825 self,
1826 starts,
1827 out_rank,
1828 out_shape_hint,
1829 self.dtype,
1830 ))
1831 }
1832
1833 pub fn tril(&self, k: i64) -> TracedTensor {
1835 apply_unary(
1836 StdTensorOp::Tril { k },
1837 self,
1838 self.rank,
1839 self.shape_hint.clone(),
1840 )
1841 }
1842
1843 pub fn triu(&self, k: i64) -> TracedTensor {
1845 apply_unary(
1846 StdTensorOp::Triu { k },
1847 self,
1848 self.rank,
1849 self.shape_hint.clone(),
1850 )
1851 }
1852
1853 pub fn transpose(&self, perm: &[usize]) -> Result<TracedTensor> {
1869 validate_traced_perm(self.rank, perm, "TracedTensor::transpose")?;
1870 let out_shape_hint = self
1871 .shape_hint
1872 .as_ref()
1873 .map(|shape| perm.iter().map(|&p| shape[p].clone()).collect());
1874 Ok(apply_unary(
1875 StdTensorOp::Transpose {
1876 perm: perm.to_vec(),
1877 },
1878 self,
1879 self.rank,
1880 out_shape_hint,
1881 ))
1882 }
1883
1884 pub fn extract_diag(&self, axis_a: usize, axis_b: usize) -> Result<TracedTensor> {
1900 validate_traced_axis(self, axis_a, "TracedTensor::extract_diag")?;
1901 validate_traced_axis(self, axis_b, "TracedTensor::extract_diag")?;
1902 if axis_a == axis_b {
1903 return Err(Error::InvalidGraphBuild {
1904 op: "TracedTensor::extract_diag",
1905 message: "diagonal axes must be distinct".into(),
1906 });
1907 }
1908 let op = StdTensorOp::ExtractDiag { axis_a, axis_b };
1909 let (out_rank, out_shape_hint) =
1910 infer_traced_single_output_shape("TracedTensor::extract_diag", &op, &[self])?;
1911 Ok(apply_unary(op, self, out_rank, out_shape_hint))
1912 }
1913
1914 pub fn embed_diag(&self, axis_a: usize, axis_b: usize) -> Result<TracedTensor> {
1930 validate_traced_axis(self, axis_a, "TracedTensor::embed_diag")?;
1931 validate_traced_insert_axis(self.rank, axis_b, "TracedTensor::embed_diag")?;
1932 let out_shape_hint = self.shape_hint.as_ref().map(|shape| {
1933 let mut out_shape = shape.clone();
1934 out_shape.insert(axis_b, shape[axis_a].clone());
1935 out_shape
1936 });
1937 Ok(apply_unary(
1938 StdTensorOp::EmbedDiag { axis_a, axis_b },
1939 self,
1940 self.rank + 1,
1941 out_shape_hint,
1942 ))
1943 }
1944
1945 pub fn shape_of(&self, axis: usize) -> Result<TracedTensor> {
1968 validate_traced_axis(self, axis, "TracedTensor::shape_of")?;
1969 Ok(apply_unary_with_dtype(
1970 StdTensorOp::ShapeOf { axis },
1971 self,
1972 0,
1973 Some(vec![]),
1974 DType::F64,
1975 ))
1976 }
1977
1978 pub fn dynamic_truncate(&self, size: &TracedTensor, axis: usize) -> Result<TracedTensor> {
2004 validate_traced_axis(self, axis, "TracedTensor::dynamic_truncate")?;
2005 if size.rank != 0 {
2006 return Err(Error::InvalidGraphBuild {
2007 op: "TracedTensor::dynamic_truncate",
2008 message: format!("size must be a scalar tensor, got rank {}", size.rank),
2009 });
2010 }
2011 Ok(apply_binary_preserve_input_dtypes(
2012 StdTensorOp::DynamicTruncate { axis },
2013 self,
2014 size,
2015 self.rank,
2016 None,
2017 self.dtype,
2018 ))
2019 }
2020
2021 pub fn pad_to_match(&self, reference: &TracedTensor, axis: usize) -> Result<TracedTensor> {
2045 validate_traced_axis(self, axis, "TracedTensor::pad_to_match")?;
2046 validate_traced_axis(reference, axis, "TracedTensor::pad_to_match")?;
2047 let op = StdTensorOp::PadToMatch { axis };
2048 let (out_rank, out_shape_hint) = infer_traced_single_output_shape(
2049 "TracedTensor::pad_to_match",
2050 &op,
2051 &[self, reference],
2052 )?;
2053 Ok(apply_binary_preserve_input_dtypes(
2054 op,
2055 self,
2056 reference,
2057 out_rank,
2058 out_shape_hint,
2059 self.dtype,
2060 ))
2061 }
2062}
2063
2064pub(crate) fn apply_unary(
2065 op: StdTensorOp,
2066 input: &TracedTensor,
2067 out_rank: usize,
2068 out_shape_hint: Option<Vec<SymDim>>,
2069) -> TracedTensor {
2070 let out_dtype = inferred_output_dtype(&op, &[input.dtype], "apply_unary");
2071 apply_unary_with_dtype(op, input, out_rank, out_shape_hint, out_dtype)
2072}
2073
2074pub(crate) fn apply_unary_with_dtype(
2075 op: StdTensorOp,
2076 input: &TracedTensor,
2077 out_rank: usize,
2078 out_shape_hint: Option<Vec<SymDim>>,
2079 out_dtype: DType,
2080) -> TracedTensor {
2081 let mut builder = GraphBuilder::new();
2082 builder.add_parent(input.graph.clone());
2083 let input_ref = ValueRef::External(input.graph.values()[input.val].key.clone());
2084 let outputs = builder.add_operation(op, vec![input_ref], OperationRole::Primary);
2085 builder.set_outputs(outputs.clone());
2086 let graph = Arc::new(builder.build());
2087 let metadata_scope =
2088 register_single_output_metadata(graph.as_ref(), outputs[0], out_dtype, &out_shape_hint);
2089
2090 TracedTensor {
2091 id: next_traced_id(),
2092 rank: out_rank,
2093 dtype: out_dtype,
2094 graph,
2095 val: outputs[0],
2096 data: None,
2097 shape_hint: out_shape_hint,
2098 inputs_map: input.inputs_map.clone(),
2099 extra_roots: input.extra_roots.clone(),
2100 checkpoint_chain: input.checkpoint_chain.clone(),
2101 metadata_scopes: metadata_scopes_with_new(
2102 metadata_scope,
2103 [input.metadata_scopes.as_slice()],
2104 ),
2105 }
2106}
2107
2108pub(crate) fn apply_unary_with_shape_refs(
2117 op: StdTensorOp,
2118 input: &TracedTensor,
2119 shape_refs: &[&TracedTensor],
2120 out_rank: usize,
2121 out_shape_hint: Option<Vec<SymDim>>,
2122) -> TracedTensor {
2123 let mut builder = GraphBuilder::new();
2124 builder.add_parent(input.graph.clone());
2125 for t in shape_refs {
2126 builder.add_parent(t.graph.clone());
2127 }
2128 let mut op_inputs: Vec<ValueRef<StdTensorOp>> = Vec::with_capacity(1 + shape_refs.len());
2129 op_inputs.push(ValueRef::External(
2130 input.graph.values()[input.val].key.clone(),
2131 ));
2132 for t in shape_refs {
2133 op_inputs.push(ValueRef::External(t.graph.values()[t.val].key.clone()));
2134 }
2135 let outputs = builder.add_operation(op, op_inputs, OperationRole::Primary);
2136 builder.set_outputs(outputs.clone());
2137 let graph = Arc::new(builder.build());
2138 let metadata_scope =
2139 register_single_output_metadata(graph.as_ref(), outputs[0], input.dtype, &out_shape_hint);
2140
2141 let mut merged = (*input.inputs_map).clone();
2142 for t in shape_refs {
2143 merged.extend(t.inputs_map.iter().map(|(k, v)| (k.clone(), v.clone())));
2144 }
2145
2146 let mut extra_roots = input.extra_roots.clone();
2147 for t in shape_refs {
2148 extra_roots.extend(t.extra_roots.iter().cloned());
2149 }
2150
2151 let mut checkpoint_chain = input.checkpoint_chain.clone();
2152 for t in shape_refs {
2153 checkpoint_chain =
2154 CheckpointNode::merge_chains(checkpoint_chain, t.checkpoint_chain.clone());
2155 }
2156
2157 TracedTensor {
2158 id: next_traced_id(),
2159 rank: out_rank,
2160 dtype: input.dtype,
2161 graph,
2162 val: outputs[0],
2163 data: None,
2164 shape_hint: out_shape_hint,
2165 inputs_map: Arc::new(merged),
2166 extra_roots,
2167 checkpoint_chain,
2168 metadata_scopes: {
2169 let mut scopes =
2170 metadata_scopes_with_new(metadata_scope, [input.metadata_scopes.as_slice()]);
2171 for t in shape_refs {
2172 for scope in &t.metadata_scopes {
2173 push_metadata_scope(&mut scopes, Arc::clone(scope));
2174 }
2175 }
2176 scopes
2177 },
2178 }
2179}
2180
2181pub(crate) fn apply_nullary(
2182 op: StdTensorOp,
2183 rank: usize,
2184 dtype: DType,
2185 shape_hint: Option<Vec<SymDim>>,
2186) -> TracedTensor {
2187 let mut builder = GraphBuilder::new();
2188 let outputs = builder.add_operation(op, vec![], OperationRole::Primary);
2189 builder.set_outputs(outputs.clone());
2190 let graph = Arc::new(builder.build());
2191 let metadata_scope =
2192 register_single_output_metadata(graph.as_ref(), outputs[0], dtype, &shape_hint);
2193
2194 TracedTensor {
2195 id: next_traced_id(),
2196 rank,
2197 dtype,
2198 graph,
2199 val: outputs[0],
2200 data: None,
2201 shape_hint,
2202 inputs_map: Arc::new(HashMap::new()),
2203 extra_roots: Vec::new(),
2204 checkpoint_chain: None,
2205 metadata_scopes: metadata_scopes_for_scope(metadata_scope),
2206 }
2207}
2208
2209pub(crate) fn apply_binary(
2210 op: StdTensorOp,
2211 lhs: &TracedTensor,
2212 rhs: &TracedTensor,
2213 out_rank: usize,
2214 out_shape_hint: Option<Vec<SymDim>>,
2215) -> TracedTensor {
2216 let input_dtype = crate::shape_infer::promote_dtype_for_binary_op(&op, lhs.dtype, rhs.dtype);
2217 let out_dtype = inferred_output_dtype(&op, &[lhs.dtype, rhs.dtype], "apply_binary");
2218
2219 let lhs = if lhs.dtype != input_dtype {
2221 lhs.cast(input_dtype)
2222 } else {
2223 lhs.clone()
2224 };
2225 let rhs = if rhs.dtype != input_dtype {
2226 rhs.cast(input_dtype)
2227 } else {
2228 rhs.clone()
2229 };
2230
2231 apply_binary_with_output_dtype(op, &lhs, &rhs, out_rank, out_shape_hint, out_dtype)
2232}
2233
2234pub(crate) fn apply_binary_preserve_input_dtypes(
2235 op: StdTensorOp,
2236 lhs: &TracedTensor,
2237 rhs: &TracedTensor,
2238 out_rank: usize,
2239 out_shape_hint: Option<Vec<SymDim>>,
2240 out_dtype: DType,
2241) -> TracedTensor {
2242 apply_binary_with_output_dtype(op, lhs, rhs, out_rank, out_shape_hint, out_dtype)
2243}
2244
2245pub(crate) fn apply_broadcast_binary_op(
2246 op: StdTensorOp,
2247 lhs: &TracedTensor,
2248 rhs: &TracedTensor,
2249) -> Result<TracedTensor> {
2250 let (lhs, rhs) = broadcast_binary(lhs, rhs)?;
2251 Ok(apply_binary(
2252 op,
2253 &lhs,
2254 &rhs,
2255 lhs.rank,
2256 lhs.shape_hint.clone(),
2257 ))
2258}
2259
2260pub(crate) fn apply_broadcast_ternary_op(
2261 op: StdTensorOp,
2262 first: &TracedTensor,
2263 second: &TracedTensor,
2264 third: &TracedTensor,
2265) -> Result<TracedTensor> {
2266 let (first, second, third) = broadcast_ternary(first, second, third)?;
2267 Ok(apply_ternary(
2268 op,
2269 &first,
2270 &second,
2271 &third,
2272 first.rank,
2273 first.shape_hint.clone(),
2274 ))
2275}
2276
2277pub(crate) fn apply_ternary(
2278 op: StdTensorOp,
2279 first: &TracedTensor,
2280 second: &TracedTensor,
2281 third: &TracedTensor,
2282 out_rank: usize,
2283 out_shape_hint: Option<Vec<SymDim>>,
2284) -> TracedTensor {
2285 let out_dtype = inferred_output_dtype(
2286 &op,
2287 &[first.dtype, second.dtype, third.dtype],
2288 "apply_ternary",
2289 );
2290 let (first, second, third) = match op {
2291 StdTensorOp::Select => {
2292 let value_dtype = crate::shape_infer::promote_dtype(second.dtype, third.dtype);
2293 let second = if second.dtype != value_dtype {
2294 second.cast(value_dtype)
2295 } else {
2296 second.clone()
2297 };
2298 let third = if third.dtype != value_dtype {
2299 third.cast(value_dtype)
2300 } else {
2301 third.clone()
2302 };
2303 (first.clone(), second, third)
2304 }
2305 _ => {
2306 let input_dtype =
2307 crate::shape_infer::promote_dtypes([first.dtype, second.dtype, third.dtype]);
2308 let first = if first.dtype != input_dtype {
2309 first.cast(input_dtype)
2310 } else {
2311 first.clone()
2312 };
2313 let second = if second.dtype != input_dtype {
2314 second.cast(input_dtype)
2315 } else {
2316 second.clone()
2317 };
2318 let third = if third.dtype != input_dtype {
2319 third.cast(input_dtype)
2320 } else {
2321 third.clone()
2322 };
2323 (first, second, third)
2324 }
2325 };
2326 apply_ternary_with_output_dtype(
2327 op,
2328 &first,
2329 &second,
2330 &third,
2331 out_rank,
2332 out_shape_hint,
2333 out_dtype,
2334 )
2335}
2336
2337fn apply_binary_with_output_dtype(
2338 op: StdTensorOp,
2339 lhs: &TracedTensor,
2340 rhs: &TracedTensor,
2341 out_rank: usize,
2342 out_shape_hint: Option<Vec<SymDim>>,
2343 out_dtype: DType,
2344) -> TracedTensor {
2345 let lhs_ref = ValueRef::External(lhs.graph.values()[lhs.val].key.clone());
2346 let rhs_ref = ValueRef::External(rhs.graph.values()[rhs.val].key.clone());
2347
2348 let mut builder = GraphBuilder::new();
2349 builder.add_parent(lhs.graph.clone());
2350 builder.add_parent(rhs.graph.clone());
2351 let outputs = builder.add_operation(op, vec![lhs_ref, rhs_ref], OperationRole::Primary);
2352 builder.set_outputs(outputs.clone());
2353 let graph = Arc::new(builder.build());
2354 let metadata_scope =
2355 register_single_output_metadata(graph.as_ref(), outputs[0], out_dtype, &out_shape_hint);
2356
2357 let mut merged = (*lhs.inputs_map).clone();
2358 merged.extend(rhs.inputs_map.iter().map(|(k, v)| (k.clone(), v.clone())));
2359 let mut extra_roots = lhs.extra_roots.clone();
2360 extra_roots.extend(rhs.extra_roots.iter().cloned());
2361
2362 TracedTensor {
2363 id: next_traced_id(),
2364 rank: out_rank,
2365 dtype: out_dtype,
2366 graph,
2367 val: outputs[0],
2368 data: None,
2369 shape_hint: out_shape_hint,
2370 inputs_map: Arc::new(merged),
2371 extra_roots,
2372 checkpoint_chain: CheckpointNode::merge_chains(
2373 lhs.checkpoint_chain.clone(),
2374 rhs.checkpoint_chain.clone(),
2375 ),
2376 metadata_scopes: metadata_scopes_with_new(
2377 metadata_scope,
2378 [
2379 lhs.metadata_scopes.as_slice(),
2380 rhs.metadata_scopes.as_slice(),
2381 ],
2382 ),
2383 }
2384}
2385
2386fn apply_ternary_with_output_dtype(
2387 op: StdTensorOp,
2388 first: &TracedTensor,
2389 second: &TracedTensor,
2390 third: &TracedTensor,
2391 out_rank: usize,
2392 out_shape_hint: Option<Vec<SymDim>>,
2393 out_dtype: DType,
2394) -> TracedTensor {
2395 let first_ref = ValueRef::External(first.graph.values()[first.val].key.clone());
2396 let second_ref = ValueRef::External(second.graph.values()[second.val].key.clone());
2397 let third_ref = ValueRef::External(third.graph.values()[third.val].key.clone());
2398
2399 let mut builder = GraphBuilder::new();
2400 builder.add_parent(first.graph.clone());
2401 builder.add_parent(second.graph.clone());
2402 builder.add_parent(third.graph.clone());
2403 let outputs = builder.add_operation(
2404 op,
2405 vec![first_ref, second_ref, third_ref],
2406 OperationRole::Primary,
2407 );
2408 builder.set_outputs(outputs.clone());
2409 let graph = Arc::new(builder.build());
2410 let metadata_scope =
2411 register_single_output_metadata(graph.as_ref(), outputs[0], out_dtype, &out_shape_hint);
2412
2413 let mut merged = (*first.inputs_map).clone();
2414 merged.extend(
2415 second
2416 .inputs_map
2417 .iter()
2418 .map(|(k, v)| (k.clone(), v.clone())),
2419 );
2420 merged.extend(third.inputs_map.iter().map(|(k, v)| (k.clone(), v.clone())));
2421
2422 let mut extra_roots = first.extra_roots.clone();
2423 extra_roots.extend(second.extra_roots.iter().cloned());
2424 extra_roots.extend(third.extra_roots.iter().cloned());
2425
2426 let checkpoint_chain = CheckpointNode::merge_chains(
2427 CheckpointNode::merge_chains(
2428 first.checkpoint_chain.clone(),
2429 second.checkpoint_chain.clone(),
2430 ),
2431 third.checkpoint_chain.clone(),
2432 );
2433
2434 TracedTensor {
2435 id: next_traced_id(),
2436 rank: out_rank,
2437 dtype: out_dtype,
2438 graph,
2439 val: outputs[0],
2440 data: None,
2441 shape_hint: out_shape_hint,
2442 inputs_map: Arc::new(merged),
2443 extra_roots,
2444 checkpoint_chain,
2445 metadata_scopes: metadata_scopes_with_new(
2446 metadata_scope,
2447 [
2448 first.metadata_scopes.as_slice(),
2449 second.metadata_scopes.as_slice(),
2450 third.metadata_scopes.as_slice(),
2451 ],
2452 ),
2453 }
2454}
2455
2456fn register_single_output_metadata(
2457 graph: &Graph<StdTensorOp>,
2458 output: LocalValueId,
2459 dtype: DType,
2460 shape_hint: &Option<Vec<SymDim>>,
2461) -> GlobalMetadataScope {
2462 if let Some(shape) = shape_hint {
2463 register_scoped_value_metadata(
2466 graph.values()[output].key.clone(),
2467 tensor_meta(dtype, shape.clone()),
2468 )
2469 .expect("fresh traced graph output metadata registration failed")
2470 } else {
2471 register_scoped_graph_metadata(graph, std::iter::empty())
2474 .expect("fresh traced graph metadata registration failed")
2475 }
2476}
2477
2478impl TracedTensor {
2479 pub(crate) fn resolve_roots(&self) -> Vec<Arc<Graph<StdTensorOp>>> {
2480 let mut roots = Vec::with_capacity(1 + self.extra_roots.len());
2481 roots.push(self.graph.clone());
2482 roots.extend(self.extra_roots.iter().cloned());
2483 roots
2484 }
2485}
2486
2487#[cfg(test)]
2488mod tests;