Skip to content

Add minimal wheel build mode - #19899

Merged
shoumikhin merged 5 commits into
mainfrom
export-only-wheel
Jun 6, 2026
Merged

shoumikhin merged 5 commits into
mainfrom
export-only-wheel

Conversation

@JacobSzwejbka

@JacobSzwejbka JacobSzwejbka commented May 30, 2026 •

Copy link
Copy Markdown
Contributor

Adds an opt-in EXECUTORCH_BUILD_MINIMAL=1 wheel build mode that packages only the Python EXIR export path (plus flatc), for distributors that need ExecuTorch's ahead-of-time .pte export but not its runtime. For example, Torch-TensorRT's output_format="executorch" uses ExecuTorch only to export and runs the result with its own runtime.

The minimal wheel:

  • omits runtime pybindings, kernels, backend packages, headers, examples, and devtools;
  • declares only the dependencies the export path needs (flatbuffers, numpy, packaging, pyyaml, ruamel.yaml, sympy, tabulate, typing-extensions) instead of the full set (coremltools, scikit-learn, pandas, hydra-core, omegaconf, and so on), so a normal install stays small;
  • produces byte-identical .pte output to the full wheel.

The default (non-minimal) wheel is unchanged: its dependencies move from static pyproject.toml to a dynamic install_requires in setup.py, but the declared set is identical.

Build from source and bundle it:

EXECUTORCH_BUILD_MINIMAL=1 pip wheel . --no-deps
# or: EXECUTORCH_BUILD_MINIMAL=1 pip install .

A redistributor (e.g. NVIDIA, for a Torch-TensorRT container) can build the slim wheel at a pinned ExecuTorch version and ship it. torch is consumer-provided in both modes.

CI (test-minimal-wheel-linux) builds the minimal wheel, asserts the excluded runtime/backend content and heavy deps are absent, installs it in a clean venv with full dependency resolution (no --no-deps), runs the bundled flatc, and exports MobileNetV2 to a .pte.

Local result: minimal Linux x86_64 wheel ~2.1 MiB compressed vs ~15 MiB for the full wheel; MobileNetV2 .pte is 13,995,880 bytes, byte-identical to the published 1.3.1 wheel.

@pytorch-bot

pytorch-bot Bot commented May 30, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

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

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

⏳ No Failures, 2 Pending

As of commit 54de550 with merge base 12684ef (image):
💚 Looks good so far! There are no failures yet. 💚

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

@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 30, 2026
@JacobSzwejbka
JacobSzwejbka marked this pull request as draft May 30, 2026 05:38
@JacobSzwejbka JacobSzwejbka changed the title Add export-only wheel build mode Add minimal wheel build mode May 30, 2026
@JacobSzwejbka

Copy link
Copy Markdown
Contributor Author

@claude review this PR

@JacobSzwejbka
JacobSzwejbka marked this pull request as ready for review May 30, 2026 05:44
@claude

claude Bot commented May 30, 2026 •

Copy link
Copy Markdown

Claude finished @JacobSzwejbka's task in 2m 7s —— View job


Reviewing PR #19899: Add minimal wheel build mode

  • Read changed files and gather context
  • Review setup.py changes
  • Review .ci/scripts/test_minimal_wheel.sh
  • Review .github/workflows/pull.yml changes
  • Review exir/_serialize/_flatbuffer.py changes
  • Review README-wheel.md changes
  • Post review feedback

Summary

Well-structured PR that introduces an opt-in EXECUTORCH_BUILD_MINIMAL=1 wheel build mode. The separation of concerns is clean — minimal build disables all backends/kernels/pybindings and only builds flatbuffers_ep, then the package list is trimmed to just the export path. The smoke test is thorough (content verification + end-to-end MobileNetV2 export). A few observations below.


Feedback

1. Potential race in CI: cmake_cache.is_enabled() after minimal build (setup.py:889–917)

After --target flatbuffers_ep is built, the code falls through to additional cmake_cache.is_enabled(...) checks (lines 889–917). Since _minimal_cmake_flags() explicitly sets all those options to OFF, this is safe in practice — but the if minimal_build branch only covers the build target selection, not the subsequent target additions. Consider adding an early return or else guard to make it immediately clear that minimal builds skip all subsequent target additions:

if minimal_build:
    cmake_build_args += ["--target", "flatbuffers_ep"]
else:
    if cmake_cache.is_enabled("EXECUTORCH_BUILD_PYBIND"):
        cmake_build_args += ["--target", "portable_lib"]
        ...
    # rest of target additions

This would prevent any future additions below the if/elif from accidentally applying to minimal builds.

2. _is_minimal_build() called at module level (setup.py:932)

setup_kwargs = {}
if _is_minimal_build():
    setup_kwargs["packages"] = _minimal_packages()

