You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I was starting work on #442 (SIMD-ifying the MLA-absorb f32 reductions in attention_rows). The plan from that issue was to use the glm_tiny token-exactness oracle as the correctness gate — "a fixed-order SIMD reduction can stay bit-identical and self-validates against the oracle." Before touching any code I wanted to confirm the gate was green, so I ran:
SNAP=./glm_tiny TF=1 NGEN=0 ./colibri
I expected 32/32 (the engine self-documents this at colibri.c:6177: "Engine self-test: SNAP=./glm_tiny TF=1 ./glm 64 16 16 (expect 32/32)"). It returned 30/32, deterministically, on current dev (c98e5f8, post-#298). That stopped the #442 work cold — I could not use a 30/32 oracle as a "did my SIMD change break anything" gate without first understanding the 2 mismatches.
This issue tracks those 2 mismatches. They are not caused by the #442 SIMD change (I verified — see "Correctness gate" below).
The mismatch
Deterministic across 5 runs and across a fresh oracle regeneration. Same 2 positions every time:
PREFILL (teacher-forcing) C vs oracle: 30/32 positions
[ORACLE] mismatch pos=5 expected=34 got=197
[ORACLE] mismatch pos=25 expected=103 got=136
I spent several hours convinced this was an argmax or buffer-aliasing bug, because the DEBUG_LOGITS dump I wrote to investigate it appeared to show the expected token winning while pred held a different one. That was a bug in my own diagnostic (the selection-sort seeded bv=0 with no NaN guard, producing misleading top-K output). Once I fixed the diagnostic, the picture inverted completely: the engine is correct, the logits genuinely disagree with the oracle at these 2 positions by a near-tie margin.
With a corrected top-5 dump, both mismatches are clean near-ties — the engine's argmax picks the true top-1 of its own logits, and the oracle's expected token is a close #2:
So the C engine's forward pass produces a logit distribution that is very close to the Python transformers reference at every position, but at 2 positions the top-1/top-2 ordering flips. This is a small forward-pass numeric discrepancy, not a logic error.
What I ruled out
Oracle drift / stale weights. Regenerated glm_tiny + ref_glm.json from tools/make_glm_oracle.py — got a byte-identical reference (the generator is deterministic), and the fresh engine still scores 30/32.
GLM [gMASK]<sop> prefix. Neither the Python forward (make_glm_oracle.py:147, model(torch.tensor([full]))) nor the C forward_all prepends it; full_ids starts directly with the prompt ([3, 14, 159, ...]), no prefix tokens. Inspected GlmMoeDsaForCausalLM.forward — it takes input_ids as-is.
Non-determinism / OMP race. 5 identical runs, same result every time. Also reproduced at OMP_NUM_THREADS=1 (still 30/32, same positions).
NaN-pinning argmax (my leading wrong hypothesis — the test_logit_nan.c regression class). The mismatched logits are finite; isfinite(lo[best]) was 1 at both positions. Swapping the raw argmax loop for the NaN-safe argmax_v (sample.h) made no difference — as expected, since there were no non-finites to skip.
Buffer aliasing / mutation between argmax and memcpy. Probed lo[best] immediately after argmax and again immediately before the memcpy three lines later — identical values (Δ=0.0) at both positions. No mutation.
What I did not pin down
The actual source of the numeric divergence. The candidates, in rough order of likelihood, all live in forward_all → layers_forward → the MLA-absorb attention path or the RMSNorm/router upstream:
The idot path on lm_head. With g_idot on (banner: idot: avx2) and lm_head at fmt=1, matmul_qt quantizes x to int8 via qrow_i8 and computes dot_i8i8 * scale * sx — a different numeric computation than transformers's f32 lm_head. At a near-tie this could easily flip top-1/top-2. Would need to A/B with g_idot off (or lm_head at fmt=0) to confirm.
RMSNorm epsilon or the router matmul feeding subtly different hidden states into the final projection.
I stopped here because this is a forward-pass numeric-resolution investigation that needs careful per-stage A/B isolation, and it was pulling me away from the #442 work that surfaced it. The 30/32 floor is stable and well-characterized, so it can serve as a regression gate in the meantime (a correctness change should not move it).
Proposal: implement the advertised DEBUG_LOGITS dump
While investigating this, I discovered that colibri.c:6202 advertises a feature that does not exist:
Nothing in the engine reads DEBUG_LOGITS. Anyone who hit an oracle mismatch, read that hint, and set the env var got nothing — which is exactly the trap I fell into when I had to write my own dump to see these near-ties.
I have a 32-line implementation on a local branch (fix/tf-oracle-argmax-mismatch, commit a9d1ba5): forward_all optionally stashes the full [S, vocab] logit matrix; the TF block prints a NaN-safe top-K at every mismatch position (K via DEBUG_LOGITS_K, default 5). It is what produced the exact numbers above.
Happy to PR it if useful — it is a developer-facing debug tool, not a behavior change to the engine, so it may be too small to merit its own PR. Leaving the call to the maintainers; the diff is available if anyone wants it.
The #442 SIMD reductions PR (#481) was gated against this 30/32 floor: the SIMD change preserves the exact same 2 mismatches at the exact same positions with the exact same wrong tokens. So the floor is usable as a regression gate — just not at the documented 32/32.
How I found this
I was starting work on #442 (SIMD-ifying the MLA-absorb f32 reductions in
attention_rows). The plan from that issue was to use theglm_tinytoken-exactness oracle as the correctness gate — "a fixed-order SIMD reduction can stay bit-identical and self-validates against the oracle." Before touching any code I wanted to confirm the gate was green, so I ran:I expected 32/32 (the engine self-documents this at
colibri.c:6177: "Engine self-test: SNAP=./glm_tiny TF=1 ./glm 64 16 16 (expect 32/32)"). It returned 30/32, deterministically, on currentdev(c98e5f8, post-#298). That stopped the #442 work cold — I could not use a 30/32 oracle as a "did my SIMD change break anything" gate without first understanding the 2 mismatches.This issue tracks those 2 mismatches. They are not caused by the #442 SIMD change (I verified — see "Correctness gate" below).
The mismatch
Deterministic across 5 runs and across a fresh oracle regeneration. Same 2 positions every time:
Environment
dev@c98e5f8(post the cuda+engine: full fmt=4 (grouped int4 gs=64) support + diagnostic harness #298 fmt=4 dense/attention fix)-march=x86-64-v3(-O3 -fopenmp, no-ffast-math)transformers==5.13.1(well above the 5.11 interleaved-RoPE floor from make_glm_oracle.py on transformers 5.5.0 produces an oracle the engine scores 25/32 against (expected 32/32) #281)glm_tiny: 5 layers, hidden=128, kv_lora=32, vocab=256What it actually is (not an argmax bug)
I spent several hours convinced this was an argmax or buffer-aliasing bug, because the
DEBUG_LOGITSdump I wrote to investigate it appeared to show the expected token winning whilepredheld a different one. That was a bug in my own diagnostic (the selection-sort seededbv=0with no NaN guard, producing misleading top-K output). Once I fixed the diagnostic, the picture inverted completely: the engine is correct, the logits genuinely disagree with the oracle at these 2 positions by a near-tie margin.With a corrected top-5 dump, both mismatches are clean near-ties — the engine's argmax picks the true top-1 of its own logits, and the oracle's expected token is a close #2:
pos 5 (input token 58):
pos 25 (input token 103, inside the long 103-run at pos 14-25):
So the C engine's forward pass produces a logit distribution that is very close to the Python
transformersreference at every position, but at 2 positions the top-1/top-2 ordering flips. This is a small forward-pass numeric discrepancy, not a logic error.What I ruled out
glm_tiny+ref_glm.jsonfromtools/make_glm_oracle.py— got a byte-identical reference (the generator is deterministic), and the fresh engine still scores 30/32.make_glm_oracle.pyhard-fails below 5.11 anyway.[gMASK]<sop>prefix. Neither the Python forward (make_glm_oracle.py:147,model(torch.tensor([full]))) nor the Cforward_allprepends it;full_idsstarts directly with the prompt ([3, 14, 159, ...]), no prefix tokens. InspectedGlmMoeDsaForCausalLM.forward— it takesinput_idsas-is.OMP_NUM_THREADS=1(still 30/32, same positions).test_logit_nan.cregression class). The mismatched logits are finite;isfinite(lo[best])was 1 at both positions. Swapping the raw argmax loop for the NaN-safeargmax_v(sample.h) made no difference — as expected, since there were no non-finites to skip.lo[best]immediately after argmax and again immediately before thememcpythree lines later — identical values (Δ=0.0) at both positions. No mutation.What I did not pin down
The actual source of the numeric divergence. The candidates, in rough order of likelihood, all live in
forward_all→layers_forward→ the MLA-absorb attention path or the RMSNorm/router upstream:lm_head. Withg_idoton (banner:idot: avx2) andlm_headat fmt=1,matmul_qtquantizesxto int8 viaqrow_i8and computesdot_i8i8 * scale * sx— a different numeric computation thantransformers's f32lm_head. At a near-tie this could easily flip top-1/top-2. Would need to A/B withg_idotoff (orlm_headat fmt=0) to confirm.I stopped here because this is a forward-pass numeric-resolution investigation that needs careful per-stage A/B isolation, and it was pulling me away from the #442 work that surfaced it. The 30/32 floor is stable and well-characterized, so it can serve as a regression gate in the meantime (a correctness change should not move it).
Proposal: implement the advertised
DEBUG_LOGITSdumpWhile investigating this, I discovered that
colibri.c:6202advertises a feature that does not exist:"[ORACLE] %d/%d mismatches — run: TF=1 DEBUG_LOGITS=1 for top-5 logit dump\n"Nothing in the engine reads
DEBUG_LOGITS. Anyone who hit an oracle mismatch, read that hint, and set the env var got nothing — which is exactly the trap I fell into when I had to write my own dump to see these near-ties.I have a 32-line implementation on a local branch (
fix/tf-oracle-argmax-mismatch, commita9d1ba5):forward_alloptionally stashes the full[S, vocab]logit matrix; the TF block prints a NaN-safe top-K at every mismatch position (K viaDEBUG_LOGITS_K, default 5). It is what produced the exact numbers above.Happy to PR it if useful — it is a developer-facing debug tool, not a behavior change to the engine, so it may be too small to merit its own PR. Leaving the call to the maintainers; the diff is available if anyone wants it.
Correctness gate (for #442)
The #442 SIMD reductions PR (#481) was gated against this 30/32 floor: the SIMD change preserves the exact same 2 mismatches at the exact same positions with the exact same wrong tokens. So the floor is usable as a regression gate — just not at the documented 32/32.