Repository navigation
vulkan : grow the context-sized scratch in 64 MiB steps - #118
Conversation
7f49dae to
aca18ab
Compare
dzannotti
left a comment
There was a problem hiding this comment.
Tests pass on Strix Halo (RADV, master + this PR): TOPK_QSA 4/4, FLASH_ATTN_EXT 5344/5344. Three issues with the rounding:
- No clamp. At
ggml-vulkan.cpp:9737and:12969, a need that fits exactly can round pastmax_buffer_sizewhen the limit isn't a multiple of 64 MiB, andggml_vk_create_bufferthen throws. Something likestd::max(need, std::min(padded, device->max_buffer_size)). - The top-k descriptor binds the whole buffer (
:12982,prealloc_x->size). After rounding, that can exceedmaxStorageBufferRangewhen the exact size wouldn't. Bindscratch_sizeinstead. - Ungated. The FA dequant copy is used by every Vulkan user, so small models now hold up to 64 MiB extra. Suggest rounding only once the need is at least 64 MiB.
Comment generated via automated review of clanker.
aca18ab to
542a402
Compare
|
Thanks, all three are addressed in cf62cc8d7. The branch is rebased onto master 4e0d917, so it no longer carries #115.
Re-measuring on master turned up one more thing. Since #119, Flash-Next on Vulkan takes the selected-key path, and there the scratch grows in neither of these two passes. It grows in Checked on gfx1150 (RADV):
The description is updated. Assisted-by: Claude Opus 5.5 |
|
@rcfa ready to merge after a last rebase |
Two passes size the shared scratch (prealloc_x) from the current context: the flash-attention K/V dequant/contiguize copy (K + V in f16) and the fused QSA top-k (n_kv x rows F32). Both grow by one ubatch of cells with every prompt ubatch, and both sized the scratch to the exact need, so it was freed and allocated again on every ubatch: with Qwen3.8-Flash-Next and 174k cells in the cache, 341, 342, ... 348 MiB, once per ubatch. Rounding only one of them is not enough: the top-k runs first in each layer and sets the exact size, so the copy then finds the scratch large enough. Round both up to 64 MiB: one reallocation per ~64 ubatches. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… buffer limit Follow-up to the 64 MiB rounding of the flash-attention K/V copy and the fused QSA top-k scratch (review on halo-box#118): - a need below 64 MiB keeps its exact size, so a model whose K/V copy is small holds no extra memory - the rounded size never exceeds max_buffer_size: a need that fits exactly no longer rounds up into a buffer the device cannot create - the top-k binds the scratch it uses (scratch_size), not the whole shared buffer, so its descriptor range stays within maxStorageBufferRange whenever the exact need does Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On master, Flash-Next takes the selected-key path on Vulkan (halo-box#119), where the QSA block selection is a GGML_OP_TOP_K over the KV cells. ggml_vk_topk sizes its double buffer in the shared scratch (prealloc_x) to the exact need, so the scratch was still reallocated as the context grew: 172, 173, 174, 176 MiB during one 4096-token prompt next to 174k resident cells. Size it with ggml_vk_scratch_size() like the other context-sized scratches; below 64 MiB (any ordinary top-k) nothing changes. Its passes bind dbl_buf_size subranges, not the whole buffer. Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
542a402 to
04c6faa
Compare
|
Rebased onto master 6aa81b8 (head 04c6faa). There was one conflict: #159 now reserves the V^T region in the same flash-attention scratch ( Checked on gfx1150 (RADV):
One thing to know before merging. On this master, Flash-Next's scratch no longer grows in our memory probe (174k resident cells, a 4096-token prompt). It peaks at 6 MiB on both builds. The compute-buffer churn is gone too, probably through #164. So the probe is identical with and without this PR, and the Flash-Next numbers in the description, measured on 4e0d917, no longer reproduce. I added a note to the description. The change still sizes the three scratches as described, but we have not measured another model where they reach 64 MiB on this master. Merge or close it as you see fit. Assisted-by: Claude Opus 5.5 |
|
What this PR still does on master 6aa81b8, measured on gfx1150 (RADV). On devices without coopmat2 (RADV here), master copies K and V into the shared scratch for every prompt ubatch in two cases: when the cache is quantised, and when it is f16 with more than one KV head. In the second case the cache view is head-interleaved, so Test: Qwen2.5-1.5B-Instruct Q4_K_M with its default f16 cache (2 KV heads),
For this model the scratch needs about 1.5 KiB per cell, so it passes 64 MiB at about 43k cells. A model with more KV heads gets there sooner. The PR removes the reallocation churn at the cost of up to 64 MiB more scratch. On our machine it does not change the speed measurably. The other two sites:
Assisted-by: Claude Opus 5.5 |
|
Follow-up on the generic top-k: with one long Flash-Next conversation, the scratch still grows on master. Test:
So the PR applies to Flash-Next too, once a single conversation passes about 64k tokens. As with the f16-cache case above, it removes allocation churn, not time. Assisted-by: Claude Opus 5.5 |
… buffer limit Follow-up to the 64 MiB rounding of the flash-attention K/V copy and the fused QSA top-k scratch (review on halo-box#118): - a need below 64 MiB keeps its exact size, so a model whose K/V copy is small holds no extra memory - the rounded size never exceeds max_buffer_size: a need that fits exactly no longer rounds up into a buffer the device cannot create - the top-k binds the scratch it uses (scratch_size), not the whole shared buffer, so its descriptor range stays within maxStorageBufferRange whenever the exact need does Assisted-by: Claude Opus 5.5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 531601c)
Overview
Three Vulkan passes size the shared scratch (
prealloc_x) from the current context:ggml_vk_topk_qsa):n_kv x rowsF32ggml_vk_topk), which is master's default onVulkan since vulkan : selected-key flash attention for QSA block selection #119
All three set the scratch to the exact need, and that need grows with every prompt ubatch. So the scratch was freed and
allocated again as the context grew. With Qwen3.8-Flash-Next and 174k cells in the cache, it grew to 172, 173, 174 and
176 MiB during one 4096-token prompt on master. On the masked path it was 341, 342, ... 348 MiB, once per ubatch.
This PR sizes all three with one helper,
ggml_vk_scratch_size():max_buffer_size, and never falls below the need.The fused QSA top-k now also binds only the scratch it uses (
scratch_size), not the whole shared buffer.Measurements
Update, master 6aa81b8 (2026-10-11): the Flash-Next numbers below were measured on master 4e0d917. On
6aa81b8 the 174k-resident probe no longer grows the scratch with or without this PR: since #117 the block selection
scores only the tracked conversation's own blocks. What the PR still does on this master is in the comments:
at +41 MiB peak.
Device memory near a full context.
llama-server -c 262144 -np 4 -kvu, MTP n=1,GGML_VK_MEMORY_LOGGER=1.The table counts the allocations and frees during that probe, with the logger's own device total:
The remaining large events are a compute buffer growing with the graph (809 -> 811 -> ... -> 824 MiB). That buffer belongs to the graph allocator's reserve, which this PR does not change. On the masked path (#115, measured on 307c50d), the same rounding took the scratch from every ubatch to once.
A small model keeps its exact sizes. Qwen2.5-1.5B-Instruct Q4_K_M with a q8_0 cache,
llama-bench -fa 1 -ctk q8_0 -ctv q8_0 -p 2048 -n 0 -d 8192,GGML_VK_MEMORY_LOGGER=1:Correctness:
llama-server(1 slot, no MTP), each as is (570-876 tokens) and repeated to 12.7k-53.4k tokens. Compared on prompt tokens, the complete next-token distribution after prefill, and 64 greedy steps with top-20 log-probabilities, the output is bit-identical to master on all eight prompts.test-backend-ops -b Vulkan0: TOP_K 579/579, ARGSORT 98/98, TOPK_QSA 4/4, FLASH_ATTN_EXT 5344/5344, QSA_PREFILL 26/26, QSA_DECODE 92/92, QSA_PREFILL_MASKLESS 6/6, QSA_DECODE_MASKLESS 25/25.Requirements
flash-attention copy and QSA top-k passes. Our measurements come from Strix Point (gfx1150), not Strix Halo.
this description, and ran every measurement. Every commit carries
Assisted-by: Claude Opus 5.5.max_buffer_size: on our device the limit is far above these sizes, so it never applies.