docs: frank-primary runs FreeToken (PR587 ROCm) — runbook, index, AGENTS, log

New runbook frank-primary-freetoken-pr587 (build laws, shape, measured
cutover numbers, TP=2 non-boot signature, rollback); LLM/index.md intro +
mermaid + active/retired entries; AGENTS.md fleet row + section + bench
and model-config mentions + runbook index; log.md entry. Seam pins the
measured KV pool 299968 with the multiple-of-64 guard.

NOTE: intended PGP signature skipped — key 0x76EA601A11B178D7 has no
secret key in this machine's keyring (fresh ~/.gnupg); the PGP identity
lives elsewhere.
This commit is contained in:
Frank
2026-10-05 19:35:31 -07:00
parent 6ba19f6f48
commit e2308dff71
5 changed files with 295 additions and 139 deletions
@@ -0,0 +1,170 @@
# frank-primary — FreeToken PR #587 ROCm (Qwen3.8-Flash-Next-Uncensored-FP8, 1× R9700)
**Status: ACTIVE 2026-10-05 →** · **Host:** Dankserver (192.168.0.235) · **Port:** 7770 · **GPU:** 1× Radeon AI PRO R9700 (gfx1201) — the second R9700 sits idle
The tcclaviger RDNA4 vLLM fork was replaced by **FreeToken at PR #587 head
`77a7201f98f00d93f618c6cf8b81ffe8360e0899`** (branch `rocm714_torch214`), built
on the host into the local image
`localhost/frank-primary-freetoken:pr587-77a7201` by
`scripts/docker/frank-primary-freetoken.Dockerfile`. Same port, same served
alias (`frank-primary`), same 131-shard payload
(`/home/dank/models/Qwen3.8-Flash-Next-Uncensored-FP8`). Rollback:
git tag `frank-primary-vllm-shapeG-20261003` (the vLLM trio + its env lore),
runbook [[dankserver-qwen38-fp8-expert-offload-tuning]].
## Why FreeToken
The vLLM fork planned a per-boot VRAM/host tiering from an estimator: util
0.982 was a hard law (1.00 was **unbootable** — worker init raises
`ValueError: Free memory on device cuda:N (31.29/31.86 GiB)` when budget ==
total), the expert arena capped at 17.77 GiB / 7,762 slots, and live traffic
still degraded ~40k tokens in (the GDN state-fault class, never fixed in the
fork). FreeToken keeps **all expert weights in pinned host RAM** with an LRU
GPU cache, sizes **KV as an explicit token pool**, and boots in one
deterministic pass — no per-boot estimator to fight.
## The build (on dankserver, rootless podman as dank)
```bash
cd /home/dank/serverAppConfigs
podman build -t frank-primary-freetoken:pr587-77a7201 \
-f scripts/docker/frank-primary-freetoken.Dockerfile scripts/docker/
```
Laws baked into the Dockerfile (each one cost a failure):
- **Base digest-pinned**: `rocm/pytorch:rocm7.14_ubuntu24.04_py3.12_pytorch_release_2.11.0@sha256:a223aee1…` —
the PR doc's known-good stack (torch 2.11.0+rocm7.14, triton 3.7). No
rocm7.14+torch2.14 image exists on Docker Hub (the 2.14 images are
rocm10.1.0 — an untested pair for this PR).
- **`--no-build-isolation --no-deps -e .`** — a plain
`pip install -e .` on ROCm can REPLACE the image's ROCm torch with a CUDA
build (the dependency set is authored for NVIDIA). Runtime deps are
curated explicitly; `flashlib==0.3.0 --no-deps` (its numba /
nvidia-cutlass-dsl deps are CUDA-only); ninja for the QSA JIT;
**flashinfer / sgl_kernel intentionally absent** (no ROCm builds;
FreeToken falls back to its Triton kernels — the PR's 12 pytest failures
are exactly those backends).
- **`libhiprtc.so` dev symlink**: the pip-ROCm torch ships only the
versioned `libhiprtc.so.7` in
`/opt/venv/.../site-packages/_rocm_sdk_core/lib`, but the PR's `_cpu_moe`
extension links `-lhiprtc`. First build died `ld: cannot find -lhiprtc`;
the Dockerfile symlinks the unversioned name into the ALREADY-LINKED dir.
- FreeToken source is checked out at the **pinned PR head**, not the branch
(a future push to `rocm714_torch214` cannot change the deployed engine).
- The stale Docker Hub login (`docker login` creds at
`~/.docker/config.json`) poisons podman's ANONYMOUS pulls of public images
with "invalid username/password" — `docker logout` fixes the pull. Hit
2026-10-05 while pulling the base image.
## Measured at cutover (2026-10-05/06)
- **Boot**: ~2.3 min container-start → API-ready (131 shards page-in + PLE
pin + expert staging + CUDA graphs `[1,2]`). VRAM after graphs:
**1.80 GiB free** of 31.86. RAM: 122 GB used + **113 GB shared (the
pinned expert/PLE slabs)**, 123 GB still available.
- **Decode: 17.1 tok/s** single-stream, temp 0, 400-token probe (clean
1→N counting). The vLLM incumbent measured ~30 tok/s on TWO cards;
FreeToken single-card is the deployed baseline.
- **The single-card LRU ceiling**: the KV contract (harness worst case
177,356 tokens) + dense fp8 + the vision tower + graphs leave ~12 GiB for
the expert LRU at rate 0.10 — and only 1.8 GiB of live headroom, so
`ft ctl cache --moe` cannot raise it far on one card. The paths to a
better decode rate are TP=2 (below) or a smaller pool (contract
violation — the 177,356 floor is real traffic, not padding).
- **TP=2 exists but does not boot on ROCm** (probed 2026-10-06,
zero-impact window): `--tensor-parallel-size 2 --gpu 0,1` dies in the TP
allreduce JIT — `pynccl.cu` compiles per-rank at boot and clang can't
resolve `cuda_runtime.h` from the hipified include path
(`1 error generated when compiling for gfx1201`, ninja stopped). The PR
author validated single-GPU only; this is FreeToken's untested-TP-on-ROCm
edge, not a board or driver fault (radiance TP=2 allreduces fine on this
pair since the 2026-09-20 swap). A future fix = patch the JIT include
path (HIP's cuda_runtime shim) or vendor an ROCm allreduce — re-probe
then.
- **Verification gates passed**: /health ok; plain completion `FLEET-OK`
(finish stop); thinking split — `reasoning_content` non-empty AND
`content` carries the answer (17×24 → "408"); native `tool_calls` with
correct name+arguments (finish tool_calls); 256×256 solid-red PNG through
an `image_url` part answers exactly "Red" (prompt_tokens 127); stats:
native ctx 262,144, KV 4,687 pages × 64 = 299,968, modalities
[text,image], sampling defaults from generation_config.json
(temp 1.0 / top_p 0.95 / top_k 20).
- **Thinking counts against max_tokens** (same as the vLLM era): a
`max_tokens: 10` probe returns EMPTY content with the budget spent in the
think block. Clients must budget for thinking or disable it.
- **Prometheus scrapes /metrics → 404** (Dankclaw scrapes 192.168.0.235:7770
from the llm-fleet job; FreeToken has no /metrics endpoint). The target
will read down until the scrape config is repointed or a metrics shim
lands — same class as the retired HTML-probe targets
(see [[Observability]]).
## The shape
| Knob | Value | Why |
|---|---|---|
| `--gpu` / `HIP_VISIBLE_DEVICES` | 0 / 0 | ONE R9700; visibility pinned so in-container index 0 is the right card |
| `--num-tokens` | **299,968** | KV pool in TOKENS; **must be a multiple of 64** (the auto attention backend is `qsa_sparse`, which overrides page size to 64 — 300,000 was refused: `nearest valid values: 299968 or 300032`) |
| `--max-running-requests` | 2 | first-boot conservative; raise after real-traffic slot-occupancy look |
| `--max-output-tokens` | 36,864 | harness maxTokens; requests that omit max_tokens get this default |
| `--moe-cache-rate` | 0.10 | GPU expert LRU as fraction of all experts (~120 GiB fp8 → ~12 GiB VRAM); first-boot conservative |
| parsers | AUTO | thinking → `reasoning_content` natively; tools in the qwen3.8 dialect; NO `--reasoning-parser`/`--tool-call-parser` flags |
KV math: 12 full-attention layers × 2 KV heads × 256 head_dim × 2 (K+V) ×
2 B = **24 KiB/token** (f16 KV; the 36 GDN layers use fixed per-seq state,
not per-token KV) → 299,968 × 24 KiB ≈ 7.0 GiB. Clears the harness worst
case (140,492-token prompt + 36,864 max_tokens = 177,356) and admits ONE
native 262,144-token conversation + full budget. `models.yml`
`contextWindow: 200704` stays (compacts early, never 400s).
## RAM math (why the root unit + memlock)
246 GiB total. FreeToken pins ~120 GiB of fp8 expert rows + the ~47.7 GiB
PLE ngram table in host RAM → the unit carries
`LimitMEMLOCK=infinity` (root unit; dank's login session caps at 8 MiB and
a user quadlet cannot raise it). Weights+PLE+overhead ≈ 190 GiB — the vLLM
container's ~170 GiB MUST be down first; `systemctl stop frank-primary`
frees it (measured: 207 GB free right after the stop).
## Resize live
```bash
ft ctl cache --kv <tokens> # grows the KV pool LIVE (shrink needs restart)
ft ctl cache --moe <rate> # same for the expert LRU
ft ctl stats # pool occupancy, VRAM, throughput
```
## Verification (the fleet's real-completion law)
1. `/health` 200 — necessary, not sufficient.
2. Plain completion returns the asked-for string.
3. Thinking request: `reasoning_content` non-empty AND `content` carries the
answer (the models.yml `reasoningContentField: reasoning_content` flip
rides on this).
4. Vision probe: a solid-color PNG through an `image_url` part answers the
exact color (the legacy top-level `images` field trap does not exist
here — FreeToken is OpenAI-native).
5. Unit-managed: `systemctl show -p MainPID` behind the health answer (the
2026-09-26 stray-port false green).
## Rollback
```bash
git checkout frank-primary-vllm-shapeG-20261003 -- \
scripts/frank-primary.env scripts/serve-frank-primary.sh \
scripts/frank-primary.service
./tests/run.sh && git commit -m "frank-primary: rollback to vLLM shape G" \
&& git push && ./scripts/deploy.sh dankserver --service frank-primary
```
The tcclaviger image and the FreeToken image coexist in dank's rootless
storage; the deploy re-pulls the pinned digest if absent.
## Index
- [[dankserver-qwen38-fp8-expert-offload-tuning]] — the retired vLLM shape
(rollback target; its 362-line env lore lives in the tag)
- [[freetoken-frank-small]] — the v0.1.2 CUDA precedent (ortobox, RTX 3090)
- [[LLM/index]] — fleet index
+33 -20
View File
@@ -1,18 +1,17 @@
# LLM Fleet — Knowledge Base
Documentation for the LLM inference fleet on the two hosts (2026-10-03):
**Dankserver runs the tcclaviger RDNA4 vLLM fork on frank-primary** (the
digest-pinned `tcclaviger/vllm` image driving `podman run` directly, serving
orcarouter/Qwen3.8-Flash-Next-Uncensored-FP8 across both Radeon AI PRO R9700s
on :7770, TP=2 with automatic tiered-expert offload, **shape E since
2026-10-02: 3 slots x 200704 ctx** with the KV pool sized for **~2 full-length
conversations** — measured 398,658 tokens = 1.99x — fp8 KV, no speculative
decode, MNBT 1024, MoE gather 32x64, util 0.98, reasoning/tool parsers wired
(qwen3 / qwen3_xml) with preserve_thinking on, plus **`--expert-precise-memory`
(the arena grows to 17.22 GiB / 7,522 slots, +16%), `GPU_MAX_HW_QUEUES=2`, and a
mounted cache dir** (the image ships no `/root/.cache` at all; unmounted, every boot re-pays
`torch.compile took 181.20 s`). `--max-num-seqs` must stay 3:
SEQS 2 cannot compile this model. Rollback frank-primary-fp8-mtp-20261001),
Documentation for the LLM inference fleet on the two hosts (2026-10-05):
**Dankserver runs FreeToken (PR #587 ROCm) on frank-primary** (the local
image `frank-primary-freetoken:pr587-77a7201` built on the host from
`scripts/docker/frank-primary-freetoken.Dockerfile` — FreeToken at PR head
`77a7201` in the PR's own ROCm 7.14 base image, torch 2.11.0+rocm7.14 —
driving `podman run` directly, serving orcarouter
/Qwen3.8-Flash-Next-Uncensored-FP8 on ONE R9700, :7770: all experts in
pinned host RAM behind a GPU LRU (`--moe-cache-rate 0.10`), KV pool
`--num-tokens 299968` (multiple-of-64 law of the qsa_sparse backend), 2
decode slots, parsers AUTO (thinking → `reasoning_content` natively;
measured 17.1 tok/s decode, TP=2 exists but does not boot on ROCm — see the
runbook). Rollback tag frank-primary-vllm-shapeG-20261003),
and Dankclaw
runs two endpoints — **halogen container (frank-large, port 7770:
ghcr.io/peonist-ai/halogen @ pinned digest serving peonist-ai
@@ -34,12 +33,12 @@ graph LR
subgraph Dankserver[AMD Dankserver - Ubuntu 26.04]
A[2x Radeon AI PRO R9700
gfx1201, 32 GiB each]
B2[tcclaviger RDNA4 vLLM fork, digest-pinned
B2[FreeToken PR587 ROCm, local image
podman run, frank-primary, port 7770]
C[Qwen3.8-Flash-Next FP8, no spec decode
TP=2, expert offload automatic, 3 x 200704 ctx,
fp8 KV pool ~2 full-length, MNBT 1024, gather 32x64, qwen3 parsers,
expert-precise-memory arena 7,522 slots, /root/.cache mounted]
C[Qwen3.8-Flash-Next FP8
1x R9700, experts in pinned host RAM + GPU LRU 10%,
KV pool 299,968 tokens, 2 slots, parsers AUTO,
TP=2 untested on ROCm; second R9700 idle]
end
subgraph Dankclaw[AMD Strix Halo - Ubuntu 26.04]
@@ -95,8 +94,22 @@ frank-small, port 7771]
target; payload on disk; owns the measured boot-pair table)**: the same
runtime and the same 2 x 200,704 + f16 KV shape one quant rung lower —
104.8 tok/s, 3,571 MiB true free
- [[dankserver-qwen38-fp8-expert-offload-tuning]] — **ACTIVE since
2026-10-02 (shape E)** (root systemd unit, port 7770): the tcclaviger RDNA4
- [[frank-primary-freetoken-pr587]] — **ACTIVE since 2026-10-05**
(root systemd unit, port 7770): FreeToken at PR #587 head `77a7201`
(branch rocm714_torch214) built on the host into the local image
`frank-primary-freetoken:pr587-77a7201` (Dockerfile: digest-pinned
rocm/pytorch rocm7.14 torch-2.11.0 base, libhiprtc dev-symlink law,
curated --no-deps install per docs/install_amd.md), serving the SAME
orcarouter FP8 payload on ONE R9700 — experts in pinned host RAM
(~120 GiB + 47.7 GiB PLE), GPU LRU at 0.10, KV pool 299,968 tokens
(multiple-of-64 law), 2 slots, parsers AUTO (thinking →
`reasoning_content`). Measured: boot ~2.3 min, decode 17.1 tok/s
single-stream, vision "Red" probe passes, native tool_calls; TP=2 does
not boot on ROCm (pynccl JIT) — second card idle. Rollback: git tag
frank-primary-vllm-shapeG-20261003
- [[dankserver-qwen38-fp8-expert-offload-tuning]] — **RETIRED 2026-10-05
(rollback target; the vLLM trio + env lore live in the tag)**: the
tcclaviger RDNA4
vLLM fork serving orcarouter/Qwen3.8-Flash-Next-Uncensored-FP8 across both
R9700s, TP=2 with automatic expert offload — `--max-model-len 200704` x 3
slots, fp8 KV, pool 3,150,000,000 B/rank (measured 398,658 tokens = 1.99x),
+26
View File
@@ -1,5 +1,31 @@
# Bundle Change Log
## 2026-10-05
- **frank-primary: tcclaviger vLLM fork → FreeToken (PR #587 ROCm) on
dankserver.** Built FreeToken at PR head `77a7201` on the host into the
local image `frank-primary-freetoken:pr587-77a7201`
(digest-pinned rocm/pytorch rocm7.14 torch-2.11.0 base; two build laws
bought with failures: the pip-ROCm wheel needs a `libhiprtc.so` dev
symlink for the PR's `_cpu_moe` link, and a stale `docker login` at
`~/.docker/config.json` poisons podman's ANONYMOUS pulls). Trio rewritten
through git (env/serve/unit + seam + deployer): ONE R9700, experts in
pinned host RAM behind a 0.10 GPU LRU, PLE 47.7 GiB pinned, KV pool
299,968 tokens (multiple-of-64 — the `qsa_sparse` auto backend overrides
page size to 64; 300,000 was refused), 2 slots, parsers AUTO (thinking →
`reasoning_content`; models.yml flipped accordingly). Container renamed
to the unit name (Frank's call; `frank-primary-qwen` → legacy list);
deployer image gate → `podman image exists` (local builds carry no
RepoDigests) and health gate reads the BODY — FreeToken 200s with
`{"status":"loading"}` mid-load, which false-greened a deploy at 6 s
(vLLM 503'd; the bare curl was honest there). Verified: FLEET-OK
completion, reasoning split (17×24 → "408"), native `tool_calls`, vision
"Red" probe, boot 2.3 min cold / 92 s warm, decode 17.1 tok/s
single-stream (vLLM was ~30 on two cards; the KV contract caps the
single-card LRU; TP=2 exists but the pynccl JIT can't compile for
gfx1201 — probed, signature in the runbook). `/metrics` is a 404 — the
Dankclaw Prometheus llm-fleet scrape of :7770 reads down until
repointed. Rollback: tag `frank-primary-vllm-shapeG-20261003`.
Runbook: `serverAppConfigs-docs/LLM/frank-primary-freetoken-pr587.md`.
## 2026-10-05
- **e2e: the FrankBook-Pro leg scoped to its own trigger-gated Playwright
project.** The `11-admin` walk included the six `*arr`/qBittorrent admin