Skip to content

Commit 8bda9ac

Browse files
mergennachinclaude
andauthored
Centralize torch-family version pins in torch_pin.py (#19155)
Centralize torch-family version pins in torch_pin.py torch_pin.py is now the single source of truth for torch + the three domain libraries (vision/audio/codec). It exposes a CHANNEL field (nightly/test/release), the four version constants, NIGHTLY_VERSION, and helpers — torch_spec() / torchaudio_spec() / torchcodec_spec() / torchvision_spec() emit the right pip spec, torch_index_url_base() returns the right wheel index, and torch_branch() / torchaudio_branch() / torchvision_branch() derive the upstream release/M.N branch from each package's version. Every consumer — install_requirements.py, the two install_pytorch.sh / utils.sh shell helpers, test_model_e2e.sh, test_wheel_package_qnn.sh, the moshi/mimi install_requirements.sh, the update_pytorch_pin.py script, and the weekly bump workflow — reads through these helpers instead of re-encoding the version strings. Switching to a release candidate is now a one-line change (CHANNEL = "test") plus bumping the four version constants. The header in torch_pin.py walks through the procedure. update_pytorch_pin.py imports CHANNEL / NIGHTLY_VERSION / torch_branch directly (no more regex parsing of the file). For nightly it pins to an immutable SHA looked up by date; for test/release it writes torch_branch() (e.g. "release/2.12") into .ci/docker/ci_commit_pins/pytorch.txt so git checkout follows cherry-picks as they land. The weekly-pytorch-pin-bump workflow is guarded on CHANNEL == "nightly" and uses an in-place re.sub on NIGHTLY_VERSION (the previous `printf '...' > torch_pin.py` would have clobbered the new constants and helpers). test/test_torch_pin.py covers all three channels, all four specs, and the release/M.N branch derivation. Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent ea69046 commit 8bda9ac

14 files changed

Lines changed: 348 additions & 130 deletions

File tree

.ci/docker/build.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,18 @@ esac
9292
TORCH_VERSION=$(cat ci_commit_pins/pytorch.txt)
9393
BUILD_DOCS=1
9494

95+
# Pull channel + spec/url helpers out of torch_pin.py so install_pytorch.sh
96+
# (which runs inside the docker build, where torch_pin.py isn't available)
97+
# can decide between wheel install (test/release) and source build (nightly).
98+
# Self-hosted runners often have python3 but not the unversioned python alias.
99+
PYTHON_BIN=$(command -v python3 || command -v python)
100+
TORCH_PIN_HELPERS=$(cd ../.. && "$PYTHON_BIN" -c "from torch_pin import CHANNEL, torch_spec, torchaudio_spec, torchvision_spec, torch_index_url_base; print(CHANNEL); print(torch_spec()); print(torchaudio_spec()); print(torchvision_spec()); print(torch_index_url_base())")
101+
TORCH_CHANNEL=$(echo "${TORCH_PIN_HELPERS}" | sed -n '1p')
102+
TORCH_SPEC=$(echo "${TORCH_PIN_HELPERS}" | sed -n '2p')
103+
TORCHAUDIO_SPEC=$(echo "${TORCH_PIN_HELPERS}" | sed -n '3p')
104+
TORCHVISION_SPEC=$(echo "${TORCH_PIN_HELPERS}" | sed -n '4p')
105+
TORCH_INDEX_URL=$(echo "${TORCH_PIN_HELPERS}" | sed -n '5p')
106+
95107
# Copy requirements-lintrunner.txt from root to here
96108
cp ../../requirements-lintrunner.txt ./
97109

@@ -104,6 +116,11 @@ docker build \
104116
--build-arg "PYTHON_VERSION=${PYTHON_VERSION}" \
105117
--build-arg "MINICONDA_VERSION=${MINICONDA_VERSION}" \
106118
--build-arg "TORCH_VERSION=${TORCH_VERSION}" \
119+
--build-arg "TORCH_CHANNEL=${TORCH_CHANNEL}" \
120+
--build-arg "TORCH_SPEC=${TORCH_SPEC}" \
121+
--build-arg "TORCHAUDIO_SPEC=${TORCHAUDIO_SPEC}" \
122+
--build-arg "TORCHVISION_SPEC=${TORCHVISION_SPEC}" \
123+
--build-arg "TORCH_INDEX_URL=${TORCH_INDEX_URL}" \
107124
--build-arg "BUCK2_VERSION=${BUCK2_VERSION}" \
108125
--build-arg "LINTRUNNER=${LINTRUNNER:-}" \
109126
--build-arg "BUILD_DOCS=${BUILD_DOCS}" \
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
release/2.11
1+
release/2.11

.ci/docker/common/install_pytorch.sh

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ install_domains() {
1717
}
1818

1919
install_pytorch_and_domains() {
20+
if [ "${TORCH_CHANNEL}" != "nightly" ]; then
21+
# Test/release: install the published wheels directly. The specs and URL
22+
# are passed in as docker build args (computed from torch_pin.py by
23+
# .ci/docker/build.sh). RC wheels at /whl/test/ get re-uploaded under the
24+
# same version, so use --no-cache-dir there to avoid stale cache hits.
25+
local cache_flag=""
26+
if [ "${TORCH_CHANNEL}" = "test" ]; then
27+
cache_flag="--no-cache-dir"
28+
fi
29+
pip_install --force-reinstall ${cache_flag} \
30+
"${TORCH_SPEC}" "${TORCHVISION_SPEC}" "${TORCHAUDIO_SPEC}" \
31+
--index-url "${TORCH_INDEX_URL}/cpu"
32+
return
33+
fi
34+
35+
# Nightly: build pytorch from source against the pinned SHA in pytorch.txt
36+
# so we catch upstream regressions, then install audio/vision from the
37+
# commits that pytorch itself pins.
2038
git clone https://github.com/pytorch/pytorch.git
2139

2240
# Fetch the target commit
@@ -27,14 +45,19 @@ install_pytorch_and_domains() {
2745
chown -R ci-user .
2846

2947
export _GLIBCXX_USE_CXX11_ABI=1
48+
# PyTorch's FindARM.cmake hard-fails when the SVE+BF16 compile probe
49+
# doesn't pass — gcc-11 in this image is too old to accept the combined
50+
# NEON/SVE/bfloat16 intrinsics the probe exercises. Executorch's aarch64
51+
# runtime targets (phones, embedded) don't use SVE, so bypass the check.
52+
export BUILD_IGNORE_SVE_UNAVAILABLE=1
3053
# Then build and install PyTorch
3154
conda_run python setup.py bdist_wheel
3255
pip_install "$(echo dist/*.whl)"
3356

34-
# Grab the pinned audio and vision commits from PyTorch
35-
TORCHAUDIO_VERSION=release/2.11
57+
# Defer to PyTorch's own pinned audio/vision commits.
58+
TORCHAUDIO_VERSION=$(cat .github/ci_commit_pins/audio.txt)
3659
export TORCHAUDIO_VERSION
37-
TORCHVISION_VERSION=release/0.26
60+
TORCHVISION_VERSION=$(cat .github/ci_commit_pins/vision.txt)
3861
export TORCHVISION_VERSION
3962

4063
install_domains

.ci/docker/ubuntu/Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ ENV SCCACHE_S3_KEY_PREFIX executorch
6464
ENV SCCACHE_REGION us-east-1
6565

6666
ARG TORCH_VERSION
67+
ARG TORCH_CHANNEL
68+
ARG TORCH_SPEC
69+
ARG TORCHAUDIO_SPEC
70+
ARG TORCHVISION_SPEC
71+
ARG TORCH_INDEX_URL
6772
ARG SKIP_PYTORCH
6873
COPY ./common/install_pytorch.sh install_pytorch.sh
6974
COPY ./common/utils.sh utils.sh

.ci/scripts/test_model_e2e.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,10 @@ if [ "$AUDIO_URL" != "" ]; then
260260
elif [[ "$MODEL_NAME" == *whisper* ]] || [ "$MODEL_NAME" = "voxtral_realtime" ]; then
261261
conda install -y -c conda-forge "ffmpeg<8"
262262
pip install datasets soundfile
263-
pip install torchcodec==0.11.0 --extra-index-url https://download.pytorch.org/whl/test/cpu
263+
# We pushd'd into EXECUTORCH_ROOT above, so torch_pin is importable here.
264+
TORCHCODEC_PKG=$(python -c "from torch_pin import torchcodec_spec; print(torchcodec_spec())")
265+
TORCHCODEC_INDEX=$(python -c "from torch_pin import torch_index_url_base; print(torch_index_url_base())")
266+
pip install "$TORCHCODEC_PKG" --extra-index-url "${TORCHCODEC_INDEX}/cpu"
264267
python -c "from datasets import load_dataset;import soundfile as sf;sample = load_dataset('distil-whisper/librispeech_long', 'clean', split='validation')[0]['audio'];sf.write('${MODEL_DIR}/$AUDIO_FILE', sample['array'][:sample['sampling_rate']*30], sample['sampling_rate'])"
265268
fi
266269

.ci/scripts/test_wheel_package_qnn.sh

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -150,25 +150,26 @@ run_core_tests () {
150150
echo "=== [$LABEL] Installing wheel & deps ==="
151151
"$PIPBIN" install --upgrade pip
152152
"$PIPBIN" install "$WHEEL_FILE"
153-
TORCH_VERSION=$(
153+
# runpy.run_path uses a relative path, so the caller must run this script
154+
# from the executorch repo root (where torch_pin.py lives).
155+
TORCH_SPEC=$(
154156
"$PYBIN" - <<'PY'
155157
import runpy
156158
module_vars = runpy.run_path("torch_pin.py")
157-
print(module_vars["TORCH_VERSION"])
159+
print(module_vars["torch_spec"]())
158160
PY
159161
)
162+
TORCH_INDEX=$(
163+
"$PYBIN" - <<'PY'
164+
import runpy
165+
module_vars = runpy.run_path("torch_pin.py")
166+
print(module_vars["torch_index_url_base"]())
167+
PY
168+
)
169+
echo "=== [$LABEL] Install $TORCH_SPEC from ${TORCH_INDEX}/cpu ==="
160170

161-
# NIGHTLY_VERSION=$(
162-
# "$PYBIN" - <<'PY'
163-
# import runpy
164-
# module_vars = runpy.run_path("torch_pin.py")
165-
# print(module_vars["NIGHTLY_VERSION"])
166-
# PY
167-
# )
168-
echo "=== [$LABEL] Install torch==${TORCH_VERSION} ==="
169-
170-
# Install torch based on the pinned PyTorch version, preferring the PyTorch test index
171-
"$PIPBIN" install torch=="${TORCH_VERSION}" --extra-index-url "https://download.pytorch.org/whl/test"
171+
# Install torch based on the pinned PyTorch version from the channel index.
172+
"$PIPBIN" install "$TORCH_SPEC" --index-url "${TORCH_INDEX}/cpu"
172173
"$PIPBIN" install wheel
173174

174175
# Install torchao based on the pinned commit from third-party/ao submodule
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import importlib
2+
3+
import pytest
4+
5+
6+
@pytest.fixture
7+
def pin():
8+
"""Yield a fresh import of torch_pin so tests can mutate CHANNEL safely."""
9+
import torch_pin
10+
11+
yield torch_pin
12+
importlib.reload(torch_pin)
13+
14+
15+
@pytest.mark.parametrize(
16+
"channel, expected_torch, expected_url",
17+
[
18+
(
19+
"nightly",
20+
"torch=={TORCH_VERSION}.{NIGHTLY_VERSION}",
21+
"https://download.pytorch.org/whl/nightly",
22+
),
23+
("test", "torch=={TORCH_VERSION}", "https://download.pytorch.org/whl/test"),
24+
("release", "torch=={TORCH_VERSION}", "https://download.pytorch.org/whl"),
25+
],
26+
)
27+
def test_channel_resolution(pin, channel, expected_torch, expected_url):
28+
pin.CHANNEL = channel
29+
expected = expected_torch.format(
30+
TORCH_VERSION=pin.TORCH_VERSION, NIGHTLY_VERSION=pin.NIGHTLY_VERSION
31+
)
32+
assert pin.torch_spec() == expected
33+
assert pin.torch_index_url_base() == expected_url
34+
35+
36+
def test_all_specs_share_nightly_suffix(pin):
37+
pin.CHANNEL = "nightly"
38+
suffix = f".{pin.NIGHTLY_VERSION}"
39+
assert pin.torch_spec().endswith(suffix)
40+
assert pin.torchaudio_spec().endswith(suffix)
41+
assert pin.torchcodec_spec().endswith(suffix)
42+
assert pin.torchvision_spec().endswith(suffix)
43+
44+
45+
def test_specs_drop_suffix_off_nightly(pin):
46+
pin.CHANNEL = "test"
47+
assert pin.torch_spec() == f"torch=={pin.TORCH_VERSION}"
48+
assert pin.torchaudio_spec() == f"torchaudio=={pin.TORCHAUDIO_VERSION}"
49+
assert pin.torchcodec_spec() == f"torchcodec=={pin.TORCHCODEC_VERSION}"
50+
assert pin.torchvision_spec() == f"torchvision=={pin.TORCHVISION_VERSION}"
51+
52+
53+
def test_torch_branch_derived_from_version(pin):
54+
assert pin.torch_branch() == f"release/{pin.TORCH_VERSION.rsplit('.', 1)[0]}"

.ci/scripts/utils.sh

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ dedupe_macos_loader_path_rpaths() {
5353
pushd ..
5454
torch_lib_dir=$(python -c "import importlib.util; print(importlib.util.find_spec('torch').submodule_search_locations[0])")/lib
5555
popd
56-
56+
5757
if [[ -z "${torch_lib_dir}" || ! -d "${torch_lib_dir}" ]]; then
5858
return
5959
fi
@@ -89,6 +89,30 @@ install_domains() {
8989
}
9090

9191
install_pytorch_and_domains() {
92+
# CWD is the executorch repo root, where torch_pin.py lives.
93+
TORCH_CHANNEL=$(python -c "from torch_pin import CHANNEL; print(CHANNEL)")
94+
95+
if [ "${TORCH_CHANNEL}" != "nightly" ]; then
96+
# Test/release: install the published wheels directly from torch_pin.py's
97+
# channel index, skipping the source-build path entirely. RC wheels at
98+
# /whl/test/ get re-uploaded under the same version, so use --no-cache-dir
99+
# there to avoid stale cache hits.
100+
local torch_spec=$(python -c "from torch_pin import torch_spec; print(torch_spec())")
101+
local torchvision_spec=$(python -c "from torch_pin import torchvision_spec; print(torchvision_spec())")
102+
local torchaudio_spec=$(python -c "from torch_pin import torchaudio_spec; print(torchaudio_spec())")
103+
local torch_index_url=$(python -c "from torch_pin import torch_index_url_base; print(torch_index_url_base())")
104+
local cache_flag=""
105+
if [ "${TORCH_CHANNEL}" = "test" ]; then
106+
cache_flag="--no-cache-dir"
107+
fi
108+
pip install --force-reinstall ${cache_flag} \
109+
"${torch_spec}" "${torchvision_spec}" "${torchaudio_spec}" \
110+
--index-url "${torch_index_url}/cpu"
111+
return
112+
fi
113+
114+
# Nightly: source-build pytorch from the pinned SHA so CI catches upstream
115+
# regressions; pytorch's own audio/vision pins drive those installs.
92116
pushd .ci/docker || return
93117
TORCH_VERSION=$(cat ci_commit_pins/pytorch.txt)
94118
popd || return
@@ -140,10 +164,10 @@ install_pytorch_and_domains() {
140164
fi
141165

142166
dedupe_macos_loader_path_rpaths
143-
# Grab the pinned audio and vision commits from PyTorch
144-
TORCHAUDIO_VERSION=release/2.11
167+
# We're on the nightly path here; defer to PyTorch's own pinned commits.
168+
TORCHAUDIO_VERSION=$(cat .github/ci_commit_pins/audio.txt)
145169
export TORCHAUDIO_VERSION
146-
TORCHVISION_VERSION=release/0.26
170+
TORCHVISION_VERSION=$(cat .github/ci_commit_pins/vision.txt)
147171
export TORCHVISION_VERSION
148172

149173
install_domains
@@ -218,17 +242,21 @@ download_stories_model_artifacts() {
218242
}
219243

220244
do_not_use_nightly_on_ci() {
221-
# An assert to make sure that we are not using PyTorch nightly on CI to prevent
222-
# regression as documented in https://github.com/pytorch/executorch/pull/6564
223-
TORCH_VERSION=$(pip list | grep -w 'torch ' | awk -F ' ' {'print $2'} | tr -d '\n')
245+
# Sanity check that prevents accidentally landing a PR that pins to PyTorch
246+
# nightly without exercising the source-build path (see #6564).
247+
#
248+
# For CHANNEL=nightly, CI source-builds pytorch from the SHA in pytorch.txt,
249+
# so the installed torch shows up as e.g. 2.13.0a0+gitc8a648d — assert that.
250+
# For CHANNEL=test/release, we install published wheels by design (e.g.
251+
# 2.11.0), so the +git assertion doesn't apply.
252+
TORCH_CHANNEL=$(python -c "from torch_pin import CHANNEL; print(CHANNEL)")
253+
if [ "${TORCH_CHANNEL}" != "nightly" ]; then
254+
return 0
255+
fi
224256

225-
# The version of PyTorch building from source looks like 2.6.0a0+gitc8a648d that
226-
# includes the commit while nightly (2.6.0.dev20241019+cpu) or release (2.6.0)
227-
# won't have that. Note that we couldn't check for the exact commit from the pin
228-
# ci_commit_pins/pytorch.txt here because the value will be different when running
229-
# this on PyTorch CI
257+
TORCH_VERSION=$(pip list | grep -w 'torch ' | awk -F ' ' {'print $2'} | tr -d '\n')
230258
if [[ "${TORCH_VERSION}" != *"+git"* ]]; then
231-
echo "Unexpected torch version. Expected binary built from source, got ${TORCH_VERSION}"
259+
echo "Unexpected torch version. Expected binary built from source for CHANNEL=nightly, got ${TORCH_VERSION}"
232260
exit 1
233261
fi
234262
}

0 commit comments

Comments
 (0)