vulkan: support TOP_K when k exceeds the workgroup limit - #28062
Closed
Anothers wants to merge 1 commit into
Closed
Conversation
TOP_K falls back to the CPU on Vulkan once k goes past 1024. Qwen3.8-Flash-Next hits that on 12 attention layers for every decoded token, so past about 1K of context the model does 12 round trips per token. ggml_vk_topk reduces a row one workgroup at a time and the last pass has to hold all k candidates in a single workgroup, which caps at 1024 invocations. When k is larger, sort the whole row with the existing argsort_large pipelines and keep the leading k instead: ggml_top_k does not order its output, so that is a valid answer. Gate it on row count. A full sort is more work than the selection it replaces, so it only pays while the round trip dominates: measured with ncols 54822 / k 2051, 3 rows is 11% faster on the GPU and 1024 rows is 10% slower. gfx1151, RADV, Mesa 26.1.7, Qwen3.8-Flash-Next UD-Q4_K_XL: +6.7% tg at depth 1024, +15.4% at 16384, +11.4% at 65536.
|
Hi @Anothers, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
4 tasks
Contributor
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.
Problem
On Vulkan,
TOP_Kfalls back to the CPU once k goes past 1024. Qwen3.8-Flash-Next(qwen4exp) hits that on 12 attention layers for every token it decodes, so past about 1K of
context the model is doing 12 round trips per token. It is a real problem for me — this is
my daily driver and every session runs past that point.
The limit is in
supports_op:opis the destination, soop->ne[0]is k.ggml_vk_topkreduces a row one workgroup ata time and the last pass has to hold all k candidates in a single workgroup, which caps at
1024 invocations. qwen4exp's QSA indexer asks for
min(n_kv, indexer_top_k + compress_ratio - 1)=min(n_kv, 2051), so below ~1K of contextk is the window and fits, and above it k pins at 2051 and the scheduler moves all twelve
nodes to the CPU.
It shows up as unsupported rather than slow:
Change
The comment already in
supports_opsuggested the way out:So: when k is larger than one workgroup can hold, sort the whole row with the existing
argsort_largepipelines and keep the leading k. That is a valid answer becauseggml_top_kdoes not order its output — the CPU reference swaps the first two entries tomake the point:
The argsort dispatch was only reachable through
ggml_vk_argsort, which took its buffersfrom the tensors, so it is split into
ggml_vk_argsort_rowstaking explicit subbuffers.ggml_vk_argsortbecomes a wrapper and is otherwise unchanged.prealloc_xholds the sortscratch followed by the permutation, and one
copyBufferwith a region per row gathers theleading k into the destination.
Why it is gated on row count
A full sort is more work than the selection it replaces, so it only pays for itself while
the row count is low enough that the round trip dominates. Measured here with
ncols 54822, k 2051:The bound is 32 — above any decode batch, well below any prefill ubatch. My first version
had no bound and traded a 10% prefill regression for the decode win; it was only visible
because prefill was measured too.
llama-bench -p 0 -n 128measures generation alone andhides it.
Numbers
gfx1151 (Radeon 8060S, RADV, Mesa 26.1.7), Qwen3.8-Flash-Next UD-Q4_K_XL,
llama-bench -fa 1 -n 128 -d .... Same build both columns; "before" only has thesupports_oprelaxation reverted.The gain starts where k crosses the limit and not before: at depth 0 the window is still
narrower than k, so the node never takes the new path.
On the server, with the MTP draft head and a 52K prompt, five runs each: decode
21.46 -> 23.39(+9.0%), prefill192.0 -> 190.0(unchanged).Note this is a slope here, not the cliff #27856 reports on HIP. This is an APU, so the
fallback pays for a synchronisation rather than a transfer. #27466 fixes the CUDA/HIP side
of the same limit; I did not find anything covering Vulkan.
Tests
test-backend-ops -o TOP_Kpasses, 523/523. Thek = 9999cases already in the suite nowrun on Vulkan instead of being skipped as unsupported.
Added cases in the shape qwen4exp produces —
k = 2051over 54822 columns, with andwithout ties, at 1, 3, 32 and 33 rows. 33 is over the bound, so the fallback side is covered
too.
AI disclosure
The code was written with AI assistance. I reviewed it, I understand it, and I will maintain
it.