frank-large: cut over to halogen container (qwen3.8-27b .hgn + DFlash2)

Runtime swap ROCmFPX llama.cpp -> peonist-ai/halogen (image digest
dc0a39a0, tag 0.1.4), serving qwen3.8-27b-p1w4d-d2.hgn (33.4 GiB, the
.hgn format loads in no other engine) with HALOGEN_DRAFTER=2 (DFlash2)
on ONE slot of 200700 ctx: slots>1 disables the drafter (engine law).
Accepted: TEXT-ONLY (vision encoder ignored; frank-small owns images)
and the censored base (rollback = revert this commit; payload, fork
build and build-rocmfpx.sh pin stay on disk).

- serve script: podman run under the root unit, exec podman wait so
  Restart=always is real; gates image digest, checkpoint byte-count,
  flat regular-file tokenizer, GPU nodes; engine stdout to log.
- deploy handler: gates the artifacts, sweeps the legacy bare
  llama-server + old container name by NAME (never pkill llama-server:
  frank-small), retires the 2026-09 quadlet, port cutover 7770.
- fetch script: -C - resume, -f, byte count, sha256, --verify-only.
- seam rewritten (17 tests): halogen is now the deployed runtime.
This commit is contained in:
Frank
2026-10-05 16:29:11 -07:00
parent aa9908257d
commit fece3ffdd0
6 changed files with 552 additions and 560 deletions
+57 -57
View File
@@ -1086,32 +1086,31 @@ deploy_anubis() {
wait_container_healthy anubis 60 || return 1
}
# frank-large is the ROCmFPX fork of llama.cpp (kingjones30/ROCmFPX, pinned by
# scripts/build-rocmfpx.sh), serving
# kingjones777/Qwen3.8-27B-Uncensored-ROCmFP4-STRIX-MTP-GGUF:Q6_0_ROCMFPX_AGENT
# with the repo's own Q4_0 MTP draft head and the BF16 vision projector, on the
# Strix Halo iGPU. It is a ROOT unit: the runtime is a native binary linked
# against the host ROCm, so there is no container image to install and no
# generated unit to dodge. relink_unit points
# /etc/systemd/system/frank-large.service at the repo file and boot persistence
# is that unit's [Install] section.
# frank-large is the halogen CONTAINER (ghcr.io/peonist-ai/halogen,
# digest-pinned through scripts/frank-large.env) serving
# peonist-ai/halogen-qwen3.8-27b — qwen3.8-27b-p1w4d-d2.hgn, 33.4 GiB,
# DFlash2 spec decode, 1 slot x 200700, text-only — on the Strix Halo
# iGPU. It stays a ROOT unit because the serve script drives `podman run`
# directly (the frank-primary pattern): relink_unit points
# /etc/systemd/system/frank-large.service at the repo file, boot
# persistence is that unit's [Install], and LimitMEMLOCK=infinity is
# fleet parity. 2026-10-05 cutover from the ROCmFPX llama.cpp shape —
# rollback = revert the swap commit's trio; that payload, its draft head
# and the fork build all stay on disk
# (~/models/kingjones-Qwen3.8-27B-Uncensored-ROCmFP4-STRIX-MTP,
# ~/GitPackages/kingjones-ROCmFPX).
#
# The runtime gate matters as much as the payload gate: the fork is built from
# source on this host, and the payload is ftype 114 (tensor types 102
# GGML_TYPE_Q6_0_ROCMFPX and 103 GGML_TYPE_Q8_0_ROCMFPX) which only this fork
# can read — so a machine whose /home/dank/GitPackages tree was never built has
# a unit that can never become healthy, and a green deploy would leave the
# endpoint dark.
#
# It replaces the ALIGNED kingjones Q8_0_ROCMFPX_AGENT file (rollback target:
# its payload and its draft head are still on disk in
# ~/models/kingjones-Qwen3.8-27B-ROCmFP4-STRIX-MTP, where this payload's
# draft-head hardlink also lives). The port gate is the same as every cutover
# since 2026-09-16: nothing starts until 7770 is released.
LLM_LARGE_RUNTIME="${LLM_LARGE_RUNTIME:-/home/dank/GitPackages/kingjones-ROCmFPX/build/bin/llama-server}"
LLM_LARGE_MODEL="${LLM_LARGE_MODEL:-/home/dank/models/kingjones-Qwen3.8-27B-Uncensored-ROCmFP4-STRIX-MTP/Qwen3.8-27B-Uncensored-Q6_0_ROCMFPX_AGENT.gguf}"
LLM_LARGE_DRAFT="${LLM_LARGE_DRAFT:-/home/dank/models/kingjones-Qwen3.8-27B-Uncensored-ROCmFP4-STRIX-MTP/mtp-Qwen3.8-27B-Q4_0.gguf}"
LLM_LARGE_MMPROJ="${LLM_LARGE_MMPROJ:-/home/dank/models/kingjones-Qwen3.8-27B-Uncensored-ROCmFP4-STRIX-MTP/mmproj-Qwen3.8-27B-BF16.gguf}"
# The gates mirror serve-frank-large.sh's predicates EXACTLY (the
# 2026-10-01 crash-loop lesson): image digest in dank's podman storage,
# the .hgn checkpoint at the exact byte count, the tokenizer a regular
# (not symlinked) file — a green deploy against a missing artifact would
# leave a restart-looping unit and a dark endpoint. The port gate is the
# same as every cutover since 2026-09-16: nothing starts until 7770 is
# released.
LLM_LARGE_IMAGE="${LLM_LARGE_IMAGE:-ghcr.io/peonist-ai/halogen@sha256:dc0a39a0016d6cfc58a197978febaafdf8d28403f724ded6111d98b5fb7ac0ea}"
LLM_LARGE_CKPT="${LLM_LARGE_CKPT:-/home/dank/models/halogen-qwen3.8-27b/qwen3.8-27b-p1w4d-d2.hgn}"
LLM_LARGE_CKPT_BYTES="${LLM_LARGE_CKPT_BYTES:-35880329102}"
LLM_LARGE_TOKENIZER="${LLM_LARGE_TOKENIZER:-/home/dank/models/halogen-qwen3.8-27b/frank-large.tokenizer.gguf}"
retire_halogen_large_quadlet() {
local trash="$HOME/.AGENTTRASH/20260919-orca-cutover/quadlet"
@@ -1144,43 +1143,44 @@ wait_port_free() {
}
deploy_llm_large() {
# Refuse rather than restart-loop. A unit whose runtime or payload is
# missing never becomes healthy, and a green deploy would leave the
# endpoint dark with nothing in the deploy log to say why. The gates
# mirror serve-frank-large.sh's own predicates exactly: runtime
# executable, target GGUF, MTP head, mmproj.
if [[ ! -x "$LLM_LARGE_RUNTIME" ]]; then
echo "ERROR: ROCmFPX runtime absent at $LLM_LARGE_RUNTIME" >&2
echo "frank-large: build it: bash scripts/build-rocmfpx.sh" >&2
# Refuse rather than restart-loop; every gate below has a twin in
# serve-frank-large.sh and they must agree (2026-10-01 lesson).
# Capture podman output before grepping: `podman images | grep -q`
# races under pipefail (SIGPIPE 141 becomes "no match", 2026-09-30).
if ! grep -qF "${LLM_LARGE_IMAGE#*@}" <<<"$(podman images --format '{{.RepoDigests}}' 2>/dev/null)"; then
echo "ERROR: halogen image absent: $LLM_LARGE_IMAGE" >&2
echo "frank-large: pull it: podman pull ${LLM_LARGE_IMAGE%@*}" >&2
return 1
fi
if [[ ! -f "$LLM_LARGE_CKPT" ]]; then
echo "ERROR: halogen checkpoint absent at $LLM_LARGE_CKPT" >&2
echo "frank-large: fetch it: bash scripts/download-frank-large-halogen.sh" >&2
return 1
fi
if [[ "$(stat -c %s "$LLM_LARGE_CKPT")" != "$LLM_LARGE_CKPT_BYTES" ]]; then
echo "ERROR: halogen checkpoint is not $LLM_LARGE_CKPT_BYTES bytes (partial download?)" >&2
echo "frank-large: re-check: bash scripts/download-frank-large-halogen.sh --verify-only" >&2
return 1
fi
if [[ ! -f "$LLM_LARGE_TOKENIZER" || -L "$LLM_LARGE_TOKENIZER" ]]; then
echo "ERROR: tokenizer missing or a symlink at $LLM_LARGE_TOKENIZER" >&2
echo "frank-large: fetch it: bash scripts/download-frank-large-halogen.sh" >&2
return 1
fi
local missing=0 f
for f in "$LLM_LARGE_MODEL" "$LLM_LARGE_DRAFT" "$LLM_LARGE_MMPROJ"; do
if [[ ! -f "$f" ]]; then
echo "ERROR: payload absent at $f" >&2
echo "frank-large: fetch it: bash scripts/download-frank-large-kingjones-uncensored.sh" >&2
missing=1
fi
done
[[ "$missing" -eq 0 ]] || return 1
retire_halogen_large_quadlet
# On a re-deploy the predecessor is THIS unit: stop it before waiting on
# the port, or the wait deadlocks on the server it just stopped serving.
# Then sweep a stray manual run -- that is how a bench harness leaves the
# port busy. Match the TARGET GGUF name, which every frank-large command
# line carries via --model and which no other process on this host uses:
# frank-small also runs llama-server here (with the MiMo GGUF) and must
# not be killed, so keying on the binary name would take the 3090 endpoint
# down with it. The pattern never appears in the deploying shell's own
# command line, so -f is safe here (it is not in `bash deploy-remote.sh`).
# The predecessor on a re-deploy is THIS unit: stop it (ExecStop stops
# the container) before waiting on the port. The stray sweep is keyed
# on the FULL container name, which appears in the argv of a manual
# `podman run --name frank-large-halogen` / `podman wait` but NOT in
# this deployer's own command line (`bash deploy-remote.sh
# frank-large`) — so it cannot shoot itself in the foot (the fleet
# pkill law), and it can never match frank-small's llama-server.
run_privileged systemctl stop frank-large.service 2>/dev/null || true
pkill -f 'Qwen3.8-27B-Uncensored-Q6_0_ROCMFPX_AGENT' 2>/dev/null || true
# Nothing may start until the predecessor has released the port. On
# 2026-09-16 the cutover failed exactly here: the old server logged
# "Deactivated successfully", the next engine tried to bind 7770 a second
# later and reported "Address already in use". Gate on the port, not on
# hope.
pkill -f 'frank-large-halogen' 2>/dev/null || true
podman rm -f frank-large frank-large-halogen >/dev/null 2>&1 || true
# Nothing may start until the predecessor has released the port
# (2026-09-16: "Deactivated successfully" was not "port free").
wait_port_free 7770 || return 1
relink_unit frank-large
run_privileged systemctl daemon-reload