# Quadlet unit: frank-medium (rootless, boot-persistent). # # Install path on dankserver: ~/.config/containers/systemd/frank-medium.container # Unit name: frank-medium.service (container name: frank-medium) # # The dankserver LLM endpoint (:7770, alias frank-medium) since 2026-09-20: # the vllm-radiance MXFP4 stack (stilldeadcode/vllm-radiance:0.9.3 image + # the ggz14/radiance-vllm-mxfp4 patches) serving # just1moremodel/Qwen3.8-27B-Uncensored-MXFP4-awq on the 2x Radeon AI PRO # R9700 (gfx1201) as ONE tensor-parallel process (TP=2, R4D one-shot # all-reduce) with the DFlash2-FP8 block-diffusion drafter (SPEC=7). # It replaced the mainline llama.cpp layer-split deployment, which served the # same alias from the orcarouter Q8_0 GGUF across the same two cards. # # SHAPE. The launcher the upstream repo ships binds the server with # --network=host (vLLM takes --host 0.0.0.0 --port 7770 directly), which is # what this unit does too — no PublishPort. The fleet contract is unchanged: # :7770, served model name frank-medium (SERVED_NAMES), OpenAI /v1. # # TP=2 IS INSTALLABLE HERE NOW, and it won on measurement. Until 2026-09-20 # this unit was archived un-installed because the second card sat behind B650 # chipset bridges advertising `AtomicOpsCap: Routing-`, which stopped RCCL # kernel dispatch. The board swap (ProArt B850-Creator WiFi NEO) put both # cards on CPU GPP root ports — Gen5 x8 each, every bridge Routing+, both # endpoints ReqEn+ — and the collective-free R4D path runs at TP=2 without # complaint. The conversation battery (scripts/bench-conversation.py, salted, # same 10 prompts, 2026-09-20) measured this shape against the incumbent: # decode geomean 95.8 t/s vs 45.7, mean TTFT 0.21 s vs 0.72 s, and 259 t/s # aggregate against 4 concurrent streams vs 71.7. Evidence and the llama-side # caveats: serverAppConfigs-docs/LLM/frank-medium-radiance-mxfp4.md and # Dual-R9700-Cooperation.md. # # DEVICES. /dev/kfd + /dev/dri through the amdgpu/KFD stack; HIP/ROCR device # indices 0,1 (the two R9700s). The 2 GiB Raphael iGPU enumerates as index 2 # and is excluded both by the index pin and by the upstream MIN_GPU_MIB=8192 # logic this unit replaces. --privileged --ipc=host --security-opt # seccomp=unconfined --cap-add SYS_PTRACE is the upstream launcher's exact # podman posture. # # MOUNTS. /models (rw checkpoints), the HF cache (tokenizer files), # /cache (Triton/inductor/aiter/vLLM compile cache — first start compiles for # several minutes, later starts reuse), /patches (the pinned radiance clone — # its patches are applied at EVERY container start by the entrypoint), /r4d # (the patched libr4d build; the image's shipped r4d.so NaNs the # gated-delta-net and must never be used), and the entrypoint + healthcheck # scripts from this checkout (mounted read-only straight out of # /home/dank/serverAppConfigs — deploy.sh git-pulls the checkout before # dispatching). # # IMAGE. The unit runs the localhost/-aliased tag: this host's podman quadlet # path cannot resolve registry-prefixed refs from local storage at create # time (same as frank-large on dankclaw). deploy-remote.sh pulls the # docker.io ref and re-tags it before the restart. Pull=never keeps boots off # the registry. # # KEEP IN SYNC: the radiance clone is pinned (97353293...); if it is updated, # re-diff dankserver/frank-medium/radiance-entrypoint.sh against the new # serve-mxfp4.sh container command and bump the pin deliberately. # # DO NOT name an environment variable TEMP, HOST or GPU_MAX_HEAP_SIZE: the # HIP runtime reads bare TEMP for its JIT cache path (2026-08-13 incident). [Unit] Description=frank-medium — vllm-radiance MXFP4 Qwen3.8-27B-Uncensored on 2x R9700 (gfx1201) After=network-online.target [Container] ContainerName=frank-medium Image=localhost/radiance-vllm:0.9.3 # vLLM binds 0.0.0.0:7770 on the host network directly; no PublishPort. AddDevice=/dev/kfd AddDevice=/dev/dri GroupAdd=keep-groups # The upstream launcher's exact podman posture (rootless; podman-only flags # like --replace are not needed under systemd management). PodmanArgs=--privileged --ipc=host --security-opt seccomp=unconfined --cap-add SYS_PTRACE --network=host # The two R9700s. The Raphael iGPU (index 2, 2 GiB) stays out: shared system # memory is not a compute target. Environment=ROCR_VISIBLE_DEVICES=0,1 Environment=HIP_VISIBLE_DEVICES=0,1 # The uncensored MXFP4 checkpoint ships its fp8 MTP head already, so no # fp8_mtp.py rewrite step exists or is needed (unlike amd/Qwen3.8-27B-Quark-AWQ-MXFP4). Environment=SNAP=/models/Qwen3.8-27B-Uncensored-MXFP4-awq Environment=DRAFTER=/models/Qwen3.8-27B-DFlash2-FP8 Environment=CHAT_TEMPLATE=/patches/qwen-fixed-v22.3.jinja # Fleet identity: the id at /v1/models and in every response body. Every # client (Hermes, the harness, services.json) points at frank-medium. Environment=SERVED_NAMES=frank-medium Environment=PORT=7770 # Fixed 2-card tensor parallel: the tested configuration of the upstream # repo; the R4D one-shot all-reduce kernel serves it natively. Environment=TP=2 # Measured production batch shape (upstream README): MAXSEQS 8 peaks # aggregate throughput at 573 t/s; MAXLEN is the model's native context. Environment=MAXSEQS=8 Environment=CHUNK=8192 Environment=MAXLEN=262144 # MAXLEN is the whole pool shared across the MAXSEQS slots: 8 concurrent # sessions split 262,144 tokens between them, so a full-window request # excludes the others until it finishes (vLLM preempts rather than refuses). # The llama deployment it replaced advertised 327,680 across 2 slots. # # TEXT ONLY. The MXFP4 checkpoint carries language_model_only=true and the # launcher loads no vision projector, so an input_audio/image request is a # 400 here. Vision traffic routes to frank-small on dankclaw (harness # modelRoles vision -> frank-small). Environment=GPU_UTIL=0.98 # Speculative decoding: the DFlash2 drafter at depth 7 is the configuration # every published number (224 t/s single-stream decode, 4749-4810 t/s # prefill) was measured with. Environment=SPEC_METHOD=dflash Environment=SPEC=7 # KV cache pin, bytes. From the upstream repo's shipped kv-profiles.tsv row # sig=2x7551-32624 maxseqs=8 chunk=8192 maxlen=262144 spec=dflash # which is EXACTLY this host (2x R9700 DID 0x7551, 32624 MiB each). Pinned # explicitly because this unit does not run the launcher's lookup; valid # only at this batch shape (change MAXSEQS/CHUNK/SPEC_METHOD => recalibrate # with calibrate-kv.sh or set KV_MEM=0 to let vLLM profile). Worth # 943,581 KV tokens vs ~892,799 profiled (~5.7%). Environment=KV_MEM=18563072000 # Checkpoints (rw: nothing downloads at runtime, but vLLM mmaps the shards; # keep parity with the launcher's rw mount). Volume=/home/dank/models:/models # Tokenizer files ride the HF cache; HF_HUB_OFFLINE=1 is set in the # entrypoint. Volume=/home/dank/.cache/huggingface:/root/.cache/huggingface # Compile cache. Keyed on model + stack version + every graph-changing knob; # never share it across configurations (upstream rule). Volume=/home/dank/.radiance-cache-w4a8-093-gdnm-nqft-fp8s-gnq:/cache # The pinned radiance clone: patches applied at every container start. Volume=/home/dank/GitPackages/radiance-vllm-mxfp4:/patches:z # The patched libr4d (pin b9e42ab + repo extras, key b9e42ab-rx6). The # image's shipped r4d.so predates the GDN overflow fix and NaNs this model. Volume=/home/dank/.cache/radiance-libr4d/b9e42ab-rx6:/r4d:z # Entrypoint + healthcheck from this checkout, mounted read-only. Volume=/home/dank/serverAppConfigs/dankserver/frank-medium/radiance-entrypoint.sh:/usr/local/bin/frank-medium-entrypoint.sh:ro Volume=/home/dank/serverAppConfigs/dankserver/frank-medium/frank-medium-healthcheck.py:/usr/local/bin/frank-medium-healthcheck.py:ro # The entrypoint must be the container command; the image's own entrypoint # (which the patched script execs at the end) must not run unpatched. PodmanArgs=--entrypoint /usr/local/bin/frank-medium-entrypoint.sh # 200 only once the engine serves; during first-start compilation the # container is legitimately unhealthy. HealthCmd is split on spaces into an # argv array by the podman 5.x v1 generator, hence the file-based probe. HealthCmd=python3 /usr/local/bin/frank-medium-healthcheck.py HealthInterval=60s HealthTimeout=30s HealthRetries=3 HealthStartPeriod=1800s Pull=never [Service] # First start compiles Triton/inductor kernels (several minutes) before the # engine loads ~9.5 GiB of weights per card. TimeoutStartSec=1800 Restart=on-failure RestartSec=10 [Install] WantedBy=default.target