Skip to content

Break the exir backend_api <-> program import cycle - #24802

Merged
JakeStevens merged 6 commits into
pytorch:mainfrom
JakeStevens:fix-backend-api-program-import-cycle
Nov 10, 2026
Merged

JakeStevens merged 6 commits into
pytorch:mainfrom
JakeStevens:fix-backend-api-program-import-cycle

Conversation

@JakeStevens

@JakeStevens JakeStevens commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stacked on #24799. Until that lands this branch also contains its commits; review only the last commit, 121f6d6.

exir/backend/backend_api.py imports executorch.exir.program._fake_program, which runs exir/program/__init__.py and so _program.py. _program.py imported MethodProgramsPartitionerSpec and to_backend from the still half-initialised backend_api at module scope:

ImportError: cannot import name 'MethodProgramsPartitionerSpec' from partially initialized module 'executorch.exir.backend.backend_api' (most likely due to a circular import)

Normally exir/__init__.py loads program first, so this only fails when backend_api is the first executorch.exir import, for example under lazy imports (Cinder lazy imports on Python 3.14) when a backend imports to_backend directly. On main, that import order fails earlier on the capture cycle fixed in #24799, which is why this is stacked.

_program.py uses those two names only in EdgeProgramManager.to_backend, so the import moves into that method. Nothing imports them through _program.

This PR was authored with AI assistance (Claude Code).

Test plan

Added executorch.exir.backend.backend_api to exir/tests/test_import_order.py (added in #24799). It fails with the error above without the _program.py change and passes with it. The other entry points in that test still pass.

python -m unittest executorch.exir.program.test.test_program executorch.exir.backend.test.test_backends_lifted executorch.exir.backend.test.test_partitioner executorch.backends.xnnpack.test.ops.test_add gives the same results with and without this commit, apart from the new import-order case. The shared failures come from my local runtime build, which lacks BackendWithCompilerDemo and some kernels such as aten::_embedding_bag.out.

verifier.py imported EdgeCompileConfig from executorch.exir.capture._config
at module scope. That runs exir/capture/__init__.py, which imports
_capture.py and exir/program/_program.py, and both import back from the
still half-initialised verifier. Normally exir/__init__.py loads capture
first, so the cycle stays hidden. Under lazy imports (Cinder, Python 3.14)
the first import can enter through verifier, e.g. via
backends/xnnpack/xnnpack_preprocess.py, and fails with "cannot import name
'EXIRATenDialectVerifierBase' from partially initialized module".

EdgeCompileConfig is now imported only for type checking, plus a local
import at its single runtime use in _EXIREdgeDialectVerifier.__init__.

Test plan: simulated lazy imports by deferring exir/__init__.py until first
attribute access, then imported exir.verification.verifier and
backends.xnnpack.partition.xnnpack_partitioner first; both failed before
this change and succeed after. exir/verification/test/test_verifier.py and
backends/xnnpack/test/ops/test_add.py pass.

Authored with AI assistance (Claude Code).
Address review: the verifier.py-only fix still ran the cycle during
verifier's own import (through the module-level EXIREdgeDialectVerifier()
instance), left the exir.program and exir._serialize entry points broken,
and stopped EdgeCompileConfig being importable from verifier.

capture/__init__.py loads _capture.py, which imported exir.program and
exir.verification.verifier at module scope, so any import of the leaf
module capture._config pulled both back in. Those imports now live in the
two functions that use them, and verifier.py is unchanged from main.

The new test imports each entry point in a fresh interpreter with a bare
executorch.exir package, so exir/__init__.py doesn't load capture first.
It fails on main for all three modules and passes with this change.

Authored with AI assistance (Claude Code).
@pytorch-bot

pytorch-bot Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

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

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

✅ No Failures

As of commit c56ce62 with merge base c3d2644 (image):
💚 Looks good so far! There are no failures yet. 💚

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

@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

@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 Nov 9, 2026
@JakeStevens

Copy link
Copy Markdown
Contributor Author

@claude review effort=max

@claude

claude Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Claude finished @JakeStevens's task in 23m 7s —— View job


The fix works for the backend_api-first import order, but its regression case goes into test_verifier.py, which pytest.ini excludes from OSS CI, and the PR description doesn't mention that import executorch.exir no longer loads backend_api.

Detailed review

