1use std::cell::{RefCell, RefMut};
2use std::fmt;
3use std::rc::Rc;
4use std::sync::Arc;
5
6use tenferro_ops::ext_op::ExtensionOp;
7use tenferro_ops::input_key::TensorInputKey;
8use tenferro_tensor::Tensor;
9
10use crate::checkpoint::RetainedValue;
11use crate::extension_cache::ExtensionCacheStore;
12use crate::program::{
13 BindingKey, CoreSemanticOp, FrozenProgram, FrozenProgram as SnapshotProgram, ProgramBindings,
14 ProgramBuildError, ProgramFinishError, ProgramInputSpec, ProgramValue, ProgramValueMetadata,
15 SemanticProgram, SemanticProgramBuilder,
16};
17
18pub(crate) struct CheckpointAlias {
21 pub(crate) input: ProgramValue,
22 pub(crate) target: ProgramValue,
23 pub(crate) bound_shape: Vec<usize>,
24}
25
26struct TraceInner {
27 builder: SemanticProgramBuilder,
28 extension_caches: ExtensionCacheStore,
29 inputs: Vec<TraceInput>,
31 aliases: Vec<CheckpointAlias>,
32}
33
34struct TraceInput {
35 value: ProgramValue,
36 key: TensorInputKey,
37}
38
39impl TraceInner {
40 fn new() -> Self {
41 Self {
42 builder: SemanticProgramBuilder::new(),
43 extension_caches: ExtensionCacheStore::new(),
44 inputs: Vec::new(),
45 aliases: Vec::new(),
46 }
47 }
48}
49
50pub struct TraceContext {
73 inner: Rc<RefCell<TraceInner>>,
74}
75
76impl Clone for TraceContext {
77 fn clone(&self) -> Self {
78 Self {
79 inner: Rc::clone(&self.inner),
80 }
81 }
82}
83
84impl fmt::Debug for TraceContext {
85 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
86 let operations = self
89 .inner
90 .try_borrow()
91 .ok()
92 .map(|inner| inner.builder.operation_count());
93 formatter
94 .debug_struct("TraceContext")
95 .field("operations", &operations)
96 .finish_non_exhaustive()
97 }
98}
99
100impl TraceContext {
101 pub fn new() -> Self {
112 Self {
113 inner: Rc::new(RefCell::new(TraceInner::new())),
114 }
115 }
116
117 fn borrow_mut(&self) -> Result<RefMut<'_, TraceInner>, ProgramBuildError> {
118 self.inner
119 .try_borrow_mut()
120 .map_err(|_| ProgramBuildError::ReentrantContextBorrow)
121 }
122
123 fn borrow_mut_for_finish(&self) -> Result<RefMut<'_, TraceInner>, ProgramFinishError> {
124 self.inner
125 .try_borrow_mut()
126 .map_err(|_| ProgramFinishError::ReentrantContextBorrow)
127 }
128
129 pub fn input(&self, spec: ProgramInputSpec) -> Result<TraceValue, ProgramBuildError> {
137 let key = crate::traced::next_input_key();
138 let mut inner = self.borrow_mut()?;
139 let value = inner.builder.input(spec)?;
140 inner.inputs.push(TraceInput { value, key });
141 Ok(TraceValue { value })
142 }
143
144 pub fn input_with_default(
153 &self,
154 spec: ProgramInputSpec,
155 tensor: Tensor,
156 ) -> Result<TraceValue, ProgramBuildError> {
157 let input = self.input(spec)?;
158 self.bind_input(input, tensor)?;
159 Ok(input)
160 }
161
162 pub fn bind_input(
171 &self,
172 input: TraceValue,
173 tensor: Tensor,
174 ) -> Result<BindingKey, ProgramBuildError> {
175 self.borrow_mut()?.builder.bind_input(input.value, tensor)
176 }
177
178 pub fn add_op(
189 &self,
190 op: CoreSemanticOp,
191 inputs: &[TraceValue],
192 ) -> Result<Box<[TraceValue]>, ProgramBuildError> {
193 let inputs = program_values(inputs);
194 let outputs = self.borrow_mut()?.builder.add_op(op, &inputs)?;
195 Ok(trace_values(outputs))
196 }
197
198 pub fn add_extension(
210 &self,
211 op: Arc<dyn ExtensionOp>,
212 inputs: &[TraceValue],
213 ) -> Result<Box<[TraceValue]>, ProgramBuildError> {
214 let inputs = program_values(inputs);
215 let outputs = self.borrow_mut()?.builder.add_extension(op, &inputs)?;
216 Ok(trace_values(outputs))
217 }
218
219 pub fn value_metadata(
227 &self,
228 value: TraceValue,
229 ) -> Result<ProgramValueMetadata, ProgramBuildError> {
230 self.borrow_mut()?
231 .builder
232 .value_metadata(value.value)
233 .cloned()
234 }
235
236 pub fn with_extension_caches<R>(
246 &self,
247 f: impl FnOnce(&mut ExtensionCacheStore) -> R,
248 ) -> Result<R, ProgramBuildError> {
249 let mut inner = self.borrow_mut()?;
250 Ok(f(&mut inner.extension_caches))
251 }
252
253 #[doc(hidden)]
255 pub fn same_context(&self, other: &TraceContext) -> bool {
256 std::rc::Rc::ptr_eq(&self.inner, &other.inner)
257 }
258
259 #[doc(hidden)]
271 pub fn bound_input_concrete(
272 &self,
273 data: Arc<RetainedValue>,
274 scalar_identity: Option<&'static str>,
275 ) -> Result<TraceValue, ProgramBuildError> {
276 let dtype = data.dtype();
277 let shape = data.shape().to_vec();
278 let metadata = ProgramValueMetadata::from_extents(
279 dtype,
280 shape.iter().map(|&size| {
281 tenferro_ops::ShapeExtent::Exact(tenferro_ops::dim_expr::DimExpr::Const(size))
282 }),
283 );
284 let mut spec = ProgramInputSpec::from_metadata(metadata);
285 if let Some(identity) = scalar_identity {
286 spec = spec.with_scalar_identity(identity);
287 }
288 self.input_bound(spec, data)
289 }
290
291 #[doc(hidden)]
293 pub fn value_scalar_identity(&self, value: TraceValue) -> Option<&'static str> {
294 self.inner
295 .try_borrow()
296 .ok()?
297 .builder
298 .value_metadata(value.value)
299 .ok()
300 .and_then(|metadata| metadata.scalar_identity())
301 }
302
303 pub(crate) fn input_count(&self) -> usize {
305 self.inner
306 .try_borrow()
307 .map(|inner| inner.inputs.len())
308 .unwrap_or_default()
309 }
310
311 pub(crate) fn input_bound(
313 &self,
314 spec: ProgramInputSpec,
315 data: Arc<RetainedValue>,
316 ) -> Result<TraceValue, ProgramBuildError> {
317 let key = crate::traced::next_input_key();
318 let mut inner = self.borrow_mut()?;
319 let value = inner.builder.input(spec)?;
320 inner.builder.bind_input_retained(value, data)?;
321 inner.inputs.push(TraceInput { value, key });
322 Ok(TraceValue { value })
323 }
324
325 #[doc(hidden)]
327 pub fn input_key(&self, value: TraceValue) -> Option<TensorInputKey> {
328 self.inner
329 .try_borrow()
330 .ok()?
331 .inputs
332 .iter()
333 .find(|input| input.value == value.value)
334 .map(|input| input.key.clone())
335 }
336
337 #[doc(hidden)]
339 pub fn input_index(&self, value: TraceValue) -> Option<usize> {
340 self.inner
341 .try_borrow()
342 .ok()?
343 .inputs
344 .iter()
345 .position(|input| input.value == value.value)
346 }
347
348 #[doc(hidden)]
350 pub fn inputs(&self) -> Vec<(TensorInputKey, TraceValue)> {
351 self.inner
352 .try_borrow()
353 .map(|inner| {
354 inner
355 .inputs
356 .iter()
357 .map(|input| (input.key.clone(), TraceValue { value: input.value }))
358 .collect()
359 })
360 .unwrap_or_default()
361 }
362
363 #[doc(hidden)]
365 pub fn checkpoint_value(
366 &self,
367 target: TraceValue,
368 data: Arc<RetainedValue>,
369 ) -> Result<TraceValue, ProgramBuildError> {
370 let key = crate::traced::next_input_key();
371 let shape = data.shape().to_vec();
372 let metadata = ProgramValueMetadata::from_extents(
373 data.dtype(),
374 shape.iter().map(|&size| {
375 tenferro_ops::ShapeExtent::Exact(tenferro_ops::dim_expr::DimExpr::Const(size))
376 }),
377 );
378 let mut inner = self.borrow_mut()?;
379 let input = inner
380 .builder
381 .input(ProgramInputSpec::from_metadata(metadata))?;
382 inner.builder.bind_input_retained(input, data)?;
383 inner.inputs.push(TraceInput { value: input, key });
384 inner.aliases.push(CheckpointAlias {
385 input,
386 target: target.value,
387 bound_shape: shape,
388 });
389 Ok(TraceValue { value: input })
390 }
391
392 #[doc(hidden)]
404 pub fn import_values(
405 &self,
406 request: crate::program::ProgramImport<'_>,
407 ) -> Result<Vec<TraceValue>, ProgramBuildError> {
408 let mut inner = self.borrow_mut()?;
409 let imported = inner.builder.import(request)?;
410 Ok(imported
411 .roots()
412 .iter()
413 .map(|value| TraceValue { value: *value })
414 .collect())
415 }
416
417 #[doc(hidden)]
424 pub fn derive_tensor(&self, value: TraceValue) -> crate::Result<crate::TracedTensor> {
425 crate::trace_ops::finish_derived(self, value)
426 }
427
428 #[doc(hidden)]
434 pub fn ad_snapshot(&self, outputs: &[TraceValue]) -> Result<FrozenProgram, ProgramFinishError> {
435 let inner = self.borrow_mut_for_finish()?;
436 let values = program_values(outputs);
437 inner
438 .builder
439 .snapshot_pruned_with_aliases(&values, &inner.aliases)
440 .map(|(frozen, _)| frozen)
441 }
442
443 pub(crate) fn snapshot_with_input_specs(
448 &self,
449 outputs: &[TraceValue],
450 specs: &[(TensorInputKey, ProgramInputSpec)],
451 ) -> Result<(FrozenProgram, Vec<(TensorInputKey, TraceValue)>), ProgramFinishError> {
452 let inner = self.borrow_mut_for_finish()?;
453 let values = program_values(outputs);
454 let declared: Vec<(usize, ProgramInputSpec)> = specs
455 .iter()
456 .filter_map(|(key, spec)| {
457 inner
458 .inputs
459 .iter()
460 .position(|input| input.key == *key)
461 .map(|index| (index, spec.clone()))
462 })
463 .collect();
464 let (frozen, kept) = inner
465 .builder
466 .snapshot_pruned_with_input_specs(&values, &declared)?;
467 let kept = kept
468 .into_iter()
469 .filter_map(|value| {
470 inner
471 .inputs
472 .iter()
473 .find(|input| input.value == value)
474 .map(|input| (input.key.clone(), TraceValue { value }))
475 })
476 .collect();
477 Ok((frozen, kept))
478 }
479
480 pub(crate) fn plain_snapshot_with_inputs(
482 &self,
483 outputs: &[TraceValue],
484 ) -> Result<(FrozenProgram, Vec<(TensorInputKey, TraceValue)>), ProgramFinishError> {
485 let inner = self.borrow_mut_for_finish()?;
486 let values = program_values(outputs);
487 let (frozen, kept) = inner.builder.snapshot_pruned(&values)?;
488 let kept = kept
489 .into_iter()
490 .filter_map(|value| {
491 inner
492 .inputs
493 .iter()
494 .find(|input| input.value == value)
495 .map(|input| (input.key.clone(), TraceValue { value }))
496 })
497 .collect();
498 Ok((frozen, kept))
499 }
500
501 #[doc(hidden)]
503 pub fn ad_snapshot_with_inputs(
504 &self,
505 outputs: &[TraceValue],
506 ) -> Result<(FrozenProgram, Vec<(TensorInputKey, TraceValue)>), ProgramFinishError> {
507 let inner = self.borrow_mut_for_finish()?;
508 let values = program_values(outputs);
509 let (frozen, kept) = inner
510 .builder
511 .snapshot_pruned_with_aliases(&values, &inner.aliases)?;
512 let kept = kept
513 .into_iter()
514 .filter_map(|value| {
515 inner
516 .inputs
517 .iter()
518 .find(|input| input.value == value)
519 .map(|input| (input.key.clone(), TraceValue { value }))
520 })
521 .collect();
522 Ok((frozen, kept))
523 }
524
525 pub fn finish(&self, outputs: &[TraceValue]) -> Result<TracedGraph, ProgramFinishError> {
537 let frozen = self.frozen_snapshot(outputs)?;
538 Ok(TracedGraph { frozen })
539 }
540
541 #[doc(hidden)]
543 pub fn frozen_snapshot(
544 &self,
545 outputs: &[TraceValue],
546 ) -> Result<SnapshotProgram, ProgramFinishError> {
547 let inner = self.borrow_mut_for_finish()?;
548 let values = program_values(outputs);
549 inner
550 .builder
551 .snapshot_pruned(&values)
552 .map(|(frozen, _)| frozen)
553 }
554}
555
556impl Default for TraceContext {
557 fn default() -> Self {
558 Self::new()
559 }
560}
561
562#[derive(Clone, Copy)]
564pub struct TraceValue {
565 value: ProgramValue,
566}
567
568impl TraceValue {
569 #[doc(hidden)]
571 pub fn program_value(self) -> ProgramValue {
572 self.value
573 }
574}
575
576impl fmt::Debug for TraceValue {
577 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
578 formatter.write_str("TraceValue(<opaque>)")
579 }
580}
581
582#[derive(Clone)]
584pub struct TracedGraph {
585 frozen: FrozenProgram,
586}
587
588impl TracedGraph {
589 pub fn program(&self) -> &SemanticProgram {
591 &self.frozen.program
592 }
593
594 pub fn bindings(&self) -> &ProgramBindings {
596 &self.frozen.bindings
597 }
598
599 pub(crate) fn frozen(&self) -> &FrozenProgram {
600 &self.frozen
601 }
602}
603
604impl fmt::Debug for TracedGraph {
605 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
606 formatter
607 .debug_struct("TracedGraph")
608 .field("inputs", &self.program().inputs().len())
609 .field("outputs", &self.program().outputs().len())
610 .field("bindings", &self.bindings().len())
611 .field(
612 "semantic_fingerprint",
613 &self.program().semantic_fingerprint(),
614 )
615 .finish()
616 }
617}
618
619fn program_values(values: &[TraceValue]) -> Vec<ProgramValue> {
620 values.iter().map(|value| value.value).collect()
621}
622
623fn trace_values(values: Box<[ProgramValue]>) -> Box<[TraceValue]> {
624 values
625 .into_vec()
626 .into_iter()
627 .map(|value| TraceValue { value })
628 .collect()
629}
630
631#[cfg(test)]
632mod tests {
633 use tenferro_ops::dim_expr::DimExpr;
634 use tenferro_ops::ShapeExtent;
635 use tenferro_tensor::DType;
636
637 use super::*;
638
639 #[test]
640 fn unknown_input_extents_keep_ordered_semantic_inputs() {
641 let spec = || {
642 ProgramInputSpec::from_metadata(ProgramValueMetadata::from_extents(
643 DType::F64,
644 [ShapeExtent::Unknown],
645 ))
646 };
647 use crate::TraceContext;
648 let context = TraceContext::new();
649 let first = context.input(spec()).unwrap();
650 let second = context.input(spec()).unwrap();
651 let graph = context.finish(&[first, second]).unwrap();
652
653 assert_eq!(graph.program().inputs().len(), 2);
654 for input in graph.program().inputs() {
655 assert_eq!(
656 graph.program().value_metadata(*input).unwrap().shape(),
657 [ShapeExtent::Unknown]
658 );
659 }
660 }
661
662 #[test]
663 fn finish_snapshots_without_consuming_the_context() {
664 let mut builder = SemanticProgramBuilder::new();
665 let input = builder
666 .input(ProgramInputSpec::new(DType::F64, [DimExpr::Const(2)]))
667 .unwrap();
668 let output = builder.add_op(CoreSemanticOp::Neg, &[input]).unwrap()[0];
669 let frozen = builder.finish(&[output]).unwrap();
670
671 let context = TraceContext::new();
672 let value = context
673 .input(ProgramInputSpec::new(DType::F64, [DimExpr::Const(2)]))
674 .unwrap();
675 let negated = context.add_op(CoreSemanticOp::Neg, &[value]).unwrap()[0];
676 let first = context.finish(&[negated]).unwrap();
677 let doubled = context.add_op(CoreSemanticOp::Neg, &[negated]).unwrap()[0];
679 let second = context.finish(&[doubled]).unwrap();
680
681 assert_eq!(first.program().operations().count(), 1);
682 assert_eq!(second.program().operations().count(), 2);
683 assert_eq!(
684 first
685 .program()
686 .value_metadata(first.program().outputs()[0])
687 .unwrap(),
688 frozen
689 .program
690 .value_metadata(frozen.program.outputs()[0])
691 .unwrap()
692 );
693 }
694
695 #[test]
696 fn foreign_values_are_rejected_by_metadata_and_ops() {
697 let first = TraceContext::new();
698 let second = TraceContext::new();
699 let value = first
700 .input(ProgramInputSpec::new(DType::F64, [DimExpr::Const(1)]))
701 .unwrap();
702 let foreign = second
703 .input(ProgramInputSpec::new(DType::F64, [DimExpr::Const(1)]))
704 .unwrap();
705
706 assert!(first.value_metadata(value).is_ok());
707 assert!(matches!(
708 first.value_metadata(foreign),
709 Err(ProgramBuildError::ForeignValue)
710 ));
711 assert!(matches!(
712 first.add_op(CoreSemanticOp::Neg, &[foreign]),
713 Err(ProgramBuildError::ForeignValue)
714 ));
715 }
716
717 #[test]
718 fn formatting_a_context_inside_the_cache_callback_does_not_panic() {
719 let context = TraceContext::new();
720 let rendered = context
721 .with_extension_caches(|_| format!("{context:?}"))
722 .unwrap();
723 assert!(rendered.contains("TraceContext"));
724 }
725
726 #[test]
727 fn extension_caches_are_reached_through_a_scoped_borrow() {
728 let context = TraceContext::new();
729 let entries = context
730 .with_extension_caches(|caches| caches.len())
731 .unwrap();
732 assert_eq!(entries, 0);
733 }
734}