Skip to content

Ship only the TensorRT delegate in the ExecuTorch runtime wheel - #4567

Open
shoumikhin wants to merge 78 commits into
pytorch:mainfrom
shoumikhin:executorch-slim-runtime-wheel
Open

shoumikhin wants to merge 78 commits into
pytorch:mainfrom
shoumikhin:executorch-slim-runtime-wheel

Conversation

@shoumikhin

@shoumikhin shoumikhin commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Stacked on #4566. Its diff against main currently includes #4566's commits too, so please review
and merge that one first, after which this reduces to the delegate change alone.

Problem

The runtime wheel bundled a second copy of the ExecuTorch runtime and swapped in its own Python
bindings at import time. Two copies of a runtime in one process can disagree, and whether loading
worked depended on import order. The TensorRT delegate should use the ExecuTorch runtime the user
already installed.

Change

Ship the TensorRT delegate library, its Python registration module, and a CMake package for C++
consumers. Build against the installed ExecuTorch wheel rather than bundling a private runtime, so
there is one runtime in the process.

Keep the delegate on the shared build, repair, artifact and upload path the main wheel already uses,
so its packaging cannot drift from the wheel it ships beside. It keeps its own version, and it pins
the exact ExecuTorch it was built against, because the delegate links that runtime's shared library
and the two have to be the same build.

Take the TensorRT requirement from the main wheel's own declared dependencies instead of repeating
it, so a delegate build cannot select a different TensorRT than the wheel it links.

Keep torch_tensorrt.load(..., format="executorch") working, with the entry points the published
package already exposes, so upgrading this wheel alone does not break existing callers. Examples use
ExecuTorch's own Module API. The C++ guidance covers linking the CUDA backend alongside this one and
explains caller-owned outputs for device-resident exports.

Test plan

Ran the ExecuTorch test directory on Linux.

Built and installed the main and companion wheels on Linux aarch64 with CUDA 13.2, then exported and
numerically executed four kinds of program through stock ExecuTorch: TensorRT only, mixed CUDA and
TensorRT in one program, device resident, and externally weighted. Ran the same programs from a C++
consumer built against the installed wheels.

Confirmed the controls fail as expected: a missing delegate and missing external data both error
rather than silently producing wrong output.

Checked the shipped library's dependencies, symbol retention and library search paths, and that a
C++ consumer resolves CUDA, TensorRT and ExecuTorch from the installed wheels.

The x86_64 main wheel passed installation and dependency checks. Full x86_64 companion qualification
is not complete, and one Arm platform-tag check is still open.

@meta-cla meta-cla Bot added the cla signed label Aug 23, 2026
@github-actions github-actions Bot added component: tests Issues re: Tests component: build system Issues re: Build system component: api [Python] Issues re: Python API component: api [C++] Issues re: C++ API labels Aug 23, 2026
@github-actions
github-actions Bot requested a review from narendasan August 23, 2026 14:13
@shoumikhin
shoumikhin force-pushed the executorch-slim-runtime-wheel branch 3 times, most recently from 44796ff to 3c104cb Compare August 23, 2026 19:00
@shoumikhin
shoumikhin marked this pull request as ready for review August 23, 2026 19:08
@shoumikhin
shoumikhin force-pushed the executorch-slim-runtime-wheel branch from 3c104cb to 4adc20b Compare August 23, 2026 19:27
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Aug 23, 2026
@shoumikhin
shoumikhin force-pushed the executorch-slim-runtime-wheel branch from 4adc20b to 7cac1af Compare August 23, 2026 19:30
@shoumikhin
shoumikhin marked this pull request as draft August 23, 2026 19:31
@shoumikhin
shoumikhin marked this pull request as ready for review August 23, 2026 19:57
@shoumikhin
shoumikhin force-pushed the executorch-slim-runtime-wheel branch 7 times, most recently from ff3379e to a008221 Compare August 24, 2026 17:00
@lanluo-nvidia lanluo-nvidia added this to the v2.15.0 milestone Aug 24, 2026
@lanluo-nvidia lanluo-nvidia added the ci: nightly Run the nightly lane (all tiers incl. llm / kernels / distributed) on every push label Aug 24, 2026
@github-actions
github-actions Bot requested a review from lanluo-nvidia August 24, 2026 17:03
@shoumikhin
shoumikhin force-pushed the executorch-slim-runtime-wheel branch 4 times, most recently from 49b052e to 370c368 Compare August 25, 2026 06:55
pytorchbot and others added 30 commits September 15, 2026 21:13
The step that builds the companion wheel was gated on the row's CUDA major, because
the release matrix still carried a CUDA 12.6 row and that channel has no ExecuTorch
build, so the step's own install failed and took the whole job with it.

