Files
3036766ae8 deriver: resolve HF_HOME from MODEL_DIR so bare pull.sh lands on the model disk (#646)
resolve_hf_home was `--hf-home > $HF_HOME > $XDG > ~/.cache`, ignoring MODEL_DIR.
So a bare `pull.sh <repo>` (or a nohup apply-swap) run with only .env's MODEL_DIR
set and no explicit HF_HOME silently fell to ~/.cache on the ROOT disk — the
footgun that misplaced a brought model's 35 GB of weights off the models volume
(and would bite users the same way). Insert a MODEL_DIR step (env, else parsed
from the repo .env, encoding=utf-8) between $HF_HOME and $XDG, matching what
switch.sh/launch.sh/c3 already resolve. c3 is unaffected — it sets HF_HOME
explicitly, which still wins. New guard test-hf-home-resolve (PASS 1-5);
test-pull / test-pullgate-download / test-download-lock still green.


Claude-Session: https://claude.ai/code/session_01EfF565T9eSLaqGzidyJ1Pm

Co-authored-by: noonghunna <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-09 04:40:20 +05:00

1142 lines
45 KiB
Python

"""v0.8.0 Pull-Gate — `[A]` transformers deriver.
Given an HF repo slug, derive a ModelProfile-shaped spec from the repo's own
`config.json` + HF Hub model API (`?blobs=true` for LFS-resolved sizes) +
a bounded, pre-download safetensors-header probe. This is the `[A]` slice
that produces the spec P1's generic-dense `[B]` branch consumes.
Public API (stable for P3/P4):
from scripts.lib.profiles import deriver
res = deriver.derive(slug, *, hf_token=None, hf_home=None, fetcher=None,
profiles=None)
# res: DeriveResult
# .error -> DeriverError | None (stratum-1 structured error)
# .tier1 -> Tier1Match | None (curated lookup hit)
# .confidence -> Confidence enum
# .generic_dense_eligible -> bool | None
# .spec -> dict | None (kv-calc generic-dense spec shape)
# .profile -> dict | None (derived ModelProfile-shaped dict)
P2 ONLY classifies. The stratum-5 `no-fit-model` abort, `[C0]`/`[C2a]`/`[C1]`
and the orchestrator are P3/P4 — this module never raises a traceback for a
stratum-1 condition; it returns a structured `DeriverError`.
Network: all HTTP goes through an injectable `fetcher` (see `HttpFetcher`).
Tests pass a recorded-fixture fetcher so there is NO live network and NO
weight file is ever downloaded — the header probe is range-bounded.
How P1's kv-calc is imported (per the in-file import contract at
`tools/kv-calc.py` ~line 645): via importlib, registering the module in
`sys.modules["kv_calc"]` BEFORE `exec_module` so `@dataclass` resolves
`cls.__module__`. See `_load_kv_calc()`.
"""
from __future__ import annotations
import importlib.util
import json
import os
import re
import struct
import sys
import urllib.error
import urllib.request
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Optional
REPO_ROOT = Path(__file__).resolve().parents[3]
_HF_API = "https://huggingface.co/api/models"
_HF_RESOLVE = "https://huggingface.co"
_NET_TIMEOUT = 30 # seconds (per brief)
_MAX_HEADER_BYTES = 16 * 1024 * 1024 # 16 MiB safetensors-header ceiling
# Name patterns that mark a *.safetensors blob as an adapter/LoRA, not a
# complete weight set. Matched case-insensitively on the basename.
_ADAPTER_PATTERNS = (
"adapter_model",
"adapter-model",
"lora",
"/adapter",
)
# ---------------------------------------------------------------------------
# Structured stratum-1 errors (NEVER raised as raw tracebacks)
# ---------------------------------------------------------------------------
class DeriverErrorKind(str, Enum):
REPO_NOT_FOUND = "repo-not-found"
GATED_NO_TOKEN = "gated-no-token"
UNSUPPORTED_FORMAT = "unsupported-format"
AMBIGUOUS_WEIGHT_SET = "ambiguous-weight-set"
QUANT_DTYPE_UNKNOWN = "quant-dtype-unknown"
@dataclass(frozen=True)
class DeriverError:
"""A stratum-1 structured error. Returned, never raised."""
kind: DeriverErrorKind
detail: str = ""
def __str__(self) -> str: # pragma: no cover - cosmetic
return f"{self.kind.value}: {self.detail}" if self.detail else self.kind.value
class Confidence(str, Enum):
"""§4 confidence tiers. `DERIVED` is RESERVED for the future
override-registry phase and unused in v0.8.0."""
EXACT = "exact"
ESTIMATED_LOWER_BOUND = "estimated-lower-bound"
DERIVED = "derived" # RESERVED — not assigned this phase
NOT_ELIGIBLE = "not-generic-dense-eligible"
@dataclass(frozen=True)
class Tier1Match:
"""A curated lookup hit: slug ∈ a curated model variant's `hf_repos`."""
model_id: str
weights_variant: str
slug: str
@dataclass
class DeriveResult:
slug: str
error: Optional[DeriverError] = None
tier1: Optional[Tier1Match] = None
confidence: Optional[Confidence] = None
generic_dense_eligible: Optional[bool] = None
spec: Optional[dict[str, Any]] = None
profile: Optional[dict[str, Any]] = None
diagnostics: dict[str, Any] = field(default_factory=dict)
# ---------------------------------------------------------------------------
# HTTP fetcher abstraction (injectable for fixture-driven tests)
# ---------------------------------------------------------------------------
@dataclass
class FetchResponse:
status: int
body: bytes
class HttpFetcher:
"""Real-network fetcher. Tests inject a recorded-fixture replacement.
`get(url, headers=None, range_=None)` returns a FetchResponse. HTTP errors
surface as the response's status (not exceptions) where the caller maps
them to structured stratum-1 errors; only true network failures raise
`NetworkError`.
"""
timeout = _NET_TIMEOUT
def get(
self,
url: str,
headers: Optional[dict[str, str]] = None,
range_: Optional[tuple[int, int]] = None,
) -> FetchResponse:
req_headers = dict(headers or {})
if range_ is not None:
lo, hi = range_
req_headers["Range"] = f"bytes={lo}-{hi}"
req = urllib.request.Request(url, headers=req_headers, method="GET")
try:
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
return FetchResponse(status=resp.status, body=resp.read())
except urllib.error.HTTPError as exc: # 4xx/5xx — surface status
try:
body = exc.read()
except Exception: # pragma: no cover - defensive
body = b""
return FetchResponse(status=exc.code, body=body)
except (urllib.error.URLError, TimeoutError, OSError) as exc:
raise NetworkError(str(exc)) from exc
class NetworkError(RuntimeError):
"""True transport failure (timeout / DNS / connection). Distinct from an
HTTP status code, which the fetcher returns as a FetchResponse."""
Fetcher = HttpFetcher # type alias for callers
# ---------------------------------------------------------------------------
# kv-calc import (per the documented sys.modules contract)
# ---------------------------------------------------------------------------
_KV_CALC = None
def _load_kv_calc():
"""Load tools/kv-calc.py via importlib, registering it in sys.modules
BEFORE exec_module (required: kv-calc.py uses @dataclass, which resolves
cls.__module__ via sys.modules during class creation)."""
global _KV_CALC
if _KV_CALC is not None:
return _KV_CALC
if "kv_calc" in sys.modules:
_KV_CALC = sys.modules["kv_calc"]
return _KV_CALC
kv_path = REPO_ROOT / "tools" / "kv-calc.py"
spec = importlib.util.spec_from_file_location("kv_calc", kv_path)
mod = importlib.util.module_from_spec(spec)
sys.modules["kv_calc"] = mod # MUST precede exec_module
spec.loader.exec_module(mod)
_KV_CALC = mod
return _KV_CALC
# ---------------------------------------------------------------------------
# HF_HOME resolution
# --hf-home > $HF_HOME > $MODEL_DIR/.cache/huggingface > $XDG_CACHE_HOME/hf > ~
# ---------------------------------------------------------------------------
def _model_dir_from_env_or_dotenv() -> Optional[str]:
"""MODEL_DIR from the environment, else parsed from the repo `.env` (the
SAME value switch.sh / launch.sh / c3 resolve). `None` if set in neither.
Read with `encoding="utf-8"` (non-UTF-8-locale rigs, #599)."""
env = os.environ.get("MODEL_DIR")
if env:
return env
try:
for raw in (REPO_ROOT / ".env").read_text(
encoding="utf-8", errors="replace"
).splitlines():
s = raw.strip().rstrip("\r")
if not s or s.startswith("#") or "=" not in s:
continue
if s.startswith("export "):
s = s[len("export "):]
key, _, val = s.partition("=")
if key.strip() == "MODEL_DIR":
val = val.strip().strip('"').strip("'")
return val or None
except OSError:
pass
return None
def resolve_hf_home(hf_home: Optional[str] = None) -> Path:
"""HF_HOME precedence: `--hf-home > $HF_HOME > $MODEL_DIR/.cache/huggingface
> $XDG_CACHE_HOME/huggingface > ~/.cache/huggingface`.
The MODEL_DIR step keeps a bare `pull.sh <repo>` — run with only `.env`'s
MODEL_DIR set and no explicit HF_HOME — on the MODEL DISK, instead of
silently falling to `~/.cache` on root (the footgun that misplaced a brought
model's weights, #617-followup). c3 is unaffected: it sets HF_HOME
explicitly, which still wins here."""
if hf_home:
return Path(hf_home).expanduser()
env = os.environ.get("HF_HOME")
if env:
return Path(env).expanduser()
model_dir = _model_dir_from_env_or_dotenv()
if model_dir:
return Path(model_dir).expanduser() / ".cache" / "huggingface"
xdg = os.environ.get("XDG_CACHE_HOME")
if xdg:
return Path(xdg).expanduser() / "huggingface"
return Path.home() / ".cache" / "huggingface"
# v0.8.0 [E] E2 (additive): the canonical fetcher E1's deferred dtype
# header-probe step (2) uses. E1's `_resolve_compute_dtype()` calls the
# deriver's existing `probe_safetensors_dtype()` ONLY when a usable fetcher
# is present at `einput.diagnostics["fetcher"]`. E2 standardizes the SOURCE
# of that fetcher here (a real range-bounded `HttpFetcher`) so E4 can wire
# `diagnostics["fetcher"]` deterministically and tests can inject a fixture.
# This does NOT change E1's resolution ORDER/semantics — it only makes the
# probe path live by supplying the fetcher E1 already looks for.
def default_probe_fetcher() -> "HttpFetcher":
"""The canonical real bounded-header-probe fetcher (range-GET only;
never downloads a full weight). Tests inject a recorded fixture
instead."""
return HttpFetcher()
# ---------------------------------------------------------------------------
# Tier-1 curated lookup
# ---------------------------------------------------------------------------
def _tier1_lookup(slug: str, profiles) -> Optional[Tier1Match]:
"""slug ∈ a curated model variant's `hf_repos` → that (model, variant).
Matched case-insensitively (per brief: `hf_repos` entries are full HF
slugs, matched case-insensitively)."""
needle = slug.strip().lower()
for model in profiles.models.values():
for variant, meta in model.weights.items():
for repo in meta.get("hf_repos", []) or []:
if str(repo).strip().lower() == needle:
return Tier1Match(
model_id=model.id, weights_variant=variant, slug=str(repo)
)
return None
# ---------------------------------------------------------------------------
# HF fetch helpers
# ---------------------------------------------------------------------------
def _auth_headers(hf_token: Optional[str]) -> dict[str, str]:
return {"Authorization": f"Bearer {hf_token}"} if hf_token else {}
def _fetch_model_api(
slug: str, fetcher: HttpFetcher, hf_token: Optional[str]
) -> tuple[Optional[dict], Optional[DeriverError]]:
url = f"{_HF_API}/{slug}?blobs=true"
resp = fetcher.get(url, headers=_auth_headers(hf_token))
if resp.status == 404:
return None, DeriverError(DeriverErrorKind.REPO_NOT_FOUND, slug)
if resp.status in (401, 403):
if not hf_token:
return None, DeriverError(DeriverErrorKind.GATED_NO_TOKEN, slug)
# token present but still denied → treat as not-found (no access)
return None, DeriverError(DeriverErrorKind.REPO_NOT_FOUND, f"{slug} (auth denied)")
if resp.status != 200:
return None, DeriverError(
DeriverErrorKind.REPO_NOT_FOUND, f"{slug} (HF API status {resp.status})"
)
try:
return json.loads(resp.body.decode("utf-8")), None
except (ValueError, UnicodeDecodeError) as exc:
return None, DeriverError(
DeriverErrorKind.REPO_NOT_FOUND, f"{slug} (malformed HF API: {exc})"
)
def _fetch_config_json(
slug: str, fetcher: HttpFetcher, hf_token: Optional[str]
) -> tuple[Optional[dict], Optional[DeriverError]]:
url = f"{_HF_RESOLVE}/{slug}/resolve/main/config.json"
resp = fetcher.get(url, headers=_auth_headers(hf_token))
if resp.status == 404:
return None, DeriverError(
DeriverErrorKind.UNSUPPORTED_FORMAT, f"{slug} (no config.json)"
)
if resp.status in (401, 403):
if not hf_token:
return None, DeriverError(DeriverErrorKind.GATED_NO_TOKEN, slug)
return None, DeriverError(DeriverErrorKind.REPO_NOT_FOUND, f"{slug} (auth denied)")
if resp.status != 200:
return None, DeriverError(
DeriverErrorKind.REPO_NOT_FOUND, f"{slug} (config.json status {resp.status})"
)
try:
return json.loads(resp.body.decode("utf-8")), None
except (ValueError, UnicodeDecodeError) as exc:
return None, DeriverError(
DeriverErrorKind.QUANT_DTYPE_UNKNOWN, f"{slug} (malformed config.json: {exc})"
)
# ---------------------------------------------------------------------------
# File selection
# ---------------------------------------------------------------------------
def _is_adapter(name: str) -> bool:
low = name.lower()
return any(p in low for p in _ADAPTER_PATTERNS)
def _siblings(api: dict) -> list[dict]:
out = []
for s in api.get("siblings", []) or []:
if isinstance(s, dict) and s.get("rfilename"):
out.append(s)
return out
def _declares_mtp(config: dict) -> bool:
"""True when config.json declares a multi-token-prediction head — Qwen3-Next
uses `mtp_num_hidden_layers`; other families use `num_nextn_predict_layers`.
Checks the top level AND a nested `text_config` (VLMs nest the LM config)."""
for cfg in (config or {}, (config or {}).get("text_config") or {}):
if not isinstance(cfg, dict):
continue
for key in ("mtp_num_hidden_layers", "num_nextn_predict_layers"):
v = cfg.get(key)
if isinstance(v, int) and v > 0:
return True
return False
def _has_mtp_weight_file(api: dict) -> bool:
"""True when the repo ships a dedicated MTP-head weights file — the layout
fine-tune re-quants use (e.g. `model_mtp_bf16.safetensors`)."""
for s in _siblings(api):
name = (s.get("rfilename") or "").lower()
if name.endswith(".safetensors") and ("mtp" in name or "nextn" in name):
return True
return False
def detect_mtp_head(config: dict, api: dict) -> bool:
"""Whether a brought checkpoint actually carries an MTP draft head, so the
Route-C weight-swap keeps `--speculative-config` instead of blanket-dropping
it. The blanket drop was a bug: fine-tunes that PRESERVE the head (e.g.
ThinkingCap) were served MTP-off. Signal (no extra fetch — config + siblings
are already in hand): config DECLARES the MTP layers AND a dedicated mtp
weights file is present. Ground-truth for the separate-file layout every
fine-tune uses. An embedded-head repo (head baked into the shards with no
named file) still falls back to drop — the named-file layout is the norm and
the alternative is reading each shard's index weight_map."""
return _declares_mtp(config) and _has_mtp_weight_file(api)
def select_weight_files(
api: dict,
) -> tuple[Optional[list[str]], Optional[DeriverError]]:
"""Per brief file selection:
- `*.safetensors.index.json` present → the shard set in its weight_map.
(The index itself must be fetched separately to read weight_map; here
we only need the shard filenames the index points to, which equal the
set of top-level shard *.safetensors. We resolve the shard set from
the siblings list filtered to non-adapter *.safetensors.)
- Else → after excluding adapter/LoRA patterns, accept EXACTLY ONE
top-level *.safetensors regardless of basename; multiple plausible
complete sets → `ambiguous-weight-set`.
- No *.safetensors → `unsupported-format`.
"""
sibs = _siblings(api)
names = [s["rfilename"] for s in sibs]
safet = [
n
for n in names
if n.endswith(".safetensors") and "/" not in n and not _is_adapter(n)
]
if not safet:
return None, DeriverError(
DeriverErrorKind.UNSUPPORTED_FORMAT,
"no top-level *.safetensors (GGUF/.bin not supported — this path is vLLM + safetensors only)",
)
index_files = [
n
for n in names
if n.endswith(".safetensors.index.json") and "/" not in n
]
if index_files:
# Sharded set: the shards are the non-adapter top-level *.safetensors.
# A single complete sharded set is unambiguous; >1 distinct index
# implies >1 plausible complete set.
if len(index_files) > 1:
return None, DeriverError(
DeriverErrorKind.AMBIGUOUS_WEIGHT_SET,
f"multiple safetensors index files: {sorted(index_files)}",
)
shards = sorted(
n for n in safet if "-of-" in n or n.startswith("model-")
)
if not shards:
# Index present but no obvious shard naming — fall back to all
# non-adapter top-level safetensors as the set.
shards = sorted(safet)
else:
# A dedicated MTP/nextn head (e.g. `mtp_grafted.safetensors`) is a
# real weight the model needs with MTP enabled, but it's neither a
# `model-*` nor `-of-` shard, so the filter above drops it —
# which silently omitted Tess-4-27B-FP8's MTP head and would break
# MTP serving (club-3090 #617). `detect_mtp_head` already sees such a
# file; union it into the download set so it's actually fetched.
mtp_head = [
n for n in safet
if n not in shards
and ("mtp" in n.lower() or "nextn" in n.lower())
]
if mtp_head:
shards = sorted(set(shards) | set(mtp_head))
return shards, None
# No index: must be exactly one complete set.
if len(safet) == 1:
return safet, None
# Multiple top-level safetensors with no index → ambiguous.
return None, DeriverError(
DeriverErrorKind.AMBIGUOUS_WEIGHT_SET,
f"{len(safet)} top-level *.safetensors and no index.json: {sorted(safet)}",
)
def _sum_blob_gb(api: dict, selected: list[str]) -> float:
by_name = {}
for s in _siblings(api):
size = s.get("size")
if size is None and isinstance(s.get("lfs"), dict):
size = s["lfs"].get("size")
if size is not None:
by_name[s["rfilename"]] = size
total = 0
wanted = set(selected) | {"config.json"}
for name, size in by_name.items():
base = name
if name in wanted or (
name.endswith("config.json") and "/" not in name
) or any(name == w for w in selected):
total += int(size)
continue
# tokenizer files (required) count toward footprint
if base in (
"tokenizer.json",
"tokenizer.model",
"tokenizer_config.json",
"special_tokens_map.json",
"vocab.json",
"merges.txt",
):
total += int(size)
# weights authority is the summed selected blobs (config/tokenizer are
# negligible but included for footprint completeness).
return round(total / (1024 ** 3), 4)
def _selected_weight_gb(api: dict, selected: list[str]) -> float:
by_name = {}
for s in _siblings(api):
size = s.get("size")
if size is None and isinstance(s.get("lfs"), dict):
size = s["lfs"].get("size")
if size is not None:
by_name[s["rfilename"]] = int(size)
total = sum(by_name.get(n, 0) for n in selected)
return round(total / (1024 ** 3), 4)
# ---------------------------------------------------------------------------
# v0.8.0 [E] CONTRACT-3 — the SINGLE shared download allowlist.
#
# `select_weight_files()` returns only `*.safetensors`; vLLM also needs the
# config/tokenizer/template assets. CONTRACT-3 reconciles v2's `*.jinja`
# addition with the legacy `[C2a]` footprint's `vocab.json`/`merges.txt` into
# ONE union, used identically by `[C2a]` sizing (gates.c2a_disk), E2 download
# (downloader.download_model -> snapshot_download allow_patterns), and E3
# smoke. There is exactly ONE function — no parallel lists that can drift.
# ---------------------------------------------------------------------------
# Exact non-glob metadata basenames (the brief's REQUIRED_METADATA, minus the
# `*.jinja` glob which is matched separately). "those that exist in siblings".
REQUIRED_METADATA = (
"config.json",
"generation_config.json",
"tokenizer.json",
"tokenizer_config.json",
"tokenizer.model",
"special_tokens_map.json",
"vocab.json",
"merges.txt",
)
def download_set(api: dict) -> list[str]:
"""CONTRACT-3 reconciled union — the ONE allowlist:
select_weight_files(api) # *.safetensors (no adapters)
+ the *.safetensors.index.json if present
+ REQUIRED_METADATA siblings that exist # config/tokenizer/...
+ every top-level *.jinja sibling # chat templates
Deterministic ordering: weights (as `select_weight_files` returns them),
then the index, then metadata in REQUIRED_METADATA order, then sorted
`*.jinja`. Only siblings that ACTUALLY EXIST are included ("those that
exist in siblings"). On an unselectable weight set this returns `[]`
(the caller already surfaced the structured `select_weight_files` error;
`download_set` never raises — it is a pure projection of `api`).
This is the literal list E2 passes as `snapshot_download(...,
allow_patterns=...)` and the exact set `[C2a]` sizes — a test asserts
fetched-set == sized-set.
"""
selected, err = select_weight_files(api or {})
if err is not None or not selected:
return []
names = {s["rfilename"] for s in _siblings(api or {})}
out: list[str] = list(selected)
# the *.safetensors.index.json (top-level) if present
for n in sorted(names):
if n.endswith(".safetensors.index.json") and "/" not in n:
out.append(n)
# REQUIRED_METADATA — exact basenames that exist (top-level)
for meta in REQUIRED_METADATA:
if meta in names:
out.append(meta)
# every top-level *.jinja (chat templates)
for n in sorted(names):
if n.endswith(".jinja") and "/" not in n and n not in out:
out.append(n)
# de-dup while preserving first-seen order (a weight is never metadata,
# but be defensive against an index/metadata name collision).
seen: set[str] = set()
deduped: list[str] = []
for n in out:
if n not in seen:
seen.add(n)
deduped.append(n)
return deduped
def sized_download_gb(api: dict) -> float:
"""Σ size of EXACTLY `download_set(api)` (LFS-resolved), GiB.
This is the CONTRACT-3 footprint: `[C2a]` sizes precisely the set E2
fetches, so `[C2a]`/E2/E3 cannot drift. Replaces the legacy
`_sum_blob_gb` heuristic (which approximated the same union but with a
hand-listed metadata set that omitted `*.jinja`); behaviour-equivalent
to within KB on any real model (metadata is negligible vs multi-GB
weights) and now provably == the fetched set."""
by_name = {}
for s in _siblings(api or {}):
size = s.get("size")
if size is None and isinstance(s.get("lfs"), dict):
size = s["lfs"].get("size")
if size is not None:
by_name[s["rfilename"]] = int(size)
total = sum(by_name.get(n, 0) for n in download_set(api or {}))
return round(total / (1024 ** 3), 4)
# ---------------------------------------------------------------------------
# Bring-funnel stage-1 INSPECT — artifact inventory (design §2 / §2b)
# ---------------------------------------------------------------------------
# GGUF quant token in a basename (Q4_K_M / IQ4_XS / UD-Q5_K_XL / Q8_0 / TQ1_0 /
# BF16 / F16), tolerant of multi-part suffixes which are stripped first.
_GGUF_PART_RE = re.compile(r"-(\d{5})-of-(\d{5})$", re.I)
_GGUF_QUANT_RE = re.compile(
r"(?:^|[-_.])((?:UD-)?(?:I?Q\d|TQ\d|BF16|F16|F32)[A-Z0-9_]*)$", re.I
)
def _blob_sizes(api: dict) -> dict[str, int]:
by_name: dict[str, int] = {}
for s in _siblings(api or {}):
size = s.get("size")
if size is None and isinstance(s.get("lfs"), dict):
size = s["lfs"].get("size")
if size is not None:
by_name[s["rfilename"]] = int(size)
return by_name
def artifact_inventory(api: dict) -> dict:
"""What servable artifacts does this repo carry? — WITHOUT gating on
format. A GGUF-only repo is a first-class bring here (design §2b-1/2:
the staged Bring UI reveals nothing template-side until this says the
repo is supported, and presents ALL discovered GGUF variants for the
user to pick BEFORE any engine/slug appears). `select_weight_files`
stays the vLLM/safetensors gate — this never replaces it.
Returns (all sizes GiB, from the ?blobs=true siblings):
formats ["safetensors", "gguf"] — whichever are present
safetensors {weight_files, size_gb} | None (top-level, non-adapter)
gguf_variants [{quant, size_gb, parts, files}] sorted by size —
multi-part files grouped under one quant token; a file
with no parseable token keys by its stem (never dropped)
gguf_mmproj vision-projector *.gguf names (NOT variants)
lineage_base_model cardData.base_model when the API carries it
(friction #11 — ⑤'s taxonomy default + credits)"""
sizes = _blob_sizes(api)
names = [s["rfilename"] for s in _siblings(api or {})]
safet = [
n for n in names
if n.endswith(".safetensors") and "/" not in n and not _is_adapter(n)
]
st = None
if safet:
st = {
"weight_files": sorted(safet),
"size_gb": round(sum(sizes.get(n, 0) for n in safet) / (1024 ** 3), 4),
}
# GGUF: any depth (quant subdirs are common), mmproj split out.
# Grouping key = the STEM (basename minus the -NNNNN-of-NNNNN part
# suffix), NOT the quant token — a repo can ship DISTINCT artifacts
# sharing a token (live dogfood 2026-07-05: Qwythos ships
# `…-Q4_K_M.gguf` AND `…-MTP-Q4_K_M.gguf` per quant; token-keying
# merged them into one "2-part variant" with a summed, wrong size).
# True multi-part shards share a stem, so `parts` still counts them.
variants: dict[str, dict] = {}
mmproj: list[str] = []
for n in names:
if not n.lower().endswith(".gguf"):
continue
base = n.rsplit("/", 1)[-1][: -len(".gguf")]
if base.lower().startswith("mmproj"):
mmproj.append(n)
continue
stem = _GGUF_PART_RE.sub("", base)
v = variants.setdefault(stem, {"size_gb": 0.0, "parts": 0, "files": []})
v["size_gb"] += sizes.get(n, 0) / (1024 ** 3)
v["parts"] += 1
v["files"].append(n)
# Display label: the stem minus the repo-wide COMMON prefix — for
# standard repos that IS the quant token ("Q4_K_M"); for multi-artifact
# repos it keeps the distinguishing part ("MTP-Q4_K_M"). Falls back to
# the parsed token, then the full stem (labels stay unique: stems are).
common = os.path.commonprefix(list(variants)) if len(variants) > 1 else ""
out_variants = []
for stem, v in variants.items():
label = stem[len(common):].strip("-_. ")
if not label:
m = _GGUF_QUANT_RE.search(stem)
label = m.group(1).upper() if m else stem
out_variants.append(
{"quant": label, "size_gb": round(v["size_gb"], 4),
"parts": v["parts"], "files": sorted(v["files"])}
)
gguf_variants = sorted(out_variants, key=lambda v: (v["size_gb"], v["quant"]))
formats = []
if st:
formats.append("safetensors")
if gguf_variants or mmproj:
formats.append("gguf")
card = api.get("cardData") if isinstance(api, dict) else None
base_model = (card or {}).get("base_model") if isinstance(card, dict) else None
return {
"formats": formats,
"safetensors": st,
"gguf_variants": gguf_variants,
"gguf_mmproj": sorted(mmproj),
"lineage_base_model": base_model,
}
def inspect_repo(
slug: str,
*,
hf_token: Optional[str] = None,
fetcher: Optional[HttpFetcher] = None,
) -> dict:
"""Stage-1 INSPECT entry: fetch the model API + return the inventory.
Structured errors, never a traceback (same discipline as derive())."""
if fetcher is None:
fetcher = HttpFetcher()
hf_token = hf_token or os.environ.get("HF_TOKEN") or None
try:
api, err = _fetch_model_api(slug, fetcher, hf_token)
except NetworkError as exc:
return {"repo": slug, "error": f"network error: {exc}"}
if err is not None:
return {"repo": slug, "error": str(err)}
inv = artifact_inventory(api or {})
inv["repo"] = slug
if not inv["formats"]:
inv["error"] = "no servable artifacts (no safetensors weight set, no *.gguf)"
return inv
# ---------------------------------------------------------------------------
# Bounded safetensors-header probe (pre-download, range-bounded)
# ---------------------------------------------------------------------------
def probe_safetensors_dtype(
slug: str,
weight_file: str,
fetcher: HttpFetcher,
hf_token: Optional[str],
) -> Optional[str]:
"""Range-bounded header probe. NEVER downloads a full weight file.
1. Range-GET bytes=0-7 → little-endian u64 = header length N.
2. if N > 16 MiB → None (caller maps to quant-dtype-unknown).
3. Range-GET bytes=8-(8+N-1) [HTTP ranges inclusive; read exactly N].
4. parse JSON; read __metadata__ / first tensor dtype.
Any failure/malformed → None.
"""
url = f"{_HF_RESOLVE}/{slug}/resolve/main/{weight_file}"
try:
r1 = fetcher.get(url, headers=_auth_headers(hf_token), range_=(0, 7))
except NetworkError:
return None
if r1.status not in (200, 206) or len(r1.body) < 8:
return None
n = struct.unpack("<Q", r1.body[:8])[0]
if n <= 0 or n > _MAX_HEADER_BYTES:
return None
# bytes 8 .. 8+N-1 inclusive == exactly N bytes from offset 8.
try:
r2 = fetcher.get(
url, headers=_auth_headers(hf_token), range_=(8, 8 + n - 1)
)
except NetworkError:
return None
if r2.status not in (200, 206):
return None
blob = r2.body[:n]
if len(blob) < n:
return None
try:
hdr = json.loads(blob.decode("utf-8"))
except (ValueError, UnicodeDecodeError):
return None
if not isinstance(hdr, dict):
return None
meta = hdr.get("__metadata__")
if isinstance(meta, dict):
for k in ("dtype", "torch_dtype", "format"):
if isinstance(meta.get(k), str):
return meta[k]
for key, tinfo in hdr.items():
if key == "__metadata__":
continue
if isinstance(tinfo, dict) and isinstance(tinfo.get("dtype"), str):
return tinfo["dtype"]
return None
# ---------------------------------------------------------------------------
# Quant / dtype chain + effective bits-per-weight
# ---------------------------------------------------------------------------
_DTYPE_BPW = {
"BF16": 16.0,
"F16": 16.0,
"FP16": 16.0,
"FLOAT16": 16.0,
"BFLOAT16": 16.0,
"F32": 32.0,
"FP32": 32.0,
"FLOAT32": 32.0,
"F8_E5M2": 8.0,
"F8_E4M3": 8.0,
"FLOAT8_E5M2": 8.0,
"FLOAT8_E4M3FN": 8.0,
"I8": 8.0,
"INT8": 8.0,
"U8": 8.0,
"I4": 4.0,
"INT4": 4.0,
}
def _quant_bpw(quant_cfg: dict) -> Optional[float]:
bits = quant_cfg.get("bits") or quant_cfg.get("w_bit") or quant_cfg.get(
"weight_bits"
)
if isinstance(bits, (int, float)) and bits > 0:
return float(bits)
# compressed-tensors (llm-compressor) checkpoints nest the bit-width per
# config-group instead of top-level: config_groups.<g>.weights =
# {num_bits: 8, type: "float"|"int", ...}. Take the widest weights
# num_bits across groups (mixed-precision groups exist; the widest
# dominates the VRAM footprint the fit-check cares about). Explicit
# structure beats the method-name heuristics below — "compressed-tensors"
# as a method name matches none of them (the Agents-A1-FP8-dynamic
# producer-zero dogfood finding, 2026-07-02).
groups = quant_cfg.get("config_groups")
if isinstance(groups, dict):
bits_seen = []
for g in groups.values():
w = g.get("weights") if isinstance(g, dict) else None
nb = w.get("num_bits") if isinstance(w, dict) else None
if isinstance(nb, (int, float)) and nb > 0:
bits_seen.append(float(nb))
if bits_seen:
return max(bits_seen)
method = str(
quant_cfg.get("quant_method")
or quant_cfg.get("method")
or quant_cfg.get("quant_algo")
or ""
).lower()
if any(k in method for k in ("awq", "gptq", "autoround", "int4", "4bit")):
return 4.0
if "fp8" in method or "8bit" in method or "int8" in method:
return 8.0
return None
def resolve_quant_dtype(
slug: str,
config: dict,
selected: list[str],
fetcher: HttpFetcher,
hf_token: Optional[str],
) -> tuple[Optional[str], Optional[float], Optional[DeriverError]]:
"""`quantization_config` → `torch_dtype` → bounded header probe →
else `quant-dtype-unknown`. Returns (weight_format, bpw, error)."""
qcfg = config.get("quantization_config")
if isinstance(qcfg, dict) and qcfg:
method = str(
qcfg.get("quant_method") or qcfg.get("method") or "quantized"
).lower()
bpw = _quant_bpw(qcfg)
if bpw is not None:
return method, bpw, None
# known method, undeterminable bits
return None, None, DeriverError(
DeriverErrorKind.QUANT_DTYPE_UNKNOWN,
f"{slug} (quantization_config method={method!r} bits undeterminable)",
)
td = config.get("torch_dtype") or config.get("dtype")
if isinstance(td, str) and td.strip():
bpw = _DTYPE_BPW.get(td.strip().upper())
if bpw is not None:
return td.strip(), bpw, None
# bounded header probe (sharded → first shard only)
if selected:
dtype = probe_safetensors_dtype(
slug, sorted(selected)[0], fetcher, hf_token
)
if dtype:
bpw = _DTYPE_BPW.get(dtype.strip().upper())
if bpw is not None:
return dtype.strip(), bpw, None
return None, None, DeriverError(
DeriverErrorKind.QUANT_DTYPE_UNKNOWN,
f"{slug} (no quantization_config / torch_dtype / probeable header dtype)",
)
# ---------------------------------------------------------------------------
# Spec / profile assembly
# ---------------------------------------------------------------------------
def _int(config: dict, key: str) -> Optional[int]:
v = config.get(key)
return v if isinstance(v, int) and not isinstance(v, bool) else None
def _build_generic_dense_spec(
slug: str, config: dict, weight_gb: float
) -> dict[str, Any]:
hidden = _int(config, "hidden_size")
n_layers = _int(config, "num_hidden_layers")
n_heads = _int(config, "num_attention_heads")
n_kv = _int(config, "num_key_value_heads")
head_dim = _int(config, "head_dim")
if head_dim is None and hidden and n_heads and hidden % n_heads == 0:
head_dim = hidden // n_heads
arch_list = config.get("architectures") or []
arch = str(arch_list[0]) if arch_list else None
return {
"model_id": slug,
"model_family": "generic-dense",
"arch": arch,
"hidden_size": hidden,
"num_hidden_layers": n_layers,
"num_attn_heads": n_heads,
"num_kv_heads": n_kv,
"head_dim_attn": head_dim,
"weights_total_gb": weight_gb,
"valid_tp": [1, 2],
"max_ctx_supported": _int(config, "max_position_embeddings") or 131072,
}
# ---------------------------------------------------------------------------
# Public entry point
# ---------------------------------------------------------------------------
def derive(
slug: str,
*,
hf_token: Optional[str] = None,
hf_home: Optional[str] = None,
fetcher: Optional[HttpFetcher] = None,
profiles=None,
) -> DeriveResult:
"""Derive a ModelProfile-shaped result for an HF repo slug.
Resolution order (§4):
1. Tier-1 curated lookup (slug ∈ a curated variant's hf_repos) →
confidence EXACT, no network for the spec (curated profile is
authoritative). Stratum-1 file/quant checks are NOT run for a
curated hit (the curated profile already encodes them); P3/P4 do
the Path-A weights_variant compat check.
2. else fetch config.json + HF siblings; run stratum-1 deriver checks;
if is_generic_dense_eligible → confidence ESTIMATED_LOWER_BOUND.
3. else → NOT_ELIGIBLE (P4 wires the stratum-5 abort; P2 only marks).
"""
res = DeriveResult(slug=slug)
if fetcher is None:
fetcher = HttpFetcher()
if profiles is None:
from .compat import load_profiles
profiles = load_profiles()
hf_token = hf_token or os.environ.get("HF_TOKEN") or None
# --- §4 Tier-1 curated lookup ------------------------------------------
t1 = _tier1_lookup(slug, profiles)
if t1 is not None:
model = profiles.models[t1.model_id]
variant_meta = model.weights.get(t1.weights_variant, {})
# Schema invariant: hf_repos must only attach to safetensors-compatible
# variants. If a curated slug somehow resolves to a gguf / non-
# safetensors variant, surface stratum-1 unsupported-format honestly
# rather than a silent mismatch (Codex-r5 Med-3).
fmt = str(variant_meta.get("format", "")).lower()
if fmt in ("gguf",):
res.error = DeriverError(
DeriverErrorKind.UNSUPPORTED_FORMAT,
f"{slug} resolves to {t1.model_id}.{t1.weights_variant} "
f"(format={fmt!r}); GGUF not supported — vLLM + safetensors only",
)
return res
res.tier1 = t1
res.confidence = Confidence.EXACT
res.profile = {
"model_id": model.id,
"weights_variant": t1.weights_variant,
"arch": None,
"family": model.family,
"hidden_size": model.hidden_size,
"num_hidden_layers": model.num_hidden_layers,
"num_attn_heads": model.num_attn_heads,
"num_kv_heads": model.num_kv_heads,
"weight_format": variant_meta.get("format"),
"weights_variant_size_gb": variant_meta.get("size_gb"),
}
res.diagnostics["resolution"] = "tier1-curated"
return res
# --- stratum-1: HF model API first (repo existence / gating) -----------
# Order matters: the HF model API is the authority for repo-not-found
# (404) and gated-no-token (401/403 w/o token). config.json 404 on an
# existing repo means "no transformers config" -> unsupported-format.
try:
api, err = _fetch_model_api(slug, fetcher, hf_token)
except NetworkError as exc:
res.error = DeriverError(
DeriverErrorKind.REPO_NOT_FOUND, f"{slug} (network error: {exc})"
)
return res
if err is not None:
res.error = err
return res
try:
config, err = _fetch_config_json(slug, fetcher, hf_token)
except NetworkError as exc:
res.error = DeriverError(
DeriverErrorKind.REPO_NOT_FOUND, f"{slug} (network error: {exc})"
)
return res
if err is not None:
res.error = err
return res
selected, err = select_weight_files(api or {})
if err is not None:
res.error = err
return res
weight_format, bpw, err = resolve_quant_dtype(
slug, config or {}, selected or [], fetcher, hf_token
)
if err is not None:
res.error = err
return res
weight_gb = _selected_weight_gb(api or {}, selected or [])
# v0.8.0 [E] CONTRACT-3: footprint sizes EXACTLY the shared download_set
# (the same union E2 fetches + [C2a] gates on) — single source, no drift.
# (`_sum_blob_gb` kept above as append-only history; superseded here.)
footprint_gb = sized_download_gb(api or {})
# --- §4 generic-dense eligibility (reuse P1's predicate) ---------------
kv = _load_kv_calc()
eligible = bool(kv.is_generic_dense_eligible(config or {}))
res.generic_dense_eligible = eligible
arch_list = (config or {}).get("architectures") or []
arch = str(arch_list[0]) if arch_list else None
has_auto_map = bool((config or {}).get("auto_map"))
# v0.8.0 [E] CONTRACT-2 — scoped ADDITIVE surface: expose config.json's
# raw `torch_dtype` (or its `dtype` alias) so [E]'s derived-vllm template
# can resolve `--dtype` for quantized rows (resolve_quant_dtype()
# short-circuits at quantization_config and never records it). Additive
# field ONLY — every existing field is byte-unchanged; no behaviour
# depends on it inside the deriver. None when config.json omits it.
_raw_td = (config or {}).get("torch_dtype") or (config or {}).get("dtype")
config_torch_dtype = _raw_td.strip() if isinstance(_raw_td, str) and _raw_td.strip() else None
res.profile = {
"model_id": slug,
"weights_variant": None,
"arch": arch,
"family": "generic-dense" if eligible else None,
"auto_map": has_auto_map,
"weight_format": weight_format,
"torch_dtype": config_torch_dtype,
"effective_bpw": bpw,
"weights_total_gb": weight_gb,
"footprint_gb": footprint_gb,
"selected_weight_files": selected,
# v0.8.0 [E] CONTRACT-3 (additive): the raw HF siblings API so
# gates.c2a_disk can size the SHARED download_set() directly (single
# function, [C2a]/E2/E3 cannot drift). Additive field ONLY — no
# existing field/behaviour changes; absent for a tier-1 curated hit
# (curated footprint comes from the variant size_gb, not the API).
"_hf_api": api or {},
"download_set": download_set(api or {}),
"config_hidden_size": _int(config or {}, "hidden_size"),
"config_num_hidden_layers": _int(config or {}, "num_hidden_layers"),
"config_num_attention_heads": _int(config or {}, "num_attention_heads"),
"config_num_key_value_heads": _int(config or {}, "num_key_value_heads"),
# Additive: does the brought checkpoint carry an MTP draft head? The
# Route-C weight-swap (pull.sh _swap_path) reads this to keep vs drop
# --speculative-config, instead of the old blanket "fine-tune → no MTP"
# drop that silently served head-preserving fine-tunes MTP-off.
"has_mtp_head": detect_mtp_head(config or {}, api or {}),
}
res.diagnostics["resolution"] = "derived"
if eligible:
res.confidence = Confidence.ESTIMATED_LOWER_BOUND
res.spec = _build_generic_dense_spec(slug, config or {}, weight_gb)
else:
res.confidence = Confidence.NOT_ELIGIBLE # P4 wires stratum-5 abort
return res
# ---------------------------------------------------------------------------
# CLI — stage-1 INSPECT for the Bring funnel (c3 subprocess + standalone use)
# python3 scripts/lib/profiles/deriver.py --inventory <org/Model> [--json]
# ---------------------------------------------------------------------------
def _cli(argv: list[str]) -> int:
import argparse
ap = argparse.ArgumentParser(
prog="deriver", description="Bring-funnel stage-1 INSPECT (artifact inventory)"
)
ap.add_argument("repo", help="HF repo slug, e.g. org/Model")
ap.add_argument("--inventory", action="store_true", required=True,
help="emit the artifact inventory (the only CLI mode)")
ap.add_argument("--json", action="store_true", help="JSON output (default: pretty)")
ns = ap.parse_args(argv)
inv = inspect_repo(ns.repo)
if ns.json:
print(json.dumps(inv))
else:
if inv.get("error"):
print(f"error: {inv['error']}")
else:
print(f"repo: {inv['repo']} formats: {', '.join(inv['formats'])}")
if inv.get("safetensors"):
st = inv["safetensors"]
print(f" safetensors: {len(st['weight_files'])} file(s), {st['size_gb']:.1f} GiB")
for v in inv.get("gguf_variants") or []:
print(f" gguf {v['quant']}: {v['size_gb']:.1f} GiB ({v['parts']} file(s))")
for m in inv.get("gguf_mmproj") or []:
print(f" mmproj: {m}")
if inv.get("lineage_base_model"):
print(f" base_model: {inv['lineage_base_model']}")
return 1 if inv.get("error") else 0
if __name__ == "__main__": # pragma: no cover - thin CLI shim
raise SystemExit(_cli(sys.argv[1:]))