Download train/evaluate_local.py from tchbcb/qwenjev: direct link, hf CLI and curl.
- Browser
- Download file 6.44 kB
-
https://huggingface.co/tchbcb/qwenjev/resolve/main/train/evaluate_local.py
- Command line
-
hf download hf://tchbcb/qwenjev/train/evaluate_local.py
-
curl -L -o evaluate_local.py https://huggingface.co/tchbcb/qwenjev/resolve/main/train/evaluate_local.py
6.44 kB
| """Local evaluation: run the hybrid agent on public games and report | |
| per-game scores in the spirit of the official RHAE metric. | |
| per-level = min(human/agent, 1.0) ^ 2 (human baseline unknown locally; | |
| we report completion + actions) | |
| game = level-weighted average (1-indexed weights) | |
| total = average over games | |
| Without official human baselines this is a RELATIVE tool: compare agent | |
| variants against each other, never treat absolute numbers as leaderboard | |
| predictions. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[1])) | |
| import yaml # noqa: E402 | |
| from data.collect_selfplay import PUBLIC_GAMES_FALLBACK, grids_from, make_env, meta_from # noqa: E402 | |
| from data.mock_env import step_env # noqa: E402 | |
| from qwenjev.hybrid import HybridAgent, HybridConfig # noqa: E402 | |
| from qwenjev.system1 import QwenJevModel, System1Config # noqa: E402 | |
| from qwenjev.system2 import System2Config, System2Controller # noqa: E402 | |
| from qwenjev.vision import TinyVLMConfig # noqa: E402 | |
| def run_game(env, game_id: str, brain: HybridAgent, max_actions: int) -> dict: | |
| """Play one episode against any environment (mock or official). | |
| Lifecycle: WIN ends the episode; GAME_OVER triggers the hybrid brain's | |
| episode-boundary hook and a fresh reset, then play continues within the | |
| same action budget — identical budget for every agent variant, so | |
| best_level/actions stay comparable across runs.""" | |
| env.reset() | |
| actions = 0 | |
| resets = 0 | |
| best_level = 0 | |
| final_state = "NOT_FINISHED" | |
| started = time.time() | |
| for _ in range(max_actions): | |
| meta = meta_from(env) | |
| final_state = meta["state"] | |
| if final_state == "WIN": | |
| break | |
| if final_state == "GAME_OVER": | |
| brain.on_game_over(game_id, [f"game over at action {actions}"]) | |
| env.reset() | |
| resets += 1 | |
| continue | |
| grids = grids_from(env) | |
| best_level = max(best_level, meta["levels_completed"]) | |
| cand = brain.decide( | |
| game_id, grids, | |
| levels_completed=meta["levels_completed"], | |
| win_levels=meta["win_levels"], | |
| available_actions=meta["available_actions"], | |
| game_state=meta["state"], | |
| ) | |
| if cand.is_complex: | |
| step_env(env, cand.name, int(cand.x or 0), int(cand.y or 0)) | |
| else: | |
| step_env(env, cand.name) | |
| actions += 1 | |
| best_level = max(best_level, meta_from(env)["levels_completed"]) | |
| return { | |
| "game_id": game_id, | |
| "win": final_state == "WIN", | |
| "final_state": final_state, | |
| "best_level": best_level, | |
| "resets": resets, | |
| "actions": actions, | |
| "seconds": round(time.time() - started, 1), | |
| } | |
| def build_s1(cfg: dict): | |
| """System-1 from the yaml config; checkpoint loaded when present.""" | |
| s1_cfg = cfg.get("system1", {}) | |
| from pathlib import Path | |
| # v0.4.9: pool_vision auto-detection. The saved qwenjev_s1.pt config | |
| # carries the flag from v0.4.9 on; ABSENCE = v3-era checkpoint = | |
| # last-position pooling (heads built with matching H input). | |
| pool_vision = s1_cfg.get("pool_vision") | |
| if pool_vision is None: | |
| pool_vision = False | |
| try: | |
| import torch as _torch | |
| ck = (Path(cfg.get("out_dir", "checkpoints/qwenjev-s1")) | |
| / "qwenjev_s1.pt") | |
| if ck.exists(): | |
| payload = _torch.load(ck, map_location="cpu", | |
| weights_only=False) | |
| pool_vision = bool( | |
| (payload.get("config") or {}).get("pool_vision", False)) | |
| except Exception: | |
| pool_vision = False | |
| model_cfg = System1Config( | |
| backend=s1_cfg.get("backend", "tiny"), | |
| hf_model=s1_cfg.get("hf_model", "Qwen/Qwen3-VL-2B-Instruct"), | |
| max_leaf_tokens=int(s1_cfg.get("max_leaf_tokens", 512)), | |
| score_levels=int(s1_cfg.get("score_levels", 10)), | |
| leaf_batch_size=int(s1_cfg.get( | |
| "eval_leaf_batch", s1_cfg.get("leaf_batch_size", 32))), | |
| pool_vision=bool(pool_vision), | |
| tiny=TinyVLMConfig(**(s1_cfg.get("tiny") or {})), | |
| ) | |
| s1 = QwenJevModel(model_cfg).build() | |
| ckpt = Path(cfg.get("out_dir", "checkpoints/qwenjev-s1")) / "qwenjev_s1.pt" | |
| if ckpt.exists(): | |
| s1.load_heads(ckpt.parent) | |
| # stage B/C continuity: merge the SFT adapters into the fp16 base | |
| # so evaluation runs on the SAME hidden states the heads saw | |
| if model_cfg.backend == "hf": | |
| from qwenjev.hf_compat import resume_adapters | |
| hf_lora = ckpt.parent / "hf_lora" | |
| if hf_lora.exists(): | |
| s1._backbone, _merged = resume_adapters( | |
| s1._backbone, str(hf_lora), merge=True) | |
| print(f"stage B LoRA merged -> {hf_lora}") | |
| print(f"System-1 checkpoint loaded -> {ckpt}") | |
| return s1 | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--config", default="configs/train.yaml") | |
| ap.add_argument("--games", nargs="*", default=None) | |
| ap.add_argument("--out", default="eval_report.json") | |
| args = ap.parse_args() | |
| cfg = yaml.safe_load(open(args.config)) | |
| s1 = None | |
| try: | |
| s1 = build_s1(cfg) | |
| except Exception as exc: | |
| print(f"[warn] System-1 unavailable ({exc}); heuristic policy will run") | |
| s2 = None | |
| if cfg.get("system2", {}).get("enabled"): | |
| s2 = System2Controller(System2Config(endpoint=cfg["system2"].get("endpoint"))) | |
| brain = HybridAgent(s1=s1, s2=s2, config=HybridConfig(game_time_limit_s=600)) | |
| games = args.games or PUBLIC_GAMES_FALLBACK[:5] # default: quick smoke set | |
| report = [] | |
| max_actions = int(cfg.get("eval_max_actions", 1500)) | |
| for game_id in games: | |
| try: | |
| env = make_env(game_id, None) | |
| res = run_game(env, game_id, brain, max_actions=max_actions) | |
| report.append(res) | |
| print(res) | |
| except Exception as exc: | |
| print(f"[warn] {game_id}: {exc}") | |
| Path(args.out).write_text(json.dumps(report, indent=2)) | |
| wins = sum(1 for r in report if r["win"]) | |
| print(f"\nsummary: {wins}/{len(report)} wins -> {args.out}") | |
| if __name__ == "__main__": | |
| main() | |