Skip to content

Commit caf8e03

Browse files
authored
Qualcomm AI Engine Direct - LPAI Support Partition (#19835)
### Summary - Support LPAI to fallback ops to CPU. Traditionally, when it gets to partial delegation for quantized model, we need to wrap qdq nodes around the unsupported node. e.g., supported_node_1(u8) -> dq(fp32) -> cpu_node(fp32) -> q(u8) -> supported_node_2. All the nodes besides from cpu_node(fp32) is run on HTP. However, LPAI's accuracy when computing dq and q node isn't as good as HTP, so to achieve good accuracy, we want to fallback q and dq nodes back to cpu too. This PR is trying to achieve this while ensuring edge graph is a valid graph to PyTorch. ### Test plan A series of test validating cases to fallback multi-output node, multi-input node, contiguous fallback, etc. `python backends/qualcomm/tests/test_qnn_delegate.py -k TestQNNQuantizedUtils.test_qnn_backend_skip_node_id_partitioner --model SM8850 --device $DEVICE --build_folder build-android --seed 42 --backend lpai` `python backends/qualcomm/tests/test_qnn_delegate.py -k TestQNNQuantizedUtils.test_qnn_backend_skip_node_op_partitioner --model SM8850 --device $DEVICE --build_folder build-android --seed 42 --backend lpai` cc @cccclai @cbilgin @abhinaykukkadapu
1 parent ca22d56 commit caf8e03

17 files changed

Lines changed: 834 additions & 285 deletions

backends/qualcomm/_passes/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
from .insert_reshape_for_reduce_ops import InsertReshapeForReduceOps
5454
from .layout_transform import LayoutTransform
5555
from .lift_constant_scalar_operands import LiftConstantScalarOperands
56+
from .lpai_partition_fallback_support import LpaiPartitionFallbackSupport
5657
from .recompose_pad_maxpool2d import RecomposePadMaxPool2d
5758
from .recompose_pixel_unshuffle import RecomposePixelUnshuffle
5859
from .recompose_rms_norm import RecomposeRmsNorm
@@ -115,6 +116,7 @@
115116
InsertRequantize,
116117
LayoutTransform,
117118
LiftConstantScalarOperands,
119+
LpaiPartitionFallbackSupport,
118120
RecomposePadMaxPool2d,
119121
RecomposePixelUnshuffle,
120122
RecomposeRmsNorm,

backends/qualcomm/_passes/backends/lpai/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
# This source code is licensed under the BSD-style license found in the
55
# LICENSE file in the root directory of this source tree.
66

7-
from .fold_qdq import LpaiFoldQDQ
87
from .qnn_lpai_pass_manager import QnnLpaiPassManager
98

109
__all__ = [
11-
LpaiFoldQDQ,
1210
QnnLpaiPassManager,
1311
]

backends/qualcomm/_passes/backends/lpai/fold_qdq.py

Lines changed: 0 additions & 77 deletions
This file was deleted.

backends/qualcomm/_passes/backends/lpai/qnn_lpai_pass_manager.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,19 @@
55
# LICENSE file in the root directory of this source tree.
66

77
from executorch.backends.qualcomm._passes import (
8+
ConvertMhaToSha,
89
DecomposeHardsigmoid,
910
DecomposeReciprocal,
1011
FoldQDQ,
12+
FuseConsecutiveCast,
13+
FuseConsecutiveTranspose,
14+
InsertRequantize,
15+
LayoutTransform,
16+
LpaiPartitionFallbackSupport,
1117
RemoveRedundancy,
18+
ResolveDebugHandle,
19+
TagQuantIO,
1220
)
13-
from executorch.backends.qualcomm._passes.backends.lpai.fold_qdq import LpaiFoldQDQ
1421
from executorch.backends.qualcomm._passes.qnn_pass_manager import QnnPassManager
1522

1623

@@ -24,37 +31,38 @@ class QnnLpaiPassManager(QnnPassManager):
2431
@classmethod
2532
def get_default_pass_activations(cls):
2633
pass_activations = super().get_default_pass_activations()
27-
pass_activations = [
28-
(LpaiFoldQDQ if p is FoldQDQ else p, act) for p, act in pass_activations
29-
]
3034
# Hardsigmoid and Reciprocal no longer appear at to_edge stage as it is decomposed in the export/annotation pipeline.
3135
# The current change is intended to proactively prepare for the upcoming deprecation of the export pipeline.
3236
pass_activations.extend(
3337
[
3438
(DecomposeHardsigmoid, True),
3539
(DecomposeReciprocal, True),
40+
(LpaiPartitionFallbackSupport, True),
3641
]
3742
)
3843
return pass_activations
3944

4045
@classmethod
4146
def get_passes_dependency_for_capture_program(cls):
4247
deps = super().get_passes_dependency_for_capture_program()
43-
# Replace FoldQDQ with LpaiFoldQDQ in the dependency table
44-
if FoldQDQ in deps:
45-
deps[LpaiFoldQDQ] = deps.pop(FoldQDQ)
46-
for key in deps:
47-
deps[key] = [LpaiFoldQDQ if v is FoldQDQ else v for v in deps[key]]
4848
# Hardsigmoid and Reciprocal no longer appear at to_edge stage as it is decomposed in the export/annotation pipeline.
4949
# The current change is intended to proactively prepare for the upcoming deprecation of the export pipeline.
5050
deps.update(
5151
{
5252
DecomposeHardsigmoid: [RemoveRedundancy],
5353
DecomposeReciprocal: [RemoveRedundancy],
54+
LpaiPartitionFallbackSupport: [TagQuantIO],
55+
ResolveDebugHandle: [LpaiPartitionFallbackSupport],
5456
}
5557
)
5658
return deps
5759

60+
def _validate_edge_passes(self) -> None:
61+
super()._validate_edge_passes()
62+
assert isinstance(
63+
self.passes[-2], LpaiPartitionFallbackSupport
64+
), "Please ensure LpaiPartitionFallbackSupport is the last edge pass before ResolveDebugHandle."
65+
5866
@classmethod
5967
def get_annotation_passes(cls):
6068
passes = [DecomposeHardsigmoid, DecomposeReciprocal]
@@ -74,5 +82,14 @@ def get_export_passes(
7482

7583
@classmethod
7684
def get_preprocess_passes(cls, use_mha2sha=False):
77-
passes = super().get_preprocess_passes(use_mha2sha)
78-
return [LpaiFoldQDQ if p is FoldQDQ else p for p in passes]
85+
passes = [
86+
FoldQDQ,
87+
ConvertMhaToSha,
88+
InsertRequantize,
89+
LayoutTransform,
90+
FuseConsecutiveCast,
91+
FuseConsecutiveTranspose,
92+
]
93+
if not use_mha2sha:
94+
passes.remove(ConvertMhaToSha)
95+
return passes

backends/qualcomm/_passes/fold_qdq.py

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@
66
import torch
77
from executorch.backends.qualcomm.builders.node_visitor import dq_ops, q_ops
88
from executorch.backends.qualcomm.builders.utils import is_parameter
9-
from executorch.backends.qualcomm.utils.constants import (
10-
QCOM_BYPASS_NODE,
11-
QCOM_FALLBACK_NODE,
12-
)
9+
from executorch.backends.qualcomm.utils.constants import QCOM_BYPASS_NODE
1310
from executorch.exir.dialects._ops import ops as exir_ops
1411
from executorch.exir.pass_base import ExportPass, PassResult
1512
from executorch.exir.passes import dead_code_elimination_pass
@@ -42,26 +39,20 @@ def _fold_dq(self, graph_module: torch.fx.GraphModule) -> torch.fx.GraphModule:
4239
if n.target not in dq_ops:
4340
continue
4441

45-
if not self.force_fold and (
46-
QCOM_BYPASS_NODE in n.meta or QCOM_FALLBACK_NODE in n.meta
47-
):
48-
continue
49-
50-
for user_n in user_list:
51-
user_n.replace_input_with(n, n.args[0])
52-
graph_module.graph.erase_node(n)
42+
# skip parameters & buffers
43+
if not self.force_fold and is_parameter(n.args[0], self.edge_program):
44+
self._annotate_bypass(n)
45+
else:
46+
for user_n in user_list:
47+
user_n.replace_input_with(n, n.args[0])
48+
graph_module.graph.erase_node(n)
5349

5450
def _fold_q(self, graph_module: torch.fx.GraphModule) -> torch.fx.GraphModule:
5551
# remove q
5652
for n in graph_module.graph.nodes:
5753
if n.target not in q_ops:
5854
continue
5955

60-
if not self.force_fold and (
61-
QCOM_BYPASS_NODE in n.meta or QCOM_FALLBACK_NODE in n.meta
62-
):
63-
continue
64-
6556
to_be_removed = [n]
6657
source_n = n.args[0]
6758

@@ -85,16 +76,7 @@ def _fold_q(self, graph_module: torch.fx.GraphModule) -> torch.fx.GraphModule:
8576
for n in to_be_removed:
8677
graph_module.graph.erase_node(n)
8778

88-
def _preserve_qdq(self, graph_module: torch.fx.GraphModule) -> torch.fx.GraphModule:
89-
for n in graph_module.graph.nodes:
90-
# skip parameters & buffers
91-
if n.target in dq_ops and is_parameter(n.args[0], self.edge_program):
92-
self._annotate_bypass(n)
93-
continue
94-
9579
def call(self, graph_module: torch.fx.GraphModule):
96-
if not self.force_fold:
97-
self._preserve_qdq(graph_module)
9880
self._fold_dq(graph_module)
9981
self._fold_q(graph_module)
10082
dead_code_elimination_pass(graph_module)

0 commit comments

Comments
 (0)