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.
351 lines
18 KiB
Markdown
351 lines
18 KiB
Markdown
---
|
||
type: reference
|
||
title: "gufo, Qwen3.8-27B UD-Q4_K_XL (DFlash2) on Dankclaw (frank-dragoon)"
|
||
description: "The RETIRED frank-dragoon runbook (rollback target) — gufo (gufo-org/gufo) v0.1.1, the Strix Halo inference engine, built from source on Dankclaw and serving unsloth/Qwen3.8-27B-GGUF UD-Q4_K_XL (vanilla) with the z-lab DFlash2 Q4_K_M drafter on the gfx1151 iGPU, ONE session at the full native 262,144 window with unquantized f16 KV: the from-source build and its two host workarounds, the GGUF header evidence, why the fleet's Qwen-Sharp chat template CANNOT be used on this runtime, the client-contract delta, the measured prefill/decode trade, and the failure modes worth knowing."
|
||
tags:
|
||
- llm
|
||
- gufo
|
||
- swift-1.5
|
||
- dankclaw
|
||
- frank-dragoon
|
||
- strix-halo
|
||
- gfx1151
|
||
- q8
|
||
- dflash2
|
||
- rocm
|
||
timestamp: 2026-09-28
|
||
---
|
||
|
||
# frank-dragoon — gufo, Qwen3.8-27B (UD-Q4_K_XL, DFlash2)
|
||
|
||
> **RETIRED 2026-10-02 — the rollback target.** Superseded by
|
||
> [[frank-dragoon-rocmfpx-q8agent-mtp]] (Q8_0_ROCMFPX_AGENT at 8.39 bpw with
|
||
> MTP4, 17.0 t/s measured). This era was itself a return after one day of
|
||
> [[frank-dragoon-vllm-mimo-v26-bf16]] (battery decode 6.19-9.01 t/s; every
|
||
> ≥6-bit vLLM quant path dead on gfx1151). The shape
|
||
> moved on the way back: payload is now **unsloth/Qwen3.8-27B-GGUF
|
||
> UD-Q4_K_XL** (16.35 GiB, vanilla — the abliterated Swift Q8 was
|
||
> reclaimed), and the session grid is **one session at the full native
|
||
> 262,144 window** (`SESSIONS=1 CONTEXT_SIZE=262144`, f16 KV). The
|
||
> Swift-1.5 Q8 history below is retained for the engine/template law,
|
||
> which still governs.
|
||
> **Originally ACTIVE 2026-09-28.** Replaces
|
||
> [[frank-dragoon-unsloth-qwen38-27b-q8xl]] (stock llama.cpp, the unsloth
|
||
> b11030 prebuilt), which had replaced
|
||
> [[frank-dragoon-kairic-edge-qwen38-27b]]. Same host, same port (7770), same
|
||
> alias (`frank-dragoon`).
|
||
>
|
||
> **The payload also changed on 2026-09-28**, later the same day: the target
|
||
> went from `unsloth/Qwen3.8-27B-GGUF:UD-Q8_K_XL` to
|
||
> `ajgazin/Swift-1.5-Qwen3.8-27B-Uncensored-Dynamic-MTP-GGUF`'s
|
||
> `UD-Q8_K_XL` — an abliterated (uncensored) fine-tune of the same base. See
|
||
> [Payload](#payload) and, for what that costs, [the template
|
||
> law](#the-template-law-qwen-sharp-is-gone).
|
||
|
||
## Identity
|
||
|
||
- **Endpoint:** `frank-dragoon` at `192.168.0.143:7770` (Dankclaw).
|
||
- **Runtime:** [gufo](https://github.com/gufo-org/gufo) — "the Strix Halo
|
||
inference engine", a vertical engine built for exactly this hardware
|
||
(Ryzen AI MAX+ 395 / Radeon 8060S, `gfx1151`, unified memory). Pinned at
|
||
**v0.1.1** (commit `b0f8673`), built from source on the host to
|
||
`/home/dank/GitPackages/gufo-v0.1.1/`.
|
||
- **Service shape:** root systemd unit `scripts/frank-dragoon.service`
|
||
(`User=dank`, `LimitMEMLOCK=infinity`), launched by
|
||
`scripts/serve-frank-dragoon.sh` with tunables from
|
||
`scripts/frank-dragoon.env`.
|
||
|
||
gufo ships **no prebuilt Linux binaries** (the v0.1.0/v0.1.1 release assets
|
||
are empty) and no Nix on this host, so the runtime is a source build. It is
|
||
also young: v0.1.0 landed 2026-09-28, the same day as this cutover.
|
||
|
||
## Why this engine
|
||
|
||
gufo's own measurement of the two engines on the same Qwen3.8-27B targets
|
||
(Strix Halo, `628ed18e` vs llama.cpp `b11069`, greedy, DFlash2):
|
||
|
||
| Q8_K_XL, DFlash2 | gufo | llama.cpp | delta |
|
||
|---|---:|---:|---:|
|
||
| prefill, depth 0 | 457.05 t/s | 333.85 t/s | **+36.9 %** |
|
||
| prefill, depth 131,072 | 237.24 t/s | 131.80 t/s | **+80.0 %** |
|
||
| decode, depth 0 (mixed) | 16.08 t/s | 15.00 t/s | +7.2 % |
|
||
| decode, depth 32,768 (mixed) | 14.41 t/s | 14.44 t/s | −0.2 % |
|
||
| decode, depth 65,536 (mixed) | 10.87 t/s | 12.62 t/s | **−13.9 %** |
|
||
| decode, depth 131,072 (mixed) | 8.87 t/s | 11.62 t/s | **−23.7 %** |
|
||
| cold load to HTTP-ready | 8.17 s | 36.47 s | **4.5× faster** |
|
||
| peak memory @ 262,144 ctx | 50.58 GiB | 48.60 GiB | +4 % |
|
||
|
||
Read that honestly: **prefill, load time and short-context decode improve;
|
||
deep-context decode on the 8-bit target gets worse.** The crossover on Q8 is
|
||
around 32k of cached depth. frank-dragoon runs one 262,144-token conversation,
|
||
so a long agent session spends its life on the losing side of that line
|
||
while a short turn wins. The Q4_K_XL target does not show the regression
|
||
(gufo is +11.7 % to +17.0 % at every depth) — that is the honest reason to
|
||
revisit the quantization separately from the runtime.
|
||
|
||
## Build
|
||
|
||
`scripts/build-gufo.sh` reproduces the runtime end to end. It clones the
|
||
pinned tag, configures the `release` preset (HIP, `--offload-arch=gfx1151`),
|
||
builds, installs, and finishes with `gufo diagnose`. Two host facts it
|
||
encodes, both found the hard way on 2026-09-28:
|
||
|
||
- **Ubuntu's `librocwmma-dev` must not be installed.** It ships a truncated
|
||
rocWMMA header set (the public headers without `rocwmma/internal/`) into
|
||
`/usr/include`, where it shadows AMD's complete copy under
|
||
`/opt/rocm/include`. The first build died on `'internal/accessors.hpp'
|
||
file not found` from `/usr/include/rocwmma/rocwmma.hpp`, and **a `-I`
|
||
cannot fix it**: in HIP mode the host compilation pass searches the system
|
||
directories first. The package has no reverse dependencies; the script
|
||
removes it.
|
||
- **`--gcc-install-dir` must point at GCC 15.** Ubuntu 26.04 has
|
||
`/usr/include/c++/16`, and the ROCm clang picks the newest tree, whose
|
||
`<atomic_wait.h>` references `std::__detail::__notify_impl` at a symbol
|
||
version the installed `libstdc++.so.6` (GLIBCXX_3.4.35) does not export.
|
||
The link then fails with `undefined symbol`. Pinning the headers to the
|
||
GCC the runtime library came from fixes it.
|
||
|
||
Verify an installed runtime with `bash scripts/build-gufo.sh --verify`, or
|
||
`gufo diagnose` directly — it reports `[PASS]` plus the detected
|
||
architecture, driver and queue budget.
|
||
|
||
## Shape
|
||
|
||
### Payload
|
||
|
||
- **Model:** `ajgazin/Swift-1.5-Qwen3.8-27B-Uncensored-Dynamic-MTP-GGUF`,
|
||
file `UD-Q8_K_XL` — 27B dense, 31,457,990,272 bytes, fetched and
|
||
sha256-verified (`9076f99a…`, the Hub LFS oid) by
|
||
`scripts/download-frank-dragoon-swift.sh`. It is an **abliterated
|
||
(uncensored)** fine-tune of Qwen3.8-27B. The repository also publishes an
|
||
`mmproj-BF16.gguf`, which is **not** used: this endpoint is text-only.
|
||
- **Licence:** the repo declares `other` — the **SWIFT OPEN LICENSE 1.0**,
|
||
which is *not* an OSI licence. That is a real constraint on redistribution,
|
||
noted here so nobody discovers it later. It is fine for a private fleet.
|
||
- **Replaced:** `unsloth/Qwen3.8-27B-GGUF:UD-Q8_K_XL`, still on disk and
|
||
still the rollback target for the payload.
|
||
|
||
The two targets are **structurally interchangeable for this engine**, which
|
||
is why the swap needed no engine change. Header-diffed directly:
|
||
|
||
| | unsloth | Swift-1.5 |
|
||
|---|---|---|
|
||
| `general.architecture` | `qwen35` | `qwen35` |
|
||
| tensors (count, names, order) | 866 | 866, **identical** |
|
||
| per-tensor `ggml_type` | Q8_0×453, F32×360, BF16×53 | **identical** |
|
||
| per-tensor dims | — | **0 mismatches** |
|
||
| MTP head | 4 tensors at `blk.64.nextn.*` | identical |
|
||
| geometry | 65 blk, 5120 embd, 17408 FFN, 24/4 heads | identical |
|
||
|
||
The two metadata differences that look alarming are both no-ops, and both
|
||
were confirmed against gufo's source rather than assumed:
|
||
|
||
- **`general.file_type` is 15 vs 7.** gufo's `gguf_reader.cpp` states it is
|
||
*"deliberately not consulted… The Unsloth UD-\*_K_XL artifacts this repo
|
||
targets carry a stale one — UD-Q8_K_XL records ftype 15 (Q4_K_M) … The
|
||
tensor table always is."* The file gufo already served lies about itself
|
||
here, so gufo reads the tensor table.
|
||
- **Swift adds `qwen35.attention.recurrent_layers`.** gufo never reads that
|
||
key anywhere in `src/`; layer kinds come from `full_attention_interval`,
|
||
which both files carry as `4` and which agrees with it.
|
||
|
||
### Serving shape
|
||
|
||
- **Context:** one session, full 262,144 tokens (the model's native window),
|
||
`SESSIONS=1`. The fleet's single stream is unchanged.
|
||
- **Spec method:** DFlash2, gufo's **adaptive** block policy
|
||
(`--draft-policy adaptive`, `--draft-tokens 7`). The drafter is the
|
||
**unchanged** z-lab `Qwen3.8-27B-DFlash2-Q4_K_M` (1.07 GiB) — it did not
|
||
move with the target, which is why `DRAFTER_DIR` is a separate tunable
|
||
from `MODEL_DIR` and why the deployer's stray-run sweep keys on the
|
||
drafter's filename rather than the target's.
|
||
- **Sampling:** greedy (`--temperature 0`) server-side, as every prior
|
||
frank-dragoon served. The harness overrides per request via `extraBody`
|
||
(1.0 / 0.95 / top-k 20).
|
||
- **Memory:** gufo's own table puts the 8-bit target at **50.58 GiB** peak
|
||
with context capacity 262,144, against the iGPU's ~114 GiB GTT aperture.
|
||
|
||
### Measured at the 2026-09-28 cutover
|
||
|
||
Live on Dankclaw. The **target** numbers are the Swift-1.5 load test
|
||
(a standalone run under the production flags, service stopped first); the
|
||
**engine** numbers are from the gufo cutover earlier the same day and are
|
||
unchanged by the payload swap.
|
||
|
||
- **9.2 s** `load_started` → `load_completed` for the Swift target, against a
|
||
20-minute `LLM_WAIT` budget. The unsloth target loaded in 13 s.
|
||
- **66.5 GiB** of the 116.7 GiB iGPU device at context capacity 262,144;
|
||
host RSS 30.6 GiB. No loader warnings, no skipped sections.
|
||
- Contract battery, all green on the Swift target: `/v1/models` →
|
||
`context_length 262144`, a chat completion carrying both `content` and
|
||
`reasoning_content`, a `tool_calls` response with
|
||
`finish_reason: tool_calls`, SSE streaming, and a **two-turn tool loop**
|
||
(tool call → tool result → follow-up) with prior reasoning preserved.
|
||
Arithmetic spot-check: "17*23" → `391`.
|
||
- Behavioural spot-check that this is the abliterated model: asked for a
|
||
two-sentence lockpicking scene, it wrote one without hedging or refusing.
|
||
- From the gufo cutover, on the unsloth target: a short-prompt live request
|
||
returned
|
||
`Server-Timing: ttft;dur=549.8, inter_token;dur=24.2` (≈41 tok/s).
|
||
Short-prompt decode is gufo's *winning* regime; see the depth table above
|
||
before quoting it as the endpoint's number.
|
||
|
||
### Tuning: the served flags are already optimal (2026-09-28)
|
||
|
||
Frank asked for iterative decode/prefill improvement at a 32k working point
|
||
without touching the model or the quantization. **Result: no win exists at
|
||
this engine's served settings, and nothing was changed.** Measured on real
|
||
repo prose and shell sources at **32,762 prompt tokens**, salted per run so
|
||
a warm prefix cache could not help, 3–5 repetitions each.
|
||
|
||
| lever | range swept | outcome |
|
||
|---|---|---|
|
||
| `--prefill-chunk` | 512 → 8192 | **no effect** — 410.7–412.4 t/s prefill across a 16× change |
|
||
| `--draft-tokens` | 5 → 10 | **flat** — 13.79–15.86 t/s decode, inside run-to-run noise |
|
||
| `--draft-policy` | fixed vs adaptive | 35.90 vs 35.50 (bench) — no effect |
|
||
| `--min-draft-tokens` | — | **rejected by the engine**: "DFlash2 requires … 1" |
|
||
| MTP backend | — | **unsupported for Q8**: `token_embd.weight` is Q8_0 |
|
||
| `--context` 262144 → 40960 | diagnostic | 13.93 vs 14.71 t/s — no gain, and it would break the declared client context |
|
||
|
||
The confirmation round at 5 reps **reversed** the 3-rep leaders: the pair
|
||
that looked best (`dt9 + chunk4096`) came last at 12.86 t/s, and the
|
||
**deployed configuration came first at 14.71 t/s**. Every difference sits
|
||
inside the noise band.
|
||
|
||
What *does* move the number is speculative decoding itself: **6.65 t/s off →
|
||
~15 t/s on, a 5.3× step** — and that is already switched on. Prefill is
|
||
insensitive to everything tried, which is consistent with gufo's own
|
||
`EXPERIMENTS.md`, where dozens of kernel-level alternatives are recorded as
|
||
rejected for "no release throughput improvement".
|
||
|
||
**Do not repeat this investigation without new information.** Two traps
|
||
cost most of the time, and both are in AGENTS.md's benchmarking policy:
|
||
`gufo bench` reports `acceptance=0.933 ± 0.000` on an identical trace hash
|
||
and **inverts** the draft-width ranking; and a live A/B needs ≥128 generated
|
||
tokens or `inter_token` is first-token noise.
|
||
|
||
## The template law: Qwen-Sharp is gone
|
||
|
||
`scripts/qwen-sharp-v22.4.0.jinja` is the fleet's chat template — it is
|
||
wired into `frank-archon` as `R9V_CHAT_TEMPLATE_FILE` and it was
|
||
`frank-dragoon`'s `--chat-template-file` under llama.cpp. **frank-dragoon can no
|
||
longer use it**, and the reason is stronger than "gufo lacks the flag":
|
||
|
||
> gufo has **no Jinja engine at all.** `src/models/qwen/chat_template.cpp`
|
||
> is 587 lines of hand-written C++ that appends strings to build ChatML.
|
||
> There is no `--chat-template-file`, no environment override, and no
|
||
> template body anywhere in the binary to point at. Qwen-Sharp is 453 lines
|
||
> of Jinja2 using `namespace()`, macros, `raise_exception` and image/video
|
||
> branches — supplying it would mean **reimplementing it in C++**, not
|
||
> handing gufo a file.
|
||
|
||
Given the choice on 2026-09-28, the decision was to **ship on gufo's stock
|
||
Jinja and accept the loss**. What that gives up, concretely:
|
||
|
||
| Qwen-Sharp feature | On gufo |
|
||
|---|---|
|
||
| `terse` system-prompt injection (the headline behaviour) | **gone** |
|
||
| in-band `<\|think_off\|>` / `<\|think_xhigh\|>` control | **gone** |
|
||
| `tool_call_format` `json` \| `xml` switch | **gone** |
|
||
| `max_tool_arg_chars` / `max_tool_response_chars` truncation | **gone** |
|
||
| `add_vision_id` → `Picture N:` prefixes | **gone** |
|
||
| effort instruction strings in the system prompt | kept (gufo has its own, same text) |
|
||
| `preserve_thinking` | kept (`--preserve-thinking on`) |
|
||
|
||
The `terse` injection is the one that matters most here, and it is worth
|
||
being blunt about the combination: this endpoint now runs an **abliterated**
|
||
model *without* the prompt that tells it to stop waffling. If verbosity
|
||
shows up, the cheapest fix is to send the `terse` text as an ordinary
|
||
**system message from the client** (`models.yml` / the harness) rather than
|
||
through a template — that recovers the behaviour without forking the
|
||
renderer. The full-fidelity options are reverting to llama.cpp or porting
|
||
Qwen-Sharp into the C++ renderer; both are worse trades today, and the
|
||
second is the same class of change that produced the 2026-09-21
|
||
"73,600-token prompt" incident.
|
||
|
||
## Client contract — what actually changed
|
||
|
||
Everything the fleet depended on still holds: port 7770, alias `frank-dragoon`,
|
||
`reasoning_content`, `tool_calls`, streaming SSE, and text-only (gufo
|
||
auto-discovers a `mmproj` beside the model; this payload has none).
|
||
|
||
The differences worth knowing:
|
||
|
||
- **No custom chat template — see
|
||
[The template law](#the-template-law-qwen-sharp-is-gone) above**, which is
|
||
the most consequential change on this endpoint. Thinking defaults to on at
|
||
`xhigh` with prior reasoning preserved (`--think auto
|
||
--preserve-thinking on`, spelled out in the serve script).
|
||
- **`reasoning_effort` accepts the full OpenAI set** (`none`, `minimal`,
|
||
`low`, `medium`, `high`, `xhigh`, `max`), all verified 200 against the
|
||
live endpoint. Qwen-Sharp rejected anything outside `{xhigh, medium, low}`
|
||
with an HTTP 500, which is why `models.yml` still carries a
|
||
`reasoningEffortMap` fold for this model. **The fold is kept on
|
||
purpose**: removing it would silently move every `high` role from `xhigh`
|
||
to `high` effort. Drop it as its own change.
|
||
- **Every field the harness sends is accepted.** `top_k`, `min_p`,
|
||
`presence_penalty`, `repetition_penalty` and `preserve_thinking` are all
|
||
outside gufo's documented request subset, and all returned 200. They are
|
||
applied from `models.yml`'s `extraBody` unchanged.
|
||
- **No KV-cache quantization flag.** gufo picks its own KV dtype; there is
|
||
no `--cache-type-k`. The old `CACHE_RAM=8192` tunable is gone with it.
|
||
Prompt reuse is on by default, and `--cache-disk` (restart-safe) is opt-in
|
||
and currently off.
|
||
- **Health/readiness split:** `GET /health` is process liveness and is what
|
||
the deployer gates on; `GET /ready` is model/backend readiness.
|
||
`GET /metrics` is Prometheus-format.
|
||
|
||
- **Long bodies:** `--max-request-bytes` defaults to 8 MiB.
|
||
|
||
## Failure modes worth knowing
|
||
|
||
- **A missing runtime is a deploy error, not a restart loop.** The handler
|
||
gates on `LLM_DRAGOON_RUNTIME` *before* it stops anything, and the serve
|
||
script exits with a `build-gufo.sh` pointer rather than exec'ing.
|
||
- **Host memory is the ceiling, and it is shared.** frank-dragoon and ComfyUI
|
||
both hold gfx1151 compute queues. gufo reads
|
||
`/sys/class/kfd/kfd/proc/*/queues/*/type` and caps itself
|
||
(`GPU_MAX_HW_QUEUES`, 2 resident for `serve llm`) so a full device
|
||
time-slices instead of failing; four HIP processes is the device ceiling.
|
||
- **Load is seconds, not minutes** (9.2 s measured cold on the Swift
|
||
target), so a redeploy no longer spends 20 minutes in `LLM_WAIT`.
|
||
- **A payload is two files in two directories.** The target and the drafter
|
||
no longer live together, so "the payload is present" is not one check; the
|
||
deployer gates on both paths and the serve script refuses to start if
|
||
either is absent. A symlinked "one payload directory" would hide that.
|
||
- **A v0.1.x runtime is a moving target.** Rebuilds are not automatic —
|
||
`RUNTIME_DIR` names a version, and the deployer will not silently
|
||
substitute another.
|
||
|
||
## Rollback
|
||
|
||
Two independent rollbacks, because two things changed on 2026-09-28:
|
||
|
||
1. **Payload only** — repoint `MODEL_FILE` in `scripts/frank-dragoon.env` at
|
||
`/home/dank/models/Qwen3.8-27B-UD-Q8_K_XL/Qwen3.8-27B-UD-Q8_K_XL.gguf`
|
||
and redeploy. **The 29.3 GiB target was reclaimed on 2026-09-28**, so this
|
||
path now needs a re-fetch first: `scripts/download-frank-dragoon.sh`, then
|
||
the env edit. The DFlash2 drafter was never touched and still lives in
|
||
that directory.
|
||
2. **Engine** — [[frank-dragoon-unsloth-qwen38-27b-q8xl]]: the unsloth b11030
|
||
prebuilt is still at `/home/dank/GitPackages/unsloth-llama-b11030/`.
|
||
Revert `RUNTIME_DIR` and the gufo-only tunables, restore the serve
|
||
script's llama.cpp command, and redeploy. This is also the only way to
|
||
get `qwen-sharp-v22.4.0.jinja` back.
|
||
|
||
# Citations
|
||
|
||
- Runtime: https://github.com/gufo-org/gufo (pinned `v0.1.1`, MIT)
|
||
- Measured tables: `docs/models/qwen3.8-27b/BENCHMARKS.md` in that repo
|
||
- API contract: `docs/SERVER.md`; flags: `docs/CLI.md`; the C++ renderer
|
||
that rules out a Jinja template:
|
||
`src/models/qwen/chat_template.cpp` (587 lines)
|
||
- GGUF header evidence for the payload swap: both files' headers were parsed
|
||
and diffed directly (architecture, all 866 tensor names in order, per-tensor
|
||
`ggml_type` and dims) before either was loaded
|
||
- Build: `scripts/build-gufo.sh`; serve: `scripts/serve-frank-dragoon.sh`;
|
||
tunables: `scripts/frank-dragoon.env`
|
||
- Payload: `scripts/download-frank-dragoon-swift.sh` (target),
|
||
`scripts/download-frank-dragoon.sh` (DFlash2 drafter)
|
||
- Predecessor: [[frank-dragoon-unsloth-qwen38-27b-q8xl]]
|