Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backends/qualcomm/partition/common_defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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,
]

constant_operator = [
Expand Down
5 changes: 5 additions & 0 deletions backends/qualcomm/partition/qnn_partitioner.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ def is_node_supported(self, _, node: torch.fx.Node) -> bool:
return False

supported = False
if node.target.__name__ not in self.node_visitors:
logger.info(
f"[{self.phase}] {node.target.__name__} | No node visitor, unsupported"
)
return False
op_wrapper = self.node_visitors[node.target.__name__].define_node(
node, self.nodes_to_wrappers
)
Expand Down
40 changes: 40 additions & 0 deletions backends/qualcomm/tests/test_passes.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,46 @@ def test_decompose_hardsigmoid_backend_aware(self):
f"hardsigmoid {'should' if should_decompose else 'should NOT'} be decomposed for {backend.name}",
)

def test_partitioner_falls_back_on_op_without_visitor(self):
"""QnnOperatorSupport must reject an op that has no node visitor by returning
False (CPU fallback), not by KeyError-ing on the node_visitors lookup.

Uses aten.frac.default: it has no QNN node visitor and is not on any partition
operator list, so it exercises the missing-visitor guard directly. (bitwise_not,
the op that first surfaced this on Mamba2, is now in to_be_implemented_operator,
which returns earlier and would not reach the guard.)
"""

class FracModule(torch.nn.Module):
def forward(self, x):
return torch.frac(x) + x

sample_input = (torch.randn(1, 4),)

# Guard against a vacuous test: the visitor-less op must actually be present.
exported = torch.export.export(FracModule().eval(), sample_input)
self.assertTrue(
any(
node.op == "call_function" and "frac" in str(node.target)
for node in exported.graph.nodes
),
"expected aten.frac.default in the traced graph",
)

compiler_specs = generate_qnn_executorch_compiler_spec(
soc_model=QcomChipset.SM8650,
backend_options=generate_htp_compiler_spec(use_fp16=True),
)
try:
# Must not raise KeyError: frac falls back to CPU; the rest may delegate.
edge = to_edge_transform_and_lower_to_qnn(
FracModule().eval(), sample_input, compiler_specs
)
edge.to_executorch()
except RuntimeError as e:
if "QNN" in str(e) or "qnn" in str(e):
self.skipTest(f"QNN SDK not available: {e}")

def test_index_put_int64_value_not_quantized(self):
"""QNN's IndexPut annotator must skip a non-float (int64) value arg.

Expand Down
Loading