Files
serverAppConfigs/serverAppConfigs-docs/LLM/frank-zealot-llama-prism-bonsai9-mtp.md
T
Frank 06a9cb60eb docs(frank-zealot): PTQ1_0 was ordered, measured and approved — not an unfinished arm
Frank's 02:36 instruction (archive 01a14c93) ordered the 27B PTQ1_0 arm at
2 x 200,704 with the Q8 mmproj and batch/ubatch 3072/1536; it served
production 03:14:39-03:38:57 (pid 191358, journal type ptq1_0: 402 tensors).
Recovered its numbers from that process's own print_timing lines: ~48 t/s
single-stream decode, 25-27 t/s per stream at two streams, prefill 1,295 t/s
at 30,637 -> 943 at 97,856 -> 801 at 141,365. It bought the promised 1,201 MiB
and lost 37% of decode against the PQ2_0 record, so the publisher's Ampere
warning generalizes to GA102; Frank scratched the 27B plan at 03:33 for the 9B.

Every 'unfinished experiment' claim is re-keyed, and the AGENTS.md law gains
the durable lesson: a bench verdict that never reaches the repo is
indistinguishable from a rogue arm.
2026-10-18 13:50:19 -07:00

17 KiB
Raw Blame History

type, title, description, tags, timestamp
type title description tags timestamp
reference frank-zealot on Dankclaw — llama.cpp (PrismML fork 10777), Bonsai-2-9B PQ2_0 with the MTP head, 3 × 262144, q8_0 KV (ACTIVE) The ACTIVE frank-zealot runbook (since 2026-10-18) — the PrismML fork build 10777 of llama.cpp serving prism-ml/Ternary-Bonsai-2-9B-gguf at PQ2_0 on the RTX 3090: THREE conversations of 262,144 tokens (every slot at the model's trained window) over q8_0 K + q8_0 V, vision through the publisher's Q8_0 Qwen3-VL projector, and the five-arm boot census that put speculative decoding OFF by measurement even though the checkpoint carries the MTP head.
llm
frank-zealot
dankclaw
llama-cpp
gguf
rtx3090
mtp
spec-decode
2026-10-18

frank-zealot — llama.cpp (PrismML) Bonsai-2-9B PQ2_0 + MTP head, 3 × 262,144, q8_0 KV

Live since 2026-10-18, replacing the frank-zealot-llama-prism-bonsai2-pq20 era (Ternary-Bonsai-2-27B PQ2_0 at 2 × 200,704). The trade in one sentence: the 9B bills half the KV per token of the 27B, and that is exactly what funds three conversations at the model's native 262,144 window where the 27B afforded only two shortened ones (Frank's ask: "if you can possibly fit a third context, do it"). Every axis improved — decode, prefill, three-stream aggregate, and free VRAM.

Payload identity

Item Value
Runtime /home/dank/GitPackages/llama-prism-b10777/llama-server — PrismML fork build, --version → 0.2.0-dev (build 10777, commit e3de71522)
Checkpoint /home/dank/models/Bonsai2-9B-MTP/Ternary-Bonsai-2-9B-PQ2_0-MTP-Q8_0.gguf — 2,675,410,720 B, Hub-LFS sha256 c79fcdfaf6b7bf47089b50dadcfd11f9ce7d3e96adf3bbefde7912e5ef69e22b
Vision /home/dank/models/Bonsai2-9B-MTP/Ternary-Bonsai-2-9B-mmproj-Q8_0.gguf — 624,229,664 B, Hub-LFS sha256 e0cb6f6988cea323ef798cd77d019bbb819af75582f034eaab0e7232314953db
Fetch scripts/download-frank-zealot-bonsai9.sh [DEST] (--verify-only; -f, byte-count and LFS sha256 per file)
Alias frank-zealot (ALIAS); the SERVICE/unit/scripts keep the StarCraft name, only the wire id is the model id
Port 7771, root systemd unit frank-zealot.service (relinked), TimeoutStopSec=180

The runtime lock has two tumblers now

  1. The tensor type. general.file_type 141 with 202 tensors of the fork's PQ2_0 type (of 442 total; the rest are f32 norms/scalars and 8 q8_0 in the MTP block) plus prism.hadamard.* metadata (normalized-sylvester-walsh-hadamard, block_size 4096, axis input-last-dimension, sign_mode explicit over 201 weight names, inverse on token_embd.weight, gdn_v_grouped true). Stock llama.cpp refuses the custom type at load; a loader that ignored the Hadamard metadata would dequant wrong, silently. Same lock class as the ROCmFPX era.
  2. The build floor. The fork's card gates MTP beside a quantized V cache on prism-b10776-d00e252 or newer, so the prebuilt moved 10754 → 10777 for this swap. The 10754 tree stays on disk (~/GitPackages/llama-prism/) as the 27B rollback runtime. tests/frank_zealot_unit.bats pins both paths.

