frank-primary: Lucebox DeepSeek-V4.1-Flash-ROCMFP23 quadlet + deploy handler

Replaces frank-medium (vllm-radiance TP=2) on :7770. The 377.2 GiB
43-layer MoE (256 routed experts, top-6) is mmapped: dense work and the
MLA KV cache on the R9700, routed experts computed on CPU from the
page cache (246 GiB RAM). The unit bypasses the image entrypoint
(Qwen/DFlash-only flag set) and execs luce_server with the DS4 launch
shape; --ds4-fused-decode's monolithic-load fallback IS the serving
mode here. deploy_llm_primary gates on image + model byte count,
stops/masks the radiance unit, waits for :7770, installs and starts.
Runbook: frank-primary-lucebox-ds4.md.
This commit is contained in:
Frank
2026-09-26 15:29:59 -07:00
parent 441c4f1c99
commit acd14ab7df
8 changed files with 352 additions and 1 deletions
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
# frank-primary entrypoint: exec the native luce_server with the DeepSeek V4
# launch shape for this host (2x R9700 gfx1201, 246 GiB RAM).
#
# The image's own entrypoint (server/scripts/entrypoint.sh) only forwards a
# fixed Qwen/DFlash flag set and cannot express the DS4 flags, so this unit
# bypasses it (PodmanArgs=--entrypoint) and execs the binary directly. The
# C++ server reads LUCE_* environment variables straight from the process
# environment, so no env plumbing is needed here.
#
# LAUNCH SHAPE (server/docs/DS4.md, server/docs/RECOMMENDED_SETUPS.md):
# --ds4-fused-decode requests a monolithic GPU load; on a 377 GiB model
# that allocation fails and the backend falls back
# to hybrid expert placement + layered decode (dense
# work on the R9700, routed experts computed on CPU
# from the mmapped page cache). That fallback IS the
# intended serving mode on this host.
# --ds4-prefill sparse the batched prefill; use for prompts beyond a few
# hundred tokens (exact is token-wise decode cost).
# --ds4-expert-top-k 6 the model default. Do NOT reduce on adaptive
# (qtype-105/106) artifacts: measured degenerate
# output at top-4 (DS4.md, 2026-08-05).
# --chunk 8192 the qualified Strix Halo profile's chunk.
# --max-ctx 131072 the qualified Strix Halo window; DS4's MLA KV is
# compressed, so the pool is small.
# --cache-type-k/v q4_0 the qualified launch's KV dtype.
#
# No DSpark draft is published for DeepSeek-V4.1-Flash, so this is plain
# autoregressive decode (no --draft, no LUCE_DS4_SPEC).
set -euo pipefail
MODEL="${FRANK_PRIMARY_MODEL:-/models/DeepSeek-V4.1-Flash/DeepSeek-V4.1-Flash-ROCMFP23.gguf}"
BIN="${FRANK_PRIMARY_BIN:-/opt/lucebox-hub/server/build/luce_server}"
if [[ ! -f "$MODEL" ]]; then
echo "frank-primary: model missing at $MODEL" >&2
exit 1
fi
if [[ ! -x "$BIN" ]]; then
echo "frank-primary: binary missing at $BIN (image build failed?)" >&2
exit 1
fi
exec "$BIN" "$MODEL" \
--host 0.0.0.0 \
--port 7770 \
--model-name frank-primary \
--target-device hip:0 \
--max-ctx 131072 \
--chunk 8192 \
--cache-type-k q4_0 \
--cache-type-v q4_0 \
--ds4-fused-decode \
--ds4-prefill sparse \
--ds4-expert-top-k 6
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env python3
"""frank-primary health probe: 200 on /health only once the engine serves.
File-based (not an inline HealthCmd) because the podman 5.x v1 quadlet
generator splits HealthCmd on spaces into an argv array.
"""
import sys
import urllib.request
try:
with urllib.request.urlopen("http://127.0.0.1:7770/health", timeout=10) as r:
sys.exit(0 if r.status == 200 else 1)
except Exception:
sys.exit(1)
@@ -0,0 +1,85 @@
# 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, 43-layer MoE,
# 256 routed experts + 1 shared per layer, top-6 routing) on a single
# Radeon AI PRO R9700 (gfx1201). The model does not fit VRAM: dense work
# and the MLA KV cache sit on the R9700, the routed experts are computed
# on CPU from the mmapped page cache (246 GiB RAM holds the working set;
# the rest streams 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. HIP_VISIBLE_
# DEVICES=0 pins the first R9700 (the Raphael iGPU enumerates as index 2
# and stays out). The second R9700 is not used: the in-process
# heterogeneous expert-parallel path (LUCE_DS4_MOE_TP_INPROC) is qualified
# for gfx1151+gfx1201 pairs, not gfx1201+gfx1201.
#
# 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
# First R9700 only (index pin excludes the Raphael iGPU at index 2).
Environment=HIP_VISIBLE_DEVICES=0
Environment=ROCR_VISIBLE_DEVICES=0
# 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