1use std::sync::Arc;
4
5use computegraph::GraphOperation;
6use tenferro_ops::std_tensor_op::StdTensorOp;
7use tenferro_runtime::ad_support::push_metadata_scope;
8use tenferro_runtime::{Error, Result};
9use tenferro_tensor::{Tensor, TensorValue};
10
11use crate::eager::{eager_grad_recording_enabled, record_eager_outputs, EagerRuntime, EagerTensor};
12
13pub use tenferro_ops::ext_op::{
14 ExtensionLinearTransposeRule, ExtensionLinearizeRule, ExtensionPrimalVjpRule,
15 ExtensionRegistryError, ExtensionRuleRole, ExtensionRuleSet, HostReference,
16};
17pub use tenferro_runtime::extension::{
18 apply, ExtensionCacheKey, ExtensionCacheLimits, ExtensionCacheSelector, ExtensionCacheStore,
19 ExtensionExecutionContext, ExtensionExecutor, ExtensionFamilyId, ExtensionOp,
20 ExtensionRegistry, ExtensionRuntime, ExtensionRuntimeRegistryError,
21};
22
23#[must_use]
48pub fn adopt_untracked_eager_value(ctx: Arc<EagerRuntime>, value: TensorValue) -> EagerTensor {
49 EagerTensor::new_untracked_value_result(ctx, value)
50}
51
52pub fn apply_eager(op: Arc<dyn ExtensionOp>, inputs: &[&EagerTensor]) -> Result<Vec<EagerTensor>> {
71 let Some(first) = inputs.first() else {
72 return Err(Error::Internal(
73 "extension::apply_eager requires at least one input tensor".to_string(),
74 ));
75 };
76 if inputs.len() != op.input_count() {
77 return Err(Error::Internal(format!(
78 "extension::apply_eager: op family {:?} expects {} inputs, got {}",
79 op.family_id(),
80 op.input_count(),
81 inputs.len()
82 )));
83 }
84
85 let ctx = Arc::clone(&first.ctx);
86 for tensor in inputs.iter().skip(1) {
87 if !first.same_context(tensor) {
88 return Err(Error::ContextMismatch {
89 lhs: first.ctx_id(),
90 rhs: tensor.ctx_id(),
91 });
92 }
93 }
94
95 let op = StdTensorOp::Extension(op);
96 let input_reads: Vec<_> = inputs.iter().map(|tensor| tensor.tensor_read()).collect();
97 let outputs = ctx.exec_outputs_read(&op, &input_reads)?;
98 if outputs.len() != op.output_count() {
99 return Err(Error::Internal(format!(
100 "expected {} eager outputs for {:?}, got {}",
101 op.output_count(),
102 op,
103 outputs.len()
104 )));
105 }
106
107 if !eager_grad_recording_enabled() || !inputs.iter().any(|input| input.requires_grad) {
108 return outputs
109 .into_iter()
110 .map(|output| EagerTensor::new_untracked_result(Arc::clone(&ctx), output))
111 .collect();
112 }
113
114 let outputs: Vec<Arc<Tensor>> = outputs.into_iter().map(Arc::new).collect();
115 let recorded = record_eager_outputs(&op, &outputs, inputs)?;
116 if recorded.traces.len() != outputs.len() {
117 return Err(Error::Internal(format!(
118 "expected {} eager traces for {:?}, got {}",
119 outputs.len(),
120 op,
121 recorded.traces.len()
122 )));
123 }
124 let mut metadata_scopes = vec![Arc::clone(&recorded.metadata_scope)];
125 for input in inputs {
126 for scope in &input.metadata_scopes {
127 push_metadata_scope(&mut metadata_scopes, Arc::clone(scope));
128 }
129 }
130
131 recorded
132 .traces
133 .into_iter()
134 .zip(outputs)
135 .map(|(trace, output)| {
136 EagerTensor::new_result(
137 Arc::clone(&ctx),
138 trace.key,
139 output.as_ref().clone(),
140 trace.requires_grad,
141 trace.trace,
142 metadata_scopes.clone(),
143 )
144 })
145 .collect()
146}
147
148pub fn apply_standard_op(op: StdTensorOp, inputs: &[&EagerTensor]) -> Result<EagerTensor> {
153 if matches!(op, StdTensorOp::Extension(_)) {
154 return Err(Error::Internal(
155 "extension::apply_standard_op does not accept Extension ops".into(),
156 ));
157 }
158 EagerTensor::nary_op(inputs, op)
159}