#!/usr/bin/env python3 """ Baseline evaluation for TICKETMELT. Runs N episodes with a (pre-trained) model and writes component rewards to JSON. Run BEFORE training so you have a comparison baseline for the plots. Usage: python training/baseline_eval.py --model Qwen/Qwen2.5-3B-Instruct --n_episodes 50 --output baseline_results.json python training/baseline_eval.py --model ./ticketmelt_final --output trained_results.json """ import argparse import json import sys from pathlib import Path def parse_args(): p = argparse.ArgumentParser() p.add_argument("--model", default="Qwen/Qwen2.5-3B-Instruct") p.add_argument("--n_episodes", type=int, default=50) p.add_argument("--output", default="baseline_results.json") p.add_argument("--device", default="cuda") p.add_argument("--seed_offset", type=int, default=0, help="First seed used. Avoids overlap with training seeds.") return p.parse_args() def load_model(model_name, device): import torch from transformers import AutoModelForCausalLM, AutoTokenizer print(f"Loading {model_name} ...") tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.float16, device_map=device ) model.eval() return model, tokenizer def main(): args = parse_args() model, tokenizer = load_model(args.model, args.device) sys.path.insert(0, str(Path(__file__).parent.parent)) from src.environment import TicketmeltEnv from src.prompt import observation_to_prompt from src.rollout import run_episode results = [] wins = 0 for i in range(args.n_episodes): seed = args.seed_offset + i env = TicketmeltEnv(seed=seed) result = run_episode( model, tokenizer, env, observation_to_prompt, seed=seed, device=args.device ) final_reward = result["final_reward"] bd = result["info"].get("reward_breakdown", {}) summary = result["info"].get("episode_summary", {}) results.append({ "episode": i, "seed": seed, "final_reward": final_reward, "r1": bd.get("r1_service_restored", 0.0), "r2": bd.get("r2_site_uptime", 0.0), "r3": bd.get("r3_clean_deploys", 0.0), "r4": bd.get("r4_yield_to_critical", 0.0), "weighted_sum": bd.get("weighted_sum", 0.0), "services_restored": summary.get("services_restored", 0), "total_collisions": summary.get("total_collisions", 0), }) wins += int(final_reward == 1.0) if (i + 1) % 10 == 0: print(f" {i+1}/{args.n_episodes} — win rate: {wins/(i+1):.1%}") n = args.n_episodes avg = lambda key: sum(r[key] for r in results) / n output = { "model": args.model, "n_episodes": n, "win_rate": wins / n, "avg_r1": avg("r1"), "avg_r2": avg("r2"), "avg_r3": avg("r3"), "avg_r4": avg("r4"), "episodes": results, } Path(args.output).write_text(json.dumps(output, indent=2)) print(f"\nSaved → {args.output}") print( f"win_rate={output['win_rate']:.1%} " f"R1={output['avg_r1']:.3f} R2={output['avg_r2']:.3f} " f"R3={output['avg_r3']:.3f} R4={output['avg_r4']:.3f}" ) if __name__ == "__main__": main()