From 5048bb766dbddf993abc400ea4b097d77abb3659 Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 14 Jun 2026 02:42:10 -0300 Subject: [PATCH 1/8] Studio: source CPU llama.cpp prebuilts from the unslothai fork --- .../tests/test_install_resolve_prebuilt.py | 61 +--------- studio/install_llama_prebuilt.py | 40 ++----- studio/setup.ps1 | 11 +- studio/setup.sh | 72 +++--------- .../install/test_llama_pr_force_and_source.py | 15 +-- tests/studio/install/test_pr4562_bugfixes.py | 16 +-- tests/studio/install/test_rocm_support.py | 109 ++++++++---------- tests/studio/install/test_selection_logic.py | 101 ++++++++++++++-- 8 files changed, 192 insertions(+), 233 deletions(-) diff --git a/studio/backend/tests/test_install_resolve_prebuilt.py b/studio/backend/tests/test_install_resolve_prebuilt.py index ede5629664b..7e0e2ea5fdb 100644 --- a/studio/backend/tests/test_install_resolve_prebuilt.py +++ b/studio/backend/tests/test_install_resolve_prebuilt.py @@ -24,13 +24,10 @@ ilp = importlib.import_module("install_llama_prebuilt") -if not hasattr(ilp, "published_repo_for_host") or not hasattr( - ilp, "resolve_simple_install_release_plans" -): +if not hasattr(ilp, "resolve_simple_install_release_plans"): pytest.skip("PR symbols not present - check branch", allow_module_level = True) FORK = ilp.DEFAULT_PUBLISHED_REPO # unslothai/llama.cpp -UPSTREAM = ilp.UPSTREAM_REPO # ggml-org/llama.cpp def _host(**kw): @@ -56,55 +53,6 @@ def _host(**kw): return ilp.HostInfo(**base) -def test_published_repo_for_host(): - # CPU-only Linux (x64 and arm64) -> ggml-org upstream. - assert ilp.published_repo_for_host(_host(is_linux = True, is_x86_64 = True)) == UPSTREAM - assert ( - ilp.published_repo_for_host(_host(is_linux = True, is_arm64 = True, machine = "aarch64")) - == UPSTREAM - ) - # GPU Linux -> fork. - assert ( - ilp.published_repo_for_host(_host(is_linux = True, is_x86_64 = True, has_usable_nvidia = True)) - == FORK - ) - assert ilp.published_repo_for_host(_host(is_linux = True, is_x86_64 = True, has_rocm = True)) == FORK - # CPU-only Windows -> ggml-org (setup.ps1: the fork ships no win-cpu bundle). - assert ( - ilp.published_repo_for_host(_host(system = "Windows", is_windows = True, is_x86_64 = True)) - == UPSTREAM - ) - # GPU Windows -> fork. - assert ( - ilp.published_repo_for_host( - _host(system = "Windows", is_windows = True, is_x86_64 = True, has_usable_nvidia = True) - ) - == FORK - ) - # macOS -> fork regardless of GPU (ggml-org macOS bundles need too-new macOS). - assert ( - ilp.published_repo_for_host( - _host(system = "Darwin", is_macos = True, is_arm64 = True, machine = "arm64") - ) - == FORK - ) - # Linux with AMD tooling but no probed GPU -> fork (setup.sh routes on tooling). - assert ( - ilp.published_repo_for_host( - _host(is_linux = True, is_x86_64 = True), linux_amd_tooling_present = True - ) - == FORK - ) - # The tooling hint is Linux-only: Windows CPU stays on ggml-org. - assert ( - ilp.published_repo_for_host( - _host(system = "Windows", is_windows = True, is_x86_64 = True), - linux_amd_tooling_present = True, - ) - == UPSTREAM - ) - - def _run_resolve(monkeypatch, capsys, plans_or_exc): monkeypatch.setattr( ilp, @@ -150,11 +98,10 @@ def test_resolve_prebuilt_unavailable(monkeypatch, capsys): assert out["repo"] == FORK -def test_resolve_prebuilt_linux_amd_tooling_routes_to_fork(monkeypatch, capsys): - # CPU-probed Linux host but rocminfo on PATH: the dispatch must route to the - # fork so a HIP source build is not offered an upstream CPU prebuilt. +def test_resolve_prebuilt_cpu_linux_routes_to_fork(monkeypatch, capsys): + # CPU-only Linux host (no GPU): the dispatch routes to the fork, which now + # ships the CPU prebuilt -- it no longer falls back to ggml-org upstream. monkeypatch.setattr(ilp, "detect_host", lambda: _host(is_linux = True, is_x86_64 = True)) - monkeypatch.setattr(ilp.shutil, "which", lambda tool: tool == "rocminfo") seen = {} def _resolver(tag, host, repo, published_release_tag): diff --git a/studio/install_llama_prebuilt.py b/studio/install_llama_prebuilt.py index b33d0b63253..32ceb636390 100644 --- a/studio/install_llama_prebuilt.py +++ b/studio/install_llama_prebuilt.py @@ -3082,21 +3082,6 @@ def _apply_host_overrides( return host -def published_repo_for_host(host: HostInfo, *, linux_amd_tooling_present: bool = False) -> str: - """The release repo setup.sh / setup.ps1 pick for this host: macOS always the - fork (ggml-org macOS bundles need too-new macOS); else CPU-only Linux/Windows - -> ggml-org upstream (the fork ships no CPU bundle) and any usable GPU (NVIDIA - or ROCm) -> the fork. linux_amd_tooling_present mirrors setup.sh routing Linux - hosts that expose AMD tooling (rocminfo/amd-smi/hipconfig/hipinfo) to the fork - even when the probe cannot confirm an active GPU. Mirrors the shell routing.""" - if host.is_macos: - return DEFAULT_PUBLISHED_REPO - has_gpu = ( - host.has_usable_nvidia or host.has_rocm or (host.is_linux and linux_amd_tooling_present) - ) - return DEFAULT_PUBLISHED_REPO if has_gpu else UPSTREAM_REPO - - def pick_windows_cuda_runtime(host: HostInfo) -> str | None: if not host.driver_cuda_version: return None @@ -4037,6 +4022,9 @@ def resolve_release_asset_choice( published_choice = published_rocm_choice_for_host(release, host, "windows-rocm") else: published_choice = published_asset_choice_for_kind(release, "windows-cpu") + elif host.is_windows and host.is_arm64: + # Windows arm64 has no GPU prebuilt, so it always takes the CPU bundle. + published_choice = published_asset_choice_for_kind(release, "windows-arm64") elif host.is_macos and host.is_arm64: published_choice = published_asset_choice_for_kind(release, "macos-arm64") elif host.is_macos and host.is_x86_64: @@ -5962,8 +5950,10 @@ def _linux_published_attempts(host: HostInfo, bundle: PublishedReleaseBundle) -> # CPU-only host. A usable-NVIDIA host never reaches here -- if its CUDA # selection produced nothing we want an empty attempt list so the caller # source-builds with CUDA, not a CPU-only binary silently installed on a - # GPU host (mirrors the ROCm branch, and Windows NVIDIA). - cpu_choice = published_asset_choice_for_kind(bundle, "linux-cpu") + # GPU host (mirrors the ROCm branch, and Windows NVIDIA). x64 takes the + # linux-cpu bundle, arm64 the linux-arm64 one (manifest install_kinds). + kind = "linux-arm64" if host.is_arm64 else "linux-cpu" + cpu_choice = published_asset_choice_for_kind(bundle, kind) if cpu_choice is not None: attempts.append(cpu_choice) return attempts @@ -6829,24 +6819,16 @@ def main() -> int: return EXIT_SUCCESS if args.resolve_prebuilt is not None: - # Host-aware "is a prebuilt available" probe, no download. A default repo - # means "pick the repo for this host"; PrebuiltFallback == source build. + # Host-aware "is a prebuilt available" probe, no download. Every host now + # plans against the fork (args.published_repo defaults to it); an explicit + # --published-repo overrides. PrebuiltFallback == source build. host = _apply_host_overrides( detect_host(), override_has_rocm = args.has_rocm, override_rocm_gfx = args.rocm_gfx, force_cpu = args.cpu_fallback, ) - # setup.sh routes Linux hosts with AMD tooling to the fork even when no GPU - # is probed; mirror that so a HIP source build is not offered a CPU prebuilt. - amd_tooling = host.is_linux and any( - shutil.which(t) for t in ("rocminfo", "amd-smi", "hipconfig", "hipinfo") - ) - repo = ( - published_repo_for_host(host, linux_amd_tooling_present = amd_tooling) - if args.published_repo == DEFAULT_PUBLISHED_REPO - else args.published_repo - ) + repo = args.published_repo try: _requested, plans = resolve_simple_install_release_plans( args.resolve_prebuilt, host, repo, args.published_release_tag or "" diff --git a/studio/setup.ps1 b/studio/setup.ps1 index acc7e928755..5e64d64a86a 100644 --- a/studio/setup.ps1 +++ b/studio/setup.ps1 @@ -2642,12 +2642,11 @@ $LlamaCppDir = Join-Path $UnslothHome "llama.cpp" $NeedLlamaSourceBuild = $false $SkipPrebuiltInstall = $false $RequestedLlamaTag = if ($env:UNSLOTH_LLAMA_TAG) { $env:UNSLOTH_LLAMA_TAG } else { $DefaultLlamaTag } -# GPU Windows (CUDA / ROCm) installs the fork's app-* prebuilts; CPU-only stays -# on ggml-org (the fork ships no windows-cpu bundle). Mirrors setup.sh's routing. -# A resolved gfx arch counts as a GPU host even when $HasROCm is false (Adrenalin -# driver only, no HIP runtime): the fork's per-gfx bundle ships its own runtime, -# so route there instead of ggml-org / a CPU build. -$HelperReleaseRepo = if ($HasNvidiaSmi -or $HasROCm -or $script:ROCmGfxArch) { "unslothai/llama.cpp" } else { "ggml-org/llama.cpp" } +# Every host installs the fork's app-* prebuilts now: GPU Windows (CUDA / ROCm) +# already did, and the fork now also ships the CPU bundles for Windows x64 and +# arm64 (windows-cpu / windows-arm64). ggml-org artifacts are no longer used by +# default. Mirrors setup.sh's routing. +$HelperReleaseRepo = "unslothai/llama.cpp" $LlamaPr = if ($env:UNSLOTH_LLAMA_PR) { $env:UNSLOTH_LLAMA_PR.Trim() } else { "" } $LlamaPrForce = if ($env:UNSLOTH_LLAMA_PR_FORCE) { $env:UNSLOTH_LLAMA_PR_FORCE.Trim() } else { $DefaultLlamaPrForce } diff --git a/studio/setup.sh b/studio/setup.sh index 4fba761cef9..66aa11707ef 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -977,63 +977,19 @@ _REQUESTED_LLAMA_TAG="${UNSLOTH_LLAMA_TAG:-${_DEFAULT_LLAMA_TAG}}" _HOST_SYSTEM="$(uname -s 2>/dev/null || true)" _HOST_MACHINE="$(uname -m 2>/dev/null || true)" -# Pick the release repo install_llama_prebuilt.py plans against. -# The fork ships CUDA (Linux x64/arm64, Windows), ROCm (Linux/Windows) and -# macOS bundles. Only the plain CPU/Vulkan bundles still come from ggml-org, so -# CPU-only Linux (x86_64 and arm64) routes there; GPU Linux, Windows and macOS -# use unslothai. -_LINUX_HAS_GPU=false -# Route to the fork only for a usable GPU. NVIDIA counts only when a device is -# actually enumerated and not hidden via CUDA_VISIBLE_DEVICES=""/-1 -# (_setup_nvidia_usable, from _setup_has_usable_nvidia_gpu above) -- mirroring -# install_llama_prebuilt.py's has_usable_nvidia. Mere nvidia-smi presence -# (CPU-only CUDA-toolkit containers, broken drivers) or a hidden GPU therefore -# takes the ggml-org CPU prebuilt instead of a slow source build. AMD is -# deliberately left on tooling presence, not usability: an unusable NVIDIA host -# has a good CPU prebuilt to fall back to, whereas tightening AMD would regress -# ROCm hosts exposing only hipconfig/hipinfo into an unnecessary CPU build. -if [ "$_setup_nvidia_usable" = true ]; then - _LINUX_HAS_GPU=true -else - for _GPU_TOOL in rocminfo amd-smi hipconfig hipinfo; do - if command -v "$_GPU_TOOL" >/dev/null 2>&1; then - _LINUX_HAS_GPU=true - break - fi - done -fi +# Pick the release repo install_llama_prebuilt.py plans against. Every host this +# installer supports now pulls its llama.cpp prebuilt from the unslothai fork: it +# ships the CUDA (Linux x64/arm64, Windows), ROCm (Linux/Windows) and macOS +# bundles, plus the CPU (and Vulkan) bundles for Linux/Windows on both x86_64 and +# arm64. ggml-org artifacts are no longer used by default. +_HELPER_RELEASE_REPO="unslothai/llama.cpp" # UNSLOTH_ROCM_GFX_ARCH may be set on a host where no probe fired, so the override # nested in the AMD-detected branch above never ran and _setup_gfx is still empty. -# Honour it here so the routing guard below and the --rocm-gfx forwarding both see -# it (install_llama_prebuilt.py reads the same env var as the --rocm-gfx default). +# Honour it here so the --rocm-gfx forwarding below still sees it +# (install_llama_prebuilt.py reads the same env var as the --rocm-gfx default). if [ "$_setup_nvidia_usable" != true ] && [ -z "${_setup_gfx:-}" ] && [ -n "${UNSLOTH_ROCM_GFX_ARCH:-}" ]; then _setup_gfx="${UNSLOTH_ROCM_GFX_ARCH}" fi -# A resolved/forwarded gfx arch (UNSLOTH_ROCM_GFX_ARCH) means an AMD GPU even when -# no ROCm tooling is on PATH; route it to the fork so the per-gfx prebuilt is -# picked instead of ggml-org / a source build. -if [ "$_LINUX_HAS_GPU" = false ] && [ -n "${_setup_gfx:-}" ]; then - _LINUX_HAS_GPU=true -fi - -if [ "$_HOST_SYSTEM" = "Linux" ] \ - && [ "$_HOST_MACHINE" = "x86_64" ] \ - && [ "$_LINUX_HAS_GPU" = false ]; then - _HELPER_RELEASE_REPO="ggml-org/llama.cpp" -elif [ "$_HOST_SYSTEM" = "Linux" ] \ - && { [ "$_HOST_MACHINE" = "aarch64" ] || [ "$_HOST_MACHINE" = "arm64" ]; } \ - && [ "$_LINUX_HAS_GPU" = false ]; then - # CPU-only Linux ARM64 (Ampere Altra, Raspberry Pi 5, GitHub - # `ubuntu-24.04-arm`, CPU-only Jetson rescue mode, ...). The fork ships no - # arm64 CPU bundle, so without this branch the prebuilt resolver returns 0 - # attempts and the installer falls back to a source build. ggml-org ships - # llama-bNNNN-bin-ubuntu-arm64.tar.gz from at least b9072 onward. - _HELPER_RELEASE_REPO="ggml-org/llama.cpp" -else - # GPU Linux (x64 CUDA/ROCm, arm64 CUDA), Windows (CUDA/ROCm), and macOS. - _HELPER_RELEASE_REPO="unslothai/llama.cpp" -fi -unset _GPU_TOOL _LLAMA_PR="${UNSLOTH_LLAMA_PR:-}" _SKIP_PREBUILT_INSTALL=false _LLAMA_PR_FORCE="${UNSLOTH_LLAMA_PR_FORCE:-${_DEFAULT_LLAMA_PR_FORCE}}" @@ -1654,19 +1610,19 @@ else fi # end _SKIP_GGUF_BUILD check # ── arm64 Linux GPU: CPU prebuilt as a last resort ── -# arm64 Linux with a GPU has no CUDA prebuilt anywhere (the unslothai fork is -# x64 only; ggml-org ships no Linux CUDA build), so it source-builds for the -# GPU above. If that produced no binary, install ggml-org's arm64 CPU prebuilt -# instead of leaving the host without llama.cpp. +# An arm64 Linux GPU host source-builds for the GPU above. If that produced no +# binary, install the fork's arm64 CPU prebuilt (app--linux-arm64-cpu.tar.gz) +# instead of leaving the host without llama.cpp. --cpu-fallback drops the GPU +# attributes so the CPU bundle is selected rather than re-attempting CUDA. if [ "$_LLAMA_CPP_DEGRADED" = true ] \ && [ "$_HOST_SYSTEM" = "Linux" ] \ && { [ "$_HOST_MACHINE" = "aarch64" ] || [ "$_HOST_MACHINE" = "arm64" ]; }; then - substep "GPU source build unavailable; trying ggml-org arm64 CPU prebuilt..." + substep "GPU source build unavailable; trying arm64 CPU prebuilt..." _ARM64_CPU_CMD=( python "$SCRIPT_DIR/install_llama_prebuilt.py" --install-dir "$LLAMA_CPP_DIR" --llama-tag "$_REQUESTED_LLAMA_TAG" - --published-repo "ggml-org/llama.cpp" + --published-repo "unslothai/llama.cpp" --cpu-fallback ) # Trust the installer's exit code: it validates the server before exiting 0, diff --git a/tests/studio/install/test_llama_pr_force_and_source.py b/tests/studio/install/test_llama_pr_force_and_source.py index 86438061274..9ebf9ec3211 100644 --- a/tests/studio/install/test_llama_pr_force_and_source.py +++ b/tests/studio/install/test_llama_pr_force_and_source.py @@ -396,8 +396,11 @@ def test_source_env_override_removed(self): assert '_LLAMA_SOURCE="${_DEFAULT_LLAMA_SOURCE}"' in self.content def test_release_repo_override_removed(self): + # No env-based release-repo override, and CPU-only hosts no longer fall + # back to ggml-org -- every host now routes to the fork. assert "UNSLOTH_LLAMA_RELEASE_REPO:-unslothai/llama.cpp" not in self.content - assert '_HELPER_RELEASE_REPO="ggml-org/llama.cpp"' in self.content + assert '_HELPER_RELEASE_REPO="unslothai/llama.cpp"' in self.content + assert '_HELPER_RELEASE_REPO="ggml-org/llama.cpp"' not in self.content def test_force_compile_skips_prebuilt_resolution_early(self): assert 'if [ "$_LLAMA_FORCE_COMPILE" = "1" ]; then' in self.content @@ -463,13 +466,11 @@ def test_source_env_override_removed(self): assert "$LlamaSource = $DefaultLlamaSource" in self.content def test_release_repo_override_removed(self): - # No env-based release-repo override; the repo is chosen by GPU detection - # (GPU -> fork, CPU -> ggml-org), mirroring setup.sh. + # No env-based release-repo override; every host now routes to the fork + # (the CPU-only ggml-org fallback was removed), mirroring setup.sh. assert "$HelperReleaseRepo = if ($env:UNSLOTH_LLAMA_RELEASE_REPO)" not in self.content - assert ( - "$HelperReleaseRepo = if ($HasNvidiaSmi -or $HasROCm -or $script:ROCmGfxArch) " - '{ "unslothai/llama.cpp" } else { "ggml-org/llama.cpp" }' in self.content - ) + assert '$HelperReleaseRepo = "unslothai/llama.cpp"' in self.content + assert "$HelperReleaseRepo = if (" not in self.content def test_force_compile_skips_prebuilt_resolution_early(self): assert 'if ($env:UNSLOTH_LLAMA_FORCE_COMPILE -eq "1") {' in self.content diff --git a/tests/studio/install/test_pr4562_bugfixes.py b/tests/studio/install/test_pr4562_bugfixes.py index 9dfa4e0005e..3d09f4881ad 100644 --- a/tests/studio/install/test_pr4562_bugfixes.py +++ b/tests/studio/install/test_pr4562_bugfixes.py @@ -694,17 +694,17 @@ def test_setup_sh_prebuilt_install_entrypoint(self): assert "_HELPER_RELEASE_REPO}/releases/latest" not in content assert "ggml-org/llama.cpp/releases/latest" not in content - def test_setup_sh_routes_to_fork_only_on_usable_gpu(self): - """Linux fork-vs-ggml routing must gate NVIDIA on actual GPU usability, - not mere nvidia-smi presence, so CPU-only / hidden-GPU hosts (e.g. - CUDA_VISIBLE_DEVICES=-1) get the ggml CPU prebuilt instead of a source - build. Guards against a silent revert to the old presence-only loop.""" + def test_setup_sh_routes_every_host_to_fork(self): + """CPU-only Linux (the last ggml-org artifact consumer) now routes to the + fork like every other host, so the release-repo decision is unconditional. + Guards against a silent reintroduction of a ggml-org CPU routing branch. + GPU usability detection (used for PyTorch / source decisions) must stay.""" content = SETUP_SH.read_text() + assert '_HELPER_RELEASE_REPO="unslothai/llama.cpp"' in content + assert '_HELPER_RELEASE_REPO="ggml-org/llama.cpp"' not in content + # Usability gating (not routing) still distinguishes a hidden GPU. assert '[ "$_setup_nvidia_usable" = true ]' in content assert "CUDA_VISIBLE_DEVICES" in content - # nvidia-smi must NOT be back in the bare presence loop. - assert "for _GPU_TOOL in nvidia-smi" not in content - assert "for _GPU_TOOL in rocminfo amd-smi hipconfig hipinfo" in content def test_setup_sh_reports_installed_prebuilt_release(self): """Shell wrapper should report the installed prebuilt release from metadata.""" diff --git a/tests/studio/install/test_rocm_support.py b/tests/studio/install/test_rocm_support.py index a3c9555d0b6..ce3ce08144e 100644 --- a/tests/studio/install/test_rocm_support.py +++ b/tests/studio/install/test_rocm_support.py @@ -3219,25 +3219,25 @@ def test_setup_ps1_forwards_rocm_gfx(self): assert "--rocm-gfx" in source assert "$script:ROCmGfxArch" in source - def test_setup_sh_routes_inferred_gfx_to_fork(self): - # A forwarded/inferred gfx arch must route to the fork even without ROCm - # tooling on PATH, so the per-gfx prebuilt is picked over ggml-org. Pin - # the routing guard specifically -- a bare "${_setup_gfx:-}" check also - # appears in the unrelated --rocm-gfx forwarding block. + def test_setup_sh_routes_unconditionally_to_fork(self): + # CPU-only hosts no longer fall back to ggml-org -- the release-repo + # decision is an unconditional fork assignment now. Pin the line text. source = _SETUP_SH_PATH.read_text(encoding = "utf-8") - assert '[ "$_LINUX_HAS_GPU" = false ] && [ -n "${_setup_gfx:-}" ]' in source + assert '_HELPER_RELEASE_REPO="unslothai/llama.cpp"' in source + assert '_HELPER_RELEASE_REPO="ggml-org/llama.cpp"' not in source - def test_setup_ps1_routes_inferred_gfx_to_fork(self): - # Same on Windows: a resolved $script:ROCmGfxArch counts as a fork/GPU - # install even when $HasROCm is false (Adrenalin-only, no HIP runtime). + def test_setup_ps1_routes_unconditionally_to_fork(self): + # Same on Windows: the fork now ships the windows-cpu / windows-arm64 + # bundles, so $HelperReleaseRepo is an unconditional fork assignment. source = _SETUP_PS1_PATH.read_text(encoding = "utf-8") - assert "$HasNvidiaSmi -or $HasROCm -or $script:ROCmGfxArch" in source + assert '$HelperReleaseRepo = "unslothai/llama.cpp"' in source + assert "$HelperReleaseRepo = if (" not in source - # The two assertions above pin the guard *text*. The tests below *execute* - # the real routing block from setup.sh / setup.ps1 and assert the resolved - # release repo, so a refactor that keeps the literal but breaks (or drops) - # the inferred-gfx -> fork decision is still caught. All inputs are faked -- - # no GPU, no ROCm tooling on PATH, no network. + # The text pins above guard the literal. The tests below *execute* the real + # routing line from setup.sh / setup.ps1 and assert the resolved release repo, + # so a refactor that reintroduces a conditional (or a ggml-org branch) is still + # caught. Inputs are varied -- CPU-only, inferred/forwarded gfx, usable NVIDIA -- + # to prove no host slips back onto ggml-org. No GPU, no tooling, no network. @staticmethod def _resolve_setup_sh_repo( @@ -3247,18 +3247,17 @@ def _resolve_setup_sh_repo( rocm_gfx_arch_env = "", ): """Run setup.sh's release-repo routing block under bash and return the - resolved _HELPER_RELEASE_REPO. PATH is emptied so the rocminfo/amd-smi/ - hipconfig/hipinfo `command -v` probes all miss (no ROCm tooling). - rocm_gfx_arch_env populates UNSLOTH_ROCM_GFX_ARCH for the env-forwarded - path that fires when no probe set _setup_gfx.""" + resolved _HELPER_RELEASE_REPO. PATH is emptied so any stray tooling probe + misses; routing is unconditional, so the GPU inputs only prove no branch + reroutes a host to ggml-org.""" import shutil bash = shutil.which("bash") if bash is None: pytest.skip("bash not available") source = _SETUP_SH_PATH.read_text(encoding = "utf-8") - start = source.index("\n_LINUX_HAS_GPU=false\n") + 1 - end = source.index("\nunset _GPU_TOOL", start) + len("\nunset _GPU_TOOL") + start = source.index('\n_HELPER_RELEASE_REPO="unslothai/llama.cpp"\n') + 1 + end = source.index("\n_LLAMA_PR=", start) block = source[start:end] assert "_HELPER_RELEASE_REPO" in block, "setup.sh routing anchors not found" env = { @@ -3280,31 +3279,31 @@ def _resolve_setup_sh_repo( assert result.returncode == 0, result.stderr return result.stdout.strip() - def test_setup_sh_inferred_gfx_resolves_to_fork(self): - # No usable NVIDIA, no ROCm tooling on PATH, only a name-inferred gfx - # arch -> the host must still be treated as a GPU host and routed to the - # fork's per-gfx prebuilt, not ggml-org / a source build. Linux x64 and - # arm64 both go through the same fork branch. - assert self._resolve_setup_sh_repo("x86_64", False, "gfx1100") == "unslothai/llama.cpp" - assert self._resolve_setup_sh_repo("aarch64", False, "gfx1100") == "unslothai/llama.cpp" - - def test_setup_sh_env_forwarded_gfx_resolves_to_fork(self): - # UNSLOTH_ROCM_GFX_ARCH set on a host where no probe fired (_setup_gfx - # empty, no usable NVIDIA, no ROCm tooling): setup.sh adopts the env arch - # and routes to the fork, same as the name-inference path. - repo = self._resolve_setup_sh_repo("x86_64", False, "", rocm_gfx_arch_env = "gfx1100") - assert repo == "unslothai/llama.cpp" - - def test_setup_sh_cpu_host_still_resolves_to_ggml(self): - # Guard against over-correcting the fix: a real CPU host (no usable GPU, - # no inferred gfx, no env override) must keep routing to ggml-org for the - # CPU prebuilt. - assert self._resolve_setup_sh_repo("x86_64", False, "") == "ggml-org/llama.cpp" + @pytest.mark.parametrize( + "machine, nvidia_usable, setup_gfx, env_gfx", + [ + ("x86_64", False, "", ""), # plain CPU host (used to take ggml-org) + ("aarch64", False, "", ""), # plain CPU arm64 host (used to take ggml-org) + ("x86_64", False, "gfx1100", ""), # name-inferred gfx + ("x86_64", False, "", "gfx1100"), # env-forwarded gfx + ("x86_64", True, "", ""), # usable NVIDIA + ], + ) + def test_setup_sh_routing_block_always_resolves_to_fork( + self, machine, nvidia_usable, setup_gfx, env_gfx + ): + assert ( + self._resolve_setup_sh_repo( + machine, nvidia_usable, setup_gfx, rocm_gfx_arch_env = env_gfx + ) + == "unslothai/llama.cpp" + ) @staticmethod - def _resolve_setup_ps1_repo(has_nvidia, has_rocm, gfx_arch): - """Run setup.ps1's $HelperReleaseRepo selection under pwsh and return the - resolved repo.""" + def _resolve_setup_ps1_repo(): + """Run setup.ps1's $HelperReleaseRepo assignment under pwsh and return the + resolved repo. The assignment is unconditional now, so there are no host + inputs to vary.""" import shutil pwsh = shutil.which("pwsh") @@ -3315,18 +3314,12 @@ def _resolve_setup_ps1_repo(has_nvidia, has_rocm, gfx_arch): ( ln for ln in source.splitlines() - if ln.strip().startswith("$HelperReleaseRepo = if (") + if ln.strip().startswith("$HelperReleaseRepo =") ), None, ) assert line is not None, "$HelperReleaseRepo selection not found in setup.ps1" - harness = ( - f"$HasNvidiaSmi = ${'true' if has_nvidia else 'false'}\n" - f"$HasROCm = ${'true' if has_rocm else 'false'}\n" - f"$script:ROCmGfxArch = '{gfx_arch}'\n" - f"{line}\n" - "Write-Output $HelperReleaseRepo" - ) + harness = f"{line}\nWrite-Output $HelperReleaseRepo" result = subprocess.run( [pwsh, "-NoProfile", "-Command", harness], capture_output = True, @@ -3336,14 +3329,10 @@ def _resolve_setup_ps1_repo(has_nvidia, has_rocm, gfx_arch): assert result.returncode == 0, result.stderr return result.stdout.strip() - def test_setup_ps1_inferred_gfx_resolves_to_fork(self): - # Adrenalin-only Windows host: $HasROCm is false (no HIP runtime) but a - # gfx arch was inferred -> route to the fork's windows-rocm bundle. - assert self._resolve_setup_ps1_repo(False, False, "gfx1100") == "unslothai/llama.cpp" - - def test_setup_ps1_cpu_host_still_resolves_to_ggml(self): - # No NVIDIA, no ROCm, no inferred gfx -> CPU host stays on ggml-org. - assert self._resolve_setup_ps1_repo(False, False, "") == "ggml-org/llama.cpp" + def test_setup_ps1_routing_resolves_to_fork(self): + # Windows routing is unconditional now: CPU-only Windows (x64 and arm64) + # uses the fork's windows-cpu / windows-arm64 bundles, not ggml-org. + assert self._resolve_setup_ps1_repo() == "unslothai/llama.cpp" # TEST: _pick_rocm_gfx_target -- visible-device selection from rocminfo output. diff --git a/tests/studio/install/test_selection_logic.py b/tests/studio/install/test_selection_logic.py index 0282370fe05..6fbc4ffc65b 100644 --- a/tests/studio/install/test_selection_logic.py +++ b/tests/studio/install/test_selection_logic.py @@ -1610,10 +1610,10 @@ def test_windows_cuda_uses_selected_release_upstream_tag(self, monkeypatch): assert approved.release_tag == "llama-prebuilt-latest" def test_linux_cpu_fork_without_bundle_raises_no_upstream_fallback(self, monkeypatch): - # A CPU-only Linux host on the fork no longer falls back to the ggml-org - # CPU asset: production routes CPU-only Linux to ggml-org, never the fork. - # With no fork CPU bundle in the manifest the resolver raises rather than - # quietly reaching for an upstream asset. + # A CPU-only Linux host on the fork never falls back to the ggml-org CPU + # asset. CPU-only Linux now routes to the fork, but if a release manifest + # happens to ship no CPU bundle the resolver raises rather than quietly + # reaching for an upstream asset. host = make_host( has_usable_nvidia = False, has_physical_nvidia = False, @@ -1738,6 +1738,86 @@ def test_windows_cpu_prefers_published_asset(self, monkeypatch): assert attempts[0].name == asset_name assert attempts[0].source_label == "published" + @pytest.mark.parametrize( + "system, machine, asset_name, install_kind, bundle_profile", + [ + # CPU-only Linux x64 -> fork linux-cpu (was ggml-org ubuntu-x64). + ("Linux", "x86_64", "app-b9625-linux-x64-cpu.tar.gz", "linux-cpu", "linux-cpu-x64"), + # CPU-only Linux arm64 -> fork linux-arm64 (was ggml-org ubuntu-arm64). + ("Linux", "aarch64", "app-b9625-linux-arm64-cpu.tar.gz", "linux-arm64", "linux-cpu-arm64"), + # CPU-only Windows arm64 -> fork windows-arm64 (was ggml-org win-cpu-arm64). + ("Windows", "arm64", "app-b9625-windows-arm64-cpu.zip", "windows-arm64", "windows-cpu-arm64"), + ], + ) + def test_cpu_host_prefers_published_fork_asset( + self, monkeypatch, system, machine, asset_name, install_kind, bundle_profile + ): + # CPU-only hosts now select the fork's CPU bundle from the manifest and + # must never query ggml-org upstream assets. Windows x64 CPU is covered + # separately by test_windows_cpu_prefers_published_asset. + host = make_host( + system = system, + machine = machine, + has_usable_nvidia = False, + has_physical_nvidia = False, + nvidia_smi = None, + ) + release = make_release( + [ + make_artifact( + asset_name, + install_kind = install_kind, + runtime_line = None, + coverage_class = None, + supported_sms = [], + min_sm = None, + max_sm = None, + bundle_profile = bundle_profile, + rank = 1000, + ) + ], + release_tag = "llama-prebuilt-latest", + upstream_tag = "b9625", + assets = {asset_name: f"https://published.example/{asset_name}"}, + ) + checksums = make_checksums_with_source( + [asset_name], + release_tag = release.release_tag, + upstream_tag = "b9625", + ) + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_resolved_published_releases", + lambda requested_tag, published_repo, published_release_tag = "": iter( + [ + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = release, + checksums = checksums, + ) + ] + ), + ) + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "github_release_assets", + lambda repo, tag: (_ for _ in ()).throw( + AssertionError("fork CPU host must not query upstream assets") + ), + ) + + _requested_tag, resolved_tag, attempts, _approved = resolve_install_attempts( + "latest", + host, + "unslothai/llama.cpp", + "", + ) + + assert resolved_tag == "b9625" + assert attempts[0].name == asset_name + assert attempts[0].install_kind == install_kind + assert attempts[0].source_label == "published" + def test_macos_prefers_published_asset(self, monkeypatch): host = make_host( system = "Darwin", @@ -3584,10 +3664,15 @@ def test_cpu_forced_arm64_selects_ubuntu_arm64(self): def test_setup_sh_has_arm64_cpu_prebuilt_fallback(self): source = self._SETUP_SH.read_text(encoding = "utf-8") - assert "--cpu-fallback" in source - # Fallback targets ggml-org (the only repo with an arm64 Linux build) and - # is gated on a degraded source build for arm64. - assert "ggml-org/llama.cpp" in source + # The arm64 GPU last-resort CPU fallback now pulls the fork's arm64 CPU + # bundle (app--linux-arm64-cpu.tar.gz), not ggml-org's, and is gated + # on a degraded source build for arm64. + start = source.index("_ARM64_CPU_CMD=(") + end = source.index(")", start) + block = source[start:end] + assert "--cpu-fallback" in block + assert '--published-repo "unslothai/llama.cpp"' in block + assert '--published-repo "ggml-org/llama.cpp"' not in block assert "_LLAMA_CPP_DEGRADED" in source From 2f0c1beb42eb6fce5866fbcbb2ce6e34ab58d127 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 14 Jun 2026 05:54:55 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/studio/install/test_rocm_support.py | 16 ++++++---------- tests/studio/install/test_selection_logic.py | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/tests/studio/install/test_rocm_support.py b/tests/studio/install/test_rocm_support.py index ce3ce08144e..5a3d3f70c88 100644 --- a/tests/studio/install/test_rocm_support.py +++ b/tests/studio/install/test_rocm_support.py @@ -3282,11 +3282,11 @@ def _resolve_setup_sh_repo( @pytest.mark.parametrize( "machine, nvidia_usable, setup_gfx, env_gfx", [ - ("x86_64", False, "", ""), # plain CPU host (used to take ggml-org) - ("aarch64", False, "", ""), # plain CPU arm64 host (used to take ggml-org) - ("x86_64", False, "gfx1100", ""), # name-inferred gfx - ("x86_64", False, "", "gfx1100"), # env-forwarded gfx - ("x86_64", True, "", ""), # usable NVIDIA + ("x86_64", False, "", ""), # plain CPU host (used to take ggml-org) + ("aarch64", False, "", ""), # plain CPU arm64 host (used to take ggml-org) + ("x86_64", False, "gfx1100", ""), # name-inferred gfx + ("x86_64", False, "", "gfx1100"), # env-forwarded gfx + ("x86_64", True, "", ""), # usable NVIDIA ], ) def test_setup_sh_routing_block_always_resolves_to_fork( @@ -3311,11 +3311,7 @@ def _resolve_setup_ps1_repo(): pytest.skip("pwsh not available") source = _SETUP_PS1_PATH.read_text(encoding = "utf-8") line = next( - ( - ln - for ln in source.splitlines() - if ln.strip().startswith("$HelperReleaseRepo =") - ), + (ln for ln in source.splitlines() if ln.strip().startswith("$HelperReleaseRepo =")), None, ) assert line is not None, "$HelperReleaseRepo selection not found in setup.ps1" diff --git a/tests/studio/install/test_selection_logic.py b/tests/studio/install/test_selection_logic.py index 6fbc4ffc65b..22a8b25df7c 100644 --- a/tests/studio/install/test_selection_logic.py +++ b/tests/studio/install/test_selection_logic.py @@ -1744,9 +1744,21 @@ def test_windows_cpu_prefers_published_asset(self, monkeypatch): # CPU-only Linux x64 -> fork linux-cpu (was ggml-org ubuntu-x64). ("Linux", "x86_64", "app-b9625-linux-x64-cpu.tar.gz", "linux-cpu", "linux-cpu-x64"), # CPU-only Linux arm64 -> fork linux-arm64 (was ggml-org ubuntu-arm64). - ("Linux", "aarch64", "app-b9625-linux-arm64-cpu.tar.gz", "linux-arm64", "linux-cpu-arm64"), + ( + "Linux", + "aarch64", + "app-b9625-linux-arm64-cpu.tar.gz", + "linux-arm64", + "linux-cpu-arm64", + ), # CPU-only Windows arm64 -> fork windows-arm64 (was ggml-org win-cpu-arm64). - ("Windows", "arm64", "app-b9625-windows-arm64-cpu.zip", "windows-arm64", "windows-cpu-arm64"), + ( + "Windows", + "arm64", + "app-b9625-windows-arm64-cpu.zip", + "windows-arm64", + "windows-cpu-arm64", + ), ], ) def test_cpu_host_prefers_published_fork_asset( From 26eb5f8392ef09ed3e26d790a4d93e83e05dcc9e Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 14 Jun 2026 14:04:46 -0300 Subject: [PATCH 3/8] Studio: reject unknown Linux CPU arches and keep ROCm-tooling hosts off the CPU prebuilt --- .../tests/test_install_resolve_prebuilt.py | 28 ++++++++-- studio/install_llama_prebuilt.py | 26 +++++++-- studio/setup.sh | 2 +- tests/studio/install/test_selection_logic.py | 53 +++++++++++++++++++ 4 files changed, 100 insertions(+), 9 deletions(-) diff --git a/studio/backend/tests/test_install_resolve_prebuilt.py b/studio/backend/tests/test_install_resolve_prebuilt.py index 7e0e2ea5fdb..7528e8e5639 100644 --- a/studio/backend/tests/test_install_resolve_prebuilt.py +++ b/studio/backend/tests/test_install_resolve_prebuilt.py @@ -98,14 +98,13 @@ def test_resolve_prebuilt_unavailable(monkeypatch, capsys): assert out["repo"] == FORK -def test_resolve_prebuilt_cpu_linux_routes_to_fork(monkeypatch, capsys): - # CPU-only Linux host (no GPU): the dispatch routes to the fork, which now - # ships the CPU prebuilt -- it no longer falls back to ggml-org upstream. - monkeypatch.setattr(ilp, "detect_host", lambda: _host(is_linux = True, is_x86_64 = True)) +def _run_resolve_capture_host(monkeypatch, capsys): + """Drive --resolve-prebuilt and return the host the resolver was handed.""" seen = {} def _resolver(tag, host, repo, published_release_tag): seen["repo"] = repo + seen["host"] = host raise ilp.PrebuiltFallback("no asset") monkeypatch.setattr(ilp, "resolve_simple_install_release_plans", _resolver) @@ -116,5 +115,26 @@ def _resolver(tag, host, repo, published_release_tag): ) assert ilp.main() == ilp.EXIT_SUCCESS out = json.loads(capsys.readouterr().out.strip().splitlines()[-1]) + return seen, out + + +def test_resolve_prebuilt_cpu_linux_routes_to_fork(monkeypatch, capsys): + # CPU-only Linux host (no GPU, no ROCm tooling): the dispatch routes to the + # fork, which now ships the CPU prebuilt, and the host is left CPU-only. + monkeypatch.setattr(ilp, "detect_host", lambda: _host(is_linux = True, is_x86_64 = True)) + monkeypatch.setattr(ilp.shutil, "which", lambda tool: None) + seen, out = _run_resolve_capture_host(monkeypatch, capsys) assert seen["repo"] == FORK assert out["repo"] == FORK + assert seen["host"].has_rocm is False + + +def test_resolve_prebuilt_rocm_tooling_host_not_offered_cpu(monkeypatch, capsys): + # A Linux host whose runtime probe could not confirm ROCm (has_rocm False) but + # exposes ROCm tooling (e.g. hipconfig) must be treated as ROCm so the probe + # does not offer the CPU bundle over a possible HIP source build. + monkeypatch.setattr(ilp, "detect_host", lambda: _host(is_linux = True, is_x86_64 = True)) + monkeypatch.setattr(ilp.shutil, "which", lambda tool: "/opt/rocm/bin/hipconfig" if tool == "hipconfig" else None) + seen, _out = _run_resolve_capture_host(monkeypatch, capsys) + assert seen["repo"] == FORK + assert seen["host"].has_rocm is True diff --git a/studio/install_llama_prebuilt.py b/studio/install_llama_prebuilt.py index 32ceb636390..b1c88fd3129 100644 --- a/studio/install_llama_prebuilt.py +++ b/studio/install_llama_prebuilt.py @@ -5950,10 +5950,13 @@ def _linux_published_attempts(host: HostInfo, bundle: PublishedReleaseBundle) -> # CPU-only host. A usable-NVIDIA host never reaches here -- if its CUDA # selection produced nothing we want an empty attempt list so the caller # source-builds with CUDA, not a CPU-only binary silently installed on a - # GPU host (mirrors the ROCm branch, and Windows NVIDIA). x64 takes the - # linux-cpu bundle, arm64 the linux-arm64 one (manifest install_kinds). - kind = "linux-arm64" if host.is_arm64 else "linux-cpu" - cpu_choice = published_asset_choice_for_kind(bundle, kind) + # GPU host (mirrors the ROCm branch, and Windows NVIDIA). Only x86_64 and + # arm64 have a CPU bundle; any other Linux arch (ppc64le, riscv64, s390x) + # has none, so leave attempts empty and source-build rather than hand it + # the x86_64 linux-cpu binary (the Linux preflight checks libraries, not + # ELF arch, so a wrong-arch binary would not be caught). + kind = "linux-cpu" if host.is_x86_64 else "linux-arm64" if host.is_arm64 else None + cpu_choice = published_asset_choice_for_kind(bundle, kind) if kind else None if cpu_choice is not None: attempts.append(cpu_choice) return attempts @@ -6828,6 +6831,21 @@ def main() -> int: override_rocm_gfx = args.rocm_gfx, force_cpu = args.cpu_fallback, ) + # This probe carries no --rocm-gfx, so a Linux host that exposes ROCm + # tooling but whose runtime probe could not enumerate a GPU (detect_host + # leaves has_rocm False) would look CPU-only and be offered the CPU + # bundle -- on a box running a HIP source build that is a silent + # GPU->CPU downgrade. Treat the presence of ROCm tooling as a hint and + # mark the host ROCm so the probe reports "no prebuilt" (ROCm-or-source) + # rather than the CPU bundle. + if ( + host.is_linux + and not host.has_usable_nvidia + and not host.has_rocm + and not args.cpu_fallback + and any(shutil.which(t) for t in ("rocminfo", "amd-smi", "hipconfig", "hipinfo")) + ): + host = _apply_host_overrides(host, override_has_rocm = True) repo = args.published_repo try: _requested, plans = resolve_simple_install_release_plans( diff --git a/studio/setup.sh b/studio/setup.sh index 66aa11707ef..e866efd4760 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -987,7 +987,7 @@ _HELPER_RELEASE_REPO="unslothai/llama.cpp" # nested in the AMD-detected branch above never ran and _setup_gfx is still empty. # Honour it here so the --rocm-gfx forwarding below still sees it # (install_llama_prebuilt.py reads the same env var as the --rocm-gfx default). -if [ "$_setup_nvidia_usable" != true ] && [ -z "${_setup_gfx:-}" ] && [ -n "${UNSLOTH_ROCM_GFX_ARCH:-}" ]; then +if [ "${_setup_nvidia_usable:-}" != true ] && [ -z "${_setup_gfx:-}" ] && [ -n "${UNSLOTH_ROCM_GFX_ARCH:-}" ]; then _setup_gfx="${UNSLOTH_ROCM_GFX_ARCH}" fi _LLAMA_PR="${UNSLOTH_LLAMA_PR:-}" diff --git a/tests/studio/install/test_selection_logic.py b/tests/studio/install/test_selection_logic.py index 22a8b25df7c..0fda8f668a6 100644 --- a/tests/studio/install/test_selection_logic.py +++ b/tests/studio/install/test_selection_logic.py @@ -1830,6 +1830,59 @@ def test_cpu_host_prefers_published_fork_asset( assert attempts[0].install_kind == install_kind assert attempts[0].source_label == "published" + def test_cpu_only_unsupported_arch_source_builds(self, monkeypatch): + # A CPU-only Linux host that is neither x86_64 nor arm64 (ppc64le, + # riscv64, s390x) has no compatible CPU bundle. It must source-build, not + # receive the x86_64 linux-cpu binary (the Linux preflight checks libs, + # not ELF arch, so a wrong-arch binary would slip through). + host = make_host( + machine = "ppc64le", + has_usable_nvidia = False, + has_physical_nvidia = False, + nvidia_smi = None, + ) + assert not host.is_x86_64 and not host.is_arm64 + x64_asset = "app-b9625-linux-x64-cpu.tar.gz" + release = make_release( + [ + make_artifact( + x64_asset, + install_kind = "linux-cpu", + runtime_line = None, + coverage_class = None, + supported_sms = [], + min_sm = None, + max_sm = None, + bundle_profile = "linux-cpu-x64", + rank = 1000, + ) + ], + release_tag = "llama-prebuilt-latest", + upstream_tag = "b9625", + assets = {x64_asset: f"https://published.example/{x64_asset}"}, + ) + checksums = make_checksums_with_source( + [x64_asset], + release_tag = release.release_tag, + upstream_tag = "b9625", + ) + + monkeypatch.setattr( + INSTALL_LLAMA_PREBUILT, + "iter_resolved_published_releases", + lambda requested_tag, published_repo, published_release_tag = "": iter( + [ + INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease( + bundle = release, + checksums = checksums, + ) + ] + ), + ) + + with pytest.raises(PrebuiltFallback, match = "no compatible Linux prebuilt asset was found"): + resolve_install_attempts("latest", host, "unslothai/llama.cpp", "") + def test_macos_prefers_published_asset(self, monkeypatch): host = make_host( system = "Darwin", From a50b19782cb285cd0349f4b5b2d4ba16ce280ee8 Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 14 Jun 2026 14:22:33 -0300 Subject: [PATCH 4/8] Studio: extend the resolve-prebuilt ROCm-tooling guard to Windows --- .../tests/test_install_resolve_prebuilt.py | 22 ++++++++++++++----- studio/install_llama_prebuilt.py | 17 +++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/studio/backend/tests/test_install_resolve_prebuilt.py b/studio/backend/tests/test_install_resolve_prebuilt.py index 7528e8e5639..e28fb3755ac 100644 --- a/studio/backend/tests/test_install_resolve_prebuilt.py +++ b/studio/backend/tests/test_install_resolve_prebuilt.py @@ -129,12 +129,22 @@ def test_resolve_prebuilt_cpu_linux_routes_to_fork(monkeypatch, capsys): assert seen["host"].has_rocm is False -def test_resolve_prebuilt_rocm_tooling_host_not_offered_cpu(monkeypatch, capsys): - # A Linux host whose runtime probe could not confirm ROCm (has_rocm False) but - # exposes ROCm tooling (e.g. hipconfig) must be treated as ROCm so the probe - # does not offer the CPU bundle over a possible HIP source build. - monkeypatch.setattr(ilp, "detect_host", lambda: _host(is_linux = True, is_x86_64 = True)) +@pytest.mark.parametrize( + "os_kwargs", + [ + {"is_linux": True, "is_x86_64": True}, + {"system": "Windows", "is_windows": True, "is_x86_64": True, "machine": "AMD64"}, + ], +) +def test_resolve_prebuilt_rocm_tooling_host_not_offered_cpu(monkeypatch, capsys, os_kwargs): + # A Linux or Windows host whose runtime probe could not confirm ROCm (has_rocm + # False) but exposes ROCm tooling (e.g. hipconfig) must be treated as ROCm so + # the probe does not offer the CPU bundle over a possible HIP source build. + monkeypatch.setattr(ilp, "detect_host", lambda: _host(**os_kwargs)) monkeypatch.setattr(ilp.shutil, "which", lambda tool: "/opt/rocm/bin/hipconfig" if tool == "hipconfig" else None) - seen, _out = _run_resolve_capture_host(monkeypatch, capsys) + seen, out = _run_resolve_capture_host(monkeypatch, capsys) assert seen["repo"] == FORK assert seen["host"].has_rocm is True + # The gfx-less ROCm host yields no covered bundle -> the probe reports the + # headline promise: no prebuilt available (source build), never a CPU bundle. + assert out["prebuilt_available"] is False diff --git a/studio/install_llama_prebuilt.py b/studio/install_llama_prebuilt.py index b1c88fd3129..cfcffe26d2d 100644 --- a/studio/install_llama_prebuilt.py +++ b/studio/install_llama_prebuilt.py @@ -6831,15 +6831,16 @@ def main() -> int: override_rocm_gfx = args.rocm_gfx, force_cpu = args.cpu_fallback, ) - # This probe carries no --rocm-gfx, so a Linux host that exposes ROCm - # tooling but whose runtime probe could not enumerate a GPU (detect_host - # leaves has_rocm False) would look CPU-only and be offered the CPU - # bundle -- on a box running a HIP source build that is a silent - # GPU->CPU downgrade. Treat the presence of ROCm tooling as a hint and - # mark the host ROCm so the probe reports "no prebuilt" (ROCm-or-source) - # rather than the CPU bundle. + # This probe carries no --rocm-gfx, so a Linux or Windows host that exposes + # ROCm tooling but whose runtime probe could not enumerate a GPU + # (detect_host leaves has_rocm False) would look CPU-only and be offered the + # CPU bundle -- on a box running a HIP source build that is a silent + # GPU->CPU downgrade. Treat the presence of ROCm tooling as a hint and mark + # the host ROCm: no hash-approved fork ROCm bundle covers a gfx-less host, so + # the resolver reports "no prebuilt" and the host source-builds instead of + # taking the CPU bundle. macOS has no ROCm. if ( - host.is_linux + (host.is_linux or host.is_windows) and not host.has_usable_nvidia and not host.has_rocm and not args.cpu_fallback From c0e34f99f8905b10dd5ef222879ed60749ac6db4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 14 Jun 2026 17:31:52 +0000 Subject: [PATCH 5/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- studio/backend/tests/test_install_resolve_prebuilt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/studio/backend/tests/test_install_resolve_prebuilt.py b/studio/backend/tests/test_install_resolve_prebuilt.py index e28fb3755ac..7f76ae075e8 100644 --- a/studio/backend/tests/test_install_resolve_prebuilt.py +++ b/studio/backend/tests/test_install_resolve_prebuilt.py @@ -141,7 +141,9 @@ def test_resolve_prebuilt_rocm_tooling_host_not_offered_cpu(monkeypatch, capsys, # False) but exposes ROCm tooling (e.g. hipconfig) must be treated as ROCm so # the probe does not offer the CPU bundle over a possible HIP source build. monkeypatch.setattr(ilp, "detect_host", lambda: _host(**os_kwargs)) - monkeypatch.setattr(ilp.shutil, "which", lambda tool: "/opt/rocm/bin/hipconfig" if tool == "hipconfig" else None) + monkeypatch.setattr( + ilp.shutil, "which", lambda tool: "/opt/rocm/bin/hipconfig" if tool == "hipconfig" else None + ) seen, out = _run_resolve_capture_host(monkeypatch, capsys) assert seen["repo"] == FORK assert seen["host"].has_rocm is True From 353d7090408ab25bf1bb52a47b548371addde414 Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 14 Jun 2026 15:28:46 -0300 Subject: [PATCH 6/8] Studio: let ROCm-SDK-only CPU hosts take the fork CPU prebuilt --- .../tests/test_install_resolve_prebuilt.py | 30 +++++++------------ studio/install_llama_prebuilt.py | 16 ---------- 2 files changed, 10 insertions(+), 36 deletions(-) diff --git a/studio/backend/tests/test_install_resolve_prebuilt.py b/studio/backend/tests/test_install_resolve_prebuilt.py index 7f76ae075e8..342fcfb238e 100644 --- a/studio/backend/tests/test_install_resolve_prebuilt.py +++ b/studio/backend/tests/test_install_resolve_prebuilt.py @@ -119,34 +119,24 @@ def _resolver(tag, host, repo, published_release_tag): def test_resolve_prebuilt_cpu_linux_routes_to_fork(monkeypatch, capsys): - # CPU-only Linux host (no GPU, no ROCm tooling): the dispatch routes to the - # fork, which now ships the CPU prebuilt, and the host is left CPU-only. + # CPU-only Linux host (no GPU): the dispatch routes to the fork, which now + # ships the CPU prebuilt -- it no longer falls back to ggml-org upstream. monkeypatch.setattr(ilp, "detect_host", lambda: _host(is_linux = True, is_x86_64 = True)) - monkeypatch.setattr(ilp.shutil, "which", lambda tool: None) seen, out = _run_resolve_capture_host(monkeypatch, capsys) assert seen["repo"] == FORK assert out["repo"] == FORK - assert seen["host"].has_rocm is False -@pytest.mark.parametrize( - "os_kwargs", - [ - {"is_linux": True, "is_x86_64": True}, - {"system": "Windows", "is_windows": True, "is_x86_64": True, "machine": "AMD64"}, - ], -) -def test_resolve_prebuilt_rocm_tooling_host_not_offered_cpu(monkeypatch, capsys, os_kwargs): - # A Linux or Windows host whose runtime probe could not confirm ROCm (has_rocm - # False) but exposes ROCm tooling (e.g. hipconfig) must be treated as ROCm so - # the probe does not offer the CPU bundle over a possible HIP source build. - monkeypatch.setattr(ilp, "detect_host", lambda: _host(**os_kwargs)) +def test_resolve_prebuilt_rocm_sdk_only_host_still_offered_cpu(monkeypatch, capsys): + # A CPU-only host that merely has ROCm/HIP SDK tools on PATH (no AMD GPU, so + # detect_host leaves has_rocm False) is a valid CPU-prebuilt target. The probe + # must NOT reclassify it as ROCm from tool presence alone and suppress the CPU + # bundle -- that would deny the fork CPU prebuilt to a legitimate CPU source + # build. The host is left CPU-only and resolves against the fork. + monkeypatch.setattr(ilp, "detect_host", lambda: _host(is_linux = True, is_x86_64 = True)) monkeypatch.setattr( ilp.shutil, "which", lambda tool: "/opt/rocm/bin/hipconfig" if tool == "hipconfig" else None ) seen, out = _run_resolve_capture_host(monkeypatch, capsys) assert seen["repo"] == FORK - assert seen["host"].has_rocm is True - # The gfx-less ROCm host yields no covered bundle -> the probe reports the - # headline promise: no prebuilt available (source build), never a CPU bundle. - assert out["prebuilt_available"] is False + assert seen["host"].has_rocm is False diff --git a/studio/install_llama_prebuilt.py b/studio/install_llama_prebuilt.py index cfcffe26d2d..c26b408cbb4 100644 --- a/studio/install_llama_prebuilt.py +++ b/studio/install_llama_prebuilt.py @@ -6831,22 +6831,6 @@ def main() -> int: override_rocm_gfx = args.rocm_gfx, force_cpu = args.cpu_fallback, ) - # This probe carries no --rocm-gfx, so a Linux or Windows host that exposes - # ROCm tooling but whose runtime probe could not enumerate a GPU - # (detect_host leaves has_rocm False) would look CPU-only and be offered the - # CPU bundle -- on a box running a HIP source build that is a silent - # GPU->CPU downgrade. Treat the presence of ROCm tooling as a hint and mark - # the host ROCm: no hash-approved fork ROCm bundle covers a gfx-less host, so - # the resolver reports "no prebuilt" and the host source-builds instead of - # taking the CPU bundle. macOS has no ROCm. - if ( - (host.is_linux or host.is_windows) - and not host.has_usable_nvidia - and not host.has_rocm - and not args.cpu_fallback - and any(shutil.which(t) for t in ("rocminfo", "amd-smi", "hipconfig", "hipinfo")) - ): - host = _apply_host_overrides(host, override_has_rocm = True) repo = args.published_repo try: _requested, plans = resolve_simple_install_release_plans( From 467faac692c2056b992416e3173b9f592ef7e77e Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Mon, 15 Jun 2026 08:06:38 -0300 Subject: [PATCH 7/8] Studio: accept windows-arm64 prebuilt kind and refresh stale fork-routing comments --- studio/install_llama_prebuilt.py | 10 +++++----- studio/setup.ps1 | 12 +++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/studio/install_llama_prebuilt.py b/studio/install_llama_prebuilt.py index c26b408cbb4..7f4f08d418c 100644 --- a/studio/install_llama_prebuilt.py +++ b/studio/install_llama_prebuilt.py @@ -5971,9 +5971,9 @@ def _fork_manifest_release_plans( max_release_fallbacks: int = DEFAULT_MAX_PREBUILT_RELEASE_FALLBACKS, ) -> tuple[str, list[InstallReleasePlan]]: """Manifest-reading branch of resolve_simple_install_release_plans, used for - the fork's bundles whose GPU/arch coverage lives in - llama-prebuilt-manifest.json rather than in the filename: arm64 CUDA, Windows - CUDA, per-gfx ROCm, and macOS. Linux x64 takes the faster filename path.""" + every fork host: all of the fork's bundles describe their GPU/arch coverage + in llama-prebuilt-manifest.json rather than in the asset filename (CPU, + x64/arm64 CUDA, Windows CUDA, per-gfx ROCm, and macOS).""" requested_tag = normalized_requested_llama_tag(llama_tag) allow_older_release_fallback = requested_tag == "latest" and not published_release_tag release_limit = max(1, max_release_fallbacks) @@ -6546,8 +6546,8 @@ def install_prebuilt( log( f"no existing llama.cpp install detected at {install_dir}; performing fresh prebuilt install" ) - # Single resolver: linux-x64 takes the fast filename path internally, - # every other fork host reads the manifest. + # Single resolver: every fork host selects from the release manifest; + # an explicit ggml-org override selects by asset filename instead. requested_tag, release_plans = resolve_simple_install_release_plans( llama_tag, host, diff --git a/studio/setup.ps1 b/studio/setup.ps1 index 5e64d64a86a..8efda6f5b55 100644 --- a/studio/setup.ps1 +++ b/studio/setup.ps1 @@ -2757,11 +2757,13 @@ if ($env:UNSLOTH_LLAMA_FORCE_COMPILE -eq "1") { # treat a valid ROCm install as mismatched. A name-inferred gfx # arch (Adrenalin-only, no confirmed runtime) still counts as # ROCm-capable -- the ROCm prebuilt bundles its own runtime, - # mirroring the --rocm-gfx forward below. NOTE: this block is - # currently inert -- write_prebuilt_metadata does not persist an - # install_kind key, so $existingKind is always null. If that changes, - # add the remaining host kinds (e.g. windows-arm64) before relying on it. - $expectedKinds = if ($HasROCm -or $script:ROCmGfxArch) { @("windows-rocm", "windows-hip") } elseif ($HasNvidiaSmi) { @("windows-cuda") } else { @("windows-cpu") } + # mirroring the --rocm-gfx forward below. The CPU branch covers both + # the x64 windows-cpu and arm64 windows-arm64 bundles (Windows arm64 + # has no GPU prebuilt). NOTE: this block is currently inert -- + # write_prebuilt_metadata does not persist an install_kind key, so + # $existingKind is always null; keep $expectedKinds in sync with the + # kinds install_llama_prebuilt.py installs before relying on it. + $expectedKinds = if ($HasROCm -or $script:ROCmGfxArch) { @("windows-rocm", "windows-hip") } elseif ($HasNvidiaSmi) { @("windows-cuda") } else { @("windows-cpu", "windows-arm64") } if ($existingKind -and ($existingKind -notin $expectedKinds)) { substep "Removing mismatched llama.cpp install (found '$existingKind', need one of: $($expectedKinds -join ', '))..." Remove-Item -Recurse -Force -LiteralPath $LlamaCppDir -ErrorAction SilentlyContinue From 732b12a121cfebb65b0e1d16ff3b1da1dde49454 Mon Sep 17 00:00:00 2001 From: oobabooga <112222186+oobabooga@users.noreply.github.com> Date: Sun, 21 Jun 2026 22:23:04 -0300 Subject: [PATCH 8/8] Studio: correct stale fork-routing comments and --resolve-prebuilt help --- studio/install_llama_prebuilt.py | 10 +++++----- studio/setup.sh | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/studio/install_llama_prebuilt.py b/studio/install_llama_prebuilt.py index bc2e5812d61..dcaeb54b7aa 100644 --- a/studio/install_llama_prebuilt.py +++ b/studio/install_llama_prebuilt.py @@ -125,9 +125,9 @@ def env_int( # errors. Only use "master" temporarily when the latest release is missing # support for a new model architecture. DEFAULT_LLAMA_TAG = os.environ.get("UNSLOTH_LLAMA_TAG", "latest") -# Default published repo for prebuilt release resolution. Linux uses -# Unsloth prebuilts; setup.sh/setup.ps1 pass --published-repo explicitly -# for macOS/Windows to override with ggml-org/llama.cpp when needed. +# Default published repo for prebuilt release resolution. Every host plans +# its prebuilt against the Unsloth fork; setup.sh/setup.ps1 pass it via +# --published-repo. ggml-org is reachable only via an explicit override. DEFAULT_PUBLISHED_REPO = "unslothai/llama.cpp" DEFAULT_PUBLISHED_TAG = os.environ.get("UNSLOTH_LLAMA_RELEASE_TAG") DEFAULT_PUBLISHED_MANIFEST_ASSET = os.environ.get( @@ -6801,8 +6801,8 @@ def parse_args() -> argparse.Namespace: const = "latest", help = ( "Report whether an official prebuilt exists for this host without " - "downloading. Picks the host's published repo when --published-repo " - "is left at the default. Use --output-format json." + "downloading. Plans against --published-repo (defaults to the " + "fork). Use --output-format json." ), ) parser.add_argument( diff --git a/studio/setup.sh b/studio/setup.sh index 75f1047a1c3..de6bb07942b 100755 --- a/studio/setup.sh +++ b/studio/setup.sh @@ -997,8 +997,8 @@ _HOST_MACHINE="$(uname -m 2>/dev/null || true)" # Pick the release repo install_llama_prebuilt.py plans against. Every host this # installer supports now pulls its llama.cpp prebuilt from the unslothai fork: it # ships the CUDA (Linux x64/arm64, Windows), ROCm (Linux/Windows) and macOS -# bundles, plus the CPU (and Vulkan) bundles for Linux/Windows on both x86_64 and -# arm64. ggml-org artifacts are no longer used by default. +# bundles, plus the CPU bundles for Linux/Windows on both x86_64 and arm64. +# ggml-org artifacts are no longer used by default. _HELPER_RELEASE_REPO="unslothai/llama.cpp" # UNSLOTH_ROCM_GFX_ARCH may be set on a host where no probe fired, so the override # nested in the AMD-detected branch above never ran and _setup_gfx is still empty.