anpaurehf commited on
Commit
e700710
·
verified ·
1 Parent(s): 23e91c8

Upload generate_and_eval_v6.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. generate_and_eval_v6.py +291 -0
generate_and_eval_v6.py ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import json
4
+ import random
5
+ from pathlib import Path
6
+ from typing import Iterable
7
+
8
+ import numpy as np
9
+ import torch
10
+
11
+ from collect_experts import (
12
+ _load_llm_with_fallback,
13
+ _load_tokenizer_with_fallback,
14
+ collect_router_scores,
15
+ )
16
+ from v6_model import EncoderOnlyModel
17
+
18
+
19
+ def _set_seed(seed: int) -> None:
20
+ random.seed(seed)
21
+ np.random.seed(seed)
22
+ torch.manual_seed(seed)
23
+ if torch.cuda.is_available():
24
+ torch.cuda.manual_seed_all(seed)
25
+
26
+
27
+ def _default_device() -> str:
28
+ return "cuda" if torch.cuda.is_available() else "cpu"
29
+
30
+
31
+ def _load_ckpt(path: str) -> dict:
32
+ return torch.load(path, map_location="cpu")
33
+
34
+
35
+ def _load_state_dict(path: str) -> dict:
36
+ ckpt = _load_ckpt(path)
37
+ state = ckpt.get("model", ckpt)
38
+ if any(key.startswith("_orig_mod.") for key in state.keys()):
39
+ state = {key.replace("_orig_mod.", ""): value for key, value in state.items()}
40
+ return state
41
+
42
+
43
+ def _load_ckpt_config(path: str) -> dict:
44
+ ckpt = _load_ckpt(path)
45
+ cfg = ckpt.get("config", None)
46
+ return cfg if isinstance(cfg, dict) else {}
47
+
48
+
49
+ def non_overlapping_chunks(
50
+ token_ids: list[int],
51
+ expert_topk_idx: torch.Tensor,
52
+ seq_len: int,
53
+ pad_id: int,
54
+ ) -> Iterable[tuple[list[int], torch.Tensor, list[bool]]]:
55
+ n_tokens = len(token_ids)
56
+ if n_tokens == 0:
57
+ return
58
+
59
+ start = 0
60
+ while start < n_tokens:
61
+ end = min(start + seq_len, n_tokens)
62
+ chunk_len = end - start
63
+ chunk_tokens = token_ids[start:end]
64
+ chunk_experts = expert_topk_idx[start:end]
65
+
66
+ if chunk_len < seq_len:
67
+ chunk_tokens = chunk_tokens + [pad_id] * (seq_len - chunk_len)
68
+ if chunk_len > 0:
69
+ pad_row = chunk_experts[-1].unsqueeze(0)
70
+ else:
71
+ pad_row = torch.zeros_like(expert_topk_idx[:1])
72
+ pad_block = pad_row.expand(seq_len - chunk_len, -1, -1)
73
+ chunk_experts = torch.cat([chunk_experts, pad_block], dim=0)
74
+
75
+ attention_mask = [True] * chunk_len + [False] * (seq_len - chunk_len)
76
+ yield chunk_tokens, chunk_experts, attention_mask
77
+ start += seq_len
78
+
79
+
80
+ def main() -> None:
81
+ parser = argparse.ArgumentParser(
82
+ description="Decode text from GPT-OSS expert selections with the V6 inverter."
83
+ )
84
+ parser.add_argument("--checkpoint", default="inverter_v6.pt")
85
+ parser.add_argument("--text-file", default="text.txt")
86
+ parser.add_argument("--text", default=None)
87
+
88
+ parser.add_argument("--model", default="openai/gpt-oss-20b")
89
+ parser.add_argument("--model-revision", default=None)
90
+ parser.add_argument(
91
+ "--attn-impl",
92
+ choices=["auto", "flash_attention_2", "sdpa", "eager"],
93
+ default="auto",
94
+ )
95
+ parser.add_argument("--seed", type=int, default=0)
96
+ parser.add_argument("--limit-tokens", type=int, default=None)
97
+
98
+ parser.add_argument("--router-topk", type=int, default=4)
99
+ parser.add_argument("--router-chunk-size", type=int, default=2048)
100
+ parser.add_argument("--router-min-chunk-size", type=int, default=128)
101
+
102
+ parser.add_argument("--seq-len", type=int, default=256)
103
+ parser.add_argument("--batch-size", type=int, default=8)
104
+ parser.add_argument("--eval-topk", default="1,5,10")
105
+
106
+ parser.add_argument("--use-ckpt-config", action="store_true", default=True)
107
+ parser.add_argument("--no-use-ckpt-config", action="store_false", dest="use_ckpt_config")
108
+ parser.add_argument("--layers", type=int, default=24)
109
+ parser.add_argument("--d-model", type=int, default=768)
110
+ parser.add_argument("--n-head", type=int, default=12)
111
+ parser.add_argument("--d-ff", type=int, default=2048)
112
+ parser.add_argument("--n-layer", type=int, default=6)
113
+ parser.add_argument("--layer-hidden", type=int, default=64)
114
+ parser.add_argument("--layer-proj", type=int, default=64)
115
+ parser.add_argument("--dropout", type=float, default=0.1)
116
+ parser.add_argument("--logit-softcap", type=float, default=0.0)
117
+ parser.add_argument("--layer-gating", action="store_true", default=False)
118
+ parser.add_argument("--position-type", choices=["auto", "learned", "rope"], default="auto")
119
+ parser.add_argument("--rope-theta", type=float, default=10000.0)
120
+ parser.add_argument("--qk-norm", action="store_true", default=True)
121
+ parser.add_argument("--no-qk-norm", action="store_false", dest="qk_norm")
122
+ parser.add_argument("--qk-norm-eps", type=float, default=1e-5)
123
+
124
+ parser.add_argument("--out", default="text_eval_v6.json")
125
+ parser.add_argument("--decoded-out", default="decoded_top1_v6.txt")
126
+ args = parser.parse_args()
127
+
128
+ device = _default_device()
129
+ if device == "cuda":
130
+ try:
131
+ torch.backends.cuda.matmul.fp32_precision = "tf32"
132
+ torch.backends.cudnn.conv.fp32_precision = "tf32"
133
+ except AttributeError:
134
+ torch.backends.cuda.matmul.allow_tf32 = True
135
+ torch.backends.cudnn.allow_tf32 = True
136
+ torch.set_float32_matmul_precision("high")
137
+
138
+ _set_seed(args.seed)
139
+
140
+ ckpt_cfg = _load_ckpt_config(args.checkpoint)
141
+ state_dict = _load_state_dict(args.checkpoint)
142
+ ckpt_has_gate = bool(ckpt_cfg.get("layer_gating", False)) or ("encoder_in.layer_gate" in state_dict)
143
+ if ckpt_has_gate and not args.layer_gating:
144
+ args.layer_gating = True
145
+
146
+ if args.use_ckpt_config and ckpt_cfg:
147
+ mapping = {
148
+ "seq_len": "seq_len",
149
+ "layers": "layers",
150
+ "d_model": "d_model",
151
+ "n_head": "n_head",
152
+ "d_ff": "d_ff",
153
+ "n_layer": "n_layer",
154
+ "layer_hidden": "layer_hidden",
155
+ "layer_proj": "layer_proj",
156
+ "dropout": "dropout",
157
+ "logit_softcap": "logit_softcap",
158
+ "rope_theta": "rope_theta",
159
+ "qk_norm_eps": "qk_norm_eps",
160
+ }
161
+ for ckpt_key, arg_key in mapping.items():
162
+ if ckpt_key in ckpt_cfg:
163
+ setattr(args, arg_key, ckpt_cfg[ckpt_key])
164
+ if "position_type" in ckpt_cfg and args.position_type == "auto":
165
+ args.position_type = ckpt_cfg["position_type"]
166
+ if "qk_norm" in ckpt_cfg:
167
+ args.qk_norm = bool(ckpt_cfg["qk_norm"])
168
+
169
+ if args.position_type == "auto":
170
+ args.position_type = "learned" if "pos_emb.weight" in state_dict else "rope"
171
+
172
+ text = args.text
173
+ if text is None:
174
+ text = Path(args.text_file).read_text(encoding="utf-8")
175
+
176
+ tokenizer = _load_tokenizer_with_fallback(args.model, args.model_revision)
177
+ if tokenizer.pad_token_id is None:
178
+ tokenizer.pad_token_id = tokenizer.eos_token_id
179
+
180
+ attn_impl = args.attn_impl
181
+ if attn_impl == "auto":
182
+ attn_impl = "flash_attention_2" if device != "cpu" else "eager"
183
+ llm = _load_llm_with_fallback(args.model, args.model_revision, device, attn_impl)
184
+
185
+ inverter = EncoderOnlyModel(
186
+ vocab_size=len(tokenizer),
187
+ num_experts=32,
188
+ num_layers=int(args.layers),
189
+ topk=int(args.router_topk),
190
+ d_model=int(args.d_model),
191
+ n_head=int(args.n_head),
192
+ d_ff=int(args.d_ff),
193
+ n_layer=int(args.n_layer),
194
+ dropout=float(args.dropout),
195
+ max_len=int(args.seq_len),
196
+ layer_gating=bool(args.layer_gating),
197
+ logit_softcap=float(args.logit_softcap),
198
+ layer_hidden=int(args.layer_hidden),
199
+ layer_proj=int(args.layer_proj),
200
+ position_type=str(args.position_type),
201
+ rope_theta=float(args.rope_theta),
202
+ qk_norm=bool(args.qk_norm),
203
+ qk_norm_eps=float(args.qk_norm_eps),
204
+ ).to(device)
205
+ inverter.load_state_dict(state_dict, strict=True)
206
+ inverter.eval()
207
+
208
+ input_ids = tokenizer(text, return_tensors="pt", add_special_tokens=False)["input_ids"][0].tolist()
209
+ if args.limit_tokens is not None:
210
+ input_ids = input_ids[: args.limit_tokens]
211
+ if not input_ids:
212
+ raise ValueError("No tokens found in the provided text.")
213
+
214
+ input_ids_cpu = torch.tensor([input_ids], dtype=torch.long, device="cpu")
215
+ topk_scores = collect_router_scores(
216
+ model=llm,
217
+ input_ids_cpu=input_ids_cpu,
218
+ chunk_size=max(1, int(args.router_chunk_size)),
219
+ min_chunk_size=max(1, int(args.router_min_chunk_size)),
220
+ topk=int(args.router_topk),
221
+ save_dtype=torch.float16,
222
+ pin_memory=(device == "cuda"),
223
+ )
224
+ topk_idx_cpu = topk_scores["topk_idx"][:, : int(args.layers), :]
225
+
226
+ eval_topk = sorted({int(item) for item in args.eval_topk.split(",") if item.strip() and int(item) > 0})
227
+ correct = {k: 0 for k in eval_topk}
228
+ total = 0
229
+ predicted_ids: list[int] = []
230
+
231
+ def run_batch(batch_tokens, batch_experts, batch_attn) -> None:
232
+ nonlocal total
233
+ target_ids = torch.tensor(batch_tokens, dtype=torch.long, device=device)
234
+ expert_idx = torch.stack(batch_experts, dim=0).to(device=device, dtype=torch.long)
235
+ attention_mask = torch.tensor(batch_attn, dtype=torch.bool, device=device)
236
+
237
+ with torch.autocast(device_type=device, dtype=torch.bfloat16, enabled=(device == "cuda")):
238
+ logits = inverter(expert_idx, attention_mask)
239
+
240
+ top1 = torch.argmax(logits, dim=-1)
241
+ for row_idx in range(top1.shape[0]):
242
+ valid_len = int(attention_mask[row_idx].sum().item())
243
+ predicted_ids.extend(top1[row_idx, :valid_len].tolist())
244
+
245
+ for k in eval_topk:
246
+ topk_pred = torch.topk(logits, k=k, dim=-1).indices
247
+ match = (topk_pred == target_ids.unsqueeze(-1)).any(dim=-1)
248
+ match = match & attention_mask
249
+ correct[k] += int(match.sum().item())
250
+
251
+ total += int(attention_mask.sum().item())
252
+
253
+ batch_tokens = []
254
+ batch_experts = []
255
+ batch_attn = []
256
+ for chunk_tokens, chunk_experts, attn_mask in non_overlapping_chunks(
257
+ token_ids=input_ids,
258
+ expert_topk_idx=topk_idx_cpu,
259
+ seq_len=int(args.seq_len),
260
+ pad_id=int(tokenizer.pad_token_id),
261
+ ):
262
+ batch_tokens.append(chunk_tokens)
263
+ batch_experts.append(chunk_experts)
264
+ batch_attn.append(attn_mask)
265
+ if len(batch_tokens) >= int(args.batch_size):
266
+ run_batch(batch_tokens, batch_experts, batch_attn)
267
+ batch_tokens, batch_experts, batch_attn = [], [], []
268
+
269
+ if batch_tokens:
270
+ run_batch(batch_tokens, batch_experts, batch_attn)
271
+
272
+ decoded_text = tokenizer.decode(
273
+ predicted_ids,
274
+ skip_special_tokens=False,
275
+ clean_up_tokenization_spaces=False,
276
+ )
277
+ Path(args.decoded_out).write_text(decoded_text, encoding="utf-8")
278
+
279
+ result = {
280
+ "text_file": args.text_file if args.text is None else None,
281
+ "tokens": total,
282
+ "accuracy": {str(k): correct[k] / total for k in eval_topk},
283
+ "checkpoint": args.checkpoint,
284
+ "decoded_out": args.decoded_out,
285
+ }
286
+ Path(args.out).write_text(json.dumps(result, indent=2), encoding="utf-8")
287
+ print(json.dumps(result, indent=2))
288
+
289
+
290
+ if __name__ == "__main__":
291
+ main()