"""Evaluate a saved Unsloth/TRL GRPO checkpoint on the Red Shift eval rows.""" from __future__ import annotations import argparse import json import re from pathlib import Path from typing import Any from train_unsloth_grpo import evaluate_model def checkpoint_step(path: Path) -> int: match = re.search(r"checkpoint-(\d+)$", path.name) return int(match.group(1)) if match else -1 def latest_checkpoint(run_dir: Path) -> Path: checkpoints = sorted(run_dir.glob("checkpoint-*"), key=checkpoint_step) if not checkpoints: raise FileNotFoundError(f"No checkpoint-* directories found under {run_dir}") return checkpoints[-1] def load_rows(dataset_path: Path, eval_tasks: int) -> list[dict[str, Any]]: rows = [json.loads(line) for line in dataset_path.read_text(encoding="utf-8").splitlines() if line.strip()] return rows[: min(eval_tasks, len(rows))] def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--run-dir", type=Path, default=Path("training_results/unsloth_grpo_qwen3b_easy")) parser.add_argument("--checkpoint", type=Path, default=None) parser.add_argument("--model-name", default="unsloth/Qwen2.5-3B-Instruct-bnb-4bit") parser.add_argument("--max-seq-length", type=int, default=1280) parser.add_argument("--max-completion-length", type=int, default=256) parser.add_argument("--eval-tasks", type=int, default=32) parser.add_argument("--reward-mode", choices=["hard", "easy"], default="easy") parser.add_argument("--out-name", default="checkpoint_generations.json") args = parser.parse_args() checkpoint = args.checkpoint or latest_checkpoint(args.run_dir) dataset_path = args.run_dir / "dataset.jsonl" if not dataset_path.exists(): raise FileNotFoundError(f"Missing {dataset_path}; run training at least until dataset creation.") eval_rows = load_rows(dataset_path, args.eval_tasks) from peft import PeftModel from unsloth import FastLanguageModel model, tokenizer = FastLanguageModel.from_pretrained( model_name=args.model_name, max_seq_length=args.max_seq_length, load_in_4bit=True, fast_inference=False, ) if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token model = PeftModel.from_pretrained(model, str(checkpoint), is_trainable=False) try: FastLanguageModel.for_inference(model) except Exception: pass generations_path = args.run_dir / args.out_name checkpoint_eval = evaluate_model( model, tokenizer, eval_rows, generations_path, args.max_completion_length, args.reward_mode, ) baseline_path = args.run_dir / "baseline_generations.json" baseline = json.loads(baseline_path.read_text(encoding="utf-8")) if baseline_path.exists() else {} summary = { "status": "checkpoint_eval", "run_dir": str(args.run_dir), "checkpoint": str(checkpoint), "checkpoint_step": checkpoint_step(checkpoint), "model_name": args.model_name, "num_eval_tasks": len(eval_rows), "reward_mode": args.reward_mode, "baseline_mean_reward": baseline.get("mean_reward"), "checkpoint_mean_reward": checkpoint_eval["mean_reward"], "generations_path": str(generations_path), } (args.run_dir / "checkpoint_eval_summary.json").write_text(json.dumps(summary, indent=2), encoding="utf-8") print(json.dumps(summary, indent=2)) if __name__ == "__main__": main()