Merge remote-tracking branch 'origin/main' into docker-blackwell-build

Three conflicts, all where main rewrote code this branch had also touched:

- .github/workflows/studio-backend-ci.yml path filter: kept both sides,
  so docker/** still triggers Backend CI and main's install.sh,
  install.ps1 and scripts/** triggers come along too.
- The same workflow's shell-test step and tests/run_all.sh: took main's
  directory discovery over this branch's hand-written file lists, which
  had drifted. tests/studio/test_ci_shell_suite_coverage.py passes.
- studio/install_llama_prebuilt.py: took main's delegation of
  _os_error_messages and is_busy_lock_error to prebuilt_core and kept
  this branch's is_cross_device_error, which the EXDEV copy-and-remove
  fallback still calls. BusyInstallConflict is already the prebuilt_core
  class here, so the delegated isinstance check is unchanged.
This commit is contained in:
Daniel Han 2026-07-26 14:25:51 +00:00
commit 30f667c9de
624 changed files with 67183 additions and 11903 deletions

View file

@ -249,22 +249,42 @@ class TestTorchIndexOverrideParity:
class TestGfx211AllowlistParity:
"""The gfx per-arch 2.11-floor leaves (gfx120X-all / gfx1151 / gfx1150) must be the
SAME set in every installer and its stale/mismatch check. When they diverged, a
pinned gfx110X-all / gfx90a / gfx908 wheel (<2.11) was force-reinstalled every update."""
"""The gfx per-arch 2.11-floor leaves must be the SAME set in every installer
and its stale/mismatch check. When they diverged, a pinned gfx110X-all /
gfx90a / gfx908 wheel (<2.11) was force-reinstalled every update.
EXPECTED = {"gfx120x-all", "gfx1151", "gfx1150"}
Each test extracts the set each installer actually holds and compares it
against EXPECTED, rather than matching one hardcoded ordering. Order and
spacing are free; membership is not. The earlier literal-string form had to
be edited in four places whenever a leaf was added, which is how adding
gfx1152 (Krackan Point) turned this class red without any installer
actually disagreeing with another."""
EXPECTED = {"gfx120x-all", "gfx1151", "gfx1150", "gfx1152"}
@staticmethod
def _leaves(blob: str) -> set[str]:
"""The gfx leaves named in an allowlist literal, quoting-agnostic."""
return set(re.findall(r"gfx[0-9a-z-]+", blob.lower()))
def test_install_sh_allowlist(self):
text = INSTALL_SH.read_text(encoding = "utf-8").lower()
# install.sh: the TORCH_CONSTRAINT case (rocm7.2|gfx120x-all|gfx1151|gfx1150).
m = re.search(r"rocm7\.2\|gfx120x-all\|gfx1151\|gfx1150", text)
# install.sh: the TORCH_CONSTRAINT case (rocm7.2|gfx...|gfx...).
m = re.search(r"^\s*(rocm7\.2\|[a-z0-9|.\-]*)\)", text, re.MULTILINE)
assert m, "install.sh gfx-2.11 allowlist case not found / changed"
assert self._leaves(m.group(1)) == self.EXPECTED, (
f"install.sh gfx-2.11 allowlist is {sorted(self._leaves(m.group(1)))}, "
f"expected {sorted(self.EXPECTED)}"
)
def test_install_ps1_allowlist(self):
text = INSTALL_PS1.read_text(encoding = "utf-8").lower()
m = re.search(r"@\('gfx120x-all',\s*'gfx1151',\s*'gfx1150'\)", text)
m = re.search(r"\$_pingfx211\s*=\s*@\(([^)]*)\)", text)
assert m, "install.ps1 $_pinGfx211 allowlist not found / changed"
assert self._leaves(m.group(1)) == self.EXPECTED, (
f"install.ps1 $_pinGfx211 is {sorted(self._leaves(m.group(1)))}, "
f"expected {sorted(self.EXPECTED)}"
)
def test_setup_ps1_defines_single_allowlist_helper(self):
# setup.ps1 must define the allowlist once (Test-RocmGfx211Leaf) and reuse it, so
@ -273,9 +293,12 @@ class TestGfx211AllowlistParity:
assert (
"function Test-RocmGfx211Leaf" in text
), "setup.ps1 should define a single Test-RocmGfx211Leaf allowlist helper"
assert re.search(
r"@\('gfx120x-all',\s*'gfx1151',\s*'gfx1150'\)", text.lower()
), "Test-RocmGfx211Leaf should hold the gfx-2.11 allowlist"
m = re.search(r"function test-rocmgfx211leaf[\s\S]{0,400}?@\(([^)]*)\)", text.lower())
assert m, "Test-RocmGfx211Leaf should hold the gfx-2.11 allowlist"
assert self._leaves(m.group(1)) == self.EXPECTED, (
f"Test-RocmGfx211Leaf holds {sorted(self._leaves(m.group(1)))}, "
f"expected {sorted(self.EXPECTED)}"
)
assert "$_pinGfx211 = Test-RocmGfx211Leaf" in text, (
"setup.ps1 install-spec path should reuse Test-RocmGfx211Leaf, not "
"re-hardcode the allowlist (they must not diverge)"
@ -283,9 +306,12 @@ class TestGfx211AllowlistParity:
def test_stack_py_allowlist(self):
text = STACK_PY.read_text(encoding = "utf-8").lower()
assert (
'"gfx120x-all", "gfx1151", "gfx1150"' in text
), "install_python_stack.py _ROCM_GFX_TORCH211_LEAVES not found / changed"
m = re.search(r"_rocm_gfx_torch211_leaves[^=]*=\s*frozenset\(\s*\{([^}]*)\}", text)
assert m, "install_python_stack.py _ROCM_GFX_TORCH211_LEAVES not found / changed"
assert self._leaves(m.group(1)) == self.EXPECTED, (
f"_ROCM_GFX_TORCH211_LEAVES is {sorted(self._leaves(m.group(1)))}, "
f"expected {sorted(self.EXPECTED)}"
)
class TestCudaLeafDigitParity:
@ -351,15 +377,21 @@ class TestCudaLeafDigitParity:
class TestKnown211SetParity:
"""The KNOWN-2.11 rocm/gfx set must be identical across all four installers:
exactly {rocm7.2} plus the gfx allowlist {gfx120x-all, gfx1151, gfx1150}.
exactly {rocm7.2} plus TestGfx211AllowlistParity.EXPECTED.
rocm7.3 / torch 2.12 do not exist, so no side may floor them speculatively."""
def test_install_sh_known_211_leaf_is_rocm72_and_gfx_allowlist(self):
text = INSTALL_SH.read_text(encoding = "utf-8")
# The 2.11 floor case matches exactly rocm7.2 + the three gfx leaves.
assert re.search(
r"rocm7\.2\|gfx120x-all\|gfx1151\|gfx1150\)", text
), "install.sh 2.11 floor must be exactly rocm7.2|gfx120x-all|gfx1151|gfx1150"
# The 2.11 floor case matches exactly rocm7.2 + the gfx allowlist, in
# any order: it is the same set as TestGfx211AllowlistParity.EXPECTED,
# asserted here so the rocm-version half cannot drift on its own.
m = re.search(r"^\s*(rocm7\.2\|[a-zA-Z0-9|.\-]*)\)", text, re.MULTILINE)
assert m, "install.sh 2.11 floor case (rocm7.2|gfx...) not found / changed"
alternatives = set(m.group(1).lower().split("|"))
assert alternatives == {"rocm7.2"} | TestGfx211AllowlistParity.EXPECTED, (
f"install.sh 2.11 floor is {sorted(alternatives)}, expected "
f"{sorted({'rocm7.2'} | TestGfx211AllowlistParity.EXPECTED)}"
)
# No speculative rocm7.3 anywhere.
assert "rocm7.3" not in text, "install.sh must not reference a non-existent rocm7.3"

View file

@ -0,0 +1,129 @@
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
"""torch / torchcodec ABI guardrails (unslothai/unsloth#7225)."""
from __future__ import annotations
import importlib.util
import re
import sys
import types
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
PYPROJECT = REPO_ROOT / "pyproject.toml"
IMPORT_FIXES_PATH = REPO_ROOT / "unsloth" / "import_fixes.py"
def _load_import_fixes_module():
spec = importlib.util.spec_from_file_location(
"unsloth_import_fixes_under_test",
IMPORT_FIXES_PATH,
)
assert spec and spec.loader
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
def test_pyproject_declares_torch210_audio_extra_with_python_gate():
text = PYPROJECT.read_text(encoding = "utf-8")
assert "audio-torch210 = [" in text
assert "torchcodec>=0.10.0,<0.11.0" in text
assert "python_version >= '3.10'" in text
assert "audio-torch290 = [" in text
assert "audio-torch280 = [" in text
assert "\naudio = [" not in text
def _stub_torch(monkeypatch, version: str):
torch_mod = types.ModuleType("torch")
torch_mod.__version__ = version
monkeypatch.setitem(sys.modules, "torch", torch_mod)
def test_torch210_extras_bundle_audio_torch210():
text = PYPROJECT.read_text(encoding = "utf-8")
for extra in (
"cu128-torch2100",
"cu126-ampere-torch2100",
"rocm72-torch2100",
):
match = re.search(rf"^{extra} = \[(.*?)^\]", text, re.MULTILINE | re.DOTALL)
assert match is not None, extra
assert "unsloth[audio-torch210]" in match.group(1)
def test_torchcodec_matrix_matches_notebook_validator():
from scripts import notebook_validator as nv
fixes = _load_import_fixes_module()
assert fixes._TORCH_TORCHCODEC_MINORS == nv.TORCH_TORCHCODEC
def test_torchcodec_exclusive_upper_bound():
fixes = _load_import_fixes_module()
assert fixes._torchcodec_exclusive_upper("0.10") == "<0.11.0"
assert fixes._torchcodec_exclusive_upper("0.9") == "<0.10.0"
def test_torch290_rejects_torchcodec_07(monkeypatch):
import importlib.metadata
fixes = _load_import_fixes_module()
_stub_torch(monkeypatch, "2.9.0+cu128")
monkeypatch.setattr(importlib.metadata, "version", lambda _name: "0.7.0")
hint = fixes._torchcodec_version_mismatch_hint()
assert hint is not None
assert "audio-torch210" not in hint
def test_torch280_accepts_torchcodec_07(monkeypatch):
import importlib.metadata
fixes = _load_import_fixes_module()
_stub_torch(monkeypatch, "2.8.0+cu128")
monkeypatch.setattr(importlib.metadata, "version", lambda _name: "0.7.0")
assert fixes._torchcodec_version_mismatch_hint() is None
def test_torch210_rejects_torchcodec_011(monkeypatch):
import importlib.metadata
fixes = _load_import_fixes_module()
_stub_torch(monkeypatch, "2.10.0+cu128")
monkeypatch.setattr(
importlib.metadata,
"version",
lambda _name: "0.11.0",
)
hint = fixes._torchcodec_version_mismatch_hint()
assert hint is not None
assert "torchcodec 0.11.0" in hint
assert "audio-torch210" in hint
assert "<0.11.0" in hint
assert "<11.0" not in hint
def test_torch210_accepts_torchcodec_010(monkeypatch):
import importlib.metadata
fixes = _load_import_fixes_module()
_stub_torch(monkeypatch, "2.10.0+cu128")
monkeypatch.setattr(
importlib.metadata,
"version",
lambda _name: "0.10.0+cu128",
)
assert fixes._torchcodec_version_mismatch_hint() is None
def test_import_fixes_loads_on_python39_syntax():
"""Regression: module must import on 3.9 (postponed annotations for str | None)."""
fixes = _load_import_fixes_module()
assert callable(fixes._torchcodec_version_mismatch_hint)