Skip to content

#606 follow-up: clamp the three remaining unclamped q8_1 scale/sum emit sites - #1448

Closed
MatthewHines wants to merge 2 commits into
Niko1221:mainfrom
MatthewHines:fix/606-q8_1-unclamped-ds-sites
Closed

MatthewHines wants to merge 2 commits into
Niko1221:mainfrom
MatthewHines:fix/606-q8_1-unclamped-ds-sites

Conversation

@MatthewHines

@MatthewHines MatthewHines commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Three kernels still emit the (d, sum) pair of a q8_1 activation block with a raw make_half2(d, sum), bypassing the finite helper the rest of the tree has used since 19de7f4 / f6601bf (include/strata/kernels/q8_1_finite.hpp):

site path
fused_gr.cu — gr_q8_tail fused gate-reduce tail (QFUSE)
verify_kernels.cu — gdn_q8_1_store batched GDN verify store (QFUSE)
iq_kernels.cu — s26_swiglu_q8_1_kernel S26 swiglu quantize

A block whose amax overflows (or that reads a non-finite input) emits d = inf/NaN; fp16 turns the pair into NaN (or the pathological (0, 0)). Nothing downstream tests for finiteness — the dot kernels assume the producer upheld the invariant, because every other producer shares q8_1_ds(). A NaN scale poisons the fp16 dot of the quantized activation and every sum downstream of it through the layer stack; a fully non-finite logit row ends at the sampler's all-non-finite fallback — vocabulary id 0, the ! face of #606.

This is the same defect class already fixed at the other emit sites; these three were missed. The fix is the same one-liner pattern: route the pair through q8_1_ds(), which clamps both halves into the fp16-finite range (finite d, non-negative finite sum — the sum is a mean of |x|) and produces for the GPU paths the same bits the CPU pool and the already-clamped GPU sites produce for the same block. Content-blind producer-side clamp; nothing model-text-aware anywhere in it.

Verification

  • nvcc -std=c++20 -arch=sm_120 -c clean on all three changed translation units (CUDA 13.4.2).
  • Field evidence from a Qwen3-Next-class hybrid deployment (GDN/PLE + sparse experts, conversation cache + speculative decoding): the id-0 (!) degeneration face of After a 36,689-token request at 155k, every later request answers one repeated token until the engine reloads #606 appears on builds where the fused/S26 paths are enabled and is absent on boots that use the clamped path exclusively — consistent with the site inventory. The wider carried-state failure (restart-cures-it, spans sessions) is analyzed in docs/DEGENERATION_RCA.md; it involves two further purity violations of the state cache that this PR deliberately does not change code for.

Scope

3 clamp lines + 2 includes + 2 docs. No behavior, tuning, or policy changes. Deployment-side mitigations we use in the interim (freezing the expert migrator with the existing flag, disabling the suffix-draft source, serve-layer repetition detectors) are listed as prose in the RCA doc so maintainers can route them properly — no code for them here.

Docs added:

  • docs/Q8_1_FINITE_DS.md — the invariant, the missed sites, diagnostic criteria for this defect class.
  • docs/DEGENERATION_RCA.md — the full root-cause analysis: the restart-cure and cross-session-persistence localization levers, the three purity violations of state = f(prefix), the falsification tests that pinned each one, a field diagnostic protocol, and recommended engine-side remedies (tier-aware checkpoint identity for the residency split; reproducible policy inputs for the drafting side).

… q8_1_ds

Three kernels still emit the (d, sum) pair of a q8_1 activation block
with a raw make_half2(d, sum), bypassing the finite helper the rest of
the tree uses (q8_1_finite.hpp, from 19de7f4 / f6601bf):

  fused_gr.cu   gr_q8_tail                 - the fused gate-reduce tail (QFUSE)
  verify_kernels.cu  gdn_q8_1_store        - the batched GDN verify store (QFUSE)
  iq_kernels.cu  s26_swiglu_q8_1_kernel   - the S26 swiglu path

