Skip to content

Enable split-KV for paged FlashAttention decode - #32102

Open
Baiju Meswani (baijumeswani) wants to merge 4 commits into
mainfrom
baijumeswani/paged-flash-split-kv
Open

Enable split-KV for paged FlashAttention decode#32102
Baiju Meswani (baijumeswani) wants to merge 4 commits into
mainfrom
baijumeswani/paged-flash-split-kv

Conversation

@baijumeswani

Copy link
Copy Markdown
Contributor

Summary

EnableFlashAttention'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:

  • decode with one new token per request;
  • global attention without attention sinks;
  • KV sequences longer than 512 tokens.

Short contexts, local-window attention, prefill, and mixed-length query batches retain their existing paths.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread onnxruntime/contrib_ops/cuda/bert/paged_attention.cc Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 |

@tianleiwu Tianlei Wu (tianleiwu) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/contrib_ops/cuda/paged_attention.md Outdated
Base automatically changed from baijumeswani/paged-attention-telemetry to main August 15, 2026 05:05
@baijumeswani
Baiju Meswani (baijumeswani) force-pushed the baijumeswani/paged-flash-split-kv branch from 1d9c58c to 0d0a484 Compare August 15, 2026 05:05
@baijumeswani
Baiju Meswani (baijumeswani) force-pushed the baijumeswani/paged-flash-split-kv branch from 0d0a484 to f5be287 Compare August 15, 2026 05:24
| 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 |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@tianleiwu Tianlei Wu (tianleiwu) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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.

3 participants