From bd9324b634356ecc281297c91dc9cc71f13fd558 Mon Sep 17 00:00:00 2001 From: Michael Han <107991372+shimmyshimmer@users.noreply.github.com> Date: Sat, 13 Jun 2026 04:14:36 -0700 Subject: [PATCH] Studio: offer llama.cpp update for same-base mix builds on source installs (#6280) The update banner is driven by update_available from /api/llama/update-status. Prebuilt (marker) installs decide this via freshness.is_behind, which is mix-aware: a release at the same upstream base build but with a new -mix- suffix counts as behind. Source-build installs went through _source_build_status, which compared only the numeric base build (installed_build < latest_build). When a new prebuilt shared the installed upstream base (our usual mix re-tag at the same base), it returned update_available=False and the banner never showed. This is the common macOS case, where a failed prebuilt fetch falls back to a source build that lacks the mix patches. Make the source-build path mix-aware to match the marker path: same base plus a -mix- tag now offers the update (and displays the mix tag), a bare same-base rebuild does not, and the downgrade guard and unknown -version fallback are unchanged. --- studio/backend/tests/test_llama_cpp_update.py | 33 +++++++++++++++ studio/backend/utils/llama_cpp_update.py | 40 ++++++++++++++----- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/studio/backend/tests/test_llama_cpp_update.py b/studio/backend/tests/test_llama_cpp_update.py index e207306c91..326b3dc6aa 100644 --- a/studio/backend/tests/test_llama_cpp_update.py +++ b/studio/backend/tests/test_llama_cpp_update.py @@ -233,6 +233,39 @@ def test_status_source_build_suppressed_when_newer(monkeypatch, tmp_path): assert st["installed_tag"] == "b9600" +def test_status_source_build_offers_same_base_mix(monkeypatch, tmp_path): + # The reported banner bug: a source build at the same upstream base as a new + # Unsloth prebuilt that adds a mix- suffix. The base build numbers match + # (9596 == 9596) but the mix carries extra patches the source build lacks, so + # the update must still surface -- mirroring the marker path's is_behind. + binary = tmp_path / "llama.cpp" / "build" / "bin" / "llama-server" + binary.parent.mkdir(parents = True) + binary.write_text("stub") + monkeypatch.setattr(upd, "_find_binary", lambda: str(binary)) + _prebuilt(monkeypatch, release_tag = "b9596-mix-e6f2453", llama_tag = "b9596") + monkeypatch.setattr(upd, "_installed_build_number", lambda b: 9596) + st = upd.get_update_status() + assert st["supported"] is True + assert st["update_available"] is True + assert st["source_build"] is True + assert st["installed_tag"] == "b9596" + assert st["latest_tag"] == "b9596-mix-e6f2453" + + +def test_status_source_build_same_base_bare_not_offered(monkeypatch, tmp_path): + # Same base, but the prebuilt is a bare rebuild (no mix suffix): nothing extra + # to gain, so do not nag. + binary = tmp_path / "llama.cpp" / "build" / "bin" / "llama-server" + binary.parent.mkdir(parents = True) + binary.write_text("stub") + monkeypatch.setattr(upd, "_find_binary", lambda: str(binary)) + _prebuilt(monkeypatch, release_tag = "b9596", llama_tag = "b9596") + monkeypatch.setattr(upd, "_installed_build_number", lambda b: 9596) + st = upd.get_update_status() + assert st["update_available"] is False + assert st["latest_tag"] == "b9596" + + def test_status_source_build_skips_probe_while_job_runs(monkeypatch, tmp_path): # While the updater swaps the tree, status polls must not exec the binary # being replaced (on Windows that exec can fail the installer's os.replace); diff --git a/studio/backend/utils/llama_cpp_update.py b/studio/backend/utils/llama_cpp_update.py index 8b90d36bc5..3518247d7e 100644 --- a/studio/backend/utils/llama_cpp_update.py +++ b/studio/backend/utils/llama_cpp_update.py @@ -37,6 +37,7 @@ from utils.llama_cpp_freshness import ( _INSTALL_MARKER_NAME, check_prebuilt_freshness, latest_published_release, + parse_base_build, read_install_marker, reset_caches, ) @@ -220,23 +221,42 @@ def _source_build_status(binary: str, *, force_refresh: bool) -> Optional[dict]: res = _resolve_prebuilt_for_host(force_refresh = force_refresh) if not res or not res.get("prebuilt_available"): return None - # llama_tag is the upstream build (bNNNN, what --version reports); release_tag - # can be a fork wrapper tag, so compare/display against llama_tag. - latest = res.get("llama_tag") or res.get("release_tag") - if not latest: + # llama_tag is the upstream base (bNNNN, what --version reports); release_tag + # is the full tag, either a same-base mix (bNNNN-mix-) or a fork wrapper + # (e.g. v1.0). Compare the numeric base against llama_tag. + base_tag = res.get("llama_tag") or res.get("release_tag") + release_tag = res.get("release_tag") + if not base_tag: return None # No resolvable install root (e.g. a pinned LLAMA_SERVER_PATH we cannot # manage) means an apply would not take effect, so do not offer. if _llama_install_root(binary) is None: return None installed_build = _installed_build_number(binary) - m = re.search(r"(\d+)", latest) - latest_build = int(m.group(1)) if m else None - # Suppress only when the source build is reliably newer/equal; unknown - # version (the involuntary source-build case) is treated as behind. - update_available = ( - installed_build is None or latest_build is None or installed_build < latest_build + latest_build = parse_base_build(base_tag) + # A same-base mix adds patches the bare base lacks, so it is newer even at an + # unchanged build number (the marker path's is_behind already does this). The + # bNNNN anchor keeps a fork wrapper tag from being read as a mix. + latest_is_mix = ( + isinstance(release_tag, str) + and latest_build is not None + and parse_base_build(release_tag) == latest_build + and release_tag.strip() != f"b{latest_build}" ) + if installed_build is None or latest_build is None: + # Unknown installed/latest version (the involuntary source-build case): + # treat as behind so we still offer the prebuilt. + update_available = True + elif installed_build < latest_build: + update_available = True + elif installed_build == latest_build: + # Same upstream base: offer the extra-patch mix, never a bare rebuild. + update_available = latest_is_mix + else: + # Source build newer than the latest prebuilt: downgrade guard. + update_available = False + # Display the mix tag when that's what makes it newer; otherwise the base. + latest = release_tag if latest_is_mix else base_tag with _job_lock: job = dict(_job) return {