Replace chunk prefill with AppendKV FMHA prefill - #307
Conversation
There was a problem hiding this comment.
Pull request overview
This PR replaces the prior two-launch “chunkprefill” mixed-batch path with a single prefill kernel path that supports in-kernel KV append (AppendKV), routing mixed/prefill batches through prefill::mha_fwd while keeping pure paged decode on decode::mha_fwd. It also wires optional k_new/v_new/cu_seqlens_k_new through the Python → Torch op → native SYCL layers and expands tests/benchmarks accordingly.
Changes:
- Extend the public op/API surface to accept optional AppendKV inputs (
k_new,v_new,cu_seqlens_k_new) and plumb them into the prefill mainloop. - Update kernel mainloop/epilogue to support AppendKV prefill (and remove/limit unsupported fp8 prefill variants as described).
- Enable AppendKV coverage in
test_flash_attn_kvcacheand align benchmark config with supported fp8 paths.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_flash_attention.py |
Enables AppendKV test coverage and adds guardrails for cache capacity when appending. |
src/torch_extension_sycl.cc |
Extends Torch library schema to accept optional k_new/v_new/cu_seqlens_k_new. |
src/sycl/xe_fmha_fwd_prefill_kernel.cpp.in |
Adds hd128 mixed/small paged prefill tile dispatch and removes legacy fp8 prefill handling. |
src/sycl/kernels/flash_attention_v2/xe_fmha_fwd_prefill_runner.hpp |
Adds AppendKV argument wiring into the prefill kernel mainloop args. |
src/sycl/kernels/flash_attention_v2/xe_fmha_fwd_prefill_dispatch.hpp |
Fully-qualifies the dispatch macro to avoid namespace ambiguity. |
src/sycl/kernels/flash_attention_v2/xe_fmha_fwd_decode_runner.hpp |
Updates fp8 scale pointer types and split-decode mainloop arg wiring (non-split needs a fix). |
src/sycl/kernels/flash_attention_v2/kernel/xe_fhma_fwd_kernel.hpp |
Implements effective-K length handling and append-store length logic for AppendKV. |
src/sycl/kernels/flash_attention_v2/collective/xe_fmha_fwd_mainloop.hpp |
Introduces AppendKV params/storage and refactors decode fp8 scaling + masking/prefetch logic. |
src/sycl/kernels/flash_attention_v2/collective/xe_fmha_fwd_epilogue.hpp |
Refactors sink handling and softmax normalization paths (and removes fp8 scale folding in epilogue). |
src/sycl/flash_attention.cpp |
Removes chunkprefill dispatcher, routes mixed batches to prefill, and wires AppendKV inputs/validation. |
python/sgl_kernel/flash_attn.py |
Forwards k/v/cu_seqlens_k_new to the native op and computes an appropriate max_seqlen_k for native. |
include/sgl_flash_kernel_ops.h |
Updates C++ interface for mha_fwd to accept AppendKV inputs. |
benchmark/bench_flash_attn.py |
Narrows fp8 benchmarking to supported decode path and adjusts local window sampling. |
Comments suppressed due to low confidence (1)
src/sycl/kernels/flash_attention_v2/xe_fmha_fwd_decode_runner.hpp:331
- Non-split decode runner is not forwarding fp8 KV descale pointers (k_scale_ptr/v_scale_ptr) and total KV length into the decode mainloop arguments. After the DecodeFwdMainloop argument layout change, this will either mis-initialize the mainloop args (wrong field order) or drop fp8 scaling entirely.
{params.softmax_scale,
params.page_table,
params.page_size,
params.max_num_pages_per_seq,
params.window_size_left,
params.window_size_right},
| int total_knew = 0; | ||
| int seqlen_knew = max_seqlen_k > 0 ? max_seqlen_k : max_seqlen_q; | ||
| if (k_new.dim() == 3) { | ||
| total_knew = k_new.size(0); | ||
| CHECK_SHAPE(k_new, total_knew, num_heads_k, head_size); | ||
| CHECK_SHAPE(v_new, total_knew, num_heads_k, head_size_v); | ||
| TORCH_CHECK( | ||
| cu_seqlens_k_new_.has_value() || seqlen_knew > 0, | ||
| "ragged k_new requires cu_seqlens_k_new or positive max_seqlen_k"); | ||
| } else { | ||
| TORCH_CHECK(k_new.size(0) == batch_size, "batched k_new first dimension must match batch size"); | ||
| int const k_new_seqlen = k_new.size(1); | ||
| total_knew = batch_size * k_new_seqlen; | ||
| seqlen_knew = max_seqlen_k > 0 ? max_seqlen_k : k_new_seqlen; | ||
| CHECK_SHAPE(k_new, batch_size, k_new_seqlen, num_heads_k, head_size); | ||
| CHECK_SHAPE(v_new, batch_size, k_new_seqlen, num_heads_k, head_size_v); | ||
| } |
| params.o_row_stride = out.stride(-3); | ||
| params.o_head_stride = out.stride(-2); | ||
|
|
|
Hi @yuankuns, I suppose this PR mainly targets to support the new kv cache in addition to the current one. Firstly, could you please answer several questions:
|
45df940 to
ca1eb26
Compare
c91f681 to
365294c
Compare
|
@Valentine233 This kernel aims to replace chunk prefill, to merge 3 kernel launch (appendkv, prefill, decode) into 1 kernel launch (chunk-prefill) and according to the measurement, it's faster. I only tuned hddim=128 cases, will add more headdim tuning. If you believe there are some regression in performance, please also provide the shape so that I can tune these shapes. |
|
Thanks, @yuankuns .
|
@Valentine233 I think Yuankun means fusing storing kv cache into attention. In current intel_xpu backend, storing kv cache is a saparete kernel |
|
@Valentine233 @airMeng This pr also merges 2 fmha kernel launch into 1 kernel launch in chunk prefill cases. The master branch launchs 1 prefill + 1 decode for chunk prefill. The script benchmark/bench_flash_attn.py didn't include chunk prefill cases, either. |
cf2c45f to
590d8d1
Compare
|
With |
fb64b73 to
4129ac7
Compare
|
sycl-tla example06 supports both sgl-kernel was derived by stripping down code that originally supported the coexistence of both Change the existing |
| constexpr int elems_per_n = tSrS.size() / n_reps; | ||
| int k_base = K * kTileK; | ||
| // Need to get global col and row indices to mask the elements | ||
| Tensor cPgP = make_identity_tensor(make_shape(seq_len, seq_len)); |
There was a problem hiding this comment.
I’ve tested this implementation before; TFLOPS drop by 3–5 T. Although this modification looks better
@Valentine233 I remember it being like this. right?
There was a problem hiding this comment.
Yes, please keep the original one, referring to #260.
There was a problem hiding this comment.
I have validate the pr260 change, no significant performance difference in those 2 style. <1%. Which shape you've measured results in 3-5Tops dropping?
There was a problem hiding this comment.
Have you tried the shape in pr260's description? What is the tflops now?
| params.knew_ptr = nullptr; | ||
| params.vnew_ptr = nullptr; | ||
| params.cu_seqlens_knew = nullptr; | ||
| params.seqlen_knew = 0; |
There was a problem hiding this comment.
Change the original total in the code to seqlen_kvcache.
There was a problem hiding this comment.
seqlen_knew is max new_kv len in single request, and seqlen_kvcache is the maximum size of current page table.
| std::optional<at::Tensor> skip_batch_mask_opt = std::nullopt) { | ||
| std::optional<at::Tensor> skip_batch_mask_opt = std::nullopt, | ||
| std::optional<const at::Tensor> k_new_ = std::nullopt, | ||
| std::optional<const at::Tensor> v_new_ = std::nullopt, |
There was a problem hiding this comment.
It is adjacent to kvche and is a mandatory input parameter.
| int const sm_margin, | ||
| std::optional<at::Tensor> out_opt = std::nullopt, | ||
| std::optional<at::Tensor> skip_batch_mask_opt = std::nullopt) { | ||
| return mha_fwd_appendkv( |
There was a problem hiding this comment.
Why make this modification? The appendkv logic is only entered when kvnew exists. If you need to modify the prefill or decode phases, please make those changes within the decode or prefill functions themselves.
or The unified interface should be named |
| " int sm_margin," | ||
| " Tensor(a!)? out=None," | ||
| " Tensor? k_new=None," | ||
| " Tensor? v_new=None," |
Port the q128/k64/sg16 paged tile configuration from cutlass-sycl commit 9bf71715.
The appendKV in example06 is slower. So not going to copy the code directly. Already changed the coding style |
2554979 to
2d09c4a
Compare
change
There are many code blocks are shared between prefill/chunkprefill. If go this branch style, there will be more dupilicated code block |
391f541 to
06961ea
Compare
06961ea to
0d94217
Compare
|
I am providing results for Gemma-4-31b-it. The below table simulates the dump shapes of the model in the SWA layers where head_dim=256. Sliding family — nkv=4, hd=256, window (1023,0):
with head_dim=512, there are no improvements but #342 will be taking care of this: Full-attn family — nkv=1, hd=512, full window:
|
Summary
chunkprefilldispatcher with the FMHA prefill-with-KVcache AppendKV path.prefill::mha_fwd; keep pure paged decode ondecode::mha_fwd.k/v/cu_seqlens_k_newinputs through to native AppendKV params.test_flash_attn_kvcache.Validation
MAX_JOBS=4 CMAKE_BUILD_PARALLEL_LEVEL=4 python -m pip install -v .passed.pytest -q tests/test_flash_attention.py::test_flash_attn_kvcache: 1200 passed, 1872 skipped.pytest -q tests/test_flash_attention.py::test_flash_attn_decode_kvcache: 640 passed, 384 skipped.python -m pre_commit run --all-files --show-diff-on-failurepassed.benchmark/bench_flash_attn.py Paged Prefill Sweep
Measured with
benchmark/bench_flash_attn.pyfiltered to the paged prefill path.Command ran in the
sglang-sykcontainer withZE_AFFINITY_MASK=0andONEAPI_DEVICE_SELECTOR=level_zero:gpu.Filtered configs:
head_dim={64,128}q_seq_length=128,kv_seq_length=4096page_size=128dtype=bf16,local=False,use_sinks=Falsebatch={1,8,16},num_heads_q=16,num_heads_kv={4,8},causal={True,False}Baseline:
590d8d1prefill kernel.so.Current: this PR's rebuilt prefill kernel
.so.Notes:
benchmark/bench_flash_attn.pydoes not exercise AppendKV or sparse varlen mixed-query cases, so this sweep mainly measures the paged prefill/page-table path.Benchmark Notes
The main baseline is the complete
dfa5630(origin/main) build. Its timed callable consumesnew_k/new_v: main's fusedstore_cache_xpuwrites both tensors to their physical paged-cache slots in one launch, then the oldchunkprefillpath runsdecode::mha_fwdplusprefill::mha_fwd. The baseline therefore measures one cache-store launch plus the old two attention launches.Main's
flash_attn_with_kvcachePython signature acceptsk/v, but the main native call does not consume them. Validation confirmed that passingk/valone leaves the cache unchanged; the composite baseline cache and attention output match explicit reference cache writes.AppendKV timing is measured on the current PR build through
torch.ops.sgl_kernel.fwd_appendkvwith old KV cache plusk_new/v_new/cu_seqlens_k_new. AppendKV ms and speedup ratio are filled forhead_dim={64,96,128,192,256}.Benchmarks use bf16 KV, heads q/kv = 16/4,
ZE_AFFINITY_MASK=0,ONEAPI_DEVICE_SELECTOR=level_zero:gpu, andtriton.testing.do_bench(warmup=100, rep=500, return_mode="mean"). Each table cell is the median of three full sweeps. Speedup ratio ismain store + double-launch ms / AppendKV ms, shown as a percentage.AppendKV Size Sweep
Cache old length is 4096, page size is 64, causal/local are both false.
[8,16,4,64][8,64,4,64][8,128,4,64][8,256,4,64][16,16,4,64][16,64,4,64][16,128,4,64][16,256,4,64][8,16,4,96][8,64,4,96][8,128,4,96][8,256,4,96][16,16,4,96][16,64,4,96][16,128,4,96][16,256,4,96][8,16,4,128][8,64,4,128][8,128,4,128][8,256,4,128][16,16,4,128][16,64,4,128][16,128,4,128][16,256,4,128][8,16,4,192][8,64,4,192][8,128,4,192][8,256,4,192][16,16,4,192][16,64,4,192][16,128,4,192][16,256,4,192][8,16,4,256][8,64,4,256][8,128,4,256][8,256,4,256][16,16,4,256][16,64,4,256][16,128,4,256][16,256,4,256]Mixed Varlen Chunk-Prefill
The first row mirrors the standalone window-3 style shape. The remaining mixed rows cover max new_k/v <= 256 for decode-heavy, balanced, and prefill-heavy mixes.
Current mixed-table AppendKV values were refreshed at
a04b0b6.1/5128191/7680[513,4,64]1/1/1/1/1/1/1/1/16/32/64/128/1/1/8/2564096 x16[514,4,64]1/1/1/1/1/1/1/1/16/32/64/128/1/1/8/2564096 x16[514,4,64]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,64]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,64]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,64]64/128/256/128/64/32/16/8/1/1/64/128/256/32/1/164096 x16[1195,4,64]64/128/256/128/64/32/16/8/1/1/64/128/256/32/1/164096 x16[1195,4,64]1/5128191/7680[513,4,96]1/1/1/1/1/1/1/1/16/32/64/128/1/1/8/2564096 x16[514,4,96]1/1/1/1/1/1/1/1/16/32/64/128/1/1/8/2564096 x16[514,4,96]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,96]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,96]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,96]64/128/256/128/64/32/16/8/1/1/64/128/256/32/1/164096 x16[1195,4,96]64/128/256/128/64/32/16/8/1/1/64/128/256/32/1/164096 x16[1195,4,96]1/5128191/7680[513,4,128]1/1/1/1/1/1/1/1/16/32/64/128/1/1/8/2564096 x16[514,4,128]1/1/1/1/1/1/1/1/16/32/64/128/1/1/8/2564096 x16[514,4,128]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,128]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,128]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,128]64/128/256/128/64/32/16/8/1/1/64/128/256/32/1/164096 x16[1195,4,128]64/128/256/128/64/32/16/8/1/1/64/128/256/32/1/164096 x16[1195,4,128]1/5128191/7680[513,4,192]1/1/1/1/1/1/1/1/16/32/64/128/1/1/8/2564096 x16[514,4,192]1/1/1/1/1/1/1/1/16/32/64/128/1/1/8/2564096 x16[514,4,192]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,192]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,192]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,192]64/128/256/128/64/32/16/8/1/1/64/128/256/32/1/164096 x16[1195,4,192]64/128/256/128/64/32/16/8/1/1/64/128/256/32/1/164096 x16[1195,4,192]1/5128191/7680[513,4,256]1/1/1/1/1/1/1/1/16/32/64/128/1/1/8/2564096 x16[514,4,256]1/1/1/1/1/1/1/1/16/32/64/128/1/1/8/2564096 x16[514,4,256]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,256]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,256]1/16/1/32/1/64/1/128/1/256/8/1/64/1/32/14096 x16[608,4,256]64/128/256/128/64/32/16/8/1/1/64/128/256/32/1/164096 x16[1195,4,256]64/128/256/128/64/32/16/8/1/1/64/128/256/32/1/164096 x16[1195,4,256]