feat(local-models): pin Flash Next's downloads to the revisions the catalog describes

Downloads resolved files from main, so a re-upload on the publisher's side would change the bytes
behind the catalog's exact sizes and the pricing measured from them. AssetFile carries a revision
and the download plan resolves each file at it. Flash Next pins unsloth's weights and projector to
38bb39ee and ggml-org's head to 052beea; entries without a revision still follow main.

Co-authored-by: Ryan Hill <38674843+RyanUnderhill@users.noreply.github.com>
This commit is contained in:
emozilla
2026-10-06 14:38:20 -04:00
parent 47703c53e5
commit 93fb5efc61
4 changed files with 20 additions and 13 deletions

View File

@@ -50,15 +50,18 @@
"files": [
{
"path": "UD-IQ4_XS/Qwen3.8-Flash-Next-UD-IQ4_XS-00001-of-00003.gguf",
"size_bytes": 10946624
"size_bytes": 10946624,
"revision": "38bb39ee97821de2c9009abb7e93950eec396e66"
},
{
"path": "UD-IQ4_XS/Qwen3.8-Flash-Next-UD-IQ4_XS-00002-of-00003.gguf",
"size_bytes": 49835229856
"size_bytes": 49835229856,
"revision": "38bb39ee97821de2c9009abb7e93950eec396e66"
},
{
"path": "UD-IQ4_XS/Qwen3.8-Flash-Next-UD-IQ4_XS-00003-of-00003.gguf",
"size_bytes": 43836407744
"size_bytes": 43836407744,
"revision": "38bb39ee97821de2c9009abb7e93950eec396e66"
}
],
"lazy_bytes": 28800138240
@@ -75,12 +78,14 @@
"mmproj": {
"path": "mmproj-BF16.gguf",
"size_bytes": 907542944,
"local": "mmproj-Qwen3.8-Flash-Next-BF16.gguf"
"local": "mmproj-Qwen3.8-Flash-Next-BF16.gguf",
"revision": "38bb39ee97821de2c9009abb7e93950eec396e66"
},
"mtp_head": {
"repo": "ggml-org/Qwen3.8-Flash-Next-GGUF",
"path": "mtp-Qwen3.8-Flash-Next-Q8_0.gguf",
"size_bytes": 4137429280
"size_bytes": 4137429280,
"revision": "052beeaca7bec4a303e59cc7bc630c4f3a1b845d"
},
"min_engine": "b11370",
"quality": 95,

View File

@@ -39,6 +39,7 @@ class AssetFile:
size_bytes: int
local: str | None = None
repo: str | None = None # HF repo when it is not the entry's own (a companion published elsewhere)
revision: str = "main" # commit whose bytes size_bytes and the entry's pricing describe
@property
def local_name(self) -> str:
@@ -301,7 +302,7 @@ def _asset_from(d: "dict | None") -> "AssetFile | None":
if not d:
return None
return AssetFile(path=d["path"], size_bytes=int(d["size_bytes"]), local=d.get("local"),
repo=d.get("repo"))
repo=d.get("repo"), revision=d.get("revision", "main"))
# Scalar CatalogEntry fields parsed from JSON: key -> (coerce, default); None default = required.

View File

@@ -397,8 +397,8 @@ def _assign_default(job: Dict[str, Any], model_id: str) -> None:
# ── downloads: ranged parallel streams ───────────────────────
def _hf_url(repo: str, path: str) -> str:
return f"https://huggingface.co/{repo}/resolve/main/{path}"
def _hf_url(repo: str, path: str, revision: str = "main") -> str:
return f"https://huggingface.co/{repo}/resolve/{revision}/{path}"
def _model_id_for(gguf: Path) -> str:
@@ -418,8 +418,9 @@ def _variant_files_on_disk(model_id: str) -> "list[Path]":
def _download_plan(entry, variant) -> list:
"""Everything a variant needs: split parts + mmproj/draft/MTP head assets, as (url, dest, bytes) tuples."""
plan = [(_hf_url(entry.repo, a.path), bootstrap.models_dir() / a.local_name, a.size_bytes) for a in variant.files]
plan += [(_hf_url(a.repo or entry.repo, a.path), bootstrap.assets_dir() / a.local_name, a.size_bytes)
plan = [(_hf_url(entry.repo, a.path, a.revision), bootstrap.models_dir() / a.local_name, a.size_bytes)
for a in variant.files]
plan += [(_hf_url(a.repo or entry.repo, a.path, a.revision), bootstrap.assets_dir() / a.local_name, a.size_bytes)
for a in (entry.mmproj, entry.draft, entry.mtp_head) if a is not None]
return plan

View File

@@ -27,15 +27,15 @@ ENTRY = CatalogEntry(
id="head-model", display_name="Head Model", description="", repo="org/Head-Model-GGUF",
variants=(BUILD,), n_ctx_train=262144, full_layers=12, recurrent_layers=36, per_layer_f16=2048,
moe=True, n_vocab=248320, mtp_draft_depth=3,
mtp_head=AssetFile("mtp-Head-Model-Q8_0.gguf", 4 * GIB, repo="other/Head-Model-GGUF"))
mtp_head=AssetFile("mtp-Head-Model-Q8_0.gguf", 4 * GIB, repo="other/Head-Model-GGUF", revision="0123abc"))
def test_the_head_downloads_with_the_model_from_its_own_repo_and_is_priced_before_download():
without_head = replace(ENTRY, mtp_head=None)
urls = [url for url, _, _ in local_models._download_plan(ENTRY, BUILD)]
assert "https://huggingface.co/other/Head-Model-GGUF/resolve/main/mtp-Head-Model-Q8_0.gguf" in urls
assert all("/org/Head-Model-GGUF/" in url for url in urls[:2])
assert "https://huggingface.co/other/Head-Model-GGUF/resolve/0123abc/mtp-Head-Model-Q8_0.gguf" in urls
assert all("/org/Head-Model-GGUF/resolve/main/" in url for url in urls[:2])
assert ENTRY.mtp_capable and not without_head.mtp_capable
with_plan = ENTRY.launch_plan(BUILD, UNIFIED_128)
without_plan = without_head.launch_plan(BUILD, UNIFIED_128)