Skip to content

[perf] vulkan: SDPA KV Cache update - #6801

Open
crafcat7 wants to merge 3 commits into
Tencent:masterfrom
crafcat7:feat/vulkan-spda
Open

[perf] vulkan: SDPA KV Cache update#6801
crafcat7 wants to merge 3 commits into
Tencent:masterfrom
crafcat7:feat/vulkan-spda

Conversation

@crafcat7

@crafcat7 crafcat7 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR updates the Vulkan SDPA KV cache path so K/V cache materialization is handled inside SDPA_vulkan with dedicated Vulkan shaders instead of a generic cache concat path.

The final implementation keeps public K/V cache outputs compact. This is required because VkMat is the shared Vulkan tensor representation used by ncnn layers, packing conversion, download/extract, and tests. A cache tensor returned from SDPA_vulkan::forward() must therefore expose a normal, self-consistent w/h/c/cstep layout.

Committed code changes are limited to Vulkan SDPA implementation files and shaders. This PR does not modify benchmark tools or external tooling.

What Changed

1. Dedicated KV cache update shaders

The Vulkan SDPA KV cache path now uses SDPA-specific shaders for cache materialization:

past_key   + cur_key   -> new_key_cache
past_value + cur_value -> new_value_cache

The new shader roles are:

  • sdpa_kvcache_update.comp
    • Copies non-empty past and current cur into a compact past + cur cache output.
  • sdpa_kvcache_append.comp
    • Copies current cur into the output at past_seqlen + y.
    • Used by the compact-output path for empty-past initialization.

This removes the dependency on a generic concat layer for the SDPA KV cache update step and makes the cache path explicit inside SDPA_vulkan.

2. Compact public cache outputs

The final cache output layout is:

w = head_dim
h = past_seqlen + cur_seqlen
c = kv_group_count
cstep ~= w * h

The public VkMat output does not encode hidden cache capacity in cstep.

3. Existing attention compute paths remain unchanged

The cache update happens before attention kernels read K/V. The existing SDPA compute paths are preserved:

  • flash attention
  • cooperative-matrix flash attention
  • regular qk -> softmax -> qkv

Performance

Comparison basis

All results compare clean worktrees built with the same compiler, CMake options, and benchmark parameters:

  • Baseline: 81e8cbff
  • Optimized: 39cc7404
  • GPU: NVIDIA GeForce RTX 5070 Ti
  • Build: Windows/MSVC Release, Vulkan enabled
  • CPU side: scalar x86 options in this local build to avoid a local Illegal instruction issue
  • Run order: baseline, optimized, optimized, baseline

Improvement is calculated as:

(baseline time - optimized time) / baseline time * 100%

End-to-end LLM benchmark

The normal committed benchmark path was used. It keeps K/V cache as host Mat between decoder calls; no GPU-resident cache mode or modified benchmark path is involved.

benchncnn_llm.exe 3 1 0 0 0

Parameters and sampling:

  • loop_count = 3
  • num_threads = 1
  • powersave = 0
  • gpu_device = 0
  • cooling_down = 0
  • 8 warmup iterations per case, as defined by the committed benchmark
  • two complete runs per revision; table values are the mean of the two reported averages
  • decode case: prefill 256 tokens, then decode one token with a 256-token KV cache
Model Baseline decode ms Optimized decode ms Improvement
hunyuan_0.5b 571.27 576.56 -0.93%
minicpm4_0.5b 239.00 231.84 +2.99%
qwen2.5_0.5b 49.31 52.45 -6.36%
qwen3_0.6b 670.12 664.28 +0.87%
llama3.2_1b 377.49 378.88 -0.37%
tinyllama_1.1b 527.79 524.04 +0.71%
youtu_llm_2b 986.72 971.26 +1.57%
Suite total 3421.69 3399.29 +0.65%

The prefill suite total changed from 3330.81 ms to 3384.11 ms (-1.60%). Prefill has no existing cache to concatenate, so it is a control case rather than the target of this optimization.

The end-to-end result is small and noisy because model compute and host Mat cache upload/download dominate the dedicated KV update dispatch. It is still the comparable normal benchmark result: across the complete decode suite, the optimized revision reduces measured time by 0.65%.

Isolated Vulkan SDPA decode benchmark

perf_sdpa_decode isolates SDPA forward and uploads inputs before timing, so it exposes the cost removed by replacing generic Concat with the SDPA-specific KV update pipeline.

Both revisions used the same measurement cases on gpu-0:

query       = [128, 1, 16]
current K/V = [128, 1, 4]
past K/V    = [128, past_seqlen, 4]
past_seqlen = 128, 256, 512, 1024, 2048

Each value below is the mean of two runs. Each run uses the median of 20 timed command-buffer batches, divided by the calibrated batch size to obtain per-forward latency.

Past tokens FP32 baseline us FP32 optimized us FP32 improvement FP16 arithmetic baseline us FP16 arithmetic optimized us FP16 improvement
128 34.56 30.85 +10.73% 101.52 99.00 +2.48%
256 50.23 46.54 +7.36% 176.35 174.11 +1.27%
512 81.45 76.15 +6.51% 326.25 325.35 +0.28%
1024 144.35 136.00 +5.78% 619.75 619.65 +0.02%
2048 273.65 259.25 +5.26% 1206.90 1208.55 -0.14%

The dedicated KV update path improves isolated FP32 SDPA decode by 5.26% to 10.73%. The FP16 arithmetic (fp16psa) path improves by up to 2.48% at shorter cache lengths and becomes neutral as attention computation dominates at longer lengths.

These two benchmark levels describe different effects:

generic Concat -> dedicated KV update shader
    |
    +-> isolated SDPA: visible dispatch and cache-update reduction
    |
    +-> complete decoder: mostly hidden by attention, GEMM, and host cache transfer

Summary:
  Replace the generic Concat-based SDPA KV cache path with dedicated Vulkan cache update and append shaders. This keeps decode cache updates on GPU and reduces repeated full-cache copies during autoregressive generation.

Changes:
  1. Add dedicated append and grow-copy shaders for SDPA KV cache updates
  2. Reuse existing VkMat cache capacity when appending new decode tokens
  3. Wire SDPA Vulkan key and value cache outputs through the new cache helper
@github-actions github-actions Bot added the vulkan label Jul 4, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb3bc8449f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/layer/vulkan/sdpa_vulkan.cpp Outdated
Comment on lines +117 to +119
top_blob = past_blob;
top_blob.h = dst_seqlen;
record_kvcache_append(cur_blob, top_blob, width, past_seqlen, cur_seqlen, num_group, pipeline_append, cmd);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve input cache buffers when appending

When the previous cache has spare capacity, this aliases top_blob to past_blob and then records an append into that same buffer, so a normal forward call mutates one of its input blobs. This breaks callers that reuse a prefix cache for multiple continuations/beam branches or keep the first returned cache while running another step from the same input cache, because later appends overwrite the shared storage that earlier outputs also reference; the old concat path always produced an independent output cache.

Useful? React with 👍 / 👎.

@codecov-commenter

codecov-commenter commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.49%. Comparing base (127b414) to head (eb3bc84).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6801      +/-   ##
==========================================
+ Coverage   95.13%   95.49%   +0.36%     
==========================================
  Files         947      878      -69     
  Lines      416089   392899   -23190     
==========================================
- Hits       395853   375207   -20646     
+ Misses      20236    17692    -2544     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Summary:
  Return compact K/V cache outputs from Vulkan SDPA so generic download, packing, and layer tests interpret the cache layout consistently. This fixes the SDPA KV cache mismatch caused by encoding hidden capacity in VkMat::cstep.

Changes:
  1. Remove hidden KV cache capacity growth from the public SDPA Vulkan output path.
  2. Allocate updated K/V cache tensors with the logical sequence length.
  3. Rename the cache helper to reflect compact update behavior.
@crafcat7 crafcat7 changed the title [perf] vulkan: reduce SDPA KV cache append overhead [perf] vulkan: SDPA KV Cache update Jul 10, 2026
Summary:
  Restore the empty past-cache fast path so SDPA can alias the current K/V tensors without allocating or dispatching extra work. This keeps the Vulkan KV cache path aligned with the intended zero-cost initialization behavior while removing an unnecessary append shader dependency.

Changes:
  1. Skip cache allocation and copy work when past_seqlen is zero by reusing the current K/V tensors directly.
  2. Remove the append-only KV cache shader and the related pipeline plumbing from the Vulkan SDPA implementation.
  3. Keep the cache update path focused on the non-empty history case so future growth still uses the dedicated copy logic.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants