Skip to content

Commit 7d51b01

Browse files
authored
Break the exir backend_api <-> program import cycle (#24802)
### 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`.
1 parent bc5bd66 commit 7d51b01

2 files changed

Lines changed: 8 additions & 4 deletions

File tree

‎exir/program/_program.py‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@
2323
from executorch.exir._serialize._serialize import serialize_for_executorch
2424
from executorch.exir._serialize.data_serializer import DataSerializer
2525
from executorch.exir._warnings import experimental
26-
from executorch.exir.backend.backend_api import (
27-
MethodProgramsPartitionerSpec,
28-
to_backend,
29-
)
3026
from executorch.exir.backend.partitioner import Partitioner
3127
from executorch.exir.capture._config import EdgeCompileConfig, ExecutorchBackendConfig
3228
from executorch.exir.delegate import executorch_call_delegate, is_lowered_module
@@ -1725,6 +1721,13 @@ def to_backend(
17251721
EdgeProgramManager: A copy of the calling EdgeProgramManager with the
17261722
specified subgraphs lowered.
17271723
"""
1724+
# backend_api imports executorch.exir.program (via program._fake_program),
1725+
# so importing it at module scope is circular.
1726+
from executorch.exir.backend.backend_api import (
1727+
MethodProgramsPartitionerSpec,
1728+
to_backend,
1729+
)
1730+
17281731
new_edge_programs: Dict[str, ExportedProgram] = {}
17291732
method_to_partitioner: Dict[str, Partitioner] = {}
17301733
if not isinstance(partitioner, dict):

‎exir/tests/test_import_order.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def test_import_without_exir_init(self) -> None:
2828
"executorch.exir.verification.verifier",
2929
"executorch.exir.program",
3030
"executorch.exir._serialize._serialize",
31+
"executorch.exir.backend.backend_api",
3132
):
3233
with self.subTest(module=module):
3334
completed = subprocess.run(

0 commit comments

Comments
 (0)