# Quadlet unit: frank-primary (rootless, boot-persistent). # # Install path on dankserver: ~/.config/containers/systemd/frank-primary.container # Unit name: frank-primary.service (container name: frank-primary) # # The dankserver LLM endpoint (:7770, alias frank-primary): the Lucebox # runtime (github.com/Luce-Org/lucebox, local image localhost/lucebox-rocm: # gfx1201 built from Dockerfile.rocm with LUCE_HIP_ARCHES=gfx1201 and # ROCM_VERSION=7.14.1-full to match the host driver's ROCm 7.x line) # serving DeepSeek-V4.1-Flash-ROCMFP23 (377.2 GiB GGUF, 40-layer MoE, # 384 routed experts + 1 shared per layer, top-6 routing, Engram tables at # layers 1 and 14) on the two Radeon AI PRO R9700s (gfx1201). The model # cannot be resident: dense weights, the compressed MLA caches and a hot # expert stack sit on hip:0, the streamed-expert slot cache on hip:1, and # every other routed expert is pulled out of the mmapped page cache (246 GiB # RAM holds most of the 179 GiB expert set; the rest reads from NVMe). Lazy # mmap page-in is the engine default — no flag. # # The image's entrypoint only forwards a fixed Qwen/DFlash flag set and # cannot express the DS4 flags, so the unit bypasses it (--entrypoint) and # runs the repo's frank-primary-entrypoint.sh, which execs the native # luce_server with the DS4 launch shape (see that script's header). # # SHAPE. Host network, :7770, served model name frank-primary, OpenAI /v1 # (Chat Completions with tool metadata, Responses, Anthropic Messages, # Open WebUI metadata) — the fleet contract is unchanged from # frank-medium. # # DEVICES. /dev/kfd + /dev/dri through the amdgpu/KFD stack. Both R9700s are # visible (indices 0 and 1; the Raphael iGPU enumerates as index 2 and stays # out). They are NOT driven as a symmetric pair: the in-process expert- # parallel path (LUCE_DS4_MOE_TP_INPROC) is qualified for gfx1151+gfx1201 # pairs, not gfx1201+gfx1201. Instead hip:0 owns the dense weights and the # hot expert stack while LUCE_EXPERT_STREAM_DEVICE=1 (entrypoint) gives hip:1 # the streamed-expert slot cache, so each card carries one tier and neither # waits on the other. # # MOUNTS. /models (ro — the GGUF is mmapped, nothing writes), and the # entrypoint + healthcheck scripts straight out of this checkout # (deploy.sh git-pulls the checkout before dispatching). # # IMAGE. localhost/-aliased tag: this host's podman quadlet path cannot # resolve registry-prefixed refs from local storage at create time (same # as frank-medium). Pull=never keeps boots off the registry. # # CUTOVER. This unit REPLACES frank-medium on :7770. The deploy handler # (deploy_llm_primary in scripts/deploy-remote.sh) stops and masks the # radiance unit before starting this one; the radiance unit is retired to # the home trash after frank-primary is verified (trash policy, never rm). # # 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-primary — Lucebox DeepSeek-V4.1-Flash-ROCMFP23 on R9700 (gfx1201), experts in RAM After=network-online.target [Container] ContainerName=frank-primary Image=localhost/lucebox-rocm:gfx1201 # Host network: luce_server binds 0.0.0.0:7770 directly; no PublishPort. AddDevice=/dev/kfd AddDevice=/dev/dri GroupAdd=keep-groups PodmanArgs=--privileged --ipc=host --security-opt seccomp=unconfined --cap-add SYS_PTRACE --network=host --entrypoint /usr/local/bin/frank-primary-entrypoint.sh # Both R9700s: hip:0 target, hip:1 the streamed-expert slot cache. Index 2, # the Raphael iGPU, stays out. Environment=HIP_VISIBLE_DEVICES=0,1 Environment=ROCR_VISIBLE_DEVICES=0,1 # The 377 GiB GGUF, mmapped; pages pull from disk/page cache on demand. Volume=/home/dank/models:/models:ro # Entrypoint + healthcheck from this checkout, mounted read-only. Volume=/home/dank/serverAppConfigs/dankserver/frank-primary/frank-primary-entrypoint.sh:/usr/local/bin/frank-primary-entrypoint.sh:ro Volume=/home/dank/serverAppConfigs/dankserver/frank-primary/frank-primary-healthcheck.py:/usr/local/bin/frank-primary-healthcheck.py:ro # 200 only once the engine serves; first start page-ins the dense weights # and builds the graphs, which takes a while. HealthCmd is split on spaces # into an argv array by the podman 5.x v1 generator, hence the file probe. HealthCmd=python3 /usr/local/bin/frank-primary-healthcheck.py HealthInterval=60s HealthTimeout=30s HealthRetries=3 HealthStartPeriod=1800s Pull=never [Service] TimeoutStartSec=1800 Restart=on-failure RestartSec=10 [Install] WantedBy=default.target