Repository navigation
ggml-openvino: enable in-place 4-D stateless KV cache updates on GPU - #344
Merged
Merged
Conversation
In stateless GPU mode, bind the entire KV cache as a `[1, ctx, n_heads_kv, head_size]` tensor, with one token per row, instead of binding the first `n_kv` rows as a `[1, 1, ctx, n_heads_kv * head_size]` tensor. Write cache updates with `ScatterUpdate` along the token axis, and read the cache through `VariadicSplit` and `Transpose`. This allows the GPU plugin's `StatelessKVFusion` to replace cache reads and writes with in-place `StatelessKV` nodes, avoiding full-cache copies on every decode step and repeated memory pool allocations as the bound cache size grows. Set `GGML_OPENVINO_DISABLE_KV_4D=1` to retain the previous layout.
osabnis
force-pushed
the
ov-kv-cache-4d
branch
from
October 12, 2026 21:39
7b2757e to
dd04231
Compare
osabnis
added a commit
to osabnis/llama.cpp
that referenced
this pull request
Oct 13, 2026
With -fa off, the 4-D KV cache layout introduced in ravi9#344 breaks MUL_MAT attention for Phi-3.5 mini and Phi-4 mini, causing oneDNN to fail with "could not construct a memory descriptor using strides". Preserve the [1, 1, ctx, n_heads_kv * head_size] layout when the graph writes a transposed V cache.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Improve long-context decode performance and reduce GPU memory usage across models on the OpenVINO GPU backend in stateless mode.
The stateless KV cache currently uses a
[1, 1, ctx, n_heads_kv * head_size]model input, bound to its firstn_kvrows and updated withScatterUpdate. The GPU plugin'sScatterUpdatecopies the entire input to the output before writing the updated rows. Consequently, every decode step copies the full KV cache of every layer. As the context grows, the changing bound shape also forces the plugin to allocate new memory pool buffers that cannot be reused.This PR uses the 4-D layout already used in stateful mode:
[1, ctx, n_heads_kv, head_size]. The input is bound over the entire cache, keeping its shape constant as the context grows. Cache updates useScatterUpdatealong the token axis, while reads useVariadicSplitto select the firstattention_sizetokens, followed by aTranspose.This pattern enables the GPU plugin's
StatelessKVFusionto replace cache reads and writes with aStatelessKVnode that updates the cache in place and exposes it to attention without copying it. In the compiled Llama-3.2 1B graph, all 32 KV caches becomeStatelessKVnodes, attention uses 16 SDPA nodes, and noScatterUpdatenodes remain.The 4-D layout is enabled only on the GPU in stateless mode when the cache holds a single sequence with one token per row, is written exclusively through
SET_ROWS, and is read through views that split rows into heads. Other configurations retain the previous layout, including CPU, NPU, stateful mode, static or split graphs, multiple streams, and the transposed V cache used by-fa off.Set
GGML_OPENVINO_DISABLE_KV_4D=1to restore the previous layout in all cases.Additional information
Performance
llama-bench -fa 1, Q4_K_M, Arc B390 (Panther Lake) GPU. One console run, two rounds (round 1 / round 2), comparing the same build with and withoutGGML_OPENVINO_DISABLE_KV_4D=1, on top of #343.Decode at context depth 4096 (
-n 64). Throughput is in tokens/s; peak GPU memory is measured during the same test.At context depth 0, decode changes range from -3% to +4%. Using
-ub 256produces similar decode gains at depth 4096.Prefill at context depth 4096 (
-p 512):Peak prefill GPU memory also drops substantially:
Accuracy
KL divergence against the CPU backend at context length 1024 (decode,
-b 1) is unchanged from the previous layout:At context length 4096, KL divergence between the new and previous layouts is at most 0.00018 for both decode and prompt evaluation. Perplexity with four sequences per batch (
-c 256 -b 1024, 16 chunks) is unchanged: Llama-3.2 1B 21.6656 with both layouts, SmolLM2 1.7B 12.5269 → 12.5265.test-backend-ops: 8024/8024 passed.Notes
SDPA takes slightly longer to read the cache through the padded view. In a per-op GPU profile of Llama-3.2 1B decode at context depth 4096, SDPA increases from 8.1 to 9.3 ms. The cache update it replaces costs much more: 3.4 ms with ScatterUpdate against 0.6 ms with StatelessKV.
In prefill, the trade-off is closer. In a per-op profile of one 512-token prompt pass at context depth 4096 (-ub 512), Llama-3.2 1B SDPA increases from 19.3 to 25.5 ms, while the cache update drops from 4.5 to 2.1 ms. That is a net cost of about 4% of the pass. With -ub 2048, SDPA time is about the same in both layouts. For Gemma-4 E2B and Qwen3.5 4B, the difference is about 1 ms or less.
Requirements