Skip to content

Commit d8a028b

Browse files
committed
Make the CUDA docs agree with the TensorRT page
The CUDA backend page and the getting started page said several things that the TensorRT page, and the code, contradict: - The CUDA page said the Python runtime allocates host memory for an unplanned output, so a method that keeps its outputs on the GPU could not run from Python. The Python runtime allocates a new device buffer for each call. - It said a planned input fed with GPU memory makes an undefined host memcpy and crashes, and so did the PropagateDeviceConfig docstring. The runtime refuses that copy and the call fails with NotSupported. The docstring also asked for alloc_graph_output=False, while outputs can stay planned, as the CUDA page and the exporters in this repository do. - It asked for a CUDA 11.x or later toolkit to run a model. The prebuilt packages use CUDA 13 and bring the CUDA runtime with them on Linux (on Windows it comes from a CUDA 13 toolkit), a build from source also supports CUDA 12.6, and the toolkit is needed for export, where nvcc compiles the kernels. It also gave no minimum GPU, while the packages are built for compute capability 8.0 and newer, and it asked for Python 3.8+, while the package needs 3.11 to 3.15. - It listed Windows without saying that programs for Windows are exported on Linux and run from C++. It now links to the C++ guide section that shows how. Its link to the getting started page was also broken. - The getting started page said to use the CPU packages on Windows, while CUDA packages are built for Windows too, and it called CUDA 13.4 nightly only. It now also says that on Windows they run from C++, and that programs for the CUDA backend are exported on Linux.
1 parent beafc96 commit d8a028b

3 files changed

Lines changed: 44 additions & 40 deletions

File tree

‎docs/source/backends/cuda/cuda-overview.md‎

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,28 @@ The CUDA backend is the ExecuTorch solution for running models on NVIDIA GPUs. I
77
- **Optimized GPU Execution**: Uses AOTInductor to generate highly optimized CUDA kernels for model operators
88
- **Triton Kernel Support**: Leverages Triton for GEMM (General Matrix Multiply), convolution, and SDPA (Scaled Dot-Product Attention) kernels.
99
- **Quantization Support**: INT4 weight quantization with tile-packed format for improved performance and reduced memory footprint
10-
- **Cross-Platform**: Supports both Linux and Windows platforms
10+
- **Cross-Platform**: Runs on Linux, and on Windows from C++. Programs for Windows are exported on Linux.
1111
- **Multiple Model Support**: Works with various models including LLMs, vision-language models, and audio models
1212

1313
## Target Requirements
1414

1515
Below are the requirements for running a CUDA-delegated ExecuTorch model:
1616

