Skip to content
43 changes: 39 additions & 4 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,43 @@ run_install_cmd() {
return $_rc
}

# Install bitsandbytes on AMD ROCm hosts. Prefers the continuous-release_main
# wheel from the bnb GitHub release so we pick up the CDNA/RDNA 4-bit GEMV
# fix in PR #1887 (merged 2026-03-09, post-0.49.2). The GEMV kernel in
# bitsandbytes <= 0.49.2 produces NaN at decode-shape (seq_len=1) on every
# ROCm target -- CDNA (gfx90a/gfx942/gfx950 = MI210/MI300X/MI350) via a
# broken blocksize=32/64 warp64 kernel, RDNA (gfx1100-1103/gfx1150-1152)
# via a compile-time warp-size dispatch bug -- so autoregressive generation
# is broken even though training passes. Falls back to PyPI >=0.49.1 when
# the pre-release URL is unreachable (offline installs, firewalled hosts,
# unknown architectures). Drop the pre-release pin once bnb cuts a 0.50+
# tag on PyPI.
_install_bnb_rocm() {
_label="$1"
_venv_py="$2"
case "$_ARCH" in
x86_64|amd64)
_bnb_whl_url="https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_x86_64.whl"
;;
aarch64|arm64)
_bnb_whl_url="https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_aarch64.whl"
Comment on lines +106 to +109

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The wheel URLs contain a hardcoded version number (1.33.7.preview). If the upstream bitsandbytes project updates the version number in their continuous-release_main tag, these URLs will return a 404 error, breaking the installer for ROCm users. Consider if there is a way to resolve the latest asset URL dynamically or ensure the version number remains stable.

;;
*)
_bnb_whl_url=""
;;
esac
Comment on lines +104 to +114

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve maintainability and reduce duplication, you could define a base URL for the wheel and append the architecture-specific part. This would make it easier to update the pinned version in the future.

Suggested change
case "$_ARCH" in
x86_64|amd64)
_bnb_whl_url="https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_x86_64.whl"
;;
aarch64|arm64)
_bnb_whl_url="https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_aarch64.whl"
;;
*)
_bnb_whl_url=""
;;
esac
_bnb_base_url="https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_main/bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24"
case "$_ARCH" in
x86_64|amd64)
_bnb_whl_url="${_bnb_base_url}_x86_64.whl"
;;
aarch64|arm64)
_bnb_whl_url="${_bnb_base_url}_aarch64.whl"
;;
*)
_bnb_whl_url=""
;;
esac

if [ -n "$_bnb_whl_url" ]; then
substep "installing bitsandbytes for AMD ROCm (pre-release main, bnb PR #1887 GEMV fix)..."
if run_install_cmd "$_label (pre-release)" uv pip install --python "$_venv_py" \
--force-reinstall --no-cache-dir --no-deps "$_bnb_whl_url"; then
return 0
fi
substep "[WARN] bnb pre-release wheel unreachable; falling back to PyPI >=0.49.1 (4-bit decode will be broken on ROCm -- use 16-bit instead)" "$C_WARN"
fi
run_install_cmd "$_label (pypi fallback)" uv pip install --python "$_venv_py" \
--force-reinstall --no-cache-dir --no-deps "bitsandbytes>=0.49.1"
}

