Skip to content

Commit c055892

Browse files
apullinmeta-codesync[bot]
authored andcommitted
Minor speedup for model lowering: Skip redundant run_decompositions when no ops match decomp table (#18496)
Summary: Pull Request resolved: #18496 Adds an early-exit check to _gen_edge_manager_for_partitioners: before calling program.run_decompositions(table), scan the graph for ops that appear in the decomposition table. If none are found, skip the call entirely. Each run_decompositions call performs a full re-export of the program via make_fx(), re-tracing every node through FakeTensor dispatch. On the EDGE_DO_NOT_DECOMP path this function is called up to 3 times; the early-exit eliminates at least one redundant call where the previous pass already decomposed all matching ops. The check recursively walks control flow submodules (cond/map/scan) to avoid incorrectly skipping when decomposable ops are nested. ## Benchmark Model: small CNN feature extractor (~50K params, 9 conv layers with LayerNorm, targeting Ethos-U55 via the ARM/TOSA lowering pipeline). Graph: ~1200 nodes. lower() before: 82 s lower() after: 71 s Delta: -11 s (-13 %) Differential Revision: D96489903
1 parent 7014d2e commit c055892

1 file changed

Lines changed: 36 additions & 5 deletions

File tree

exir/program/_program.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,7 +1091,33 @@ def _can_skip_using_EDGE_DO_NOT_DECOMP(
10911091
return check_op_support is None
10921092

10931093

1094-
def _gen_edge_manager_for_partitioners(
1094+
def _has_decomposable_ops(
1095+
program: "ExportedProgram",
1096+
decomp_table: dict,
1097+
) -> bool:
1098+
"""Check if any ops in the graph match the decomposition table.
1099+
1100+
Returns True if the graph contains at least one op that appears in the
1101+
decomposition table, meaning run_decompositions would actually decompose
1102+
something. Returns True for empty tables (functionalization-only path)
1103+
since we can't cheaply determine if the graph needs functionalization.
1104+
"""
1105+
if not decomp_table:
1106+
return True # empty table = functionalize, can't skip cheaply
1107+
1108+
def _graph_has_match(gm: torch.fx.GraphModule) -> bool:
1109+
for node in gm.graph.nodes:
1110+
if node.op == "call_function" and node.target in decomp_table:
1111+
return True
1112+
for _, submod, _ in get_control_flow_submodules(gm):
1113+
if _graph_has_match(submod):
1114+
return True
1115+
return False
1116+
1117+
return _graph_has_match(program.graph_module)
1118+
1119+
1120+
def _gen_edge_manager_for_partitioners( # noqa: C901
10951121
partitioner: Dict[str, List[Partitioner]],
10961122
aten_programs: Dict[str, ExportedProgram],
10971123
config: EdgeCompileConfig,
@@ -1126,7 +1152,8 @@ def _gen_edge_manager_for_partitioners(
11261152
table = _default_decomposition_table()
11271153
for op in config.preserve_ops:
11281154
table.pop(op, None)
1129-
program = program.run_decompositions(table)
1155+
if _has_decomposable_ops(program, table):
1156+
program = program.run_decompositions(table)
11301157

11311158
# Process each partitioner individually using their specific requirements
11321159
for curr_partitioner in partitioners_for_program:
@@ -1146,7 +1173,8 @@ def _gen_edge_manager_for_partitioners(
11461173
if table.pop(op, None) is not None:
11471174
ops_needing_preservation.append(op)
11481175

1149-
program = program.run_decompositions(table)
1176+
if _has_decomposable_ops(program, table):
1177+
program = program.run_decompositions(table)
11501178
final_ops_to_preserve.update(ops_needing_preservation)
11511179
else:
11521180
# EDGE_DO_NOT_DECOMP path for the partitioner
@@ -1160,7 +1188,8 @@ def _gen_edge_manager_for_partitioners(
11601188
table.pop(op, None)
11611189

11621190
# First pass of decompositions with this partitioner's preserved ops
1163-
program = program.run_decompositions(table)
1191+
if _has_decomposable_ops(program, table):
1192+
program = program.run_decompositions(table)
11641193

11651194
# Filter ops using EDGE_DO_NOT_DECOMP
11661195
temp_partitioner_dict = {name: [curr_partitioner]}
@@ -1173,7 +1202,9 @@ def _gen_edge_manager_for_partitioners(
11731202
final_ops_to_preserve.update(preserved_ops)
11741203

11751204
# Second pass of decompositions with this partitioner's preserved ops after filtering
1176-
program = program.run_decompositions(_default_decomposition_table())
1205+
full_table = _default_decomposition_table()
1206+
if _has_decomposable_ops(program, full_table):
1207+
program = program.run_decompositions(full_table)
11771208

11781209
# Restore ops from edge_no_decomp_namespace to aten ops
11791210
_restore_transformed_ops_to_aten_ops(program)

0 commit comments

Comments
 (0)