Files
serverAppConfigs/serverAppConfigs-docs/LLM/Llama Cpp Serving Sizing.md
T
Frank a5fe045b58 docs(vault): one fact, one owner — repair the doubled pages, single-source the facts
Structural repair:
- 13 pages were the same document twice (each exactly 2x its HEAD size),
  a second frontmatter block whose closing fence had been glued onto a body
  line by a frontmatter rewrite that re-read its own output. Un-doubled by
  cutting at the second frontmatter and keeping the copy whose body is a
  superset; the orphan census reports 0 lost tokens against HEAD.
- log.md: one entry per date, bare `## YYYY-MM-DD` headings, newest first —
  66 dated headings merged into 34, titles demoted to bold leads, every
  content line verified present. The file had been descending at the head
  and ascending at the tail.

Ownership moves:
- New `LLM/frank-medium.md` is the locator for the retired Dankserver
  endpoint: era windows quoted from each era page's own banner, no numbers
  restated, and the two unresolved date conflicts recorded instead of
  silently resolved. `frank-archon.md` links to it instead of restating the
  pre-rename window.
- The four Pipeline-Parallelism stubs (frontmatter only, no body) are gone;
  `Multi-Node Runtimes.md` owns runtime construction, launch, mixed-vendor
  verdicts and cross-split spec decode, and the four links that pointed at
  the stubs were repointed at it or at the drafter page.
- Authentik's three stub pages fold into `Authentik.md`, with the process
  detail and the outpost reference split out so the parent stays under the
  length hint.

Verified corrections (host census + live probe, 2026-10-19):
- The MXFP4 checkpoint two pages called the "on-disk rollback" is gone:
  /home/dank/models/ holds StillDeadcode, rad and tpl.jinja. Stated once, on
  the era page that owns that payload; the swap page now links to it.
- frank-medium-radiance-mxfp4: the 4.24/7 accept rate belongs to the DFlash2
  arm measured on the FP8 target, not to the MXFP4 era; the 3-family soak run
  census is kept once (155 requests) instead of twice (~120 and 155).
- Fleet shapes re-confirmed against the live endpoints before touching any
  page: 3x200000 fp8 KV, 2x200704, 2x262144.

Index rows are regenerated, not hand-carved. Net -1,849 vault lines.
2026-10-19 05:00:43 -07:00

10 KiB
Raw Blame History

type, title, description, tags, timestamp
type title description tags timestamp
reference Llama Cpp Serving Sizing Measurement-first sizing for llama.cpp shapes: slot pools against a headroom band, per-device accounting, prompt cache.
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.