if [ "$_next_is_package" = true ]; then
echo "❌ ERROR: --package requires an argument." >&2
exit 1
Expand Down Expand Up @@ -1296,8 +1333,7 @@ if [ "$_MIGRATED" = true ]; then
if [ "$SKIP_TORCH" = false ]; then
case "$TORCH_INDEX_URL" in
*/rocm*)
substep "installing bitsandbytes for AMD ROCm..."
run_install_cmd "install bitsandbytes (AMD)" uv pip install --python "$_VENV_PY" --force-reinstall --no-cache-dir --no-deps "bitsandbytes>=0.49.1"
_install_bnb_rocm "install bitsandbytes (AMD)" "$_VENV_PY"
# Repair ROCm torch if overwritten during migrated install
_has_hip=$("$_VENV_PY" -c "import torch; print(getattr(torch.version,'hip','') or '')" 2>/dev/null || true)
if [ -z "$_has_hip" ]; then
Expand Down Expand Up @@ -1437,8 +1473,7 @@ elif [ -n "$TORCH_INDEX_URL" ]; then
if [ "$SKIP_TORCH" = false ]; then
case "$TORCH_INDEX_URL" in
*/rocm*)
substep "installing bitsandbytes for AMD ROCm..."
run_install_cmd "install bitsandbytes (AMD)" uv pip install --python "$_VENV_PY" --force-reinstall --no-cache-dir --no-deps "bitsandbytes>=0.49.1"
_install_bnb_rocm "install bitsandbytes (AMD)" "$_VENV_PY"
;;
esac
fi
Expand Down
110 changes: 102 additions & 8 deletions studio/install_python_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,41 @@
}
_PYTORCH_WHL_BASE = "https://download.pytorch.org/whl"

# bitsandbytes continuous-release_main wheels. bnb <= 0.49.2 ships with a
# broken 4-bit GEMV kernel on every ROCm target: CDNA (gfx90a / gfx942 /
# gfx950) via a broken blocksize=32/64 warp64 kernel, and RDNA3/3.5
# (gfx1100-1103 / gfx1150-1152) via a compile-time warp-size dispatch bug.
# At decode shape (seq_len=1) the GEMV returns NaN, so autoregressive
# generation is broken even though training passes. bnb commit 713a3b8
# ("[ROCm] Enable blocksize 32 4-bit quantization and GEMV kernels on AMD
# CDNA", PR #1887, merged 2026-03-09) fixes both bugs but has not shipped
# to PyPI yet. Pin to the continuous-release_main wheels so ROCm users
# get correct 4-bit decode on install. Drop the pin once bnb cuts a
# 0.50+ tag on PyPI.
_BNB_ROCM_PRERELEASE_URLS: dict[str, str] = {
"x86_64": (
"https://github.com/bitsandbytes-foundation/bitsandbytes/releases/"
"download/continuous-release_main/"
"bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_x86_64.whl"
),
"aarch64": (
"https://github.com/bitsandbytes-foundation/bitsandbytes/releases/"
"download/continuous-release_main/"
"bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_aarch64.whl"
Comment on lines +53 to +58

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the shell script, these URLs hardcode the 1.33.7.preview version. This makes the installation process fragile if the upstream continuous release updates its versioning. Since this is a pre-release pin, it might be worth adding a comment about the expected stability of this specific version string.

),
}
Comment on lines +49 to +60

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve maintainability, you could define the base URL and wheel filename as constants. This avoids repeating the long URL string and makes future version updates easier.

_BNB_BASE_URL = (
    "https://github.com/bitsandbytes-foundation/bitsandbytes/releases/"
    "download/continuous-release_main"
)
_BNB_WHEEL_TEMPLATE = "bitsandbytes-1.33.7.preview-py3-none-manylinux_2_24_{arch}.whl"
_BNB_ROCM_PRERELEASE_URLS: dict[str, str] = {
    "x86_64": f"{_BNB_BASE_URL}/{_BNB_WHEEL_TEMPLATE.format(arch='x86_64')}",
    "aarch64": f"{_BNB_BASE_URL}/{_BNB_WHEEL_TEMPLATE.format(arch='aarch64')}",
}

_BNB_ROCM_PYPI_FALLBACK = "bitsandbytes>=0.49.1"


def _bnb_rocm_prerelease_url() -> str | None:
"""Return the continuous-release_main bnb wheel URL for the current
architecture, or None when no pre-release wheel is available.
Normalises amd64/arm64 aliases to x86_64/aarch64.
"""
arch = platform.machine().lower()
arch = {"amd64": "x86_64", "arm64": "aarch64"}.get(arch, arch)
return _BNB_ROCM_PRERELEASE_URLS.get(arch)


