llama : add an attention split separate from the tensor split - #69
Open
Piggidragon wants to merge 10 commits into
Open
llama : add an attention split separate from the tensor split#69Piggidragon wants to merge 10 commits into
Piggidragon wants to merge 10 commits into
Conversation
This was referenced Sep 3, 2026
Piggidragon
force-pushed
the
mgpu/attn-split
branch
from
September 4, 2026 22:01
f2d9059 to
1311c7b
Compare
Owner
|
Automated preliminary review by Codex; the repository owner plans a separate manual review. Verdict: FAIL. Blocking
Will slow review
Developmental progressThe broad #57 predecessor was split. This version fixes the maximum-device off-by-one at Reviewed head: 1311c7b |
Piggidragon
force-pushed
the
mgpu/attn-split
branch
from
September 6, 2026 06:18
1311c7b to
dd7420c
Compare
A copy of an input is named "<backend>#<source>#<copy>" in a name field of fixed size. With many devices the backend label of the meta backend lists all of them and the source name is what gets cut, so a consumer can no longer tell which tensor the copy was made from. Cut the label instead. Assisted-by: Claude Opus 5
A KV cache in host memory reaches attention as a scheduler copy, which is a leaf in the compute buffer. Such a leaf never reached the device split-state callback and fell through to MIRRORED, while the queries stayed split by head: each device then attended heads whose keys live on the other device. With more than one KV head that aborts in the FlashAttention kernel, or returns wrong output where the query split happens to stay a multiple of the KV head count. Offer a copied-in leaf to the callback under the name the graph gave it, and let the callback recognise the cache there. The cache folds its heads into one flat axis but the copy arrives permuted, with the heads on an axis of their own, so its segments and granularity are rescaled to whole heads. Assisted-by: Claude Opus 5
The KV granularity was derived from the query granularity through n_gqa, which assumes the V side has the head size of the K side. Count whole KV heads and scale each side by its own head size. Assisted-by: Claude Opus 5
A fused QKV puts Kcur and Vcur in a strided view, which a host-resident cache reads back through the meta buffer. The rows split, so the chunk splice could not express the read. Assisted-by: Claude Opus 5
A linear-attention op packs the state it writes back together with its output, so that split does not line up with the one a host-resident state expects. On device the two orders agree; in host memory they disagree, and the split state of the fused op no longer resolves. The state is small next to the attention cache that -nkvo exists to move, so keep it device-resident. Assisted-by: Claude Opus 5
Run the tensor-split architecture matrix a second time with the cache in host memory, and add an 8-device CI run, where the scheduler copy name is long enough to be truncated. Assisted-by: Claude Opus 5
The strided head split path ran only for a single stream, so a host-resident cache built with --parallel N fell through to the chunk splice, which cannot express that write. Loop over ne[3] and offset each side by its own stride. Assisted-by: Claude Opus 5
A split that ends with a view of a host tensor left the subgraph bookkeeping short of the node count and aborted. That happens with more than one cache stream, which the test matrix now covers. Also make the scheduler copy name a stated contract instead of a grammar that two files reconstruct on their own, note the 2d transfer fallback at both strided cache paths, say out loud that split mode tensor overrides the recurrent state placement, and record the Gemma 4 host cache accuracy gap where it is skipped. Assisted-by: Claude Opus 5
The share of the attention heads a device gets decides both how much of a host- resident cache it receives and how much attention work it does, and neither has to follow the memory split. Add --attn-split (-as) to set it; unset it follows --tensor-split and nothing changes. The share is rounded to whole heads by the granularity rules that already keep GQA consistent. Assisted-by: Claude Opus 5
std::stof took a partial parse, a negative, a NaN and an all-zero set without a word, and those reach a cumulative division and a conversion to whole heads. Reject them at the command line and fall back to the tensor split at the API boundary. Also say what the rounding does instead of promising the nearest share. Assisted-by: Claude Opus 5
Piggidragon
force-pushed
the
mgpu/attn-split
branch
from
September 6, 2026 06:43
dd7420c to
dd75518
Compare
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
Adds
--attn-split(-as), the fraction of the attention heads each GPU gets under--split-mode tensor.That share decides two things: how much of a host-resident KV cache a device receives, and how much
attention work it does. Neither has to follow the memory split. On an asymmetric pair the device with
the slow host link is exactly the one that should hold less of the cache, while the memory split still
wants to fill both cards.
Unset, it follows
--tensor-splitand nothing changes. Only the attention tensors and the cache move;the rest of the model still follows
--tensor-split. A linear-attention layer is a differentmechanism and keeps
--tensor-split. The share is rounded to whole heads by the granularity rulesthat already keep GQA consistent, so a ratio the head count cannot express lands on the nearest one it
can.
Stacked on the host-cache correctness PR, which is what makes a host-resident cache work under
-sm tensorat all.This is a new CLI and public API addition, so per CONTRIBUTING it carries a higher bar. The argument
for it over the existing mechanism:
--tensor-splitcannot express it. With a device-resident cacheboth devices run concurrently inside a layer and the collective is a barrier, so
--tensor-splitwants the compute ratio; with a host-resident cache the cache placement wants the bandwidth ratio.
One knob cannot serve both, and the measurements below show the second one is worth several times more
than the first on this pair.
Testing
Two GPUs, RTX 4070 (gen4 x16) + RTX 3060 (gen3 x4), CUDA, NCCL, stock clocks.
Qwen3.8-27B-UD-Q5_K_M, a 56935-token prompt from this repository's docs,
-c 65536 -n 128 -ngl 99,q8_0 cache,
-sm tensor -ts 50,50 -nkvo --kv-cpu-pinned --recurrent-state-offload. The model hasn_head_kv = 4, so the whole-head shares available are 2,2 / 3,1 / 4,0.--attn-split-ts)0.75,0.253,11,03.03x generation from moving the cache off the slow card, and +16% prefill.
0.75,0.25and anexplicit
3,1agree to within noise, which is the rounding working: a ratio the head count cannotexpress takes the nearest one it can, and it lands strictly between its two neighbours.
Setting
--attn-splitwhen it is not used costs nothing.llama-bench -ngl 99 -ts 50/50 -r 3,all 16 rows against the parent PR without this commit:
tests/test-llama-archs.cppruns the tensor-split matrix a third time with all attention heads on thefirst device (
attn_split = 1,0) and the cache in host memory, checking the logits against CPU.test-llama-archs -s 1at 1, 2, 3 and 4 virtual CUDA devices: passes.test-arg-parser: passes.Built with
-DLLAMA_FATAL_WARNINGS=ON.Not done here
-asforllama-bench, which only takes-sm,-ts,-ub,-ngland-fa; the sweeps below hadto be run through
llama-completion.Requirements
Assisted-by:commit trailers.