Files
TheRock/external-builds/pytorch/build_prod_wheels.py
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1476 lines
55 KiB
Python
Executable File

#!/usr/bin/env python
# Copyright Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
r"""Builds production PyTorch wheels based on the rocm wheels.
This script is designed to be used from CI but should be serviceable for real
users. It is not optimized for providing a development experience for PyTorch.
Under Linux, it is standard to run this under an appropriate manylinux container
for producing portable binaries. On Windows, it should run in an environment
with suitable VC redistributables to use the rocm-sdk.
In both cases, it should be run from a venv.
## Building interactively
A full build consists of multiple steps (can be mixed/matched for multi version
builds, etc):
1. Checkout repositories:
The following commands check out custom patched versions into this directory,
which the script will use by default if they exist. Otherwise, checkout your
own and specify `--root-checkout-dir` or the more specific `--pytorch-dir`,
`--pytorch-audio-dir`, and `--pytorch-vision-dir` options during the build step.
```
# On Linux, using default paths (nested under this folder):
# Note that triton must be checked out after pytorch as it depends on pins
# in the former.
python pytorch_torch_repo.py checkout
python pytorch_audio_repo.py checkout
python pytorch_apex_repo.py checkout
python pytorch_vision_repo.py checkout
python pytorch_triton_repo.py checkout
# On Windows, using shorter paths to avoid compile command length limits:
python pytorch_torch_repo.py checkout --checkout-dir C:/b/pytorch
python pytorch_audio_repo.py checkout --checkout-dir C:/b/audio
python pytorch_vision_repo.py checkout --checkout-dir C:/b/vision
```
2. Install rocm wheels:
You must have the `rocm[libraries,devel]` packages installed. The `install-rocm`
command gives a one-stop to fetch the latest nightlies from the CI or elsewhere.
Below we are using nightly rocm-sdk packages from the CI bucket. See `RELEASES.md`
for further options. Specific versions can be specified via `--rocm-sdk-version`
and `--no-pre` (to disable searching for pre-release candidates). The installed
version will be printed and subsequently will be embedded into torch builds as
a dependency. Such an arrangement is a head-on-head build (i.e. torch head on top
of ROCm head). Other arrangements are possible by passing pinned versions, official
repositories, etc.
You can also install in the same invocation as build by passing `--install-rocm`
to the build sub-command (useful for docker invocations).
```
# For therock-nightly-python
build_prod_wheels.py \
install-rocm \
--index-url https://rocm.nightlies.amd.com/v2/gfx110X-all/
# For therock-dev-python (unstable but useful for testing outside of prod)
build_prod_wheels.py \
install-rocm \
--index-url https://rocm.devreleases.amd.com/v2/gfx110X-all/
```
3. Build torch, torchaudio and torchvision for one or more gfx architectures.
Target architectures are resolved in priority order from `--pytorch-rocm-arch`
(comma-separated), the `PYTORCH_ROCM_ARCH` environment variable, and finally
`rocm-sdk targets` from the installed rocm-sdk-core. Passing the flag or env
var explicitly is preferred; see TODO on get_rocm_sdk_targets.
```
# On Linux, using default paths for each repository:
python build_prod_wheels.py build \
--pytorch-rocm-arch gfx942 \
--output-dir $HOME/tmp/pyout
# On Windows, using shorter custom paths:
python build_prod_wheels.py build ^
--pytorch-rocm-arch gfx1201 ^
--output-dir %HOME%/tmp/pyout ^
--pytorch-dir C:/b/pytorch ^
--pytorch-audio-dir C:/b/audio ^
--pytorch-vision-dir C:/b/vision
```
4. Compiler caching (optional):
```
# Use ccache:
python build_prod_wheels.py build --use-ccache --output-dir ...
# Use sccache (caches host + HIP device code via HIP_CLANG_LAUNCHER):
python build_prod_wheels.py build --use-sccache --output-dir ...
# Use sccache for host C/C++ only (no HIP device code caching):
python build_prod_wheels.py build --use-sccache --sccache-no-wrap --output-dir ...
```
``--use-ccache`` and ``--use-sccache`` are mutually exclusive.
``--use-sccache`` sets the CMake C/C++ compiler launchers and, on Linux, the
``HIP_CLANG_LAUNCHER`` environment variable so that ``hipcc`` routes its clang
invocations — including the ``-x hip --offload-arch`` device passes — through
sccache. The real clang binary is left in place, so compiler-detection probes
work normally. ``--sccache-no-wrap`` skips ``HIP_CLANG_LAUNCHER`` (host C/C++
caching only), for toolchains whose hipcc predates HIP_CLANG_LAUNCHER support
(ROCm < 7.13). See ``build_tools/setup_sccache_rocm.py``.
## Building Linux portable wheels
On Linux, production wheels are typically built in a manylinux container and must have
some custom post-processing to ensure that system deps are bundled. This can be done
via the `build_tools/linux_portable_build.py` utility in the root of the repo.
Example (note that the use of `linux_portable_build.py` can be replaced with custom
docker invocations, but we keep this tool up to date with respect to mounts and image
versions):
```
./build_tools/linux_portable_build.py --docker=podman --exec -- \
/usr/bin/env CCACHE_DIR=/therock/output/ccache \
/opt/python/cp312-cp312/bin/python \
/therock/src/external-builds/pytorch/build_prod_wheels.py \
build \
--install-rocm \
--pip-cache-dir /therock/output/pip_cache \
--index-url https://rocm.nightlies.amd.com/v2/gfx110X-all/ \
--clean \
--output-dir /therock/output/cp312/wheels
```
TODO: Need to add an option to post-process wheels, set the manylinux tag, and
inline system deps into the audio and vision wheels as needed.
"""
import argparse
import json
import os
from pathlib import Path
from packaging.version import parse
import platform
import shutil
import shlex
import subprocess
import sys
import tarfile
import tempfile
import textwrap
import urllib.request
script_dir = Path(__file__).resolve().parent
is_windows = platform.system() == "Windows"
# LLVM download URL for triton-windows
LLVM_BASE_URL = "https://oaitriton.blob.core.windows.net/public/llvm-builds"
# List of library preloads for Linux to generate into _rocm_init.py
LINUX_LIBRARY_PRELOADS = [
"amd_comgr",
"amdhip64",
"rocprofiler-sdk", # Linux only: needed by torch since kineto uses rocprofiler-sdk.
"rocprofiler-sdk-roctx", # Linux only for the moment.
# TODO: Remove roctracer64 and roctx64 once fully switched to rocprofiler-sdk.
"roctracer64", # Linux only for the moment.
"roctx64", # Linux only for the moment.
"hiprtc",
"hipblas",
"hipfft",
"hiprand",
"hipsparse",
"hipsparselt",
"hipsolver",
"rccl", # Linux only for the moment.
"hipblaslt",
"miopen",
"hipdnn",
"rocm_sysdeps_liblzma",
"rocm-openblas",
"rocm_smi64",
]
# List of library preloads for Windows to generate into _rocm_init.py
WINDOWS_LIBRARY_PRELOADS = [
"amd_comgr",
"amdhip64",
"hiprtc",
"hipblas",
"hipfft",
"hiprand",
"hipsparse",
"hipsparselt",
"hipsolver",
"hipblaslt",
"miopen",
"hipdnn",
"rocm-openblas",
]
def run_command(args: list[str | Path], cwd: Path, env: dict[str, str] | None = None):
args = [str(arg) for arg in args]
full_env = dict(os.environ)
print(f"++ Exec [{cwd}]$ {shlex.join(args)}")
if env:
print(f":: Env:")
for k, v in env.items():
print(f" {k}={v}")
full_env.update(env)
subprocess.check_call(args, cwd=str(cwd), env=full_env)
def capture(args: list[str | Path], cwd: Path) -> str:
args = [str(arg) for arg in args]
print(f"++ Capture [{cwd}]$ {shlex.join(args)}")
try:
return subprocess.check_output(
args, cwd=str(cwd), stderr=subprocess.STDOUT, text=True
).strip()
except subprocess.CalledProcessError as e:
print(f"Error capturing output: {e}")
print(f"Output from the failed command:\n{e.output}")
return ""
def get_rocm_sdk_version() -> str:
return capture(
[sys.executable, "-m", "rocm_sdk", "version"], cwd=Path.cwd()
).strip()
# TODO(#4687): Remove this fallback once every caller passes --pytorch-rocm-arch
# (or PYTORCH_ROCM_ARCH) explicitly. Reading dist_amdgpu_targets from the
# installed rocm-sdk-core misreports targets in two known cases:
# 1. Multi-arch kpack-split builds share one superset rocm-sdk-core, so every
# per-family job would compile torch for every arch in the superset.
# 2. Prebuilt-reuse flows where the installed rocm-sdk-core's targets do not
# match the build intent (e.g. issue #4687).
# This fallback is kept for legacy CI and release workflows that have not yet
# been updated to plumb --pytorch-rocm-arch through from the caller.
def get_rocm_sdk_targets() -> str:
# Run `rocm-sdk targets` to get the default architecture
targets = capture([sys.executable, "-m", "rocm_sdk", "targets"], cwd=Path.cwd())
if not targets:
print("Warning: rocm-sdk targets returned empty or failed")
return ""
# Convert space-separated targets to comma-separated for PYTORCH_ROCM_ARCH
return targets.replace(" ", ",")
def get_installed_package_version(dist_package_name: str) -> str:
lines = capture(
[sys.executable, "-m", "pip", "show", dist_package_name], cwd=Path.cwd()
).splitlines()
if not lines:
raise ValueError(f"Did not find installed package '{dist_package_name}'")
prefix = "Version: "
for line in lines:
if line.startswith(prefix):
return line[len(prefix) :]
joined_lines = "\n".join(lines)
raise ValueError(
f"Did not find Version for installed package '{dist_package_name}' in output:\n{joined_lines}"
)
def get_version_suffix_for_installed_rocm_package() -> str:
rocm_version = get_installed_package_version("rocm")
print(f"Computing version suffix for installed rocm package: {rocm_version}")
# Compute a version suffix to be used as a local version identifier:
# https://packaging.python.org/en/latest/specifications/version-specifiers/#local-version-identifiers
# This logic is copied from build_tools/github_actions/determine_version.py.
parsed_version = parse(rocm_version)
base_name = "devrocm" if "dev" in rocm_version else "rocm"
version_suffix = f"+{base_name}{str(parsed_version).replace('+','-')}"
print(f"Version suffix is: {version_suffix}")
return version_suffix
def get_triton_windows_llvm_hash(triton_dir: Path) -> str:
"""Read the LLVM hash from triton-windows cmake/llvm-hash.txt."""
hash_file = triton_dir / "cmake" / "llvm-hash.txt"
if not hash_file.exists():
raise RuntimeError(f"LLVM hash file not found: {hash_file}")
return hash_file.read_text().strip()
def download_llvm_for_triton_windows(triton_dir: Path) -> Path:
"""Download and extract pre-built LLVM binaries for triton-windows.
triton-windows requires a specific LLVM version that matches the hash
in cmake/llvm-hash.txt. Pre-built binaries are hosted at oaitriton.blob.core.windows.net.
"""
full_hash = get_triton_windows_llvm_hash(triton_dir)
short_hash = full_hash[:8]
llvm_dir = triton_dir.parent / f"llvm-{short_hash}-windows-x64"
llvm_hash_marker = llvm_dir / ".llvm-hash"
if llvm_hash_marker.exists():
installed_hash = llvm_hash_marker.read_text().strip()
if installed_hash == full_hash:
print(f"LLVM already downloaded: {llvm_dir}")
return llvm_dir
if llvm_dir.exists():
shutil.rmtree(llvm_dir)
filename = f"llvm-{short_hash}-windows-x64.tar.gz"
download_url = f"{LLVM_BASE_URL}/{filename}"
print(f"Downloading LLVM for triton-windows...")
print(f" Hash: {short_hash}")
print(f" URL: {download_url}")
with tempfile.TemporaryDirectory() as temp_dir:
download_path = Path(temp_dir) / filename
print(" Downloading (this may take a few minutes, ~500MB)...")
try:
urllib.request.urlretrieve(download_url, download_path)
except Exception as e:
raise RuntimeError(
f"Failed to download LLVM from {download_url}: {e}\n"
"You may need to download manually and extract to "
f"{llvm_dir}"
)
print(" Extracting...")
with tarfile.open(download_path, "r:gz") as tar:
tar.extractall(triton_dir.parent, filter="data")
if not llvm_dir.exists():
raise RuntimeError(f"Extracted LLVM directory not found: {llvm_dir}")
llvm_hash_marker.write_text(full_hash)
print(f" LLVM downloaded to: {llvm_dir}")
return llvm_dir
def get_rocm_path(path_name: str) -> Path:
return Path(
capture(
[sys.executable, "-m", "rocm_sdk", "path", f"--{path_name}"], cwd=Path.cwd()
).strip()
)
def get_rocm_init_contents(args: argparse.Namespace):
"""Gets the contents of the _rocm_init.py file to add to the build."""
sdk_version = get_rocm_sdk_version()
library_preloads = (
WINDOWS_LIBRARY_PRELOADS if is_windows else LINUX_LIBRARY_PRELOADS
)
library_preloads_formatted = ", ".join(f"'{s}'" for s in library_preloads)
return textwrap.dedent(
f"""
def initialize():
import rocm_sdk
rocm_sdk.initialize_process(
preload_shortnames=[{library_preloads_formatted}],
check_version='{sdk_version}')
"""
)
def remove_dir_if_exists(dir: Path):
if dir.exists():
print(f"++ Removing {dir}")
shutil.rmtree(dir)
def find_built_wheel(dist_dir: Path, dist_package: str) -> Path:
dist_package = dist_package.replace("-", "_")
glob = f"{dist_package}-*.whl"
all_wheels = list(dist_dir.glob(glob))
if not all_wheels:
raise RuntimeError(f"No wheels matching '{glob}' found in {dist_dir}")
if len(all_wheels) != 1:
raise RuntimeError(f"Found multiple wheels matching '{glob}' in {dist_dir}")
return all_wheels[0]
def copy_to_output(args: argparse.Namespace, src_file: Path):
output_dir: Path = args.output_dir
print(f"++ Copy {src_file} -> {output_dir}")
output_dir.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_file, output_dir)
def directory_if_exists(dir: Path) -> Path | None:
if dir.exists():
return dir
else:
return None
def apply_root_checkout_dir(args: argparse.Namespace) -> None:
"""Default per-project source dirs from --root-checkout-dir."""
root_checkout_dir: Path | None = args.root_checkout_dir
if not root_checkout_dir:
return
if args.pytorch_dir is None:
args.pytorch_dir = directory_if_exists(root_checkout_dir / "pytorch")
if args.pytorch_audio_dir is None:
args.pytorch_audio_dir = directory_if_exists(
root_checkout_dir / "pytorch_audio"
)
if args.pytorch_vision_dir is None:
args.pytorch_vision_dir = directory_if_exists(
root_checkout_dir / "pytorch_vision"
)
if args.triton_dir is None:
args.triton_dir = directory_if_exists(root_checkout_dir / "triton")
if args.apex_dir is None:
args.apex_dir = directory_if_exists(root_checkout_dir / "apex")
def do_install_rocm(args: argparse.Namespace):
# Because the rocm package caches current GPU selection and such, we
# always purge it to ensure a clean rebuild.
#
# This can fail in environments where the pip cache is disabled or
# unwritable (e.g. manylinux containers), which is fine — if there's no
# cache, there's nothing stale to purge.
cache_dir_args = (
["--cache-dir", str(args.pip_cache_dir)] if args.pip_cache_dir else []
)
try:
run_command(
[sys.executable, "-m", "pip", "cache", "remove", "rocm"] + cache_dir_args,
cwd=Path.cwd(),
)
except subprocess.CalledProcessError:
print("Warning: pip cache remove failed (cache may be disabled), continuing")
# Do the main pip install.
pip_args = [
sys.executable,
"-m",
"pip",
"install",
"--force-reinstall",
]
if args.pre:
pip_args.extend(["--pre"])
if args.index_url:
pip_args.extend(["--index-url", args.index_url])
if args.find_links:
pip_args.extend(["--find-links", args.find_links])
if args.pip_cache_dir:
pip_args.extend(["--cache-dir", str(args.pip_cache_dir)])
rocm_sdk_version = args.rocm_sdk_version if args.rocm_sdk_version else ""
extras = "libraries,devel"
if args.rocm_extras:
extras += f",{args.rocm_extras}"
pip_args.extend([f"rocm[{extras}]{rocm_sdk_version}"])
run_command(pip_args, cwd=Path.cwd())
print(f"Installed version: {get_rocm_sdk_version()}")
def add_env_compiler_flags(env: dict[str, str], flagname: str, *compiler_flags: str):
current = env.get(flagname, "")
append = ""
for compiler_flag in compiler_flags:
append += f"{compiler_flag} "
env[flagname] = f"{current}{append}"
print(f"-- Appended {flagname}+={append}")
def find_dir_containing(file_name: str, *possible_paths: Path) -> Path:
for path in possible_paths:
if (path / file_name).exists():
return path
raise ValueError(f"No directory contains {file_name}: {possible_paths}")
def _setup_common_build_env(
cmake_prefix: Path,
rocm_dir: Path,
pytorch_rocm_arch: str,
triton_dir: Path | None,
is_windows: bool,
) -> dict[str, str]:
"""Construct the common environment dict shared by all wheel builds."""
env: dict[str, str] = {
"PYTHONUTF8": "1", # Some build files use utf8 characters, force IO encoding
"CMAKE_PREFIX_PATH": str(cmake_prefix),
"ROCM_HOME": str(rocm_dir),
"ROCM_PATH": str(rocm_dir),
"PYTORCH_ROCM_ARCH": pytorch_rocm_arch,
"USE_KINETO": os.environ.get("USE_KINETO", "ON" if not is_windows else "OFF"),
}
env["USE_GLOO"] = "ON"
# At checkout, we compute some additional env vars that influence the way that
# the wheel is named/versioned.
if triton_dir:
triton_env_file = triton_dir / "build_env.json"
if triton_env_file.exists():
with open(triton_env_file, "r") as f:
addl_triton_env = json.load(f)
print(f"-- Additional triton build env vars: {addl_triton_env}")
env.update(addl_triton_env)
# With `CMAKE_PREFIX_PATH` set, `find_package(LLVM)` (called in
# `MLIRConfig.cmake` shipped as part of the LLVM bundled with
# trition) may pick up TheRock's LLVM instead of triton's.
# Here, `CMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH` is set
# and passed via `TRITON_APPEND_CMAKE_ARGS` to avoid this.
# See also https://github.com/ROCm/TheRock/issues/1999.
env["TRITON_APPEND_CMAKE_ARGS"] = (
"-DCMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH=FALSE"
)
if is_windows:
llvm_dir = rocm_dir / "lib" / "llvm" / "bin"
env.update(
{
"HIP_CLANG_PATH": str(llvm_dir.resolve().as_posix()),
"CC": str((llvm_dir / "clang-cl.exe").resolve()),
"CXX": str((llvm_dir / "clang-cl.exe").resolve()),
}
)
else:
env.update(
{
# Workaround GCC12 compiler flags.
"CXXFLAGS": " -Wno-error=maybe-uninitialized -Wno-error=uninitialized -Wno-error=restrict ",
"CPPFLAGS": " -Wno-error=maybe-uninitialized -Wno-error=uninitialized -Wno-error=restrict ",
}
)
# Workaround missing devicelib bitcode
# TODO: When "ROCM_PATH" and/or "ROCM_HOME" is set in the environment, the
# clang frontend ignores its default heuristics and (depending on version)
# finds the wrong path to the device library. This is bad/annoying. But
# the PyTorch build shouldn't even need these to be set. Unfortunately, it
# has been hardcoded for a long time. So we use a clang env var to force
# a specific device lib path to workaround the hack to get pytorch to build.
# This may or may not only affect the Python wheels with their own quirks
# on directory layout.
# Obviously, this should be completely burned with fire once the root causes
# are eliminted.
hip_device_lib_path = rocm_dir / "lib" / "llvm" / "amdgcn" / "bitcode"
if not hip_device_lib_path.exists():
print(
"WARNING: Default location of device libs not found. Relying on "
"clang heuristics which are known to be buggy in this configuration"
)
else:
env["HIP_DEVICE_LIB_PATH"] = str(hip_device_lib_path)
# OpenBLAS path setup
host_math_path = rocm_dir / "lib" / "host-math"
if not host_math_path.exists():
print(
"WARNING: Default location of host-math not found. "
"Will not build with OpenBLAS support."
)
else:
env["BLAS"] = "OpenBLAS"
env["OpenBLAS_HOME"] = str(host_math_path)
env["OpenBLAS_LIB_NAME"] = "rocm-openblas"
return env
def _do_build_wheels_core(
args: argparse.Namespace,
env: dict[str, str],
triton_dir: Path | None,
pytorch_dir: Path | None,
pytorch_audio_dir: Path | None,
pytorch_vision_dir: Path | None,
apex_dir: Path | None,
) -> None:
"""Execute all wheel builds (triton, pytorch, audio, vision, apex)."""
# Build triton.
triton_requirement = None
if args.build_triton or (args.build_triton is None and triton_dir):
assert triton_dir, "Must specify --triton-dir if --build-triton"
triton_requirement = do_build_triton(args, triton_dir, dict(env))
else:
print("--- Not building triton (no --triton-dir)")
# Build pytorch.
if pytorch_dir:
do_build_pytorch(
args, pytorch_dir, dict(env), triton_requirement=triton_requirement
)
else:
print("--- Not building pytorch (no --pytorch-dir)")
# Build pytorch audio.
if args.build_pytorch_audio or (
args.build_pytorch_audio is None and pytorch_audio_dir
):
assert (
pytorch_audio_dir
), "Must specify --pytorch-audio-dir if --build-pytorch-audio"
do_build_pytorch_audio(args, pytorch_audio_dir, dict(env))
else:
print("--- Not build pytorch-audio (no --pytorch-audio-dir)")
# Build pytorch vision.
if args.build_pytorch_vision or (
args.build_pytorch_vision is None and pytorch_vision_dir
):
assert (
pytorch_vision_dir
), "Must specify --pytorch-vision-dir if --build-pytorch-vision"
do_build_pytorch_vision(args, pytorch_vision_dir, dict(env))
else:
print("--- Not build pytorch-vision (no --pytorch-vision-dir)")
# Build apex.
if args.build_apex or (args.build_apex is None and apex_dir):
assert apex_dir, "Must specify --apex-dir if --build-apex"
do_build_apex(args, apex_dir, dict(env))
else:
print("--- Not build apex (no --apex-dir)")
print("--- Builds all completed")
def do_build(args: argparse.Namespace):
if args.install_rocm:
do_install_rocm(args)
if not args.version_suffix:
args.version_suffix = get_version_suffix_for_installed_rocm_package()
triton_dir: Path | None = args.triton_dir
pytorch_dir: Path | None = args.pytorch_dir
pytorch_audio_dir: Path | None = args.pytorch_audio_dir
pytorch_vision_dir: Path | None = args.pytorch_vision_dir
apex_dir: Path | None = args.apex_dir
rocm_sdk_version = get_rocm_sdk_version()
cmake_prefix = get_rocm_path("cmake")
bin_dir = get_rocm_path("bin")
rocm_dir = get_rocm_path("root")
print(f"rocm version {rocm_sdk_version}:")
print(f" PYTHON VERSION: {sys.version}")
print(f" CMAKE_PREFIX_PATH = {cmake_prefix}")
print(f" BIN = {bin_dir}")
print(f" ROCM_HOME = {rocm_dir}")
system_path = str(bin_dir) + os.path.pathsep + os.environ.get("PATH", "")
print(f" PATH = {system_path}")
# Priority: --pytorch-rocm-arch > PYTORCH_ROCM_ARCH env > `rocm-sdk targets`
# fallback (legacy; see TODO on get_rocm_sdk_targets()).
pytorch_rocm_arch = args.pytorch_rocm_arch or os.environ.get("PYTORCH_ROCM_ARCH")
if pytorch_rocm_arch:
print(f" Using provided PYTORCH_ROCM_ARCH: {pytorch_rocm_arch}")
else:
pytorch_rocm_arch = get_rocm_sdk_targets()
print(
f" Using default PYTORCH_ROCM_ARCH from rocm-sdk targets: {pytorch_rocm_arch}"
)
if not pytorch_rocm_arch:
raise ValueError(
"No --pytorch-rocm-arch provided, PYTORCH_ROCM_ARCH not set, and "
"rocm-sdk targets returned empty. "
"Please specify --pytorch-rocm-arch (e.g., gfx942)."
)
# PyTorch's CMake consumes PYTORCH_ROCM_ARCH as a CMake-style list, so any
# comma-separated input needs to be rewritten with semicolons before
# CMake runs — otherwise the whole string is treated as one arch.
pytorch_rocm_arch = pytorch_rocm_arch.replace(",", ";")
env = _setup_common_build_env(
cmake_prefix, rocm_dir, pytorch_rocm_arch, triton_dir, is_windows
)
if args.use_ccache:
if not shutil.which("ccache"):
raise RuntimeError(
"ccache not found but --use-ccache was specified. "
"Please install ccache before building."
)
print("Building with ccache, clearing stats first")
env["CMAKE_C_COMPILER_LAUNCHER"] = "ccache"
env["CMAKE_CXX_COMPILER_LAUNCHER"] = "ccache"
if is_windows:
# ccache does not support MSVC's /Zi flag. Embedded (/Z7) is needed.
# See: https://github.com/ccache/ccache/issues/1040
env["CMAKE_MSVC_DEBUG_INFORMATION_FORMAT"] = "Embedded"
run_command(["ccache", "--zero-stats"], cwd=tempfile.gettempdir())
elif args.use_sccache:
build_tools_dir = Path(__file__).resolve().parent.parent.parent / "build_tools"
sys.path.insert(0, str(build_tools_dir))
from setup_sccache_rocm import find_sccache, sccache_build_env
sccache_path = find_sccache()
if not sccache_path:
raise RuntimeError(
"sccache not found but --use-sccache was specified.\n"
"Install: https://github.com/mozilla/sccache#installation\n"
"For CI, sccache is pre-installed in the manylinux build image:\n"
" https://github.com/ROCm/TheRock/tree/main/dockerfiles"
)
try:
if args.use_sccache:
# sccache_build_env sets the CMake C/C++ launchers and, unless
# disabled, HIP_CLANG_LAUNCHER so hipcc routes its clang calls
# (incl. the -x hip --offload-arch device passes) through sccache
# without replacing the clang binary. See setup_sccache_rocm.py.
hip_launcher = not args.sccache_no_wrap
env.update(sccache_build_env(sccache_path, hip_launcher=hip_launcher))
if hip_launcher and not is_windows:
print(f"Setting up sccache via HIP_CLANG_LAUNCHER={sccache_path}")
else:
print("Setting up sccache (CMAKE launchers only)...")
try:
run_command(
[str(sccache_path), "--start-server"], cwd=tempfile.gettempdir()
)
except subprocess.CalledProcessError:
pass # Server may already be running
run_command([str(sccache_path), "--zero-stats"], cwd=tempfile.gettempdir())
_do_build_wheels_core(
args,
env,
triton_dir,
pytorch_dir,
pytorch_audio_dir,
pytorch_vision_dir,
apex_dir,
)
finally:
if args.use_sccache:
sccache_stats = capture(
[str(sccache_path), "--show-stats"], cwd=tempfile.gettempdir()
)
print(f"sccache --show-stats output:\n{sccache_stats}")
if args.use_ccache:
ccache_stats_output = capture(
["ccache", "--show-stats"], cwd=tempfile.gettempdir()
)
print(f"ccache --show-stats output:\n{ccache_stats_output}")
def build_triton_windows(args: argparse.Namespace, triton_dir: Path) -> str:
"""Build triton wheel for Windows using triton-windows repository."""
print("Building Triton for Windows (using triton-windows repository)")
llvm_build_dir = download_llvm_for_triton_windows(triton_dir)
# Prepare environment for triton-windows build.
# Note: MSVC environment (vcvars64.bat) must already be set up.
windows_env = dict(os.environ)
windows_env.update(
{
"PYTHONUTF8": "1",
"LLVM_BUILD_DIR": str(llvm_build_dir),
"LLVM_INCLUDE_DIRS": str(llvm_build_dir / "include"),
"LLVM_LIBRARY_DIR": str(llvm_build_dir / "lib"),
"LLVM_SYSPATH": str(llvm_build_dir),
"TRITON_BUILD_PROTON": "OFF",
"TRITON_APPEND_CMAKE_ARGS": "-DCMAKE_FIND_USE_CMAKE_ENVIRONMENT_PATH=FALSE",
# Override package name to "triton" for consistency with Linux
"TRITON_WHEEL_NAME": "triton",
}
)
print("+++ Installing build dependencies:")
run_command(
[sys.executable, "-m", "pip", "install", "build", "wheel"],
cwd=triton_dir,
)
remove_dir_if_exists(triton_dir / "dist")
if args.clean:
remove_dir_if_exists(triton_dir / "build")
print("+++ Building triton:")
run_command(
[sys.executable, "-m", "build", "--wheel"],
cwd=triton_dir,
env=windows_env,
)
# Build produces wheel named "triton" (overridden via TRITON_WHEEL_NAME)
built_wheel = find_built_wheel(triton_dir / "dist", "triton")
print(f"Found built wheel: {built_wheel}")
copy_to_output(args, built_wheel)
wheel_version = built_wheel.stem.split("-")[1]
return f"triton=={wheel_version}"
def build_triton_linux(
args: argparse.Namespace, triton_dir: Path, env: dict[str, str]
) -> str:
"""Build triton wheel for Linux using ROCm/triton repository."""
print("Building Triton for Linux (using ROCm/triton repository)")
version_suffix = env.get("TRITON_WHEEL_VERSION_SUFFIX", "")
# Triton's setup.py constructs the final version string by using
# a few components:
# * Base version: `3.3.1`
# * Version suffix
#
# Version suffix itself consist of from following two parts:
# * git hash suffix:
# * "+git<githash>" for development builds
# * empty string "" for builds made from git release branches
# * Additional version information is passed by using environment variable
# TRITON_WHEEL_VERSION_SUFFIX
# For example:
# env["TRITON_WHEEL_VERSION_SUFFIX"] = "+rocm7.0.0rc20250728"
#
# Version suffix part of the version is allowed to have only a single
# "+"-character. Therefore if there are multiple suffixes,
# they are joined togeher with `-` characters
# instead of `+` characters in Triton's setup.py so that
# there is only a single `+` character after the base version.
#
# For example:
# * PyTorch release/2.7 builds use Triton versions like:
# 3.3.1+rocm7.0.0rc20250728
# * PyTorch nightly builds use Triton versions like:
# 3.4.0+git12345678-rocm7.0.0rc20250728
version_suffix += str(args.version_suffix)
env["TRITON_WHEEL_VERSION_SUFFIX"] = version_suffix
triton_wheel_name = env.get("TRITON_WHEEL_NAME", "triton")
print(f"+++ Uninstall {triton_wheel_name}")
run_command(
[sys.executable, "-m", "pip", "uninstall", triton_wheel_name, "-y"],
cwd=tempfile.gettempdir(),
)
print("+++ Installing triton requirements:")
pip_install_args = []
if args.pip_cache_dir:
pip_install_args.extend(["--cache-dir", args.pip_cache_dir])
run_command(
[
sys.executable,
"-m",
"pip",
"install",
"-r",
triton_dir / "python" / "requirements.txt",
]
+ pip_install_args,
cwd=triton_dir,
)
print("+++ Building triton:")
# In early ~2.9, setup.py moved from the python/ dir to the root. Check both.
triton_python_dir = find_dir_containing(
"setup.py", triton_dir / "python", triton_dir
)
remove_dir_if_exists(triton_python_dir / "dist")
if args.clean:
remove_dir_if_exists(triton_python_dir / "build")
run_command(
[sys.executable, "setup.py", "bdist_wheel"], cwd=triton_python_dir, env=env
)
built_wheel = find_built_wheel(triton_python_dir / "dist", triton_wheel_name)
print(f"Found built wheel: {built_wheel}")
copy_to_output(args, built_wheel)
print("+++ Installing built triton:")
run_command(
[sys.executable, "-m", "pip", "install", built_wheel], cwd=tempfile.gettempdir()
)
installed_triton_version = get_installed_package_version(triton_wheel_name)
return f"{triton_wheel_name}=={installed_triton_version}"
def do_build_triton(
args: argparse.Namespace, triton_dir: Path, env: dict[str, str]
) -> str:
"""Build triton wheel. Dispatches to platform-specific build functions."""
if is_windows:
return build_triton_windows(args, triton_dir)
else:
return build_triton_linux(args, triton_dir, env)
def copy_msvc_libomp_to_torch_lib(pytorch_dir: Path):
# When USE_OPENMP is set (it is by default), torch_cpu.dll depends on OpenMP.
#
# Typically implementations of OpenMP are:
# * Intel OpenMP, `libiomp`, which PyTorch upstream uses
# * MSVC OpenMP, `libomp140`, which we'll use here since we have MSVC already
# * (?) LLVM OpenMP (https://openmp.llvm.org/)?
#
# Torch's CMake build selects which OpenMP to use in `FindOpenMP.cmake`,
# then the relevant .dll files must be copied into the torch/lib/ folder or
# torch will fail to initialize. This feels like something that could be
# handled upstream as part of the centralized setup.py and/or CMake build
# processes, but given the varied scripts and build workflows upstream and
# multiple choices for where to source an implementation, we handle it here.
#
# If we wanted to switch to Intel OpenMP, we could:
# 1. Install Intel OpenMP (and/or MKL?)
# 2. Set CMAKE_INCLUDE_PATH and CMAKE_LIBRARY_PATH (?) so `FindOpenMP.cmake` finds them
# 3. Copy `libiomp5md.dll` to torch/lib
# Then remove the rest of the code from this function.
vc_tools_redist_dir = os.environ.get("VCToolsRedistDir", "")
if not vc_tools_redist_dir:
raise RuntimeError("VCToolsRedistDir not set, can't copy libomp to torch lib")
omp_name = "libomp140.x86_64.dll"
dll_paths = sorted(Path(vc_tools_redist_dir).rglob(omp_name))
if not dll_paths:
raise RuntimeError(
f"Did not find '{omp_name}' under '{vc_tools_redist_dir}', can't copy libomp to torch lib"
)
omp_path = dll_paths[0]
target_lib = pytorch_dir / "torch" / "lib"
print(f"Copying libomp from '{omp_path}' to '{target_lib}'")
shutil.copy2(omp_path, target_lib)
def copy_libuv_to_torch_lib(pytorch_dir: Path):
libuv_root = os.environ.get("libuv_ROOT", "")
if not libuv_root:
return
uv_dll = Path(libuv_root) / "bin" / "uv.dll"
if not uv_dll.exists():
raise RuntimeError(f"Did not find uv.dll at '{uv_dll}'")
target_lib = pytorch_dir / "torch" / "lib"
print(f"Copying libuv from '{uv_dll}' to '{target_lib}'")
shutil.copy2(uv_dll, target_lib)
def do_build_pytorch(
args: argparse.Namespace,
pytorch_dir: Path,
env: dict[str, str],
*,
triton_requirement: str | None,
):
# Compute version.
pytorch_build_version = (pytorch_dir / "version.txt").read_text().strip()
pytorch_build_version += args.version_suffix
pytorch_build_version_parsed = parse(pytorch_build_version)
print(f" Using PYTORCH_BUILD_VERSION: {pytorch_build_version}")
is_pytorch_2_11_or_later = pytorch_build_version_parsed.release[:2] >= (2, 11)
# aotriton supports a subset of GPU architectures. When at least one
# target arch is supported, we enable flash attention and let aotriton's
# build system (gpu_targets.py) filter to just the supported ones. The
# runtime (check_gpu in sdp_utils.cpp) gracefully falls back to math/CK
# backends on unsupported GPUs. We only disable flash attention when
# *no* target arch is supported — otherwise aotriton's configure step
# fails on the empty target list (https://github.com/ROCm/aotriton/issues/169).
#
# These prefixes match what aotriton's gpu_targets.py recognizes.
# See also the image list in pytorch/cmake/External/aotriton.cmake.
AOTRITON_SUPPORTED_ARCH_PREFIXES = ("gfx90a", "gfx942", "gfx950", "gfx11", "gfx12")
# gfx1152/53: supported in aotriton 0.11.2b+ (https://github.com/ROCm/aotriton/pull/142),
# which is pinned by pytorch >= 2.11. Older versions don't include it.
aotriton_unsupported_archs_for_version = []
if not is_pytorch_2_11_or_later:
aotriton_unsupported_archs_for_version = ["gfx1152", "gfx1153"]
rocm_arch_list = env.get("PYTORCH_ROCM_ARCH", "").split(";")
has_aotriton_supported_arch = any(
arch.startswith(AOTRITON_SUPPORTED_ARCH_PREFIXES)
and arch not in aotriton_unsupported_archs_for_version
for arch in rocm_arch_list
)
if not is_windows:
if args.enable_pytorch_flash_attention_linux is None:
# Default: enable when triton is available AND at least one
# target arch is supported by aotriton. When all targets are
# unsupported, aotriton can't produce a valid library.
use_flash_attention = (
"ON" if triton_requirement and has_aotriton_supported_arch else "OFF"
)
print(
f"Flash Attention default behavior (triton={bool(triton_requirement)},"
f" aotriton_arch={has_aotriton_supported_arch}): {use_flash_attention}"
)
else:
# Explicit override: user has set the flag to true/false
if args.enable_pytorch_flash_attention_linux:
assert (
triton_requirement
), "Must build with triton if wanting to use flash attention"
use_flash_attention = "ON"
else:
use_flash_attention = "OFF"
print(f"Flash Attention override set by flag: {use_flash_attention}")
env.update(
{
"USE_FLASH_ATTENTION": use_flash_attention,
"USE_MEM_EFF_ATTENTION": use_flash_attention,
}
)
print(
f"Flash Attention and Memory efficiency enabled: {env['USE_FLASH_ATTENTION'] == 'ON'}"
)
env["USE_ROCM"] = "ON"
env["USE_CUDA"] = "OFF"
env["USE_MPI"] = "OFF"
env["USE_NUMA"] = "OFF"
env["PYTORCH_BUILD_VERSION"] = pytorch_build_version
env["PYTORCH_BUILD_NUMBER"] = args.pytorch_build_number
# Determine which install requirements to add.
install_requirements = [
f"rocm[libraries]=={get_rocm_sdk_version()}",
]
if triton_requirement:
install_requirements.append(triton_requirement)
env["PYTORCH_EXTRA_INSTALL_REQUIREMENTS"] = "|".join(install_requirements)
print(
f"--- PYTORCH_EXTRA_INSTALL_REQUIREMENTS = {env['PYTORCH_EXTRA_INSTALL_REQUIREMENTS']}"
)
# Add the _rocm_init.py file.
(pytorch_dir / "torch" / "_rocm_init.py").write_text(get_rocm_init_contents(args))
# Windows-specific settings.
if is_windows:
copy_msvc_libomp_to_torch_lib(pytorch_dir)
copy_libuv_to_torch_lib(pytorch_dir)
use_flash_attention = (
"1"
if args.enable_pytorch_flash_attention_windows
and has_aotriton_supported_arch
else "0"
)
env.update(
{
"USE_FLASH_ATTENTION": use_flash_attention,
"USE_MEM_EFF_ATTENTION": use_flash_attention,
"DISTUTILS_USE_SDK": "1",
# Workaround compile errors in 'aten/src/ATen/test/hip/hip_vectorized_test.hip'
# on Torch 2.7.0: https://gist.github.com/ScottTodd/befdaf6c02a8af561f5ac1a2bc9c7a76.
# error: no member named 'modern' in namespace 'at::native'
# using namespace at::native::modern::detail;
# error: no template named 'has_same_arg_types'
# static_assert(has_same_arg_types<func1_t>::value, "func1_t has the same argument types");
# We may want to fix that and other issues to then enable building tests.
"BUILD_TEST": "0",
}
)
print(f" Flash attention enabled: {use_flash_attention == '1'}")
if not is_windows:
# Prepend the ROCm sysdeps dir so that we use bundled libraries.
# While a decent thing to be doing, this is presently required because:
# TODO: include/rocm_smi/kfd_ioctl.h is included without its advertised
# transitive includes. This triggers a compilation error for a missing
# libdrm/drm.h.
rocm_dir = get_rocm_path("root")
sysdeps_dir = rocm_dir / "lib" / "rocm_sysdeps"
assert sysdeps_dir.exists(), f"No sysdeps directory found: {sysdeps_dir}"
add_env_compiler_flags(env, "CXXFLAGS", f"-I{sysdeps_dir / 'include'}")
# Add correct include path for roctracer.h (for Kineto)
add_env_compiler_flags(
env, "CXXFLAGS", f"-I{rocm_dir / 'include' / 'roctracer'}"
)
add_env_compiler_flags(env, "LDFLAGS", f"-L{sysdeps_dir / 'lib'}")
# needed to find liblzma packaged by rocm as sysdep to build aotriton
os.environ["PKG_CONFIG_PATH"] = f"{sysdeps_dir / 'lib' / 'pkgconfig'}"
os.environ["LD_LIBRARY_PATH"] = f"{sysdeps_dir / 'lib'}"
print("+++ Uninstalling pytorch:")
run_command(
[sys.executable, "-m", "pip", "uninstall", "torch", "-y"],
cwd=tempfile.gettempdir(),
)
print("+++ Installing pytorch requirements:")
pip_install_args = []
if args.pip_cache_dir:
pip_install_args.extend(["--cache-dir", args.pip_cache_dir])
run_command(
[
sys.executable,
"-m",
"pip",
"install",
"-r",
pytorch_dir / "requirements.txt",
]
+ pip_install_args,
cwd=pytorch_dir,
)
# PEP 517 build backend requirements. We build below with
# `python -m build --wheel --no-isolation`, which (unlike an isolated build)
# does not auto-install the backend declared in pyproject.toml's
# [build-system]. PyTorch migrated its build backend from setuptools to
# scikit-build-core (ROCm/TheRock#6523; setup.py no longer builds wheels on
# recent checkouts). Newer checkouts ship requirements-build.txt (pinning
# scikit-build-core>=1.0), older ones do not. `build` provides the
# `python -m build` frontend itself.
print("+++ Installing pytorch build backend requirements:")
build_backend_install = [sys.executable, "-m", "pip", "install", "build"]
pytorch_build_requirements = pytorch_dir / "requirements-build.txt"
if pytorch_build_requirements.exists():
build_backend_install += ["-r", pytorch_build_requirements]
run_command(build_backend_install + pip_install_args, cwd=pytorch_dir)
build_command = [sys.executable, "-m", "build", "--wheel", "--no-isolation"]
if is_windows:
# The PyPI `ninja` package is unusable on Windows: 1.11.1 loops without
# making progress and 1.13.0 has an MSVC link.exe RSP-file regression
# (LNK1104/LNK1181), and no fixed version has been published
# (scikit-build/ninja-python-distributions#308). requirements-build.txt
# just pulled it in, so remove it; the runner provides a good system
# ninja (>=1.13.1) on PATH that CMake uses instead.
run_command(
[sys.executable, "-m", "pip", "uninstall", "ninja", "-y"],
cwd=pytorch_dir,
)
# With the PyPI ninja gone, `python -m build`'s PEP 517 dependency check
# would abort with "Missing dependencies: ninja". Skip that check: the
# backend drives CMake, which finds the system ninja on PATH.
build_command.append("--skip-dependency-check")
print("+++ Building pytorch:")
remove_dir_if_exists(pytorch_dir / "dist")
if args.clean:
remove_dir_if_exists(pytorch_dir / "build")
# `python -m build --wheel --no-isolation` is the standard replacement for
# the removed `setup.py bdist_wheel` (ROCm/TheRock#6523), used on all
# platforms. It drives the legacy setuptools backend and the new
# scikit-build-core backend alike, and all build-configuration env vars
# (USE_ROCM, PYTORCH_ROCM_ARCH, MAX_JOBS, ...) continue to be honored as they
# now seed the CMake cache directly.
run_command(build_command, cwd=pytorch_dir, env=env)
built_wheel = find_built_wheel(pytorch_dir / "dist", "torch")
print(f"Found built wheel: {built_wheel}")
copy_to_output(args, built_wheel)
print("+++ Installing built torch:")
run_command(
[sys.executable, "-m", "pip", "install", built_wheel], cwd=tempfile.gettempdir()
)
print("+++ Sanity checking installed torch (unavailable is okay on CPU machines):")
sanity_check_output = capture(
[sys.executable, "-c", "import torch; print(torch.cuda.is_available())"],
cwd=tempfile.gettempdir(),
)
if not sanity_check_output:
raise RuntimeError("torch package sanity check failed (see output above)")
else:
print(f"Sanity check output:\n{sanity_check_output}")
def do_build_pytorch_audio(
args: argparse.Namespace, pytorch_audio_dir: Path, env: dict[str, str]
):
# Compute version.
build_version = (pytorch_audio_dir / "version.txt").read_text().strip()
build_version += args.version_suffix
print(f" pytorch audio BUILD_VERSION: {build_version}")
env["BUILD_VERSION"] = build_version
env["BUILD_NUMBER"] = args.pytorch_build_number
env.update(
{
"USE_ROCM": "1",
"USE_CUDA": "0",
"USE_FFMPEG": "1",
"USE_OPENMP": "1",
"BUILD_SOX": "0",
}
)
if is_windows:
env.update(
{
"DISTUTILS_USE_SDK": "1",
}
)
remove_dir_if_exists(pytorch_audio_dir / "dist")
if args.clean:
remove_dir_if_exists(pytorch_audio_dir / "build")
run_command(
[sys.executable, "setup.py", "bdist_wheel"], cwd=pytorch_audio_dir, env=env
)
built_wheel = find_built_wheel(pytorch_audio_dir / "dist", "torchaudio")
print(f"Found built wheel: {built_wheel}")
copy_to_output(args, built_wheel)
def do_build_pytorch_vision(
args: argparse.Namespace, pytorch_vision_dir: Path, env: dict[str, str]
):
# Compute version.
build_version = (pytorch_vision_dir / "version.txt").read_text().strip()
build_version += args.version_suffix
print(f" pytorch vision BUILD_VERSION: {build_version}")
env["BUILD_VERSION"] = build_version
env["VERSION_NAME"] = build_version
env["BUILD_NUMBER"] = args.pytorch_build_number
env.update(
{
"FORCE_CUDA": "1",
"TORCHVISION_USE_NVJPEG": "0",
"TORCHVISION_USE_VIDEO_CODEC": "0",
}
)
if is_windows:
env.update(
{
"DISTUTILS_USE_SDK": "1",
}
)
remove_dir_if_exists(pytorch_vision_dir / "dist")
if args.clean:
remove_dir_if_exists(pytorch_vision_dir / "build")
run_command(
[sys.executable, "setup.py", "bdist_wheel"], cwd=pytorch_vision_dir, env=env
)
built_wheel = find_built_wheel(pytorch_vision_dir / "dist", "torchvision")
print(f"Found built wheel: {built_wheel}")
copy_to_output(args, built_wheel)
def do_build_apex(args: argparse.Namespace, apex_dir: Path, env: dict[str, str]):
# Compute version.
build_version = (apex_dir / "version.txt").read_text().strip()
build_version += args.version_suffix
print(f" Default apex BUILD_VERSION: {build_version}")
env["BUILD_VERSION"] = build_version
env["BUILD_NUMBER"] = args.pytorch_build_number
remove_dir_if_exists(apex_dir / "dist")
if args.clean:
remove_dir_if_exists(apex_dir / "build")
run_command(
[
sys.executable,
"-m",
"build",
"--wheel",
"--no-isolation",
"-C--build-option=--cpp_ext",
"-C--build-option=--cuda_ext",
],
cwd=apex_dir,
env=env,
)
built_wheel = find_built_wheel(apex_dir / "dist", "apex")
print(f"Found built wheel: {built_wheel}")
copy_to_output(args, built_wheel)
def main(argv: list[str]):
p = argparse.ArgumentParser(prog="build_prod_wheels.py")
def add_common(p: argparse.ArgumentParser):
p.add_argument("--index-url", help="Base URL of the Python Package Index.")
p.add_argument(
"--find-links",
help="URL or path for pip --find-links (flat package index).",
)
p.add_argument("--pip-cache-dir", type=Path, help="Pip cache dir")
# Note that we default to >1.0 because at the time of writing, we had
# 0.1.0 release placeholder packages out on pypi and we don't want them
# taking priority.
p.add_argument(
"--rocm-sdk-version",
default=">1.0",
help="rocm-sdk version to match (with comparison prefix)",
)
p.add_argument(
"--pre",
default=True,
action=argparse.BooleanOptionalAction,
help="Include pre-release packages (default True)",
)
p.add_argument(
"--rocm-extras",
default="",
help=(
"Comma-separated additional extras for rocm package install "
"(e.g. 'device-gfx942,device-gfx943'). "
"Added alongside the base 'libraries,devel' extras."
),
)
sub_p = p.add_subparsers(dest="command", required=True)
install_rocm_p = sub_p.add_parser(
"install-rocm", help="Install rocm-sdk wheels to the current venv"
)
add_common(install_rocm_p)
install_rocm_p.set_defaults(func=do_install_rocm)
build_p = sub_p.add_parser("build", help="Build pytorch wheels")
add_common(build_p)
build_p.add_argument(
"--install-rocm",
action=argparse.BooleanOptionalAction,
help="Install rocm-sdk before building",
)
build_p.add_argument(
"--output-dir",
type=Path,
required=True,
help="Directory to copy built wheels to",
)
cache_group = build_p.add_mutually_exclusive_group()
cache_group.add_argument(
"--use-ccache",
action="store_true",
default=False,
help="Use ccache as the compiler launcher",
)
cache_group.add_argument(
"--use-sccache",
action="store_true",
default=False,
help="Use sccache as the compiler launcher. Sets the CMake C/C++ "
"launchers and (on Linux) HIP_CLANG_LAUNCHER so hipcc routes its clang "
"invocations -- including the HIP device passes -- through sccache, "
"caching host and device code. Requires hipcc with HIP_CLANG_LAUNCHER "
"support (ROCm 7.13+).",
)
build_p.add_argument(
"--sccache-no-wrap",
action="store_true",
default=False,
help="With --use-sccache: set only the CMake C/C++ launchers and skip "
"HIP_CLANG_LAUNCHER (caches host C/C++ but not HIP device code). Use "
"when hipcc lacks HIP_CLANG_LAUNCHER support.",
)
build_p.add_argument(
"--root-checkout-dir",
default=script_dir,
type=Path,
help=(
"Root directory containing PyTorch source checkouts named pytorch, "
"pytorch_audio, pytorch_vision, triton, and apex. Explicit "
"--pytorch-dir, --pytorch-audio-dir, --pytorch-vision-dir, "
"--triton-dir, and --apex-dir arguments override this."
),
)
build_p.add_argument(
"--pytorch-dir",
default=None,
type=Path,
help="PyTorch source directory",
)
build_p.add_argument(
"--pytorch-audio-dir",
default=None,
type=Path,
help="pytorch_audio source directory",
)
build_p.add_argument(
"--pytorch-vision-dir",
default=None,
type=Path,
help="pytorch_vision source directory",
)
build_p.add_argument(
"--triton-dir",
default=None,
type=Path,
help="pinned triton directory",
)
build_p.add_argument(
"--apex-dir",
default=None,
type=Path,
help="apex source directory",
)
build_p.add_argument(
"--pytorch-rocm-arch",
help="Comma-separated gfx arches to build pytorch for (e.g. 'gfx942' or "
"'gfx942,gfx1201'). May also be supplied via the PYTORCH_ROCM_ARCH "
"environment variable. Falls back to `rocm-sdk targets` when unset "
"(legacy; see TODO on get_rocm_sdk_targets).",
)
build_p.add_argument(
"--pytorch-build-number", default="1", help="Build number to append to version"
)
build_p.add_argument(
"--build-triton",
action=argparse.BooleanOptionalAction,
default=None,
help="Enable building of triton (requires --triton-dir)",
)
build_p.add_argument(
"--build-pytorch-audio",
action=argparse.BooleanOptionalAction,
default=None,
help="Enable building of torch audio (requires --pytorch-audio-dir)",
)
build_p.add_argument(
"--build-pytorch-vision",
action=argparse.BooleanOptionalAction,
default=None,
help="Enable building of torch vision (requires --pytorch-vision-dir)",
)
build_p.add_argument(
"--build-apex",
action=argparse.BooleanOptionalAction,
default=None,
help="Enable building of apex (requires --apex-dir)",
)
build_p.add_argument(
"--enable-pytorch-flash-attention-windows",
action=argparse.BooleanOptionalAction,
default=None,
help="Enable building of torch flash attention on Windows (enabled by default for Linux)",
)
build_p.add_argument(
"--enable-pytorch-flash-attention-linux",
action=argparse.BooleanOptionalAction,
default=None,
help="Enable building of torch flash attention on Linux (enabled by default, sets USE_FLASH_ATTENTION=1)",
)
build_p.add_argument(
"--version-suffix",
help="Explicit PyTorch version suffix (e.g. `+rocm7.10.0a20251124`). Typically computed with build_tools/github_actions/determine_version.py. If omitted it will be derived from the installed rocm package",
)
build_p.add_argument(
"--clean",
action=argparse.BooleanOptionalAction,
help="Clean build directories before building",
)
build_p.set_defaults(func=do_build)
args = p.parse_args(argv)
if args.command == "build":
apply_root_checkout_dir(args)
args.func(args)
if __name__ == "__main__":
main(sys.argv[1:])