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"
|
WHISPER_MODELS_REPO = "ggerganov/whisper.cpp"
|
||||||
LLM_AUTHOR = "ggml-org"
|
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
|
# What the whisper repository holds besides models: Core ML encoders for Apple
|
||||||
# hardware and the odd loose file.
|
# hardware and the odd loose file.
|
||||||
WHISPER_PREFIX = "ggml-"
|
WHISPER_PREFIX = "ggml-"
|
||||||
@@ -277,6 +281,65 @@ def _wanted_assets(program):
|
|||||||
return (f"bin-ubuntu-{arch}.tar.gz",)
|
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):
|
def _install_record(program):
|
||||||
return BIN_DIR / program.name / "installed.json"
|
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.
|
whisper.cpp has one.
|
||||||
"""
|
"""
|
||||||
try:
|
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:
|
except hub.HubError as exc:
|
||||||
raise LocalError(str(exc)) from 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:
|
if item is None:
|
||||||
# Nothing to download and nothing to install for you: whisper.cpp
|
# Nothing to download and nothing to install for you: whisper.cpp
|
||||||
# publishes no macOS binary, and Homebrew's whisper-cpp is configured
|
# 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
|
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):
|
def release(repo, tag="latest", refresh=False):
|
||||||
"""(tag, [Item]) for one GitHub release, newest when no tag is given."""
|
"""(tag, [Item]) for one GitHub release, newest when no tag is given."""
|
||||||
where = "latest" if tag in ("", "latest") else f"tags/{tag}"
|
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)
|
f"{GITHUB_API}/repos/{repo}/releases/{where}", refresh=refresh)
|
||||||
if not isinstance(data, dict) or not data.get("assets"):
|
if not isinstance(data, dict) or not data.get("assets"):
|
||||||
raise HubError(t("{repo} has no downloadable release.", repo=repo))
|
raise HubError(t("{repo} has no downloadable release.", repo=repo))
|
||||||
assets = [Item(a.get("name") or "", a.get("browser_download_url") or "",
|
return data.get("tag_name") or tag, _assets(data)
|
||||||
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
|
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):
|
def newest_release(repo, refresh=False):
|
||||||
|
|||||||
@@ -238,6 +238,45 @@ class InstallProgram(Local):
|
|||||||
"whisper-bin-ubuntu-x64.tar.gz")
|
"whisper-bin-ubuntu-x64.tar.gz")
|
||||||
self.assertTrue(urls[1].endswith("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):
|
def test_a_release_with_nothing_for_this_machine_says_so(self):
|
||||||
self.patch_attr(ggml, "_arch", lambda: "x64")
|
self.patch_attr(ggml, "_arch", lambda: "x64")
|
||||||
with fake_urlopen(self.release("whisper-bin-Win32.zip")):
|
with fake_urlopen(self.release("whisper-bin-Win32.zip")):
|
||||||
|
|||||||
Reference in New Issue
Block a user