feat(frank-medium): swap MXFP4 -> FP8; add the one-command swap toolchain
Checkpoint: just1moremodel/Qwen3.8-27B-Uncensored-MXFP4-awq -> orcarouter/Qwen3.8-27B-Uncensored-FP8 (FP8 E4M3 block-wise, 30.9 GB / 7 shards, its own single-layer MTP head). The quantization-aware entrypoint serves the plain FP8 path: --quantization fp8, ROCM_AITER_UNIFIED_ATTN, MTP self-speculation (SPEC_METHOD=mtp, SPEC=8). Measured: MTP mean accept 5.66/8 (66.2% draft acceptance), KV pool auto-sized to 461,540 tokens (1.76x), battery 85.9 t/s decode / 0.06 s TTFT / 179.7 burst-4 (vs MXFP4 92.4/0.22/ 248.9). MXFP4 checkpoint stays on disk as the rollback target. Streamlining: scripts/hf-manifest.py (generate a <filename> <size> <sha256> manifest from the live repo tree), scripts/hf-fetch.sh (resumable, byte + sha256 verified, idempotent fetch), scripts/swap-frank-medium.sh (generate manifest -> download -> detect quantization -> edit the quadlet -> deploy). Docs: new frank-medium-model-swap runbook; radiance runbook, both indexes, AGENTS.md, and log.md flipped to the FP8 shape.
This commit is contained in:
1 parent
2458d754ce
commit
c1a4ccbbcd
8 files changed
+534
-45
No files matched your search
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""hf-manifest.py — generate an hf-fetch.sh manifest from a HuggingFace repo.
|
||||
|
||||
Usage: python3 scripts/hf-manifest.py <owner/repo> [outfile]
|
||||
|
||||
Lists the repo tree via the HF API and, for each file, records the size and
|
||||
the LFS sha256 (the X-Linked-ETag header on the resolve endpoint). Non-LFS
|
||||
files (config.json, tokenizer, ...) have no LFS sha256, so they get a blank
|
||||
hash field — hf-fetch.sh then checks size only for them.
|
||||
|
||||
Output: one line per file, three space-separated fields:
|
||||
<filename> <size_bytes> <sha256-or-blank>
|
||||
|
||||
Token: $HF_TOKEN, else ~/.cache/huggingface/token, else the llamaswap conf
|
||||
(grep hf_...). Gated repos need a token that has been granted access.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
|
||||
def get_token():
|
||||
t = os.environ.get("HF_TOKEN", "")
|
||||
if not t:
|
||||
p = os.path.expanduser("~/.cache/huggingface/token")
|
||||
if os.path.exists(p):
|
||||
with open(p) as f:
|
||||
t = f.read().strip()
|
||||
if not t:
|
||||
p = os.path.expanduser("~/.config/llamaswap.conf.yaml")
|
||||
if os.path.exists(p):
|
||||
with open(p) as f:
|
||||
m = re.search(r"hf_[a-zA-Z0-9]+", f.read())
|
||||
if m:
|
||||
t = m.group(0)
|
||||
return t
|
||||
|
||||
|
||||
def head(url, token):
|
||||
h = {"User-Agent": "hf-manifest/1.0"}
|
||||
if token:
|
||||
h["Authorization"] = f"Bearer {token}"
|
||||
req = urllib.request.Request(url, method="HEAD", headers=h)
|
||||
return urllib.request.urlopen(req, timeout=30)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(f"usage: {sys.argv[0]} <owner/repo> [outfile]", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
repo = sys.argv[1]
|
||||
outfile = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
token = get_token()
|
||||
# 1. List the repo tree (siblings[].rfilename). Sizes and LFS sha256s
|
||||
# come from the resolve endpoint's HEAD (Content-Length + X-Linked-ETag);
|
||||
# the API's siblings carry only rfilename.
|
||||
api = f"https://huggingface.co/api/models/{repo}"
|
||||
req = urllib.request.Request(
|
||||
api, headers={"User-Agent": "hf-manifest/1.0",
|
||||
**({"Authorization": f"Bearer {token}"} if token else {})})
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
meta = json.load(r)
|
||||
siblings = meta.get("siblings", [])
|
||||
print(f"# {repo}: {len(siblings)} entries", file=sys.stderr)
|
||||
|
||||
lines = []
|
||||
for s in siblings:
|
||||
name = s.get("rfilename", "")
|
||||
if not name or name.endswith("/"):
|
||||
continue
|
||||
# 2. Size (Content-Length) + LFS sha256 (X-Linked-ETag) from the
|
||||
# resolve endpoint's HEAD. Non-LFS files have no 64-hex etag, so the
|
||||
# hash field stays blank (hf-fetch.sh checks size only for them).
|
||||
size = 0
|
||||
sha = ""
|
||||
resolve = f"https://huggingface.co/{repo}/resolve/main/{name}"
|
||||
try:
|
||||
with head(resolve, token) as r:
|
||||
size = int(r.headers.get("Content-Length") or 0)
|
||||
etag = (r.headers.get("X-Linked-ETag")
|
||||
or r.headers.get("ETag") or "").strip('"')
|
||||
if re.fullmatch(r"[0-9a-f]{64}", etag):
|
||||
sha = etag
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code != 404:
|
||||
raise
|
||||
lines.append(f"{name} {size} {sha}")
|
||||
print(f" {name} {size:>12} {sha or '(size only)'}", file=sys.stderr)
|
||||
|
||||
out = "\n".join(lines) + "\n"
|
||||
if outfile:
|
||||
with open(outfile, "w") as f:
|
||||
f.write(out)
|
||||
print(f"wrote {outfile}", file=sys.stderr)
|
||||
else:
|
||||
sys.stdout.write(out)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user