Repository navigation
Conversation
…label_dims) Three new loss terms in the FSQ label_dims path, operating directly on the reserved-digit softmax (dim_lg) so the negative steers the move distribution, not the reconstruction matrix: - HardNegTerm: FIXED to route through the predicted emission (dim_lg soft-argmax + STE) on the FSQ path; the old version pushed on the frozen target recon (up_proj/down_proj), leaving ZERO gradient on the move logits (dim_lg / level_proj) so 'don't emit this blunder' trained nothing at any weight. Continuous path unchanged. Golden regenerated (the FSQ math changed). - MoveNegTerm: penalizes P(blunder) = Pi over reserved digits of softmax(dim_lg[digit])[code]. Building block (shares the codeword->index precompute); empirically weak alone (raw codeword P~0.002). - LegalMoveNegTerm: renormalizes over the per-row legal-move support set and penalizes the blunder's SHARE = sum_neg P(neg) / sum_legal P(legal) — the same space the decode metric reads (~10-35% blunder share), so the gradient is ~50x larger than MoveNegTerm's and actually reshapes mass. Both terms read precomputed padded [N_rows, max_K, n_reserved] index tensors built once in Trainer.__init__ — the step loop does ONE batched gather+prod per set, no Python row loop. Validated (chess, SmolLM2-135M full-FT, held-out shard_1 n=400): the legal-move blunder MASS drops monotonically with weight — 34.5% (no-neg) -> 31.3% (0.3) -> 29.9% (0.6) -> 28.7% (1.0) — the first negative form to move mass outside the noise floor (latent-cosine HardNegTerm and raw MoveNegTerm both left it flat). 0.6 is the operating point (best cp-regret 435 AND argmax-blunder 24%; at 1.0 the argmax regresses as the term over-regularizes the base digit CE). Tests: test_hardneg_reaches_move (the fix reaches dim_lg), test_moveneg_reaches_mass, test_legalneg_reaches_legal_mass (loss in legal-renorm range, grad ~6x MoveNegTerm, one SGD step lowers legal-renorm P(blunder) 0.85->0.03). Full 14-test suite green.
…MoveNegTerm->SupportNegTerm The terms are generic langset _LossTerm machinery (operate on the reserved- digit softmax for any label_dims facet); chess was only the use case, so the chess-flavored names were misleading. Renamed: MoveNegTerm -> LabelNegTerm (raw negative-label codeword penalty) LegalMoveNegTerm -> SupportNegTerm (renormalized over a support set) plus the matching config fields / trainer attrs / loss keys / test files. move_neg_field -> label_neg_field legal_move_field -> support_field lam_move_neg -> lam_label_neg lam_legal_neg -> lam_support_neg No behavior change; the chess consumer (separate repo) passes the renamed field names.
…share)
The missing positive loss: LabelDimsTerm trains per-digit MARGINALS only, but the decode reads the
JOINT-RENORMALIZED P(best)/Σ_legal P(legal) — never directly trained, so SF-best sits at rank 17-37
with <1% mass on the trained models (the 'barely considers best' failure, validated on 4 boards).
SupportPosTerm = cross-entropy toward best over the legal-move renormalized distribution:
loss = -log( P(best) / Σ_legal P(legal) )
Same machinery as SupportNegTerm (the precomputed legal-move support index tensor), opposite sign.
Grad flows into dim_lg / level_proj; mechanistically concentrates on the WEAKER (unsolved) marginal
(the to-square, where TO-acc < FROM-acc) because the joint product bottlenecks on the smaller factor.
Test (test_supportpos_raises_best): loss in legal-renorm CE range, one SGD step raises best's share
0.15->1.0, grad reaches reserved digits only (not non-reserved/STOP), and concentrates on the weaker
(to) marginal when from is more solved. Full 15-test suite green; golden unaffected (self-skips off).
…esis, not validated)
Contributor
Author
|
Closing as superseded by #20. The FSQ emitter, |
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.
Summary
Three new/changed loss terms in the FSQ
label_dimspath, operating directly on the reserved-digit softmax (dim_lg) so a hard negative steers the move distribution, not the reconstruction matrix.HardNegTerm— bugfix (the negative trained nothing on the move)The FSQ path pushed on
c.recon=head.encode(stop-grad EMA target)= a function ofup_proj/down_projonly. The move lives indim_lg=level_proj(hid)— a separate Linear with no shared graph edge. Soloss_hard_neg.backward()filledup_proj/down_projand leftdim_lg/level_projat exactly zero — "don't emit this blunder" could not change which move the model commits to at any weight. The continuous (non-FSQ) path is unchanged.Reproduced deterministically in
tests/test_hardneg_reaches_move.py: positive control (loss_dims) puts grad 1.75 ondim_lg; the oldHardNegTermput 0.00 ondim_lg(1105 onup_proj). Now routes the query through the predicted emission (soft-argmax + straight-through) so grad reachesdim_lg/level_proj.MoveNegTerm— raw-codeword distribution penalty (building block)Penalizes
P(blunder) = Π softmax(dim_lg[digit])[code]directly. Empirically weak on its own — the raw codeword probability is ~0.002 (mass spread across 8 levels × several digits), ~60× too small to move the legal-move mass the decode metric reads. Kept as a building block (shares the codeword→index precompute) and documented as superseded.LegalMoveNegTerm— the win ✅Renormalizes over the per-row legal-move support set and penalizes the blunder's share =
Σ_neg P(neg) / Σ_legal P(legal)— the same space the decode metric reads (~10–35% blunder share). The renormalized gradient is ~50× larger thanMoveNegTerm's and actually reshapes mass.Generic (no chess logic): both the neg-subset and legal-support codeword→index tensors are precomputed once in
Trainer.__init__(padded[N_rows, max_K, n_reserved]long + count masks). The step loop does one batched gather+prod per set — no Python row loop. Self-skips when off → byte-identical to before.Validation
Tests (deterministic, CPU, tiny model):
test_hardneg_reaches_move— the fix reachesdim_lg(positive control guards the rig)test_moveneg_reaches_mass— blunder digit-level grad push-down, non-blunder levels push-up, non-reserved + STOP untouchedtest_legalneg_reaches_legal_mass— loss in the legal-renorm range [0.05, 0.95] (not the raw ~0.002), blunder grad ~6×MoveNegTerm, one SGD step lowers legal-renormP(blunder)0.85→0.03Full 14-test suite green.
golden_trainer_multi.npzregenerated (theHardNegTermfix changed the FSQ math — intentional, documented flow); identity + sensitivity pass.Empirical (chess, SmolLM2-135M full-FT, held-out shard_1, n=400):
The legal-move blunder mass drops monotonically with weight — the first negative form to move mass outside the noise floor (latent-cosine
HardNegTermand rawMoveNegTermboth left it flat):0.6 is the operating point (best cp-regret AND argmax-blunder); at 1.0 the argmax regresses as the term over-regularizes the base digit CE.
Breaking changes
None. All three terms self-skip when their weight ≤ 0 / field is
None/dim_lgis absent (non-FSQ), so existing configs are byte-identical. The one behavior change isHardNegTermon the FSQ path (it now actually trains the move) — that's the bugfix, and the golden reflects it.Out of scope (not in this PR)
kv_cacherollout — already onmain(separate prior PR)target_meanpool/frontier_field/text_neg_field— unrelated prior-session work, not included