17-
- **Hardware**: NVIDIA GPU with CUDA compute capability
18-
- **CUDA Toolkit**: CUDA 11.x or later (CUDA 12.x recommended)
19-
- **Operating System**: Linux or Windows
17+
- **Hardware**: NVIDIA GPU with compute capability 8.0 (Ampere) or newer. This is the floor the
18+
prebuilt packages are built for.
19+
- **CUDA**: 13 for the prebuilt packages. On Linux they bring the CUDA runtime with them. On
20+
Windows it comes from a CUDA 13 toolkit install. A build from source also supports CUDA 12.6.
21+
- **Operating System**: Linux, or Windows from C++ for programs exported on Linux (see
22+
[CUDA on Windows](../../using-executorch-cpp.md#cuda-on-windows))
2023
- **Drivers**: PyTorch-Compatible NVIDIA GPU drivers installed
2124

2225
## Development Requirements
2326

2427
To develop and export models using the CUDA backend:
2528

26-
- **Python**: Python 3.8+
29+
- **Python**: Python 3.11 to 3.15
2730
- **PyTorch**: PyTorch with CUDA support
31+
- **CUDA Toolkit**: export compiles kernels with `nvcc`, the CUDA compiler, so it needs a CUDA toolkit
2832
- **ExecuTorch**: Install ExecuTorch with CUDA backend support
2933

3034
## Using the CUDA Backend
@@ -83,7 +87,7 @@ For a complete working example, see the [CUDA export script](https://github.com/
8387

8488
## Runtime Integration
8589

86-
To run the model on device, use the standard ExecuTorch runtime APIs. See [Running on Device](getting-started.md#running-on-device) for more information.
90+
To run the model on device, use the standard ExecuTorch runtime APIs. See [Running on Device](../../getting-started.md#running-on-device) for more information.
8791

8892
When building from source, pass `-DEXECUTORCH_BUILD_CUDA=ON` when configuring the CMake build to compile the CUDA backend.
8993

@@ -140,10 +144,9 @@ exec_program = et_program.to_executorch(
140144
skip_d2h_for_method_outputs=True,
141145
),
142146
enable_non_cpu_memory_planning=True,
143-
# Required alongside the skips. Memory planning reserves a buffer for graph inputs by
144-
# default, and the runtime fills a planned input by copying the caller's memory into that
145-
# buffer, which reintroduces the copy you just asked to skip. On device memory that copy is
146-
# a host memcpy into a device pointer, which is undefined.
147+
# Required alongside the skips. Memory planning reserves a device buffer for graph inputs
148+
# by default, and the runtime refuses to copy the caller's tensor into it, so without this
149+
# every call fails.
147150
# Outputs stay planned, so the runtime allocates them on the device and hands back a
148151
# pointer to its own buffer. See the note below for when to unplan them instead.
149152
memory_planning_pass=MemoryPlanningPass(alloc_graph_input=False),
@@ -170,24 +173,23 @@ it to `False` while asking for either skip raises a `ValueError`, because copy i
170173
during device-aware memory planning.
171174

172175
**Inputs must be unplanned**, via `MemoryPlanningPass(alloc_graph_input=False)`. Without it the
173-
program reserves its own input buffer and the runtime copies the caller's memory into it, so the
174-
copy comes back at run time. On device memory that copy is a host memcpy into a device pointer,
175-
which is undefined, so the program crashes rather than returning a wrong answer.
176-
177-
**Outputs have two valid shapes, and which one you want depends on the language.**
178-
179-
Leave outputs planned, which is the default, when the caller cannot supply device memory itself.
180-
The planned buffer is allocated on the device because `enable_non_cpu_memory_planning` is on, and
181-
`skip_d2h_for_method_outputs` means nothing copies it back, so the result stays on the device and
182-
the caller receives a pointer to the runtime's own buffer. That buffer belongs to the method and is
183-
reused by the next execution, so read or copy the result before running again. This is the shape
184-
every Python caller wants, and it is what the exporters in this repository use, for example
176+
program reserves its own input buffer on the device, and the runtime refuses to copy the caller's
177+
tensor into it. The call fails with error `0x10` (`NotSupported`), and the log says
178+
`Error copying tensor data at input 0`.
179+
180+
**Outputs have two valid shapes.**
181+
182+
Leave outputs planned, which is the default, in most cases. The planned buffer is allocated on the
183+
device because `enable_non_cpu_memory_planning` is on, and `skip_d2h_for_method_outputs` means
184+
nothing copies it back, so the result stays on the device. That buffer belongs to the method and is
185+
reused by the next execution. A C++ caller gets a pointer to it, so read or copy the result before
186+
running again. From Python, `execute()` returns a copy on the GPU unless you pass
187+
`clone_outputs=False`. This is what the exporters in this repository use, for example
185188
`examples/models/gemma4_31b/export.py`.
186189

187-
Set `alloc_graph_output=False` only when the caller supplies the output memory itself, which today
188-
means a C++ caller handing the runtime a device tensor through `Module::set_output`. The Python
189-
bindings allocate host memory for an unplanned output, so a device resident method exported this
190-
way cannot run from Python.
190+
Set `alloc_graph_output=False` when the caller should own the output memory instead. A C++ caller
191+
hands the runtime a device tensor through `Module::set_output`. The Python runtime allocates a new
192+
device buffer for each call and returns it without a copy.
191193

192194
**Per-method selection is on the outer config, not on the flags.** Pass a dict of
193195
`PropagateDeviceConfig` keyed by method name:

‎docs/source/getting-started.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ pip install executorch torch \
3737
| NVIDIA GPU, CUDA 13.2 | `cu132` |
3838
| NVIDIA GPU, CUDA 13.4 | `cu134` |
3939

40-
The CUDA packages are built for Linux, on x86_64 and ARM64. Use the `cpu`
41-
variant on macOS and on Windows. If your CUDA version is not in the table,
40+
The CUDA packages are built for Linux, on x86_64 and ARM64, and for Windows on
41+
x86_64. On Windows they run CUDA programs from C++, and programs for the CUDA
42+
backend are exported on Linux (see [CUDA on Windows](using-executorch-cpp.md#cuda-on-windows)).
43+
Use the `cpu` variant on macOS. If your CUDA version is not in the table,
4244
choose the closest lower one with the same major version, because CUDA works
4345
across minor versions but not across major ones. There is no package for CUDA 12,
4446
so on CUDA 12 use the `cpu` variant or build from source. On CUDA 13.0 or 13.1 no
@@ -54,8 +56,7 @@ To get a change that has landed on the `main` branch but is not in a release
5456
yet, use a nightly build. These are rebuilt every day. Put `nightly/` in front
5557
of the variant name, for example `nightly/cu132`, and add `--pre` to the
5658
command, otherwise pip skips development versions. Nightly builds cover the same
57-
variants. CUDA 13.4 is the newest, and until the next release it is in nightly
58-
builds only.
59+
variants.
5960

6061
The second index is needed because a bare `--index-url` replaces PyPI instead of
6162
adding to it, and some dependencies are published only on PyPI.

‎exir/passes/propagate_device_config.py‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,32 @@
2727
class PropagateDeviceConfig:
2828
"""Controls whether the runtime copies method inputs and outputs across the device boundary.
2929
30-
Skipping a copy also means the runtime must not reserve its own buffer for the tensor, or it
31-
would fill that buffer from the caller's memory and the copy would come back. Memory planning
32-
allocates graph inputs and outputs by default, so a program using either skip below needs::
30+
Skipping the input copy also means the runtime must not reserve its own buffer for the input.
31+
Memory planning allocates graph inputs by default, so a program that skips the input copy
32+
needs::
3333
3434
ExecutorchBackendConfig(
3535
propagate_device_config=PropagateDeviceConfig(
3636
skip_h2d_for_method_inputs=True,
3737
skip_d2h_for_method_outputs=True,
3838
),
3939
enable_non_cpu_memory_planning=True,
40-
memory_planning_pass=MemoryPlanningPass(
41-
alloc_graph_input=False, alloc_graph_output=False
42-
),
40+
memory_planning_pass=MemoryPlanningPass(alloc_graph_input=False),
4341
)
4442
4543
Without ``alloc_graph_input=False`` the runtime reserves device memory for an input the caller
46-
already owns, then copies into it with a host memcpy, which is undefined for device memory.
44+
already owns and tries to copy into it, which fails with ``NotSupported`` for device memory.
45+
Outputs can stay planned: they then live in the method's device memory, which the next run
46+
reuses. Pass ``alloc_graph_output=False`` too when the caller should own the output memory.
4747
"""
4848

4949
# When True, method-level input tensors that feed directly into a device
5050
# delegate are NOT wrapped with _h2d_copy. The user must provide tensors
5151
# already on the target device. Useful for pipelines where inputs are
5252
# pre-staged on GPU.
5353
#
54-
# Pair with MemoryPlanningPass(alloc_graph_input=False), or the runtime reserves its own buffer
55-
# for the input and copies the caller's memory into it, which is what this exists to avoid.
54+
# Pair with MemoryPlanningPass(alloc_graph_input=False), or the runtime reserves its own device
55+
# buffer for the input and refuses to copy the caller's memory into it.
5656
# A dict can be used to set per-method values, keyed by method name.
5757
skip_h2d_for_method_inputs: Union[bool, Dict[str, bool]] = False
5858

@@ -61,7 +61,8 @@ class PropagateDeviceConfig:
6161
# Useful for cross-method GPU pipelines where the next method consumes
6262
# GPU tensors directly.
6363
#
64-
# Pair with MemoryPlanningPass(alloc_graph_output=False) for the same reason as the input flag.
64+
# Outputs can stay planned, in the method's device memory. Pair with
65+
# MemoryPlanningPass(alloc_graph_output=False) only when the caller should own that memory.
6566
# A dict can be used to set per-method values, keyed by method name.
6667
skip_d2h_for_method_outputs: Union[bool, Dict[str, bool]] = False
6768

0 commit comments

Comments
 (0)