Repository navigation
Qualcomm AI Engine Direct - Keep node names unique across QNN passes - #24316
shoumikhin wants to merge 5 commits into
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24316
Note: Links to docs will display an error until the docs builds have been completed. ✅ No FailuresAs of commit e5f40e2 with merge base 7586c16 ( This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 19m 11s —— View job The reservation fix is correct for QNN's export pipeline, but it patches an upstream torch.export bug by writing private torch.fx state with no TODO, and both changed tests check less than they claim. Detailed reviewPR Review: #24316Summary
This review did not run the QNN tests (there is no QNN SDK on the runner). Claims about torch internals come from reading code, not from running it. Code Quality
Testing
RecommendationApprove Nothing blocks. The reservation fixes the reported export-pipeline failures. Add the upstream TODO before merging. The shared-helper move and the test hardening can come later. |
|
@haowhsu-quic @shewu-quic @winskuo-quic could you take a look? This changes the Qualcomm backend. |
3e91127 to
b84aa23
Compare
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 20m 42s —— View job The node name reservation fixes the reported Detailed reviewPR Review: #24316Summary
All findings are minor:
This review could not run Python or the QNN tests on this runner. Claims about torch internals come from reading code, not from running it. Code Quality
Testing
RecommendationApprove Nothing blocks: the reservation fixes the reported export failures. Link the upstream issue in the TODO before merging. The other changes can come later. |
…st keeps the constants on CPU test_partitioner_keeps_constant_only_subgraph_on_cpu only lowered the model and serialized it. It caught the original crash, but it would also pass if the partitioner dropped every partition, so nothing was delegated at all. Check the lowered graph instead. The constant-only part (table * 2 - 1) must stay in the top-level graph, and the rest (the add that reads x) must still go to a delegate. With the partitioner fix removed, the test still fails as before, when building the context binary. With the fix changed to drop every partition, the test now fails on the delegate check, where it used to pass. Ran test_passes.py with the QNN SDK on Linux x86_64.
torch.export names a program's inputs after the user's arguments, but it
does not record those names in the graph's namespace. So a QNN pass that
adds nodes can give a new node the name of an input, and the pass then
fails. With an input named t_1 and an autocast region that calls t()
twice, DecomposeWrapWithAutocast copies a second transpose also named
t_1. With an input named mul, the attention decomposition creates a
second mul:
RuntimeError: Node redefined name t_1!
RuntimeError: Node redefined name mul!
LeRobot's vla_jepa and molmoact2 policies hit this when lowering to QNN.
The QNN pass manager now records every existing node name in the graph's
namespace before it runs a pipeline's passes, so every pass that adds
nodes gets fresh names. torch.fx has no public way to reserve a name, so
this adds to the namespace's private name set.
test_export_pipeline_keeps_node_names_unique runs the export pipeline on
both models and compares with eager. Both cases fail without this
change.
…round The QNN pass manager writes the graph namespace's private name set to work around torch.export, which assigns input names, and renames other nodes to make room, without recording them. Say so in a TODO at that loop, so it can be removed once torch.export records the names. test_export_pipeline_keeps_node_names_unique only compared outputs. ep.module() copies the graph and renames duplicate names, so that comparison cannot see a collision. It failed without the fix only because the passes that collide here happen to lint the graph. The test now lints the graph the pipeline returns, and first checks that t_1 and mul really are input names, so the test cannot pass by accident. Without the reservation, the Autocast and Attention cases fail with "Node redefined name". Ran test_passes.py with the QNN SDK on Linux x86_64.
…h stays on CPU test_partitioner_keeps_constant_only_subgraph_on_cpu checked that the mul of table * 2 - 1 stays in the top-level graph and that something is delegated. It would still pass if the sub of that branch were delegated, so the constant-only branch would be split between the CPU and the delegate. Also check that the sub stays in the top-level graph and that the add that reads x does not. Ran test_passes.py with the QNN SDK on Linux x86_64.
…and tighten its test The comment on the node name reservation is now a short TODO that says when it can go and why it writes private torch.fx state. The test now also checks that torch.export leaves the colliding input name unrecorded. Once torch.export records it, the test fails with a message saying the reservation can go, instead of passing without exercising it. It also checks that the autocast and attention decompositions, which add the nodes that would collide, have run.
b84aa23 to
e5f40e2
Compare
What is wrong today
A QNN pass that adds nodes can give a new node the name of a model input, and export then fails with
Node redefined name t_1!. This happens because torch.export does not record the names it assigns in the graph's name table, so a later pass can hand out a name that is already taken. Models with autocast regions hit it, such as robot policies exported in float16.What this change does
Before its passes run, the QNN pass manager records the names of the existing nodes, so new nodes always get unused names. This fixes every pass in the pipeline at once, including the attention (SDPA) pass, rather than one place that creates nodes. torch.fx has no public way to reserve a name, so this writes the namespace's private name set. A TODO marks it for removal once torch.export records the names itself.
The to-edge passes need nothing: they run on the program that
to_edgerebuilds, where every name is already recorded. Names inside nested autocast regions are already recorded too, so only the top-level graph needs this.The test-only commits make the constant-only partition test check, in the lowered graph, that the whole constant branch stays on the CPU and that the rest is delegated.
Tests
On Linux x86_64, with torch 2.14 and a torch 2.16 nightly:
t_1andmul) fails without the fix and passes with it. It lints the graph the pipeline returns, sinceep.module()would rename duplicates. It also checks that torch.export left the input name unrecorded, so once torch.export records names the test fails with a message saying the workaround can go, and that the autocast and attention decompositions ran.test_passes.pypasses, excepttest_mha_to_sha, which needs atorchaomodule that this environment does not have.