Env file is the source of truth (unit ExecStart runs the serve script which sources it); serve-script default and the deployer health-gate URL flipped in lockstep so the post-restart health gate polls the new port. Docs, fleet table, and both index bullets updated to match.
6.6 KiB
type, title, description, tags, timestamp
| type | title | description | tags | timestamp | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| configuration | frank-default-sightsound Runbook | FastFlowLM (FLM) NPU sight+sound endpoint on Dankclaw — XDNA2 NPU serving the Gemma4 E4B vision+audio pack on port 7771, including the IOMMU/SVA root cause that took it down at first deploy. |
|
2026-08-30 |
frank-default-sightsound — FLM NPU sight+sound Runbook
frank-default-sightsound is the third inference endpoint on Dankclaw: a
FastFlowLM (FLM) v1.0.1 server (/usr/bin/flm, dpkg package fastflowlm)
running on the XDNA2 NPU (50 TOPS). It exists to serve sight and sound —
image and audio input — which the iGPU llama.cpp fleet (kingjones-strix-rocmfp4,
frank-default-large) does not do. The model-selection tradeoff: the only
NPU2 pack family on this box with BOTH a vision and an audio tower is the
Gemma4 E4B IT pack; the uncensored candidate (qwythos:9b, 8.94 GB, 262k ctx)
is text-only and fails the hard modality requirement, so this endpoint trades
"uncensored" for "sight + sound".
Deployment shape
| Field | Value |
|---|---|
| Runtime | FLM v1.0.1 (/usr/bin/flm, dpkg fastflowlm) on the XDNA2 NPU |
| Service | frank-default-sightsound.service (systemd system unit, User=dank, Restart=on-failure, RestartSec=10, LimitMEMLOCK=infinity) |
| Files | scripts/frank-default-sightsound.env, scripts/serve-frank-default-sightsound.sh, scripts/frank-default-sightsound.service |
| Deploy | scripts/deploy.sh dankclaw --service frank-default-sightsound (relink + daemon-reload + enable + restart; health gate polls /v1/version, 300 s cap — FLM has no /health route) |
| Port | 7771 on 0.0.0.0 (52625/52626 left free for ad-hoc flm serve experiments) |
| Power mode | FLM_PMODE=turbo — no other tenant uses the NPU on this host (the large model runs on the iGPU), so the top power mode costs no neighbour anything; drop to performance if iGPU power-budget contention ever shows |
| Context | FLM_CTX_LEN = verified max for the E4B pack on this box (native 128K context; the registry's default context length is only the fallback). FLM rounds non-power-of-2 values up. |
| ASR | FLM_ASR=0 — the E4B pack answers audio natively through its own audio tower in /v1/chat/completions; setting FLM_ASR=1 additionally loads Whisper-V3-Turbo-NPU2 (+0.62 GB) and exposes /v1/audio/transcriptions |
The serve script exports FLM_CONFIG_PATH=/home/dank/.config/flm/model_list.json
and FLM_XCLBIN_PATH=/home/dank/.config/flm/xclbins; kernels are symlinked
from /opt/fastflowlm/share/flm/xclbins/ into the per-pack directories. A tag
swap is therefore an env-file edit, not a script edit.
Serving model and API contract
- The endpoint serves the whole local registry (36 tags at first deploy) on
one port;
/v1/modelslists them all. Chat completions route per request by the exact FLM registry tag in themodelfield — never a service alias. - Unknown model tags do NOT 404 (live-probed):
/v1/chat/completionsanswers HTTP 200 with a flat error body{"error":"Failed to load <tag> model!"}and nochoices;/v1/completionsanswers an OpenAI-shaped nested error (type: server_error,code: 500), also under HTTP 200. A valid tag routes that request to that model (response echoes the requested tag); no server restart is needed to switch between registry tags. Status-code-only clients silently get an empty response — check for theerrorkey. - Multimodal content parts accepted by
/v1/chat/completions:{"type":"image_url","image_url":{"url":"data:image/png;base64,..."}}and{"type":"input_audio","input_audio":{"data":"<base64 WAV, no data: prefix>","format":"wav"}}. - Chat template roles: only
system/user/assistant; any other role fails the request withUnexpected message role.. - The NPU is a single-lane device: generations serialize in FIFO order. This is a background/triage endpoint, not an interactive multi-user chat.
Live-verified behavior (2026-08-30, first deploy)
- Vision tower: a generated red 8×8 PNG posted as an
image_urldata-URI was answered correctly ("The image is a solid red color."); the request consumed 279 prompt tokens — the vision tower consumed the pixels (a text-only model given zero image bytes cannot answer "what color"). - Audio tower: a 440 Hz decaying sine WAV posted as
input_audioconsumed 37 prompt tokens vs 15 for the identical text-only control → ~22 audio-tower tokens; the model answered truthfully that it heard no speech (it is a tone, not speech — ASR stays off).
The IOMMU/SVA root cause (first deploy crashed, fixed same day)
First deploy crash-looped every 10 s (RestartSec=10) with
Error: Open /dev/accel/accel0 failed (err=-19): No such device. Root-cause
chain, all live-verified on the host:
amdxdna_drm_openwas failing SVA bind with ret -19 for every client — the XDNA2 driver cannot hand out the accel device without PASID/SVA.- SVA requires the AMD IOMMU to be enabled; the kernel cmdline carried
iommu=off(a perf/DMA tuning experiment from 2026-08-20, applied at the 2026-08-26 reboot). With IOMMU off there are no IOMMU groups and no PASID state;PASIDCtl: Enable-was observed disabled on0000:c5:00.1. - Fix: replace
iommu=offwithiommu=ptinGRUB_CMDLINE_LINUX_DEFAULTon/etc/default/grub,update-grub, reboot. Passthrough mode keeps the iGPU default domain identity-mapped (zero DMA-translation overhead forfrank-default-large) while enabling IOMMU hardware + PASID, which is what the NPU driver's SVA bind requires — PASID domains are paged regardless of the default-domain type. - Post-boot verification:
/sys/kernel/iommu_groups/populated (30 groups), zeroSVA bind device failedjournal entries since boot, accel0 recreated at boot, service active withNRestarts=0, and the image/audio probes above served live.
The same root cause explains the older amdgpu 0000:c4:00.0: no vmid pasid mapping supported kernel log line — a consequence of IOMMU-off, not a GPU
fault. Do not set iommu=off on this host again; it silently kills the NPU.
Client wiring
The pi client model config (/home/dank/.pi/agent/models.json on Dankware)
registers this endpoint under the Dankclaw provider with the exact FLM
registry tag as the model id (unknown tags answer a 200-wrapped error, not
a silent 1B fallback), baseUrl http://192.168.0.143:7771/v1, and a per-request context
window equal to the serving ctx-len. Health checks for this service must use
GET /v1/version (there is no /health route).