Skip to content
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
RalphLabsAI:mainfrom
rockmania99:fix/eval-forward-trust
Closed

eval: reject non-causal forward() so a miner can't control its own val_bpb#62
rockmania99 wants to merge 1 commit into
RalphLabsAI:mainfrom
rockmania99:fix/eval-forward-trust

Conversation

@rockmania99

@rockmania99 rockmania99 commented Jun 25, 2026

Copy link
Copy Markdown

Problem — a miner can control its own val_bpb

The validator scores the miner's own forward() to compute val_bpb. For any structural patch, op4_hidden_eval falls back to _patched_hidden_eval, which runs the patched model code (eval_in_workdir.py). compute_val_bpb feeds the model the whole window in one call, and the target for position t is just input[t+1] — which is inside the model's own input.

So a non-causal forward() can read input[t+1] (the answer for position t) and emit a perfect prediction, driving val_bpb to ~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]):

honest untrained (uniform)   val_bpb = 3.9043
look-ahead forward           val_bpb = 0.0610   # crown margin ~3.8, floor is 0.013

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

  1. assert_causal() (in eval/val_bpb.py, called from run_hidden_eval so it covers both the canonical and patched-eval paths). Before trusting val_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.
    • The decoy future is realistic (not uniform-random) on purpose: a random future is trivially distinguishable from real text, which would let an adaptive cheat behave causally during the probe and look ahead only on the real eval. A realistic-but-different future closes that evasion.
    • atol is 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.
  2. 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

tests/test_eval_forward_trust.py: honest causal model passes (no false positive), look-ahead model rejected (directly and via run_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_eval copies eval/ into the workdir and passes ralph_root). Closing it needs OS sandboxing of that subprocess (no filesystem access to eval/, no network) — recommended as a follow-up. I kept this PR focused on the proven look-ahead vector + the window-pin.

Caveat: the look-ahead half is reproduced end-to-end against the eval harness (the val_bpb collapse above); the op2/op3-bypass half is from reading the gate code, not a live mainnet submission. Flagging so nothing is overclaimed.

…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
rockmania99 force-pushed the fix/eval-forward-trust branch from ae58565 to 1e768d3 Compare June 26, 2026 01:58
@bitzic bitzic closed this Jul 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants