diff --git a/install.sh b/install.sh index d90195399d..bfe45a2c96 100755 --- a/install.sh +++ b/install.sh @@ -259,8 +259,10 @@ run_install_cmd_retry() { # Install bitsandbytes on AMD ROCm hosts. Uses the continuous-release_main # wheel for the ROCm 4-bit GEMV fix (bnb PR #1887, post-0.49.2); bnb <= 0.49.2 -# NaNs at decode shape on every AMD GPU. Falls back to PyPI >=0.49.1 if the -# pre-release URL is unreachable. Drop the pin once bnb 0.50+ ships on PyPI. +# NaNs at decode shape on every AMD GPU. Falls back to PyPI >=0.50.0 if the +# pre-release URL is unreachable: 0.50.0 (2026-07-24) is the first PyPI release +# carrying that fix, and its manylinux x86_64 wheel ships the same +# libbitsandbytes_rocm{64,70,71,714,72}.so set as the pre-release. _install_bnb_rocm() { _label="$1" _venv_py="$2" @@ -277,7 +279,8 @@ _install_bnb_rocm() { esac # uv rejects the continuous-release_main bitsandbytes wheel because the # filename version (1.33.7rc0) does not match the embedded metadata version - # (0.50.0.dev0). pip accepts the mismatch, so bootstrap pip and use it. + # (0.50.x.dev0, whatever main is at). pip accepts the mismatch, so bootstrap + # pip and use it. if ! "$_venv_py" -m pip --version >/dev/null 2>&1; then if ! run_maybe_quiet "$_venv_py" -m ensurepip --upgrade; then run_maybe_quiet uv pip install --python "$_venv_py" pip || \ @@ -304,7 +307,7 @@ _install_bnb_rocm() { substep "[WARN] bnb pre-release install failed; falling back to PyPI (4-bit decode broken on ROCm)" "$C_WARN" fi run_install_cmd "$_label (pypi fallback)" "$_venv_py" -m pip install \ - --force-reinstall --no-cache-dir --no-deps "bitsandbytes>=0.49.1" + --force-reinstall --no-cache-dir --no-deps "bitsandbytes>=0.50.0" } if [ "$_next_is_package" = true ]; then diff --git a/studio/install_python_stack.py b/studio/install_python_stack.py index a91f26910f..c4d9c1049f 100644 --- a/studio/install_python_stack.py +++ b/studio/install_python_stack.py @@ -390,7 +390,8 @@ _GFX_TO_AMD_INDEX_ARCH: dict[str, str] = { # bitsandbytes continuous-release_main wheels with the ROCm 4-bit GEMV fix # (bnb PR #1887, post-0.49.2). bnb <= 0.49.2 NaNs at decode shape on every -# AMD GPU. Drop the pin once bnb 0.50+ ships on PyPI. +# AMD GPU. PyPI 0.50.0 (2026-07-24) is the first release carrying that fix, so +# _BNB_ROCM_PYPI_FALLBACK below is a safe floor when these URLs are unreachable. _BNB_ROCM_PRERELEASE_URLS: dict[str, str] = { "x86_64": ( "https://github.com/bitsandbytes-foundation/bitsandbytes/releases/" @@ -411,7 +412,8 @@ _BNB_ROCM_PRERELEASE_URLS: dict[str, str] = { "bitsandbytes-1.33.7.preview-py3-none-win_amd64.whl" ), } -_BNB_ROCM_PYPI_FALLBACK = "bitsandbytes>=0.49.1" +# Keep in step with the install.sh fallback (and the amd extra, once it lands here). +_BNB_ROCM_PYPI_FALLBACK = "bitsandbytes>=0.50.0" def _bnb_rocm_prerelease_url() -> str | None: @@ -1182,7 +1184,7 @@ def _install_bnb_windows_rocm() -> bool: The continuous-release wheel is intentionally mismatched: the filename encodes 1.33.7.preview (parsed as 1.33.7rc0 by PEP 440) while the wheel - metadata reports 0.50.0.dev0. uv rejects this filename/metadata mismatch, + metadata reports 0.50.x.dev0. uv rejects this filename/metadata mismatch, and bypassing it with UV_SKIP_WHEEL_FILENAME_CHECK still leaves uv mangling the bitsandbytes install. Per the AMD install guide (https://unsloth.ai/docs/get-started/install/amd/amd-hackathon) the wheel diff --git a/tests/python/test_cross_platform_parity.py b/tests/python/test_cross_platform_parity.py index b0a5c763d4..6afbcfe3ef 100644 --- a/tests/python/test_cross_platform_parity.py +++ b/tests/python/test_cross_platform_parity.py @@ -818,3 +818,49 @@ class TestPipNoIndexScrubParity: text = SETUP_PS1.read_text(encoding = "utf-8") assert "'PIP_NO_INDEX'" in text assert "'PIP_INDEX_URL'" in text + + +class TestAmdBnbFloorParity: + """bitsandbytes <= 0.49.2 NaNs at 4-bit decode shape on every AMD GPU; the ROCm + 4-bit GEMV fix (bnb #1887) first ships on PyPI in 0.50.0. The install.sh PyPI + fallback and the Studio stack fallback are the two ways a supported AMD flow + resolves bitsandbytes when the pre-release wheel URL is unreachable, so neither + may float back into the broken range.""" + + FLOOR = "0.50.0" + PYPROJECT = REPO_ROOT / "pyproject.toml" + + def test_install_sh_pypi_fallback_floor(self): + text = INSTALL_SH.read_text(encoding = "utf-8") + assert f'"bitsandbytes>={self.FLOOR}"' in text, ( + f"install.sh _install_bnb_rocm PyPI fallback must floor at {self.FLOOR}" + ) + + def test_stack_py_pypi_fallback_floor(self): + text = STACK_PY.read_text(encoding = "utf-8") + assert f'_BNB_ROCM_PYPI_FALLBACK = "bitsandbytes>={self.FLOOR}"' in text, ( + f"install_python_stack.py PyPI fallback must floor at {self.FLOOR}" + ) + + def test_amd_extra_floor_when_present(self): + """The amd extra is not on this branch yet (#7278). Assert it only once it is, + so the extra and the two installer fallbacks cannot drift apart later.""" + text = self.PYPROJECT.read_text(encoding = "utf-8") + amd = re.search(r"^amd = \[(.*?)^\]", text, re.S | re.M) + if amd is None: + pytest.skip("pyproject.toml has no amd extra on this branch") + specs = re.findall(r'"(bitsandbytes[^"]*)"', amd.group(1)) + assert specs, "the amd extra must pin bitsandbytes" + for spec in specs: + assert spec.startswith(f"bitsandbytes>={self.FLOOR}"), ( + f"amd extra bitsandbytes floor must be >={self.FLOOR}, got {spec!r}" + ) + + def test_no_installer_still_allows_the_broken_range(self): + for path in (INSTALL_SH, INSTALL_PS1, SETUP_PS1, STACK_PY, self.PYPROJECT): + text = path.read_text(encoding = "utf-8") + for line in text.splitlines(): + if "bitsandbytes>=0.49" in line and not line.lstrip().startswith(("#", "//")): + raise AssertionError( + f"{path.name} still floors bitsandbytes in the broken ROCm range: {line.strip()!r}" + )