laguna-martini / scripts /eval_pruned_mmlu.py
nikgeo's picture
Publish Laguna Martini grouped-pruning model card and reproducibility artifacts
6f11713 verified
Raw
History Blame Contribute Delete
6.58 kB
#!/usr/bin/env python3
"""Run MMLU against a loaded baseline, atomic-pruned, or repacked grouped model."""
from __future__ import annotations
import argparse
import json
import sys
from contextlib import nullcontext
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import numpy as np
from heapr.grouped_model import RepackedExpandedGroupedLagunaContext
from heapr.model_utils import (
build_max_memory,
load_causal_lm,
load_tokenizer,
validate_model_device_placement,
)
from heapr.prune import apply_atomic_mask_to_model, apply_group_mask_to_model, atomic_mask_from_scores
from heapr.utils import require_torch
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--model-id", required=True)
parser.add_argument("--output-path", required=True)
parser.add_argument(
"--mode",
choices=["baseline", "atomic", "native-group", "repacked-group"],
default="baseline",
)
parser.add_argument("--scores-path")
parser.add_argument("--ratio", type=float)
parser.add_argument("--group-mask")
parser.add_argument("--group-indices")
parser.add_argument(
"--child-budget-mode",
choices=["backfill", "layer-scaled", "parent-weighted"],
default="backfill",
)
parser.add_argument("--tasks", default="mmlu")
parser.add_argument("--limit", type=float)
parser.add_argument("--num-fewshot", type=int)
parser.add_argument("--max-gen-toks", type=int)
parser.add_argument("--batch-size", default="1")
parser.add_argument("--revision")
parser.add_argument("--dtype", default="bfloat16")
parser.add_argument("--gpu-memory-per-device")
parser.add_argument("--max-gpu-memory")
parser.add_argument("--max-cpu-memory")
parser.add_argument("--offload-folder")
parser.add_argument("--allow-cpu-offload", action="store_true")
return parser.parse_args()
def _batch_size(value: str):
return int(value) if value.isdigit() else value
def _json_default(value):
if isinstance(value, np.generic):
return value.item()
if isinstance(value, np.ndarray):
return value.tolist()
return str(value)
def _generation_kwargs(max_gen_toks: int | None):
if max_gen_toks is None:
return None
return {"max_gen_toks": max_gen_toks}
def main() -> None:
args = parse_args()
if args.offload_folder and not args.allow_cpu_offload:
raise ValueError("--offload-folder requires --allow-cpu-offload")
if args.mode == "atomic" and (args.scores_path is None or args.ratio is None):
raise ValueError("--scores-path and --ratio are required for atomic mode")
if args.mode in {"native-group", "repacked-group"} and (
args.group_mask is None or args.group_indices is None
):
raise ValueError("--group-mask and --group-indices are required for grouped modes")
max_memory = build_max_memory(
gpu_memory_per_device=args.gpu_memory_per_device,
max_gpu_memory=args.max_gpu_memory,
max_cpu_memory=args.max_cpu_memory,
allow_cpu_offload=args.allow_cpu_offload,
)
torch = require_torch()
requested_gpu_count = torch.cuda.device_count() if args.gpu_memory_per_device else None
model = load_causal_lm(
args.model_id,
revision=args.revision,
dtype=args.dtype,
max_memory=max_memory,
offload_folder=args.offload_folder if args.allow_cpu_offload else None,
use_cache=False,
)
if args.max_gen_toks is not None:
# Laguna ships max_new_tokens=4096 in its generation config. Align it
# with the lm-eval limit so it does not override the harness max_length.
model.generation_config.max_new_tokens = args.max_gen_toks
validate_model_device_placement(
model,
allow_cpu_offload=args.allow_cpu_offload,
requested_gpu_count=requested_gpu_count,
)
tokenizer = load_tokenizer(args.model_id, revision=args.revision)
context = nullcontext(model)
pruning_summary = {"mode": args.mode}
if args.mode == "atomic":
keep_mask = atomic_mask_from_scores(np.load(args.scores_path), args.ratio)
apply_atomic_mask_to_model(model, keep_mask)
pruning_summary.update(
ratio=args.ratio,
kept=int(keep_mask.sum()),
pruned=int((~keep_mask).sum()),
)
elif args.mode == "native-group":
keep_mask = np.load(args.group_mask)
group_indices = np.load(args.group_indices)
apply_group_mask_to_model(model, keep_mask, group_indices=group_indices)
pruning_summary.update(
kept_groups=int(keep_mask.sum()),
pruned_groups=int((~keep_mask).sum()),
)
elif args.mode == "repacked-group":
keep_mask = np.load(args.group_mask)
group_indices = np.load(args.group_indices)
context = RepackedExpandedGroupedLagunaContext(
model,
keep_group_masks=keep_mask,
group_indices=group_indices,
child_budget_mode=args.child_budget_mode,
)
pruning_summary.update(
kept_groups=int(keep_mask.sum()),
pruned_groups=int((~keep_mask).sum()),
child_budget_mode=args.child_budget_mode,
)
from lm_eval import evaluator
from lm_eval.models.huggingface import HFLM
with context:
harness_model = HFLM(
pretrained=model,
tokenizer=tokenizer,
backend="causal",
batch_size=_batch_size(args.batch_size),
)
results = evaluator.simple_evaluate(
model=harness_model,
tasks=[task.strip() for task in args.tasks.split(",") if task.strip()],
num_fewshot=args.num_fewshot,
batch_size=_batch_size(args.batch_size),
limit=args.limit,
gen_kwargs=_generation_kwargs(args.max_gen_toks),
)
payload = {
"model_id": args.model_id,
"pruning": pruning_summary,
"tasks": args.tasks,
"limit": args.limit,
"num_fewshot": args.num_fewshot,
"max_gen_toks": args.max_gen_toks,
"batch_size": args.batch_size,
"results": results,
}
output_path = Path(args.output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(
json.dumps(payload, indent=2, sort_keys=True, default=_json_default) + "\n",
encoding="utf-8",
)
print(output_path)
if __name__ == "__main__":
main()