docs: record the 2026-10-02 swaps — frank-large on ROCmFPX, frank-small on Q5_K_M

frank-large: new runbook frank-large-rocmfpx-q8agent-mtp.md — the ftype-115
tensor-type lock-in that forced the fork, the pinned build (set_rows history),
the measured MTP A/B (17.0 vs 7.6 t/s decode, acceptance 0.462), the live
client contract (reasoning_content, effort fold, vision restored) and rollback.
frank-small: new runbook frank-small-llama-mimo-q5km.md; q6k marked retired
(rollback target). gufo runbook marked retired (rollback target). Both indexes,
the fleet topology table, the vision/models.yml notes, the benchmarking law and
three new Key Lessons updated.
This commit is contained in:
Frank committed 2026-10-02 01:51:11 -07:00
1 parent 125d04967f
commit 72bd4c967e
7 files changed
+522 -147

No files matched your search

@@ -0,0 +1,215 @@
---
type: reference
title: "frank-large on Dankclaw — ROCmFPX llama.cpp, Qwen3.8-27B Q8_0_ROCMFPX_AGENT + MTP"
description: "The active frank-large runbook — the kingjones30/ROCmFPX llama.cpp fork (pinned commit, gfx1151) serving kingjones777's Q8_0_ROCMFPX_AGENT GGUF, a ftype-115 file no other loader can read, with the repo's own MTP-Q4_0 head drafting four tokens at one native 262,144 window, f16 KV, and the bundled BF16 projector for vision. Measured MTP-vs-none A/B, the client contract, and rollback to gufo."
tags:
- llm
- frank-large
- dankclaw
- llama-cpp
- rocmfpx
- gfx1151
- gguf
- spec-decode
- mtp
timestamp: 2026-10-02
---
# frank-large — ROCmFPX Q8_0_ROCMFPX_AGENT + MTP (gfx1151)
> **ACTIVE since 2026-10-02.** Cut over from
> [[frank-large-gufo-qwen38-27b]] (Q4_K_XL, ~15 t/s). Both requests came from
> Frank: *highest quality that runs*, and *one full context*. This endpoint
> delivers 8.39 bpw weights — two quant rungs above what gufo could load — and
> measures **17.0 t/s** decode instead of 15, so quality was not bought with
> speed.
>
> The capability that forced this whole shape is a GGUF tensor type:
> **`GGML_TYPE_Q8_0_ROCMFPX` (103)**. It exists in exactly one loader on this
> host, so the runtime was decided by the file, not by preference.
## Identity
| | |
|---|---|
| Host / device | Dankclaw (192.168.0.143), Radeon 8060S iGPU `gfx1151`, ~114 GiB GTT aperture |
| Endpoint | `http://192.168.0.143:7770` — alias `frank-large`, `/v1` OpenAI surface |
| Runtime | **`kingjones30/ROCmFPX`** llama.cpp fork @ `36e9acd40e10a87cd3c3ef8ec734668757dc8520`, built by [`scripts/build-rocmfpx.sh`](../../scripts/build-rocmfpx.sh) → `/home/dank/GitPackages/kingjones-ROCmFPX/build/bin/llama-server` |
| Unit | root systemd `frank-large.service` ([`scripts/frank-large.service`](../../scripts/frank-large.service)), `User=dank`, `Restart=always` |
| Serve script | [`scripts/serve-frank-large.sh`](../../scripts/serve-frank-large.sh) (owns the argv), tunables [`scripts/frank-large.env`](../../scripts/frank-large.env) |
| Weights | `/home/dank/models/kingjones-Qwen3.8-27B-ROCmFP4-STRIX-MTP/Qwen3.8-27B-Q8_0_ROCMFPX_AGENT.gguf` — 28,214,425,952 B, sha256 `f3ef2bdbf56e244f…` |
| Draft | `…/mtp-Qwen3.8-27B-Q4_0.gguf` — 1,680,271,648 B, sha256 `051a1764cff8c4f3…` |
| Projector | `…/mmproj-Qwen3.8-27B-BF16.gguf` — 931,145,888 B, sha256 `de2a4986…` |
| Fetch | [`scripts/download-frank-large-kingjones.sh`](../../scripts/download-frank-large-kingjones.sh) (`-f`, byte-count + LFS sha256 verified, `--verify-only`, token never on the command line) |
| Seam | `tests/frank_large_unit.bats` (19 tests: payload gates, deployer/serve parity, spec block, KV dtype, sweep identity) |
## Why this file, and why only this runtime
`kingjones777/Qwen3.8-27B-ROCmFP4-STRIX-MTP-GGUF` ships several rungs. Parsed
headers of the two candidates:
| File | ftype | Tensor mix | Output-head tensors at plain Q8_0 | Card MTP acceptance |
|---|---|---|---|---|
| `Qwen3.8-27B-Q8_0_ROCMFPX_AGENT.gguf` | **115** | 353 F32 + 187 Q8_0 + **311 type-103** | **187** | 0.953 |
| `Qwen3.8-27B-Q8_0_ROCMFPX.gguf` | 111 | 2 Q8_0 + type-103 | 2 | 0.911 |
Two consequences, both measured off the header rather than guessed:
1. **Quality.** The AGENT routing variant keeps the output-head (and other
routing-sensitive) tensors at plain Q8_0 instead of folding them into the
ROCmFPX kernel layout — 187 vs 2 — so the logits the sampler reads are the
highest-precision tensors in the file. It is also the card's MTP-tuned
variant, so it is the *faster* drafter base at the same time.
2. **The runtime is forced.** Type 103 is a private extension: stock llama.cpp
and gufo both fail at load (`unknown tensor type`). Verified on the host
before deploying — the bake-off was not a horse race, it was a lock-in
check.
**The build identity matters as much as the fork.** The 2026-08-25 ROCmFPX
builds read this file and then died in `ggml_compute_forward_set_rows` — the
exact failure documented in [[kingjones-q8agent-rocmfpx]] when this payload was
first attempted on Dankclaw and routed to Dankserver as
[[frank-medium-rocmfpx]] instead. `scripts/build-rocmfpx.sh` therefore checks
the commit out **by SHA** and refuses to build anything else; the pin is
load-bearing, not tidiness.
## Shape and budget
One session, the model's full native window, unquantized KV:
| Dial | Value | Why |
|---|---|---|
| `--ctx-size` | 262,144 | "one full context" — the native window, no rope scaling |
| `--parallel` | 1 | one conversation, all memory to it |
| KV dtype | **f16** (both K and V) | ~64 KiB/token → ~16 GiB; free inside a 114 GiB aperture, so there is no reason to pay a quant |
| `--fit off` | on | the auto-fitter would silently resize the window we asked for |
| `--flash-attn on` | on | required for the long-window attention path to fit |
| `--direct-io` | on | bypasses page cache for the 26 GiB weight stream (mmap is disabled by it — deliberate) |
| `--ctx-checkpoints 16` | 16 | slot state for checkpoint/rollback |
| `--cache-ram 8192` + `--cache-idle-slots` | 8 GiB | prompt cache; verified a repeat pass over the same 6k prompt replays in **0.5 s** |
| `--jinja` | on | without it `chat_template_kwargs` are silently ignored |
| `--reasoning on --reasoning-format deepseek --reasoning-budget -1` | | thinking in its own channel, unbounded |
Measured steady state (production, one model copy resident):
`mem_info_gtt_used` **63.1 GiB** of the ~114 GiB aperture, `mem_info_vram_used`
0.17 / 2.0 GiB (the iGPU's dedicated carve-out is a stub; everything real lives
in GTT). llama-server RSS 2.1 GiB. Boot to `/health` 200: **16 s** — the fastest
endpoint bring-up in the fleet, because there is no engine compile step.
## Speculative decode — measured, not assumed
```
--spec-type draft-mtp --model-draft <mtp-Qwen3.8-27B-Q4_0.gguf>
--spec-draft-n-max 4 --spec-draft-n-min 0 --spec-draft-p-min 0.0
--spec-draft-device ROCm0 --spec-draft-ngl 99
```
MTP in llama.cpp is a property of the file, not of the flags: the head is a
trained tensor set (`nextn_predict_layers = 1` in this repo's head, arch
`qwen35`, 65 blocks) and the draft context runs on the main model's weights, so
there is no second 26 GiB copy in memory. See the fleet law in
`AGENTS.md → llama.cpp serving`.
**A/B, 2026-10-02** — same session, temp 0, 2,202-token prompt, 512 tokens,
`cache_prompt: false`, `frank-large.service` stopped for every scratch arm (the
benchmarking law), baseline arm stripped of `--model-draft` **and** every
`--spec-*` flag:
| Arm | Decode t/s (2 runs) | Prefill t/s | Draft acceptance |
|---|---|---|---|
| **MTP, n-max 4 (deployed)** | **17.00 / 17.14** | 350.6 / 324.9 | 0.462 (mean acceptance length ≈ 1.86) |
| no draft | 7.60 / 7.66 | 381.8 / 381.0 | — |
**+123 % decode for an 8–15 % prefill tax.** Notes that matter before someone
re-litigates this:
- Acceptance here (0.462) is nowhere near the card's 0.953. The card measures
its own short, repetitive text; this is a 2.2k-token prose prompt. Treat the
card's number as a file property, not a throughput promise.
- `n-max 4` is where the gain was measured; 2 and 8 are **unmeasured**. The
gufo-era law applies in spirit — never rank spec settings on a
self-predictable corpus.
- End-to-end on the chat path is slower than the `/completion` number because
thinking tokens are included: a 2,253-token chat prompt generating 512
tokens took 43.8 s wall (11.7 t/s end-to-end) — that is the cost of an
unbounded reasoning budget, not a regression in decoding.
## Client contract (live-verified 2026-10-02)
| Behavior | Observation |
|---|---|
| Model id | `/v1/models` → `frank-large` |
| Tools | native: `get_weather` → `{"city": "Seattle"}` in `tool_calls` |
| Thinking | in **`reasoning_content`** (llama.cpp field — *not* vLLM's `reasoning`); `models.yml` sets `reasoningContentField` accordingly |
| `reasoning_effort` | accepts **only** `xhigh`, `medium`, `low`; anything else is HTTP 500 (the template raises, it does not clamp). `models.yml` folds `high`/`max` → `xhigh` |
| Sampling | server defaults = the card's thinking recipe (temp 1.0, top_p 0.95, top_k 20, min_p 0, presence 0), mirrored in the harness `extraBody` |
| Vision | the BF16 projector loads (`loaded multimodal model`); a red square answers **`Red`** (`usage: 75 prompt / 71 cached`) |
| Prompt cache | second pass over the same 6k prompt: 0.5 s wall |
Three traps, all hit for real:
1. **`content: null` when reasoning eats the budget.** With
`--reasoning-parser`-style separation, a request whose `max_tokens` is
consumed entirely by thinking returns `message.content = None`. A bench
client that indexes `content` crashes *after* measuring and loses the
numbers — fall back to `reasoning_content`, or size the budget (200 was
enough for the vision probe).
2. **`MTP boundary missing for seq_id=… ; resyncing after a non-token step`**
appears in the log under draft resets. Self-recovering; not an outage signal.
3. **Never `pkill -f llama-server` on Dankclaw** — frank-small is also a
`llama-server`. The sweep key here is the payload name:
`pkill -f 'Qwen3.8-27B-Q8_0_ROCMFPX_AGENT'`.
## Operations
```bash
# deploy (planner selects this endpoint only via its own unit/env/serve trio)
./scripts/deploy.sh dankclaw --service frank-large
# status / logs / cause of a crash loop
systemctl status frank-large
journalctl -u frank-large -n 200 --no-pager
# rebuild the pinned fork (only when deliberately moving the pin)
scripts/build-rocmfpx.sh
# re-verify the payload without re-downloading
scripts/download-frank-large-kingjones.sh --verify-only
```
`serve-frank-large.sh` refuses to start — before touching the port — unless the
fork binary, the weights, the MTP head and (unless `FRANK_LARGE_MMPROJ=""`) the
projector all exist; `scripts/deploy-remote.sh` mirrors the same predicate so a
broken payload never stops the running unit. Caller environment **overrides**
`frank-large.env`, and defaults use `:=` so an explicitly empty value surfaces
as the error it reads as.
## Rollback
git tag **`frank-large-gufo-q4kxl-20261001`**: gufo v0.1.1
(`scripts/build-gufo.sh` still on disk), the Q4_K_XL Qwen3.8-27B payload and the
DFlash2 drafter all remain on the host. Restore that tag's unit/env/serve trio
and redeploy — nothing here deletes anything.
Payloads retired alongside this swap (MiMo-V2.6 BF16, Qwen3.5-9B MTP source,
DFlash draft dirs) are still on disk under `/home/dank/models/` and are the
subject of the [[deployment]] disk-pressure note, not of a rollback plan.
## When it misbehaves
- **`unknown tensor type` / ftype 115 refused** — something moved off the
ROCmFPX binary. Check `readlink -f $(systemctl show frank-large -p ExecStart)`
and the fork commit; this file cannot run on stock llama.cpp or gufo.
- **SIGSEGV in `ggml_compute_forward_set_rows` during load** — a fork build
older than the `36e9acd` pin; rebuild with `scripts/build-rocmfpx.sh`.
History: [[kingjones-q8agent-rocmfpx]].
- **Slow decode with no log error** — check whether the draft is engaged
(`/metrics`, `draft_n` / `draft_n_accepted`; a zero draft count means the head
did not load), and check GTT pressure with
`cat /sys/class/drm/card1/device/mem_info_gtt_used` — a second resident copy
of this model (an orphaned scratch server) drives the whole host into
eviction and halves the rate silently.
- **`500` naming Jinja** — a `chat_template_kwargs` field reached a server
without `--jinja`.
- **HTTP 500 naming the template on the first request of a role** — an
unmapped `reasoning_effort`; fix the fold in `models.yml`, not the model.