ObsidianSmall-Base / evaluation /benchmark_lm_tasks.py
Metris's picture
Upload 5 files
e0a700d verified
Raw
History Blame Contribute Delete
7.15 kB
from __future__ import annotations
import argparse
import json
import math
import time
from pathlib import Path
import torch
import torch.nn.functional as F
import yaml
from datasets import get_dataset_config_names, load_dataset
from litgpt import Tokenizer
from litgpt.config import Config
from litgpt.model import GPT
def load_model(checkpoint_dir: Path, device: torch.device) -> GPT:
config = Config.from_file(checkpoint_dir / "model_config.yaml")
model = GPT(config)
hyperparameters_path = checkpoint_dir / "hyperparameters.yaml"
if hyperparameters_path.is_file():
hyperparameters = yaml.safe_load(hyperparameters_path.read_text(encoding="utf-8")) or {}
if hyperparameters.get("train", {}).get("tie_embeddings"):
model.transformer.wte.weight = model.lm_head.weight
checkpoint = torch.load(checkpoint_dir / "lit_model.pth", map_location="cpu")
state_dict = checkpoint["model"] if "model" in checkpoint else checkpoint
model.load_state_dict(state_dict)
model.to(device)
model.eval()
return model
def encode_pair(tokenizer: Tokenizer, prompt: str, continuation: str, block_size: int) -> tuple[torch.Tensor, int]:
prompt_ids = tokenizer.encode(prompt, bos=True, eos=False).long()
continuation_ids = tokenizer.encode(continuation, bos=False, eos=False).long()
ids = torch.cat([prompt_ids, continuation_ids])
if ids.numel() > block_size:
keep = min(block_size, continuation_ids.numel() + min(32, prompt_ids.numel()))
ids = ids[-keep:]
continuation_len = min(continuation_ids.numel(), ids.numel() - 1)
else:
continuation_len = continuation_ids.numel()
return ids, continuation_len
@torch.no_grad()
def continuation_nll(
model: GPT,
tokenizer: Tokenizer,
prompt: str,
continuation: str,
device: torch.device,
) -> float:
ids, continuation_len = encode_pair(tokenizer, prompt, continuation, model.max_seq_length)
if continuation_len <= 0 or ids.numel() <= 1:
return float("inf")
inputs = ids[:-1].unsqueeze(0).to(device)
targets = ids[1:].to(device)
use_autocast = device.type == "cuda"
with torch.autocast(device_type="cuda", dtype=torch.bfloat16, enabled=use_autocast):
logits = model(inputs)[0].float()
start = max(0, targets.numel() - continuation_len)
loss = F.cross_entropy(logits[start:], targets[start:], reduction="sum")
return float(loss.cpu()) / continuation_len
def normalize_answer_key(answer_key: str, labels: list[str], texts: list[str]) -> int | None:
if answer_key in labels:
return labels.index(answer_key)
for index, text in enumerate(texts):
if answer_key.strip().lower() == text.strip().lower():
return index
return None
def benchmark_arc_easy(model: GPT, tokenizer: Tokenizer, device: torch.device, limit: int) -> dict[str, float | int]:
dataset = load_dataset("ai2_arc", "ARC-Easy", split=f"validation[:{limit}]")
correct = 0
total = 0
start = time.perf_counter()
for row in dataset:
question = row["question"].strip()
labels = [str(label) for label in row["choices"]["label"]]
texts = [str(text) for text in row["choices"]["text"]]
target = normalize_answer_key(str(row["answerKey"]), labels, texts)
if target is None:
continue
prompt = f"Question: {question}\nAnswer:"
scores = [continuation_nll(model, tokenizer, prompt, " " + choice, device) for choice in texts]
prediction = min(range(len(scores)), key=scores.__getitem__)
correct += int(prediction == target)
total += 1
elapsed = time.perf_counter() - start
return {
"arc_easy_validation_examples": total,
"arc_easy_accuracy": correct / total if total else math.nan,
"arc_easy_seconds": elapsed,
}
def benchmark_blimp(
model: GPT,
tokenizer: Tokenizer,
device: torch.device,
configs: list[str],
examples_per_config: int,
) -> dict[str, float | int | dict[str, float]]:
per_config: dict[str, float] = {}
correct = 0
total = 0
start = time.perf_counter()
for config in configs:
dataset = load_dataset("nyu-mll/blimp", config, split=f"train[:{examples_per_config}]")
config_correct = 0
config_total = 0
for row in dataset:
good = str(row["sentence_good"]).strip()
bad = str(row["sentence_bad"]).strip()
good_score = continuation_nll(model, tokenizer, "", good, device)
bad_score = continuation_nll(model, tokenizer, "", bad, device)
config_correct += int(good_score < bad_score)
config_total += 1
per_config[config] = config_correct / config_total if config_total else math.nan
correct += config_correct
total += config_total
elapsed = time.perf_counter() - start
return {
"blimp_examples": total,
"blimp_configs": len(configs),
"blimp_accuracy": correct / total if total else math.nan,
"blimp_per_config": per_config,
"blimp_seconds": elapsed,
}
def default_blimp_configs(limit: int) -> list[str]:
preferred = [
"adjunct_island",
"anaphor_number_agreement",
"determiner_noun_agreement_1",
"irregular_past_participle_adjectives",
"subject_verb_agreement_simple",
]
available = set(get_dataset_config_names("nyu-mll/blimp"))
configs = [name for name in preferred if name in available]
if len(configs) < limit:
configs.extend(name for name in sorted(available) if name not in configs)
return configs[:limit]
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--checkpoint-dir", type=Path, required=True)
parser.add_argument("--tokenizer-dir", type=Path, required=True)
parser.add_argument("--arc-limit", type=int, default=100)
parser.add_argument("--blimp-configs", type=int, default=5)
parser.add_argument("--blimp-examples", type=int, default=50)
parser.add_argument("--out", type=Path)
args = parser.parse_args()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = load_model(args.checkpoint_dir, device)
tokenizer = Tokenizer(args.tokenizer_dir)
metrics: dict[str, object] = {
"checkpoint_dir": str(args.checkpoint_dir),
"tokenizer_dir": str(args.tokenizer_dir),
"device": str(device),
"model_name": model.config.name,
"parameters": sum(parameter.numel() for parameter in model.parameters()),
}
metrics.update(benchmark_arc_easy(model, tokenizer, device, args.arc_limit))
metrics.update(
benchmark_blimp(
model,
tokenizer,
device,
default_blimp_configs(args.blimp_configs),
args.blimp_examples,
)
)
text = json.dumps(metrics, indent=2, sort_keys=True)
print(text)
if args.out:
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(text + "\n", encoding="utf-8")
if __name__ == "__main__":
main()