Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions .ci/scripts/wheel/cuda_arch_list.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env bash
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

# GPU architectures to compile device code for, chosen per release row rather than detected
# from the build machine.
#
# Without this, the build compiles for whichever GPU the builder happens to have. The wheel
# then installs on every machine the row claims and fails when a model runs on a different
# generation. Detection is the right default for a local build and the wrong one for a
# published artifact.
#
# The value is published as TORCH_CUDA_ARCH_LIST rather than CMAKE_CUDA_ARCHITECTURES.
# PyTorch's own CMake explicitly rejects the latter and overrides it with OFF, so setting it
# alone silently reduces the build to a single detected architecture.

# Data center and desktop parts on the CUDA 13 trains: Ampere, Hopper, Blackwell data center,
# and Blackwell desktop.
_cuda_arch_x86_64_cu130="8.0 9.0 10.0 12.0"
_cuda_arch_x86_64_cu132="${_cuda_arch_x86_64_cu130}"

# Server-class ARM plus the Jetson modules whose CUDA train matches: Hopper for Grace-Hopper,
# Blackwell data center for GB200, and Thor.
_cuda_arch_aarch64_cu130="9.0 10.0 11.0"
_cuda_arch_aarch64_cu132="${_cuda_arch_aarch64_cu130}"

# The older CUDA train, where Orin is the target.
_cuda_arch_aarch64_cu126="8.7"
_cuda_arch_x86_64_cu126="8.0 9.0"

# A CUDA train with no architecture list would otherwise leave the build detecting the
# builder's GPU, which is the failure this file exists to prevent. Adding a train to the release
# matrix without adding its architectures should fail loudly.
_executorch_unknown_train() {
echo "cuda_arch_list.sh: no GPU architecture list for CUDA train '$1' on $(uname -m)." >&2
echo "Add one before building this row, or the wheel ships device code for one GPU only." >&2
return 64
}

# The architectures for the current row, space separated in the dotted form PyTorch expects.
# Empty when the row is unknown, which leaves the build detecting as before.
executorch_cuda_arch_list() {
local machine
machine="$(uname -m)"
# The wheel build exports the row's CUDA train as CU_VERSION. DESIRED_CUDA is the name of
# the matrix field, not of the variable, so reading only that one leaves every row falling
# back to detecting the builder's GPU.
local train="${CU_VERSION:-${DESIRED_CUDA:-}}"
# A CPU row names no CUDA train and needs no architectures, so it is not an error.
case "${train}" in
"" | cpu | CPU | none | NONE) return 0 ;;
esac
# The value arrives as cu130; some callers pass 13.0 instead.
train="${train#cu}"
train="${train//./}"

case "${machine}" in
aarch64 | arm64)
case "${train}" in
126) printf '%s' "${_cuda_arch_aarch64_cu126}" ;;
130) printf '%s' "${_cuda_arch_aarch64_cu130}" ;;
132) printf '%s' "${_cuda_arch_aarch64_cu132}" ;;
*) _executorch_unknown_train "${train}" ;;
esac
;;
x86_64)
case "${train}" in
126) printf '%s' "${_cuda_arch_x86_64_cu126}" ;;
130) printf '%s' "${_cuda_arch_x86_64_cu130}" ;;
132) printf '%s' "${_cuda_arch_x86_64_cu132}" ;;
*) _executorch_unknown_train "${train}" ;;
esac
;;
*) _executorch_unknown_train "${train}" ;;
esac
}

# The same architectures in CMake's own form, for targets outside PyTorch's CMake.
executorch_cuda_cmake_arch_list() {
local dotted
dotted="$(executorch_cuda_arch_list)"
if [ -z "${dotted}" ]; then
return 0
fi
local out="" entry
for entry in ${dotted}; do
entry="${entry//./}"
out="${out:+${out};}${entry}-real"
done
printf '%s' "${out}"
}
15 changes: 15 additions & 0 deletions .ci/scripts/wheel/envvar_linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,18 @@
# any variables so that subprocesses will see them.

source "${GITHUB_WORKSPACE}/${REPOSITORY}/.ci/scripts/wheel/envvar_base.sh"

# Compile device code for the GPU architectures this release row claims, rather than for
# whichever GPU the builder happens to have. A wheel built with detection alone installs on
# every machine the row covers and then fails when a model runs on a different generation.
source "${GITHUB_WORKSPACE}/${REPOSITORY}/.ci/scripts/wheel/cuda_arch_list.sh"
_executorch_cuda_arch="$(executorch_cuda_arch_list)"
if [ -n "${_executorch_cuda_arch}" ]; then
# PyTorch's CMake rejects CMAKE_CUDA_ARCHITECTURES and overrides it with OFF, which leaves
# the build compiling for one detected architecture, so the list has to go through the
# variable PyTorch reads. Both are set: targets that go through PyTorch's CMake honour the
# first, and any that do not honour the second.
export TORCH_CUDA_ARCH_LIST="${_executorch_cuda_arch}"
export CMAKE_ARGS="${CMAKE_ARGS} -DCMAKE_CUDA_ARCHITECTURES=$(executorch_cuda_cmake_arch_list)"
echo "CUDA architectures for this row: ${_executorch_cuda_arch}"
fi
7 changes: 6 additions & 1 deletion .github/workflows/build-wheels-aarch64-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ jobs:
os: linux-aarch64
test-infra-repository: pytorch/test-infra
test-infra-ref: main
with-cuda: disabled
# CUDA enabled so the accelerator rows are built and published. The generator
# emits one cell per supported CUDA train, and each cell carries its own local
# version label so the CPU and accelerator artifacts stay distinguishable. A plain
# `pip install executorch` continues to resolve the CPU wheel from the default
# index; an accelerator wheel requires pointing at the matching index.
with-cuda: enable
with-rocm: disabled
python-versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'

Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/build-wheels-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ jobs:
os: linux
test-infra-repository: pytorch/test-infra
test-infra-ref: main
with-cuda: disabled
# CUDA enabled so the accelerator rows are built and published. The generator
# emits one cell per supported CUDA train, and each cell carries its own local
# version label so the CPU and accelerator artifacts stay distinguishable. A plain
# `pip install executorch` continues to resolve the CPU wheel from the default
# index; an accelerator wheel requires pointing at the matching index.
with-cuda: enable
with-rocm: disabled
python-versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'

Expand Down
55 changes: 54 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,59 @@ def _is_minimal_build() -> bool:
return _is_env_flag_enabled("EXECUTORCH_BUILD_MINIMAL")


def _cuda_train() -> str:
"""The CUDA train this wheel is being built for, as a bare number like "130".

Read from the wheel build environment rather than detected from an installed compiler.
A CPU wheel built on a machine that happens to have a CUDA toolkit must not declare CUDA
dependencies, and detection cannot tell the two cases apart.

The wheel build exports this as CU_VERSION; DESIRED_CUDA is the matrix field name and is
accepted only so local invocations keep working.
"""
train = (
(os.environ.get("CU_VERSION") or os.environ.get("DESIRED_CUDA") or "")
.strip()
.lower()
)
if not train:
return ""
train = train.removeprefix("cu").replace(".", "")
return train if train.isdigit() else ""


# The published project names for the CUDA runtime components this wheel links but does not
# bundle, keyed by CUDA major. These are not derivable from a suffix rule: the CUDA 12 wheels
# carry a "-cu12" suffix while the CUDA 13 wheels are published under unsuffixed names, and the
# suffixed CUDA 13 projects are placeholders that ship no binaries. A train with no entry gets
# no declared dependencies, which is safer than requesting a name that may not exist.
_CUDA_RUNTIME_PACKAGES = {
"12": ("nvidia-cuda-runtime-cu12", "nvidia-curand-cu12", "nvidia-cublas-cu12"),
"13": ("nvidia-cuda-runtime", "nvidia-curand", "nvidia-cublas"),
}


def _cuda_dependencies() -> List[str]:
"""Runtime libraries a CUDA wheel needs but does not bundle.

Empty for a CPU wheel, and empty for a build whose CUDA train is unknown or unmapped, so
the CPU rows are unaffected.
"""
train = _cuda_train()
if not train:
return []
# The CUDA train alone decides this. An earlier version also required
# EXECUTORCH_BUILD_CUDA, but that reaches the build as a CMake argument rather than an
# environment variable, so the condition was never true and a CUDA wheel shipped with no
# declared CUDA dependencies at all.
packages = _CUDA_RUNTIME_PACKAGES.get(train[:2])
if not packages:
return []
# Only what the delegate and its shim actually link. A shorter list keeps a CUDA install
# from pulling in libraries nothing in this wheel references.
return [f"{name}; platform_system == 'Linux'" for name in packages]


def _minimal_cmake_flags() -> List[str]:
return [
"-DEXECUTORCH_BUILD_COREML=OFF",
Expand Down Expand Up @@ -1221,7 +1274,7 @@ def run(self): # noqa C901
setup_kwargs["packages"] = _minimal_packages()
setup_kwargs["install_requires"] = _minimal_dependencies()
else:
setup_kwargs["install_requires"] = _base_dependencies()
setup_kwargs["install_requires"] = _base_dependencies() + _cuda_dependencies()


setup(
Expand Down
Loading