1use std::sync::Arc;
20
21use computegraph::graph::GraphBuilder;
22use computegraph::types::{OperationRole, ValueRef};
23use computegraph::GraphOperation;
24use tenferro_ops::dim_expr::DimExpr;
25use tenferro_ops::std_tensor_op::StdTensorOp;
26use tenferro_ops::SymDim;
27
28use crate::checkpoint::CheckpointNode;
29use crate::error::{Error, ErrorPhase, Result};
30use crate::metadata::{
31 register_scoped_graph_analysis, registered_meta, MetadataScopeChain, RegisteredGraphAnalysis,
32};
33use crate::shape_constraint::{ConstraintScopeChain, ScopedShapeConstraint, ShapeConstraintScope};
34use crate::shape_infer::{infer_extension_output_meta_with_constraints, InferredExtensionMeta};
35use crate::traced::{merge_traced_inputs_map, next_traced_id, TracedTensor};
36
37type ExpandedOutputMetas = Vec<(tenferro_tensor::DType, Vec<SymDim>)>;
38
39pub use crate::compiler::CompilerOptions;
40#[doc(hidden)]
41pub use crate::shape_infer::{
42 infer_output_dtype, infer_output_extents, infer_output_shapes, promote_dtype,
43 promote_dtype_div_like, promote_dtype_for_binary_op, promote_dtypes,
44};
45pub use tenferro_ops::ext_op::ExtensionOp;
46pub use tenferro_ops::ExtensionFamilyId;
47
48pub use crate::extension_cache::{
49 ExtensionCacheKey, ExtensionCacheLimits, ExtensionCacheSelector, ExtensionCacheStore,
50};
51pub use crate::extension_execution_context::ExtensionExecutionContext;
52
53pub fn apply(op: Arc<dyn ExtensionOp>, inputs: &[&TracedTensor]) -> Result<Vec<TracedTensor>> {
111 if inputs.len() != op.input_count() {
112 return Err(Error::invalid_argument(
113 "extension::apply",
114 ErrorPhase::GraphBuild,
115 "inputs",
116 format!(
117 "op family {:?} expects {} inputs, got {}",
118 op.family_id(),
119 op.input_count(),
120 inputs.len()
121 ),
122 ));
123 }
124
125 let mut builder = GraphBuilder::<StdTensorOp>::new();
128 for input in inputs {
129 builder.add_parent(input.graph.clone());
130 }
131 let op_inputs: Vec<ValueRef<StdTensorOp>> = inputs
132 .iter()
133 .map(|t| ValueRef::External(t.graph.values()[t.val].key.clone()))
134 .collect();
135 let carrier = StdTensorOp::Extension(op.clone());
136 let outputs = builder.add_operation(carrier, op_inputs, OperationRole::Primary);
137 builder.set_outputs(outputs.clone());
138 let graph = Arc::new(builder.build());
139 let analysis = register_scoped_graph_analysis(graph.as_ref(), std::iter::empty())?;
140 let output_metas = outputs
141 .iter()
142 .map(|&output| {
143 let meta = registered_meta(&graph.values()[output].key)?;
144 let shape = meta.bound_shape().ok_or_else(|| {
145 Error::invalid_argument(
146 "extension::apply",
147 ErrorPhase::Compile,
148 "output_metadata",
149 format!(
150 "extension family {:?} produced unknown output shape metadata",
151 op.family_id()
152 ),
153 )
154 })?;
155 Ok((meta.dtype, shape))
156 })
157 .collect::<Result<Vec<_>>>()?;
158 traced_outputs_from_analysis(inputs, graph, &outputs, output_metas, analysis)
159}
160
161#[doc(hidden)]
186pub fn apply_standard_op(op: StdTensorOp, inputs: &[&TracedTensor]) -> Result<Vec<TracedTensor>> {
187 if matches!(op, StdTensorOp::Extension(_)) {
188 return Err(Error::invalid_argument(
189 "extension::apply_standard_op",
190 ErrorPhase::GraphBuild,
191 "op",
192 "Extension ops must be passed to extension::apply",
193 ));
194 }
195 let expected = op.input_count();
196 if inputs.len() != expected {
197 return Err(Error::invalid_argument(
198 "extension::apply_standard_op",
199 ErrorPhase::GraphBuild,
200 "inputs",
201 format!("op expects {expected} inputs, got {}", inputs.len()),
202 ));
203 }
204
205 let mut builder = GraphBuilder::<StdTensorOp>::new();
206 for input in inputs {
207 builder.add_parent(input.graph.clone());
208 }
209 let op_inputs: Vec<ValueRef<StdTensorOp>> = inputs
210 .iter()
211 .map(|t| ValueRef::External(t.graph.values()[t.val].key.clone()))
212 .collect();
213 let outputs = builder.add_operation(op, op_inputs, OperationRole::Primary);
214 builder.set_outputs(outputs.clone());
215 let graph = Arc::new(builder.build());
216 let analysis = register_scoped_graph_analysis(graph.as_ref(), std::iter::empty())?;
217 let output_metas = outputs
218 .iter()
219 .map(|&output| {
220 let meta = registered_meta(&graph.values()[output].key)?;
221 let shape = meta.bound_shape().ok_or_else(|| {
222 Error::invalid_argument(
223 "extension::apply_standard_op",
224 ErrorPhase::Compile,
225 "output_metadata",
226 "standard op produced unknown output shape metadata",
227 )
228 })?;
229 Ok((meta.dtype, shape))
230 })
231 .collect::<Result<Vec<_>>>()?;
232 traced_outputs_from_analysis(inputs, graph, &outputs, output_metas, analysis)
233}
234
235#[doc(hidden)]
278pub fn attach_expanded_shape_contract(
279 op: &dyn ExtensionOp,
280 inputs: &[&TracedTensor],
281 output: TracedTensor,
282) -> Result<TracedTensor> {
283 if op.output_count() != 1 {
284 return Err(Error::invalid_argument(
285 "extension::attach_expanded_shape_contract",
286 ErrorPhase::GraphBuild,
287 "outputs",
288 format!(
289 "extension family {:?} contract expects {} outputs, got one expanded output",
290 op.family_id(),
291 op.output_count(),
292 ),
293 ));
294 }
295 let (_, inferred) = infer_expanded_shape_contract(op, inputs)?;
296 attach_inferred_expanded_shape_contract(inputs, vec![output], inferred)?
297 .into_iter()
298 .next()
299 .ok_or_else(|| Error::Internal("expanded shape contract returned no output".into()))
300}
301
302fn infer_expanded_shape_contract(
303 op: &dyn ExtensionOp,
304 inputs: &[&TracedTensor],
305) -> Result<(ExpandedOutputMetas, InferredExtensionMeta)> {
306 if inputs.len() != op.input_count() {
307 return Err(Error::invalid_argument(
308 "extension::infer_expanded_shape_contract",
309 ErrorPhase::GraphBuild,
310 "inputs",
311 format!(
312 "extension family {:?} contract expects {} inputs, got {}",
313 op.family_id(),
314 op.input_count(),
315 inputs.len()
316 ),
317 ));
318 }
319 let input_dtypes: Vec<_> = inputs.iter().map(|input| input.dtype).collect();
320 let input_shapes: Vec<_> = inputs
321 .iter()
322 .enumerate()
323 .map(|(input_idx, input)| DimExpr::input_shape(input_idx, input.rank))
324 .collect();
325 let input_shape_refs: Vec<_> = input_shapes.iter().map(Vec::as_slice).collect();
326 let inferred =
327 infer_extension_output_meta_with_constraints(op, &input_dtypes, &input_shape_refs)?;
328 let input_sym_shapes = inputs
329 .iter()
330 .map(|input| {
331 (0..input.rank)
332 .map(|axis| input.axis_sym_dim(axis))
333 .collect::<Result<Vec<_>>>()
334 })
335 .collect::<Result<Vec<_>>>()?;
336 let input_sym_shape_refs = input_sym_shapes
337 .iter()
338 .map(Vec::as_slice)
339 .collect::<Vec<_>>();
340 let output_metas = inferred
341 .output_metas
342 .iter()
343 .map(|(dtype, shape)| {
344 (
345 *dtype,
346 shape
347 .iter()
348 .map(|dim| SymDim::from_dim_expr(dim, &input_sym_shape_refs))
349 .collect(),
350 )
351 })
352 .collect();
353 Ok((output_metas, inferred))
354}
355
356fn attach_inferred_expanded_shape_contract(
357 inputs: &[&TracedTensor],
358 mut outputs: Vec<TracedTensor>,
359 inferred: InferredExtensionMeta,
360) -> Result<Vec<TracedTensor>> {
361 if inferred.output_metas.len() != outputs.len() {
362 return Err(Error::invalid_argument(
363 "extension::attach_expanded_shape_contract",
364 ErrorPhase::GraphBuild,
365 "outputs",
366 format!(
367 "extension contract inferred {} outputs, but expanded graph produced {}",
368 inferred.output_metas.len(),
369 outputs.len()
370 ),
371 ));
372 }
373 for (output, (dtype, local_shape)) in outputs.iter().zip(inferred.output_metas.iter()) {
374 if output.dtype != *dtype || output.rank != local_shape.len() {
375 return Err(Error::invalid_argument(
376 "extension::attach_expanded_shape_contract",
377 ErrorPhase::GraphBuild,
378 "outputs",
379 format!(
380 "extension contract inferred output {:?} rank {}, but expanded output is {:?} rank {}",
381 dtype,
382 local_shape.len(),
383 output.dtype,
384 output.rank
385 ),
386 ));
387 }
388 }
389 if inferred.constraints.is_empty() {
390 return Ok(outputs);
391 }
392
393 let origins = outputs
394 .iter()
395 .map(|output| output.graph.values()[output.val].key.clone())
396 .collect::<Vec<_>>();
397 let input_keys = inputs
398 .iter()
399 .map(|input| input.graph.values()[input.val].key.clone())
400 .collect::<Vec<_>>();
401 let constraints = inferred
402 .constraints
403 .into_iter()
404 .map(|local| ScopedShapeConstraint {
405 origins: origins.clone(),
406 inputs: input_keys.clone(),
407 local,
408 })
409 .collect();
410 let scope = Arc::new(ShapeConstraintScope::new(constraints));
411 for output in &mut outputs {
412 output.constraint_scopes =
413 ConstraintScopeChain::with_scope(Arc::clone(&scope), [&output.constraint_scopes]);
414 }
415 Ok(outputs)
416}
417
418#[doc(hidden)]
465pub fn apply_expanded_graph_with_shape_contract(
466 op: &dyn ExtensionOp,
467 inputs: &[&TracedTensor],
468 build: impl FnOnce(&mut GraphBuilder<StdTensorOp>, &[ValueRef<StdTensorOp>]) -> Result<Vec<usize>>,
469) -> Result<Vec<TracedTensor>> {
470 let (output_metas, inferred) = infer_expanded_shape_contract(op, inputs)?;
471 let outputs = apply_expanded_graph(inputs, output_metas, build)?;
472 attach_inferred_expanded_shape_contract(inputs, outputs, inferred)
473}
474
475pub fn apply_expanded_graph(
488 inputs: &[&TracedTensor],
489 output_metas: Vec<(tenferro_tensor::DType, Vec<SymDim>)>,
490 build: impl FnOnce(&mut GraphBuilder<StdTensorOp>, &[ValueRef<StdTensorOp>]) -> Result<Vec<usize>>,
491) -> Result<Vec<TracedTensor>> {
492 let mut builder = GraphBuilder::<StdTensorOp>::new();
493 for input in inputs {
494 builder.add_parent(input.graph.clone());
495 }
496 let op_inputs: Vec<ValueRef<StdTensorOp>> = inputs
497 .iter()
498 .map(|t| ValueRef::External(t.graph.values()[t.val].key.clone()))
499 .collect();
500 let outputs = build(&mut builder, &op_inputs)?;
501 if outputs.len() != output_metas.len() {
502 return Err(Error::invalid_argument(
503 "extension::apply_expanded_graph",
504 ErrorPhase::GraphBuild,
505 "outputs",
506 format!(
507 "extension expanded graph returned {} outputs for {} output metadata entries",
508 outputs.len(),
509 output_metas.len()
510 ),
511 ));
512 }
513 builder.set_outputs(outputs.clone());
514 let graph = Arc::new(builder.build());
515 let analysis = register_scoped_graph_analysis(graph.as_ref(), std::iter::empty())?;
516 traced_outputs_from_analysis(inputs, graph, &outputs, output_metas, analysis)
517}
518
519fn traced_outputs_from_analysis(
520 inputs: &[&TracedTensor],
521 graph: Arc<computegraph::graph::Graph<StdTensorOp>>,
522 outputs: &[usize],
523 output_metas: Vec<(tenferro_tensor::DType, Vec<SymDim>)>,
524 analysis: RegisteredGraphAnalysis,
525) -> Result<Vec<TracedTensor>> {
526 let metadata_scope = Arc::new(analysis.metadata);
527 let constraint_scope = Arc::new(analysis.constraints);
528
529 let merged_map = merge_traced_inputs_map(inputs.iter().copied());
530 let mut extra_roots = Vec::new();
531 let mut checkpoint_chain = None;
532 let metadata_scopes = MetadataScopeChain::with_scope(
533 Arc::clone(&metadata_scope),
534 inputs.iter().map(|input| &input.metadata_scopes),
535 );
536 let constraint_scopes = if constraint_scope.is_empty() {
537 ConstraintScopeChain::merge(inputs.iter().map(|input| &input.constraint_scopes))
538 } else {
539 ConstraintScopeChain::with_scope(
540 constraint_scope,
541 inputs.iter().map(|input| &input.constraint_scopes),
542 )
543 };
544 for input in inputs {
545 extra_roots.extend(input.extra_roots.iter().cloned());
546 checkpoint_chain =
547 CheckpointNode::merge_chains(checkpoint_chain, input.checkpoint_chain.clone());
548 }
549 let all_inputs_concrete = inputs.iter().all(|t| t.shape_hint.is_some());
550 Ok(outputs
551 .iter()
552 .zip(output_metas)
553 .map(|(&val, (dtype, shape))| {
554 let shape_hint = if all_inputs_concrete {
555 Some(shape.clone())
556 } else {
557 None
558 };
559 TracedTensor {
560 id: next_traced_id(),
561 rank: shape.len(),
562 dtype,
563 graph: graph.clone(),
564 val,
565 data: None,
566 shape_hint,
567 inputs_map: merged_map.clone(),
568 extra_roots: extra_roots.clone(),
569 checkpoint_chain: checkpoint_chain.clone(),
570 metadata_scopes: metadata_scopes.clone(),
571 constraint_scopes: constraint_scopes.clone(),
572 }
573 })
574 .collect())
575}
576
577#[cfg(test)]
578mod tests;