This repository was archived by the owner on Aug 3, 2026. It is now read-only.
eval: reject non-causal forward() so a miner can't control its own val_bpb - #62
Closed
rockmania99 wants to merge 1 commit into
Closed
eval: reject non-causal forward() so a miner can't control its own val_bpb#62rockmania99 wants to merge 1 commit into
rockmania99 wants to merge 1 commit into
Conversation
…l_bpb The validator scores the miner's OWN forward() to compute val_bpb — op4 runs the patched model code for any structural patch — feeding the whole window in one call. The target for position t is input[t+1], which sits inside the model's input, so a non-causal forward can read the answer and emit a perfect prediction, collapsing val_bpb to ~0 and crowning an unbeatable, fraudulent king. This is not closed by the merged-PR gate (RalphLabsAI#60) or eval fail-closed (RalphLabsAI#59): the cheat carries a real merged recipe PR and a real attestation. Fix: - assert_causal(): before trusting val_bpb, probe the model — overwrite the FUTURE positions of a real eval window with a DIFFERENT real held-out slice and require the earlier logits to be unchanged. A causal model is invariant; a look-ahead forward is not. A realistic (not uniform-random) decoy future closes the adaptive-probe evasion. Runs inside run_hidden_eval, covering both the canonical and patched-eval paths. - Pin the eval window validator-side (EVAL_SEQ_LEN), capped at the model's max_seq_len, instead of the miner-controlled cfg.max_seq_len // 2 — otherwise a miner can enlarge the eval context to score an easier eval than the king. - tests/test_eval_forward_trust.py: honest model passes, look-ahead rejected (directly and via run_hidden_eval), short-stream no-op, pinned constant. Follow-up (see PR description): the related eval-file-read variant — a forward that reads the held-out shard off disk inside the patched-eval subprocess — needs OS sandboxing of that subprocess; the causality probe does not cover it.
rockmania99
force-pushed
the
fix/eval-forward-trust
branch
from
June 26, 2026 01:58
ae58565 to
1e768d3
Compare
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Problem — a miner can control its own
val_bpbThe validator scores the miner's own
forward()to computeval_bpb. For any structural patch,op4_hidden_evalfalls back to_patched_hidden_eval, which runs the patched model code (eval_in_workdir.py).compute_val_bpbfeeds the model the whole window in one call, and the target for positiontis justinput[t+1]— which is inside the model's own input.So a non-causal
forward()can readinput[t+1](the answer for positiont) and emit a perfect prediction, drivingval_bpbto ~0 → an unbeatable, fraudulent king with up to 90% recurring emission. The cheat can gate on eval mode (if not self.training:) so training logs look normal (op3 passes) and op2 attestation passes.PoC (non-causal forward that one-hots
input[t+1]):This is not closed by the merged-PR gate (#60) or eval fail-closed (#59): the cheat carries a real merged recipe PR and a real CC attestation. It's a structural eval-trust gap.
Fix
assert_causal()(ineval/val_bpb.py, called fromrun_hidden_evalso it covers both the canonical and patched-eval paths). Before trustingval_bpb, it overwrites the future positions of a real eval window with a different real held-out slice and asserts the earlier logits are unchanged. A causal model is invariant; a look-ahead forward is not.atolis intentionally generous: honest prefix logits are invariant by construction (~0 difference), while a useful cheat must move logits by a large margin — so detection stays certain with zero false-positive risk from GPU/bf16 kernel noise.EVAL_SEQ_LEN, capped at the model'smax_seq_len) instead of the miner-controlledcfg.max_seq_len // 2— otherwise a miner can enlarge the eval context to score an easier eval than the king.Tests
tests/test_eval_forward_trust.py: honest causal model passes (no false positive), look-ahead model rejected (directly and viarun_hidden_eval), short-stream no-op, pinned-constant. Validated locally.Scope / follow-up (not in this PR)
There is a related variant the causality probe does not cover: a
forward()that reads the held-out shard off disk inside the patched-eval subprocess (_patched_hidden_evalcopieseval/into the workdir and passesralph_root). Closing it needs OS sandboxing of that subprocess (no filesystem access toeval/, no network) — recommended as a follow-up. I kept this PR focused on the proven look-ahead vector + the window-pin.