Repository navigation
Commit 3114ccc
authored
Ship the CUDA runtime in the Windows CUDA wheel (pytorch#23123)
Builds on pytorch#23121 (merged), which ships linkable libraries in the Windows
wheel. Stacked on
pytorch#23405, which makes the CUDA backend fall back to cudaMalloc on devices
without memory
pools: the Windows GPU CI runner's A10G is one, so this PR's end-to-end
runs need it.
A Windows user can run a CUDA program but cannot lower one: lowering
compiles the model
with a toolchain only the Linux side has, and the CUDA Windows CI job
already exports on
Linux and runs on Windows for exactly this reason. What was missing is a
wheel that gives
that Windows user the runtime. This adds a Windows CUDA wheel carrying
the CUDA delegate
as C++ libraries, the same components the Linux CUDA wheel ships
(pytorch#21645, pytorch#21668), and
nothing CUDA in the Python module, which on Windows has no CUDA program
to lower. The
export (AOT) side is unchanged and identical to the CPU wheel's; only
the runtime links CUDA.
lib/executorch_backend_cuda.dll + .lib the delegate
executorch::backend_cuda
lib/executorch_extension_cuda.dll + .lib allocator + streams
executorch::extension_cuda
backends/cuda/aoti_cuda_shims.dll the AOTI shim layer (runtime DLL of
the delegate)
data/lib/aoti_cuda_shims.lib already shipped as the lowering stub; also
the
shim layer's import library here
extension/pybindings/_C.pyd unchanged: no CUDA dependency, no
CudaBackend
A C++ application links it exactly as on Linux:
find_package(executorch REQUIRED COMPONENTS kernels_optimized
backend_cuda)
target_link_libraries(app PRIVATE executorch::runtime
executorch::kernels_optimized
executorch::backend_cuda)
# plus the $<TARGET_RUNTIME_DLLS:app> copy from the CPU PR, which now
also brings the
# CUDA extension and the shim layer
The wheel's DLLs import no CUDA runtime DLL: CMake links cudart.lib into
them on Windows,
and that reaches the driver (nvcuda.dll). The compiled model is
different: the library
AOTInductor builds into model.pte imports cudart64_13.dll, which comes
from the CUDA
Toolkit's bin directory on PATH, as the guide says. PyPI's
nvidia-cuda-runtime has win_amd64
builds that carry that DLL, but a C++ program does not search
site-packages, so declaring it
would not make it loadable; the wheel declares no NVIDIA package for
Windows.
## How
- Generator. The CUDA toolkit's Visual Studio integration fails compiler
identification
under the ClangCL toolset on some toolkit and Visual Studio pairs
(MSB4023 in its targets
file), and the MSVC toolset cannot compile the Python extension's
sources. A Windows CUDA
build therefore uses Ninja Multi-Config with clang-cl, the compiler the
CPU wheel uses,
and nvcc with cl.exe as its host compiler, in a Visual Studio developer
environment that
has ninja (ninja, clang-cl and cl all on PATH, which is how the wheel
build runs).
Elsewhere, such as a plain shell, the build keeps the Visual Studio
generator it always
used. CMAKE_CUDA_COMPILER is passed only when CUDACXX is unset, so
options a user put in
CUDACXX (such as -allow-unsupported-compiler) still apply. The toolkit
is the one
install_utils already reports, so what compiles is the train packaging
declares.
- CUDA 13.2 and newer. Their CCCL stops with an #error under cl.exe's
traditional
preprocessor, so the CUDA sources pass -Xcompiler=/Zc:preprocessor on
Windows (13.0
accepts it too).
- flatc / flatcc byproducts and imported locations share one host
executable suffix chosen
by CMAKE_HOST_WIN32. CMAKE_EXECUTABLE_SUFFIX is the target's: a Zephyr
toolchain sets
.elf, and naming the byproduct after it left Ninja with no rule for the
host tool.
- The shims resolve the runtime from executorch.dll rather than a static
core. (The PAL's
strong symbols are in pytorch#23121; C++20 for the delegate and shims comes
from pytorch#23135.)
- The shims keep WINDOWS_EXPORT_ALL_SYMBOLS OFF in a static build, as
upstream sets it,
and turn it ON in the shared layout: the delegate DLL imports the stream
guard functions
(get/set/peek/clearCurrentCUDAStream) from the shim DLL, and those carry
no
AOTI_SHIM_EXPORT, so the annotations alone would not export them. The
allocator is not
in either DLL: main moved it into extension_cuda (pytorch#23447), a shared
library the
delegate and the shims both import it from, so the process has one.
- pytorch#23121 keeps a Windows CUDA build static; this drops that guard, so
the pybind preset
now builds the shared layout with CUDA too, and adds the CUDA import
libraries to the
Windows packaging list.
- The root CMake file no longer links the CUDA delegate into the Python
extension on
Windows; extension/cuda finds the CUDA toolkit itself, as on main.
- The package config adds the CUDA extension (allocator and stream
helpers) and the shim
layer to the delegate's runtime
DLL set, and reports EXECUTORCH_RUNTIME_DLLS_EXTRA for the pre-3.28
route, since the
shim layer ships outside lib/.
## CI
- build-wheels-cuda-windows.yml mirrors the Linux CUDA workflow on the
Windows wheel
builder and reuses filter_cuda_matrix.py. pre_build_script.sh drops the
blanket Windows
CUDA=OFF (a CPU row is still forced off by the generic row
classification), resolves
TORCH_CUDA_ARCH_LIST from cuda_arch_list.sh, and writes it to
BUILD_ENV_FILE as well as
GITHUB_ENV: the reusable workflow sources that file after the hook, and
its own list had
replaced the row's (sm_75 in, sm_89 out). It also installs ninja for the
generator.
- End-to-end, per published CUDA train, the route a Windows user has,
modeled on
cuda-windows.yml. The trains are read from filter_cuda_matrix.py's
SUPPORTED_CUDA_VERSIONS
(today cu132 and cu134; main stopped publishing cu130 in pytorch#23380), the
same list the release
rows are built from, so adding or
dropping a train there changes what is tested. The end-to-end jobs take
their trains from
the build job's own filtered matrix, so a pull request without the
ciflow/binaries/all
label tests the one train it builds, and a train the generator dropped
is not tested
against a wheel that does not exist. (install_utils.py's list also has
12.6 for
source builds, but 12.6 is no longer a published wheel train.) A Linux
GPU job exports a
Windows-target program with that train's torch (`test_cuda_windows.py
--export`); a small
job bundles it with the wheel the build job published for that train
(windows_job.yml
downloads one artifact and gives its script no token for a second); and
a Windows GPU job
installs that published wheel and runs test_cuda_windows.py with the
program
(test_cuda_windows_e2e.ps1), so CI runs a CUDA program through the
artifact users install
rather than a wheel rebuilt on the GPU runner. The export and end-to-end
jobs check out the
commit the build job builds (on a pull request, its merge commit), so
the wheel, the
program and the scripts that test them match. Each train's CUDA (on
Linux for the export,
and on Windows for the smoke test's cuobjdump and the model's CUDA
runtime) is assembled
from NVIDIA's checksummed redistributable archives by
install_cuda_redist.py, which needs no installer or admin rights and
fails on a train it
has no release for. Each train's Windows job waits only for its own
bundle, so one train
failing does not skip the others, and like the export it does not run
for a
pull request from a fork. The Windows GPU runners come without an NVIDIA
driver (nvcuda.dll is missing and cudaGetDeviceCount returns
cudaErrorInsufficientDriver),
so the job first installs the same data-center driver, from the same
place, that
pytorch/pytorch's Windows CUDA smoke tests install (driver_update.bat,
580.88); cu132 and
cu134 run on it through CUDA's minor-version compatibility, as PyTorch's
own wheels do. The
wheel build's own smoke test has no GPU program and says so.
## Tests
test_cuda_windows.py, the Windows counterpart of test_cuda_linux.py:
- the CUDA DLLs and import libraries ship;
- _C depends on no CUDA DLL and registers no CUDA backend;
- no shipped CUDA DLL imports cudart64_*.dll (only the driver), and no
Windows NVIDIA
requirement is declared;
- every DLL with device code carries exactly the row's GPUs, both
directions, and the
newest also as PTX; the expected list comes from
executorch_cuda_arch_list in
cuda_arch_list.sh, called the way the pre-build hook calls it and keyed
by the wheel's
+cuXYZ tag, not from the TORCH_CUDA_ARCH_LIST the build consumed;
- a C++ application linking executorch::backend_cuda gets the three DLLs
copied beside it
and sees CudaBackend registered;
- a program lowered on Linux for Windows runs through the C++ SDK and
matches eager, after
checking it was exported with the same CUDA train as the wheel (every
CUDA 13 train loads
cudart64_13.dll, so a mismatch would not fail on its own);
- then what a CPU Windows row checks: test_shared_libraries.py (with the
three CUDA
ownership rows), test_cpp_sdk.py and the MobileNetV3/XNNPACK model run,
including the Arm
Cortex-M Python module, which builds under Ninja too: CMSIS-NN's
test-only shared library
took the static library's name, so its import library collided with it
on Windows, and
backends/cortex_m now gives the import library its own name.
## Test plan
On Windows 11 x64 with an RTX 5080 (sm_120, driver 595.95), VS 2022
BuildTools, CPython 3.12,
and WSL Ubuntu for the export, once per CUDA train. 13.2 and 13.4 are
the published trains
and the ones CI covers; 13.0 is no longer published (pytorch#23380) but still
builds from source, so
it was run too:
| train | Windows nvcc | WSL export torch | wheel | test_cuda_windows.py
| Linux-exported program |
|---|---|---|---|---|---|
| 13.0 | 13.0.88 (installer) | 2.14.1+cu130 | executorch-1.6.0+cu130 |
52 checks pass | runs on the GPU, maxdiff 2.4e-7 |
| 13.2 | 13.2.86 (redist) | 2.14.1+cu132 | executorch-1.6.0+cu132 | 52
checks pass | runs on the GPU, maxdiff 2.4e-7 |
| 13.4 | 13.4.59 (redist) | 2.14.0.dev20260810+cu134 |
executorch-1.6.0+cu134 | 52 checks pass | runs on the GPU, maxdiff
2.4e-7 |
- Each wheel built with TORCH_CUDA_ARCH_LIST from cuda_arch_list.sh:
device code for exactly
sm_80/86/89/90/100/120, plus sm_120 PTX. 13.4 runs on a driver that
reports 13.2, through
CUDA's minor-version compatibility.
- Before the /Zc:preprocessor change, 13.2 and 13.4 failed to compile
the .cu shims with
CCCL's "traditional preprocessor" #error; 13.0 was unaffected.
- Negative: the cu134 wheel pointed at the 13.0-exported program fails
the train check.
- The export used `test_cuda_windows.py --export` from this branch in
WSL, with mingw-w64 and
the Windows CUDA runtime of the same train (13.0 from
install_cuda_windows_cross_compile.sh,
13.2/13.4 from the redist archives, the steps the guide now gives).
- CI, before the rebase onto main: the end-to-end jobs passed for both
published trains on
the Windows A10G runner, with a Linux-exported program matching eager
(maxdiff 1.3e-7 for
13.2, 2.4e-7 for 13.4).
- After rebasing onto main with the allocator moved into extension_cuda
(pytorch#23447), the cu132
wheel was rebuilt and passes all 55 checks, including the new ownership
row: a single CUDA
allocator and stream helpers owned by executorch_extension_cuda.dll
across the 15 shipped
libraries. The rows above were measured before that rebase.
- Linux: the CMake changes are behind WIN32 / MSVC, or pick the same
suffix there (empty on a
Linux host); setup.py's new path runs only on Windows.1 parent 9c3bcd8 commit 3114ccc
16 files changed
Lines changed: 1433 additions & 62 deletions
File tree
- .ci/scripts/wheel
- .github/workflows
- backends
- cortex_m
- cuda
- docs/source
- third-party
- tools/cmake
- preset
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
116 | 116 | | |
117 | 117 | | |
118 | 118 | | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
119 | 136 | | |
120 | 137 | | |
121 | 138 | | |
| |||
125 | 142 | | |
126 | 143 | | |
127 | 144 | | |
128 | | - | |
129 | | - | |
130 | | - | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | 145 | | |
137 | 146 | | |
138 | 147 | | |
| |||
202 | 211 | | |
203 | 212 | | |
204 | 213 | | |
205 | | - | |
| 214 | + | |
| 215 | + | |
206 | 216 | | |
207 | 217 | | |
208 | 218 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1741 | 1741 | | |
1742 | 1742 | | |
1743 | 1743 | | |
1744 | | - | |
| 1744 | + | |
| 1745 | + | |
1745 | 1746 | | |
1746 | 1747 | | |
1747 | 1748 | | |
1748 | | - | |
| 1749 | + | |
| 1750 | + | |
1749 | 1751 | | |
1750 | 1752 | | |
1751 | 1753 | | |
| |||
0 commit comments