Repository navigation
fix(pr_eval): restore the submitter's theta after novelty — 2 of 3 benchmarks were scored as the king - #379
Merged
James-CUDA merged 1 commit intoJul 22, 2026
Conversation
…chmarks were scored as the king)
`_compute_novelty` loads the reigning king's head + SVF into the policy to collect
the king's routing decisions, and never puts the submitter's weights back:
policy.configure(king_theta, spec)
king_decisions = _routing_decisions(policy, eval_items, ref_count=ref_count)
return novelty_score(submitter_decisions, king_decisions)
That `policy` is the SHARED object the benchmark loop scores with. It is configured
with the submitter's theta exactly once, before the loop, and novelty runs only for
`benchmarks[0]` — at the END of the first iteration. So with
COMPETITION_BENCHMARKS = (math500, mmlu, livecodebench), every submission evaluated
while a king exists had:
- `hidden_acc`, `audit_acc` and `live_acc` for **mmlu and livecodebench** measured on
the KING's head + SVF, not the submitter's — two thirds of the composite score
(`sum(...) / len(per_benchmark)`);
- the overfit gate for those two benchmarks comparing king-vs-king;
- no miner able to displace the king on 2 of 3 boards (they reproduce its score, and
WIN_MARGIN then requires beating it), while a weak head silently inherits the
king's accuracies.
The fix hands the policy back on the submitter's theta. `submitter_theta` is hoisted
in `evaluate_pr` and passed in explicitly (so a future caller cannot silently
reintroduce this), and the restore sits in a `finally` so a failure mid-comparison
cannot leak the king's weights into the caller either. The king is still loaded
mid-flight, so novelty measures exactly what it did before.
Verified with a fake policy that records every `configure` call: before the fix the
policy ends on the king's theta; after it, on the submitter's — on the normal path
and when the comparison raises. 8 regression tests, including the three-benchmark
loop shape and the early-return paths (no king / no eval items / unreadable king
weights) which must not touch theta at all. Removing just the restore (keeping the
signature) fails exactly the 4 behavioural tests.
Offline: no torch, no encoder, no network — the tests stub the routing plumbing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
_compute_noveltyloads the reigning king's head + SVF into the policy to collect the king's routing decisions, and never puts the submitter's weights back (pr_eval.py:349):That
policyis the shared object the benchmark loop scores with. It's configured with the submitter's θ exactly once, before the loop (:574), and novelty runs only forbenchmarks[0]— at the end of the first iteration (:610).So with
COMPETITION_BENCHMARKS = (math500, mmlu, livecodebench), for every submission evaluated while a king exists:hidden_acc,audit_acc,live_accfor mmlu and livecodebench are measured on the king's head + SVF, not the submitter's — that's two thirds of the composite (sum(...) / len(per_benchmark));WIN_MARGINthen requires beating it;Proof
A fake policy recording every
configurecall, with a king on disk:The fix
Hand the policy back on the submitter's θ.
submitter_thetais hoisted inevaluate_prand passed in explicitly (so a future caller can't silently reintroduce this), and the restore sits in afinallyso a failure mid-comparison can't leak the king's weights into the caller either. The king is still loaded mid-flight — novelty measures exactly what it did before.20 insertions, 6 deletions in one function plus its call site.
Tests
8 regression tests (no torch / encoder / network — the routing plumbing is stubbed):
submitter_thetaas required.Rigor check: removing only the restore while keeping the new signature fails exactly the 4 behavioural tests and leaves the 4 early-return/signature ones passing — so these guard the behaviour, not just the shape.
ruffon the CI target passes; the 9 pre-existing ruff findings inpr_eval.pyare unchanged (identical count before/after, and CI does not lint that path). 93 pr_eval/submission/novelty tests green.🤖 Generated with Claude Code