qwen4exp: Qwen3.8-Flash-Next long-context decode, +66% at 131k - #9
Open
firelzrd wants to merge 1 commit into
Open
qwen4exp: Qwen3.8-Flash-Next long-context decode, +66% at 131k#9firelzrd wants to merge 1 commit into
firelzrd wants to merge 1 commit into
Conversation
Its only consumer is the RMS norm below it, and RMS norm is scale invariant: rms(x*s) = x*s / sqrt(mean(x^2)*s^2 + eps) = x / sqrt(mean(x^2) + eps/s^2) so dropping the divide only moves the effective epsilon from eps to r^2*eps -- 1e-6 to 1.6e-5 against a mean square of order 1. The scale was a full read and write of [idx_dim, n_blocks] f32 per layer per ubatch: 2.25 ms of a 97 ms decode step at 131k context. This is the one patch in the series that is not bit-exact by construction. Measured on Strix Halo (gfx1151, Vulkan), ctx 262144, f16 KV, MTP n_max=3: 131072 goes 28.26 -> 32.46 t/s cumulative with the previous patch.
Nathanw1014
changed the base branch from
release/v0.7.4-staging
to
release/v0.7.5-staging
September 9, 2026 10:40
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.
Six patches on
release/v0.7.4-staging(ea35c5066) that cut work the QSA indexerwas repeating, plus two dead allocations in the caches around it. No shader is
touched: every change is in how the graph is assembled, or in what the memory module
keeps.
Why
Qwen3.8-Flash-Next slows down badly with context. On a Strix Halo it does 47 t/s at
8k and 25 t/s at 131k. Profiling the decode step with
GGML_VK_PERF_LOGGER=1at twodepths and taking the increment says where it goes:
FLASH_ATTN_EXTCONTTOPK_QSA GET_ROWSGET_ROWSRMS_NORM_MULROPEADDAttention itself is 29%. The other 55% is the plumbing that feeds the indexer, and
most of it is recomputed from scratch every ubatch even though nothing it depends on
has changed.
Result
Qwen3.8-Flash-Next UD-IQ4_XS,
-ctk f16 -ctv f16, MTPn_max=3, ctx 262144,llama-server with prompt cache reuse,
temp=0,n_predict=160, gfx1151 / Vulkan:+66% at 131k. The gain scales with context because what is removed is
proportional to
n_kv, so there is little to win at 2,048 and a lot at 131,072.The 2,048 row moves around between runs rather than tracking the patches: with MTP
on, a run that happens to accept one more draft token per step reads several percent
faster, and at that depth the patches themselves are worth almost nothing. The deep
rows are the ones that say what the series does.
Also measured on 2x RTX 3090 (CUDA, IQ1_S,
-ctk q8_0 -ctv q8_0, no MTP, ctx154624): 131,072: 11.97 → 13.52 t/s, +13.0%. What is removed is memory traffic
and dispatches proportional to
n_kv, and a 3090 has several times a Strix Halo'sbandwidth, so removing the same bytes buys less. The two V narrowings gave back
700 MiB there, which moved that host's usable context ceiling from 157696 to 180224.
The patches
set_input_qsais an O(n_kv)scan and it ran once per QSA layer on byte-identical data: twelve scans where one
would do, about 41 ms per step at 131k, on the CPU and invisible to a GPU
profiler. Not new work: this is upstream ggml-org's code, from the commit
that added qwen4exp (
6c84c7d5d, model: add Qwen3.8-Flash-Next (qwen4exp) ggml-org/llama.cpp#27742). This fork'sindependent port did not carry it, so it is picked back out and applied on its
own; authorship on the commit is Daniel Han's.
ggml_conton the block-mean slices —ggml_addhas no contiguityrequirement on either the Vulkan or the CUDA backend (both gate it on type alone
and index through the
nbstrides), andggml_dup_tensoralready gives the sum acontiguous home. The copies were about 34 MB per ubatch at 33k over 12 layers.
Addition order is unchanged, so the arithmetic is identical.
changes again: its cells are written once, and pooling, normalisation and rotation
are all position-determined. Rebuilding every block every ubatch was about a
quarter of a 97 ms decode step at 131k. The commit message documents four things
this has to get right, each of which cost a real bug during development.
build_qsa_top_konly evercalls
cpy_kandget_kon that cache; the indexer scores blocks against a keyand has no value side.
llama_kv_cacheallocated one anyway at the model's ownn_embd_head_vof 256. Worth 1.6 GiB at ctx 262144 with an f16 cache.cache patch 3 adds.
KV buffer size768.01 → 387.01 MiB, and GTT after load movesby the same 381 MiB with every other buffer unchanged to the byte.
1/rscale — its only consumer is an RMS norm, which is scaleinvariant:
rms(x*s) = x / sqrt(mean(x^2) + eps/s^2). This is the one patchthat is not bit-exact by construction: the effective epsilon moves from
epstor^2*eps, 1e-6 to 1.6e-5 against a mean square of order 1. It is worth 2.25 ms ofa 97 ms decode step at 131k.
It is last on purpose. Everything below it is bit-exact, so if that trade is not
wanted, dropping the top commit leaves a series that changes no output at all.
The per-commit numbers were taken in the order the patches were originally
developed, which had 6 in third position. The endpoints are unaffected; only the
intermediate cumulative column would shift.
Verifying
Speed alone will not catch the failure modes here. Patch 3 in particular can be
completely inert — recomputing everything every step — and still produce correct
output, so a correctness suite that only walks forward passes while the cache does
nothing. What was used:
except patch 6 is bit-exact.
shrinks the cache through
seq_rm; the changed prefix goes through the server'sprompt cache and its restore path, which never calls
seq_rmat all. The same spanmust give the same output however it was reached.
GGML_VK_PERF_LOGGER=1aggregated over 40+ decodesteps. This is what caught an invalidation bug: end to end the build was only 5%
slow, but
GET_ROWShad not moved at all, which meant the incremental path wasnever running. Speculative decoding calls
seq_rmon every single step, andtreating that as "forget everything" makes the cache correct and useless at once.
One caveat on that profiler: it inserts a sync per op, so an op's time there is not
its contribution to the step. Two other candidates were dropped after checking that
distinction end to end.
Not included
FLASH_ATTN_EXTis 4.5 ms there against 4.6 ms at 32k, so the gather already capsit. Ranking on the 32k profile alone would have overvalued this.
be the identity, and checking that is an O(n_kv) CPU scan at graph build time — the
cost patch 1 removes, reintroduced in another form.