Skip to content

Add eigen_eval_loss: forward-only held-out cross-entropy#671

Merged
InauguralPhysicist merged 2 commits into
mainfrom
eigen-eval-loss
Jul 18, 2026
Merged

Add eigen_eval_loss: forward-only held-out cross-entropy#671
InauguralPhysicist merged 2 commits into
mainfrom
eigen-eval-loss

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

Why

A language model should be selected on held-out loss. That metric didn't exist in the model extension, so iLambdaAi ranked checkpoints on a generate-and-parse rate — and that rate cannot see the model.

Three checkpoints scored an identical 60/300 on the greedy parse metric while their held-out loss spanned 3.679 to 1.751 (perplexity 39.6 vs 5.76):

checkpoint greedy parse val loss perplexity
untrained 0/300 7.004 1102
unshuffled training 60/300 3.679 39.6
shuffled training 60/300 1.751 5.76

Greedy decoding collapses onto frequency attractors regardless of how good the model underneath is, so the metric separates trained from untrained and then flatlines across every trained candidate. Deterministic is not the same as informative.

Sample size compounded it: at n=30 a 20% rate carries a 95% interval of roughly 8%–39%, so an A/B whose arms differed by 14 points of accuracy read as "no difference."

What

eigen_eval_loss of [prompt_ids, target_id] → cross-entropy in nats.

Forward only — no backward, no weight update, no requantise, no observer state, and g_model_age/g_training_samples untouched, so scoring a checkpoint never mutates it.

Log-softmax subtracts the max before exponentiating; without it a large logit overflows float and returns inf, which would silently poison a mean taken over thousands of windows.

Testing

Suite [47d] pins it against the property that an untrained model must score ln(vocab_size) — a near-uniform output distribution has that cross-entropy whatever the target is:

  • vocab=1109 → measured 7.004, predicted 7.011
  • vocab=8 fixture → measured 2.016, predicted 2.079

That single anchor catches a missing max-subtraction, a mis-indexed target, a softmax summed over the wrong axis, and sign errors simultaneously. Also verifies out-of-range targets return the -1 sentinel rather than reading past the logits array.

Suite: 3168/3168 on the http+model build.

🤖 Generated with Claude Code

InauguralPhysicist and others added 2 commits July 18, 2026 13:50
A language model should be selected on held-out loss. That metric did
not exist here, so iLambdaAi ranked checkpoints on a generate-and-parse
rate instead -- and that rate could not see the model.

Three checkpoints scored an IDENTICAL 60/300 on the greedy parse metric
while their held-out loss spanned 3.679 to 1.751, a perplexity gap of
39.6 vs 5.76. Greedy decoding collapses onto frequency attractors
regardless of how good the model underneath is, so the metric separates
trained from untrained (0 vs 200) and then flatlines across every
trained candidate. Deterministic is not the same as informative.

The sample size compounded it: at n=30 a 20% rate carries a 95%
interval of roughly 8%-39%, so an A/B whose arms differed by 14 points
of accuracy read as "no difference". A loss gives one datapoint per
window on a continuous scale -- 2000 windows where there were 30 binary
trials.

Forward only: no backward, no weight update, no requantise, no observer
state, and g_model_age/g_training_samples untouched, so scoring a
checkpoint never mutates it. Log-softmax subtracts the max before
exponentiating; without that a large logit overflows float and returns
inf, which would silently poison a mean taken over thousands of windows.

Suite [47d] pins it against the property that an untrained model must
score ln(vocab_size), since a near-uniform output distribution has that
cross-entropy whatever the target is. Measured 7.004 against a predicted
7.011 at vocab=1109, and 2.016 against 2.079 on the vocab=8 fixture.
One anchor catches a missing max-subtraction, a mis-indexed target, a
softmax summed over the wrong axis, and sign errors at once. Also checks
out-of-range targets return the -1 sentinel rather than reading past the
logits array.

Suite: 3168/3168 on the http+model build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Optional 4th argument to eigen_generate. Keeps the smallest set of
tokens whose cumulative probability reaches p, so the candidate set
adapts to the model's confidence, instead of the fixed top-k=40 that
admits 39 near-zero tokens where the distribution is sharp and truncates
real choices where it is flat. Absent or outside (0,1) keeps the
historical top-k path, so existing callers and every recorded number are
unchanged -- decoding policy is opt-in, not a silent default flip.

Honest scope: this was written to fix a repetition degeneracy and it
does not. The model assigns its repeat-loop continuation p=0.987 (loss
0.0127, alternatives at ~3e-8), so a 0.95 nucleus keeps exactly one
token and produces output identical to top-k, token for token. The
degeneracy is a genuine self-consistent fixed point in the model, not a
decoding artifact. Nucleus sampling is still the better default policy
and is worth having; it just is not the fix for that.

Testing needed care. Generation draws from a global rand(), so two calls
with the SAME policy already disagree on most positions -- measured 3/40
identical. Comparing token sequences therefore proves nothing about the
policy, and an earlier version of this check "verified" top-p by
observing 0/40 matches, which the control shows is indistinguishable
from noise. The robust signal is the statistical signature: on a
near-uniform model top-p and top-k emit visibly different numbers of
DISTINCT tokens (61 vs 257 in 300 tokens at vocab=1109), which is a
property of the candidate set rather than of any rand() draw. Suite
[47e] asserts a restrictive nucleus narrows the emitted set and that
out-of-range top_p falls back to top-k without emptying the set.

qsort comparator is NaN-safe: a NaN comparison yields 0 rather than an
inconsistent ordering, which qsort is permitted to fault on.

Suite: 3172/3172 on the http+model build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@InauguralPhysicist
InauguralPhysicist merged commit 1267932 into main Jul 18, 2026
18 checks passed
@InauguralPhysicist
InauguralPhysicist deleted the eigen-eval-loss branch July 18, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant