diff --git a/.ci/docker/build.sh b/.ci/docker/build.sh index ef963961787..be96ae7eba4 100755 --- a/.ci/docker/build.sh +++ b/.ci/docker/build.sh @@ -88,7 +88,7 @@ case "${IMAGE_NAME}" in LINTRUNNER="" GCC_VERSION=11 CUDA_WINDOWS_CROSS_COMPILE=yes - CUDA_VERSION=13.0 + CUDA_VERSION=13.2 SKIP_PYTORCH=yes ;; executorch-ubuntu-24.04-gcc14) diff --git a/.ci/docker/ci_commit_pins/pytorch.txt b/.ci/docker/ci_commit_pins/pytorch.txt index 257c075fe46..1067b14354d 100644 --- a/.ci/docker/ci_commit_pins/pytorch.txt +++ b/.ci/docker/ci_commit_pins/pytorch.txt @@ -1 +1 @@ -e91899ca45f99811049ac7d539820f92a9acddde +31a77fc9c0a7c6fff15a1ff58c743f53b1a9d457 diff --git a/.ci/docker/common/install_cuda_windows_cross_compile.sh b/.ci/docker/common/install_cuda_windows_cross_compile.sh index b5d92c8215d..67a8e1d526b 100644 --- a/.ci/docker/common/install_cuda_windows_cross_compile.sh +++ b/.ci/docker/common/install_cuda_windows_cross_compile.sh @@ -18,6 +18,7 @@ declare -A CUDA_DRIVER_MAP=( ["12.8"]="12.8.1:572.61" ["12.9"]="12.9.1:576.57" ["13.0"]="13.0.2:" + ["13.2"]="13.2.2:" ) install_mingw() { diff --git a/.ci/scripts/install-rocm-dependencies.sh b/.ci/scripts/install-rocm-dependencies.sh new file mode 100644 index 00000000000..35aab8cef1a --- /dev/null +++ b/.ci/scripts/install-rocm-dependencies.sh @@ -0,0 +1,57 @@ +#!/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. + +set -euo pipefail + +if ! command -v conda >/dev/null; then + echo "The ROCm CI image must provide conda for its C++ runtime libraries" + exit 1 +fi +conda install -y --override-channels -c conda-forge \ + gxx_linux-64=16.2.0 libstdcxx-ng=16.2.0 libgcc-ng=16.2.0 \ + sysroot_linux-64=2.28 +ROCM_PYTHON_PREFIX="$(python -c 'import sys; print(sys.prefix)')" +export CC="${ROCM_PYTHON_PREFIX}/bin/x86_64-conda-linux-gnu-cc" +export CXX="${ROCM_PYTHON_PREFIX}/bin/x86_64-conda-linux-gnu-c++" +export LD_LIBRARY_PATH="${ROCM_PYTHON_PREFIX}/lib:${LD_LIBRARY_PATH:-}" + +TORCH_VERSION="$(python -c 'from torch_pin import TORCH_VERSION; print(TORCH_VERSION)')" +python -m pip install "torch==${TORCH_VERSION}+rocm${ROCM_VERSION}" \ + --index-url "${PYTORCH_ROCM_INDEX}" +ROCM_SDK_VERSION="$(python -c 'import rocm_sdk; print(rocm_sdk.__version__)')" +python -m pip install "rocm[devel]==${ROCM_SDK_VERSION}" \ + --index-url "${PYTORCH_ROCM_INDEX}" +ROCM_PATH="$(python -m rocm_sdk path --root)" +export ROCM_PATH +export ROCM_HOME="${ROCM_PATH}" +export HIP_PATH="${ROCM_PATH}" +export PATH="${ROCM_PATH}/bin:${PATH}" +export LD_LIBRARY_PATH="${ROCM_PYTHON_PREFIX}/lib:${ROCM_PATH}/lib:${ROCM_PATH}/lib64:${LD_LIBRARY_PATH:-}" +python -m pip install -r requirements-dev.txt ninja +# ROCm 7.14 TorchAO wheels are Python-only. Build the pinned extension against +# the installed torch and HIP libraries instead of reusing a ROCm 7.2 binary. +USE_CPP=1 USE_CPU_KERNELS=1 MAX_JOBS="${MAX_JOBS:-8}" \ + python -m pip install --no-build-isolation --no-deps third-party/ao + +python - <<'PY' +from pathlib import Path + +import torch +import torchao +from packaging.version import Version + +from install_requirements import TORCHAO_MIN_VERSION + +assert torch.version.hip is not None, "PyTorch is not a ROCm build" +assert Version(torchao.__version__) >= Version(TORCHAO_MIN_VERSION) +libraries = list(Path(torchao.__file__).parent.glob("_C*.so")) +assert libraries, "TorchAO was built without its compiled extension" +for library in libraries: + # TorchAO's import logs load failures and continues; CI must fail on them. + torch.ops.load_library(str(library)) +print("Loaded TorchAO extensions:", libraries) +PY diff --git a/.ci/scripts/test-cuda-build.sh b/.ci/scripts/test-cuda-build.sh index 4798136f567..660821942ad 100755 --- a/.ci/scripts/test-cuda-build.sh +++ b/.ci/scripts/test-cuda-build.sh @@ -7,7 +7,7 @@ set -exu -CUDA_VERSION=${1:-"13.0"} +CUDA_VERSION=${1:-"13.2"} echo "=== Testing ExecuTorch CUDA ${CUDA_VERSION} Build ===" diff --git a/.ci/scripts/test-rocm-aoti.sh b/.ci/scripts/test-rocm-aoti.sh index 44ad516d197..494399e72c2 100644 --- a/.ci/scripts/test-rocm-aoti.sh +++ b/.ci/scripts/test-rocm-aoti.sh @@ -7,10 +7,9 @@ set -euo pipefail -ROCM_VERSION="${ROCM_VERSION:-7.2}" +ROCM_VERSION="${ROCM_VERSION:-7.14}" ROCM_PATH="${ROCM_PATH:-/opt/rocm}" -PYTORCH_ROCM_INDEX="${PYTORCH_ROCM_INDEX:-https://download.pytorch.org/whl/test/rocm${ROCM_VERSION}}" -TORCHAO_ROCM_WHEEL_BASE="${TORCHAO_ROCM_WHEEL_BASE:-https://download.pytorch.org/whl/nightly/rocm${ROCM_VERSION}}" +PYTORCH_ROCM_INDEX="${PYTORCH_ROCM_INDEX:-https://download.pytorch.org/whl/rocm${ROCM_VERSION}}" ROCM_CI_TMP_ROOT="${RUNNER_TEMP:-/tmp}" mkdir -p "${ROCM_CI_TMP_ROOT}" 2>/dev/null || ROCM_CI_TMP_ROOT=/tmp ROCM_CI_TMPDIR="$(mktemp -d "${ROCM_CI_TMP_ROOT}/executorch-rocm-ci.XXXXXX")" @@ -22,30 +21,10 @@ export CUDA_VISIBLE_DEVICES=0 export TORCHINDUCTOR_CACHE_DIR="${ROCM_CI_TMPDIR}/inductor-cache" export TORCHINDUCTOR_COMPILE_THREADS=1 -read -r TORCH_VERSION TORCHAO_VERSION < <( - python - <<'PY' -from install_requirements import ROCM_TORCHAO_NIGHTLY_VERSION -from torch_pin import TORCH_VERSION - -print(TORCH_VERSION, ROCM_TORCHAO_NIGHTLY_VERSION) -PY -) -# TorchAO ROCm wheels are not exposed by the per-version pip index. -TORCHAO_WHEEL="${TORCHAO_ROCM_WHEEL_BASE}/torchao-${TORCHAO_VERSION}" -TORCHAO_WHEEL+="%2Brocm${ROCM_VERSION}-cp310-abi3-manylinux_2_28_x86_64.whl" -python -m pip install "torch==${TORCH_VERSION}" \ - --index-url "${PYTORCH_ROCM_INDEX}" -python -m pip install -r requirements-dev.txt \ - "${TORCHAO_WHEEL}" +source .ci/scripts/install-rocm-dependencies.sh EXECUTORCH_BUILD_MINIMAL=1 \ python -m pip install --editable . --no-build-isolation -if ! command -v conda >/dev/null; then - echo "The ROCm CI image must provide conda for its C++ runtime libraries" - exit 1 -fi -conda install -y -c conda-forge 'libstdcxx-ng>=12' - python - <<'PY' import torch import torchao @@ -54,7 +33,6 @@ import triton assert torch.version.hip is not None, "PyTorch is not a ROCm build" assert torch.version.cuda is None, "PyTorch unexpectedly reports a CUDA runtime" assert torch.cuda.is_available(), "No AMD GPU is visible through PyTorch" -assert "+rocm" in torchao.__version__, "TorchAO is not a ROCm build" device = torch.cuda.get_device_properties(0) arch = device.gcnArchName.split(":", 1)[0] diff --git a/.ci/scripts/test-rocm-voxtral.sh b/.ci/scripts/test-rocm-voxtral.sh index a47e50a4971..ad3931129a0 100644 --- a/.ci/scripts/test-rocm-voxtral.sh +++ b/.ci/scripts/test-rocm-voxtral.sh @@ -7,12 +7,11 @@ set -euo pipefail -ROCM_VERSION="${ROCM_VERSION:-7.2}" +ROCM_VERSION="${ROCM_VERSION:-7.14}" ROCM_PATH="${ROCM_PATH:-/opt/rocm}" EXPECTED_ROCM_ARCH="${EXPECTED_ROCM_ARCH:-gfx950}" EXPECTED_WARP_SIZE="${EXPECTED_WARP_SIZE:-64}" -PYTORCH_ROCM_INDEX="${PYTORCH_ROCM_INDEX:-https://download.pytorch.org/whl/test/rocm${ROCM_VERSION}}" -TORCHAO_ROCM_WHEEL_BASE="${TORCHAO_ROCM_WHEEL_BASE:-https://download.pytorch.org/whl/nightly/rocm${ROCM_VERSION}}" +PYTORCH_ROCM_INDEX="${PYTORCH_ROCM_INDEX:-https://download.pytorch.org/whl/rocm${ROCM_VERSION}}" VOXTRAL_CI_TMP_ROOT="${RUNNER_TEMP:-/tmp}" if ! mkdir -p "${VOXTRAL_CI_TMP_ROOT}" 2>/dev/null || [[ ! -w "${VOXTRAL_CI_TMP_ROOT}" ]]; then @@ -33,23 +32,8 @@ mkdir -p "${HF_HOME}" HF_TOKEN="$(printf '%s' "${SECRET_EXECUTORCH_HF_TOKEN}" | tr -d '\r\n')" export HF_TOKEN -read -r TORCH_VERSION TORCHAO_VERSION < <( - python - <<'PY' -from install_requirements import ROCM_TORCHAO_NIGHTLY_VERSION -from torch_pin import TORCH_VERSION - -print(TORCH_VERSION, ROCM_TORCHAO_NIGHTLY_VERSION) -PY -) -# TorchAO ROCm wheels are not exposed by the per-version pip index. -TORCHAO_WHEEL="${TORCHAO_ROCM_WHEEL_BASE}/torchao-${TORCHAO_VERSION}" -TORCHAO_WHEEL+="%2Brocm${ROCM_VERSION}-cp310-abi3-manylinux_2_28_x86_64.whl" -python -m pip install "torch==${TORCH_VERSION}" \ - --index-url "${PYTORCH_ROCM_INDEX}" -python -m pip install -r requirements-dev.txt \ - "${TORCHAO_WHEEL}" -# ExecuTorch requires torchao>=TORCHAO_NIGHTLY_VERSION, which has no ROCm build, so -# install its other dependencies separately to keep the ROCm TorchAO wheel in place. +source .ci/scripts/install-rocm-dependencies.sh +# Keep the source-built TorchAO extension while installing the remaining dependencies. python -m pip install --editable . --no-build-isolation --no-deps python - <<'PY' >"${VOXTRAL_CI_TMPDIR}/executorch-deps.txt" from importlib.metadata import requires @@ -65,16 +49,12 @@ for spec in requires("executorch") or []: PY python -m pip install -r "${VOXTRAL_CI_TMPDIR}/executorch-deps.txt" -if ! command -v conda >/dev/null; then - echo "The ROCm CI image must provide conda for its runtime libraries" - exit 1 -fi -conda install -y -c conda-forge ffmpeg 'libstdcxx-ng>=12' +conda install -y -c conda-forge ffmpeg python -m pip install 'fsspec[http]<=2025.3.0' python -m pip install datasets huggingface_hub librosa mistral-common safetensors soundfile -python -m pip install torchcodec==0.11.0 \ - --extra-index-url https://download.pytorch.org/whl/test/cpu +python -m pip install torchcodec==0.17.0 \ + --index-url https://download.pytorch.org/whl/cpu python - <<'PY' import os @@ -85,7 +65,6 @@ import torchao assert torch.version.hip is not None, "PyTorch is not a ROCm build" assert torch.version.cuda is None, "PyTorch unexpectedly reports a CUDA runtime" assert torch.cuda.is_available(), "No AMD GPU is visible through PyTorch" -assert "+rocm" in torchao.__version__, "TorchAO is not a ROCm build" device = torch.cuda.get_device_properties(0) arch = device.gcnArchName.split(":", 1)[0] diff --git a/.ci/scripts/test_model_e2e.sh b/.ci/scripts/test_model_e2e.sh index 786cd81f9e2..11f58f96d78 100755 --- a/.ci/scripts/test_model_e2e.sh +++ b/.ci/scripts/test_model_e2e.sh @@ -390,7 +390,7 @@ elif [[ "$MODEL_NAME" == *whisper* ]] || [ "$MODEL_NAME" = "voxtral_realtime" ]; fi fi pip install datasets soundfile - pip install torchcodec==0.11.0 --extra-index-url https://download.pytorch.org/whl/test/cpu + pip install torchcodec==0.17.0 --index-url https://download.pytorch.org/whl/cpu python -c "from datasets import load_dataset;import soundfile as sf;sample = load_dataset('distil-whisper/librispeech_long', 'clean', split='validation')[0]['audio'];sf.write('${MODEL_DIR}/$AUDIO_FILE', sample['array'][:sample['sampling_rate']*30], sample['sampling_rate'])" fi diff --git a/.ci/scripts/test_wheel_package_qnn.sh b/.ci/scripts/test_wheel_package_qnn.sh index 568c90ed14c..65e60ef21ad 100644 --- a/.ci/scripts/test_wheel_package_qnn.sh +++ b/.ci/scripts/test_wheel_package_qnn.sh @@ -166,8 +166,8 @@ PY # ) echo "=== [$LABEL] Install torch==${TORCH_VERSION} ===" - # Install torch based on the pinned PyTorch version, preferring the PyTorch test index - "$PIPBIN" install torch=="${TORCH_VERSION}" --extra-index-url "https://download.pytorch.org/whl/test" + # Install torch based on the pinned PyTorch version. + "$PIPBIN" install --no-cache-dir torch=="${TORCH_VERSION}" --index-url "https://download.pytorch.org/whl/cpu" "$PIPBIN" install wheel # Install torchao based on the pinned commit from third-party/ao submodule diff --git a/.ci/scripts/tests/test_cu134_dependencies.py b/.ci/scripts/tests/test_cu134_dependencies.py index 224d5dcafeb..ef46bbf7808 100644 --- a/.ci/scripts/tests/test_cu134_dependencies.py +++ b/.ci/scripts/tests/test_cu134_dependencies.py @@ -5,12 +5,15 @@ # LICENSE file in the root directory of this source tree. import ast +import importlib.metadata import importlib.util import os +import re import subprocess import sys import unittest from pathlib import Path +from typing import List from unittest.mock import patch from packaging.requirements import Requirement @@ -40,10 +43,11 @@ def install_commands( nightly=True, system="Linux", python_version=(3, 14), + cmake_args="", ): self.utils.determine_torch_url.cache_clear() with ( - patch.dict(os.environ, {}, clear=True), + patch.dict(os.environ, {"CMAKE_ARGS": cmake_args}, clear=True), patch.object( self.utils, "_get_cuda_version", @@ -60,84 +64,119 @@ def install_commands( self.installer.install_optional_example_requirements(nightly) return [call.args[0] for call in run.call_args_list] - def test_all_install_steps_preserve_exact_cu134_selection(self): - for machine, ao_variant in (("x86_64", "cu134"), ("aarch64", "cpu")): - with self.subTest(machine=machine): - commands = self.install_commands((13, 4), machine) - self.assertEqual(len(commands), 4) - expected = { - "torch==2.15.0.dev20260921+cu134", - "torchvision==0.30.0.dev20260922+cu134", - "torchaudio==2.11.0.dev20260922+cu134", - f"torchao=={self.installer.CU134_TORCHAO_NIGHTLY_VERSION}+{ao_variant}", - } - for index, command in enumerate(commands): - required = ( - expected - if index >= 2 - else { - requirement - for requirement in expected - if requirement.startswith(("torch==", "torchao==")) - } - ) - self.assertTrue(required.issubset(command), command) - if index < 2: - self.assertFalse( - any( - arg.startswith(("torchvision", "torchaudio")) - for arg in command + def test_all_install_steps_preserve_exact_cuda_selection(self): + for cuda in ((13, 2), (13, 4)): + variant = f"cu{cuda[0]}{cuda[1]}" + for machine, ao_variant in (("x86_64", variant), ("aarch64", "cpu")): + with self.subTest(cuda=cuda, machine=machine): + commands = self.install_commands(cuda, machine) + self.assertEqual(len(commands), 4) + expected = { + f"torch==2.15.0+{variant}", + f"torchvision==0.29.2+{variant}", + f"torchaudio==2.11.1+{variant}", + f"torchao>={self.installer.TORCHAO_MIN_VERSION}", + } + for index, command in enumerate(commands): + required = ( + expected + if index >= 2 + else { + requirement + for requirement in expected + if requirement.startswith(("torch==", "torchao>=")) + } + ) + self.assertTrue(required.issubset(command), command) + if index < 2: + self.assertFalse( + any( + arg.startswith(("torchvision", "torchaudio")) + for arg in command + ) ) + self.assertIn( + f"https://download.pytorch.org/whl/{variant}", command ) - self.assertIn( - "https://download.pytorch.org/whl/nightly/cu134", command - ) - self.assertNotIn( - "https://download.pytorch.org/whl/test/cu134", command - ) - self.assertNotIn("--no-deps", command) - if machine == "aarch64": self.assertIn( - "https://download.pytorch.org/whl/nightly/cpu", command + f"https://download.pytorch.org/whl/nightly/{ao_variant}", + command, ) + self.assertNotIn("--no-deps", command) - def test_other_cuda_trains_keep_existing_pins(self): - for cuda in ((12, 6), (13, 0), (13, 2)): - for machine in ("x86_64", "aarch64"): - with self.subTest(cuda=cuda, machine=machine): - core, local, domains, examples = self.install_commands( - cuda, machine - ) - self.assertIn("torch==2.14.0", core) - self.assertIn( - f"torchao=={self.installer.TORCHAO_NIGHTLY_VERSION}", core - ) - self.assertIn("torchvision==0.29.0", domains) - self.assertIn("torchaudio==2.11.0", domains) - self.assertFalse(any("==" in arg for arg in local)) - self.assertFalse(any("==" in arg for arg in examples)) + def test_unpublished_cuda_variants_fail_before_pip(self): + for variant in ("cu126", "cu130"): + for install in ( + self.installer.install_requirements, + self.installer.install_optional_example_requirements, + ): + with ( + self.subTest(variant=variant, install=install.__name__), + patch.object( + self.installer, + "determine_torch_url", + return_value=f"https://download.pytorch.org/whl/{variant}", + ), + patch.object(self.installer.subprocess, "run") as run, + ): + with self.assertRaisesRegex( + RuntimeError, f"no released {variant} wheels" + ): + install(True) + run.assert_not_called() def test_source_pinned_torch_is_not_replaced(self): - for cuda in ((13, 2), (13, 4)): + for cuda in ((12, 6), (13, 0), (13, 2), (13, 4)): with self.subTest(cuda=cuda): core, _, domains, _ = self.install_commands(cuda, nightly=False) self.assertIn("torch", core) - self.assertNotIn("torch==2.15.0.dev20260921+cu134", core) + self.assertNotIn("torch==2.15.0+cu134", core) self.assertIn("torchvision", domains) self.assertIn("torchaudio", domains) def test_no_cuda_keeps_default_pins(self): core, _, domains, _ = self.install_commands(None) - self.assertIn("torch==2.14.0", core) - self.assertIn(f"torchao=={self.installer.TORCHAO_NIGHTLY_VERSION}", core) - self.assertIn("torchvision==0.29.0", domains) - self.assertIn("https://download.pytorch.org/whl/test/cpu", core) + self.assertIn("torch==2.15.0", core) + self.assertIn(f"torchao>={self.installer.TORCHAO_MIN_VERSION}", core) + self.assertIn("torchvision==0.29.2", domains) + self.assertIn("https://download.pytorch.org/whl/cpu", core) + + def test_disabled_cuda_keeps_cpu_dependencies_on_gpu_runners(self): + for cuda in ((12, 6), (13, 2)): + with self.subTest(cuda=cuda): + core, _, domains, _ = self.install_commands( + cuda, + cmake_args="-DEXECUTORCH_BUILD_VULKAN=ON -DEXECUTORCH_BUILD_CUDA=OFF", + ) + self.assertIn("torch==2.15.0", core) + self.assertIn("torchvision==0.29.2", domains) + self.assertIn("https://download.pytorch.org/whl/cpu", core) + self.assertIn("https://download.pytorch.org/whl/nightly/cpu", core) + + def test_disabled_cuda_skips_toolkit_detection(self): + for args in ( + "-DEXECUTORCH_BUILD_CUDA=OFF", + "-D EXECUTORCH_BUILD_CUDA:BOOL=0", + "-DEXECUTORCH_BUILD_CUDA=ON -DEXECUTORCH_BUILD_CUDA=OFF", + ): + self.utils.determine_torch_url.cache_clear() + with ( + self.subTest(args=args), + patch.dict(os.environ, {"CMAKE_ARGS": args}, clear=True), + patch.object(self.utils.platform, "system", return_value="Linux"), + patch.object(self.utils, "_get_cuda_version") as detect, + ): + self.assertEqual( + self.utils.determine_torch_url(self.installer.TORCH_URL_BASE), + "https://download.pytorch.org/whl/cpu", + ) + detect.assert_not_called() def test_windows_does_not_select_cu134(self): core, _, domains, _ = self.install_commands((13, 4), system="Windows") - self.assertIn("torch==2.14.0", core) - self.assertIn("torchvision==0.29.0", domains) - self.assertIn("https://download.pytorch.org/whl/test/cpu", core) + self.assertIn("torch==2.15.0", core) + self.assertIn("torchvision==0.29.2", domains) + self.assertIn("https://download.pytorch.org/whl/cpu", core) def test_python315_cpu_domains_use_available_torchaudio(self): for system in ("Linux", "Darwin", "Windows"): @@ -146,9 +185,9 @@ def test_python315_cpu_domains_use_available_torchaudio(self): _, _, domains, _ = self.install_commands( None, machine="arm64", system=system, python_version=version ) - audio = "2.11.1" if version >= (3, 15) else "2.11.0" + audio = "2.11.1" self.assertIn(f"torchaudio=={audio}", domains) - self.assertIn("torchvision==0.29.0", domains) + self.assertIn("torchvision==0.29.2", domains) def test_python315_cuda_domains_use_available_torchaudio(self): for machine in ("x86_64", "aarch64"): @@ -156,9 +195,9 @@ def test_python315_cuda_domains_use_available_torchaudio(self): _, _, domains, _ = self.install_commands( (13, 2), machine=machine, python_version=(3, 15) ) - self.assertIn("torchaudio==2.11.1", domains) - self.assertIn("torchvision==0.29.0", domains) - self.assertIn("https://download.pytorch.org/whl/test/cu132", domains) + self.assertIn("torchaudio==2.11.1+cu132", domains) + self.assertIn("torchvision==0.29.2+cu132", domains) + self.assertIn("https://download.pytorch.org/whl/cu132", domains) def test_python315_preserves_cu134_and_source_pins(self): for cuda in ((13, 2), (13, 4)): @@ -168,9 +207,9 @@ def test_python315_preserves_cu134_and_source_pins(self): cuda, nightly=nightly, python_version=(3, 15) ) audio = ( - "torchaudio==2.11.0.dev20260922+cu134" - if nightly and cuda == (13, 4) - else "torchaudio==2.11.1" if nightly else "torchaudio" + f"torchaudio==2.11.1+cu{cuda[0]}{cuda[1]}" + if nightly + else "torchaudio" ) self.assertIn(audio, domains) @@ -265,13 +304,87 @@ def test_package_install_preserves_source_pinned_torchao(self): self.assertIn(".", commands[-1]) core = commands[0] torchao = Requirement( - next(arg for arg in core if arg.startswith("torchao==")) + next(arg for arg in core if arg.startswith("torchao>=")) ) version = next(iter(torchao.specifier)).version self.assertIn(version, metadata.specifier) self.assertIn("torch", core) self.assertFalse(any(arg.startswith("torch==") for arg in core)) + def test_package_metadata_preserves_source_torch_only_when_requested(self): + with patch.dict(sys.modules, {"install_requirements": self.installer}): + package_installer = load_module("install_executorch") + tree = ast.parse((ROOT / "setup.py").read_text()) + functions = [ + node + for node in tree.body + if isinstance(node, ast.FunctionDef) + and node.name in {"_base_dependencies", "_minimal_dependencies"} + ] + namespace = { + "List": List, + "os": os, + "re": re, + "importlib": importlib, + "_torchao_requirement": self.torchao_requirement, + } + exec( + compile(ast.Module(body=functions, type_ignores=[]), "setup.py", "exec"), + namespace, + ) + source_version = "2.15.0a0+git31a77fc" + for pinned in (False, True): + with self.subTest(pinned=pinned): + argv = ["install_executorch", "--minimal"] + if pinned: + argv.append("--use-pt-pinned-commit") + with ( + patch.dict( + os.environ, + {"_EXECUTORCH_USE_PT_PINNED_COMMIT": "1"}, + clear=True, + ), + patch.object(sys, "argv", argv), + patch.object( + package_installer, "python_is_compatible", return_value=True + ), + patch.object(package_installer, "check_and_update_submodules"), + patch.object(package_installer, "install_requirements"), + patch.object(package_installer.subprocess, "run") as run, + ): + package_installer.main([]) + package_env = run.call_args.kwargs["env"] + self.assertEqual( + os.environ["_EXECUTORCH_USE_PT_PINNED_COMMIT"], "1" + ) + self.assertEqual( + package_env["_EXECUTORCH_USE_PT_PINNED_COMMIT"], + "1" if pinned else "0", + ) + with ( + patch.dict(os.environ, package_env, clear=True), + patch.object( + importlib.metadata, "version", return_value=source_version + ), + ): + deps = namespace["_base_dependencies"]() + torch = Requirement( + next(dep for dep in deps if dep.startswith("torch==")) + ) + self.assertEqual(source_version in torch.specifier, pinned) + self.assertEqual("2.15.0" in torch.specifier, not pinned) + self.assertEqual( + f"torchao>={self.installer.TORCHAO_MIN_VERSION}", + next(dep for dep in deps if dep.startswith("torchao>=")), + ) + self.assertIn("pytorch-tokenizers", deps) + self.assertFalse( + any( + dep.startswith("torch") + for dep in namespace["_minimal_dependencies"]() + ) + ) + def test_cu134_keeps_explicit_torchao_source_build(self): with patch.dict(sys.modules, {"install_requirements": self.installer}): package_installer = load_module("install_executorch") @@ -302,13 +415,9 @@ def test_cu134_keeps_explicit_torchao_source_build(self): self.assertIn("third-party/ao", commands[1]) self.assertIn(".", commands[2]) for command in commands: - self.assertFalse( - any(arg.startswith("torchao==") for arg in command) - ) - self.assertIn("torch==2.15.0.dev20260921+cu134", commands[-1]) - source_version = self.installer.TORCHAO_NIGHTLY_VERSION.partition( - ".dev" - )[0] + self.assertFalse(any(arg.startswith("torchao") for arg in command)) + self.assertIn("torch==2.15.0+cu134", commands[-1]) + source_version = self.installer.TORCHAO_MIN_VERSION source_commit = subprocess.run( ["git", "rev-parse", "HEAD:third-party/ao"], cwd=ROOT, @@ -320,15 +429,10 @@ def test_cu134_keeps_explicit_torchao_source_build(self): f"{source_version}+git{source_commit[:7]}", metadata.specifier ) - def test_wheel_torchao_bound_matches_selected_train(self): + def test_wheel_torchao_minimum_matches_installer(self): for cuda in ((13, 4), (13, 2), None): - version = ( - self.installer.CU134_TORCHAO_NIGHTLY_VERSION - if cuda == (13, 4) - else self.installer.TORCHAO_NIGHTLY_VERSION - ) - major, minor = (int(part) for part in version.split(".")[:2]) - expected = f"torchao>={version},<{major}.{minor + 1}" + version = self.installer.TORCHAO_MIN_VERSION + expected = f"torchao>={version}" self.utils.determine_torch_url.cache_clear() with ( patch.object( @@ -340,6 +444,18 @@ def test_wheel_torchao_bound_matches_selected_train(self): patch.object(self.installer.platform, "system", return_value="Linux"), ): self.assertEqual(self.torchao_requirement(), expected) + requirement = Requirement(self.torchao_requirement()) + self.assertNotIn("0.17.0", requirement.specifier) + for newer in ( + "0.18.0", + "0.19.0.dev20260908", + "0.19.0", + "0.20.0", + "1.0.0", + ): + self.assertTrue( + requirement.specifier.contains(newer, prereleases=True) + ) if __name__ == "__main__": diff --git a/.ci/scripts/tests/test_cuda_workflow.py b/.ci/scripts/tests/test_cuda_workflow.py index 112f9a6f5e1..2a8a3f3a2a3 100644 --- a/.ci/scripts/tests/test_cuda_workflow.py +++ b/.ci/scripts/tests/test_cuda_workflow.py @@ -32,11 +32,9 @@ def _model_quant(entry): class CudaWorkflowTest(unittest.TestCase): - def test_build_matrix_preserves_existing_cuda_versions(self): + def test_build_matrix_uses_released_torch_cuda_versions(self): job = WORKFLOW["jobs"]["test-cuda-builds"] - self.assertEqual( - job["strategy"]["matrix"]["cuda-version"], ["12.6", "13.0", "13.4"] - ) + self.assertEqual(job["strategy"]["matrix"]["cuda-version"], ["13.2", "13.4"]) self.assertEqual(job["with"]["gpu-arch-version"], "${{ matrix.cuda-version }}") def test_cuda_builds_take_the_node_driver_on_an_unpinned_v3(self): @@ -58,9 +56,7 @@ def test_cuda_runtime_update_precedes_build_and_propagates_failure(self): source() { printf 'BUILD %s\n' "$*"; printf 'LIBRARY PATH %s\n' "$LD_LIBRARY_PATH"; } """ for version, conda_status in ( - (version, status) - for version in ("12.6", "13.0", "13.4") - for status in (0, 1) + (version, status) for version in ("13.2", "13.4") for status in (0, 1) ): with self.subTest(version=version, conda_status=conda_status): result = subprocess.run( diff --git a/.ci/scripts/tests/test_rocm_dependencies.py b/.ci/scripts/tests/test_rocm_dependencies.py new file mode 100644 index 00000000000..f4000eb8112 --- /dev/null +++ b/.ci/scripts/tests/test_rocm_dependencies.py @@ -0,0 +1,115 @@ +# 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. + +import os +import subprocess +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[3] + + +class RocmDependenciesTest(unittest.TestCase): + def test_compiled_torchao_uses_the_selected_torch_and_propagates_failures(self): + stubs = """ +conda() { echo "RUNTIME $*"; } +python() { + if [[ "$1" == "-c" ]]; then + if [[ "$2" == *torch_pin* ]]; then echo 2.15.0 + elif [[ "$2" == *rocm_sdk* ]]; then echo 7.14.1 + else echo /env; fi + elif [[ "$*" == "-m rocm_sdk path --root" ]]; then + echo /sdk + elif [[ "$1" == "-" ]]; then + cat >/dev/null + echo VERIFY + return "$VERIFY_STATUS" + else + echo "PIP $* CPP=${USE_CPP:-unset} CPU=${USE_CPU_KERNELS:-unset} JOBS=${MAX_JOBS:-unset}" + if [[ "$*" == *third-party/ao* ]]; then return "$AO_STATUS"; fi + if [[ "$*" == *torch==* ]]; then return "$TORCH_STATUS"; fi + if [[ "$*" == *'rocm[devel]'* ]]; then return "$SDK_STATUS"; fi + fi +} +""" + for torch_status, sdk_status, ao_status, verify_status in ( + (0, 0, 0, 0), + (1, 0, 0, 0), + (0, 1, 0, 0), + (0, 0, 1, 0), + (0, 0, 0, 1), + ): + with self.subTest( + torch=torch_status, sdk=sdk_status, ao=ao_status, verify=verify_status + ): + result = subprocess.run( + [ + "bash", + "-c", + stubs + + "source .ci/scripts/install-rocm-dependencies.sh\necho INSTALL_ET\necho SDK_HOME=$ROCM_HOME HIP_PATH=$HIP_PATH ROCM_PATH=$ROCM_PATH\necho CC=$CC CXX=$CXX", + ], + cwd=ROOT, + env={ + **os.environ, + "ROCM_VERSION": "7.14", + "PYTORCH_ROCM_INDEX": "https://download.pytorch.org/whl/rocm7.14", + "TORCH_STATUS": str(torch_status), + "SDK_STATUS": str(sdk_status), + "AO_STATUS": str(ao_status), + "VERIFY_STATUS": str(verify_status), + "MAX_JOBS": "8", + }, + capture_output=True, + text=True, + ) + self.assertEqual( + result.returncode, + max(torch_status, sdk_status, ao_status, verify_status), + result.stderr, + ) + self.assertTrue( + result.stdout.startswith( + "RUNTIME install -y --override-channels -c conda-forge " + "gxx_linux-64=16.2.0 libstdcxx-ng=16.2.0 libgcc-ng=16.2.0 " + "sysroot_linux-64=2.28\n" + ) + ) + self.assertIn( + "torch==2.15.0+rocm7.14 --index-url https://download.pytorch.org/whl/rocm7.14", + result.stdout, + ) + if not torch_status: + self.assertIn( + "rocm[devel]==7.14.1 --index-url https://download.pytorch.org/whl/rocm7.14", + result.stdout, + ) + if not (torch_status or sdk_status): + self.assertIn( + "--no-build-isolation --no-deps third-party/ao CPP=1 CPU=1 JOBS=8", + result.stdout, + ) + else: + self.assertNotIn("third-party/ao", result.stdout) + self.assertEqual( + "VERIFY" in result.stdout, + not (torch_status or sdk_status or ao_status), + ) + self.assertEqual("INSTALL_ET" in result.stdout, result.returncode == 0) + if result.returncode == 0: + self.assertIn( + "SDK_HOME=/sdk HIP_PATH=/sdk ROCM_PATH=/sdk", result.stdout + ) + self.assertIn( + "CC=/env/bin/x86_64-conda-linux-gnu-cc " + "CXX=/env/bin/x86_64-conda-linux-gnu-c++", + result.stdout, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/.ci/scripts/wheel/vc_env_helper.bat b/.ci/scripts/wheel/vc_env_helper.bat index d7fa2c1a596..be43e962657 100644 --- a/.ci/scripts/wheel/vc_env_helper.bat +++ b/.ci/scripts/wheel/vc_env_helper.bat @@ -23,6 +23,12 @@ if "%VSDEVCMD_ARGS%" == "" ( @echo on +REM CUDAHOSTCXX is read after Torch overwrites CMAKE_CUDA_HOST_COMPILER. +if /I "%CU_VERSION:~0,2%" == "cu" ( + where cl.exe >nul 2>&1 || exit /b 1 + for /f "delims=" %%i in ('where cl.exe') do if not defined CUDAHOSTCXX set "CUDAHOSTCXX=%%i" +) + if "%CU_VERSION%" == "xpu" call "C:\Program Files (x86)\Intel\oneAPI\setvars.bat" set DISTUTILS_USE_SDK=1 diff --git a/.github/workflows/cuda-windows.yml b/.github/workflows/cuda-windows.yml index 624a071c9fd..83f34c4d114 100644 --- a/.github/workflows/cuda-windows.yml +++ b/.github/workflows/cuda-windows.yml @@ -105,7 +105,7 @@ jobs: secrets-env: EXECUTORCH_HF_TOKEN runner: mt-l-x86aavx2-11-41-a10g gpu-arch-type: cuda - gpu-arch-version: "13.0" + gpu-arch-version: "13.2" docker-image: ${{ needs.docker-image.outputs.docker-registry }}/ci-image:executorch-ubuntu-22.04-cuda-windows-${{ needs.docker-image.outputs.ci-docker-hash }} submodules: recursive upload-artifact: ${{ matrix.model_repo }}-${{ matrix.model_name }}-cuda-windows-${{ matrix.quant }} @@ -206,9 +206,6 @@ jobs: timeout: 240 runner: windows.g5.4xlarge.nvidia.gpu gpu-arch-type: cuda - # The Windows runner image carries only the toolkits the CUDA wheels are built for, - # 13.2 and 13.4, so this cannot match the export's 13.0. The exported program loads - # cudart64_13.dll, which every CUDA 13 toolkit provides. gpu-arch-version: "13.2" download-artifact: ${{ matrix.model_repo }}-${{ matrix.model_name }}-cuda-windows-${{ matrix.quant }} ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} diff --git a/.github/workflows/cuda.yml b/.github/workflows/cuda.yml index 07653ae8bd4..0c1f2289903 100644 --- a/.github/workflows/cuda.yml +++ b/.github/workflows/cuda.yml @@ -1,6 +1,6 @@ # Test ExecuTorch CUDA Build Compatibility # This workflow tests whether ExecuTorch can be successfully built with CUDA support -# across different CUDA versions (12.6, 13.0, 13.4) using the command: +# across different CUDA versions (13.2, 13.4) using the command: # ./install_executorch.sh # # It also runs model accuracy (e2e EXPECTED_OUTPUT checks) and, for the perf @@ -8,8 +8,6 @@ # .pte/.ptd artifacts. Benchmark results are uploaded to S3 + the HUD dashboard. # The retired cuda-perf.yml was merged into this workflow. # -# Intentionally skipped CUDA version 13.2 check due to ci image unsupported. -# # Note: ExecuTorch automatically detects the system CUDA version using nvcc and # installs the appropriate PyTorch wheel. No manual CUDA/PyTorch installation needed. @@ -139,7 +137,7 @@ jobs: strategy: fail-fast: false matrix: - cuda-version: ["12.6", "13.0", "13.4"] + cuda-version: ["13.2", "13.4"] name: test-executorch-cuda-build-${{ matrix.cuda-version }} uses: pytorch/test-infra/.github/workflows/linux_job_v3.yml@main @@ -156,8 +154,7 @@ jobs: script: | set -eux - # AOTI model libraries need a newer C++ runtime than the 12.6 image's - # system library; the 13.4 image also needs the thread-local cleanup fix. + # AOTI model libraries need the newer C++ runtime and thread-local cleanup fix. conda install -y -n base -c conda-forge 'libstdcxx-ng=16.2.0' 'libgcc-ng=16.2.0' export LD_LIBRARY_PATH="/opt/conda/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" source .ci/scripts/test-cuda-build.sh "${{ matrix.cuda-version }}" @@ -213,7 +210,7 @@ jobs: timeout: 90 runner: mt-l-x86aavx2-11-41-a10g gpu-arch-type: cuda - gpu-arch-version: "13.0" + gpu-arch-version: "13.2" submodules: recursive ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} script: | @@ -254,7 +251,7 @@ jobs: timeout: 90 runner: mt-l-x86iavx512-11-125-a100 gpu-arch-type: cuda - gpu-arch-version: "13.0" + gpu-arch-version: "13.2" submodules: recursive upload-artifact: cuda-multi-arch-a100 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} @@ -312,7 +309,7 @@ jobs: timeout: 90 runner: mt-l-x86aavx2-11-41-a10g gpu-arch-type: cuda - gpu-arch-version: "13.0" + gpu-arch-version: "13.2" submodules: recursive download-artifact: cuda-multi-arch-a100 upload-artifact: cuda-multi-arch-merged @@ -416,7 +413,7 @@ jobs: timeout: 90 runner: mt-l-x86iavx512-11-125-a100 gpu-arch-type: cuda - gpu-arch-version: "13.0" + gpu-arch-version: "13.2" submodules: recursive download-artifact: cuda-multi-arch-merged ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} @@ -506,7 +503,7 @@ jobs: timeout: 90 runner: mt-l-x86aavx2-11-41-a10g gpu-arch-type: cuda - gpu-arch-version: "13.0" + gpu-arch-version: "13.2" submodules: recursive ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} script: | @@ -604,7 +601,7 @@ jobs: fi # Install flash-linear-attention for chunk_gated_delta_rule triton kernel tests - pip install "flash-linear-attention==0.4.2" + pip install "flash-linear-attention==0.6.0" # Build executor_runner (needed by CUDA backend e2e tests in both shards) cmake --build cmake-out --target executor_runner @@ -653,7 +650,7 @@ jobs: timeout: 120 runner: mt-l-x86aavx2-11-41-a10g gpu-arch-type: cuda - gpu-arch-version: "13.0" + gpu-arch-version: "13.2" submodules: recursive ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} script: | @@ -733,7 +730,7 @@ jobs: timeout: 90 runner: mt-l-x86aavx2-11-41-a10g gpu-arch-type: cuda - gpu-arch-version: "13.0" + gpu-arch-version: "13.2" submodules: recursive ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} script: | @@ -764,7 +761,7 @@ jobs: timeout: 90 runner: mt-l-x86aavx2-11-41-a10g gpu-arch-type: cuda - gpu-arch-version: "13.0" + gpu-arch-version: "13.2" submodules: recursive ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} script: | @@ -967,7 +964,7 @@ jobs: secrets-env: EXECUTORCH_HF_TOKEN runner: ${{ (matrix.model.name == 'Qwen3.5-35B-A3B-HQQ-INT4' || matrix.model.name == 'gemma-4-31B-it-GGUF') && 'mt-l-x86iavx512-11-125-a100' || 'mt-l-x86aavx2-11-41-a10g' }} gpu-arch-type: cuda - gpu-arch-version: "13.0" + gpu-arch-version: "13.2" submodules: recursive upload-artifact: cuda-bench-${{ matrix.model.repo }}_${{ matrix.model.name }}-${{ matrix.quant }} ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} @@ -1228,7 +1225,7 @@ jobs: timeout: 240 runner: mt-l-x86iavx512-11-125-a100 gpu-arch-type: cuda - gpu-arch-version: "13.0" + gpu-arch-version: "13.2" submodules: recursive ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} script: | diff --git a/.github/workflows/rocm.yml b/.github/workflows/rocm.yml index 66cd3baeb2e..b7b6149b098 100644 --- a/.github/workflows/rocm.yml +++ b/.github/workflows/rocm.yml @@ -16,6 +16,7 @@ on: - .github/workflows/rocm.yml - .github/workflows/_ci-run-decision.yml - .github/workflows/_get-changed-files.yml + - .ci/scripts/install-rocm-dependencies.sh - .ci/scripts/test-rocm-aoti.sh - .ci/scripts/test-rocm-voxtral.sh - CMakeLists.txt @@ -80,6 +81,7 @@ jobs: .github/workflows/rocm.yml | \ .github/workflows/_ci-run-decision.yml | \ .github/workflows/_get-changed-files.yml | \ + .ci/scripts/install-rocm-dependencies.sh | \ .ci/scripts/test-rocm-aoti.sh | \ CMakeLists.txt | \ CMakePresets.json | \ @@ -146,6 +148,7 @@ jobs: .github/workflows/_ci-run-decision.yml | \ .github/workflows/_get-changed-files.yml | \ install_requirements.py | \ + .ci/scripts/install-rocm-dependencies.sh | \ torch_pin.py) RUN_VOXTRAL=true ;; @@ -169,7 +172,7 @@ jobs: strategy: fail-fast: false matrix: - rocm-version: ["7.2"] + rocm-version: ["7.14"] uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main permissions: id-token: write @@ -209,7 +212,7 @@ jobs: strategy: fail-fast: false matrix: - rocm-version: ["7.2"] + rocm-version: ["7.14"] with: timeout: 180 no-sudo: true @@ -251,7 +254,7 @@ jobs: strategy: fail-fast: false matrix: - rocm-version: ["7.2"] + rocm-version: ["7.14"] with: timeout: 180 no-sudo: true diff --git a/.github/workflows/vulkan.yml b/.github/workflows/vulkan.yml index 1fce832be6a..e9bcb82b8ff 100644 --- a/.github/workflows/vulkan.yml +++ b/.github/workflows/vulkan.yml @@ -100,7 +100,7 @@ jobs: # setup-linux.sh's "deps already in the image" assumption does not hold. # CMAKE_ARGS enables Vulkan in the pybindings so the model --test runs and # the pt2e/torchao e2e tests below execute on the GPU (default is OFF). - CMAKE_ARGS="-DEXECUTORCH_BUILD_VULKAN=ON" PYTHON_EXECUTABLE=python ./install_executorch.sh + CMAKE_ARGS="-DEXECUTORCH_BUILD_VULKAN=ON -DEXECUTORCH_BUILD_CUDA=OFF" PYTHON_EXECUTABLE=python ./install_executorch.sh python -m unittest backends/vulkan/test/test_vulkan_dynamic.py @@ -193,7 +193,7 @@ jobs: export PIP_EXTRA_INDEX_URL= pip install filelock typing-extensions "setuptools<82" sympy networkx jinja2 "fsspec[http]<=2025.3.0" numpy pillow export USE_MKL=OFF - CMAKE_ARGS="-DEXECUTORCH_BUILD_VULKAN=ON -DEXECUTORCH_BUILD_KERNELS_LLM_AOT=ON" \ + CMAKE_ARGS="-DEXECUTORCH_BUILD_VULKAN=ON -DEXECUTORCH_BUILD_CUDA=OFF -DEXECUTORCH_BUILD_KERNELS_LLM_AOT=ON" \ PYTHON_EXECUTABLE=python ./install_executorch.sh python -m pip list --format=json > "${RUNNER_ARTIFACT_DIR}/python-packages.json" diff --git a/.github/workflows/windows-msvc.yml b/.github/workflows/windows-msvc.yml index 269f2cf2381..22f57a3e06b 100644 --- a/.github/workflows/windows-msvc.yml +++ b/.github/workflows/windows-msvc.yml @@ -91,7 +91,7 @@ jobs: - name: Install build dependencies shell: pwsh - run: python -m pip install pyyaml torch==2.14.0 --extra-index-url https://download.pytorch.org/whl/test/cpu + run: python -m pip install pyyaml torch==2.15.0 --extra-index-url https://download.pytorch.org/whl/cpu - name: Build ExecuTorch shell: pwsh diff --git a/backends/cuda/tests/test_chunk_gated_delta_rule.py b/backends/cuda/tests/test_chunk_gated_delta_rule.py index 6ce177dd70a..a57b71115a3 100644 --- a/backends/cuda/tests/test_chunk_gated_delta_rule.py +++ b/backends/cuda/tests/test_chunk_gated_delta_rule.py @@ -252,6 +252,38 @@ def test_eager_matches_fla(self): self.assertLess((o_ours.float() - o_ref.float()).abs().max().item(), 0.01) + def test_batched_partial_chunks_match_reference(self): + from fla.ops.gated_delta_rule.naive import naive_recurrent_gated_delta_rule + + torch.manual_seed(42) + for length in (63, 129): + with self.subTest(length=length), torch.no_grad(): + q = F.normalize( + torch.rand(2, length, 3, 64, device="cuda", dtype=torch.bfloat16), + dim=-1, + ) + k = F.normalize(torch.rand_like(q), dim=-1) + v = torch.rand(2, length, 3, 96, device="cuda", dtype=torch.bfloat16) + g = F.logsigmoid( + torch.rand(2, length, 3, device="cuda", dtype=torch.bfloat16) + ) + beta = torch.rand_like(g).sigmoid() + h0 = torch.rand(2, 3, 64, 96, device="cuda", dtype=torch.bfloat16) + actual = torch.ops.triton.chunk_gated_delta_rule(q, k, v, g, beta, h0) + expected = naive_recurrent_gated_delta_rule( + q=q, + k=k, + v=v, + g=g, + beta=beta, + initial_state=h0, + output_final_state=True, + ) + for result, reference in zip(actual, expected): + self.assertLess( + (result.float() - reference.float()).abs().max().item(), 0.01 + ) + def test_export_cuda(self): with tempfile.TemporaryDirectory() as tmpdir: pte_path = export_chunk_gated_delta(tmpdir) diff --git a/backends/cuda/triton/kernels/chunk_gated_delta_rule.py b/backends/cuda/triton/kernels/chunk_gated_delta_rule.py index 437374457a1..e98f7528139 100644 --- a/backends/cuda/triton/kernels/chunk_gated_delta_rule.py +++ b/backends/cuda/triton/kernels/chunk_gated_delta_rule.py @@ -33,6 +33,7 @@ from fla.ops.common.chunk_o import chunk_fwd_kernel_o from fla.ops.common.chunk_scaled_dot_kkt import chunk_scaled_dot_kkt_fwd_kernel from fla.ops.gated_delta_rule.wy_fast import recompute_w_u_fwd_kernel +from fla.ops.utils.constant import RCP_LN2 from fla.ops.utils.cumsum import chunk_local_cumsum_scalar_kernel from fla.ops.utils.solve_tril import merge_16x16_to_64x64_inverse_kernel from fla.utils import IS_TMA_SUPPORTED @@ -114,20 +115,20 @@ def chunk_gated_delta_rule( # 1. chunk_local_cumsum: cumulative sum of g within each chunk g_cumsum = torch.empty(B, T, H, dtype=torch.float32, device=q.device) - wrap_triton(_unwrap(chunk_local_cumsum_scalar_kernel))[(NT, B * H)]( + wrap_triton(_unwrap(chunk_local_cumsum_scalar_kernel))[(NT * B * H,)]( s=g, o=g_cumsum, - scale=0, + scale=RCP_LN2, cu_seqlens=0, chunk_indices=0, T=T, B=B, H=H, BT=BT, - HEAD_FIRST=False, REVERSE=False, - HAS_SCALE=False, + HAS_SCALE=True, IS_VARLEN=False, + USE_GRAPH=False, ) # 2. chunk_scaled_dot_kkt: compute beta * K * K^T with gating @@ -141,6 +142,7 @@ def chunk_gated_delta_rule( chunk_indices=0, T=T, H=H, + HV=H, K=K, BT=BT, USE_G=True, @@ -177,6 +179,7 @@ def chunk_gated_delta_rule( chunk_indices=0, T=T, H=H, + HV=H, K=K, V=V, BT=BT, @@ -192,7 +195,7 @@ def chunk_gated_delta_rule( v_new = torch.empty_like(v) def grid_h(meta): - return (triton.cdiv(V, meta["BV"]), B * H) + return (triton.cdiv(V, meta["BV"]) * B * H,) wrap_triton(_unwrap(chunk_gated_delta_rule_fwd_kernel_h_blockdim64))[grid_h]( k=k, @@ -208,11 +211,11 @@ def grid_h(meta): chunk_offsets=0, T=T, H=H, + HV=H, K=K, V=V, BT=BT, - USE_EXP2=False, - TRANSPOSE_STATE=False, + STATE_V_FIRST=False, USE_G=True, USE_GK=False, USE_INITIAL_STATE=True, @@ -225,7 +228,7 @@ def grid_h(meta): o = torch.empty_like(v) def grid_o(meta): - return (triton.cdiv(V, meta["BV"]), NT, B * H) + return (triton.cdiv(V, meta["BV"]) * NT, B * H) wrap_triton(_unwrap(chunk_fwd_kernel_o))[grid_o]( q=q, @@ -240,10 +243,11 @@ def grid_o(meta): scale=scale, T=T, H=H, + HV=H, K=K, V=V, BT=BT, - TRANSPOSE_STATE=False, + STATE_V_FIRST=False, USE_G=True, USE_G_GAMMA=False, IS_VARLEN=False, diff --git a/examples/models/moshi/mimi/install_requirements.sh b/examples/models/moshi/mimi/install_requirements.sh index 93167ead9a1..3097c939886 100755 --- a/examples/models/moshi/mimi/install_requirements.sh +++ b/examples/models/moshi/mimi/install_requirements.sh @@ -15,7 +15,7 @@ if command -v apt-get >/dev/null 2>&1; then sudo apt-get update sudo apt-get install -y --no-install-recommends ffmpeg fi -pip install torchcodec==0.11.0 --extra-index-url https://download.pytorch.org/whl/test/cpu +pip install torchcodec==0.17.0 --index-url https://download.pytorch.org/whl/cpu pip install moshi==0.2.11 pip install bitsandbytes soundfile einops # Run llama2/install requirements for torchao deps diff --git a/examples/models/qwen3_5_moe/requirements.txt b/examples/models/qwen3_5_moe/requirements.txt index 0c196f92d29..506dd5469b8 100644 --- a/examples/models/qwen3_5_moe/requirements.txt +++ b/examples/models/qwen3_5_moe/requirements.txt @@ -1 +1 @@ -flash-linear-attention==0.4.2 +flash-linear-attention==0.6.0 diff --git a/install_executorch.py b/install_executorch.py index 276841364e6..f11f65b9d17 100644 --- a/install_executorch.py +++ b/install_executorch.py @@ -251,7 +251,11 @@ def main(args): ) if args.verbose: cmd.append("-v") - subprocess.run(cmd, check=True) + package_env = os.environ.copy() + package_env["_EXECUTORCH_USE_PT_PINNED_COMMIT"] = ( + "1" if args.use_pt_pinned_commit else "0" + ) + subprocess.run(cmd, check=True, env=package_env) # Step 3: Extra (optional) packages that is only useful for running examples. if not args.minimal: diff --git a/install_requirements.py b/install_requirements.py index 285c4bf281f..c9b213dc898 100644 --- a/install_requirements.py +++ b/install_requirements.py @@ -13,22 +13,15 @@ from install_utils import determine_torch_url, is_intel_mac_os, python_is_compatible -# The pip repository that hosts nightly torch packages. +# The pip repository that hosts released torch packages. # This will be dynamically set based on CUDA availability and CUDA backend enabled/disabled. -TORCH_URL_BASE = "https://download.pytorch.org/whl/test" +TORCH_URL_BASE = "https://download.pytorch.org/whl" TORCHAO_URL_BASE = "https://download.pytorch.org/whl/nightly" -TORCHAO_NIGHTLY_VERSION = "0.19.0.dev20260907" -CU134_TORCHAO_NIGHTLY_VERSION = "0.19.0.dev20260907" -# Newest rocm7.2 TorchAO nightly with compiled kernels (cp310-abi3); later ROCm nightlies -# are py3-none-any only, and none are published after 0.19.0.dev20260831. -ROCM_TORCHAO_NIGHTLY_VERSION = "0.19.0.dev20260805" -# torchvision 0.30.0.dev20260922 requires exactly torch 2.15.0.dev20260921. -# download.pytorch.org keeps about 60 days of nightlies, so this pin expires; 2026-09-22 -# is the newest date whose torch, torchvision and torchaudio still all carry cp310. -CU134_TORCH_PACKAGES = [ - "torch==2.15.0.dev20260921+cu134", - "torchvision==0.30.0.dev20260922+cu134", - "torchaudio==2.11.0.dev20260922+cu134", +TORCHAO_MIN_VERSION = "0.18.0" +TORCH_PACKAGES = [ + "torch==2.15.0", + "torchvision==0.29.2", + "torchaudio==2.11.1", ] @@ -39,30 +32,25 @@ def torchao_from_source(): ) -def cu134_requirements(torch_url, include_domains=False): - if not torch_url.endswith("/cu134"): +def cuda_requirements(torch_url, include_domains=False): + variant = torch_url.rsplit("/", 1)[-1] + if not variant.startswith("cu"): return [] - packages = list( - CU134_TORCH_PACKAGES if include_domains else CU134_TORCH_PACKAGES[:1] - ) - if not torchao_from_source(): - torchao_variant = ( - "cpu" if platform.machine().lower() in ("aarch64", "arm64") else "cu134" + if variant not in {"cu132", "cu134"}: + raise RuntimeError( + f"Torch 2.15.0 has no released {variant} wheels. Use CUDA 13.2 or " + "13.4, or --use-pt-pinned-commit with an installed source build." ) - packages.append(f"torchao=={CU134_TORCHAO_NIGHTLY_VERSION}+{torchao_variant}") + packages = [ + f"{package}+{variant}" + for package in (TORCH_PACKAGES if include_domains else TORCH_PACKAGES[:1]) + ] + if not torchao_from_source(): + packages.append(f"torchao>={TORCHAO_MIN_VERSION}") return packages -# Since ExecuTorch often uses main-branch features of pytorch, only the nightly -# pip versions will have the required features. -# -# NOTE: If a newly-fetched version of the executorch repo changes the value of -# NIGHTLY_VERSION, you should re-run this script to install the necessary -# package versions. -# -# NOTE: If you're changing, make the corresponding change in .ci/docker/ci_commit_pins/pytorch.txt -# by picking the hash from the same date in -# https://hud.pytorch.org/hud/pytorch/pytorch/nightly/ @lint-ignore +# Keep the release packages aligned with .ci/docker/ci_commit_pins/pytorch.txt. # # NOTE: If you're changing, make the corresponding supported CUDA versions in # SUPPORTED_CUDA_VERSIONS in install_utils.py if needed. @@ -80,33 +68,24 @@ def install_requirements(use_pytorch_nightly): # Determine the appropriate PyTorch URL based on CUDA delegate status torch_url = determine_torch_url(TORCH_URL_BASE) - cu134_packages = cu134_requirements(torch_url) - if cu134_packages: - torch_url = determine_torch_url(TORCHAO_URL_BASE) - if not use_pytorch_nightly: - cu134_packages[0] = "torch" - # torchao's CUDA channel publishes x86_64 only, so asking for a CUDA build makes the pin - # unsatisfiable on aarch64. Only that case is special-cased: falling back everywhere would - # change which torchao a CPU x86_64 install resolves, and the CUDA build is genuinely wanted - # where it exists. This nightly is what a development checkout is tested against, and the - # wheel's own torchao lower bound is this same version so that installing the package - # afterwards leaves this pin in place rather than replacing it. + cuda_packages = cuda_requirements(torch_url) if use_pytorch_nightly else [] + # TorchAO's CUDA channel publishes x86_64 only, so use the CPU channel on + # aarch64. The installer and wheel metadata share a minimum version, allowing + # newer TorchAO packages without downgrading a preinstalled compatible build. if platform.machine().lower() in ("aarch64", "arm64"): - # The cpu channel specifically, not the index root. The root carries every variant, and a - # pin without a local segment admits all of them while ordering a local segment highest, - # so the xpu channel's pure python wheel would win on version before pip compares wheel - # tags, silently replacing the compiled aarch64 build. + # The index root carries every variant; a local segment sorts above the + # base version, so an XPU wheel could replace the compiled aarch64 build. torchao_url = f"{TORCHAO_URL_BASE}/cpu" else: torchao_url = determine_torch_url(TORCHAO_URL_BASE) # pip packages needed by exir. - TORCH_PACKAGE = cu134_packages or [ + TORCH_PACKAGE = cuda_packages or [ # Setting use_pytorch_nightly to false to test the pinned PyTorch commit. Note # that we don't need to set any version number there because they have already # been installed on CI before this step, so pip won't reinstall them - ("torch==2.14.0" if use_pytorch_nightly else "torch"), - f"torchao=={TORCHAO_NIGHTLY_VERSION}", + ("torch==2.15.0" if use_pytorch_nightly else "torch"), + f"torchao>={TORCHAO_MIN_VERSION}", ] # Install the requirements for core ExecuTorch package. @@ -177,10 +156,10 @@ def install_requirements(use_pytorch_nightly): # Without --no-build-isolation, setup.py can't find the torch module. "--no-build-isolation", *LOCAL_REQUIREMENTS, - *cu134_packages, + *cuda_packages, *( ["--extra-index-url", torch_url, "--extra-index-url", torchao_url] - if cu134_packages + if cuda_packages else [] ), ], @@ -192,23 +171,26 @@ def install_requirements(use_pytorch_nightly): def install_optional_example_requirements(use_pytorch_nightly): # Determine the appropriate PyTorch URL based on CUDA delegate status torch_url = determine_torch_url(TORCH_URL_BASE) - cu134_packages = ( - cu134_requirements(torch_url, include_domains=True) + cuda_packages = ( + cuda_requirements(torch_url, include_domains=True) if use_pytorch_nightly else [] ) - if cu134_packages: - torch_url = determine_torch_url(TORCHAO_URL_BASE) + torchao_url = ( + f"{TORCHAO_URL_BASE}/cpu" + if platform.machine().lower() in ("aarch64", "arm64") + else determine_torch_url(TORCHAO_URL_BASE) + ) torchao_index = ( - ["--extra-index-url", f"{TORCHAO_URL_BASE}/cpu"] - if cu134_packages and platform.machine().lower() in ("aarch64", "arm64") + ["--extra-index-url", torchao_url] + if cuda_packages and not torchao_from_source() else [] ) print("Installing torch domain libraries") - torchaudio_version = "2.11.1" if sys.version_info >= (3, 15) else "2.11.0" - DOMAIN_LIBRARIES = cu134_packages or [ - ("torchvision==0.29.0" if use_pytorch_nightly else "torchvision"), + torchaudio_version = "2.11.1" + DOMAIN_LIBRARIES = cuda_packages or [ + ("torchvision==0.29.2" if use_pytorch_nightly else "torchvision"), (f"torchaudio=={torchaudio_version}" if use_pytorch_nightly else "torchaudio"), ] # Then install domain libraries @@ -235,7 +217,7 @@ def install_optional_example_requirements(use_pytorch_nightly): "install", "-r", "requirements-examples.txt", - *cu134_packages, + *cuda_packages, "--extra-index-url", torch_url, *torchao_index, diff --git a/install_utils.py b/install_utils.py index 4e1397413b3..658b820a2d1 100644 --- a/install_utils.py +++ b/install_utils.py @@ -346,6 +346,12 @@ def determine_torch_url(torch_nightly_url_base): ) return f"{torch_nightly_url_base}/cpu" + if not is_cmake_option_on( + _cmake_args_from_env(), "EXECUTORCH_BUILD_CUDA", default=True + ): + print("CUDA delegate disabled, using CPU-only PyTorch") + return f"{torch_nightly_url_base}/cpu" + print("Attempting to detect CUDA via nvcc...") try: diff --git a/pyproject.toml b/pyproject.toml index 116552df1ed..74310df374e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,7 @@ requires-python = ">=3.11,<3.16" # computed in setup.py, so the EXECUTORCH_BUILD_MINIMAL wheel can ship a slimmer # set than the full wheel. See `_base_dependencies()` / `_minimal_dependencies()` # in setup.py. +# The 1.6 full wheel pins torch==2.15.0 and requires torchao>=0.18.0. [project.optional-dependencies] vgf = [ diff --git a/setup.py b/setup.py index e5fefa74bc1..7169d7d010e 100644 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ # Import this before distutils so that setuptools can intercept the distuils # imports. +import importlib.metadata import importlib.util import logging import os @@ -1116,12 +1117,7 @@ def _package_relative_depth(library: Path) -> int: def _torchao_requirement() -> str: - """The torchao dependency, pinned to the series install_requirements.py installs. - - Derived from that module rather than written out, so a nightly bump cannot move the - pin without moving this bound with it. A bump into the next series would otherwise - silently stop satisfying the lower bound, and installing this package over a - development checkout would replace the torchao that was just installed. + """The TorchAO minimum version, shared with install_requirements.py. Loaded by path, the way install_utils is above, because setuptools executes this file without the project directory on sys.path, so a plain import does not resolve. @@ -1136,14 +1132,7 @@ def _torchao_requirement() -> str: sys.modules.setdefault("install_utils", install_utils) spec.loader.exec_module(module) - version = module.TORCHAO_NIGHTLY_VERSION - if ( - install_utils.determine_torch_url(module.TORCH_URL_BASE).endswith("/cu134") - and not module.torchao_from_source() - ): - version = module.CU134_TORCHAO_NIGHTLY_VERSION - major, minor = (int(part) for part in version.split(".")[:2]) - return f"torchao>={version},<{major}.{minor + 1}" + return f"torchao>={module.TORCHAO_MIN_VERSION}" def _base_dependencies() -> List[str]: @@ -1153,6 +1142,11 @@ def _base_dependencies() -> List[str]: dynamic) so the minimal build can ship a slimmer set. Keep in sync with the project's runtime needs. """ + torch_requirement = "torch==2.15.0" + if os.environ.get("_EXECUTORCH_USE_PT_PINNED_COMMIT") == "1": + # CI source builds carry an a0+git version even at the release tag. + # Preserve the build that our extension libraries link against. + torch_requirement = f"torch=={importlib.metadata.version('torch')}" return [ "flatbuffers", "mpmath==1.3.0", @@ -1166,20 +1160,15 @@ def _base_dependencies() -> List[str]: "py-cpuinfo", "requests", "pytorch-tokenizers", + torch_requirement, # Shipped code imports torchao at module scope in many places, so a plain install cannot # lower a model without it. Among others: the XNNPACK utilities the partitioner uses # (backends/xnnpack/utils/utils.py), the Core ML quantizer, and executorch.export itself. # The MLX backend needs it too, though indirectly: it registers a torchao operator that # only exists once torchao has been imported. # - # The lower bound is the nightly install_requirements.py pins, so that installing this - # package over a development checkout leaves that pin in place. A bound at the stable - # release instead would evict it, because a dev release sorts below its own final. - # - # The upper bound is what makes naming a pre-release safe. A specifier that names one - # accepts pre-releases for this requirement, so without the bound pip would resolve a - # pre-release of the next series wherever one is published, such as an index carrying - # nightlies. Keep both bounds in the same series. + # Use the installer's minimum so a compatible preinstalled TorchAO build + # satisfies the wheel metadata, including newer nightly and release versions. _torchao_requirement(), "pyyaml", "ruamel.yaml", @@ -1223,7 +1212,8 @@ def _minimal_dependencies() -> List[str]: Derived as the subset of _base_dependencies() that executorch.exir needs to lower and serialize a .pte, so version pins and markers stay in sync with the - full set. torch is intentionally absent from both (consumers bring their own). + full set. torch is intentionally absent from the minimal wheel (consumers + bring their own). mpmath is intentionally dropped too: it is pulled transitively by sympy, whose "mpmath<1.4" cap resolves to the same 1.3.0 the full wheel pins. Keep the name set below in sync with the `expected` set in .ci/scripts/test_minimal_wheel.sh. diff --git a/torch_pin.py b/torch_pin.py index 5737d105625..6e8b8a81e82 100644 --- a/torch_pin.py +++ b/torch_pin.py @@ -1,2 +1 @@ -TORCH_VERSION = "2.14.0" -NIGHTLY_VERSION = "dev20261004" +TORCH_VERSION = "2.15.0"