diff --git a/hermes_cli/local_runtime/binaries.py b/hermes_cli/local_runtime/binaries.py index db0a1c9d12..c2f7b7d049 100644 --- a/hermes_cli/local_runtime/binaries.py +++ b/hermes_cli/local_runtime/binaries.py @@ -2,14 +2,21 @@ from __future__ import annotations +import json +import logging +import os +import re import threading +from contextlib import contextmanager, suppress from dataclasses import dataclass from pathlib import Path -from typing import Callable +from typing import Callable, Iterator import pm from pm.downloader import ProgressFn +logger = logging.getLogger(__name__) + BACKEND_PACKAGES = { "cuda": "llamacpp-cuda", "vulkan": "llamacpp-vulkan", @@ -98,18 +105,135 @@ def resolve_backend(requested: str = "auto", *, gpu_vendor: str | None = None, raise BinaryResolutionError("; ".join(reasons)) +def _installed(candidates: tuple[str, ...], allow_outdated: bool) -> Engine | None: + for candidate in candidates: + found = pm.installed_package(BACKEND_PACKAGES[candidate], allow_outdated=allow_outdated) + if found is not None and found.binary is not None: + return Engine(candidate, f"b{found.version}", found.binary) + return None + + def installed_engine(backend: str = "auto", *, allow_outdated: bool = True) -> Engine | None: - """Boot may retain a prior PM pin, but never installs or adopts unmanaged bytes.""" + """Boot may retain a prior PM pin and moves a pre-PM engine into PM's store once; it never downloads.""" vendor = None if backend == "auto": from hermes_cli.local_runtime.bootstrap import _detect_gpu_vendor vendor = _detect_gpu_vendor() - for candidate in _candidates(backend, vendor, pm.current_target()): - found = pm.installed_package(BACKEND_PACKAGES[candidate], allow_outdated=allow_outdated) - if found is not None and found.binary is not None: - return Engine(candidate, f"b{found.version}", found.binary) - return None + candidates = _candidates(backend, vendor, pm.current_target()) + engine = _installed(candidates, allow_outdated) + if engine is None and any(adopt_legacy_engine(candidate) for candidate in candidates): + engine = _installed(candidates, allow_outdated) + return engine + + +# Before PM owned binaries, Hermes installed each engine to runtimes/llamacpp/b// +# and wrote a manifest.json with the archive digests and the llama-server --version it saw. +_SHA256 = re.compile(r"[0-9a-f]{64}") +_ADOPTION_LOCK = threading.Lock() +# Installs that failed to move stay put for this process; the pane polls status every few seconds. +_LEFT_IN_PLACE: set[Path] = set() + + +def _legacy_installs(backend: str) -> list[tuple[str, Path, dict]]: + """Verified pre-PM installs of ``backend`` as (version, dir, manifest), newest first.""" + found = [] + for manifest_path in runtimes_root().glob(f"b*/{backend}/manifest.json"): + tag = manifest_path.parent.parent.name + try: + number = int(tag.removeprefix("b")) + manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig")) + except (OSError, ValueError): + continue + if (isinstance(manifest, dict) and manifest.get("tag") == tag and manifest.get("backend") == backend + and manifest.get("verified_version") and isinstance(manifest.get("assets"), dict) + and manifest_path.parent not in _LEFT_IN_PLACE): + found.append((number, str(number), manifest_path.parent, manifest)) + return [(version, path, manifest) for _, version, path, manifest in sorted(found, reverse=True)] + + +def _legacy_artifacts(package, version: str, target: str, assets: dict) -> list[str] | None: + """The manifest's archive digests in PM's archive order, or None when one is missing.""" + shas = [assets.get(url.rsplit("/", 1)[-1]) for url in package.fetch_urls(version, target)] + if not shas or not all(isinstance(sha, str) and _SHA256.fullmatch(sha) for sha in shas): + return None + return shas + + +@contextmanager +def _store_lock(root: Path) -> Iterator[bool]: + """PM's store lock, or False when another PM operation holds it (a download can take minutes).""" + from pm.filesystem import lock_fd + + root.mkdir(parents=True, exist_ok=True) + fd = os.open(root / ".install.lock", os.O_CREAT | os.O_RDWR, 0o600) + try: + yield lock_fd(fd, wait=True, timeout=2) + finally: + os.close(fd) + + +def adopt_legacy_engine(backend: str) -> bool: + """Move the newest pre-PM install of ``backend`` into PM's store and record it as installed. + + A machine that already ran a Hermes-installed engine keeps it across the update to PM. The + manifest's archive digests become the PM identity, so a tag that matches the pin counts as + current and an older tag counts as outdated, which offers the update. ``os.rename`` only: + instant on one volume, and a store on another volume leaves the engine where it is. Never + raises; returns whether an engine was moved. + """ + try: + candidates = _legacy_installs(backend) + if not candidates: + return False + from pm import paths as pm_paths + + name = BACKEND_PACKAGES[backend] + package = pm.get_package(name) + target = pm.current_target() + root = pm_paths.writable_store_root() + facts_path = pm_paths.facts_path() if root == pm_paths.store_root() else root / "facts.json" + with _ADOPTION_LOCK, _store_lock(root) as held: + if not held or pm.installed_package(name, allow_outdated=True) is not None: + return False + for version, source, manifest in candidates: + if _adopt(package, version, target, source, manifest, root, facts_path): + return True + _LEFT_IN_PLACE.add(source) + except Exception as exc: # noqa: BLE001 - a failed move must not break the callers that ask + logger.warning("could not move the pre-PM llama.cpp %s engine into the PM store: %s", backend, exc) + return False + + +def _adopt(package, version: str, target: str, source: Path, manifest: dict, root: Path, + facts_path: Path) -> bool: + from pm.store import tree_digest + + artifacts = _legacy_artifacts(package, version, target, manifest["assets"]) + entry_name = package.store_entry(version, target) + entry = root / entry_name + if artifacts is None or entry.exists() or entry.is_symlink(): + return False + reason = package.verify(source, target) + if reason: + logger.warning("pre-PM llama.cpp at %s left in place: %s", source, reason) + return False + try: + os.rename(source, entry) + except OSError as exc: + logger.warning("pre-PM llama.cpp at %s left in place: %s", source, exc) + return False + try: + pm.Facts(facts_path).record(package.name, version, entry_name, package.env(entry, target), root, + target=target, artifacts=artifacts, digest=tree_digest(entry)) + except BaseException: + with suppress(OSError): + os.rename(entry, source) + raise + with suppress(OSError): + source.parent.rmdir() + logger.info("moved llama.cpp b%s (%s) from %s into the PM store", version, package.name, source) + return True def ensure_engine(backend: str, *, progress: Callable[[str, int, int, str], None] | None = None, diff --git a/pm/lock.json b/pm/lock.json index 76e72f22b4..2efbedb8d6 100644 --- a/pm/lock.json +++ b/pm/lock.json @@ -264,99 +264,99 @@ "llamacpp-cpu": { "artifacts": { "darwin-arm64": { - "sha256": "e353a453cadb25960bea9b24692b72bd0a6b7b50b3bab5860bd5df8a434e7c5b", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-macos-arm64.tar.gz" + "sha256": "033c845c1df9bf945ff37bb193238b40910b2244be3e1e637b2ceb5878f1a6f5", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-macos-arm64.tar.gz" }, "darwin-x64": { - "sha256": "007ad98aec86111c87f5a602342d9da30f410dfb2e21c35b26c338cb915196e0", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-macos-x64.tar.gz" + "sha256": "03430a394d0a169a5e6d8f01c09f48cf58eb026af6fc95940a4a528e2e50cf38", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-macos-x64.tar.gz" }, "linux-arm64": { - "sha256": "69bddd2aa441982bb7cf79ff65faa41a82b0629ad79741b32521ce00cba5165f", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-ubuntu-arm64.tar.gz" + "sha256": "5f0e9c95d970892e43380f82ebcab960edfd20a1cd0f7abffa13b29fdb924949", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-ubuntu-arm64.tar.gz" }, "linux-x64": { - "sha256": "d55a64e814e0a379082f79b9a974499fe14bcc6f4b491ffae23e4a2993d1b85f", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-ubuntu-x64.tar.gz" + "sha256": "9abf88aea48a55d0f80edb1ee20220b186848cca0b4e919d71518cfd7ca67443", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-ubuntu-x64.tar.gz" }, "win32-arm64": { - "sha256": "901a5c6c680f17e17c3dae1baf134b695144e300ac1e2c3f65607401406f5c75", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-win-cpu-arm64.zip" + "sha256": "4b6a004b076eea47c318bea35cf1db2ff2bf037738b04645646ae8d7c3159478", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-win-cpu-arm64.zip" }, "win32-x64": { - "sha256": "a9d95d26cf00664f2902f73cb0fd9b167a3a1f252294bb2f8b236305f57d6363", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-win-cpu-x64.zip" + "sha256": "917f39c076402c421224824607397af20f53625a60defc20e8dd22446bf4c5d7", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-win-cpu-x64.zip" } }, - "version": "10362" + "version": "10964" }, "llamacpp-cuda": { "artifacts": { "win32-arm64": [ { - "sha256": "e96c44911a48f8813760b9076c7c7799db4374debb28d26a5464919bc0f27e8f", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-win-cuda-13.4-arm64.zip" + "sha256": "93590d8c74fb2e729b06160b524ee06ebed2aed5619b8c8fbd52a431e453831c", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-win-cuda-13.4-arm64.zip" }, { - "sha256": "5a40dc7c5fa3d0a80ceeba4f16f9e8d25d87bcf1399c9233588953c43436c33c", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/cudart-llama-bin-win-cuda-13.4-arm64.zip" + "sha256": "642dcde8805b3e3165ca710a5443b3b4044b27d96bd3ee3132473988c9bcb774", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/cudart-llama-bin-win-cuda-13.4-arm64.zip" } ], "win32-x64": [ { - "sha256": "65d74d6164ed5c8245762f6a7d646269e1c0cd6f8972e74771a43f6d2b3ce2ea", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-win-cuda-13.3-x64.zip" + "sha256": "cd63ae76ad78a1540aa0f30f6c6284bab14c146d99a58f70c3f0a38cb9c62351", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-win-cuda-13.3-x64.zip" }, { "sha256": "1462a050eb4c684921ba51dcc4cc488a036674c3e73e9945ee705b854808d03e", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/cudart-llama-bin-win-cuda-13.3-x64.zip" + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/cudart-llama-bin-win-cuda-13.3-x64.zip" } ] }, - "version": "10362" + "version": "10964" }, "llamacpp-hip": { "artifacts": { "linux-x64": { - "sha256": "8df777b54d88405fb5d8bb8207b80ae0d2051cb03f5a7dd69f4b1f67f94f3f2b", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-ubuntu-rocm-7.14-x64.tar.gz" + "sha256": "162b9645b84fa0a354767ccb5379b1457701134dc1ff4cd8b0ecc5ccec248455", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-ubuntu-rocm-10.0-x64.tar.gz" }, "win32-x64": { - "sha256": "fcd1899e9577aa8805e761eb8feb8fde028644975b8c7270edd66c60bcceceb4", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-win-rocm-7.14-x64.zip" + "sha256": "1f75c2a7cc64b7d4ee30f1e5a65ebec3681e4a67be788fc7251abc898f98748e", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-win-rocm-10.0-x64.zip" } }, - "version": "10362" + "version": "10964" }, "llamacpp-metal": { "artifacts": { "darwin-arm64": { - "sha256": "e353a453cadb25960bea9b24692b72bd0a6b7b50b3bab5860bd5df8a434e7c5b", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-macos-arm64.tar.gz" + "sha256": "033c845c1df9bf945ff37bb193238b40910b2244be3e1e637b2ceb5878f1a6f5", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-macos-arm64.tar.gz" }, "darwin-x64": { - "sha256": "007ad98aec86111c87f5a602342d9da30f410dfb2e21c35b26c338cb915196e0", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-macos-x64.tar.gz" + "sha256": "03430a394d0a169a5e6d8f01c09f48cf58eb026af6fc95940a4a528e2e50cf38", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-macos-x64.tar.gz" } }, - "version": "10362" + "version": "10964" }, "llamacpp-vulkan": { "artifacts": { "linux-arm64": { - "sha256": "aaa5cd203ad591ffc637535239ebd5e341fa4d272f70a976825d8823d9325b0e", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-ubuntu-vulkan-arm64.tar.gz" + "sha256": "f7864baa0edf5a059fb42c5efb5aceb96075aa1f41e6c3142b71ca69286cb0bb", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-ubuntu-vulkan-arm64.tar.gz" }, "linux-x64": { - "sha256": "cd9dc4885a32bae4c7640454e15aeba1324fff8bb6ea003555b5b112aa16d3bc", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-ubuntu-vulkan-x64.tar.gz" + "sha256": "55d1e58e14c11eedea090bf088fdeefbfe7b4b09ee03bf6dba9834651769afcf", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-ubuntu-vulkan-x64.tar.gz" }, "win32-x64": { - "sha256": "5da6d2c0cac199b917f0a7677c22b619750a9dd12432586d5d61457350e65a50", - "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10362/llama-b10362-bin-win-vulkan-x64.zip" + "sha256": "1ee3ad952f4ba71f438bd6d7bebef19e1c7af04adcaa35d08b4ddabb27d4c642", + "url": "https://github.com/ggml-org/llama.cpp/releases/download/b10964/llama-b10964-bin-win-vulkan-x64.zip" } }, - "version": "10362" + "version": "10964" }, "node": { "artifacts": { diff --git a/pm/packages.py b/pm/packages.py index bfd82a45c6..94ab9a4f40 100644 --- a/pm/packages.py +++ b/pm/packages.py @@ -1098,9 +1098,10 @@ class LlamaCppVulkan(LlamaCpp): class LlamaCppHip(LlamaCpp): name = "llamacpp-hip" backend = "hip" + # Upstream renamed the ROCm archives to 10.0 at b10767 (cff184438e). assets = { - "win32-x64": "win-rocm-7.14-x64", - "linux-x64": "ubuntu-rocm-7.14-x64", + "win32-x64": "win-rocm-10.0-x64", + "linux-x64": "ubuntu-rocm-10.0-x64", } diff --git a/tests/hermes_cli/test_local_legacy_engine.py b/tests/hermes_cli/test_local_legacy_engine.py new file mode 100644 index 0000000000..585d02c350 --- /dev/null +++ b/tests/hermes_cli/test_local_legacy_engine.py @@ -0,0 +1,189 @@ +"""A pre-PM engine under runtimes/llamacpp/ moves into PM's store instead of forcing first-run setup.""" + +from __future__ import annotations + +import json +import os +import threading + +import pytest + +import pm +from pm import paths +from pm.lock import Facts, Lockfile + +PINNED = ("a" * 64, "c" * 64) # CUDA pins the engine archive plus cudart; CPU pins one archive + + +@pytest.fixture(params=["cpu", "cuda"]) +def legacy_env(request, tmp_path, monkeypatch): + backend = request.param + home = tmp_path / "home" + store = home / "tools" + lock_path = tmp_path / "lock.json" + monkeypatch.setenv("HERMES_HOME", str(home)) + monkeypatch.setattr(paths, "store_root", lambda: store) + monkeypatch.setattr(paths, "lockfile_path", lambda: lock_path) + target = pm.current_target() + package = pm.get_package(f"llamacpp-{backend}") + if package.missing_reason(target): + pytest.skip(f"{package.name} has no build for {target}") + urls = package.fetch_urls("10964", target) + lock = Lockfile(lock_path) + lock.set_pin(package.name, "10964", {target: [{"url": u, "sha256": s} for u, s in zip(urls, PINNED)]}) + lock.save() + # verify() runs `llama-server --version`; the fixture binary is not executable. + monkeypatch.setattr(type(package), "verify", lambda self, entry, target: "") + from hermes_cli.local_runtime import binaries + + monkeypatch.setattr(binaries, "_LEFT_IN_PLACE", set()) + return home, store, package, target, backend, list(PINNED[:len(urls)]) + + +def legacy_install(home, package, target, backend, tag, digests, *, verified=True): + install = home / "runtimes" / "llamacpp" / tag / backend + install.mkdir(parents=True) + package.binary(install, target).write_bytes(b"engine " + tag.encode()) + (install / "ggml-base.dll").write_bytes(b"dll") + names = [url.rsplit("/", 1)[-1] for url in package.fetch_urls(tag.removeprefix("b"), target)] + manifest = {"tag": tag, "backend": backend, "assets": dict(zip(names, digests))} + if verified: + manifest["verified_version"] = f"version: 0.4.1-dev (build {tag[1:]}, commit 0)" + (install / "manifest.json").write_bytes(json.dumps(manifest).encode("utf-8")) + return install + + +def test_matching_legacy_engine_moves_into_the_store_as_current(legacy_env): + from hermes_cli.local_runtime import binaries + + home, store, package, target, backend, pinned = legacy_env + install = legacy_install(home, package, target, backend, "b10964", pinned) + + engine = binaries.installed_engine(backend, allow_outdated=False) + + entry = store / package.store_entry("10964", target) + assert engine == binaries.Engine(backend, "b10964", package.binary(entry, target)) + assert engine.binary.read_bytes() == b"engine b10964" + assert not install.exists() and not install.parent.exists() + fact = Facts(store / "facts.json").get(package.name) + assert fact["version"] == "10964" and fact["artifacts"] == pinned + + +def test_older_legacy_engine_counts_as_installed_but_outdated(legacy_env): + from hermes_cli.local_runtime import binaries + + home, _, package, target, backend, pinned = legacy_env + legacy_install(home, package, target, backend, "b10679", ["d" * 64, *pinned[1:]]) + + assert binaries.installed_engine(backend).tag == "b10679" + assert pm.installed_package(package.name) is None # the pane offers the update + assert binaries.installed_engine(backend, allow_outdated=False) is None + + +def test_newest_legacy_engine_wins_and_older_stays(legacy_env): + from hermes_cli.local_runtime import binaries + + home, _, package, target, backend, pinned = legacy_env + old = legacy_install(home, package, target, backend, "b10679", ["d" * 64, *pinned[1:]]) + legacy_install(home, package, target, backend, "b10964", pinned) + + assert binaries.installed_engine(backend).tag == "b10964" + assert old.is_dir() + + +@pytest.mark.parametrize("damage", ["unverified", "missing_digest", "wrong_backend", "tag_mismatch"]) +def test_unverified_or_damaged_legacy_engine_is_left_alone(legacy_env, damage): + from hermes_cli.local_runtime import binaries + + home, store, package, target, backend, pinned = legacy_env + install = legacy_install(home, package, target, backend, "b10964", + ["not-a-digest", *pinned[1:]] if damage == "missing_digest" else pinned, + verified=damage != "unverified") + manifest = json.loads((install / "manifest.json").read_bytes()) + if damage == "wrong_backend": + manifest["backend"] = "vulkan" + if damage == "tag_mismatch": + manifest["tag"] = "b10679" + (install / "manifest.json").write_bytes(json.dumps(manifest).encode("utf-8")) + + assert binaries.installed_engine(backend) is None + assert install.is_dir() + assert Facts(store / "facts.json").get(package.name) is None + + +def test_engine_that_fails_verification_stays_and_is_not_retried(legacy_env, monkeypatch): + from hermes_cli.local_runtime import binaries + + home, _, package, target, backend, pinned = legacy_env + install = legacy_install(home, package, target, backend, "b10964", pinned) + probes = [] + + def failing(self, entry, target): + probes.append(entry) + return "llama-server --version exited 1" + + monkeypatch.setattr(type(package), "verify", failing) + assert binaries.installed_engine(backend) is None + assert binaries.installed_engine(backend) is None + assert install.is_dir() and probes == [install] + + +def test_existing_pm_engine_is_never_replaced(legacy_env): + from hermes_cli.local_runtime import binaries + from pm.store import tree_digest + + home, store, package, target, backend, pinned = legacy_env + entry = store / package.store_entry("10964", target) + entry.mkdir(parents=True) + package.binary(entry, target).write_bytes(b"pm engine") + Facts(store / "facts.json").record(package.name, "10964", entry.name, {}, store, target=target, + artifacts=pinned, digest=tree_digest(entry)) + install = legacy_install(home, package, target, backend, "b10964", pinned) + + assert binaries.installed_engine(backend).binary.read_bytes() == b"pm engine" + assert install.is_dir() + + +def test_busy_store_leaves_the_engine_for_a_later_call(legacy_env): + from hermes_cli.local_runtime import binaries + from pm.filesystem import lock_fd + + home, store, package, target, backend, pinned = legacy_env + install = legacy_install(home, package, target, backend, "b10964", pinned) + store.mkdir(parents=True, exist_ok=True) + held, release = threading.Event(), threading.Event() + + def hold(): + # A second open file: msvcrt byte locks and flock both exclude it within one process. + fd = os.open(store / ".install.lock", os.O_CREAT | os.O_RDWR, 0o600) + try: + assert lock_fd(fd, wait=True) + held.set() + release.wait(30) + finally: + os.close(fd) + + holder = threading.Thread(target=hold) + holder.start() + try: + assert held.wait(10) + assert binaries.installed_engine(backend) is None + assert install.is_dir() + finally: + release.set() + holder.join() + assert binaries.installed_engine(backend).tag == "b10964" + + +def test_status_route_reports_the_moved_engine(legacy_env, monkeypatch): + from hermes_cli.web_routers import local_models as lm + + home, _, package, target, backend, pinned = legacy_env + legacy_install(home, package, target, backend, "b10964", pinned) + monkeypatch.setattr(lm, "_runtime_section", lambda: {"enabled": True, "backend": backend}) + monkeypatch.setattr(lm, "_state_endpoint", lambda: None) + + status = lm.local_models_status() + + assert status["runtime_installed"] and status["runtime_backend"] == backend + assert status["tag"] == "b10964" and not status["update_available"] diff --git a/tests/pm/test_llamacpp_pins.py b/tests/pm/test_llamacpp_pins.py index c36039058f..913f21f369 100644 --- a/tests/pm/test_llamacpp_pins.py +++ b/tests/pm/test_llamacpp_pins.py @@ -8,7 +8,10 @@ from pm.store import ALL_TARGETS def test_llamacpp_backends_have_pins_for_their_supported_targets(): lock = Lockfile(paths.lockfile_path()) - for backend in ("cpu", "cuda", "vulkan", "metal", "hip"): + backends = ("cpu", "cuda", "vulkan", "metal", "hip") + # One engine build everywhere: the catalog and presets are calibrated against a single tag. + assert len({lock.version(f"llamacpp-{backend}") for backend in backends}) == 1 + for backend in backends: name = f"llamacpp-{backend}" package = pm.get_package(name) version = lock.version(name)