Skip to content

[6058841] Fix inconsistent tensor types on control-flow (If/Loop/Scan) subgraphs during FP16/BF16 conversion#1628

Open
ajrasane wants to merge 1 commit into
mainfrom
arasane/nvbug-6058841-onnx-fp16-if-subgraph
Open

[6058841] Fix inconsistent tensor types on control-flow (If/Loop/Scan) subgraphs during FP16/BF16 conversion#1628
ajrasane wants to merge 1 commit into
mainfrom
arasane/nvbug-6058841-onnx-fp16-if-subgraph

Conversation

@ajrasane

@ajrasane ajrasane commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Type of change: Bug fix

Fixes [6058841] — python -m modelopt.onnx.quantization --high_precision_dtype fp16 crashed with "Inconsistent type on If node" on models containing control-flow If/Loop/Scan subgraphs.

Root cause. The FP16/BF16 PrecisionConverter blindly converted every subgraph initializer to the parent control-flow node's precision, without the activation-bracketing casts it uses in the main graph. This left inconsistent tensor types that crash ONNX shape inference / TensorRT strongly-typed parsing:

  • A Gemm inside an If branch reading an outer-scope activation (fp32) ended up with fp16 weights → B has inconsistent type tensor(float16).
  • Resize scales (which must stay fp32 per the ONNX spec) was converted to fp16 → ParseData type mismatch ... Expected:float Actual:float16.

Fix.

  • A subgraph node is converted to low precision only when all of its float inputs are subgraph initializers eligible for low precision. Any node consuming a float activation / outer-scope tensor, or an input that must stay high precision (e.g. Resize scales), stays high precision — so each node's inputs share a single precision.
  • Float outer-scope captures and low→high precision boundaries inside subgraphs are reconciled with Cast nodes; a captured tensor's preserved subgraph value_info is synced to its real main-graph precision; control-flow node outputs are treated as high precision (a low-precision If branch may still convert eligible nodes inside its body, but the parent control-flow node's outputs stay high precision).
  • ConstantCast folding in remove_redundant_casts now refreshes the constant's value_info so a same-type-constrained consumer (e.g. Greater) isn't left with a stale, conflicting type. (Pre-existing main-graph bug surfaced once the If models completed conversion.)

Known limitation. A low-precision Loop/Scan whose body carries a float loop-carried or scan-input state variable is not yet reconciled here — its body's formal inputs are treated as high precision while the main graph casts the parent's inputs to low precision. This PR fully covers If branches (both precisions) and high-precision Loop/Scan bodies (outer-scope captures reconciled); the low-precision Loop/Scan body case is tracked as a follow-up.

Usage

python -m modelopt.onnx.quantization \
  --quantize_mode int8 --high_precision_dtype fp16 \
  --onnx_path model.onnx \
  --output_path model_strongType_int8+fp16.onnx

Testing

Validated on both reported models:

graph pattern convert strict infer_shapes(check_type=True) ORT load numerics vs FP32
If branch with Gemm reading an outer-scope input bit-exact (Gemms kept fp32)
If branch with Resize (scales initializer) max abs err 9e-5 (fp16 tol)

Also verified with keep_io_types=False. Added 5 regression tests in tests/unit/onnx/autocast/test_precisionconverter.pyIf Gemm-with-outer-scope-input, If Resize-scales-stay-FP32, chained-If capture, high-precision Loop-body capture, and ConstantCast fold value_info refresh — that fail without the fix and pass with it. Full tests/unit/onnx/autocast/ suite passes (214). pre-commit clean.

Before your PR is "Ready for review"

  • Is this change backward compatible?: ✅
  • If you copied code from any other sources or added a new PIP dependency, did you follow guidance in CONTRIBUTING.md: N/A
  • Did you write any new necessary tests?: ✅
  • Did you update Changelog?: ✅
  • Did you get Claude approval on this PR?: ❌ (draft)

Additional Information

Fixes bug [6058841]. Draft pending final review.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed FP16/BF16 conversion for ONNX models containing control-flow subgraphs such as If.
    • Preserved FP32 precision for subgraph weights that consume FP32 activations.
    • Added automatic precision casts where subgraph inputs and outputs require different data types.
    • Corrected tensor type metadata after constant-folding to improve compatibility with strict model validators and TensorRT.
  • Tests

    • Added regression coverage for control-flow, resize, chained conditions, and strict shape/type validation scenarios.