def _detect_rocm_version() -> tuple[int, int] | None:
"""Return (major, minor) of the installed ROCm stack, or None."""
Expand Down Expand Up @@ -290,15 +325,42 @@ def _ensure_rocm_torch() -> None:
# runtime is older than any published torch wheel. Uses
# --force-reinstall so an existing CPU/CUDA bitsandbytes is replaced
# by the AMD build during upgrades.
#
# Prefer the continuous-release_main wheel, which contains the CDNA
# and RDNA 4-bit GEMV fix (bnb PR #1887, merged 2026-03-09, post-0.49.2).
# Without that fix, autoregressive decode on every ROCm GPU produces
# NaN at seq_len=1 and generation returns gibberish or crashes in
# torch.multinomial. Falls back to PyPI >=0.49.1 on unknown architectures
# or when the pre-release URL is unreachable (offline / firewalled hosts).
if rocm_torch_ready:
pip_install(
"bitsandbytes (AMD)",
"--force-reinstall",
"--no-cache-dir",
"--no-deps",
"bitsandbytes>=0.49.1",
constrain = False,
)
_bnb_url = _bnb_rocm_prerelease_url()
_bnb_installed = False
if _bnb_url is not None:
_bnb_installed = pip_install_try(
"bitsandbytes (AMD, pre-release main)",
"--force-reinstall",
"--no-cache-dir",
"--no-deps",
_bnb_url,
constrain = False,
)
if not _bnb_installed:
print(
_red(
" bnb pre-release wheel unreachable; falling back "
"to PyPI (4-bit decode will be broken on ROCm -- "
"use 16-bit instead)"
)
)
if not _bnb_installed:
pip_install(
"bitsandbytes (AMD)",
"--force-reinstall",
"--no-cache-dir",
"--no-deps",
_BNB_ROCM_PYPI_FALLBACK,
constrain = False,
)


def _infer_no_torch() -> bool:
Expand Down Expand Up @@ -593,6 +655,38 @@ def _build_uv_cmd(args: tuple[str, ...]) -> list[str]:
return cmd


def pip_install_try(
label: str,
*args: str,
constrain: bool = True,
) -> bool:
"""Try to install with pip/uv. Returns True on success, False on failure
(without raising or exiting). For optional install attempts with a
follow-up fallback, such as the bnb ROCm pre-release wheel.
"""
constraint_args: list[str] = []
if constrain and CONSTRAINTS.is_file():
constraint_args = ["-c", str(CONSTRAINTS)]

if USE_UV:
cmd = _build_uv_cmd(args) + constraint_args
else:
cmd = _build_pip_cmd(args) + constraint_args

if VERBOSE:
_step(_LABEL, f"{label}...", _dim)
result = subprocess.run(
cmd,
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT,
)
if result.returncode == 0:
return True
if VERBOSE and result.stdout:
print(result.stdout.decode(errors = "replace"))
return False
Comment on lines +638 to +666

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The pip_install_try function is a good addition for handling optional installation steps. However, it uses print directly for error output (line 686). To maintain consistency with the rest of the installer and ensure compatibility with potentially non-UTF-8 consoles on Windows, consider using the _safe_print helper defined earlier in the file.

def pip_install_try(
    label: str,
    *args: str,
    constrain: bool = True,
) -> bool:
    """Try to install with pip/uv. Returns True on success, False on failure
    (without raising or exiting). For optional install attempts with a
    follow-up fallback, such as the bnb ROCm pre-release wheel.
    """
    constraint_args: list[str] = []
    if constrain and CONSTRAINTS.is_file():
        constraint_args = ["-c", str(CONSTRAINTS)]

    if USE_UV:
        cmd = _build_uv_cmd(args) + constraint_args
    else:
        cmd = _build_pip_cmd(args) + constraint_args

    if VERBOSE:
        _step(_LABEL, f"{label}...", _dim)
    result = subprocess.run(
        cmd,
        stdout = subprocess.PIPE,
        stderr = subprocess.STDOUT,
    )
    if result.returncode == 0:
        return True
    if VERBOSE and result.stdout:
        _safe_print(result.stdout.decode(errors = "replace"))
    return False



def pip_install(
label: str,
*args: str,
Expand Down
Loading