Skip to content

Commit a7e7e99

Browse files
authored
Remove pass-local ExportPass replays (pytorch#22091)
Differential Revision: D115472301 Pull Request resolved: pytorch#22091
1 parent 88a5f60 commit a7e7e99

9 files changed

Lines changed: 153 additions & 43 deletions

‎backends/cadence/aot/passes.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
from executorch.exir.passes.scalar_to_tensor_pass import ScalarToTensorPass
6969
from executorch.exir.passes.spec_prop_pass import SpecPropPass
7070
from torch.export.exported_program import ExportedProgram
71+
from torch.fx.passes.infra.pass_base import PassBase
7172

7273

7374
class InitializePipeline(ExportPass):
@@ -104,7 +105,7 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult:
104105

105106
# The passes that must run for the graph to be legal on the target, regardless
106107
# of compile mode. Everything else in the pipeline is an optimization.
107-
REQUIRED_PASSES: frozenset[Type[ExportPass]] = frozenset(
108+
REQUIRED_PASSES: frozenset[Type[PassBase]] = frozenset(
108109
{
109110
InitializePipeline,
110111
FinalizePipeline,
@@ -139,7 +140,7 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult:
139140
)
140141

141142

142-
def _get_pipeline() -> list[Type[ExportPass]]:
143+
def _get_pipeline() -> list[Type[PassBase]]:
143144
"""The full ordered pass pipeline.
144145
145146
Order is load-bearing and levels are interleaved, so this list is the single
@@ -168,7 +169,7 @@ def _get_pipeline() -> list[Type[ExportPass]]:
168169
def get_passes(
169170
mode: CompileMode | str,
170171
edge_passes_config: Optional[EdgePassesConfig] = None,
171-
) -> list[Type[ExportPass]]:
172+
) -> list[Type[PassBase]]:
172173
# Coerce at the choke point: modes arrive from CLIs and JSON as plain
173174
# strings, and a str silently matches none of the checks below.
174175
mode = CompileMode(mode)

‎backends/cadence/aot/remove_ops.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from torch.export import ExportedProgram
4141
from torch.export.graph_signature import InputKind, OutputKind
4242
from torch.fx.node import Node
43+
from torch.fx.passes.infra.pass_base import PassBase
4344
from torch.utils import _pytree as pytree
4445

4546

@@ -727,7 +728,7 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult:
727728
return PassResult(graph_module, False)
728729

729730

730-
class RemoveBranchedQuantDequant(ExportPass):
731+
class RemoveBranchedQuantDequant(PassBase):
731732
"""
732733
This pass looks for adjacent quant and dequant nodes with identical
733734
parameters, where the quant node has other users in addition to the
@@ -755,10 +756,9 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult:
755756

756757
if modified:
757758
graph_module.graph.eliminate_dead_code()
758-
result = super().call(graph_module)
759-
return result
759+
graph_module.recompile()
760760

761-
return PassResult(graph_module, False)
761+
return PassResult(graph_module, modified)
762762

763763
def remove_branched(
764764
self,
@@ -848,7 +848,7 @@ def maybe_remove_or_replace(self, node: Node) -> bool:
848848

849849

850850
class CommonRemovePasses:
851-
passes: List[Type[ExportPass]] = [
851+
passes: List[Type[PassBase]] = [
852852
# Canonicalise squeeze/unsqueeze to view_copy first: the nop-view and
853853
# permute passes below both reason about view_copy only.
854854
ReplaceSqueezeAndUnsqueezeWithViewPassImported,
@@ -964,7 +964,7 @@ def call(self, exported_program: ExportedProgram) -> ExportedProgramPassResult:
964964

965965

966966
class CadenceRemoveNops:
967-
passes: List[Type[ExportPass]] = CommonRemovePasses.passes + [
967+
passes: List[Type[PassBase]] = CommonRemovePasses.passes + [
968968
SimplifySliceOpPass,
969969
RemoveNopRequantizeOpPass,
970970
RemoveZeroSizedConstantPadNd,

‎backends/cadence/aot/replace_ops.py‎

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
)
3838
from executorch.exir.dialects._ops import ops as exir_ops
3939
from executorch.exir.dialects.edge._ops import EdgeOpOverload
40-
from executorch.exir.pass_base import ExportPass, PassResult
40+
from executorch.exir.pass_base import PassResult
41+
from torch.fx.passes.infra.pass_base import PassBase
4142

4243
# A map to represent ops that:
4344
# (a) are functionally equivalent; and
@@ -1981,47 +1982,37 @@ def maybe_remove_or_replace(self, node: torch.fx.Node) -> bool:
19811982
return True
19821983

19831984

1984-
class ReplaceEmptyTensorsWithFullPass(ExportPass):
1985+
class ReplaceEmptyTensorsWithFullPass(PassBase):
19851986
"""Replaces nodes that produce empty tensors with full nodes."""
19861987

1987-
def call_operator(self, op, args, kwargs, meta):
1988-
val = meta.data.get("val", None)
1989-
if isinstance(val, torch.Tensor) and val.numel() == 0:
1990-
return super().call_operator(
1991-
exir_ops.edge.aten.full.default,
1992-
args=(val.shape, 0),
1993-
kwargs={"dtype": val.dtype},
1994-
meta=meta,
1995-
)
1996-
return super().call_operator(op, args, kwargs, meta)
1997-
19981988
def call(self, graph_module: torch.fx.GraphModule) -> PassResult:
19991989
changed = False
20001990
for module in filter(
20011991
lambda m: isinstance(m, torch.fx.GraphModule), graph_module.modules()
20021992
):
2003-
module = cast(torch.fx.GraphModule, module)
2004-
for node in module.graph.nodes:
1993+
graph_module_to_update = cast(torch.fx.GraphModule, module)
1994+
module_changed = False
1995+
for node in graph_module_to_update.graph.nodes:
20051996
if node.op != "call_function":
20061997
continue
20071998
val = node.meta.get("val", None)
20081999
if isinstance(val, torch.Tensor) and val.numel() == 0:
2009-
with module.graph.inserting_before(node):
2010-
new_node = module.graph.call_function(
2000+
with graph_module_to_update.graph.inserting_before(node):
2001+
new_node = graph_module_to_update.graph.call_function(
20112002
exir_ops.edge.aten.full.default,
20122003
args=(val.shape, 0),
20132004
kwargs={"dtype": val.dtype},
20142005
)
2015-
new_node.meta = node.meta
2006+
new_node.meta = node.meta.copy()
20162007
node.replace_all_uses_with(new_node)
2017-
changed = True
2008+
module_changed = True
20182009

2019-
if changed:
2020-
graph_module.graph.eliminate_dead_code()
2021-
graph_module.recompile()
2022-
return super().call(graph_module)
2010+
if module_changed:
2011+
graph_module_to_update.graph.eliminate_dead_code()
2012+
graph_module_to_update.recompile()
2013+
changed = True
20232014

2024-
return PassResult(graph_module, False)
2015+
return PassResult(graph_module, changed)
20252016

20262017

20272018
class ReplaceWhereWithFullArgsWithWhereScalar(RemoveOrReplacePassInterface):

‎backends/cadence/aot/simplify_ops.py‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
from executorch.backends.cadence.aot.utils import rebind
1818
from executorch.exir.dialects._ops import ops as exir_ops
1919
from executorch.exir.dialects.edge._ops import EdgeOpOverload
20-
from executorch.exir.pass_base import ExportPass, PassResult
20+
from executorch.exir.pass_base import PassResult
2121
from torch.fx import Node
22+
from torch.fx.passes.infra.pass_base import PassBase
2223

2324

2425
class SimplifySliceOpPass(RemoveOrReplacePassInterface):
@@ -169,7 +170,7 @@ def maybe_remove_or_replace(self, node: Node) -> bool:
169170
return True
170171

171172

172-
class BindOptionalArgsPass(ExportPass):
173+
class BindOptionalArgsPass(PassBase):
173174
"""Bind all optional args and kwargs."""
174175

175176
def call(self, graph_module: torch.fx.GraphModule) -> PassResult:
@@ -181,7 +182,9 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult:
181182
for module in filter(
182183
lambda m: isinstance(m, torch.fx.GraphModule), graph_module.modules()
183184
):
184-
for node in cast(torch.fx.GraphModule, module).graph.nodes:
185+
graph_module_to_update = cast(torch.fx.GraphModule, module)
186+
module_modified = False
187+
for node in graph_module_to_update.graph.nodes:
185188
if node.op != "call_function":
186189
continue
187190
if not isinstance(node.target, EdgeOpOverload):
@@ -198,11 +201,11 @@ def call(self, graph_module: torch.fx.GraphModule) -> PassResult:
198201
if new_args != node.args or new_kwargs != node.kwargs:
199202
node.args = new_args
200203
node.kwargs = new_kwargs
201-
modified = True
204+
module_modified = True
202205

203-
if modified:
204-
graph_module.recompile()
205-
return super().call(graph_module)
206+
if module_modified:
207+
graph_module_to_update.recompile()
208+
modified = True
206209

207210
return PassResult(graph_module, modified)
208211

‎backends/cadence/aot/tests/test_remove_ops_passes.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,9 +913,28 @@ def test_remove_dequant_on_branch(self) -> None:
913913
)
914914
builder.output([x1_output, y1_output])
915915
original = builder.get_graph_module()
916+
original_nodes = list(original.graph.nodes)
917+
original_abs_nodes = original.graph.find_nodes(
918+
op="call_function", target=exir_ops.edge.aten.abs.default
919+
)
920+
x1_output_node = original_abs_nodes[1]
921+
x1_output_node.meta["remove_branched_test"] = "preserved"
922+
x1_output_meta = x1_output_node.meta
916923
pass_result = cast(PassResult, RemoveBranchedQuantDequant()(original))
917924
self.assertTrue(pass_result.modified)
918925
graph_after_passes = pass_result.graph_module
926+
self.assertIs(graph_after_passes, original)
927+
self.assertIs(x1_output_node.meta, x1_output_meta)
928+
self.assertEqual(x1_output_node.meta["remove_branched_test"], "preserved")
929+
self.assertIs(
930+
next(
931+
node
932+
for node in graph_after_passes.graph.nodes
933+
if node.name == x1_output_node.name
934+
),
935+
x1_output_node,
936+
)
937+
self.assertLess(len(list(graph_after_passes.graph.nodes)), len(original_nodes))
919938
self.assertEqual(
920939
count_node(
921940
graph_after_passes,

‎backends/cadence/aot/tests/test_replace_ops_passes.py‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3179,10 +3179,32 @@ def test_empty_slice(self) -> None:
31793179

31803180
# Deepcopy before the pass
31813181
gm_before = copy.deepcopy(gm)
3182+
original_graph_module = gm
3183+
original_nodes = list(gm.graph.nodes)
3184+
empty_slice = gm.graph.find_nodes(
3185+
op="call_function", target=exir_ops.edge.aten.slice_copy.Tensor
3186+
)[0]
3187+
empty_slice.meta["replace_empty_test"] = "preserved"
3188+
empty_slice_meta = empty_slice.meta
3189+
unchanged_slice = gm.graph.find_nodes(
3190+
op="call_function", target=exir_ops.edge.aten.slice_copy.Tensor
3191+
)[1]
3192+
unchanged_slice_meta = unchanged_slice.meta
31823193

31833194
result = ReplaceEmptyTensorsWithFullPass().call(gm)
31843195
self.assertTrue(result.modified)
31853196
updated_gm = result.graph_module
3197+
self.assertIs(updated_gm, original_graph_module)
3198+
self.assertIs(unchanged_slice.meta, unchanged_slice_meta)
3199+
self.assertIn(unchanged_slice, updated_gm.graph.nodes)
3200+
self.assertNotIn(empty_slice, updated_gm.graph.nodes)
3201+
full = updated_gm.graph.find_nodes(
3202+
op="call_function", target=exir_ops.edge.aten.full.default
3203+
)[0]
3204+
self.assertIsNot(full.meta, empty_slice_meta)
3205+
self.assertEqual(full.meta, empty_slice_meta)
3206+
self.assertEqual(full.meta["replace_empty_test"], "preserved")
3207+
self.assertEqual(len(list(updated_gm.graph.nodes)), len(original_nodes))
31863208

31873209
# Validate numerical accuracy
31883210
inputs = [x_input]

‎backends/cadence/aot/tests/test_simplify_ops_passes.py‎

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,49 @@ def test_simplify_slice_op_args(self) -> None:
147147
args=(x, 1),
148148
kwargs={"end": 3},
149149
)
150+
original_graph_module = gm
150151
original_slice_copy = list(gm.graph.nodes)[1]
152+
original_metadata = original_slice_copy.meta
153+
original_slice_copy.meta["bind_optional_args_test"] = "preserved"
151154
self.assertEqual(original_slice_copy.args[1:], (1,))
152155
self.assertEqual(original_slice_copy.kwargs, {"end": 3})
156+
153157
result = transform_and_check_numerics(
154158
gm, (x,), BindOptionalArgsPass(), "BindOptionalArgsPass"
155159
)
160+
156161
self.assertTrue(result.modified)
157-
gm = result.graph_module
158-
modified_slice_copy = list(gm.graph.nodes)[1]
162+
self.assertIs(result.graph_module, original_graph_module)
163+
modified_slice_copy = list(result.graph_module.graph.nodes)[1]
164+
self.assertIs(modified_slice_copy, original_slice_copy)
165+
self.assertIs(modified_slice_copy.meta, original_metadata)
166+
self.assertEqual(
167+
modified_slice_copy.meta["bind_optional_args_test"], "preserved"
168+
)
159169
self.assertEqual(modified_slice_copy.args[1:], (1, None, 3, 1))
160170
self.assertEqual(modified_slice_copy.kwargs, {})
171+
172+
def test_bind_optional_args_no_change(self) -> None:
173+
x = torch.rand(4, 5)
174+
gm = single_op_builder(
175+
placeholders=(x,),
176+
op=exir_ops.edge.aten.slice_copy.Tensor,
177+
args=(x, 1, None, 3, 1),
178+
)
179+
original_nodes = list(gm.graph.nodes)
180+
slice_copy = original_nodes[1]
181+
original_metadata = slice_copy.meta
182+
slice_copy.meta["bind_optional_args_test"] = "preserved"
183+
184+
result = BindOptionalArgsPass().call(gm)
185+
186+
self.assertFalse(result.modified)
187+
self.assertIs(result.graph_module, gm)
188+
self.assertEqual(
189+
len(list(result.graph_module.graph.nodes)), len(original_nodes)
190+
)
191+
self.assertIs(list(result.graph_module.graph.nodes)[1], slice_copy)
192+
self.assertIs(slice_copy.meta, original_metadata)
193+
self.assertEqual(slice_copy.meta["bind_optional_args_test"], "preserved")
194+
self.assertEqual(slice_copy.args[1:], (1, None, 3, 1))
195+
self.assertEqual(slice_copy.kwargs, {})

‎backends/transforms/permute_pass_utils.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,11 @@ def _apply_hierarchical_inplace(self, graph_module: torch.fx.GraphModule) -> boo
157157
modified |= self._apply_flat_inplace(module)
158158
return modified
159159

160+
def apply_inplace(self, graph_module: torch.fx.GraphModule) -> bool:
161+
return self._apply_hierarchical_inplace(graph_module)
162+
160163
def call(self, graph_module: torch.fx.GraphModule) -> PassResult:
161-
modified = self._apply_hierarchical_inplace(graph_module)
164+
modified = self.apply_inplace(graph_module)
162165
if modified:
163166
graph_module.graph.eliminate_dead_code()
164167
graph_module.recompile()

‎backends/transforms/test/test_permute_optimization_passes.py‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,42 @@ def test_permute_transpose_fusion(self) -> None:
125125
"FuseCascadedTransposeOrPermuteOps",
126126
)
127127

128+
def test_apply_inplace_preserves_identity_and_output_metadata(self) -> None:
129+
builder = GraphBuilder()
130+
x = builder.placeholder("x", torch.randn(2, 3, 4))
131+
permute1 = builder.call_operator(
132+
op=exir_ops.edge.aten.permute_copy.default, args=(x, [0, 2, 1])
133+
)
134+
permute2 = builder.call_operator(
135+
op=exir_ops.edge.aten.permute_copy.default, args=(permute1, [2, 1, 0])
136+
)
137+
builder.output([permute2])
138+
graph_module = builder.get_graph_module()
139+
placeholder = next(
140+
node for node in graph_module.graph.nodes if node.op == "placeholder"
141+
)
142+
placeholder.meta["apply_inplace_test"] = "preserved"
143+
placeholder_meta = placeholder.meta
144+
output_permute = graph_module.graph.find_nodes(
145+
op="call_function", target=exir_ops.edge.aten.permute_copy.default
146+
)[1]
147+
output_permute.meta["apply_inplace_test"] = "output"
148+
output_meta = output_permute.meta
149+
150+
modified = FuseCascadedTransposeOrPermuteOps().apply_inplace(graph_module)
151+
graph_module.graph.eliminate_dead_code()
152+
graph_module.recompile()
153+
154+
self.assertTrue(modified)
155+
self.assertIn(placeholder, graph_module.graph.nodes)
156+
self.assertIs(placeholder.meta, placeholder_meta)
157+
self.assertEqual(placeholder.meta["apply_inplace_test"], "preserved")
158+
fused_permute = graph_module.graph.find_nodes(
159+
op="call_function", target=exir_ops.edge.aten.permute_copy.default
160+
)[0]
161+
self.assertIs(fused_permute.meta, output_meta)
162+
self.assertEqual(fused_permute.meta["apply_inplace_test"], "output")
163+
128164
def test_cascaded_permutes_multiple_users(self) -> None:
129165
builder = GraphBuilder()
130166
x = builder.placeholder("x", torch.randn(2, 3, 4, 5))

0 commit comments

Comments
 (0)