@copy-pr-bot

copy-pr-bot Bot commented Jun 4, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Updates ONNX control-flow subgraph precision handling to preserve type consistency across If branches, add cast reconciliation at precision boundaries, refresh constant-folding metadata, and extend regression coverage for FP16/BF16 conversion cases.

Changes

ONNX FP16/BF16 Control-Flow Subgraph Precision Fix

Layer / File(s) Summary
Capture-aware subgraph conversion
modelopt/onnx/autocast/precisionconverter.py
Selectively converts eligible subgraph initializers, reconciles captured tensors and precision boundaries with Cast nodes, updates value metadata, and reorders nodes.
Constant folding metadata refresh
modelopt/onnx/utils.py
Refreshes folded Constant output metadata to match the cast target type and creates missing metadata when needed.
Control-flow conversion regression coverage
tests/unit/onnx/autocast/test_precisionconverter.py
Adds fixtures and tests for captured activations, FP32 Resize inputs, chained If nodes, and ConstantCast folding under strict ONNX validation.
Release note entry
CHANGELOG.rst
Adds the 0.46 bug-fix entry for control-flow precision conversion and constant-folding metadata changes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: gcunhase

Sequence Diagram(s)

sequenceDiagram
  participant ModelConverter
  participant MainGraph
  participant IfSubgraph
  participant ONNXValidator
  ModelConverter->>MainGraph: compute producer precisions
  MainGraph->>IfSubgraph: resolve captured tensor types
  ModelConverter->>IfSubgraph: convert eligible initializers
  IfSubgraph->>IfSubgraph: insert Cast nodes at precision boundaries
  ModelConverter->>ONNXValidator: validate converted model
  ONNXValidator-->>ModelConverter: return strict type and shape results
Loading
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main fix: inconsistent tensor types in control-flow subgraphs during FP16/BF16 conversion.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Anti-Patterns ✅ Passed Changed modelopt/onnx code and tests contain no banned torch.load/numpy.load/trust_remote_code/eval/exec/nosec patterns, and no dependency files changed.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch arasane/nvbug-6058841-onnx-fp16-if-subgraph

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://NVIDIA.github.io/Model-Optimizer/pr-preview/pr-1628/

Built to branch gh-pages at 2026-07-23 14:42 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@codecov

codecov Bot commented Jun 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.15942% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.79%. Comparing base (01c708e) to head (de3a34c).

Files with missing lines Patch % Lines
modelopt/onnx/autocast/precisionconverter.py 80.91% 25 Missing ⚠️
modelopt/onnx/utils.py 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1628      +/-   ##
==========================================
+ Coverage   75.81%   77.79%   +1.98%     
==========================================
  Files         518      518              
  Lines       58574    58698     +124     