The CUDA 12 rows are gone. Every matrix that reaches this step now offers CUDA 13
only, and no caller turns on the Jetson path that kept a CUDA 12.6 entry. So the
condition can never be false, and a condition that can never be false is worse than
no condition: it states a rule the code no longer enforces, and it repeats a value
the matrix filter already derives.

Test plan:

Ran the ExecuTorch test directory: 248 passed.

Confirmed no caller enables the Jetson path, and that every wheel and release matrix
offers CUDA 13 rows only.
A scheduled workflow run reaches only the default branch, so the arm that gave a release
branch the stable track on a schedule could never execute. Worse, the check that stops a
shipped release from being re-pinned lived inside that arm, so the one guard that mattered
was unreachable while a manual dispatch, the only way a release branch is ever re-pinned,
walked straight past it.

The dispatch path now carries that check. It also matches a final tag rather than any tag
beginning with the version, because a release candidate is not a release and a dispatch
during one should still be allowed to move the pin.

The header comment already described the real behaviour. It now also says why, so the next
reader does not restore the arm.

Test plan:

Ran the ExecuTorch test directory: 252 passed.

Sampled published workflow runs for the schedule event and found every one on the default
branch, which is what makes the removed arm unreachable.

Confirmed a dispatch on a tagged release refuses, a dispatch on an open release still takes
the stable track, a dispatch asking for a nightly off the default branch still refuses, and
a schedule on any other branch still does nothing.
The ExecuTorch extra carries a Linux marker, so off Linux the suggested pip command matches
nothing, installs nothing, and still exits successfully. A user on Windows or macOS with a
CUDA 13 build followed it, saw it succeed, and hit the same error again. This is reachable
rather than theoretical, because the project builds Windows CUDA 13 wheels.

The helper now checks the platform as well as the CUDA version, and falls back to the same
guidance it already gives an unsupported CUDA build, which names the requirement.

The load error for a missing delegate runtime also claimed the wheel is not published to any
index, which stopped being true once it started shipping. It now names where to get it.

Test plan:

Ran the ExecuTorch test directory: 252 passed.

Confirmed the helper returns an install command on Linux and the guidance text on Windows and
macOS, with the same supported CUDA build in all three cases, so only the platform decides.

Confirmed the wheel the load error described as unpublished is present on the index the new
message points at.
Writing happens per file after every site has been read and rewritten in memory, so a parsing
problem cannot split the tree. A disk or signal failure partway through the writes still can,
and the tool then refused every later run, including one aiming at a newer version, because the
sites it had already moved no longer matched the version it expected to replace.

A site already carrying the target now counts as satisfied, so the next run completes what the
interrupted one started instead of needing a human to unpick it.

The docstring claimed everything was preflighted before any write. That was only ever true of
parsing, so it now says what the function actually guarantees.

Test plan:

Ran the ExecuTorch test directory: 252 passed.

Confirmed a run whose sites are split between the old and the target version succeeds and
converges, and that the same case fails without this change.
Bundling a second ExecuTorch runtime makes backend registration depend on import order. Ship one delegate linked to the installed runtime, with import-time registration and direct Module API examples that preserve device-resident inputs.

Keep companion builds, repair, artifact checks and uploads on the shared Linux path. Build the companion for CUDA 13.0 and 13.2, retain its independent version, and leave ordinary release rows unchanged.

This removes the custom runtime wrapper and the released loader entry point. The API removal still requires a compatibility path or an explicit maintainer exception before merge.

Test Plan: 142 CPU-safe runtime checks passed, including real wheel tagging, setup metadata, CMake version checks, native-guard fixtures and example API routing. All 178 pin/updater checks passed, including installed-wheel provenance and the history-free repository integration. Downstream native operations were stubbed in CPU-safe checks. No fresh native Linux/CUDA build or delegate GPU execution was run.
A preloaded delegate can lose registration to another library. Accepting it by name alone reports success while the other library remains active. Check ownership before retaining the loaded handle, while preserving valid preload adoption and repeated registration.

Test Plan: 40 loader tests passed, including two presence-only removal controls; 40 passed again from the exported staged tree. The combined CPU-safe packaging and loader suite passed 154 tests, and the pin/updater suite passed 186. Six native load-order and concurrency cases passed on Linux aarch64. A native presence-only control reproduced the wrong acceptance, and both missing-query paths were rejected. Full wheel and numerical GPU validation remain separate.
Repeated package discovery must restore the caller's library list even when the imported target already exists. Select the fixed delegate filename shipped by the wheel so neighboring versioned files cannot replace it.

Test Plan: Six configure-only discovery cases and four guard-removal controls passed, including repeated and function-scoped calls. The same ten checks passed from the exported staged tree. A broader selection of 163 CPU-safe packaging and loader tests passed.
Consumers must get RUNPATH even when their toolchain defaults to old dynamic tags. Preserve scoped delegate retention so static registration runs without keeping unrelated libraries.

Test Plan: A Linux native fixture reproduced the old-dtags failure before repair. Six fixture cases passed afterward and from the staged snapshot, including retention, ordering, scope and new-dtags removal controls. The real ARM64 delegate consumer retained its registration and emitted RUNPATH. Thirteen existing CMake checks passed.
A consumer requesting a range that spans major versions must not silently accept the companion. Match the standard same-major rules while preserving the exclusive next-major boundary and independent companion version.

Test Plan: Eleven real CMake package requests agree with the standard generator. Three guard-removal controls reproduce the original cross-major acceptance. All fourteen cases pass from the staged snapshot. The selected CPU-safe suite passed 176 cases, and the complete pin/updater suite passed 186 cases.
Requesting a CMake component does not link it. Link the CUDA backend explicitly so both documented mixed consumers register CUDA and TensorRT.

Tested ten CPU-native consumer cases and four installed-runtime registration cases on Linux aarch64, including CUDA-link removal controls. Repeated the repository cases from the staged snapshot.
Reject a model path ending in .expected before exporting or writing the reference. Separate model and reference paths retain the existing naming.

Tested four output-boundary cases, including existing-file preservation and a removed-guard control, from the working tree and an independent staged snapshot.
Reject a non-CUDA input explicitly before loading a device-resident program. Python optimization no longer removes the guard.

Tested normal and optimized compilation with invalid inputs, valid inputs, and guard removal. All ten example-boundary cases passed from an independent staged snapshot.
Keep format="executorch" working through the installed ExecuTorch Module loader. Preserve the released method interface and CPU input behavior, and warn callers to migrate during a minimum six-month deprecation period.

Tested 82 compatibility, runtime-policy, and example-boundary cases from the exact staged snapshot. Nine removal controls detected broken behavior. Real TensorRT and CUDA execution are not covered by these tests.
Require an explicit true value before allowing an unpinned ExecuTorch build or selecting debug mode. Nonempty false values must not change build safety or optimization.

Test Plan: Executed actual setup commands for 30 flag cases and two removed-check controls. The exact staged snapshot passed 72 packaging and Python runtime cases.
Use the same case-insensitive true values as the build flags so tooling can consistently suppress import-time registration.

Test Plan: Executed imports for 15 flag values and a missing-on control. The exact staged snapshot passed 88 packaging and Python runtime cases.
Editable installs lost the native library and CMake files when temporary build directories were removed. Build them beside the source package and report the stable output mapping to setuptools. Keep ordinary wheel payloads and build-directory cleanup unchanged while preserving source-root files.

Test Plan: 98 packaging and runtime cases passed from the staged snapshot. Real default and strict editable builds and pip installs load a compiled probe after temporary directories disappear; removing the fix fails both modes. Ordinary wheel members are byte-identical with the fix removed. All 42 packaging cases also pass on setuptools 68.0.0 and wheel 0.40.0, and 16 existing version cases pass. These are controlled packaging tests, not a CUDA delegate build.
The test workflow installs the companion wheel. Source loading is deliberate so these tests exercise checkout code with controlled dependencies, not evidence that the wheel is absent.

Test Plan: Verified all four misleading comments were replaced and executable syntax is unchanged. All 98 packaging and runtime tests passed from the exact staged snapshot.
The main wheel is installed without dependencies, so a clean builder lacks
the TensorRT Python metadata required by companion setup. Install only the
TensorRT requirements declared by that wheel, preserving the matrix's torch
and architecture-specific dependency selection.

Test plan: execute the shared build commands with metadata fixtures for both
architectures, CUDA 13.0/13.2, and release/nightly versioning. Both dependency
installation removal controls fail as expected. Selected workflow checks pass.
No native build or GPU execution is claimed.
Source-text assertions accepted commented or disabled device commands. Run
the parsed workflow script with bounded fake tools and record export and
runner arguments. Check artifact handoff, failure propagation, and exclusion
of device inputs from the host-input reference runner.

Keep serialized-program checks separate. Correct the builder GPU and
registry-only coverage descriptions and remove the stale line citation.

Test plan: CPU-only workflow execution, both command failure controls,
commented/disabled/removed command controls, and a disabled-job control.
The selected workflow and wheel-policy suite passes. No numerical GPU
execution is claimed.
The unpinned fixture used an exact wrong version and omitted unrelated
required dependencies. Use a genuine range while retaining valid dependency
metadata, and keep the separate exact-wrong-pin case.

Test plan: the real checker rejects the range through its version branch.
Deleting only exact-version enforcement admits that same wheel. The original
fixture still failed on a missing dependency under this control.
Run the shared installed-check commands through bounded tools using real
wheel metadata and archive extraction on both architecture tags. Keep native
loading simulated, but require the installed path and clear build-only
library paths. Bound the existing shared repair test as well.

Test plan: successful checks and missing-library, undefined-symbol, ldd, and
import failures; deleted/false resolution guard and disabled-step controls.
Real wheel tags and RECORD hashes survive shared repair. No native ELF or
GPU execution is claimed.
Check CUDA dependencies against the artifact's actual RUNPATH so a valid
build does not depend on optional expected text. Reject invalid supplied
options and unsupported platform tags instead of silently skipping checks.
Use auditwheel 6.8.2 numeric and named symbol policies for the two supported
architectures, and propagate dependency-inspection failures.

Keep shared registry and CUDA stream linkage, exercise the production
post-build check and remove the unused private-runtime guard.

Test Plan:
100 artifact cases passed on Linux aarch64; 89 passed with 11 Linux-only
skips on macOS. Real GNU, LLVM and elfutils readers accepted valid ELF and
rejected legacy RPATH. Production native builds exercised removed,
early-return and inert guard controls, alias retention and static-target
rejection. Original selected guard definitions passed 43 Linux cases and
42 macOS cases with one Linux-only skip. Complete pin/updater suites passed
186 cases. The initial before-fix run had 41 failures and 36 passes.
Full wheel and GPU numerical tests were not run.
Explain rebuilding generated native files, restarting consumers, and explicit boolean flag values. Verified the build-recipe check against the staged documentation.
The companion includes a CMake package for installed consumers. Remove an outdated explanation that described a library-only archive while keeping the existing target checks.

Test Plan: Both focused CMake export and installed-package tests passed; assertions are unchanged.
The active build environment can retain an older wheel tool when build isolation is disabled. Require the existing minimum version so companion repair can retag the wheel before artifact upload.

