The 2026-10-09 wire aliases become the service names everywhere: systemd units, serve scripts, env files and FRANK_* variables, deploy-plan/deploy-remote service keys, container names, Prometheus job names + alert rules, Grafana dashboard queries, bats seams, runbook filenames, homepage/AGENTS lore, and historical prose. Rollback tags renamed in lockstep. Residue in the tree: zero. Retired frank-medium/small/npu names are history and stay. Known casualty: scripts/patches/*.patch context lines were rewritten; those retired-era vLLM-fork patches no longer apply byte-exact.
8.6 KiB
type, description, tags, timestamp
| type | description | tags | timestamp | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
| reference | frank-dragoon on Dankclaw serving the mradermacher Qwen3.8-Flash-Next-Uncensored i1-Q4_K_M GGUF via ik_llama.cpp on the RTX 3090 + Strix Halo hybrid — measured performance envelope, root-cause analysis, tuning levers, and traps |
|
2026-09-16T03:30:00-07:00 |
frank-dragoon — ik_llama.cpp, Qwen3.8-Flash-Next-Uncensored i1-Q4_K_M
RETIRED 2026-09-16, after one day. Replaced by halogen-flash-server,
which was the runtime before it as well — the fleet went ik → halogen → back.
Its unit, env, serve script and the Qwen-Sharp template are archived in the repo
under scripts/.AGENTTRASH/*.ik-llama.cpp and *.ik-llama-qwen-sharp, and the
payload, build and chat template stay on disk. The measurements below are not a
stale plan: they are the complete account of what this model costs on this
hardware, and they are why the fleet went back. It served
mradermacher/Qwen3.8-Flash-Next-Uncensored (111 GiB GGUF + mmproj-f16) at
192.168.0.143:7770 while it ran.
Shape
- Runtime: ik_llama.cpp master
d85a1d7, built at/home/dank/GitPackages/ik_llama.cpp/buildwith-DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=86 -DGGML_AVX512=ON -DGGML_AVX512_VBMI=ON -DGGML_AVX512_VNNI=ON -DGGML_AVX512_BF16=ON -DLLAMA_CURL=OFF. - Unit: root systemd unit
/etc/systemd/system/frank-dragoon.servicesymlinked toscripts/frank-dragoon.servicein the repo; enabled at boot;Restart=always. Serve scriptscripts/serve-frank-dragoon.sh+ envscripts/frank-dragoon.env. - Topology: 3090 (PCIe x4 in the M.2 dock, link downgraded from x16) carries dense/attention/KV (~11.5 GiB); ALL MoE expert tensors (~84 GiB) and the ~33 GiB PLE n-gram table stay in LPDDR via CPU buffers.
- Client contract: greedy default (
--temp 0.0), reasoning on (deepseek format, unlimited budget), vision via mmproj, slot-state persistence underscripts/slot-state, 262144 ctx, 1 slot.
Measured performance envelope (2026-09-15/16 campaign, all single-instance verified)
| config | decode tok/s | prefill tok/s | verdict |
|---|---|---|---|
shipped (T16, -ot exps=CPU, NO_PINNED_WEIGHTS) |
3.8-4.0 | ~193 at 1.8k / 191 at 32k | shipped |
| T8 / T4 / T2 / T1 | 4.18 / 4.20 / 4.26 / 3.97 | small-prompt only | decode flat across threads |
-ncmoe 42 (6 layers' experts on GPU) |
4.54 | 32 (small prompt) | VRAM 22.0/24.6 GiB — fragile |
-ncmoe 48 -thp |
4.60 | 18 | 110 GiB anon host buffer — co-tenant killer, never ship |
--prefetch-experts |
4.03 | — | no effect (page cache already resident) |
--defer-experts --defer-ple -thp |
crashes | — | experts under -ngl 999 still target CUDA0 → cudaMalloc OOM |
mainline llama.cpp 37b53fd (--n-cpu-moe 48) |
1.01 | 34 | ruled out — CPU MoE kernels 4x slower than ik's iqk |
- Large-prompt prefill ≈ 192 tok/s is the CPU AVX512-VNNI compute ceiling for CPU-resident experts (expert weight reads amortize across the batch; compute-bound). Halogen's "4x llama.cpp family" prefill claim is therefore unreachable with experts on CPU — the physics caps hybrid MoE prefill here.
- Decode is gated by a serial per-token critical path: ~100 CPU↔GPU
backend splits per token (
graph splits = 100in the log); the 3090 sits at 5% SM utilization during decode; thread count does not matter (T1 ≈ T16 ≈ 4 tok/s). The GPU sections are launch/sync-bound, not compute-bound.
Root cause chain (perf evidence)
perfat T16: ~53% cycles inggml_graph_compute_thread+ ~37% inggml_compute_forward_mul_mat_id, of which ~98% is thepauseinstruction (thread-pool barrier spin). Actual vec_dot math: ~4%.- T1 = T16 decode (3.97 vs 3.8-4.0) ⇒ the barrier spin is threads waiting on a serial critical path, not parallel overhead.
- GPU 95% idle during decode ⇒ the critical path is the GPU-section launch/sync overhead per split (x4 dock link, eager kernel launches).
- ik's CUDA-graph capture would collapse those launches but aborts on
qwen4exp: the per-layer hyper-connection broadcasts (
ggml_repeat_4d, 4 sites insrc/graphs/build_qwen4exp.cpp) resolve to a REPEAT node whose CUDA path does a capture-illegal operation (CUDA error: operation not permitted when stream is capturing→ abort). - Memory placement is NOT the lever: mmap-off/anon/THP/deferred variants all land at 3.8-4.6 tok/s.
Why the 30 tok/s halogen bar is out of reach (honest record)
Halogen's engine held the whole 115.55 GiB w4b checkpoint resident in LPDDR
(HALOGEN_FLASH_PIN_TRUNK=1) and served from one engine. This deployment must
split 84 GiB of expert tensors across a 24 GB GPU + CPU, and every public
runtime pays the split penalty above. Paths to >30 tok/s, in order of
realism: (a) a runtime with capture-safe CUDA graphs + dedicated hc ops —
mainline is building exactly this (ggml-org PR #28901 "qwen4exp: add hc ops");
re-evaluate mainline when its hc-ops + graph handling mature; (b) experts on
GPU — impossible, 84 GiB > 24 GiB; (c) a smaller model.
Upstream issues tracking the slow TG path
ik_llama.cpp: #2374 (QSA opt POC), #2406 (single-threaded sigmoid), #2433
(cache-miss full reprocess), #2460 (PLE history correctness). MTP/speculative
decoding is unavailable for this GGUF — mradermacher stripped the NextN head
(verified: no blk.48.*/nextn tensors in the file).
Traps (learned the hard way 2026-09-15/16)
- Pinned host buffers strand the host: a default-policy start
page-locks ~108 GiB (
CUDA_Host buffer), evicting co-tenants into swap and starving sshd until a power cycle.GGML_CUDA_NO_PINNED_WEIGHTS=1+ the serve-script guard exist for this.-ncmoe Nwith default NO_PINNED settings produces the same 108 GiB pinned buffer — never run it without the guard. - Any
-ot/-ncmoeoverride disables mmap for the whole model load (create_tensorsreturns false →ml.use_mmap = false): the "mmap-backed CPU buffer" comment in the serve script is wrong on the mmap word; the buffer is anonymous. All placement variants perform alike anyway. --defer-expertsdoes not keep experts off the GPU under-ngl 999— only PLE defers (32.78 GiB). With ngl 999 the load dies withcudaMalloc failed: out of memoryat ~79.7 GiB on device 0.- The systemd unit self-heals through manual tests:
pkillthe server and the unit respawns it ~4 s later (Restart=always) — two instances, port races, phantom measurements. For manual experiments:sudo systemctl stop frank-dragoon.servicefirst (it is a root unit; plainsystemctl --userlines in older docs are stale), and restore withsudo systemctl start frank-dragoon.service. - Verify "HTTP server listening" in the CURRENT log and
pgrep -xc llama-server== 1 before trusting any measurement. - FA must stay ON — FA-off crashes the loader on this arch.
- Do not run builds concurrently with decode measurements (
-j16starves the service and corrupts both results).
Rollback
Stop and disable the root unit, trash it, restore the halogen quadlet from
~/.AGENTTRASH/20260915-ik-swap/halogen-quadlet to
~/.config/containers/systemd/, systemctl --user daemon-reload, start;
the .hgn checkpoint and the kingjones STRIX llama.cpp payload remain on disk.
Hybrid campaign envelope (2026-09-16, 3090 + Strix Halo)
Why this deployment could not be tuned into a production endpoint: decode
3.8–4.3 tok/s flat across thread counts (T1 ≈ T16); prefill ~192 tok/s at
1.8k–32k prompts = the CPU AVX512-VNNI compute ceiling for CPU experts.
The decode root cause is structural: ~100 CPU↔GPU backend splits per token
with the GPU at 5 % SM during decode — launch/sync-bound over the PCIe x4
dock, not compute- or memory-bound. T16 profiling shows ~90 % of cycles in
pause (ggml_barrier spin): threads wait on the serial critical path.
Memory placement (mmap/anon/THP/deferred) does not matter — all variants
landed 3.8–4.6.
- CUDA-graph capture aborts: qwen4exp's hyper-connection broadcasts use
per-layer
ggml_repeat_4d, whose CUDA path is capture-illegal; until ik gains capture-safe repeat/hc ops (mainline PR #28901 direction), graphs stay dead. - Mainline llama.cpp itself runs 1.0 tok/s on this model (CPU MoE kernels 4× slower than ik's iqk) — do not switch engines for speed.
- MTP/speculative decoding is impossible for mradermacher i1 GGUFs: the
NextN head is stripped (no
blk.48tensors). - Tuning lever ranked first:
-ncmoe 42(6 layers' experts on the 3090) → decode 4.54, small-prompt prefill 32 — but VRAM 22/24.6 GiB at 262k ctx, an operator OOM-margin decision, and it needsGGML_CUDA_NO_PINNED_WEIGHTS=1.