diff --git a/studio/backend/tests/test_llama_cpp_freshness.py b/studio/backend/tests/test_llama_cpp_freshness.py index cb17e0d5e7..7f7a433494 100644 --- a/studio/backend/tests/test_llama_cpp_freshness.py +++ b/studio/backend/tests/test_llama_cpp_freshness.py @@ -222,6 +222,34 @@ def test_check_prebuilt_freshness_not_stale_when_tag_matches(monkeypatch, tmp_pa assert info["latest_tag"] == "b9300" +def test_check_prebuilt_freshness_not_stale_when_installed_newer(monkeypatch, tmp_path): + """/releases/latest can lag behind out-of-order fork releases; an installed + build newer than "latest" must never be flagged stale.""" + install_dir = tmp_path / "llama.cpp" + _write_marker( + install_dir, + tag = "b9596", + 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: "b9594") + info = fr.check_prebuilt_freshness(str(bin_path)) + assert info["stale"] is False + assert info["installed_tag"] == "b9596" + assert info["latest_tag"] == "b9594" + + +def test_parse_build_number(): + assert fr.parse_build_number("b9596") == 9596 + assert fr.parse_build_number(" b9596 ") == 9596 + assert fr.parse_build_number("9596") is None + assert fr.parse_build_number("b9596-custom") is None + assert fr.parse_build_number("") is None + assert fr.parse_build_number(None) is None + + 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" diff --git a/studio/backend/tests/test_llama_cpp_update.py b/studio/backend/tests/test_llama_cpp_update.py index d21d653b76..83ab028d5e 100644 --- a/studio/backend/tests/test_llama_cpp_update.py +++ b/studio/backend/tests/test_llama_cpp_update.py @@ -275,6 +275,26 @@ def test_status_up_to_date(monkeypatch, tmp_path): assert st["update_available"] is False +def test_status_installed_newer_than_latest_release_not_offered(monkeypatch, tmp_path): + """GitHub /releases/latest sorts by commit date and can return an older + build than the one just installed; never offer that as an "update".""" + binary = _write_install(tmp_path, "b9596") + monkeypatch.setattr(upd, "_find_binary", lambda: binary) + monkeypatch.setattr(freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9594") + st = upd.get_update_status(force_refresh = True) + assert st["installed_tag"] == "b9596" + assert st["latest_tag"] == "b9594" + assert st["update_available"] is False + + +def test_status_non_numeric_tags_fall_back_to_inequality(monkeypatch, tmp_path): + binary = _write_install(tmp_path, "custom-build") + monkeypatch.setattr(upd, "_find_binary", lambda: binary) + monkeypatch.setattr(freshness, "_fetch_latest_release_tag", lambda repo, timeout = 5.0: "b9594") + st = upd.get_update_status(force_refresh = True) + assert st["update_available"] is True + + def test_start_update_no_marker_no_prebuilt_refuses(monkeypatch, tmp_path): binary = tmp_path / "llama-server" binary.write_text("stub") # no marker diff --git a/studio/backend/utils/llama_cpp_freshness.py b/studio/backend/utils/llama_cpp_freshness.py index 3e5066ca2d..556ea71d69 100644 --- a/studio/backend/utils/llama_cpp_freshness.py +++ b/studio/backend/utils/llama_cpp_freshness.py @@ -13,6 +13,7 @@ from __future__ import annotations import json import os +import re import time from datetime import datetime, timezone from pathlib import Path @@ -43,6 +44,14 @@ def _cache_dir() -> Path: return Path.home() / ".unsloth" / "studio" / "cache" / "llama_cpp_freshness" +def parse_build_number(tag: object) -> Optional[int]: + """Numeric build from a ``bNNNN`` tag, None for anything else.""" + if not isinstance(tag, str): + return None + m = re.fullmatch(r"b(\d+)", tag.strip()) + return int(m.group(1)) if m else None + + def read_install_marker(binary_path: Optional[str]) -> Optional[dict]: """Walk up from binary_path to find UNSLOTH_PREBUILT_INFO.json. None = no marker (source build / custom path) or invalid JSON.""" @@ -208,6 +217,14 @@ def check_prebuilt_freshness( if not latest or latest == out["installed_tag"]: return out + # GitHub /releases/latest sorts by commit date, not build number, so it can + # lag the installed build when releases land out of order; never call a + # downgrade stale. + installed_build = parse_build_number(out["installed_tag"]) + latest_build = parse_build_number(latest) + if installed_build is not None and latest_build is not None and latest_build <= installed_build: + return out + installed_at = _parse_installed_at(out["installed_at_utc"]) if installed_at is None: return out diff --git a/studio/backend/utils/llama_cpp_update.py b/studio/backend/utils/llama_cpp_update.py index 55bf4b3d84..58963f0f85 100644 --- a/studio/backend/utils/llama_cpp_update.py +++ b/studio/backend/utils/llama_cpp_update.py @@ -10,7 +10,8 @@ the next model load uses it. Design notes: - Detection is delegated to check_prebuilt_freshness(). We surface an - ``update_available`` flag (installed_tag != latest_tag) which is laxer than + ``update_available`` flag (installed build older than the latest release; + tag inequality only when either tag is not bNNNN) which is laxer than freshness' ``stale`` (which additionally requires the install to be >= 3 days old). The UI shows the "Update llama.cpp" affordance on update_available. - The install is slow (download + extract + validate), so it runs on a daemon @@ -37,6 +38,7 @@ from utils.llama_cpp_freshness import ( _INSTALL_MARKER_NAME, check_prebuilt_freshness, latest_published_release, + parse_build_number, read_install_marker, reset_caches, ) @@ -286,9 +288,16 @@ def get_update_status(*, force_refresh: bool = False) -> dict: freshness = check_prebuilt_freshness(binary) installed = freshness.get("installed_tag") latest = freshness.get("latest_tag") - update_available = bool( - freshness.get("has_marker") and installed and latest and installed != latest - ) + # GitHub /releases/latest sorts by commit date, so it can point at an older + # build than the one the installer just resolved; compare build numbers and + # only offer real upgrades. Tag inequality is the fallback for non-bNNNN tags. + installed_build = parse_build_number(installed) + latest_build = parse_build_number(latest) + if installed_build is not None and latest_build is not None: + behind = installed_build < latest_build + else: + behind = installed != latest + update_available = bool(freshness.get("has_marker") and installed and latest and behind) with _job_lock: job = dict(_job)