docs(frank-zealot): measured exact-KV boot, floor rates, chat-path instrument

Deployed shape verified live: KV 16,384.00 MiB (ledger-exact 32,768 B/token x
524,288), RS 100.50 MiB (2 cells), settled nvidia-smi 21,176 used / 2,949 free,
n_slots 2, n_ctx_slot 262144, DropInPaths empty.

Rates recorded as FLOORS: another consumer drove the endpoint through the whole
window (56 prompts on the other slot in three minutes, 96% GPU-util). Single
stream 134.1-141.5 (145.8/163.2 in a lighter minute) vs the 168.7 q8_0 record;
two-stream aggregate 256.6-265.1 vs ~300 across three; prefill 3,159.5 at
106,282 beats the 2,877 record while contended; decode at 106k depth 71.5 (new
baseline, no prior). Per-stream concurrency improved: ~130 each vs 102.8.

Instrument correction: the PrismML fork build 10777 answers 500 'The model
produced output that does not match the expected Content-only format' for a
greedy ignore_eos run on the raw /completion path, so the per-request timings
must be read off /v1/chat/completions. AGENTS.md Benchmarking Policy corrected.
This commit is contained in:
Frank
2026-10-18 21:28:04 -07:00
parent 71bc1735d3
commit d457985b8d
3 changed files with 91 additions and 16 deletions
+6 -2
View File
@@ -252,8 +252,12 @@ archive.
real users, and a bench beside a live service starves the GPU and corrupts real users, and a bench beside a live service starves the GPU and corrupts
results. Use the runtime's own harness: `llama-bench` for the llama.cpp results. Use the runtime's own harness: `llama-bench` for the llama.cpp
endpoints; a live llama.cpp server (frank-zealot) also reports honest endpoints; a live llama.cpp server (frank-zealot) also reports honest
per-request numbers in its `/completion` `timings` object (prefill/decode per-request numbers in a `timings` object (prefill/decode rates, `draft_n`,
rates, `draft_n`, `draft_n_accepted`), which is how frank-zealot was measured. `draft_n_accepted`) — but on the PrismML fork build 10777 that object is only
reaching you on **`/v1/chat/completions`**: a greedy `ignore_eos` run on the raw
`/completion` path there answers **500 "The model produced output that does not
match the expected Content-only format"**, so measure through the chat path
(measured 2026-10-18 on the exact-KV reslice).
halogen has NO `/completion` path at all — frank-dragoon's rates come from halogen has NO `/completion` path at all — frank-dragoon's rates come from
wall-clock around `/v1/chat/completions` plus the boot ledger. Rolling your own wall-clock around `/v1/chat/completions` plus the boot ledger. Rolling your own
bench requires asking first. bench requires asking first.
@@ -187,14 +187,28 @@ 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`, numbers from the 27B era) excludes the projector — **read settled `nvidia-smi`,
never the projection**. never the projection**.
**The exact-KV arm's projection, recorded before its own boot.** Every term of **The exact-KV boot, measured** (2026-10-18 evening, PR #21, under the unit,
that ledger survives the reslice except the KV pool (13,056 → 16,384 MiB) and verbosity 4 — the live shape):
one recurrent-state cell (−50.25 MiB), so the pre-flight prediction is
`17,922 − 13,056 + 16,384 − 50 ≈ 21,200 MiB used / ~2,920 free`, with ~2 GiB ```
still standing through the mmproj worst case. **Replace this paragraph with the CUDA0 model buffer size = 2036.71 MiB # + 257.66 MiB CPU_Mapped (mmap)
MEASURED ledger and settled `nvidia-smi` from the first f16 boot** — a CUDA0 KV buffer size = 16384.00 MiB # f16 K + f16 V, 524,288 positions
projection is a pre-flight check, not a record, and the whole reason the shape CUDA0 RS buffer size = 100.50 MiB # 2 cells, 32 layers, 2 seqs
changed dtype is that projections drift from ledgers. CUDA0 compute buffer = 1408.06 MiB # + 1088.07 MiB CUDA_Host
mmproj worst-case (est.) = 868.32 MiB # 248.10 CUDA + 24.93 CPU, AFTER fit
nvidia-smi settled: 21,176 MiB used / 2,949 MiB free of 24,123
n_slots = 2, n_ctx_slot = 262144, kv_unified = false
```
The pre-flight projection was right to the MiB, because the two terms it moved
are exact arithmetic: **16,384.00 MiB of KV is precisely 32,768 B/token ×
524,288 positions** — rate derivation and ledger agree with no slop, which is
what buries the old 26,112 MiB figure for good — and the recurrent state is one
50.25 MiB cell lighter (100.50 exactly). Settled card state landed at **21,176
used / 2,949 free**, within 24 MiB of the 21,200 / 2,920 projection,
`DropInPaths` empty, no clamp line, no `cudaMalloc` error. It is a desktop
margin, though: the mmproj's 868 MiB worst case is still charged AFTER the
engine's own fit check, so ~2 GiB is what an image storm has to live inside.
## Measured — the q8_0 3-slot boot (`/completion` `timings`, greedy, `ignore_eos`) ## Measured — the q8_0 3-slot boot (`/completion` `timings`, greedy, `ignore_eos`)
@@ -211,19 +225,49 @@ changed dtype is that projections drift from ledgers.
stops on its own: a 24-token countdown measured 72 t/s across three streams stops on its own: a 24-token countdown measured 72 t/s across three streams
while the same shape with `ignore_eos` gave 300. while the same shape with `ignore_eos` gave 300.
**These are the numbers the exact-KV arm is judged against.** Exact KV makes ## Measured — the exact-KV boot (floors, not a clean A/B)
attention read 1.882× the bytes per generated token, so a decode drop is
EXPECTED and has to be measured, not assumed; prefill should hold. Re-run the Instrument: `/v1/chat/completions` `timings` (this fork refuses the raw
same instrument on the first f16 boot — greedy, `ignore_eos`, 512 tokens, the `/completion` path for greedy runs — see *Contract traps*), greedy,
second request or later, and the two-stream aggregate in place of the `ignore_eos`, 512 tokens, first request discarded.
three-stream one — and record it here.
|Arm|q8_0 3-slot record|f16 2-slot, live co-tenancy|
|---|---|---|
|single-stream decode|168.7|**134.1 – 141.5** over five back-to-back runs; 145.8 and 163.2 in a lighter minute|
|concurrent aggregate|≈300 across 3 (102.8 each)|**256.6 / 262.4 / 265.1** across 2 (≈128–133 each)|
|prefill at ~106k|2,877 at 106,418|**3,159.5 at 106,282**|
|decode at 106k depth|never measured|**71.5**|
**These are floors, not the shape's rates.** Someone else was driving the
endpoint through the whole window — 56 new prompts on the other slot in three
minutes, `nvidia-smi` at 96 % GPU-util — which is the exact condition the
Benchmarking Policy forbids, and a contended deep arm even came back truncated
(a 512 KB body accepted as `prompt_n = 4`). What the floors already support:
**per-conversation concurrency improved** (two streams hold ~130 t/s each where
three held 102.8), endpoint aggregate sits ~12 % under the 3-slot record,
prefill beat the record (+9.8 %) while contended, and single-stream decode is
between parity and −20 % against it. The 1.882× KV byte bill is real; its true
size on this card is still unmeasured, and quoting 134 t/s or 163 t/s as "the"
number would be a guess in either direction. Re-run the same arms with the box
quiet before the figure is carried anywhere else.
## Contract traps ## Contract traps
- **`--metrics` is mandatory.** Without it llama.cpp answers **501** on - **`--metrics` is mandatory.** Without it llama.cpp answers **501** on
`/metrics`, so the Prometheus scrape reads DOWN while `/health` answers 200 — `/metrics`, so the Prometheus scrape reads DOWN while `/health` answers 200 —
that is how this endpoint sat red across four payload generations that is how this endpoint sat red across four payload generations
(`up{job="frank-zealot"}` oldest retained sample, 2026-10-11, is already 0). (`up{job="frank-zealot"}` oldest retained sample, 2026-10-11, is already 0).
- **Bare `/completion` is a dead instrument on this fork build.** A greedy
`ignore_eos` run answers **500 — "The model produced output that does not
match the expected Content-only format"**, because the fork guards the raw
path the moment reasoning markup leaks (older builds only logged `failed to
parse reasoning content` and answered anyway). The honest per-request
instrument on THIS build is `/v1/chat/completions`, whose non-stream response
carries the same `timings` object (`prompt_n`, `prompt_per_second`,
`predicted_n`, `predicted_per_second`). `AGENTS.md` still names the
`/completion` `timings` object as this endpoint's instrument — on build 10777
read it off the chat path.
- **Q8_0 mmproj, not BF16.** The publisher's card: the BF16 tower "can crash - **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" — llama-server when several image requests run at once; Q8_0 is not affected" —
and `--parallel 2` is still precisely that shape. and `--parallel 2` is still precisely that shape.
+27
View File
@@ -1,5 +1,32 @@
# Bundle Change Log # Bundle Change Log
## 2026-10-18
- **frank-zealot resliced to two windows of exact KV (Frank's call, PR #21).**
`--ctx-size 786432 --parallel 3` at q8_0 K+V → **`524288 / 2` at f16 K+V**, so
both remaining conversations keep the native 262,144 window and the
compaction hub reads exact K and V. Halving the pool is what funds it: the
f16 pair is 32,768 B/token over the 8 full-attention layers = 16,384.00 MiB,
while the old 786,432-position pool would have needed ~24,576 MiB of KV alone
on a card that leaves 24,576 addressable. Measured boot: KV **16,384.00**,
RS **100.50** (2 cells), compute 1,408.06 + 1,088.07, settled `nvidia-smi`
**21,176 used / 2,949 free**, `n_ctx_slot = 262144`, `DropInPaths` empty. The
doc's old "f16 needs 26,112 MiB and cannot boot" figure was the 27B's
34,816 B/token applied to the 9B — corrected in the runbook.
- **Rates are floors, and that is recorded as such.** The endpoint had another
consumer for the whole measurement window (56 prompts on the other slot in
three minutes, 96 % GPU-util), so single-stream decode came back 134.1–141.5
(145.8/163.2 in a lighter minute) against the 3-slot record of 168.7,
two-stream aggregate 256.6–265.1 against ≈300 across three, and prefill
**3,159.5 at 106,282** beat the record's 2,877 while contended. Per-stream
concurrency improved (≈130 each vs 102.8); a clean re-measure needs the box
quiet.
- **Instrument correction (fleet law).** On the PrismML fork build 10777 the raw
`/completion` path answers **500 "The model produced output that does not
match the expected Content-only format"** for a greedy `ignore_eos` run, so
the honest per-request `timings` for this endpoint must be read off
`/v1/chat/completions`. `AGENTS.md` said `/completion`; it now says the chat
path.
## 2026-10-13 ## 2026-10-13
- **frank-archon outage: a kernel knob, not the model.** The 2026-10-12 23:17 - **frank-archon outage: a kernel knob, not the model.** The 2026-10-12 23:17
power failure on dankserver reset `/sys/module/ttm/parameters/pages_limit` power failure on dankserver reset `/sys/module/ttm/parameters/pages_limit`