Files
serverAppConfigs/serverAppConfigs-docs/LLM/frank-zealot-llama-prism-bonsai2-pq20.md
T
Frank 466fd477d8 feat(frank-zealot): Bonsai-2-9B PQ2_0 with the MTP head at 3 x 262,144
Swap the 3090 endpoint from Ternary-Bonsai-2-27B PQ2_0 (2 x 200,704,
fork build 10754) to Ternary-Bonsai-2-9B PQ2_0 with the MTP head IN the
file, three conversations at the trained 262,144 window
(--ctx-size 786432 --parallel 3), on vendor fork build 10777 -- the
minimum build that pairs MTP with a quantized V cache. q8_0 K + q8_0 V
is the only KV that fits: 13,056 MiB against a 26,112 MiB f16 pair.
Settled boot 17,876 used / 6,249 free.

MTP is armed and then DECLINED by measurement: the draft context bills
its own f16 KV over the whole pool (3,072 MiB) and at UBATCH_SIZE=2048
that halves decode (167.2 -> 109.2 t/s). The documented MTP arm
(SPEC_TYPE=draft-mtp + UBATCH_SIZE=1024, 213.5 t/s) is recorded in the
env header rather than left to rediscovery.

Two bugs the trial caught: ${SPEC_PRESENCE_PENALTY:=0.0} was a
default-assignment, so the env value always won and the no-presence-penalty
MTP arm was unreachable (now ${VAR=0.0}); and the Q8_0 mmproj is the
publisher's requirement for concurrent image requests, which matters at
--parallel 3.

contextWindow is now 262144 -- the widest in the fleet -- so :7771 is the
compaction hub. 27B runbook re-banned RETIRED with its bench-window
section closed; indexes, homepage card, OpenWebUI doc and the worklog
re-keyed.
2026-10-18 13:15:18 -07:00

15 KiB
Raw Blame History

type, title, description, tags, timestamp
type title description tags timestamp
reference frank-zealot on Dankclaw — llama.cpp (PrismML fork 10754), Bonsai-2-27B PQ2_0, 2 x 200704, q8_0 KV (RETIRED, rollback target) The frank-zealot runbook of 2026-10-17 → 2026-10-18 (RETIRED — rollback target) — the PrismML fork build 10754 of llama.cpp serving prism-ml/Ternary-Bonsai-2-27B-gguf at PQ2_0 on the RTX 3090: two conversations of 200,704 tokens with q8_0 K + q8_0 V (f16 KV does not fit this card at this window), vision through the publisher's Q8_0 Qwen3-VL projector, MTP impossible (no tensors in the file), and the measured four-arm VRAM census that priced the shape. Superseded by the 9B MTP export at 3 × 262,144.
llm
frank-zealot
dankclaw
llama-cpp
gguf
rtx3090
2026-10-17

frank-zealot — llama.cpp (PrismML) Bonsai-2-27B PQ2_0, 2 × 200,704, q8_0 KV

RETIRED 2026-10-17 → 2026-10-18 (AGENTS.md Runbook Index), superseded by frank-zealot-llama-prism-bonsai9-mtp — prism-ml/Ternary-Bonsai-2-9B PQ2_0 with the MTP head, THREE conversations at the native 262,144 window on fork build 10777. This era stays the rollback target: the payload (/home/dank/models/Bonsai2-PQ2_0/) and the 10754 fork tree (~/GitPackages/llama-prism/) are both still on disk. It owns the laws below — the 34,816 B/token KV figure over 16 full-attention layers, the 25,088 MiB f16 OOM wall, and the two-boot VRAM pricing that every later shape on this card reused.

2026-10-18 bench window — production unit stopped, candidate arm on 7771

