Files
serverAppConfigs/serverAppConfigs-docs/LLM/frank-primary-r9v-radiance-qwen38-q8.md
T

225 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: frank-primary — R9V radiance-vLLM Qwen3.8-Flash-Next Q8_0
service: frank-primary
host: dankserver
port: 7770
status: active
deployed: 2026-09-27
supersedes: "[[frank-primary-r9v-radiance-qwen38-uncensored]]"
tags: [llm, frank-primary, r9v, vllm, radiance, gfx1201, q8]
---
# frank-primary — R9V radiance-vLLM Q8_0 (dankserver, ACTIVE)
The Dankserver LLM endpoint is the **R9V radiance-vLLM fork**
(`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**.
Cut over from the uncensored IQ4_XS payload on 2026-09-27 at Frank's
direction. Rollback target: [[frank-primary-r9v-radiance-qwen38-uncensored]]
— the uncensored state dir (`/home/dank/r9v-state/uncensored/`, `ready:
true`), its frozen config (`restore-config-20260927.env`) and payload stay
on disk; a rollback is an env-file `R9V_STATE_FILE` flip plus a redeploy.
## 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-primary.service` | Root unit, `User=dank`, `LimitMEMLOCK=infinity`, `ExecStop=podman stop -t 120 frank-primary-r9v`, `Restart=on-failure` |
| Env | `scripts/frank-primary.env` | Production overrides ONLY: bind 0.0.0.0, :7770, alias `frank-primary`, container `frank-primary-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-primary.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_primary` | 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-small/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_embedding` module), rides
along automatically; it is not the llama.cpp `ngram-mod` matcher and
cannot be "added" as a second spec method.
- `ngram_gpu` exists 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-large)
`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
1. **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 commit `363d606`) mounts a
dev-overlay adapter that dequantizes a quantized inject shard
in-stream; uncensored payloads pass through unchanged.
2. **`R9V_FULL_MUTABLE_CACHE=0`** — the runtime overlay's `env.always`
injects 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 commits
`363d606` dev-overlay, `2fe6dbb`, `50a79ec` qualification doc,
`30d4a32` chat-template passthrough, `2de0357` PLE-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 six `Q8_0/Qwen3.8-Flash-Next-Q8_0-0000N-of-00006.gguf` shards
+ 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:
1. **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 answers `image inspect sha256:X` from RepoDigests and rewrites
bare `sha256:X` run refs to `repo@sha256:X`.
2. **LogConfig law** — podman inspect answers
`HostConfig.LogConfig.Config: null` where docker returns the log-opts
map (the doctor crashes on `.get("Config", {}) == null`). The shim
records `run --log-opt` pairs and normalizes inspect output.
3. **docker info law** — podman lacks `SecurityOptions`/`ServerVersion`;
the shim answers rootless + `27.5.1` (liveness probe).
- 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`; harness `models.yml`
carries `contextWindow: 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 with `R9V_PLE_READY_TIMEOUT=1800` in
`frank-primary.env` (forwarded by launch.sh `2de0357`).
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).
## Ops
- **Restart**: `sudo systemctl restart frank-primary` (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_PORT` to a free port and `R9V_CONTAINER_NAME` to 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-primary.env` `R9V_STATE_FILE` back 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 and payload are intact on disk.
- **Models on the wire**: served alias `frank-primary`; thinking channel
is `reasoning` (vLLM); tool parser `qwen3_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.