fix: persist Windows ROCm BNB version (#6048)

* fix: persist Windows ROCm BNB version

* style: apply kwarg spacing hook

* fix: avoid persisting caller ROCm overrides

* fix: redetect managed BNB ROCm defaults

* style: apply ROCm guard test formatting

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
This commit is contained in:
Bubu 2026-06-10 21:01:05 +08:00 committed by GitHub
commit 8af9fe63a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 315 additions and 13 deletions

View file

@ -18,6 +18,7 @@ import re
import shutil
import subprocess
import sys
import sysconfig
import tempfile
import urllib.request
from pathlib import Path
@ -489,6 +490,73 @@ def _detect_bnb_rocm_dll_ver() -> str | None:
return max(all_vers, key = lambda v: int(v)) if all_vers else None
_BNB_ROCM_SITECUSTOMIZE_BEGIN = "# BEGIN Unsloth BNB_ROCM_VERSION"
_BNB_ROCM_SITECUSTOMIZE_END = "# END Unsloth BNB_ROCM_VERSION"
_BNB_ROCM_VERSION_SOURCE_ENV = "UNSLOTH_BNB_ROCM_VERSION_SOURCE"
_BNB_ROCM_VERSION_SOURCE_SITECUSTOMIZE = "sitecustomize"
_BNB_ROCM_VERSION_SOURCE_DETECTED = "detected"
def _persist_bnb_rocm_version(version: str) -> bool:
"""Persist BNB_ROCM_VERSION for future Python processes in this venv."""
version = str(version).strip()
if not version:
return False
site_packages = sysconfig.get_path("purelib")
if not site_packages:
return False
sitecustomize_path = Path(site_packages) / "sitecustomize.py"
block = (
f"{_BNB_ROCM_SITECUSTOMIZE_BEGIN}\n"
"import os as _unsloth_os\n"
"_unsloth_existing_bnb_rocm = _unsloth_os.environ.get('BNB_ROCM_VERSION')\n"
f"_unsloth_os.environ.setdefault('BNB_ROCM_VERSION', {version!r})\n"
"if _unsloth_existing_bnb_rocm is None and "
f"_unsloth_os.environ.get('BNB_ROCM_VERSION') == {version!r}:\n"
" _unsloth_os.environ.setdefault("
f"{_BNB_ROCM_VERSION_SOURCE_ENV!r}, "
f"{_BNB_ROCM_VERSION_SOURCE_SITECUSTOMIZE!r})\n"
"del _unsloth_existing_bnb_rocm\n"
f"{_BNB_ROCM_SITECUSTOMIZE_END}\n"
)
try:
sitecustomize_path.parent.mkdir(parents = True, exist_ok = True)
existing = (
sitecustomize_path.read_text(encoding = "utf-8") if sitecustomize_path.exists() else ""
)
# Strip all managed regions, including one whose END marker was lost to
# an interrupted write, then append exactly one fresh block.
pattern = re.compile(
rf"{re.escape(_BNB_ROCM_SITECUSTOMIZE_BEGIN)}.*?"
rf"(?:{re.escape(_BNB_ROCM_SITECUSTOMIZE_END)}\n?|\Z)",
re.DOTALL,
)
remainder = pattern.sub("", existing)
separator = "" if not remainder or remainder.endswith("\n") else "\n"
updated = f"{remainder}{separator}{block}"
tmp_path = sitecustomize_path.with_name(
f"{sitecustomize_path.name}.unsloth-tmp{os.getpid()}"
)
try:
tmp_path.write_text(updated, encoding = "utf-8")
if sitecustomize_path.exists():
shutil.copymode(sitecustomize_path, tmp_path)
os.replace(tmp_path, sitecustomize_path)
finally:
tmp_path.unlink(missing_ok = True)
except (OSError, UnicodeDecodeError) as exc:
print(
f" Warning: could not persist BNB_ROCM_VERSION={version} "
f"to {sitecustomize_path}: {exc}"
)
return False
return True
def _has_rocm_gpu() -> bool:
"""Return True only if an actual AMD GPU is visible (not just ROCm tools installed)."""
for cmd, check_fn in (
@ -636,15 +704,27 @@ def _install_bnb_windows_rocm() -> bool:
)
if not _ok:
return False
# After install: detect the actual ROCm DLL suffix in the wheel and set
# BNB_ROCM_VERSION so bitsandbytes loads the correct DLL regardless of what
# torch.version.hip reports. The wheel may ship an older suffix (e.g. "72")
# while torch reports a newer HIP version (e.g. 7.13); the override stops
# bitsandbytes from failing on a non-existent DLL. The worker subprocess
# inherits this env var. Fall back to "72" if detection fails (no-op/dry-run).
if "BNB_ROCM_VERSION" not in os.environ:
# After install: detect the actual ROCm DLL suffix shipped in the wheel and
# set BNB_ROCM_VERSION so bitsandbytes loads the correct DLL regardless of
# what torch.version.hip reports. The wheel may ship an older suffix (e.g.
# "72") while torch reports a newer HIP version (e.g. 7.13); the env var
# override ensures bitsandbytes does not fail looking for a non-existent DLL.
# The worker subprocess inherits this env var automatically.
# Fall back to "72" if detection fails (e.g. install was a no-op / dry-run).
_env_ver = os.environ.get("BNB_ROCM_VERSION")
_env_is_persisted_default = (
os.environ.get(_BNB_ROCM_VERSION_SOURCE_ENV) == _BNB_ROCM_VERSION_SOURCE_SITECUSTOMIZE
)
_persist_detected_version = False
if _env_ver and not _env_is_persisted_default:
_ver = _env_ver
else:
_ver = _detect_bnb_rocm_dll_ver() or "72"
os.environ["BNB_ROCM_VERSION"] = _ver
os.environ[_BNB_ROCM_VERSION_SOURCE_ENV] = _BNB_ROCM_VERSION_SOURCE_DETECTED
_persist_detected_version = True
if _persist_detected_version:
_persist_bnb_rocm_version(_ver)
# Make hipInfo.exe (shipped into the venv Scripts dir by the AMD torch
# wheel) resolvable via PATH for this process and every child python the
# installer spawns (import checks, precompile): bitsandbytes runs