diff --git a/studio/install_llama_prebuilt.py b/studio/install_llama_prebuilt.py index f7d4b1fa21..6d2756b83e 100644 --- a/studio/install_llama_prebuilt.py +++ b/studio/install_llama_prebuilt.py @@ -1611,7 +1611,12 @@ def direct_upstream_release_plan( install_kind = "macos-x64", ) ) - elif host.is_linux and host.is_x86_64 and not host.has_usable_nvidia: + elif ( + host.is_linux + and host.is_x86_64 + and not host.has_usable_nvidia + and not host.has_rocm + ): asset_name = f"llama-{release_tag}-bin-ubuntu-x64.tar.gz" asset_url = assets.get(asset_name) if asset_url: @@ -1625,7 +1630,12 @@ def direct_upstream_release_plan( install_kind = "linux-cpu", ) ) - elif host.is_linux and host.is_arm64 and not host.has_usable_nvidia: + elif ( + host.is_linux + and host.is_arm64 + and not host.has_usable_nvidia + and not host.has_rocm + ): # Upstream ggml-org/llama.cpp ships llama-bNNNN-bin-ubuntu-arm64.tar.gz # (visible in the b9334 release manifest). Without this branch the # selector returned 0 attempts and the installer fell back to a @@ -5766,6 +5776,7 @@ def validate_server( *, runtime_line: str | None = None, install_kind: str | None = None, + require_gpu_signal: bool = False, ) -> None: last_failure: PrebuiltFallback | None = None for port_attempt in range(1, SERVER_PORT_BIND_ATTEMPTS + 1): @@ -5908,10 +5919,10 @@ def validate_server( # the next bundle / source build instead of stopping here. if _enable_gpu_layers: log_handle.flush() - if ( - server_log_shows_gpu_offload(read_full_log(log_path)) - is False - ): + offload = server_log_shows_gpu_offload( + read_full_log(log_path) + ) + if offload is False: raise GpuOffloadFailure( "llama-server served a completion but loaded the " "model entirely on CPU despite GPU offload being " @@ -5924,6 +5935,17 @@ def validate_server( "(unslothai/unsloth#5807, #5106):\n" + read_log_excerpt(log_path) ) + # No GPU signal: install validation stays conservative + # (never reject on no evidence), but --smoke-test sets + # require_gpu_signal so its "0 = offload confirmed" + # contract does not pass an unproven log. + if offload is None and require_gpu_signal: + raise PrebuiltFallback( + "llama-server served a completion but its startup " + "log carried no GPU-offload signal; smoke-test " + "result is inconclusive:\n" + + read_log_excerpt(log_path) + ) return finally: if process is not None and process.poll() is None: @@ -6925,6 +6947,9 @@ def smoke_test_server_binary( Path(install_dir).expanduser().resolve() if install_dir else server_path.parent ) resolved_kind = install_kind or resolve_smoke_test_install_kind(host) + # For a GPU kind, the CLI contract is "0 = offload confirmed", so an + # inconclusive (no-signal) log must not pass -- require a positive signal. + require_signal = resolved_kind in _GPU_INSTALL_KINDS if probe: probe_path = Path(probe).expanduser().resolve() if not probe_path.exists(): @@ -6935,6 +6960,7 @@ def smoke_test_server_binary( host, resolved_install_dir, install_kind = resolved_kind, + require_gpu_signal = require_signal, ) else: with tempfile.TemporaryDirectory(prefix = "unsloth-llama-smoke-") as tmp: @@ -6948,6 +6974,7 @@ def smoke_test_server_binary( host, resolved_install_dir, install_kind = resolved_kind, + require_gpu_signal = require_signal, ) return resolved_kind diff --git a/studio/setup.ps1 b/studio/setup.ps1 index da4729f135..bd13b1d561 100644 --- a/studio/setup.ps1 +++ b/studio/setup.ps1 @@ -2667,6 +2667,14 @@ if (-not $NeedLlamaSourceBuild) { substep "GGUF inference and export will not be available." "Yellow" substep "Install CMake from https://cmake.org/download/ and re-run setup." "Yellow" $script:LlamaCppDegraded = $true +} elseif ($HasROCm -and -not $HasNvidiaSmi) { + # Windows has no HIP source-build path, so a ROCm host whose HIP prebuilt was + # missing or rejected must not silently CPU-source-build. Mark degraded so + # the CPU-prebuilt last resort below installs a clearly labelled CPU build + # instead of a "built" install that runs on CPU (#5807). + Write-Host "" + step "llama.cpp" "no usable HIP prebuilt and no Windows HIP source build; using CPU prebuilt" "Yellow" + $script:LlamaCppDegraded = $true } else { # A source build is committed here. The CUDA toolkit is only needed now, so # resolve (and winget-install if needed) it lazily, failing fast if no diff --git a/tests/studio/install/run_smoke_spoof.py b/tests/studio/install/run_smoke_spoof.py index aa94492c27..b4aa5391f6 100644 --- a/tests/studio/install/run_smoke_spoof.py +++ b/tests/studio/install/run_smoke_spoof.py @@ -84,7 +84,7 @@ def main() -> int: ("cuda", GPU_KIND, 0, "GPU binary tagged GPU is accepted"), ("cuda_buffer", GPU_KIND, 0, "GPU buffer-format binary is accepted"), ("cpu", CPU_KIND, 0, "CPU binary tagged CPU is not gated"), - ("no_signal", GPU_KIND, 0, "no-signal log is not rejected"), + ("no_signal", GPU_KIND, 1, "no-signal GPU log is inconclusive (exit 1)"), ] for mode, kind, expected, label in cases: rc = run_smoke(wrapper, probe, kind, mode) diff --git a/tests/studio/install/test_validate_server_gpu_offload.py b/tests/studio/install/test_validate_server_gpu_offload.py index cc64d2560d..82c7746d5e 100644 --- a/tests/studio/install/test_validate_server_gpu_offload.py +++ b/tests/studio/install/test_validate_server_gpu_offload.py @@ -408,11 +408,26 @@ def test_cpu_kind_not_gpu_gated(patched_server, tmp_path): def test_gpu_intent_no_signal_accepted(patched_server, tmp_path): - # No buffer/device signal -> conservative: do not reject on no evidence. + # No buffer/device signal -> conservative: do not reject on no evidence + # (plain install validation; require_gpu_signal defaults False). patched_server(NO_SIGNAL_LOG) _run_validate(tmp_path, nvidia_host(), "linux-cuda") # no raise +def test_smoke_test_no_signal_gpu_is_inconclusive(patched_server, tmp_path): + # The smoke-test CLI sets require_gpu_signal, so a no-signal GPU log is + # inconclusive (PrebuiltFallback -> EXIT_ERROR), not a silent pass. + patched_server(NO_SIGNAL_LOG) + server = tmp_path / "llama-server" + server.write_text("#!/bin/sh\n") + probe = tmp_path / "probe.gguf" + probe.write_bytes(b"GGUF") + with pytest.raises(PrebuiltFallback): + smoke_test_server_binary( + str(server), nvidia_host(), install_dir = str(tmp_path), probe = str(probe) + ) + + def test_rocm_cpu_only_rejected(patched_server, tmp_path): patched_server(CPU_ONLY_DEVICE_INFO_LOG) with pytest.raises(PrebuiltFallback):