Enable split-KV for paged FlashAttention decode - #32102
Enable split-KV for paged FlashAttention decode#32102Baiju Meswani (baijumeswani) wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Enables FlashAttention split-KV parallelism for eligible paged decode workloads.
Changes:
- Adds split-KV workspace allocation and dispatch plumbing.
- Extends the FlashAttention varlen API with split accumulators.
- Adds heuristic and CUDA end-to-end coverage.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
attention_split_heuristic_test.cc |
Tests split selection. |
paged_attention_op_test.cc |
Tests paged split-KV decode. |
paged_attention.cc |
Selects splits and allocates workspaces. |
paged_attention_impl.cu |
Passes split configuration to FlashAttention. |
flash_api.h |
Extends the varlen API. |
flash_api.cc |
Configures split-KV dispatch. |
attention_data.h |
Stores split workspace metadata. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.
Suppressed comments (1)
docs/contrib_ops/cuda/paged_attention.md:387
- This row is missing the
>blockquote prefix used by the surrounding table, so Markdown renders it outside the table and splits the documented bounds table. Keep the row inside the blockquote.
| split-KV eligibility | FlashAttention decode dispatch | `min_max_kv_len_for_split`, else disabled unless exact lengths were read back |
Tianlei Wu (tianleiwu)
left a comment
There was a problem hiding this comment.
Reviewed the split-KV dispatch, accumulator sizing, packed decode strides, metadata compatibility, and CUDA graph replay behavior. The follow-up cleanly separates the replay-wide upper bound used for fixed workspace sizing from the lower bound used for split eligibility; I found no active CUDA correctness blocker. One documentation formatting issue is noted inline.
1d9c58c to
0d0a484
Compare
0d0a484 to
f5be287
Compare
| | decode vs. prefill dispatch | static shapes: `query.shape[0] == cumulative_sequence_length.shape[0] - 1` | shapes are fixed for a captured graph | | ||
| | grid size, split count, gather/workspace extents | static capacity bound `max_kv_len_bound = block_table.shape[1] * block_size` | independent of step | | ||
| | grid size, gather/workspace extents | static capacity bound `max_kv_len_bound = block_table.shape[1] * block_size` | independent of step | | ||
| | split-KV eligibility | `min_max_kv_len_for_split` lower bound, when supplied | proves splitting is worthwhile on every replay | |
There was a problem hiding this comment.
It is cuda flash attention specified parameter so it might not fit other EPs.
There are a few options: (1) session option if the value is same for all paged attention node in a model. (2) run some offline experiment then use simple rules (like if...else...) to determine the value given input shapes.
Tianlei Wu (tianleiwu)
left a comment
There was a problem hiding this comment.
No correctness blocker found in the current head. The replay-wide lower bound addresses the earlier short-context dispatch concern, and the varlen API guards the split-combine assumptions. I left two non-blocking coverage suggestions below; the provider-neutral schema concern is already tracked in the existing attention_metadata thread.
| EXPECT_NE(debug_output.find("EffectiveKvLengthBound=256"), std::string::npos) << debug_output; | ||
| } | ||
|
|
||
| TEST(PagedAttention, Cuda_FlashSplitKvLongContext) { |
There was a problem hiding this comment.
Suggestion: Since this optimization is designed around replay-wide bounds, could this test capture and replay a CUDA Graph while updating device-resident past_seqlens and cumulative_sequence_length at stable addresses? Varying unequal sequence lengths across replays and comparing each result against the reference would catch regressions that freeze capture-time lengths or scratch state; a single ordinary Run cannot.
| max_query_len, data.max_kv_len, token_count, scale, softcap, /*is_causal*/ true, is_bf16, | ||
| local_window_size - 1)); | ||
| local_window_size - 1, /*max_num_blocks_per_seq*/ 0, /*page_block_size*/ 1, | ||
| data.flash_num_splits, data.flash_softmax_lse_accum, data.flash_out_accum)); |
There was a problem hiding this comment.
Suggestion: This changed launch also covers quantized caches after dense gather/dequantization, but the current split execution test uses only unquantized paged KV. Please add an INT8 or FP8 case with decoder attention disabled, GQA or unequal long lengths, and a verified NumSplits > 1 to exercise packed offsets/strides and the accumulator layout.
Summary
Enable
FlashAttention's existing split-KV path for paged decode.Split-KV divides long KV sequences across multiple CUDA thread blocks and combines their partial results. This improves GPU utilization during low-batch, long-context decoding.
The optimization is limited to safe cases:
Short contexts, local-window attention, prefill, and mixed-length query batches retain their existing paths.