anandselvadurai-techus commited on
Commit
db05743
·
verified ·
1 Parent(s): 1c35d59

Upload params.py

Browse files
Files changed (1) hide show
  1. params.py +165 -0
params.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Exact parameter accounting.
2
+
3
+ The headline number is trainable weights PLUS frozen buffer values.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from collections import OrderedDict
8
+
9
+ import torch
10
+ import torch.nn as nn
11
+
12
+ # Buffers that hold only derived constants would be excluded and listed here.
13
+ # The design deliberately has none; RoPE tables are plain Python state.
14
+ DERIVED_BUFFERS: tuple[str, ...] = ()
15
+
16
+
17
+ def _tied_targets(model: nn.Module) -> set[str]:
18
+ """Parameter names that are copies of another parameter, so count once.
19
+
20
+ Object identity is not enough: `from_pretrained` constructs the model before
21
+ tying the weights, so during construction `lm_head.weight` is still a
22
+ distinct tensor. Counting it would double the embedding and trip the budget
23
+ check on an otherwise valid model.
24
+ """
25
+ if not getattr(model.config, "tie_word_embeddings", False):
26
+ return set()
27
+ keys = getattr(model, "_tied_weights_keys", None) or []
28
+ # transformers 4.x: a list of target names. 5.x: {target: source}.
29
+ return set(keys.keys()) if isinstance(keys, dict) else set(keys)
30
+
31
+
32
+ def account(model: nn.Module) -> dict:
33
+ """Walk named_parameters() and named_buffers(). Returns a count breakdown."""
34
+ trainable, frozen, excluded = 0, 0, 0
35
+ by_param: "OrderedDict[str, int]" = OrderedDict()
36
+ by_buffer: "OrderedDict[str, int]" = OrderedDict()
37
+ excluded_names: list[str] = []
38
+
39
+ tied = _tied_targets(model)
40
+ seen: set[int] = set()
41
+ for name, p in model.named_parameters():
42
+ if id(p) in seen: # tied weights counted once
43
+ continue
44
+ if name in tied or any(name.endswith("." + t) for t in tied):
45
+ continue
46
+ seen.add(id(p))
47
+ n = p.numel()
48
+ by_param[name] = n
49
+ trainable += n
50
+
51
+ for name, b in model.named_buffers():
52
+ if b is None or id(b) in seen:
53
+ continue
54
+ seen.add(id(b))
55
+ n = b.numel()
56
+ if any(name.endswith(suffix) for suffix in DERIVED_BUFFERS):
57
+ excluded += n
58
+ excluded_names.append(name)
59
+ continue
60
+ by_buffer[name] = n
61
+ frozen += n
62
+
63
+ return {
64
+ "trainable": trainable,
65
+ "frozen": frozen,
66
+ "total": trainable + frozen,
67
+ "excluded": excluded,
68
+ "excluded_names": excluded_names,
69
+ "by_param": by_param,
70
+ "by_buffer": by_buffer,
71
+ }
72
+
73
+
74
+ def _group(model: nn.Module) -> "OrderedDict[str, int]":
75
+ """Spec-shaped component table, computed from the live model."""
76
+ cfg = model.config
77
+ base = model.model if hasattr(model, "model") else model
78
+ rows: "OrderedDict[str, int]" = OrderedDict()
79
+
80
+ def count(mods) -> int:
81
+ seen, total = set(), 0
82
+ for m in mods:
83
+ if m is None:
84
+ continue
85
+ for p in m.parameters(recurse=True):
86
+ if id(p) not in seen:
87
+ seen.add(id(p))
88
+ total += p.numel()
89
+ return total
90
+
91
+ layer0 = base.layers[0]
92
+ n_layer = len(base.layers)
93
+
94
+ attn = count([layer0.attn.q_proj, layer0.attn.k_proj, layer0.attn.v_proj, layer0.attn.o_proj])
95
+ qk = count([layer0.attn.q_norm, layer0.attn.k_norm])
96
+ gate = count([layer0.attn.gate_proj])
97
+ mlp = count([layer0.mlp])
98
+ norms = count([layer0.attn_in_norm, layer0.attn_out_norm, layer0.mlp_in_norm, layer0.mlp_out_norm])
99
+ canon = count([layer0.canon_a, layer0.canon_c])
100
+ per_layer = attn + qk + gate + mlp + norms + canon
101
+
102
+ rows["Attention q, k, v, o"] = attn
103
+ rows["QK-norm"] = qk
104
+ rows[f"{cfg.attn_gate.capitalize()} gate (W + b)"] = gate
105
+ rows[f"SwiGLU MLP (3 x {cfg.d_model} x {cfg.ffn_hidden})"] = mlp
106
+ rows[f"Peri-LN, 4 norms x {cfg.d_model}"] = norms
107
+ rows["Canon A + C"] = canon
108
+ rows["--- Per layer"] = per_layer
109
+ rows[f"{n_layer} layers"] = per_layer * n_layer
110
+
111
+ emb = base.embed_tokens.weight.numel()
112
+ rows[f"Tied embedding ({cfg.vocab_size} x {cfg.d_model})"] = emb
113
+ rows["Final norm"] = base.final_norm.weight.numel()
114
+
115
+ info = account(model)
116
+ rows["--- Trainable"] = info["trainable"]
117
+ if info["frozen"]:
118
+ rows["Frozen buffers"] = info["frozen"]
119
+ rows["--- Total"] = info["total"]
120
+ return rows
121
+
122
+
123
+ def table(model: nn.Module) -> str:
124
+ rows = _group(model)
125
+ info = account(model)
126
+ width = max(len(k) for k in rows) + 2
127
+ lines = [f"{'Component'.ljust(width)}{'Count':>12}", "-" * (width + 12)]
128
+ for key, val in rows.items():
129
+ if key.startswith("--- "):
130
+ lines.append("-" * (width + 12))
131
+ key = key[4:]
132
+ lines.append(f"{key.ljust(width)}{val:>12,}")
133
+ lines.append("-" * (width + 12))
134
+
135
+ budget = getattr(model.config, "max_total_params", None)
136
+ warn = getattr(model.config, "warn_total_params", None)
137
+ if budget:
138
+ ok = "OK" if info["total"] <= budget else "OVER BUDGET"
139
+ lines.append(f"{'Budget (max_total_params)'.ljust(width)}{budget:>12,} [{ok}]")
140
+ if warn:
141
+ flag = "ok" if info["total"] <= warn else "WARN"
142
+ lines.append(f"{'Warn threshold'.ljust(width)}{warn:>12,} [{flag}]")
143
+ if info["excluded"]:
144
+ lines.append(
145
+ f"Excluded derived buffers ({info['excluded']:,}): "
146
+ + ", ".join(info["excluded_names"])
147
+ )
148
+ return "\n".join(lines)
149
+
150
+
151
+ def count_from_config(cfg_or_model) -> dict:
152
+ """Build the model if given a config dict, then account for it.
153
+
154
+ Construction itself raises when the budget is exceeded, so this doubles as
155
+ the budget check.
156
+ """
157
+ if isinstance(cfg_or_model, nn.Module):
158
+ return account(cfg_or_model)
159
+ from ethoric.model.configuration_ethoric import EthoricConfig
160
+ from ethoric.model.modeling_ethoric import EthoricForCausalLM
161
+
162
+ model_cfg = EthoricConfig.from_pipeline(cfg_or_model)
163
+ with torch.device("meta"):
164
+ model = EthoricForCausalLM(model_cfg)
165
+ return account(model)