Uh oh!
There was an error while loading. Please reload this page.
fix(dynamo): correct legacy exporter (retrace=False) submodule inlining for hybrid graphs - #4446
Conversation
shoumikhin
commented
Aug 1, 2026
Reviewed this one carefully because #4440 lands right on top of it: the composable The bug is real, and it is worse than mis-wiringI built a small parent graph whose submodule input placeholder name collides with an Your two unit tests are genuine regression tests, not decoration. I ran them both ways:
That is exactly the property that matters, and it is nice that they need neither a GPU On the approachWiring positionally from Letting Questions on the two compat fixesThese are separate from the inlining fix and I would like to understand them a bit 1. 2. The I tried to reach that path and could not, which is reassuring but leaves the question So the fallback did not fire for either, and in both cases the exported program returned That leaves two possibilities, and it would help to know which you saw. If the fallback Minor: the comment says "torch>=2.13" while the Composition with the ExecuTorch workFor anyone tracking how these fit together:
Thanks for tracking this down. The name-collision path has probably been quietly wrong |
d504244 to
60e39a4CompareThank you for your suggestions. I have added clarifications in the comments.
Verified that |
60e39a4 to
464d756CompareUh oh!
There was an error while loading. Please reload this page.
464d756 to
456f3caCompare456f3ca to
98cc621Compare9dd7190 to
5b37a9bCompareThe new branch can silently return wrong numbers when keyword arguments are passed in a different order than the placeholders.
The |
The submodule placeholders are named Worth doing, since this is the test covering the multi-output half of the change. The name-collision test in the same file does genuinely fail on main, so the core claim is well covered, it is only this one that is inert. One thing that may be worth adding to the description: the bug this fixes is a wrong answer, not a crash. With a parent node named |
In support of the Main builds |
…ng for hybrid graphs torch_tensorrt.save(retrace=False) uses the legacy dynamo exporter, which inlines the partitioned _run_on_gpu (non-TensorRT) submodules back into the graph before building an ExportedProgram. For a hybrid graph interleaving TensorRT engines with a CUDA/pytorch delegated op, inline_torch_modules wired each submodule's inputs by MATCHING placeholder names to graph nodes (get_duplicate_nodes). Name matching binds an input to a same-named but unrelated node on a collision (e.g. a submodule input placeholder name-matching a different engine's getitem), which: - rewires a consumer to the wrong producer and orphans the real one; the orphan is then pruned by dead-code elimination, leaving a delegate short an output at runtime (an aliased engine reports "expected N args, got N-1"); and - for a submodule mixing graph-input and computed-intermediate inputs, leaks the computed intermediates as spurious graph placeholders (misclassified USER_INPUTs). Wire submodule inputs POSITIONALLY from the call_module args (gm_node.args, which is authoritative) instead of by name: let graph_copy create a fresh placeholder for each submodule input, then rewire each to submodule_inputs[i] by position and erase it. Drop get_duplicate_nodes (now unused). Also fix two torch-version-compat gaps this path hits on recent torch: - lift(): pass an explicit persistent= flag on BUFFER InputSpecs (required since 2.3). - create_trt_exp_program(): an inlined GraphModule may carry a plain fx.CodeGen (no pytree_info); fall back to specs rebuilt from the example inputs + graph outputs. With these, retrace=False export of a hybrid TensorRT+CUDA program is bit-identical to retrace=True (validated on a 2-layer int4 MoE decode: per-step argmax + logits match). Tests: tests/py/dynamo/models/test_exporter_inlining.py -- positional input wiring under a name collision, and multi-output preservation (GPU-free fx unit tests).
…g tests Address review feedback on the legacy exporter (retrace=False): - `create_trt_exp_program`: pytree flattens kwargs in dict insertion order while the graph consumes its placeholders positionally, so kwargs passed in a different order than the forward signature silently bound each value to the wrong input (the `num_leaves` assert only checks the count, not the mapping). Reorder kwargs into placeholder order (matching each kwarg placeholder's `target`) before flattening. - `test_inline_torch_modules_preserves_all_submodule_outputs`: rename the submodule's first placeholder to `y` so it collides with the parent's second input. The test was inert before (passed with and without the fix); it now fails on the old name-matching path. - Add `test_create_trt_exp_program_reorders_kwargs_to_placeholder_order` pinning the kwargs-order fix above. - Add `test_lift_sets_persistent_true_on_buffer_spec` pinning the `persistent=True` flag on lifted BUFFER specs (torch>=2.3 asserts it).
b5db904 to
35509caCompareUh oh!
There was an error while loading. Please reload this page.
Description
torch_tensorrt.save(..., retrace=False)runs the legacy exporter, which callsinline_torch_modulesto collapse submodules into the parent graph. That inlining wired submodule inputs by placeholder name. When a submodule input placeholder's name collides with an unrelated node in the parent graph,graph_copymaps the submodule body onto that node through a pre-seededval_map— so the original producer is consumed and disappears from the graph entirely. This is silent: it surfaces only later as a delegate (or TensorRT engine) reporting the wrong argument count at runtime, or as a wrong numerical result rather than a crash.A related failure hit multi-output submodules: a mis-wired input orphaned one submodule output, dead-code elimination then pruned it, and a downstream consumer (in the hybrid case, a TensorRT engine) was left short an output.
This path is on by default for composable hybrid TensorRT + CUDA
.pteexport (retrace=False), so it matters for that flow.Fix
call_modulenode'sargs(the authoritative ordered list of what the node actually consumes) instead of matching by placeholder name, and remove the name-basedget_duplicate_nodesmatching. Names are incidental and can legally collide after graph surgery; there is no correct name-matching here.create_trt_exp_programkwargs ordering:pytreeflattens kwargs in dict-insertion order while the graph consumes placeholders positionally, so kwargs passed out of signature order silently bound each value to the wrong input (thenum_leavesassert only checks the count). Reorder kwargs into placeholder order before flattening.persistent=Trueon lifted BUFFER specs: torch >= 2.3 asserts an explicitpersistentflag onBUFFER-kindInputSpecs. A buffer only reaches this branch when it is instate_dict, which excludes non-persistent buffers by construction, soTrueis always correct here.Tests
tests/py/dynamo/models/test_exporter_inlining.py(all CPU-only, no GPU/TensorRT build required):test_inline_torch_modules_preserves_all_submodule_outputs— a submodule placeholder is deliberately named to collide with a parent input, so the test fails on the old name-matching path and passes with positional wiring.test_inline_torch_modules_wires_inputs_by_position— pins the positional-wiring behavior / no leaked placeholder.test_create_trt_exp_program_reorders_kwargs_to_placeholder_order— exportsa - b, passes kwargs reversed, assertsin_specrecords placeholder order and the result is7.0(not-7.0).test_lift_sets_persistent_true_on_buffer_spec— pins thepersistent=Trueflag on lifted BUFFER specs.test_create_trt_exp_program_rebuilds_in_spec_without_inputs— the no-inputin_specpath.