fix(frank-zealot): 2 × 262,144 over unquantized f16 KV #21

Merged
dank merged 2 commits from fix/zealot-2x262144-f16-kv into main 2026-10-18 21:06:08 -07:00
5 changed files with 217 additions and 141 deletions
Showing only changes of commit 68afc6dbaf - Show all commits

No files matched your search

+1 -1
View File
@@ -1459,7 +1459,7 @@ LLM_ZEALOT_RUNTIME="${LLM_ZEALOT_RUNTIME:-/home/dank/GitPackages/llama-prism-b10
# image path — a deploy that starts without it silently ships a text-only # image path — a deploy that starts without it silently ships a text-only
# endpoint. Checked unconditionally, like frank-dragoon's. Q8_0 is MANDATORY, # endpoint. Checked unconditionally, like frank-dragoon's. Q8_0 is MANDATORY,
# not a preference: the card says the BF16 tower can crash llama-server when # not a preference: the card says the BF16 tower can crash llama-server when
# several image requests run at once, and this endpoint runs --parallel 3. Its # several image requests run at once, and this endpoint runs --parallel 2. Its
# 868.32 MiB worst-case VRAM (595.29 model + 248.10 compute in the boot ledger) # 868.32 MiB worst-case VRAM (595.29 model + 248.10 compute in the boot ledger)
# lands AFTER the engine's fit check, so it is not inside the q8_0 budget. # lands AFTER the engine's fit check, so it is not inside the q8_0 budget.
LLM_ZEALOT_MMPROJ="${LLM_ZEALOT_MMPROJ:-/home/dank/models/Bonsai2-9B-MTP/Ternary-Bonsai-2-9B-mmproj-Q8_0.gguf}" LLM_ZEALOT_MMPROJ="${LLM_ZEALOT_MMPROJ:-/home/dank/models/Bonsai2-9B-MTP/Ternary-Bonsai-2-9B-mmproj-Q8_0.gguf}"
+76 -57
View File
@@ -2,12 +2,14 @@
# prism-ml/Ternary-Bonsai-2-9B-gguf at PQ2_0 **with the MTP head in the file** # prism-ml/Ternary-Bonsai-2-9B-gguf at PQ2_0 **with the MTP head in the file**
# on the RTX 3090, :7771. # on the RTX 3090, :7771.
# #
# 2026-10-18 (Frank): swap from the Bonsai-2-**27B** PQ2_0 to the 9B MTP export # 2026-10-18 (Frank): swap from the Bonsai-2-**27B** PQ2_0 to the 9B MTP export,
# at THREE 262,144-token conversations (Frank: "if you can possibly fit a third # and the same evening the shape moved to TWO 262,144-token conversations over an
# context, do it"). The 9B bills HALF the KV of the 27B per token, which is what # UNQUANTIZED (f16) KV cache — "shift frank-zealot to 2 parallels of unquantized
# funds 3 x the native window on one 24,123 MiB card. Speculative decoding ships # kv". Exact K and V is the retrieval-fidelity ask; the price is one concurrent
# OFF — measured, not assumed; see the MTP law below, which is the whole story # conversation, because f16 owns the card. Speculative decoding ships OFF —
# and the reason the publisher's "depth 2 works best" line does not transfer. # measured, not assumed; see the MTP law below, which is the whole story and the
# reason the publisher's "depth 2 works best" line does not transfer (and why
# exact KV makes the drafter illegal outright).
# #
# THE RUNTIME IS A LOCK, NOT A PREFERENCE. The file is general.file_type 141 # THE RUNTIME IS A LOCK, NOT A PREFERENCE. The file is general.file_type 141
# carrying custom GGML type 142 tensors plus prism.hadamard.* metadata # carrying custom GGML type 142 tensors plus prism.hadamard.* metadata
@@ -27,7 +29,7 @@ MODEL_FILE=/home/dank/models/Bonsai2-9B-MTP/Ternary-Bonsai-2-9B-PQ2_0-MTP-Q8_0.g
# 624,229,664 B, Hub LFS sha256 e0cb6f69… — Q8_0 IS MANDATORY, not a preference: # 624,229,664 B, Hub LFS sha256 e0cb6f69… — Q8_0 IS MANDATORY, not a preference:
# the publisher's card says the BF16 mmproj "can crash llama-server when several # the publisher's card says the BF16 mmproj "can crash llama-server when several
# image requests run at once; Q8_0 is not affected", and this endpoint runs # image requests run at once; Q8_0 is not affected", and this endpoint runs
# --parallel 3, i.e. exactly that shape. The projector's worst case lands AFTER # --parallel 2, i.e. exactly that shape. The projector's worst case lands AFTER
# the engine's own fit check, so it is never inside a projection number. # the engine's own fit check, so it is never inside a projection number.
MMPROJ_FILE=/home/dank/models/Bonsai2-9B-MTP/Ternary-Bonsai-2-9B-mmproj-Q8_0.gguf MMPROJ_FILE=/home/dank/models/Bonsai2-9B-MTP/Ternary-Bonsai-2-9B-mmproj-Q8_0.gguf
# Base alignment: Bonsai-2 is the censored BASE lineage. The 27B CRACK re-export # Base alignment: Bonsai-2 is the censored BASE lineage. The 27B CRACK re-export
@@ -42,16 +44,18 @@ MMPROJ_FILE=/home/dank/models/Bonsai2-9B-MTP/Ternary-Bonsai-2-9B-mmproj-Q8_0.ggu
ALIAS=frank-zealot ALIAS=frank-zealot
BIND_ADDR=0.0.0.0 BIND_ADDR=0.0.0.0
LISTEN_PORT=7771 LISTEN_PORT=7771
# CTX_SIZE is the POOL llama.cpp divides across slots: 786,432 / 3 = 262,144 per # CTX_SIZE is the POOL llama.cpp divides across slots: 524,288 / 2 = 262,144 per
# conversation — each slot at the model's TRAINED window, which is the point of # conversation — each slot STILL at the model's TRAINED window; the per-slot
# the swap (the 27B era had to cut each conversation to 200,704 to fit two). # contract did not move. MUST stay divisible by PARALLEL (the seam enforces it)
# MUST stay divisible by PARALLEL (the seam enforces it) and the harness # and the harness contextWindow quotes the PER-SLOT number — a longer prompt is
# contextWindow quotes the PER-SLOT number — a longer prompt is answered with a # answered with a 400, never truncated. THE POOL CANNOT STAY AT 786,432 UNDER
# 400, never truncated. The ceiling cannot go past 3 x 262,144: that is the # EXACT KV: f16 bills 32,768 B/token, so that pool is 24,576 MiB — more than the
# native wall (no YaRN in this path) and the arithmetic agrees — a 4th slot # 24,123 MiB the driver leaves addressable, before a single weight loads.
# would push each below the compaction contract. # Halving the POOL to 524,288 (16,384 MiB) is what funds exact KV. The window
CTX_SIZE=786432 # cannot go past 262,144 a slot either: that is the native wall (no YaRN in this
PARALLEL=3 # path), so a 3rd slot would be a KV-dtype cut, not more context.
CTX_SIZE=524288
PARALLEL=2
BATCH_SIZE=4096 BATCH_SIZE=4096
# Ubatch is a prefill knob (decode never moves) with NO interior optimum on this # Ubatch is a prefill knob (decode never moves) with NO interior optimum on this
# CUDA card up to 2048 — 2048 is the measured optimum (+4.6 % prefill). It has a # CUDA card up to 2048 — 2048 is the measured optimum (+4.6 % prefill). It has a
@@ -60,38 +64,47 @@ BATCH_SIZE=4096
UBATCH_SIZE=2048 UBATCH_SIZE=2048
# KV law (header + boot ledger, both confirmed): qwen35 hybrid, 32 layers with # KV law (header + boot ledger, both confirmed): qwen35 hybrid, 32 layers with
# qwen35.full_attention_interval = 4 ⇒ 8 full-attention + 24 Gated DeltaNet; # qwen35.full_attention_interval = 4 ⇒ 8 full-attention + 24 Gated DeltaNet;
# 4 KV heads × 256 dim = 8,192 elements per token per cache. q8_0 bills 8,704 B # 4 KV heads × 256 dim (n_embd_k_gqa = n_embd_v_gqa = 1,024 per layer) = 8,192
# per cache ⇒ 17,408 B/token = 17 KiB, so the 786,432 pool is 13,056.00 MiB — # elements per token per cache. EXACT KV: f16 = 2 B/element ⇒ 16,384 B per cache
# the ledger said exactly that. HALF the 27B's 34,816 B/token, which is the # ⇒ 32,768 B/token = 32 KiB, so the 524,288 pool is 16,384.00 MiB. q8_0 bills
# whole reason three native windows fit. f16 would bill 26,112 MiB at this pool # 1.0625 B/element (32 codes + one 2-byte scale per 32-block) ⇒ 17,408 B/token,
# (against a 24,123 MiB card) and dies at cudaMalloc; the 24 DeltaNet layers # which is the 13,056.00 MiB the 3-slot ledger printed at the 786,432 pool.
# bill a fixed recurrent state per SLOT instead of per token — the PRODUCTION # THE RATIO IS 1.882×, NOT 2×: the old note here said "f16 would bill 26,112
# ledger prints "RS buffer size = 150.75 MiB (3 cells, 32 layers, 3 seqs)" # MiB" because it doubled the q8_0 figure with its scale overhead included;
# (~50 MiB a cell), while the SPEC-ARMED arms print 452.25 MiB, because MTP # exact arithmetic says 24,576 MiB at that pool. The wall stands, only its
# depth 2 bills three seqs per slot (9 cells) — which is why pool arithmetic # height moves. The 24 DeltaNet layers bill a fixed recurrent state per SLOT
# alone under-prices the shape. # instead of per token — the 3-slot ledger printed "RS buffer size = 150.75 MiB
CACHE_TYPE_K=q8_0 # (3 cells, 32 layers, 3 seqs)", ~50.25 MiB a cell so two slots bill 100.50 —
CACHE_TYPE_V=q8_0 # while the SPEC-ARMED arms print 452.25 MiB, because MTP depth 2 bills three
# seqs per slot (9 cells). Pool arithmetic alone under-prices the shape.
CACHE_TYPE_K=f16
CACHE_TYPE_V=f16
# SPECULATIVE DECODING: OFF, MEASURED. The head is in the file and the drafter # SPECULATIVE DECODING: OFF, MEASURED. The head is in the file and the drafter
# is GOOD (acceptance 0.798/0.617 per position, mean accepted length 2.41) — it # is GOOD (acceptance 0.798/0.617 per position, mean accepted length 2.41) — it
# still loses, because the draft context bills its OWN f16 KV over the WHOLE # still loses, because the draft context bills its OWN f16 KV over the WHOLE
# pool: 4,096 B/position × 786,432 = 3,072.00 MiB, plus its compute buffers, # pool at its default dtype. That dtype IS a knob (--spec-draft-type-k /
# 4,830 MiB all told. Booted arms, same card, same traffic (2026-10-18): # --spec-draft-type-v, both default f16 — verified against this build's --help),
# but SPEC_TYPE=none never sends them. Under the
# 3-slot q8_0 shape that bill was 4,096 B/position × 786,432 = 3,072.00 MiB plus
# ~1,758 MiB of buffers. Booted arms, same card, same traffic (2026-10-18, the
# WHOLE table is q8_0 K + q8_0 V — it predates exact KV):
# 3 × 262,144 spec OFF ub 2048 → 17,876 used / 6,249 free, decode 167.2 t/s # 3 × 262,144 spec OFF ub 2048 → 17,876 used / 6,249 free, decode 167.2 t/s
# 3 × 262,144 spec ON ub 2048 → 22,706 used / 1,419 free, decode 109.2 t/s # 3 × 262,144 spec ON ub 2048 → 22,706 used / 1,419 free, decode 109.2 t/s
# 3 × 262,144 spec ON ub 1024 → 21,400 used / 2,725 free, decode 213.5 t/s # 3 × 262,144 spec ON ub 1024 → 21,400 used / 2,725 free, decode 213.5 t/s
# 2 × 262,144 spec ON ub 2048 → 17,178 used / 6,947 free, decode 213.2 t/s # 2 × 262,144 spec ON ub 2048 → 17,178 used / 6,947 free, decode 213.2 t/s
# 1 × 262,144 spec ON ub 2048 → 11,652 used / 12,473 free, decode 214.2 t/s # 1 × 262,144 spec ON ub 2048 → 11,652 used / 12,473 free, decode 214.2 t/s
# i.e. MTP is worth +28 % decode whenever the card has room, and at the 3-slot # MTP is worth +28 % decode whenever the card has room. At the SHIPPED exact-KV
# pool it costs 1.3 GiB of compute buffers that ubatch 2048 refuses to give up — # shape an f16-draft arm has none: the draft KV alone is 4,096 × 524,288 =
# the squeeze, not the drafter, is what halves decode. The arm that WOULD run # 2,048.00 MiB plus its buffers, on a trunk that already projects ~21,200 MiB of
# MTP at three windows is SPEC_TYPE=draft-mtp + UBATCH_SIZE=1024 (decode 213.5, # the 24,123 the driver leaves addressable. DOORS BACK TO THE DRAFTER: one
# 3-stream 250.8 aggregate) and pays prefill −17 % and 2.7 GiB of headroom on a # window per conversation; KV back to q8_0 with UBATCH_SIZE=1024 (213.5 t/s);
# card whose projector worst-case lands after the fit check. This endpoint's # or the UNMEASURED arm --spec-draft-type-k q8_0 --spec-draft-type-v q8_0, which
# load is the fleet's COMPACTION HUB (prefill-bound, ~170k in) plus 2-3 # halves the draft bill to 1,088 MiB and is legal on this build (>= b10776).
# concurrent subagents, so the deployed arm takes the +17 % prefill # Nobody has booted that beside exact KV — measure before promising it.
# (2,877 vs 2,395 t/s at 106,418 tokens) and the +12 % three-stream aggregate # This endpoint's load is still the fleet's COMPACTION HUB (prefill-bound,
# (280.2 vs 250.8) over single-stream speed, and keeps 6.2 GiB free. # ~170k in): prefill is weight-bandwidth bound and does not read the KV cache
# per generated token, which is why exact KV is affordable here at all; what it
# buys back is concurrency — TWO conversations, not three.
# SPEC_TYPE=none strips the WHOLE spec set (--spec-type, every --spec-draft-*): # SPEC_TYPE=none strips the WHOLE spec set (--spec-type, every --spec-draft-*):
# flags move as a set or the arm measures a crash instead of an absence. # flags move as a set or the arm measures a crash instead of an absence.
SPEC_TYPE=none SPEC_TYPE=none
@@ -105,27 +118,33 @@ SPEC_DRAFT_P_MIN=0
# PRESENCE_PENALTY="" is the documented opt-out. # PRESENCE_PENALTY="" is the documented opt-out.
PRESENCE_PENALTY=1.0 PRESENCE_PENALTY=1.0
NGL=99 NGL=99
# Boot ledger (2026-10-18 PRODUCTION boot under the unit, verbosity 4): # Boot ledger REFERENCE — the previous production boot (2026-10-18 13:19,
# 17,922 MiB used / 6,203 MiB free of 24,123 (nvidia-smi settled) # 3 × 262,144, q8_0 K + q8_0 V, verbosity 4), i.e. the shape this file replaced:
# 17,956 MiB used / 6,169 MiB free of 24,123 addressable (nvidia-smi settled;
# its total column reads 24,576 — the driver reserves the difference, and
# older notes quoting "a 24,123 MiB card" mean this addressable figure)
# CUDA0 model 2,036.71 MiB + CPU_Mapped 257.66 MiB # CUDA0 model 2,036.71 MiB + CPU_Mapped 257.66 MiB
# CUDA0 KV 13,056.00 MiB (q8_0 K + q8_0 V, 786,432 positions) # CUDA0 KV 13,056.00 MiB (q8_0 K + q8_0 V, 786,432 positions)
# CUDA0 RS 150.75 MiB (3 cells, 32 layers, 3 seqs) # CUDA0 RS 150.75 MiB (3 cells, 32 layers, 3 seqs)
# compute 1,409.02 MiB CUDA + 1,088.87 MiB CUDA_Host (+2.84 MiB output buf) # compute 1,409.02 MiB CUDA + 1,088.87 MiB CUDA_Host (+2.84 MiB output buf)
# mmproj worst-case estimate 868.32 MiB (248.10 CUDA + 24.93 CPU compute # mmproj 595 MiB weights + 248.10 MiB CUDA compute meta; estimated worst case
# meta) — charged AFTER the engine's own fit check, as always # 868.32 MiB — charged AFTER the engine's own fit check, as always
# no clamp line, no cudaMalloc error # no clamp line, no cudaMalloc error
# Measured on this boot (/completion timings, greedy, ignore_eos, 512 tokens): # Measured on THAT boot (/completion timings, greedy, ignore_eos, 512 tokens):
# 168.7 t/s decode (three runs 168.4-168.7) and ~300 t/s aggregate across # 168.7 t/s decode (three runs 168.4-168.7), ~300 t/s aggregate across THREE
# THREE concurrent streams (102.8 each). The trial arm's record (167.2 decode, # concurrent streams (102.8 each), 2,877 t/s prefill at 106,418. The 27B it
# 280.2 aggregate, 2,877 t/s prefill at 106,418) stands; the 27B it replaces # replaced gave 76.6 / 109.4 / 691.7 at 166k. These are the numbers the exact-KV
# gave 76.6 / 109.4 / 691.7 at 166k. The FIRST request after a boot is a # arm is judged against: decode reads 1.882× the KV bytes per token, so a decode
# warmup artifact (37.7 t/s class) and a natural generation that stops early # drop is EXPECTED and must be measured, not assumed, while prefill should hold.
# makes a wall-clock aggregate read 4x low - measure with ignore_eos and read # Every non-KV term of that ledger survives this reslice except RS (−50.25 MiB,
# the engine's own timings. # one fewer cell) and one slot's worth of scheduler state, so the f16 arm
# Fallback rung if the margin is ever eaten (desktop, co-tenant): q4_0 KV on the # PROJECTS 17,956 − 13,056 + 16,384 − 50 = 21,234 MiB used / 2,889 free, and
# 27B era measured 16,392 used / 7,733 free at 2 × 200,704; the same drop here # ~2.0 GiB free through the projector's worst case. REPLACE THIS PROJECTION WITH
# frees ~6.5 GiB at three windows. UBATCH_SIZE=1024 is the cheaper lever # THE MEASURED PAIR after the boot: read the new ledger AND settled nvidia-smi.
# (−1,306 MiB measured on this shape). # Fallback rungs if that margin gets eaten (desktop, co-tenant), cheapest first:
# CACHE_TYPE_V=q8_0 (exact K, quantized V) frees 8,192 MiB at this pool;
# CTX_SIZE=401408 (two 200,704 conversations) puts exact KV at 12,544.00 MiB and
# buys ~6.7 GiB back; UBATCH_SIZE=1024 hands 1,306 MiB of compute buffers back.
# Log verbosity threshold (llama.cpp -lv). 4 = debug: KV layout, buffer sizes # Log verbosity threshold (llama.cpp -lv). 4 = debug: KV layout, buffer sizes
# and graph capture land in the journal, which is what the VRAM-fit forensics # and graph capture land in the journal, which is what the VRAM-fit forensics
# read. Levels above 4 flood the journal. # read. Levels above 4 flood the journal.
+1 -1
View File
@@ -1,5 +1,5 @@
[Unit] [Unit]
Description=llama.cpp (PrismML build 10777) Ternary-Bonsai-2-9B PQ2_0-MTP GGUF (qwen35 hybrid, 3x262144 slots over one 786432 pool, q8_0 K+V, MTP off, mmproj Q8_0 vision) on the RTX 3090 (dankclaw, frank-zealot) Description=llama.cpp (PrismML build 10777) Ternary-Bonsai-2-9B PQ2_0-MTP GGUF (qwen35 hybrid, 2x262144 slots over one 524288 pool, unquantized f16 K+V, MTP off, mmproj Q8_0 vision) on the RTX 3090 (dankclaw, frank-zealot)
After=network-online.target After=network-online.target
Wants=network-online.target Wants=network-online.target
+57 -49
View File
@@ -3,33 +3,35 @@
# in the file) as frank-zealot on Dankclaw's RTX 3090 via llama.cpp (PrismML # in the file) as frank-zealot on Dankclaw's RTX 3090 via llama.cpp (PrismML
# vendor build 10777, CUDA sm_86). # vendor build 10777, CUDA sm_86).
# #
# 2026-10-18 (Frank): Bonsai-2 27B PQ2_0 → the 9B MTP export at THREE # 2026-10-18 (Frank): Bonsai-2 27B PQ2_0 → the 9B MTP export, then the same
# 262,144-token conversations (--ctx-size 786432 --parallel 3), q8_0 K + q8_0 V. # evening TWO 262,144-token conversations (--ctx-size 524288 --parallel 2) over
# The 9B bills 17,408 B/token against the 27B's 34,816 (8 full-attention layers # an UNQUANTIZED f16 KV cache. The 9B bills 8,192 elements per token per cache
# instead of 16), and that halved rate is what funds three NATIVE windows where # (8 full-attention layers instead of the 27B's 16), so exact K and V costs
# the 27B could only afford two cut to 200,704. Verbosity 4 ships so the # 32,768 B/token — 16,384.00 MiB at the 524,288 pool, which is what a 24,123 MiB
# VRAM-fit forensics (KV layout, buffer sizes, graph capture) land in the # addressable card can carry beside the trunk. Verbosity 4 ships so the VRAM-fit
# journal. # forensics (KV layout, buffer sizes, graph capture) land in the journal.
# #
# Architecture (engine ledger, not the marketing page): qwen35 hybrid, 32 # Architecture (engine ledger, not the marketing page): qwen35 hybrid, 32
# layers with qwen35.full_attention_interval = 4 ⇒ 8 full-attention + 24 Gated # layers with qwen35.full_attention_interval = 4 ⇒ 8 full-attention + 24 Gated
# DeltaNet, 4 KV heads × 256 dim, native 262,144. The KV pool scales with the 8 # DeltaNet, 4 KV heads × 256 dim, native 262,144. The KV pool scales with the 8
# full layers (13,056.00 MiB at the 786,432 pool, ledger-exact); the DeltaNet # full layers (16,384.00 MiB of f16 at the 524,288 pool); the DeltaNet layers
# layers cost a fixed recurrent state per SLOT — 452.25 MiB for 3 cells, ~151 # cost a fixed recurrent state per SLOT — ~50.25 MiB a cell, so 100.50 MiB for
# MiB each. The runtime is a hard lock, not a preference: custom GGML type 142 # two. The runtime is a hard lock, not a preference: custom GGML type 142 plus
# plus prism.hadamard.* metadata, which only the vendor build registers, and # prism.hadamard.* metadata, which only the vendor build registers (the b10776
# MTP beside quantized V needs build ≥ b10776 — so the prebuilt moved 10754 → # gate that moved the prebuilt 10754 → 10777 was MTP beside quantized V, which
# 10777 with the payload. Vision comes from the publisher's Q8_0 projector (the # exact KV now makes moot — the type-142 lock alone keeps the build pinned).
# BF16 one can crash the server on concurrent images, and this endpoint runs # Vision comes from the publisher's Q8_0 projector (the BF16 one can crash the
# three slots), so --mmproj is what gives this endpoint eyes; MMPROJ_FILE="" is # server on concurrent images, and this endpoint runs two slots), so --mmproj is
# the documented text-only opt-out. # what gives this endpoint eyes; MMPROJ_FILE="" is the documented text-only
# opt-out.
# #
# Speculative decoding ships OFF, measured: the head loads and accepts well # Speculative decoding ships OFF, measured: the head loads and accepts well
# (0.798/0.617 per position, mean length 2.41) but the draft context bills its # (0.798/0.617 per position, mean length 2.41) but the draft context bills its
# own f16 KV over the WHOLE pool — 4,096 B/position × 786,432 = 3,072 MiB plus # own f16 KV over the WHOLE pool — 4,096 B/position × 524,288 = 2,048 MiB plus
# buffers — and at ubatch 2048 that squeeze HALVES decode (167.2 → 109.2 t/s). # buffers — and the trunk already projects 21,234 MiB with exact KV. Exact KV
# frank-zealot.env carries the six-arm table and the arm that turns it back on # and MTP cannot share this card; frank-zealot.env carries the q8_0-era arm table
# (--ubatch-size 1024, decode 213.5 t/s) with its price. # and the two doors back to the drafter (one window, or KV back to q8_0 at
# --ubatch-size 1024 for 213.5 t/s).
# #
# Sampling: the checkpoint carries the publisher's thinking recipe as GGUF # Sampling: the checkpoint carries the publisher's thinking recipe as GGUF
# metadata (general.sampling.* = temp 1.0 / top_p 0.95 / top_k 20 / min_p 0 / # metadata (general.sampling.* = temp 1.0 / top_p 0.95 / top_k 20 / min_p 0 /
@@ -56,40 +58,46 @@ fi
: "${ALIAS:=frank-zealot}" : "${ALIAS:=frank-zealot}"
: "${BIND_ADDR:=0.0.0.0}" : "${BIND_ADDR:=0.0.0.0}"
: "${LISTEN_PORT:=7771}" : "${LISTEN_PORT:=7771}"
# ctx-size is the POOL llama.cpp divides across slots: 786,432 / 3 = 262,144 # ctx-size is the POOL llama.cpp divides across slots: 524,288 / 2 = 262,144
# per conversation — every slot at the model's trained window. The harness # per conversation — every slot still at the model's trained window. The harness
# contextWindow MUST quote the PER-SLOT number — a longer prompt is answered # contextWindow MUST quote the PER-SLOT number — a longer prompt is answered
# with a 400, never truncated. # with a 400, never truncated. Under exact KV the pool cannot stay at 786,432:
: "${CTX_SIZE:=786432}" # 32,768 B/token × 786,432 = 24,576 MiB, more than the card's whole addressable
: "${PARALLEL:=3}" # 24,123 MiB before a single weight loads.
: "${CTX_SIZE:=524288}"
: "${PARALLEL:=2}"
# Compute-buffer geometry. Ubatch is a prefill knob (decode never moves). # Compute-buffer geometry. Ubatch is a prefill knob (decode never moves).
# 2048 is the measured CUDA optimum (MiMo era, same arch/shape: +4.6 % prefill # 2048 is the measured CUDA optimum (MiMo era, same arch/shape: +4.6 % prefill
# at 8.9k depth, decode flat, 616 MiB). The logical batch is VRAM- and # at 8.9k depth, decode flat, 616 MiB). The logical batch is VRAM- and
# throughput-neutral at a fixed ubatch. It ALSO sets the compute-buffer size, # throughput-neutral at a fixed ubatch. It ALSO sets the compute-buffer size,
# which is the term that decides whether an MTP draft context fits beside a # which is the term that decides whether an MTP draft context fits beside a KV
# 786,432-position pool: 1024 hands back 1,306 MiB and is the arm that runs the # pool: 1024 hands back 1,306 MiB and was the arm that ran the drafter at three
# drafter at three slots (frank-zealot.env carries the table). # q8_0 windows (frank-zealot.env carries the table).
: "${BATCH_SIZE:=4096}" : "${BATCH_SIZE:=4096}"
: "${UBATCH_SIZE:=2048}" : "${UBATCH_SIZE:=2048}"
# Quantized KV: q8_0 K + q8_0 V — the only pair that funds 3 × 262,144 on this # Exact KV: f16 K + f16 V — no quantization error anywhere in the attention
# card. 17 KiB/token at this geometry (8 full-attention layers × 4 KV heads × # state. 32 KiB/token at this geometry (8 full-attention layers × 4 KV heads ×
# 256 dim); 13,056.00 MiB at the 786,432 pool, ledger-exact. f16 would bill # 256 dim × 2 B × 2 caches); 16,384.00 MiB at the 524,288 pool. That is the whole
# 26,112 MiB against a 24,123 MiB card and dies at cudaMalloc; q4_0 is the # reason the slot count is 2: the same dtype at the old 786,432 pool is 24,576
# fallback rung. Explicit either way so the contract is visible in the argv, # MiB and dies at cudaMalloc. q8_0 K + q8_0 V (17,408 B/token, 13,056.00 MiB at
# and --flash-attn stays required for quantized V (no true fp8 KV dtype exists # 786,432) is the documented fallback rung and funds three windows; CACHE_TYPE_V
# in this build). # alone is the halfway door. Explicit either way so the contract is visible in
: "${CACHE_TYPE_K:=q8_0}" # the argv, and --flash-attn stays pinned because quantized V — the fallback —
: "${CACHE_TYPE_V:=q8_0}" # is illegal without it (no true fp8 KV dtype exists in this build).
: "${CACHE_TYPE_K:=f16}"
: "${CACHE_TYPE_V:=f16}"
# Speculative decoding defaults OFF, measured on this card and shape: the head # Speculative decoding defaults OFF, measured on this card and shape: the head
# IS in the checkpoint (qwen35.nextn_predict_layers = 1 — MTP is a property of # IS in the checkpoint (qwen35.nextn_predict_layers = 1 — MTP is a property of
# the FILE, so the plain PQ2_0 sibling could not run it at any flag value), it # the FILE, so the plain PQ2_0 sibling could not run it at any flag value), it
# loads, and it accepts well (mean accepted length 2.41) — but the draft context # loads, and it accepts well (mean accepted length 2.41) — but the draft context
# bills its own f16 KV over the WHOLE pool (4,096 B/position, 3,072 MiB here) # bills its own f16 KV over the WHOLE pool (4,096 B/position, 2,048 MiB at the
# and at ubatch 2048 that squeeze halves decode: 167.2 → 109.2 t/s. There is no # 524,288 pool) and at ubatch 2048 that squeeze halves decode: 167.2 → 109.2 t/s
# --spec-draft-cache-type knob in this build to make the draft cheap, and no # on the q8_0 3-slot shape. The draft cache dtype IS a knob (--spec-draft-type-k
# --spec-draft-model either: the draft rides the main model's weights. Card gate: # / --spec-draft-type-v, both default f16 — verified in this build's --help), so
# quantized V alongside MTP needs fork build >= prism-b10776, which is why # an armed drafter can halve its own bill; there is no --spec-draft-model because
# LLAMA_BIN moved to the 10777 prebuilt; on an older build the pair is illegal. # the draft rides the main model's weights. Card gate: quantized V alongside MTP
# needs fork build >= prism-b10776, which is why LLAMA_BIN moved to the 10777
# prebuilt; exact KV removes that gate but NOT the type-142 loader lock.
# SPEC_TYPE=draft-mtp + UBATCH_SIZE=1024 is the documented MTP arm (213.5 t/s); # SPEC_TYPE=draft-mtp + UBATCH_SIZE=1024 is the documented MTP arm (213.5 t/s);
# SPEC_TYPE=none strips the whole set (flags move together). # SPEC_TYPE=none strips the whole set (flags move together).
: "${SPEC_TYPE:=none}" : "${SPEC_TYPE:=none}"
@@ -104,7 +112,7 @@ fi
: "${NGL:=99}" : "${NGL:=99}"
# Log verbosity threshold (llama.cpp -lv). 3 is the engine default (info); # Log verbosity threshold (llama.cpp -lv). 3 is the engine default (info);
# 4 adds debug detail (KV layout, buffer sizes, graph capture) which is what # 4 adds debug detail (KV layout, buffer sizes, graph capture) which is what
# the VRAM-fit forensics need — the q8_0 KV contract and the draft-KV bill are # the VRAM-fit forensics need — the exact-KV size and the draft-KV bill are
# only provable from this log. Levels above 4 flood the journal. # only provable from this log. Levels above 4 flood the journal.
: "${VERBOSITY:=4}" : "${VERBOSITY:=4}"
# /metrics needs the flag: llama.cpp returns 501 unless the server starts with # /metrics needs the flag: llama.cpp returns 501 unless the server starts with
@@ -169,11 +177,11 @@ fi
# open after boot; make sure it exists before the server needs it. # open after boot; make sure it exists before the server needs it.
nvidia-modprobe -u -c=0 2>/dev/null || true nvidia-modprobe -u -c=0 2>/dev/null || true
# Flash attention stays pinned on: it is REQUIRED for quantized V (q8_0), # Flash attention stays pinned on. Exact f16 KV runs fine with or without it,
# and the build carries GGML_CUDA_FA_ALL_QUANTS=ON (since 2026-09-17), so # but the documented fallback rung (quantized V) is ILLEGAL without it, and the
# the q8_0 KV contract in frank-zealot.env is a real path — without this # build carries GGML_CUDA_FA_ALL_QUANTS=ON (since 2026-09-17) so that rung is a
# flag or that build option the engine silently falls back and the # real path — dropping the flag here would leave the retreat route silently
# contract becomes a lie. # falling back to something the env file does not describe.
ARGS=( ARGS=(
--model "$MODEL_FILE" --model "$MODEL_FILE"
--alias "$ALIAS" --alias "$ALIAS"
+82 -33
View File
@@ -3,15 +3,16 @@
# prism-ml/Ternary-Bonsai-2-9B-gguf at PQ2_0 — the ternary Bonsai-2-9B, a # prism-ml/Ternary-Bonsai-2-9B-gguf at PQ2_0 — the ternary Bonsai-2-9B, a
# qwen35 hybrid with 32 layers (8 full-attention + 24 Gated DeltaNet) and the # qwen35 hybrid with 32 layers (8 full-attention + 24 Gated DeltaNet) and the
# MTP draft head INSIDE the file — via the PrismML fork build of llama.cpp # MTP draft head INSIDE the file — via the PrismML fork build of llama.cpp
# (vendor build 10777, the minimum that pairs MTP with a quantized V cache), # (vendor build 10777), the ONLY loader that registers the payload's custom
# the ONLY loader that registers the payload's custom tensor type 142 and # tensor type 142 and honours its prism.hadamard.* metadata: TWO 262,144-token
# honours its prism.hadamard.* metadata: THREE 262,144-token conversations # conversations (--ctx-size 524288 --parallel 2) over an UNQUANTIZED f16 KV
# (--ctx-size 786432 --parallel 3), q8_0 K + q8_0 V under flash attention (f16 # cache. Exact KV bills 32,768 B/token here, so the pool is what pays for it —
# KV would bill 26,112 MiB of a 24,123 MiB card), the publisher's own Q8_0 # the same dtype at the old 786,432 pool is 24,576 MiB against the 24,123 MiB
# vision projector (the BF16 one can crash on concurrent images), and MTP OFF # the driver leaves addressable, and the slot count is the term that moved. The
# — measured, not because the head is missing: the draft context bills f16 KV # publisher's own Q8_0 vision projector rides along (the BF16 one can crash on
# over the whole pool and halves decode at this shape. Log verbosity 4 ships so # concurrent images), and MTP stays OFF: the head is present, but exact KV
# the VRAM-fit forensics land in the journal. # leaves no room for a draft context that bills f16 KV over the whole pool. Log
# verbosity 4 ships so the VRAM-fit forensics land in the journal.
# This seam runs the serve script against a stub engine and asserts on the # This seam runs the serve script against a stub engine and asserts on the
# argv it actually execs, so what is pinned is the command the operator gets. # argv it actually execs, so what is pinned is the command the operator gets.
@@ -76,6 +77,22 @@ argv_value() {
grep -A1 -xF -- "$1" <<<"$output" | tail -1 grep -A1 -xF -- "$1" <<<"$output" | tail -1
} }
# kv_bytes_per_token DTYPE — bytes ONE cache costs per token at this payload's
# geometry: 8 full-attention layers × 4 KV heads × 256 dim = 8,192 elements
# (measured from the GGUF header: n_embd_k_gqa = n_embd_v_gqa = 1024). f16 is
# 2 B/element; q8_0 is 1.0625 (32 codes plus one 2-byte scale per 32-block);
# q4_0 is 0.625 (codes + scale + min per 32-block). Unknown dtype is a FAILURE,
# never a silent zero — the fit gate below is only honest if it cannot be
# satisfied by a rung it does not understand.
kv_bytes_per_token() {
case "$1" in
f16|bf16) echo 16384 ;;
q8_0) echo 8704 ;;
q4_0) echo 5120 ;;
*) return 1 ;;
esac
}
# run_stage — exec the CURRENT $STAGE/test.env as-is. serve_argv REWRITES # run_stage — exec the CURRENT $STAGE/test.env as-is. serve_argv REWRITES
# that file from the shipped env, so any test that mutates it (drop a line, # that file from the shipped env, so any test that mutates it (drop a line,
# append a drafter) must exec through this, not serve_argv — which silently # append a drafter) must exec through this, not serve_argv — which silently
@@ -126,7 +143,7 @@ run_stage() {
done done
} }
@test "three conversations share the pool at a 262144 window each" { @test "two conversations share the pool at a 262144 window each" {
serve_argv serve_argv
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[ "$(argv_value --alias)" = "frank-zealot" ] [ "$(argv_value --alias)" = "frank-zealot" ]
@@ -134,15 +151,13 @@ run_stage() {
[ "$(argv_value --host)" = "0.0.0.0" ] [ "$(argv_value --host)" = "0.0.0.0" ]
[ "$(argv_value --model)" = "$MODEL_DIR/$MODEL_STUB" ] [ "$(argv_value --model)" = "$MODEL_DIR/$MODEL_STUB" ]
[ "$(argv_value --n-gpu-layers)" -gt 0 ] [ "$(argv_value --n-gpu-layers)" -gt 0 ]
# llama.cpp divides --ctx-size across slots, so the observable contract # llama.cpp divides --ctx-size across slots, so the observable contract is
# is the per-slot window. The 9B swap exists precisely so every # the per-slot window. Exact KV costs the SLOT COUNT, not the window: every
# conversation gets the model's TRAINED 262,144 (the 27B had to be cut to # conversation still gets the model's TRAINED 262,144, and the harness
# 200,704 to fund two), and the harness contextWindow must quote 262144 — # contextWindow must quote that number — the pool and the slot count are
# the pool and the slot count are neither, and a longer prompt is a 400. # neither, and a longer prompt is a 400, never a truncation.
# Three is also the ceiling: a fourth slot would push each window under the [ "$(argv_value --parallel)" -eq 2 ]
# fleet's worst-case compaction input, which is this endpoint's job. [ "$(argv_value --ctx-size)" -eq 524288 ]
[ "$(argv_value --parallel)" -eq 3 ]
[ "$(argv_value --ctx-size)" -eq 786432 ]
local pool slots local pool slots
pool="$(argv_value --ctx-size)"; slots="$(argv_value --parallel)" pool="$(argv_value --ctx-size)"; slots="$(argv_value --parallel)"
[ $((pool % slots)) -eq 0 ] [ $((pool % slots)) -eq 0 ]
@@ -151,15 +166,16 @@ run_stage() {
@test "the KV dtype pair follows the env file and flash-attn stays pinned on" { @test "the KV dtype pair follows the env file and flash-attn stays pinned on" {
# Asserted against the env rather than a literal so a retune moves one # Asserted against the env rather than a literal so a retune moves one
# file, not two. The shipped pair is q8_0/q8_0: this payload bills # file, not two. The shipped pair is f16/f16: exact attention state, and the
# 8 full-attention layers × 4 KV heads × 256 dim = 8,192 elements per token # slot count is what makes it affordable — this payload bills 8,192 elements
# per cache, so f16/f16 would need 26,112 MiB of the 24,123 MiB card while # per token per cache (8 full-attention layers × 4 KV heads × 256 dim), so
# q8_0 bills 17,408 B/token = 13,056.00 MiB at the 786,432 pool — the # exact KV is 32,768 B/token = 16,384.00 MiB at the 524,288 pool, where the
# ledger said exactly that, and it is the referee. The 24 DeltaNet layers # q8_0 pair this file used to ship billed 17,408 B/token = 13,056.00 MiB at
# bill a fixed recurrent state per slot (452.25 MiB for three cells), which # the old 786,432 pool (ledger-exact both times). The 24 DeltaNet layers
# is why pool arithmetic alone under-prices the shape. flash-attn is not # bill a fixed recurrent state per slot (~50.25 MiB a cell), which is why
# decoration: quantized V is only legal with it, and it must stay explicit # pool arithmetic alone under-prices the shape. flash-attn is not
# so a future quant swap cannot silently fall back. # decoration: the quantized-V fallback is only legal with it, so it stays
# explicit and a dtype retune cannot silently fall back.
local env_k env_v local env_k env_v
env_k="$(sed -nE 's/^CACHE_TYPE_K=(.+)$/\1/p' "$ENV_FILE")" env_k="$(sed -nE 's/^CACHE_TYPE_K=(.+)$/\1/p' "$ENV_FILE")"
env_v="$(sed -nE 's/^CACHE_TYPE_V=(.+)$/\1/p' "$ENV_FILE")" env_v="$(sed -nE 's/^CACHE_TYPE_V=(.+)$/\1/p' "$ENV_FILE")"
@@ -170,6 +186,38 @@ run_stage() {
[ "$(argv_value --flash-attn)" = "on" ] [ "$(argv_value --flash-attn)" = "on" ]
} }
@test "the shipped KV dtype and pool actually fit the card this boots on" {
# No health gate can see this mistake: llama.cpp sizes the KV pool as
# rate × pool-positions and cudaMalloc fails at boot ON THE LIVE ENDPOINT.
# The fit is arithmetic off the payload geometry plus the non-KV terms
# measured off the boot ledger (weights 2,036.71 + compute buffers 1,409.02
# + projector 595 + 248.10 + CUDA context ≈ 460 = 4,749 MiB, dtype- and
# slot-independent at this ubatch), plus one ~51 MiB recurrent-state cell
# per slot. 24,123 MiB is what the driver leaves addressable (nvidia-smi
# reports it as used + free; its total column reads 24,576).
local env_k env_v pool slots rate_k rate_v kv total
env_k="$(sed -nE 's/^CACHE_TYPE_K=(.+)$/\1/p' "$ENV_FILE")"
env_v="$(sed -nE 's/^CACHE_TYPE_V=(.+)$/\1/p' "$ENV_FILE")"
pool="$(sed -nE 's/^CTX_SIZE=([0-9]+)$/\1/p' "$ENV_FILE")"
slots="$(sed -nE 's/^PARALLEL=([0-9]+)$/\1/p' "$ENV_FILE")"
rate_k="$(kv_bytes_per_token "$env_k")"
rate_v="$(kv_bytes_per_token "$env_v")"
kv="$(( pool * (rate_k + rate_v) / 1048576 ))"
total="$(( kv + 4749 + 51 * slots ))"
# The shipped arm has to leave real room: the projector's worst case is
# charged AFTER the engine's own fit check, so a projection that lands on
# the ceiling is a crash waiting for the first image request.
[ "$total" -le $(( 24123 - 1024 )) ]
# And the gate must be able to REFUSE: the same exact KV at the pool this
# shape left behind is 24,576 MiB of cache alone — a boot that dies before
# /health ever answers. Proving the gate bites is what makes passing one
# meaningful.
local over
over="$(( 786432 * (rate_k + rate_v) / 1048576 + 4749 + 51 * 3 ))"
[ "$over" -gt 24123 ]
}
@test "log verbosity follows the env file" { @test "log verbosity follows the env file" {
serve_argv serve_argv
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
@@ -233,10 +281,11 @@ run_stage() {
# Frank, 2026-10-18: this file DOES carry the MTP head (the boot arms prove # Frank, 2026-10-18: this file DOES carry the MTP head (the boot arms prove
# it loads and drafts, acceptance 0.798/0.617 per position, mean accepted # it loads and drafts, acceptance 0.798/0.617 per position, mean accepted
# length 2.41) and it is still off, because the draft context bills its own # length 2.41) and it is still off, because the draft context bills its own
# f16 KV over the WHOLE pool — 4,096 B/position × 786,432 = 3,072 MiB plus # f16 KV over the WHOLE pool — 4,096 B/position × 524,288 = 2,048 MiB plus
# buffers — and at --ubatch-size 2048 that squeeze halves decode: 167.2 → # buffers — on a trunk that already projects 21,234 MiB with exact KV. The
# 109.2 t/s. The MTP arm is SPEC_TYPE=draft-mtp + UBATCH_SIZE=1024 (213.5 # MTP arms in frank-zealot.env are all q8_0-era measurements; the doors back
# t/s), documented in frank-zealot.env. The flags move as a set: # to the drafter are one window per conversation, or KV back to q8_0 with
# UBATCH_SIZE=1024 (213.5 t/s). The flags move as a set:
# SPEC_TYPE=none strips --spec-type, --spec-draft-n-max AND # SPEC_TYPE=none strips --spec-type, --spec-draft-n-max AND
# --spec-draft-model together, so a no-spec boot cannot carry half a spec # --spec-draft-model together, so a no-spec boot cannot carry half a spec
# pair (the ROCmFPX-era baseline arm segfaulted at load doing that). # pair (the ROCmFPX-era baseline arm segfaulted at load doing that).
@@ -276,7 +325,7 @@ run_stage() {
# The Bonsai-2 text file carries no vision tensors — the publisher's Q8_0 # The Bonsai-2 text file carries no vision tensors — the publisher's Q8_0
# projector argument IS the vision capability: no flag, no eyes. Q8_0 (not # projector argument IS the vision capability: no flag, no eyes. Q8_0 (not
# BF16) is the publisher's own requirement for concurrent image requests, # BF16) is the publisher's own requirement for concurrent image requests,
# which is what --parallel 3 means in practice. # which is what --parallel 2 means in practice.
serve_argv serve_argv
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[ "$(argv_value --mmproj)" = "$MODEL_DIR/$MM_STUB" ] [ "$(argv_value --mmproj)" = "$MODEL_DIR/$MM_STUB" ]