[CuTe, SM100] Fix FP8 e4m3 accuracy: make max_offset dtype-aware to avoid P saturation - #2717
Merged
Merged
Conversation
With rescale_threshold=4 the online-softmax row max can be stale by up to 4 (in log2 units), so P reaches 2^(max_offset + 4). max_offset=8 puts that at 4096, past e4m3fn's 448 ceiling: the largest probabilities saturate on the f32->fp8 satfinite convert and e4m3 accuracy degrades below e5m2 (up to 1.6x worse rel_l2, growing with seqlen). Cap max_offset at 4 for e4m3 so the worst case is 2^8 = 256 <= 448; e5m2 keeps 8 (57344 ceiling absorbs the overshoot). B200: restores e4m3 to ~2x lower error than e5m2 across seqlen 256-4096, uniform and peaked softmax, matching quantization-only emulation; LSE consistent; fwd timing unchanged (0.387 vs 0.390 ms, hd128 s4096). Related: Dao-AILab#2716
Running the suite with dtype=float8_e4m3fn has bit-rotted: - the test sets requires_grad on fp8 tensors, which the interface now rejects (FP8 is forward-only); gate it on non-fp8 dtypes. - it generates random descales and applies them in attention_ref, but the flash_attn_func call site has no descale kwargs (only _flash_attn_fwd takes them), so kernel and reference disagreed by construction; stop generating them. With these, the fp8 sweep runs cleanly (378 cases on SM100 with the e4m3 max_offset fix; 190 of them fail without it). fp8 stays out of the default dtype parametrize. Related: Dao-AILab#2716
yunweili3
marked this pull request as draft
July 18, 2026 07:33
yunweili3
marked this pull request as ready for review
July 19, 2026 06:03
Johnsonms
approved these changes
Jul 19, 2026
This was referenced Jul 22, 2026
Open
MatthewBonanni
pushed a commit
to MatthewBonanni/flash-attention
that referenced
this pull request
Jul 22, 2026
…void P saturation (Dao-AILab#2717) * [CuTe, SM100] Make FP8 max_offset dtype-aware to avoid e4m3 P saturation With rescale_threshold=4 the online-softmax row max can be stale by up to 4 (in log2 units), so P reaches 2^(max_offset + 4). max_offset=8 puts that at 4096, past e4m3fn's 448 ceiling: the largest probabilities saturate on the f32->fp8 satfinite convert and e4m3 accuracy degrades below e5m2 (up to 1.6x worse rel_l2, growing with seqlen). Cap max_offset at 4 for e4m3 so the worst case is 2^8 = 256 <= 448; e5m2 keeps 8 (57344 ceiling absorbs the overshoot). B200: restores e4m3 to ~2x lower error than e5m2 across seqlen 256-4096, uniform and peaked softmax, matching quantization-only emulation; LSE consistent; fwd timing unchanged (0.387 vs 0.390 ms, hd128 s4096). Related: Dao-AILab#2716 * [CuTe, Tests] Unrot the FP8 dtype path in test_flash_attn_output Running the suite with dtype=float8_e4m3fn has bit-rotted: - the test sets requires_grad on fp8 tensors, which the interface now rejects (FP8 is forward-only); gate it on non-fp8 dtypes. - it generates random descales and applies them in attention_ref, but the flash_attn_func call site has no descale kwargs (only _flash_attn_fwd takes them), so kernel and reference disagreed by construction; stop generating them. With these, the fp8 sweep runs cleanly (378 cases on SM100 with the e4m3 max_offset fix; 190 of them fail without it). fp8 stays out of the default dtype parametrize. Related: Dao-AILab#2716
MatthewBonanni
pushed a commit
to MatthewBonanni/flash-attention
that referenced
this pull request
Jul 22, 2026
…void P saturation (Dao-AILab#2717) * [CuTe, SM100] Make FP8 max_offset dtype-aware to avoid e4m3 P saturation With rescale_threshold=4 the online-softmax row max can be stale by up to 4 (in log2 units), so P reaches 2^(max_offset + 4). max_offset=8 puts that at 4096, past e4m3fn's 448 ceiling: the largest probabilities saturate on the f32->fp8 satfinite convert and e4m3 accuracy degrades below e5m2 (up to 1.6x worse rel_l2, growing with seqlen). Cap max_offset at 4 for e4m3 so the worst case is 2^8 = 256 <= 448; e5m2 keeps 8 (57344 ceiling absorbs the overshoot). B200: restores e4m3 to ~2x lower error than e5m2 across seqlen 256-4096, uniform and peaked softmax, matching quantization-only emulation; LSE consistent; fwd timing unchanged (0.387 vs 0.390 ms, hd128 s4096). Related: Dao-AILab#2716 * [CuTe, Tests] Unrot the FP8 dtype path in test_flash_attn_output Running the suite with dtype=float8_e4m3fn has bit-rotted: - the test sets requires_grad on fp8 tensors, which the interface now rejects (FP8 is forward-only); gate it on non-fp8 dtypes. - it generates random descales and applies them in attention_ref, but the flash_attn_func call site has no descale kwargs (only _flash_attn_fwd takes them), so kernel and reference disagreed by construction; stop generating them. With these, the fp8 sweep runs cleanly (378 cases on SM100 with the e4m3 max_offset fix; 190 of them fail without it). fp8 stays out of the default dtype parametrize. Related: Dao-AILab#2716 Signed-off-by: Matthew Bonanni <mbonanni@redhat.com>
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.
Proposed fix #2716.
Problem
On the SM100 FP8 forward, inputs for e4m3 consistently produced higher error than e5m2 (up to 1.6× worse rel_l2, growing with seqlen), while FP8 quantization theory — and the reporter's CPU emulation in #2716 — predicts e4m3 should actually be around 2× better.
Root cause
Two FP8 constants interact:
max_offset = 8: P is computed asexp2(scale·s − scale·row_max + 8), which scales scaling probabilities into(0, 256]before the f32→fp8 convert.rescale_threshold = 4.0: when a new KV block raises the row max by less than 4 (log2-scaled units), the kernel will keep the stale row max to skip an O-rescale.With a stale max, P can reach
2^(8+4) = 4096. The f32→fp8 conversion is satfinite: e5m2 (max 57344) absorbs the overshoot, but e4m3fn (max 448) clamps the largest attention weights — precisely the entries that dominate each row — by up to 9×.A secondary effect:
row_sumaccumulates the unclamped f32 P while the P·V MMA consumes the clamped fp8 P, so normalization is also inconsistent.Fix
Keep the rescale-skip (it's a perf optimization and is innocent once the budget is respected) and enforce
max_offset + rescale_threshold ≤ log2(fp8_max)per dtype:max_offset = 4for e4m3fn (worst case2^8 = 256 ≤ 448), unchanged8for e5m2.Both definitions (softmax warp and correction warp, with
max_offset_scale = 2^offset) would be updated consistently, and the LSE formula is self-consistent w.r.t. the offset. The only cost is P's flush-to-zero floor moving frommax·2^-17tomax·2^-13— negligible softmax tail mass.A second commit un-rots the fp8 dtype path in
test_flash_attn_output(it setrequires_gradon fp8 tensors, now rejected by the forward-only interface, and generated descales that only the reference applied —flash_attn_funchas no descale kwargs). fp8 stays out of the default parametrize; this just makes the sweep runnable.Evidence (1× B200, CUDA 13.0, cutlass-dsl 4.6.0.dev0, base 77aacb6)
The following is the reporter's geometry (b=2, h=32, hkv=2, d=128, GQA 16:1, per-(b,hkv) amax scaling, rel_l2 vs unquantized-fp32 reference, via
_flash_attn_fwdwith descales):After the fix, e4m3 lands exactly on the quantization-error floor and is approximately 2 times better than e5m2, as expected. LSE max error ≤ 1.1e-4 both formats.
Test suite (420-case fp8-e4m3
test_flash_attn_outputsweep: seqlens 64/128 … 4096/4096, d 64–256, causal × sink × mha/gqa/mqa, pack_gqa × num_splits inner sweep):bf16 unaffected (dtype-gated change): the CI
FA4_TEST_FILTER8-case suite passes.Perf parity (hd128, s4096, b2 h32, median of 200): e4m3 0.387 ms (fix) vs 0.390 ms (main); e5m2 0.386 ms both — the fix is a different constant in an existing FMA.
Notes
flash_fwd_mla_sm100.py) is unaffected, since it usesrescale_threshold=0and no max offset.rescale_threshold=0for FP8 — recovers identical accuracy but costs the skip optimization, meaning the measured numbers were equivalent.