Header census (read from the GGUF, not the card)

general.architecture qwen35, general.name Ternary-Bonsai-2-9B, general.size_label 9B, general.quantization_version 2, general.file_type 141:

Key Value Note
qwen35.block_count 33 32 trunk blocks + blk.32 = the MTP block
qwen35.full_attention_interval 4 ⇒ 8 full-attention + 24 Gated DeltaNet trunk layers
qwen35.context_length 262,144 native; the deployed slot equals it
qwen35.embedding_length / feed_forward_length 4,096 / 12,288
attention.head_count / head_count_kv 16 / 4
attention.key_length / value_length 256 / 256
ssm.conv_kernel / state_size / group_count 4 / 128 / 16 the 27B: 4 / 128 / 16
ssm.time_step_rank / inner_size 32 / 4,096 the 27B: 48 / 6,144
rope.freq_base / dimension_count / dimension_sections 1e7 / 64 / [11,11,10,0]
qwen35.nextn_predict_layers 1 the MTP head is IN the file
general.sampling.* temp 1.0 / top_p 0.95 / top_k 20 / min_p 0.0 / repeat 1.0 the card recipe; llama.cpp reads it itself

KV accounting. Only the 8 full-attention layers scale with context: 8 × 4 KV heads × 256 dim = 8,192 elements/token per cache, so q8_0 K + q8_0 V bills 17,408 B/token — the 786,432-position pool is 13,056.00 MiB, ledger-exact. That is exactly HALF the 27B's 34,816 B/token. f16 KV would bill 26,112 MiB against a 24,123 MiB card and dies at cudaMalloc. The 24 DeltaNet layers bill a fixed recurrent state per SLOT instead: the PRODUCTION ledger prints RS buffer size = 150.75 MiB (3 cells, 32 layers, 3 seqs) ≈ 50 MiB a cell, while the SPEC-ARMED arms printed 452.25 MiB — MTP depth 2 bills three seqs per slot, so the drafter triples even the recurrent state. Pool arithmetic alone under-prices the shape.

The shape (scripts/frank-zealot.env)

--ctx-size 786432 --parallel 3 (⇒ n_ctx_slot = 262,144), --flash-attn on, --cache-type-k q8_0 --cache-type-v q8_0, --batch-size 4096, --ubatch-size 2048, --n-gpu-layers 99, --jinja, --reasoning auto, --reasoning-format deepseek, --metrics, --mmproj …, --log-verbosity 4, --presence-penalty 1.0, SPEC_TYPE=none (which strips --spec-type AND every --spec-draft-*).

Per-slot window is the contract the harness quotes: models.yml carries contextWindow: 262144. llama.cpp refuses a prompt longer than the SLOT with a 400 naming the limit — it never truncates — and since this swap the slot window equals the native window, so no YaRN and no cap is in play.

The MTP verdict: the head is in the file and it is still OFF

The drafter is good: acceptance 0.798 / 0.617 per position, mean accepted length 2.41, and the card recommends depth 2. It loses at this shape because the draft context bills its own f16 KV over the WHOLE pool — blk.32 carries real attention (4 KV heads × 256 × 2 caches × 2 B = 4,096 B/position) × 786,432 positions = 3,072.00 MiB, 4,830 MiB with its compute buffers — and --ubatch-size 2048 refuses to give that up.

Booted arms, same card, same traffic, 2026-10-18 (/completion timings, greedy 512 tokens; never the first request after a boot):

slots spec ubatch used / free decode 3-stream prefill @106k
3 × 262,144 off 2048 17,876 / 6,249 167.2 280.2 2,877
3 × 262,144 on 2048 22,706 / 1,419 109.2 — —
3 × 262,144 on 1024 21,400 / 2,725 213.5 250.8 2,395
2 × 262,144 on 2048 17,178 / 6,947 213.2 — —
1 × 262,144 on 2048 11,652 / 12,473 214.2 — —

MTP is worth +28 % single-stream decode whenever the card has room (rows 4–5 prove the drafter itself is a win; row 2 proves the squeeze is what halves it). The arm that runs MTP at three windows is SPEC_TYPE=draft-mtp + UBATCH_SIZE=1024 (row 3) and pays prefill −17 % and 2,725 MiB of headroom on a card whose mmproj worst-case lands AFTER the engine's own fit check. This endpoint is the fleet's compaction hub — prefill-bound ~170k inputs plus 2–3 concurrent subagents — so the shipped arm takes the +17 % prefill and +12 % three-stream aggregate over single-stream speed, and keeps 6.2 GiB free. Frank confirmed this arm on 2026-10-18.

