mirror of
https://github.com/unslothai/unsloth.git
synced 2026-07-09 15:58:41 +00:00
Studio: source CPU llama.cpp prebuilts from unslothai/llama.cpp (#6311)
* Studio: source CPU llama.cpp prebuilts from the unslothai fork * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: reject unknown Linux CPU arches and keep ROCm-tooling hosts off the CPU prebuilt * Studio: extend the resolve-prebuilt ROCm-tooling guard to Windows * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: let ROCm-SDK-only CPU hosts take the fork CPU prebuilt * Studio: accept windows-arm64 prebuilt kind and refresh stale fork-routing comments * Studio: correct stale fork-routing comments and --resolve-prebuilt help * Refresh stale ggml-org routing comments --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Daniel Han <danielhanchen@gmail.com>
This commit is contained in:
parent
41dd95ea0a
commit
fcb1152c76
11 changed files with 322 additions and 265 deletions
|
|
@ -6,9 +6,9 @@
|
|||
# windows-latest runner:
|
||||
#
|
||||
# 1. install.ps1 --local --no-torch installs Studio AND auto-fetches
|
||||
# the prebuilt llama.cpp Windows binary (llama-bNNNN-bin-win-cpu-
|
||||
# x64 from ggml-org/llama.cpp). Hitting the source-build fallback
|
||||
# is treated as an Unsloth bug -- Studio must always pick the
|
||||
# the prebuilt llama.cpp Windows binary (app-<tag>-windows-x64-cpu
|
||||
# from unslothai/llama.cpp). Hitting the source-build fallback is
|
||||
# treated as an Unsloth bug -- Studio must always pick the
|
||||
# prebuilt on Windows.
|
||||
# 2. unsloth studio update --local is idempotent. Two consecutive
|
||||
# runs both report "prebuilt up to date and validated", no
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||
|
||||
"""install_llama_prebuilt.py: host->repo mapping and the --resolve-prebuilt mode.
|
||||
"""install_llama_prebuilt.py: the --resolve-prebuilt probe (plans against the fork
|
||||
by default; --published-repo overrides).
|
||||
|
||||
These back the in-app update for source-build (markerless) installs: the backend
|
||||
asks the installer whether an official prebuilt exists for this host without
|
||||
|
|
@ -24,9 +25,7 @@ if str(_studio) not in sys.path:
|
|||
|
||||
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
|
||||
|
|
@ -56,73 +55,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 test_macos_intel_and_arm_both_route_to_fork():
|
||||
# macOS uses the unslothai fork's own Mac prebuilts for BOTH arm64 and Intel;
|
||||
# there is no longer any upstream-on-macOS default path, so the obsolete
|
||||
# pre-macOS-26 pin (b9415) is gone.
|
||||
assert (
|
||||
ilp.published_repo_for_host(
|
||||
_host(system = "Darwin", is_macos = True, is_arm64 = True, machine = "arm64")
|
||||
)
|
||||
== FORK
|
||||
)
|
||||
assert (
|
||||
ilp.published_repo_for_host(
|
||||
_host(system = "Darwin", is_macos = True, is_x86_64 = True, machine = "x86_64")
|
||||
)
|
||||
== FORK
|
||||
)
|
||||
|
||||
|
||||
def test_macos_upstream_pin_only_for_explicit_pre26_upstream():
|
||||
pre26 = _host(
|
||||
system = "Darwin",
|
||||
|
|
@ -188,15 +120,13 @@ 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.
|
||||
monkeypatch.setattr(ilp, "detect_host", lambda: _host(is_linux = True, is_x86_64 = True))
|
||||
monkeypatch.setattr(ilp.shutil, "which", lambda tool: tool == "rocminfo")
|
||||
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)
|
||||
|
|
@ -207,10 +137,33 @@ def test_resolve_prebuilt_linux_amd_tooling_routes_to_fork(monkeypatch, capsys):
|
|||
)
|
||||
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): 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))
|
||||
seen, out = _run_resolve_capture_host(monkeypatch, capsys)
|
||||
assert seen["repo"] == FORK
|
||||
assert out["repo"] == FORK
|
||||
|
||||
|
||||
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 False
|
||||
|
||||
|
||||
# Blackwell floor is sm_100 (data-center B100/B200, B300/GB300), below consumer
|
||||
# sm_120 -- 120 wrongly excluded data-center hosts from the prebuilt selection.
|
||||
|
||||
|
|
|
|||
|
|
@ -137,9 +137,9 @@ def test_read_install_marker_finds_windows_cmake_layout(tmp_path):
|
|||
|
||||
@pytest.mark.parametrize("repo", ["unslothai/llama.cpp", "ggml-org/llama.cpp"])
|
||||
def test_read_install_marker_carries_published_repo_dynamically(tmp_path, repo):
|
||||
# The freshness check queries whichever release repo the marker records,
|
||||
# so CUDA (unslothai), CPU/macOS (ggml-org), and ROCm all get the right
|
||||
# "latest" tag.
|
||||
# The freshness check queries whichever release repo the marker records:
|
||||
# new installs record the fork, legacy CPU/macOS markers still say ggml-org,
|
||||
# and both must get the right "latest" tag.
|
||||
install_dir = tmp_path / "llama.cpp"
|
||||
_write_marker(install_dir, tag = "b9000", published_repo = repo)
|
||||
bin_path = _fake_binary(install_dir, layout = "cmake")
|
||||
|
|
|
|||
|
|
@ -594,9 +594,10 @@ def test_install_cmd_fork_rocm_marker_forwards_has_rocm(monkeypatch, tmp_path):
|
|||
|
||||
|
||||
def test_install_cmd_ggml_cpu_marker_has_no_cpu_fallback(monkeypatch, tmp_path):
|
||||
# CPU installs come from ggml-org. Re-running into the same install-dir/repo
|
||||
# reproduces the same CPU bundle; --cpu-fallback (which force-drops GPU
|
||||
# detection) is reserved for setup.sh's arm64 rescue and must not appear here.
|
||||
# Legacy CPU installs recorded a ggml-org marker (new installs use the fork).
|
||||
# Re-running into the same install-dir/repo reproduces the same CPU bundle;
|
||||
# --cpu-fallback (which force-drops GPU detection) is reserved for setup.sh's
|
||||
# arm64 rescue and must not appear here.
|
||||
cmd = _capture_install_cmd(
|
||||
monkeypatch,
|
||||
tmp_path,
|
||||
|
|
|
|||
|
|
@ -165,9 +165,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(
|
||||
|
|
@ -3135,21 +3135,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
|
||||
|
|
@ -4015,6 +4000,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:
|
||||
|
|
@ -6127,8 +6115,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).
|
||||
cpu_choice = published_asset_choice_for_kind(bundle, "linux-cpu")
|
||||
# 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
|
||||
|
|
@ -6143,9 +6136,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)
|
||||
|
|
@ -6714,8 +6707,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,
|
||||
|
|
@ -6903,8 +6896,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(
|
||||
|
|
@ -6992,24 +6985,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 ""
|
||||
|
|
|
|||
|
|
@ -3088,12 +3088,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 }
|
||||
|
|
@ -3283,11 +3282,13 @@ if ($LocalLlamaCppLinked) {
|
|||
# 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
|
||||
|
|
|
|||
|
|
@ -1176,63 +1176,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 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).
|
||||
if [ "$_setup_nvidia_usable" != true ] && [ -z "${_setup_gfx:-}" ] && [ -n "${UNSLOTH_ROCM_GFX_ARCH:-}" ]; then
|
||||
# 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}}"
|
||||
|
|
@ -1939,19 +1895,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-<tag>-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,
|
||||
|
|
|
|||
|
|
@ -362,8 +362,11 @@ class TestSourcePatternsSh:
|
|||
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
|
||||
|
|
@ -425,12 +428,11 @@ class TestSourcePatternsPs1:
|
|||
assert "$LlamaSource = $DefaultLlamaSource" in self.content
|
||||
|
||||
def test_release_repo_override_removed(self):
|
||||
# Repo chosen by GPU detection (GPU -> fork, CPU -> ggml-org), no env override.
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -658,15 +658,21 @@ class TestSourceCodePatterns:
|
|||
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 routing gates NVIDIA on GPU usability, not nvidia-smi presence, so
|
||||
CPU-only/hidden-GPU hosts get the ggml CPU prebuilt. Guards 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
|
||||
# The GPU-tooling probe (PR #4562) stays: ROCm detection goes through
|
||||
# command -v, not a bare presence loop that mishandled a hidden nvidia-smi.
|
||||
assert "command -v rocminfo" in content
|
||||
assert "command -v amd-smi" in content
|
||||
|
||||
def test_setup_sh_reports_installed_prebuilt_release(self):
|
||||
"""Shell wrapper should report the installed prebuilt release from metadata."""
|
||||
|
|
|
|||
|
|
@ -3164,21 +3164,25 @@ class TestRocmGfxForwarding:
|
|||
assert "--rocm-gfx" in source
|
||||
assert "$script:ROCmGfxArch" in source
|
||||
|
||||
def test_setup_sh_routes_inferred_gfx_to_fork(self):
|
||||
# An inferred gfx arch must route to the fork even without ROCm tooling.
|
||||
# Pin the specific guard (a bare "${_setup_gfx:-}" also appears elsewhere).
|
||||
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):
|
||||
# On Windows, a resolved $script:ROCmGfxArch counts as a fork 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 assertions above pin the guard *text*; the tests below *execute* the
|
||||
# real routing block and assert the resolved repo, so a refactor that keeps
|
||||
# the literal but breaks the inferred-gfx -> fork decision is still caught.
|
||||
# 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(
|
||||
|
|
@ -3187,15 +3191,18 @@ class TestRocmGfxForwarding:
|
|||
setup_gfx,
|
||||
rocm_gfx_arch_env = "",
|
||||
):
|
||||
"""Run setup.sh's routing block under bash with PATH emptied (no ROCm tooling) and return _HELPER_RELEASE_REPO."""
|
||||
"""Run setup.sh's release-repo routing block under bash and return the
|
||||
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 = {
|
||||
|
|
@ -3217,25 +3224,31 @@ class TestRocmGfxForwarding:
|
|||
assert result.returncode == 0, result.stderr
|
||||
return result.stdout.strip()
|
||||
|
||||
def test_setup_sh_inferred_gfx_resolves_to_fork(self):
|
||||
# Only a name-inferred gfx arch -> route to the fork's per-gfx prebuilt
|
||||
# (not ggml-org). x64 and arm64 share the 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):
|
||||
# No probe fired but UNSLOTH_ROCM_GFX_ARCH is set: adopt the env arch
|
||||
# and route to the fork, same as name-inference.
|
||||
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):
|
||||
# A real CPU host (no GPU, no inferred gfx, no env override) must keep routing to ggml-org.
|
||||
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")
|
||||
|
|
@ -3243,21 +3256,11 @@ class TestRocmGfxForwarding:
|
|||
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 = if (")
|
||||
),
|
||||
(ln for ln in source.splitlines() 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,
|
||||
|
|
@ -3267,13 +3270,10 @@ class TestRocmGfxForwarding:
|
|||
assert result.returncode == 0, result.stderr
|
||||
return result.stdout.strip()
|
||||
|
||||
def test_setup_ps1_inferred_gfx_resolves_to_fork(self):
|
||||
# Adrenalin-only host: $HasROCm false but gfx inferred -> 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.
|
||||
|
|
|
|||
|
|
@ -1720,8 +1720,10 @@ class TestResolveInstallAttempts:
|
|||
assert approved.release_tag == "llama-prebuilt-latest"
|
||||
|
||||
def test_linux_cpu_fork_without_bundle_raises_no_upstream_fallback(self, monkeypatch):
|
||||
# CPU-only Linux on the fork must not fall back to the ggml-org CPU asset; with
|
||||
# no fork CPU bundle the resolver raises rather than reaching upstream.
|
||||
# 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,
|
||||
|
|
@ -1846,6 +1848,151 @@ class TestResolveInstallAttempts:
|
|||
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_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",
|
||||
|
|
@ -3447,8 +3594,9 @@ class TestLinuxArm64ForkFallsBackToSource:
|
|||
assert plans == ["plan"]
|
||||
|
||||
def test_arm64_cpu_on_ggml_org_is_not_blocked(self, monkeypatch):
|
||||
# CPU-only arm64 routes to ggml-org, so the guard must not fire; it reaches the
|
||||
# iterator (empty here -> generic message).
|
||||
# ggml-org is reachable only via an explicit --published-repo override now,
|
||||
# but the guard must still not fire on arm64 there; it reaches the iterator
|
||||
# (empty here -> generic message).
|
||||
monkeypatch.setattr(
|
||||
INSTALL_LLAMA_PREBUILT,
|
||||
"iter_release_payloads_by_time",
|
||||
|
|
@ -3474,7 +3622,7 @@ class TestLinuxArm64ForkFallsBackToSource:
|
|||
|
||||
|
||||
class TestCpuFallback:
|
||||
"""--cpu-fallback drops GPU attributes so the host's OS/arch CPU prebuilt is selected, letting an arm64 GPU host install ggml-org's arm64 CPU build when its source build produced no binary."""
|
||||
"""--cpu-fallback drops GPU attributes so the host's OS/arch CPU prebuilt is selected, letting an arm64 GPU host install the fork's arm64 CPU bundle when its source build produced no binary."""
|
||||
|
||||
_SETUP_SH = PACKAGE_ROOT / "studio" / "setup.sh"
|
||||
|
||||
|
|
@ -3553,10 +3701,15 @@ class TestCpuFallback:
|
|||
|
||||
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 (only repo with an arm64 Linux build), gated on a
|
||||
# degraded arm64 source build.
|
||||
assert "ggml-org/llama.cpp" in source
|
||||
# The arm64 GPU last-resort CPU fallback now pulls the fork's arm64 CPU
|
||||
# bundle (app-<tag>-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
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue