1use std::collections::{HashMap, HashSet};
4
5use onnx_runtime_ir::{
6 DataType, Dim, Graph, Node, SymbolConstraints, SymbolId, ValueId, WeightRef,
7};
8
9use crate::context::{MergePolicy, NodeIo, SymbolInterner, TypeInfo, TypedShape, merge_shapes};
10use crate::dim_expr::DimExpr;
11use crate::error::ShapeInferError;
12use crate::registry::InferenceRegistry;
13use crate::report::InferenceReport;
14use crate::shape_data::ShapeData;
15
16type ScopeBindings = HashMap<String, Option<NodeIo>>;
17
18struct ScopedInference {
19 report: InferenceReport,
20 parent_symbols: HashMap<SymbolId, SymbolId>,
21}
22
23const ANON_SYMBOL_FLOOR: u32 = 0x8000_0000;
30
31impl InferenceRegistry {
32 pub fn infer_graph(
43 &self,
44 graph: &mut Graph,
45 opset_imports: &HashMap<String, u64>,
46 policy: MergePolicy,
47 ) -> Result<InferenceReport, ShapeInferError> {
48 self.infer_graph_scoped(graph, opset_imports, policy, &HashMap::new())
49 .map(|result| result.report)
50 }
51
52 fn infer_graph_scoped(
53 &self,
54 graph: &mut Graph,
55 opset_imports: &HashMap<String, u64>,
56 policy: MergePolicy,
57 outer_scope: &ScopeBindings,
58 ) -> Result<ScopedInference, ShapeInferError> {
59 let mut interner = SymbolInterner::new(seed_next_symbol(graph));
60 let (imported_scope, parent_symbols) = import_scope(graph, outer_scope, &mut interner);
61
62 let order = graph
63 .topological_order()
64 .map_err(|_| ShapeInferError::CycleDetected)?;
65
66 let mut types: HashMap<ValueId, TypeInfo> = HashMap::new();
67 let mut shape_data: HashMap<ValueId, ShapeData> = HashMap::new();
68
69 seed_sources(graph, &mut types, &mut shape_data);
70 bind_captures(graph, &imported_scope, &mut types, &mut shape_data);
71
72 let declared_out: HashMap<ValueId, Vec<Dim>> = graph
74 .outputs
75 .iter()
76 .filter_map(|&vid| graph.try_value(vid).map(|v| (vid, v.shape.clone())))
77 .collect();
78
79 let mut child_scope = None;
80 let mut scope_sources_added = false;
81 let mut pending_scope_values = Vec::new();
82 let mut remaining_subgraph_nodes = graph
83 .subgraphs
84 .keys()
85 .map(|(owner, _)| *owner)
86 .collect::<HashSet<_>>()
87 .len();
88
89 for nid in order {
91 let node = graph.node(nid).clone();
92 let mut child_keys: Vec<_> = graph
93 .subgraphs
94 .keys()
95 .filter(|(owner, _)| *owner == nid)
96 .cloned()
97 .collect();
98 child_keys.sort_by(|left, right| left.1.cmp(&right.1));
99 let mut subgraph_results = HashMap::new();
100 if !child_keys.is_empty() {
101 let scope = child_scope.get_or_insert_with(|| imported_scope.clone());
102 if !scope_sources_added {
103 let formal_inputs: HashSet<_> = graph.inputs.iter().copied().collect();
104 let source_values: Vec<_> = graph
105 .values
106 .iter()
107 .filter(|(vid, _)| {
108 formal_inputs.contains(vid) || graph.initializers.contains_key(vid)
109 })
110 .map(|(vid, _)| vid)
111 .collect();
112 extend_visible_scope(
113 graph,
114 &types,
115 &shape_data,
116 scope,
117 source_values,
118 &mut interner,
119 );
120 scope_sources_added = true;
121 }
122 extend_visible_scope(
123 graph,
124 &types,
125 &shape_data,
126 scope,
127 pending_scope_values.drain(..),
128 &mut interner,
129 );
130
131 for key in child_keys {
132 let subgraph =
133 graph
134 .subgraphs
135 .get_mut(&key)
136 .ok_or_else(|| ShapeInferError::Invalid {
137 op: node.op_type.clone(),
138 detail: format!("subgraph attribute `{}` disappeared", key.1),
139 })?;
140 let result = self.infer_graph_scoped(subgraph, opset_imports, policy, scope)?;
141 subgraph_results.insert(key.1, result);
142 }
143 remaining_subgraph_nodes -= 1;
144 }
145
146 if is_standard_if(&node) {
147 if let Some(outputs) =
148 infer_if_outputs(graph, &node, &subgraph_results, &mut interner)?
149 {
150 for (slot, output) in node.outputs.iter().zip(outputs) {
151 match output {
152 IfOutput::Typed(type_info) => {
153 types.insert(*slot, type_info);
154 }
155 IfOutput::UnknownRank(element_type) => {
156 types.remove(slot);
157 shape_data.remove(slot);
158 let value = graph.value_mut(*slot);
159 value.dtype = element_type;
160 graph.mark_value_type_known(*slot);
161 graph.mark_value_shape_unknown(*slot);
162 }
163 IfOutput::Unresolved => {}
164 }
165 }
166 }
167 if remaining_subgraph_nodes > 0 {
168 pending_scope_values.extend(node.outputs.iter().copied());
169 }
170 continue;
171 }
172
173 let inputs = gather_inputs(&node, &types, &shape_data);
174 let outputs = self.infer_node(&node, opset_imports, inputs, policy, &mut interner)?;
175 for (slot, io) in node.outputs.iter().zip(outputs) {
176 if let Some(ti) = io.type_info {
177 types.insert(*slot, ti);
178 }
179 if let Some(sd) = io.shape_data
180 && sd.within_bounds()
181 {
182 shape_data.insert(*slot, sd);
183 }
184 }
185 if remaining_subgraph_nodes > 0 {
186 pending_scope_values.extend(node.outputs.iter().copied());
187 }
188 }
189
190 for (&vid, declared) in &declared_out {
192 if let Some(ti) = types.get(&vid) {
193 let merged = merge_shapes(vid, &ti.shape, declared, policy)?;
194 let dtype = ti.dtype;
195 types.insert(vid, TypeInfo::new(dtype, merged));
196 }
197 }
198
199 let mut resolved = Vec::new();
201 for (&vid, ti) in &types {
202 if graph.try_value(vid).is_none() {
203 continue;
204 }
205 let dims: Vec<Dim> = ti.shape.iter().map(|d| interner.lower(d)).collect();
206 let value = graph.value_mut(vid);
207 value.shape = dims;
208 value.dtype = ti.dtype;
209 resolved.push(vid);
210 }
211
212 for &sym in interner.fresh_symbols() {
214 graph
215 .symbol_constraints
216 .entry(sym)
217 .or_insert_with(|| SymbolConstraints::new(sym, None));
218 }
219
220 let unresolved: Vec<ValueId> = graph
221 .values
222 .keys()
223 .filter(|vid| !types.contains_key(vid))
224 .collect();
225
226 Ok(ScopedInference {
227 report: InferenceReport {
228 total_values: graph.num_values(),
229 fresh_symbols: interner.fresh_symbols().len(),
230 resolved,
231 unresolved,
232 },
233 parent_symbols,
234 })
235 }
236}
237
238fn is_standard_if(node: &Node) -> bool {
239 node.op_type == "If" && node.is_default_domain()
240}
241
242enum IfOutput {
243 Typed(TypeInfo),
244 UnknownRank(DataType),
245 Unresolved,
246}
247
248fn infer_if_outputs(
249 graph: &Graph,
250 node: &Node,
251 subgraph_results: &HashMap<String, ScopedInference>,
252 interner: &mut SymbolInterner,
253) -> Result<Option<Vec<IfOutput>>, ShapeInferError> {
254 let then_key = (node.id, "then_branch".to_string());
255 let else_key = (node.id, "else_branch".to_string());
256 let Some(then_branch) = graph.subgraphs.get(&then_key) else {
257 return Ok(None);
258 };
259 let Some(else_branch) = graph.subgraphs.get(&else_key) else {
260 return Ok(None);
261 };
262 let Some(then_result) = subgraph_results.get("then_branch") else {
263 return Ok(None);
264 };
265 let Some(else_result) = subgraph_results.get("else_branch") else {
266 return Ok(None);
267 };
268 let then_resolved: HashSet<_> = then_result.report.resolved.iter().copied().collect();
269 let else_resolved: HashSet<_> = else_result.report.resolved.iter().copied().collect();
270
271 let paired_outputs = node
272 .outputs
273 .len()
274 .min(then_branch.outputs.len())
275 .min(else_branch.outputs.len());
276 let mut outputs = Vec::with_capacity(node.outputs.len());
277 for (&then_id, &else_id) in then_branch
278 .outputs
279 .iter()
280 .zip(&else_branch.outputs)
281 .take(paired_outputs)
282 {
283 if !branch_output_is_resolved(then_branch, then_id, &then_resolved)
284 || !branch_output_is_resolved(else_branch, else_id, &else_resolved)
285 {
286 outputs.push(IfOutput::Unresolved);
287 continue;
288 }
289 let then_value =
290 then_branch
291 .try_value(then_id)
292 .ok_or_else(|| ShapeInferError::Invalid {
293 op: "If".to_string(),
294 detail: format!("then_branch output {then_id:?} is not live"),
295 })?;
296 let else_value =
297 else_branch
298 .try_value(else_id)
299 .ok_or_else(|| ShapeInferError::Invalid {
300 op: "If".to_string(),
301 detail: format!("else_branch output {else_id:?} is not live"),
302 })?;
303
304 if then_value.dtype != else_value.dtype {
305 return Err(ShapeInferError::Invalid {
306 op: "If".to_string(),
307 detail: format!(
308 "branch output element types differ: {:?} != {:?}",
309 then_value.dtype, else_value.dtype
310 ),
311 });
312 }
313
314 if then_value.shape.len() != else_value.shape.len() {
315 outputs.push(IfOutput::UnknownRank(then_value.dtype));
316 continue;
317 }
318
319 let shape = then_value
320 .shape
321 .iter()
322 .zip(&else_value.shape)
323 .map(|(&then_dim, &else_dim)| match (then_dim, else_dim) {
324 (Dim::Static(then_size), Dim::Static(else_size)) if then_size == else_size => {
325 i64::try_from(then_size)
326 .map(DimExpr::constant)
327 .unwrap_or_else(|_| interner.fresh_dim())
328 }
329 (Dim::Symbolic(then_symbol), Dim::Symbolic(else_symbol))
330 if then_result.parent_symbols.get(&then_symbol)
331 == else_result.parent_symbols.get(&else_symbol)
332 && then_result.parent_symbols.contains_key(&then_symbol) =>
333 {
334 DimExpr::symbol(then_result.parent_symbols[&then_symbol])
335 }
336 _ => interner.fresh_dim(),
337 })
338 .collect();
339 outputs.push(IfOutput::Typed(TypeInfo::new(then_value.dtype, shape)));
340 }
341 outputs.resize_with(node.outputs.len(), || IfOutput::Unresolved);
342
343 Ok(Some(outputs))
344}
345
346fn branch_output_is_resolved(branch: &Graph, output: ValueId, resolved: &HashSet<ValueId>) -> bool {
347 resolved.contains(&output) && branch.try_value(output).is_some()
348}
349
350fn seed_sources(
356 graph: &Graph,
357 types: &mut HashMap<ValueId, TypeInfo>,
358 shape_data: &mut HashMap<ValueId, ShapeData>,
359) {
360 for (vid, value) in graph.values.iter() {
361 if !graph.value_type_is_known(vid) || !graph.value_shape_is_known(vid) {
362 continue;
363 }
364 let shape: TypedShape = value.shape.iter().map(|&d| DimExpr::from(d)).collect();
365 types.insert(vid, TypeInfo::new(value.dtype, shape));
366 }
367 for (&vid, weight) in &graph.initializers {
369 if let WeightRef::Inline(t) = weight
370 && let Some(sd) = ShapeData::from_tensor(t.dtype, &t.dims, &t.data)
371 {
372 shape_data.insert(vid, sd);
373 }
374 }
375}
376
377fn bind_captures(
378 graph: &Graph,
379 scope: &ScopeBindings,
380 types: &mut HashMap<ValueId, TypeInfo>,
381 shape_data: &mut HashMap<ValueId, ShapeData>,
382) {
383 let formal_inputs: HashSet<_> = graph.inputs.iter().copied().collect();
384 for (vid, value) in graph.values.iter() {
385 if value.producer.is_some()
386 || formal_inputs.contains(&vid)
387 || graph.initializers.contains_key(&vid)
388 {
389 continue;
390 }
391 let Some(name) = value.name.as_deref() else {
392 continue;
393 };
394 let Some(Some(binding)) = scope.get(name) else {
395 continue;
396 };
397 if let Some(type_info) = &binding.type_info {
398 types.insert(vid, type_info.clone());
399 }
400 if let Some(data) = &binding.shape_data {
401 shape_data.insert(vid, data.clone());
402 }
403 }
404}
405
406fn import_scope(
407 graph: &Graph,
408 outer_scope: &ScopeBindings,
409 interner: &mut SymbolInterner,
410) -> (ScopeBindings, HashMap<SymbolId, SymbolId>) {
411 let local_names = local_value_names(graph);
412 let mut parent_to_child = HashMap::new();
413 let mut child_to_parent = HashMap::new();
414 let mut names: Vec<_> = outer_scope
415 .keys()
416 .filter(|name| !local_names.contains(name.as_str()))
417 .collect();
418 names.sort_unstable();
419 let imported = names
420 .into_iter()
421 .map(|name| {
422 let binding = outer_scope[name]
423 .as_ref()
424 .map(|io| remap_node_io(io, interner, &mut parent_to_child, &mut child_to_parent));
425 (name.clone(), binding)
426 })
427 .collect();
428 (imported, child_to_parent)
429}
430
431fn local_value_names(graph: &Graph) -> HashSet<&str> {
432 let formal_inputs: HashSet<_> = graph.inputs.iter().copied().collect();
433 graph
434 .values
435 .iter()
436 .filter(|(vid, value)| {
437 value.producer.is_some()
438 || formal_inputs.contains(vid)
439 || graph.initializers.contains_key(vid)
440 })
441 .filter_map(|(_, value)| value.name.as_deref())
442 .collect()
443}
444
445fn remap_node_io(
446 io: &NodeIo,
447 interner: &mut SymbolInterner,
448 parent_to_child: &mut HashMap<SymbolId, SymbolId>,
449 child_to_parent: &mut HashMap<SymbolId, SymbolId>,
450) -> NodeIo {
451 NodeIo {
452 type_info: io.type_info.as_ref().map(|type_info| {
453 TypeInfo::new(
454 type_info.dtype,
455 type_info
456 .shape
457 .iter()
458 .map(|dim| remap_dim_expr(dim, interner, parent_to_child, child_to_parent))
459 .collect(),
460 )
461 }),
462 shape_data: io.shape_data.as_ref().map(|data| {
463 let mut data = data.clone();
464 data.elems = data
465 .elems
466 .iter()
467 .map(|dim| remap_dim_expr(dim, interner, parent_to_child, child_to_parent))
468 .collect();
469 data
470 }),
471 }
472}
473
474fn remap_dim_expr(
475 dim: &DimExpr,
476 interner: &mut SymbolInterner,
477 parent_to_child: &mut HashMap<SymbolId, SymbolId>,
478 child_to_parent: &mut HashMap<SymbolId, SymbolId>,
479) -> DimExpr {
480 if let Some(value) = dim.as_const() {
481 return DimExpr::constant(value);
482 }
483 let Some(parent) = dim.as_symbol() else {
484 return interner.fresh_dim();
485 };
486 let child = *parent_to_child.entry(parent).or_insert_with(|| {
487 let child = interner.fresh_symbol();
488 child_to_parent.insert(child, parent);
489 child
490 });
491 DimExpr::symbol(child)
492}
493
494fn extend_visible_scope(
495 graph: &Graph,
496 types: &HashMap<ValueId, TypeInfo>,
497 shape_data: &HashMap<ValueId, ShapeData>,
498 scope: &mut ScopeBindings,
499 values: impl IntoIterator<Item = ValueId>,
500 interner: &mut SymbolInterner,
501) {
502 for vid in values {
503 let Some(value) = graph.try_value(vid) else {
504 continue;
505 };
506 let Some(name) = value.name.as_ref() else {
507 continue;
508 };
509 let binding = types.get(&vid).map(|type_info| NodeIo {
510 type_info: Some(TypeInfo::new(
511 type_info.dtype,
512 type_info
513 .shape
514 .iter()
515 .map(|dim| DimExpr::from(interner.lower(dim)))
516 .collect(),
517 )),
518 shape_data: shape_data.get(&vid).map(|data| {
519 let mut data = data.clone();
520 data.elems = data
521 .elems
522 .iter()
523 .map(|dim| DimExpr::from(interner.lower(dim)))
524 .collect();
525 data
526 }),
527 });
528 scope.insert(name.clone(), binding);
529 }
530}
531
532fn gather_inputs(
534 node: &Node,
535 types: &HashMap<ValueId, TypeInfo>,
536 shape_data: &HashMap<ValueId, ShapeData>,
537) -> Vec<NodeIo> {
538 node.inputs
539 .iter()
540 .map(|slot| match slot {
541 Some(vid) => NodeIo {
542 type_info: types.get(vid).cloned(),
543 shape_data: shape_data.get(vid).cloned(),
544 },
545 None => NodeIo::default(),
546 })
547 .collect()
548}
549
550fn seed_next_symbol(graph: &Graph) -> u32 {
553 let mut max = ANON_SYMBOL_FLOOR.saturating_sub(1);
554 for &SymbolId(id) in graph.symbol_constraints.keys() {
555 max = max.max(id);
556 }
557 for value in graph.values.values() {
558 for dim in &value.shape {
559 if let Dim::Symbolic(SymbolId(id)) = dim {
560 max = max.max(*id);
561 }
562 }
563 }
564 max.saturating_add(1)
565}