mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
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.
This commit is contained in:
+64
-6
@@ -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
|
||||
|
||||
+48
-4
@@ -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):
|
||||
|
||||
@@ -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")):
|
||||
|
||||
Reference in New Issue
Block a user