Boot ledger (production boot 2026-10-18 13:19, under the unit, verbosity 4)

CUDA0 model buffer size  =  2036.71 MiB    # + 257.66 MiB CPU_Mapped (mmap)
CUDA0 KV buffer size     = 13056.00 MiB    # q8_0 K + q8_0 V, 786,432 positions
CUDA0 RS buffer size     =   150.75 MiB    # 3 cells, 32 layers, 3 seqs
CUDA0 compute buffer     =  1409.02 MiB    # + 1088.87 MiB CUDA_Host, 2.84 output
mmproj worst-case (est.) =   868.32 MiB    # 248.10 CUDA + 24.93 CPU, AFTER fit
nvidia-smi settled: 17,922 MiB used / 6,203 MiB free of 24,123
n_slots = 3, n_ctx_slot = 262144, kv_unified = false

No clamp line, no cudaMalloc error. The engine's own projection (21,439-class numbers from the 27B era) excludes the projector — read settled nvidia-smi, never the projection.

Measured (production boot, /completion timings, greedy, ignore_eos)

  • 168.7 t/s decode — three runs of 512 tokens: 168.4 / 168.7 / 168.7 (the trial arm printed 167.2; same shape, same noise band).
  • ~300 t/s aggregate across three concurrent 512-token streams (102.8 t/s per stream, round 1 and 2 both) — the trial arm's 280.2.
  • 2,877 t/s prefill at 106,418 tokens — the trial arm; the production boot has not re-run that depth.
  • The 27B record it replaces: 76.6 decode, 109.4 aggregate across two streams, 691.7 prefill at 166k. Beaten on every axis.
  • The first request after a boot is a warmup artifact (37.7 t/s class) — never report it. And never time a wall-clock aggregate on a generation that stops on its own: a 24-token countdown measured 72 t/s across three streams while the same shape with ignore_eos gave 300.

Contract traps

  • --metrics is mandatory. Without it llama.cpp answers 501 on /metrics, so the Prometheus scrape reads DOWN while /health answers 200 — that is how this endpoint sat red across four payload generations (up{job="frank-zealot"} oldest retained sample, 2026-10-11, is already 0).
  • Q8_0 mmproj, not BF16. The publisher's card: the BF16 tower "can crash llama-server when several image requests run at once; Q8_0 is not affected" — and --parallel 3 is precisely that shape.
  • Only the image_url content part reaches the model. The legacy top-level images field bumps usage.prompt_tokens and the model still invents an answer. Probe with a solid color and expect the exact word.
  • ≥256 px probe images or the model answers "video".
  • Thinking tokens count against max_tokens. A vision probe at 64 tokens returns EMPTY content + finish_reason: "length" — the endpoint looks blind while it is only mid-thought. Budget ≥512.
  • presence_penalty has no GGUF metadata key, so the one recipe value that must reach the argv is this one. The serve script uses ${PRESENCE_PENALTY=1.0} — not := — because "" is the documented opt-out and := would silently re-add the flag (caught by the seam, fixed 2026-10-18).
  • Spec flags move as a SET. A bare --model-draft/--spec-type arm without the whole set has segfaulted at load on this fork family in the past; SPEC_TYPE=none strips all of them.
  • Base alignment is the censored base lineage, and the CRACK swap is NOT one line here. /home/dank/models/Bonsai2-CRACK/ matches the 27B geometry, not this 9B file; a dealigned 9B would have to be fetched.
  • A raw /completion request logs failed to parse reasoning content (… parse_reasoning=true) when the model closes a bare prompt with `` — cosmetic (probe_request.cpp:789); /v1/chat/completions parses reasoning_content correctly. Do not lower VERBOSITY: the KV/buffer ledger is what VRAM forensics read.
  • A failed stop leaves a failed unit, and an orphan keeps answering. On 2026-10-18 the trial stop hit State 'stop-sigterm' timed out. Killing. → Active: failed (Result: timeout), while the hand-booted arm kept serving 7771 — a live port with no unit behind it. TimeoutStopSec=180 is now on the unit (llama.cpp needs the drain time to release slots and the card); the hand-booted arm must be killed by PID, never pkill -f llama-server.
  • A leftover arm drop-in hijacks the production boot, and no health check sees it. The sanctioned bench method is a transient Environment=FRANK_ZEALOT_ENV_FILE=/tmp/<arm>.env drop-in, and an arm env sources the repo env and then overrides it. On 2026-10-18 a stale frank-zealot.service.d/ptq1.conf — written 03:29 for the 27B PTQ1_0 arm Frank had ordered at 02:36 and measured from 03:14 (deploy ledger below) — was still installed, so the cutover deploy restarted the unit and booted THAT: --model …/Bonsai2-PTQ1_0/Ternary-Bonsai-2-27B-PTQ1_0.gguf --ctx-size 401408 --parallel 3 --batch-size 3072 --ubatch-size 1536, three slots of 133,888, 21,188 MiB used. /health was 200, /v1/models said frank-zealot (the alias is a flag, not a fact), and the Playwright gate passed 33/33 — none of those read the payload. After deploying an endpoint that was recently benched: systemctl show -p DropInPaths, then tr '\0' ' ' </proc/$pid/cmdline, then the ledger — in that order. Retire the drop-in to ~/.AGENTTRASH/<date>-<topic>/, daemon-reload, restart.

