Skip to content

Commit d03dee5

Browse files
Arm backend: Skip irrelevant lowering passes (#20659)
Add custom should_run_pass() pre-scans for Arm passes whose full ExportPass retracing path is only useful for specific graph patterns. The pre-scan keeps existing transforms unchanged when a relevant node is present, but returns an unmodified PassResult when the graph cannot be affected by the pass. This avoids rebuilding graphs for placeholder, rank, dtype, buffer, layout, no-op, and decomposition passes that would otherwise scan the whole graph in call() and make no changes. | Model | Improvement | |-------------|------------:| | DeepLabV3 | +10.9% | | Wav2Letter | +5.0% | | InceptionV3 | +3.2% | | MobileNetV2 | +12.8% | | MobileNetV3 | +12.1% | Signed-off-by: Sebastian Larsson <sebastian.larsson@arm.com>
1 parent 1b26db1 commit d03dee5

19 files changed

Lines changed: 206 additions & 69 deletions

backends/arm/_passes/broadcast_args_pass.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,35 @@ class BroadcastArgsPass(ArmPass):
2828

2929
_passes_required_after: Set[Type[ExportPass]] = set()
3030

31-
targeted_ops = {
31+
target_ops = {
3232
exir_ops.edge.aten.add.Tensor,
3333
exir_ops.edge.aten.sub.Tensor,
3434
# mul is indirectly targeting div as div is decompsed to reciprocal + mul
3535
exir_ops.edge.aten.mul.Tensor,
3636
}
3737

38-
def call(self, graph_module: GraphModule) -> PassResult:
38+
def should_run_pass(self, graph_module: GraphModule) -> bool:
3939
tosa_spec = get_context_spec()
4040
if not tosa_spec.is_U55_subset:
41-
return PassResult(graph_module, False)
41+
return False
42+
for node in graph_module.graph.nodes:
43+
if node.op != "call_function" or node.target not in self.target_ops:
44+
continue
45+
output_shape = get_first_fake_tensor(node).shape
46+
broadcasts = 0
47+
for arg in node.args:
48+
if not isinstance(arg, Node):
49+
continue
50+
if get_first_fake_tensor(arg).shape != output_shape:
51+
broadcasts += 1
52+
if broadcasts > 1:
53+
return True
54+
return False
55+
56+
def call(self, graph_module: GraphModule) -> PassResult:
4257
modified = False
4358
for node in graph_module.graph.nodes:
44-
if node.op != "call_function" or node.target not in self.targeted_ops:
59+
if node.op != "call_function" or node.target not in self.target_ops:
4560
continue
4661

4762
output_shape = get_first_fake_tensor(node).shape

backends/arm/_passes/cast_int64_pass.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,19 @@ def __init__(self, exported_program: ExportedProgram, *args, **kwargs):
2525
super().__init__(*args, **kwargs)
2626
self.exported_program = exported_program
2727

28+
def should_run_pass(self, graph_module: torch.fx.GraphModule) -> bool:
29+
for node in graph_module.graph.nodes:
30+
if len(node.users) == 0:
31+
continue
32+
fake_tensor = node.meta.get("val")
33+
if not isinstance(fake_tensor, torch._subclasses.fake_tensor.FakeTensor):
34+
continue
35+
if fake_tensor.dtype == torch.int64 and is_buffer(
36+
self.exported_program, node
37+
):
38+
return True
39+
return False
40+
2841
def _assert_within_int32(self, tensor: torch.Tensor, node: torch.fx.Node):
2942
if torch.min(tensor) < torch.iinfo(torch.int32).min:
3043
raise RuntimeError(

backends/arm/_passes/cast_to_int32_pass.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,33 @@
77

88
import torch
99

10-
from executorch.backends.arm._passes.arm_pass import ArmPass
10+
from executorch.backends.arm._passes.arm_pass import ArmOpTargetedPass
1111

1212
from executorch.backends.arm.tosa.specification import get_context_spec
1313
from executorch.exir.dialects._ops import ops as exir_ops
14-
from executorch.exir.pass_base import ExportPass, PassResult
14+
from executorch.exir.pass_base import ExportPass
1515

1616

17-
class CastToInt32Pass(ArmPass):
17+
class CastToInt32Pass(ArmOpTargetedPass):
1818
"""Casts the input to int32 if it is not already and casts back the output
1919
to the original input dtype.
2020
"""
2121

2222
_passes_required_after: Set[Type[ExportPass]] = set()
2323

24-
targeted_ops = {
24+
target_ops = {
2525
exir_ops.edge.aten.bitwise_left_shift.Tensor,
2626
exir_ops.edge.aten.bitwise_right_shift.Tensor,
2727
}
2828

29-
def call(self, graph_module: torch.fx.GraphModule) -> PassResult:
29+
def should_run_pass(self, graph_module: torch.fx.GraphModule) -> bool:
3030
tosa_spec = get_context_spec()
3131
if not tosa_spec.is_U55_subset:
32-
return PassResult(graph_module, False)
33-
return super().call(graph_module)
32+
return False
33+
return super().should_run_pass(graph_module)
3434

3535
def call_operator(self, op, args, kwargs, meta):
36-
if op not in self.targeted_ops:
36+
if op not in self.target_ops:
3737
return super().call_operator(op, args, kwargs, meta)
3838

3939
new_args: list = []

backends/arm/_passes/conv1d_unsqueeze_pass.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from executorch.exir.dialects._ops import ops as exir_ops
1717
from executorch.exir.pass_base import ExportPass
18+
from torch.fx import GraphModule
1819

1920

2021
class Conv1dUnsqueezePass(ArmOpTargetedPass):
@@ -36,6 +37,17 @@ class Conv1dUnsqueezePass(ArmOpTargetedPass):
3637
}
3738
target_ops = (exir_ops.edge.aten.convolution.default,)
3839

40+
def should_run_pass(self, graph_module: GraphModule) -> bool:
41+
for node in graph_module.graph.nodes:
42+
if node.op != "call_function" or node.target not in self.target_ops:
43+
continue
44+
if len(node.args) > 3 and len(node.args[3]) == 1:
45+
return True
46+
return any(
47+
isinstance(child, GraphModule) and self.should_run_pass(child)
48+
for child in graph_module.children()
49+
)
50+
3951
def call_operator(self, op, args, kwargs, meta):
4052
if op not in self.target_ops:
4153
return super().call_operator(op, args, kwargs, meta)

backends/arm/_passes/convert_split_to_slice.py

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

99
import torch.fx
10-
from executorch.backends.arm._passes import ArmPass
10+
from executorch.backends.arm._passes import ArmOpTargetedPass
1111
from executorch.backends.arm._passes.arm_pass_utils import (
1212
create_node,
1313
get_first_fake_tensor,
@@ -16,7 +16,7 @@
1616
from executorch.exir.pass_base import ExportPass, PassResult
1717

1818

19-
class ConvertSplitToSlicePass(ArmPass):
19+
class ConvertSplitToSlicePass(ArmOpTargetedPass):
2020
"""Replace a split operation with many slice operations."""
2121

2222
_passes_required_after: Set[Type[ExportPass]] = set()
@@ -25,6 +25,7 @@ class ConvertSplitToSlicePass(ArmPass):
2525
exir_ops.edge.aten.split_with_sizes_copy.default,
2626
exir_ops.edge.aten.split_copy.Tensor,
2727
)
28+
target_ops = split_ops
2829
slice = exir_ops.edge.aten.slice_copy.Tensor
2930

3031
def call(self, graph_module: torch.fx.GraphModule):

backends/arm/_passes/decompose_add_sub_alpha_pass.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from executorch.backends.arm._passes import ArmOpTargetedPass
1313
from executorch.exir.dialects._ops import ops as exir_ops
1414
from executorch.exir.pass_base import ExportPass
15+
from torch.fx import GraphModule
1516

1617

1718
_ADD_OPS = (
@@ -61,6 +62,19 @@ class DecomposeAddSubAlphaPass(ArmOpTargetedPass):
6162
_passes_required_after: Set[Type[ExportPass]] = set()
6263
target_ops = _ADD_OPS + _SUB_OPS
6364

65+
def should_run_pass(self, graph_module: GraphModule) -> bool:
66+
for node in graph_module.graph.nodes:
67+
if (
68+
node.op == "call_function"
69+
and node.target in self.target_ops
70+
and _should_decompose(node.kwargs.get("alpha", 1))
71+
):
72+
return True
73+
return any(
74+
isinstance(child, GraphModule) and self.should_run_pass(child)
75+
for child in graph_module.children()
76+
)
77+
6478
def call_operator(self, op, args, kwargs, meta, updated: bool | None = False):
6579
if op not in self.target_ops:
6680
return super().call_operator(op, args, kwargs, meta, updated)

backends/arm/_passes/decompose_batch_norm_no_stats.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from typing import Set, Type
99

1010
import torch
11-
from executorch.backends.arm._passes import ArmPass
11+
from executorch.backends.arm._passes import ArmOpTargetedPass
1212
from executorch.backends.arm._passes.arm_pass_utils import create_node
1313
from executorch.backends.arm._passes.fuse_constant_ops_pass import (
1414
ComputeConstantOpsAOTPass,
@@ -19,7 +19,7 @@
1919
from executorch.exir.pass_base import ExportPass, PassResult
2020

2121

22-
class DecomposeBatchNormNoStatsPass(ArmPass):
22+
class DecomposeBatchNormNoStatsPass(ArmOpTargetedPass):
2323
"""
2424
Decompose BatchNorm2d(track_running_stats=False) (aten._native_batch_norm_legit_no_training)
2525
into a sequence of elementwise operations:
@@ -42,21 +42,21 @@ class DecomposeBatchNormNoStatsPass(ArmPass):
4242
ComputeConstantOpsAOTPass,
4343
InsertTableOpsPass,
4444
}
45+
target_ops = (
46+
exir_ops.edge.aten._native_batch_norm_legit.no_stats,
47+
exir_ops.edge.aten._native_batch_norm_legit_no_training.default,
48+
torch.ops.aten._native_batch_norm_legit_no_training.default,
49+
torch.ops.aten.batch_norm.default,
50+
torch.ops.aten.native_batch_norm.default,
51+
)
52+
check_allowed_to_transform = True
4553

4654
def call(self, graph_module: torch.fx.GraphModule) -> PassResult: # noqa: C901
47-
bn_ops = (
48-
exir_ops.edge.aten._native_batch_norm_legit.no_stats,
49-
exir_ops.edge.aten._native_batch_norm_legit_no_training.default,
50-
torch.ops.aten._native_batch_norm_legit_no_training.default,
51-
torch.ops.aten.batch_norm.default,
52-
torch.ops.aten.native_batch_norm.default,
53-
)
54-
5555
modified = False
5656
for node in graph_module.graph.nodes:
5757
if (
5858
node.op != "call_function"
59-
or node.target not in bn_ops
59+
or node.target not in self.target_ops
6060
or not self.allowed_to_transform(node.meta)
6161
):
6262
continue

backends/arm/_passes/decompose_dynamic_full_pass.py

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

88
import torch
9-
from executorch.backends.arm._passes.arm_pass import ArmPass
9+
from executorch.backends.arm._passes.arm_pass import ArmOpTargetedPass
1010
from executorch.backends.arm._passes.unsqueeze_before_repeat_pass import (
1111
UnsqueezeBeforeRepeatPass,
1212
)
1313
from executorch.exir.dialects._ops import ops as exir_ops
1414
from executorch.exir.pass_base import ExportPass
1515

1616

17-
class DecomposeDynamicFullPass(ArmPass):
17+
class DecomposeDynamicFullPass(ArmOpTargetedPass):
1818
"""Rewrite dynamic-shape `full` into scalar `full` plus `repeat`."""
1919

2020
_passes_required_after: Set[Type[ExportPass]] = {UnsqueezeBeforeRepeatPass}
@@ -23,6 +23,7 @@ class DecomposeDynamicFullPass(ArmPass):
2323
torch.ops.aten.full.default,
2424
exir_ops.edge.aten.full.default,
2525
}
26+
target_ops = full_targets
2627
repeat = exir_ops.edge.aten.repeat.default
2728

2829
@staticmethod

backends/arm/_passes/decompose_layernorm_pass.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from typing import Set, Type
99

1010
import torch
11-
from executorch.backends.arm._passes import ArmPass
11+
from executorch.backends.arm._passes import ArmOpTargetedPass
1212
from executorch.backends.arm._passes.arm_pass_utils import create_node, insert_scalar
1313
from executorch.backends.arm._passes.decompose_meandim_pass import DecomposeMeanDimPass
1414
from executorch.backends.arm._passes.decompose_var_pass import DecomposeVarPass
@@ -46,7 +46,7 @@ def get_layer_norm_decomposition(op) -> tuple:
4646
raise RuntimeError(f"Can't get layer_norm composition for op {op}")
4747

4848

49-
class DecomposeLayerNormPass(ArmPass):
49+
class DecomposeLayerNormPass(ArmOpTargetedPass):
5050
"""
5151
layernorm is defined as: ((x - E[x]) / sqrt(Var[x] + eps)) * weights + bias
5252
Decompose layernorm(x, normalized_shape, weights, bias, eps) to a sequence of:
@@ -73,6 +73,8 @@ class DecomposeLayerNormPass(ArmPass):
7373
exir_ops.edge.aten.native_layer_norm.default,
7474
torch.ops.aten.layer_norm.default,
7575
}
76+
target_ops = _TARGET_OPS
77+
check_allowed_to_transform = True
7678

7779
def call(self, graph_module: torch.fx.GraphModule):
7880
modified = False

backends/arm/_passes/decompose_linear_pass.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import torch
1010

11-
from executorch.backends.arm._passes import ArmPass
11+
from executorch.backends.arm._passes import ArmOpTargetedPass
1212
from executorch.backends.arm._passes.arm_pass_utils import (
1313
create_node,
1414
get_first_fake_tensor,
@@ -18,7 +18,7 @@
1818
from executorch.exir.pass_base import ExportPass, PassResult
1919

2020

21-
class DecomposeLinearPass(ArmPass):
21+
class DecomposeLinearPass(ArmOpTargetedPass):
2222
"""This pass decomposes linear into a Conv2D with view operations.
2323
2424
Example:
@@ -31,6 +31,7 @@ class DecomposeLinearPass(ArmPass):
3131
"""
3232

3333
_passes_required_after: Set[Type[ExportPass]] = {InsertRescaleInt32Pass}
34+
target_ops = (exir_ops.edge.aten.linear.default,)
3435

3536
def call(self, graph_module):
3637
modified = False

0 commit comments

Comments
 (0)