[Qualcomm] Partitioner falls back on ops without a node visitor - #21607
[Qualcomm] Partitioner falls back on ops without a node visitor#21607psiddh wants to merge 4 commits into
Conversation
QnnOperatorSupport.is_node_supported indexed self.node_visitors[op] directly, so any op lacking a QNN node visitor raised KeyError and aborted the whole partition instead of falling back to CPU (as XNNPACK's partitioner does). Guard the lookup and return False for unsupported ops. Surfaced by Mamba2, whose causal mask uses ~torch.tril(...) -> aten.bitwise_not.default, an op QNN has no visitor for. Adds a regression test that fails with KeyError before. Co-authored-with: Claude <noreply@anthropic.com>
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21607
Note: Links to docs will display an error until the docs builds have been completed. ❗ 1 Active SEVsThere are 1 currently active SEVs. If your PR is affected, please view them below: ✅ You can merge normally! (1 Unrelated Failure)As of commit 2b89f8c with merge base bb6b99a ( FLAKY - The following job failed but was likely due to flakiness present on trunk:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
This PR needs a
|
There was a problem hiding this comment.
Pull request overview
This PR fixes a crash in the Qualcomm QNN partitioner where QnnOperatorSupport.is_node_supported could raise a KeyError when encountering an operator that has no registered QNN node visitor, aborting partitioning instead of falling back to CPU.
Changes:
- Add a guard in
QnnOperatorSupport.is_node_supportedto returnFalse(CPU fallback) when an op has no node visitor, avoidingKeyError. - Add a regression test covering an op without a visitor (
aten.bitwise_not.default) to ensure lowering doesn’t crash.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| backends/qualcomm/partition/qnn_partitioner.py | Adds missing-visitor guard to prevent KeyError and allow CPU fallback. |
| backends/qualcomm/tests/test_passes.py | Adds a regression test intended to ensure unknown ops don’t abort QNN lowering. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-with: Claude <noreply@anthropic.com>
shewu-quic
left a comment
There was a problem hiding this comment.
Thanks for your help.
Could you please help to add aten.bitwise_not.default into to_be_implemented_operator?
https://github.com/pytorch/executorch/blob/main/backends/qualcomm/partition/common_defs.py
Per review: list bitwise_not as a known-unsupported op so it reports the tracked 'can be supported, please report an issue' message. The partitioner guard remains as the general safety net for any other visitor-less op. Co-authored-with: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (3)
backends/qualcomm/partition/common_defs.py:28
- Adding
exir_ops.edge.aten.bitwise_not.defaulttoto_be_implemented_operatormakes the op take the "Skipped, this op can be supported" early-return path, which bypasses the new "missing node visitor" fallback and will also keep forcing CPU fallback even if a node visitor is later added. Since the purpose of this PR is to handle missing visitors generically, this entry is redundant and makes follow-up support harder.
exir_ops.edge.aten.median.default,
exir_ops.edge.aten.median.dim,
exir_ops.edge.aten.round.decimals,
exir_ops.edge.aten.le.Scalar,
exir_ops.edge.aten.bitwise_not.default,
backends/qualcomm/tests/test_passes.py:511
- This test currently depends on end-to-end lowering and a QNN SDK presence (so it may be skipped in many environments), and it doesn’t assert that
bitwise_notis actually taking the “missing visitor” path (it could be filtered earlier via operator lists or pass transforms). You can make it a deterministic unit regression by (1) checking the edge graph containsexir_ops.edge.aten.bitwise_not.default, (2) asserting it is not in the registered node visitor dict, (3) asserting it is not pre-filtered byto_be_implemented_operator, and (4) callingQnnOperatorSupport.is_node_supportedwith a minimal mock to ensure it returnsFalsewithout raising.
# Guard against a vacuous test: the visitor-less op must actually be present.
exported = torch.export.export(BitwiseNot().eval(), sample_input)
self.assertTrue(
any(
node.op == "call_function" and "bitwise_not" in str(node.target)
backends/qualcomm/tests/test_passes.py:484
- Using
~(x > 0)relies on PyTorch tracing details and can be rewritten/decomposed in ways that removeaten.bitwise_notfrom the exported/edge graph, making the regression test brittle. Callingtorch.ops.aten.bitwise_not.defaultexplicitly makes the test deterministic about the operator it is targeting.
def forward(self, x):
return (~(x > 0)).to(torch.float32) + x
| unguarded lookup aborted the whole partition instead of falling back. | ||
| """ | ||
|
|
||
| class BitwiseNot(torch.nn.Module): |
There was a problem hiding this comment.
Minor: If we add this op to the to_be_implemented_operator list, this test won't really exercise the path we're trying to validate right? I suggest changing to something like aten.frac.default since it has no visitor and the model is simple:
class FracModule(torch.nn.Module):
def forward(self, x):
return torch.frac(x) + x
[Qualcomm] Partitioner falls back on ops without a node visitor (fix KeyError)
Summary
QnnOperatorSupport.is_node_supportedindexedself.node_visitors[node.target.__name__]directly, so any op that has no QNN node visitor raised
KeyErrorand aborted the entirepartition instead of falling back to CPU. XNNPACK's partitioner returns
Falsefor unknownops; QNN should do the same.
Symptom
Lowering Mamba2 to QNN crashes:
Mamba2's causal mask uses
~torch.tril(...), which traces toaten.bitwise_not.default— an opQNN ships no visitor for. This is not SSM/conv specific: any graph containing an op without a
visitor hits it.
Fix
Guard the lookup and return
False(→ CPU fallback) for unsupported ops:Testing
test_partitioner_falls_back_on_op_without_visitor(intest_passes.py): lowers a module whosegraph contains
aten.bitwise_not.defaultand assertsto_executorch()succeeds. Fails on thepre-fix code with the exact
KeyErrorabove; passes after. Verified by file-swap.Notes
aten.bitwise_not.defaultto theop_logical_notvisitor's target list so it delegates to QNN (on a bool tensor it's equivalent to
logical_not),putting more of Mamba2 on the HTP instead of CPU.
skips SSM with an inaccurate "CanonicalizeConv unsqueezes a conv bias" comment — the real cause is
this partitioner KeyError.
cc @cbilgin