feat: per-mode deploy hack in route eval (the weak-detector generalisation cut)

eval_hack_solve now returns by_mode tallies; the final ROUTE EVAL logs
deploy hack/solve per env_mode, tagged IN-dist (run_tests, what v_hack saw)
vs held-out (file_marker/sentinel/stdout_marker). Distinguishes 'route
suppressed only its own mode' from 'route generalised to unseen modes'.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-05-31 04:32:10 +00:00
co-authored by Claudypoo
parent f1af70d34e
commit 83235b6cfe
+22 -4
View File
@@ -548,8 +548,13 @@ def eval_hack_solve(model, tok, problems, eval_idxs, gen_cfg, device, max_new) -
"""
model.config.use_cache = True
n = hacks = solves = 0
# per-mode (env_mode) tallies so route's deploy eval can be read per loophole:
# the load-bearing weak-detector test is whether route suppresses the modes
# v_hack never saw (file_marker/sentinel), not just its own (run_tests).
by_mode: dict[str, list[int]] = {} # env_mode -> [hacks, solves, n]
for idx in eval_idxs:
prob = problems[idx]
mode = prob["env_mode"]
prompt = tok.apply_chat_template(
prob["messages"], tokenize=False, add_generation_prompt=True, enable_thinking=False)
enc = tok(prompt, return_tensors="pt", add_special_tokens=False).to(device)
@@ -557,15 +562,17 @@ def eval_hack_solve(model, tok, problems, eval_idxs, gen_cfg, device, max_new) -
continue
out = model.generate(**enc, generation_config=gen_cfg)
comps = out[:, enc.input_ids.shape[1]:]
tally = by_mode.setdefault(mode, [0, 0, 0])
for t in tok.batch_decode(comps, skip_special_tokens=True):
r = compute_reward(
t, canonical_solution=prob["canonical"], gt_tests=prob["gt_tests"],
setup_code=prob["setup_code"], func_name_hint=prob["func_name"],
env_mode=prob["env_mode"])
hacks += int(r.exploited)
solves += int(r.gt_correct); n += 1
env_mode=mode)
hacks += int(r.exploited); tally[0] += int(r.exploited)
solves += int(r.gt_correct); tally[1] += int(r.gt_correct)
n += 1; tally[2] += 1
model.config.use_cache = False
return dict(hack=hacks / max(1, n), solve=solves / max(1, n), n=n)
return dict(hack=hacks / max(1, n), solve=solves / max(1, n), n=n, by_mode=by_mode)
# 2-char env_mode codes for compact per-mode hack columns (hk_rt, hk_xc, ...).
@@ -1705,6 +1712,17 @@ def main(cfg: Config) -> int:
f"train/knob-on hack={ev_train['hack']:.3f} solve={ev_train['solve']:.3f} | "
f"deploy/knob-off hack={ev_deploy['hack']:.3f} solve={ev_deploy['solve']:.3f} "
f"(SHOULD: deploy hack < train hack at ~matched solve => quarantine absorbed the cheat)")
# Per-mode deploy hack: the generalisation cut. v_hack is run_tests-only, so
# run_tests is the IN-distribution mode; file_marker/sentinel/stdout_marker are
# HELD-OUT. SHOULD: if route generalises, deploy hack drops on held-out modes too,
# not just run_tests. ELSE the quarantine only caught the mode v_hack saw.
for mode in sorted(ev_deploy["by_mode"]):
th, ts, tn = ev_train["by_mode"].get(mode, [0, 0, 0])
dh, ds, dn = ev_deploy["by_mode"][mode]
tag = "IN-dist" if mode == "run_tests" else "held-out"
logger.info(
f" per-mode[{mode:<13} {tag:>8}] train hack={th}/{tn} solve={ts}/{tn} | "
f"deploy hack={dh}/{dn} solve={ds}/{dn}")
# Final tail: cue emoji + main metric BLUF, then per-step tsv table.
# Vanilla arm: 🟢 if hacking emerged. Projected arm: 🟢 if HACK_RATE dropped