Repository navigation
[Triton/Gluon] FlashKDA in-kernel paged state_cache I/O - #5754
Conversation
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
PR title tags & labels: |
e305d77 to
78bba27
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Unresolved safety, output-semantics, performance, and test-coverage issues remain.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (5)
What changed in this PR
Adds in-kernel fp32 paged state-cache I/O to FlashKDA, avoiding dense gather/scatter operations.
Changes:
- Adds paged cache, slot indices, initial-state flags, and caller-owned output support.
- Implements cache reads/writes for segmented and unsegmented paths.
- Adds cache equivalence, stride, initialization, fallback, and dispatch tests.
| File | Summary |
|---|---|
op_tests/triton_tests/chunk_delta_attn/test_flash_kda.py |
Adds paged-cache tests; coverage should use the public wrapper and include multi-sequence, permuted-slot cases. |
aiter/ops/triton/kimi_delta_attn/chunk_delta_attn.py |
Extends the public FlashKDA API for paged state-cache arguments. |
aiter/ops/triton/_triton_kernels/chunk_delta_attn/flash_kda.py |
Implements cache I/O; unresolved issues include outer-stride validation, masked zero-state loads, synchronous index validation, and non-contiguous output handling. |
aiter/ops/triton/_triton_kernels/chunk_delta_attn/chunk_fwd.py |
Dispatches FlashKDA cache requests, but supplied out is ignored when the default pipeline is selected. |
aiter/ops/triton/_triton_kernels/chunk_delta_attn/chunk_delta_attn_utils.py |
Preserves cache storage and strides through input guarding. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
78bba27 to
473fb27
Compare
Serving can pass the V-first paged pool with state_indices instead of gather/scatter around the kernel. The cache is not packed so padded stride(0) writes the live pool; the default KDA path rejects the argument rather than dropping it. Co-authored-by: Cursor <cursoragent@cursor.com>
473fb27 to
dcfdf74
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Positional cache/output handling and missing multi-sequence coverage leave moderate correctness issues unresolved.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
| args = tuple( | ||
| a.contiguous() if isinstance(a, torch.Tensor) else a for a in args | ||
| ) |


Motivation
FlashKDA still spoke a dense ABI:
initial_state[N, H, V, K]plus a freshly allocatedout. vLLM Kimi-K3 serving keeps recurrent state in a paged pool[slots, H, V, K]withstate_indices/has_initial_state.Wiring that through AITER meant:
gather_initial_states(orstate_cache[indices]) into a dense tensorrun
chunk_kimi_delta_attnscatter
final_statebackout.copy_(kernel_out)Those copies sit in the serving timer. On MI350, a K3-shaped FlashKDA prefill paid ~40–60 µs for them (4K: 171 → 130 µs, 16K: 499 → 440 µs with the same segment pin). The fused vLLM path already reads and writes the pool in-kernel (vLLM #54038); this PR gives FlashKDA the same cache contract so a later vLLM dispatch (#56036) does not have to gather around AITER.
Technical Details
Public kwargs on
chunk_kimi_delta_attn/flash_kda_fwd:state_cache: fp32 V-first[slots, H, V, K]. Each slot plane is dense;stride(0)may pad.state_indices: int32[N]has_initial_state: bool[N]; false starts that sequence from zero and does not read a dirty slotout: required withstate_cache, same shape asv(K2 still loadsv, sooutmust not alias it)K2 and the scan load/store
slot * stride(0) + head * V * K + …. The last segment writes the final state back to the same row. Segmented pass A does not bindstate_cache(keeps the existing K2 binary / autotune; binding it here previously ~2× slowed K2).@input_guard(skip=("state_cache",))leaves the pool’s storage andstride(0)alone. Other operands are still packed. The skip is an AITER decorator default, not a vLLM flag; the caller just passesstate_cache=as a keyword.Dense
initial_stateis unchanged and mutually exclusive withstate_cache.final_stateisNonewhen the cache is used.The default five-kernel path indexes a dense
h0and is GVA / C=64 / K-first. Ifstate_cacheis set and FlashKDA is not selected,chunk_delta_attn_fwdraises instead of ignoring the argument.Test Plan
pytest op_tests/triton_tests/chunk_delta_attn/test_flash_kda.py \ -k 'paged_cache or flash_kda' -qAdded cases:
test_paged_cache_matches_dense— paged vs denseinitial_state, bit-identicaloutand selected slots; empty slots untouched; unsegmented and segmentedtest_paged_cache_has_initial_state_false— false flag matches a zeroinitial_statetest_paged_cache_padded_stride_writes_live_pool— non-contiguousstride(0)updates the original storage pointertest_paged_cache_rejected_on_default_pipeline— non-FlashKDA call withstate_cacheraisesServing-shaped A/B (K3 H=12, D=128,
lower_bound=-5, varlencu_seqlens)vs gather and vs vLLM fused prefill on gfx950.
Test Result
Paged vs dense FlashKDA:
torch.equalon output and selected cache rows (rel err 0).Paged FlashKDA vs vLLM fused prefill (same op, not e2e): output rel L2 ~4.8e-3, state ~2.5e-3 at 4K/16K.
MI350X, official vLLM ROCm image, serving-style call (µs, smaller is better):
--segment 8Default-pipeline reject and padded-stride tests pass.
Submission Checklist