Repository navigation
Conversation
On several GPUs the fit treated llama.cpp's cards as one pool: the summed budgets less the summed weights, KV and overhead. It then wrote -ts as each card's budget in GiB. llama.cpp places whole layers, though, and with --n-cpu-moe the layers whose experts stay on the GPU are the last ones. The Xeon fixture's Flash-Next IQ4_NL plan, -ts 30,30 --n-cpu-moe 28 at 262144 x 4, therefore put 43.5 GiB on the second 32 GiB card. The fit now sizes each card from the layers llama.cpp b11749 gives it with -ngl 999 (src/llama-model.cpp load_tensors): - layer il goes to the device upper_bound(cumsum(ts) / sum, il / (layers + 1)); - the output layer, holding lm_head, goes the same way and lands on the last card; - the first --n-cpu-moe layers keep their experts in host RAM; - each layer's KV or recurrent state sits on its card; - every card carries its own overhead. -ts is now integer layer counts summing to layers + 1, so each card gets exactly the layers it was sized for. Every card is held to the smallest budget of the cards used, because the device plugin orders them inside the pod. The largest card load is balanced, and a split that cannot fit is refused. The experts-in-DRAM placement uses the same per-card search, so -ts and --n-cpu-moe are chosen together. --explain lists each card, and --json has card_bytes. Flash-Next IQ4_NL on the Xeon is now -ts 35,14 --n-cpu-moe 29 at 262144 x 4: 29.9 and 28.9 GiB on cards with 30.4 and 30.2 GiB budgets. Tests: - llama_layer_devices matches the upper_bound rule. - The old -ts 30,30 --n-cpu-moe 28 plan works out to 43.5 GiB on its second card. - For the Flash-Next IQ4_NL plan and a two-card Gemma QAT layer split, each card's bytes, recomputed in the test from the flags, stay within budget and agree with card_bytes. - A split that cannot fit is refused. - fit_test.sh checks the Flash-Next plan's -ts and card_bytes.
…picks its card count
The per-layer weights counted the MTP head for every format, so a model sized from
safetensors for a GGUF conversion carried 4.9 GiB (Qwen3.8-Flash-Next) on its output
layer that model_profile leaves out of the bytes to serve. One-card moe-offload plans
therefore no longer matched the old arithmetic: Intel/Qwen3.8-Flash-Next-W4A16-AutoRound
with --gpus 1 on the fixture Xeon went from --n-cpu-moe 43 to 47. The output layer now
holds the bytes to serve less the lookup tables, experts, other and vision: lm_head, plus
MTP only when the GGUF holds it. The one-card plan is --n-cpu-moe 43 again.
The vision projector was put on the output layer, the last card. llama.cpp loads the
projector through tools/mtmd/clip.cpp, which takes the first GPU backend, so it now
counts with layer 0 on the first card, and --explain says so.
moe-offload always used every free card. Each card is held to the smallest budget of the
cards used, so a smaller card beside two large ones lowers the room on all of them. On
the fixture Xeon with 0, 0, 12 and 14 GiB in use on its cards (budgets 30.4, 30.4, 18.4
and 16.4 GiB), Intel/Qwen3.8-Flash-Next-W4A16-AutoRound at 262144 x 6 with --device gpu was refused
("do not fit 4 card(s) of 16.4 GiB each") while --gpus 2 fit. The fit now tries 1, 2,
... of the free cards by budget (or the --gpus count) and keeps the plan with the most
context, then the fewest CPU-expert layers, then the most sequences, then the fewest
cards; that case is now two cards, -ts 31,18 --n-cpu-moe 41. --gpus above the free card
count is refused, as the GPU placements already refuse it, instead of running on fewer
cards.
The fixture Xeon's GGUF plan is unchanged: -ts 35,14 --n-cpu-moe 29 at 262144 x 4.
Tests (each fails on the previous commit):
- llama_layer_bytes adds up to the bytes to serve less the lookup tables, its output
layer is lm_head alone and layer 0 carries the vision projector, for Qwen3.8-Flash-Next
from safetensors.
- On the mixed-budget Xeon, the automatic moe-offload plan fits, has no more CPU-expert
layers than any --gpus 1..4 plan, uses fewer than four cards and keeps every card in
budget.
- --gpus 3 on the fixture Xeon, two cards free, is refused for moe-offload.
- The test's own per-card arithmetic follows the same vision and MTP rule.
88plug
added a commit
that referenced
this pull request
Oct 29, 2026
…checkpoints With #214, #216 and #217 together, the multi-card llama.cpp path (_llama_multi, which now also runs every moe-offload plan) had two gaps. Its per-layer KV counted a sliding-window layer at the window (1024 tokens for Gemma 4) while llama.cpp b11749 allocates GGML_PAD(min(n_ctx_seq, n_swa + n_ubatch), 256) = 1536 cells per slot, so card_bytes disagreed with kv_bytes. And it left the context checkpoints out of the host bytes and the pod limit, which #216 counts on one card: the Flash-Next IQ4_NL plan on the Xeon fixture reported ckpt_bytes 0 with -ctxcp 4 in its flags. kv_parts now takes the sliding-window tokens from swa_tokens, so per_seq, the per-layer KV and the checkpoints share one cell count, and _llama_multi adds slots x checkpoints to the host bytes. The Flash-Next plan's pod limit goes from 74Gi to 76Gi. Tests: the Gemma split's per-card bytes are worked out at 1536 SWA cells and the KV over the cards equals kv_bytes; a split plan counts its checkpoints in host bytes; the Flash-Next two-card shell check asserts the checkpoints are on the host.
88plug
added a commit
that referenced
this pull request
Oct 29, 2026
…checkpoints With #214, #216 and #217 together, the multi-card llama.cpp path (_llama_multi, which now also runs every moe-offload plan) had two gaps. Its per-layer KV counted a sliding-window layer at the window (1024 tokens for Gemma 4) while llama.cpp b11749 allocates GGML_PAD(min(n_ctx_seq, n_swa + n_ubatch), 256) = 1536 cells per slot, so card_bytes disagreed with kv_bytes. And it left the context checkpoints out of the host bytes and the pod limit, which #216 counts on one card: the Flash-Next IQ4_NL plan on the Xeon fixture reported ckpt_bytes 0 with -ctxcp 4 in its flags. kv_parts now takes the sliding-window tokens from swa_tokens, so per_seq, the per-layer KV and the checkpoints share one cell count, and _llama_multi adds slots x checkpoints to the host bytes. The Flash-Next plan's pod limit goes from 74Gi to 76Gi. Tests: the Gemma split's per-card bytes are worked out at 1536 SWA cells and the KV over the cards equals kv_bytes; a split plan counts its checkpoints in host bytes; the Flash-Next two-card shell check asserts the checkpoints are on the host.
Merged
Owner
Author
|
Superseded by #229 (merged) |
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 several GPUs the fit sized llama.cpp's cards as one pool: the summed budgets against the summed weights, KV and overhead. It then wrote
-tsas each card's budget in GiB.llama.cpp places whole layers. With
--n-cpu-moe, the layers that keep their experts on the GPU are the last ones. So the Xeon fixture's Flash-Next IQ4_NL plan,-ts 30,30 --n-cpu-moe 28at 262144 x 4, put 43.5 GiB on the second 32 GiB card.How llama.cpp b11749 assigns layers
src/llama-model.cppload_tensors, with-ngl 999:ilgoes to deviceupper_bound(cumsum(ts)/sum, il/(n_layer+1));il = n_layer, which is the last card;--n-cpu-moe Nadds CPU buffer overrides forblk.<i>.ffn_*_expswith i < N (common/common.hllm_add_n_cpu_ffn_overrides).Changes
console/fit.py:
llama_layer_devices(ts, n_layer)follows the rule above exactly.llama_layer_bytesgives each layer's GPU weights and KV:--n-cpu-moelayers;config.jsonlayer_types, and each layer's KV or recurrent state sits on its card;lm_head(and the MTP and vision tensors, when the GGUF has them).llama_splitpicks integer layer counts summing ton_layer + 1, so every card gets exactly the layers it was sized for. It keeps every card's layers plus its own overhead within the smallest budget of the cards used, because the device plugin orders the cards inside the pod. It balances the largest card's load, and returns nothing when no split fits._llama_multi): the most context first, then the fewest CPU-expert layers, then the most sequences. On one card that search gives the same arithmetic as before.--explainlists each card's layers, weights, KV and total.--jsongainscard_bytes, andgpu_bytesis the largest card.docs/fit.md: the per-card rule and
-tsas layer counts.Result on the Xeon fixture: Flash-Next IQ4_NL becomes
-ts 35,14 --n-cpu-moe 29at 262144 x 4, with 29.9 and 28.9 GiB on cards whose budgets are 30.4 and 30.2 GiB.Tests
tests/fit_test.py
LlamaSplit:llama_layer_devicesmatches upper_bound, including a 0 entry;-ts 30,30 --n-cpu-moe 28plan, worked out independently in the test, puts more than 32 GiB on its second card;card_bytes;tests/fit_test.sh: the Flash-Next IQ4_NL
--jsonplan has integer-tssumming to 49, is not30,30, and everycard_bytesentry is within budget.The new tests fail on origin/main's fit.py.
An extra sweep, not committed, covered every fixture model and host x
--gpusNone/1/2/3 x--deviceNone/gpu x three ctx/seqs settings: 1152 plans, no exceptions, and 92 llama.cpp split plans each checked per card against budget, with no violation.Run before opening:
Not changed, found while here
A GGUF with tied embeddings has no
output.weight. Gemma 4 is one: insrc/models/gemma4.cpp,outputfalls back totoken_embdwithTENSOR_DUPLICATED. llama.cpp then keeps a copy oftoken_embdon the output layer's device, 0.56 GiB for the Gemma QAT fixture. No placement counts that copy today, single-card included. It is left for a follow-up because it changes single-GPU plans as well.Overlap
#214 and #216 also touch
per_seqand the llama.cpp flags in console/fit.py. Whichever lands later needs a rebase. Hereper_seqgoes throughkv_parts, so #214'sswa_tokensbelongs insidekv_parts.