Download training/scripts/count_params.py from SinhalaMMLU-SharedTask/team-nexus: direct link, hf CLI and curl.
- Browser
- Download file 8.24 kB
-
https://huggingface.co/SinhalaMMLU-SharedTask/team-nexus/resolve/main/training/scripts/count_params.py
- Command line
-
hf download hf://SinhalaMMLU-SharedTask/team-nexus/training/scripts/count_params.py
-
curl -L -o count_params.py https://huggingface.co/SinhalaMMLU-SharedTask/team-nexus/resolve/main/training/scripts/count_params.py
8.24 kB
| """Exact parameter accounting for candidate models -> MODEL_MANIFEST.md + reports/param_counts.json. | |
| Two independent counts per model: | |
| stored sum of every tensor in the repo's safetensors headers (read via HTTP range requests, | |
| no weight download), split into text / vision / audio / MTP heads by tensor name. | |
| Tied input/output embeddings are counted once. | |
| instantiated parameters of the module transformers builds (meta device, no weights) with | |
| AutoModelForCausalLM, falling back to AutoModelForImageTextToText. | |
| LoRA/adapter repos are counted as base model + adapter tensors (shared-task rule). | |
| Usage: python scripts/count_params.py [--models id1 id2 ...] | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import re | |
| import sys | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[1])) | |
| from eval.common import ROOT, load_env # noqa: E402 | |
| LIMIT = 8_000_000_000 | |
| CUTOFF = "2026-10-01" | |
| MANIFEST = ROOT / "MODEL_MANIFEST.md" | |
| BEGIN, END = "<!-- AUTO:PARAMS:BEGIN -->", "<!-- AUTO:PARAMS:END -->" | |
| PATTERNS = [ | |
| ("vision", re.compile(r"vision|visual|image|siglip|patch_embed|multi_modal_projector|mm_projector|embed_vision")), | |
| ("audio", re.compile(r"audio|embed_audio|speech")), | |
| ("mtp", re.compile(r"(^|\.)mtp[._]")), | |
| ] | |
| def tensor_kind(name: str) -> str: | |
| for kind, pat in PATTERNS: | |
| if pat.search(name): | |
| return kind | |
| return "text" | |
| def _tied(cfg: dict) -> bool: | |
| for c in (cfg, cfg.get("text_config") or {}): | |
| if "tie_word_embeddings" in c: | |
| return bool(c["tie_word_embeddings"]) | |
| return False | |
| def stored_counts(api, repo: str, sha: str) -> dict: | |
| from huggingface_hub import hf_hub_download | |
| meta = api.get_safetensors_metadata(repo, revision=sha) | |
| counts = {"text": 0, "vision": 0, "audio": 0, "mtp": 0} | |
| names = {} | |
| for fm in meta.files_metadata.values(): | |
| for name, t in fm.tensors.items(): | |
| n = 1 | |
| for d in t.shape: | |
| n *= d | |
| names[name] = n | |
| counts[tensor_kind(name)] += n | |
| cfg = json.load(open(hf_hub_download(repo, "config.json", revision=sha))) | |
| tied = _tied(cfg) | |
| lm_head = [k for k in names if k.endswith("lm_head.weight")] | |
| deduped = 0 | |
| if tied and lm_head: # stored twice but shared at runtime | |
| deduped = sum(names[k] for k in lm_head) | |
| counts["text"] -= deduped | |
| emb = sum(v for k, v in names.items() if re.search(r"embed_tokens|wte|lm_head|embeddings\.word", k) | |
| and tensor_kind(k) == "text") - deduped | |
| return {**counts, "embeddings": emb, "tied_embeddings": tied, "total": sum(counts.values()), | |
| "model_type": cfg.get("model_type"), "config": cfg} | |
| def instantiated_count(repo: str, sha: str) -> tuple[int | None, str]: | |
| import torch | |
| from accelerate import init_empty_weights | |
| from transformers import AutoConfig, AutoModelForCausalLM | |
| try: | |
| cfg = AutoConfig.from_pretrained(repo, revision=sha) | |
| except Exception as e: # noqa: BLE001 | |
| return None, f"config: {type(e).__name__}" | |
| for cls_name in ("AutoModelForCausalLM", "AutoModelForImageTextToText"): | |
| try: | |
| import transformers | |
| cls = getattr(transformers, cls_name) | |
| with init_empty_weights(): | |
| m = cls.from_config(cfg) | |
| m.tie_weights() # meta init leaves tied embeddings as separate tensors | |
| return sum(p.numel() for p in m.parameters()), type(m).__name__ | |
| except Exception as e: # noqa: BLE001 | |
| err = f"{cls_name}: {type(e).__name__}: {str(e)[:80]}" | |
| return None, err | |
| def count_repo(api, repo: str) -> dict: | |
| info = api.model_info(repo) | |
| sha = info.sha | |
| row = {"id": repo, "sha": sha, "created": str(info.created_at)[:10], | |
| "last_modified": str(info.last_modified)[:10], | |
| "license": (info.card_data or {}).get("license") if info.card_data else None, | |
| "gated": info.gated} | |
| files = {s.rfilename for s in info.siblings} | |
| if "adapter_config.json" in files: | |
| from huggingface_hub import hf_hub_download | |
| acfg = json.load(open(hf_hub_download(repo, "adapter_config.json", revision=sha))) | |
| base = acfg["base_model_name_or_path"] | |
| fm = api.parse_safetensors_file_metadata(repo, "adapter_model.safetensors", revision=sha) | |
| adapter = sum(_numel(t.shape) for t in fm.tensors.values()) | |
| b = count_repo(api, base) | |
| row.update(kind="adapter", base=base, adapter_params=adapter, | |
| modules_to_save=acfg.get("modules_to_save"), | |
| text=b.get("text"), vision=b.get("vision"), audio=b.get("audio"), mtp=b.get("mtp"), | |
| embeddings=b.get("embeddings"), tied_embeddings=b.get("tied_embeddings"), | |
| total=(b.get("total") or 0) + adapter, text_loaded=(b.get("text_loaded") or 0) + adapter, | |
| instantiated=None, inst_class=f"base {b.get('total')} + adapter") | |
| return row | |
| s = stored_counts(api, repo, sha) | |
| s.pop("config") | |
| inst, cls = instantiated_count(repo, sha) | |
| row.update(kind="model", **s, text_loaded=s["text"], instantiated=inst, inst_class=cls) | |
| return row | |
| def _numel(shape) -> int: | |
| n = 1 | |
| for d in shape: | |
| n *= d | |
| return n | |
| def verdict(r: dict) -> str: | |
| if r.get("error"): | |
| return "ERROR" | |
| flags = [] | |
| if r["created"] >= CUTOFF: | |
| flags.append("RELEASED TOO LATE") | |
| if r["total"] <= LIMIT: | |
| flags.append("OK") | |
| elif r["text_loaded"] <= LIMIT: | |
| flags.append("OK only text-only (strip vision/audio/MTP)") | |
| else: | |
| flags.append("OVER 8B") | |
| return "; ".join(flags) | |
| def b(n) -> str: | |
| return "-" if n in (None, 0) else f"{n / 1e9:.3f}B" | |
| def write_manifest(rows: list[dict]) -> None: | |
| lines = [BEGIN, "", | |
| "| Model | Verdict | Total (all stored) | Text model | Vision | Audio | MTP | Embeddings | Tied | " | |
| "Instantiated (class) | Repo created | License | Commit |", | |
| "|---|---|---:|---:|---:|---:|---:|---:|---|---|---|---|---|"] | |
| for r in rows: | |
| if r.get("error"): | |
| lines.append(f"| `{r['id']}` | ERROR: {r['error']} |" + " |" * 11) | |
| continue | |
| inst = f"{b(r['instantiated'])} ({r['inst_class']})" if r.get("instantiated") else r.get("inst_class", "-") | |
| name = f"`{r['id']}`" + (f"<br>adapter on `{r['base']}` (+{b(r['adapter_params'])})" if r["kind"] == "adapter" else "") | |
| lines.append(f"| {name} | {verdict(r)} | {b(r['total'])} | {b(r['text_loaded'])} | {b(r['vision'])} | " | |
| f"{b(r['audio'])} | {b(r['mtp'])} | {b(r['embeddings'])} | {'yes' if r['tied_embeddings'] else 'no'} | " | |
| f"{inst} | {r['created']} | {r['license'] or '?'} | `{r['sha'][:10]}` |") | |
| lines += ["", END] | |
| text = MANIFEST.read_text() | |
| start, stop = text.index(BEGIN), text.index(END) + len(END) | |
| MANIFEST.write_text(text[:start] + "\n".join(lines) + text[stop:]) | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--models", nargs="*") | |
| args = ap.parse_args() | |
| load_env() | |
| import yaml | |
| from huggingface_hub import HfApi | |
| repos = args.models or [c["id"] for c in yaml.safe_load(open(ROOT / "configs" / "models.yaml"))["candidates"]] | |
| api = HfApi() | |
| rows = [] | |
| for repo in repos: | |
| try: | |
| r = count_repo(api, repo) | |
| except Exception as e: # noqa: BLE001 | |
| r = {"id": repo, "error": f"{type(e).__name__}: {str(e)[:100]}"} | |
| rows.append(r) | |
| print(f"{repo:42s} {verdict(r):45s} total={b(r.get('total'))} text={b(r.get('text_loaded'))} " | |
| f"inst={b(r.get('instantiated'))}") | |
| out = ROOT / "reports" / "param_counts.json" | |
| out.parent.mkdir(exist_ok=True) | |
| if args.models and out.exists(): # partial recount: update those rows, keep the rest | |
| fresh = {r["id"]: r for r in rows} | |
| old = json.loads(out.read_text()) | |
| rows = [fresh.pop(r["id"], r) for r in old] + list(fresh.values()) | |
| out.write_text(json.dumps(rows, indent=2, default=str)) | |
| write_manifest(rows) | |
| if __name__ == "__main__": | |
| main() | |