Files
serverAppConfigs/serverAppConfigs-docs/LLM/Llama Cpp Serving Sizing.md
Frank 04f4e2c6d4 docs(vault): strip restated fleet facts from 17 technique pages
Enforces "one fact, one owner": technique pages keep mechanism, laws, traps
and verdicts; per-endpoint shapes, digests and era measurements are cited to
the endpoint page that owns them.

Also repairs the four pointers the pass left empty (each verified against the
cited owner before landing):
- EMULATION dequant doubles host RAM (118 GiB -> ~238 GiB) -> [[frank-archon]]
- 12.11 tok/s flat 304 -> 16,095 tokens -> [[Tcclaviger Fork Ops]]
- bundled-head acceptance ladder 0.739/0.554/0.427/0.340 -> [[Qwen3.8 PP Deployment]]
- llama.cpp PR #23897 dedup provenance -> [[qwen3.8-spec-matrix]]

Replaces the retired-shape illustration in Llama Cpp Serving Sizing with the
owned MiMo Q5_K_M row (6.40 GiB payload -> 20,530 MiB used), and unwraps a
hard-wrapped [[Dankserver System Specs]] link that failed link integrity.

Orphan census over the diff: 0 numbers lost (comma-insensitive match against
the whole vault plus AGENTS.md).
2026-10-19 01:33:47 -07:00

10 KiB
Raw Permalink Blame History

type, title, description, tags, timestamp
type title description tags timestamp
reference Llama Cpp Serving Sizing Slot-pool sizing against a headroom band, per-device VRAM accounting, prompt-cache/state sizing and the QSA flash-attn fix — the measurement-first procedures for llama.cpp serving shapes.
llama.cpp
vram
kv-cache
slots
flash-attn
sizing
2026-10-05T00:00:00-07:00

Llama Cpp Serving Sizing

1. Slot-pool sizing against a headroom band

