add config/param_count.py (exact param accounting, 90-110M window)
Browse files- config/param_count.py +115 -0
config/param_count.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Exact parameter accounting for a Llama-style decoder-only model.
|
| 2 |
+
|
| 3 |
+
The parameter budget (§2: 90-110M *including* embeddings) cannot be met by eyeballing hidden size
|
| 4 |
+
and layer count: at a 50k vocab the embedding alone is 25-50% of the model, and whether the
|
| 5 |
+
embeddings are tied changes the total by the whole embedding matrix. So count it arithmetically,
|
| 6 |
+
then confirm against the real object.
|
| 7 |
+
|
| 8 |
+
Two independent counts are printed deliberately:
|
| 9 |
+
formula -- closed form, so the number is explainable without instantiating the model
|
| 10 |
+
torch -- sum of p.numel() over a constructed LlamaForCausalLM, which is the number a third
|
| 11 |
+
party can reproduce from the published config
|
| 12 |
+
|
| 13 |
+
They must agree. If they do not, the formula is wrong (usually GQA head packing or tying), and the
|
| 14 |
+
torch count is authoritative because that is what `AutoModelForCausalLM` will report.
|
| 15 |
+
|
| 16 |
+
Run: python code/config/param_count.py # candidate table
|
| 17 |
+
python code/config/param_count.py --verify # also build each model (needs transformers)
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
import argparse
|
| 21 |
+
import json
|
| 22 |
+
|
| 23 |
+
# Measured on Kaggle (docs/00-platform-notes.md section 9): this shape is 112.9M, i.e. OVER budget.
|
| 24 |
+
PROBE_SHAPE = dict(hidden_size=768, num_hidden_layers=12, num_attention_heads=12,
|
| 25 |
+
num_key_value_heads=3, intermediate_size=2048, vocab_size=50257,
|
| 26 |
+
tie_word_embeddings=True)
|
| 27 |
+
|
| 28 |
+
CANDIDATES = {
|
| 29 |
+
# Shrinking the probe shape toward the window from several directions, so the choice is between
|
| 30 |
+
# real options rather than a single lucky guess.
|
| 31 |
+
"A_768_h_11l_gqa3_2048": dict(PROBE_SHAPE, num_hidden_layers=11),
|
| 32 |
+
"B_768_h_12l_gqa3_1792": dict(PROBE_SHAPE, intermediate_size=1792),
|
| 33 |
+
"C_768_h_12l_gqa2_1824": dict(PROBE_SHAPE, intermediate_size=1824, num_key_value_heads=2),
|
| 34 |
+
"D_640_h_16l_gqa4_2048": dict(PROBE_SHAPE, hidden_size=640, num_hidden_layers=16,
|
| 35 |
+
num_attention_heads=10, num_key_value_heads=4,
|
| 36 |
+
intermediate_size=2048),
|
| 37 |
+
"E_768_h_12l_gqa3_32k": dict(PROBE_SHAPE, vocab_size=32768),
|
| 38 |
+
"F_896_h_9l_gqa2_2432": dict(PROBE_SHAPE, hidden_size=896, num_hidden_layers=9,
|
| 39 |
+
num_attention_heads=14, num_key_value_heads=2,
|
| 40 |
+
intermediate_size=2432),
|
| 41 |
+
"G_1024_h_8l_gqa2_2816": dict(PROBE_SHAPE, hidden_size=1024, num_hidden_layers=8,
|
| 42 |
+
num_attention_heads=8, num_key_value_heads=2,
|
| 43 |
+
intermediate_size=2816),
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def count_formula(v, h, nl, nh, nkv, ff, tied):
|
| 48 |
+
"""Closed-form Llama param count. All terms explained inline so the arithmetic is checkable."""
|
| 49 |
+
embed = v * h
|
| 50 |
+
d_head = h // nh
|
| 51 |
+
# GQA: k/v are d_head * nkv wide, not h, so the ratio nkv/nh matters.
|
| 52 |
+
attn = h * h + 2 * d_head * nkv * h + h * h # q, k, v, o
|
| 53 |
+
mlp = 3 * h * ff # SwiGLU gate + up + down
|
| 54 |
+
norms = 2 * h * nl # input + post-attention RMSNorm
|
| 55 |
+
per_layer = attn + mlp
|
| 56 |
+
total = embed * (1 if tied else 2) + per_layer * nl + norms + h
|
| 57 |
+
return {
|
| 58 |
+
"embedding_one_copy": embed,
|
| 59 |
+
"per_layer_attn": attn, "per_layer_mlp": mlp, "layers": nl,
|
| 60 |
+
"transformer_blocks": per_layer * nl,
|
| 61 |
+
"norms": norms + h,
|
| 62 |
+
"tied_embeddings": tied,
|
| 63 |
+
"total": total,
|
| 64 |
+
"embedding_pct_of_total": round(100.0 * embed / total, 1),
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def count_torch(cfg):
|
| 69 |
+
import torch
|
| 70 |
+
from transformers import LlamaConfig, LlamaForCausalLM
|
| 71 |
+
|
| 72 |
+
m = LlamaForCausalLM(LlamaConfig(**cfg))
|
| 73 |
+
n = sum(p.numel() for p in m.parameters())
|
| 74 |
+
emb = m.get_input_embeddings().weight.numel()
|
| 75 |
+
tied = m.config.tie_word_embeddings and (
|
| 76 |
+
m.get_input_embeddings().weight.data_ptr() == m.lm_head.weight.data_ptr())
|
| 77 |
+
del m
|
| 78 |
+
torch.cuda.empty_cache() if torch.cuda.is_available() else None
|
| 79 |
+
return {"total": n, "embedding": emb, "actually_tied": bool(tied)}
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def main():
|
| 83 |
+
ap = argparse.ArgumentParser()
|
| 84 |
+
ap.add_argument("--verify", action="store_true",
|
| 85 |
+
help="build each model with transformers and compare (slow, needs the lib)")
|
| 86 |
+
args = ap.parse_args()
|
| 87 |
+
|
| 88 |
+
rows = []
|
| 89 |
+
for name, cfg in CANDIDATES.items():
|
| 90 |
+
f = count_formula(cfg["vocab_size"], cfg["hidden_size"], cfg["num_hidden_layers"],
|
| 91 |
+
cfg["num_attention_heads"], cfg["num_key_value_heads"],
|
| 92 |
+
cfg["intermediate_size"], cfg["tie_word_embeddings"])
|
| 93 |
+
row = {"name": name, "formula_total": f["total"], "emb_pct": f["embedding_pct_of_total"],
|
| 94 |
+
"in_90_110M": 90_000_000 <= f["total"] <= 110_000_000, **{
|
| 95 |
+
k: cfg[k] for k in ("hidden_size", "num_hidden_layers", "num_attention_heads",
|
| 96 |
+
"num_key_value_heads", "intermediate_size", "vocab_size")}}
|
| 97 |
+
if args.verify:
|
| 98 |
+
t = count_torch(cfg)
|
| 99 |
+
row["torch_total"] = t["total"]
|
| 100 |
+
row["matches_formula"] = t["total"] == f["total"]
|
| 101 |
+
row["torch_reports_tied"] = t["actually_tied"]
|
| 102 |
+
rows.append(row)
|
| 103 |
+
|
| 104 |
+
p = count_formula(**{ # the probe shape, for the record
|
| 105 |
+
"v": PROBE_SHAPE["vocab_size"], "h": PROBE_SHAPE["hidden_size"],
|
| 106 |
+
"nl": PROBE_SHAPE["num_hidden_layers"], "nh": PROBE_SHAPE["num_attention_heads"],
|
| 107 |
+
"nkv": PROBE_SHAPE["num_key_value_heads"], "ff": PROBE_SHAPE["intermediate_size"],
|
| 108 |
+
"tied": PROBE_SHAPE["tie_word_embeddings"]})
|
| 109 |
+
print("probe shape (measured 112,934,400 in the image):", json.dumps(p))
|
| 110 |
+
for r in rows:
|
| 111 |
+
print(json.dumps(r))
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
if __name__ == "__main__":
|
| 115 |
+
main()
|