A block whose amax overflows (or that reads a non-finite input) emits
d = inf/NaN; fp16 makes the pair NaN (or the pathological (0,0)). Nothing
downstream tests for it: a NaN scale poisons the fp16 dot of the quantized
activation and every sum downstream of it, and a fully non-finite logit
row ends at the sampler's all-NaN fallback - vocabulary id 0, the '!'
face of the degeneration reported in Niko1221#606. Same defect class already
fixed at the other emit sites; these three were missed.

q8_1_ds() clamps both halves into the fp16-finite range (finite d,
non-negative finite sum - the sum is a mean of |x|), producing for the
GPU paths the same bits the CPU pool and the clamped GPU sites produce
for the same block. A clamp at the producer, content-blind: nothing
downstream of it is model-text-aware.

Criteria and the wider carried-state RCA: docs/Q8_1_FINITE_DS.md and
docs/DEGENERATION_RCA.md.
Q8_1_FINITE_DS.md: the invariant, the three missed sites, what a NaN
scale does downstream, the diagnostic criteria for this defect class.

DEGENERATION_RCA.md: the carried-state RCA behind the fix - restart-cure
and cross-session persistence as localization levers, the three purity
violations of the conversation cache's state = f(prefix) contract (this
defect, residency-tier numerics, wall-time-learned draft policy), the
falsification tests that pinned each one, and a field diagnostic
protocol. Recommendations for engine-side remedies; no behavior or
tuning changes included.
Niko1221 added a commit that referenced this pull request Oct 8, 2026
…the other producers do

The three sites fixed by the previous commit stored a clamped (d, sum) but still
quantized against the unclamped d, so an overflowing block stored int8 values
that no longer matched its stored scale. They now use q8_1_finite(d) and
q8_1_quant like native_quantize_q8_1_kernel, the swiglu quantizer and the CPU
pool. A block whose amax is finite and below 8.3M is stored bit for bit as
before.

Co-authored-by: Matthew Hines <matthewbretthines@gmail.com>
@Niko1221

Niko1221 commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Thank you, taken. The three emit sites now go through q8_1_ds(). On top of your change the three sites also use q8_1_finite(d) and q8_1_quant() for the scale and the int8 values, as native_quantize_q8_1_kernel, the swiglu quantizer and the CPU pool do, so a block that overflows stores values that match its stored scale. A block whose amax is finite and below 8.3M is stored bit for bit as before. Identity gates (Q2_0 on an RTX 5070, IQ3_XXS on an RTX 3060, also with STRATA_QFUSE=1 STRATA_LFUSE=1) are identical. The two docs files are not part of this port; the RCA doc makes engine claims we have not checked.

Closing as ported.