Test Plan: 208 pin, updater, and workflow tests passed. Removing the minimum is detected by both architecture cases. Two real wheel archive retagging tests passed with wheel 0.40.0; their payloads are fixtures, not native builds.
Keep controlled packaging and registration checks in the unit selection so they run when the unit marker is requested.

Test Plan: Normal API and Python runtime unit selection passed 231 tests on Linux with a compatible published compiler wheel. This checks collection and test behavior, not a final native wheel build.
C++ applications following the installed-wheel example cannot execute programs
that copy tensors between the host and GPU because the copy kernels are not
registered. Include the required kernel library and document caller-owned
outputs for device-resident programs. Extend the native example checks to
verify registration and library retention.

Test Plan:
- Built and ran an installed-wheel C++ consumer on Linux aarch64 with CUDA 13.2.
  Nine numerical calls passed across TensorRT-only, mixed, and device-resident
  programs. Omitting the delegate failed with an unregistered backend.
- The updated documentation checks failed in six cases before the fix.
  All 12 native CMake checks passed on the exact staged snapshot afterward.
Fresh wheel-checker subprocesses cannot use the private copy of wheel exposed by setuptools in the parent test process. Install wheel>=0.40 in both ExecuTorch test entry points so artifact checks can run reliably.

Tests: 267 pin, updater and workflow cases passed; 42 Linux artifact cases passed, including rejection and removed-guard controls. All 26 workflow cases passed from the final staged tree. Five targeted cases failed before the setup fix.
The delegate tests restated which CUDA rows the shared build accepts, as a
literal set in three assertions. The matrix filter already declares that, so the
same fact lived in two places and a CUDA version bump had to be applied to both.

Read the rows from the filter script instead. The assertions now check that the
tests and the filter agree, rather than checking the tests against themselves.

Also translate the shared build gate's startsWith expression when evaluating it,
so the gate test exercises the expression the workflow actually uses.

Test plan:

Ran the delegate test module on Linux aarch64. Confirmed the row assertions fail
when the filter and the tests disagree.
The companion wheel on the nightly index today exports `activate` and
`get_runtime` and ships a `runtime` submodule. This change removed all three with
no alias, so code written against the published wheel breaks on upgrade.

`activate()` is back as a deprecated alias for `register()`. `get_runtime()` raises
with what to do instead, since this package no longer bundles a runtime and there
is no runtime object left to return.

The loader had the same break from the other side: it imported `register`
unconditionally, which raises ImportError against that published companion.
ImportError is not ModuleNotFoundError, so its own handler did not catch it and the
failure surfaced as a traceback instead of the guidance the function exists to
give. It accepts either entry point now, so upgrading the main wheel alone keeps
loading programs during the compatibility window this change promises.

Also make a failed native build fail the install. setuptools runs a customized
`build_py` inside a broad `except Exception` during editable installs and
downgrades the error to a warning pip hides without `-v`, so a failed delegate
build left pip printing "Successfully installed" with no delegate present.
SystemExit escapes that catch. A misconfiguration this file raises itself is left
as it is, so the reason a build stopped is still the reason it reports.

The delegate lane's matrix flag was asserted by nothing, so deleting it left every
guard green while the lane tested rows whose channel carries no ExecuTorch.

Test plan:

Ran the pin, updater and shared runtime suites on Linux aarch64: 287 passed.

Downloaded the companion published on the nightly index and confirmed it exports
activate and get_runtime and ships runtime.py, then confirmed the loader resolves
that wheel through activate where it previously raised ImportError.

Confirmed the shims are exported, that get_runtime explains itself rather than
failing obscurely, and that the filter keeps only CUDA 13 rows when the lane passes
its flag. Deleting the flag from the workflow fails that test.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci: nightly Run the nightly lane (all tiers incl. llm / kernels / distributed) on every push cla signed component: api [C++] Issues re: C++ API component: api [Python] Issues re: Python API component: build system Issues re: Build system component: tests Issues re: Tests documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants