#!/usr/bin/env bash # serve-frank-primary.sh — frank-primary endpoint (tcclaviger RDNA4 vLLM fork, # orcarouter Qwen3.8-Flash-Next-Uncensored-FP8 + MTP4 spec decode, 2x R9700, # dankserver :7770; 2026-09-29 cutover from the R9V radiance fork — git tag # frank-primary-r9v-q8-20260929 is the pre-cutover state). # # The engine is driven by `podman run` DIRECTLY: the image's entrypoint execs # `vllm serve "$@"`, so the run line below IS the engine argv. This replaces # the R9V launcher/shim machinery (that fork needed a docker->podman shim and # a 104-key on-host setup state; this stack is fully expressed by flags, and # every flag here was qualified by boot probe on 2026-09-29 — see # frank-primary.env for the qualification record). # # Shape (2026-10-01 bf16 cutover): MAXLEN 262144 x 3 sessions (786,432 # tokens), KV UNQUANTIZED (--kv-cache-dtype auto = bf16; the fp8 q8/q8 shape # is retired to git tag frank-primary-fp8-mtp-20261001), KV_BYTES # 11,047,010,304 PER RANK (the engine measured 798,280 tokens of capacity / # 3.05x concurrency), NO speculative decode, --max-num-batched-tokens 1024 # (Frank's lock), MoE gather geometry R4D_LRU_GATHER_CHUNKS=32 / # R4D_LRU_GATHER_LANES=64 (+18.7% decode, grows with depth), prefix caching, # expert prefetch OFF (36% precision measured), TunableOp off. # # The root unit (scripts/frank-primary.service, User=dank, # LimitMEMLOCK=infinity) grants the pin budget for the tiered-expert host # slabs (~20.5 GiB/rank of pinned anonymous memory). A plain shell session # also works, 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 : "${FRANK_PRIMARY_IMAGE:?FRANK_PRIMARY_IMAGE must name the digest-pinned image}" : "${FRANK_PRIMARY_MODEL_DIR:?FRANK_PRIMARY_MODEL_DIR must name the FP8 payload}" : "${FRANK_PRIMARY_MODEL_SHARDS:?FRANK_PRIMARY_MODEL_SHARDS must give the shard count}" : "${FRANK_PRIMARY_CONTAINER:?FRANK_PRIMARY_CONTAINER must name the container}" : "${FRANK_PRIMARY_SERVED_NAME:?FRANK_PRIMARY_SERVED_NAME must name the alias}" : "${FRANK_PRIMARY_PUBLISH:?FRANK_PRIMARY_PUBLISH must be host:container}" : "${FRANK_PRIMARY_MAXLEN:?FRANK_PRIMARY_MAXLEN must be set}" : "${FRANK_PRIMARY_SEQS:?FRANK_PRIMARY_SEQS must be set}" : "${FRANK_PRIMARY_KV_BYTES:?FRANK_PRIMARY_KV_BYTES must be set (PER RANK)}" : "${FRANK_PRIMARY_KV_DTYPE:?FRANK_PRIMARY_KV_DTYPE must be set (fp8 = q8/q8 KV)}" # Capture podman's output BEFORE grepping: under `set -o pipefail`, the # classic `podman images | grep -q` pattern races — grep exits on its first # match, podman dies SIGPIPE (141) writing the remaining lines, and pipefail # turns the 141 into "no match" (the 2026-09-30 deploy refusal). DIGESTS="$(podman images --format '{{.RepoDigests}}')" if ! grep -qF "${FRANK_PRIMARY_IMAGE#*@}" <<<"$DIGESTS"; then echo "serve-frank-primary: pinned image absent from rootless storage: $FRANK_PRIMARY_IMAGE" >&2 echo " fetch it: podman pull $FRANK_PRIMARY_IMAGE" >&2 exit 1 fi if [ ! -f "$FRANK_PRIMARY_MODEL_DIR/config.json" ]; then echo "serve-frank-primary: payload config missing: $FRANK_PRIMARY_MODEL_DIR/config.json" >&2 exit 1 fi SHARDS="$(find "$FRANK_PRIMARY_MODEL_DIR" -maxdepth 1 -name 'model-*-of-*.safetensors' | wc -l)" if [ "$SHARDS" != "$FRANK_PRIMARY_MODEL_SHARDS" ]; then echo "serve-frank-primary: payload shard count wrong: $FRANK_PRIMARY_MODEL_DIR" >&2 echo " found $SHARDS safetensors, want $FRANK_PRIMARY_MODEL_SHARDS" >&2 echo " re-fetch: scripts/download-frank-primary.sh" >&2 exit 1 fi if [ ! -f "$FRANK_PRIMARY_MODEL_DIR/model.safetensors.index.json" ]; then echo "serve-frank-primary: weight-map index missing: $FRANK_PRIMARY_MODEL_DIR/model.safetensors.index.json" >&2 exit 1 fi 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). Clear OUR name plus the legacy # identities (the retired R9V container and the ad hoc experiment container # both hold these GPUs); all idempotent. for CTR in $FRANK_PRIMARY_LEGACY_CONTAINERS "$FRANK_PRIMARY_CONTAINER"; do podman rm -f "$CTR" >/dev/null 2>&1 || true done LOG=/home/dank/logs/frank_primary.log mkdir -p "$(dirname "$LOG")" # --device /dev/dri is the WHOLE DIRECTORY (individual renderD nodes enumerate # without the card nodes and HIP refuses contexts — "No CUDA GPUs are # available" while device_count() says 2). --ipc=host for the engine's shared # memory; memlock unbounded for the pinned host slabs. # The entrypoint already execs `vllm serve "$@"`, so no `serve` here. podman run -d --name "$FRANK_PRIMARY_CONTAINER" \ --publish "$FRANK_PRIMARY_PUBLISH" \ --ipc=host --ulimit memlock=-1:-1 \ --cap-add SYS_PTRACE \ --device /dev/kfd --device /dev/dri --device /dev/mem \ -e HIP_VISIBLE_DEVICES=0,1 -e VLLM_ROCM_USE_AITER=0 \ ${FRANK_PRIMARY_GATHER_CHUNKS:+-e R4D_LRU_GATHER_CHUNKS="$FRANK_PRIMARY_GATHER_CHUNKS"} \ ${FRANK_PRIMARY_GATHER_LANES:+-e R4D_LRU_GATHER_LANES="$FRANK_PRIMARY_GATHER_LANES"} \ -v "$FRANK_PRIMARY_MODEL_DIR":/models:ro \ "$FRANK_PRIMARY_IMAGE" /models \ --served-model-name "$FRANK_PRIMARY_SERVED_NAME" --port 7770 \ --tensor-parallel-size 2 \ --max-model-len "$FRANK_PRIMARY_MAXLEN" \ --max-num-seqs "$FRANK_PRIMARY_SEQS" \ ${FRANK_PRIMARY_MNBT:+--max-num-batched-tokens "$FRANK_PRIMARY_MNBT"} \ --gpu-memory-utilization "$FRANK_PRIMARY_UTIL" \ --kv-cache-memory-bytes "$FRANK_PRIMARY_KV_BYTES" \ --kv-cache-dtype "$FRANK_PRIMARY_KV_DTYPE" \ --enable-prefix-caching \ --trust-remote-code \ > "$LOG" 2>&1 # 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 the unit's # Restart=on-failure is real. (systemd stop goes through ExecStop in the # unit — podman wait alone cannot stop the container child.) exec podman wait "$FRANK_PRIMARY_CONTAINER"