As of 2026-10-18 frank-zealot sits in a deliberate, dated bench window (fleet Benchmarking Policy: stop the production unit, bench candidate boots against the service itself). The journal shows the stop at 03:38:31 and the known llama.cpp shutdown shape — SIGTERM ignored past TimeoutStopSec, State 'stop-sigterm' timed out. Killing. at 03:40:01 — after which the unit reports failed (is-enabled → enabled); self-healing is merely parked while the window runs. What answers 7771 is a hand-booted candidate arm (llama-prism-b10777/llama-server on the 9B MTP file Bonsai2-9B-MTP/Ternary-Bonsai-2-9B-PQ2_0-MTP-Q8_0.gguf, alias frank-zealot), not this runbook's payload: candidate-arm flags are not a state — the pid and shape moved between two censuses ten minutes apart (one argv read --ctx-size 786432 --parallel 3 --spec-type draft-mtp; a later /props read n_ctx 262144, speculative.types=none); quote neither, and expect /v1/models to report the candidate until the window closes. The window's remedy is the redeploy: ./scripts/deploy.sh dankclaw --service frank-zealot. The production trio below is unchanged — the last PRODUCTION boot under the unit (03:14–03:38 the same morning) printed the contract exactly: n_ctx = 401408, n_ctx_seq = 200704, CUDA0 KV buffer size = 13328.00 MiB, 2 slots (journal).

Window CLOSED 2026-10-18: that candidate became production. The redeploy shipped the 9B MTP export at 3 × 262,144 on fork build 10777 — see frank-zealot-llama-prism-bonsai9-mtp; this payload is the rollback target.

Identity

Host / device Dankclaw (192.168.0.143), RTX 3090, 24,576 MiB (24,123 MiB visible to the engine)
Endpoint http://192.168.0.143:7771 — served model id frank-zealot (ALIAS), /v1 OpenAI surface
Runtime PrismML fork build /home/dank/GitPackages/llama-prism/llama-server — vendor release, --version = 0.2.0-dev (build 10754, commit 2459f68b5), sha256 12ae5baba033d8cd…. NOT a preference: see Why the fork
Unit root systemd frank-zealot.service (scripts/frank-zealot.service), User=dank, Restart=on-failure, LimitMEMLOCK=infinity
Serve script scripts/serve-frank-zealot.sh, tunables scripts/frank-zealot.env
Weights /home/dank/models/Bonsai2-PQ2_0/Ternary-Bonsai-2-27B-PQ2_0.gguf — 7,206,168,928 B, sha256 3907dc1658db1f78a9826bf8d5bcb8dc65db0d466388937af57f2294fae62ec1
Projector Ternary-Bonsai-2-27B-mmproj-Q8_0.gguf — 629,246,976 B (full sha256 pinned in the fetch script), Qwen3-VL tower; 873.10 MiB worst-case VRAM, loaded AFTER the engine's fit check
Fetch scripts/download-frank-zealot-bonsai2.sh — -f, byte-count + sha256 verified, idempotent, --verify-only, no token on the command line
Seam tests/frank_zealot_unit.bats (argv assertions against a stub engine)

Why the fork — the format is the lock

Read from the file's own metadata, not the model card:

