Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52dbe18486 | ||
|
|
2a12487653 | ||
|
|
a483e51804 | ||
|
|
b9d21ae947 | ||
|
|
30e8ed55a0 | ||
|
|
ba69ea563e | ||
|
|
a7062d1cb3 | ||
|
|
25a8b00043 |
No files matched your search
@@ -388,10 +388,11 @@ the publisher's own **Q8_0** mmproj passed as `--mmproj` — Q8_0 is the
|
||||
publisher's requirement for concurrent image requests (the card states the
|
||||
BF16 tower can crash `llama-server` when several image requests run at once,
|
||||
and this endpoint runs several slots). `frank-zealot`'s `contextWindow` is
|
||||
**262144** — the PER-SLOT window (`--ctx-size 524288 --parallel 2` since the
|
||||
2026-10-18 reslice to exact f16 KV), which EQUALS the model's
|
||||
native window; llama.cpp answers a longer prompt with a 400 naming the limit,
|
||||
never a truncation, so the harness must compact before it.
|
||||
**200704** — the PER-SLOT window (`--ctx-size 602112 --parallel 3` since the
|
||||
2026-10-19 reslice that funded a third slot AND an armed MTP drafter),
|
||||
deliberately below the model's native window; llama.cpp answers a longer prompt
|
||||
with a 400 naming the limit, never a truncation, so the harness must compact
|
||||
before it.
|
||||
Note also
|
||||
that
|
||||
the reasoning channel differs by runtime: llama.cpp AND SGLang (qwen3
|
||||
@@ -439,10 +440,11 @@ disagrees with it is the bug.
|
||||
loader ignoring that metadata dequantizes wrong); payload
|
||||
`Bonsai2-9B-MTP/…PQ2_0.gguf` + the publisher's **Q8_0** mmproj (the BF16
|
||||
tower can crash the server with concurrent image requests).
|
||||
- Shape: `--ctx-size 524288 --parallel 2` = **two conversations at the
|
||||
model's native 262,144**, `CACHE_TYPE_K/V=f16`, batch 4096 / ubatch 2048,
|
||||
`SPEC_TYPE=none`, `--flash-attn on`, `--jinja`, `--metrics`.
|
||||
- Harness/gate contract: `contextWindow: 262144` is the **per-slot** window
|
||||
- Shape: `--ctx-size 602112 --parallel 3` = **three conversations at
|
||||
200,704 each**, `CACHE_TYPE_K=f16` + `CACHE_TYPE_V=q8_0` (asymmetric: 25,088
|
||||
B/token, 14,406 MiB of pool), batch 6144 / ubatch 1024,
|
||||
`SPEC_TYPE=draft-mtp` (depth 2), `--flash-attn on`, `--jinja`, `--metrics`.
|
||||
- Harness/gate contract: `contextWindow: 200704` is the **per-slot** window
|
||||
(`n_ctx_slot = CTX_SIZE / PARALLEL`), and llama.cpp answers a longer prompt
|
||||
with a 400 rather than truncating. `--metrics` is mandatory or Prometheus
|
||||
reads 501 = DOWN beside a healthy service. Presence penalty has no GGUF
|
||||
|
||||
@@ -12,7 +12,8 @@ guidance, so "conforms to OKF" is a test and not a claim:
|
||||
TIMESTAMP `timestamp` present, ISO 8601, not in the future
|
||||
LENGTH lines between 1 and 500 inclusive
|
||||
INDEX-FM no index.md may carry frontmatter (index.md is reserved)
|
||||
LOG-DATE log.md date headings start `## YYYY-MM-DD`, newest first
|
||||
LOG-DATE log.md date headings are `## YYYY-MM-DD` or `## YYYY-MM-DD — Title`,
|
||||
one entry per date, newest first
|
||||
INDEX-COVER every index.md links each of its sibling pages; the root index
|
||||
links every page in the bundle
|
||||
INDEX-NUM an index table row restates a fleet quantity — a digit run of
|
||||
@@ -143,8 +144,12 @@ def check_file(path: Path, root: Path, length_exempt=frozenset()):
|
||||
if not DATE_RE.match(head):
|
||||
continue
|
||||
dates.append(head[:10])
|
||||
if head != head[:10]:
|
||||
v.append(("LOG-DATE", rel, f"date heading carries a title: '## {head}'"))
|
||||
# OKF requires the heading to USE the date format; a title after an
|
||||
# em-dash is allowed and costs zero lines, so `## 2026-08-17 — Why the
|
||||
# pair was rebuilt` is conformant and `## 2026-08-17Rebuilt` is not.
|
||||
if head != head[:10] and not re.match(r"^\d{4}-\d{2}-\d{2}\s+—\s+\S", head):
|
||||
v.append(("LOG-DATE", rel,
|
||||
f"date heading must be the date alone or `date — Title`: '## {head}'"))
|
||||
if len(dates) != len(set(dates)):
|
||||
v.append(("LOG-DATE", rel, "duplicate date heading"))
|
||||
if dates != sorted(dates, reverse=True):
|
||||
|
||||
@@ -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
|
||||
# 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
|
||||
# several image requests run at once, and this endpoint runs --parallel 2. Its
|
||||
# several image requests run at once, and this endpoint runs --parallel 3. Its
|
||||
# 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.
|
||||
LLM_ZEALOT_MMPROJ="${LLM_ZEALOT_MMPROJ:-/home/dank/models/Bonsai2-9B-MTP/Ternary-Bonsai-2-9B-mmproj-Q8_0.gguf}"
|
||||
|
||||
+37
-21
@@ -11,35 +11,51 @@ LLAMA_BIN=/home/dank/GitPackages/llama-prism-b10777/llama-server
|
||||
# metadata dequantizes wrong, silently.
|
||||
MODEL_FILE=/home/dank/models/Bonsai2-9B-MTP/Ternary-Bonsai-2-9B-PQ2_0-MTP-Q8_0.gguf
|
||||
# Q8_0 IS MANDATORY: the publisher states the BF16 tower can crash llama-server
|
||||
# under concurrent image requests, which is exactly --parallel 2.
|
||||
# under concurrent image requests, which is exactly --parallel 3.
|
||||
MMPROJ_FILE=/home/dank/models/Bonsai2-9B-MTP/Ternary-Bonsai-2-9B-mmproj-Q8_0.gguf
|
||||
# Served id — the harness/family name, not a fact about the payload.
|
||||
# Id-keyed consumers: OpenWebUI (model row, access_grant, api_configs), models.yml.
|
||||
ALIAS=frank-zealot
|
||||
BIND_ADDR=0.0.0.0
|
||||
LISTEN_PORT=7771
|
||||
# CTX_SIZE is the POOL llama.cpp divides across slots: 524,288 / 2 = 262,144 a
|
||||
# conversation, each still at the trained window. MUST stay divisible by
|
||||
# PARALLEL (the seam enforces it) and models.yml contextWindow quotes the
|
||||
# PER-SLOT number — a longer prompt is a 400, never a truncation. The pool
|
||||
# cannot go back to 786,432 under exact f16 KV: 24,576 MiB of cache against the
|
||||
# 24,123 MiB the driver leaves addressable, before a weight loads.
|
||||
CTX_SIZE=524288
|
||||
PARALLEL=2
|
||||
BATCH_SIZE=4096
|
||||
# CTX_SIZE is the POOL llama.cpp divides across slots: 602,112 / 3 = 200,704 a
|
||||
# conversation. MUST stay divisible by PARALLEL (the seam enforces it) and
|
||||
# models.yml contextWindow quotes the PER-SLOT number — a longer prompt is a
|
||||
# 400, never a truncation. The pool is bought, not granted: K+V at 25,088
|
||||
# B/token is 14,406.00 MiB here and the MTP draft context bills its own f16 KV
|
||||
# over the WHOLE pool (4,096 B/position ⇒ 2,352.00 MiB), so the old 786,432
|
||||
# pool cannot carry a drafter at any KV dtype — 18,816 + 3,072 of cache alone
|
||||
# against the 24,123 MiB the driver leaves addressable.
|
||||
CTX_SIZE=602112
|
||||
PARALLEL=3
|
||||
# The logical batch is VRAM- and throughput-neutral at a fixed ubatch (the law
|
||||
# on [[frank-zealot]]); 1024×6 keeps it a clean multiple of the ubatch.
|
||||
BATCH_SIZE=6144
|
||||
# Ubatch is a prefill knob (decode never moves with it) and it sets the
|
||||
# compute-buffer size — which is what decides whether a drafter fits.
|
||||
# UBATCH_SIZE=1024 is the documented MTP arm.
|
||||
UBATCH_SIZE=2048
|
||||
# Exact K and V: the retrieval-fidelity ask, paid with one conversation of
|
||||
# concurrency. Rate law and the arithmetic: [[frank-zealot]] → Laws.
|
||||
# compute-buffer size, which decides whether a drafter fits. 1024 is the rung
|
||||
# every measured MTP arm here ran on (213.5 t/s against 109.2 at 2048). Its
|
||||
# VRAM credit is SHAPE-specific: the 786,432-pool arm census showed ~1,306 MiB
|
||||
# back, this boot measured 2,089.88 MiB of target compute against 2,496.13 at
|
||||
# ubatch 2048 — 406 MiB back, with the split moving (CUDA0 +257, CUDA_Host
|
||||
# −663). The drafter is funded by CACHE_TYPE_V below, not by this knob.
|
||||
UBATCH_SIZE=1024
|
||||
# K exact, V in the 8-bit class: 16,384 + 8,704 = 25,088 B/token (8,192
|
||||
# elements per token per cache × 2 B for f16, × 1.0625 for q8_0 — there is no
|
||||
# true fp8 KV dtype in this build). That 7,680 B/token saved against exact V is
|
||||
# what funds BOTH the third conversation and the drafter. Quantized V is
|
||||
# illegal without --flash-attn, which the serve script pins. Rate law and the
|
||||
# arithmetic: [[frank-zealot]] → Laws.
|
||||
CACHE_TYPE_K=f16
|
||||
CACHE_TYPE_V=f16
|
||||
# Speculation OFF — measured, not assumed: the draft context bills its own f16
|
||||
# KV over the WHOLE pool, and there is no room beside exact KV (the arm census
|
||||
# is on the page). SPEC_TYPE=none strips the WHOLE set (--spec-type and every
|
||||
# --spec-draft-*); half a set has segfaulted at load.
|
||||
SPEC_TYPE=none
|
||||
CACHE_TYPE_V=q8_0
|
||||
# Speculation ON (Frank, 2026-10-19): the head is in the file
|
||||
# (qwen35.nextn_predict_layers = 1, blk.32.nextn.*) and rides the MAIN model's
|
||||
# weights, so there is no --spec-draft-model. Its bill is pool × 4,096 B of
|
||||
# draft KV plus ~0.45 GiB of buffers and a tripled recurrent state — paid for
|
||||
# by CACHE_TYPE_V=q8_0 and UBATCH_SIZE=1024 above. Build gate: quantized V
|
||||
# beside MTP needs fork build >= b10776; LLAMA_BIN is 10777.
|
||||
# SPEC_TYPE=none is the rollback and strips the WHOLE set (--spec-type and
|
||||
# every --spec-draft-*); half a set has segfaulted at load.
|
||||
SPEC_TYPE=draft-mtp
|
||||
SPEC_DRAFT_N_MAX=2
|
||||
SPEC_DRAFT_N_MIN=0
|
||||
SPEC_DRAFT_P_MIN=0
|
||||
|
||||
+24
-8
@@ -50,23 +50,39 @@ def held(tok, corpus):
|
||||
|
||||
def main(argv):
|
||||
new_dir = pathlib.Path(argv[1])
|
||||
corpus = [
|
||||
p.read_text(errors="replace")
|
||||
for p in new_dir.rglob("*.md")
|
||||
if ".AGENTTRASH" not in str(p) and ".obsidian" not in str(p)
|
||||
]
|
||||
blob = "\n".join(corpus)
|
||||
lost = 0
|
||||
checked = 0
|
||||
|
||||
def corpus_excluding(exclude):
|
||||
"""Every vault page except the one under test.
|
||||
|
||||
The corpus is what must still hold a fact AFTER the file is gone, so a
|
||||
file that lives inside the scanned directory has to be removed from it —
|
||||
otherwise every token is "held" by the file itself and the census proves
|
||||
nothing (it reports 0 losses for every page, which is the bug this
|
||||
guards).
|
||||
"""
|
||||
out = []
|
||||
for p in new_dir.rglob("*.md"):
|
||||
s = str(p)
|
||||
if ".AGENTTRASH" in s or ".obsidian" in s:
|
||||
continue
|
||||
if exclude is not None and p.resolve() == exclude:
|
||||
continue
|
||||
out.append(p.read_text(errors="replace"))
|
||||
return out
|
||||
|
||||
lost = checked = 0
|
||||
for spec in argv[2:]:
|
||||
if spec.startswith("git-"):
|
||||
ref, path = spec[4:].split(":", 1)
|
||||
old = subprocess.run(["git", "show", f"{ref}:{path}"],
|
||||
capture_output=True, text=True).stdout
|
||||
label = f"{ref}:{path}"
|
||||
exclude = None
|
||||
else:
|
||||
old = pathlib.Path(spec).read_text(errors="replace")
|
||||
label = spec
|
||||
exclude = pathlib.Path(spec).resolve()
|
||||
blob = "\n".join(corpus_excluding(exclude))
|
||||
for line in old.splitlines():
|
||||
s = line.strip().strip("->*|# ").strip()
|
||||
if len(s) < 30 or s.startswith("```"):
|
||||
|
||||
@@ -5,33 +5,45 @@
|
||||
#
|
||||
# 2026-10-18 (Frank): Bonsai-2 27B PQ2_0 → the 9B MTP export, then the same
|
||||
# evening TWO 262,144-token conversations (--ctx-size 524288 --parallel 2) over
|
||||
# an UNQUANTIZED f16 KV cache. The 9B bills 8,192 elements per token per cache
|
||||
# (8 full-attention layers instead of the 27B's 16), so exact K and V costs
|
||||
# 32,768 B/token — 16,384.00 MiB at the 524,288 pool, which is what a 24,123 MiB
|
||||
# addressable card can carry beside the trunk. Verbosity 4 ships so the VRAM-fit
|
||||
# forensics (KV layout, buffer sizes, graph capture) land in the journal.
|
||||
# an UNQUANTIZED f16 KV cache.
|
||||
#
|
||||
# 2026-10-19 (Frank): THREE 200,704-token conversations over a 602,112 pool
|
||||
# (--ctx-size 602112 --parallel 3), K left at the engine's own default (f16),
|
||||
# V dropped to q8_0, batch 1024×6 / ubatch 1024, and the MTP drafter ARMED
|
||||
# (--spec-type draft-mtp). The 9B bills 8,192 elements per token per cache (8
|
||||
# full-attention layers instead of the 27B's 16), so f16 K is 16,384 B/token and
|
||||
# q8_0 V is 8,704: 25,088 B/token = 14,406.00 MiB at the 602,112 pool. That
|
||||
# 7,680 B/token against exact V is exactly what funds the third conversation
|
||||
# AND a draft context that bills f16 KV over the whole pool. Verbosity 4 ships
|
||||
# so the VRAM-fit forensics (KV layout, buffer sizes, graph capture) land in
|
||||
# the journal.
|
||||
#
|
||||
# Architecture (engine ledger, not the marketing page): qwen35 hybrid, 32
|
||||
# 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
|
||||
# full layers (16,384.00 MiB of f16 at the 524,288 pool); the DeltaNet layers
|
||||
# cost a fixed recurrent state per SLOT — ~50.25 MiB a cell, so 100.50 MiB for
|
||||
# two. The runtime is a hard lock, not a preference: custom GGML type 142 plus
|
||||
# prism.hadamard.* metadata, which only the vendor build registers (the b10776
|
||||
# gate that moved the prebuilt 10754 → 10777 was MTP beside quantized V, which
|
||||
# exact KV now makes moot — the type-142 lock alone keeps the build pinned).
|
||||
# full layers (14,406.00 MiB of f16 K + q8_0 V at the 602,112 pool); the
|
||||
# DeltaNet layers cost a fixed recurrent state per SLOT — ~50.25 MiB a cell, so
|
||||
# 150.75 MiB for three, and an armed depth-2 drafter triples that again. The
|
||||
# runtime is a hard lock, not a preference: custom GGML type 142 plus
|
||||
# prism.hadamard.* metadata, which only the vendor build registers — and the
|
||||
# b10776 gate that moved the prebuilt 10754 → 10777 (MTP beside a quantized V)
|
||||
# is LOAD-BEARING again on this shape, not moot as it was under exact KV.
|
||||
# Vision comes from the publisher's Q8_0 projector (the BF16 one can crash the
|
||||
# server on concurrent images, and this endpoint runs two slots), so --mmproj is
|
||||
# server on concurrent images, and this endpoint runs three slots), so --mmproj is
|
||||
# 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
|
||||
# (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 × 524,288 = 2,048 MiB plus
|
||||
# buffers — and the trunk already projects 21,234 MiB with exact KV. Exact KV
|
||||
# and MTP cannot share this card; frank-zealot.env carries the q8_0-era arm table
|
||||
# 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).
|
||||
# Speculative decoding ships ON (Frank, 2026-10-19). The head is IN the file and
|
||||
# accepts well (0.798/0.617 per position, mean length 2.41, measured 2026-10-18);
|
||||
# what made it lose that evening was the bill, not the drafts — the draft context
|
||||
# is built against the target's WHOLE pool, so blk.32 adds 4,096 B/position
|
||||
# (2,352.00 MiB at 602,112) plus 480.97 MiB of its own compute buffers on the
|
||||
# card, and it triples the recurrent state per slot (452.25 MiB for three cells
|
||||
# against 50.25 a cell with the drafter off). CACHE_TYPE_V=q8_0 is what pays
|
||||
# that: 7,680 B/token cheaper than exact V, 4,410 MiB at this pool. The ubatch
|
||||
# drop to 1024 contributes ~406 MiB here, NOT the ~1,306 the 786,432-pool census
|
||||
# showed — that figure does not transfer between shapes. SPEC_TYPE=none is
|
||||
# the rollback and strips the whole flag set.
|
||||
#
|
||||
# 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 /
|
||||
@@ -58,49 +70,46 @@ fi
|
||||
: "${ALIAS:=frank-zealot}"
|
||||
: "${BIND_ADDR:=0.0.0.0}"
|
||||
: "${LISTEN_PORT:=7771}"
|
||||
# ctx-size is the POOL llama.cpp divides across slots: 524,288 / 2 = 262,144
|
||||
# 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
|
||||
# with a 400, never truncated. Under exact KV the pool cannot stay at 786,432:
|
||||
# 32,768 B/token × 786,432 = 24,576 MiB, more than the card's whole addressable
|
||||
# 24,123 MiB before a single weight loads.
|
||||
: "${CTX_SIZE:=524288}"
|
||||
: "${PARALLEL:=2}"
|
||||
# ctx-size is the POOL llama.cpp divides across slots: 602,112 / 3 = 200,704 a
|
||||
# conversation. The harness contextWindow MUST quote the PER-SLOT number — a
|
||||
# longer prompt is answered with a 400, never truncated. The pool is priced by
|
||||
# the KV bill, not by the trained window: at 25,088 B/token (f16 K + q8_0 V) the
|
||||
# 602,112 pool costs 14,406.00 MiB and the armed drafter adds its own f16 KV over
|
||||
# the whole pool (2,352.00 MiB). Exact V at this pool (18,816 MiB) or the old
|
||||
# 786,432 pool at any dtype cannot boot inside the 24,123 MiB the driver leaves
|
||||
# addressable.
|
||||
: "${CTX_SIZE:=602112}"
|
||||
: "${PARALLEL:=3}"
|
||||
# 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
|
||||
# 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,
|
||||
# which is the term that decides whether an MTP draft context fits beside a KV
|
||||
# pool: 1024 hands back 1,306 MiB and was the arm that ran the drafter at three
|
||||
# q8_0 windows (frank-zealot.env carries the table).
|
||||
: "${BATCH_SIZE:=4096}"
|
||||
: "${UBATCH_SIZE:=2048}"
|
||||
# Exact KV: f16 K + f16 V — no quantization error anywhere in the attention
|
||||
# state. 32 KiB/token at this geometry (8 full-attention layers × 4 KV heads ×
|
||||
# 256 dim × 2 B × 2 caches); 16,384.00 MiB at the 524,288 pool. That is the whole
|
||||
# reason the slot count is 2: the same dtype at the old 786,432 pool is 24,576
|
||||
# MiB and dies at cudaMalloc. q8_0 K + q8_0 V (17,408 B/token, 13,056.00 MiB at
|
||||
# 786,432) is the documented fallback rung and funds three windows; CACHE_TYPE_V
|
||||
# alone is the halfway door. Explicit either way so the contract is visible in
|
||||
# the argv, and --flash-attn stays pinned because quantized V — the fallback —
|
||||
# is illegal without it (no true fp8 KV dtype exists in this build).
|
||||
# The logical batch is VRAM- and throughput-neutral at a fixed ubatch, so 6144
|
||||
# (1024×6) is free; the ubatch is what sets the compute-buffer size, and 1024 is
|
||||
# the rung every measured MTP arm here ran on (213.5 t/s at 1024 against 109.2
|
||||
# at 2048) — worth ~406 MiB at THIS shape, not the ~1,306 the old pool showed.
|
||||
: "${BATCH_SIZE:=6144}"
|
||||
: "${UBATCH_SIZE:=1024}"
|
||||
# Asymmetric KV: f16 K (the engine's own default, spelled out so the contract is
|
||||
# visible in argv) + q8_0 V. 8,192 elements per token per cache (8 full-attention
|
||||
# layers × 4 KV heads × 256 dim) ⇒ 16,384 B/token for K and 8,704 for V (q8_0 is
|
||||
# 1.0625 B/element — llama.cpp has no true fp8 KV dtype), 25,088 B/token and
|
||||
# 14,406.00 MiB at the 602,112 pool. That is the halfway door between exact KV
|
||||
# (32,768 B/token, which costs the third conversation and the drafter both) and
|
||||
# the fully quantized pair (17,408 B/token, which costs retrieval fidelity in the
|
||||
# keys); --flash-attn stays pinned because quantized V is illegal without it.
|
||||
: "${CACHE_TYPE_K:=f16}"
|
||||
: "${CACHE_TYPE_V:=f16}"
|
||||
# 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
|
||||
# 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
|
||||
# bills its own f16 KV over the WHOLE pool (4,096 B/position, 2,048 MiB at the
|
||||
# 524,288 pool) and at ubatch 2048 that squeeze halves decode: 167.2 → 109.2 t/s
|
||||
# on the q8_0 3-slot shape. The draft cache dtype IS a knob (--spec-draft-type-k
|
||||
# / --spec-draft-type-v, both default f16 — verified in this build's --help), so
|
||||
# an armed drafter can halve its own bill; there is no --spec-draft-model because
|
||||
# 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=none strips the whole set (flags move together).
|
||||
: "${SPEC_TYPE:=none}"
|
||||
: "${CACHE_TYPE_V:=q8_0}"
|
||||
# Speculative decoding defaults ON: the head 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) and the draft context rides
|
||||
# the MAIN model's weights, so there is no --spec-draft-model. Its bill is the
|
||||
# target's whole pool at 4,096 B/position plus ~0.45 GiB of buffers at this
|
||||
# ubatch and a tripled recurrent state; CACHE_TYPE_V=q8_0 and UBATCH_SIZE=1024
|
||||
# above are what pay for it. The draft cache dtype IS a further knob
|
||||
# (--spec-draft-type-k / --spec-draft-type-v, both default f16 — verified in this
|
||||
# build's --help) and stays at its default here. Card gate: quantized V alongside
|
||||
# MTP needs fork build >= prism-b10776, which is why LLAMA_BIN is the 10777
|
||||
# prebuilt; the type-142 loader lock keeps it pinned regardless.
|
||||
# SPEC_TYPE=none is the rollback and strips the whole set (flags move together).
|
||||
: "${SPEC_TYPE:=draft-mtp}"
|
||||
: "${SPEC_DRAFT_N_MAX:=2}"
|
||||
: "${SPEC_DRAFT_N_MIN:=0}"
|
||||
: "${SPEC_DRAFT_P_MIN:=0}"
|
||||
|
||||
@@ -13,7 +13,7 @@ timestamp: 2026-08-14
|
||||
|
||||
This page estimates the speed of a 25-40B model split across two machines
|
||||
over ethernet. It uses the bandwidth math in [[Ethernet Bandwidth Math]]
|
||||
and the mixed-vendor measurements in [[Multi-Node Runtimes]].
|
||||
and the mixed-vendor measurements in [[x4-pair-experiment]].
|
||||
|
||||
## The Bandwidth Ceiling Is Not the Limit
|
||||
|
||||
@@ -93,5 +93,5 @@ roughly 200k-260k tokens across both nodes.
|
||||
## See Also
|
||||
|
||||
- [[Ethernet Bandwidth Math]]
|
||||
- [[Multi-Node Runtimes]]
|
||||
- [[Qwen3.8 PP Deployment]]
|
||||
- [[Sample Script Set]]
|
||||
@@ -1,257 +0,0 @@
|
||||
---
|
||||
type: reference
|
||||
title: Multi-Node Runtimes
|
||||
description: "How each two-machine runtime is built and launched, what it can combine with, and the mixed-vendor verdicts."
|
||||
tags:
|
||||
- pipeline-parallelism
|
||||
- llamacpp
|
||||
- vllm
|
||||
- speculative
|
||||
timestamp: 2026-10-19
|
||||
---
|
||||
|
||||
# Multi-Node Runtimes
|
||||
|
||||
Two runtimes can put one model on two machines: llama.cpp over the ggml-rpc
|
||||
backend, and vLLM with pipeline parallelism. This page owns the cross-runtime
|
||||
facts — how each is built and launched, what each can and cannot be combined
|
||||
with, and the mixed-vendor verdicts. Facts owned by a sibling page are linked,
|
||||
not restated. Bytes-per-token arithmetic and link budgets belong to
|
||||
[[Ethernet Bandwidth Math]], throughput estimates to [[Expected Performance]],
|
||||
the real deployment and its verified gotchas to [[Qwen3.8 PP Deployment]], the
|
||||
educational scripts to [[Sample Script Set]], drafter mechanisms to
|
||||
[[DFlash DSpark Speculative Decoding]], single-host multi-GPU placement to
|
||||
[[Llama Cpp Multi-Device Splits]].
|
||||
|
||||
## What each runtime is
|
||||
|
||||
Both distribute by **layers**, not inside layers.
|
||||
|
||||
| | llama.cpp over ggml-rpc | vLLM pipeline parallelism |
|
||||
| --- | --- | --- |
|
||||
| unit of distribution | a contiguous slice of layers (`--split-mode layer`, the default) | a contiguous slice of layers (`--pipeline-parallel-size`) |
|
||||
| process model | one `llama-server`/`llama-cli` on the head; a `ggml-rpc-server` per worker | one `vllm serve` per node, one engine across them |
|
||||
| what crosses the wire | whatever graph work the head offloads to the remote devices | the hidden state at each stage boundary, point to point |
|
||||
| KV cache | distributed with the weights; the KV of a layer lives with the owner of that layer | each stage holds the KV of the layers it owns |
|
||||
| speculation together with the split | yes in llama.cpp (see below) | no, per vLLM's own docs |
|
||||
|
||||
Upstream's own summary is why layer-wise is the ethernet choice: pipeline
|
||||
parallel "minimizes data transfers between GPUs but requires many tokens to
|
||||
scale well", while tensor parallel "does multiple cross-GPU reductions per
|
||||
layer … much more bottlenecked by the GPU interconnect speed".
|
||||
|
||||
Do not carry the tensor path across machines: `--split-mode tensor` is
|
||||
experimental, needs flash attention, rejects quantized KV, and is not
|
||||
implemented for the MoE and hybrid architectures the guide lists — and
|
||||
per-tensor placement across vendors is not expressible (§1 of [[Llama Cpp Multi-Device Splits]]).
|
||||
|
||||
## llama.cpp over ggml-rpc
|
||||
|
||||
### Build and launch
|
||||
|
||||
Both sides take `-DGGML_RPC=ON` next to that host's own backend:
|
||||
|
||||
```bash
|
||||
cmake .. -DGGML_CUDA=ON -DGGML_RPC=ON # a CUDA worker; -DGGML_HIP=ON for an AMD one
|
||||
cmake --build . --config Release
|
||||
```
|
||||
|
||||
Worker side — `ggml-rpc-server` exposes ggml devices on the remote host:
|
||||
|
||||
```bash
|
||||
bin/ggml-rpc-server # endpoint 127.0.0.1:50052 by default
|
||||
bin/ggml-rpc-server --device CUDA0 -c # or CUDA_VISIBLE_DEVICES=0; -c = local cache
|
||||
```
|
||||
|
||||
- It exposes every accelerator it can see; a host with no accelerator exposes a
|
||||
single `CPU` device. `--device` and `CUDA_VISIBLE_DEVICES` are equivalent
|
||||
ways to narrow that set.
|
||||
- `-c` keeps large tensors in `$HOME/.cache/llama.cpp/rpc` (redirect with
|
||||
`LLAMA_CACHE`) so a big model does not re-stream per load; that cache
|
||||
filling the worker disk is a recorded fleet incident ([[Qwen3.8 PP Deployment]]).
|
||||
|
||||
Head side — build the local backends plus `-DGGML_RPC=ON`, then name the
|
||||
workers:
|
||||
|
||||
```bash
|
||||
llama-server -m model.gguf --rpc 192.168.88.10:50052,192.168.88.11:50052 -ngl 99
|
||||
```
|
||||
|
||||
`--rpc` is a comma-separated list of `host:port`, one entry per
|
||||
`ggml-rpc-server`; the head's own devices stay in the pool next to the remote
|
||||
ones. Launch scripts for this shape: [[Sample Script Set]].
|
||||
|
||||
### Split, context, KV
|
||||
|
||||
- Default placement: weights **and** KV across all devices, local and remote,
|
||||
"in proportion to each device's available memory". Override with
|
||||
`--tensor-split`, whose proportions follow the order in `--device`. Why the
|
||||
fleet always pins it (a Strix reports a huge GTT window, so the automatic
|
||||
split lands on the slow device) is [[Qwen3.8 PP Deployment]]'s.
|
||||
- `-sm layer` (the default) is pipeline parallelism: each device holds a
|
||||
contiguous slice of layers and the KV cache of a layer lives on the device
|
||||
that owns that layer.
|
||||
- KV types are the usual `-ctk` / `-ctv`, `f16` by default. `--fit` is on by
|
||||
default and sizes unset arguments to device memory; turn it off to hold a
|
||||
context you set on purpose. Context is one global `--ctx-size` — the RPC
|
||||
README documents no per-node context or per-node KV knob.
|
||||
- Upstream's out-of-memory ladder (stated for tensor mode, useful order
|
||||
anyway): lower `--ctx-size`, then `--parallel`, then `--n-gpu-layers`.
|
||||
- Transport is TCP; RDMA is negotiated at the handshake with no flag changes
|
||||
and falls back to TCP unless both peers can use it (Linux RoCEv2 via
|
||||
`libibverbs`, macOS RDMA over Thunderbolt 5 via `librdma`). It is point to
|
||||
point — name the peer's RDMA-capable address in `--rpc` or the session stays
|
||||
on TCP. `GGML_RPC_NO_RDMA=1` forces TCP; `GGML_RPC_DEBUG` traces the backend.
|
||||
|
||||
Upstream calls the backend a proof of concept, "fragile and insecure" (see the
|
||||
Security section). Two limits shape the design: decode is bound by the per-step
|
||||
round trip, not by the link's bytes ([[Ethernet Bandwidth Math]]), and every
|
||||
node must run the identical build or the handshake hangs ([[Qwen3.8 PP Deployment]]).
|
||||
|
||||
## vLLM pipeline parallelism
|
||||
|
||||
> **Provenance.** Flags and mechanics below were recorded 2026-08-14 and are
|
||||
> not re-checked upstream in this pass (see "Unverified in this pass"); the
|
||||
> incompatibility lines, the method catalog and the issue history are re-verified.
|
||||
|
||||
- `--pipeline-parallel-size` divides the layers into contiguous stages;
|
||||
`--tensor-parallel-size` still works inside a node, so the canonical two-node
|
||||
shape is tp = GPUs per node, pp = nodes.
|
||||
- Two backends: Ray (`--distributed-executor-backend ray`, the worker joining
|
||||
a `ray start --head`) or multiprocessing (`--distributed-executor-backend mp`
|
||||
with `--nnodes`, `--node-rank`, `--master-addr`, `--headless` on the
|
||||
follower). `mp` avoids the Ray dependency; Ray brings the cluster manager.
|
||||
- What the interconnect must carry: one activation tensor per stage boundary
|
||||
per forward pass, point to point — not a per-layer reduction. That is the
|
||||
whole cross-node traffic story; the byte arithmetic and link budgets are
|
||||
[[Ethernet Bandwidth Math]]. Pin the NIC vLLM talks on
|
||||
(`NCCL_SOCKET_IFNAME`) rather than letting it guess an interface.
|
||||
- The PP bubble: with N stages and one sequence in flight, one stage works
|
||||
while N-1 idle, so pp=2 costs half the machine at batch one. Batching is the
|
||||
cure — pipeline parallelism "requires many tokens to scale well" — and the
|
||||
microbatch formula is [[Ethernet Bandwidth Math]] §5.3.
|
||||
- KV and context: each stage keeps the KV of its own layers, so the pool grows
|
||||
with the stage count, and `--max-model-len` is not reduced by the split
|
||||
(recorded 2026-08-14, unverified here).
|
||||
- What it cannot combine with, in vLLM's words: "Pipeline parallelism is not
|
||||
composable with speculative decoding as of `vllm<=0.15.0`", and "Speculative
|
||||
decoding with a draft models is not supported in `vllm<=0.10.0`". Composing
|
||||
the two has been requested since 2024 and closed stale (#6911, #10615); PP
|
||||
also carries a Qwen3.5 bug report closed stale without a fix (#36643).
|
||||
- Method catalog at v0.25.1: EAGLE, MTP, draft model, PARD, MLP, n-gram, suffix
|
||||
decoding, hidden-state extraction, a custom proposer class, dynamic
|
||||
speculative decoding; `dflash` is an accepted `method` value.
|
||||
|
||||
## Speculative decoding across the split
|
||||
|
||||
llama.cpp chains several implementations behind one comma-separated
|
||||
`--spec-type` list; the split does not change the chain, only where its passes
|
||||
run. Mechanisms, chaining laws and drafter KV sizing are
|
||||
[[DFlash DSpark Speculative Decoding]]'s; only the split interaction is here.
|
||||
|
||||
| Method | llama.cpp type | Over ggml-rpc | With vLLM PP | Why |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| MTP | `draft-mtp` | works; not documented upstream, but run this way on this fleet's split head with acceptance measured ([[Qwen3.8 PP Deployment]]) | no | Context-backed drafter: it needs a draft context and runs in the head process, so RPC devices are simply more devices under it |
|
||||
| DFlash | `draft-dflash` | same gate, same status as MTP | no | Same draft-context gate (`ctx_dft`); the drafter's KV lands on whichever device the draft context is built on — the one thing to check on a split |
|
||||
| ngram-mod | `ngram-mod` | safest of the four: no draft model, no draft context, no remote draft pass | no | Draftless — proposals come from cached token n-grams in the head, so the split adds no device work |
|
||||
| DSpark | `draft-dspark` | **unproven over RPC** — not a missing feature | no | `draft-dspark` exists upstream; nobody has measured it with the target split across two machines |
|
||||
|
||||
**Correction (2026-10-19).** A retired stub in this directory asserted "DSpark
|
||||
is vLLM-only." False. `draft-dspark` is in upstream llama.cpp — parsed in
|
||||
`common/arg.cpp`, mapped to `COMMON_SPECULATIVE_TYPE_DRAFT_DSPARK` in
|
||||
`common/speculative.cpp`, where it carries a Markov head and an anchor-first
|
||||
block layout and aborts with `DSpark draft has no confidence head: please set
|
||||
--spec-draft-p-min 0` when the drafter has no confidence head. What is unproven
|
||||
is running it over RPC, and that is the honest limit of the claim.
|
||||
|
||||
The vLLM column is uniform because that statement is blanket: it names pipeline
|
||||
parallelism, not a method, so n-gram and suffix are excluded as written. vLLM
|
||||
gives no mechanism with it; "the drafter must sit next to the target's layers,
|
||||
which a stage split separates" is a reading, not upstream's words.
|
||||
|
||||
Upstream likewise documents neither for nor against `--rpc` plus
|
||||
`--spec-type`. What the implementation does say is structural: every
|
||||
speculative implementation is built in the head process against the head's
|
||||
device set, and draftless types need no draft context at all. Routing draft
|
||||
passes to a remote worker, however, has never been tested.
|
||||
|
||||
## Mixed-vendor reality
|
||||
|
||||
What works today:
|
||||
|
||||
- **One vendor per node.** RPC is backend-agnostic by construction: upstream's
|
||||
own diagram has a host exposing CUDA0+CUDA1, a host exposing Metal, and a
|
||||
host exposing CUDA0+CPU, all driven by one main host over TCP. Each node
|
||||
builds its own backend plus `-DGGML_RPC=ON`; nothing speaks CUDA-to-ROCm.
|
||||
- **Identical build tag on every node**, worker restarted before head. The
|
||||
verified version-skew gotcha is [[Qwen3.8 PP Deployment]]'s.
|
||||
- **Speculation on the head** while the split carries the target (table above).
|
||||
- **RDMA when both peers have it**, auto-negotiated at the handshake; the size
|
||||
of the gain is third-party and unverified here.
|
||||
|
||||
Unproven or broken:
|
||||
|
||||
- **One binary holding both CUDA and ROCm for a same-host layer split**:
|
||||
reported to compile and then list only CUDA devices (issue #11506,
|
||||
third-party, unverified). Vulkan is the reported single-binary cross-vendor
|
||||
path, and single-host placement is [[Llama Cpp Multi-Device Splits]]'s, whose
|
||||
verdict is that per-tensor placement across a vendor mix never works.
|
||||
- **vLLM across a CUDA node and a ROCm node**: no known experiment; both ends
|
||||
run the same backend (recorded 2026-08-14). AMD's own two-Halo clustering
|
||||
guide is tensor parallelism over RCCL, not a mixed-vendor split.
|
||||
- **DSpark over RPC**: unproven, see the correction above.
|
||||
- **The exact NVIDIA-desktop + Strix-Halo-NUC pair has no published
|
||||
benchmark**, and the third-party mixed-vendor figures it is estimated from
|
||||
(Metal+CUDA over 10 GbE, two Strix Halos over USB4, a three-card CUDA+ROCm set
|
||||
over 1 GbE, a Vulkan triple in one box, an AMD four-node Strix cluster) live on
|
||||
[[Expected Performance]] and [[Ethernet Bandwidth Math]], each named with its
|
||||
model and hardware. None was re-verified in this pass.
|
||||
|
||||
## Security
|
||||
|
||||
`ggml-rpc-server` has **no authentication and no encryption**. Upstream's own
|
||||
warning: the backend is "in a proof-of-concept development stage. As such, the
|
||||
functionality is fragile and insecure. Never run the RPC server on an open
|
||||
network or in a sensitive environment!" It binds `127.0.0.1:50052` by default —
|
||||
safe by accident. Bind a routable address and any host that can reach the port
|
||||
can allocate memory there and supply the tensors it computes on. Trusted LAN
|
||||
only, firewalled or tunnelled; never a public address.
|
||||
|
||||
## Unverified in this pass
|
||||
|
||||
Every item was first recorded 2026-08-14, in the research that preceded this
|
||||
page. Nothing is silently deleted; none is backed by the sources cited below.
|
||||
|
||||
- **RPC protocol version and handshake rule** — recorded as protocol `5.0.0`
|
||||
with "major must match, server minor must be ≥ client". The README's own
|
||||
banner prints `Starting RPC server v3.0.0`; the conflict is unresolved.
|
||||
- **RPC internals** — a hard cap of 16 servers, synchronous graph compute, no
|
||||
tensor copy between two RPC servers, workers loading tensors on first use, a
|
||||
hash-keyed cache above roughly 10 MB, asserts on quantized tensors whose rows
|
||||
are not 512-aligned, and `-rpc` on `llama-bench` (the README names only
|
||||
`llama-cli` and `llama-server`).
|
||||
- **vLLM flag surface** — `--pipeline-parallel-size`, `--tensor-parallel-size`,
|
||||
`--distributed-executor-backend {ray,mp}`, `--nnodes`, `--node-rank`,
|
||||
`--master-addr`, `--headless`, the `ray start --head` join,
|
||||
`NCCL_SOCKET_IFNAME`.
|
||||
- **vLLM mechanics** — one activation handoff per stage boundary per forward
|
||||
pass, a worked ~512 MB handoff for a hidden-5120 model at batch 8 and 2048
|
||||
positions, `--max-model-len` unchanged by PP, ~50% idle at pp=2 batch one.
|
||||
- **vLLM throughput verdicts** for a 27B across two nodes over gigabit-class
|
||||
links; superseded as an estimate by [[Expected Performance]].
|
||||
- **Mixed-vendor claims** — a CUDA+HIP build listing only CUDA devices
|
||||
(#11506), Vulkan as the universal cross-vendor backend, AMD's two-Halo
|
||||
clustering guide being RCCL tensor parallelism, WiFi costing about ten times
|
||||
wired, the slowest hop setting the decode pace, RDMA worth a few percent.
|
||||
- **Draft over RPC** — that the draft model stays on the head, and that routing
|
||||
draft passes to a remote worker has never been confirmed.
|
||||
|
||||
## Citations
|
||||
|
||||
- [llama.cpp RPC README](https://github.com/ggml-org/llama.cpp/blob/master/tools/rpc/README.md), accessed 2026-10-19 — server usage and device exposure, `-DGGML_RPC=ON` on both sides, the `--rpc host:port` list, proportional weight+KV split and `--tensor-split`, the `-c` local cache, RDMA negotiation and `GGML_RPC_NO_RDMA`, the proof-of-concept and no-authentication warning.
|
||||
- [llama.cpp multi-GPU guide](https://github.com/ggml-org/llama.cpp/blob/master/docs/multi-gpu.md), accessed 2026-10-19 — split modes (`-sm layer` = pipeline parallelism, KV with the layer owner), `-ts`/`-ngl`/`-ctk`/`-ctv`/`-fit`, the pipeline-versus-tensor summary, the tensor-mode limits.
|
||||
- [vLLM speculative decoding docs, v0.25.1](https://docs.vllm.ai/en/v0.25.1/features/speculative_decoding/), accessed 2026-10-19 — the pipeline-parallelism incompatibility statement, the draft-model version floor, the method catalog.
|
||||
- [llama.cpp `common/speculative.cpp`](https://github.com/ggml-org/llama.cpp/blob/master/common/speculative.cpp), accessed 2026-10-19 — the speculative implementation set including `draft-dspark`, its draft-context gate, its confidence-head error.
|
||||
- vLLM issue #6911, "[Feature]: Combine pipeline parallelism with speculative decoding", opened 2024-07-29, closed not-planned 2025-03-28, accessed 2026-10-19.
|
||||
- vLLM issue #10615, "[Usage]: Does speculative decoding support pipeline parallelism?", opened 2024-11-25, closed not-planned 2025-04-18, accessed 2026-10-19.
|
||||
- vLLM issue #36643, "[Bug]: Qwen3.5 does not work with pipeline parallelism", opened 2026-03-10, closed not-planned 2026-09-24, accessed 2026-10-19.
|
||||
@@ -255,7 +255,7 @@ is 30 GiB.
|
||||
|
||||
## See Also
|
||||
|
||||
- [[Multi-Node Runtimes]]
|
||||
- [[Qwen3.8 PP Deployment]]
|
||||
- [[Expected Performance]]
|
||||
- [[Ethernet Bandwidth Math]]
|
||||
- [[DFlash DSpark Speculative Decoding]]
|
||||
@@ -69,4 +69,3 @@ graph LR
|
||||
|
||||
| Page | Owns |
|
||||
|---|---|
|
||||
| [[Multi-Node Runtimes]] | How each two-machine runtime is built and launched, what it can combine with, and the mixed-vendor verdicts. |
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
type: reference
|
||||
title: "frank-zealot on Dankclaw — llama.cpp (PrismML fork 10777), Ternary-Bonsai-2-9B PQ2_0-MTP, 2 × 262144, exact f16 KV"
|
||||
title: "frank-zealot on Dankclaw — llama.cpp (PrismML fork 10777), Ternary-Bonsai-2-9B PQ2_0-MTP, 3 × 200704, f16 K + q8_0 V, MTP armed"
|
||||
description: "The RTX 3090 endpoint: live shape exactly as the env ships it, every era with its rollback path, and its card laws."
|
||||
tags:
|
||||
- llm
|
||||
@@ -13,10 +13,11 @@ tags:
|
||||
timestamp: 2026-10-18
|
||||
---
|
||||
|
||||
# frank-zealot — llama.cpp (PrismML) Bonsai-2-9B PQ2_0-MTP, 2 × 262,144, exact f16 KV
|
||||
# frank-zealot — llama.cpp (PrismML) Bonsai-2-9B PQ2_0-MTP, 3 × 200,704, f16 K + q8_0 V, MTP armed
|
||||
|
||||
Dankclaw's RTX 3090 endpoint — `http://192.168.0.143:7771`, wire id `frank-zealot`, the fleet's **compaction hub**
|
||||
(widest window it has). One page per endpoint: eras are rows below, not pages. Siblings: [[frank-archon]],
|
||||
(three conversations at 200,704 each, the window the other two endpoints also cap at). One page per
|
||||
endpoint: eras are rows below, not pages. Siblings: [[frank-archon]],
|
||||
[[frank-dragoon]]. Deploy with `./scripts/deploy.sh dankclaw --service frank-zealot`; swap mechanics in
|
||||
[[Model Swap and Rollback]], sizing math in [[Llama Cpp Serving Sizing]]. `scripts/frank-zealot.env` and
|
||||
`scripts/serve-frank-zealot.sh` are the authority for the shape; where any prose disagrees with them, the prose
|
||||
@@ -32,72 +33,87 @@ is the bug.
|
||||
| Runtime | `/home/dank/GitPackages/llama-prism-b10777/llama-server` — PrismML **fork**, `0.2.0-dev (build 10777, commit e3de71522)`; the only loader for this payload |
|
||||
| Trio | `scripts/frank-zealot.service` (root, relinked, `User=dank`, `Restart=on-failure`, `RestartSec=10`, `LimitMEMLOCK=infinity`, `TimeoutStopSec=180`) · `scripts/frank-zealot.env` · `scripts/serve-frank-zealot.sh` |
|
||||
| Address / alias | `BIND_ADDR=0.0.0.0`, `LISTEN_PORT=7771`, `ALIAS=frank-zealot` (a flag, not a fact) |
|
||||
| Flags | `--ctx-size 524288 --parallel 2` ⇒ 262,144 per slot · `--cache-type-k f16 --cache-type-v f16` (exact, **16,384 MiB**) · `--flash-attn on` · `--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`** |
|
||||
| Flags | `--ctx-size 602112 --parallel 3` ⇒ 200,704 per slot · `--cache-type-k f16 --cache-type-v q8_0` (asymmetric, **14,406.00 MiB**) · `--flash-attn on` · `--batch-size 6144 --ubatch-size 1024` · `--n-gpu-layers 99` · `--jinja --reasoning auto --reasoning-format deepseek` · `--metrics` · `--mmproj` · `--log-verbosity 4` · `--presence-penalty 1.0` · **`--spec-type draft-mtp`** with `--spec-draft-n-max 2 --spec-draft-n-min 0 --spec-draft-p-min 0` |
|
||||
|
||||
**Why this shape.** `general.architecture qwen35`: 33 blocks (32 trunk + `blk.32` = the MTP block),
|
||||
`full_attention_interval 4` ⇒ 8 full-attention + 24 Gated DeltaNet, 4 KV heads × 256 dim, `nextn_predict_layers 1`
|
||||
(the head **is in the file**), trained window 262,144, `general.sampling.*` 1.0/0.95/20. Only the 8 full layers bill
|
||||
per token (32,768 B at exact f16 ⇒ 16,384 MiB over the pool) and the DeltaNet layers add ~50 MiB of recurrent state
|
||||
**per slot**: exact KV costs the third conversation and nothing else.
|
||||
per token and the DeltaNet layers add ~50 MiB of recurrent state **per slot**, so the shape is a single budget
|
||||
equation at 25,088 B/token (f16 K 16,384 + q8_0 V 8,704): **14,406.00 MiB** of cache over a 602,112 pool +
|
||||
**2,352.00 MiB** of draft cache (blk.32 at its own f16 rate, billed over the WHOLE pool) + ~7.7 GiB of weights,
|
||||
projector, buffers and states, inside the 24,123 MiB the driver leaves addressable. Exact V (32,768 B/token ⇒
|
||||
18,816 MiB here) buys neither the third conversation nor the drafter; the fully quantized pair (17,408 B/token)
|
||||
buys both and costs the keys their precision.
|
||||
|
||||
**MTP is OFF by measurement, not by absence of a head.** The drafter is *good* — 2026-10-18 measured
|
||||
acceptance 0.798 / 0.617 per position, mean accepted length 2.41, and the publisher's card says depth 2 works
|
||||
best — and it still loses here, because the draft context is built against the *target's* pool: `blk.32` bills
|
||||
4,096 B/position at its default f16 ⇒ 2,048 MiB at 524,288 positions on a trunk that projects 21,234 MiB of the
|
||||
24,123 MiB addressable (that projection predicts 2,889 MiB free; replace it with the measured pair). Exact KV and a
|
||||
drafter cannot share this card. `--spec-draft-type-k`/`--spec-draft-type-v` (default f16, verified in this build's
|
||||
`--help`) would cut the draft to 1,088 MiB — legal on b10777, **never booted beside exact KV**. There is no
|
||||
`--spec-draft-model`: the MTP draft rides the main model's weights. The env carries the recipe
|
||||
(`SPEC_DRAFT_N_MAX=2`, `SPEC_DRAFT_N_MIN=0`, `SPEC_DRAFT_P_MIN=0`); `SPEC_TYPE=none` never sends it.
|
||||
**MTP is ON (Frank, 2026-10-19).** The drafter was always good — 2026-10-18 measured acceptance 0.798 / 0.617 per
|
||||
position, mean accepted length 2.41, and the publisher's card says depth 2 works best — and arming it is a
|
||||
budget decision, not a flag: the draft context is built against the *target's* pool, so `blk.32` bills 4,096
|
||||
B/position (2,352.00 MiB at 602,112) plus 480.97 MiB of its own CUDA0 buffers, and it triples the recurrent
|
||||
state (452.25 MiB for three cells against 150.75 with the drafter off). `CACHE_TYPE_V=q8_0` is what pays that:
|
||||
7,680 B/token cheaper than exact V = **4,410.00 MiB** at this pool. `UBATCH_SIZE=1024` — the rung every measured
|
||||
MTP arm here ran on — contributes only ~406 MiB at THIS shape, not the ~1,306 the 786,432-pool census showed,
|
||||
and `UBATCH_SIZE=2048` beside an armed drafter is still the shape that halved decode (167.2 → 109.2 t/s).
|
||||
`--spec-draft-type-k`/`--spec-draft-type-v` (default f16, verified in this build's `--help`) would cut the draft's bill again and stay at their default; there is no
|
||||
`--spec-draft-model` — the draft rides the main model's weights, so a draft file would load the trunk twice.
|
||||
`SPEC_TYPE=none` is the rollback and strips the whole set.
|
||||
|
||||
**Harness contract** (`~/.omp/agent/models.yml`, provider `llamacpp (dankclaw-3090)`, id `frank-zealot`):
|
||||
`contextWindow: 262144` — **the per-slot window**, because llama.cpp divides `--ctx-size` across slots and answers a
|
||||
`contextWindow: 200704` — **the per-slot window**, because llama.cpp divides `--ctx-size` across slots and answers a
|
||||
longer prompt with a 400, never a truncation — plus `maxTokens: 16384`, `input: [text, image]`,
|
||||
`compat.reasoningContentField: reasoning_content`, `compat.reasoningEffortMap` (**load-bearing**: the template takes
|
||||
only `xhigh|medium|low` and raises on anything else), `extraBody` 1.0 / 0.95 / 20 / `min_p 0`, and the
|
||||
`compactionModel` of [[frank-archon]], [[frank-dragoon]] **and** frank-zealot all pointing here.
|
||||
|
||||
**The exact-KV boot, measured** (2026-10-18 evening, under the unit, verbosity 4). KV **16,384.00 MiB** =
|
||||
32,768 B/token × 524,288 positions, to the MiB — rate derivation and ledger agree with no slop, which is what
|
||||
buries the doc's old "f16 needs 26,112 MiB and cannot boot" figure for good: that was the 27B's 34,816 B/token
|
||||
applied to the 9B. RS **100.50 MiB** (2 cells, one 50.25 MiB cell lighter than the q8_0 era), compute
|
||||
1,408.06 + 1,088.07 CUDA_Host, mmproj worst case **868.32 MiB** charged *after* the engine's own fit check,
|
||||
settled `nvidia-smi` **21,176 used / 2,949 free** of 24,123, `n_slots = 2`, `n_ctx_slot = 262144`,
|
||||
`DropInPaths` empty, no clamp line, no `cudaMalloc` error — within 24 MiB of the pre-flight projection. That is
|
||||
a desktop margin: the ~2 GiB of slack is what an image storm has to live inside.
|
||||
**No rates, by law.** Frank ordered the reslice, not a number: the evidence a swap needs is
|
||||
the seam's fit gate, the deployer's health gate, and the boot ledger below. The previous era's
|
||||
exact-KV boot and its rate floors live in that era's row; those floors were measured while
|
||||
another consumer drove the other slot (56 prompts in three minutes, 96 % GPU-util), the exact
|
||||
condition a bench window exists to exclude.
|
||||
|
||||
**Rates on this shape are FLOORS, not the shape's rates** — measured the same evening greedy with `ignore_eos`
|
||||
at 512 tokens, first request discarded, while another consumer drove the other slot (56 new prompts in three
|
||||
minutes, 96 % GPU-util): the exact condition the bench window exists to exclude, and one contended deep arm
|
||||
even came back truncated (a 512 KB body accepted as `prompt_n = 4`).
|
||||
**The 2026-10-19 boot, measured** — verbosity-4 ledger of `ba69ea5`, started 03:21:56 PDT.
|
||||
Recorded here because projections drift, and nowhere else:
|
||||
|
||||
| Arm | q8_0 3-slot record | f16 2-slot, contended |
|
||||
|---|---|---|
|
||||
| single-stream decode | 168.7 | **134.1 – 141.5** (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** |
|
||||
| Term | Ledger |
|
||||
|---|---|
|
||||
| Target KV over the 602,112 pool | **14,406.00 MiB** — K (f16) 9,408.00 + V (q8_0) 4,998.00, `200704 cells, 8 layers, 3/3 seqs` |
|
||||
| Draft KV (`blk.32`, its own f16 context) | **2,352.00 MiB** — 1,176.00 + 1,176.00, `200704 cells, 1 layers, 3/3 seqs`: the whole pool, again |
|
||||
| Recurrent state | **452.25 MiB** — `3 cells, 32 layers, 3 seqs, 2 rs_seq`, R (f32) 20.25 + S (f32) 432.00 |
|
||||
| Compute buffers | target 1,665.00 CUDA0 + 424.88 CUDA_Host; draft context 480.97 + 424.86 |
|
||||
| Weights | 2,283.29 CUDA0 + 257.66 CPU-mapped |
|
||||
| Projector | 595.29 model + 248.10 compute + 24.93 CPU (its 868.32 worst case is still charged AFTER the engine's fit check) |
|
||||
| Engine's own verdict | projected 21,639 vs 23,404 free, then **"cannot meet free memory target of 1,867 MiB, need to reduce device memory by 102 MiB"** |
|
||||
| Settled | **22,870 used / 1,255 free**, `n_slots = 3`, `n_ctx_slot = 200704`, `kv_unified = false` |
|
||||
|
||||
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 while
|
||||
contended, and single-stream decode is between parity and −20 %. The 1.882× KV byte bill is real; its true size
|
||||
on this card is still unmeasured, so quoting 134 or 163 t/s as "the" number is a guess in either direction —
|
||||
re-run the same arms with the box quiet before a figure is carried anywhere else.
|
||||
This is the first boot on this card that does **not** meet llama.cpp's own free-memory target,
|
||||
and 1,255 MiB settled sits under the ~2 GiB band every previous era held. It boots, `/health`
|
||||
answers, and the seam's gate still passes — it priced 22,428 MiB, conservative by 442 against
|
||||
this ledger. The cheapest rungs back into the band are `CTX_SIZE=401408` (two conversations) or
|
||||
`SPEC_TYPE=none`; both are one line and neither changes the payload.
|
||||
|
||||
**The drafter is engaged, measured on the same boot** (three ordinary requests, not a bench):
|
||||
acceptance 0.706 and 0.763 accepted-per-generated, mean accepted length 2.41 / 2.53,
|
||||
per-position rates (0.882, 0.765) and (0.871, 0.699) — depth 2 earning its keep at 0.3–1.5 MiB
|
||||
of draft state per saved prompt. The three probes that prove a swap: chat → `"zealot alive"`
|
||||
with 448 completion tokens (at `max_tokens: 64` the same request returns EMPTY content with
|
||||
`finish_reason: length` — the thinking-budget trap, not a fault); tools →
|
||||
`finish_reason: tool_calls` with `{"a":24,"b":7}`; vision → a 512 px solid-red PNG answers
|
||||
"Red" at `prompt_tokens` 280.
|
||||
|
||||
**Fallback rungs**, cheapest first — one line of `frank-zealot.env` each, and each must keep the seam's fit gate green:
|
||||
|
||||
| Knob | Effect |
|
||||
| Knob | Effect from the shipped shape |
|
||||
|---|---|
|
||||
| `UBATCH_SIZE=1024` | hands back 1,306 MiB of compute buffers; prefill −17 %; it is also the arm that ran the drafter at three windows (213.5 t/s) |
|
||||
| `CACHE_TYPE_V=q8_0` | exact K + quantized V: KV 16,384 → **12,544 MiB**, freeing 3,840 (llama.cpp has no true fp8 KV dtype — q8_0 is the 8-bit class) |
|
||||
| `CTX_SIZE=401408` | two 200,704 conversations, exact KV at 12,544 MiB (same 3,840 freed) — `contextWindow` MUST follow the per-slot number |
|
||||
| `CTX_SIZE=786432 PARALLEL=3 CACHE_TYPE_K/V=q8_0` | the era below this one: 13,056 MiB of cache, the third conversation back |
|
||||
| `CACHE_TYPE_K/V=q4_0` | 10,240 B/token ⇒ 5,120 MiB (the 27B era measured q4_0 at 16,392 used / 7,733 free at 2 × 200,704) |
|
||||
| `SPEC_TYPE=none` | the drafter off: hands back its 2,352.00 MiB of cache, 480.97 MiB of CUDA0 buffers and the 301.50 MiB of rollback planes it adds to the recurrent state (~3.1 GiB), at the cost of the ~+28 % decode the 2026-10-18 census measured whenever the card has room |
|
||||
| `UBATCH_SIZE=2048` | +4.6 % prefill and only ~406 MiB of compute buffers at THIS shape (the ~1,306 MiB the 786,432-pool census showed does not transfer) — and the rung that HALVED decode beside an armed drafter (167.2 → 109.2 t/s) |
|
||||
| `CTX_SIZE=401408` | two 200,704 conversations: frees 4,802 MiB of cache — `contextWindow` MUST follow the per-slot number |
|
||||
| `CACHE_TYPE_V=f16` | exact V: +4,410 MiB at this pool with the drafter armed, which is a `cudaMalloc` death the seam refuses — legal only once the drafter or a slot is gone |
|
||||
| `CACHE_TYPE_K=q8_0` | the fully quantized pair (17,408 B/token): frees 4,410 MiB and moves the quantization error into the keys |
|
||||
|
||||
## Eras
|
||||
|
||||
| Window | Payload | Runtime | Shape | Replaced because | Rollback |
|
||||
|---|---|---|---|---|---|
|
||||
| 2026-10-18 21:06 → 2026-10-19 | the same 9B MTP file and projector | PrismML fork 10777 | 2 × 262,144 (pool 524,288), exact f16 K+V = 16,384.00 MiB, `SPEC_TYPE=none`, batch 4096 / ubatch 2048; boot ledger KV 16,384.00 / RS 100.50 / compute 1,408.06 + 1,088.07 CUDA_Host / mmproj worst case 868.32 charged after the fit check, settled **21,176 used / 2,949 free**, `n_ctx_slot = 262144`, within 24 MiB of the projection; floors 134.1–141.5 t/s single-stream, 256.6–265.1 aggregate over two, 3,159.5 t/s prefill at 106,282, 71.5 t/s decode at 106k depth (contended — floors, not the shape's rates) | Frank ordered the third conversation back AND the drafter armed, and exact V funds neither: it bills 18,816 MiB at the pool that pays for the third slot, leaving nothing for the draft's 2,352 | one line each — `CTX_SIZE=524288 PARALLEL=2 CACHE_TYPE_V=f16 BATCH_SIZE=4096 UBATCH_SIZE=2048 SPEC_TYPE=none` — and redeploy; no payload move |
|
||||
| 2026-10-18 13:19 → same evening (the boot `68afc6d` replaced) | the same 9B MTP file and projector | PrismML fork 10777 | 3 × 262,144 (pool 786,432), q8_0 K+V = 13,056 MiB, settled 17,956 / 6,169 MiB; 168.7 t/s decode (three runs 168.4–168.7), ~300 t/s over three streams (102.8 each), 2,877 t/s prefill at 106,418 | Frank ordered unquantized KV, and that pool cannot survive it: 24,576 MiB of f16 cache > 24,123 addressable | one env line — `CTX_SIZE=786432 PARALLEL=3 CACHE_TYPE_K/V=q8_0` — and redeploy; no payload move |
|
||||
| 2026-10-18 03:14 → 13:19 (arm under the unit, then the leftover drop-in) | `Bonsai2-PTQ1_0/Ternary-Bonsai-2-27B-PTQ1_0.gguf` 5,946,648,928 B + hardlinked Q8 mmproj | PrismML fork 10777 | arm: 2 × 200,704, batch/ubatch 3072/1536. The leftover `frank-zealot.service.d/ptq1.conf` then made the 13:16 cutover deploy boot THIS payload at 3 × 133,888 (`new slot, n_ctx = 133888` x3, journal read 2026-10-19) until the 13:19 boot | Frank scratched the 27B plan — ~48 t/s decode vs 76.6 for PQ2_0 on the same weights | `/home/dank/models/Bonsai2-PTQ1_0/` + `scripts/download-frank-zealot-bonsai2-ptq1.sh` |
|
||||
| 2026-10-17 → 10-18 | Ternary-Bonsai-2-**27B** PQ2_0 7,206,168,928 B `3907dc16…` + Q8_0 mmproj 629,246,976 B | PrismML fork **10754** (`~/GitPackages/llama-prism/`) | 2 × 200,704 (pool 401,408), q8_0 K+V = 13,328 MiB, settled 22,664 / 1,461; 76.6 t/s decode, 109.4 aggregate over two streams, 691.7 t/s prefill at 166,027; MTP impossible — no head in the file | the 9B bills half the KV per token (8 full layers, not 16), which funded three slots at the native window and beat it on every rate | `/home/dank/models/Bonsai2-PQ2_0/` + the 10754 fork tree, `scripts/download-frank-zealot-bonsai2.sh`, revert the swap commit |
|
||||
@@ -139,6 +155,10 @@ re-run the same arms with the box quiet before a figure is carried anywhere else
|
||||
`tests/frank_zealot_unit.bats`'s fit gate can see a boot that would die at `cudaMalloc`, so every retune keeps it
|
||||
green — and the first verbosity-4 ledger plus settled `nvidia-smi` of a new dtype belongs on this page, because
|
||||
projections drift.
|
||||
The same trap one knob down: the ubatch 2048 → 1024 step handed back **~1,306 MiB** at the 786,432 / q8_0-pair
|
||||
arm and **406 MiB** at the 602,112 / f16-K + q8_0-V boot (target compute 2,496.13 → 2,089.88, with CUDA0 going
|
||||
UP 257 while CUDA_Host went down 663). Changing the dtype or the pool moves the compute buffers too, so a
|
||||
credit measured at one shape is not a budget line at the next.
|
||||
The two boots that priced non-KV: 2 × 262,144 **without** the projector — 20,190 used, KV 12,800 ⇒ non-KV
|
||||
**7,390 MiB**; 3 × 196,608 **with** it — 22,972 used, KV 14,113 ⇒ non-KV **8,859 MiB**. The difference prices the
|
||||
vision tower at **~1.4 GiB for a 918 MB file**, because the encoder's activation and compute buffers ride with it.
|
||||
@@ -163,7 +183,7 @@ re-run the same arms with the box quiet before a figure is carried anywhere else
|
||||
|
||||
## Contract
|
||||
|
||||
- `/health` → `{"status":"ok"}`; `/props` reports `n_ctx` and `presence_penalty`; `/slots` shows occupancy; a third
|
||||
- `/health` → `{"status":"ok"}`; `/props` reports `n_ctx` and `presence_penalty`; `/slots` shows occupancy; a fourth
|
||||
concurrent conversation **waits** for a slot — the slot count is the concurrency contract.
|
||||
- `/metrics` answers 200 with 15 `llamacpp:*` families **only with `--metrics`**; without it llama.cpp answers **501**, which Prometheus reads as DOWN while `/health` stays 200. The scrape target is `host.containers.internal:7771/metrics` and the oldest retained sample of `up{job="frank-zealot"}` (2026-10-11) is already 0 — this target was never successfully scraped, across every payload era. `METRICS=0` is the documented opt-out and re-breaks it; the counters cost nothing on the request path.
|
||||
- Reasoning arrives in **`reasoning_content`** (`--reasoning-format deepseek`) and **counts against `max_tokens`**: a short budget yields empty `content` with `finish_reason: "length"`, which reads as a broken model, not a mid-thought one. The slot prefix cache is live (`cached_tokens` in `usage`).
|
||||
@@ -171,9 +191,9 @@ re-run the same arms with the box quiet before a figure is carried anywhere else
|
||||
(1.0 / 0.95 / 20). `presence_penalty` is the one recipe value with no GGUF metadata key, so it must reach argv;
|
||||
the serve script uses `${PRESENCE_PENALTY=1.0}`, deliberately **not** `:=`, because `""` is the documented opt-out.
|
||||
- Vision: only the OpenAI **`image_url`** content part reaches the model — the legacy top-level `images` field bumps `usage.prompt_tokens` while the model invents an answer. Probe with a **≥ 256 px** solid color (a postage stamp answers "video") and budget **≥ 512** tokens; a 512 px image bills ~280 prompt tokens.
|
||||
- **Q8_0 mmproj, not BF16**: the publisher warns the BF16 tower "can crash llama-server when several image requests run at once", which is exactly the `--parallel 2` shape. `MMPROJ_FILE=` (explicitly empty) is the text-only opt-out and returns ~1.4 GiB; a path at a missing file is a boot **refusal**, never silent blindness.
|
||||
- **Q8_0 mmproj, not BF16**: the publisher warns the BF16 tower "can crash llama-server when several image requests run at once", which is exactly the `--parallel 3` shape. `MMPROJ_FILE=` (explicitly empty) is the text-only opt-out and returns ~1.4 GiB; a path at a missing file is a boot **refusal**, never silent blindness.
|
||||
- Tools: native via `--jinja`; `finish_reason: tool_calls` with valid JSON arguments.
|
||||
- Two non-faults: a `400` naming 262,144 is one conversation outgrowing its **slot**, not pool exhaustion; and
|
||||
- Two non-faults: a `400` naming 200,704 is one conversation outgrowing its **slot**, not pool exhaustion; and
|
||||
the raw `/completion` path 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 build 10777
|
||||
guards the raw path the moment reasoning markup leaks (older builds only logged `failed to parse reasoning
|
||||
|
||||
@@ -169,7 +169,6 @@ is a locator: one row, one clause, no numbers.
|
||||
|---|---|
|
||||
| [[Ethernet Bandwidth Math]] | Bytes-per-token math for pipeline versus tensor parallelism, and the link budgets that follow from it. |
|
||||
| [[Expected Performance]] | Estimated split-model throughput over ethernet, with the benchmark anchors the estimates rest on. |
|
||||
| [[Multi-Node Runtimes]] | How each two-machine runtime is built and launched, what it can combine with, and the mixed-vendor verdicts. |
|
||||
| [[Qwen3.8 PP Deployment]] | The real split deployment: which node is head and which is worker, the flags, and the verified gotchas. |
|
||||
| [[Sample Script Set]] | An educational two-node RPC split script set, for reading only — never run it or deploy it. |
|
||||
|
||||
|
||||
+27
-113
@@ -43,9 +43,32 @@
|
||||
holds `StillDeadcode`, `rad`, `tpl.jinja` — so the MXFP4 checkpoint that two
|
||||
pages called the "on-disk rollback" is a re-fetch, now stated once, on the era
|
||||
page that owns that payload.
|
||||
- **Fleet state confirmed live, not from files**: frank-archon 3 × 200,000 fp8 KV,
|
||||
frank-dragoon 2 slots × 200,704, frank-zealot 2 × 262,144; all three answer
|
||||
`/health` 200 and every env value matches its page.
|
||||
- **Fleet state must be probed, and probed again at the end.** The first pass read
|
||||
frank-archon 3 × 200,000 fp8 KV, frank-dragoon 2 × 200,704, frank-zealot
|
||||
2 × 262,144 and every env value matched its page. `origin/main` then moved under
|
||||
the campaign — PR #25/#26 resliced frank-zealot — and the live re-probe on the
|
||||
same day says **`--ctx-size 602112 --parallel 3` ⇒ 3 × 200,704, f16 K + q8_0 V,
|
||||
`--spec-type draft-mtp`, `--jinja --reasoning`, `--metrics`**, matching
|
||||
[[frank-zealot]] exactly. **Law: a documentation pass that reports live shapes is
|
||||
stale the moment main moves; re-probe `/slots` and the running cmdline before
|
||||
writing the number, not after.**
|
||||
|
||||
- **A census that includes the file under test proves nothing.** `scripts/port-census.py`
|
||||
takes the new directory as its corpus, so every era page checked against
|
||||
`serverAppConfigs-docs` was trivially held by *itself*: ten pages reported
|
||||
`0 unheld tokens`, including `kingjones-q8agent-rocmfpx` at 481 lines whose own load
|
||||
ledger cannot live anywhere else. Real numbers after the corpus excluded the page under
|
||||
test: **14–63 sole-record lines per page — no era page in this vault is padding**, each
|
||||
the only copy of a campaign ledger, which is what *Documentation Policy* protects.
|
||||
**Law: a tool's own docstring promise ("excluding it") is not a check; assert the file
|
||||
is absent from the corpus before believing a zero.** Same shape as *Key Lessons* → "A
|
||||
scout verdict is a lead, never a finding": a mechanical tool is a scout too.
|
||||
- So "compact the docs" reduces to deleting **restatement**, which is what landed:
|
||||
`Multi-Node Runtimes.md` (−257, it restated launch material owned by
|
||||
[[Qwen3.8 PP Deployment]]), 78 lines of transient deploy chatter out of this log (dropped
|
||||
only where every fact token in the line is held by an owner page, preserved under
|
||||
`.AGENTTRASH/20261019-log-transient/`), and 148 lines out of `AGENTS.md`. Docs flat,
|
||||
operator doc down; the growth is the two enforcement tools.
|
||||
|
||||
## 2026-10-18
|
||||
- **frank-zealot resliced to two windows of exact KV (Frank's call, PR #21).**
|
||||
@@ -73,7 +96,6 @@
|
||||
|
||||
**issue #3 research: AMD PerfOpt retires the reason for `amd_iommu=off`**
|
||||
|
||||
|
||||
- Ask: does a recent kernel change remove the IOMMU translation penalty
|
||||
that justifies the 2026-09-11 `amd_iommu=off` boot line, i.e. can the
|
||||
XDNA2 NPU come back? Answered and documented; **no host change** at the
|
||||
@@ -107,11 +129,9 @@
|
||||
`frank-npu.md` § *Why it is retired* corrected — the "`pt` is not a
|
||||
middle ground" law is dead — the AGENTS.md key-lesson bullet re-keyed, and
|
||||
a 4-step maintenance-window procedure that measures locally instead of
|
||||
inheriting the vendor number. Nothing deployed — no service was touched.
|
||||
|
||||
**boot line flipped: Dankclaw now boots `iommu=pt`**
|
||||
|
||||
|
||||
- Window executed 01:24–01:35 on Frank's go-ahead, with the A/B the
|
||||
research demanded. Provenance first: `/etc/default/grub` had never been in
|
||||
the repo while the IOMMU line was the most consequential kernel decision
|
||||
@@ -119,7 +139,6 @@
|
||||
byte-exact) and moves only through `scripts/set-dankclaw-bootline.sh` —
|
||||
diff, install 0644 root:root, `update-grub`, **verify the default
|
||||
`grub.cfg` entry**, reboot on `--reboot` only. The deployer plans nothing
|
||||
under `dankclaw/host/`: kernel state is not a container restart.
|
||||
- **Verdict: identity DMA costs nothing on this host.** Byte-identical
|
||||
traffic, +2.0 % cold prefill going OFF→PT; the 13–16 % that justified
|
||||
`amd_iommu=off` for five weeks never existed here. Decode's apparent
|
||||
@@ -127,7 +146,6 @@
|
||||
same boot spread 31.8–47.8 t/s.
|
||||
- **The XDNA2 NPU is back:** `amdxdna` bound `0000:c6:00.1`,
|
||||
`/dev/accel/accel0` `crw-rw---- root:render 261,0` with `dank` in
|
||||
`render`. No endpoint deployed — revival needs a quadlet with
|
||||
`AddDevice=/dev/accel/accel0` + `GroupAdd=keep-groups`.
|
||||
- Deliberately NOT done: the `pp_dpm_fclk` level-7 pin (it changes iGPU
|
||||
timing for every workload and nothing co-runs on the NPU yet) and any
|
||||
@@ -136,7 +154,6 @@
|
||||
- Gates after boot: 0 failed units, 32 IOMMU groups (31 identity + 1 DMA =
|
||||
the Genesys Logic SD reader, a kernel quirk), interrupt remapping on;
|
||||
frank-dragoon's ledger byte-for-byte the same shape and Gitea / Caddy
|
||||
edge / Grafana answered 200. New tool `scripts/stage-frank-dragoon-eval.sh`
|
||||
rebuilds the A/B corpus byte-identically — run it through `bash -lc`, `uv`
|
||||
is in `~/.local/bin`. Docs re-keyed: the decision record
|
||||
`dankclaw-iommu-translation-off.md` (new § *execution*), `frank-npu.md`
|
||||
@@ -165,15 +182,11 @@
|
||||
`SPEC_TYPE=draft-mtp` drop the flag. And `up{job="frank-zealot"}` had read
|
||||
0 for the **entire retained Prometheus history**: llama.cpp answers
|
||||
**501** on `/metrics` unless `--metrics` is passed, so the scrape failed
|
||||
beside a healthy `/health` and `LlmEndpointDown` fired for days.
|
||||
`METRICS=1` is now the default and the seam pins it.
|
||||
- Deployed 13:16 (`deploy.sh dankclaw --service frank-zealot`) — and the
|
||||
first boot was **not the shipped payload**: a leftover
|
||||
`frank-zealot.service.d/ptq1.conf` from the bench window pointed
|
||||
`FRANK_ZEALOT_ENV_FILE` at `/tmp/zealot-arm-current.env`, so the unit
|
||||
booted that arm. `/health` 200, `/v1/models` echoed the alias, Playwright
|
||||
33/33 — **nothing in the gate reads the payload.** Drop-in moved to
|
||||
`~/.AGENTTRASH/`, `daemon-reload`, restart: the shipped trio booted. (The
|
||||
drop-in was not a rogue — see the PTQ1_0 entry below.)
|
||||
- Measurement law: the first battery reported 117 and 72 t/s because it
|
||||
timed natural generations that stopped at 24 tokens — wall-clock aggregate
|
||||
@@ -212,7 +225,6 @@
|
||||
|
||||
**frank-dragoon: the 401,408-position pool goes live; docs re-keyed to the measured boot**
|
||||
|
||||
|
||||
- `./scripts/deploy.sh dankclaw --service frank-dragoon` — e2e 33/33, and
|
||||
the drop-in check the zealot incident bought ran FIRST: `DropInPaths`
|
||||
empty, so the repo env governed. 2026-10-18 — frank-dragoon: the KV pool
|
||||
@@ -226,7 +238,6 @@
|
||||
page cache loses.
|
||||
- Every document quoted **~10.8 GiB**, the ~28 KiB/position arithmetic; the
|
||||
engine reserves **11.0 GiB** and its preflight bills **11.3**. The measured
|
||||
figure wins, and every "committed but not yet deployed" clause was retired
|
||||
with the measurement (AGENTS.md, the env lore block, both index pages, the
|
||||
iommu page, the runbook).
|
||||
- PTQ1_0 provenance corrected (see the dedicated entry): the arm that
|
||||
@@ -237,7 +248,6 @@
|
||||
MiB freed, then scratched by Frank's 03:33 order in favour of the 9B.
|
||||
Nothing is owed on it.
|
||||
- Census of the halogen HTTP surface while doing it, because the fleet bench
|
||||
law quoted the wrong engine: frank-dragoon answers `/health`, `/metrics`,
|
||||
`/v1/models`, `/v1/chat/completions`(POST) and 404s `/ping`, `/props`,
|
||||
`/completion`, `/tokenize`; the chat response carries **no
|
||||
`Server-Timing`** header. So the `/completion` `timings` object and the
|
||||
@@ -245,15 +255,12 @@
|
||||
dragoon's. AGENTS.md's Benchmarking Policy re-keyed to say which engine
|
||||
owns which instrument.
|
||||
|
||||
**frank-tts phase 1: the cloned-voice endpoint, named and tested (not deployed)**
|
||||
|
||||
- Voice-over goal, phase 1 (Gitea issue #2). New service **frank-tts** on
|
||||
Dankclaw: `dankclaw/voiceservice/` (stdlib-only `app.py` + `Containerfile`
|
||||
+ rootless quadlet), `scripts/frank-tts.env`, `tests/frank_tts_unit.bats`.
|
||||
One OpenAI-shaped `POST /v1/audio/speech` surface; `voice=<name>` is
|
||||
honoured only when `/voices/<name>/reference.wav` exists, otherwise a 400
|
||||
listing the voices that are — **no silent fallback voice**; oversized
|
||||
input is a 414, not a half-narrated sentence; `/health` reports backend +
|
||||
voice list; no audio ever reaches the log.
|
||||
- **Naming law applied (Frank's call):** treat it as a first-class model
|
||||
endpoint → alias-named `frank-tts` (TTS endpoint, so not `frank-asr`),
|
||||
@@ -303,7 +310,6 @@
|
||||
only and a bridge container cannot reach a host `127.0.0.1` bind — the
|
||||
lesson that 502'd hermes — so `host.containers.internal:8880`, which
|
||||
`scripts/frank-tts.env` documents, is correct only for a routable bind.
|
||||
The deployed pair shares one quadlet network and the façade addresses the
|
||||
engine by container name.
|
||||
- **PR #12** (`feat/open-notebook-podcast` @ `114dd7a`) is **merged** as
|
||||
`1eb8b21` (Frank's OK): open-notebook + SurrealDB quadlets on the private
|
||||
@@ -313,9 +319,6 @@
|
||||
runnable: nothing listens on 7772 until the voice stack lands, and the
|
||||
episode destination stays interim pending Gitea #7.
|
||||
|
||||
**open-notebook: the podcast generator's config lands (nothing deployed)**
|
||||
|
||||
|
||||
- Goal: **open-notebook** as the fleet's podcast generator on Dankclaw —
|
||||
notes and sources in, a multi-speaker episode out, script written by
|
||||
**frank-zealot**, voices rendered by **frank-tts**, no cloud provider
|
||||
@@ -326,7 +329,6 @@
|
||||
frank-dragoon's pinning style, because `:latest` moving underneath a
|
||||
rootless service is how you get an unbootable pod after an unrelated
|
||||
`systemctl-daemon-reload`. Both health probes are argument-only
|
||||
(`curl -fsS …/health`, `/surreal is-ready`), and `is-ready` refuses to
|
||||
pass without `SURREAL_PASS` in the environment — a credential that must
|
||||
never ride the command line, so `Exec=` carries flags only.
|
||||
- **Host 5055 was already spoken for** — seerr has held `127.0.0.1:5055` —
|
||||
@@ -363,20 +365,16 @@
|
||||
`podman unshare`. One mount line; one deletion undoes it.
|
||||
- **Two UI buttons will fail, and that is cosmetic:** Test Connection does
|
||||
`GET <base>/models` and the TTS probe synthesises voice `alloy`;
|
||||
frank-tts serves `/health` and `/v1/audio/speech` only. Model rows are
|
||||
created *by name* and the voice field is free text, so `narrator1/2/3` is
|
||||
typed in and never discovered. Documented, not worked around.
|
||||
- Three deployer seams cost time and are now law-shaped: a service must be
|
||||
registered in **five** places — `SERVICE_ORDER`, `DANKCLAW_SERVICES`, the
|
||||
`deploy-remote.sh` dispatch case, its pre-deploy requirement, and
|
||||
`deploy.sh`'s dispatch regex, where an omission silently plans an empty
|
||||
set for `--only`; a `dankclaw/<svc>/*` case must precede the
|
||||
`dankclaw/*.container` glob because that `*` crosses `/`; and the
|
||||
changed-path routing lives *inside* the per-path loop, after the
|
||||
`scripts/*.env)` case, not at the top of the file.
|
||||
|
||||
**frank-tts-engine: the arm A engine becomes deployable (nothing deployed)**
|
||||
|
||||
- Goal: turn the engine the day's A/B picked into a **repo-owned,
|
||||
boot-persistent, self-healing container** — `frank-tts-engine`,
|
||||
`qwentts.cpp` commit `5916bf7c79dbb4e66fc059cdc507a79a23a28bb4`, resident
|
||||
@@ -389,7 +387,6 @@
|
||||
containers therefore sit on one committed quadlet network,
|
||||
`frank-tts-engine.network` at **10.89.5.0/24** (the next free /24 after
|
||||
open-notebook's 10.89.4.0/24), the façade addresses the engine by
|
||||
container name, and diagnostics — including the deployer's health gate
|
||||
(`wait_container_healthy`, there is no URL to dial) — run through
|
||||
`podman exec`.
|
||||
- **The boot warmer is a unit hook, not an operator habit.** The registry is
|
||||
@@ -397,7 +394,6 @@
|
||||
reference, so `ExecStartPost=-/usr/bin/podman exec frank-tts-engine
|
||||
/app/warm-voices.sh` with `TimeoutStartSec=900`; the `-` prefix is what
|
||||
stops a failed warm from crash-looping the endpoint under
|
||||
`Restart=always`, and it logs `cold=` and `warm=` per voice on every boot.
|
||||
- **Thread law recorded before anyone tunes it away:** no `--threads`
|
||||
exists; `src/backend.h` uses `hardware_concurrency()/2`, so a cpuset pin
|
||||
silently changes the thread count. Nothing pins this container.
|
||||
@@ -411,7 +407,6 @@
|
||||
|
||||
**frank-tts: the 7772 endpoint gets its deploy wiring, and voice naming is written down**
|
||||
|
||||
|
||||
- Stage 2 of the voice stack: the **façade** in front of the engine is now a
|
||||
deployable service too. Runbook: [[Frank TTS]].
|
||||
- **Two attached networks, in a meaningful order.** `dankclaw_default`
|
||||
@@ -440,14 +435,12 @@
|
||||
- **No Prometheus job, on purpose.** The façade exports no `/metrics`,
|
||||
Prometheus 3 refuses HTML/JSON content types (the `comfyui` and `services`
|
||||
probes are documented down for exactly that), and `LlmEndpointDown` is
|
||||
scoped `up{job=~"frank-.*"}` — so a `/health` scrape would page on Signal
|
||||
every few minutes for a healthy endpoint. That is the frank-archon
|
||||
never-green lesson pointed the other way, written in the page instead of
|
||||
quietly ignored.
|
||||
|
||||
**voice-pack builder: the 10-second source-material gate becomes code**
|
||||
|
||||
|
||||
- Third slice of the voice stack: `scripts/build-voice-pack.sh` turns a
|
||||
source recording plus a crop offset into a deployable pack —
|
||||
`reference.wav` (24 kHz mono `pcm_s16le`, 18–25 s), the engine's own
|
||||
@@ -499,18 +492,14 @@
|
||||
skips `podman build` on a matching `cfhash` label, and that hash covered
|
||||
the `Containerfile` alone — so editing `app.py` or `tts-engine-warm.sh`
|
||||
(both `COPY`'d) skipped the build while the deploy reported success and
|
||||
`/health` answered with old code. Each case arm now hashes the files its
|
||||
`Containerfile` copies.
|
||||
- Docs carry the lessons where an operator will hit them: a **Locally built
|
||||
images** section in [[deployment]], two new troubleshooting lines in
|
||||
[[Frank TTS Engine]] (all-voices-cold = quoted `WARM_TEXT`; façade 502
|
||||
beside 200 = model loaded ≠ voices registered), and `Voice Packs.md`
|
||||
step 4 corrected — the warm hook re-registers **every** pack on every run,
|
||||
because the engine replaces a name it already holds, so a restart is what
|
||||
an engine bump needs and not what a rebuilt pack needs.
|
||||
|
||||
**frank-tts-engine deployed: it builds, it serves, and the warm hook died on the kernel's argument cap**
|
||||
|
||||
- Merged as **#14** (`747c665`) and deployed `--service frank-tts-engine`;
|
||||
the image built clean — the first real proof of the `useradd` fix — and the
|
||||
unit was `healthy` after 31 s.
|
||||
@@ -520,7 +509,6 @@
|
||||
a single argument at 131,072 bytes (`MAX_ARG_STRLEN`) and a shipped ~955 KB
|
||||
`reference.wav` base64's past that, so curl never executed and the hook
|
||||
logged that as "HTTP no response". Fourth silent fault of the voice stack,
|
||||
same species as the other three: `/health` stayed green throughout. Fix:
|
||||
the body goes to a `mktemp` file and curl gets `@file`; the regression test
|
||||
is the honest shape — real listener, real `curl`, a 300 KB fixture pack —
|
||||
so it fails with the same kernel error the host produced. Every shipped
|
||||
@@ -546,7 +534,6 @@
|
||||
- Merged **#15** (`0315a62`, the warmer's argument-cap fix) and **#16**
|
||||
(`d2ce669`, the comment sweep), then deployed both voice services in order
|
||||
(`--service frank-tts-engine`, then `--service frank-tts`); Playwright gate
|
||||
33/33 twice. Deployed-state proof from a LAN client: `/health` →
|
||||
`omni-http` with all three voices, and the same sentence in `narrator1` vs
|
||||
`narrator2` came back with different sha256 and different RMS — the name
|
||||
actually selects a voice.
|
||||
@@ -558,7 +545,6 @@
|
||||
errors instead of degrading; and `models.yml` loads at process start while
|
||||
`config.yml` is read live, so a session older than the `frank-tts` row
|
||||
fails exactly like a dead endpoint.
|
||||
- Corrected the same evening, by breaking it in a restarted session: a
|
||||
harness call that names no voice reaches 7772 with **no `voice` key at
|
||||
all** — `400 unknown voice None; available voices: …` — because no setting
|
||||
feeds one. The endpoint therefore grew `DEFAULT_VOICE` (its own PR): a
|
||||
@@ -609,7 +595,6 @@
|
||||
- Also in the window: **PR #22 merged** (`0c7fe8b`, `dank/command-center`) so
|
||||
`agent/extensions/dankvoice.ts` landed on the auto-load path — proven in a
|
||||
plain `omp` session with no flags: `/say merged build check` → WAV to the
|
||||
player, temp dir cleaned. Sessions open before the merge need a restart to
|
||||
pick the command up.
|
||||
|
||||
**frank-zealot: 2 × 262,144 over unquantized f16 KV**
|
||||
@@ -632,7 +617,6 @@
|
||||
## 2026-10-17
|
||||
**frank-zealot: Bonsai-2-27B PQ2_0 trial + cutover (2 × 200,704, q8_0 KV)**
|
||||
|
||||
|
||||
- 2026-10-17 — frank-zealot: **Ternary-Bonsai-2-27B (PQ2_0) cutover**, two
|
||||
slots at 200,704 over q8_0 K + q8_0 V on the RTX 3090, fetched by
|
||||
`scripts/download-frank-zealot-bonsai2.sh` (exact bytes + sha256,
|
||||
@@ -666,12 +650,10 @@
|
||||
`serve-frank-zealot.sh` still DEFAULTED to the retired payload's paths
|
||||
(a defaulted run would have served the old model — now re-keyed to the
|
||||
shipped trio), and the OpenWebUI `model` row's `meta.description` is live
|
||||
DB state (re-keyed with a DB backup; no `open-webui` restart forced —
|
||||
forcing it logs every family session out for one cosmetic string).
|
||||
|
||||
**dankserver disk purge: Swift w4dn8 rollback + orphan caches (docs re-key)**
|
||||
|
||||
|
||||
- Ask: "dump the swift rollback and the GLM GGUF — make sure the model
|
||||
caches and agenttrash are empty as well." Preflight first: `lsof` empty
|
||||
on both payloads, the serving trio names only the live path, and a podman
|
||||
@@ -683,7 +665,6 @@
|
||||
rollback is now a **re-download, not a local file**. Corpora (grams,
|
||||
kld-ref, calibration JSONL) deliberately KEPT: they are the re-convert
|
||||
and KLD-gate inputs.
|
||||
- Post-checks: frank-archon active and `/health` ok, serving payload
|
||||
byte-size intact, the live kvcache tier untouched, no process references
|
||||
a deleted path.
|
||||
- Docs re-keyed: AGENTS.md fleet row + frank-archon section + both runbook
|
||||
@@ -695,7 +676,6 @@
|
||||
|
||||
**frank-dragoon cutover: native v2 abliterated payload, card's memory-saving pool**
|
||||
|
||||
|
||||
- 2026-10-17 — frank-dragoon: cutover to the native v2 abliterated halogen
|
||||
payload with the card's memory-saving KV pool; census first: the n-gram
|
||||
table was ALREADY disk-backed and the only real lever was the KV pool.
|
||||
@@ -727,7 +707,6 @@
|
||||
|
||||
**frank-dragoon: ceiling and pool cut to 200,704; crawl reset fix**
|
||||
|
||||
|
||||
- 2026-10-17 — frank-dragoon: context ceiling and KV pool cut to 200,704
|
||||
(49 × 4096, the per-slot rung frank-zealot runs; nothing in the fleet
|
||||
needed the bigger window). See [[frank-dragoon]].
|
||||
@@ -741,7 +720,6 @@
|
||||
- Probes on the live endpoint: over the ceiling → `400 … tokens over` in
|
||||
0.86 s — preflight rejects before any prefill, so probing costs the
|
||||
endpoint nothing.
|
||||
- Deploy verdict: the endpoint dispatched healthy (`/health` 200), but the
|
||||
`[3/3]` Playwright gate failed on `12-crawl` and the deployer correctly
|
||||
said **DEPLOY INCOMPLETE**. Root cause was NOT the endpoint — it was Plex
|
||||
Web, signed out in the `qa-e2e` profile, running its own sign-in flow,
|
||||
@@ -760,7 +738,6 @@
|
||||
## 2026-10-15
|
||||
**frank-archon: publisher iq4r payload swap (MODEL_FILE-only)**
|
||||
|
||||
|
||||
- 2026-10-15 — frank-archon: publisher iq4r payload swap, `MODEL_FILE`-only
|
||||
— `StillDeadcode/qwen3.8-next-flash-fp8-iq4r-moe`, the engine author's
|
||||
published rad-convert of the OFFICIAL Qwen3.8-Flash-Next, **no other
|
||||
@@ -773,19 +750,15 @@
|
||||
- Fetch: new `scripts/download-frank-archon-iq4r.sh` (hfget, exact bytes +
|
||||
Hub LFS sha256, `--verify-only` default) as a transient unit. The cutover
|
||||
**self-severed by design** — a detached `deploy.sh` survived the session
|
||||
death when frank-archon restarted; engine healthy in ~100 s, and the
|
||||
prefix tier RECOVERED across the swap (arch+tokenizer unchanged).
|
||||
- e2e gate went red on three dankclaw specs — NOT the swap: dankclaw
|
||||
rebooted 01:41 and the gluetun boot-race dependency-failed `searxng`,
|
||||
`marreta`, and `marreta-browser` (jobs consumed while gluetun's start job
|
||||
retried; `Restart=always` never armed because the services never ran).
|
||||
Cascade restarted; skill codified
|
||||
(`gluetun-dependent-boot-race-recovery`). frank-archon's own health gate
|
||||
passed every deploy.
|
||||
|
||||
**authentik: two more human accounts (Dankclaw)**
|
||||
|
||||
|
||||
- **Live — mitch (pk 22) and eric (pk 23) provisioned via `ak shell` ORM,
|
||||
no repo objects**, each `<user>@franksdank.cloud` in the human-user
|
||||
template shape (password via `set_password` on stdin only — never argv,
|
||||
@@ -799,8 +772,6 @@
|
||||
power failure on dankserver reset `/sys/module/ttm/parameters/pages_limit`
|
||||
from the manual 200 GiB raise back to its default (half of RAM = 125,988 MiB),
|
||||
below the ~147 GiB pinned-RAM ledger the engine needs; the serve script's
|
||||
preflight named the refusal and exited 1, and `Restart=always` re-ran it
|
||||
**6,919 times in ~20 h** — `activating (auto-restart)`, exit 1 in 50 ms, no
|
||||
container, no engine log. Law adopted: **a sysfs raise is a countdown to the
|
||||
next reboot**, so the gate raises the knob itself. New
|
||||
`scripts/ttm-preflight.sh` is sourced by the serve script (verify, raise,
|
||||
@@ -873,7 +844,6 @@
|
||||
(4,379 t/s), so the cliff is real and sits at ~26 GiB. The 2-slot arm that
|
||||
would clear it (pool 3,072 + slab 27,897) remains untested — and the
|
||||
paragraph above is amended: that arm's payoff is +22 % prefill at 2 slots,
|
||||
not the +8.9 % the deployed pair ever showed (24,000 → 25,600 = 0 %).
|
||||
- **Frank's decode challenge, answered with numbers**: 512 tokens, greedy,
|
||||
cold — **decode 99.0 t/s single-stream at 43.5k and 93.3 at 197k** (decode
|
||||
is nearly window-free: −6 % for 4.5× depth), while **3 concurrent streams
|
||||
@@ -887,10 +857,7 @@
|
||||
tokens, per-stream + aggregate) so no future shape change can be priced on
|
||||
prefill alone.
|
||||
|
||||
## 2026-10-11
|
||||
**frank-archon: unquantized (bf16) KV at 2 × 200,000**
|
||||
|
||||
|
||||
## 2026-10-11 — frank-archon: unquantized (bf16) KV at 2 × 200,000
|
||||
- 2026-10-11 — frank-archon: **unquantized (bf16) KV at 2 × 200,000** on
|
||||
exact attention state, with pool, slab and host pool rebalanced around
|
||||
the bigger cache. See [[frank-archon]].
|
||||
@@ -935,7 +902,6 @@
|
||||
`frank-archon` active/enabled (container `frank-archon`, :7770),
|
||||
`frank-dragoon` active/enabled (container `frank-dragoon-halogen`,
|
||||
:7770), `frank-zealot` active/enabled (journal shows 87–93 t/s serving
|
||||
compaction on :7771). `/health` 200 ×3; `/v1/models` reports the three
|
||||
ids; host checkouts grep zero residue; e2e passed 32+1flaky, 32+1flaky,
|
||||
33; Prometheus `frank-archon`/`frank-dragoon` up.
|
||||
- **Pre-existing gap found by the verification, NOT a rename regression:**
|
||||
@@ -944,7 +910,6 @@
|
||||
it, so `up{job="frank-secondary"}` had NO series at all in the last 24 h
|
||||
(the zealot metrics + the queue-depth alerts on it have been dead since
|
||||
the Qwen3.5 swap). Fix = one flag in `serve-frank-zealot.sh` + a ~1 min
|
||||
zealot restart; not applied in the rename window.
|
||||
|
||||
## 2026-10-09
|
||||
- **Live — five authentik human accounts provisioned (Dankclaw, `ak shell`
|
||||
@@ -980,7 +945,6 @@
|
||||
`webui.db.bak-20261009`): `model` row (`frank-zealot` → `frank-zealot`,
|
||||
params + `meta.description` preserved), the `access_grant` row
|
||||
(`model`/`frank-zealot`/`user`/`*`/`read`), and `openai.api_configs`
|
||||
`model_ids` on all three backends; `systemctl --user restart
|
||||
open-webui.service`. Family-account proof, in a real browser as `qa-e2e`:
|
||||
the welcome screen selects `frank-zealot` and
|
||||
`POST /api/v1/chat/completions {"model":"frank-zealot"}` answers
|
||||
@@ -991,7 +955,6 @@
|
||||
- **Live — Hermes re-keyed** (`config.yaml` `frank-dragoon` → `frank-dragoon`
|
||||
in the provider block, `model.default` and the three `dankclaw-large`
|
||||
auxiliary routes; backup `config.yaml.bak-20261009`; `hermes.service` +
|
||||
`hermes-webui.service` restarted, gateway log clean, `/health` ok) and the
|
||||
repo baseline `dankclaw/hermes/config.yaml.example` synced to the live
|
||||
shape (context 262144). **Flagged, not fixed:** Hermes still routes its
|
||||
`dankserver-small` auxiliary tasks at the id `frank-medium` on
|
||||
@@ -1000,13 +963,10 @@
|
||||
they need a routing decision (archon capacity), not a rename.
|
||||
- **Deployer blind spot found by accident:** every
|
||||
`deploy.sh dankserver --service frank-archon` since 2026-10-08 04:09
|
||||
restarted the **staging worktree**, not the deploy checkout — a campaign
|
||||
drop-in `frank-archon.service.d/bench.conf` overrode `Environment=` and
|
||||
`ExecStart`. The health gate went green each time. Verified parity before
|
||||
removing it (serve script + unit byte-identical, env identical except the
|
||||
alias, staging HEAD an ancestor of `main`), moved the drop-in to
|
||||
`/home/dank/.AGENTTRASH/20261009-frank-archon-bench-dropin/`, and
|
||||
restarted: `/v1/models` then answered `frank-archon`. Post-deploy
|
||||
behavioural proof is now part of the runbook.
|
||||
- Repo: seams re-pinned (`frank_zealot`/`frank_dragoon`/`frank_archon`
|
||||
unit bats), fleet harnesses retargeted (`bench-probe`, `deep-probe`,
|
||||
@@ -1126,13 +1086,11 @@
|
||||
(refusals gone), vision back (the v2 tower loads beside the checkpoint),
|
||||
MTP head inside the checkpoint, 2×200,192 slots, `HALOGEN_SPEC_ADAPT=0`.
|
||||
Runbook: `serverAppConfigs-docs/LLM/frank-dragoon-flash-server-v2-swift15.md`.
|
||||
**Incident (03:09:39–03:11:42):** the deploy started the unit while host
|
||||
`MemAvailable` was ~74.7 GiB, 3.34 GiB short of the engine's pin-gate
|
||||
requirement (62.11 GiB checkpoint + 16 GiB floor = 78.11 GiB); it
|
||||
crash-looped 8 boots, each dying in ~5 s at the pin gate, and
|
||||
self-resolved in ~2 min when the dying 27B engine freed RAM (boot #9
|
||||
pinned clean at 88.4 GiB available). **Latent risk + fix:** the host was
|
||||
left with only ~1.8 GiB `MemAvailable`, so a restart would crash-loop
|
||||
again; `serve-frank-dragoon.sh` now has a host-RAM preflight that checks
|
||||
`MemAvailable` against the pin gate BEFORE `podman run` and WAITS up to
|
||||
`FRANK_DRAGOON_RAM_WAIT` (600 s) for the RAM to clear, refusing loudly if
|
||||
@@ -1154,7 +1112,6 @@
|
||||
`reasoning_content`; models.yml flipped accordingly). Container renamed
|
||||
to the unit name (Frank's call; `frank-archon-qwen` → legacy list);
|
||||
deployer image gate → `podman image exists` (local builds carry no
|
||||
RepoDigests) and health gate reads the BODY — FreeToken 200s with
|
||||
`{"status":"loading"}` mid-load, which false-greened a deploy at 6 s
|
||||
(vLLM 503'd; the bare curl was honest there). Verified: FLEET-OK
|
||||
completion, reasoning split (17×24 → "408"), native `tool_calls`, vision
|
||||
@@ -1275,11 +1232,9 @@
|
||||
7,653 slots, budget 31.29.
|
||||
- Shape G v1 (0a4ff30) tried util 1.0: **both workers refused init before a
|
||||
single weight loaded** — `ValueError: Free memory on device cuda:N
|
||||
(31.29/31.86 GiB ...)`, NRestarts=2 crash loop, zero payload progress.
|
||||
The CLI field is `Field(gt=0, le=1)`, but the worker demands free >
|
||||
budget, so budget == total is a trap, not a knife-edge. Frank: ".982 is
|
||||
the law, never 1.00" — now with a second proof.
|
||||
- Shape G v2 (c67a2ae, deployed green incl. e2e 33/33): util back to
|
||||
0.982, graph base to 0 (the shape F boot reported a NEGATIVE -0.06 GiB
|
||||
capture delta, so the 256 MiB base was fiction; 0 + 12 MiB/graph still
|
||||
prices a future 18-graph spec-decode shape at 0.21 GiB), KV_BYTES
|
||||
@@ -1299,7 +1254,6 @@
|
||||
## 2026-10-02
|
||||
**frank-dragoon cutover: vLLM MiMo -> ROCmFPX Q8_0_ROCMFPX_AGENT + MTP**
|
||||
|
||||
|
||||
- 2026-10-02 — frank-dragoon: vLLM MiMo → ROCmFPX `Q8_0_ROCMFPX_AGENT`
|
||||
payload + MTP from the repo's own bundled head, one session at the native
|
||||
262,144. See [[frank-dragoon]].
|
||||
@@ -1318,7 +1272,6 @@
|
||||
|
||||
**frank-zealot: Q5_K_M, then three contexts + vision, sized by two boots**
|
||||
|
||||
|
||||
- 2026-10-02 — frank-zealot: Q5_K_M payload, then three contexts + vision
|
||||
in one day, sized by two boots (`CTX_SIZE=540672 PARALLEL=3`, f16 K +
|
||||
q8_0 V under flash attention, `--mmproj`). See [[frank-zealot]].
|
||||
@@ -1347,7 +1300,6 @@
|
||||
|
||||
**frank-archon shape D: 3 x 200704, fp8, pool sized for 2 contexts**
|
||||
|
||||
|
||||
Frank's order: shrink frank-archon to a 200704-token window with about two
|
||||
resident full-length contexts, cut the KV pool, and drop MNBT to 1024. The
|
||||
first attempt (`30904ba`, 2 x 200704 + SEQS=2) **crash-looped**; the revert
|
||||
@@ -1356,7 +1308,6 @@ top of the proven-good base, root cause first.
|
||||
|
||||
- **ROOT CAUSE: `--max-num-seqs 2` cannot compile this model. It was never
|
||||
OOM, never MNBT, never the KV dtype.** Four single-variable boot probes
|
||||
against the then-deployed shape (production stopped for each, candidate
|
||||
booted AS the service): 262144 x SEQS 2 fails at BOTH MNBT rungs with
|
||||
`torch.fx.experimental.symbolic_shapes.ConstraintViolationError:
|
||||
Constraints violated (L['query_start_loc'].size()[0]) ... specialized it
|
||||
@@ -1392,13 +1343,11 @@ top of the proven-good base, root cause first.
|
||||
- **Harness contract followed the window:** `models.yml` `contextWindow`
|
||||
262144 -> **200704**; side effect: frank-archon's compaction request fits
|
||||
inside a frank-zealot slot again. A comment-only follow-up `295d578` was
|
||||
deployed so the per-service revision marker sits at HEAD. Rollback for
|
||||
the pre-shape-D fp8+MTP4 era stays git tag
|
||||
`frank-archon-fp8-mtp-20261001`. Runbook [[frank-archon]].
|
||||
|
||||
**plex: the readiness gate probed HTTP against a TLS-only service**
|
||||
|
||||
|
||||
Found by accident, and it had been latent since the secure-connections posture
|
||||
was set: `deploy_plex` gated on `curl -fsS http://127.0.0.1:32400/identity`,
|
||||
which **cannot** succeed. PMS drops plain HTTP off-loopback - the probe's
|
||||
@@ -1422,18 +1371,14 @@ and then answers with nothing, and curl reports "Empty reply from server" (52).
|
||||
got the same treatment. Fixed to `curl -fsSk --max-time 3
|
||||
https://127.0.0.1:32400/identity` (`-k`: PMS serves its own self-signed
|
||||
cert on 32400). Re-deployed `39f0e0d`: plex recorded deployed, e2e
|
||||
33/33, frank-archon untouched (`health=200`, NRestarts=0).
|
||||
- **Operating lesson: `deploy.sh <target>` with no `--service` is a
|
||||
fleet-wide drain.** It restarts every service whose recorded revision is
|
||||
behind HEAD and whose paths changed since. To touch one service, name it.
|
||||
|
||||
**frank-archon shape E: `--expert-precise-memory` (+1,026 arena slots), a mounted compile cache, and the P2P answer**
|
||||
|
||||
|
||||
Shape D left one question open — the plan's overhead term was reserving 2.35 GiB
|
||||
of compile headroom the boot never used — and the answer to Frank's free-VRAM
|
||||
question arrived together with a boot-cost bug that had been costing 181 s on
|
||||
every restart since the cutover.
|
||||
|
||||
- **`--expert-precise-memory` is real and it fits (option A).** Boot-probed on
|
||||
the shape-D base with production stopped, the candidate booted AS the service
|
||||
@@ -1457,11 +1402,9 @@ every restart since the cutover.
|
||||
1.38x` — measured, rejected as the shipped value, kept as the conservative
|
||||
fallback. With 3.15e9/rank the endpoint keeps shape D's 1.99x concurrency and
|
||||
gains the arena, which is the shape D follow-up had actually been looking for.
|
||||
- **THE COMPILE CACHE WAS BEING THROWN AWAY ON EVERY RESTART — 181 s each.**
|
||||
Measured: a fresh container has **no `/root/.cache` at all** (`No such file or
|
||||
directory`); after our boot it holds `/root/.cache/vllm` at
|
||||
**1,154,714,333 B in 4,665 files** plus `/root/.cache/comgr` at 229 MB, inside
|
||||
the ephemeral layer, and `podman rm -f` (every deploy, restart, reboot)
|
||||
discards them. The engine names the loss itself: `Compiling a graph for compile
|
||||
range (1, 1024) takes 173.19 s` ... `torch.compile took 181.20 s in total` ...
|
||||
`init engine (profile, create kv cache, warmup model) took 217.26 s
|
||||
@@ -1514,7 +1457,6 @@ every restart since the cutover.
|
||||
not a crash — the container and its serve script must be polled by recorded id
|
||||
and their logs dumped on the exit path, or a failure looks like a bad flag.
|
||||
The flag was fine both times.
|
||||
- Live: `health=200`, `NRestarts=0`, arena 17.22 GiB / 7,522 slots, KV 398,658
|
||||
tokens / 1.99x. Docs: [[frank-archon]].
|
||||
- **Deployed `1ac4d48`, e2e 33/33, and the live plan matched the probe exactly:**
|
||||
`'expert_precise_memory': True`, `runtime overhead … 5.25 GiB`, `expert cache
|
||||
@@ -1523,7 +1465,6 @@ every restart since the cutover.
|
||||
precise overhead and the arena growth are now production numbers, not a
|
||||
probe's projection.
|
||||
- **The compile-cache fix was then proven on the same endpoint, two consecutive
|
||||
boots of the deployed shape:** cold `Compiling a graph for compile range
|
||||
(1, 1024) takes 179.24 s` → `torch.compile took 188.26 s`; warm `Directly load
|
||||
the compiled graph(s) for compile range (1, 1024) from the cache, took
|
||||
2.143 s` → `torch.compile took 3.97 s in total` → `init engine (profile,
|
||||
@@ -1554,14 +1495,11 @@ every restart since the cutover.
|
||||
|
||||
**frank-archon RESTORED to fp8 3 x 262144 (bf16 boot fails)**
|
||||
|
||||
|
||||
bf16 (--kv-cache-dtype auto) at 2 x 262144 did not stay up, with OR without
|
||||
--expert-precise-memory:
|
||||
|
||||
- with precise: NRestarts 1->3, each attempt ~6.7 min then
|
||||
`Deactivated successfully` (API server drains after an EngineCore fatal
|
||||
error) — cause not captured, the serve script rm'd the container.
|
||||
- without precise (default 7.60 GiB overhead, arena 14.3 GiB): NRestarts
|
||||
climbing, container `Exited (1)`, and the journal finally showed the real
|
||||
error:
|
||||
`RuntimeError: Worker failed with error 'Constraints violated
|
||||
@@ -1582,12 +1520,9 @@ Next: bisect bf16 vs SEQS in a window (bf16 + SEQS=3 isolates the dtype).
|
||||
|
||||
**frank-archon: --expert-precise-memory + bf16 = boot crash loop**
|
||||
|
||||
|
||||
**Reverted.** The two changes Frank asked for were applied together and the
|
||||
result crash-looped: `NRestarts` climbed 1 -> 2 -> 3, each attempt running
|
||||
~6.7 minutes then logging `Deactivated successfully` (vLLM's API server drains
|
||||
and exits 0 after an EngineCore fatal error, so `podman wait` returns 0; the
|
||||
new `Restart=always` turned a silent death into a visible loop).
|
||||
|
||||
The budget arithmetic explains it without needing the error text, because the
|
||||
arena is DEVICE-resident (the fp8 3-window shape's plan predicts 28.27 GiB and
|
||||
@@ -1615,7 +1550,6 @@ by paying for it elsewhere (smaller pool, fewer sessions, or a shorter context).
|
||||
|
||||
**frank-archon: 2 bf16 windows + --expert-precise-memory**
|
||||
|
||||
|
||||
Frank's final shape for the night. Two changes at once, both measured in
|
||||
principle before the boot:
|
||||
|
||||
@@ -1641,7 +1575,6 @@ planned arena; it comes out of free VRAM instead.
|
||||
|
||||
**frank-dragoon: Q4_K_XL payload + 4 x 163840 sessions**
|
||||
|
||||
|
||||
Frank's call. Payload swapped from ajgazin's **abliterated** Swift-1.5
|
||||
UD-Q8_K_XL (29.3 GiB) to **unsloth/Qwen3.8-27B-GGUF UD-Q4_K_XL** (16.35 GiB,
|
||||
sha256 3f227079…b01e, vanilla/Apache-2.0 — the endpoint is no longer
|
||||
@@ -1663,7 +1596,6 @@ weights 16.35 + KV 40.0 + drafter 1.07 + linear state 0.56 + runtime ~4 =
|
||||
|
||||
**e2e gate flakiness (12-crawl) — do not panic at the banner**
|
||||
|
||||
|
||||
`specs/12-crawl.spec.ts:23` ("every user open link reaches its app") failed the
|
||||
deploy gate twice and then passed cleanly on a manual re-run:
|
||||
- run 1 (deploy): `Expected "plex.franksdank.cloud"` / `Received "app.plex.tv"`
|
||||
@@ -1682,7 +1614,6 @@ any repo service.
|
||||
|
||||
**frank-archon bf16 no-spec cutover**
|
||||
|
||||
|
||||
- `/scripts/frank-archon.env` — bf16 no-spec cutover, FINAL shape after the
|
||||
night ladder: MAXLEN **241664** x SEQS 3, KV_BYTES **10,184,000,000**/rank,
|
||||
KV_DTYPE auto (bf16), MNBT 1024, GATHER CHUNKS=32/LANES=64, UTIL 0.98;
|
||||
@@ -1719,7 +1650,6 @@ any repo service.
|
||||
- **Client contract fixed (thinking + tools).** The live endpoint returned a
|
||||
null `reasoning` field and dumped the thinking text INLINE in `content`
|
||||
with a dangling `</think>` (measured: `'We need answer…Brief.\n</think>\n\n391'`)
|
||||
— the malformed thinking block clients choke on — because the deployed argv
|
||||
had no `--reasoning-parser`. The GLM rollback (`d91379d`) had dropped all
|
||||
three contract flags and the qwen rollback inherited the gap. Restored from
|
||||
`ecaad36`'s values, confirmed against this fork's registries:
|
||||
@@ -1749,11 +1679,9 @@ any repo service.
|
||||
knife-edge shape change, send a large-`max_tokens` request before calling it
|
||||
done. FOUR slots x 262144 fp8 (KV_BYTES 8,311,013,376/rank) returns ~1.93
|
||||
GiB per rank for activations/graph capture. Frank's call 2026-10-01.
|
||||
- `/scripts/frank-archon.service` — **Restart=always, not on-failure** (plus
|
||||
`StartLimitIntervalSec=0` and `OOMScoreAdjust=-500`). A runtime worker fault
|
||||
makes vLLM's API server exit CLEAN, so ExecStart returned 0, systemd logged
|
||||
"Deactivated successfully", and on-failure NEVER fired: the endpoint stayed
|
||||
down until a human noticed. An explicit `systemctl stop` still wins, so
|
||||
bench windows and deploys are unaffected.
|
||||
- **Final shape (Frank's call, 2026-10-01): 3 x 262144, pool at the FLOOR,
|
||||
MNBT 2048, rest to the arena.** MAXLEN 262144 x SEQS 3 = 786,432 tokens of
|
||||
@@ -1773,7 +1701,6 @@ any repo service.
|
||||
|
||||
**frank-dragoon cutover: gufo -> vLLM TheRock, MiMo-V2.6 BF16 + cross-model MTP**
|
||||
|
||||
|
||||
- **Question that started it:** can a Qwen3.5 MTP drafter attach to
|
||||
frank-zealot, and how does vLLM run the FULL BF16 MiMo checkpoint? Answer 1:
|
||||
not on frank-zealot — SGLang reads the MTP head from the TARGET checkpoint
|
||||
@@ -1787,7 +1714,6 @@ any repo service.
|
||||
prefill / 512 decode):** base 17,876 t/s prefill / 13.1 t/s decode (13.1
|
||||
is ~94% of the LPDDR5x BF16 roof - decode here is bandwidth, not a bug);
|
||||
+DFlash-8 7,976 / 17.4 (acceptance 59%/26% per position); +MTP-2 7,380 /
|
||||
19.8 (71%/44%, token rate 57.4%). Deployed MTP-2: +51% decode. Both spec
|
||||
arms pay ~55-59% of prefill. `num_speculative_tokens` beyond 2 unmeasured.
|
||||
- **Measured numerics law:** spec arms do NOT reproduce the no-spec greedy
|
||||
text (bf16 batched verification reorders reductions; near-tie argmaxes
|
||||
@@ -1798,9 +1724,7 @@ any repo service.
|
||||
`reasoningContentField: "reasoning"`, and the extraBody moved to the
|
||||
Qwen3.5-family recipe (0.7/0.8) with `chat_template_kwargs`
|
||||
(a top-level `preserve_thinking` is ignored by vLLM).
|
||||
- Deployer gap found pre-deploy: the spec-draft gate demanded a single
|
||||
`model.safetensors` (the dflash layout) and would have rejected the
|
||||
deployed MTP arm (4 shards behind an index); fixed to accept both
|
||||
layouts, gated by bats. `z-lab/Qwen3.5-9B-DFlash`'s "vLLM PR #40898
|
||||
unmerged" card claim is FALSE - `vllm/v1/spec_decode/dflash.py` ships in
|
||||
0.30 (verified in-image); the drafter stays on disk as the alternative.
|
||||
@@ -1839,7 +1763,6 @@ any repo service.
|
||||
cutover), TP=2, 131,072 ctx x 2 seqs, bf16 KV, util 0.98, NBATCH 2048,
|
||||
`--enable-expert-offload --expert-precise-memory`, prefix caching, vision +
|
||||
`glm47` tool and reasoning parsers, MTP OFF, prefetch OFF.
|
||||
- Measured on the deployed engine: **decode 9.9-10.0 tok/s, prefill 918 tok/s**
|
||||
(warm, 2.6k prompt), KV 138,561 tokens; arena 14.51 GiB = 2,383 slots per
|
||||
rank over 12,096 host-backed experts (73.64 GiB pinned per rank).
|
||||
- **The wall — and a corrected one.** The first account of the shortfall
|
||||
@@ -1945,7 +1868,6 @@ any repo service.
|
||||
per-sequence KDA state and the MTP draft's KV, and `gpu_worker` caps KV at
|
||||
that reserve, making a legal `--max-model-len` unbootable. Shipped
|
||||
`scripts/patches/gpu_worker-kv-cap.patch` (lets an explicit operator KV
|
||||
request raise the cap) — NOT needed by the deployed shape.
|
||||
- Rollback: commit `2339fae` (qwen FP8 + MTP4); the qwen payload and the R9V
|
||||
state dirs are intact on disk.
|
||||
|
||||
@@ -2125,7 +2047,6 @@ any repo service.
|
||||
(`20260921-frank-dragoon-assembled` 88 G, `-redownload` 30 G) and never
|
||||
purged. Total now staged on this host: **627 G**.
|
||||
- Verified after the moves: frank-dragoon and frank-zealot both `active`,
|
||||
both `/health` 200, and all three live assets (Swift target, DFlash2
|
||||
drafter, Gemma4 target) still present.
|
||||
- **frank-dragoon payload swapped to Swift-1.5 (uncensored)** (production
|
||||
change, Dankclaw). Frank asked whether the UD Q8 of
|
||||
@@ -2242,7 +2163,6 @@ any repo service.
|
||||
8 s, drafter 2 s, session 3 s) against a 20-minute `LLM_WAIT` budget;
|
||||
RSS 30.6 GiB; a live short-prompt request returned
|
||||
`Server-Timing: ttft;dur=549.8, inter_token;dur=24.2` (~41 tok/s).
|
||||
Verified live: `/health` 200, `/ready` 200, `/v1/models` →
|
||||
`frank-dragoon` at `context_length 262144`, a chat completion carrying
|
||||
`reasoning_content`, a `tool_calls` response with
|
||||
`finish_reason: tool_calls`, and SSE streaming.
|
||||
@@ -2309,9 +2229,6 @@ any repo service.
|
||||
- **LANDMINE, not changed here:** `/home/dank/models` was cleared by Frank
|
||||
mid-session to free space. frank-archon is still answering `:7770` from
|
||||
mmapped-but-unlinked inodes (`/models` is empty inside the running
|
||||
container) — **the endpoint cannot be restarted until the 175 GiB payload
|
||||
is re-fetched.** A reboot or a container restart takes frank-archon down.
|
||||
- Deploy-state reconciliation (Frank's call: no restart) — the autoresearch trio
|
||||
(`f187165` PLE 1800, `41e9189` prefix caching, `ba2100e` docs) was found already
|
||||
MERGED to main + pushed AND already live on frank-archon: the running
|
||||
`frank-archon-r9v` container (booted 12:13 from the `ba2100e` checkout) carries
|
||||
@@ -2324,9 +2241,7 @@ any repo service.
|
||||
failure `11-admin` is the FrankBook-Pro Mac mini (`192.168.0.112:8989`) offline
|
||||
(100 % ICMP loss + TCP timeout), the documented asleep-host condition, not a
|
||||
regression.
|
||||
- `scripts/frank-archon.env`, runbook — **prefix caching DEPLOYED on
|
||||
frank-archon** (Frank's go-ahead; commit `41e9189`): E8-qualified, zero
|
||||
VRAM cost, 9x/63x reuse TTFT. Verified on :7770 (argv flag, health 200,
|
||||
completion "PREFIX-CACHE-LIVE"). Gotcha re-confirmed: the container is
|
||||
in dank's ROOTLESS podman store (`sudo podman` inspects an empty store
|
||||
and reports nothing); probe via the shim's `docker` or the unit MainPID.
|
||||
@@ -2458,7 +2373,6 @@ any repo service.
|
||||
## 2026-08-26
|
||||
- `/dankclaw/Caddyfile` — Both Plex vhosts proxy to PMS over a TLS upstream (`insecure_skip_verify`); PMS drops plain-HTTP connections from non-loopback sources (empty-body 502s).
|
||||
- `/dankclaw/gitea/quadlet/gitea-caddy.container` — Standalone Gitea Caddy quadlet retired; web proxy ownership moved to canonical Caddy; deploy-plan/deploy.sh/deploy-remote.sh + tests updated for the migration.
|
||||
- Incident — Post-reboot: Caddy (a bare compose container — `restart: unless-stopped` does not survive shutdown's explicit stop) and Pi-hole (quadlet crash-loop, restart counter 1645) were both down. Root cause: `net.ipv4.ip_unprivileged_port_start` reset to 80 by a later-sorted sysctl drop-in AND a stale line in `/etc/sysctl.conf`, which systemd-sysctl applies AFTER all of `sysctl.d/`.
|
||||
- `/AGENTS.md` — New MANDATORY "Boot Persistence & Self-Healing" section.
|
||||
- `/dankclaw/caddy/quadlet/caddy.container`, `/dankclaw/caddy/env.example` — Caddy is now a boot-persistent quadlet (joins `dankclaw_default`, `Pull=never`, env from `/home/dank/Containers/caddy/env`); removed from compose.yaml.
|
||||
- `/dankclaw/pihole/sysctl/99-dnsmasq.conf` — Repo source of truth for `ip_unprivileged_port_start=53`; `deploy_pihole` stages conflicting drop-ins, comments out the stale sysctl.conf line, and applies `sysctl --system` (the boot code path).
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
# 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
|
||||
# (vendor build 10777), the ONLY loader that registers the payload's custom
|
||||
# tensor type 142 and honours its prism.hadamard.* metadata: TWO 262,144-token
|
||||
# conversations (--ctx-size 524288 --parallel 2) over an UNQUANTIZED f16 KV
|
||||
# cache. Exact KV bills 32,768 B/token here, so the pool is what pays for it —
|
||||
# the same dtype at the old 786,432 pool is 24,576 MiB against the 24,123 MiB
|
||||
# the driver leaves addressable, and the slot count is the term that moved. The
|
||||
# publisher's own Q8_0 vision projector rides along (the BF16 one can crash on
|
||||
# concurrent images), and MTP stays OFF: the head is present, but exact KV
|
||||
# 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.
|
||||
# tensor type 142 and honours its prism.hadamard.* metadata: THREE 200,704-token
|
||||
# conversations (--ctx-size 602112 --parallel 3) over an f16 K + q8_0 V cache —
|
||||
# 25,088 B/token, 14,406.00 MiB at this pool — with the MTP drafter ARMED
|
||||
# (--spec-type draft-mtp). The asymmetry IS the shape: exact V (32,768 B/token)
|
||||
# leaves nothing for a draft context that bills its own f16 KV over the WHOLE
|
||||
# pool (4,096 B/position, 2,352.00 MiB here), and the 786,432 pool this shape
|
||||
# replaced cannot carry a drafter at any dtype inside the 24,123 MiB the driver
|
||||
# leaves addressable. The publisher's own Q8_0 vision projector rides along (the
|
||||
# BF16 one can crash on concurrent images). Log
|
||||
# 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.
|
||||
|
||||
@@ -143,7 +143,7 @@ run_stage() {
|
||||
done
|
||||
}
|
||||
|
||||
@test "two conversations share the pool at a 262144 window each" {
|
||||
@test "three conversations share the pool at a 200704 window each" {
|
||||
serve_argv
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(argv_value --alias)" = "frank-zealot" ]
|
||||
@@ -152,30 +152,36 @@ run_stage() {
|
||||
[ "$(argv_value --model)" = "$MODEL_DIR/$MODEL_STUB" ]
|
||||
[ "$(argv_value --n-gpu-layers)" -gt 0 ]
|
||||
# llama.cpp divides --ctx-size across slots, so the observable contract is
|
||||
# the per-slot window. Exact KV costs the SLOT COUNT, not the window: every
|
||||
# conversation still gets the model's TRAINED 262,144, and the harness
|
||||
# contextWindow must quote that number — the pool and the slot count are
|
||||
# neither, and a longer prompt is a 400, never a truncation.
|
||||
[ "$(argv_value --parallel)" -eq 2 ]
|
||||
[ "$(argv_value --ctx-size)" -eq 524288 ]
|
||||
local pool slots
|
||||
# the per-slot window: harness contextWindow, Prometheus dashboards and every
|
||||
# consumer's compaction trigger read THAT number, and a longer prompt is a
|
||||
# 400, never a truncation. Two invariants the pool/slot split has to keep:
|
||||
# it divides evenly (llama.cpp floors it otherwise) and a slot never exceeds
|
||||
# the model's TRAINED window (there is no YaRN here, so the engine would be
|
||||
# quoting a window it cannot fill).
|
||||
[ "$(argv_value --parallel)" -eq 3 ]
|
||||
[ "$(argv_value --ctx-size)" -eq 602112 ]
|
||||
local pool slots window
|
||||
pool="$(argv_value --ctx-size)"; slots="$(argv_value --parallel)"
|
||||
[ $((pool % slots)) -eq 0 ]
|
||||
[ $((pool / slots)) -eq 262144 ]
|
||||
window=$((pool / slots))
|
||||
[ "$window" -eq 200704 ]
|
||||
[ "$window" -le 262144 ]
|
||||
}
|
||||
|
||||
@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
|
||||
# file, not two. The shipped pair is f16/f16: exact attention state, and the
|
||||
# slot count is what makes it affordable — this payload bills 8,192 elements
|
||||
# per token per cache (8 full-attention layers × 4 KV heads × 256 dim), so
|
||||
# exact KV is 32,768 B/token = 16,384.00 MiB at the 524,288 pool, where the
|
||||
# q8_0 pair this file used to ship billed 17,408 B/token = 13,056.00 MiB at
|
||||
# the old 786,432 pool (ledger-exact both times). The 24 DeltaNet layers
|
||||
# file, not two. The shipped pair is ASYMMETRIC — f16 K (the engine's own
|
||||
# default) plus q8_0 V: this payload bills 8,192 elements per token per cache
|
||||
# (8 full-attention layers × 4 KV heads × 256 dim), so K costs 16,384 B/token
|
||||
# and V 8,704 (q8_0 is 1.0625 B/element; there is no true fp8 KV dtype in
|
||||
# this build) = 25,088 B/token = 14,406.00 MiB at the 602,112 pool. Exact V
|
||||
# would bill 32,768 B/token and the all-q8_0 pair this file used to ship
|
||||
# billed 17,408 — the halfway rung is what pays for the third slot AND the
|
||||
# armed drafter (the fit gate below prices both). The 24 DeltaNet layers
|
||||
# bill a fixed recurrent state per slot (~50.25 MiB a cell), which is why
|
||||
# pool arithmetic alone under-prices the shape. flash-attn is not
|
||||
# decoration: the quantized-V fallback is only legal with it, so it stays
|
||||
# explicit and a dtype retune cannot silently fall back.
|
||||
# decoration: quantized V is only legal with it, so it stays explicit and a
|
||||
# dtype retune cannot silently fall back.
|
||||
local env_k env_v
|
||||
env_k="$(sed -nE 's/^CACHE_TYPE_K=(.+)$/\1/p' "$ENV_FILE")"
|
||||
env_v="$(sed -nE 's/^CACHE_TYPE_V=(.+)$/\1/p' "$ENV_FILE")"
|
||||
@@ -192,30 +198,47 @@ run_stage() {
|
||||
# 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
|
||||
# slot-independent at ubatch 2048), plus one ~51 MiB recurrent-state cell per
|
||||
# slot. That constant is deliberately NOT credited with the ~1,306 MiB the
|
||||
# shipped ubatch 1024 hands back: the gate stays conservative.
|
||||
# An ARMED drafter is billed, because it is the term this shape pays
|
||||
# CACHE_TYPE_V=q8_0 to afford: the draft context is built against the
|
||||
# TARGET's whole pool at its own f16 rate (measured 4,096 B/position at
|
||||
# blk.32) plus buffers and its own recurrent cells — the measured spec-ON
|
||||
# against spec-OFF delta at the 786,432 / ubatch-1024 arm was 3,524 MiB
|
||||
# beside 3,072 MiB of draft KV, i.e. ~452 MiB, doubled here for the third
|
||||
# slot's extra cells. 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 spec f16 exact_pool exact_v
|
||||
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")"
|
||||
spec="$(sed -nE 's/^SPEC_TYPE=(.+)$/\1/p' "$ENV_FILE")"
|
||||
rate_k="$(kv_bytes_per_token "$env_k")"
|
||||
rate_v="$(kv_bytes_per_token "$env_v")"
|
||||
f16="$(kv_bytes_per_token f16)"
|
||||
kv="$(( pool * (rate_k + rate_v) / 1048576 ))"
|
||||
total="$(( kv + 4749 + 51 * slots ))"
|
||||
if [ "$spec" != "none" ]; then
|
||||
total="$(( total + pool * 4096 / 1048576 + 768 ))"
|
||||
fi
|
||||
# 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 ]
|
||||
# And the gate must be able to REFUSE, or passing above means nothing. Two
|
||||
# boots this shape can walk straight into: the retired dtype pair at the
|
||||
# retired pool (exact f16 at 786,432 = 24,576 MiB of cache alone, which died
|
||||
# at cudaMalloc), and the shipped pool with V moved back to f16 while the
|
||||
# drafter stays armed — 18,816 MiB of cache plus the draft's 2,352. Both die
|
||||
# before /health ever answers, and no health gate can see either.
|
||||
exact_pool="$(( 786432 * (rate_k + f16) / 1048576 + 4749 + 51 * 3 ))"
|
||||
[ "$exact_pool" -gt 24123 ]
|
||||
exact_v="$(( pool * (rate_k + f16) / 1048576 + 4749 + 51 * slots
|
||||
+ pool * 4096 / 1048576 + 768 ))"
|
||||
[ "$exact_v" -gt 24123 ]
|
||||
}
|
||||
|
||||
@test "log verbosity follows the env file" {
|
||||
@@ -277,48 +300,54 @@ run_stage() {
|
||||
argv_value --ubatch-size | grep -qE '^[0-9]+$'
|
||||
}
|
||||
|
||||
@test "speculation is off: no drafter flag reaches the argv" {
|
||||
# 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
|
||||
# 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 × 524,288 = 2,048 MiB plus
|
||||
# buffers — on a trunk that already projects 21,234 MiB with exact KV. The
|
||||
# MTP arms in frank-zealot.env are all q8_0-era measurements; the doors back
|
||||
# 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-draft-model together, so a no-spec boot cannot carry half a spec
|
||||
# pair (the ROCmFPX-era baseline arm segfaulted at load doing that).
|
||||
@test "the armed drafter ships the whole MTP set and no second copy of the trunk" {
|
||||
# Frank, 2026-10-19: the drafter is ON. The head is IN this file
|
||||
# (qwen35.nextn_predict_layers = 1, blk.32.nextn.*) and it accepted well
|
||||
# when it was measured off (0.798/0.617 per position, mean accepted length
|
||||
# 2.41); what it now pays for is CACHE_TYPE_V=q8_0 plus UBATCH_SIZE=1024,
|
||||
# which is why the dtype pair and the batch pair are pinned next door.
|
||||
# The observable contract is the SET: --spec-type plus every --spec-draft-*
|
||||
# the env names, and NO --spec-draft-model — the MTP draft rides the MAIN
|
||||
# model's weights, so a draft file would load the trunk a second time.
|
||||
local n_max
|
||||
n_max="$(sed -nE 's/^SPEC_DRAFT_N_MAX=([0-9]+)$/\1/p' "$ENV_FILE")"
|
||||
serve_argv
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(argv_value --spec-type)" = "draft-mtp" ]
|
||||
[ "$(argv_value --spec-draft-n-max)" = "$n_max" ]
|
||||
[ "$(argv_value --spec-draft-n-min)" = "0" ]
|
||||
[ "$(argv_value --spec-draft-p-min)" = "0" ]
|
||||
! grep -qxF -- '--spec-draft-model' <<<"$output"
|
||||
}
|
||||
|
||||
@test "the rollback knob strips the entire spec set" {
|
||||
# SPEC_TYPE=none is the documented retreat (and the shape this endpoint
|
||||
# shipped before 2026-10-19). The law it exists to obey: the flags move as a
|
||||
# SET — a no-spec boot that keeps --spec-draft-n-max, or a baseline arm that
|
||||
# hands over --model-draft with no --spec-type, has segfaulted at load with
|
||||
# no diagnostic. So the rollback is proven by ABSENCE of all of them.
|
||||
serve_argv
|
||||
sed -i 's/^SPEC_TYPE=.*/SPEC_TYPE=none/' "$STAGE/test.env"
|
||||
run_stage
|
||||
[ "$status" -eq 0 ]
|
||||
! grep -qxF -- '--spec-type' <<<"$output"
|
||||
! grep -qxF -- '--spec-draft-n-max' <<<"$output"
|
||||
! grep -qxF -- '--spec-draft-n-min' <<<"$output"
|
||||
! grep -qxF -- '--spec-draft-p-min' <<<"$output"
|
||||
! grep -qxF -- '--spec-draft-model' <<<"$output"
|
||||
grep -qE '^SPEC_TYPE=none$' "$ENV_FILE"
|
||||
}
|
||||
|
||||
@test "naming a drafter ships the MTP set, with or without a separate draft file" {
|
||||
# The documented MTP arm (frank-zealot.env): SPEC_TYPE=draft-mtp ALONE is
|
||||
# legal here because the head is IN the checkpoint and the draft context
|
||||
# rides the main model's weights — so --spec-draft-model must NOT appear,
|
||||
# or the trunk would be loaded twice.
|
||||
@test "an external drafter still reaches the engine as a complete set" {
|
||||
# The other arm the ROCmFPX-era crash was about: a named draft FILE must
|
||||
# arrive alongside --spec-type and the depth recipe, never alone. This
|
||||
# endpoint does not use it (the head is in the trunk), so the knob ships
|
||||
# empty and only this test sets it.
|
||||
serve_argv
|
||||
{
|
||||
echo 'SPEC_TYPE=draft-mtp'
|
||||
echo 'SPEC_DRAFT_N_MAX=2'
|
||||
} >> "$STAGE/test.env"
|
||||
run_stage
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(argv_value --spec-type)" = "draft-mtp" ]
|
||||
[ "$(argv_value --spec-draft-n-max)" = "2" ]
|
||||
! grep -qxF -- '--spec-draft-model' <<<"$output"
|
||||
|
||||
# And an external drafter still reaches the engine as a complete set — the
|
||||
# pair the ROCmFPX-era crash was about.
|
||||
echo "SPEC_DRAFT_FILE=$MODEL_DIR/$DRAFT_STUB" >> "$STAGE/test.env"
|
||||
run_stage
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(argv_value --spec-draft-model)" = "$MODEL_DIR/$DRAFT_STUB" ]
|
||||
[ "$(argv_value --spec-type)" = "draft-mtp" ]
|
||||
}
|
||||
|
||||
@test "vision ships by default and MMPROJ_FILE empty opts out of the image path" {
|
||||
|
||||
Reference in new issue
Block a user