This is evaluated at import time. If someone imports setup.py for inspection (unlikely but possible in tooling), the env var state at import time determines the result. This is fine for the setup.py bdist_wheel use case, just noting the coupling.

3. Test script: missing cleanup on failure (.ci/scripts/test_minimal_wheel.sh:15)

The script does rm -rf of the venvs at the start but doesn't trap for cleanup on failure. In CI this doesn't matter since the runner is ephemeral, but for local development it leaves .venv-minimal-build and .venv-minimal-test behind. Minor — not blocking.

4. Test script: setup.py bdist_wheel is deprecated (.ci/scripts/test_minimal_wheel.sh:36)

EXECUTORCH_BUILD_MINIMAL=1 python setup.py bdist_wheel

setup.py bdist_wheel is deprecated as of setuptools 70+ and will eventually be removed. The modern equivalent is pip wheel --no-build-isolation --no-deps . or python -m build --wheel. Since the rest of ExecuTorch CI may still use setup.py bdist_wheel, this is consistent with existing practice — but worth a note for future migration. The README documentation says pip wheel or pip install which is good.

5. _flatbuffer.py: silent fallback order could mask packaging bugs (exir/_serialize/_flatbuffer.py:272–274)

for package, resource_name in (
    (__package__, _FLATC_RESOURCE_NAME),
    ("executorch.data.bin", "flatc"),
):