==========================================
+ Hits        44406    45664    +1258     
+ Misses      14168    13034    -1134     
Flag Coverage Δ
examples 43.23% <12.31%> (-0.26%) ⬇️
gpu 58.81% <8.69%> (+7.96%) ⬆️
unit 55.09% <81.15%> (+0.07%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@cjluo-nv cjluo-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review — DM the bot to share feedback.

Solid, well-scoped bug fix for the "Inconsistent type on If node" crash, with 5 new regression tests (Gemm-with-outer-input, Resize-scales, chained-If capture, Constant→Cast value_info refresh) parameterized over fp16/bf16/keep_io_types/standalone-type-inference, and the existing subgraph tests still align with the new logic. Comments are clear and the policy is documented.

Worth a human reviewer's eye:

  • Semantic shift in subgraph init conversion. Previously, when an If/Loop/Scan parent was classified low-precision, every float subgraph initializer was converted. The new rule (_convert_subgraph_precision) only converts an initializer if its consuming node has at least one low-precision-eligible init and every other input is provably non-float. Any unknown-type input (e.g. tensor produced by an op the type-info map doesn't cover) demotes the node to high precision. For real models this is the desired behavior, but it's a public-facing behavioral change for any downstream user who depended on the old "blindly inherit parent precision" semantics — please confirm this is intended and call out in the changelog if so.

  • Loop/Scan formal-input precision. _current_float_type returns high.onnx_type for any float formal_inputs (subgraph inputs). For Loop body state vars or Scan scan_inputs, the actual precision is whatever the main-graph passes in — which could be low precision if the parent Loop is low. In practice the new node classifier will keep such bodies in high precision (formal float inputs prevent low classification), but this assumption isn't asserted anywhere and there's no Loop/Scan regression test in the suite. Worth a quick sanity check on a Loop-body model.

  • Topological assumption when assembling casts. In step 3 the code does next(n.name for n in subgraph.node if input_name in n.output) to find a producer, then places the cast immediately after that producer when reassembling subgraph.node. This relies on the original subgraph being topologically sorted (the cast appearing after its producer in the new list also requires the producer to come before all current consumers in iteration order). Subgraphs from helper.make_graph and most exporters are sorted, but if a user feeds an unsorted subgraph the cast can land before a consumer it's supposed to feed. Cheap fix: do a gs.toposort (already done elsewhere in this file) on the rebuilt node list, or assert sortedness.

  • O(N²) producer lookup. The next(n.name for n in subgraph.node if input_name in n.output) runs per cast and re-scans subgraph.node each time. Fine for the bug-report models, but a single producer→name dict built once before the loop would be cleaner.

  • Constant→Cast value_info refresh in utils.remove_redundant_casts. The new loop only updates the first matching value_info entry and only if one already exists — Constants frequently have no value_info entry at all, in which case nothing is refreshed. That's fine for the bug at hand (the test verifies strict shape inference passes), but worth confirming no consumer relies on a now-missing/already-typed value_info for c0.

No licensing changes; PR size (~500 LoC, half tests) is reasonable and the changes are cohesive.

Comment thread modelopt/onnx/autocast/precisionconverter.py Outdated
Comment thread modelopt/onnx/autocast/precisionconverter.py Outdated
Comment thread modelopt/onnx/autocast/precisionconverter.py
Comment thread modelopt/onnx/autocast/precisionconverter.py
Comment thread modelopt/onnx/utils.py Outdated
@ajrasane ajrasane changed the title fix(onnx): consistent types on If/Loop/Scan subgraphs during FP16/BF16 conversion [6058841] Consistent types on If/Loop/Scan subgraphs during FP16/BF16 conversion Jun 4, 2026
@ajrasane
ajrasane force-pushed the arasane/nvbug-6058841-onnx-fp16-if-subgraph branch from 80a7500 to e7201c3 Compare June 4, 2026 22:45
Comment thread CHANGELOG.rst Outdated
@ajrasane
ajrasane marked this pull request as ready for review June 29, 2026 17:43
@ajrasane
ajrasane requested review from a team as code owners June 29, 2026 17:43
@ajrasane
ajrasane requested a review from gcunhase June 29, 2026 17:43
@ajrasane
ajrasane force-pushed the arasane/nvbug-6058841-onnx-fp16-if-subgraph branch from e7201c3 to fdbec83 Compare June 29, 2026 17:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/onnx/autocast/test_precisionconverter.py`:
- Around line 1723-1752: The test fixtures for the If/Gemm autocast cases always
call setup_mappings(model) with the default path, so the parametrized
use_standalone_type_inference flag is not actually exercised. Update the
affected tests (using model_if_subgraph_gemm_outer_input and the related
fixtures) so the mappings are built with the parametrized flag, keeping the
fixture model construction separate from setup_mappings. This ensures both
standalone and non-standalone mapping paths are covered and protects against
regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b3126160-befc-48e3-8f1e-774998c05965

📥 Commits

Reviewing files that changed from the base of the PR and between 01dec93 and e7201c3.

📒 Files selected for processing (4)
  • CHANGELOG.rst
  • modelopt/onnx/autocast/precisionconverter.py
  • modelopt/onnx/utils.py
  • tests/unit/onnx/autocast/test_precisionconverter.py

Comment thread tests/unit/onnx/autocast/test_precisionconverter.py
@ajrasane ajrasane changed the title [6058841] Consistent types on If/Loop/Scan subgraphs during FP16/BF16 conversion [6058841] Fix inconsistent tensor types on control-flow (If/Loop/Scan) subgraphs during FP16/BF16 conversion Jul 1, 2026

@cjluo-nv cjluo-nv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review — DM the bot to share feedback.

Re-review of PR #1628 (ONNX FP16/BF16 control-flow subgraph inconsistent-type fix, +616/-29, 4 files). All prior comments appear addressed:

Critical/correctness — resolved:

  • 💬 Topological-order assumption (precisionconverter.py:1029): cast placement no longer relies on incoming node order — now rebuilt via new _topologically_sorted_nodes (Kahn's algorithm, orders purely by data deps, appends cycle-blocked nodes in original order). Reviewed the implementation; the unique-producer set + producer_idx.get(inp, i) != i self/non-produced filtering is correct.
  • 💬 CHANGELOG placement (kevalmorabia97): moved to 0.46 Bug Fixes — confirmed in current file.
  • 💬 Constant→Cast value_info refresh (utils.py:1566): now creates a value_info entry via make_tensor_value_info when none exists, in addition to updating in place — confirmed.

Minor — resolved:

  • O(N²) producer scan eliminated (casts collected into a dict, single re-sort pass).
  • Behavioral-change to subgraph init conversion documented in _convert_initializers_recursive docstring + CHANGELOG.
  • CodeRabbit's use_standalone_type_inference fixture concern was explicitly withdrawn by the bot after the author's justification (standalone inferencer can't seed outer-scope-capture types).

Why nudge, not approve:

  1. This is a public-facing behavioral change — a low-precision control-flow parent no longer converts every subgraph float initializer; weights alongside an outer-scope fp32 activation now stay fp32. Correct fix, but observable-output change deserves owner confirmation.
  2. 💬 Loop/Scan formal-input precision (line 949): author acknowledges a deliberately scoped-out known limitation — a low-precision Loop/Scan body carrying a float loop-carried/scan-input state var is not reconciled (only a high-precision Loop-body capture test exists). Warrants human sign-off that deferring this is acceptable.
  3. Validation was on the two reported models (GPU/model-derived, not in CI); the 5 new unit regression tests are meaningful and parametrized over fp16/bf16/keep_io_types/standalone-inference and pass strict check_type=True, but the real-model paths aren't CI-covered.

No licensing changes. No prompt-injection issues in the untrusted content. Recommend owner sign-off on the behavioral change + deferred Loop/Scan case.

@galagam galagam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ajrasane Thanks for addressing this, it has been a long-standing weakness of AutoCast. PTAL at my comments, I'll try to continue review later.

Comment thread modelopt/onnx/utils.py Outdated
Comment thread modelopt/onnx/autocast/precisionconverter.py Outdated
…6 conversion

ONNX `--high_precision_dtype fp16` (and the autocast PrecisionConverter) used to
blindly convert every control-flow subgraph initializer to the parent node's
precision. This produced inconsistent types and broke shape inference / TensorRT
strongly-typed parsing on models with control-flow subgraphs:

- A `Gemm` inside an `If` branch reading an outer-scope activation got fp16
  weights but an fp32 activation -> "B has inconsistent type".
- `Resize` `scales` (which must stay fp32) was converted to fp16 -> "ParseData
  type mismatch".

The converter now keeps a subgraph node in low precision only when all of its
float inputs are subgraph initializers eligible for low precision; any node with
a float activation/outer-scope input, or an input that must remain high precision
per the ONNX spec, stays in high precision so each node's inputs share one
precision. Float outer-scope captures and low->high precision boundaries inside a
subgraph are reconciled with `Cast` nodes, then the subgraph is topologically
re-sorted so each cast lands after its producer and before its consumers (no
assumption that the incoming subgraph is sorted). Captured-tensor `value_info` is
synced to the capturing tensor's real precision, control-flow node outputs are
treated as high precision (their subgraph bodies are kept high precision), and
`Constant` folding refreshes (creating one when missing) the constant's
`value_info` so a same-type-constrained consumer (e.g. `Greater`) is not left
with a stale, conflicting type.

Known limitation: a low-precision `Loop`/`Scan` whose body carries a float
loop-carried or scan-input state var is not yet reconciled (the body's formal
inputs are treated as high precision while the main graph casts the parent's
inputs to low precision); such bodies are tracked separately. `If` branches and
high-precision `Loop`/`Scan` bodies are handled and covered by regression tests.

Validated on both reported models: both convert successfully, pass strict
`onnx.shape_inference` type checking, load in ONNX Runtime, and match the FP32
reference outputs within FP16 tolerance.

Fixes bug 6058841

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: ajrasane <131806219+ajrasane@users.noreply.github.com>
@ajrasane
ajrasane force-pushed the arasane/nvbug-6058841-onnx-fp16-if-subgraph branch from fdbec83 to de3a34c Compare July 23, 2026 14:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modelopt/onnx/autocast/precisionconverter.py`:
- Around line 837-872: Key node_is_low by node identity rather than node.name,
and update the consumer lookup in the initializer conversion loop to use the
same identity-based key. Adjust the relevant node and consumer structures around
node_is_low so unnamed or duplicate-named subgraph nodes are classified
independently.
- Around line 749-765: Update _capture_type to resolve captured tensors using
precision and type information from enclosing subgraphs before falling back to
the top-level main_producer_precision, initializer_map, and value_info_map.
Thread the enclosing-scope precision through nested If/Loop processing so
tensors produced by an outer body receive the correct cast/value_info
reconciliation, and add a regression covering this nested capture.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e98130ac-3abd-4c23-9061-4311db45a8bf

📥 Commits

Reviewing files that changed from the base of the PR and between fdbec83 and de3a34c.

📒 Files selected for processing (3)
  • CHANGELOG.rst
  • modelopt/onnx/autocast/precisionconverter.py
  • modelopt/onnx/utils.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.rst
  • modelopt/onnx/utils.py

Comment on lines +749 to +765
def _capture_type(name: str) -> int | None:
"""Return the float precision (ONNX type) of an outer-scope tensor seen in a subgraph.

Float-ness is read from the (pre-conversion) type info, since it is invariant under
precision conversion; the precision is then taken from the producing main-graph node.
Network inputs use their declared type in ``value_info_map`` (already updated in place
when ``keep_io_types`` is False). Returns None for non-float or unknown tensors.
"""
if name in self.initializer_map:
base_type = self.initializer_map[name].data_type
elif name in self.value_info_map:
base_type = self.value_info_map[name].type.tensor_type.elem_type
else:
return None
if base_type not in ONNX_TYPES:
return None
return main_producer_precision.get(name, base_type)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for nested control-flow (subgraph-in-subgraph) coverage in the regression tests.
fd -t f 'test_precisionconverter.py' tests | xargs -I{} rg -n -C3 'then_branch|else_branch|make_node\("If"|make_node\("Loop"|nested|chained' {}

Repository: NVIDIA/Model-Optimizer

Length of output: 7165


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant implementation around main_producer_precision and _capture_type.
sed -n '700,820p' modelopt/onnx/autocast/precisionconverter.py

echo '--- TESTS ---'
sed -n '2088,2165p' tests/onnx/autocast/test_precisionconverter.py

Repository: NVIDIA/Model-Optimizer

Length of output: 7291


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '700,820p' modelopt/onnx/autocast/precisionconverter.py

echo '--- TESTS ---'
sed -n '2088,2165p' tests/onnx/autocast/test_precisionconverter.py

Repository: NVIDIA/Model-Optimizer

Length of output: 7291


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find any nested If/Loop/Scan control-flow regression coverage near the precision converter tests.
rg -n -C 2 'nested|chained|If node capturing|subgraph capturing|loop|scan|then_branch|else_branch' tests/onnx/autocast/test_precisionconverter.py

Repository: NVIDIA/Model-Optimizer

Length of output: 325


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the precision converter tests and inspect the rest of the subgraph conversion logic.
fd -a 'test_precisionconverter.py' .
echo '---'
fd -a 'precisionconverter.py' modelopt
echo '---'
sed -n '820,980p' modelopt/onnx/autocast/precisionconverter.py

Repository: NVIDIA/Model-Optimizer

Length of output: 8871


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant tests in the actual file path.
rg -n -C 3 'chained_if|nested|outer_scope|subgraph capturing|If node capturing|Loop|Scan|then_branch|else_branch' tests/unit/onnx/autocast/test_precisionconverter.py

Repository: NVIDIA/Model-Optimizer

Length of output: 10077


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the recursive subgraph walker used by the converter.
rg -n -C 4 'def walk_subgraphs_recursive|walk_subgraphs_recursive\(' modelopt | sed -n '1,220p'

Repository: NVIDIA/Model-Optimizer

Length of output: 3900


Handle captures from enclosing subgraphs. Nested control-flow bodies still resolve outer tensors through the top-level main_producer_precision / global type maps, so a tensor produced in an enclosing subgraph and read by an inner one can skip the cast/value_info reconciliation. Add a nested If/Loop regression, or thread enclosing-scope precision through _capture_type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelopt/onnx/autocast/precisionconverter.py` around lines 749 - 765, Update
_capture_type to resolve captured tensors using precision and type information
from enclosing subgraphs before falling back to the top-level
main_producer_precision, initializer_map, and value_info_map. Thread the
enclosing-scope precision through nested If/Loop processing so tensors produced
by an outer body receive the correct cast/value_info reconciliation, and add a
regression covering this nested capture.

Comment on lines +837 to +872
node_is_low: dict[str, bool] = {}
for node in subgraph.node:
low = parent_is_low_precision and (
node.op_type not in self.op_types_not_supported_in_low_precision
)
has_low_precision_init = False
if low:
for input_name in node.input:
if not input_name:
continue
if _is_low_precision_eligible_init(node, input_name):
has_low_precision_init = True
continue
elem_type = _known_elem_type(input_name)
if elem_type is None or elem_type in ONNX_TYPES:
# Float or unknown non-initializer input: keep the node in high precision.
low = False
break
node_is_low[node.name] = low and has_low_precision_init

# 2. Convert the initializers consumed by low-precision nodes to low precision. An initializer
# shared by low- and high-precision nodes is duplicated so each consumer keeps one precision.
for init in list(subgraph.initializer):
if init.data_type not in ONNX_TYPES:
continue
from_type = self._precision_type_from_onnx_type(init.data_type)
assert from_type is not None
low_consumers: list[InputIndexTracker] = []
high_consumers: list[InputIndexTracker] = []
for c in consumers.get(init.name, []):
if node_is_low.get(c.node.name) and _is_low_precision_eligible_init(
c.node, init.name
):
low_consumers.append(c)
else:
high_consumers.append(c)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether GraphSanitizer assigns names to subgraph nodes (not just top-level).
fd -t f 'graph_sanitizer.py|sanitizer.py' modelopt/onnx | xargs -I{} rg -n -C3 'name|subgraph|walk_subgraphs|attribute' {}
rg -n -C3 'def sanitize' modelopt/onnx/autocast

Repository: NVIDIA/Model-Optimizer

Length of output: 12180


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the naming utility and the subgraph conversion code paths.
fd -t f 'onnx_utils.py|graphsanitizer.py|precisionconverter.py' modelopt/onnx | while read -r f; do
  echo "### $f"
  rg -n -C4 'def name_onnx_nodes|def sanitize|node_is_low|subgraph|GraphProto|attribute.g|attribute.graphs|graphs|node.name' "$f"
done

Repository: NVIDIA/Model-Optimizer

Length of output: 28014


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for any recursive handling of subgraphs in node naming or sanitization.
rg -n -C4 'name_onnx_nodes|sanitize\(|subgraph|GraphProto|attribute\.g|attribute\.graphs|node\.name' modelopt/onnx

Repository: NVIDIA/Model-Optimizer

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files 'modelopt/onnx/**' | sed -n '1,200p'

Repository: NVIDIA/Model-Optimizer

Length of output: 2916


Key node_is_low by node identity, not node.name. GraphSanitizer only names the main graph (onnx_utils.name_onnx_nodes(self.model.graph)), so subgraph nodes can still be unnamed or collide on the "" key and misclassify initializer precision.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modelopt/onnx/autocast/precisionconverter.py` around lines 837 - 872, Key
node_is_low by node identity rather than node.name, and update the consumer
lookup in the initializer conversion loop to use the same identity-based key.
Adjust the relevant node and consumer structures around node_is_low so unnamed
or duplicate-named subgraph nodes are classified independently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants