ValidButVacuous / scripts /score_models.py
koushiks29292's picture
Valid but Vacuous: paper, code, figures
01b6af9 verified
Raw History Blame Contribute Delete
3.05 kB
"""Score multiple-choice medical questions with a small open model.
Confidence = softmax over the option letters' next-token logits (no temperature).
Each question is scored in its original and in a shuffled option order.
python scripts/score_models.py --model Qwen/Qwen2.5-1.5B-Instruct \
--questions data/medqa.jsonl --out scores/qwen1.5b_medqa.npz [--nf4]
questions.jsonl, one per line: {"question": str, "options": [str, ...], "answer": int}
Output .npz: conf, correct, pred, conf_shuf, correct_shuf, pred_shuf_in_original_order.
Needs: torch, transformers (+ bitsandbytes for --nf4). Runs on a single 16 GB GPU.
"""
import argparse
import json
import numpy as np
LETTERS = "ABCDEFGHIJ"
def prompt(q, options):
lines = [f"Question: {q}"] + [f"{LETTERS[i]}. {o}" for i, o in enumerate(options)]
return "\n".join(lines) + "\nAnswer:"
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--model", required=True)
ap.add_argument("--questions", required=True)
ap.add_argument("--out", required=True)
ap.add_argument("--nf4", action="store_true", help="load in 4-bit NF4")
ap.add_argument("--seed", type=int, default=0)
args = ap.parse_args()
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
kw = dict(device_map="auto", torch_dtype=torch.float16)
if args.nf4:
from transformers import BitsAndBytesConfig
kw["quantization_config"] = BitsAndBytesConfig(
load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.float16)
tok = AutoTokenizer.from_pretrained(args.model)
model = AutoModelForCausalLM.from_pretrained(args.model, **kw).eval()
rows = [json.loads(l) for l in open(args.questions)]
rng = np.random.default_rng(args.seed)
def letter_ids(k):
return [tok.encode(" " + LETTERS[i], add_special_tokens=False)[-1] for i in range(k)]
@torch.no_grad()
def score(q, opts):
ids = tok(prompt(q, opts), return_tensors="pt").to(model.device)
logits = model(**ids).logits[0, -1].float()
p = torch.softmax(logits[letter_ids(len(opts))], -1).cpu().numpy()
return int(p.argmax()), float(p.max())
out = {k: [] for k in ["pred", "conf", "correct", "pred_shuf", "conf_shuf", "correct_shuf"]}
for r in rows:
pr, c = score(r["question"], r["options"])
out["pred"].append(pr); out["conf"].append(c); out["correct"].append(pr == r["answer"])
perm = rng.permutation(len(r["options"]))
pr2, c2 = score(r["question"], [r["options"][j] for j in perm])
orig = int(perm[pr2])
out["pred_shuf"].append(orig); out["conf_shuf"].append(c2)
out["correct_shuf"].append(orig == r["answer"])
np.savez(args.out, **{k: np.array(v) for k, v in out.items()})
acc = np.mean(out["correct"]); agree = np.mean(np.array(out["pred"]) == np.array(out["pred_shuf"]))
print(f"{args.model}: accuracy {acc:.3f}, agreement under shuffling {agree:.3f}")
if __name__ == "__main__":
main()