Buckets:

HCAI-Lab/comma-2t-tom-unlearning / eval_tombench_unlearn.py
glennmatlin's picture
download
raw
6.39 kB
"""Evaluate a Comma unlearned LoRA adapter on ToMBench and compute gamma.
The core-4 unlearning harness (``unlearning.eval_harness``) runs a fixed OLMES
task list and only aggregates suites in ``SUITE_TO_KEY``; ToMBench is neither in
that task list nor wired for gamma. This standalone wrapper closes that gap
without touching the proven core-4 path:
1. merge the LoRA adapter into a full model (reuses ``eval_harness.merge_adapter``);
2. run OLMES on the single task ``tombench:mc::socialtda`` via the team-standard
command builder (``recipes.olmes.evaluation._build_command`` with a --task
override), so the task config (num_shots 0, split test, acc_raw, max_length
8192) matches the base ToMBench eval exactly;
3. read ``primary_score`` (== ``acc_raw``) from the produced metrics JSON;
4. gamma = (acc - baseline) / |baseline| against the base Comma-2T ToMBench score.
Usage:
python scripts/unlearning/eval_tombench_unlearn.py \
--model-id common-pile/comma-v0.1-2t \
--adapter-dir .../adapter \
--output-json .../cell_tombench_eval.json \
--baseline 0.5132867 --topic-bin social_life --seed 42
"""
from __future__ import annotations
import argparse
import datetime as dt
import json
import logging
import os
import shutil
import sys
import tempfile
from pathlib import Path
logger = logging.getLogger(__name__)
DEFAULT_TASK = "tombench:mc::socialtda"
def read_primary_score(metrics_path: Path) -> float:
"""Read the primary metric (acc_raw) from an OLMES task metrics JSON."""
data = json.loads(Path(metrics_path).read_text(encoding="utf-8"))
metrics = data.get("metrics", {})
for key in ("primary_score", "acc_raw", "acc_per_char"):
if key in metrics and metrics[key] is not None:
return float(metrics[key])
raise ValueError(
f"no primary score in {metrics_path}: metrics keys {list(metrics)}"
)
def find_metrics_file(run_dir: Path, task_substr: str = "tombench") -> Path:
"""Locate the single task metrics JSON matching task_substr under run_dir."""
matches = sorted(
p for p in Path(run_dir).rglob("*-metrics.json") if task_substr in p.name
)
if not matches:
raise FileNotFoundError(f"no '*{task_substr}*-metrics.json' under {run_dir}")
if len(matches) > 1:
logger.warning("multiple metrics files, using first: %s", matches)
return matches[0]
def compute_gamma(score: float, baseline: float) -> float | None:
"""Relative score change against a baseline (matches eval_harness)."""
if baseline == 0:
return None
return (score - baseline) / abs(baseline)
def _olmes_args(model_path: str, task: str, batch_size: int, model_max_length: int):
return argparse.Namespace(
model_id=model_path,
model_type="vllm",
model_max_length=model_max_length,
batch_size=str(batch_size),
gpus=1,
limit=None,
random_subsample_seed=None,
dry_run=False,
task=[[task]],
suite=None,
instruct_model=False,
instruct_base=False,
)
def run_tombench_olmes(
model_path: str,
run_dir: Path,
task: str,
batch_size: int,
model_max_length: int,
) -> None:
import subprocess
from data_attribution.recipes.olmes.evaluation import _build_command
run_dir.mkdir(parents=True, exist_ok=True)
args = _olmes_args(model_path, task, batch_size, model_max_length)
cmd = _build_command(args, run_dir)
logger.info("Running OLMES ToMBench: %s", " ".join(cmd))
subprocess.run(cmd, check=True)
def evaluate(args: argparse.Namespace) -> dict:
output_json = Path(args.output_json)
output_json.parent.mkdir(parents=True, exist_ok=True)
run_dir = (
output_json.parent
/ "olmes_runs"
/ (output_json.stem + "_" + dt.datetime.now(dt.UTC).strftime("%Y%m%d_%H%M%S"))
)
merged_dir: str | None = None
try:
if args.adapter_dir:
from unlearning.eval_harness import merge_adapter
merged_dir = tempfile.mkdtemp(prefix="merged_tom_")
merge_adapter(args.model_id, args.adapter_dir, merged_dir)
eval_model_path = merged_dir
else:
eval_model_path = args.model_id
run_tombench_olmes(
eval_model_path,
run_dir,
args.task,
args.batch_size,
args.model_max_length,
)
metrics_path = find_metrics_file(run_dir, "tombench")
acc = read_primary_score(metrics_path)
gamma = compute_gamma(acc, args.baseline)
result = {
"topic_bin": args.topic_bin,
"seed": args.seed,
"model_id": args.model_id,
"adapter_dir": args.adapter_dir,
"task": args.task,
"tombench_acc": acc,
"baseline": args.baseline,
"gamma": gamma,
"metrics_path": str(metrics_path),
}
output_json.write_text(json.dumps(result, indent=2), encoding="utf-8")
logger.info(
" tombench acc=%.4f baseline=%.4f gamma=%s -> %s",
acc,
args.baseline,
f"{gamma:+.4f}" if gamma is not None else "N/A",
output_json,
)
return result
finally:
if merged_dir and os.path.exists(merged_dir):
shutil.rmtree(merged_dir, ignore_errors=True)
def _parse_args(argv=None) -> argparse.Namespace:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--model-id", default="common-pile/comma-v0.1-2t")
ap.add_argument("--adapter-dir", default=None, help="omit for base baseline")
ap.add_argument("--output-json", required=True)
ap.add_argument("--baseline", type=float, required=True)
ap.add_argument("--task", default=DEFAULT_TASK)
ap.add_argument("--topic-bin", default=None)
ap.add_argument("--seed", default=None)
ap.add_argument("--batch-size", type=int, default=1)
ap.add_argument("--model-max-length", type=int, default=8192)
return ap.parse_args(argv)
def main(argv=None) -> int:
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s: %(message)s",
handlers=[logging.StreamHandler(sys.stdout)],
)
evaluate(_parse_args(argv))
return 0
if __name__ == "__main__":
raise SystemExit(main())

Xet Storage Details

Size:
6.39 kB
·
Xet hash:
e5fa6c10de98e7bf728e890ca165ce77a1b6890121cc91d116a43c64e85bf68a

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.