Skip to content

exir/passes: return PassResult.modified=False when passes leave graph unchanged#19872

Open
vacu9708 wants to merge 1 commit into
pytorch:mainfrom
vacu9708:main
Open

exir/passes: return PassResult.modified=False when passes leave graph unchanged#19872
vacu9708 wants to merge 1 commit into
pytorch:mainfrom
vacu9708:main

Conversation

@vacu9708
Copy link
Copy Markdown
Contributor

@vacu9708 vacu9708 commented May 29, 2026

Summary

Several passes in exir/passes/ return PassResult(graph_module, True) unconditionally — even when they walk the graph and rewrite nothing.

# exir/program/_program.py:271
if transformed_gm is self.graph_module and not res.modified:
    return self
return _update_exported_program_graph_module(self, transformed_gm, override_verifiers)

A false-positive modified=True forces the consumer to rebuild the ExportedProgram (deep-copy module_call_graph, recompute the graph signature, re-run verifiers) on every pass run, even when nothing changed.

PR #16231 fixed this for ScalarToTensorPass. This PR applies the same pattern to the remaining passes.

Passes touched

Pass Before After
RemoveNoopPass always True tracks per-node replacement
RemoveToCopyPass always True tracks per-node replacement
PruneEmptyTensorsPass always True tracks cat-input rewrites
RemoveGraphAssertsPass always True, also called recompile() per submodule unconditionally tracks erased asserts, gates recompile() / DCE on actual mutation
RemoveNonCoreAtenOpGraphAssertsPass same as above same as above
ReplaceSymSizeOpPass always True tracks overload-packet rewrites
ToDevicePass already computed modified, then discarded it and returned True returns the computed value

Each fix follows the pattern used by CSEPass, ScalarToTensorPass, and others: a local modified boolean is set at the point of actual graph mutation and returned via PassResult(gm, modified). Cleanup operations (eliminate_dead_code, lint, recompile) are only called when modified=True.

As a minor cleanup, RemoveGraphAssertsPass and RemoveNonCoreAtenOpGraphAssertsPass are refactored to share a private helper _erase_asserts_from_modules — the two classes were near-identical, differing only in the target op set.

flowchart TD
    A["EdgeProgramManager.transform(MyPass)"] --> B[_transform_with_pass_manager]
    B --> C{res.modified}
    C -- True --> D[rebuild ExportedProgram]
    C -- False --> E[return self\nintended fast path]
Loading

Test results

pytest exir/tests/test_passes.py::TestPassModifiedFlag -q
# 0 failed

lintrunner exir/passes/remove_noop_pass.py \
           exir/passes/prune_empty_tensors_pass.py \
           exir/passes/remove_graph_asserts_pass.py \
           exir/passes/replace_sym_size_op_pass.py \
           exir/passes/to_device_pass.py \
           exir/tests/test_passes.py
# No lint issues.

@pytorch-bot
Copy link
Copy Markdown

pytorch-bot Bot commented May 29, 2026

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19872

Note: Links to docs will display an error until the docs builds have been completed.

❌ 1 New Failure, 104 Cancelled Jobs

As of commit 4c57e68 with merge base acce7cd (image):

NEW FAILURE - The following job has failed:

CANCELLED JOBS - The following jobs were cancelled. Please retry:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented May 29, 2026

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: vacu9708 / name: Youngsik Yang (15d62eb)

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label May 29, 2026
… unchanged

Several passes in exir/passes/ returned PassResult(graph_module, True)
unconditionally, even when they left the graph unchanged.

PR pytorch#16231 fixed this for ScalarToTensorPass. This applies the same correction to:
- RemoveNoopPass, RemoveToCopyPass, PruneEmptyTensorsPass, ReplaceSymSizeOpPass,
  RemoveGraphAssertsPass, RemoveNonCoreAtenOpGraphAssertsPass
- ToDevicePass — already computed the flag correctly but discarded it

RemoveGraphAssertsPass and RemoveNonCoreAtenOpGraphAssertsPass are also
refactored to share a helper since they had near-identical loop bodies.

Signed-off-by: Youngsik Yang <vacu9708@gmail.com>
@vacu9708
Copy link
Copy Markdown
Contributor Author

@pytorchbot label "release notes: none"

@pytorch-bot pytorch-bot Bot added the release notes: none Do not include this in the release notes label May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. release notes: none Do not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants