From cfeed2af8c30270a9565f2332bddefa36c532a4f Mon Sep 17 00:00:00 2001 From: yusufipk Date: Sat, 5 Sep 2026 09:47:22 +0300 Subject: [PATCH] Find the llama.cpp builds where they are actually published "latest" for llama.cpp is a version marker carrying one file, nightly-tag.txt, and the archives it names hang off a prerelease that "latest" never points at. Reading only the latest release meant Dikte offered no llama-server for any machine, so _pick_asset now follows that pointer, and when there is none it walks the recent releases and takes the newest one that does carry a build for this machine. hub grows releases() for the listing and text() for the pointer file; the pointer is read rather than cached, because what it carries is a few bytes on the way to a download that is checksummed in full. The extra lookups are best effort: whatever goes wrong in them leaves the caller's own message standing, but a first release that could not be fetched at all is kept and re-raised when nothing else turns up, so an unreachable GitHub still reads as an unreachable GitHub rather than as a machine nobody publishes for. --- dikte/ggml.py | 70 ++++++++++++++++++++++++++++++++++++++++++---- dikte/hub.py | 52 +++++++++++++++++++++++++++++++--- tests/test_ggml.py | 39 ++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 10 deletions(-) diff --git a/dikte/ggml.py b/dikte/ggml.py index c2f8da8..03a43ef 100644 --- a/dikte/ggml.py +++ b/dikte/ggml.py @@ -83,6 +83,10 @@ LLAMA = Program("llama", "ggml-org/llama.cpp", "llama-server", "/health") WHISPER_MODELS_REPO = "ggerganov/whisper.cpp" LLM_AUTHOR = "ggml-org" +# The file llama.cpp attaches to its version releases in place of the binaries: +# a line naming the nightly tag those are published under. +NIGHTLY_TAG = "nightly-tag.txt" + # What the whisper repository holds besides models: Core ML encoders for Apple # hardware and the odd loose file. WHISPER_PREFIX = "ggml-" @@ -277,6 +281,65 @@ def _wanted_assets(program): return (f"bin-ubuntu-{arch}.tar.gz",) +def _matching_asset(program, assets): + """The archive this machine wants out of one release's files, or None.""" + for ending in _wanted_assets(program): + item = next((a for a in assets if a.name.endswith(ending)), None) + if item: + return item + return None + + +def _pick_asset(program, tag="", refresh=False): + """(tag, Item) for the release archive to install. Item is None when there + is none for this machine. + + A named tag is taken as given. For the newest, what GitHub answers is not + always where the builds are: llama.cpp's latest release is a version marker + carrying a single nightly-tag.txt, which names the tag the archives are + actually attached to, and those are prereleases that "latest" never points + at. The pointer is followed when it is there, and when it is not, the newest + release that does carry a build for this machine is taken instead. + """ + named = bool(tag) and tag != "latest" + missing = None + try: + tag, assets = hub.release(program.repo, tag or "latest", refresh=refresh) + except hub.HubError as exc: + # A release carrying no files at all is the case the search below exists + # for, not a reason to stop before it: the build for this machine may be + # attached to a prerelease that "latest" never points at. The failure is + # kept rather than dropped, because an unreachable GitHub arrives here + # the same way and that one is the message the caller wants. + if named: + raise + missing, assets = exc, [] + item = _matching_asset(program, assets) + if item or named: + return tag, item + # Best effort from here on: a machine this project publishes nothing for is + # not a failed lookup, and the caller's message about that is the useful + # one. Whatever goes wrong while looking further leaves it standing. + try: + pointer = next((a for a in assets if a.name == NIGHTLY_TAG), None) + if pointer: + nightly = hub.text(pointer.url).strip() + if nightly: + found, assets = hub.release(program.repo, nightly, refresh=refresh) + item = _matching_asset(program, assets) + if item: + return found, item + for found, assets in hub.releases(program.repo, refresh=refresh): + item = _matching_asset(program, assets) + if item: + return found, item + except hub.HubError: + pass + if missing is not None: + raise missing + return tag, None + + def _install_record(program): return BIN_DIR / program.name / "installed.json" @@ -375,15 +438,10 @@ def install_program(program, tag="", on_progress=None, should_stop=None, whisper.cpp has one. """ try: - tag, assets = hub.release(program.repo, tag or "latest", refresh=refresh) + tag, item = _pick_asset(program, tag, refresh=refresh) except hub.HubError as exc: raise LocalError(str(exc)) from exc - item = None - for ending in _wanted_assets(program): - item = next((a for a in assets if a.name.endswith(ending)), None) - if item: - break if item is None: # Nothing to download and nothing to install for you: whisper.cpp # publishes no macOS binary, and Homebrew's whisper-cpp is configured diff --git a/dikte/hub.py b/dikte/hub.py index f5da71a..8c19f5f 100644 --- a/dikte/hub.py +++ b/dikte/hub.py @@ -121,6 +121,13 @@ def _digest(value): return value.split(":", 1)[1] if value.startswith("sha256:") else value +def _assets(data): + return [Item(a.get("name") or "", a.get("browser_download_url") or "", + int(a.get("size") or 0), _digest(a.get("digest"))) + for a in (data.get("assets") or []) + if a.get("browser_download_url")] + + def release(repo, tag="latest", refresh=False): """(tag, [Item]) for one GitHub release, newest when no tag is given.""" where = "latest" if tag in ("", "latest") else f"tags/{tag}" @@ -128,10 +135,47 @@ def release(repo, tag="latest", refresh=False): f"{GITHUB_API}/repos/{repo}/releases/{where}", refresh=refresh) if not isinstance(data, dict) or not data.get("assets"): raise HubError(t("{repo} has no downloadable release.", repo=repo)) - assets = [Item(a.get("name") or "", a.get("browser_download_url") or "", - int(a.get("size") or 0), _digest(a.get("digest"))) - for a in data["assets"] if a.get("browser_download_url")] - return data.get("tag_name") or tag, assets + return data.get("tag_name") or tag, _assets(data) + + +def releases(repo, limit=20, refresh=False): + """[(tag, [Item])] for the recent releases, newest first, with their files. + + "latest" is one release and this is the list behind it, prereleases + included: a project that attaches its builds to a prerelease is invisible + to release() above, and its newest usable build is in here. + """ + data = _fetch(f"gh-list-{repo}-{limit}", + f"{GITHUB_API}/repos/{repo}/releases?per_page={limit}", + refresh=refresh) + if not isinstance(data, list): + raise HubError(t("{repo} has no downloadable release.", repo=repo)) + out = [] + for entry in data: + tag, items = entry.get("tag_name") or "", _assets(entry) + if tag and items: + out.append((tag, items)) + return out + + +def text(url, limit=4096, timeout=20): + """A small text file from a release, as a string. + + Not cached and not checksummed, because what it carries is a pointer: a few + bytes naming the release the actual archives are attached to, read once on + the way to a download that is checked in full. + """ + request = urllib.request.Request(url, headers={"User-Agent": USER_AGENT}) + try: + with urllib.request.urlopen(request, timeout=timeout) as response: + return response.read(limit).decode("utf-8", "replace") + except urllib.error.HTTPError as exc: + exc.close() + raise HubError(t("{url} answered HTTP {code}.", + url=urllib.parse.urlsplit(url).netloc, code=exc.code)) from exc + except (urllib.error.URLError, OSError, ValueError) as exc: + raise HubError(t("Could not reach {url}: {error}", + url=urllib.parse.urlsplit(url).netloc, error=exc)) from exc def newest_release(repo, refresh=False): diff --git a/tests/test_ggml.py b/tests/test_ggml.py index 44b9305..33633c4 100644 --- a/tests/test_ggml.py +++ b/tests/test_ggml.py @@ -238,6 +238,45 @@ class InstallProgram(Local): "whisper-bin-ubuntu-x64.tar.gz") self.assertTrue(urls[1].endswith("whisper-bin-ubuntu-x64.tar.gz")) + def test_the_nightly_pointer_is_followed_to_where_the_builds_are(self): + """llama.cpp's latest release carries a tag name, not the binaries.""" + self.patch_attr(ggml, "_arch", lambda: "x64") + self.patch_attr(ggml, "_has_vulkan", lambda: False) + marker = self.release(ggml.NIGHTLY_TAG) + nightly = dict(self.release("llama-b10809-bin-ubuntu-x64.tar.gz"), + tag_name="b10809") + + def opener(request, timeout=None): + url = request.full_url + if url.endswith("/releases/latest"): + return json_body(marker) + if url.endswith("/releases/tags/b10809"): + return json_body(nightly) + if url.endswith(ggml.NIGHTLY_TAG): + return body(b"b10809\n") + return body(self.archive) + + with mock.patch("urllib.request.urlopen", side_effect=opener): + tag, found = ggml._pick_asset(ggml.LLAMA) + self.assertEqual(tag, "b10809") + self.assertEqual(found.name, "llama-b10809-bin-ubuntu-x64.tar.gz") + + def test_without_a_pointer_the_newest_release_that_has_a_build_is_taken(self): + self.patch_attr(ggml, "_arch", lambda: "x64") + self.patch_attr(ggml, "_has_vulkan", lambda: False) + marker = self.release("source.zip") + listing = [dict(self.release("llama-b2-bin-win-cpu-x64.zip"), tag_name="b2"), + dict(self.release("llama-b1-bin-ubuntu-x64.tar.gz"), tag_name="b1")] + + def opener(request, timeout=None): + url = request.full_url + return json_body(listing if "per_page" in url else marker) + + with mock.patch("urllib.request.urlopen", side_effect=opener): + tag, found = ggml._pick_asset(ggml.LLAMA) + self.assertEqual(tag, "b1") + self.assertEqual(found.name, "llama-b1-bin-ubuntu-x64.tar.gz") + def test_a_release_with_nothing_for_this_machine_says_so(self): self.patch_attr(ggml, "_arch", lambda: "x64") with fake_urlopen(self.release("whisper-bin-Win32.zip")):