Installer: select CUDA wheels that cover the host's GPUs (#7814)

* Installer: select CUDA wheels that cover the host's GPUs

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix Windows venv wipe and warning dedupe for PR #7814

- Windows pins torch<2.11, whose cu128 still ships sm_70, so capping a Volta
  to cu126 there rewrote a working family. The stale-venv check then read that
  as drift and deleted the venv on a direct "unsloth studio update", which
  cannot recreate it. Make the pre-Turing floor per-family (70 for cu128).
- Repair an unpinned cu* -> cu* move in place instead of rebuilding the venv.
- Decide the cu126 advice before deduping the uncovered-host warning: the host
  facts are release invariant but the artifact list is not, so the release
  walk-back let an unhelpful release swallow the remedy.
- Gate the new coverage repair and the cu126 advice on x86_64, matching the cap.
- Add tests/studio/test_pre_turing_cap.ps1: the parity test only greps for the
  call spelling, so neither PowerShell copy had behavioural coverage.

* Tighten comments for PR #7814

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: danielhanchen <unslothshared@gmail.com>
This commit is contained in:
oobabooga 2026-08-04 10:55:29 -03:00 committed by GitHub
parent f26727faec
commit f8730f4339
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 1049 additions and 42 deletions

View file

@ -68,6 +68,53 @@ class TestInstallShHasGpuDetection:
), "install.sh should assign TORCH_INDEX_URL from get_torch_index_url()"
class TestPreTuringCapParity:
"""Every wheel-selection site caps cu128/cu130 on a pre-Turing host (issue #7765).
PyTorch 2.11 builds those families for sm_75 and newer, so a Maxwell/Pascal/Volta
box needs cu126 -- both for torch itself and for the CUDA 12 runtime that gets it
a llama.cpp GGUF bundle. Four scripts pick the family; none may be left behind.
"""
# (file, call spelling, selection function that must invoke it, its end marker). The
# spelling carries the first argument, so a prose mention cannot satisfy the assertion.
_SITES = (
(INSTALL_SH, '_cap_cuda_family_for_pre_turing "', "get_torch_index_url() {", "\n}"),
(
INSTALL_PS1,
"Get-CudaFamilyCappedForPreTuring $",
"function Get-TorchIndexUrl",
"\n }",
),
(SETUP_PS1, "Get-CudaFamilyCappedForPreTuring $", "function Get-PytorchCudaTag", "\n}"),
(
STACK_PY,
"_cap_cuda_family_for_pre_turing(",
"def _detect_cuda_torch_index_url",
"\ndef ",
),
)
def test_cu126_span_agrees_across_the_python_modules(self):
# Neither module imports the other (the installer runs before dependencies
# exist), so assert the shared span here rather than let it drift silently.
span = "_CU126_SM_RANGE = (50, 90)"
for path in (STACK_PY, REPO_ROOT / "studio" / "install_llama_prebuilt.py"):
assert span in path.read_text(encoding = "utf-8"), f"{path.name} lost {span}"
@pytest.mark.parametrize("path,call,start,end", _SITES)
def test_selection_function_applies_the_cap(self, path, call, start, end):
text = path.read_text(encoding = "utf-8")
assert start in text, f"{path.name} no longer defines {start!r}"
body = text.split(start, 1)[1].split(end, 1)[0]
assert call in body, f"{path.name}'s selection function never applies {call!r}"
# A ladder rung names its family either as an index-URL suffix ("$base/cu128") or as a
# variable a later step can still cap ("_cuda_tag=cu128"), so accept both spellings.
_CUDA_LEAF_RE = r"""[/=]\s*["']?(cu\d+|cpu)"""
class TestCudaMappingParity:
"""CUDA version thresholds must match between install.sh and install.ps1."""
@ -84,7 +131,7 @@ class TestCudaMappingParity:
if in_func and line.startswith("}"):
break
if in_func and ("_major" in line or "_minor" in line):
m = re.search(r"/(cu\d+|cpu)", line)
m = re.search(_CUDA_LEAF_RE, line)
if m:
results.append(m.group(1))
return results
@ -106,7 +153,7 @@ class TestCudaMappingParity:
break
# Only match the if-chain lines that compare $major/$minor
if "$major" in line or "$minor" in line:
m = re.search(r"/(cu\d+|cpu)", line)
m = re.search(_CUDA_LEAF_RE, line)
if m:
results.append(m.group(1))
return results