From 3f79b5e53d461b7764e6a9688e92beae04adf73d Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Mon, 20 Jul 2026 10:44:42 +0000 Subject: [PATCH] install: make torch 2.11 the default supported line on wheel-backed platforms Fresh installs now resolve the torch 2.11 trio (torch 2.11.0, torchvision 0.26.0, torchaudio 2.11.0) everywhere the wheels exist, verified by live index resolution for py3.11-3.13: Linux x86_64 (cpu, cu126, cu128, cu130, rocm7.1, rocm7.2), Linux aarch64 (cpu, cu130), Windows x86_64 (cpu, cu126, cu128, cu130; triton-windows<3.7 resolves 3.6.0.post26, the 2.11 pairing) and macOS arm64. Existing installs are unaffected: the release preservation on both installers keeps the installed torch on re-runs (verified end to end against the live cpu index: a seeded 2.10.0 venv stays 2.10.0 with paired 0.25/2.10 companions while a fresh venv resolves the 2.11 trio). - install.sh: the supported range is centralized in _TORCH_CEILING / _TORCHVISION_CEILING / _TORCHAUDIO_CEILING and composed into the default constraints, so the next bump (torch 2.12) is a three-line change. The now-redundant cu* widen arm and custom-pin companion block collapse into the default; the curated ROCm >=2.11 floors stay literal. - install.ps1: the hoisted default trio, the CUDA flavor repair and the ROCm CPU fallback all use the <2.12 trio (the leaf gate collapsed since cu and non-cu now share the range). - setup.ps1: the unknown-leaf pinned CUDA trio widens to <2.12, matching the pinned cpu path. Deliberately NOT widened: rocm6.4 (tops at torch 2.9.1) and rocm7.0 (2.10.0) by index content; macOS x86_64 (no >=2.4 wheels, stays no-torch); the AMD per-arch repo.amd.com curated bounds for arches outside the 2.11 allowlist. Constraint suites updated to the ceiling scheme; parity suites updated to the widened trio. All sh, ps1 and pytest installer suites pass (the host-defaults suite and the tokenizers negative-control are known pre-existing failures). --- install.ps1 | 39 +++------ install.sh | 30 +++---- studio/setup.ps1 | 6 +- tests/python/test_cross_platform_parity.py | 35 ++++---- .../test_tokenizers_and_torch_constraint.py | 39 ++++----- tests/sh/test_torch_constraint.sh | 81 +++++++++---------- 6 files changed, 99 insertions(+), 131 deletions(-) diff --git a/install.ps1 b/install.ps1 index a561e6f9b0..ab60b06e0a 100644 --- a/install.ps1 +++ b/install.ps1 @@ -2156,29 +2156,21 @@ exit 0 } } } elseif ($TorchIndexUrl -or $ROCmIndexUrl) { - # Leaf-gated bounded trio, HOISTED so the release-preservation decision below can use it as the - # default route window: torchaudio 2.11 dropped its torch pin, so a bare companion can drift from - # a capped torch. cu families ship torch 2.11.x (paired triton-windows 3.6) so the ceiling - # widens to <2.12; other leaves keep the 2.10 line. Mirrors install.sh and _CUDA_TORCH_PKG_SPEC. - $_idxLeaf = (($TorchIndexUrl -split '[?#]', 2)[0].TrimEnd('/') -split '/')[-1].ToLowerInvariant() - if ($_idxLeaf -match '^cu[0-9]+$') { - $_pinTorchSpec = "torch>=2.4,<2.12.0" - $_pinVisionSpec = "torchvision>=0.19,<0.27.0" - $_pinAudioSpec = "torchaudio>=2.4,<2.12.0" - } else { - $_pinTorchSpec = "torch>=2.4,<2.11.0" - $_pinVisionSpec = "torchvision>=0.19,<0.26.0" - $_pinAudioSpec = "torchaudio>=2.4,<2.11.0" - } + # Bounded default trio (torch 2.11 line; wheels verified on cpu + cu126/cu128/cu130 for + # win_amd64 with paired triton-windows 3.6), HOISTED so the release-preservation decision + # below can use it as the default route window. torchaudio 2.11 dropped its torch pin, so a + # bare companion can drift from a capped torch. Bump the three ceilings together with + # install.sh's _TORCH_CEILING trio and the repair/fallback sites below (2 more literals). + $_pinTorchSpec = "torch>=2.4,<2.12.0" + $_pinVisionSpec = "torchvision>=0.19,<0.27.0" + $_pinAudioSpec = "torchaudio>=2.4,<2.12.0" # Release preservation (twin of install.sh's _PREV_TORCH_PIN decision): evaluated after every # index/floor choice, incl. the ROCm reroute, so a raised floor rejects an older release. The - # route window is the leaf-gated CUDA trio by default; the ROCm path uses its floor (or the - # torch>=2.4,<2.11.0 range the ROCm->CPU fallback installs). The kept release is exported for - # setup.ps1 (UNSLOTH_KEPT_TORCH) and cleared after setup runs. + # kept release is exported for setup.ps1 (UNSLOTH_KEPT_TORCH) and cleared after setup runs. $script:PrevTorchPin = $null if (-not $SkipTorch -and $script:PrevTorchVer) { $_routeWindow = $_pinTorchSpec - if ($ROCmIndexUrl) { if ($ROCmTorchFloor) { $_routeWindow = $ROCmTorchFloor } else { $_routeWindow = "torch>=2.4,<2.11.0" } } + if ($ROCmIndexUrl -and $ROCmTorchFloor) { $_routeWindow = $ROCmTorchFloor } $script:PrevTorchPin = Get-PreviousTorchPin -TorchVersion $script:PrevTorchVer -Constraint $_routeWindow if ($script:PrevTorchPin) { $env:UNSLOTH_KEPT_TORCH = $script:PrevTorchPin.Release.PublicBase @@ -2212,7 +2204,7 @@ exit 0 $CpuFallbackIndexUrl = if ($env:UNSLOTH_PYTORCH_MIRROR) { "$($env:UNSLOTH_PYTORCH_MIRROR.TrimEnd('/'))/cpu" } else { "https://download.pytorch.org/whl/cpu" } substep "ROCm PyTorch install failed (exit $torchInstallExit); using a CPU base, Unsloth setup retries ROCm." "Yellow" # --force-reinstall: a failed ROCm install can leave an unpinned ROCm torch that still satisfies the CPU torch>= range, so without it uv would keep the ROCm build and only swap companions -- a mismatched venv the flavor-repair block won't fix. (No kept-release attempt: the ROCm attempts above always resolve or clear $script:PrevTorchPin first, so a pin never reaches this CPU base.) - $torchInstallExit = Invoke-InstallCommandRetry -Label "install PyTorch (CPU fallback)" { uv pip install --python $VenvPython --force-reinstall "torch>=2.4,<2.11.0" "torchvision>=0.19,<0.26.0" "torchaudio>=2.4,<2.11.0" --default-index $CpuFallbackIndexUrl } + $torchInstallExit = Invoke-InstallCommandRetry -Label "install PyTorch (CPU fallback)" { uv pip install --python $VenvPython --force-reinstall "torch>=2.4,<2.12.0" "torchvision>=0.19,<0.27.0" "torchaudio>=2.4,<2.12.0" --default-index $CpuFallbackIndexUrl } if ($torchInstallExit -ne 0) { Write-Host "[ERROR] Failed to install PyTorch (ROCm and CPU base both failed, exit code $torchInstallExit)" -ForegroundColor Red return (Exit-InstallFailure "Failed to install PyTorch (exit code $torchInstallExit)" $torchInstallExit) @@ -2363,13 +2355,8 @@ exit 0 } $installedTorchTag = Get-InstalledTorchTag -PythonExe $VenvPython } elseif ($expectedTorchTag -ne 'rocm') { - # CUDA: stale +cpu (or wrong cuXXX) against a CUDA index -> reinstall triplet. Same leaf-gated ceiling as the install above: cu* serves 2.11. - $_fixLeaf = (($TorchIndexUrl -split '[?#]', 2)[0].TrimEnd('/') -split '/')[-1].ToLowerInvariant() - if ($_fixLeaf -match '^cu[0-9]+$') { - $_fixTorchSpec = "torch>=2.4,<2.12.0"; $_fixVisionSpec = "torchvision>=0.19,<0.27.0"; $_fixAudioSpec = "torchaudio>=2.4,<2.12.0" - } else { - $_fixTorchSpec = "torch>=2.4,<2.11.0"; $_fixVisionSpec = "torchvision>=0.19,<0.26.0"; $_fixAudioSpec = "torchaudio>=2.4,<2.11.0" - } + # CUDA: stale +cpu (or wrong cuXXX) against a CUDA index -> reinstall triplet with the default 2.11-line trio (ceiling bump site, see the hoisted trio above). + $_fixTorchSpec = "torch>=2.4,<2.12.0"; $_fixVisionSpec = "torchvision>=0.19,<0.27.0"; $_fixAudioSpec = "torchaudio>=2.4,<2.12.0" # Kept-release substitution (twin of install.sh's _install_torch_default_index honoring _PREV_TORCH_PIN): honor the preserved torch when the pin survived the E-decision; restore the range specs and retry if it isn't installable here. The --reinstall-package triplet stays on both attempts. $_cudaKept = $false if ($script:PrevTorchPin) { diff --git a/install.sh b/install.sh index 7a4eb572e4..3919d4b53e 100755 --- a/install.sh +++ b/install.sh @@ -1888,18 +1888,24 @@ if [ -x "$VENV_DIR/bin/python" ]; then substep "${VENV_DIR}" fi +# Supported torch line: the default range admits torch 2.11 (wheels verified on +# cpu/cu126/cu128/cu130/rocm7.1+/mac arm64). Bump the three ceilings together +# when the next torch minor is validated; curated ROCm floors below stay literal. +_TORCH_CEILING="2.12.0" +_TORCHVISION_CEILING="0.27.0" +_TORCHAUDIO_CEILING="2.12.0" # Default torch constraint; tightened for Python 3.13+ on arm64 macOS (torch <2.6 has no cp313 macOS arm64 wheels). -TORCH_CONSTRAINT="torch>=2.4,<2.11.0" +TORCH_CONSTRAINT="torch>=2.4,<${_TORCH_CEILING}" if [ "$SKIP_TORCH" = false ] && [ "$OS" = "macos" ] && [ "$_ARCH" = "arm64" ]; then _PY_MINOR=$("$VENV_DIR/bin/python" -c \ "import sys; print(sys.version_info.minor)" 2>/dev/null || echo "0") if [ "$_PY_MINOR" -ge 13 ] 2>/dev/null; then - TORCH_CONSTRAINT="torch>=2.6,<2.11.0" + TORCH_CONSTRAINT="torch>=2.6,<${_TORCH_CEILING}" fi fi -# Companion constraints bounded to torch's window: torchaudio 2.11 dropped its torch pin, so a bare companion beside a <2.11 torch resolves 2.11. -TORCHVISION_CONSTRAINT="torchvision>=0.19,<0.26.0" -TORCHAUDIO_CONSTRAINT="torchaudio>=2.4,<2.11.0" +# Companion constraints bounded to torch's window: torchaudio 2.11 dropped its torch pin, so a bare companion can drift from a capped torch. +TORCHVISION_CONSTRAINT="torchvision>=0.19,<${_TORCHVISION_CEILING}" +TORCHAUDIO_CONSTRAINT="torchaudio>=2.4,<${_TORCHAUDIO_CEILING}" # ── Resolve repo root (for --local installs) ── _REPO_ROOT="$(cd "$(dirname "$0" 2>/dev/null || echo ".")" && pwd)" @@ -2480,27 +2486,15 @@ else fi # rocm7.2 and the per-gfx indexes (Strix _grouped_mm fix) ship torch 2.11.0: raise the floor and pin companions; match the FINAL leaf only. +# (cu*/cpu/custom leaves all use the default <2.12 trio above.) case "$_torch_index_leaf" in rocm7.2|gfx120x-all|gfx1151|gfx1150) TORCH_CONSTRAINT="torch>=2.11.0,<2.12.0" TORCHVISION_CONSTRAINT="torchvision>=0.26.0,<0.27.0" TORCHAUDIO_CONSTRAINT="torchaudio>=2.11.0,<2.12.0" ;; - # CUDA cu12x/cu13x indexes ship torch 2.11.x: widen the trio ceiling to <2.12.0. - cu[0-9]*) - TORCH_CONSTRAINT="torch>=2.4,<2.12.0" - TORCHVISION_CONSTRAINT="torchvision>=0.19,<0.27.0" - TORCHAUDIO_CONSTRAINT="torchaudio>=2.4,<2.12.0" - ;; esac -# A pinned custom/unknown-leaf index has no curated companion set: bound the companions to the same <2.11 range the Python path pins. -if [ "$_torch_index_pinned" = true ] && \ - [ -z "$(_expected_torch_flavor_tag "$TORCH_INDEX_URL")" ]; then - TORCHVISION_CONSTRAINT="torchvision>=0.19,<0.26.0" - TORCHAUDIO_CONSTRAINT="torchaudio>=2.4,<2.11.0" -fi - # Detect a Radeon card (*/rocm* index + rocminfo "Marketing Name:.*Radeon"); skipped when the index is pinned. _amd_gpu_radeon=false if [ "$_torch_index_pinned" = false ]; then diff --git a/studio/setup.ps1 b/studio/setup.ps1 index 79690fb76f..a63454e395 100644 --- a/studio/setup.ps1 +++ b/studio/setup.ps1 @@ -2999,9 +2999,9 @@ if (-not $ROCmIndexUrl -and ($CuTag -eq "cpu" -or $ROCmCpuFallback)) { $cudaVisionSpec = "torchvision" $cudaAudioSpec = "torchaudio" if ($TorchIndexPinned -and -not (Test-CudaFamilyLeaf $CuTag)) { - $cudaTorchSpec = "torch>=2.4,<2.11.0" - $cudaVisionSpec = "torchvision>=0.19,<0.26.0" - $cudaAudioSpec = "torchaudio>=2.4,<2.11.0" + $cudaTorchSpec = "torch>=2.4,<2.12.0" + $cudaVisionSpec = "torchvision>=0.19,<0.27.0" + $cudaAudioSpec = "torchaudio>=2.4,<2.12.0" } # Release preservation: keep install.ps1's exported torch RELEASE (UNSLOTH_KEPT_TORCH) so a re-run # reinstalls the same CUDA build (+cuXXX follows this index). On a failed install, restore the computed diff --git a/tests/python/test_cross_platform_parity.py b/tests/python/test_cross_platform_parity.py index d7e4f4ffdd..751b12253d 100644 --- a/tests/python/test_cross_platform_parity.py +++ b/tests/python/test_cross_platform_parity.py @@ -405,30 +405,25 @@ class TestKnown211SetParity: ), f"{label} floor gate must not use the unanchored ^rocm(\\d+)\\.(\\d+) prefix" def test_install_ps1_bounds_unknown_leaf_pinned_torch(self): - """install.ps1's pinned-torch install must bound BOTH companions on EVERY - index, cu families included: torchaudio 2.11 dropped its exact torch - pin from the wheel metadata, so a bare companion beside torch<2.11 can - resolve a mismatched 2.11.0 build (Codex P2, then unconditional per the - torchaudio 2.11 unpinning).""" + """install.ps1's pinned-torch install must bound the whole trio on EVERY + index with the default torch 2.11 line (<2.12 trio, matching install.sh's + ceiling-composed default and _CUDA_TORCH_PKG_SPEC): torchaudio 2.11 + dropped its exact torch pin from the wheel metadata, so a bare companion + beside a capped torch can resolve a mismatched build.""" text = INSTALL_PS1.read_text(encoding = "utf-8") - assert ( - '$_pinVisionSpec = "torchvision>=0.19,<0.26.0"' in text - ), "install.ps1 custom-pin install must bound torchvision (>=0.19,<0.26.0)" - assert ( - '$_pinAudioSpec = "torchaudio>=2.4,<2.11.0"' in text - ), "install.ps1 custom-pin install must bound torchaudio (>=2.4,<2.11.0)" - # cu leaves widen the whole trio to the torch 2.11 line (<2.12), - # matching install.sh's cu[0-9]* widen and _CUDA_TORCH_PKG_SPEC; other - # leaves keep the 2.10 line. The trio stays bounded on every index. assert '$_pinTorchSpec = "torch>=2.4,<2.12.0"' in text, ( - "install.ps1 must widen torch to <2.12.0 on cu index leaves" + "install.ps1 default install must use the torch 2.11 line (<2.12.0)" ) assert ( '$_pinVisionSpec = "torchvision>=0.19,<0.27.0"' in text - ), "install.ps1 cu-leaf install must pair torchvision <0.27.0 with torch <2.12" + ), "install.ps1 must pair torchvision <0.27.0 with torch <2.12" assert ( '$_pinAudioSpec = "torchaudio>=2.4,<2.12.0"' in text - ), "install.ps1 cu-leaf install must pair torchaudio <2.12.0 with torch <2.12" + ), "install.ps1 must pair torchaudio <2.12.0 with torch <2.12" + # No stale 2.10-line default remains anywhere in the Windows installer. + assert '"torch>=2.4,<2.11.0"' not in text, ( + "install.ps1 must not retain a <2.11.0 default torch range" + ) # The bounded trio must actually be passed to the install command. assert re.search( r'\$_pinTorchSpec \$_pinVisionSpec \$_pinAudioSpec --default-index \$TorchIndexUrl', @@ -672,9 +667,9 @@ class TestPinnedIndexClearsUvEnvParity: # The custom-leaf branch bounds torch AND both companions (parity with the # other installers' custom-pin trio bounds), gated on a non-cu-family leaf. for spec in ( - '$cudaTorchSpec = "torch>=2.4,<2.11.0"', - '$cudaVisionSpec = "torchvision>=0.19,<0.26.0"', - '$cudaAudioSpec = "torchaudio>=2.4,<2.11.0"', + '$cudaTorchSpec = "torch>=2.4,<2.12.0"', + '$cudaVisionSpec = "torchvision>=0.19,<0.27.0"', + '$cudaAudioSpec = "torchaudio>=2.4,<2.12.0"', ): assert spec in text, f"setup.ps1 must bound the custom-leaf trio: {spec}" assert ( diff --git a/tests/python/test_tokenizers_and_torch_constraint.py b/tests/python/test_tokenizers_and_torch_constraint.py index c58808689b..a7ad585583 100644 --- a/tests/python/test_tokenizers_and_torch_constraint.py +++ b/tests/python/test_tokenizers_and_torch_constraint.py @@ -64,35 +64,30 @@ class TestStructuralTorchConstraint: _sh = _read(_INSTALL_SH) def test_default_assignment_exists(self): - assert 'TORCH_CONSTRAINT="torch>=2.4,<2.11.0"' in self._sh + """The default range composes the per-file ceiling variable, so the + supported line (torch 2.11 today) is bumped in one place.""" + assert '_TORCH_CEILING="2.12.0"' in self._sh + assert 'TORCH_CONSTRAINT="torch>=2.4,<${_TORCH_CEILING}"' in self._sh def test_tightened_assignment_exists(self): - assert 'TORCH_CONSTRAINT="torch>=2.6,<2.11.0"' in self._sh + assert 'TORCH_CONSTRAINT="torch>=2.6,<${_TORCH_CEILING}"' in self._sh - def test_cuda_constraint_widened_to_2_12(self): - """A fresh CUDA install widens the ceiling to <2.12.0 so cu12x/cu13x - land torch 2.11.x (matches the base image and _CUDA_TORCH_PKG_SPEC); - without it cu128/cu130 resolves torch 2.10.x.""" - assert 'TORCH_CONSTRAINT="torch>=2.4,<2.12.0"' in self._sh - - def test_cuda_case_widens_via_index_leaf(self): - """The cu* branch of the _torch_index_leaf case sets the widened - constraint (parallel to rocm7.2), anchored on the leaf.""" - m = re.search( - r'cu\[0-9\]\*\)\s*TORCH_CONSTRAINT="torch>=2\.4,<2\.12\.0"', - self._sh, - ) - assert m is not None, "CUDA (cu*) TORCH_CONSTRAINT widening case not found" + def test_companion_ceilings_composed(self): + """Companions bound to the same window via their own ceiling vars.""" + assert '_TORCHVISION_CEILING="0.27.0"' in self._sh + assert '_TORCHAUDIO_CEILING="2.12.0"' in self._sh + assert 'TORCHVISION_CONSTRAINT="torchvision>=0.19,<${_TORCHVISION_CEILING}"' in self._sh + assert 'TORCHAUDIO_CONSTRAINT="torchaudio>=2.4,<${_TORCHAUDIO_CEILING}"' in self._sh def test_variable_used_in_pip_install(self): """$TORCH_CONSTRAINT must appear in a uv pip install line.""" assert '"$TORCH_CONSTRAINT"' in self._sh - def test_hardcoded_torch_constraint_only_once(self): - """The hard-coded torch>=2.4,<2.11.0 string should appear exactly once - in install.sh (the default assignment), not in pip install lines.""" - count = self._sh.count('"torch>=2.4,<2.11.0"') - assert count == 1, f"Expected 1, found {count}" + def test_hardcoded_torch_constraint_gone(self): + """No hard-coded default ranges remain outside the ceiling-composed + assignments (curated ROCm >=2.11 floors stay literal).""" + assert self._sh.count('"torch>=2.4,<2.11.0"') == 0 + assert self._sh.count('"torch>=2.4,<2.12.0"') == 0 def test_tightening_guarded_by_skip_torch(self): """The block must check SKIP_TORCH=false.""" @@ -122,7 +117,7 @@ class TestStructuralInstallPs1Unchanged: assert "$TorchConstraint" not in self._ps1 def test_hardcoded_torch_constraint_present(self): - assert '"torch>=2.4,<2.11.0"' in self._ps1 + assert '"torch>=2.4,<2.12.0"' in self._ps1 class TestInstallPs1UvDefaultIndex: diff --git a/tests/sh/test_torch_constraint.sh b/tests/sh/test_torch_constraint.sh index bfafbd161b..d36581987b 100644 --- a/tests/sh/test_torch_constraint.sh +++ b/tests/sh/test_torch_constraint.sh @@ -76,11 +76,12 @@ run_constraint_snippet() { OS=\"$_os\" _ARCH=\"$_arch\" VENV_DIR=\"$_venv_dir\" - TORCH_CONSTRAINT=\"torch>=2.4,<2.11.0\" + _TORCH_CEILING=\"2.12.0\" + TORCH_CONSTRAINT=\"torch>=2.4,<\${_TORCH_CEILING}\" if [ \"\$SKIP_TORCH\" = false ] && [ \"\$OS\" = \"macos\" ] && [ \"\$_ARCH\" = \"arm64\" ]; then _PY_MINOR=\$(\"\$VENV_DIR/bin/python\" -c \"import sys; print(sys.version_info.minor)\" 2>/dev/null || echo \"0\") if [ \"\$_PY_MINOR\" -ge 13 ] 2>/dev/null; then - TORCH_CONSTRAINT=\"torch>=2.6,<2.11.0\" + TORCH_CONSTRAINT=\"torch>=2.6,<\${_TORCH_CEILING}\" fi fi echo \"\$TORCH_CONSTRAINT\" @@ -94,43 +95,39 @@ echo "=== Structural: TORCH_CONSTRAINT in install.sh ===" _SH_CONTENT=$(cat "$INSTALL_SH") -_count=$(grep -c 'TORCH_CONSTRAINT="torch>=2.4,<2.11.0"' "$INSTALL_SH" || true) -assert_eq "default TORCH_CONSTRAINT assignment exists" "1" "$_count" +# The supported line is centralized in per-file ceiling variables so a future +# torch 2.12 bump is a three-line change; the default range admits torch 2.11. +_count=$(grep -c '_TORCH_CEILING="2.12.0"' "$INSTALL_SH" || true) +assert_eq "torch ceiling variable defined once" "1" "$_count" +_count=$(grep -c '_TORCHVISION_CEILING="0.27.0"' "$INSTALL_SH" || true) +assert_eq "torchvision ceiling variable defined once" "1" "$_count" +_count=$(grep -c '_TORCHAUDIO_CEILING="2.12.0"' "$INSTALL_SH" || true) +assert_eq "torchaudio ceiling variable defined once" "1" "$_count" -_count=$(grep -c 'TORCH_CONSTRAINT="torch>=2.6,<2.11.0"' "$INSTALL_SH" || true) -assert_eq "tightened TORCH_CONSTRAINT assignment exists" "1" "$_count" +_count=$(grep -c 'TORCH_CONSTRAINT="torch>=2.4,<${_TORCH_CEILING}"' "$INSTALL_SH" || true) +assert_eq "default TORCH_CONSTRAINT composes the ceiling" "1" "$_count" +_count=$(grep -c 'TORCH_CONSTRAINT="torch>=2.6,<${_TORCH_CEILING}"' "$INSTALL_SH" || true) +assert_eq "tightened TORCH_CONSTRAINT composes the ceiling" "1" "$_count" _count=$(grep -c '"\$TORCH_CONSTRAINT"' "$INSTALL_SH" || true) _has_var=$([ "$_count" -ge 1 ] && echo "yes" || echo "no") assert_eq "\$TORCH_CONSTRAINT used in pip install" "yes" "$_has_var" -# Hardcoded torch>=2.4,<2.11.0 should only appear once (the default assignment) -_hardcoded=$(grep -c '"torch>=2.4,<2.11.0"' "$INSTALL_SH" || true) -assert_eq "hardcoded torch>=2.4 appears exactly once" "1" "$_hardcoded" +# No stray hardcoded default ranges outside the ceiling-composed assignments +# (the curated ROCm >=2.11 floors are deliberately literal). +_hardcoded=$(grep -c '"torch>=2.4,<2.11.0"\|"torch>=2.4,<2.12.0"' "$INSTALL_SH" || true) +assert_eq "no hardcoded default torch range remains" "0" "$_hardcoded" -# Companions must be bounded to torch's window everywhere: the <2.11 bound appears -# twice (default assignments + the pinned custom-leaf block), never bare. torchaudio -# 2.11 dropped its exact torch pin, so a bare companion next to a <2.11-capped torch -# resolves a mismatched 2.11 build. -_count=$(grep -c 'TORCHVISION_CONSTRAINT="torchvision>=0.19,<0.26.0"' "$INSTALL_SH" || true) -assert_eq "torchvision bounded (<0.26) at default + custom-leaf" "2" "$_count" -_count=$(grep -c 'TORCHAUDIO_CONSTRAINT="torchaudio>=2.4,<2.11.0"' "$INSTALL_SH" || true) -assert_eq "torchaudio bounded (<2.11) at default + custom-leaf" "2" "$_count" +# Companions must be bounded to torch's window everywhere, never bare: torchaudio +# 2.11 dropped its exact torch pin, so a bare companion can drift from a capped torch. +_count=$(grep -c 'TORCHVISION_CONSTRAINT="torchvision>=0.19,<${_TORCHVISION_CEILING}"' "$INSTALL_SH" || true) +assert_eq "torchvision default composes the ceiling" "1" "$_count" +_count=$(grep -c 'TORCHAUDIO_CONSTRAINT="torchaudio>=2.4,<${_TORCHAUDIO_CEILING}"' "$INSTALL_SH" || true) +assert_eq "torchaudio default composes the ceiling" "1" "$_count" _count=$(grep -c 'TORCHVISION_CONSTRAINT="torchvision"$' "$INSTALL_SH" || true) assert_eq "no bare torchvision companion remains" "0" "$_count" _count=$(grep -c 'TORCHAUDIO_CONSTRAINT="torchaudio"$' "$INSTALL_SH" || true) assert_eq "no bare torchaudio companion remains" "0" "$_count" -# The cu* widen must carry the companions with it (torch <2.12 with torchaudio <2.11 -# would cap a mismatched pair the other way). -assert_eq "cu widen pairs torchaudio (<2.12)" "1" "$(grep -c 'TORCHAUDIO_CONSTRAINT="torchaudio>=2.4,<2.12.0"' "$INSTALL_SH" || true)" -_gated=$(grep -c '_expected_torch_flavor_tag "$TORCH_INDEX_URL"' "$INSTALL_SH" || true) -_has_gate=$([ "$_gated" -ge 1 ] && echo "yes" || echo "no") -assert_eq "custom-companion bound gated on empty flavor tag" "yes" "$_has_gate" - -# A fresh CUDA install widens the ceiling to <2.12.0 so cu12x/cu13x land torch -# 2.11.x (matches the base image and _CUDA_TORCH_PKG_SPEC). -_cuda_widen=$(grep -c 'TORCH_CONSTRAINT="torch>=2.4,<2.12.0"' "$INSTALL_SH" || true) -assert_eq "CUDA TORCH_CONSTRAINT widened to <2.12.0" "1" "$_cuda_widen" # Widening keys off the final leaf (_torch_index_leaf), not the full URL, so a # mirror base path with cu*/rocm7.2 but a cpu/older-rocm leaf is not mis-widened. @@ -181,7 +178,7 @@ _PS1_CONTENT=$(cat "$INSTALL_PS1") _ps1_has_var=$(echo "$_PS1_CONTENT" | grep -c 'TORCH_CONSTRAINT\|TorchConstraint' || true) assert_eq "install.ps1 has no TORCH_CONSTRAINT variable" "0" "$_ps1_has_var" -_ps1_hardcoded=$(echo "$_PS1_CONTENT" | grep -c '"torch>=2.4,<2.11.0"' || true) +_ps1_hardcoded=$(echo "$_PS1_CONTENT" | grep -c '"torch>=2.4,<2.12.0"' || true) _ps1_has_hc=$([ "$_ps1_hardcoded" -ge 1 ] && echo "yes" || echo "no") assert_eq "install.ps1 has hardcoded torch constraint" "yes" "$_ps1_has_hc" @@ -196,55 +193,55 @@ trap 'rm -rf "$TMPDIR_BASE"' EXIT # 1. arm64 macOS py3.13 -> tightened _result=$(run_constraint_snippet false macos arm64 13 "$TMPDIR_BASE/v1") -assert_eq "arm64+macos+py313 -> tightened" "torch>=2.6,<2.11.0" "$_result" +assert_eq "arm64+macos+py313 -> tightened" "torch>=2.6,<2.12.0" "$_result" # 2. arm64 macOS py3.14 -> tightened (future-proofed) _result=$(run_constraint_snippet false macos arm64 14 "$TMPDIR_BASE/v2") -assert_eq "arm64+macos+py314 -> tightened" "torch>=2.6,<2.11.0" "$_result" +assert_eq "arm64+macos+py314 -> tightened" "torch>=2.6,<2.12.0" "$_result" # 3. arm64 macOS py3.12 -> default _result=$(run_constraint_snippet false macos arm64 12 "$TMPDIR_BASE/v3") -assert_eq "arm64+macos+py312 -> default" "torch>=2.4,<2.11.0" "$_result" +assert_eq "arm64+macos+py312 -> default" "torch>=2.4,<2.12.0" "$_result" # 4. arm64 macOS py3.11 -> default _result=$(run_constraint_snippet false macos arm64 11 "$TMPDIR_BASE/v4") -assert_eq "arm64+macos+py311 -> default" "torch>=2.4,<2.11.0" "$_result" +assert_eq "arm64+macos+py311 -> default" "torch>=2.4,<2.12.0" "$_result" # 5. Linux x86_64 py3.13 -> default (Linux unaffected) _result=$(run_constraint_snippet false linux x86_64 13 "$TMPDIR_BASE/v5") -assert_eq "linux+x86_64+py313 -> default" "torch>=2.4,<2.11.0" "$_result" +assert_eq "linux+x86_64+py313 -> default" "torch>=2.4,<2.12.0" "$_result" # 6. Linux aarch64 py3.13 -> default (guard checks OS=macos) _result=$(run_constraint_snippet false linux aarch64 13 "$TMPDIR_BASE/v6") -assert_eq "linux+aarch64+py313 -> default" "torch>=2.4,<2.11.0" "$_result" +assert_eq "linux+aarch64+py313 -> default" "torch>=2.4,<2.12.0" "$_result" # 7. Intel Mac x86_64 py3.12 -> default (arch mismatch) _result=$(run_constraint_snippet false macos x86_64 12 "$TMPDIR_BASE/v7") -assert_eq "macos+x86_64+py312 -> default" "torch>=2.4,<2.11.0" "$_result" +assert_eq "macos+x86_64+py312 -> default" "torch>=2.4,<2.12.0" "$_result" # 8. SKIP_TORCH=true arm64 macOS py3.13 -> block skipped, default _result=$(run_constraint_snippet true macos arm64 13 "$TMPDIR_BASE/v8") -assert_eq "SKIP_TORCH=true -> default" "torch>=2.4,<2.11.0" "$_result" +assert_eq "SKIP_TORCH=true -> default" "torch>=2.4,<2.12.0" "$_result" # 9. WSL py3.13 -> default _result=$(run_constraint_snippet false wsl x86_64 13 "$TMPDIR_BASE/v9") -assert_eq "wsl+py313 -> default" "torch>=2.4,<2.11.0" "$_result" +assert_eq "wsl+py313 -> default" "torch>=2.4,<2.12.0" "$_result" # 10. py_minor=0 (failed query fallback) -> default _result=$(run_constraint_snippet false macos arm64 0 "$TMPDIR_BASE/v10") -assert_eq "py_minor=0 fallback -> default" "torch>=2.4,<2.11.0" "$_result" +assert_eq "py_minor=0 fallback -> default" "torch>=2.4,<2.12.0" "$_result" # 11. Boundary: py_minor=12 -> NOT tightened _result=$(run_constraint_snippet false macos arm64 12 "$TMPDIR_BASE/v11") -assert_eq "boundary py_minor=12 -> default" "torch>=2.4,<2.11.0" "$_result" +assert_eq "boundary py_minor=12 -> default" "torch>=2.4,<2.12.0" "$_result" # 12. Boundary: py_minor=13 -> tightened _result=$(run_constraint_snippet false macos arm64 13 "$TMPDIR_BASE/v12") -assert_eq "boundary py_minor=13 -> tightened" "torch>=2.6,<2.11.0" "$_result" +assert_eq "boundary py_minor=13 -> tightened" "torch>=2.6,<2.12.0" "$_result" # 13. Intel Mac py3.13 -> default (arch=x86_64, not arm64) _result=$(run_constraint_snippet false macos x86_64 13 "$TMPDIR_BASE/v13") -assert_eq "macos+x86_64+py313 -> default" "torch>=2.4,<2.11.0" "$_result" +assert_eq "macos+x86_64+py313 -> default" "torch>=2.4,<2.12.0" "$_result" # ====================================================================== # Mock uv integration