Ship the CUDA delegate in the wheel - #21645
Open
shoumikhin wants to merge 81 commits into
Open
Conversation
Contributor
Author
shoumikhin
requested review from
abhinaykukkadapu,
digantdesai,
kirklandsign,
larryliu0820,
mergennachin and
psiddh
as code owners
August 7, 2026 15:49
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21645
Note: Links to docs will display an error until the docs builds have been completed. ❌ 2 Cancelled Jobs, 3 Pending, 2 Unrelated Failures, 1 Unclassified FailureAs of commit 3a2908b with merge base 46f9696 ( UNCLASSIFIED FAILURE - DrCI could not classify the following job because the workflow did not run on the merge base. The failure may be pre-existing on trunk or introduced by this PR:
CANCELLED JOBS - The following jobs were cancelled. Please retry:
FLAKY - The following job failed but was likely due to flakiness present on trunk:
BROKEN TRUNK - The following job failed but was present on the merge base:👉 Rebase onto the `viable/strict` branch to avoid these failures
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The CUDA delegate runs a model on an NVIDIA GPU. It is built into the Python extension, so only
Python can use it. A C++ application has no way to link it, and nothing else can reuse it either.
There is a sharing problem too. A program may use more than one GPU backend at once, and they need
to agree on which CUDA stream (the queue the GPU runs work on) the caller chose. If each backend
carries its own copy of that state, work queued through one is invisible to the other.
Ship the CUDA delegate and a small stream helper as their own shared libraries, and name both as
CMake components. The stream helper is shared so a process has exactly one copy of the caller's
stream choice, which is what lets two backends agree on it.
A CUDA wheel does not bundle the CUDA runtime. It declares it as a dependency, the way the PyTorch
CUDA wheels do, so one copy is shared with torch rather than shipping a second one:
Each library records a relative path to where pip installs the CUDA runtime, so it resolves without
the caller setting a library search path and without depending on a toolkit being installed.
The declared set is only what a shipped library actually loads, which is the CUDA runtime and
nothing else. cuRAND is used through its device-side header API, which compiles into the object
rather than linking a library, and the generated model library embeds its kernels rather than
compiling them at run time, so there is no runtime compiler to declare. Each declared package also
needs its own directory recorded, since that is where the loader looks. CUDA 13 collects every
component in one directory while CUDA 12 gives each its own, so the recorded set differs by train.
The stream helper's header no longer includes
cuda_runtime.h, which the wheel does not publish. Itonly ever uses a CUDA stream as an opaque handle, so it declares that handle itself, and a consumer
can compile against the wheel with no CUDA toolkit installed. A ROCm build keeps the runtime alias
header it already used, where the same handle is a hip type that only that header can name.
Built a CUDA wheel, installed it into a clean environment, and:
same weights and inputs (largest absolute difference 0).
same reference.
into every consumer put three copies in one wheel, and a stream selected through one was invisible
to the others.
that links the CUDA runtime has a relative path to it.
so a builder without a matching toolkit fails while configuring. Before this, such a row produced
a wheel tagged for CUDA, carrying no CUDA library, that still declared the CUDA runtime packages:
it installed cleanly and then reported the backend as unregistered when a model ran.
declared packages and the loader paths come from the row while the binaries come from the
toolkit, and nothing compared the two, so a
cu126row built against a 13.0 toolkit attachedCUDA 12 metadata to binaries needing
libcudart.so.13. An unrecognised train fails too, insteadof silently reporting whatever the builder happened to have. Detection reads the toolkit major
directly, so the guard fires on any mismatch rather than only on the three exact
(major, minor)pairs the supported list carries; on those three pairs it behaved correctly before, and on every
other minor it saw an empty detection and skipped the check.
spelled with an unsupported minor means. The shell classifier reduces the row to digits and
matches against
SUPPORTED_CUDA_VERSIONS. Packaging did the same shape on the outer decisionand then took only the first two digits when picking runtime packages, so
cu125classified asCPU on one side and declared CUDA 12 on the other. Packaging now matches on the same digits and
raises loudly on an unsupported train instead.
listing the spellings that mean "no CUDA". Checked 16 row values including
cpu-aarch64,rocm6.2andcu118; the previous list-based form was wrong on several, and each wrong answermade a non-CUDA wheel declare the CUDA runtime.
optional unconditionally, so a wheel tagged
+cu126with no CUDA library at all passed everycheck.
dependency whenever CUDA is on, while packaging named only the shared-build spelling, so a
non-shared build shipped a shim whose dependency resolved to nothing.
A fixed pair was correct at one depth only: measured over every shipped location, 6 of 12 hops
landed on a directory that does not exist, and the hop from
lib/climbed out of the packageentirely, where an unrelated library with a matching soname could satisfy the dependency first.
opaque handle and calls no CUDA function, so it needs no toolkit include and no libcudart link.
A ROCm build still links hip, because the alias header it uses there does include hip's own.
compiles into the object rather than resolving against a library, so the link only recorded a
dependency nothing calls into: the shipped library names
libcurand.so.10as needed and leavesnot one cuRAND symbol undefined. Dropping it removes a runtime package the wheel would otherwise
have to place for a library that never opens it.
records one only when something resolved from outside a default directory, which is a property of
the builder image rather than of the wheel, so keying the hops off it would drop the route to the
CUDA wheels this package declares as dependencies on an image that keeps the runtime in
/usr/lib64.Measured on a library built with nothing outside a default directory: it now carries the CUDA and
sibling hops, and a non-CUDA build of the same library is rewritten only where a hop applies.
Also fixed in this commit:
python3orpython, whicheverexists) instead of assuming one name, and no longer discards stderr. Builders disagree on the
name: Linux and macOS provide
python3, while the Windows builder runs inside a condaenvironment that provides only
python. Assuming either name breaks the other platform, andtreating the failure as "not a CUDA row" silently rebuilt a CUDA row as a CPU row.
CU_VERSION=cpu pip install .is handled explicitly instead of running the CUDA-train parserover it, which previously turned
cpuintoputhrough a character-set strip and reached theunsupported-train error.
Ran end to end on H100, A100 and Jetson Thor, covering compute capabilities 9.0, 8.0 and 11.0.
Known gap, not introduced here: the Python
Runtime.load_programpath allocates activation memoryon the host, so a program exported to keep activations on the GPU fails there. The supported Python
loader and the C++ path both work. This is upstream in the Python bindings, which this change does
not touch.
cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani