Cion-lab commited on
Commit
e6aee19
·
verified ·
1 Parent(s): ea283ea

add config/param_count.py (exact param accounting, 90-110M window)

Browse files
Files changed (1) hide show
  1. 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()