diff --git a/studio/backend/main.py b/studio/backend/main.py index e4392792a7..d4593c2ab4 100644 --- a/studio/backend/main.py +++ b/studio/backend/main.py @@ -198,27 +198,41 @@ async def lifespan(app: FastAPI): # Detect hardware first — sets DEVICE global used everywhere detect_hardware() - # llama.cpp capability probe; warns if the prebuilt lacks MTP support. + # llama.cpp probes: capability (MTP support) + freshness (release age). + # Both cached; freshness has a 24h disk TTL. try: from core.inference.llama_cpp import LlamaCppBackend + from utils.llama_cpp_freshness import ( + check_prebuilt_freshness, + format_stale_warning, + ) - _caps = LlamaCppBackend.probe_server_capabilities() + _bin = LlamaCppBackend._find_llama_server_binary() + _caps = LlamaCppBackend.probe_server_capabilities(_bin) app.state.llama_cpp_capabilities = _caps - if _caps.get("found") and not _caps.get("supports_mtp"): - import structlog as _structlog + _freshness = check_prebuilt_freshness(_bin) + app.state.llama_cpp_freshness = _freshness + import structlog as _structlog + + _log = _structlog.get_logger(__name__) + if _caps.get("found") and not _caps.get("supports_mtp"): _msg = ( "llama.cpp prebuilt lacks MTP support " "(--spec-type mtp/draft-mtp). Run `unsloth studio update`. " "MTP GGUFs will load without speculative decoding." ) - _structlog.get_logger(__name__).warning(_msg) + _log.warning(_msg) + print(f"WARNING: {_msg}", flush = True) + if _freshness.get("stale"): + _msg = format_stale_warning(_freshness) + _log.warning(_msg) print(f"WARNING: {_msg}", flush = True) except Exception as _probe_exc: import structlog as _structlog _structlog.get_logger(__name__).debug( - "llama.cpp capability probe failed: %s", _probe_exc + "llama.cpp startup probes failed: %s", _probe_exc ) from storage.studio_db import cleanup_orphaned_runs diff --git a/studio/backend/models/inference.py b/studio/backend/models/inference.py index d772f517d7..99d1df37b6 100644 --- a/studio/backend/models/inference.py +++ b/studio/backend/models/inference.py @@ -349,6 +349,21 @@ class InferenceStatusResponse(BaseModel): "False -> recommend `unsloth studio update`." ), ) + llama_cpp_prebuilt_stale: bool = Field( + False, + description = ( + "Installed llama.cpp prebuilt is >=3 days behind the latest " + "release. True -> show `unsloth studio update` banner." + ), + ) + llama_cpp_installed_tag: Optional[str] = Field( + None, + description = "Installed llama.cpp tag, or None if unknown.", + ) + llama_cpp_latest_tag: Optional[str] = Field( + None, + description = "Latest published llama.cpp tag, or None if GitHub unreachable.", + ) # ===================================================================== diff --git a/studio/backend/routes/inference.py b/studio/backend/routes/inference.py index 9893f84711..d92b6b433e 100644 --- a/studio/backend/routes/inference.py +++ b/studio/backend/routes/inference.py @@ -1282,12 +1282,23 @@ async def get_status( try: llama_backend = get_llama_cpp_backend() - # MTP capability probe (cached). Drives the UI update banner. + # MTP probe + freshness check (both cached). Drive the UI banner. try: - _caps = type(llama_backend).probe_server_capabilities() + _bin = type(llama_backend)._find_llama_server_binary() + _caps = type(llama_backend).probe_server_capabilities(_bin) _supports_mtp = bool(_caps.get("supports_mtp", False)) except Exception: + _bin = None _supports_mtp = True # fail open + try: + from utils.llama_cpp_freshness import check_prebuilt_freshness + + _freshness = check_prebuilt_freshness(_bin) + except Exception: + _freshness = {} + _stale = bool(_freshness.get("stale")) + _installed_tag = _freshness.get("installed_tag") + _latest_tag = _freshness.get("latest_tag") # If a GGUF model is loaded via llama-server, report that if llama_backend.is_loaded: @@ -1332,6 +1343,9 @@ async def get_status( chat_template_override = llama_backend.chat_template_override, speculative_type = llama_backend.speculative_type, llama_cpp_supports_mtp = _supports_mtp, + llama_cpp_prebuilt_stale = _stale, + llama_cpp_installed_tag = _installed_tag, + llama_cpp_latest_tag = _latest_tag, ) # Otherwise, report Unsloth backend status @@ -1393,6 +1407,9 @@ async def get_status( supports_tools = False, chat_template = chat_template, llama_cpp_supports_mtp = _supports_mtp, + llama_cpp_prebuilt_stale = _stale, + llama_cpp_installed_tag = _installed_tag, + llama_cpp_latest_tag = _latest_tag, ) except Exception as e: diff --git a/studio/backend/tests/test_llama_cpp_freshness.py b/studio/backend/tests/test_llama_cpp_freshness.py new file mode 100644 index 0000000000..b32aeefcdb --- /dev/null +++ b/studio/backend/tests/test_llama_cpp_freshness.py @@ -0,0 +1,328 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 + +"""Tests for the llama.cpp prebuilt freshness check. + +Pins the marker parser, the disk+memory cache, the stale decision +matrix, and fail-open behaviour on missing data. +""" + +from __future__ import annotations + +import json +import os +import sys +import time +import types as _types +from datetime import datetime, timedelta, timezone +from pathlib import Path + +_BACKEND_DIR = str(Path(__file__).resolve().parent.parent) +if _BACKEND_DIR not in sys.path: + sys.path.insert(0, _BACKEND_DIR) + +_loggers_stub = _types.ModuleType("loggers") +_loggers_stub.get_logger = lambda name: __import__("logging").getLogger(name) +sys.modules.setdefault("loggers", _loggers_stub) + +_structlog_stub = _types.ModuleType("structlog") +_structlog_stub.get_logger = lambda *a, **k: __import__("logging").getLogger("stub") +sys.modules.setdefault("structlog", _structlog_stub) + +import pytest + +from utils import llama_cpp_freshness as fr + + +# Helpers. + + +def _write_marker(install_dir: Path, **overrides) -> Path: + payload = { + "requested_tag": "latest", + "tag": "b9190", + "release_tag": "b9190", + "published_repo": "unslothai/llama.cpp", + "asset": "app-b9190-linux-x64-cuda13-newer.tar.gz", + "asset_sha256": None, + "source": "published", + "installed_at_utc": (datetime.now(tz = timezone.utc) - timedelta(days = 1)) + .isoformat() + .replace("+00:00", "Z"), + } + payload.update(overrides) + install_dir.mkdir(parents = True, exist_ok = True) + (install_dir / "UNSLOTH_PREBUILT_INFO.json").write_text(json.dumps(payload)) + return install_dir / "UNSLOTH_PREBUILT_INFO.json" + + +def _fake_binary(install_dir: Path, *, layout: str = "cmake") -> Path: + """Stub llama-server under one of the supported install layouts.""" + if layout == "cmake": + bin_dir = install_dir / "build" / "bin" + bin_name = "llama-server" + elif layout == "root": + bin_dir = install_dir + bin_name = "llama-server" + elif layout == "windows": + bin_dir = install_dir / "build" / "bin" / "Release" + bin_name = "llama-server.exe" + else: + raise ValueError(f"unknown layout {layout}") + bin_dir.mkdir(parents = True, exist_ok = True) + bin_path = bin_dir / bin_name + bin_path.write_text("stub\n") + return bin_path + + +@pytest.fixture(autouse = True) +def _reset(monkeypatch, tmp_path): + # Isolate disk cache per-test; never touch the user's real cache. + monkeypatch.setattr(fr, "_cache_dir", lambda: tmp_path / ".freshness") + fr.reset_caches() + yield + fr.reset_caches() + + +# read_install_marker. + + +def test_read_install_marker_finds_cmake_layout(tmp_path): + install_dir = tmp_path / "llama.cpp" + _write_marker(install_dir, tag = "b9190") + bin_path = _fake_binary(install_dir, layout = "cmake") + marker = fr.read_install_marker(str(bin_path)) + assert marker is not None + assert marker["tag"] == "b9190" + assert marker["published_repo"] == "unslothai/llama.cpp" + + +def test_read_install_marker_finds_root_layout(tmp_path): + install_dir = tmp_path / "llama.cpp" + _write_marker(install_dir, tag = "b9999") + bin_path = _fake_binary(install_dir, layout = "root") + marker = fr.read_install_marker(str(bin_path)) + assert marker is not None + assert marker["tag"] == "b9999" + + +def test_read_install_marker_finds_windows_cmake_layout(tmp_path): + # Windows cmake puts the .exe under build/bin/Release/, so the + # marker is four levels above the binary. + install_dir = tmp_path / "llama.cpp" + _write_marker(install_dir, tag = "b8888") + bin_path = _fake_binary(install_dir, layout = "windows") + marker = fr.read_install_marker(str(bin_path)) + assert marker is not None + assert marker["tag"] == "b8888" + + +@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 Linux (unslothai), CPU Linux x86_64 / macOS + # (ggml-org), and ROCm source-build (unslothai upstream label) + # all surface 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") + marker = fr.read_install_marker(str(bin_path)) + assert marker is not None + assert marker["published_repo"] == repo + + +def test_read_install_marker_missing_returns_none(tmp_path): + bin_path = _fake_binary(tmp_path / "no_marker", layout = "root") + assert fr.read_install_marker(str(bin_path)) is None + + +def test_read_install_marker_handles_invalid_json(tmp_path): + install_dir = tmp_path / "llama.cpp" + install_dir.mkdir(parents = True) + (install_dir / "UNSLOTH_PREBUILT_INFO.json").write_text("not json") + bin_path = _fake_binary(install_dir, layout = "root") + assert fr.read_install_marker(str(bin_path)) is None + + +def test_read_install_marker_handles_none_path(): + assert fr.read_install_marker(None) is None + + +# latest_published_release (with monkeypatched fetcher). + + +def test_latest_published_release_uses_disk_cache(monkeypatch): + calls = [] + + def _fake_fetch(repo, timeout = 5.0): + calls.append(repo) + return "b9999" + + monkeypatch.setattr(fr, "_fetch_latest_release_tag", _fake_fetch) + first = fr.latest_published_release("unslothai/llama.cpp") + second = fr.latest_published_release("unslothai/llama.cpp") + assert first == "b9999" + assert second == "b9999" + # Memo + disk cache -> only one fetch. + assert len(calls) == 1 + + +def test_latest_published_release_returns_none_on_network_failure(monkeypatch): + monkeypatch.setattr(fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: None) + assert fr.latest_published_release("unslothai/llama.cpp") is None + + +def test_latest_published_release_keeps_old_cache_on_transient_failure( + monkeypatch, tmp_path +): + # Disk entry older than TTL + network fail -> return cached value. + cache_dir = tmp_path / ".freshness" + cache_dir.mkdir() + cache_file = cache_dir / "unslothai__llama.cpp.json" + yesterday = time.time() - 25 * 60 * 60 # > 24h + cache_file.write_text(json.dumps({"fetched_at": yesterday, "latest_tag": "b9000"})) + monkeypatch.setattr(fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: None) + assert fr.latest_published_release("unslothai/llama.cpp") == "b9000" + + +# check_prebuilt_freshness end-to-end. + + +def test_check_prebuilt_freshness_reports_stale_when_old_and_behind( + monkeypatch, tmp_path +): + install_dir = tmp_path / "llama.cpp" + _write_marker( + install_dir, + tag = "b9190", + installed_at_utc = (datetime.now(tz = timezone.utc) - timedelta(days = 5)) + .isoformat() + .replace("+00:00", "Z"), + ) + bin_path = _fake_binary(install_dir, layout = "root") + monkeypatch.setattr( + fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300" + ) + info = fr.check_prebuilt_freshness(str(bin_path)) + assert info["has_marker"] is True + assert info["stale"] is True + assert info["installed_tag"] == "b9190" + assert info["latest_tag"] == "b9300" + assert info["age_days"] == 5 + assert info["published_repo"] == "unslothai/llama.cpp" + + +def test_check_prebuilt_freshness_not_stale_when_tag_matches(monkeypatch, tmp_path): + install_dir = tmp_path / "llama.cpp" + _write_marker( + install_dir, + tag = "b9300", + installed_at_utc = (datetime.now(tz = timezone.utc) - timedelta(days = 30)) + .isoformat() + .replace("+00:00", "Z"), + ) + bin_path = _fake_binary(install_dir, layout = "root") + monkeypatch.setattr( + fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300" + ) + info = fr.check_prebuilt_freshness(str(bin_path)) + assert info["stale"] is False + assert info["installed_tag"] == "b9300" + assert info["latest_tag"] == "b9300" + + +def test_check_prebuilt_freshness_not_stale_within_threshold(monkeypatch, tmp_path): + # Behind by tag but within the 3-day grace window. + install_dir = tmp_path / "llama.cpp" + _write_marker( + install_dir, + tag = "b9190", + installed_at_utc = (datetime.now(tz = timezone.utc) - timedelta(days = 1)) + .isoformat() + .replace("+00:00", "Z"), + ) + bin_path = _fake_binary(install_dir, layout = "root") + monkeypatch.setattr( + fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300" + ) + info = fr.check_prebuilt_freshness(str(bin_path)) + assert info["stale"] is False + assert info["age_days"] == 1 + + +def test_check_prebuilt_freshness_fails_open_without_marker(tmp_path): + bin_path = _fake_binary(tmp_path / "custom_build", layout = "root") + info = fr.check_prebuilt_freshness(str(bin_path)) + assert info["has_marker"] is False + assert info["stale"] is False + + +def test_check_prebuilt_freshness_fails_open_when_github_unreachable( + monkeypatch, tmp_path +): + install_dir = tmp_path / "llama.cpp" + _write_marker( + install_dir, + tag = "b9190", + installed_at_utc = (datetime.now(tz = timezone.utc) - timedelta(days = 10)) + .isoformat() + .replace("+00:00", "Z"), + ) + bin_path = _fake_binary(install_dir, layout = "root") + monkeypatch.setattr(fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: None) + info = fr.check_prebuilt_freshness(str(bin_path)) + assert info["has_marker"] is True + assert info["stale"] is False + assert info["latest_tag"] is None + + +def test_check_prebuilt_freshness_handles_unparseable_install_timestamp( + monkeypatch, tmp_path +): + install_dir = tmp_path / "llama.cpp" + _write_marker(install_dir, tag = "b9190", installed_at_utc = "not-a-date") + bin_path = _fake_binary(install_dir, layout = "root") + monkeypatch.setattr( + fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300" + ) + info = fr.check_prebuilt_freshness(str(bin_path)) + assert info["stale"] is False + assert info["age_days"] is None + + +def test_check_prebuilt_freshness_respects_custom_threshold(monkeypatch, tmp_path): + install_dir = tmp_path / "llama.cpp" + _write_marker( + install_dir, + tag = "b9190", + installed_at_utc = (datetime.now(tz = timezone.utc) - timedelta(days = 2)) + .isoformat() + .replace("+00:00", "Z"), + ) + bin_path = _fake_binary(install_dir, layout = "root") + monkeypatch.setattr( + fr, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9300" + ) + info = fr.check_prebuilt_freshness(str(bin_path), threshold_days = 1) + assert info["stale"] is True + + +# format_stale_warning. + + +def test_format_stale_warning_contains_actionable_command(): + msg = fr.format_stale_warning( + {"installed_tag": "b9190", "latest_tag": "b9300", "age_days": 5} + ) + assert "b9190" in msg + assert "b9300" in msg + assert "5 days" in msg + assert "unsloth studio update" in msg + + +def test_format_stale_warning_singular_day(): + msg = fr.format_stale_warning( + {"installed_tag": "b9190", "latest_tag": "b9300", "age_days": 1} + ) + assert "1 day" in msg + assert "1 days" not in msg diff --git a/studio/backend/utils/llama_cpp_freshness.py b/studio/backend/utils/llama_cpp_freshness.py new file mode 100644 index 0000000000..2c781f4a7b --- /dev/null +++ b/studio/backend/utils/llama_cpp_freshness.py @@ -0,0 +1,244 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 + +"""llama.cpp prebuilt freshness check. + +Reads UNSLOTH_PREBUILT_INFO.json (written by install_llama_prebuilt.py) +and compares the installed release tag against the latest on GitHub. +Surfaced via main.py:lifespan() and /api/inference/status. Fails open +on any missing data so we never show a misleading banner. +""" + +from __future__ import annotations + +import json +import os +import time +from datetime import datetime, timezone +from pathlib import Path +from typing import Optional + +import structlog + +logger = structlog.get_logger(__name__) + +# 3 days matches Unsloth's typical llama.cpp release cadence. +STALENESS_THRESHOLD_DAYS = 3 + +# 24h TTL keeps the GitHub call off the hot path and within rate limits. +_RELEASE_CACHE_TTL_SECONDS = 24 * 60 * 60 + +_INSTALL_MARKER_NAME = "UNSLOTH_PREBUILT_INFO.json" + +_marker_cache: dict[str, Optional[dict]] = {} +_release_memo: dict[str, tuple[float, Optional[str]]] = {} + + +def _cache_dir() -> Path: + """Lazy import so tests can stub storage_roots.""" + try: + from utils.paths.storage_roots import cache_root + + return cache_root() / "llama_cpp_freshness" + except Exception: + return Path.home() / ".unsloth" / "studio" / "cache" / "llama_cpp_freshness" + + +def read_install_marker(binary_path: Optional[str]) -> Optional[dict]: + """Walk up from binary_path to find UNSLOTH_PREBUILT_INFO.json. + None means no marker (source build / custom path) or invalid JSON.""" + if not binary_path: + return None + cached = _marker_cache.get(binary_path) + if cached is not None or binary_path in _marker_cache: + return cached + p = Path(binary_path) + marker: Optional[dict] = None + # Cover all _find_llama_server_binary layouts: + # /llama-server (1 up) + # /build/bin/llama-server (3 up, Linux/macOS cmake) + # /build/bin/Release/llama-server.exe (4 up, Windows cmake) + for parent in p.parents[:5]: + candidate = parent / _INSTALL_MARKER_NAME + if candidate.is_file(): + try: + marker = json.loads(candidate.read_text(encoding = "utf-8")) + except (OSError, json.JSONDecodeError) as exc: + logger.debug( + "failed to parse install marker", + path = str(candidate), + error = str(exc), + ) + marker = None + break + _marker_cache[binary_path] = marker + return marker + + +def _cache_path_for(repo: str) -> Path: + safe = repo.replace("/", "__") + return _cache_dir() / f"{safe}.json" + + +def _load_disk_cache(repo: str) -> Optional[tuple[float, Optional[str]]]: + path = _cache_path_for(repo) + try: + payload = json.loads(path.read_text(encoding = "utf-8")) + except (OSError, json.JSONDecodeError): + return None + ts = payload.get("fetched_at") + tag = payload.get("latest_tag") + if not isinstance(ts, (int, float)): + return None + return float(ts), tag if isinstance(tag, str) else None + + +def _save_disk_cache(repo: str, latest_tag: Optional[str]) -> None: + path = _cache_path_for(repo) + try: + path.parent.mkdir(parents = True, exist_ok = True) + tmp = path.with_suffix(".tmp") + tmp.write_text( + json.dumps({"fetched_at": time.time(), "latest_tag": latest_tag}), + encoding = "utf-8", + ) + tmp.replace(path) + except OSError as exc: + logger.debug("freshness cache write failed", repo = repo, error = str(exc)) + + +def _fetch_latest_release_tag(repo: str, timeout: float = 5.0) -> Optional[str]: + """GitHub API call. None on any failure (offline, rate-limited, etc).""" + import urllib.error + import urllib.request + + url = f"https://api.github.com/repos/{repo}/releases/latest" + headers = { + "Accept": "application/vnd.github+json", + "User-Agent": "unsloth-studio-freshness-check", + } + token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN") + if token: + headers["Authorization"] = f"Bearer {token}" + req = urllib.request.Request(url, headers = headers) + try: + with urllib.request.urlopen(req, timeout = timeout) as resp: + data = json.loads(resp.read().decode("utf-8")) + except ( + urllib.error.URLError, + urllib.error.HTTPError, + OSError, + json.JSONDecodeError, + ) as exc: + logger.debug("freshness fetch failed", repo = repo, error = str(exc)) + return None + tag = data.get("tag_name") + return tag if isinstance(tag, str) and tag else None + + +def latest_published_release( + repo: str, *, force_refresh: bool = False +) -> Optional[str]: + """Latest release tag for `repo`. Memo + disk-cached (24h TTL). + None when offline and never previously cached.""" + if not repo: + return None + now = time.time() + if not force_refresh: + memo = _release_memo.get(repo) + if memo and now - memo[0] < _RELEASE_CACHE_TTL_SECONDS: + return memo[1] + disk = _load_disk_cache(repo) + if disk and now - disk[0] < _RELEASE_CACHE_TTL_SECONDS: + _release_memo[repo] = disk + return disk[1] + latest = _fetch_latest_release_tag(repo) + if latest is None: + # Keep last-good disk value rather than poisoning with None. + disk = _load_disk_cache(repo) + if disk: + _release_memo[repo] = disk + return disk[1] + return None + _release_memo[repo] = (now, latest) + _save_disk_cache(repo, latest) + return latest + + +def _parse_installed_at(value: object) -> Optional[datetime]: + if not isinstance(value, str) or not value: + return None + s = value.replace("Z", "+00:00") if value.endswith("Z") else value + try: + dt = datetime.fromisoformat(s) + except ValueError: + return None + if dt.tzinfo is None: + dt = dt.replace(tzinfo = timezone.utc) + return dt + + +def check_prebuilt_freshness( + binary_path: Optional[str], + *, + threshold_days: int = STALENESS_THRESHOLD_DAYS, + now: Optional[datetime] = None, +) -> dict: + """Returns {has_marker, stale, installed_tag, latest_tag, + installed_at_utc, age_days, published_repo, threshold_days}. + stale = True iff installed != latest AND age >= threshold. + Fails open on missing data (stale stays False).""" + out: dict = { + "has_marker": False, + "stale": False, + "installed_tag": None, + "latest_tag": None, + "installed_at_utc": None, + "age_days": None, + "published_repo": None, + "threshold_days": int(threshold_days), + } + marker = read_install_marker(binary_path) + if not marker: + return out + out["has_marker"] = True + out["installed_tag"] = marker.get("tag") or marker.get("release_tag") + out["installed_at_utc"] = marker.get("installed_at_utc") + out["published_repo"] = marker.get("published_repo") + + repo = out["published_repo"] + if not repo or not out["installed_tag"]: + return out + latest = latest_published_release(repo) + out["latest_tag"] = latest + if not latest or latest == out["installed_tag"]: + return out + + installed_at = _parse_installed_at(out["installed_at_utc"]) + if installed_at is None: + return out + now = now or datetime.now(tz = timezone.utc) + age_seconds = (now - installed_at).total_seconds() + out["age_days"] = max(0, int(age_seconds // 86400)) + if age_seconds >= threshold_days * 86400: + out["stale"] = True + return out + + +def format_stale_warning(info: dict) -> str: + """Human-readable one-liner for stale prebuilt info.""" + age = info.get("age_days") + installed = info.get("installed_tag") or "unknown" + latest = info.get("latest_tag") or "unknown" + age_str = f"{age} day{'s' if age != 1 else ''}" if age is not None else "some time" + return ( + f"llama.cpp prebuilt is {age_str} behind: installed " + f"{installed}, latest {latest}. Run `unsloth studio update` " + f"to refresh." + ) + + +def reset_caches() -> None: + """Test-only: drop all in-memory caches.""" + _marker_cache.clear() + _release_memo.clear()