The fallback to executorch.data.bin is the right approach for the minimal wheel (where the old flatbuffers-flatc resource doesn't exist in the _serialize package). However, if both locations somehow contain a flatc binary (e.g. a packaging mistake), the first one always wins silently. This is fine behavior, just noting that the ordering is important — existing full wheels get their existing behavior unchanged, and the minimal wheel uses the second path.

6. CI trigger conditions: extension/flat_tensor path check (.github/workflows/pull.yml:58)

The trigger uses contains(needs.changed-files.outputs.changed-files, 'extension/flat_tensor') — this will also match extension/flat_tensor_test or any other path containing that prefix. Since contains() is a substring match on the full file list, this is probably fine because there's unlikely to be a path that matches incorrectly, but extension/flat_tensor/ (with trailing slash) would be slightly more precise.

7. _minimal_packages(): extension.flat_tensor included but CMake flag is OFF (setup.py:115, 151–152)

_minimal_cmake_flags() sets EXECUTORCH_BUILD_EXTENSION_FLAT_TENSOR=OFF but _minimal_packages() includes executorch.extension.flat_tensor and executorch.extension.flat_tensor.*. This means the Python source files for flat_tensor are shipped without the C++ extension being built. If the Python code in extension/flat_tensor imports native bindings, this could cause import errors. If it's pure Python or only used for AoT serialization, this is fine. Worth verifying that users of the minimal wheel won't hit an ImportError when this module is imported transitively.


Overall

This is a clean, well-tested PR. The approach of gating on a single env var and explicitly disabling all non-essential CMake flags is the right pattern. The smoke test validates both negative (excluded content absent) and positive (export works end-to-end) properties. The 2 MiB wheel size vs. the full wheel is a nice win for export-only use cases.

The items above are minor — none are blocking. The most actionable one is item 7 (verifying flat_tensor doesn't pull in native deps when imported from the minimal wheel).

@shoumikhin

Copy link
Copy Markdown
Contributor

Thanks for putting this together. The overall approach looks right: it is opt-in, defined upstream, and uses an allowlist of what to keep instead of a list of what to delete, so it will not quietly grow again on future versions. The keep set is correct, including the two parts that are easy to miss (extension/pytree and extension/flat_tensor), and reproducing the exact same .pte bytes is good evidence it works.

A few things I think we should improve:

  1. The wheel still declares all the heavy dependencies. The minimal build trims the package files but not the dependency list in pyproject (coremltools, scikit-learn, pandas, hydra-core, omegaconf, plus the test-only deps expecttest/hypothesis/kgb/parameterized). So a normal pip install of the minimal wheel still pulls all of those in, which is most of the real install size. The CI test only passes because it uses pip install --no-deps. We already know the true minimal set, since it is the list installed in test_minimal_wheel.sh (flatbuffers, numpy, packaging, pyyaml, ruamel.yaml, sympy, tabulate, typing-extensions, torch). Could we set install_requires to that in minimal mode via the existing setup_kwargs, drop --no-deps from the test, and add a check that the wheel metadata does not list the heavy deps?

  2. flatc keeps the wheel platform specific. The minimal build still ships flatc and compiles the flatbuffers target, so the wheel is not pure Python and the build still needs cmake and a compiler. On main the .pte serialization is pure Python, and the MobileNetV2 test never actually calls flatc (it is only needed for external-weight .ptd export). If we drop flatc, the minimal wheel could be pure Python (py3-none-any), so one wheel would work for every platform including Jetson, buildable with no toolchain. The tradeoff is losing external .ptd export. Could go either way, but it would be good to confirm the intent.

  3. This is a build mode, not a published package. The minimal wheel has the same name and version as the full one, so pip install torch_tensorrt will still resolve to the full wheel on PyPI. For downstream consumers to get the slim version automatically we would need either a separate published distribution or for the consumer to build and bundle this. Probably worth a tracking issue.

Minor:

  • The new CI job is Linux only (no aarch64/Jetson), and because it installs with --no-deps the dependency resolution is never tested.
  • The smoke test exports through exir directly. Running it once through the real torch_tensorrt output_format="executorch" path would cover the actual consumer.
  • The README could note that the minimal wheel currently still installs the full set of Python dependencies and is platform specific, so readers do not assume "minimal" means a small install yet.

None of this blocks the direction. Item 1 is small and the highest value.

@JacobSzwejbka

Copy link
Copy Markdown
Contributor Author

This is a build mode, not a published package. The minimal wheel has the same name and version as the full one, so pip install torch_tensorrt

I thought they wanted the minimal build for the custom packages where they build the entire thing themself. So building from source is fine.

@shoumikhin

Copy link
Copy Markdown
Contributor

Makes sense; building from source covers the distribution side.

The main thing left is the dependency list. Minimal mode trims the files but not the declared deps, so a normal install still pulls coremltools, scikit-learn, pandas, hydra-core, omegaconf (coremltools is gated to Darwin or Linux, so it does hit the Linux/Jetson consumer). The test only passes because it uses --no-deps.

These are static [project.dependencies] in pyproject.toml, so an env flag can't subtract them. To vary them by build mode you'd need to make deps dynamic: add "dependencies" to pyproject's dynamic=[...], remove the static dependencies array, and compute install_requires in setup.py branching on EXECUTORCH_BUILD_MINIMAL (roughly the set already installed in test_minimal_wheel.sh: flatbuffers, numpy, packaging, pyyaml, ruamel.yaml, sympy, tabulate, typing-extensions; torch is assumed present). Worth also deciding what to do with mpmath and pytorch-tokenizers, which are declared today too. Then drop --no-deps from the test and assert the heavy deps are absent, so it actually proves the slim claim.

@narendasan

Copy link
Copy Markdown

I thought they wanted the minimal build for the custom packages where they build the entire thing themself. So building from source is fine.

Yeah we intend to build the package from source for the container distribution

@linux-foundation-easycla

linux-foundation-easycla Bot commented Jun 5, 2026 •

Copy link
Copy Markdown

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

@shoumikhin
shoumikhin force-pushed the export-only-wheel branch 2 times, most recently from 8fd5caf to 454deef Compare June 5, 2026 21:40
@shoumikhin shoumikhin added the release notes: none Do not include this in the release notes label Jun 5, 2026
The opt-in minimal wheel build (`EXECUTORCH_BUILD_MINIMAL=1`) previously trimmed the packaged files but not the declared dependencies. Because `pyproject.toml` `[project.dependencies]` is static, a normal `pip install` of the minimal wheel still pulled `coremltools`, `scikit-learn`, `pandas`, `hydra-core`, `omegaconf` and the test-only deps (`expecttest`, `hypothesis`, `kgb`, `parameterized`), which is most of the real install size. The CI smoke test only passed because it used `pip install --no-deps`.

Make `dependencies` dynamic in `pyproject.toml` and compute `install_requires` in `setup.py` per build mode. The full (default) wheel keeps the existing set unchanged; the minimal wheel declares the subset of that set which `executorch.exir` needs to export a `.pte`: `flatbuffers`, `numpy`, `packaging`, `pyyaml`, `ruamel.yaml`, `sympy`, `tabulate`, `typing-extensions`. `torch` stays consumer-provided and `mpmath` resolves transitively via `sympy`. The minimal set is derived from the full set (with a build-time check that every kept name matched a full dep) so pins and markers cannot drift.

Also make the minimal build skip the non-minimal cmake targets explicitly, and harden the smoke test: drop `--no-deps` so dependency resolution is exercised, assert the built wheel METADATA declares exactly the slim set, and run the bundled `flatc` (the only compiled artifact) to confirm it ships and resolves. Document the slim deps and that `torch` is consumer-provided in `README-wheel.md`.

Co-authored-by: Jacob Szwejbka <20804483+JacobSzwejbka@users.noreply.github.com>
@shoumikhin
shoumikhin force-pushed the export-only-wheel branch from 454deef to 2ba2a55 Compare June 5, 2026 21:44
@shoumikhin
shoumikhin merged commit 0d904b6 into main Jun 6, 2026
338 checks passed
@shoumikhin
shoumikhin deleted the export-only-wheel branch June 6, 2026 06:30
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.

3 participants