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.
187 lines
9.8 KiB
Markdown
187 lines
9.8 KiB
Markdown
---
|
||
type: reference
|
||
title: "frank-dragoon on Dankclaw — vLLM TheRock, MiMo-V2.6-Distill-Qwen-9B BF16 + cross-model MTP (RETIRED)"
|
||
description: "The RETIRED frank-dragoon runbook (2026-10-01, one day) — the digest-pinned kyuz0 TheRock gfx1151 vLLM image serving XiaomiMiMo MiMo-V2.6-Distill-Qwen-9B in full BF16 on the Strix Halo iGPU. Kept for the measured record: the roof math, the cross-model MTP drafter, and the autoresearch finding that spec decode is NET-NEGATIVE on real chat+reasoning traffic here (6.19 t/s with MTP-2 vs 9.01 t/s without). Rolled back to [[frank-dragoon-gufo-qwen38-27b]] by Frank the same night."
|
||
tags:
|
||
- llm
|
||
- frank-dragoon
|
||
- dankclaw
|
||
- vllm
|
||
- strix-halo
|
||
- mimo
|
||
- mtp
|
||
- spec-decode
|
||
- bf16
|
||
timestamp: 2026-10-01
|
||
---
|
||
|
||
# frank-dragoon — vLLM MiMo-V2.6-Distill-Qwen-9B BF16 (cross-model MTP)
|
||
|
||
> **RETIRED 2026-10-01 night, one day active.** Frank's call after the
|
||
> autoresearch loop: battery decode 6.19 t/s (MTP-2) / 9.01 t/s (no draft)
|
||
> against gufo's ~15 t/s on the 27B — and every ≥6-bit quant path in vLLM
|
||
> 0.30 is dead on gfx1151 (quark int8 rejected; all FP8 kernels CDNA-only;
|
||
> torchao config unwired). Back to [[frank-dragoon-gufo-qwen38-27b]]
|
||
> (Q4_K_XL, 1 x 262144). The measured content below stands as the record.
|
||
|
||
## Identity
|
||
|
||
- **Endpoint:** `frank-dragoon` at `192.168.0.143:7770` (Dankclaw).
|
||
- **Runtime:** vLLM 0.30.0 in the **digest-pinned** `docker.io/kyuz0/vllm-therock-gfx1151`
|
||
image (image ID `2d24a8c5a3fd`, digest `sha256:98c1f3ac9833…` — ROCm 10.0.0 /
|
||
torch 2.11.0 / TheRock, Triton Gated DeltaNet kernels). The official vLLM
|
||
images are CUDA-only; this fork is the fleet's gfx1151 path. Plain
|
||
`podman run` driven by `scripts/serve-frank-dragoon.sh` under a root unit
|
||
(`scripts/frank-dragoon.service`, `User=dank`, `Restart=always`,
|
||
`ExecStop=podman stop -t 120`, `TimeoutStopSec=180` > the stop grace).
|
||
- **Model:** `XiaomiMiMo/MiMo-V2.6-Distill-Qwen-9B` in **full BF16**
|
||
(17.53 GiB, 4 shards) — the same checkpoint frank-zealot serves as an INT8
|
||
W8A16 AutoRound conversion on the 3090, here unquantized.
|
||
- **Draft:** `Qwen/Qwen3.5-9B` (18 GiB on disk; only the 15 `mtp.*` tensors
|
||
are loaded into the draft pass).
|
||
|
||
## Why this shape (the roof math)
|
||
|
||
Decode speed for a dense model on Strix Halo is bandwidth-bound:
|
||
BF16 weights x one pass per token against the LPDDR5x aperture. Measured
|
||
2026-10-01 on this host: **13.1 t/s is ~94% of the roof** — the payload, not
|
||
a misconfiguration. The same INT8 payload reaches 83.9 t/s on the 3090
|
||
because it moves ~12 GiB through ~936 GB/s. The only way past a bandwidth
|
||
wall is generating more than one token per weight pass: **speculative decode
|
||
is the performance feature of this endpoint**, not a garnish.
|
||
|
||
The memory side is the opposite of a squeeze: KV grows ONLY on the 8 full
|
||
attention layers (32 KiB/token bf16: 8 x 2 x 4 heads x 256 dim x 2 B);
|
||
the 24 Gated DeltaNet layers hold a FIXED ~25 MiB state per running
|
||
sequence. `--max-model-len 262144` (the native window) with
|
||
`--max-num-seqs 4` at `--gpu-memory-utilization 0.45` boots clean — the
|
||
four-session contract the gufo shape ran, with the pool shared by the
|
||
scheduler instead of carved per session.
|
||
|
||
## Spec method: cross-model MTP — measured, not assumed
|
||
|
||
| arm | prefill | decode | acceptance |
|
||
|---|---|---|---|
|
||
| BF16 base | 17,876 t/s | 13.1 t/s | — |
|
||
| BF16 + DFlash 8 tok | 7,976 t/s | 17.4 t/s | pos0 59% pos1 26% (token rate 14.8%) |
|
||
| **BF16 + MTP 2 tok** | 7,380 t/s | **19.8 t/s** | pos0 71% pos1 44% (token rate 57.4%) |
|
||
|
||
One session, temperature 0, 28,647-token prefill / 512-token decode, same
|
||
client for all three arms (2026-10-01 bench window, frank-dragoon stopped per
|
||
Benchmarking Policy). **MTP wins (+51% vs +33%) and needs only 2 draft
|
||
tokens** — acceptance past position 1 is 44% and falling; DFlash's own curve
|
||
decays 59/26/14/7, so 8 proposals are mostly wasted verification.
|
||
`num_speculative_tokens` beyond 2 is UNMEASURED; do not raise it without a
|
||
fresh A/B. Both spec arms pay ~55–59% of prefill (the draft passes share
|
||
the compute queues) — this endpoint's thinking-heavy traffic spends its wall
|
||
clock in decode, which is the regime where that trade wins.
|
||
|
||
**Why a Qwen head accepts on a MiMo target:** MiMo-V2.6 is a distillation of
|
||
Qwen3.5-9B that preserves the hidden-state geometry — identical
|
||
4096-hidden/248320-vocab/32-layer/interval-4 structure, and the 71%/44%
|
||
per-position acceptance is the direct measurement that the distillation
|
||
preserved more than geometry. MiMo itself ships **ZERO `mtp.*` tensors**
|
||
(verified in both the BF16 base and the INT8 conversion) — which is exactly
|
||
why frank-zealot's SGLang NEXTN draft is a 0.00-acceptance ghost: SGLang
|
||
reads the head from the TARGET checkpoint, vLLM reads the draft from a
|
||
separate path. That difference is the whole trick:
|
||
|
||
```
|
||
--speculative-config '{"method":"mtp","model":"/models/Qwen3.5-9B","num_speculative_tokens":2}'
|
||
```
|
||
|
||
The alternative drafter (`z-lab/Qwen3.5-9B-DFlash`, 6 layers, target_layer_ids
|
||
[1,5,…,29]) stays fetched on disk; flip `FRANK_DRAGOON_SPEC=dflash` and repoint
|
||
`FRANK_DRAGOON_SPEC_DIR` to use it. Its card's "vLLM PR #40898 unmerged" claim
|
||
is FALSE — `vllm/v1/spec_decode/dflash.py` ships in 0.30.
|
||
|
||
## The numerics trap (greedy text is NOT preserved by spec decode)
|
||
|
||
Measured: temperature-0 completions DIFFER across arms (sha `1318aeb6` base,
|
||
`0208b45d` dflash, `20c41c38` mtp). Batched verification changes the bf16
|
||
reduction order, argmax near-ties diverge, and the divergence cascades.
|
||
vLLM spec decode is distribution-preserving, not bitwise-greedy-preserving.
|
||
The gufo-era "greedy == autoregressive text" law does not carry over; do not
|
||
chase text drift between spec and no-spec arms as a regression.
|
||
|
||
## Payload
|
||
|
||
- Target: 4 safetensors shards (17.53 GiB) + config/tokenizer under
|
||
`/home/dank/models/MiMo-V2.6-Distill-Qwen-9B-BF16/`, each shard
|
||
byte-count + LFS sha256-verified by
|
||
`old/download-frank-dragoon-mimo.sh` (`--verify-only` re-checks; no token on
|
||
the curl command line).
|
||
- MTP source: `old/download-frank-dragoon-qwen35.sh` →
|
||
`/home/dank/models/Qwen3.5-9B/` (4 shards behind
|
||
`model.safetensors.index.json`; the shards carry the odd HF asset names
|
||
`model.safetensors-0000N-of-00004.safetensors` — the index resolves them,
|
||
the engine boots from exactly this tree).
|
||
- DFlash drafter: `old/download-frank-dragoon-dflash.sh` →
|
||
`/home/dank/models/Qwen3.5-9B-DFlash/`.
|
||
- **Payload status 2026-10-02:** all three trees moved to
|
||
`/home/dank/.AGENTTRASH/20261002-retired-llm-payloads/` (59 GiB staged with
|
||
the other retired frank payloads) and the fetch trio moved to `old/` — the
|
||
endpoint is two runtimes past this one, and re-fetch is cheaper than disk.
|
||
- The serve script gates the image by REPO DIGEST (rootless podman resolves
|
||
tags from the network; the pin exists to boot exactly these bytes
|
||
offline), then config + index + exact shard count, then the spec draft
|
||
(either weight layout: single `model.safetensors` or a sharded index),
|
||
then `/dev/kfd` + whole `/dev/dri` (never individual renderD nodes —
|
||
Dankserver 2026-09-29 lesson).
|
||
|
||
## Client contract
|
||
|
||
- **Alias:** `frank-dragoon` (the `id` at `/v1/models`).
|
||
- **Reasoning:** `--reasoning-parser qwen3`; vLLM emits the thinking text in
|
||
**`reasoning`**, NOT llama.cpp/gufo's `reasoning_content` — the harness
|
||
`models.yml` compat block for this provider sets
|
||
`reasoningContentField: "reasoning"`. Miss that and thinking silently
|
||
vanishes from transcripts.
|
||
- **Tools:** `--enable-auto-tool-choice --tool-call-parser qwen3_coder`.
|
||
- **Vision:** the checkpoint carries the `qwen3_5_vision` tower but the
|
||
serve script passes `--language-model-only` — the endpoint stays
|
||
text-only by decision; dropping the flag brings vision back.
|
||
- **Sampling:** request-side; the harness sends the Qwen3.5-family recipe
|
||
(temp 0.7, top_p 0.8) via `compat.extraBody`, and
|
||
`chat_template_kwargs.{enable_thinking,preserve_thinking}` — the
|
||
`chat_template_kwargs` form is what vLLM reads; a top-level
|
||
`preserve_thinking` is ignored there.
|
||
- **Env hygiene:** every tunable is `FRANK_DRAGOON_`-prefixed because HIP
|
||
reads bare `TEMP`/`HOST`/`GPU_MAX_HEAP_SIZE` and the serve script exports
|
||
the env file with `set -a`.
|
||
|
||
## Ops
|
||
|
||
- Boot ~200–240 s warm; health 503 while loading (do not kill it).
|
||
- The serve script sweeps stale container identities before starting:
|
||
`frank-dragoon-mimo` itself and the bench-window container `vllm-mimo`.
|
||
- Container death → `podman wait` returns non-zero from ExecStart →
|
||
`Restart=always` self-heals (a clean exit-0 drain also restarts, the
|
||
frank-archon lesson).
|
||
- Bench windows: `systemctl stop frank-dragoon` is enough (mask fails on the
|
||
relinked unit; the deploy restores).
|
||
- First-deploy facts (2026-10-01, deploy #3): healthy after **230 s**;
|
||
boot log `GPU KV cache size: 819,332 tokens, Maximum concurrency for
|
||
262,144 tokens per request: 3.13x` — four sessions share the pool
|
||
(the scheduler, not a carve). Live battery green: alias `frank-dragoon`,
|
||
reasoning split into `reasoning`, tool_calls with correct args, and
|
||
`/metrics` acceptance pos0 69% pos1 44% (54/86 draft tokens accepted),
|
||
matching the bench window.
|
||
|
||
## Rollback
|
||
|
||
Git tag **`frank-dragoon-gufo-q4kxl-20261001`** — gufo v0.1.1 (built from
|
||
source, `scripts/build-gufo.sh` stays on disk) + the Q4_K_XL Qwen3.8-27B
|
||
payload + DFlash2 drafter, all still on the host. Restoring the tag's
|
||
unit/env/serve trio and redeploying returns the 27B endpoint.
|
||
|
||
# Citations
|
||
|
||
- Model: https://huggingface.co/XiaomiMiMo/MiMo-V2.6-Distill-Qwen-9B
|
||
- Draft source: https://huggingface.co/Qwen/Qwen3.5-9B
|
||
- DFlash drafter: https://huggingface.co/z-lab/Qwen3.5-9B-DFlash
|
||
- Engine image: https://hub.docker.com/r/kyuz0/vllm-therock-gfx1151
|
||
- Serve script: `scripts/serve-frank-dragoon.sh`; tunables: `scripts/frank-dragoon.env`
|
||
- Predecessor: [[frank-dragoon-gufo-qwen38-27b]]; sibling on the same arch:
|
||
[[frank-zealot-sglang-mimo-v26]]
|