File size: 10,595 Bytes
18f41dc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#!/usr/bin/env python3
"""
MicroLLM2 β€” MMLU Benchmark
Evaluates MLVXN/MicroLLM2 (or local checkpoint) on MMLU (5-shot by default)
Uses lm-evaluation-harness if available, else lightweight HF implementation.

Usage:
  python mmlu_bench.py                        # 5-shot MMLU on MLVXN/MicroLLM2
  python mmlu_bench.py --model local          # use /home/zeus/microllm2/microllm2-checkpoints/final_merged
  python mmlu_bench.py --shots 0              # zero-shot
  python mmlu_bench.py --subset abstract_algebra,philosophy  # only those subjects
  python mmlu_bench.py --limit 20             # 20 samples per subject for quick smoke test

Outputs: prints per-subject + average accuracy, saves mmlu_results.json
"""
import os, sys, json, argparse, re
from pathlib import Path

os.environ["HF_HUB_DISABLE_XET"]="1"
os.environ["TOKENIZERS_PARALLELISM"]="false"

parser = argparse.ArgumentParser(description="MicroLLM2 MMLU benchmark")
parser.add_argument("--model", default="auto", help="HF id or 'local' or path; default auto -> local if exists else MLVXN/MicroLLM2")
parser.add_argument("--shots", type=int, default=5, help="few-shot examples (0-5)")
parser.add_argument("--limit", type=int, default=None, help="max samples per subject (None=all)")
parser.add_argument("--subset", type=str, default=None, help="comma-separated MMLU subjects to run")
parser.add_argument("--batch", type=int, default=8)
parser.add_argument("--output", default="mmlu_results.json")
args = parser.parse_args()

LOCAL = Path("/home/zeus/microllm2/microllm2-checkpoints/final_merged")
HF_ID = "MLVXN/MicroLLM2"
if args.model == "auto":
    MODEL_ID = str(LOCAL) if LOCAL.exists() else HF_ID
elif args.model == "local":
    MODEL_ID = str(LOCAL)
else:
    MODEL_ID = args.model

print(f"[*] MicroLLM2 MMLU β€” model: {MODEL_ID}  shots={args.shots}  limit={args.limit}")
print(f"[*] GPT2-XL 1.5B 1024ctx  vocab=50259 (ChatML) β€” MMLU via direct eval (no harness needed)")

# Try harness first β€” if installed use it (more accurate), else fallback
USE_HARNESS = False
try:
    import lm_eval  # noqa
    USE_HARNESS = True
except ImportError:
    USE_HARNESS = False

if USE_HARNESS:
    print("[*] Detected lm-evaluation-harness β€” using official MMLU task")
    # harness expects HF model type; gpt2 works
    import lm_eval
    from lm_eval.models.huggingface import HFLM
    from lm_eval.tasks.mmlu import MMLUTask  # if available
    print("[*] Running: lm_eval --model hf --model_args pretrained={} --tasks mmlu --num_fewshot {} --batch_size {} {}".format(
        MODEL_ID, args.shots, args.batch, f"--limit {args.limit}" if args.limit else ""))
    # delegate to CLI so output is standard
    import subprocess
    cmd = [
        sys.executable, "-m", "lm_eval",
        "--model", "hf",
        "--model_args", f"pretrained={MODEL_ID},dtype=bfloat16,trust_remote_code=False",
        "--tasks", "mmlu",
        "--num_fewshot", str(args.shots),
        "--batch_size", str(args.batch),
        "--output_path", args.output,
    ]
    if args.limit:
        cmd += ["--limit", str(args.limit)]
    print(" ".join(cmd))
    subprocess.run(cmd, check=False)
    if Path(args.output).exists():
        print(f"[+] Saved to {args.output}")
        # also print summary if harness wrote it
        try:
            data = json.loads(open(args.output).read())
            # harness output is nested; try to find results
            print(json.dumps(data.get("results", data), indent=2)[:4000])
        except: pass
    sys.exit(0)

# --- Lightweight fallback: direct HF evaluation (no harness) ---
print("[*] lm-eval not installed β€” using lightweight direct MMLU eval (same logic, no harness)")
print("[*] Install harness for official numbers: pip install lm-eval==0.4.4")

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from datasets import load_dataset
from tqdm import tqdm

# MMLU subjects (57) β€” full list from hendrycks/mmlu or cais/mmlu
MMLU_SUBJECTS = [
 "abstract_algebra","anatomy","astronomy","business_ethics","clinical_knowledge","college_biology",
 "college_chemistry","college_computer_science","college_mathematics","college_medicine","college_physics",
 "computer_security","conceptual_physics","econometrics","electrical_engineering","elementary_mathematics",
 "formal_logic","global_facts","high_school_biology","high_school_chemistry","high_school_computer_science",
 "high_school_european_history","high_school_geography","high_school_government_and_politics",
 "high_school_macroeconomics","high_school_mathematics","high_school_microeconomics","high_school_physics",
 "high_school_psychology","high_school_statistics","high_school_us_history","high_school_world_history",
 "human_aging","human_sexuality","international_law","jurisprudence","logical_fallacies","machine_learning",
 "management","marketing","medical_genetics","miscellaneous","moral_disputes","moral_scenarios","nutrition",
 "philosophy","prehistory","professional_accounting","professional_law","professional_medicine","professional_psychology",
 "public_relations","security_studies","sociology","us_foreign_policy","virology","world_religions"
]
if args.subset:
    wanted = [s.strip() for s in args.subset.split(",") if s.strip()]
    MMLU_SUBJECTS = [s for s in MMLU_SUBJECTS if s in wanted]
    print(f"[*] Subset: {MMLU_SUBJECTS}")

# Load model
print(f"[*] Loading tokenizer + model {MODEL_ID} ...")
tok = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=False)
if tok.pad_token is None: tok.pad_token = tok.eos_token
if "<|im_start|>" not in tok.get_vocab():
    try: tok.add_special_tokens({"additional_special_tokens":["<|im_start|>","<|im_end|>"]})
    except: pass

dtype = torch.bfloat16 if torch.cuda.is_available() else torch.float32
device_map = "auto" if torch.cuda.is_available() else None
model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=dtype, device_map=device_map, trust_remote_code=False)
model.eval()
device = next(model.parameters()).device
print(f"[+] Loaded on {device} dtype={dtype} β€” starting MMLU")

CHOICES = ["A","B","C","D"]

def format_example(question, choices, answer=None, include_answer=False):
    # Standard MMLU 5-shot format (Hendrycks)
    prompt = question.strip() + "\n"
    for i, c in enumerate(choices):
        prompt += f"{CHOICES[i]}. {c}\n"
    prompt += "Answer:"
    if include_answer and answer is not None:
        # answer is 0-3 int or letter
        if isinstance(answer, int): ans = CHOICES[answer]
        else: ans = str(answer).strip().upper()[0]
        prompt += f" {ans}"
    return prompt

def get_answer_letter(example):
    a = example["answer"]
    if isinstance(a, int): return CHOICES[a]
    return str(a).strip().upper()[0]

# cache datasets by subject
results = {}
overall_correct = 0
overall_total = 0

# Load MMLU from cais/mmlu (canonical) with fallback to hendrycks
def load_mmlu_subject(subject):
    for name in ["cais/mmlu", "hendrycks/mmlu"]:
        try:
            ds = load_dataset(name, subject)
            return ds
        except Exception as e:
            continue
    raise RuntimeError(f"Could not load MMLU subject {subject}")

for subject in tqdm(MMLU_SUBJECTS, desc="MMLU subjects"):
    print(f"\n{'='*60}\n[>] {subject}  (shots={args.shots})")
    try:
        ds = load_mmlu_subject(subject)
    except Exception as e:
        print(f"[!] Skip {subject}: {e}")
        continue
    # hendrycks/mmlu has test split; cais/mmlu has test
    dev = ds.get("dev") or ds.get("validation") or ds["train"]
    test = ds.get("test") or ds.get("validation") or ds["train"]
    if args.limit:
        test = test.select(range(min(args.limit, len(test))))
    # build few-shot prefix from dev (5 examples)
    few_shot_prefix = ""
    if args.shots > 0:
        shots = min(args.shots, len(dev))
        for i in range(shots):
            ex = dev[i]
            q, ch, ans = ex["question"], ex["choices"], ex["answer"]
            few_shot_prefix += format_example(q, ch, ans, include_answer=True) + "\n\n"

    correct = 0
    total = 0
    # Evaluate β€” score by logprob of A/B/C/D next token (proper MMLU method)
    # For GPT2 we compute which choice token has highest logit after "Answer:"
    for ex in tqdm(test, desc=subject, leave=False):
        q, ch, ans = ex["question"], ex["choices"], ex["answer"]
        true_letter = get_answer_letter(ex)
        prompt = few_shot_prefix + format_example(q, ch, include_answer=False)
        # Tokenize prompt
        inputs = tok(prompt, return_tensors="pt", truncation=True, max_length=900).to(device)
        with torch.no_grad():
            logits = model(**inputs).logits[0, -1]  # last token logits
        # Get logits for " A", " B", etc. (with leading space)
        # GPT2 BPE: " A" is single token 32 etc. β€” check both with and without space
        scores = {}
        for letter in CHOICES:
            for variant in [f" {letter}", letter, f" {letter}.", f"\n{letter}"]:
                tid = tok.encode(variant, add_special_tokens=False)
                if len(tid)==1:
                    scores[letter] = logits[tid[0]].item()
                    break
            if letter not in scores:
                scores[letter] = float("-inf")
        pred = max(scores, key=scores.get)
        if pred == true_letter:
            correct += 1
        total += 1
        overall_total += 1
        if pred == true_letter:
            overall_correct += 1

    acc = correct/total if total else 0
    results[subject] = {"correct": correct, "total": total, "accuracy": acc}
    print(f"[=] {subject}: {correct}/{total} = {acc*100:.1f}%  (running avg {(overall_correct/overall_total*100):.1f}%)")

avg = overall_correct/overall_total if overall_total else 0
print("\n" + "="*60)
print(f"MMLU RESULT β€” {MODEL_ID}")
print(f"Shots: {args.shots}  Subjects: {len(results)}/{len(MMLU_SUBJECTS)}")
for subj, r in sorted(results.items()):
    print(f"  {subj:35s} {r['accuracy']*100:5.1f}%  ({r['correct']}/{r['total']})")
print(f"\n  OVERALL: {overall_correct}/{overall_total} = {avg*100:.2f}%")
print("="*60)

out = {"model": MODEL_ID, "shots": args.shots, "limit": args.limit,
       "overall": {"correct": overall_correct, "total": overall_total, "accuracy": avg},
       "subjects": results}
Path(args.output).write_text(json.dumps(out, indent=2))
print(f"[+] Saved {args.output}")

# Also compare note
print("\nNote: GPT2-XL base ~24-26% MMLU (random 25%). MicroLLM2 distilled should be 25-30% β€”")
print("MMLU is knowledge-heavy; GPT2 1.5B 1024ctx cannot match 7B+ models. Use as sanity check, not SOTA claim.")