Files
serverAppConfigs/scripts/serve-frank-primary.sh
T
Frank bd5c28c4e9 frank-primary: FreeToken (PR587 ROCm) cutover — trio, seam, deployer
Replaces the tcclaviger vLLM fork with FreeToken at PR #587 head 77a7201
built on dankserver into localhost/frank-primary-freetoken:pr587-77a7201
(scripts/docker/frank-primary-freetoken.Dockerfile: digest-pinned
rocm/pytorch rocm7.14 torch-2.11.0 base, libhiprtc dev symlink, curated
--no-deps install per docs/install_amd.md).

Shape (first-boot conservative): ONE R9700 (second card idle;
--tensor-parallel-size exists for future 2-card tuning), KV pool
--num-tokens 300000 (7.2 GiB at 24 KiB/token; clears the 177,356-token
harness worst case and one native 262,144 conversation), MoE LRU
--moe-cache-rate 0.10, --max-running-requests 2, --max-output-tokens
36864. Parsers AUTO: thinking -> reasoning_content natively (models.yml
flips reasoningContentField on the harness side). Experts + PLE pin
~170 GiB host RAM — root unit keeps LimitMEMLOCK=infinity.

Container identity renamed to the unit name (Frank's call): frank-primary;
vLLM-era frank-primary-qwen joins the legacy cleanup list. Deployer image
gate becomes podman image exists (a local build carries no RepoDigests;
the old digest grep refused exactly this shape). Rollback tag:
frank-primary-vllm-shapeG-20261003.
2026-10-05 19:17:13 -07:00

133 lines
6.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# serve-frank-primary.sh — frank-primary endpoint (FreeToken PR #587 ROCm
# build, orcarouter Qwen3.8-Flash-Next-Uncensored-FP8, ONE R9700 gfx1201,
# dankserver :7770; 2026-10-05 cutover from the tcclaviger vLLM fork — git
# tag frank-primary-vllm-shapeG-20261003 is the pre-cutover state).
#
# The engine is driven by `podman run` DIRECTLY and the run line below IS
# the engine argv: the image carries FreeToken installed at pinned PR head
# 77a7201 (scripts/docker/frank-primary-freetoken.Dockerfile builds it from
# the PR's own docs/install_amd.md stack). Unlike the vLLM fork there is no
# per-boot planner: experts live in pinned host RAM with an LRU GPU cache,
# KV is an explicit token pool, and the boot ledger in the log names
# Shape: 1 GPU (first-boot conservative — the second R9700 sits idle;
# --tensor-parallel-size EXISTS in FreeToken, so a 2-card TP shape is future
# tuning once this single-card shape has a decode baseline), KV pool 300,000
# tokens (24 KiB/token f16 across the 12 full-attention layers = 7.2 GiB;
# clears the harness worst case of 177,356 tokens and admits one NATIVE
# 262,144-token conversation), MoE LRU at 10% of all experts (~12 GiB),
# 2 decode slots. First-boot conservative; `ft ctl cache --kv/--moe` grows
# pools live, and the env comment block carries the resize law. Flag
# surface verified against docs/cli.md of the pinned head AND against
# `ft serve --help` inside the built image; every value here is env-driven
# (frank-primary.env).
# The root unit (scripts/frank-primary.service, User=dank,
# LimitMEMLOCK=infinity) grants the pin budget: ~120 GiB of expert weights
# plus the ~47.7 GiB PLE table pin in host RAM. 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 FreeToken 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_FT_GPU:?FRANK_PRIMARY_FT_GPU must name the GPU index}"
: "${FRANK_PRIMARY_FT_NUM_TOKENS:?FRANK_PRIMARY_FT_NUM_TOKENS must size the KV pool}"
: "${FRANK_PRIMARY_FT_MAX_RUNNING:?FRANK_PRIMARY_FT_MAX_RUNNING must be set}"
: "${FRANK_PRIMARY_FT_MAX_OUTPUT_TOKENS:?FRANK_PRIMARY_FT_MAX_OUTPUT_TOKENS must be set}"
: "${FRANK_PRIMARY_FT_MOE_RATE:?FRANK_PRIMARY_FT_MOE_RATE must be set}"
# The image gate is EXISTENCE IN STORAGE, not a registry digest: the tag is
# built ON THE HOST (podman build from
# scripts/docker/frank-primary-freetoken.Dockerfile) and local builds carry
# no RepoDigests entry — the 362-line vLLM-era RepoDigests grep refused
# exactly this shape. Pinning lives in the Dockerfile instead: the FreeToken
# source is checked out at a hardcoded PR-head SHA and the base image is
# digest-pinned there. `podman image exists` accepts both a local tag and a
# name@digest ref, and it is one command — no pipefail/grep race (the
# 2026-09-30 lesson) for either shape.
if ! podman image exists "$FRANK_PRIMARY_IMAGE"; then
echo "serve-frank-primary: image absent from rootless storage: $FRANK_PRIMARY_IMAGE" >&2
echo " build it: podman build -t $FRANK_PRIMARY_IMAGE -f scripts/docker/frank-primary-freetoken.Dockerfile scripts/docker/" >&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 vLLM-era frank-primary-qwen, the retired R9V container and
# the ad hoc experiment containers all hold this GPU); 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). --ipc=host for the
# engine's shared memory; memlock unbounded for the ~120 GiB pinned expert
# rows + the 47.7 GiB PLE pin (the unit's LimitMEMLOCK=infinity is the other
# half of that law).
# HIP_VISIBLE_DEVICES pins visibility to ONE card so in-container index 0 is
# the right R9700; --gpu 0 selects it inside the visible set.
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="$FRANK_PRIMARY_FT_GPU" \
-v "$FRANK_PRIMARY_MODEL_DIR":/models:ro \
"$FRANK_PRIMARY_IMAGE" \
ft serve --model /models \
--served-model-name "$FRANK_PRIMARY_SERVED_NAME" \
--host 0.0.0.0 --port 7770 \
--gpu "$FRANK_PRIMARY_FT_GPU" \
--num-tokens "$FRANK_PRIMARY_FT_NUM_TOKENS" \
--max-running-requests "$FRANK_PRIMARY_FT_MAX_RUNNING" \
--max-output-tokens "$FRANK_PRIMARY_FT_MAX_OUTPUT_TOKENS" \
--moe-cache-rate "$FRANK_PRIMARY_FT_MOE_RATE" \
${FRANK_PRIMARY_FT_EXTRA_ARGS:+$FRANK_PRIMARY_FT_EXTRA_ARGS} \
> "$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=always is real. (systemd stop goes through ExecStop in the unit —
# podman wait alone cannot stop the container child.)
exec podman wait "$FRANK_PRIMARY_CONTAINER"