Goal: choose CTX_SIZE/PARALLEL/KV types so N conversations fit a GPU while keeping a requested amount of VRAM free — from measurement, never arithmetic guesswork.

  1. Fix the invariants. Read the payload's KV geometry (attention layers only, KV heads, head_dim, KV dtype bytes) and the per-slot rule n_ctx_slot = CTX_SIZE / PARALLEL. A hybrid model with kv_unified = 'false' gives each slot its own cache AND a fixed per-slot state buffer — slot count is a memory term, not free.
  2. Two boots on the real card (production stopped, per the bench law), each near the target; record nvidia-smi used/total plus the boot log's n_slots/n_ctx_slot: shape A = current/cheap, shape B = new demand. non-KV = used − (tokens × KiB/token); the two rows separate weights/buffers/graphs from the new demand; a projector is billed after the fit at roughly 1.5x its file size (frank-zealot Contract has that measurement).
  3. Solve the band: KV_tokens ≤ (card − non-KV − headroom_low) / KiB/token, round the pool DOWN to a multiple of PARALLEL × 4096 so every slot gets an equal integer window. Boot and measure; if outside the band, walk one step — never re-derive from a formula.
  4. Propagate the per-slot number — it is the client contract: models.yml contextWindow = n_ctx_slot (NOT the model's native window), or long conversations die with 400 exceed_context_size_error instead of compacting; compaction/summary budgets sized to the old window get re-checked; endpoint docs and the rollback table move too.
  5. Prove behaviour: boot-log slot count, nvidia-smi free in band, N concurrent requests answered in parallel with N+1 queued, and — if a projector ships — a solid-color image probe through an image_url content part expecting the exact color. The legacy top-level images field is silently ignored, so a probe that uses it measures a hallucination, not vision.

Gotchas: shorter slots can make decode FASTER (~5 % measured); the KV pool is allocated whole at boot so post-boot memory.used IS the ceiling; record both measured rows in the service .env comment block and mirror the non-KV price in the bats budget gate so a shape that busts the band fails the suite instead of the boot.

2. Per-device VRAM accounting ("where did the memory go?")

First decide which kind of memory is in question:

device kind read it with
RTX 3090 (Dankclaw) discrete GDDR6X nvidia-smi --query-gpu=memory.total,memory.used,memory.free
gfx1151 iGPU (Dankclaw) UMA — "VRAM" is host RAM /sys/class/drm/card1/device/mem_info_gtt_{total,used} vs mem_info_vram_*; the aperture and carve-out values are Dankclaw System Specs's
R9700s (Dankserver) discrete rocm-smi --showmeminfo vram; the per-card byte/MiB value (and the stale "64 GiB SVM-backed" figure it replaced) is Dankserver System Specs's

Per-process truth on NVIDIA: nvidia-smi --query-compute-apps=pid,process_name,used_memory. Never add the iGPU's GTT to host RAM as a separate pool; free -g is the arbiter for co-tenants.

  • Engine ledger first. Halogen prints weights pinned (+ remaining 2 MiB contiguous blocks — the honest fragmentation signal: a block census that drops across a restart means the model did not come back into the same hole; the before/after census this was learned on is that era's, on frank-dragoon), device budget, KV pool/working split, "left for everything else". llama.cpp prints them at llama-model.cpp:1827 (per-backend model buffer) and llama-context.cpp:692 (compute buffer) on the era's build; on today's master the same LLAMA_LOG_INFO lines sit at llama-model.cpp:2009 ("model buffer size =") and llama-context.cpp:842 ("compute buffer size =") (source, re-read 2026-10-18) — but llama-bench suppresses loader INFO and a systemd unit did not put them in the journal (verified grep -c = 0). Recipe: one launch through llama-cli/llama-server keeping the first ~200 lines.
  • Otherwise compose from tensor sizes. Group the GGUF tensor table by role (Flash-Next example: 75.0 GiB experts at 1.5625 GiB/layer, 32.8 GiB PLE table, 3.2 GiB everything else), KV ≈ 2 KiB/position per f16 attention layer, × derived context. Vendor delta is real: same layers+KV cost 12.80 GiB under Vulkan vs 14.99 GiB under CUDA (~3.3 vs ~1.1 GiB of graph/compute pool).
  • Traps: mmap'd PLE is reclaimable page cache; -ot makes it pinned — "move layers to the card" converts reclaimable cache into device memory and frees nothing for co-tenants. A served model's footprint is not its weight size: the MiMo Q5_K_M era booted a 6.40 GiB payload to 20,530 MiB used of the 3090's addressable 24,576 — the Eras row and its addressable ≠ advertised law are on frank-zealot. What the card reports used is weights + KV + compute buffers + projector, never weights alone. Do the capacity subtraction before designing, on the card number the owner page quotes, not the advertised one.
  • Context lengths: derive, never remember. llama-bench computes n_ctx = n_prompt + n_gen + n_depth (defaults 512/128/0, type_k/v F16, flash_attn AUTO, split_mode LAYER). -p 512,16384 -n 64 = contexts 576/16,448; a plain tg row has ~zero depth — -d is the only thing that puts KV in the picture.
  • Label every number measured / derived / inference, and state the context each figure was taken at — 12.8 GiB at 65,568 tokens is not the same claim as at 200k.

3. Prompt cache and state sizing

--cache-ram (alias -cram, common/arg.cpp:1719) is HOST RAM for saved prompt states — unpinned, on demand, moves no VRAM. --ctx-checkpoints (alias -ctxcp/--swa-checkpoints, common/arg.cpp:1701) is a different mechanism: per-slot KV snapshots; 0 disables them. Both flags are current upstream; the two defaults quoted here (8192 MiB and 32) are the era's values and were not re-readable in the upstream sources checked (unverified 2026-10-18) — print them from the binary (llama-server --help) before sizing on them.

State law (compute before trusting any cap): a state is the serialized KV of the whole sequence + constant recurrent state + the draft context's KV: per_token = 2 × n_full_attn_layers × n_kv_heads × head_dim × bytes (K and V) + draft_per_token. The worked qwen35-hybrid instance of that formula (full layer count, f16 KV, MTP draft) and the slot-size table it prices are on frank-medium-llamacpp-dflash2; cross-check any such arithmetic against the journal's prompt state size lines — the two must agree.

The journal is the instrument: exceeds cache size limit … skipping = cap binding in production; making room … removing oldest / cache size limit reached = eviction, and the printed size proves the active cap. Do NOT claim a raise is proven by prompt_tokens_cached_total rising — on exact repeats that counter is 0 by construction.

Hybrid + zero-checkpoint trap: for a hybrid (DeltaNet/SSM + attention) model, seq_pos_min sits at the END of the slot's own prompt (a recurrent cell stores only the last position), so with --ctx-checkpoints 0 there is no checkpoint to fall back on and the server logs forcing full prompt re-processing … (likely due to SWA or hybrid/recurrent memory). Consequences: intra-slot reuse only survives when the request echoes the assistant's previous reply verbatim (a pure user resend is always a full re-prefill even at f_sim 1.000); the RAM prompt cache becomes the ONLY carrier of a conversation state ACROSS slots, so a cap that refuses deep states costs minutes of re-prefill whenever slots are reassigned; state restoration only runs via the LRU path (or LCP with f_keep < 0.5) — a plain prefix hit never calls prompt_load.

Verify a change: live argv carries the flag (not the deploy log); /health 200 + real completion; VRAM unchanged and host RAM mostly free; no new exceeds cache size limit lines, with a making room entry larger than the old cap as direct proof. If the ceiling itself was not exercised live, say so — crossing it needs a >120k-token conversation and must not run beside a live service (bench law).

4. QSA flash-attn assert fix (qwen4exp)

--flash-attn on aborts graph build for qwen4exp models: GGML_ASSERT(a->type == GGML_TYPE_F32) at ggml.c:5371 through build_attn_qsa. Root cause: build_attn_qsa calls ggml_fill on the F16-cast kq mask returned by get_kq_mask() when flash attn is on, but ggml_fill requires F32.

Fix (mirrors the MLA sparse path in llm_graph_context::build_attn): run the top-k mask surgery on the F32 base mask (inp->self_kq_mask), cast to F16 only at the attention call when flash_attn is enabled (kq_mask_attn = cparams.flash_attn ? ggml_cast(ctx0, kq_mask_top_k, GGML_TYPE_F16) : kq_mask_top_k). The patch lives at scripts/patches/qwen4exp-qsa-f32-mask.patch.

Only the charlie12345/ROCmFPX build-qwen38 (PR #27742 qwen4exp merged) takes it; CIRU, upstream and stock builds do not. Validate live: load with --flash-attn on, health 200, a real completion finishing "stop", journal clean of GGML_ASSERT/cudaMalloc/missing tensor, and flash-attn engaged in the timings. The context cap that build reached for full-model residency is part of that era's record on frank-dragoon.