Qualcomm AI Engine Direct - accept an ExportedProgram in to_edge_tran… - #23025
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/23025
Note: Links to docs will display an error until the docs builds have been completed. ✅ You can merge normally! (1 Unrelated Failure)As of commit 57f1267 with merge base 273cb33 ( BROKEN TRUNK - The following job failed but were present on the merge base:👉 Rebase onto the `viable/strict` branch to avoid these failures
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
|
This PR needs a
|
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Update the inputs type annotation and add coverage for dictionary ExportedProgram inputs.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Adds support for passing caller-supplied ExportedProgram instances to Qualcomm QNN lowering without re-exporting.
Changes:
- Supports single and dictionary
ExportedPrograminputs. - Preserves caller-defined graph transformations and dynamic shapes.
- Adds regression coverage for supplied programs.
| File | Summary | Review findings |
|---|---|---|
backends/qualcomm/utils/utils.py |
Extends the lowering API for ExportedProgram inputs. |
Moderate: annotate inputs=None correctly (4 votes). Nit: add dictionary-input regression coverage (1 vote). |
backends/qualcomm/tests/test_passes.py |
Tests lowering a supplied ExportedProgram. |
Coverage does not yet exercise the dictionary path. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
mergennachin
left a comment
There was a problem hiding this comment.
Review assisted by Codex.
I also agree with the existing inputs annotation comment: the public signature should allow the documented inputs=None usage, through Optional or an overload.
I found no runtime correctness issue in the dispatch change. QNN CI checks passed; full QNN lowering was not run locally on macOS.
shewu-quic
left a comment
There was a problem hiding this comment.
Thank you for your contribution. Could you help to add the test in our new test framework?
diff --git a/backends/qualcomm/tests/rework/src/utils.py b/backends/qualcomm/tests/rework/src/utils.py
index 9202007b18..95004f6e69 100644
--- a/backends/qualcomm/tests/rework/src/utils.py
+++ b/backends/qualcomm/tests/rework/src/utils.py
@@ -633,3 +633,20 @@ class QAT:
assert len(weight_fqs) > 0, "no int8-range weight FQ found for fp16a8w"
# should complete without error
__class__._get_converted_module(module, prepared, inputs)
+
+class LoweringWithExportedProgram:
+ @staticmethod
+ def test(compile_spec):
+ from unittest import mock
+ module = _UtilsModel()
+ inputs = (torch.randn(1, 4, 8, 8), torch.randn(1, 4, 8, 8))
+ exported = torch.export.export(module, inputs, strict=True)
+ with mock.patch(
+ "torch.export.export",
+ wraps=torch.export.export,
+ ) as export_spy:
+ _ = to_edge_transform_and_lower_to_qnn(
+ exported, inputs, compile_spec
+ )
+ export_spy.assert_not_called()
+
diff --git a/backends/qualcomm/tests/rework/utils/test.py b/backends/qualcomm/tests/rework/utils/test.py
index 8330f79ab8..f412714702 100644
--- a/backends/qualcomm/tests/rework/utils/test.py
+++ b/backends/qualcomm/tests/rework/utils/test.py
@@ -39,3 +39,6 @@ def test_skip_node_quantizer(subtests, quantizer, compile_spec):
def test_qat(subtests):
QAT.test(subtests) # noqa: F405
+
+def test_lowering_with_exported_program(compile_spec):
+ LoweringWithExportedProgram.test(compile_spec) # noqa: F405
8e58297 to
cae5d49
Compare
|
@mergennachin , you were right that the shape assertion couldn't detect a re-export. The test now spies on torch.export.export and asserts it is never called during lowering. I checked it against your counterexample (accept the ExportedProgram, then re-export m.module()): it fails with Expected 'export' to not have been called. Called 1 times. @shewu-quic added LoweringWithExportedProgram to the rework framework using the same spy, plus the validation you asked for: supplying inputs or dynamic_shapes alongside an ExportedProgram now warns naming which arguments were ignored, with a test for it. |
…sform_and_lower_to_qnn to_edge_transform_and_lower_to_qnn only took a module and always re-exported it internally, so a caller that has already captured a program - with its own dynamic shapes, fx fixes or graph surgery - had no way to hand that program in and had to reimplement the transform/partition/lower sequence against backends.qualcomm._passes directly. Accept ExportedProgram (and Dict[str, ExportedProgram]) and skip the internal re-export for those entries. Module inputs are unchanged. inputs is now Optional, matching the documented inputs=None usage, and supplying inputs or dynamic_shapes alongside an already-captured program warns rather than silently dropping them. A shape assertion alone cannot detect a re-export, since re-exporting exported.module() reproduces the original graph. The tests therefore spy on torch.export.export and require that lowering never calls it; both the unittest and the rework-framework test fail against a variant that accepts the program and re-exports it.
cae5d49 to
2e2e12c
Compare
Done |
|
Addressed all the feedback |


…sform_and_lower_to_qnn
to_edge_transform_and_lower_to_qnn only took a module and always re-exported it internally, so a caller that has already captured a program - with its own dynamic shapes, fx fixes or graph surgery - had no way to hand that program in and had to reimplement the transform/partition/lower sequence against backends.qualcomm._passes directly.
Accept ExportedProgram (and Dict[str, ExportedProgram]) and skip the internal re-export for those entries. Module inputs are unchanged.
cc @cbilgin