Repository navigation
Conversation
RFC FC-17: for D>128 the launcher shrinks kBc to 64, so the Q tile area holds two K stages while only the last one was gated by q_consumed (the prologue's K TMA then raced the consumer Q s2r and corrupted ~15% of the upper tile-half rows, non-deterministically). Move the wait to the first stage that maps into the Q area, plus two static asserts for the layout invariants it relies on. D=192 O_err 3.13 -> 0.013, deterministic across runs, D<=128 bitwise unchanged, perf within +-1%; 108-config matrix has no gross-error rows.
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.
Problem (RFC FC-17)
--cuda-impl fp8 --D 192returned wrong rows:O_err ≈ 3.1(fp8 dense noise floor is ~0.02-0.05),allclose=False. The error is confined to the second half of everykBr=128tile (row % 128 ∈ [64,127]), hits all 32 heads, is non-deterministic across runs (bad-row set Jaccard ≈ 0.38), is independent of every quantization knob, and is absent on the fp16 CUTE path (same D) and on the sm89 fp8 path.Root cause
cute/fp8/sm_120/persist_d.cuhdrops the Q tile from the smem allocation and lets the K stages reuse that area (kPersistQs2r). The hand-off only waited for the consumer Q s2r before the last logical stage's first TMA (k_tile == kStagesK-1) — correct only while the Q tile holds a single K tile, i.e. whilekBc == kBr(D ≤ 128).For D > 128 the launcher shrinks
kBcto 64, sokQTileElements/kKTileElements = kBr/kBc = 2: the Q tile holds two K tiles and the prologue's stage 0 (physical slot 1) also lands inside the live Q tile with no guard. Its TMA raced the consumers' Qldmatrixs2r and the Q area ended up holding whichever data arrived last.That single defect explains every fingerprint: only the
kBc < kBrbranch is affected (D = 160/192/224, not just 192); the bad rows sit in the upper half because those rows lose 2/3 of their D range to the overlap while the lower half loses 1/3 (measurably degraded too — lower-half mean 0.0505 / max 0.32 vs the 0.0015 noise floor — just below the 0.3 gross-error threshold); it floats across runs; fp16 is clean because itsk_base = q_base + kQTileElementsnever overlaps; sm89 is clean becausekBr == kBcthere, so only one slot can land in the Q area.Fix
Move the
q_consumedwait to the first stage that maps into the Q area:kQReuseSlots = kQTileElements / kKTileElements,kQGuardStage = (kPersistQs2r && kQReuseSlots > 1) ? 0 : kStagesK - 1q_consumedwhens == kQGuardStage; the main-loop guard becomesk_tile == kQGuardStageThe two guard sites are disjoint in
k_tile(prologues ∈ [0, kStagesK-2], main loopk_tile ≥ kStagesK-1), so exactly one wait runs;kQReuseSlots == 1(D ≤ 128) keeps the previous codegen. Two static asserts now pin the invariants the reuse relies on (the Q tile holds a whole number of K tiles; the K region spans the Q tile area, else the V prologue would alias it unguarded).Verification (RTX PRO 5000, sm_120)
max_abs/mean_abs)--cuda-impl fp8 --D 192 --N 8192O_err0.0132 / 0.0527 / 0.0146 / 0.0166 / 0.0137 / 0.0132)stages=2andstages=3both clean and numerically identical;FFPA_FP8_SM89_FORCE=1(simulated sm89 at D=192) clean.Tests / docs
tests/test_ffpa_fp8.py:D=192added to the parity sweep, plus a dedicatedtest_fp8_persist_d_q_reuse_race. It must use unscaled fixtures — with the usualrandn * 0.5the corruption stays inside tolerance (pre-fix 0.0137 → PASS), so the scaled sweep cannot see it. Red-green confirmed against the pre-fix binary.docs/user_guide/install.md: the publicD=192warning is now a version bound (affected ≤ 0.2.4, fixed on the 0.2.5 dev series) and its scope is corrected (D > 128window; sm89 not affected).ffpa-cuda-understand): FC-17 checked off with root cause, evidence and the follow-up.Known gaps
D=160/224share the branch by construction but could not be measured here (not in the compiled headdim set; adding 224 fails an unrelated sm89D % 64 == 0static assert).