Skip to content

Commit fbd14e6

Browse files
Arm backend: Avoid unnecessary lowering pass retraces (pytorch#21138)
Add cheap applicability checks before selected Arm passes enter the ExportPass retracing path. Skip InsertRescalePass for profiles without integer support and graphs without DQ nodes. Move symbolic shape and adaptive pool passes to ArmOpTargetedPass, and pre-scan RemoveGetItemPass producers. These checks preserve target-present transformations while avoiding retraces on non-applicable graphs. Lowering times are medians of three fresh-process exports with no warm-up. Regular models use TOSA FP with FP32; full Qwen uses BF16. | Model | Base (s) | Updated (s) | Speedup | |-------------------|---------:|------------:|--------:| | ResNet-18 | 3.633 | 3.519 | 3.16% | | MobileNetV3 | 11.063 | 10.100 | 8.70% | | DeiT-tiny | 33.323 | 31.137 | 6.56% | | Conformer | 13.121 | 12.204 | 6.98% | | Test Qwen text | 11.582 | 10.962 | 5.35% | | Test Qwen vision | 11.979 | 11.388 | 4.93% | | Full Qwen text | 155.825 | 146.824 | 5.78% | | Full Qwen vision | 126.671 | 120.153 | 5.15% | Change-Id: I3c539e023efb5d1e59a725e531c850a897758002 cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani Signed-off-by: Yufeng Shi <yufeng.shi@arm.com>
1 parent e2c9a48 commit fbd14e6

5 files changed

Lines changed: 54 additions & 5 deletions

File tree

backends/arm/_passes/decompose_adaptive_max_pool2d_pass.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from typing import Set, Type
77

88
import torch
9-
from executorch.backends.arm._passes import ArmPass
9+
from executorch.backends.arm._passes import ArmOpTargetedPass
1010
from executorch.backends.arm.constants import NHWC_INVERSE_ORDER, NHWC_ORDER
1111
from executorch.backends.arm.tosa.dialect.ops.max_pool2d import (
1212
compute_max_pool2d_output_shape,
@@ -16,7 +16,7 @@
1616
from executorch.exir.pass_base import ExportPass, NodeMetadata
1717

1818

19-
class DecomposeAdaptiveMaxPool2dPass(ArmPass):
19+
class DecomposeAdaptiveMaxPool2dPass(ArmOpTargetedPass):
2020
"""Decompose irregular TOSA MAX_POOL2D_ADAPTIVE into per-bin slices.
2121
2222
For dynamic-shape cases where ``MAX_POOL2D_ADAPTIVE`` cannot directly map
@@ -27,6 +27,7 @@ class DecomposeAdaptiveMaxPool2dPass(ArmPass):
2727
"""
2828

2929
_passes_required_after: Set[Type[ExportPass]] = set()
30+
target_ops = {exir_ops.backend.tosa.MAX_POOL2D_ADAPTIVE.default}
3031

3132
@staticmethod
3233
def _is_static_dim(dim) -> bool:

backends/arm/_passes/insert_rescales_pass.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from executorch.backends.arm._passes.quant_args import QuantArgs
2020
from executorch.backends.arm.constants import DQ_OPS, Q_OPS
2121
from executorch.backends.arm.tosa.mapping import TosaSpecialDtype
22+
from executorch.backends.arm.tosa.specification import get_context_spec
2223
from executorch.exir.dialects._ops import ops as exir_ops
2324
from executorch.exir.pass_base import ExportPass, PassResult
2425
from torch.fx import GraphModule, Node
@@ -110,6 +111,14 @@ def fold_dq_q_to_rescale(self, node: Node, user: Node, graph_module: GraphModule
110111
user.replace_all_uses_with(rescale_node)
111112
graph_module.graph.erase_node(user)
112113

114+
def should_run_pass(self, graph_module: GraphModule) -> bool:
115+
if not get_context_spec().support_integer():
116+
return False
117+
return any(
118+
graph_module.graph.find_nodes(op="call_function", target=target, sort=False)
119+
for target in DQ_OPS
120+
)
121+
113122
def call(self, graph_module: GraphModule) -> PassResult:
114123
modified = False
115124
for node in graph_module.graph.nodes:

backends/arm/_passes/remove_getitem_pass.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2025 Arm Limited and/or its affiliates.
1+
# Copyright 2025-2026 Arm Limited and/or its affiliates.
22
#
33
# This source code is licensed under the BSD-style license found in the
44
# LICENSE file in the root directory of this source tree.
@@ -12,3 +12,14 @@
1212

1313
class RemoveGetItemPass(ArmPass, remove_getitem_op.RemoveGetItemPass):
1414
_passes_required_after: Set[Type[ExportPass]] = set()
15+
_target_names = {
16+
"aten.max_pool2d_with_indices.default",
17+
"aten.max.dim",
18+
}
19+
20+
def should_run_pass(self, graph_module) -> bool:
21+
return any(
22+
node.op == "call_function"
23+
and getattr(node.target, "__name__", None) in self._target_names
24+
for node in graph_module.graph.nodes
25+
)

backends/arm/_passes/symbolic_to_tosa_shape_pass.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
from typing import Optional
88

99
import torch
10-
from executorch.backends.arm._passes.arm_pass import ArmPass
10+
from executorch.backends.arm._passes.arm_pass import ArmOpTargetedPass
1111
from executorch.exir.dialects._ops import ops as exir_ops
1212

1313

14-
class SymbolicToTosaShapesPass(ArmPass):
14+
class SymbolicToTosaShapesPass(ArmOpTargetedPass):
1515

1616
_passes_required_after = set()
17+
target_ops = {torch.ops.aten.sym_size.int}
1718

1819
def call_operator(self, op, args, kwargs, meta, updated: Optional[bool] = False):
1920
if op == torch.ops.aten.sym_size.int:

backends/arm/test/passes/test_ioquantization_pass.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,33 @@ def _build_dq_q_graph(
123123
return builder.get_graph_module()
124124

125125

126+
def test_insert_rescale_should_run_based_on_spec_and_dq():
127+
"""Check integer support and DQ presence before running the pass."""
128+
graph_module = _build_dq_q_graph(
129+
torch.randint(-128, 127, (1, 4), dtype=torch.int8),
130+
torch.int8,
131+
torch.int8,
132+
dq_scale=0.5,
133+
dq_zp=0,
134+
q_scale=0.25,
135+
q_zp=0,
136+
)
137+
insert_rescale = InsertRescalePass()
138+
139+
with TosaLoweringContext(TosaSpecification.create_from_string("TOSA-1.0+FP")):
140+
assert not insert_rescale.should_run_pass(graph_module)
141+
142+
with TosaLoweringContext(TosaSpecification.create_from_string("TOSA-1.0+INT")):
143+
assert insert_rescale.should_run_pass(graph_module)
144+
145+
builder = GraphBuilder()
146+
x = builder.placeholder("x", torch.rand(1, 4))
147+
builder.output([x])
148+
graph_without_dq = builder.get_graph_module()
149+
with TosaLoweringContext(TosaSpecification.create_from_string("TOSA-1.0+INT")):
150+
assert not insert_rescale.should_run_pass(graph_without_dq)
151+
152+
126153
def test_insert_rescale_tosa_INT_folds_uint8_input():
127154
graph_module = _build_dq_q_graph(
128155
torch.randint(0, 255, (1, 4), dtype=torch.uint8),

0 commit comments

Comments
 (0)