Skip to content

[CUDA] Enable non-causal PagedAttention with small cache pages - #32619

Merged
Tianlei Wu (tianleiwu) merged 2 commits into
mainfrom
copilot/support-causal-0-in-paged-attention
Sep 21, 2026
Merged

Tianlei Wu (tianleiwu) merged 2 commits into
mainfrom
copilot/support-causal-0-in-paged-attention

Conversation

Copilot AI commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Description

  • Backends: Remove the FlashAttention-only restriction for is_causal=0; support non-causal MEA, paged decode, XQA speculative decoding, and latent attention.
  • Masking: Allow each query to attend through the full live KV sequence while keeping left windows anchored to its own position.
  • Coverage: Add regression cases for head size 128 with 16/32/64-token pages, ragged batches, and backend-specific masking; update CUDA documentation.

Motivation and Context

DFlash2’s non-causal drafter uses head size 128, for which FlashAttention requires pages of at least 128 tokens. A valid 64-token page therefore fails the drafter’s PagedAttention node and forces target-only decoding for the rest of the run.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@tianleiwu
Tianlei Wu (tianleiwu) marked this pull request as ready for review September 15, 2026 23:38
Copilot AI balanced review requested due to automatic review settings September 15, 2026 23:38
@tianleiwu Tianlei Wu (tianleiwu) changed the title Enable non-causal CUDA PagedAttention with small cache pages [CUDA] Enable non-causal PagedAttention with small cache pages Sep 15, 2026

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.

🔵 Needs a closer look

The changes affect several specialized CUDA attention kernels and require hardware-backed validation across backend configurations.

Pull request overview

Enables non-causal CUDA PagedAttention across MEA, paged decode, XQA, and latent-attention backends.

Changes:

  • Propagates causal mode through CUDA kernels and adjusts masking.
  • Supports non-causal left-local windows in CUTLASS FMHA.
  • Adds backend, page-size, ragged-batch, and masking regression coverage.

No actionable findings identified.

File summaries
File Description
test_paged_attention.py Adds non-causal backend regression tests.
paged_attention.cc Removes Flash-only non-causal dispatch restriction.
paged_attention_impl.cu Implements backend-specific non-causal masking.
kernel_forward.h Supports non-causal left-window masking.
fmha_launch_template.h Configures CUTLASS masking offsets.
paged_attention.md Documents CUDA non-causal support.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@tianleiwu
Tianlei Wu (tianleiwu) force-pushed the copilot/support-causal-0-in-paged-attention branch from f5c8354 to 6a3edab Compare September 17, 2026 07:41
Co-authored-by: tianleiwu <30328909+tianleiwu@users.noreply.github.com>

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.

I found one blocking dispatch issue. The kernel changes and test coverage otherwise look good.

Tianlei Wu (tianleiwu) added a commit to microsoft/onnxruntime-genai that referenced this pull request Sep 17, 2026
### Description

The builder required `paged_block_size` to be a multiple of 256,
documented as "required by the ONNX Runtime PagedAttention CUDA kernel".
That is not the op's contract, and the rule is wrong in both directions.

`CheckInputs` in
[`paged_attention_helper.h`](https://github.com/microsoft/onnxruntime/blob/main/onnxruntime/contrib_ops/cpu/bert/paged_attention_helper.h)
requires a **power of two of at least 16**. The multiple-of-256 figure
comes from one FlashAttention tile size; `paged_attention.cc` treats
that as a backend-selection input, not a validity check, and falls back
to PagedDecode or MemoryEfficientAttention when the page is smaller. A
quantized KV cache is exempt from the alignment check alone
(`flash_block_size_ok = kIsQuantizedCache || block_size % tile == 0`);
it still reaches FlashAttention, through a dense dequantized path that
has no page to align.

So the old rule:

* **rejected valid values.** 16, 32, 64 and 128 all build and run.
* **accepted an invalid one.** 768 passes `% 256 == 0`, exports without
complaint, and then fails on the first forward pass: `block_size must be
a power of two and at least 16. Got block_size == 768`. (`CheckInputs`
runs from `ComputeInternal`, not at load.)

This replaces it with the op's actual contract and corrects the
documentation.

### Motivation and Context

Smaller pages cut the internal fragmentation of the KV pool, which
matters most for short sequences and high batch counts, and a quantized
cache pays nothing for them. They were simply unavailable.

### Testing

New `test/python/builder/test_paged_block_size.py`: 16 through 2048, the
string form Olive and the CLI pass, sub-16, non-powers-of-two (including
768), and non-positive values. 17 cases.

`test_precision.py` asserted that 128 was rejected; it now asserts that
768 is, which is the case the old rule got wrong. Full builder suite:
717 passed, 3 skipped.

Measured end to end on Qwen3.8-27B (H200, bf16, unquantized KV,
`head_size` 256, DFlash2 drafter), 200 MMLU-Pro questions at concurrency
8, with `num_blocks` scaled to hold the KV pool at 262144 tokens:

| `paged_block_size` | tok/s | drafts/round | output vs 256 |
| --- | --- | --- | --- |
| 64 | 161.0 | 0.00 | 84/200 identical |
| 128 | 374.6 | 7.00 | 200/200 identical |
| 256 (default) | 381.9 | 7.00 | reference |
| 512 | 369.8 | 7.00 | 200/200 identical |

128, 256 and 512 are byte-identical and their throughput spread is
inside this harness's noise, so the default stays the right choice for
this model.

### A note on the 64 column, and why it is not in the code

The 64 row is not a block-size effect. On today's `main`, PagedAttention
serves `is_causal=0` from FlashAttention only, and the DFlash2 drafter
has `head_size` 128, so a 64-token page makes the drafter's node fail
and the engine falls back to target-only decoding for the rest of the
run:

```
PagedAttention: is_causal=0 requires the FlashAttention backend
(sm>=80, fp16/bf16, head_size 128, block_size 64).
```

microsoft/onnxruntime#32619 removes exactly that restriction: it gives
`is_causal=0` non-causal MEA, paged decode, XQA and latent backends, and
adds head-size-128 regression cases at 16/32/64-token pages. Once it
lands, a small page stops being fatal for a block drafter and becomes
what it already is for the target, a backend-selection input.

Because of that, this PR deliberately keeps the runtime detail **out**
of the source. The comments in `builder.py`, `block_drafter.py` and the
README state only what stays true either way: the tile is a
per-head-size throughput choice, not a validity rule, and a drafter
usually has the smaller head size and therefore the larger tile, so the
block that is fastest for the target is not necessarily the one that is
fastest for the drafter. The `TODO` in `block_drafter.py` is scoped to
that, giving the drafter its own block size instead of inheriting the
target's, which is what a vLLM-style 16-token target page would need.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
@tianleiwu
Tianlei Wu (tianleiwu) enabled auto-merge (squash) September 17, 2026 21:52
@tianleiwu
Tianlei Wu (tianleiwu) merged commit 51bda5f into main Sep 21, 2026
94 of 96 checks passed
@tianleiwu
Tianlei Wu (tianleiwu) deleted the copilot/support-causal-0-in-paged-attention branch September 21, 2026 00:57
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.

4 participants

⌂ 换网址