diff --git a/.ci/docker/ci_commit_pins/pytorch.txt b/.ci/docker/ci_commit_pins/pytorch.txt
index 9f522bd9683..40f14cf46bf 100644
--- a/.ci/docker/ci_commit_pins/pytorch.txt
+++ b/.ci/docker/ci_commit_pins/pytorch.txt
@@ -1 +1 @@
-2bb2a9c522e7f944bf98c02c9b85fd959fa0bf0b
+a95d76a65dd39d7c6fe5c68b06f950b7fdf06a8f
diff --git a/.ci/scripts/test-rocm-aoti.sh b/.ci/scripts/test-rocm-aoti.sh
index b51fdcec068..32b5721e0f3 100644
--- a/.ci/scripts/test-rocm-aoti.sh
+++ b/.ci/scripts/test-rocm-aoti.sh
@@ -10,7 +10,6 @@ set -euo pipefail
ROCM_VERSION="${ROCM_VERSION:-7.2}"
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}}"
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,21 +21,19 @@ 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 < <(
+read -r TORCH_VERSION TORCHAO_VERSION TORCHAO_INDEX < <(
python - <<'PY'
-from install_requirements import ROCM_TORCHAO_NIGHTLY_VERSION
-from torch_pin import TORCH_VERSION
+from install_requirements import TORCHAO_INDEX_URL, TORCHAO_NIGHTLY_VERSION
+from torch_pin import ROCM_PYTORCH_VERSION
-print(TORCH_VERSION, ROCM_TORCHAO_NIGHTLY_VERSION)
+print(ROCM_PYTORCH_VERSION, TORCHAO_NIGHTLY_VERSION, f"{TORCHAO_INDEX_URL}/cpu")
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}"
+ "torchao==${TORCHAO_VERSION}" \
+ --extra-index-url "${TORCHAO_INDEX}"
EXECUTORCH_BUILD_MINIMAL=1 \
python -m pip install --editable . --no-build-isolation
@@ -54,7 +51,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..5c044b95690 100644
--- a/.ci/scripts/test-rocm-voxtral.sh
+++ b/.ci/scripts/test-rocm-voxtral.sh
@@ -12,7 +12,6 @@ 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}}"
VOXTRAL_CI_TMP_ROOT="${RUNNER_TEMP:-/tmp}"
if ! mkdir -p "${VOXTRAL_CI_TMP_ROOT}" 2>/dev/null ||
[[ ! -w "${VOXTRAL_CI_TMP_ROOT}" ]]; then
@@ -33,37 +32,20 @@ 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 < <(
+read -r TORCH_VERSION TORCHAO_VERSION TORCHAO_INDEX < <(
python - <<'PY'
-from install_requirements import ROCM_TORCHAO_NIGHTLY_VERSION
-from torch_pin import TORCH_VERSION
+from install_requirements import TORCHAO_INDEX_URL, TORCHAO_NIGHTLY_VERSION
+from torch_pin import ROCM_PYTORCH_VERSION
-print(TORCH_VERSION, ROCM_TORCHAO_NIGHTLY_VERSION)
+print(ROCM_PYTORCH_VERSION, TORCHAO_NIGHTLY_VERSION, f"{TORCHAO_INDEX_URL}/cpu")
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.
-python -m pip install --editable . --no-build-isolation --no-deps
-python - <<'PY' >"${VOXTRAL_CI_TMPDIR}/executorch-deps.txt"
-from importlib.metadata import requires
-
-from packaging.requirements import Requirement
-
-for spec in requires("executorch") or []:
- req = Requirement(spec)
- if req.name == "torchao":
- continue
- if req.marker is None or req.marker.evaluate({"extra": ""}):
- print(spec)
-PY
-python -m pip install -r "${VOXTRAL_CI_TMPDIR}/executorch-deps.txt"
+ "torchao==${TORCHAO_VERSION}" \
+ --extra-index-url "${TORCHAO_INDEX}"
+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 runtime libraries"
@@ -85,7 +67,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_wheel_package_qnn.sh b/.ci/scripts/test_wheel_package_qnn.sh
index 568c90ed14c..a74bd5afb6f 100644
--- a/.ci/scripts/test_wheel_package_qnn.sh
+++ b/.ci/scripts/test_wheel_package_qnn.sh
@@ -149,25 +149,17 @@ run_core_tests () {
echo "=== [$LABEL] Installing wheel & deps ==="
"$PIPBIN" install --upgrade pip
- TORCH_VERSION=$(
+ read -r PYTORCH_VERSION PYTORCH_INDEX_URL < <(
"$PYBIN" - <<'PY'
import runpy
module_vars = runpy.run_path("torch_pin.py")
-print(module_vars["TORCH_VERSION"])
+print(module_vars["PYTORCH_VERSION"], module_vars["PYTORCH_INDEX_URL"])
PY
)
+ echo "=== [$LABEL] Install torch==${PYTORCH_VERSION} ==="
-# NIGHTLY_VERSION=$(
-# "$PYBIN" - <<'PY'
-# import runpy
-# module_vars = runpy.run_path("torch_pin.py")
-# print(module_vars["NIGHTLY_VERSION"])
-# 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"
+ "$PIPBIN" install torch=="${PYTORCH_VERSION}" \
+ --index-url "${PYTORCH_INDEX_URL}/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
deleted file mode 100644
index ab424f41212..00000000000
--- a/.ci/scripts/tests/test_cu134_dependencies.py
+++ /dev/null
@@ -1,281 +0,0 @@
-# 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 ast
-import importlib.util
-import os
-import subprocess
-import sys
-import unittest
-from pathlib import Path
-from unittest.mock import patch
-
-from packaging.requirements import Requirement
-
-ROOT = Path(__file__).resolve().parents[3]
-
-
-def load_module(name):
- spec = importlib.util.spec_from_file_location(name, ROOT / f"{name}.py")
- module = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(module)
- return module
-
-
-class TestCu134Dependencies(unittest.TestCase):
- def setUp(self):
- self.utils = load_module("install_utils")
- self.modules = patch.dict(sys.modules, {"install_utils": self.utils})
- self.modules.start()
- self.addCleanup(self.modules.stop)
- self.installer = load_module("install_requirements")
-
- def install_commands(self, cuda, machine="x86_64", nightly=True, system="Linux"):
- self.utils.determine_torch_url.cache_clear()
- with (
- patch.dict(os.environ, {}, clear=True),
- patch.object(
- self.utils,
- "_get_cuda_version",
- return_value=cuda,
- side_effect=RuntimeError("no nvcc") if cuda is None else None,
- ),
- patch.object(self.installer.platform, "machine", return_value=machine),
- patch.object(self.installer.platform, "system", return_value=system),
- patch.object(self.installer.sys, "platform", "linux"),
- patch.object(self.installer.subprocess, "run") as run,
- ):
- self.installer.install_requirements(nightly)
- 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.14.0.dev20260810+cu134",
- "torchvision==0.29.0.dev20260811+cu134",
- "torchaudio==2.11.0.dev20260811+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
- )
- )
- 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
- )
-
- 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_source_pinned_torch_is_not_replaced(self):
- for cuda in ((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.14.0.dev20260810+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)
-
- 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)
-
- def test_failure_is_not_retried_with_another_cuda_train(self):
- with (
- patch.object(self.utils, "_get_cuda_version", return_value=(13, 4)),
- patch.object(self.installer.platform, "system", return_value="Linux"),
- patch.object(
- self.installer.subprocess,
- "run",
- side_effect=subprocess.CalledProcessError(1, "pip"),
- ) as run,
- ):
- with self.assertRaises(subprocess.CalledProcessError):
- self.installer.install_requirements(True)
- self.assertEqual(run.call_count, 1)
-
- def torchao_requirement(self):
- path = ROOT / "setup.py"
- tree = ast.parse(path.read_text())
- function = next(
- node
- for node in tree.body
- if isinstance(node, ast.FunctionDef) and node.name == "_torchao_requirement"
- )
- namespace = {
- "__file__": str(path),
- "Path": Path,
- "importlib": importlib,
- "sys": sys,
- "install_utils": self.utils,
- }
- exec(
- compile(ast.Module(body=[function], type_ignores=[]), str(path), "exec"),
- namespace,
- )
- return namespace["_torchao_requirement"]()
-
- def test_package_install_preserves_source_pinned_torchao(self):
- with patch.dict(sys.modules, {"install_requirements": self.installer}):
- package_installer = load_module("install_executorch")
- for machine in ("x86_64", "aarch64"):
- with self.subTest(machine=machine):
- self.utils.determine_torch_url.cache_clear()
- with (
- patch.dict(os.environ, {}, clear=True),
- patch.object(self.utils, "_get_cuda_version", return_value=(13, 4)),
- patch.object(
- self.installer.platform, "machine", return_value=machine
- ),
- patch.object(
- self.installer.platform, "system", return_value="Linux"
- ),
- patch.object(self.installer.sys, "platform", "linux"),
- patch.object(
- sys,
- "argv",
- ["install_executorch", "--use-pt-pinned-commit", "--minimal"],
- ),
- patch.object(
- package_installer, "python_is_compatible", return_value=True
- ),
- patch.object(package_installer, "check_and_update_submodules"),
- patch.object(self.installer.subprocess, "run") as run,
- ):
- package_installer.main([])
- commands = [call.args[0] for call in run.call_args_list]
- metadata = Requirement(self.torchao_requirement())
- self.assertEqual(len(commands), 3)
- self.assertIn(".", commands[-1])
- core = commands[0]
- torchao = Requirement(
- 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_cu134_keeps_explicit_torchao_source_build(self):
- with patch.dict(sys.modules, {"install_requirements": self.installer}):
- package_installer = load_module("install_executorch")
- for source_flag in (
- "EXECUTORCH_BUILD_KERNELS_TORCHAO",
- "TORCHAO_BUILD_EXPERIMENTAL_MPS",
- ):
- with self.subTest(source_flag=source_flag):
- self.utils.determine_torch_url.cache_clear()
- with (
- patch.dict(os.environ, {source_flag: "1"}, clear=True),
- patch.object(self.utils, "_get_cuda_version", return_value=(13, 4)),
- patch.object(
- self.installer.platform, "system", return_value="Linux"
- ),
- patch.object(self.installer.sys, "platform", "linux"),
- patch.object(sys, "argv", ["install_executorch"]),
- patch.object(
- package_installer, "python_is_compatible", return_value=True
- ),
- patch.object(package_installer, "check_and_update_submodules"),
- patch.object(self.installer.subprocess, "run") as run,
- ):
- package_installer.main([])
- metadata = Requirement(self.torchao_requirement())
- commands = [call.args[0] for call in run.call_args_list]
- self.assertEqual(len(commands), 5)
- 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.14.0.dev20260810+cu134", commands[-1])
- source_version = self.installer.TORCHAO_NIGHTLY_VERSION.partition(
- ".dev"
- )[0]
- source_commit = subprocess.run(
- ["git", "rev-parse", "HEAD:third-party/ao"],
- cwd=ROOT,
- capture_output=True,
- check=True,
- text=True,
- ).stdout.strip()
- self.assertIn(
- f"{source_version}+git{source_commit[:7]}", metadata.specifier
- )
-
- def test_wheel_torchao_bound_matches_selected_train(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}"
- self.utils.determine_torch_url.cache_clear()
- with (
- patch.object(
- self.utils,
- "_get_cuda_version",
- return_value=cuda,
- side_effect=RuntimeError("no nvcc") if cuda is None else None,
- ),
- patch.object(self.installer.platform, "system", return_value="Linux"),
- ):
- self.assertEqual(self.torchao_requirement(), expected)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/.ci/scripts/tests/test_cuda_workflow.py b/.ci/scripts/tests/test_cuda_workflow.py
index d05a18cac4d..e5aaee53484 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_supported_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.0", "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):
@@ -56,9 +54,8 @@ def test_cuda134_runtime_update_precedes_build_and_propagates_failure(self):
stubs = """
conda() { printf 'CONDA %s\n' "$*"; return "$CONDA_STATUS"; }
source() { printf 'BUILD %s\n' "$*"; }
-"""
+ """
for version, conda_status in (
- ("12.6", 0),
("13.0", 0),
("13.4", 0),
("13.4", 1),
diff --git a/.ci/scripts/tests/test_filter_cuda_matrix.py b/.ci/scripts/tests/test_filter_cuda_matrix.py
index 35a36cfe9df..8f3c7aa9c69 100644
--- a/.ci/scripts/tests/test_filter_cuda_matrix.py
+++ b/.ci/scripts/tests/test_filter_cuda_matrix.py
@@ -16,6 +16,7 @@
import json
import os
import re
+import runpy
import subprocess
import unittest
from pathlib import Path
@@ -37,6 +38,7 @@ def _load_module(name, path):
"filter_cuda_matrix", ROOT / ".github" / "scripts" / "filter_cuda_matrix.py"
)
INSTALL_UTILS = _load_module("install_utils", ROOT / "install_utils.py")
+DEPENDENCY_CONFIG = runpy.run_path(str(ROOT / "torch_pin.py"))
def _full_matrix():
@@ -284,7 +286,14 @@ class TestPublishedSets(unittest.TestCase):
"""
def test_published_cuda_versions(self):
- self.assertEqual(FILTER.SUPPORTED_CUDA_VERSIONS, ["cu130", "cu132", "cu134"])
+ self.assertEqual(
+ DEPENDENCY_CONFIG["CUDA_WHEEL_VERSIONS"],
+ ["cu130", "cu132", "cu134"],
+ )
+ self.assertEqual(
+ FILTER.SUPPORTED_CUDA_VERSIONS,
+ DEPENDENCY_CONFIG["CUDA_WHEEL_VERSIONS"],
+ )
def test_published_cuda_versions_are_documented(self):
# The install table on the getting started page is the only place a user is told
diff --git a/.ci/scripts/tests/test_pytorch_dependencies.py b/.ci/scripts/tests/test_pytorch_dependencies.py
new file mode 100644
index 00000000000..fc786d0231d
--- /dev/null
+++ b/.ci/scripts/tests/test_pytorch_dependencies.py
@@ -0,0 +1,152 @@
+# 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 importlib.util
+import os
+import subprocess
+import sys
+import unittest
+from pathlib import Path
+from unittest.mock import patch
+
+ROOT = Path(__file__).resolve().parents[3]
+
+
+def load_module(name):
+ spec = importlib.util.spec_from_file_location(name, ROOT / f"{name}.py")
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+
+
+class TestPytorchDependencies(unittest.TestCase):
+ def setUp(self):
+ self.utils = load_module("install_utils")
+ self.modules = patch.dict(sys.modules, {"install_utils": self.utils})
+ self.modules.start()
+ self.addCleanup(self.modules.stop)
+ self.installer = load_module("install_requirements")
+
+ def install_commands(self, cuda, machine="x86_64", nightly=True, system="Linux"):
+ self.utils.determine_torch_url.cache_clear()
+ with (
+ patch.dict(os.environ, {}, clear=True),
+ patch.object(
+ self.utils,
+ "_get_cuda_version",
+ return_value=cuda,
+ side_effect=RuntimeError("no nvcc") if cuda is None else None,
+ ),
+ patch.object(self.installer.platform, "machine", return_value=machine),
+ patch.object(self.installer.platform, "system", return_value=system),
+ patch.object(self.installer.sys, "platform", "linux"),
+ patch.object(self.installer.subprocess, "run") as run,
+ ):
+ self.installer.install_requirements(nightly)
+ self.installer.install_optional_example_requirements(nightly)
+ return [call.args[0] for call in run.call_args_list]
+
+ def test_every_binary_install_uses_one_pytorch_pin(self):
+ variants = [
+ (None, "cpu"),
+ *(
+ (cuda, f"cu{cuda[0]}{cuda[1]}")
+ for cuda in self.utils.SUPPORTED_CUDA_VERSIONS
+ ),
+ ]
+ for cuda, variant in variants:
+ for machine in ("x86_64", "aarch64"):
+ with self.subTest(cuda=cuda, machine=machine):
+ core, local, domains, examples = self.install_commands(
+ cuda, machine
+ )
+ self.assertIn(f"torch=={self.installer.PYTORCH_VERSION}", core)
+ self.assertIn(
+ f"torchvision=={self.installer.TORCHVISION_VERSION}", domains
+ )
+ self.assertIn(
+ f"torchaudio=={self.installer.TORCHAUDIO_VERSION}", domains
+ )
+ self.assertIn(f"{self.installer.PYTORCH_INDEX_URL}/{variant}", core)
+ self.assertFalse(any("==" in arg for arg in local))
+ self.assertFalse(any("==" in arg for arg in examples))
+
+ def test_package_dates_match_the_pytorch_pin(self):
+ versions = (
+ self.installer.PYTORCH_VERSION,
+ self.installer.TORCHVISION_VERSION,
+ self.installer.TORCHAUDIO_VERSION,
+ )
+ dates = {version.rsplit(".dev", 1)[-1] for version in versions}
+ self.assertEqual(len(dates), 1)
+ self.assertRegex(dates.pop(), r"^\d{8}$")
+
+ def test_source_pinned_torch_is_not_replaced(self):
+ for cuda in (*self.utils.SUPPORTED_CUDA_VERSIONS, None):
+ with self.subTest(cuda=cuda):
+ core, _, domains, _ = self.install_commands(cuda, nightly=False)
+ self.assertIn("torch", core)
+ self.assertFalse(any(arg.startswith("torch==") for arg in core))
+ self.assertIn("torchvision", domains)
+ self.assertIn("torchaudio", domains)
+
+ def test_windows_uses_the_cpu_nightly(self):
+ core, _, domains, _ = self.install_commands(
+ self.utils.SUPPORTED_CUDA_VERSIONS[-1], system="Windows"
+ )
+ self.assertIn(f"torch=={self.installer.PYTORCH_VERSION}", core)
+ self.assertIn(f"{self.installer.PYTORCH_INDEX_URL}/cpu", core)
+ self.assertIn(f"torchvision=={self.installer.TORCHVISION_VERSION}", domains)
+
+ def test_failure_is_not_retried_with_another_cuda_train(self):
+ with (
+ patch.object(
+ self.utils,
+ "_get_cuda_version",
+ return_value=self.utils.SUPPORTED_CUDA_VERSIONS[-1],
+ ),
+ patch.object(self.installer.platform, "system", return_value="Linux"),
+ patch.object(
+ self.installer.subprocess,
+ "run",
+ side_effect=subprocess.CalledProcessError(1, "pip"),
+ ) as run,
+ ):
+ with self.assertRaises(subprocess.CalledProcessError):
+ self.installer.install_requirements(True)
+ self.assertEqual(run.call_count, 1)
+
+ def test_explicit_torchao_source_build_omits_wheel_pin(self):
+ for source_flag in (
+ "EXECUTORCH_BUILD_KERNELS_TORCHAO",
+ "TORCHAO_BUILD_EXPERIMENTAL_MPS",
+ ):
+ with self.subTest(source_flag=source_flag):
+ self.utils.determine_torch_url.cache_clear()
+ with (
+ patch.dict(os.environ, {source_flag: "1"}, clear=True),
+ patch.object(
+ self.utils,
+ "_get_cuda_version",
+ return_value=self.utils.SUPPORTED_CUDA_VERSIONS[-1],
+ ),
+ patch.object(
+ self.installer.platform, "system", return_value="Linux"
+ ),
+ patch.object(self.installer.sys, "platform", "linux"),
+ patch.object(self.installer.subprocess, "run") as run,
+ ):
+ self.installer.install_requirements(True)
+ commands = [call.args[0] for call in run.call_args_list]
+ self.assertIn("third-party/ao", commands[1])
+ for command in commands:
+ self.assertFalse(
+ any(arg.startswith("torchao==") for arg in command)
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/.ci/scripts/tests/test_update_pytorch_pin.py b/.ci/scripts/tests/test_update_pytorch_pin.py
new file mode 100644
index 00000000000..0048abe34a7
--- /dev/null
+++ b/.ci/scripts/tests/test_update_pytorch_pin.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 importlib.util
+import os
+import tempfile
+import unittest
+from pathlib import Path
+from unittest.mock import patch
+
+ROOT = Path(__file__).resolve().parents[3]
+
+
+def load_updater():
+ path = ROOT / ".github" / "scripts" / "update_pytorch_pin.py"
+ spec = importlib.util.spec_from_file_location("update_pytorch_pin", path)
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+
+
+class Response:
+ def __init__(self, body):
+ self.body = body.encode()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *_args):
+ return None
+
+ def read(self):
+ return self.body
+
+
+class TestUpdatePytorchPin(unittest.TestCase):
+ def setUp(self):
+ self.updater = load_updater()
+
+ def test_full_wheel_version_selects_source_date(self):
+ self.assertEqual(
+ self.updater.parse_nightly_version("2.15.0.dev20260913"),
+ "2026-09-13",
+ )
+ with self.assertRaises(ValueError):
+ self.updater.parse_nightly_version("dev20260913")
+
+ def test_selects_latest_complete_package_date(self):
+ package_versions = {
+ "torch": "2.15.0",
+ "torchvision": "0.30.0",
+ "torchaudio": "2.11.0",
+ }
+
+ def index(request):
+ package = request.full_url.rstrip("/").rsplit("/", 1)[-1]
+ channel = request.full_url.rstrip("/").rsplit("/", 2)[-2]
+ platforms = (
+ self.updater.CPU_WHEEL_PLATFORMS
+ if channel == "cpu"
+ else self.updater.CUDA_WHEEL_PLATFORMS
+ )
+ versions = [f"{package_versions[package]}.dev20260912"]
+ if not (package == "torch" and channel == "cu134"):
+ versions.append(f"{package_versions[package]}.dev20260913")
+ links = "".join(
+ f'wheel'
+ for version in versions
+ for platform in platforms
+ )
+ # A newer publication that omitted the oldest supported Python must
+ # not displace the last complete wheel set.
+ links += "".join(
+ f'wheel'
+ for platform in platforms
+ )
+ return Response(links)
+
+ with patch.object(self.updater.urllib.request, "urlopen", side_effect=index):
+ selected = self.updater.get_pytorch_nightly_versions("20260914")
+ self.assertEqual(
+ selected,
+ {
+ package: f"{version}.dev20260912"
+ for package, version in package_versions.items()
+ },
+ )
+
+ def test_updates_the_single_package_configuration(self):
+ original_directory = Path.cwd()
+ with tempfile.TemporaryDirectory() as directory:
+ os.chdir(directory)
+ try:
+ Path("torch_pin.py").write_text((ROOT / "torch_pin.py").read_text())
+ self.updater.update_pytorch_package_pins(
+ {
+ "torch": "9.0.0.dev20990101",
+ "torchvision": "8.0.0.dev20990101",
+ "torchaudio": "7.0.0.dev20990101",
+ }
+ )
+ config = self.updater.runpy.run_path("torch_pin.py")
+ finally:
+ os.chdir(original_directory)
+ self.assertEqual(config["PYTORCH_VERSION"], "9.0.0.dev20990101")
+ self.assertEqual(config["TORCHVISION_VERSION"], "8.0.0.dev20990101")
+ self.assertEqual(config["TORCHAUDIO_VERSION"], "7.0.0.dev20990101")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/.githooks/README.md b/.githooks/README.md
index 796274c2e48..02fa8764d00 100644
--- a/.githooks/README.md
+++ b/.githooks/README.md
@@ -55,7 +55,7 @@ If the torch_pin hook fails:
1. Check that Python 3 is available in your PATH
2. Ensure you have internet connectivity to fetch commits from GitHub
-3. Verify that the `NIGHTLY_VERSION` in `torch_pin.py` is in the correct format (`devYYYYMMDD`)
+3. Verify that `PYTORCH_VERSION` in `torch_pin.py` is a full nightly wheel version
If lintrunner fails:
diff --git a/.github/scripts/filter_cuda_matrix.py b/.github/scripts/filter_cuda_matrix.py
index bdce1c3feab..b04297ad3f1 100644
--- a/.github/scripts/filter_cuda_matrix.py
+++ b/.github/scripts/filter_cuda_matrix.py
@@ -21,15 +21,21 @@
The aarch64 rows have no accelerator, so that check skips there and prints why. This filter
decides only which rows exist, not what each one checks.
-The values below are the current answers to those questions. They are written out rather
-than derived because each one is an external fact that can change independently.
+The values are configured in torch_pin.py rather than derived because each one is an
+external fact that can change independently.
"""
import argparse
import json
+import runpy
import sys
+from pathlib import Path
from typing import Any, Dict, List
+_DEPENDENCY_CONFIG = runpy.run_path(
+ str(Path(__file__).resolve().parents[2] / "torch_pin.py")
+)
+
# Python versions that are deliberately NOT published, with the reason, so a row naming one
# is rejected for a stated cause rather than for merely being absent from the supported list.
# The free-threaded builds are excluded because the CUDA dependencies are not published for
@@ -53,17 +59,11 @@
# Skip wholly absent trains so an upstream removal cannot block the remaining releases.
# Offered trains must still cover every supported Python version.
#
-# cu126 was the floor until PyTorch stopped offering it on the nightly channel. The generator
-# still offers it when a release is cut, so keeping it here would publish a train that no
-# nightly has built since, and a release is the wrong place to find out that it no longer
-# builds. A machine on CUDA 12.6 can still build from source, where the pinned torch comes
-# from a channel that carries 12.6.
-#
# cu132 is included because omitting it would leave a published consumer row with no
# ExecuTorch wheel to pair with. It is executable on a device one minor behind, since CUDA
# minor versions are compatible, so a cu132 wheel has been run end to end on a CUDA 13.0
# device. The packaging properties are checked on every row regardless.
-SUPPORTED_CUDA_VERSIONS: List[str] = ["cu130", "cu132", "cu134"]
+SUPPORTED_CUDA_VERSIONS: List[str] = _DEPENDENCY_CONFIG["CUDA_WHEEL_VERSIONS"]
# Python versions to publish, stated rather than derived for the same reason the CUDA
# versions are. Deriving them from the rows that survived the filter made the release
diff --git a/.github/scripts/update_pytorch_pin.py b/.github/scripts/update_pytorch_pin.py
index c6a6318e965..22b4ef78d7c 100644
--- a/.github/scripts/update_pytorch_pin.py
+++ b/.github/scripts/update_pytorch_pin.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
+import argparse
import base64
import json
import re
@@ -11,44 +12,157 @@
from urllib.parse import unquote
-TORCHAO_INDEX_URL = "https://download.pytorch.org/whl/nightly"
+_DEPENDENCY_CONFIG = runpy.run_path("torch_pin.py")
+PYTORCH_INDEX_URL = _DEPENDENCY_CONFIG["PYTORCH_INDEX_URL"]
+TORCHAO_INDEX_URL = _DEPENDENCY_CONFIG["TORCHAO_INDEX_URL"]
+PYTORCH_PACKAGES = ("torch", "torchvision", "torchaudio")
+REQUIRED_PYTHON_TAG = "cp310"
+CPU_WHEEL_PLATFORMS = (
+ "manylinux_2_28_x86_64",
+ "manylinux_2_28_aarch64",
+ "macosx_14_0_arm64",
+ "win_amd64",
+)
+CUDA_WHEEL_PLATFORMS = ("manylinux_2_28_x86_64", "manylinux_2_28_aarch64")
-def parse_nightly_version(nightly_version):
+def parse_nightly_version(pytorch_version):
"""
- Parse NIGHTLY_VERSION (e.g., 'dev20251004') to date string (e.g., '2025-10-04').
+ Parse a full nightly wheel version into its source-snapshot date.
Args:
- nightly_version: String in format 'devYYYYMMDD'
+ pytorch_version: Version such as '2.15.0.dev20251004'
Returns:
Date string in format 'YYYY-MM-DD'
"""
- match = re.match(r"dev(\d{4})(\d{2})(\d{2})", nightly_version)
+ match = re.fullmatch(r"\d+\.\d+\.\d+\.dev(\d{4})(\d{2})(\d{2})", pytorch_version)
if not match:
- raise ValueError(f"Invalid NIGHTLY_VERSION format: {nightly_version}")
+ raise ValueError(f"Invalid PyTorch nightly version: {pytorch_version}")
+ return format_nightly_date("".join(match.groups()))
+
+
+def format_nightly_date(nightly_date):
+ match = re.fullmatch(r"(\d{4})(\d{2})(\d{2})", nightly_date)
+ if not match:
+ raise ValueError(f"Invalid nightly date: {nightly_date}")
year, month, day = match.groups()
return f"{year}-{month}-{day}"
-def get_torch_nightly_version():
+def get_pytorch_version():
"""
- Read NIGHTLY_VERSION from torch_pin.py.
+ Read the authoritative PyTorch wheel version from torch_pin.py.
Returns:
- NIGHTLY_VERSION string
+ PYTORCH_VERSION string
"""
with open("torch_pin.py", "r") as f:
content = f.read()
- match = re.search(r'NIGHTLY_VERSION\s*=\s*["\']([^"\']+)["\']', content)
+ match = re.search(r'PYTORCH_VERSION\s*=\s*["\']([^"\']+)["\']', content)
if not match:
- raise ValueError("Could not find NIGHTLY_VERSION in torch_pin.py")
+ raise ValueError("Could not find PYTORCH_VERSION in torch_pin.py")
return match.group(1)
+def get_pytorch_nightly_versions(max_date):
+ """Return one compatible nightly package set available on every wheel train."""
+ config = runpy.run_path("torch_pin.py")
+ channels = ["cpu", *config["CUDA_WHEEL_VERSIONS"]]
+ versions_by_package = {}
+ common_dates = None
+
+ for package in PYTORCH_PACKAGES:
+ package_versions = {}
+ package_dates = None
+ for channel in channels:
+ url = f"{PYTORCH_INDEX_URL}/{channel}/{package}/"
+ request = urllib.request.Request(
+ url, headers={"User-Agent": "ExecuTorch-Bot"}
+ )
+ with urllib.request.urlopen(request) as response:
+ index_html = unquote(response.read().decode())
+ wheels_by_date = {}
+ pattern = re.compile(
+ rf"^{re.escape(package)}-(\d+\.\d+\.\d+\.dev(\d{{8}}))"
+ rf"(?:\+[^-]+)?-{REQUIRED_PYTHON_TAG}-[^-]+-.*\.whl$"
+ )
+ for link in re.findall(r'href="([^"]+)"', index_html):
+ filename = link.rsplit("/", 1)[-1].split("#", 1)[0]
+ match = pattern.match(filename)
+ if match is None:
+ continue
+ version, date = match.groups()
+ if date <= max_date:
+ versions, filenames = wheels_by_date.setdefault(date, (set(), []))
+ versions.add(version)
+ filenames.append(filename)
+
+ required_platforms = (
+ CPU_WHEEL_PLATFORMS if channel == "cpu" else CUDA_WHEEL_PLATFORMS
+ )
+ by_date = {
+ date: versions
+ for date, (versions, filenames) in wheels_by_date.items()
+ if all(
+ any(platform_name in filename for filename in filenames)
+ for platform_name in required_platforms
+ )
+ }
+ package_dates = (
+ set(by_date) if package_dates is None else package_dates & set(by_date)
+ )
+ for date, versions in by_date.items():
+ package_versions.setdefault(date, set()).update(versions)
+
+ package_dates = package_dates or set()
+ versions_by_package[package] = {
+ date: next(iter(package_versions[date]))
+ for date in package_dates
+ if len(package_versions[date]) == 1
+ }
+ common_dates = (
+ set(versions_by_package[package])
+ if common_dates is None
+ else common_dates & set(versions_by_package[package])
+ )
+
+ if not common_dates:
+ raise ValueError(
+ f"Could not find a PyTorch nightly on or before {max_date} for "
+ f"{', '.join(PYTORCH_PACKAGES)} on {', '.join(channels)}"
+ )
+ selected_date = max(common_dates)
+ return {
+ package: versions_by_package[package][selected_date]
+ for package in PYTORCH_PACKAGES
+ }
+
+
+def update_pytorch_package_pins(versions):
+ """Update the wheel versions that form the one selected PyTorch nightly."""
+ config_path = Path("torch_pin.py")
+ content = config_path.read_text()
+ assignments = {
+ "PYTORCH_VERSION": versions["torch"],
+ "TORCHVISION_VERSION": versions["torchvision"],
+ "TORCHAUDIO_VERSION": versions["torchaudio"],
+ }
+ for name, version in assignments.items():
+ content, count = re.subn(
+ rf'^(?P{name}\s*=\s*["\'])[^"\']+(?P["\'])$',
+ rf"\g{version}\g",
+ content,
+ flags=re.MULTILINE,
+ )
+ if count != 1:
+ raise ValueError(f"Could not find one {name} assignment in {config_path}")
+ config_path.write_text(content)
+
+
def get_json(url):
req = urllib.request.Request(url)
req.add_header("Accept", "application/vnd.github.v3+json")
@@ -68,7 +182,7 @@ def get_commit_hash_for_nightly(date_str):
Commit hash string
"""
api_url = "https://api.github.com/repos/pytorch/pytorch/commits"
- params = f"?sha=nightly&per_page=50"
+ params = "?sha=nightly&per_page=50"
url = api_url + params
try:
@@ -113,8 +227,8 @@ def update_pytorch_pin(commit_hash):
def get_supported_torchao_channels():
- cuda_versions = runpy.run_path("install_utils.py")["SUPPORTED_CUDA_VERSIONS"]
- return ["cpu", *(f"cu{major}{minor}" for major, minor in cuda_versions)]
+ cuda_versions = runpy.run_path("torch_pin.py")["CUDA_WHEEL_VERSIONS"]
+ return ["cpu", *cuda_versions]
def get_torchao_versions(channel):
@@ -164,7 +278,7 @@ def get_latest_torchao_nightly(max_date):
def get_torchao_commit_hash(nightly_version):
date = nightly_version.rsplit(".dev", 1)[-1]
- formatted_date = parse_nightly_version(f"dev{date}")
+ formatted_date = format_nightly_date(date)
url = (
"https://api.github.com/repos/pytorch/ao/actions/workflows/" # @lint-ignore
"build_wheels_linux_x86.yml/runs?event=schedule&status=success&"
@@ -179,17 +293,17 @@ def get_torchao_commit_hash(nightly_version):
def update_torchao_pins(nightly_version, commit_hash):
- requirements_path = Path("install_requirements.py")
- content = requirements_path.read_text()
- content, replacements = re.subn(
- r'^(?P(?:CU\d+_)?TORCHAO_NIGHTLY_VERSION\s*=\s*["\'])[^"\']+(?P["\'])$',
+ config_path = Path("torch_pin.py")
+ content = config_path.read_text()
+ content, default_replacements = re.subn(
+ r'^(?PTORCHAO_NIGHTLY_VERSION\s*=\s*["\'])[^"\']+(?P["\'])$',
rf"\g{nightly_version}\g",
content,
flags=re.MULTILINE,
)
- if not replacements:
- raise ValueError(f"Could not find TorchAO nightly pins in {requirements_path}")
- requirements_path.write_text(content)
+ if default_replacements != 1:
+ raise ValueError(f"Could not find the TorchAO nightly pin in {config_path}")
+ config_path.write_text(content)
for command in (
["git", "submodule", "update", "--init", "third-party/ao"],
@@ -350,13 +464,26 @@ def sync_c10_directories(commit_hash):
def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--nightly-date",
+ default="",
+ help="newest PyTorch nightly date to consider, in YYYYMMDD form",
+ )
+ args = parser.parse_args()
try:
- # Read NIGHTLY_VERSION from torch_pin.py
- nightly_version = get_torch_nightly_version()
- print(f"Found NIGHTLY_VERSION: {nightly_version}")
-
- # Parse to date string
- date_str = parse_nightly_version(nightly_version)
+ if args.nightly_date:
+ if re.fullmatch(r"\d{8}", args.nightly_date) is None:
+ raise ValueError("--nightly-date must use YYYYMMDD")
+ package_versions = get_pytorch_nightly_versions(args.nightly_date)
+ update_pytorch_package_pins(package_versions)
+ print(f"Selected PyTorch package versions: {package_versions}")
+
+ pytorch_version = get_pytorch_version()
+ print(f"Found PYTORCH_VERSION: {pytorch_version}")
+
+ # The wheel version is authoritative; derive its source snapshot date.
+ date_str = parse_nightly_version(pytorch_version)
print(f"Parsed date: {date_str}")
# Fetch commit hash from PyTorch nightly branch
diff --git a/.github/workflows/build-wheels-aarch64-linux.yml b/.github/workflows/build-wheels-aarch64-linux.yml
index eb79a9786c0..3db59284048 100644
--- a/.github/workflows/build-wheels-aarch64-linux.yml
+++ b/.github/workflows/build-wheels-aarch64-linux.yml
@@ -11,7 +11,9 @@ on:
- pyproject.toml
- setup.py
- docs/source/using-executorch-cpp.md
+ - install_requirements.py
- install_utils.py
+ - torch_pin.py
- tools/cmake/**/*
# The wheel ships these as its C++ SDK. Whole trees rather than the exact directories
# setup.py copies from, so adding one there cannot silently drop it from this list.
diff --git a/.github/workflows/build-wheels-cuda-aarch64-linux.yml b/.github/workflows/build-wheels-cuda-aarch64-linux.yml
index 882184d4dcc..134a1d4e9ab 100644
--- a/.github/workflows/build-wheels-cuda-aarch64-linux.yml
+++ b/.github/workflows/build-wheels-cuda-aarch64-linux.yml
@@ -15,6 +15,7 @@ on:
- install_utils.py
- pyproject.toml
- setup.py
+ - torch_pin.py
- docs/source/using-executorch-cpp.md
- tools/cmake/**/*
# The wheel ships these as its C++ SDK. Whole trees rather than the exact directories
diff --git a/.github/workflows/build-wheels-cuda-linux.yml b/.github/workflows/build-wheels-cuda-linux.yml
index 8de4ae4036c..3d324d75132 100644
--- a/.github/workflows/build-wheels-cuda-linux.yml
+++ b/.github/workflows/build-wheels-cuda-linux.yml
@@ -15,6 +15,7 @@ on:
- install_utils.py
- pyproject.toml
- setup.py
+ - torch_pin.py
- docs/source/using-executorch-cpp.md
- tools/cmake/**/*
# The wheel ships these as its C++ SDK. Whole trees rather than the exact directories
diff --git a/.github/workflows/build-wheels-linux.yml b/.github/workflows/build-wheels-linux.yml
index 15e6af41fb4..cf3332c456e 100644
--- a/.github/workflows/build-wheels-linux.yml
+++ b/.github/workflows/build-wheels-linux.yml
@@ -11,7 +11,9 @@ on:
- pyproject.toml
- setup.py
- docs/source/using-executorch-cpp.md
+ - install_requirements.py
- install_utils.py
+ - torch_pin.py
- tools/cmake/**/*
# The wheel ships these as its C++ SDK. Whole trees rather than the exact directories
# setup.py copies from, so adding one there cannot silently drop it from this list.
diff --git a/.github/workflows/build-wheels-macos.yml b/.github/workflows/build-wheels-macos.yml
index feb8b8f7b69..af33d67043e 100644
--- a/.github/workflows/build-wheels-macos.yml
+++ b/.github/workflows/build-wheels-macos.yml
@@ -11,7 +11,9 @@ on:
- pyproject.toml
- setup.py
- docs/source/using-executorch-cpp.md
+ - install_requirements.py
- install_utils.py
+ - torch_pin.py
- tools/cmake/**/*
# The wheel ships these as its C++ SDK. Whole trees rather than the exact directories
# setup.py copies from, so adding one there cannot silently drop it from this list.
diff --git a/.github/workflows/build-wheels-windows.yml b/.github/workflows/build-wheels-windows.yml
index 8d6cac2c9c6..31907d6fe95 100644
--- a/.github/workflows/build-wheels-windows.yml
+++ b/.github/workflows/build-wheels-windows.yml
@@ -10,7 +10,9 @@ on:
- pyproject.toml
- setup.py
- docs/source/using-executorch-cpp.md
+ - install_requirements.py
- install_utils.py
+ - torch_pin.py
- tools/cmake/**/*
# The wheel ships these as its C++ SDK. Whole trees rather than the exact directories
# setup.py copies from, so adding one there cannot silently drop it from this list.
diff --git a/.github/workflows/cuda.yml b/.github/workflows/cuda.yml
index dea94bf4ac7..e9535f833a2 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.0, 13.4) using the command:
# ./install_executorch.sh
#
# Intentionally skipped CUDA version 13.2 check due to ci image unsupported.
@@ -66,7 +66,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- cuda-version: ["12.6", "13.0", "13.4"]
+ cuda-version: ["13.0", "13.4"]
name: test-executorch-cuda-build-${{ matrix.cuda-version }}
uses: pytorch/test-infra/.github/workflows/linux_job_v3.yml@main
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 66c1e7d0e92..f78859f3a7e 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -48,12 +48,14 @@ jobs:
- name: Install dependencies
run: |
- TORCH_VERSION=$(python -c "from torch_pin import TORCH_VERSION; print(TORCH_VERSION)")
+ read -r PYTORCH_VERSION TORCHVISION_VERSION TORCHAUDIO_VERSION PYTORCH_INDEX_URL < <(
+ python -c "from torch_pin import PYTORCH_VERSION, PYTORCH_INDEX_URL, TORCHVISION_VERSION, TORCHAUDIO_VERSION; print(PYTORCH_VERSION, TORCHVISION_VERSION, TORCHAUDIO_VERSION, PYTORCH_INDEX_URL)"
+ )
pip install \
- "torch==${TORCH_VERSION}" \
- torchvision \
- torchaudio \
- --index-url https://download.pytorch.org/whl/cpu
+ "torch==${PYTORCH_VERSION}" \
+ "torchvision==${TORCHVISION_VERSION}" \
+ "torchaudio==${TORCHAUDIO_VERSION}" \
+ --index-url "${PYTORCH_INDEX_URL}/cpu"
pip install lintrunner==0.12.7 lintrunner-adapters==0.14.1
pip install -r requirements-lintrunner.txt
USE_CPP=0 pip install --no-build-isolation third-party/ao
diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml
index 5c4391ee69e..bc8092ee935 100644
--- a/.github/workflows/pull.yml
+++ b/.github/workflows/pull.yml
@@ -91,6 +91,7 @@ jobs:
contains(needs.changed-files.outputs.changed-files, 'extension/flat_tensor') ||
contains(needs.changed-files.outputs.changed-files, 'extension/pytree') ||
contains(needs.changed-files.outputs.changed-files, 'pyproject.toml') ||
+ contains(needs.changed-files.outputs.changed-files, 'torch_pin.py') ||
contains(needs.changed-files.outputs.changed-files, 'schema/') ||
contains(needs.changed-files.outputs.changed-files, 'setup.py') ||
contains(needs.changed-files.outputs.changed-files, 'tools/cmake/')
diff --git a/.github/workflows/weekly-pytorch-pin-bump.yml b/.github/workflows/weekly-pytorch-pin-bump.yml
index 0aecdaa7594..535cc166541 100644
--- a/.github/workflows/weekly-pytorch-pin-bump.yml
+++ b/.github/workflows/weekly-pytorch-pin-bump.yml
@@ -22,39 +22,31 @@ jobs:
with:
python-version: '3.11'
- - name: Determine nightly version
+ - name: Determine newest nightly date
id: nightly
run: |
NIGHTLY_DATE=$(date -u -d 'yesterday' '+%Y%m%d')
- NIGHTLY_VERSION="dev${NIGHTLY_DATE}"
- echo "version=${NIGHTLY_VERSION}" >> "$GITHUB_OUTPUT"
+ echo "date=${NIGHTLY_DATE}" >> "$GITHUB_OUTPUT"
- - name: Read current TORCH_VERSION
- id: torch
- run: |
- TORCH_VERSION=$(python -c "exec(open('torch_pin.py').read()); print(TORCH_VERSION)")
- echo "version=${TORCH_VERSION}" >> "$GITHUB_OUTPUT"
+ - name: Run pin bump script
+ run: python .github/scripts/update_pytorch_pin.py --nightly-date "${{ steps.nightly.outputs.date }}"
- - name: Update torch_pin.py with new NIGHTLY_VERSION
+ - name: Read selected PyTorch version
+ id: pytorch
run: |
- printf 'TORCH_VERSION = "%s"\nNIGHTLY_VERSION = "%s"\n' \
- "${{ steps.torch.outputs.version }}" \
- "${{ steps.nightly.outputs.version }}" > torch_pin.py
-
- - name: Run pin bump script
- run: python .github/scripts/update_pytorch_pin.py
+ PYTORCH_VERSION=$(python -c "from torch_pin import PYTORCH_VERSION; print(PYTORCH_VERSION)")
+ echo "version=${PYTORCH_VERSION}" >> "$GITHUB_OUTPUT"
- name: Create branch and PR
env:
GH_TOKEN: ${{ secrets.UPDATEBOT_TOKEN }}
run: |
- BRANCH="automated/pytorch-pin-bump-${{ steps.nightly.outputs.version }}"
+ BRANCH="automated/pytorch-pin-bump-${{ steps.pytorch.outputs.version }}"
git config user.name "pytorchbot"
git config user.email "pytorchbot@users.noreply.github.com"
git checkout -b "${BRANCH}"
git add torch_pin.py
- git add install_requirements.py
git add .ci/docker/ci_commit_pins/pytorch.txt
git add runtime/core/portable_type/c10/
git add third-party/ao
@@ -64,7 +56,7 @@ jobs:
exit 0
fi
- git commit -m "Bump PyTorch and TorchAO pins for ${{ steps.nightly.outputs.version }}"
+ git commit -m "Bump PyTorch and TorchAO pins for ${{ steps.pytorch.outputs.version }}"
git push -u origin "${BRANCH}"
EXISTING=$(gh pr list --label "ci/pytorch-pin-bump" --state open --json number --jq '.[0].number')
@@ -73,13 +65,13 @@ jobs:
gh pr close "${EXISTING}" --comment "Superseded by newer pin bump."
fi
- NIGHTLY="${{ steps.nightly.outputs.version }}"
+ NIGHTLY="${{ steps.pytorch.outputs.version }}"
read -r -d '' PR_BODY < str:
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}"
diff --git a/torch_pin.py b/torch_pin.py
index 3010f4a22ed..88b1e4def71 100644
--- a/torch_pin.py
+++ b/torch_pin.py
@@ -1,2 +1,19 @@
-TORCH_VERSION = "2.14.0"
-NIGHTLY_VERSION = "dev20260913"
+"""Dependency and wheel-train versions shared by installation and release tooling."""
+
+# The one PyTorch wheel train used by main. Its embedded nightly date also
+# selects the matching source commit and c10 headers; those are derived outputs,
+# not an independently chosen pin.
+PYTORCH_VERSION = "2.15.0.dev20260922"
+PYTORCH_INDEX_URL = "https://download.pytorch.org/whl/nightly"
+TORCHVISION_VERSION = "0.30.0.dev20260922"
+TORCHAUDIO_VERSION = "2.11.0.dev20260922"
+
+TORCHAO_INDEX_URL = PYTORCH_INDEX_URL
+TORCHAO_NIGHTLY_VERSION = "0.19.0.dev20260907"
+# PyTorch no longer publishes current ROCm nightlies. These jobs remain on the
+# newest compatible test-index wheel instead of silently weakening the main pin.
+ROCM_PYTORCH_VERSION = "2.14.0"
+
+# CUDA wheel trains supported by main. Release preparation owns any temporary
+# filtering needed for a particular release.
+CUDA_WHEEL_VERSIONS = ["cu130", "cu132", "cu134"]