@Niko1221 Niko1221 closed this Oct 8, 2026
mobile2machan added a commit to mobile2machan/Strata that referenced this pull request Oct 8, 2026
128 commits: batch-groups auto per stage, server listen backlog 256, the
NVIDIA short-prompt CPU share, batched prompt reads (Niko1221#1323), the tokenizer
cache, the engine stall watchdog (Niko1221#1317 Niko1221#1407), conversation-cache eviction
(Niko1221#1347), prompt buffers sharing the embedding storage (Niko1221#1454), the Pascal
decode fix (Niko1221#1469), q8_1 scale clamps (Niko1221#1448), STRATA_STAGE_PIN opt-in (off:
on by default it corrupted IQ3_S decode after a long prompt), route-resident
and GDN-chunked opt-ins, gfx906 build fix, AMD docs and community benches.

main keeps 33af099 (AMD PCIe link speed/width and GPU name detection) and
a798ac0 (the k8v4 config read-back and 816 B/cell sizing).
MistVVK pushed a commit to MistVVK/XeStrata that referenced this pull request Oct 10, 2026
…U decode, the iq and native mmvq quantizers, the CPU lane) - from Strata Niko1221#1448: an activation past ~8.3M overflowed the scale to inf and the dot products to NaN; below that the output is bit-identical
Maddoxkkm added a commit to Maddoxkkm/Strata that referenced this pull request Oct 10, 2026
…tch-audit.py + fetched PR diffs)

Line-level audit of the eight patches applied on tuned-1403 against the tuned-141 tree and against
upstream 0.1.41: Niko1221#1323 and Niko1221#1372 are upstream's now, Niko1221#1381/Niko1221#1439/Niko1221#1451 are re-applied on 141, while
Niko1221#1416 (CPU share), Niko1221#1448 (q8_1 finite-ds) and Niko1221#1457 (HC scratch reuse) have no counterpart there.
patch-audit.py compares a diff's added code lines against a tree, which survives the rebases and
reworkings that defeat 'git apply -R --check'.
giridhart pushed a commit to giridhart/Strata that referenced this pull request Oct 15, 2026
… it, as the other producers do

The three sites fixed by the previous commit stored a clamped (d, sum) but still
quantized against the unclamped d, so an overflowing block stored int8 values
that no longer matched its stored scale. They now use q8_1_finite(d) and
q8_1_quant like native_quantize_q8_1_kernel, the swiglu quantizer and the CPU
pool. A block whose amax is finite and below 8.3M is stored bit for bit as
before.

Co-authored-by: Matthew Hines <matthewbretthines@gmail.com>
Ben1078 pushed a commit to Ben1078/Strata that referenced this pull request Oct 24, 2026
…1221#606/Niko1221#1448)

This branch's base is v0.1.40.3, which predates upstream's clamp of the last
three unclamped q8_1 activation emit sites (v0.1.41: 7a0e302 "clamp the three
unclamped q8_1 scale/sum emit sites via q8_1_ds", Niko1221#606, and e372d4f "q8_1 emit
sites: clamp the scale and quantize against it", Niko1221#1448).  fused_gr.cu's
gr_q8_tail, iq_kernels.cu's s26_swiglu_q8_1_kernel and verify_kernels.cu's
gdn_q8_1_store still stored the block's (d, sum) pair with a raw make_half2 and
quantized against an unclamped d, bypassing the finite helper the rest of the
tree uses.

A q8_1 block stores d = amax/127 and the block sum as fp16.  A sum past 65504
(one massive-activation dimension among the 32 is enough) rounds to inf, and d
overflows past amax = 8.3M; the dot products then read inf * 0 = NaN, and a
non-finite logit row ends at the sampler's all-NaN fallback.  The poison sits in
the conversation's K/V and recurrent state, so with the prompt cache every later
turn stays poisoned until the prompt is read again from token 0.

On this rig (RX 9060 XT + RX 6650 XT, Coder IQ1_M, 256K context, layer split 35,
MTP) the branch answered coherently up to ~115K-150K tokens of context, then
fell into a persistent local loop -- never before, and it did not clear once
started.  The base tree has zero raw make_half2(d, sum) sites; this branch had
three.

With the clamp, a full agent run crossed the whole failure band and completed:
deepest request 158,058 tokens of context, 12,577 generated, drafts accepted
6,934 of 11,756.  Every other change tried during the investigation (base
serve/frontend.py, the whole base serve/, build-flag changes, the two Q8_0
fused-kernel switches) was reverted first, so the only delta from the failing
build is these three lines.

Ported from upstream 7a0e302 (Matthew Hines) and e372d4f.  Bug report:
patches/BUG-q8_1-deep-context-loop.md; PR notes: patches/PR-q8_1-clamp.md.
Ben1078 added a commit to Ben1078/Strata that referenced this pull request Oct 24, 2026
hip: clamp the three q8_1 scale/sum emit sites (port of upstream Niko1221#606 / Niko1221#1448)
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.

2 participants