Compaction hub

models.yml routes compactionModel for frank-archon, frank-dragoon and frank-zealot here — 262,144 is the widest window in the fleet and the only one that comfortably fits a worst-case compaction input (~170k tokens; the clamp is min(maxTokens 16384, contextWindow − est − 4096), compaction fires at 85 % of the window ≈ 222,822 est, worst case ≈ 245,891 ≤ 262,144). With --parallel 3 a compaction request no longer serializes behind a live generation (the Gemma4 era's --parallel 1 law).

Deploy ledger (2026-10-18)

  • ./scripts/deploy.sh dankclaw --service frank-zealot at 13:16 PDT; the e2e gate ran 33/33. The first boot was the hijacked arm above; the drop-in went to ~/.AGENTTRASH/20261018-zealot-ptq1-arm/ and the 13:19 restart is the ledger recorded here.
  • Live probes on the shipped boot: chat 27*59 → 1593; where_am_i tool call fired with schema-correct empty arguments; 512 px solid-red PNG → Red at 278 prompt tokens with process_mtmd chunk encoding in the journal; /props reports n_ctx 262144 and presence_penalty 1.0; /metrics answers 200 with 15 llamacpp:* families and up{job="frank-zealot"} is 1 in Prometheus for the first time in its retained history.
  • OpenWebUI picker rows re-keyed in the live DB (Connection.backup() snapshot webui.db.bak-20261018 taken first; frank-dragoon's context.length also corrected 262,144 → 200,704 — see OpenWebUI).
  • What the bench window left behind is not garbage — the PTQ1_0 arm was ordered, measured, and then scratched by Frank himself. The instruction at 02:36 was: run frank-zealot on Ternary-Bonsai-2-27B-PTQ1_0.gguf, the Q8 mmproj, batch/ubatch 3072/1536, and as much context as fits in two contexts. The unit booted it at 03:14 (journal: type ptq1_0: 402 tensors, file type = PTQ1_0 - 1.75 bpw ternary, mmproj loaded, 2 × 200,704) and served 24 minutes of traffic. Its own print_timing lines measure ~48 t/s single-stream decode (48.0 / 47.9 / 47.6 / 49.0 t/s on 512 / 229 / 512 / 451-token generations), 25–27 t/s per stream with two streams live, and prefill 1,295 t/s at 30,637 → 943 at 97,856 → 801 t/s at 141,365 tokens. It bought the 1,201 MiB the fetch script promised and paid for it on decode, exactly as the publisher's Ampere row warned (the PQ2_0 record for the same weights is 76.6 t/s). At 03:33 Frank scratched the whole 27B plan and ordered the 9B PQ2_0 + MTP shape deployed here; the arm process ended at 03:38.
  • The actual failure was documentation, not measurement. An approved payload that survives only as a systemd drop-in plus a journal window is, a day later, indistinguishable from a rogue arm — which is why the 13:16 boot read as a hijack. Payload decisions belong in this runbook the moment they are made. The evidence is still perishable: /tmp/zealot-arm-current.env and the 9B /tmp/arm-*.jsonl results die on reboot, and only the journal (journalctl -u frank-zealot --since "2026-10-18 03:10") holds the PTQ1_0 numbers. /home/dank/models/Bonsai2-PTQ1_0/ (5,946,648,928 B + hardlinked mmproj) stays on disk as the 27B rollback.

Fallback rungs

  1. UBATCH_SIZE=1024 — frees 1,306 MiB measured on this shape (prefill −17 %).
  2. CACHE_TYPE_K/V=q4_0 — frees ~6.5 GiB at three windows (the 27B era measured the q4_0 rung at 16,392 used / 7,733 free at 2 × 200,704).
  3. Cut to 2 slots — but that breaks the third concurrent subagent, so it is a last resort, not a knob.

Rollback

Revert the swap commit. On disk: the 27B payload (/home/dank/models/Bonsai2-PQ2_0/) + the 10754 fork tree, the Qwen3.5-9B Q6_K payload (/home/dank/models/Qwen35-9B-HauhauCS-Q6K/) + master CUDA build, the Gemma4, MiMo, Swift and Ornith payloads. Then ./scripts/deploy.sh dankclaw --service frank-zealot.