Qualcomm AI Engine Direct - Pass migration - part 1 - #20738
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/20738
Note: Links to docs will display an error until the docs builds have been completed. ❌ 1 New FailureAs of commit 735df03 with merge base e500fee ( NEW FAILURE - The following job has failed:
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
This PR needs a
|
a81e2d9 to
c76c3cd
Compare
- Add `lift_constant_tensor_pass` after `EdgeProgramManager` completes the given passes. - Refactor the execution phase of Qualcomm‑specific passes and move to `to_edge_transform_and_lower`. - Introduce the `AnnotateGetAttr` pass to ensure quantization attributes are preserved for the `get_attr` node.
c76c3cd to
735df03
Compare
|
Hi @psiddh, |
| if transform_passes is not None: | ||
| edge_manager = edge_manager.transform(transform_passes) | ||
| for method in edge_manager.methods: | ||
| lift_constant_tensor_pass(edge_manager.exported_program(method)) |
There was a problem hiding this comment.
This is the shared export code that every backend goes through, not just specific to QNN. wouldn't it silently change the other backend's input signatures? Instead of here can we restrict it into QNN specific flow ?
There was a problem hiding this comment.
This is the most critical part of the entire migration series.
One of the main challenges is that transform_passes currently operates on a GraphModule rather than an ExportedProgram. As a result, any pass that needs to modify constants at this stage (for example, converting Conv1d to Conv2d, which requires updating the weight tensor and propagating the change back to the ExportedProgram's input signature) cannot be handled correctly without lifting the constant tensors. This is the reason lift_constant_tensor_pass is required here.
In the original workflow, this logic was confined to the QNN-specific path within to_edge_transform_and_lower_to_qnn, which is exactly the flow that this PR is attempting to deprecate and migrate away from.
Given that this is the shared export code that every backend goes through, I was wondering if these two lines of change be considered acceptable once the full CI suite passes across all backends.
There was a problem hiding this comment.
Hi @psiddh,
I'd appreciate your perspective on this part when you have a chance.
| ) -> torch.fx.GraphModule: | ||
| for node in graph_module.graph.nodes: | ||
| if node.op == "get_attr" and list(node.users)[0].target in dq_ops: | ||
| dq_op = list(node.users)[0] |
There was a problem hiding this comment.
Is it possible that get_attr has no users ? If so it'll IndexError and abort the export ? Also since it only checks the first user (is this guaranteed ), a constant whose dq isn't first would silently miss other attrs? Could we guard on node.users and scan for any dq user instead of assuming [0] ?
There was a problem hiding this comment.
Thanks for pointing this out.
I don't expect the get_attr node to have no users in this case, but I agree that adding a defensive check would make it more robust. I'll also add the check and validate for any dq user in node.users.
| from .utils import get_quant_attrs | ||
|
|
||
|
|
||
| class AnnotateGetAttr(ExportPass): |
There was a problem hiding this comment.
should it call super().init()
There was a problem hiding this comment.
I don't think this is required, as the current implementation works correctly without it.
| from .utils import get_quant_attrs | ||
|
|
||
|
|
||
| class AnnotateGetAttr(ExportPass): |
There was a problem hiding this comment.
Also this pass exists because other passes drop quant metadata on get_attr nodes ? If so, would it be better to have other passes preserve the it instead ? What do you think ?
There was a problem hiding this comment.
Yes, this pass exists because other passes can inadvertently drop quant metadata on get_attr nodes.
Although we could patch each affected pass individually, that approach does not scale well. Any future pass that recreates or transforms the graph through
graph_module = super().call(graph_module).graph_module
would need to remember to preserve the quant attributes for get_attr nodes as well. Missing that step could easily introduce regressions.
For that reason, I added a generic AnnotateGetAttr pass to address this problem once for all. I believe this provides a more uniform and maintainable solution, while also reducing the risk of future passes accidentally breaking the get_attr's quantization metadata propagation.
|
@claude review |
|
Claude finished @psiddh's task in 2m 43s —— View job Claude Code Review — Pass migration part 1I focused on the four open review threads from @psiddh plus a couple of adjacent findings. Overall the migration direction is sound; the concerns raised are legitimate and I agree with most of them. 1.
|
|
@zhaoxul-qti can you take a look at the comments above ? |
Sorry, @zhaoxul-qti is currently OoO and should be back this week. He'll take a look at the comments and respond as soon as possible. Thanks for the review! |
Summary
This is part 1 of a series whose goal is to deprecate
to_edge_transform_and_lower_to_qnnand migrate Qualcomm-specific passes from the export pipeline intoto_edge_transform_and_lower.The core change is in
exir/program/_program.py: after theEdgeProgramManagerfinishes runningtransform_passes, we now invokelift_constant_tensor_passon each method'sExportedProgram. Several QNN passes (CanonicalizeConv,ConvertLinearToConv2d, etc.) rewrite constant values and insert newget_attrnodes to store them. Without lifting, these mutated constants are not reflected back into theExportedProgram;lift_constant_tensor_passpromotes theget_attrnodes into placeholders so the modified constants are correctly propagated.Changes
exir/program/_program.py: runlift_constant_tensor_passover every method afteredge_manager.transform(transform_passes)completes.CanonicalizeConvandConvertLinearToConv2dout of the export pipeline (get_export_passes) and into the to-edge transform pipeline.AnnotateGetAttrto repopulateQCOM_QUANT_ATTRSforget_attrnodes. Passes such asI64toI32andLayoutTransformreconstruct theGraphModule(graph_module = super().call(graph_module).graph_module), which drops the quant attributes previously stored onget_attrnode metadata.Test plan
CanonicalizeConvTest cases:
TEST_MATRIX = {TestQNNFloatingPointOperator.test_qnn_backend_conv1d TestQNNFloatingPointOperator.test_qnn_conv1d_batch_norm TestQNNFloatingPointOperator.test_qnn_backend_conv2d TestQNNFloatingPointOperator.test_qnn_backend_conv3d_sequential TestQNNFloatingPointOperator.test_qnn_backend_conv_transpose1d TestQNNFloatingPointOperator.test_qnn_backend_conv_transpose2d TestQNNFloatingPointOperator.test_qnn_backend_conv_transpose3d TestQNNFloatingPointModel.test_qnn_backend_conv1d_relu_log_softmax TestQNNQuantizedOperator.test_qnn_backend_conv1d TestQNNQuantizedOperator.test_qnn_conv1d_batch_norm TestQNNQuantizedOperator.test_qnn_backend_conv2d TestQNNQuantizedOperator.test_qnn_backend_conv3d_sequential TestQNNQuantizedOperator.test_qnn_backend_conv_transpose1d TestQNNQuantizedOperator.test_qnn_backend_conv_transpose2d TestQNNQuantizedOperator.test_qnn_backend_conv_transpose3d TestQNNQuantizedModel.test_qnn_backend_conv1d_relu_log_softmax}Command:
python backends/qualcomm/tests/test_qnn_delegate.py ${TEST_MATRIX} --build_folder build-android/ --host ${HOST_NAME} --device ${DEVICE_ID} --soc_model ${SOC_ID} --seed 1126 --backend htpConvertLinearToConv2dTest cases:
TEST_MATRIX = {TestQNNFloatingPointOperator.test_qnn_backend_linear TestQNNFloatingPointOperator.test_qnn_backend_linear_to_conv2d TestQNNFloatingPointOperator.test_qnn_backend_linear_shared_weights TestQNNFloatingPointOperator.test_qnn_backend_linear_to_conv2d_shared_weights TestQNNQuantizedOperator.test_qnn_backend_linear TestQNNQuantizedOperator.test_qnn_backend_linear_to_conv2d TestQNNQuantizedOperator.test_qnn_backend_linear_shared_weights TestQNNQuantizedOperator.test_qnn_backend_linear_to_conv2d_shared_weights TestQNNQuantizedOperator.test_qnn_backend_linear_block TestQNNQuantizedOperator.test_qnn_backend_linear_to_conv2d_block TestQNNQuantizedOperator.test_qnn_backend_linear_qat}Command:
python backends/qualcomm/tests/test_qnn_delegate.py ${TEST_MATRIX} --build_folder build-android/ --host ${HOST_NAME} --device ${DEVICE_ID} --soc_model ${SOC_ID} --seed 1126 --backend htp