Files
serverAppConfigs/scripts/serve-frank-primary.sh
T
Frank b06895d725 frank-primary: cutover state to Qwen3.8-Flash-Next Q8_0 (6 shards, PLE pinned, MTP4, sharp template)
- env: STATE_FILE -> r9v-state/q8xl; add dev-overlay adapter mount
  (quantized inject shard dequant, fork 363d606), FULL_MUTABLE_CACHE=0
  (80-slot tiered vs the overlay's uncensored 160-slot injection), and
  the qwen-sharp-v22.4.0.jinja chat template via the launcher's new
  R9V_CHAT_TEMPLATE_FILE/KWARGS passthrough (fork 30d4a32)
- serve/deploy: shard gates extended to SHARD4/5/6 (unset-safe, the
  uncensored rollback state carries none)
- seams: STATE_FILE/LLM_PRIMARY_STATE -> q8xl
2026-09-27 22:25:08 -07:00

143 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# serve-frank-primary.sh — frank-primary endpoint (R9V radiance-vLLM fork,
# unsloth Qwen3.8-Flash-Next Q8_0 + MTP4 drafter, 2x R9700, dankserver
# :7770; 2026-09-27 cutover from the uncensored IQ4_XS payload, whose state
# dir stays on disk as the rollback target).
#
# The engine is the upstream R9V launcher (scripts/launch.sh in the fork
# checkout), NOT a hand-frozen podman argv: launch.sh owns the resolved
# docker/podman args (binds, overlays, device passthrough, publish lines)
# and the fork's qualification was measured through it. This script's job:
#
# 1. layer the on-host R9V setup state (the 104-key tuned shape the
# profile setup qualified) under this repo's frank-primary.env
# overrides (bind/port/alias/container);
# 2. gate on the pinned runtime image (by its OCI manifest digest, the
# podman RepoDigest), the exact-size PLE payload, and the GGUF shards;
# 3. exec the launcher, then wait on the container: launch.sh --detach
# exits as soon as the container is created, so without the wait the
# unit would be "green" while the engine crashes — the wait is what
# keeps systemd's Restart=on-failure self-healing real.
#
# The root unit (scripts/frank-primary.service, User=dank,
# LimitMEMLOCK=infinity) grants the pin budget for the tiered-expert UVA
# host copy and the PLE pinned residency (54.4 GB on the q8 payload). A
# plain shell session also works (proven 2026-09-27: podman rootless
# inherits usable limits here), but the unit is the fleet-parity
# boot-persistent shape.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${FRANK_PRIMARY_ENV_FILE:-$SCRIPT_DIR/frank-primary.env}"
if [ ! -f "$ENV_FILE" ]; then
echo "serve-frank-primary: env file missing: $ENV_FILE" >&2
exit 1
fi
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
: "${R9V_HOME:?R9V_HOME must name the fork checkout}"
: "${R9V_STATE_FILE:?R9V_STATE_FILE must name the setup state}"
: "${R9V_HOST_BIND:?R9V_HOST_BIND must be set}"
: "${R9V_HOST_PORT:?R9V_HOST_PORT must be set}"
: "${R9V_SERVED_MODEL_NAME:?R9V_SERVED_MODEL_NAME must be set}"
: "${R9V_CONTAINER_NAME:?R9V_CONTAINER_NAME must be set}"
LAUNCHER="$R9V_HOME/scripts/launch.sh"
if [ ! -x "$LAUNCHER" ]; then
echo "serve-frank-primary: R9V launcher missing: $LAUNCHER" >&2
exit 1
fi
# The docker->podman shim is REQUIRED: the launcher and its doctor speak
# docker (OCI-manifest-digest image IDs, LogConfig fields) that podman's
# native CLI answers differently. The shim carries those laws.
SHIM_DIR="$R9V_HOME/.podman-shim"
if [ ! -x "$SHIM_DIR/docker" ]; then
echo "serve-frank-primary: docker->podman shim missing: $SHIM_DIR/docker" >&2
exit 1
fi
export PATH="$SHIM_DIR:$PATH"
# --- layer 1: the qualified setup state (production overrides never come
# from here; keys already defined in the env file are skipped) -------------
STATE_FILE="$R9V_STATE_FILE"
if [ ! -f "$STATE_FILE" ]; then
echo "serve-frank-primary: R9V setup state missing: $STATE_FILE" >&2
echo " run the profile setup first (tools/setup_profile.py setup --state-dir ...)" >&2
exit 1
fi
STATE_EXPORTS="$(python3 - "$STATE_FILE" <<'PY'
import json, shlex, sys, os
state = json.load(open(sys.argv[1]))
config = state.get("config") or {}
if state.get("ready") is not True:
sys.exit("R9V setup state is not ready; rerun setup before serving")
env_keys = {k for k in os.environ if k.startswith("R9V_")}
for key in sorted(config):
if key.startswith("R9V_") and key not in env_keys:
print(f"export {key}={shlex.quote(config[key])}")
PY
)"
eval "$STATE_EXPORTS"
: "${R9V_IMAGE:?state must carry R9V_IMAGE}"
: "${R9V_MODEL_DIR:?state must carry R9V_MODEL_DIR}"
: "${R9V_PLE_PATH:?state must carry R9V_PLE_PATH}"
# --- gates: never serve a half-finished payload or a missing image --------
# Runtime image by pinned OCI manifest digest (podman keys images by config
# digest but preserves the manifest digest in RepoDigests — the shim's ID law).
if ! podman images --format '{{.RepoDigests}}' | grep -qF "${R9V_IMAGE#sha256:}"; then
echo "serve-frank-primary: pinned R9V image absent: $R9V_IMAGE" >&2
echo " load the pinned image bundle into podman rootless storage" >&2
exit 1
fi
# PLE payload at its pinned byte count (stat -c %s, never wc -c), plus the
# six target GGUF shards (mradermacher-style split parts) and the mmproj
# the launcher mounts.
PLE_BYTES="${R9V_PLE_EXPECTED_BYTES:-}"
if [ -n "$PLE_BYTES" ]; then
ACTUAL="$(stat -c %s "$R9V_PLE_PATH" 2>/dev/null || echo 0)"
if [ "$ACTUAL" != "$PLE_BYTES" ]; then
echo "serve-frank-primary: PLE payload wrong size: $R9V_PLE_PATH" >&2
echo " expected $PLE_BYTES, found $ACTUAL" >&2
exit 1
fi
else
if [ ! -f "$R9V_PLE_PATH" ]; then
echo "serve-frank-primary: PLE payload missing: $R9V_PLE_PATH" >&2
exit 1
fi
fi
for rel in "$R9V_TARGET_REL" "${R9V_TARGET_SHARD2_REL:-}" "${R9V_TARGET_SHARD3_REL:-}" \
"${R9V_TARGET_SHARD4_REL:-}" "${R9V_TARGET_SHARD5_REL:-}" "${R9V_TARGET_SHARD6_REL:-}" \
"$R9V_MMPROJ_REL"; do
if [ -z "$rel" ]; then continue; fi
if [ ! -f "$R9V_MODEL_DIR/$rel" ]; then
echo "serve-frank-primary: payload file missing: $R9V_MODEL_DIR/$rel" >&2
exit 1
fi
done
if [ ! -e /dev/kfd ] || [ ! -d /dev/dri ]; then
echo "serve-frank-primary: GPU device nodes absent (/dev/kfd, /dev/dri)" >&2
exit 1
fi
# A crashed/killed unit can leave the container name registered (systemd
# SIGKILLs conmon before cleanup runs); the launcher refuses an existing
# name forever after. Clear any orphan of OUR name first — idempotent.
docker rm -f "$R9V_CONTAINER_NAME" >/dev/null 2>&1 || true
# launch.sh detaches after preflight; from here on, this script's lifetime
# IS the container's: wait returns the container's exit code, so a crash is
# a nonzero ExecStart and Restart=on-failure fires. (systemd stop goes
# through ExecStop in the unit — docker wait alone cannot stop the child.)
"$LAUNCHER"
exec docker wait "$R9V_CONTAINER_NAME"