frank-primary: swap Dankserver to llama.cpp Qwen3.8-Flash-Next Q8_0
The 2026-09-26 same-day cutover that ended the Lucebox DS4.1 detour:
Dankserver's :7770 endpoint is now unsloth llama.cpp (the b11030-mix
prebuilt, ROCm gfx120X, bundled runtime) serving
unsloth/Qwen3.8-Flash-Next-GGUF at Q8_0 — 175.30 GiB across six sha256-
verified split parts — on both Radeon AI PRO R9700s, as a ROOT unit (the
frank-large shape: no container, no generated unit).
The deployed contract:
- Placement law: layer split -ts 1,1 with -ngl 999; the routed experts
and the 51B-param n-gram (PLE) table are COMPUTED in host RAM via
-ot exps=CPU,ngram_embeddings=CPU (the x4-pair measurement: streaming
over PCIe loses at 3.7 t/s, computing in RAM does not care).
per_layer_token_embd.weight=CPU is required by the shared MTP head's
borrow path (a GPU-resident borrow crashes the draft load).
mmap stays on; --cache-ram 16384.
- Spec chain draft-mtp,ngram-mod: the shared Q8_0 MTP head
(mtp-...-shared-Q8_0.gguf, 2,786,568,256 B) at depth 2, with the
draftless ngram-mod matcher chained behind it — the only legal way to
layer two spec implementations in one process.
- Vision explicit: --mmproj names the BF16 projector (this build never
autoloads; a server without it comes up healthy and text-only).
- Reasoning: --reasoning-format deepseek → reasoning_content; the
Qwen3.8 template's {xhigh, medium, low} reasoning_effort gate folded
by reasoningEffortMap in models.yml.
- One stream, the model's full 262,144 context; greedy server default
with Qwen-recommended sampling riding compat.extraBody.
The deployer's deploy_llm_primary is rewritten for the root unit: it
gates every artifact at its declared byte count (stat -c %s, never wc -c
— which read the whole 377 GiB lucebox file for minutes on 2026-09-26),
retires the lucebox user quadlet to dated home trash, waits out port
7770, relinks/enables/restarts the root unit and waits up to 1800 s for
/health. The quadlet-era tree under dankserver/frank-primary/ is
archived (dankserver/.AGENTTRASH/20260926-llamacpp-cutover/ on this
box; git history everywhere else) and its planner mapping replaced by
the scripts/ trio.
Observability flips back to the llamacpp namespace: a frank-primary
Prometheus job at 192.168.0.235:7770 (cross-host LAN — the netavark
published-port quirk that forces host.containers.internal for the two
Dankclaw endpoints is same-host only), the LlmEndpointDown runbook
text, and the dashboard description for three llama.cpp endpoints.
Docs: the new runbook (frank-primary-llamacpp-qwen38-flash-next-q8.md),
RETIRED banners on the lucebox and radiance runbooks and their index
entries, the AGENTS.md fleet section rewritten for the root-unit shape,
and the homepage frank-medium card renamed to frank-primary with the
Q8_0 Flash-Next spec (docs.html #models and the modality note updated:
images on frank-primary and frank-small, audio on frank-small only).
models.yml: the dankserver provider is "llamacpp (dankserver)" serving
frank-primary, contextWindow 262144, maxTokens 65536 (compaction-gap
law: 183,501 + 65,536 <= 262,144), input [text, image].
Suite: 346 green (frank_primary_unit.bats rewritten for the root unit —
16 tests pinning the placement law, the shared-head requirement, the
explicit projector, HIP env hygiene (BIND_HOST, never bare HOST), and
the deployer's byte-count gates; the quadlet-era planner and
changed-tests mappings rewritten for the trio; the observability scrape
test asserts three llamacpp jobs again).
Rollback: the lucebox image (localhost/lucebox-rocm:gfx1201) and the
378 GiB payload stay on the host; the Qwen payload stays fetched, so a
re-swap in either direction is a redeploy, not a refetch.
This commit is contained in:
1 parent
d89813363e
commit
8500600d09
25 files changed
+856
-544
No files matched your search
@@ -1,90 +0,0 @@
|
||||
#!/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.
|
||||
#
|
||||
# SOURCE BRANCH. The `deepseek41` architecture exists only on the
|
||||
# `feat/ds41-engine` branch of Luce-Org/lucebox: main carries `deepseek4`
|
||||
# alone and rejects this checkpoint with "unsupported model architecture
|
||||
# 'deepseek41'" (server/src/common/backend_plan.cpp). The image is built from
|
||||
# that branch — see serverAppConfigs-docs/LLM/frank-primary-lucebox-ds41.md.
|
||||
#
|
||||
# LAUNCH SHAPE (server/docs/DS41.md — the V4.1 page, not the V4 DS4.md one):
|
||||
# --ds4-prefill exact THE ONLY prefill this placement can run. Any
|
||||
# approximate mode (dense, sparse) makes
|
||||
# DeepSeek4Backend::requires_monolithic_model() true,
|
||||
# and a monolithic load asks ggml for the whole 187 GiB
|
||||
# expert set as ONE device-0 buffer — measured
|
||||
# 2026-09-26: "allocating 191932.25 MiB on device 0:
|
||||
# cudaMalloc failed: out of memory", then
|
||||
# "monolithic HIP load required for dense prefill".
|
||||
# DS41.md's dense-prefill section measures that mode
|
||||
# where the experts fit (in-process owners); on a
|
||||
# 32 GiB target it cannot. exact feeds the prompt token
|
||||
# by token, which is what the hybrid tier streams for.
|
||||
# --chunk 1024 DS41.md's cold-expert chunk. The runtime caps the
|
||||
# chunk to what fits after headroom regardless.
|
||||
# --max-ctx 131072 the V4.1 default window. The MLA compressed caches
|
||||
# are F16 by design and stay small; --cache-type-k/v
|
||||
# are ignored by the DeepSeek4 backend, so they are
|
||||
# not passed.
|
||||
# --prefix-cache-slots 0 --prefill-cache-slots 0
|
||||
# the DS41.md bring-up recipe (the server default is
|
||||
# 32 prefix slots). Lift them once capacity under load
|
||||
# has been measured, not before.
|
||||
#
|
||||
# EXPERT PLACEMENT. 377 GiB of weights against 64 GiB of VRAM: only the dense
|
||||
# weights, the compressed caches and a hot expert stack fit on hip:0, and the
|
||||
# remaining routed experts are streamed out of the mmapped page cache/NVMe by
|
||||
# the hybrid expert tier. LUCE_EXPERT_STREAM_DEVICE=1 moves the streamed-
|
||||
# expert slot cache onto the second R9700 — it defaults to the target device,
|
||||
# where it would compete with the hot stack. Both the owner stack and the
|
||||
# slot cache then auto-size to their device's free memory minus the 1.5 GiB
|
||||
# discrete headroom.
|
||||
#
|
||||
# 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}"
|
||||
|
||||
# The second R9700 holds the streamed-expert slot cache and computes them.
|
||||
export LUCE_EXPERT_STREAM_DEVICE="${LUCE_EXPERT_STREAM_DEVICE:-1}"
|
||||
# The hot expert stack must be sized EXPLICITLY. Left alone the runtime
|
||||
# budgets gpu_free - kv - warm - safety (22.35 GiB here), which leaves only
|
||||
# its 0.50 GiB safety margin free while the fit check then demands the 1.50
|
||||
# GiB discrete headroom — its own auto-sizing can never pass on a 32 GiB
|
||||
# target: "device 0 memory: 30.97 of 31.86 GiB used, 0.89 GiB free (headroom
|
||||
# 1.50 GiB): does not fit". 18000 MB is the value DS41.md measured its R9700
|
||||
# rows at; it puts ~1500 experts (37/layer) resident and leaves device 0
|
||||
# ~5 GiB free, so the fit check passes with room for transient buffers.
|
||||
export LUCE_EXPERT_BUDGET_MB="${LUCE_EXPERT_BUDGET_MB:-18000}"
|
||||
# rocBLAS/BLAS dispatch and queue depth, as the fleet's other ROCm servers.
|
||||
export ROCBLAS_USE_HIPBLASLT="${ROCBLAS_USE_HIPBLASLT:-0}"
|
||||
export GPU_MAX_HW_QUEUES="${GPU_MAX_HW_QUEUES:-8}"
|
||||
|
||||
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 1024 \
|
||||
--ds4-prefill exact \
|
||||
--prefix-cache-slots 0 \
|
||||
--prefill-cache-slots 0
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/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)
|
||||
@@ -1,40 +0,0 @@
|
||||
diff --git a/Dockerfile.rocm b/Dockerfile.rocm
|
||||
index 4ea7a1d7..0e22355b 100644
|
||||
--- a/Dockerfile.rocm
|
||||
+++ b/Dockerfile.rocm
|
||||
@@ -59,18 +59,17 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
git \
|
||||
git-lfs \
|
||||
- hipblas-dev \
|
||||
- hipblaslt-dev \
|
||||
- hipcub-dev \
|
||||
libcurl4-openssl-dev \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
python3 \
|
||||
- rocblas-dev \
|
||||
- rocprim-dev \
|
||||
- rocwmma-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
+# Ubuntu jammy ships cmake 3.22, which predates the block command that
|
||||
+# ROCm 7.14 hipblaslt-config.cmake requires (CMake >= 3.25). Install the
|
||||
+# official 3.31 binary; /usr/local/bin precedes /usr/bin on PATH.
|
||||
+RUN curl -fsSL https://github.com/Kitware/CMake/releases/download/v3.31.6/cmake-3.31.6-linux-x86_64.tar.gz | tar -xz -C /usr/local --strip-components=1
|
||||
+
|
||||
WORKDIR /src
|
||||
|
||||
# COPY ordering mirrors the CUDA Dockerfile: C++ build inputs first so the
|
||||
@@ -152,11 +151,8 @@ ENV PATH=/opt/rocm/bin:${PATH}
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
- hipblas \
|
||||
- hipblaslt \
|
||||
libgomp1 \
|
||||
pciutils \
|
||||
- rocblas \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# uv (pinned) manages Python 3.12 + resolves the luce + pflash
|
||||
@@ -1,90 +0,0 @@
|
||||
# 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
|
||||
Reference in new issue
Block a user