Fix packed-sequence masking in cudnn_flash_jax and add cutlass_flash GPU backend#4478
Open
bzantium wants to merge 3 commits into
Open
Fix packed-sequence masking in cudnn_flash_jax and add cutlass_flash GPU backend#4478bzantium wants to merge 3 commits into
bzantium wants to merge 3 commits into
Conversation
…GPU backend cudnn_flash_jax ignored decoder_segment_ids in training, so packed sequences attended across segment boundaries with no error. Lower segment ids to the cuDNN packed layout (per-segment seqlens/offsets, PADDING_CAUSAL) on Hopper and newer, and fall back to a fused flash backend on older GPUs where jax rejects the packed layout. Add attention=cutlass_flash: FlashAttention-2 (CUTLASS) via the optional flash-attn-jax package, with native GQA, sliding window, and packing via FA2's varlen kernel, plus a pallas kernel covering head_dim > 256 and logit soft cap. A shared backend dispatch serves attention=flash on GPU, cutlass_flash, and the cudnn_flash_jax fallback, validating dtype, head_dim, soft cap, and packed-metadata availability. Context parallelism is explicitly rejected for these backends. Validate max_segments_per_seq at config time for cudnn_flash_jax and cutlass_flash with packing, and size varlen cu_seqlens for the trailing padding run. Add a GPU test comparing each kernel against dot_product on packed batches (forward and grad, aligned and unaligned boundaries).
…ting pylint (pre-commit rev v3.3.8) flagged missing-function-docstring on setUp; pyink (pre-commit rev 24.10.1) reformatted two lines that a locally pinned older pyink left untouched.
…ton API gpu-unit CI failures traced to two issues, both environment-specific (the dev box that produced the original PR has 8 GPUs and jax 0.10.2): - gpu_attention_packing_test built its mesh from all visible devices, so a fixed batch size of 2 failed shard_map's divisibility check on CI's 4-GPU runner. Pin the mesh to a single device (as gpt3_test.py does), since this test checks kernel correctness against a reference, not multi-device sharding. - jax.experimental.pallas.triton.dot does not exist in jax 0.10.0, the repo's pinned floor (CI installs exactly that version); it was added in a later 0.10.x release. Fall back to the pre-move jax.experimental.pallas.dot alias, which works on both.
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.
Description
This PR fixes a silent correctness bug in the
cudnn_flash_jaxattention kernel and addscutlass_flash, an optional FlashAttention-2 (CUTLASS, viaflash-attn-jax) backend for GPUs, together with a pallas kernel used for the shapes FA2 cannot run. The two changes are coupled: the fused fallback that makes thecudnn_flash_jaxfix practical on pre-Hopper GPUs is provided by the new backend.Problem 1: cudnn_flash_jax ignores decoder_segment_ids
AttentionOp.cudnn_jax_flash_attentionreceiveddecoder_segment_idsbut its train branch called the jax cuDNN SDPA withmask_type=MaskType.CAUSALonly. With sequence packing enabled (the defaultpacking: truewith any real dataset), tokens attended across packed segment boundaries: no guard, no error, silently wrong loss and gradients. In a kernel-vs-reference test on packed batches, every token after the packing boundary diverged from thedot_productreference (max abs diff 5.4 vs 0.009 bf16 noise on the first segment).Fix, in order of preference at runtime:
q_seqlen/kv_seqlen[B, M]plusq_offsets/kv_offsets[B, M+1](absent segments filled with -1) withMaskType.PADDING_CAUSAL. This is the layoutjax._src.cudnn.fused_attention_stablehlo.dot_product_attentiondocuments for packed inputs.Packed layout requires a GPU with at least Hopper architecture), so the op falls back to a fused flash kernel that masks segment boundaries (see below) and logs the downgrade, instead of silently attending across boundaries.max_segments_per_seqis now validated at config time forcudnn_flash_jax(andcutlass_flash) with packing, matching the existingcudnn_flash_tevalidation.Problem 2: no fast, correct packed attention on pre-Hopper GPUs
On A100 we found no working fused path for packed sequences: the jax cuDNN packed layout is Hopper-only, and
cudnn_flash_te(TE 2.17, cuDNN 9.24) finds no sm80 THD kernel and then crashes in its unfused fallback (sequence_descriptor must be None or ndarray). The remaining correct option, unfuseddot_product, costs about 39 percent of step throughput at seq 2048 on a qwen3-4b benchmark.Key changes
attention=cutlass_flash: FlashAttention-2 CUTLASS kernels through the optionalflash-attn-jaxpackage (import-guarded; the option errors clearly when the package is absent). Native GQA/MQA, sliding window (LOCAL_SLIDING), and sequence packing lowered to FA2's varlen kernel withcu_seqlensbuilt inside jit. Requires bf16/fp16 and head_dim <= 256; decode falls back to unfused attention.src/maxtext/kernels/attention/gpu_pallas_flash.py: a fork ofjax.experimental.pallas.ops.gpu.attentionextended with sliding-window masking, gemma2-style logit soft cap (with the matching backward chain rule), and chunked head_dim accumulation for head_dim > 256, so every layer FA2 cannot run still has a fused kernel.AttentionOp._gpu_flash_backend_dispatch) now servesattention=flash(GPU),attention=cutlass_flash, and the pre-Hopper packed fallback ofcudnn_flash_jax: FA2 where eligible, pallas otherwise. Eligibility covers dtype (bf16/fp16), head_dim, soft cap, and packed-metadata availability, so no configuration silently loses segment masking.cudnn_flash_teremains the kernel to use with context parallelism.Measurements (A100-SXM4-80GB x 8, qwen3-4b, bf16, seq 2048, per-device batch 8, synthetic data)
Shortcomings / future work
cudnn_flash_jax(seqlens/offsets) is implemented per the jax API contract and covered by the new test, but our verification hardware is A100; a run oftests/unit/gpu_attention_packing_test.pyon H100 or newer would exercise it directly.SequenceDescriptorassertion) is a separate upstream issue and is not addressed here.FIXES: #4476
Tests
New test:
tests/unit/gpu_attention_packing_test.py(markedgpu_only). For each kernel (cudnn_flash_jax,cutlass_flash, and the pallas backend forced by hiding the cutlass library), it compares the forward output and the gradient wrt query against thedot_productreference on an unpacked control, a packed batch, and a packed batch with a tile-unaligned boundary, reporting relative error separately before and after the packing boundary so cross-segment leaks are directly visible. A host-side test covers the segment-ids-to-seqlens/offsets encoding.Results on A100 (seq 2048, GQA 32/8 heads, bf16): all kernels match the reference within 2 percent relative error on both forward and gradients, for all three boundary cases. Reproduce with:
pyink(23.10.0, repo settings) andpylint(repopylintrc) pass on the changed files.Checklist
Before submitting this PR, please make sure (put X in square brackets):
gemini-reviewlabel.