PR Review: #24802

Summary

a23d2ca moves _program.py's import of MethodProgramsPartitionerSpec and to_backend into EdgeProgramManager.to_backend, so executorch.exir.backend.backend_api can be the first executorch.exir module imported. I found two problems:

  • OSS CI never runs the new regression case.
  • import executorch.exir no longer loads backend_api, and the description doesn't mention it.

I reviewed only a23d2ca, as you asked. This job isn't allowed to run Python, so I didn't run any tests. Everything below comes from reading the code.

Testing

  • exir/verification/test/test_verifier.py:46: OSS CI never runs the new backend_api case.

    • pytest.ini:52 has --ignore=exir/verification/test/test_verifier.py, and no file under .ci/ or .github/ mentions it.
    • The only runner is the fbcode-only python_unittest(name = "test_verifier") in exir/verification/targets.bzl:62-71.
    • A normal import runs exir/__init__.py first, so program always loads before backend_api. If someone puts a module-scope backend_api import back into _program.py, every OSS test that does run still passes.
    • The placement comes from Break the exir capture <-> program/verifier import cycle #24799, so the fix belongs there:
      • Move TestImportOrder and _IMPORT_WITHOUT_EXIR_INIT into their own file that pytest collects, e.g. exir/tests/test_import_order.py.
      • Give that file a target that depends directly on each module the test imports. Today test_verifier only gets backend_api through other deps.
    • Low confidence: the child process is a bare sys.executable -c that doesn't get the parent's sys.path (test_verifier.py:49-54). Under a Buck PAR it may not find executorch at all. Worth confirming that the fbcode target actually passes.

    Fix this →

Backward Compatibility

  • exir/program/_program.py:1724-1729: import executorch.exir no longer loads executorch.exir.backend.backend_api.
    • _program.py was the only non-test module in exir/ that imported backend_api, and exir/backend/ has no __init__.py.
    • So code that does import executorch.exir and then uses executorch.exir.backend.backend_api.<name> without importing that module now gets AttributeError.
    • I found no such use in the repo, so the out-of-tree impact is probably small. The description's "Nothing imports them through _program" covers the two names, not this. One sentence in the description would cover it.
    • There is a way to keep the old behaviour. Keep _program.py's module-scope import, and move the _fake_program import at exir/backend/backend_api.py:35-38 into the two overloads that use it (backend_api.py:367 and :684). That's the kind of fix Break the exir capture <-> program/verifier import cycle #24799 moved away from (01c8118 → 3f8c4fb), though, so documenting the change may be the better option.

Specific Comments

  • exir/program/_program.py:1724-1725:
    • The "it" in "importing it at module scope is circular" is ambiguous.
    • This file already words cycle comments differently, at _program.py:241 ("program -> etrecord -> program").
    • Suggested wording: # Import at runtime to resolve cyclic dependencies (backend_api -> program._fake_program -> program/__init__ -> _program -> backend_api).

Recommendation

Approve

Nothing in a23d2ca blocks this. The Testing item still needs fixing in #24799 before the stack lands. Until then, the fix has no regression test in OSS CI.

pytest.ini ignores exir/verification/test/test_verifier.py, and that file's
relative import needs a parent package, so OSS pytest never ran the test.
It now lives in exir/tests/test_import_order.py, which OSS pytest collects,
with a matching Buck target.

Authored with AI assistance (Claude Code).
backend_api.py imports executorch.exir.program._fake_program, which runs
exir/program/__init__.py and so _program.py. _program.py imported
MethodProgramsPartitionerSpec and to_backend from the still half-initialised
backend_api at module scope. Normally exir/__init__.py loads program first,
so this only fails when backend_api is the first executorch.exir import,
e.g. under lazy imports.

_program.py uses those names only in EdgeProgramManager.to_backend, so the
import moves there. backend_api is added to the import-order test.

Authored with AI assistance (Claude Code).
@JakeStevens
JakeStevens force-pushed the fix-backend-api-program-import-cycle branch from a23d2ca to 121f6d6 Compare November 9, 2026 19:34
@JakeStevens
JakeStevens merged commit 7d51b01 into pytorch:main Nov 10, 2026
215 checks passed
@JakeStevens
JakeStevens deleted the fix-backend-api-program-import-cycle branch November 10, 2026 14:37
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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants