frank-primary cutover: GLM-5.3-Flash W4A16 int4 (Intel AutoRound)

Payload: Intel/GLM-5.3-Flash-W4A16-AutoRound (AutoRound symmetric int4 group
128, auto_round:auto_gptq, 34 shards + 2 extras, 113,074 tensors, ~170 GiB).
It is the only W4A16 checkpoint this fork can serve with expert offload: its
quant_method "auto-round" is overridden to "inc", which routes the MoE to
libr4d's grouped W4A16 GEMM (R4dW4a16MoEMethod). A compressed-tensors W4A16
build of the same model resolves to TritonWNA16Experts, which has no offload
recipe, and refuses to serve at all (170 GiB of experts cannot fit 64 GiB of
VRAM, so offload is mandatory).

Shape: tcclaviger RDNA4 vLLM fork (same digest-pinned image as the 2026-09-29
qwen cutover), TP=2, 131,072 ctx x 2 seqs, bf16 KV (fp8 is ILLEGAL here — it
disables the sparse MLA backend's rope-free Triton path, the only MLA decode
gfx12 has), util 0.98, NBATCH 2048, --enable-expert-offload
--expert-precise-memory, prefix caching, glm47 tool + reasoning parsers,
MTP and expert prefetch absent by measurement.

Measured: decode 9.9-10.0 tok/s, prefill 918 tok/s (warm), arena 14.51 GiB =
2,383 slots/rank over 12,096 host-backed experts (73.64 GiB pinned/rank).
THE 35 tok/s TARGET IS NOT MET and the shortfall is measured: live telemetry
(R4D_LRU_TELEMETRY=1) gives a 0.53-0.66 per-layer hit rate, a step moves
0.82 GiB of misses in ~100 ms = 8.2 GB/s effective against a link measured at
22.74 GB/s — the same structural gather-path gap the qwen campaign found.
Growing the arena 13.08 -> 14.51 GiB changed decode by nothing.

Rejected by measurement: MTP4 (head loads, acceptance 3.76-4.90, but decode
unchanged and prefill collapses 918 -> 255 — a verification forward gathers the
union of the batch's experts), NBATCH 8192 (identical prefill).

Also: scripts/patches/gpu_worker-kv-cap.patch (fork-local fix for a KV-reserve
model that under-counts GLM's per-sequence KDA state; not needed by this shape),
runbook, AGENTS.md row + section, both indexes, work log.

Rollback: 2339fae (qwen FP8 + MTP4).
This commit is contained in:
Frank
2026-09-30 04:22:11 -07:00
parent 2339faeaa4
commit f2bcf84e88
12 changed files with 668 additions and 224 deletions
+52 -76
View File
@@ -24,7 +24,7 @@ merge absorbed the former `vllmScripts` repo, history preserved).
| Target | Service | Runtime | Model | Port |
|--------|---------|---------|-------|------|
| Dankserver | `frank-primary` | R9V radiance-vLLM fork `drwolfen/radiance-vllm-qwen4exp` (docker-pinned image bundle `2dac17a2`, docker→podman shim, root systemd unit) | unsloth Qwen3.8-Flash-Next-GGUF Q8_0 (175.30 GiB, 6 GGUF shards) with the blockfp8 MTP4 head (depth 4) and mmproj, TP=2 tiered experts (80-slot LRU cache; PLE n-gram PINNED in host RAM 54.4 GB; quantized inject-shard dev-overlay adapter), CED off, 131072 ctx single stream, Qwen-Sharp template, tool parser `qwen3_coder`, reasoning channel `reasoning` | 7770 |
| Dankserver | `frank-primary` | tcclaviger RDNA4 vLLM fork (`docker.io/tcclaviger/vllm@sha256:da4029666…`, digest-pinned, plain `podman run`, root systemd unit) | `Intel/GLM-5.3-Flash-W4A16-AutoRound` — AutoRound symmetric int4 g128 (170 GiB, 34 shards, 113,074 tensors), TP=2, expert offload to 73.64 GiB/rank of pinned host rows behind a 14.51 GiB / 2,383-slot VRAM LRU arena, 131072 ctx × 2 seqs, bf16 KV, no MTP, no prefetch; measured 9.9-10.0 tok/s decode / 918 tok/s prefill | 7770 |
| Dankclaw | `frank-large` | gufo `gufo-org/gufo` v0.1.1, the Strix Halo engine, BUILT FROM SOURCE (root systemd unit; links the host ROCm 7.2.4) | Swift-1.5-Qwen3.8-27B-Uncensored UD-Q8_K_XL (27B dense, ABLITERATED/uncensored, 29.3 GiB, SWIFT OPEN LICENSE 1.0) with the z-lab DFlash2 Q4_K_M drafter (adaptive block policy, cap 7) on the gfx1151 iGPU (Strix Halo LPDDR, 114 GiB GTT), 262144 ctx single session, greedy, gufo's compiled-in Qwen renderer (NO Qwen-Sharp), text only | 7770 |
| Dankclaw | `frank-small` | mainline llama.cpp master 37b53fd (root systemd unit) | HauhauCS Gemma4-12B-QAT-Uncensored-Balanced Q4_K_M (BF16 mmproj + MTP assistant sidecar, `draft-mtp,ngram-mod`) on the RTX 3090, 1048576 pool / 4 slots of 262,144, q8_0 KV, the fleet's fastest vision endpoint | 7771 |
@@ -245,7 +245,7 @@ merely wedged kernel usually still answers ARP.
`sudo systemctl stop frank-large && sudo systemctl mask frank-large`
- Dankclaw `frank-small` (root unit, llama.cpp on the 3090):
`sudo systemctl stop frank-small && sudo systemctl mask frank-small`
- Dankserver `frank-primary` (root unit, R9V radiance on the 2x R9700):
- Dankserver `frank-primary` (root unit, tcclaviger vLLM on the 2x R9700):
`sudo systemctl stop frank-primary && sudo systemctl mask frank-primary`
2. Bench.
NOTE: `systemctl mask` FAILS on a relinked unit (the unit file in
@@ -423,86 +423,62 @@ ubatch 2048 OOMs the draft's pp buffer, and why draft depth 4 + poll
Runbook: `serverAppConfigs-docs/LLM/frank-small-gemma4-12b.md`.
Rollback: [[frank-small-qwen38-gsq-rco]].
### frank-primary — R9V radiance-vLLM Qwen3.8-Flash-Next Q8_0 (Dankserver, ACTIVE)
### frank-primary — tcclaviger vLLM, GLM-5.3-Flash W4A16 int4 (Dankserver, ACTIVE)
The **R9V radiance-vLLM fork** (`drwolfen/radiance-vllm-qwen4exp`, docker-pinned
prebuilt image bundle `r9v-qwen38-flash-next-wmma@sha256:2dac17a2…`) serving
the **unsloth Qwen3.8-Flash-Next-GGUF Q8_0** (175.30 GiB, 6 GGUF shards)
with the blockfp8 MTP4 head (depth 4) and the mmproj on **both** Radeon AI
PRO R9700s (gfx1201, BDFs `0000:03:00.0` / `0000:07:00.0`), one stream at
**131,072 ctx**, **TP=2 tiered experts** (80-slot LRU expert cache; PLE
n-gram table PINNED in host RAM, 54.4 GB), the **Qwen-Sharp v22.4.0
template** (via the launcher's `R9V_CHAT_TEMPLATE_FILE` passthrough, fork
`30d4a32`), tool parser `qwen3_coder`, reasoning channel `reasoning`. Cut
over 2026-09-27 from the uncensored IQ4_XS payload (itself cut over from
llama.cpp b11030 the same day) at Frank's direction.
The **tcclaviger RDNA4 vLLM fork** (`docker.io/tcclaviger/vllm@sha256:da4029666…`
— the same digest-pinned image the 2026-09-29 qwen tuning campaign measured)
serving **`Intel/GLM-5.3-Flash-W4A16-AutoRound`**: AutoRound **symmetric int4,
group 128**, packing `auto_round:auto_gptq`, 34 shards + 2 extra safetensors,
113,074 tensors, ~170 GiB — on **both** Radeon AI PRO R9700s (gfx1201), **TP=2**,
**131,072 ctx × 2 seqs**, bf16 KV, util 0.98, NBATCH 2048, prefix caching,
**no MTP**, **no prefetch**. Cut over 2026-09-30 from the qwen FP8 + MTP4 shape,
which itself replaced the R9V radiance fork on 2026-09-29.
**The tuned shape lives on-host, not in the repo.** The q8 profile's setup
run qualified the `R9V_*` config into
`/home/dank/r9v-state/q8xl/setup.json` (`ready: true`); the repo env
file `scripts/frank-primary.env` carries ONLY production overrides
(`R9V_HOST_BIND=0.0.0.0`, `R9V_HOST_PORT=7770`,
`R9V_SERVED_MODEL_NAME=frank-primary`, `R9V_CONTAINER_NAME=frank-primary-r9v`,
`R9V_EXPECTED_GPU_BDFS`) plus two q8-only keys: the dev-overlay adapter
(`R9V_DEV_QWEN4_EXP_ADAPTER_PY`, fork commit `363d606` — the Q8_0 payload
quantizes BOTH fused hyper-connection shards, so the shard-uniformity
check needs the in-stream dequant) and `R9V_FULL_MUTABLE_CACHE=0` (the
overlay's env.always injects the uncensored 160-slot cache geometry, which
the q8 80-slot tiered cache rejects). Never copy tuned values from the
state into the repo — it would drift from the qualification.
**The payload is chosen for the offload path, not for the quant.** 170 GiB of
int4 experts cannot fit 64 GiB of VRAM, so expert offload is mandatory — and
the fork reaches its W4A16 kernel (`R4dW4a16MoEMethod`, libr4d's grouped W4A16
GEMM, 6.234 MiB per expert-rank slot) **only** when the checkpoint's
`quant_method` is `auto-round`, which the fork overrides to `inc`. A
compressed-tensors W4A16 checkpoint of the same model resolves to
`TritonWNA16Experts`, which **has no offload recipe at all**, and refuses to
serve. Built shape: 14.51 GiB arena = **2,383 slots × 6.234 MiB per rank** over
12,096 host-backed experts = **73.64 GiB pinned host rows per rank**.
**Spec decoding is MTP4 only.** The fork's `speculative_config` is a
single dict — one method per engine, NO chaining — so the llama.cpp
`draft-mtp,ngram-mod` pattern is NOT expressible here. MTP4 is wired
(`R9V_MTP_SPEC_TOKENS=4`, draft TP=2); the PLE n-gram rides along
architecturally (`ngram_embedding`); `ngram_gpu` exists as an alternative
single method, not a chainable one.
**Measured 2026-09-30: decode 9.9–10.0 tok/s, prefill 918 tok/s** (warm, 2.6k
prompt), KV cache 138,561 tokens. **The 35 tok/s target is NOT met, and this is
a measured wall rather than a tuning gap:** each decode step must move 2.045 GiB
of routed experts per rank (42 layers × 8 experts × 6.234 MiB), live telemetry
(`R4D_LRU_TELEMETRY=1`) puts the expert-cache hit rate at **0.53–0.66**, and the
fork's gather read path delivers **~8.2 GB/s against a link measured at
22.74 GB/s** — the same structural gap the qwen campaign found. Growing the
arena 13.08 → 14.51 GiB changed decode by *nothing* (10.03 → 9.88 tok/s), so
residency stopped being the binding term; 0.89 hit rate would be needed for 35.
**Serving = the fork's own launcher through a docker→podman shim.** The R9V
tooling is docker-pinned; the shim at
`/home/dank/GitPackages/radiance-vllm-qwen4exp/.podman-shim/docker` carries
three laws: (1) the bundle is a containerd/OCI docker-save, so docker keys
the image by its OCI manifest digest while podman keys 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) podman inspect answers
`HostConfig.LogConfig.Config: null` where docker returns the log-opts map
(the fork's doctor crashes on it) — the shim records `run --log-opt` pairs
and normalizes inspect output; (3) podman lacks docker's `SecurityOptions`
/`ServerVersion` info fields — the shim answers them.
**MTP works here and is still rejected.** The head loads
(`Resolved architecture: Glm5NextMTPModel`) and accepts well (mean accepted
length 3.76–4.90, per-position 0.92/0.85/0.69/0.58, 69–97 %), but decode was
unchanged (9.6–11.2 vs 10.0 tok/s) while prefill **collapsed 918 → 255 tok/s**:
a verification forward gathers the *union* of the batch's experts, so spec
decode amortizes compute, not a residency-bound gather. It also costs 2.6 GiB
of arena. **fp8 KV is illegal on this model** — it disables the sparse MLA
backend's rope-free Triton path, and aiter's MLA decode op is gated on CDNA>2,
so the MTP draft step demands an op that cannot exist on gfx12
(`no attribute 'rocm_aiter_mla_decode_fwd'`).
**Unit and self-healing.** Root systemd unit (`scripts/frank-primary.service`,
`User=dank`, `LimitMEMLOCK=infinity`) — the PLE pinned residency alone is
54.4 GB, and a root unit grants the pin budget via PID1 (a rootless
quadlet would be capped by the user manager's limit).
The serve script (`scripts/serve-frank-primary.sh`) gates the setup state
(ready + paths), the pinned image (RepoDigests), the PLE byte count
(`stat -c %s`), every GGUF shard + the mmproj and the GPU device nodes,
clears a stale container, then execs the fork's `scripts/launch.sh` through
the shim and **blocks on `docker wait`** — the launcher `--detach`es, so
without the wait a crashed engine is a green unit; `docker wait` cannot stop
the container child, hence `ExecStop=podman stop -t 120
frank-primary-r9v`. The deployer's `deploy_llm_primary` gates state/image/
PLE/shards, `podman rm -f`s the container, waits out :7770, relinks/enables/
restarts the unit and health-gates with the MainPID check (the false-green
stray law).
`User=dank`, `LimitMEMLOCK=infinity` — the 73.64 GiB/rank expert pin needs it),
`ExecStop=podman stop -t 120 frank-primary-glm`. The serve script drives
`podman run` **directly** (the image entrypoint execs `vllm serve`), gates the
digest-pinned image, the payload's `config.json` and its exact 34-shard count
and the GPU device nodes, clears the stale container identities, then **blocks
on `podman wait`** so a crash is a nonzero ExecStart. The deployer's
`deploy_llm_primary` gates the same, waits out :7770, relinks/enables/restarts
the unit and health-gates with the MainPID check (the false-green stray law).
Boot cost ~7 min warm (~15 min cold); health returns 503 while loading.
Verified at cutover (2026-09-27): q8 setup state `ready: true`, all 20
payload artifacts sha256-verified (175.30 GiB); the scratch-port bring-up
battery answered vision ("Red" for a 64×64 red PNG), MTP4 mean acceptance
length 3.19–5.00, and the PLE pinned proof (`mapping_bytes =
mapping_rss_bytes = 54,400,261,120`, fully RSS-resident); wire + tool
round-trip + template probe re-verified on :7770 after the deploy. No
throughput bench was run (Frank declined); the probe-era 11 tok/s decode
was residency-bound (halved expert headroom at double the bytes/expert),
not the production number.
Runbook: `serverAppConfigs-docs/LLM/frank-primary-r9v-radiance-qwen38-q8.md`.
Rollback: the uncensored payload — flip `R9V_STATE_FILE` back to
`/home/dank/r9v-state/uncensored/setup.json`, drop the two q8-only keys
and the template keys, redeploy (state + `restore-config-20260927.env` +
payload intact on disk) — see
[[frank-primary-r9v-radiance-qwen38-uncensored]].
Runbook: `serverAppConfigs-docs/LLM/frank-primary-glm53-flash-w4a16.md`.
Rollback: commit `2339fae` (qwen FP8 + MTP4) — restore `scripts/` from it and
deploy; the qwen payload and the R9V state dirs stay on disk.
## Key Lessons Learned
Durable operational lessons from the fleet's history. Full detail in the
@@ -851,7 +827,7 @@ Active:
|---------|---------|
| frank-large (gufo Qwen3.8-27B UD-Q8_K_XL, Dankclaw) | `serverAppConfigs-docs/LLM/frank-large-gufo-qwen38-27b.md` |
| frank-small (llama.cpp Gemma4-12B QAT, Dankclaw) | `serverAppConfigs-docs/LLM/frank-small-gemma4-12b.md` |
| frank-primary (R9V radiance-vLLM Qwen3.8-Flash-Next Q8_0, Dankserver) | `serverAppConfigs-docs/LLM/frank-primary-r9v-radiance-qwen38-q8.md` |
| frank-primary (tcclaviger vLLM, GLM-5.3-Flash W4A16 int4, Dankserver) | `serverAppConfigs-docs/LLM/frank-primary-glm53-flash-w4a16.md` |
| Dankclaw kernel posture (`amd_iommu=off`) | `serverAppConfigs-docs/LLM/dankclaw-iommu-translation-off.md` |
| Deployment flow + incidents | `serverAppConfigs-docs/LLM/deployment.md` |
+4 -3
View File
@@ -1217,8 +1217,8 @@ deploy_llm_small() {
# (FRANK_PRIMARY_MODEL_SHARDS), and the unit env must be complete. Per-file
# sha256 verification was the fetch script's job at download time, never
# re-hashed here.
LLM_PRIMARY_CONTAINER=frank-primary-qwen
LLM_PRIMARY_LEGACY_CONTAINERS="frank-primary-r9v qwen-fp8"
LLM_PRIMARY_CONTAINER=frank-primary-glm
LLM_PRIMARY_LEGACY_CONTAINERS="frank-primary-qwen frank-primary-r9v qwen-fp8 glm-probe"
deploy_llm_primary() {
# Refuse rather than restart-loop: a unit whose image or payload is
@@ -1271,7 +1271,8 @@ deploy_llm_primary() {
# races conmon's --rm cleanup, and the orphan holds the port and GPUs.
# Stop the unit, then remove the containers BEFORE waiting on the port.
# The legacy identities (the retired R9V container and the ad hoc
# experiment container qwen-fp8) hold the same GPUs and die here too.
# experiment container qwen-fp8, the ad hoc glm-probe) hold the same GPUs
# and die here too.
run_privileged systemctl stop frank-primary.service 2>/dev/null || true
local ctr
for ctr in $LLM_PRIMARY_LEGACY_CONTAINERS "$LLM_PRIMARY_CONTAINER"; do
+23 -15
View File
@@ -1,17 +1,24 @@
#!/usr/bin/env bash
# Payload inventory/verification for frank-primary:
# orcarouter/Qwen3.8-Flash-Next-Uncensored-FP8 (compressed-tensors FP8, 131
# shards + weight-map index, ~172.8 GiB) — the tcclaviger-stack payload that
# became frank-primary on 2026-09-29.
# Intel/GLM-5.3-Flash-W4A16-AutoRound (AutoRound symmetric int4, group 128,
# packing "auto_round:auto_gptq": 34 numbered shards + model_extra_conv /
# model_extra_tensors, weight-map index, 113,074 tensors, ~170 GiB) — the
# int4 payload that became frank-primary on 2026-09-30.
#
# bash scripts/download-frank-primary.sh --verify-only (the ONLY mode)
# bash scripts/download-frank-primary.sh --verify-only (the default)
# bash scripts/download-frank-primary.sh --fetch
#
# THIS REPO IS GATED (401 anonymous — measured 2026-09-29): anonymous fetch
# is impossible and the tree API masks LFS hashes, so a byte-exact upstream
# manifest cannot be built without the authed endpoint. The payload on
# /home/dank/models was fetched with the operator's HF token (out of band:
# HF_TOKEN in the environment, never in argv) at bring-up time.
# THIS REPO IS PUBLIC (measured 2026-09-30: the payload fetched anonymously at
# bring-up). The LFS oids are still not verified byte-exactly here — the tree
# API masks them — so integrity rests on the weight-map index, the shard count
# and the loader's own tensor-shape checks at boot.
#
# WHY THIS QUANT: its quantization_config.quant_method is "auto-round", which
# the fork overrides to "inc" — that is the ONLY route to R4dW4a16MoEMethod
# (libr4d's grouped W4A16 GEMM) and therefore to an expert-offload recipe. A
# compressed-tensors W4A16 checkpoint of the same model (e.g.
# dealignai/...-CYBERSECURITY-W4A16) reaches TritonWNA16Experts instead, which
# has no offload recipe, and refuses to serve at all on this host.
#
# Modes:
# --fetch resume any missing/truncated shard via `hf download` with
@@ -24,16 +31,17 @@
# (slow, ~10 min for 173 GiB).
set -euo pipefail
REPO="orcarouter/Qwen3.8-Flash-Next-Uncensored-FP8"
DEST="/home/dank/models/Qwen3.8-Flash-Next-Uncensored-FP8"
EXPECTED_SHARDS=131
REPO="Intel/GLM-5.3-Flash-W4A16-AutoRound"
DEST="/home/dank/models/GLM-5.3-Flash-W4A16-AutoRound"
EXPECTED_SHARDS=34
mode="verify"
[[ "${1:-}" == "--fetch" ]] && mode="fetch"
if [[ "$mode" == "fetch" ]]; then
: "${HF_TOKEN:?--fetch needs HF_TOKEN in the environment (never argv)}"
export HF_TOKEN
# The repo is public, so no token is required; if one is present it is taken
# from the environment and never from argv (a -H header is readable in
# /proc/<pid>/cmdline — the 2026-09-16 leak).
mkdir -p "$DEST"
hf download "$REPO" --local-dir "$DEST"
fi
@@ -81,7 +89,7 @@ if [[ "${2:-}" == "--hash" || "${VERIFY_HASH:-0}" == "1" ]]; then
echo "hashing every shard (slow)..."
cd "$DEST"
sha256sum -c --quiet model.safetensors.index.json 2>/dev/null || \
sha256sum model-*-of-00131.safetensors > /dev/null # presence check; content hash list printed on demand
sha256sum model-*-of-00034.safetensors > /dev/null # presence check; content hash list printed on demand
fi
[[ "$fail" -eq 0 ]] || exit 1
+87 -37
View File
@@ -1,34 +1,53 @@
# frank-primary.env — tcclaviger RDNA4 vLLM production config (dankserver :7770).
#
# 2026-09-29 cutover from the R9V radiance-vLLM fork (its launcher/shim/state
# machinery is retired; /home/dank/r9v-state/{q8xl,uncensored} stay on disk as
# the rollback target, and git tag frank-primary-r9v-q8-20260929 is the last
# pre-cutover commit). The engine is the tcclaviger RDNA4 vLLM fork — the same
# digest-pinned image the 2026-09-29 tuning campaign measured — serving
# orcarouter/Qwen3.8-Flash-Next-Uncensored-FP8 (~172.8 GiB, 131 shards) with
# the MTP4 drafter on the two R9700s.
# 2026-09-30 cutover from orcarouter/Qwen3.8-Flash-Next-Uncensored-FP8 (MTP4) to
# Intel/GLM-5.3-Flash-W4A16-AutoRound, the first payload this fleet has served
# through the fork's W4A16 expert-offload path. ROLLBACK: the pre-cutover qwen
# state is commit 2339fae ("frank-primary: fix the SIGPIPE race in both
# image-digest gates") — restore scripts/ from it and deploy; the qwen payload
# (173 GiB) and the R9V state dirs are untouched on disk.
#
# Every number here was QUALIFIED by boot probe on 2026-09-29 (not inherited):
# * MAXLEN 200000 x SEQS 3 (600k total) at KV q8/q8 fits with headroom:
# the engine measured "GPU KV cache size: 729,545 tokens; Maximum
# concurrency for 200,000 tokens per request: 3.65x" at KV_BYTES 7.2 GiB
# per rank. KV_BYTES is PER RANK — sizing it to the global requirement
# OOMs the worker (measured twice: 16 GiB and 9 GiB both die with ~7.5 GiB
# free; 7.2 GiB boots).
# * --kv-cache-dtype fp8 is the fleet's q8/q8 KV ask (e4m3 for BOTH K and V
# — the stock flag quantizes them together, which is exactly what was
# requested here). Accepted by the attention path; qualified with a
# completion probe.
# * PREFETCH IS ABSENT BY LAW: expert-cache telemetry measured the
# prefetcher at 36% precision (36,720 slabs prefetched, 13,369 served) —
# roughly half the gather traffic was waste; prefetch off is the tuned
# incumbent and the +40-51% decode came from deleting the flag. Never
# re-add --expert-prefetch without a fresh A/B.
# QUALIFIED 2026-09-30 by boot probe + bench against the live engine:
# * THE 35 tok/s TARGET IS NOT MET: decode 9.9-10.0 tok/s, prefill 918 tok/s
# (warm, 2.6k-token prompt) at 131,072 ctx x 2 seqs. See the runbook's
# "Why 35 tok/s is out of reach on this hardware" — the bound is the fork's
# expert-gather read path (8.2 GB/s effective vs the link's 22.74 GB/s),
# the same structural gap the 2026-09-29 qwen campaign measured.
# * Arena 14.51 GiB = 2,383 slots x 6.234 MiB per rank; 12,096 host-backed
# experts per rank, 73.64 GiB pinned. Live per-layer telemetry
# (R4D_LRU_TELEMETRY=1, 2026-09-30): hit rate 0.53-0.66, ~500-730 installs
# vs ~2,000 routed per layer over the window.
# * --expert-precise-memory is ON: it replaces the 6.25 GiB default
# compile-headroom figure with the registry's fitted 5.46 GiB, buying
# 1.43 GiB of cache (13.08 -> 14.51 GiB). Decode measured flat across that
# change (10.03 -> 9.88 tok/s), which is the evidence that the hit rate
# saturates near 0.6 and residency is no longer the binding term.
# * KV dtype stays bf16/AUTO — fp8 KV is NOT legal on this model. It disables
# _use_rocm_sparse_triton() in the ROCM_AITER_MLA_SPARSE backend (the
# predicate requires a non-fp8 cache), and that rope-free Triton path is the
# only MLA decode available on gfx12: aiter's MLA decode op is registered
# under @if_aiter_supported, i.e. gated on CDNA>2. With fp8 KV the MTP draft
# step dies with "'_OpNamespace' 'vllm' object has no attribute
# 'rocm_aiter_mla_decode_fwd'".
# * MTP4 spec decode is ABSENT DELIBERATELY, unlike the qwen shape. It WORKS
# here (the MTP head loads, architecture resolves to Glm5NextMTPModel, and
# acceptance is excellent: mean accepted length 3.76-4.90, per-position
# 0.92/0.85/0.69/0.58, 69-97%) — but it buys nothing on an offload-bound
# deployment: measured decode 9.6-11.2 tok/s (vs 10.0 without) while prefill
# COLLAPSED 918 -> 255 tok/s. The verification forward gathers the union of
# the batch's experts, so spec decode does not amortize a residency-bound
# gather the way it amortizes compute. It also costs 2.6 GiB of arena.
# Reinstate only with a fresh A/B on a non-offload-bound shape.
# * PREFETCH IS ABSENT BY LAW, unchanged from the qwen campaign: expert-cache
# telemetry measured the prefetcher at 36% precision and deleting the flag
# bought +40-51% decode. Never re-add --expert-prefetch without a fresh A/B.
# * TunableOp stays OFF: CLAV_TUNABLEOP_SWEEP silently skips under expert
# offload unless CLAV_TUNABLEOP_WITH_OFFLOAD=1, and forcing it corrupts
# numerics (repetition loops, early halts, invalid measurements).
# Decode at the tuned shape: 40.57/37.44/37.57 tok/s (166/1087/4064 ctx) at
# the 32k window; 42.94/34.55/36.09 at this 200k shape (arena 6,032 slots).
# * KV is NOT capped explicitly: the fork's plan reserve (1.76 GiB at 131,072)
# already covers what max_model_len needs for this shape. It does NOT for an
# MTP shape — see the runbook's fork-bug section and
# scripts/patches/gpu_worker-kv-cap.patch.
#
# HIP reads bare env vars TEMP, HOST, GPU_MAX_HEAP_SIZE — every key here is
# prefixed FRANK_PRIMARY_ so none can leak.
@@ -36,26 +55,57 @@
# --- runtime ----------------------------------------------------------------
# Digest-pinned: the floating :latest tag moved once already. Local alias
# qwen-fp8-runtime:tuned-20260929 (image ID 3322dbbbc96d) points at the same
# bytes in rootless storage.
# bytes in rootless storage. Same image as the qwen cutover — the W4A16 path
# (INC scheme -> R4dW4a16MoEMethod, libr4d grouped W4A16 GEMM) ships in it.
FRANK_PRIMARY_IMAGE=docker.io/tcclaviger/vllm@sha256:da4029666668b90a0386c1ed23ea370c79916ad4e1a6ad1dd9db098b9fa069ea
# --- payload ----------------------------------------------------------------
FRANK_PRIMARY_MODEL_DIR=/home/dank/models/Qwen3.8-Flash-Next-Uncensored-FP8
FRANK_PRIMARY_MODEL_SHARDS=131
# Intel AutoRound W4A16: symmetric int4, group 128, packing "auto_round:auto_gptq".
# quantization_config.quant_method is "auto-round", which the fork overrides to
# "inc" (inc.py:364) — that is what routes the MoE to R4dW4a16MoEMethod instead
# of the compressed-tensors path's Triton experts, which has no offload recipe.
# 34 numbered shards + model_extra_{conv,tensors}.safetensors, 113,074 tensors.
FRANK_PRIMARY_MODEL_DIR=/home/dank/models/GLM-5.3-Flash-W4A16-AutoRound
FRANK_PRIMARY_MODEL_SHARDS=34
# --- identity ---------------------------------------------------------------
# The harness models.yml id stays frank-primary (provider 192.168.0.235:7770).
FRANK_PRIMARY_CONTAINER=frank-primary-qwen
FRANK_PRIMARY_CONTAINER=frank-primary-glm
FRANK_PRIMARY_SERVED_NAME=frank-primary
FRANK_PRIMARY_PUBLISH=7770:7770
# Containers removed before every start: the retired R9V identity and the ad
# hoc experiment identity (both hold the GPUs / name registrations).
FRANK_PRIMARY_LEGACY_CONTAINERS="frank-primary-r9v qwen-fp8"
# Containers removed before every start: the pre-cutover qwen identity (it holds
# the same port and GPUs), the retired R9V identity, and the ad hoc experiment
# identity.
FRANK_PRIMARY_LEGACY_CONTAINERS="frank-primary-qwen frank-primary-r9v qwen-fp8 glm-probe"
# --- qualified engine shape ---------------------------------------------------
FRANK_PRIMARY_MAXLEN=200000
FRANK_PRIMARY_SEQS=3
FRANK_PRIMARY_KV_BYTES=7730934784
FRANK_PRIMARY_KV_DTYPE=fp8
FRANK_PRIMARY_SPEC=4
# 131,072 x 2 seqs. The window is NOT chosen for capacity: the engine's own
# plan reserve is one max_model_len request, and below ~92k tokens the fork's
# reserve model (13,600 B/token + a small mamba term) falls short of what the
# engine actually needs for this model's per-sequence KDA state, which makes the
# boot refuse ("2.07 GiB needed vs 1.74 GiB available"). 131,072 clears it with
# the plan's own cap intact. KV cache as built: 138,561 tokens, 1.06x
# concurrency for a 131,072-token request.
FRANK_PRIMARY_MAXLEN=131072
FRANK_PRIMARY_SEQS=2
# 0.99 fails boot ("Free memory on device cuda:1 ..."); 0.98 leaves the
# compile/capture headroom the plan models.
FRANK_PRIMARY_UTIL=0.98
# Prefill chunk. 2,048 is the qualified value; the expert union a chunk routes
# to saturates quickly, so the chunk size is an install-amortization knob, not a
# residency one. Measured 2026-09-30: NBATCH 8,192 boots (arena 12.51 GiB) and
# leads to the SAME warm prefill (922 vs 918 tok/s) — no reason to pay its
# 2 GiB of profile buffer.
FRANK_PRIMARY_NBATCH=2048
# GLM's own tool-call dialect. Without --enable-auto-tool-choice the endpoint
# rejects tool_choice "auto" outright ("\"auto\" tool choice requires
# --enable-auto-tool-choice and --tool-call-parser to be set"), which breaks
# every tool-using agent on the endpoint. Verified 2026-09-30: a get_weather
# prompt returns tool_calls with finish_reason "tool_calls".
FRANK_PRIMARY_TOOLPARSER=glm47
# GLM's reasoning dialect. Without it the model's thinking leaks into `content`
# (measured: content began with the answer, then "Let me verify: ... = 391"
# followed by a bare "</think") and the reasoning field comes back empty. With
# it, the chain of thought lands in `reasoning` — the vLLM field name the fleet
# documents (llama.cpp endpoints use reasoning_content).
FRANK_PRIMARY_REASONINGPARSER=glm47
+12 -14
View File
@@ -1,26 +1,24 @@
# frank-primary.service — tcclaviger RDNA4 vLLM endpoint (orcarouter
# Qwen3.8-Flash-Next-Uncensored-FP8, MTP4 spec decode, KV q8/q8, 600k total
# context over 3 sessions, TP=2, tiered experts, 131-shard payload) on the
# 2x R9700 pair, dankserver :7770. 2026-09-29 cutover from the R9V radiance
# fork (git tag frank-primary-r9v-q8-20260929 is the pre-cutover state; its
# launcher/shim/setup-state machinery is retired and the state dirs stay on
# disk as rollback).
# frank-primary.service — tcclaviger RDNA4 vLLM endpoint
# (Intel/GLM-5.3-Flash-W4A16-AutoRound int4, 131,072 x 2 seqs, bf16 KV, TP=2,
# expert offload to pinned host RAM, 34-shard payload) on the 2x R9700 pair,
# dankserver :7770. 2026-09-30 cutover from orcarouter
# Qwen3.8-Flash-Next-Uncensored-FP8 + MTP4; commit 2339fae is the pre-cutover
# state and the rollback.
#
# Same port, same served alias; the harness models.yml id is unchanged.
# The serve script drives `podman run` directly (the image entrypoint execs
# `vllm serve`) and then waits on the container, so a crashed engine is a
# nonzero ExecStart and Restart=on-failure is real.
#
# WHY A ROOT UNIT (not a rootless quadlet): the tiered-expert shape pins tens
# of GiB of host RAM (the expert host slabs, ~20.5 GiB per rank of pinned
# anonymous memory). Pinned pages are charged against RLIMIT_MEMLOCK; dank's
# WHY A ROOT UNIT (not a rootless quadlet): the expert-offload shape pins
# 73.64 GiB per rank of host RAM (the expert host rows). Pinned pages are charged against RLIMIT_MEMLOCK; dank's
# login session on this host carries 8 MiB and a user quadlet is capped by the
# user manager's own hard limit — raising that means restarting
# user@1000.service and bouncing every user container on the host. A root unit
# grants LimitMEMLOCK=infinity to the dank process via PID1, and the image
# stays in dank's rootless podman storage.
[Unit]
Description=frank-primary — tcclaviger vLLM Qwen3.8-Flash-Next-Uncensored-FP8 on 2x R9700
Description=frank-primary — tcclaviger vLLM GLM-5.3-Flash int4 W4A16 on 2x R9700
After=network-online.target
Wants=network-online.target
@@ -35,9 +33,9 @@ Environment="FRANK_PRIMARY_ENV_FILE=/home/dank/serverAppConfigs/scripts/frank-pr
ExecStart=/home/dank/serverAppConfigs/scripts/serve-frank-primary.sh
# systemd stop: wait alone cannot stop the container child; stop it
# explicitly. vLLM 0.26 drains gracefully on SIGTERM.
ExecStop=/usr/bin/podman stop -t 120 frank-primary-qwen
# Boot cost: FP8 payload page-in plus torch compile/graph capture —
# ~15 min cold (measured 2026-09-29), faster warm.
ExecStop=/usr/bin/podman stop -t 120 frank-primary-glm
# Boot cost: int4 payload page-in plus torch compile/graph capture —
# ~7 min measured 2026-09-30 on a warm page cache, longer cold.
TimeoutStartSec=1800
Restart=on-failure
RestartSec=10
+65
View File
@@ -0,0 +1,65 @@
# gpu_worker-kv-cap.patch — fork-local fix for the expert-offload KV cap.
#
# STATUS: NOT part of the deployed frank-primary shape. It exists because the
# fork's plan under-models this model's KV, and it is the enabler for a future
# MTP attempt (the production GLM shape runs without MTP and boots fine on the
# plan's own reserve).
#
# THE BUG: the plan models KV as 13600 B/token + a small mamba term, but
# GLM-5.3-Flash also carries a per-sequence KDA state (34 linear-attention
# layers) and, under MTP, the draft model's KV. gpu_worker then CAPS the KV
# cache at that reserve, so the plan's own arithmetic can make a legal
# --max-model-len unbootable. Measured 2026-09-30 on the pinned image:
# - no MTP, max_model_len 131072: reserve 1.74 GiB >= need 1.86 GiB -> boots
# - MTP4, max_model_len 131072: reserve 1.74 GiB < need 2.07 GiB -> refuses
# - MTP4, max_model_len 262144: reserve 3.43 GiB < need 3.62 GiB -> refuses
# ("To serve at least one request with the model's max seq len ...")
# Raising max_model_len does not fix it: the cap's slope (~13,845 B/token) only
# overtakes the need's (~12,700 B/token + 0.52 GiB fixed) around 440k tokens,
# where the KV reserve would eat a third of the expert arena.
#
# THE FIX: let an explicit operator KV request raise the cap. The compile and
# graph-capture headroom still comes off the top, so the blast radius is one
# flag the operator chose.
#
# APPLY (into a host dir that is then bind-mounted read-only over the image):
# IMAGE=docker.io/tcclaviger/vllm@sha256:da4029666668b90a0386c1ed23ea370c79916ad4e1a6ad1dd9db098b9fa069ea
# D=/home/dank/glm-patch; mkdir -p $D/vllm/v1/worker
# podman run --rm --entrypoint cat $IMAGE \
# /opt/vllm/lib/python3.14/site-packages/vllm/v1/worker/gpu_worker.py \
# > $D/vllm/v1/worker/gpu_worker.py
# ( cd $D && patch -p1 < .../scripts/patches/gpu_worker-kv-cap.patch )
# # then add to the podman run:
# # -v $D/vllm/v1/worker/gpu_worker.py:/opt/vllm/lib/python3.14/site-packages/vllm/v1/worker/gpu_worker.py:ro
# # -e CLAV_KV_RESERVE_FLOOR_GIB=<needed> (or pass --kv-cache-memory-bytes)
#
# Base file sha256 (the pristine file out of the pinned image):
# 1f3942b1a01af075317161b06077a2e658086f1da0566c9f297129ceebe37dd7
# Patched file sha256 (verified 2026-09-30, patch applies clean and reproduces
# these bytes exactly):
# 80cd160bbc01270b0851f7b6fe7552b06ae2fae2428134fc29238d7f452af840
#
--- a/vllm/v1/worker/gpu_worker.py
+++ b/vllm/v1/worker/gpu_worker.py
@@ -824,6 +824,21 @@
expert_cfg = self.vllm_config.offload_config.expert
if expert_cfg.enable_expert_offload and expert_cfg.planned_kv_reserve_bytes > 0:
cap = int(expert_cfg.planned_kv_reserve_bytes)
+ # LOCAL PATCH (GLM-5.3 KDA + MTP draft): the plan models KV as
+ # 13600 B/token + a small mamba term, but this model also carries a
+ # per-sequence KDA state and (under MTP) the draft's KV, so the
+ # reserve can sit BELOW what max_model_len legitimately needs -- and
+ # the cap then makes a legal config unbootable (measured: need
+ # 2.07 GiB vs cap 1.74 GiB at max_model_len 131072 with MTP). An
+ # explicit --kv-cache-memory-bytes, or CLAV_KV_RESERVE_FLOOR_GIB,
+ # is the operator's word: let it raise the cap. The compile/capture
+ # headroom still comes off the top.
+ _explicit = self.cache_config.kv_cache_memory_bytes
+ _floor = os.environ.get("CLAV_KV_RESERVE_FLOOR_GIB")
+ if _explicit:
+ cap = max(cap, int(_explicit))
+ if _floor:
+ cap = max(cap, int(float(_floor) * (1 << 30)))
if self.available_kv_cache_memory_bytes > cap:
logger.info_once(
"expert offload: KV capped at the plan's reserve %s GiB; %s GiB of "
+39 -22
View File
@@ -1,27 +1,30 @@
#!/usr/bin/env bash
# serve-frank-primary.sh — frank-primary endpoint (tcclaviger RDNA4 vLLM fork,
# orcarouter Qwen3.8-Flash-Next-Uncensored-FP8 + MTP4 spec decode, 2x R9700,
# dankserver :7770; 2026-09-29 cutover from the R9V radiance fork — git tag
# frank-primary-r9v-q8-20260929 is the pre-cutover state).
# Intel/GLM-5.3-Flash-W4A16-AutoRound, 2x R9700, dankserver :7770).
#
# 2026-09-30 cutover from orcarouter Qwen3.8-Flash-Next-Uncensored-FP8 + MTP4.
# Rollback: commit 2339fae carries the whole pre-cutover scripts/ tree; restore
# it and deploy. The qwen payload and the R9V state dirs stay on disk.
#
# The engine is driven by `podman run` DIRECTLY: the image's entrypoint execs
# `vllm serve "$@"`, so the run line below IS the engine argv. This replaces
# the R9V launcher/shim machinery (that fork needed a docker->podman shim and
# a 104-key on-host setup state; this stack is fully expressed by flags, and
# every flag here was qualified by boot probe on 2026-09-29 — see
# frank-primary.env for the qualification record).
# `vllm serve "$@"`, so the run line below IS the engine argv.
#
# Shape: MAXLEN 200000 x 3 sessions (600k total), KV q8/q8 (--kv-cache-dtype
# fp8: e4m3 for both K and V — the stock flag quantizes them together, which
# is the requested q8/q8), KV_BYTES 7.2 GiB PER RANK (the engine measured
# 729,545 tokens of capacity / 3.65x concurrency at 200k per request), MTP
# depth 4, prefix caching, expert prefetch OFF (telemetry measured it at 36%
# precision — the tuned incumbent deletes it), TunableOp off.
# Why this payload runs the offload path at all: 170 GiB of int4 experts cannot
# fit in 64 GiB of VRAM, so the engine keeps 12,096 experts per rank in pinned
# host memory and serves them through a 2,383-slot VRAM LRU arena. The MoE
# kernel is libr4d's grouped W4A16 GEMM (R4dW4a16MoEMethod), reached because the
# checkpoint's quant_method "auto-round" is overridden to "inc" by the fork.
#
# Shape: MAXLEN 131,072 x 2 seqs, bf16 KV, util 0.98, MTP OFF, prefetch OFF.
# Measured 2026-09-30: decode 9.9-10.0 tok/s, prefill 918 tok/s warm. The
# 35 tok/s target is NOT met — the bound is the fork's expert-gather read path
# (8.2 GB/s effective vs the link's 22.74 GB/s); see frank-primary.env and the
# runbook's arithmetic section.
#
# The root unit (scripts/frank-primary.service, User=dank,
# LimitMEMLOCK=infinity) grants the pin budget for the tiered-expert host
# slabs (~20.5 GiB/rank of pinned anonymous memory). A plain shell session
# also works, but the unit is the fleet-parity boot-persistent shape.
# LimitMEMLOCK=infinity) grants the pin budget for the 73.64 GiB/rank of pinned
# host expert rows. A plain shell session also works, but the unit is the
# fleet-parity boot-persistent shape.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -44,8 +47,9 @@ set +a
: "${FRANK_PRIMARY_PUBLISH:?FRANK_PRIMARY_PUBLISH must be host:container}"
: "${FRANK_PRIMARY_MAXLEN:?FRANK_PRIMARY_MAXLEN must be set}"
: "${FRANK_PRIMARY_SEQS:?FRANK_PRIMARY_SEQS must be set}"
: "${FRANK_PRIMARY_KV_BYTES:?FRANK_PRIMARY_KV_BYTES must be set (PER RANK)}"
: "${FRANK_PRIMARY_KV_DTYPE:?FRANK_PRIMARY_KV_DTYPE must be set (fp8 = q8/q8 KV)}"
: "${FRANK_PRIMARY_NBATCH:?FRANK_PRIMARY_NBATCH must be set}"
: "${FRANK_PRIMARY_TOOLPARSER:?FRANK_PRIMARY_TOOLPARSER must be set}"
: "${FRANK_PRIMARY_REASONINGPARSER:?FRANK_PRIMARY_REASONINGPARSER must be set}"
# Capture podman's output BEFORE grepping: under `set -o pipefail`, the
# classic `podman images | grep -q` pattern races — grep exits on its first
# match, podman dies SIGPIPE (141) writing the remaining lines, and pipefail
@@ -106,14 +110,27 @@ podman run -d --name "$FRANK_PRIMARY_CONTAINER" \
--tensor-parallel-size 2 \
--max-model-len "$FRANK_PRIMARY_MAXLEN" \
--max-num-seqs "$FRANK_PRIMARY_SEQS" \
--max-num-batched-tokens "$FRANK_PRIMARY_NBATCH" \
--gpu-memory-utilization "$FRANK_PRIMARY_UTIL" \
--kv-cache-memory-bytes "$FRANK_PRIMARY_KV_BYTES" \
--kv-cache-dtype "$FRANK_PRIMARY_KV_DTYPE" \
--enable-expert-offload \
--expert-precise-memory \
--enable-prefix-caching \
--speculative-config "{\"method\":\"mtp\",\"num_speculative_tokens\":$FRANK_PRIMARY_SPEC}" \
--enable-auto-tool-choice \
--tool-call-parser "$FRANK_PRIMARY_TOOLPARSER" \
--reasoning-parser "$FRANK_PRIMARY_REASONINGPARSER" \
--trust-remote-code \
> "$LOG" 2>&1
# NOT passed, deliberately (each measured on this payload — see the env file):
# --kv-cache-dtype fp8 disables the sparse MLA backend's rope-free
# Triton path, which is the only MLA decode on
# gfx12 (aiter's MLA op is gated on CDNA>2).
# --speculative-config MTP head loads and accepts well, but buys zero
# decode and collapses prefill on an
# offload-bound shape.
# --expert-prefetch* 36% precision; deleting it bought +40-51%.
# --kv-cache-memory-bytes the plan's own reserve covers this shape.
# From here on, this script's lifetime IS the container's: wait returns the
# container's exit code, so a crash is a nonzero ExecStart and the unit's
# Restart=on-failure is real. (systemd stop goes through ExecStop in the
@@ -0,0 +1,224 @@
---
type: runbook
title: frank-primary — GLM-5.3-Flash W4A16 int4 (tcclaviger, 2x R9700)
description: VERIFIED 2026-09-30 by boot probe, bench and live expert-cache telemetry. frank-primary cut over from Qwen3.8-Flash-Next-Uncensored-FP8 + MTP4 to Intel/GLM-5.3-Flash-W4A16-AutoRound, the first payload this fleet serves through the fork's W4A16 expert-offload path (INC scheme -> R4dW4a16MoEMethod, libr4d grouped W4A16 GEMM). Measured decode 9.9-10.0 tok/s and prefill 918 tok/s at 131,072 ctx. THE 35 tok/s TARGET IS NOT MET and this page shows why it is out of reach: the step is bounded by the fork's expert-gather read path at ~8.2 GB/s effective against a PCIe link that delivers 22.74 GB/s, with the cache at its measured hit-rate ceiling (0.53-0.66 per layer) from an arena that cannot grow past ~15 GiB under a 73.64 GiB/rank expert set.
tags:
- dankserver
- r9700
- gfx1201
- glm5.3
- w4a16
- int4
- expert-offload
- expert-cache
- autoround
- vllm
timestamp: 2026-09-30
---
# frank-primary — GLM-5.3-Flash W4A16 int4 (tcclaviger, 2x R9700)
> **Question this page answers.** Can the fleet serve the 320B-class
> `GLM-5.3-Flash` at int4 on two R9700s with expert offload, and how fast is it?
> **Answer.** Yes — it serves, with vision, tool calls and 131,072 ctx. It runs
> at **9.9-10.0 tok/s decode / 918 tok/s prefill**, and **35 tok/s is not
> reachable** with this payload on this hardware. The bound is not the model,
> the quant, or the link: it is the fork's expert-gather read path, and it is
> measured, not inferred.
Related: [[dankserver-qwen38-fp8-expert-offload-tuning]] (the same fork, the
same hardware, the same gather-path wall, found first on qwen),
[[dankserver-frontier-moe-feasibility]] (the earlier fit analysis),
[[VLLM Index]], [[LLM/index|LLM fleet]].
## The deployed shape
| term | value |
| --- | --- |
| payload | `Intel/GLM-5.3-Flash-W4A16-AutoRound` — AutoRound **symmetric int4, group 128**, packing `auto_round:auto_gptq`, 34 shards + 2 extra safetensors, 113,074 tensors, ~170 GiB |
| runtime | `docker.io/tcclaviger/vllm@sha256:da4029666…` (same digest-pinned image as the qwen cutover) |
| engine | TP=2, `--enable-expert-offload --expert-precise-memory`, max-model-len 131,072 x 2 seqs, bf16 KV, util 0.98, NBATCH 2048, prefix caching |
| MoE kernel | `R4dW4a16MoEMethod` → libr4d grouped W4A16 GEMM, 6.234 MiB per expert-rank slot |
| expert cache | 14.51 GiB = **2,383 slots** per rank; 12,096 host-backed experts, **73.64 GiB pinned per rank** |
| unit | `scripts/frank-primary.service` (root unit, `LimitMEMLOCK=infinity` for the pin) |
| measured | **decode 9.9-10.0 tok/s**, **prefill 918 tok/s** (warm, 2.6k-token prompt), KV cache 138,561 tokens |
```mermaid
graph LR
A[Intel AutoRound W4A16<br/>sym int4 g128, auto_gptq] -->|quant_method auto-round| B[inc.py override to inc]
B --> C[inc_wna16_scheme<br/>_resolve_gptq_moe]
C -->|sym int4 g128 + gfx1201 + libr4d| D[R4dW4a16MoEMethod<br/>grouped W4A16 GEMM]
D --> E[ExpertLRUCache<br/>2,383 VRAM slots / rank]
D --> F[73.64 GiB pinned host rows / rank]
E --> G[decode step]
F -->|8.2 GB/s effective| G
```
## Why this payload and not another
The W4A16 fast path on this fork is reachable **only** through the INC
(AutoRound) quant method. `compressed_tensors` checkpoints of the same model —
for example `dealignai/GLM-5.3-Flash-CYBERSECURITY-W4A16`, 182 GiB — resolve
their MoE through the WNA16 oracle to `TritonWNA16Experts`, which **has no
offload recipe**, and the engine refuses to serve at all: 170 GiB of experts
cannot fit 64 GiB of VRAM, so offload is mandatory, not optional.
The chain that makes Intel's checkpoint work, with the exact evidence:
```
[expert_plan.py] quantization=inc ← inc.py:364 overrides "auto-round"
[inc_wna16_scheme.py:249] INC GPTQ MoE on ROCm: R4dW4a16MoEExperts (libr4d grouped W4A16 GEMM)
[r4d_w4a16_moe.py:682] Using MoEPrepareAndFinalizeNoDPEPModular / R4dW4a16MoEExperts
[expert.py:450] Expert offload: 42 MoE layers staged; 2,383 cache slots in 14.51 GiB planned per rank
[expert.py:413] 42 layers split: 0 VRAM-only experts, 2,383 swap slots, arena 14.51 GiB,
host 73.64 GiB pinned, host rows prepacked on the GPU
```
`R4dW4a16MoEExperts` is what the offload machinery wraps: the offload plan is
format-agnostic but requires the experts class to expose the hot/cold split.
## The numbers, and the wall
Measured on the deployed engine, 2026-09-30 (`bench_glm.py`, real prose, greedy):
| depth | prefill | decode |
| --- | --- | --- |
| 2,621 tok prompt, run 1 (cold cache) | 259 tok/s | 9.88 |
| 2,621 tok prompt, runs 2-3 (warm) | 918 / 885 tok/s | 9.91 / 9.41 |
| 493 tok prompt (telemetry window) | 124 tok/s | 8.90 |
Decode is flat across context and across every arena change — the signature of a
per-step cost that is not KV- or residency-driven at the margin.
### The arithmetic that bounds it
Per decode step, per rank: 42 MoE layers x 8 routed experts x 6.234 MiB =
**2.045 GiB of expert weights**, which must either be in VRAM or cross PCIe.
| hit rate | miss traffic | step @ 8.2 GB/s | decode |
| --- | --- | --- | --- |
| 0.60 (measured) | 0.82 GiB | ~105 ms | **9.5-10.0 tok/s** |
| 0.70 | 0.61 GiB | ~78 ms | 12.8 tok/s |
| 0.89 (what 35 tok/s needs) | 0.23 GiB | ~29 ms | 35 tok/s |
Two independent measurements pin the missed term:
- **Live expert-cache telemetry** (`R4D_LRU_TELEMETRY=1`, profiler window)
gives the hit rate directly — the LRU is *working*:
`layers.34: slots 56 (pinned 0, tier 0) hit 0.6279 routed 2002 installs 543
read-through 202` … `layers.44: hit 0.5335`. Per-layer range **0.53-0.66**.
- **The link delivers 22.74 GB/s** at the engine's own slab size (H2D probe,
pinned, 2.345 MiB). The step moves 0.82 GiB in ~100 ms = **8.2 GB/s —
36% of the wire**, matching the fork's own measured 6.5-9.5 GB/s.
So the step is bounded by the fork's gather read path, and the only other lever
is the hit rate — which is capped by the arena. A 14.51 GiB arena holds 20% of a
73.64 GiB expert set; the measured hit rate beats random by ~3x thanks to
routing locality, but it saturates: growing the arena 13.08 → 14.51 GiB
(+9%) changed decode by **nothing** (10.03 → 9.88 tok/s). Reaching 0.89 would
need roughly 4.5x the per-layer slot count — an arena of well over 100 GiB.
## Measured arms (all 2026-09-30, one boot each)
| arm | decode | prefill | verdict |
| --- | --- | --- | --- |
| baseline int4, MAXLEN 131,072, util 0.92, NBATCH 2048 | 10.03 / 10.08 / 9.41 | 877 / 878 / 885 | qualified incumbent |
| + `--expert-precise-memory` (arena 13.08 → 14.51 GiB) | 9.88 / 9.91 | 918 | **neutral** — proves residency is no longer the binding term |
| + MTP4 spec decode (draft loads, acceptance excellent) | 9.64-11.16 | **255** | **rejected** — see below |
| + MTP4 + explicit `--kv-cache-memory-bytes` (patched cap) | boots, 151,767-token KV | — | enabler for the MTP arm only |
### Why MTP is rejected here (it is not that it fails)
MTP **works** on GLM in this fork: the head loads, the architecture resolves to
`Glm5NextMTPModel`, and acceptance is excellent —
```
SpecDecoding metrics: Mean acceptance length: 3.83-4.90,
Per-position acceptance rate: 0.917/0.833/0.583/0.500 … 0.962/0.846/0.692/0.615,
Avg Draft acceptance rate: 69-97%
```
— and it buys **nothing**: decode 9.6-11.2 tok/s against 10.0 without it, while
prefill **collapses 918 → 255 tok/s**. The reason is structural to offload: a
verification forward gathers the **union** of the batch's experts, so spec
decode amortizes compute but not a residency-bound gather. It also costs 2.6 GiB
of arena. On a compute-bound (fully-resident) shape this would invert; here it
does not.
## Served surface (verified 2026-09-30 against the live engine)
| capability | state | evidence |
| --- | --- | --- |
| `/v1/models` | `glm53-flash`, max_model_len 131,072 | model list probe |
| completion | yes | `bench_glm.py` runs above |
| **tool calls** | **yes** — needs `--enable-auto-tool-choice --tool-call-parser glm47` | a `get_weather` prompt returns `tool_calls: [{"id": "chatcmpl-tool-…", "type": "function", "function": {"name": "get_weather", "arguments": "{\"city\": \"Seattle\"}"}}]`, `finish_reason: tool_calls` |
| **vision** | **yes** | a 64x64 solid-red PNG is answered "The image appears to be a solid red color…" |
| reasoning channel | **yes** — needs `--reasoning-parser glm47` | without it the thinking leaks into `content` (measured: content began with the answer, then "Let me verify: … = 391", then a bare `</think`) with an empty reasoning field; with it, a train-time prompt answers in `content` ("**Step 1: Add the hours.** 14:20 + 3 hours = 17:20 …") and the chain of thought lands in **`reasoning`** — the vLLM field name the fleet documents (llama.cpp endpoints use `reasoning_content`) |
Without the tool flags the engine answers **HTTP 400** — `"auto" tool choice
requires --enable-auto-tool-choice and --tool-call-parser to be set` — which
breaks every tool-using agent on the endpoint. Both parsers are `glm47`, the
newest GLM dialect the fork ships (`glm45`/`glm47` exist for both).
## Fork traps found (for the engine authors)
1. **`--kv-cache-dtype fp8` is illegal on gfx12 with this model.** The
`ROCM_AITER_MLA_SPARSE` backend's `_use_rocm_sparse_triton()` requires a
non-fp8 KV cache, and that rope-free Triton path is the **only** MLA decode
available on gfx12: aiter's MLA decode op is registered under
`@if_aiter_supported`, which is gated on **CDNA > 2**
(`is_aiter_found_and_supported()` → `get_cdna_version() > 2`). With fp8 KV
the MTP draft step demands an op that cannot exist here:
`RuntimeError: Worker failed with error ''_OpNamespace' 'vllm' object has no
attribute 'rocm_aiter_mla_decode_fwd''`. KV dtype therefore stays auto/bf16.
(`VLLM_ROCM_USE_AITER_MLA=1` does **not** help — the umbrella gate is the
CDNA check, not the env var.)
2. **The plan's KV reserve under-models this model, and the cap enforces the
mistake.** The plan charges 13,600 B/token + a small mamba term, but GLM
carries a per-sequence KDA state (34 linear-attention layers) and, under MTP,
the draft's KV. `gpu_worker` then *caps* KV at that reserve, so a legal
`--max-model-len` becomes unbootable:
`ValueError: To serve at least one request with the model's max seq len
(131072), (2.07 GiB KV cache is needed, which is larger than the available KV
cache memory (1.74 GiB))`. Raising `max_model_len` only helps past ~440k
tokens (the cap's slope is ~13,845 B/token vs the need's ~12,700 + 0.52 GiB
fixed) — where the reserve would eat a third of the arena.
`scripts/patches/gpu_worker-kv-cap.patch` lets an explicit operator KV
request raise the cap; **it is not part of the deployed shape**, which boots
on the plan's own reserve.
3. **A window is forced, not chosen.** The production `max_model_len 131072` is
what the reserve model requires for this payload (below ~92k the boot
refuses); it is not a capacity decision.
4. **Expert prefetch remains poison** — unchanged from the qwen campaign
(36% precision, +40-51% decode from deleting it). No GLM arm re-opened it.
## Operations
```bash
# rollback to the pre-cutover qwen FP8 + MTP4 shape (payload and R9V state
# dirs are untouched on disk):
git show 2339fae -- scripts/ | git apply # or: git checkout 2339fae -- scripts/
./scripts/deploy.sh dankserver --service frank-primary
# payload inventory (34 shards + 2 extras, weight-map index):
bash scripts/download-frank-primary.sh --verify-only
# live telemetry (needs the profiler window; with_stack=false is mandatory —
# with stack the stop path hangs and the trace buffer grows to tens of GB):
podman logs frank-primary-glm 2>&1 | grep "expert_lru.py"
# boot: ~7 min warm page cache, longer cold; health returns 503 while loading
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:7770/health
```
Boot gate: the engine refuses a legal-looking shape in two measured ways — the
KV cap above, and the profile-run activation peak (`TritonMLA` indexer scores:
64 heads x 2,048 topk x NBATCH tokens x 2 B = 2.06 GiB at NBATCH 8192), which is
what killed the first two boots at util 0.95.
## Rollback targets
| state | where |
| --- | --- |
| qwen FP8 + MTP4 (2026-09-29 → 09-30) | commit `2339fae`, payload `/home/dank/models/Qwen3.8-Flash-Next-Uncensored-FP8` |
| R9V radiance Qwen3.8-Flash-Next Q8_0 | [[frank-primary-r9v-radiance-qwen38-q8]], state dirs `/home/dank/r9v-state/{q8xl,uncensored}` |
+33 -24
View File
@@ -1,10 +1,13 @@
# LLM Fleet — Knowledge Base
Documentation for the LLM inference fleet on the two hosts (2026-09-27):
**Dankserver runs R9V radiance-vLLM on frank-primary** (the drwolfen fork
serving the unsloth Qwen3.8-Flash-Next Q8_0 + MTP4 across both Radeon AI
PRO R9700s on :7770, docker-pinned runtime driven through a docker→podman
shim, the Qwen-Sharp template, 131072 ctx), and Dankclaw runs two llama.cpp endpoints —
Documentation for the LLM inference fleet on the two hosts (2026-09-30):
**Dankserver runs the tcclaviger RDNA4 vLLM fork on frank-primary** (the
digest-pinned `tcclaviger/vllm` image driving `podman run` directly, serving
Intel/GLM-5.3-Flash-W4A16-AutoRound — symmetric int4 group 128 — across both
Radeon AI PRO R9700s on :7770, TP=2 with the expert set offloaded to 73.64 GiB
of pinned host RAM per rank behind a 14.51 GiB VRAM LRU arena; 131,072 ctx,
bf16 KV, vision and tool calls on, measured 9.9-10.0 tok/s decode), and
Dankclaw runs two llama.cpp endpoints —
stock llama.cpp (unsloth b11030, ROCm gfx1151; frank-large, unsloth
Qwen3.8-27B UD-Q8_K_XL + the z-lab DFlash2 Q4_K_M drafter on the gfx1151
iGPU, port 7770) and mainline llama.cpp (frank-small, HauhauCS Gemma4-12B
@@ -22,11 +25,11 @@ graph LR
subgraph Dankserver[AMD Dankserver - Ubuntu 26.04]
A[2x Radeon AI PRO R9700
gfx1201, 32 GiB each]
B2[R9V radiance-vLLM fork, docker-pinned
via docker-podman shim
frank-primary, port 7770]
C[Qwen3.8-Flash-Next Q8_0
TP=2 tiered experts, MTP4, sharp template, 131072 ctx, vision]
B2[tcclaviger RDNA4 vLLM fork, digest-pinned
podman run, frank-primary, port 7770]
C[GLM-5.3-Flash W4A16 int4
TP=2, expert offload 73.64 GiB/rank pinned,
14.51 GiB LRU arena, 131072 ctx, vision, tools]
end
subgraph Dankclaw[AMD Strix Halo - Ubuntu 26.04]
@@ -58,20 +61,26 @@ frank-small, port 7771]
## Active Deployment
- [[frank-primary-r9v-radiance-qwen38-q8]] — the ACTIVE frank-primary
runbook (2026-09-27, root systemd unit, port 7770): the R9V
radiance-vLLM fork (drwolfen/radiance-vllm-qwen4exp, docker-pinned
image bundle `2dac17a2`, driven through a docker→podman shim) serving
the unsloth Qwen3.8-Flash-Next-GGUF Q8_0 (175.30 GiB, 6 shards) +
blockfp8 MTP4 (depth 4) + vision on BOTH R9700s — TP=2 tiered experts
(80-slot LRU cache), PLE n-gram PINNED in host RAM (54.4 GB), CED off,
131072 ctx, the Qwen-Sharp v22.4.0 template via the launcher's
`R9V_CHAT_TEMPLATE_FILE` passthrough (fork `30d4a32`), the quantized
inject-shard dev-overlay adapter (fork `363d606`), reasoning channel
`reasoning`, parser `qwen3_coder`. The tuned shape lives in the
on-host q8xl setup state. Rollback:
[[frank-primary-r9v-radiance-qwen38-uncensored]]
- [[frank-primary-r9v-radiance-qwen38-uncensored]] — RETIRED 2026-09-27
- [[frank-primary-glm53-flash-w4a16]] — the ACTIVE frank-primary runbook
(2026-09-30, root systemd unit, port 7770): the tcclaviger RDNA4 vLLM fork
(digest-pinned `tcclaviger/vllm@sha256:da4029666…`, plain `podman run`,
no shim) serving Intel/GLM-5.3-Flash-W4A16-AutoRound — AutoRound symmetric
int4 group 128, ~170 GiB, 34 shards + 2 extras, 113,074 tensors — on BOTH
R9700s, TP=2, with the MoE experts offloaded to pinned host RAM (12,096
experts/rank, 73.64 GiB) behind a 14.51 GiB / 2,383-slot VRAM LRU arena; the
MoE kernel is libr4d's grouped W4A16 GEMM (`R4dW4a16MoEMethod`, reached
because the checkpoint's `quant_method: auto-round` is overridden to `inc` —
a compressed-tensors W4A16 checkpoint of the same model has NO offload recipe
and refuses to serve). 131,072 ctx x 2 seqs, bf16 KV, util 0.98, prefix
caching, vision and `glm47` tool/reasoning parsers, MTP OFF and prefetch OFF
by measurement. **Measured decode 9.9-10.0 tok/s, prefill 918 tok/s — the
35 tok/s target is NOT met, bounded by the fork's expert-gather read path
(~8.2 GB/s effective vs a 22.74 GB/s link) at a 0.53-0.66 cache hit rate.**
Rollback: commit `2339fae` (the qwen FP8 + MTP4 shape of 2026-09-29 → 09-30)
- [[frank-primary-r9v-radiance-qwen38-q8]] — RETIRED 2026-09-30 (two swaps
back; payload and R9V state dirs intact on disk, git tag
frank-primary-r9v-q8-20260929 is the last pre-cutover commit):
[[frank-primary-r9v-radiance-qwen38-uncensored]] — RETIRED 2026-09-27
(same-day swap to the Q8_0 payload; rollback target, state + payload
intact on disk): the R9V radiance-vLLM fork serving
Dyluhn/Qwen3.8-Flash-Next-Uncensored-R9V-IQ4_XS (92.39 GiB, 3 shards) +
+12
View File
@@ -169,6 +169,18 @@ The LLM inference fleet on the two hosts. Full index:
concurrency, ngram-fork-bug); the residual gap to ~60 tok/s is the
fork's gather read path (6.5-9.5 GB/s effective vs 22.7 at the same
slab size)
- [[frank-primary-glm53-flash-w4a16]] — VERIFIED 2026-09-30: the ACTIVE
frank-primary runbook. The cutover to Intel/GLM-5.3-Flash-W4A16-AutoRound
(symmetric int4 g128) on the tcclaviger fork, and the reason the int4
AutoRound payload is the ONLY one that serves here (its `auto-round` quant
method routes to the W4A16 offload kernel; a compressed-tensors W4A16
checkpoint of the same model reaches TritonWNA16Experts, which has no
offload recipe). **Measured decode 9.9-10.0 tok/s / prefill 918 tok/s; the
35 tok/s target is NOT met** — the step is bounded by the fork's gather
read path (~8.2 GB/s vs a 22.74 GB/s link) with the cache at a 0.53-0.66
hit rate, and MTP4 was measured and rejected (zero decode gain, prefill
918 -> 255). Carries the fork's KV-cap bug, the fp8-KV/gfx12 trap, and the
`glm47` tool/reasoning parser wiring
- [[frank-medium-radiance-mxfp4]] — RETIRED 2026-09-26:
vllm-radiance as ONE tensor-parallel process (TP=2 over the R4D one-shot
allreduce) serving just1moremodel/Qwen3.8-27B-Uncensored-MXFP4-awq (MXFP4
+42
View File
@@ -1,5 +1,47 @@
# Bundle Change Log
## 2026-09-30
- **frank-primary cut over to GLM-5.3-Flash W4A16 int4 — it serves, but NOT at
the 35 tok/s target, and the shortfall is measured rather than inferred.**
- Payload `Intel/GLM-5.3-Flash-W4A16-AutoRound` (AutoRound symmetric int4
group 128, `auto_round:auto_gptq` packing, ~170 GiB, 34 shards + 2 extras,
113,074 tensors). It is the only W4A16 checkpoint this fork can serve WITH
expert offload: its `quant_method: auto-round` is overridden to `inc`, which
routes the MoE to libr4d's grouped W4A16 GEMM (`R4dW4a16MoEMethod`). The
compressed-tensors W4A16 build of the same model (`dealignai/…-CYBERSECURITY-W4A16`,
182 GiB, fetched earlier) resolves to `TritonWNA16Experts`, which has NO
offload recipe — and 170 GiB of experts cannot fit 64 GiB of VRAM, so it
refused to serve at all. That checkpoint was purged with the other retired
payloads.
- Shape: tcclaviger RDNA4 vLLM fork (same digest-pinned image as the qwen
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:** live expert-cache telemetry (`R4D_LRU_TELEMETRY=1` + a
`with_stack=false` profiler window) puts the cache hit rate at 0.53-0.66 per
layer; a step moves 0.82 GiB of misses in ~100 ms = **8.2 GB/s effective
against a link measured at 22.74 GB/s** — the same structural gather-read-path
gap the 2026-09-29 qwen campaign found. Growing the arena 13.08 -> 14.51 GiB
(+9%) changed decode by *nothing* (10.03 -> 9.88 tok/s), so residency stopped
being the binding term; 35 tok/s would need a 0.89 hit rate.
- Measured and rejected: **MTP4** (the head loads, arch resolves to
`Glm5NextMTPModel`, acceptance is excellent at mean length 3.76-4.90 — but
decode was unchanged (9.6-11.2 vs 10.0) while prefill COLLAPSED 918 -> 255,
because a verification forward gathers the *union* of the batch's experts);
**fp8 KV** (disables the sparse MLA backend's rope-free Triton path, and
aiter's MLA decode op is gated on CDNA>2, so the MTP draft step demands an op
that cannot exist on gfx12); **NBATCH 8192** (boots, identical prefill).
- Fork bug found: the plan's KV reserve under-models this model's
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.
## 2026-09-29
- **frank-large tuning investigation: a measured NEGATIVE result, nothing
+75 -33
View File
@@ -1,19 +1,22 @@
#!/usr/bin/env bats
# Seam: frank-primary — the Dankserver LLM endpoint on :7770.
#
# This file asserts the tcclaviger RDNA4 vLLM ROOT-UNIT shape (the 2026-09-29
# cutover that replaced the R9V radiance fork): the serve script drives
# `podman run` DIRECTLY (the image entrypoint execs `vllm serve`), with every
# engine flag qualified by boot probe on 2026-09-29 and carried in the repo
# env file. The seam pins what the flags alone do not say: the qualification
# law (600k ctx / 3 sessions / q8-q8 KV were MEASURED to fit — 729,545 tokens
# of capacity at 7.2 GiB per rank — and KV_BYTES is PER RANK), the self-healing
# This file asserts the tcclaviger RDNA4 vLLM ROOT-UNIT shape (the 2026-09-30
# cutover to GLM-5.3-Flash W4A16 int4, which replaced the qwen FP8 + MTP4
# shape): the serve script drives `podman run` DIRECTLY (the image entrypoint
# execs `vllm serve`), with every engine flag qualified by boot probe on
# 2026-09-30 and carried in the repo env file. The seam pins what the flags
# alone do not say: the qualification law (131,072 x 2 seqs at bf16 KV, util
# 0.98 is what boots; the window is forced up by the fork's KV reserve model,
# not by capacity), the offload law (170 GiB of experts cannot fit 64 GiB of
# VRAM — the engine MUST run with --enable-expert-offload), the self-healing
# law (the run returns before the engine is up, so the serve script must wait
# on the container for Restart=on-failure to mean anything), the memlock law
# that makes the tiered-expert pin legal, the deployer's image/payload gates
# with the false-green MainPID check, and the prefetch law (expert prefetch
# measured at 36% precision — OFF is the incumbent; a regression that
# reintroduces it silently costs ~40-50% decode).
# that makes the 73.64 GiB/rank expert pin legal, the deployer's image/payload
# gates with the false-green MainPID check, and the three measured-absent
# flags: expert prefetch (36% precision, costs ~40-50% decode), MTP spec
# decode (buys zero decode and collapses prefill on an offload-bound shape),
# and fp8 KV (disables the only MLA decode path gfx12 has).
load helpers
@@ -53,8 +56,8 @@ DOWNLOAD="$REPO_ROOT/scripts/download-frank-primary.sh"
# outlives the unit and holds both GPUs. The container name must match
# the env file's identity.
grep -q '^ExecStop=/usr/bin/podman stop' "$UNIT"
grep -q 'frank-primary-qwen' "$UNIT"
grep -q '^FRANK_PRIMARY_CONTAINER=frank-primary-qwen$' "$ENV_FILE"
grep -q 'frank-primary-glm' "$UNIT"
grep -q '^FRANK_PRIMARY_CONTAINER=frank-primary-glm$' "$ENV_FILE"
}
@test "the serve shape is direct podman run, not a launcher or shim" {
@@ -69,32 +72,70 @@ DOWNLOAD="$REPO_ROOT/scripts/download-frank-primary.sh"
}
@test "the qualified engine shape is in the env file" {
# 2026-09-29 boot probe: MAXLEN 200000 x SEQS 3 (600k total) at KV q8/q8
# fits — the engine measured "GPU KV cache size: 729,545 tokens,
# Maximum concurrency for 200,000 tokens per request: 3.65x" at
# KV_BYTES 7.2 GiB per rank. KV_BYTES is PER RANK: sizing it to the
# global requirement OOMs the worker (measured twice). fp8 KV is the
# requested q8/q8 (e4m3 for both K and V together). MTP4 spec decode,
# util 0.98 (0.99 fails the free-memory wall).
grep -q '^FRANK_PRIMARY_MAXLEN=200000$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_SEQS=3$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_KV_BYTES=7730934784$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_KV_DTYPE=fp8$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_SPEC=4$' "$ENV_FILE"
# 2026-09-30 boot probe: 131,072 x 2 seqs at bf16 KV, util 0.98 boots and
# serves (measured "GPU KV cache size: 138,561 tokens"). 0.99 fails the
# free-memory wall. The window is not a capacity choice: the engine's own
# reserve model (one max_model_len request) needs a large L for this
# model's per-sequence KDA state, and below ~92k the boot REFUSES with
# "needed ... larger than the available KV cache memory". NBATCH pins the
# prefill chunk that was measured.
grep -q '^FRANK_PRIMARY_MAXLEN=131072$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_SEQS=2$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_UTIL=0.98$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_NBATCH=2048$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_MODEL_SHARDS=34$' "$ENV_FILE"
}
@test "the serve script wires the engine flags from the env" {
grep -q -- '--kv-cache-dtype "$FRANK_PRIMARY_KV_DTYPE"' "$SERVE"
grep -q -- '--kv-cache-memory-bytes "$FRANK_PRIMARY_KV_BYTES"' "$SERVE"
grep -q -- '--max-num-seqs "$FRANK_PRIMARY_SEQS"' "$SERVE"
grep -q -- '--max-model-len "$FRANK_PRIMARY_MAXLEN"' "$SERVE"
grep -q -- '--max-num-batched-tokens "$FRANK_PRIMARY_NBATCH"' "$SERVE"
grep -q -- '--tensor-parallel-size 2' "$SERVE"
grep -q 'num_speculative_tokens' "$SERVE"
grep -q -- '--enable-prefix-caching' "$SERVE"
grep -q -- '--trust-remote-code' "$SERVE"
}
@test "the endpoint advertises the GLM tool dialect" {
# Without --enable-auto-tool-choice the engine rejects tool_choice "auto"
# with a 400 ("requires --enable-auto-tool-choice and --tool-call-parser"),
# which breaks every tool-using agent on the endpoint.
grep -q -- '--enable-auto-tool-choice' "$SERVE"
grep -q -- '--tool-call-parser "$FRANK_PRIMARY_TOOLPARSER"' "$SERVE"
grep -q -- '--reasoning-parser "$FRANK_PRIMARY_REASONINGPARSER"' "$SERVE"
grep -q '^FRANK_PRIMARY_TOOLPARSER=glm47$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_REASONINGPARSER=glm47$' "$ENV_FILE"
}
@test "expert offload is ON — the payload cannot fit VRAM without it" {
# 170 GiB of int4 experts against 64 GiB of VRAM. Without the offload flag
# the engine has no home for the expert tensors at all; the arena
# (14.51 GiB = 2,383 slots) plus 73.64 GiB/rank of pinned host rows is the
# only shape that boots. --expert-precise-memory is the qualified sizing.
grep -q -- '--enable-expert-offload' "$SERVE"
grep -q -- '--expert-precise-memory' "$SERVE"
}
@test "MTP spec decode is absent BY MEASUREMENT, not by omission" {
# 2026-09-30: the MTP head loads and accepts well (mean accepted length
# 3.76-4.90), but decode was unchanged (9.6-11.2 vs 10.0 tok/s) while
# prefill collapsed 918 -> 255 tok/s — the verification forward gathers the
# union of the batch's experts, so spec decode does not amortize a
# residency-bound gather. It also costs 2.6 GiB of arena.
! grep -q -- '--speculative-config' "$SERVE"
! grep -q 'speculative' "$ENV_FILE"
}
@test "fp8 KV is absent — it would disable the only MLA decode path on gfx12" {
# _use_rocm_sparse_triton() in the ROCM_AITER_MLA_SPARSE backend requires a
# non-fp8 KV cache, and that rope-free Triton path is the only MLA decode
# available on gfx12: aiter's MLA decode op is registered under
# @if_aiter_supported, gated on CDNA>2. With fp8 KV the MTP draft step dies
# with "_OpNamespace 'vllm' object has no attribute
# 'rocm_aiter_mla_decode_fwd'". KV dtype therefore stays auto/bf16.
! grep -q -- '--kv-cache-dtype' "$SERVE"
! grep -q '^FRANK_PRIMARY_KV_DTYPE=' "$ENV_FILE"
}
@test "expert prefetch is absent BY LAW, not by omission" {
# Telemetry (2026-09-29): the prefetcher served 13,369 of 36,720
# prefetched slabs (36% precision) — roughly half the gather traffic was
@@ -149,10 +190,10 @@ DOWNLOAD="$REPO_ROOT/scripts/download-frank-primary.sh"
@test "the model id matches the payload's served alias wiring" {
# The payload dir and its shard count are the fetch script's output
# layout; the fetch script must write where the env points.
grep -q '^FRANK_PRIMARY_MODEL_DIR=/home/dank/models/Qwen3.8-Flash-Next-Uncensored-FP8$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_MODEL_SHARDS=131$' "$ENV_FILE"
grep -q 'Qwen3.8-Flash-Next-Uncensored-FP8' "$DOWNLOAD"
grep -q 'orcarouter/Qwen3.8-Flash-Next-Uncensored-FP8' "$DOWNLOAD"
grep -q '^FRANK_PRIMARY_MODEL_DIR=/home/dank/models/GLM-5.3-Flash-W4A16-AutoRound$' "$ENV_FILE"
grep -q '^FRANK_PRIMARY_MODEL_SHARDS=34$' "$ENV_FILE"
grep -q 'GLM-5.3-Flash-W4A16-AutoRound' "$DOWNLOAD"
grep -q 'Intel/GLM-5.3-Flash-W4A16-AutoRound' "$DOWNLOAD"
}
@test "no bare HIP-reserved env var is defined" {
@@ -168,12 +209,13 @@ DOWNLOAD="$REPO_ROOT/scripts/download-frank-primary.sh"
# storage or the payload is missing/wrong-sharded — then clears stale
# containers (including the legacy identities), waits out the port,
# relinks/enables/restarts the root unit and health-gates it.
grep -q 'LLM_PRIMARY_CONTAINER=frank-primary-qwen' "$REMOTE_DEPLOY"
grep -q 'LLM_PRIMARY_CONTAINER=frank-primary-glm' "$REMOTE_DEPLOY"
grep -q 'FRANK_PRIMARY_IMAGE' "$REMOTE_DEPLOY"
grep -q "podman images --format '{{.RepoDigests}}'" "$REMOTE_DEPLOY"
grep -q 'FRANK_PRIMARY_MODEL_SHARDS' "$REMOTE_DEPLOY"
grep -q 'config.json' "$REMOTE_DEPLOY"
grep -q 'qwen-fp8' "$REMOTE_DEPLOY"
grep -q 'glm-probe' "$REMOTE_DEPLOY"
grep -q 'wait_port_free 7770' "$REMOTE_DEPLOY"
grep -q 'relink_unit frank-primary' "$REMOTE_DEPLOY"
grep -q 'systemctl enable frank-primary.service' "$REMOTE_DEPLOY"