The docs-fact census wave re-read the live hosts and re-keyed every page it could: runtime/payload identities, ports, sizes, ledger figures, unit state, and the retirement banners that drifted as the fleet moved. The wave was interrupted mid-run, so this commit carries only what passed the integrity gates: every touched page parses (front matter or H1+status convention intact, balanced fences, no truncated tables, trailing newline), the docs_completeness and link-integrity seams are green, and bats is 384/384. Numbers that this session proved on live hosts are keyed to the measured value, not the page's earlier claim.
17 KiB
title, service, host, port, status, deployed, supersedes, tags
| title | service | host | port | status | deployed | supersedes | tags | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| frank-archon — R9V radiance-vLLM Qwen3.8-Flash-Next Q8_0 | frank-archon | dankserver | 7770 | retired | 2026-09-27 | frank-archon-r9v-radiance-qwen38-uncensored |
|
frank-archon — R9V radiance-vLLM Q8_0 (dankserver, RETIRED 2026-09-27 → 09-30)
The Dankserver LLM endpoint was the R9V radiance-vLLM fork (retired
2026-09-30, when frank-archon-glm53-flash-w4a16 took the port; the engine
live today, 2026-10-18, is radiance with
frank-archon-radiance-qwen38-iq4r)
(drwolfen/radiance-vllm-qwen4exp, docker-pinned prebuilt image bundle
2dac17a2) serving the unsloth Qwen3.8-Flash-Next-GGUF Q8_0 payload
(175.30 GiB, 6 GGUF shards, sha256-verified per part) with the blockfp8
MTP4 head and the mmproj, on both R9700s (gfx1201, BDFs 0000:03:00.0
/ 0000:07:00.0) as frank-primary on :7770 (the frank-archon wire alias
arrived with the 2026-10-09 rename).
Cut over from the uncensored IQ4_XS payload on 2026-09-27 at Frank's
direction. Rollback target at the time:
frank-archon-r9v-radiance-qwen38-uncensored — its state dir
(/home/dank/r9v-state/uncensored/, with setup.json and
restore-config-20260927.env) is still on the host as of 2026-10-18, but the
IQ4_XS payload itself is not (/home/dank/models/ holds only StillDeadcode,
rad, tpl.jinja), so that flip now needs a re-fetch.
Why the swap
The uncensored IQ4_XS payload measured 59–96 tok/s decode with draft-acceptance swings; the Q8_0 payload trades VRAM-resident expert headroom for precision — quantization to Q8_0 doubles expert bytes per block, so the tiered-expert residency budget halves — the probe-era 11 tok/s decode was residency-bound (expert streaming, not compute). The "Known costs" section below carries the accepted trade.
Production shape (repo-tracked)
The repo carries a thin wiring; the engine shape lives on-host:
| Piece | File | Law |
|---|---|---|
| Unit | scripts/frank-archon.service |
Root unit, User=dank, LimitMEMLOCK=infinity, ExecStop=podman stop -t 120 frank-archon-r9v, Restart=on-failure |
| Env | scripts/frank-archon.env |
Production overrides ONLY: bind 0.0.0.0, :7770, alias frank-archon, container frank-archon-r9v, GPU BDFs, dev-overlay adapter, R9V_FULL_MUTABLE_CACHE=0, R9V_PLE_READY_TIMEOUT=1800, prefix caching ON, sharp chat template |
| Serve | scripts/serve-frank-archon.sh |
Layers setup state under repo env → execs the fork's own scripts/launch.sh via the docker→podman shim → docker wait |
| Deployer | scripts/deploy-remote.sh deploy_llm_archon |
Gates on setup state ready, image digest, PLE bytes, all six shard files + mmproj; MainPID false-green check |
Why the tuned shape lives on-host: the q8 profile's setup run
qualified the R9V_* config (tiered-expert placement, per-rank offload
budgets, 80-slot tiered cache, PLE pinned residency 54.4 GB, MTP4,
parser wiring, MAX_MODEL_LEN=131072). It is written to
/home/dank/r9v-state/q8xl/setup.json and the serve script layers it
UNDER the repo env (repo keys win). Copying those values into the repo
would drift from the qualification — don't.
Spec decode: MTP4 only — the ngram-mod ask is NOT expressible
llama.cpp chains drafter types (--spec-type draft-mtp,ngram-mod on
frank-zealot/large). The fork's speculative_config is a single dict
(one method per engine): mtp, ngram, ngram_gpu, dflash, … are
mutually exclusive — no chaining seam exists in the fork. What IS active:
- MTP4 (
R9V_MTP_SPEC_TOKENS=4, blockfp8 draft head, draft TP=2) — measured mean acceptance length 3.19–5.00 during qualification. - The PLE n-gram — architectural (
ngram_embeddingmodule), rides along automatically; it is not the llama.cppngram-modmatcher and cannot be "added" as a second spec method. ngram_gpuexists as an ALTERNATIVE single method (fork's GPU n-gram proposer) — swapping MTP4 for it is an either/or, not a chain.
Sharp chat template (fleet parity with frank-dragoon)
scripts/qwen-sharp-v22.4.0.jinja is served via the launcher's opt-in
R9V_CHAT_TEMPLATE_FILE passthrough (fork commit 30d4a32): the file is
mounted read-only at /opt/r9v/share/chat-template.jinja and passed as
--chat-template, which wins over the tokenizer dir's
metadata/chat_template.jinja; R9V_CHAT_TEMPLATE_KWARGS carries
--default-chat-template-kwargs {"preserve_thinking":true} (the template
defaults preserve_thinking true natively; request-level kwargs still win
per request). The template folds reasoning efforts (high/xhigh/max →
xhigh, minimal/low → low), matching models.yml's reasoningEffortMap.
The sha-pinned metadata/chat_template.jinja in the package is NEVER
edited in place — mutating it drifts the qualification.
Two q8-only runtime laws
- Quantized inject shard — the Q8_0 payload quantizes BOTH fused
hyper-connection shards (the IQ4_XS package ships inject as F32), so
the shard-uniformity check rejects the fused projection at load.
R9V_DEV_QWEN4_EXP_ADAPTER_PY(fork commit363d606) mounts a dev-overlay adapter that dequantizes a quantized inject shard in-stream; uncensored payloads pass through unchanged. R9V_FULL_MUTABLE_CACHE=0— the runtime overlay'senv.alwaysinjects the uncensored 160-slot full-mutable cache geometry, which the q8 profile's 80-slot tiered cache fails with a baseline mismatch. The launcher passes this key AFTER the overlay envs (docker last-wins).
Fork checkout + shim (on-host, outside the repo)
- Checkout:
/home/dank/GitPackages/radiance-vllm-qwen4exp(fork commits363d606dev-overlay,2fe6dbb,50a79ecqualification doc,30d4a32chat-template passthrough,2de0357PLE-timeout forwarding — local-only, remote is GitHub) - State:
/home/dank/r9v-state/q8xl/setup.json(0600,ready: true) - Model dir:
/home/dank/models/Qwen3.8-Flash-Next-Q8_0-R9V/— 175.30 GiB payload as sixQ8_0/Qwen3.8-Flash-Next-Q8_0-0000N-of-00006.ggufshards- mmproj; PLE derived at the pinned size 54,400,261,120 B
- Docker→podman shim:
/home/dank/GitPackages/radiance-vllm-qwen4exp/.podman-shim/docker(PATH-prepended by the serve script). The R9V tooling is docker-pinned; the shim carries three laws:- Image ID law — the bundle is a containerd/OCI docker-save; docker
keys the image by its OCI MANIFEST digest (
sha256:2dac17a2…), podman by config digest but preserves the manifest digest in RepoDigests. The shim answersimage inspect sha256:Xfrom RepoDigests and rewrites baresha256:Xrun refs torepo@sha256:X. - LogConfig law — podman inspect answers
HostConfig.LogConfig.Config: nullwhere docker returns the log-opts map (the doctor crashes on.get("Config", {}) == null). The shim recordsrun --log-optpairs and normalizes inspect output. - docker info law — podman lacks
SecurityOptions/ServerVersion; the shim answers rootless +27.5.1(liveness probe).
- Image ID law — the bundle is a containerd/OCI docker-save; docker
keys the image by its OCI MANIFEST digest (
- venv:
~/.r9v-venv(huggingface_hub for fetch/verify)
Bring-up battery (qualification, 2026-09-27)
- Setup
ready: true; all 20 payload artifacts size+sha256 verified (175.30 GiB total across the six shards). - Vision: 64×64 red PNG → "Red" (79 tok, 7.4 s end-to-end on the scratch port). Prose: 100 tok in 9.1 s ≈ 11.0 tok/s wall on a cold cache with reasoning tokens included in the wall clock — decode-rate context, not a bench (no throughput bench was run, per Frank's direction).
- MTP4: mean acceptance length 3.19–5.00 across the battery.
- PLE pinned proof:
mapping_bytes = mapping_rss_bytes = 54,400,261,120(fully RSS-resident); doubling for re-pin would need 108,800,522,240 ≤ available 140,876,808,192; registration took 446 s.
Memory shape (measured)
- Load: 21.84 GiB / worker at ~158 s; cold start → serve ~16 min.
- PLE pinned residency 54.4 GB in host RAM (vs the uncensored payload's ssd-mmap residency).
- Context envelope:
R9V_MAX_MODEL_LEN=131072; harnessmodels.ymlcarriescontextWindow: 131072,maxTokens: 36864(compaction-gap math: 70% × 131,072 = 91,750 + 36,864 = 128,614 ≤ 131,072). The KV envelope at 131,072 is smaller than the uncensored 196,608 shape. - Prefix caching OFF at cutover (
R9V_ENABLE_PREFIX_CACHING=0), CACHE192 off — superseded 2026-09-28: prefix caching is ON (see bench matrix below).
Prefill / decode bench matrix (2026-09-28, scratch port 7773)
Production stopped for the window (fleet bench law); one matrix plus three follow-up runs, fork's own bench-pp + decode_speed harness, corpus-sliced prompts with the fork's canonical decode rounds. Baseline E0 = production shape (chunk 4096, 80-slot sync cache, MTP4):
| Config | Override | PP8K tok/s | PP131K tok/s | Decode | Verdict |
|---|---|---|---|---|---|
| E0 | — | 954.6 | 883.0 | 141.9 ms/step ≈ 23.8 t/s eff | baseline |
| E1R | chunk 8192 | 1201.4 (+25.9%) | 844.6 (−4.4%) | 158.5 ms (−8.5% t/s) | short-prompt win, decode tax |
| E2 | chunk 16384 | — | — | — | boots; 33.3/34.2 GiB VRAM (0.93 free) → requests hang/OOM |
| E1/E3 | chunk 8192/32768 | — | — | — | PLE registration blew the 600 s engine timeout (see law below) |
| E4 | cache async=1 | — | — | — | doctor: cache-prepare backend requires synchronous single-fill LRU |
| E5/E6 | 96/112 slots | — | — | — | doctor: capacities (48, 64, 80) only; expert-budget [108, 198] rank maxima — 80 is the policy max, prod is at it |
| E7 | MTP depth 6 | 843.1 | 819.6 | 454–489 ms/step (3.7x worse) | depth 4 stays |
Laws from the matrix:
- 4096 is the prefill-chunk ceiling twice over: the engine caps scheduled tokens by the speculative config, and E2's VRAM (0.93 GiB free at 16384) shows bigger chunks OOM at request time. 883–955 tok/s is what this payload shape delivers at long context.
- Unqualified shapes fail mid-request, not at health — E2 and E7 both answered /health 200 and then died streaming.
- PLE registration can exceed the 600 s engine timeout
(
VLLM_PLE_OFFLOAD_READY_TIMEOUT) under page-cache churn — measured 740 s after a bench teardown; a production boot failed on it the same morning (unit "Deactivated successfully", no error in journal — the serve script exits 0 on the wait). Fixed withR9V_PLE_READY_TIMEOUT=1800infrank-archon.env(forwarded by launch.sh2de0357).
Prefix caching (E8/E9, R9V_ENABLE_PREFIX_CACHING=1, same port): zero
VRAM cost (hits reuse freed KV blocks; load VRAM identical to E0),
cold path unchanged (first request ≈ cold bench), reuse hits large — 32k
prompt TTFT 44.2 s → 3.6–4.3 s (9x), 131k 439 s → 6.9 s (63x).
Hit pattern: rounds 1–2 miss, round 3+ hit (identical-prompt and
conversation-growth probes both). For the harness's resend-every-turn
traffic that is a ~40–440 s TTFT win per steady-state turn; the one-turn
miss costs nothing extra. DEPLOYED 2026-09-28 (Frank's go-ahead): the
repo env sets R9V_ENABLE_PREFIX_CACHING=1 (commit 41e9189), verified
on :7770 (--enable-prefix-caching in the container argv, health 200,
live completion answering).
Authoring a new profile (the qualification pipeline)
Order matters at every step:
- Package manifest
packages/models/<pkg>/package.json— target shards- aux artifacts with sha256. Verify every target's byte count AND sha256
after fetch (
curl -fL -C -per shard; never trust a fetch without-f).
- aux artifacts with sha256. Verify every target's byte count AND sha256
after fetch (
- Placement bootstrap from GGUF headers: parse all shards' tensor
tables (a KV-only shard1 is normal for split parts; each part carries its
own tensor table). Per-expert full per layer =
(
ffn_gate_exps+ffn_up_exps+ffn_down_exps)/512; rank slices rank0 = full×416//640, rank1 = full−rank0. Hot counts = floor(measured_budget / per-expert total). Anchor budgets to the MEASURED hot_bytes of the qualified sibling profile, never invented. - Trim + validate:
tools/trim_experts.py --hot-counts N,M, thenexpert_budget.validate_manifest(m)+expert_budget.expert_memory(m, slots, {cache_rank}, runtime=<runtime.json>)(runtime/catalog are keyword-only). Declared hot_bytes must match computed or it raises. - PLE derive (containerized
tools/prepare_ple.pywith--expected-bytes/--expected-type/--expected-shape); setup skips re-derivation when the file exists with matching size. - profile.env: set values BEFORE
source dual-r9700/profile.env(set-first-wins; an empty string does NOT survive:=).
The canonical bootstrap.json wrapper schema is exactly
{schema: "r9v.placement.v1", id, model_package, hardware, artifact_path, artifact_sha256, scope} with hardware: "dual-r9700-32gb-asymmetric"; the
wrapper id MUST equal the id in the profile.json placement descriptor
(setup rejects mismatches: "does not match profile placement"). Never
fabricate qualification metadata — use
scope: "Unranked routing bootstrap only; does not certify fit or headroom".
GGUF parsing laws for the bootstrap: expert tensor names are
blk.0.ffn_up_exps.weight — split on "." gives FOUR parts, layer number is
parts[1] (a 3-part assumption crashes int("")); outer-KV value-type sizes
{0:1,1:1,2:2,3:2,4:4,5:4,6:4,7:1,8:str,9:arr,10:8,11:8,12:8}; tensor byte
extents from consecutive OFFSETS, never a type-size table (mis-buckets tails
silently); the PLE Q8_0 tensor on this arch is shape (160, 320001536),
54,400,261,120 B, an entire shard.
Setup/start: ./r9v setup <alias> --state-dir <dir> --model-dir <dir> --image sha256:<digest> --local-image --reuse-from <sibling-model-dir> --accept-model-license (aux artifacts reuse byte-identical files; F16 vs
Q8 mmproj will not reuse). Start requires the SAME --state-dir; fixed
placement via R9V_EXPERT_MANIFEST_PATH skips headroom/catalog/seed raises.
The host-only doctor FAILs when another instance holds the cards (it wants
3 GiB free per rank; production at ~30 GiB/card is a hard wall) — a second
instance CANNOT coexist, so qualification requires a stop+mask window for
the incumbent, then unmask+restart.
Qualification gate (unqualified → qualified): first start runs the
workload battery (PONG / tool_calls / vision / long-ctx), the PLE pinning
proof (VmLck ≈ PLE bytes in /proc/<container-pid>/status when
RESIDENCY_MODE=pinned), and a VRAM headroom snapshot (floor 3.3 GiB). If
headroom is violated, lower hot counts via re-trim and re-derive
MAX_EFFECTIVE. Only then record state ready: true and author the runbook.
Remote-execution gotchas from the same windows: nohup X & inside an ssh
command races ssh teardown — the script can die before writing a byte; run
long remote scripts synchronously, or setsid + disown, or via a system
unit. zsh login shells abort on unquoted === separators; grep globs need
quoting through ssh+zsh layers.
Ops
- Restart:
sudo systemctl restart frank-archon(root unit; the serve script re-gates everything, relaunch through the shim, waits). - First start after boot re-warms ~175 GiB of page cache; expect
minutes to /health;
TimeoutStartSec=1800. - Scratch/probe: NEVER probe against the prod state dir. Copy it, set
R9V_HOST_PORTto a free port andR9V_CONTAINER_NAMEto a scratch name, and stop the probe before any deploy — a probe holding :7770 produces a false-green deploy (the 2026-09-26 lesson, guarded by the MainPID check). - Rollback to uncensored: point
scripts/frank-archon.envR9V_STATE_FILEback at/home/dank/r9v-state/uncensored/setup.json, drop the two q8-only keys (adapter, FULL_MUTABLE_CACHE) and the template keys, redeploy — the uncensored state dir is intact on disk; the IQ4_XS payload is not (it was deleted in the 2026-10-17 cleanup), so this rollback re-fetches first. - Models on the wire: served alias
frank-primary(the StarCraft wire names arrived 2026-10-09,7c77326); thinking channel isreasoning(vLLM); tool parserqwen3_coder.
Known costs (accepted at cutover)
- Residency-halved experts — Q8_0 doubles expert bytes vs IQ4_XS, so the tiered cache holds half as many experts in VRAM at the same budget; the probe's 11 tok/s decode was residency-bound, and the production number depends on the re-qualified budgets in the q8 state.
- 128K envelope — down from the uncensored 196,608 (which itself was down from 262,144 on llama.cpp).
- Reference quant — back to the unsloth Q8_0 weights (no abliteration); answer-quality profile differs from the uncensored incumbent by design.
- Docker-pinned fork — updates flow through image bundles + the shim, never a bare container run; the launcher stays the single argv owner.