index.md intro and mermaid nodes still described 4 x 262144 fp8/bf16 and frank-small at 2 x 262144; the active-deployment list had no frank-primary entry at all. Fixed both, plus a malformed GLM list item. log.md gains the shape-D entry (the SEQS=2 constraint, the arena-lever measurement, the 2x2 probe table, the measured 1.99x), and the frank-small entry's stale consequence bullet now points at the resolution. frank-small runbook: the compaction-fit note is corrected - the 200704 window moved frank-primary's trigger to 140,492, which fits a 180,224 slot again.
172 lines
10 KiB
Markdown
172 lines
10 KiB
Markdown
---
|
||
type: reference
|
||
title: "frank-small on Dankclaw — llama.cpp, MiMo-V2.6-Distill-Qwen-9B Q5_K_M GGUF"
|
||
description: "The active frank-small runbook — a CUDA (sm_86) build of llama.cpp master serving bartowski's MiMo-V2.6-Distill-Qwen-9B Q5_K_M GGUF on the RTX 3090: three conversations of 180,224 tokens, mmproj vision, the asymmetric KV contract (f16 K + q8_0 V), the two VRAM measurements that set the pool size, and what to check when it misbehaves."
|
||
tags:
|
||
- llm
|
||
- frank-small
|
||
- dankclaw
|
||
- llama-cpp
|
||
- rtx3090
|
||
- mimo
|
||
- gguf
|
||
- kv-cache
|
||
timestamp: 2026-10-02
|
||
---
|
||
|
||
# frank-small — llama.cpp MiMo-V2.6 Q5_K_M (RTX 3090)
|
||
|
||
> **ACTIVE since 2026-10-02.** One quant step down from
|
||
> [[frank-small-llama-mimo-q6k]] on the same runtime and the same KV
|
||
> contract, then **resized twice the same day** on Frank's orders: first to
|
||
> three contexts with vision, then down to the pool that keeps 2–3 GiB of the
|
||
> card free. Frank's arithmetic request — "as much as can fit while keeping
|
||
> overhead open space to 2-3gb on the 3090" — is answered by the two
|
||
> measurements in *Shape and the KV contract* below; the answer is
|
||
> **3 × 180,224**, not 3 × 262,144.
|
||
|
||
> **The tradeoff is the per-slot window.** Three sessions cost 81,920 tokens
|
||
> of per-conversation memory versus the two-slot shape: 180,224 tokens each
|
||
> instead of the model's native 262,144. See *What three slots cost*.
|
||
|
||
## Identity
|
||
|
||
|Field|Value|
|
||
|---|---|
|
||
|Endpoint|`192.168.0.143:7771`, served alias `frank-small`|
|
||
|Runtime|llama.cpp **master** (qwen35 model support), CUDA build `sm_86`, at `/home/dank/GitPackages/llama-master/build-cuda/bin/llama-server` — unchanged across the quant swap|
|
||
|Payload|`bartowski/MiMo-V2.6-Distill-Qwen-9B-GGUF` **Q5_K_M**, 6,876,124,704 B (6.40 GiB), sha256 `04cf46f2b5584ef922697d6c063f94879434fb86c467f1a09e52daf0cddaf35f` + the repo's `mmproj-MiMo-V2.6-Distill-Qwen-9B-f16.gguf`, 918,166,048 B, sha256 `ff348f3180a63188aa7285db85f550fe38acb61dd013c599eb8bad08d2cc2576` — both at `/home/dank/models/MiMo-Q5KM/`, fetched and byte- and sha256-verified by `scripts/download-frank-small.sh`|
|
||
|Unit|root `frank-small.service` (repo `scripts/frank-small.service`, `User=dank`, relinked), env `scripts/frank-small.env`, serve `scripts/serve-frank-small.sh`|
|
||
|Repo pins|`tests/frank_small_unit.bats` (stub-argv seam + the VRAM arithmetic), `deploy_llm_small` gates in `scripts/deploy-remote.sh`|
|
||
|
||
## Shape and the KV contract
|
||
|
||
`--ctx-size 540672 --parallel 3` — llama.cpp divides the pool across slots, so
|
||
each conversation gets `n_ctx_slot = 180224` (boot log: `n_slots = 3,
|
||
n_ctx_slot = 180224, kv_unified = 'false'`). `CTX_SIZE` MUST stay divisible by
|
||
`PARALLEL`; the bats seam enforces it.
|
||
|
||
The KV contract is Frank's: **K unquantized (f16), V `q8_0`** —
|
||
`--cache-type-k f16 --cache-type-v q8_0`. Two hard dependencies ride on it:
|
||
|
||
- **`--flash-attn on` is mandatory**: quantized V exists only on the FA path.
|
||
Without the flag the engine falls back silently and the contract becomes a
|
||
lie. The serve script pins the trio; the bats seam pins the script.
|
||
- The VRAM budget: attention KV lives in the 8 full-attention layers only
|
||
(4 KV heads × 256 dim). K f16 ≈ 16 KiB/token, V q8_0 ≈ 8.5 KiB/token
|
||
(34 B per 32-element block) ⇒ ~24.5 KiB/token over the pool.
|
||
|
||
**The pool size came from two boots, not from a formula.** `free`-style
|
||
arithmetic on this stack is unreliable because the compute buffers, CUDA
|
||
graphs and now the vision tower all bill separately, so the fit was measured
|
||
at two shapes and solved between them:
|
||
|
||
|Shape|Projector|`nvidia-smi` used|KV at 24.5 KiB/token|Non-KV (weights + buffers + projector)|
|
||
|---|---|---|---|---|
|
||
|2 × 262,144 (pool 524,288)|none|20,190 MiB|12,800 MiB|**7,390 MiB**|
|
||
|3 × 196,608 (pool 589,824)|f16|22,972 MiB|14,113 MiB|**8,859 MiB**|
|
||
|
||
The two rows price the projector and its image buffers at **~1.4 GiB** — not
|
||
the ~0.9 GiB the 918 MB file suggests, because the vision encoder's own
|
||
activation and compute buffers ride along. Solving the second row's non-KV cost
|
||
against Frank's 2–3 GiB band: the card is 24,576 MiB, so
|
||
`KV ≤ 24,576 − 9,216 − 2,048 = 13,312 MiB` ⇒ pool ≤ 556,630 tokens, rounded
|
||
down to a multiple of 3 × 4,096 ⇒ **540,672**, i.e. 13,265 MiB of KV and
|
||
**2,441 MiB free** (measured, 21,684 MiB used).
|
||
|
||
**Why 3 × 262,144 was never possible.** Three native windows are 786,432
|
||
tokens = 18.4 GiB of KV *alone*; plus the 8.86 GiB of non-KV cost that is
|
||
27.3 GiB on a 24 GiB card. The quant step bought 0.86 GiB; the geometry needed
|
||
6 GiB.
|
||
|
||
## Measured on this shape (2026-10-02, 3 × 180,224 + vision)
|
||
|
||
|Check|Result|
|
||
|---|---|
|
||
|Boot to healthy|~20 s, `n_slots = 3`, `n_ctx_slot = 180224`, `loaded multimodal model` logged|
|
||
|VRAM|**21,684 MiB used / 2,441 MiB free** of 24,576 (`nvidia-smi`, steady state; the KV pool is allocated whole at boot, so this is the ceiling)|
|
||
|Decode, temp 0, no thinking|**106.8 tok/s** (256-token completion at ~1,300-token depth)|
|
||
|Prefill|**3,814 tok/s** (same request)|
|
||
|Three concurrent requests|all three answered in 1.7 s wall — real parallel slots|
|
||
|Fourth concurrent request|answered in 3.0 s while three ran — queued behind a released slot, as the slot ceiling requires|
|
||
|Vision|`image_url` part with a 64×64 solid-red PNG → **"Red."**|
|
||
|Tools|native `tool_calls` with correct arguments (`get_weather {"city":"Seattle","unit":"f"}`)|
|
||
|Thinking|`reasoning_content` populated (145 chars on a "think first" prompt); `reasoning_effort: "low"` accepted|
|
||
|Over-slot prompt|`400 exceed_context_size_error: request (184001 tokens) exceeds the available context size (180224 tokens)` — refused, never silently truncated|
|
||
|
||
Decode is ~5 % **faster** than the 2 × 262,144 shape measured earlier the same
|
||
day (100.4–101.8 tok/s): a shorter per-slot window means a shorter attention
|
||
scan at equal prompt depth, and one less slot's DeltaNet state resident.
|
||
|
||
## What three slots cost
|
||
|
||
The per-slot window is the whole price. Two consequences the fleet depends on:
|
||
|
||
- **A prompt over 180,224 tokens is a hard 400**, so `~/.omp/agent/models.yml`
|
||
sets `contextWindow: 180224` for this endpoint — the harness compacts before
|
||
it reaches the ceiling instead of discovering it.
|
||
- **frank-primary's compaction prompt fits inside one slot again.** At
|
||
frank-primary's shape-D window (200,704) its compaction trigger is 140,492
|
||
tokens (70 %), so one compaction request lands here with 39,732 tokens of
|
||
room. At the previous 262,144 window the trigger was 183,501 — *above* this
|
||
slot — and the request had to be truncated or split. Nothing in the fleet
|
||
routes compaction here automatically; the point is that the 2026-10-02 window
|
||
trim restored the fit.
|
||
|
||
## Client contract
|
||
|
||
- Thinking arrives in **`reasoning_content`** (`--reasoning-format deepseek`),
|
||
the field `models.yml` reads for this endpoint.
|
||
- **Thinking is off unless the request asks**: the MiMo GGUF template defaults
|
||
`enable_thinking` false, so `models.yml` ships
|
||
`chat_template_kwargs: {enable_thinking: true, preserve_thinking: true}` in
|
||
`extraBody`. A raw client that omits it gets a direct answer — measured, and
|
||
not a fault. Trivial questions produce no thinking even with the flag on.
|
||
- Tools: `--jinja` + server-side parsing, verified 2026-10-02.
|
||
- **Vision is on**, through the projector file (`--mmproj`). Use the
|
||
OpenAI-standard `image_url` content part — that path answers correctly
|
||
("Red." on a solid-red PNG). The legacy top-level `images` field is **not**
|
||
read by `/v1/chat/completions`: the same request sent that way answered
|
||
"Brown.", i.e. it generated from text alone. `/completion` takes `images`
|
||
but refuses a request without `messages` in this build, so chat is the only
|
||
vision path.
|
||
- Setting `MMPROJ_FILE=` (explicitly empty) in `scripts/frank-small.env` drops
|
||
`--mmproj` and returns ~1.4 GiB of the card to KV — the documented text-only
|
||
opt-out. `MMPROJ_FILE` pointing at a file that does not exist is a boot
|
||
refusal, never a silent fallback to blindness.
|
||
|
||
## No spec decode
|
||
|
||
The GGUF has no MTP tensors (the same fact that made SGLang's NEXTN a 0.00
|
||
ghost), and the cross-model DFlash draft
|
||
(`/home/dank/models/DFlash-Qwen35/qwen3.5-9b-dflash-Q8_0.gguf`) is an
|
||
optimization candidate, not a default — acceptance has to be measured on this
|
||
traffic first. The bats seam fails if a draft flag ships unannounced.
|
||
|
||
## Rollback
|
||
|
||
|Target|How|
|
||
|---|---|
|
||
|Q6_K, same runtime|`scripts/frank-small.env` `MODEL_FILE=/home/dank/models/MiMo-Q6K/MiMo-V2.6-Distill-Qwen-9B-Q6_K.gguf` (7,793,710,624 B, still on disk) and the deployer's `LLM_SMALL_MODEL_FILE` default. Q6_K needs ~1.4 GiB more than Q5_K_M, so pair the rollback with `CTX_SIZE` 49,152 tokens lower or `MMPROJ_FILE=`|
|
||
|Two 262,144 windows|`CTX_SIZE=524288 PARALLEL=2` (measured 20,190 MiB used, 4,386 free) and `contextWindow: 262144` in `models.yml` — choose it when a single conversation needs the native window or when frank-primary's compaction request has to land here|
|
||
|Vision off|`MMPROJ_FILE=` in the env file; the deployer gate then has to be satisfied too, so point `LLM_SMALL_MMPROJ` at any real projector file or drop the gate in the same commit|
|
||
|SGLang + INT8|[[frank-small-sglang-mimo-v26]] — venv and old unit/env/serve trio still in place; the INT8 payload was staged to `/home/dank/.AGENTTRASH/20261002-retired-llm-payloads/` on 2026-10-02, so restore it there or re-fetch with `old/download-frank-small-sglang-int8.sh`|
|
||
|
||
## When it misbehaves
|
||
|
||
- Port 7771 dark: `journalctl -u frank-small -n 50`; the serve script exits 1
|
||
naming the missing file if the binary or GGUF moved.
|
||
- `No CUDA GPUs are available`: the 3090 vanished from the bus or the UVM node
|
||
is missing — `nvidia-modprobe -u -c=0` (the serve script runs it).
|
||
- Requests queue behind each other: three concurrent long generations are the
|
||
slot ceiling by contract; a fourth waits for a slot to free (measured: 1.7 s
|
||
for the three, 3.0 s for the fourth).
|
||
- `400 … exceeds the available context size (180224 tokens)`: the conversation
|
||
outgrew ONE slot. This is not pool exhaustion — the pool is 540,672 tokens
|
||
and the per-slot window is the binding limit. Shorten the prompt, compact, or
|
||
roll back to the 262,144 shape above.
|
||
- Vision answers a color it never saw: the client sent `images` instead of an
|
||
`image_url` content part (see *Client contract*).
|
||
- Slow decode after a CTX or quant change: check `nvidia-smi` — pushing KV out
|
||
of VRAM into unified memory halves the rate silently.
|