Key Value
general.architecture qwen35 (hybrid: Gated DeltaNet + softmax attention)
qwen35.block_count 64, full_attention_interval 4 ⇒ 16 full-attention + 48 DeltaNet layers
Attention 24 heads / 4 KV heads × key 256 / value 256; rope.dimension_sections [11,11,10,0], freq_base 1e7, partial rope dimension_count 64
Widths embedding 5,120, feed-forward 17,408
qwen35.ssm.* conv_kernel 4, state_size 128, group_count 16, time_step_rank 48, inner_size 6,144
Window / vocab context_length 262,144; vocab 248,320 (gpt2 BPE, pre qwen35), eos 248046
general.file_type 141 = PQ2_0 — 2.13 bpw (group 128)
Tensor histogram 851 tensors: pq2_0 ×402 (custom GGML type 142), f32 ×353, bf16 ×96
prism.hadamard.* transform normalized-sylvester-walsh-hadamard, block_size 1024, axis input-last-dimension, sign_mode explicit, 401 named weights, sign widths [5120, 6144, 17408], 28,672 sign values, inverse applied on token_embd.weight, gdn_v_grouped true
Sampler metadata general.sampling.temp 1.0 / top_p 0.95 / top_k 20
Chat template 8,952 chars; reads enable_thinking, reasoning_effort ∈ {xhigh (default), medium, low — anything else is raise_exception, preserve_thinking, tools/tool_call, image + video content parts

Stock llama.cpp cannot load this file: type 142 is not in its enum (gguf-py's stock GGMLQuantizationType raises ValueError: 142 is not a valid… on the same read), and even a loader that parsed it would ignore the prism.hadamard.* dequant transform the weights depend on. Same class of lock as the ROCmFPX custom-type era. The vendor build 10754 already carries the September upstream fixes (AVX-512 load segfault, and a GGML_CUDA_FA_ALL_QUANTS -capable CUDA backend — proven by q8_0 V serving under flash attention at the correct 34,816 B/token, below).

VRAM census (2026-10-17, four arms, production stopped)

The engine's own common_memory_breakdown_print ledger + settled nvidia-smi, all arms --n-gpu-layers 99 --flash-attn on --batch-size 4096 --ubatch-size 2048, mmproj attached:

Shape KV projected model + context + compute settled used / free verdict
2 × 200,704 (pool 401,408) q8_0 / q8_0 21,439 = 6,539 + 13,627 + 1,272 22,664 / 1,461 DEPLOYED
2 × 200,704 f16 / f16 33,198 = 6,539 + 25,387 + 1,272 — cudaMalloc failed: out of memory on the 25,088.00 MiB KV request
1 × 262,144 q8_0 / q8_0 16,905 = 6,539 + 8,853 + 1,512 18,130 / 5,995 fits, but one slot serializes compaction
2 × 262,144 q8_0 / q8_0 ≈25,758 (arithmetic, same terms) — does not fit (f16 analogue measured 41,118)
2 × 200,704 q4_0 / q4_0 15,167 = 6,539 + 7,355 + 1,272 16,392 / 7,733 the roomy fallback rung

KV rate law (confirmed to the MiB): 16 full layers × 4 KV heads × 256 dim = 16,384 elements/token/cache; q8_0 bills 17,408 B per cache ⇒ 34,816 B/token, so the 401,408 pool = 13,328.00 MiB — the ledger printed exactly that (CUDA0 KV buffer size = 13328.00 MiB). f16 doubles it to 25,088 MiB, which is the wall. The 48 DeltaNet layers bill a fixed recurrent state per slot: context 13,627 − KV 13,328 = 299 MiB for two slots (~150 MiB each) — pool arithmetic alone under-prices the shape by that much.

The projector is outside the fit check. [mtmd] estimated worst-case memory usage of mmproj is 873.10 MiB is printed before fitting but not billed in it: settled used (22,664) − projection (21,439) ≈ 1.2 GiB is that projector plus the CUDA context. This is the difference between "2.6 GiB free on paper" and the real 1.4 GiB — the same late-allocation failure class that killed the DSpark drafter boot in the llama.cpp eras. Any resize must re-read the settled nvidia-smi, never the projection.

Measured rates (greedy, Server-Timing-free /completion timings)

Probe Result
Short, warm, single stream (128 tok) 76.6 t/s decode (the 9B Q6_K predecessor ran 94.4 — the ternary 27B is ~19 % slower, weight bandwidth + Hadamard dequant)
First request after boot 2.6 t/s prefill / 37.7 t/s decode — warmup artifact; never report the first request (measured twice on this payload)
Prefill at ~35k depth 1,258.4 t/s (27.8 s)
Prefill at 166,027-token depth 691.7 t/s (240.0 s), zero errors, slot intact
Two concurrent streams, 256 tok each 54.7 + 54.7 = 109.4 t/s aggregate — --parallel 2 really batches
Long-context retrieval (25,960-token prompt, needle at position 180 of 300 passages) exact: "Frank keeps the fleet in git, only through it."

Production boot (the 19:44 cutover deploy, same binary/flags/file): 75.8 t/s single-stream decode at a short prompt — within 1 % of the trial arm, so the ladder above carries to the shipped shape.

Behavior contract (probes, not assumptions)

  • Reasoning lands in reasoning_content (--reasoning-format deepseek) ✓.
  • Tool calls: a get_weather request returns finish_reason: tool_calls with arguments: {"city":"Seattle"} — valid JSON, no loop, no HTTP 500. The fork's KNOWN_ISSUES.md warning about malformed/looping tool calls did not reproduce on build 10754 at this shape.
  • Vision: a 512 px solid-red PNG answers "Red" — but ONLY with a thinking budget ≥ ~512 tokens. At max_tokens: 64 the request returns finish_reason: "length" with EMPTY content and half a thought in reasoning_content: thinking tokens count against the budget, so a short-budget vision probe reads as "the model is blind". Images do reach the model (a 512 px image bills ~280 prompt tokens: 322 total vs 42 for the text alone). Keep the ≥256 px probe rule from the earlier eras.
  • Log noise law: /completion (text context) at VERBOSITY=4 prints failed to parse reasoning content (response_format json_schema is currently only supported with parse_reasoning=true), content will be stored in 'content' field (probe_request.cpp:789) whenever the model closes a raw prompt with ``. It is cosmetic — the full chain of thought is in content. The OpenAI /v1/chat/completions path parses it into reasoning_content correctly, which is the path the harness and every consumer uses. Do not diagnose this as a fault, and do not drop verbosity below 4 — the KV/buffer ledger in the journal is what VRAM forensics read.
  • Long raw-prompt completions can come back empty: at ~26k+ tokens a bare /completion request generated 0–1 tokens with no error (the model emits an end-of-turn marker immediately, and a text context has no role frame to keep it talking). Chat traffic is unaffected; nothing in the harness posts raw /completion at depth.

Ops notes

  • Trial-window law (cost one boot window on 2026-10-17): a scratch env file must use the serve script's OWN variable names — CACHE_TYPE_K/CACHE_TYPE_V, BIND_ADDR, NGL. An invented KV_CACHE_TYPE is silently swallowed by the script's : "${CACHE_TYPE_K:=f16}" default and the arm boots f16, which for this payload is an OOM that looks like a model problem.
  • /metrics needs --metrics (fixed 2026-10-18): the fork answers 501 Not Implemented without the flag, and Dankclaw's Prometheus scrapes /metrics, so up{job="frank-zealot"} has been 0 for the whole retained metric history — the oldest sample (2026-10-11) is already 0 — while the endpoint itself answered /health 200 the entire time. METRICS=1 in scripts/frank-zealot.env now ships the flag and the seam asserts it reaches the argv. halogen serves /metrics natively, which is why that target was always green.
  • Base alignment: Bonsai-2 is the BASE (censored) Qwen3.8-27B lineage, so a refusal is possible on a compaction or subagent request. The CRACK re-export (dealignai/Bonsai-2-27B-Ternary-CRACK-GGUF, already on disk at /home/dank/models/Bonsai2-CRACK/Bonsai-2-27B-PQ2_0-CRACK.gguf, sha256 5b24ea3e…) is a one-line MODEL_FILE swap: the header census proves byte-identical geometry — 851 tensors, the same {142:402, f32:353, bf16:96} histogram, 64 blocks, vocab 248,320, the same 8,952-char template. The VRAM ledger does not move. If a refusal ever surfaces, that is the fix; there is also an MTP-bearing CRACK re-export upstream (ManniX-ITA/…-CRACK-MTP-GGUF, 7,657,489,920 B, +451 MB head) if decode ever needs the drafter — untested here.
  • Compaction hub unchanged: all three endpoints still route compactionModel here; 200,704 per slot is the fleet's only window that fits a worst-case compaction input, and --parallel 2 keeps compaction off the critical path of a live generation.
  • Harness side (~/.omp/agent/models.yml): contextWindow: 200704 and maxTokens: 16384 unchanged; extraBody now quotes the payload's own 1.0 / 0.95 / 20 with preserve_thinking: true. The reasoningEffortMap fold became load-bearing (the template raises on any level outside xhigh|medium|low) — an unmapped level is a hard 500 on the role's first request, naming Jinja rather than the model.
  • Rollback: revert the swap commit. The Qwen3.5-9B Q6_K payload, its mmproj and the master CUDA build all remain on disk (/home/dank/models/Qwen35-9B-HauhauCS-Q6K/, ~/GitPackages/llama-master/build-cuda/), and the retired era keeps its own page: frank-zealot-llama-qwen35-9b-q6k-2x200704.
  • Deployed 2026-10-17 19:44 (deploy.sh dankclaw --service frank-zealot --service homepage): boot ledger 22,664 used / 1,461 free, engine fit projected 21,439 and left 2,119 ≥ 1,872 MiB, KV 13,328.00 MiB at n_ctx_seq = 200,704, n_slots = 2, projector loaded; live probes green (/v1/models serves frank-zealot, exact pong + reasoning_content, tool_calls {"city":"Seattle"}, 512 px PNG → Red). e2e gate 33/33.
  • Picker row is live state: the OpenWebUI model row's meta.description named the 9B, so it was re-keyed in the database too (backup webui.db.bak-20261017a). The row was written WITHOUT restarting open-webui — the cached list refreshes on its next restart, and forcing one would have logged every family session out for a cosmetic string. See OpenWebUI for the picker trio.