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"]