Skip to content

sampling: a single NaN/+Inf logit makes dist_build emit token 0 forever, silently (default path) #369

Description

@KingIcyCreamProjects

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)=NaNs=NaN → every g_pbuf[i]/=(float)s is NaN.
  • (+Infmx=+Infexpf(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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions