The docs-fact census wave re-read the live hosts and re-keyed every page it could: runtime/payload identities, ports, sizes, ledger figures, unit state, and the retirement banners that drifted as the fleet moved. The wave was interrupted mid-run, so this commit carries only what passed the integrity gates: every touched page parses (front matter or H1+status convention intact, balanced fences, no truncated tables, trailing newline), the docs_completeness and link-integrity seams are green, and bats is 384/384. Numbers that this session proved on live hosts are keyed to the measured value, not the page's earlier claim.
30 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | |||||
|---|---|---|---|---|---|---|---|---|---|
| research | FastFlowLM NPU Inference | FastFlowLM (FLM), the AMD NPU-first LLM runtime for Ryzen AI XDNA2 chips. Fit analysis for the 50 TOPS NPU on Dankclaw, published benchmarks, verified install state, and a test runbook. |
|
2026-09-03 |
SUPERSEDED 2026-08-17. The Frank redo retired this topology. See frank-redo-2026-08-17 and deployment for the active configuration. The
frank-zealotname and port 52625 then belonged to the vLLM service on Dankclaw (nameistoken Quark W8A8); this page is archive.The NPU endpoint is retired — 2026-09-11. On Dankclaw today
frank-npu.serviceismasked/inactiveand nothing listens on 52625 (ss -ltn, read 2026-10-18). The runtime is still installed (fastflowlm 1.0.1,libxrt2/libxrt-npu2,/opt/fastflowlm,~/.config/flm,/usr/bin/flm— all read 2026-10-18) and the device is usable again since the 2026-10-18iommu=ptboot (amdxdnabound,/dev/accel/accel0present — dankclaw-iommu-translation-off), but no service runs on it.frank-zealottoday is the llama.cpp PrismML endpoint on the RTX 3090 (frank-zealot-llama-prism-bonsai2-pq20). See frank-npu for the retirement record. Everything below is the 2026-08 research record.
FastFlowLM NPU Inference
FastFlowLM (FLM) runs LLMs entirely on the AMD Ryzen AI NPU. It targets XDNA2 chips: Strix, Strix Halo, Kraken, and Gorgon Point. The project describes itself as "Ollama, but deeply optimized for NPUs." As of 2026-08-16, FLM was installed on Dankclaw, the NPU validated, and frank-zealot (port 52625) served in production — that arrangement ended with the 2026-09-11 retirement (see the banner above). See Promotion.
Verdict (2026-08-16)
- The NPU is a complement, not a replacement. It fits dense models up to about 9B.
- Qwen3.8-27B (dense) cannot run on the NPU. No FLM build exists and the memory budget is too small.
- Qwen3.6-35B-A3B has an official FLM build (Q4_KS). AMD benchmarks it on a 96 GB machine. It probably does not fit the 30 GiB budget of Dankclaw.
- Realistic first candidates: Qwen3.5-9B, Qwen3-8B, gpt-oss-20b (at the budget edge).
- Landed 2026-08-16: gpt-oss-20b serves on the NPU at 19.3 t/s decode, concurrently with the PP head. See Measured Results.
- The memlock limit was the setup blocker. Fixed 2026-08-16 via
/etc/security/limits.d/99-flm-memlock.conf.
What FastFlowLM Is
FLM is an NPU-first inference runtime. It started as a 2025 university project (University of Rhode Island, Ken Qing Yang). The team joined AMD on 2026-07-17. The repository moved to the ROCm organization on 2026-08-11 as v1.0.0.
Key properties, per the project README and docs:
- Runs fully on the NPU. No GPU or CPU load during inference.
- CLI mode (
flm run <tag>) and server mode (flm serve <tag>). - Server mode exposes a REST API and an OpenAI-compatible API. Default port is 52625.
- Models are not GGUF. FLM downloads pre-compiled kernel packs (built with AMD IRON + AIE-MLIR) from Hugging Face.
- Context lengths up to 256k tokens on listed models.
- Vision, audio, embedding, and MoE model support.
- Runtime is about 17 MB. Orchestration code is MIT-licensed. Kernels are free for commercial use.
- Models store in
~/.config/flm/(Linux). Override withFLM_MODEL_PATH.
NPU Hardware on Dankclaw
Dankclaw runs an AMD Ryzen AI MAX+ 395 (Strix Halo). The chip integrates an XDNA2 NPU rated at 50 TOPS (vendor spec: "NPU TOPS — Up to 50 TOPS", amd.com product page). It sits beside the Radeon 8060S iGPU and shares the same unified memory pool — 30 GiB exposed to the OS in the 2026-08 reading below; the box presents 121 GiB today (free -g total 121, read 2026-10-18).
Live verification on Dankclaw, 2026-08-16:
$ flm validate
[FLM] Fetching models from: /opt/fastflowlm/share/flm/model_list.json
[Linux] Kernel: 7.0.0-29-generic
[Linux] NPU: /dev/accel/accel0 with 8 columns
[Linux] NPU FW Version: 1.1.2.65
[Linux] amdxdna version: 0.7
[ERROR] Memlock limit is too low (8MB). Please raise the limit or set to infinity.
- Kernel 7.0.0-29-generic shipped the
amdxdnadriver in-tree, loaded. The host now runs 7.0.0-38-generic and the driver is still bound (lspci -k -s c6:00.1→Kernel driver in use: amdxdna, read 2026-10-18). - Firmware 1.1.2.65 meets the 1.1.0.0 minimum.
- The XRT runtime packages (
libxrt2,libxrt-npu2) are installed — still installed on 2026-10-18 (dpkg -l→libxrt2 1:2.21.75+dfsg-4,libxrt-npu2 1:2.21.75+dfsg-4). - The only failure is the memlock ulimit (8 MB). FLM needs
unlimited. - Ubuntu 26.04 is not on the FLM supported-distro list (24.04, 25.10, Arch, generic). Validation passes anyway. Treat 26.04 as "generic Linux, working".
Memory Model
The NPU allocates from system RAM. AMD/Microsoft enforce an internal cap of about half of total system RAM for NPU use. The gpt-oss model card states a cap of about 15.6 GB on a 32 GB machine, which is half.
Budget math for this fleet:
| Machine | RAM | NPU budget (approx. half) |
|---|---|---|
| Dankclaw (Strix Halo) | 30 GiB | ~15 GiB |
| Dankserver (Ryzen 7 7700X) | 62 GB | no NPU |
| FLM bench box (Kraken Point) | 32 or 96 GB | ~16 or ~48 GB |
2026-10-18 re-read: Dankclaw's OS now reports 121 GiB (free -g) and
Dankserver 246 GiB (free -g), so the half-of-RAM rule would price the NPU
budget at ~60 GiB and the 7700X box still has no NPU. The 30 GiB / 62 GB rows
are the 2026-08-16 state; the table is moot while no NPU endpoint is deployed.
Weight sizes (approximate, Q4-class):
| Model | Quant | Weights | Fits ~15 GiB budget? |
|---|---|---|---|
| Qwen3.5-9B | Q4_1 | ~5.5 GB | yes |
| Qwen3-8B | Q4_1 | ~5 GB | yes |
| gpt-oss-20b (MoE, 3.6B active) | Q4_1 | ~15.1 GB | at the edge |
| Qwen3.6-35B-A3B (MoE, 3B active) | Q4_KS | ~20 GB | no, unproven at 30 GiB |
| Qwen3.8-27B (dense) | any Q4 | ~16 GB+ | no build exists |
Do not load a model larger than the budget first. An oversized load can starve the unified pool and crash the box while production services still run.
Supported Models (Qwen family, plus gpt-oss)
All Qwen cards use Q4_1 unless noted. Context lengths are the FLM-published maxima.
| Tag | Base model | Context max | Notes |
|---|---|---|---|
qwen3:0.6b |
Qwen3-0.6B | 32k | thinking toggle |
qwen3:1.7b |
Qwen3-1.7B | 32k | thinking toggle |
qwen3:4b |
Qwen3-4B | 32k | tool calling |
qwen3:8b |
Qwen3-8B | 32k | tool calling |
qwen3-tk:4b |
Qwen3-4B-Thinking-2507 | 256k | thinking |
qwen3-it:4b |
Qwen3-4B-Instruct-2507 | 256k | tool calling |
qwen3vl-it:4b |
Qwen3-VL-4B-Instruct | 256k | vision |
qwen2.5-it:3b |
Qwen2.5-3B-Instruct | 32k | |
qwen2.5vl-it:3b |
Qwen2.5-VL-3B-Instruct | 256k | vision |
qwen3.5:0.8b |
Qwen3.5-0.8B | 256k | vision, thinking toggle |
qwen3.5:2b |
Qwen3.5-2B | 256k | vision, tool calling |
qwen3.5:4b |
Qwen3.5-4B | 256k | vision, tool calling |
qwen3.5:9b |
Qwen3.5-9B | 256k | vision, tool calling |
qwen3.6-moe:35b-a3b |
Qwen3.6-35B-A3B | 256k | Q4_KS, MoE, vision, thinking, tool calling |
gpt-oss:20b |
gpt-oss-20b | 128k | Q4_1, ~15.1 GB in FLM |
Other families: LLaMA, DeepSeek, Gemma, MedGemma, TranslateGemma, LiquidAI/LFM, Phi, Nanbeige, Whisper, EmbeddingGemma, SmolVLA.
Published Benchmarks (FLM, vendor numbers)
All vendor results come from AMD Ryzen AI 7 350 (Kraken Point, 40 TOPS NPU) boxes under the default NPU power mode (Performance). Our NPU is a 50 TOPS Strix Halo XDNA2. Treat these numbers as directional, not as our expected speed.
Qwen3.5, FLM v0.9.38, Kraken Point 32 GB DRAM. Decode t/s by starting context length:
| Model | 1k | 2k | 4k | 8k | 16k | 32k |
|---|---|---|---|---|---|---|
| Qwen3.5-0.8B | 39.2 | 38.0 | 36.3 | 33.1 | 28.1 | 21.6 |
| Qwen3.5-2B | 26.8 | 26.2 | 25.4 | 23.7 | 21.3 | 17.0 |
| Qwen3.5-4B | 15.0 | 14.6 | 14.2 | 13.3 | 11.8 | 9.6 |
| Qwen3.5-9B | 9.3 | 9.2 | 9.0 | 8.5 | 7.8 | 6.9 |
Prefill t/s for the same run:
| Model | 1k | 2k | 4k | 8k | 16k | 32k |
|---|---|---|---|---|---|---|
| Qwen3.5-0.8B | 983 | 1257 | 1471 | 1584 | 1579 | 1447 |
| Qwen3.5-2B | 803 | 1004 | 1142 | 1223 | 1225 | 1151 |
| Qwen3.5-4B | 378 | 440 | 479 | 493 | 487 | 450 |
| Qwen3.5-9B | 284 | 333 | 362 | 379 | 378 | 357 |
Qwen3.6-35B-A3B (Q4_KS), FLM v0.9.45, Kraken Point 96 GB DRAM:
| Metric | 1k | 2k | 4k | 8k | 16k | 32k |
|---|---|---|---|---|---|---|
| Decode t/s | 13.65 | 13.41 | 13.09 | 12.51 | 11.24 | 9.51 |
| Prefill t/s | 78.98 | 118.04 | 156.43 | 197.93 | 218.84 | 221.96 |
Context: the current PP split (head + 3090 worker) decodes at 14-18 t/s for the 27B dense model. A 35B-A3B at ~10-14 t/s on an NPU with 48 GB of RAM budget is not a speed win over the existing topology.
The vendor also claims "over 10x more power-efficient" versus GPU. That is a marketing claim. We have not measured it.
Fit to the Frank Program
| Our model | NPU verdict |
|---|---|
| Qwen3.8-27B UD-Q8_K_XL (Dankclaw, dense) | No. No FLM build. ~16 GB+ even at Q4. Exceeds the ~15 GiB budget. |
| Qwen3.6-35B-A3B (Dankserver, MoE) | Unproven. Official build exists. Vendor bench ran on 96 GB RAM. A 30 GiB box has ~15 GiB for the NPU. Test last, with the service stopped. |
| Qwen3.5-9B / Qwen3-8B | Yes, the realistic tier. ~5-6 GB. Leaves room for KV and the OS. |
| gpt-oss-20b (MoE) | Landed. Weights are 13.8 GB on disk (q4nx). The model holds ~15 GiB resident on the NPU. Decode is 19.3 t/s. See Measured Results. |
Proposed role: a small, always-on, low-power endpoint for triage, summarization, embeddings, or agent side-tasks on the Dankclaw NPU. It frees the iGPU and the 3090 for the 27B/35B workloads. It does not replace either.
Install State on Dankclaw (verified 2026-08-16)
fastflowlm 1.0.1installed (deb, upgraded from 0.9.45-12-ga6bd056 on 2026-08-17).flmat/usr/bin/flm. The 0.9.45 runtime refused the qwythos pack, which declaresflm_version1.0.0.libxrt2/libxrt-npu2(Xilinx Runtime) installed./dev/accel/accel0present.flm validatepassed fully. (Re-read 2026-10-18:/dev/accel/accel0is still present andfastflowlm 1.0.1is still installed, but the endpoint is retired —frank-npu.servicemasked, no listener on 52625.)- Memlock fixed:
/etc/security/limits.d/99-flm-memlock.confsets soft and hard memlock to unlimited. A fresh sshd session picks it up via pam_limits. No reboot needed. gpt-oss:20bpulled (4/4 files verified, 13.8 GBmodel.q4nx+ tokenizer) into~/.config/flm/models/GPT-OSS-20B-NPU2/.qwythos:9b(Huihui-Qwythos-9B-Claude-Mythos-5-1M-abliterated-NPU2) installed 2026-08-17 viaflm-add.py(downloaded from the HF repo; stdlib-only, no pip needed). Tag registered in the user registry~/.config/flm/model_list.json(a copy of the v1.0.1 system registry + the custom entry); kernels symlinked~/.config/flm/xclbins/Huihui-...-NPU2 -> /opt/fastflowlm/share/flm/xclbins/Qwen3.5-9B-NPU2. The service env exportsFLM_CONFIG_PATHandFLM_XCLBIN_PATHso the tag resolves (without them flm hits the unknown-tag fallback, see the incident below).flm serve gpt-oss:20b --host 0.0.0.0 -p 52625 --pmode performanceruns as a nohup process. It is not a systemd unit. It does not survive a reboot.- The
llama-server(frank-dragoon, the PP head) holds an fd on/dev/accel/accel0from its own startup (device enumeration, not compute). FLM opened the same device concurrently without conflict. Verified live 2026-08-16.
Measured Results (2026-08-16, gpt-oss-20b on the Dankclaw NPU)
Conditions: --pmode performance, PP head (27B, port 7770) serving concurrently, 5.5 GiB system RAM headroom steady-state, NPU resident footprint ~15 GiB.
| Run | Prompt tok | Compl tok | Wall s | Prefill t/s | Decode t/s | KV occ % |
|---|---|---|---|---|---|---|
| 1 (smoke) | 74 | 63 | 7.2 | 19.5 | 19.3 | 1.7 |
| 2 (160-tok prompt) | 161 | 256 | 17.4 | 40.8 | 19.3 | 5.1 |
| 3 (95-tok prompt, 512 out) | 95 | 512 | 30.8 | 24.3 | 19.3 | n/a |
Decode is flat at 19.3 t/s across all three runs. Output includes reasoning_content (gpt-oss is a reasoning model). At max_tokens 256 one run spent its whole budget thinking and returned empty content. Give it 500+ tokens or strip reasoning in the client.
Long-context prefill is not yet measured (runs 1-3 used under 200 prompt tokens). The 35B-A3B step remains untested.
Model Bakeoff (measured 2026-08-16, 4 models)
All runs on the Dankclaw NPU (50 TOPS Strix Halo), single stream, --pmode performance, PP head (frank-dragoon) serving concurrently on 7770. Decode t/s is the FLM-reported rate. Vision accuracy = ground-truth elements out of 4 (text SPECTRUM, red circle left, blue square right, three green triangles bottom).
| Model | Tag | Weights | Decode t/s (short) | 8k prefill t/s | 8k decode t/s | Vision decode t/s | Vision accuracy | Max ctx (registry) |
|---|---|---|---|---|---|---|---|---|
| Gemma4-E4B | gemma4-it:e4b |
6.8 GB + 912 MB audio | 12.2 | 582 | 9.9 | 11.7 | 4/4 | 32768 |
| Qwen3.5-9B | qwen3.5:9b |
7.3 GB | 7.2 | 343 | 6.6 | 7.3 | 4/4 | 32768 |
| GPT-OSS-20B | gpt-oss:20b |
13.8 GB | 19.3 | 378 | 16.9 | n/a (no vision) | 0/4 | 8192 |
| Qwen3.6-35B-A3B | qwen3.6-moe:35b-a3b |
22.2 GB | 12.7 | 207 | 11.2 | 12.7 | 4/4 | 32768 |
Notes:
- gpt-oss:20b is the fastest decoder (MoE, 3.6B active) but its KV cache fills at ~8k tokens (98.85% occupancy at 7,926; requests past ~8k return 400). It has no vision encoder.
- 35B-A3B loads 28-29 GiB resident. It works but leaves ~1.5 GiB headroom with the PP head running. Do not run it alongside the head at full load.
- Vision prefill for the two vision models with ~720 token image input: gemma 141 t/s, qwen3.5 176 t/s, 35B-A3B 68 t/s.
- All four comply with the same image at 4/4 elements except gpt-oss, which cannot see it.
Promotion (2026-08-16)
frank-zealot now runs the NPU endpoint on Dankclaw (was the Dankserver llama.cpp service). Unit: scripts/frank-zealot.service (symlinked into /etc/systemd/system), wrapper scripts/serve-flm-small.sh, tunables scripts/frank-zealot.env. LimitMEMLOCK=infinity is set in the unit. Default model: gemma4-it:e4b. Swap FLM_MODEL in the env to switch.
Boot policy (2026-08-17)
The frank-zealot unit is disabled for boot. Only the PP pair (frank-dragoon + frank-pp-worker) starts at boot. The unit file was removed from /etc/systemd/system; the running FLM service keeps serving until stopped or rebooted. To restore the endpoint, run these commands on Dankclaw:
sudo ln -sf /home/dank/vllmScripts/scripts/frank-zealot.service /etc/systemd/system/frank-zealot.service
sudo systemctl daemon-reload
sudo systemctl enable frank-zealot
Live config (2026-08-16): FLM_PMODE=turbo, FLM_ASR=1 (loads Whisper-V3-Turbo-NPU2; exposes /v1/audio/transcriptions), FLM_CTX_LEN=131072 (verified max — Gemma4 natively supports 128K and the NPU budget fits it; KV occupancy measured at 0.11% for a 123-token exchange, decode 12.0 t/s, 13 GiB headroom).
The PP split was renamed frank-pp-head → frank-dragoon (2026-08-16, commit 48142f5).
Model swap (2026-08-16, commit 50c55ee): FLM_MODEL=qwen3.6-moe:35b-a3b, FLM_CTX_LEN=262144 (native max), FLM_PMODE=turbo, FLM_ASR=1 kept. Decode measured 12.5-13.1 t/s. The Gemma4 live config above (131072 ctx) applied to gemma4-it:e4b. Swap FLM_MODEL and FLM_CTX_LEN back to restore it.
Model swap (2026-08-17, commit c85ff6c): FLM_MODEL=qwythos:9b (Huihui-Qwythos-9B-Claude-Mythos-5-1M-abliterated-NPU2, Q4NX 7.76 GB). The pack installs through flm-add (user registry ~/.config/flm/model_list.json + user xclbins ~/.config/flm/xclbins, kernels symlinked from official Qwen3.5-9B-NPU2). Runtime upgraded to FLM 1.0.1 first (see Install State). FLM_CTX_LEN=262144 native max (Qwen3.5-9B hybrid: 8 of 32 layers full attention, so KV stays small; occupancy 0.008% at 21 tokens). FLM_PMODE=turbo, FLM_ASR=1 kept. The env file now exports FLM_CONFIG_PATH / FLM_XCLBIN_PATH — required, or flm treats the tag as unknown and silently serves llama3.2:1b. qwen3.6-moe:35b-a3b files stay installed for rollback. pi client entry (~/.pi/agent/models.json on dankware): id qwythos:9b, ctx 262144, input [text] (pack has no vision weights), compat supportsDeveloperRole: false kept.
Parallel Streams (measured 2026-08-16)
The NPU serializes generations. It runs exactly one stream at a time. Extra streams queue in FIFO order. FLM server flags --socket (default 10) and --q-len (default 10) cap queue depth only. They do not add concurrency.
Concurrent-request test, gpt-oss-20b, 128-token cap per stream:
| Streams | Wall s | Aggregate t/s | Per-stream wall s |
|---|---|---|---|
| 1 | 10.6 | 12.1 | 10.6 |
| 2 | 21.1 | 12.0 | 10.8 / 21.1 |
| 4 | 41.1 | 11.6 | 10.9 / 21.6 / 32.4 / 41.1 |
The 4-stream wall times form a near-perfect 10.7 s ladder. Each stream self-reports 19.3 t/s decode, counting only its own active slices. Trust per-stream wall time, not the self-reported speed, under concurrency.
Consequence: the NPU endpoint is a single lane. It suits low-frequency background work (triage, summarization, agent side-tasks). It does not suit interactive chat for several simultaneous users. The 27B llama.cpp service, by contrast, runs PARALLEL=2 with true concurrent slots.
Test Runbook
This runbook targets a retired endpoint; keep it as the procedure if the NPU is
ever revived. Do not run large model loads while the production service holds
most of the unified pool — 30 GiB exposed to the OS in the 2026-08 reading,
121 GiB today (free -g, read 2026-10-18). Check free RAM first, or stop
the service the way the bench policy requires (sudo systemctl stop frank-dragoon && sudo systemctl mask frank-dragoon — self-healing would
otherwise restart it). The NPU shares the unified pool with everything else on
the box.
graph TD
A[1. Raise memlock to unlimited] --> B[2. flm validate + xrt-smi examine]
B --> C{NPU visible to XRT?}
C -- no --> D[Fix driver or firmware path]
C -- yes --> E[3. flm run qwen3.5:4b smoke test, /verbose]
E --> F{Generates tokens?}
F -- no --> D
F -- yes --> G[4. flm serve on a scratch port, never 7770]
G --> H[5. Bench decode + prefill t/s]
H --> I{Speed acceptable?}
I -- no --> J[Record results, stop]
I -- yes --> K[6. Attempt gpt-oss:20b]
K --> L{Loads in ~15 GiB?}
L -- no --> J
L -- yes --> M[Bench, record]
M --> N[7. Last: qwen3.6-moe:35b-a3b, service stopped]
N --> O[Record, decision]
- Raise memlock. Add
* soft memlock unlimitedand* hard memlock unlimitedto/etc/security/limits.conf. Then reboot. For a systemd unit, setLimitMEMLOCK=infinityinstead. - Verify the NPU. Run
flm validate. Runxrt-smi examine. Both must show the NPU.flm validateuses the kernel device.flm runuses XRT. A passing validate does not guarantee XRT sees the device. - Smoke test. Run
flm run qwen3.5:4b. Toggle/verbosefor performance reporting. Type/byeto exit. - Server mode. Run
flm serve qwen3.5:4b. The default port is 52625. Use a scratch port if 52625 is taken. Never 7770. - Bench. Measure decode and prefill t/s at 1k/4k/16k starting context. Record to this page.
- Step up in size: 9B, then gpt-oss-20b.
- Attempt
qwen3.6-moe:35b-a3bonly after the smaller runs succeed, and only with the production service stopped. Watchfree -hduring the model download and load.
Registering packs absent from the catalog (NPU2)
Use when adding a non-catalog NPU2 pack (an extraction or fine-tuned model)
to the FLM registry. Registry facts: user registry
/home/dank/.config/flm/model_list.json, shape
{"model_path": "models", "models": {"<tag>": {"<size>": {name, url, file_url, size, flm_min_version, files[], vlm, default_context_length, max_prefill_len, details{format:"NPU2", family, …}, label[]}}}}; serve
resolves tags through FLM_CONFIG_PATH at startup, and flm list with the
env var set lists custom tags (⏬ = not downloaded).
- Back up the registry JSON first (
cp -a). - Insert the entry via a python json edit + atomic
os.replace. Setfilesto the FULL HF file list — includingvision_weight.q4nxeven for text-only packs: the qwen3.5-family loader opens it unconditionally and a missing file makes the first request HANG silently (no error log). - Do not use
flm pull <tag>for custom entries — it throwsjson.exception out_of_range key '<tag>:<size>' not foundwhile building the download list. Place files manually into/home/dank/.config/flm/models/<PackName>/withcurl -sfL "https://huggingface.co/<repo>/resolve/main/<file>?download=true"— plainraw/main/<file>returns 135-byte LFS pointer stubs, not weights; verify byte counts against the HF tree API before trusting a download. - Kernel packs: copy from the same-shape family pack (shape-match
config.jsonon hidden_size/num_hidden_layers/head_dim first) into/opt/fastflowlm/share/flm/xclbins/<PackName>/viasudo cp -a(root-owned dir). - Deploy/restart the serve unit (the deploy path, not ad-hoc restarts) so the new process loads with all files present.
- Verify with a REAL completion request (
model: "<tag>:<size>") —/v1/modelsalone proves registration, not loadability.
Hard constraints: only ONE flm serve process can hold the NPU hardware
context — a second concurrent serve process fails every load with
DRM_IOCTL_AMDXDNA_CREATE_HWCTX err=-22 (aie2 mgmt msg opcode 0x2 status
0x2000003); never run scratch/verify instances while production holds the
NPU. Tag switching happens INSIDE one serve process; the first request
after a model switch pays ~6–11 s cold-load cost (visible in
usage.load_duration). Over ssh, never pkill -f "<pattern>" with a
pattern present in your own command line — use bracket-guarded patterns
([q]wen3.5:4b) or exact PIDs.
Tag load-failure diagnosis (200 with an error body)
POST /v1/chat/completions answers HTTP 200 with a flat
{"error":"Failed to load <tag> model!"} and no choices; /v1/completions
returns a nested OpenAI-shaped server_error (code 500) also under HTTP
200. A status-code-only client reads this as a successful empty completion —
always check for the error key. Two distinct causes, distinguished via
journalctl -u <flm-service> --full:
- Unknown tag (not in the registry): immediate error, nothing loaded. Correct behavior, not a bug.
- Registered tag whose kernel pack is missing: FLM resolves the tag,
downloads missing weight files on the fly (first request hangs ~90–110 s),
then fails
Failed to load model: No such file '/home/dank/.config/flm/xclbins/<PackName>/<kernel>.xclbin'; subsequent attempts fail fast (~2 s). Root cause: the serve script exportsFLM_XCLBIN_PATH=/home/dank/.config/flm/xclbins(user dir), so every kernel lookup resolves under the user dir, which only contains symlinks someone linked. A tag listed in/v1/modelsis NOT proof it is loadable on this host. Fix: one symlink per missing pack (ln -s /opt/fastflowlm/share/flm/xclbins/<PackName> /home/dank/.config/flm/xclbins/<PackName>) — verified live: after the symlink the first request loaded and answered in 7.4 s wall.
Switch-cost behavior: the model field routes per-request (no restart
needed); a request whose model differs from the resident one triggers a full
reload (7–11 s wall for an 8-token completion on ~4B-class packs);
alternating tags pays the swap each time. Single-lane NPU: concurrent
requests queue FIFO behind the current lock holder (NPU Locked! →
NPU Lock Released!). Probe recipe: /v1/models for registered tags; grep
the journal for Failed to load model (the quoted xclbin path names the
missing pack dir exactly); diff ls ~/.config/flm/xclbins/ (symlinks =
installed) against /opt/fastflowlm/share/flm/xclbins/ (available).
Incident: qwen3.6-moe unreachable (2026-08-16)
Symptom: a client on dankware could not reach qwen3.6-moe:35b-a3b on port 52625. The journal shows three separate causes in the 14:19-15:03 window.
- EXEC 203 crash loop (14:19-14:20). The exec bit was lost on
serve-flm-small.shon the target. The unit failedstatus=203/EXEC: Permission deniedfour times, then started after the bit was restored. The git mode is100755(commit3f42210), and agit pullrestores the bit. Verify withgit ls-files -s scripts/serve-flm-small.sh. - Wrong model ID (14:29-14:52). The client config still used the model ID
frank-zealot(a service alias). FLM rejects that tag with[ERROR] Model tag 'frank-zealot' is not supported.Each such request also triggered the unknown-tag fallback below. - Swap churn (14:48-15:03). The swap
gemma4-it:e4b→qwen3.6-moe:35b-a3brestarted the service several times. Each load of the 23 GB model takes about 1.5-2.5 minutes at 262144 ctx. The endpoint does not serve during a load.
Fix: the client model ID must be the FLM registry tag qwen3.6-moe:35b-a3b. The working pi agent entry in ~/.pi/agent/models.json is baseUrl http://192.168.0.143:52625/v1, id qwen3.6-moe:35b-a3b, contextWindow 262144, reasoning true, input [text, image]. Verified end-to-end from dankware (192.168.0.226) at 15:32 on 2026-08-16.
Unknown-tag fallback (FLM behavior)
When a request names a tag that is not in the FLM registry, FLM does not return a clean 404. It logs the ERROR, then falls back to the default model llama3.2:1b:
- If the
llama3.2:1bfiles are missing, FLM downloads them (1.2 GB on 2026-08-16) while the request hangs. - FLM then loads the 1B model in place of the requested model.
- The files now exist in
~/.config/flm/models/Llama-3.2-1B-NPU2(1.3 GB) on dankclaw. A future unknown-tag request loads the 1B model silently, with no download and no error.
Correction (live-probed 2026-08-30): the fallback no longer fires on the
current serve path — an unknown tag now answers HTTP 200 with a flat
{"error":"Failed to load <tag> model!"} body and nothing is loaded or served;
/v1/completions answers a nested server_error (code 500) under HTTP 200.
Keep client model IDs as FLM registry tags. Never send a service alias. Optional cleanup: move ~/.config/flm/models/Llama-3.2-1B-NPU2 (an incident artifact) to .AGENTTRASH so a stray tag re-downloads loudly instead of serving the wrong model.
Request compatibility: the developer role (found 2026-08-16)
pi sessions failed against this endpoint with Stream ended without finish_reason while curl worked. Root cause, verified by field bisection:
- The FLM Qwen chat template accepts only the roles
system,user, andassistant. Any other role raisesUnexpected message role.in the template. - FLM reports the error differently per mode. Non-streaming: HTTP 500 with the template traceback. Streaming: HTTP 200, and the error JSON is the first body. A streaming client sees a stream with no chunks and no
finish_reason. - pi sends
role: "developer"whenmodel.reasoning && compat.supportsDeveloperRole(pi source,openai-completions.js). The defaultsupportsDeveloperRoleis true for standard OpenAI-compatible providers, so a reasoning model without acompatblock always triggers the bug. - Fix:
"compat": { "supportsDeveloperRole": false }on the model entry in~/.pi/agent/models.json. pi then sendsrole: "system". Verified with a headless pi session: full round trip,finish_reason: stop. - FLM ignores unknown body fields.
reasoning_effort,store,max_completion_tokens, andtools: []all arrive in pi requests and pass through without error.
Risks and Open Questions
- Ubuntu 26.04 is unofficial. The distro list stops at 25.10. Validation passes, but breakage would fall on the "generic Linux" path.
- The 30 GiB budget is untested at the very top. gpt-oss-20b (15 GiB resident) works. 35B-A3B (~20 GB weights) is still an unknown. Vendor data covers 32 GB and 96 GB boxes only.
- The FLM serve process was not under systemd (2026-08-era risk). A reboot
of Dankclaw killed it. The fleet law today is the opposite: every deployed
container/service ships a boot-persistent, self-healing unit, and the retired
NPU unit is deliberately
masked(frank-npu.service, read 2026-10-18) so it cannot come back at boot. - gpt-oss reasoning budget. The model can spend its full token budget on
reasoning_contentand return empty content. Clients must handle or cap this. - No llama.cpp GGUF path. FLM uses its own pre-compiled kernels. Our UD-Q8_K_XL and HauhauCS builds are not importable. NPU work means FLM-specific model tags.
- NPU production role (2026-08-16, retired 2026-09-11).
frank-zealotthen servedqwen3.6-moe:35b-a3bon port 52625; the endpoint was retired with theamd_iommu=offdecision that day and the unit is masked today. The remaining NPU content (benchmarks, runbook steps) is research on a device that is again usable since 2026-10-18 but serves nothing.
Citations
- ROCm/FastFlowLM README (chip support, Linux support date, ROCm move, quick start, model storage paths): https://github.com/ROCm/FastFlowLM , accessed 2026-08-16.
- FastFlowLM docs overview: https://fastflowlm.com/docs/ , accessed 2026-08-16.
- FastFlowLM Qwen model cards (tags, quantization, context lengths): https://fastflowlm.com/docs/models/qwen/ , accessed 2026-08-16.
- FastFlowLM gpt-oss model card (15.1 GB weight size, ~15.6 GB NPU cap, 32 GB inconsistency note): https://fastflowlm.com/docs/models/gpt-oss/ , accessed 2026-08-16.
- FastFlowLM Qwen3.5 benchmarks (v0.9.38, Kraken Point 32 GB): https://fastflowlm.com/docs/benchmarks/qwen3.5_results/ , accessed 2026-08-16.
- FastFlowLM Qwen3.6 benchmarks (v0.9.45, Kraken Point 96 GB): https://fastflowlm.com/docs/benchmarks/qwen3.6_results/ , accessed 2026-08-16.
- AMD blog, "FastFlowLM Joins AMD to Advance AI Inference" (2026-07-17): https://www.amd.com/en/blogs/2026/fastflowlm-joins-amd-to-advance-ai-inference.html
- Wikipedia, "Zen 5" article, Strix Halo section (Ryzen AI MAX+ 395: XDNA2 NPU, 50 TOPS, 256-bit LPDDR5X) via local ZIM
wikipedia_en_all_maxi_2026-02. - Live system checks on Dankclaw, 2026-08-16:
flm validateoutput,dpkg -lpackage list, kernel 7.0.0-29-generic,/proc/3052096/fddevice fd inspection.