Spaces:
Sleeping
Sleeping
Download dssd/hub.py from FlorianVal/DSSD-dev: direct link, hf CLI and curl.
- Browser
- Download file 6.04 kB
-
https://huggingface.co/spaces/FlorianVal/DSSD-dev/resolve/main/dssd/hub.py
- Command line
-
hf download hf://spaces/FlorianVal/DSSD-dev/dssd/hub.py
-
curl -L -o hub.py https://huggingface.co/spaces/FlorianVal/DSSD-dev/resolve/main/dssd/hub.py
6.04 kB
| """Hub format of a DSSD head set: `heads.safetensors` + `dssd_config.json`. | |
| heads.safetensors, for every head k (bf16 except the token ids): | |
| h{k}.norm.weight [d], h{k}.proj.weight [B_k + 1, d] (last row = reject class ⊥), | |
| h{k}.reject_bias [1], h{k}.vocab_ids [B_k] (int32). | |
| dssd_config.json: format_version, base_model, base_revision, head_layers, budgets, | |
| reject_class, dtype, default_domain, domains {name: {policy, stop}}, provenance. | |
| A policy uses the bench format (scripts/compose_final.py): type "threshold", thresholds, | |
| cum_gamma, acc_maps {k: [knots, values]}, max_draft, tree. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| from pathlib import Path | |
| import torch | |
| from .heads import ExitHead | |
| FORMAT_VERSION = 1 | |
| HEADS_FILE = "heads.safetensors" | |
| CONFIG_FILE = "dssd_config.json" | |
| BASE_DIMS = ("hidden_size", "vocab_size", "num_hidden_layers") | |
| def save_heads(out_dir: str | Path, heads: list[ExitHead], layers: list[int], config: dict, | |
| model_config=None) -> None: | |
| """Write heads.safetensors and dssd_config.json (format, layers, budgets and reject class | |
| are filled in here; every other key comes from `config`). With the base model's config, its | |
| dimensions are recorded and checked at load time.""" | |
| from safetensors.torch import save_file | |
| out = Path(out_dir) | |
| out.mkdir(parents=True, exist_ok=True) | |
| tensors = {} | |
| for k, h in enumerate(heads): | |
| if h.adapter is not None or h.vocab_ids is None or not h.reject: | |
| raise ValueError("only pruned heads with a reject class and no adapter are supported") | |
| tensors[f"h{k}.norm.weight"] = h.norm.weight.detach().to(torch.bfloat16).contiguous() | |
| tensors[f"h{k}.proj.weight"] = h.proj.weight.detach().to(torch.bfloat16).contiguous() | |
| tensors[f"h{k}.reject_bias"] = h.reject_bias.detach().to(torch.bfloat16).contiguous() | |
| tensors[f"h{k}.vocab_ids"] = h.vocab_ids.to(torch.int32).contiguous() | |
| save_file({n: t.cpu() for n, t in tensors.items()}, str(out / HEADS_FILE)) | |
| conf = {"format_version": FORMAT_VERSION, **config, "head_layers": [int(x) for x in layers], | |
| "budgets": [h.n_vocab for h in heads], "reject_class": True, "dtype": "bfloat16"} | |
| if model_config is not None: | |
| conf["base_dims"] = {k: int(getattr(model_config, k)) for k in BASE_DIMS} | |
| (out / CONFIG_FILE).write_text(json.dumps(conf, indent=1, ensure_ascii=False)) | |
| def _resolve(path_or_repo: str, revision: str | None, token: str | None) -> Path: | |
| if os.path.isdir(path_or_repo): | |
| return Path(path_or_repo) | |
| from huggingface_hub import snapshot_download | |
| return Path(snapshot_download(path_or_repo, revision=revision, token=token, | |
| allow_patterns=[HEADS_FILE, CONFIG_FILE])) | |
| def read_config(path_or_repo: str, revision: str | None = None, token: str | None = None) -> dict: | |
| return json.loads((_resolve(path_or_repo, revision, token) / CONFIG_FILE).read_text()) | |
| def load_heads(path_or_repo: str, model_config, device="cpu", dtype=torch.bfloat16, | |
| revision: str | None = None, token: str | None = None) -> tuple[list[ExitHead], list[int], dict]: | |
| """Heads, their layers and the config from a local directory or a Hub repo. Checks that | |
| they fit the base model (hidden size, vocabulary, number of layers).""" | |
| from safetensors.torch import load_file | |
| root = _resolve(path_or_repo, revision, token) | |
| conf = json.loads((root / CONFIG_FILE).read_text()) | |
| if conf.get("format_version") != FORMAT_VERSION: | |
| raise ValueError(f"unsupported format_version {conf.get('format_version')!r} (expected {FORMAT_VERSION})") | |
| for k, v in conf.get("base_dims", {}).items(): # heads trained on another base model | |
| if int(getattr(model_config, k)) != v: | |
| raise ValueError(f"base model {k} {getattr(model_config, k)} != {v} recorded with the heads") | |
| layers = [int(x) for x in conf["head_layers"]] | |
| n_layers = model_config.num_hidden_layers | |
| if any(not 0 < x < n_layers for x in layers): | |
| raise ValueError(f"head layer out of range for a {n_layers}-layer base model: {layers}") | |
| t = load_file(str(root / HEADS_FILE)) | |
| heads = [] | |
| for k in range(len(layers)): | |
| w, ids = t[f"h{k}.proj.weight"], t[f"h{k}.vocab_ids"].long() | |
| if w.shape[1] != model_config.hidden_size: | |
| raise ValueError(f"head {k}: hidden_size {w.shape[1]} != base model {model_config.hidden_size}") | |
| if int(ids.max()) >= model_config.vocab_size: | |
| raise ValueError(f"head {k}: token id {int(ids.max())} >= base model vocab_size {model_config.vocab_size}") | |
| h = ExitHead(model_config.hidden_size, model_config.vocab_size, model_config.rms_norm_eps, | |
| vocab_ids=ids, reject=True) | |
| h.load_state_dict({"norm.weight": t[f"h{k}.norm.weight"], "proj.weight": w, | |
| "reject_bias": t[f"h{k}.reject_bias"], "vocab_ids": ids}) | |
| heads.append(h.to(device=device, dtype=dtype).eval()) | |
| return heads, layers, conf | |
| def heads_from_pt(path: str, model_config) -> tuple[list[ExitHead], list[int]]: | |
| """Pruned heads from a training checkpoint {'heads': [state_dict], 'meta': {'layers'}} | |
| (scripts/train_pruned.py, merge_pruned_heads.py), dtype preserved.""" | |
| ck = torch.load(path, map_location="cpu", weights_only=False) | |
| layers = [int(x) for x in ck["meta"]["layers"]] | |
| heads = [] | |
| for k in range(len(layers)): | |
| sd = ck["heads"][k] | |
| if any(n.startswith("adapter.") for n in sd) or sd.get("vocab_ids") is None or "reject_bias" not in sd: | |
| raise ValueError(f"head {k}: only pruned heads with a reject class and no adapter are supported") | |
| n_out = sd["proj.weight"].shape[0] - 1 | |
| h = ExitHead(model_config.hidden_size, model_config.vocab_size, model_config.rms_norm_eps, | |
| vocab_ids=sd["vocab_ids"][:n_out], reject=True) | |
| h.load_state_dict(sd) | |
| heads.append(h.to(sd["proj.weight"].dtype).eval()) | |
| return heads, layers | |