openpangu: fused latent attention op (GGML_OP_LATENT_ATTN) - #2168
Merged
ikawrakow merged 1 commit intoJul 23, 2026
Merged
Conversation
Adds ggml_latent_attn_prefix_ext / ggml_latent_attn_indexed_ext: MLA latent-cache attention with an always-visible learned K/V prefix (openPangu's 128 param_sink rows), joint softmax over [prefix | cache], reading the raw F32/F16/Q8_0 latent cache directly. CUDA implementation plus a scalar CPU reference that pins the op's semantics; the CPU backend reports support truthfully, and openPangu adopts the op only on a non-CPU backend as builder policy. openPangu routes its dense/SWA/MTP full-span attention and the gathered DSA path through the op, capability-gated per layer on the attention output projection's scheduled backend, with the latent cache required resident on that same backend (--no-kv-offload keeps the unfused chain); any layer whose backend cannot run the candidate keeps the exact unfused chain.
4 tasks
Owner
I didn't say that you need to try to squeeze the prefixed FA into the regular DSA path. The main purpose of handling it via the existing But OK, let's do it that way. |
ikawrakow
approved these changes
Jul 23, 2026
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.
This is the
GGML_OP_LATENT_ATTNsplit requested in #2159. The ablation there put nearly all of the TG improvement on this op, so this PR carries exactly that piece, rebuilt as one commit on current main, with applicability checking cut down to a single per-layer capability gate. Any other #2159 ops are deferred to one-by-one follow-ups under the openPangu + DS4 usefulness bar. This op fuses openPangu's latent-attention chain, scores through joint softmax over the learned param_sink prefix rows plus the cache through value contraction, into one node.GGML_OP_LATENT_ATTNis:ggml_latent_attn_prefix_ext(q, cache, prefix_k, prefix_v, mask, ...): dense mode. q is [Dk, T, H] F32; cache is [Dk, N] in F32, F16, or Q8_0 through a windowed row view. F32/F16 rows are read in place; Q8_0 is dequantized per call into a pool-allocated F16 buffer (packed rows required). The prefix rows are always visible at bias 0 and jointly softmaxed with the cache scores; the mask applies to the cache segment only. op_params carry dv, dv_off (MLA value-slice layout, so DeepSeek-style rope-first packing is expressible), scale, and max_bias (asserted zero; the op has no ALiBi and the gate refuses it rather than silently ignoring it).ggml_latent_attn_indexed_ext(q, cache, prefix_k, prefix_v, indices, ...): sparse mode for DSA decode. Same contract, but attends only the top-k cache rows selected by the existing indexer (indices [topk, T]). The CUDA path gathers the selected rows into bounded in-op tiles rather than materializing a graph-level gathered cache tensor, so the graph carries noggml_get_rowsresult and the tile memory is pool-reused. openPangu's indexed sites pass no mask (selection is already causal-masked upstream); the indexed-mode mask path exists on both backends but has no in-tree user yet.ggml/src/ggml-cuda/latent_attn.cu) plus a scalar CPU reference forward inggml.cthat pins the op's semantics; the CPU backend truthfully reports support, and not adopting the op on CPU-resident layers is performance policy in the openPangu gate below. Internal compute precision: for an F16 or Q8_0 cache the CUDA path runs the score and value GEMMs with F16 inputs (the F32 query range-scaled to F16, softmax weights stored F16) and F32 accumulation; an F32 cache keeps the whole chain F32. The fused/unfused perplexity comparison below bounds the effect. The op'sop_paramsjoin the CUDA-graph reuse fingerprint (same treatment asGGML_OP_SCALE).Routing in
build_openpangu.cpp: the dense/SWA/MTP full-span sites and the DSA decode site build the op when a per-layer capability gate passes; otherwise they build exactly main's unfused chains, which remain intact as the fallback. The gate is one check per site: a probe node tested withggml_backend_supports_opagainst the scheduled backend of the layer's attention projection, refusing the CPU backend by policy, and requiring the latent cache resident on that same backend, so--no-kv-offloadkeeps the unfused chain instead of re-uploading the cache view every graph. For an A/B against the unfused path, makeopenpangu_fused_attn_enabled()return false, the same condition flip used for the #2159 ablation; there is no build switch and no env var. The chunked-prefill score buffering is retired on op-served paths.On the
ggml_flash_attn_set_prefixalternative sketched in #2159: I read the DSA-in-FA path before deciding. The API shape would be natural (the src[4] sink / src[5] indexer attachment precedent), but the cost is in the kernels, and two things block it: (1) the prefix rows are learned weights with independent K and V, so they can neither be represented as cache rows nor ride the sink mechanism, whose sinks are per-head scalar logits; serving them through FA means second-source score and value accumulation in every kernel variant openPangu touches, and FA exposes no LSE for a post-hoc merge. (2) The measured risk runs the wrong way: my earlier openPangu FA experiments traded TG away for PP, and the #2159 ablation shows this op carries the TG win. fattn has no F32 K/V path, while the op reads F32/F16/Q8_0 in place, which only matters for-ctk f32.DS4's FA serves it correctly by construction (scalar sinks, top-k as
cur->src[5], F16 cache), so neither blocker exists there and nothing asks DS4 to move; the dedicated op exists for the one corner FA cannot express, independently learned prefix K/V rows attending jointly with the latent cache. The op carriesdv_offso a DS4 trial stays expressible later. The indexed mode is close in shape to the dsa_attn gather, and a later convergence of openPangu decode onto that machinery with prefix-row support could collapse the two implementations.CPU-only inference: as noted in #2159, this PR does basically nothing there, by design. The builder gate adopts the op only on a non-CPU backend, so CPU-only builds keep the vectorized unfused chain and the expected delta is zero.
Validation (RTX 4070 12 GB + i7-11700K, 64 GB RAM, CUDA build, openPangu-2.0-Flash Q4_K_M,
-ngl 999 -ot exps=CPU -fa off -ctk q8_0 -ictk q8_0 -fidx -dsatk 64; every item below was run at this PR's head, raw artifacts retained):GGML_SCHED_DEBUGdump shows every LATENT_ATTN node (184/184 at-c 2048) scheduled on CUDA0 with zero CPU placements, sources verified as q, the raw latent-cache view, both param_sink tensors, and the mask (dense mask on dense layers, SWA-windowed view on SWA layers). The-fidxindexer top-k nodes from indexer_topk: fix quantized q8_1 scratch sizing on CUDA #2158 compose underneath unchanged.-ngl 0(CPU-resident layers) and--no-kv-offloadboth build zero LATENT_ATTN nodes and keep the exact unfused chain.-c 8192are clean and coherent, and byte-stable across repeated fused runs. The fused continuation diverges from the unfused one at a near-tie token, which is the expected effect of the F16 internal compute disclosed above; the perplexity comparison below is the quantitative bound.-ctk/-ictk, F16 latent cache) and a run without-fidx(unfused indexer scoring feeding the same selection path) both produce clean, factually correct greedy output, and the cache-type flags verifiably change the load (K f16 103.50 MiB vs q8_0 54.98 MiB at-c 2048).-c 34816; greedy decode returns the needle verbatim as the first generated tokens through the fused path.-c 34816(llama-cli defaults,-ub 512): KV 1009.71 MiB (q8_0) + 72.25 MiB indexer, CUDA0 compute buffer 301.00 MiB, 9989 graph nodes / 90 splits.bulgaria/quicksort/sayap/double-link-listall generate as expected.Performance (warm A/B/A/B vs main
e5357286c, same binary pair, per-N_KV medians of same-arm pairs, quiet host):Model: openPangu-2.0-Flash IQ4_NL imatrix (ji-farthing/openPangu-2.0-Flash-ik-llama-GGUF),
-ngl 999 -ot exps=CPU -fa 0 -ctk q8_0 -ictk q8_0 -fidx -dsatk 64 -t 8 -c 34816 -b 2048 -ub 2048 -n 512, llama-sweep-bench. Worst spread between same-arm pairs was 0.27% on S_PP and 2.02% on S_TG.Buffer accounting at this configuration, from the two load logs: the KV layout is identical in both builds (CUDA0 KV buffer 1009.71 MiB q8_0 plus the 72.25 MiB indexer), while the CUDA0 compute buffer drops from 2808.27 MiB on main to 1204.00 MiB with the op (graph nodes 14177 to 10565, splits unchanged at 90). Against that reduction, the op's transient pool scratch peaks near 135 MiB here: the capped 64 MiB score tile, its 32 MiB F16 weights companion, and a 38 MiB Q8_0 dequant window, all reused across layers by the pool allocator.