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, ErrorPhase, ExtensionModule, Result};
9use tenferro_tensor::{BackendSession, Tensor, TensorRead, TensorValue};
10
11use crate::eager::{eager_grad_recording_enabled, record_eager_outputs, EagerRuntime, EagerTensor};
12
13pub use tenferro_runtime::extension::{
14 apply, ExtensionCacheKey, ExtensionCacheLimits, ExtensionCacheSelector, ExtensionCacheStore,
15 ExtensionExecutionContext, ExtensionFamilyId, ExtensionOp,
16};
17
18#[must_use]
44pub fn adopt_untracked_eager_value(ctx: Arc<EagerRuntime>, value: TensorValue) -> EagerTensor {
45 EagerTensor::new_untracked_value_result(ctx, value)
46}
47
48pub fn apply_eager(op: Arc<dyn ExtensionOp>, inputs: &[&EagerTensor]) -> Result<Vec<EagerTensor>> {
74 let ctx = validate_eager_extension_inputs(op.as_ref(), inputs)?;
75 let std_op = StdTensorOp::Extension(op);
76 let input_reads: Vec<_> = inputs.iter().map(|tensor| tensor.tensor_read()).collect();
77 let outputs = ctx.exec_outputs_read(&std_op, &input_reads)?;
78 finish_eager_extension_outputs(ctx, std_op, inputs, outputs)
79}
80
81pub fn apply_eager_with_extension_session(
94 op: Arc<dyn ExtensionOp>,
95 inputs: &[&EagerTensor],
96 module: Arc<dyn ExtensionModule>,
97 execute: impl FnOnce(
98 &dyn ExtensionOp,
99 &[TensorRead<'_>],
100 &mut ExtensionExecutionContext<'_, dyn BackendSession + '_>,
101 ) -> tenferro_tensor::Result<Vec<Tensor>>
102 + Send,
103) -> Result<Vec<EagerTensor>> {
104 let ctx = validate_eager_extension_inputs(op.as_ref(), inputs)?;
105 ctx.install_extension_module(module)?;
106 let input_reads: Vec<_> = inputs.iter().map(|tensor| tensor.tensor_read()).collect();
107 let outputs = ctx.with_extension_execution_context(|extension_ctx| {
108 execute(op.as_ref(), &input_reads, extension_ctx)
109 })??;
110 finish_eager_extension_outputs(ctx, StdTensorOp::Extension(op), inputs, outputs)
111}
112
113fn validate_eager_extension_inputs(
114 op: &dyn ExtensionOp,
115 inputs: &[&EagerTensor],
116) -> Result<Arc<EagerRuntime>> {
117 let Some(first) = inputs.first() else {
118 return Err(Error::invalid_argument(
119 "extension::apply_eager",
120 ErrorPhase::Execution,
121 "inputs",
122 "at least one input tensor is required",
123 ));
124 };
125 if inputs.len() != op.input_count() {
126 return Err(Error::invalid_argument(
127 "extension::apply_eager",
128 ErrorPhase::Execution,
129 "inputs",
130 format!(
131 "op family {:?} expects {} inputs, got {}",
132 op.family_id(),
133 op.input_count(),
134 inputs.len()
135 ),
136 ));
137 }
138
139 let ctx = Arc::clone(&first.ctx);
140 for tensor in inputs.iter().skip(1) {
141 if !first.same_context(tensor) {
142 return Err(Error::ContextMismatch {
143 lhs: first.ctx_id(),
144 rhs: tensor.ctx_id(),
145 });
146 }
147 }
148 Ok(ctx)
149}
150
151fn finish_eager_extension_outputs(
152 ctx: Arc<EagerRuntime>,
153 op: StdTensorOp,
154 inputs: &[&EagerTensor],
155 outputs: Vec<Tensor>,
156) -> Result<Vec<EagerTensor>> {
157 if outputs.len() != op.output_count() {
158 return Err(Error::Internal(format!(
159 "expected {} eager outputs for {:?}, got {}",
160 op.output_count(),
161 op,
162 outputs.len()
163 )));
164 }
165
166 if !eager_grad_recording_enabled() {
167 return outputs
168 .into_iter()
169 .map(|output| EagerTensor::new_untracked_result(Arc::clone(&ctx), output))
170 .collect();
171 }
172
173 let outputs: Vec<Arc<Tensor>> = outputs.into_iter().map(Arc::new).collect();
174 let recorded = record_eager_outputs(&op, &outputs, inputs)?;
175 if recorded.traces.len() != outputs.len() {
176 return Err(Error::Internal(format!(
177 "expected {} eager traces for {:?}, got {}",
178 outputs.len(),
179 op,
180 recorded.traces.len()
181 )));
182 }
183 let mut metadata_scopes = vec![Arc::clone(&recorded.metadata_scope)];
184 for input in inputs {
185 for scope in &input.metadata_scopes {
186 push_metadata_scope(&mut metadata_scopes, Arc::clone(scope));
187 }
188 }
189
190 recorded
191 .traces
192 .into_iter()
193 .zip(recorded.semantic_traces)
194 .zip(outputs)
195 .map(|((trace, semantic_trace), output)| {
196 if trace.requires_grad {
197 EagerTensor::new_result_arc_with_semantic_trace(
198 Arc::clone(&ctx),
199 trace.key,
200 output,
201 trace.requires_grad,
202 trace.trace,
203 semantic_trace,
204 metadata_scopes.clone(),
205 )
206 } else {
207 EagerTensor::new_unregistered_result_arc_with_semantic_trace(
208 Arc::clone(&ctx),
209 trace.key,
210 output,
211 trace.requires_grad,
212 trace.trace,
213 semantic_trace,
214 metadata_scopes.clone(),
215 )
216 }
217 })
218 .collect()
219}
220
221pub fn apply_standard_op(op: StdTensorOp, inputs: &[&EagerTensor]) -> Result<EagerTensor> {
235 if matches!(op, StdTensorOp::Extension(_)) {
236 return Err(Error::invalid_argument(
237 "extension::apply_standard_op",
238 ErrorPhase::Execution,
239 "op",
240 "Extension ops must be passed to apply_eager",
241 ));
242 }
243 EagerTensor::nary_op(inputs, op)
244}