Summary
On the default serve/chat sampling path (TEMP>0, 0<NUCLEUS<1), a single NaN or +Inf logit makes dist_build() produce an all-NaN distribution, after which the sampler silently emits token 0 forever — an unbroken run of one token, with no error printed. A diagnosable upstream numerical fault becomes silent corruption.
Mechanism (traced against glm.c)
dist_build() softmax:
float mx=lo[0]; for(int i=1;i<V;i++) if(lo[i]>mx) mx=lo[i];
double s=0; ...
for(int i=0;i<V;i++){ g_pbuf[i]=expf((lo[i]-mx)*invt); s+=g_pbuf[i]; }
for(int i=0;i<V;i++) g_pbuf[i]/=(float)s;
- A
NaN logit → g_pbuf[that]=expf(NaN)=NaN → s=NaN → every g_pbuf[i]/=(float)s is NaN.
- (
+Inf → mx=+Inf → expf(Inf-Inf)=NaN → same collapse.)
- Nucleus keep-loop
cum+=g_pbuf[...]; if(cum>=g_nuc) never triggers (NaN>=x is false) → keep=V, s2=NaN, still all-NaN.
dist_sample: cum>=u never true → fallback for(i=V-1..0) if(g_pbuf[i]>0) return i is false for all (NaN>0 false) → returns 0.
- Greedy path is equally blind:
argmax_v seeds bv=lo[0] and lo[i]>NaN is always false, so a NaN at index 0 pins the argmax to 0.
There is no isfinite/isnan guard anywhere between the logits and the sampler.
Reachability
The precondition is the default, not an edge case (TEMP auto≈0.7, NUCLEUS default 0.90). A non-finite logit is plausible in this NVMe-streaming engine: a garbage/short-loaded expert tile or an fp overflow in matmul_qt at a low-RAM eviction boundary. Both dist_build call sites are affected (the sampler and the speculative-verify path).
Impact
Not a fault originator — it needs an upstream NaN/+Inf. It's a fault amplifier: it turns a catchable numeric fault into silent, unbroken token-0 output. Medium severity: it defeats loud failure on the default path.
Fix (ready)
Minimal, O(1)/free on the happy path, on fix/logit-nan-guard (off dev) on my fork:
argmax_v: skip NaN (x==x) and seed from -inf → returns the max finite/+Inf entry, not a NaN-pinned 0.
dist_build: after the softmax sum, if s is non-finite or <=0, collapse g_pbuf to a one-hot over the finite argmax and warn once — degrade + diagnose, never silently corrupt.
test_logit_nan (wired into TEST_BINS): fails on stock dev, passes with the guard.
Heads-up: since #354 rewrites dist_build's internals (heap partial-select), the dist_build half of this guard should be re-placed on top of that once it lands — happy to open the PR against whichever base you prefer, or fold it into the #354 work. @woolcoxm
Summary
On the default serve/chat sampling path (
TEMP>0,0<NUCLEUS<1), a single NaN or +Inf logit makesdist_build()produce an all-NaN distribution, after which the sampler silently emits token 0 forever — an unbroken run of one token, with no error printed. A diagnosable upstream numerical fault becomes silent corruption.Mechanism (traced against
glm.c)dist_build()softmax:NaNlogit →g_pbuf[that]=expf(NaN)=NaN→s=NaN→ everyg_pbuf[i]/=(float)sisNaN.+Inf→mx=+Inf→expf(Inf-Inf)=NaN→ same collapse.)cum+=g_pbuf[...]; if(cum>=g_nuc)never triggers (NaN>=xis false) →keep=V,s2=NaN, still all-NaN.dist_sample:cum>=unever true → fallbackfor(i=V-1..0) if(g_pbuf[i]>0) return iis false for all (NaN>0false) → returns 0.argmax_vseedsbv=lo[0]andlo[i]>NaNis always false, so aNaNat index 0 pins the argmax to 0.There is no
isfinite/isnanguard anywhere between the logits and the sampler.Reachability
The precondition is the default, not an edge case (
TEMPauto≈0.7,NUCLEUSdefault 0.90). A non-finite logit is plausible in this NVMe-streaming engine: a garbage/short-loaded expert tile or an fp overflow inmatmul_qtat a low-RAM eviction boundary. Bothdist_buildcall sites are affected (the sampler and the speculative-verify path).Impact
Not a fault originator — it needs an upstream NaN/+Inf. It's a fault amplifier: it turns a catchable numeric fault into silent, unbroken token-0 output. Medium severity: it defeats loud failure on the default path.
Fix (ready)
Minimal, O(1)/free on the happy path, on
fix/logit-nan-guard(offdev) on my fork:argmax_v: skip NaN (x==x) and seed from-inf→ returns the max finite/+Inf entry, not a NaN-pinned 0.dist_build: after the softmax sum, ifsis non-finite or<=0, collapseg_pbufto a one-hot over the finite argmax and warn once — degrade + diagnose, never silently corrupt.test_logit_nan(wired intoTEST_BINS): fails on stockdev, passes with the guard.Heads-up: since #354 rewrites
dist_build's internals (heap partial-select), thedist_buildhalf of this guard should be re-placed on top of that once it lands — happy to open the PR against whichever base you prefer, or fold it into the #354 work. @woolcoxm