MLVXN commited on
Commit
18f41dc
Β·
verified Β·
1 Parent(s): 9912597

feat: add mmlu_bench.py (5-shot MMLU, harness or lightweight)

Browse files
Files changed (1) hide show
  1. mmlu_bench.py +235 -0
mmlu_bench.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ MicroLLM2 β€” MMLU Benchmark
4
+ Evaluates MLVXN/MicroLLM2 (or local checkpoint) on MMLU (5-shot by default)
5
+ Uses lm-evaluation-harness if available, else lightweight HF implementation.
6
+
7
+ Usage:
8
+ python mmlu_bench.py # 5-shot MMLU on MLVXN/MicroLLM2
9
+ python mmlu_bench.py --model local # use /home/zeus/microllm2/microllm2-checkpoints/final_merged
10
+ python mmlu_bench.py --shots 0 # zero-shot
11
+ python mmlu_bench.py --subset abstract_algebra,philosophy # only those subjects
12
+ python mmlu_bench.py --limit 20 # 20 samples per subject for quick smoke test
13
+
14
+ Outputs: prints per-subject + average accuracy, saves mmlu_results.json
15
+ """
16
+ import os, sys, json, argparse, re
17
+ from pathlib import Path
18
+
19
+ os.environ["HF_HUB_DISABLE_XET"]="1"
20
+ os.environ["TOKENIZERS_PARALLELISM"]="false"
21
+
22
+ parser = argparse.ArgumentParser(description="MicroLLM2 MMLU benchmark")
23
+ parser.add_argument("--model", default="auto", help="HF id or 'local' or path; default auto -> local if exists else MLVXN/MicroLLM2")
24
+ parser.add_argument("--shots", type=int, default=5, help="few-shot examples (0-5)")
25
+ parser.add_argument("--limit", type=int, default=None, help="max samples per subject (None=all)")
26
+ parser.add_argument("--subset", type=str, default=None, help="comma-separated MMLU subjects to run")
27
+ parser.add_argument("--batch", type=int, default=8)
28
+ parser.add_argument("--output", default="mmlu_results.json")
29
+ args = parser.parse_args()
30
+
31
+ LOCAL = Path("/home/zeus/microllm2/microllm2-checkpoints/final_merged")
32
+ HF_ID = "MLVXN/MicroLLM2"
33
+ if args.model == "auto":
34
+ MODEL_ID = str(LOCAL) if LOCAL.exists() else HF_ID
35
+ elif args.model == "local":
36
+ MODEL_ID = str(LOCAL)
37
+ else:
38
+ MODEL_ID = args.model
39
+
40
+ print(f"[*] MicroLLM2 MMLU β€” model: {MODEL_ID} shots={args.shots} limit={args.limit}")
41
+ print(f"[*] GPT2-XL 1.5B 1024ctx vocab=50259 (ChatML) β€” MMLU via direct eval (no harness needed)")
42
+
43
+ # Try harness first β€” if installed use it (more accurate), else fallback
44
+ USE_HARNESS = False
45
+ try:
46
+ import lm_eval # noqa
47
+ USE_HARNESS = True
48
+ except ImportError:
49
+ USE_HARNESS = False
50
+
51
+ if USE_HARNESS:
52
+ print("[*] Detected lm-evaluation-harness β€” using official MMLU task")
53
+ # harness expects HF model type; gpt2 works
54
+ import lm_eval
55
+ from lm_eval.models.huggingface import HFLM
56
+ from lm_eval.tasks.mmlu import MMLUTask # if available
57
+ print("[*] Running: lm_eval --model hf --model_args pretrained={} --tasks mmlu --num_fewshot {} --batch_size {} {}".format(
58
+ MODEL_ID, args.shots, args.batch, f"--limit {args.limit}" if args.limit else ""))
59
+ # delegate to CLI so output is standard
60
+ import subprocess
61
+ cmd = [
62
+ sys.executable, "-m", "lm_eval",
63
+ "--model", "hf",
64
+ "--model_args", f"pretrained={MODEL_ID},dtype=bfloat16,trust_remote_code=False",
65
+ "--tasks", "mmlu",
66
+ "--num_fewshot", str(args.shots),
67
+ "--batch_size", str(args.batch),
68
+ "--output_path", args.output,
69
+ ]
70
+ if args.limit:
71
+ cmd += ["--limit", str(args.limit)]
72
+ print(" ".join(cmd))
73
+ subprocess.run(cmd, check=False)
74
+ if Path(args.output).exists():
75
+ print(f"[+] Saved to {args.output}")
76
+ # also print summary if harness wrote it
77
+ try:
78
+ data = json.loads(open(args.output).read())
79
+ # harness output is nested; try to find results
80
+ print(json.dumps(data.get("results", data), indent=2)[:4000])
81
+ except: pass
82
+ sys.exit(0)
83
+
84
+ # --- Lightweight fallback: direct HF evaluation (no harness) ---
85
+ print("[*] lm-eval not installed β€” using lightweight direct MMLU eval (same logic, no harness)")
86
+ print("[*] Install harness for official numbers: pip install lm-eval==0.4.4")
87
+
88
+ import torch
89
+ from transformers import AutoTokenizer, AutoModelForCausalLM
90
+ from datasets import load_dataset
91
+ from tqdm import tqdm
92
+
93
+ # MMLU subjects (57) β€” full list from hendrycks/mmlu or cais/mmlu
94
+ MMLU_SUBJECTS = [
95
+ "abstract_algebra","anatomy","astronomy","business_ethics","clinical_knowledge","college_biology",
96
+ "college_chemistry","college_computer_science","college_mathematics","college_medicine","college_physics",
97
+ "computer_security","conceptual_physics","econometrics","electrical_engineering","elementary_mathematics",
98
+ "formal_logic","global_facts","high_school_biology","high_school_chemistry","high_school_computer_science",
99
+ "high_school_european_history","high_school_geography","high_school_government_and_politics",
100
+ "high_school_macroeconomics","high_school_mathematics","high_school_microeconomics","high_school_physics",
101
+ "high_school_psychology","high_school_statistics","high_school_us_history","high_school_world_history",
102
+ "human_aging","human_sexuality","international_law","jurisprudence","logical_fallacies","machine_learning",
103
+ "management","marketing","medical_genetics","miscellaneous","moral_disputes","moral_scenarios","nutrition",
104
+ "philosophy","prehistory","professional_accounting","professional_law","professional_medicine","professional_psychology",
105
+ "public_relations","security_studies","sociology","us_foreign_policy","virology","world_religions"
106
+ ]
107
+ if args.subset:
108
+ wanted = [s.strip() for s in args.subset.split(",") if s.strip()]
109
+ MMLU_SUBJECTS = [s for s in MMLU_SUBJECTS if s in wanted]
110
+ print(f"[*] Subset: {MMLU_SUBJECTS}")
111
+
112
+ # Load model
113
+ print(f"[*] Loading tokenizer + model {MODEL_ID} ...")
114
+ tok = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=False)
115
+ if tok.pad_token is None: tok.pad_token = tok.eos_token
116
+ if "<|im_start|>" not in tok.get_vocab():
117
+ try: tok.add_special_tokens({"additional_special_tokens":["<|im_start|>","<|im_end|>"]})
118
+ except: pass
119
+
120
+ dtype = torch.bfloat16 if torch.cuda.is_available() else torch.float32
121
+ device_map = "auto" if torch.cuda.is_available() else None
122
+ model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=dtype, device_map=device_map, trust_remote_code=False)
123
+ model.eval()
124
+ device = next(model.parameters()).device
125
+ print(f"[+] Loaded on {device} dtype={dtype} β€” starting MMLU")
126
+
127
+ CHOICES = ["A","B","C","D"]
128
+
129
+ def format_example(question, choices, answer=None, include_answer=False):
130
+ # Standard MMLU 5-shot format (Hendrycks)
131
+ prompt = question.strip() + "\n"
132
+ for i, c in enumerate(choices):
133
+ prompt += f"{CHOICES[i]}. {c}\n"
134
+ prompt += "Answer:"
135
+ if include_answer and answer is not None:
136
+ # answer is 0-3 int or letter
137
+ if isinstance(answer, int): ans = CHOICES[answer]
138
+ else: ans = str(answer).strip().upper()[0]
139
+ prompt += f" {ans}"
140
+ return prompt
141
+
142
+ def get_answer_letter(example):
143
+ a = example["answer"]
144
+ if isinstance(a, int): return CHOICES[a]
145
+ return str(a).strip().upper()[0]
146
+
147
+ # cache datasets by subject
148
+ results = {}
149
+ overall_correct = 0
150
+ overall_total = 0
151
+
152
+ # Load MMLU from cais/mmlu (canonical) with fallback to hendrycks
153
+ def load_mmlu_subject(subject):
154
+ for name in ["cais/mmlu", "hendrycks/mmlu"]:
155
+ try:
156
+ ds = load_dataset(name, subject)
157
+ return ds
158
+ except Exception as e:
159
+ continue
160
+ raise RuntimeError(f"Could not load MMLU subject {subject}")
161
+
162
+ for subject in tqdm(MMLU_SUBJECTS, desc="MMLU subjects"):
163
+ print(f"\n{'='*60}\n[>] {subject} (shots={args.shots})")
164
+ try:
165
+ ds = load_mmlu_subject(subject)
166
+ except Exception as e:
167
+ print(f"[!] Skip {subject}: {e}")
168
+ continue
169
+ # hendrycks/mmlu has test split; cais/mmlu has test
170
+ dev = ds.get("dev") or ds.get("validation") or ds["train"]
171
+ test = ds.get("test") or ds.get("validation") or ds["train"]
172
+ if args.limit:
173
+ test = test.select(range(min(args.limit, len(test))))
174
+ # build few-shot prefix from dev (5 examples)
175
+ few_shot_prefix = ""
176
+ if args.shots > 0:
177
+ shots = min(args.shots, len(dev))
178
+ for i in range(shots):
179
+ ex = dev[i]
180
+ q, ch, ans = ex["question"], ex["choices"], ex["answer"]
181
+ few_shot_prefix += format_example(q, ch, ans, include_answer=True) + "\n\n"
182
+
183
+ correct = 0
184
+ total = 0
185
+ # Evaluate β€” score by logprob of A/B/C/D next token (proper MMLU method)
186
+ # For GPT2 we compute which choice token has highest logit after "Answer:"
187
+ for ex in tqdm(test, desc=subject, leave=False):
188
+ q, ch, ans = ex["question"], ex["choices"], ex["answer"]
189
+ true_letter = get_answer_letter(ex)
190
+ prompt = few_shot_prefix + format_example(q, ch, include_answer=False)
191
+ # Tokenize prompt
192
+ inputs = tok(prompt, return_tensors="pt", truncation=True, max_length=900).to(device)
193
+ with torch.no_grad():
194
+ logits = model(**inputs).logits[0, -1] # last token logits
195
+ # Get logits for " A", " B", etc. (with leading space)
196
+ # GPT2 BPE: " A" is single token 32 etc. β€” check both with and without space
197
+ scores = {}
198
+ for letter in CHOICES:
199
+ for variant in [f" {letter}", letter, f" {letter}.", f"\n{letter}"]:
200
+ tid = tok.encode(variant, add_special_tokens=False)
201
+ if len(tid)==1:
202
+ scores[letter] = logits[tid[0]].item()
203
+ break
204
+ if letter not in scores:
205
+ scores[letter] = float("-inf")
206
+ pred = max(scores, key=scores.get)
207
+ if pred == true_letter:
208
+ correct += 1
209
+ total += 1
210
+ overall_total += 1
211
+ if pred == true_letter:
212
+ overall_correct += 1
213
+
214
+ acc = correct/total if total else 0
215
+ results[subject] = {"correct": correct, "total": total, "accuracy": acc}
216
+ print(f"[=] {subject}: {correct}/{total} = {acc*100:.1f}% (running avg {(overall_correct/overall_total*100):.1f}%)")
217
+
218
+ avg = overall_correct/overall_total if overall_total else 0
219
+ print("\n" + "="*60)
220
+ print(f"MMLU RESULT β€” {MODEL_ID}")
221
+ print(f"Shots: {args.shots} Subjects: {len(results)}/{len(MMLU_SUBJECTS)}")
222
+ for subj, r in sorted(results.items()):
223
+ print(f" {subj:35s} {r['accuracy']*100:5.1f}% ({r['correct']}/{r['total']})")
224
+ print(f"\n OVERALL: {overall_correct}/{overall_total} = {avg*100:.2f}%")
225
+ print("="*60)
226
+
227
+ out = {"model": MODEL_ID, "shots": args.shots, "limit": args.limit,
228
+ "overall": {"correct": overall_correct, "total": overall_total, "accuracy": avg},
229
+ "subjects": results}
230
+ Path(args.output).write_text(json.dumps(out, indent=2))
231
+ print(f"[+] Saved {args.output}")
232
+
233
+ # Also compare note
234
+ print("\nNote: GPT2-XL base ~24-26% MMLU (random 25%). MicroLLM2 distilled should be 25-30% β€”")
235
+ print("MMLU is knowledge-heavy; GPT2 1.5B 1024ctx cannot match 7B+ models. Use as sanity check, not SOTA claim.")