|
|
|
@@ -29,7 +29,14 @@
|
|
|
|
|
# exactly like frank-archon's.
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
# The artifact gates are the shared library — the serve script and the
|
|
|
|
|
# deployer refuse the same missing/truncated payload with the same words
|
|
|
|
|
# (the 2026-10-01 crash-loop was the two hand-written twins disagreeing).
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
export GATE_PREFIX=frank-dragoon
|
|
|
|
|
# shellcheck disable=SC1091
|
|
|
|
|
source "$SCRIPT_DIR/lib/llm-gates.sh"
|
|
|
|
|
|
|
|
|
|
ENV_FILE="${FRANK_DRAGOON_ENV_FILE:-$SCRIPT_DIR/frank-dragoon.env}"
|
|
|
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
|
|
|
echo "frank-dragoon: env file not found: $ENV_FILE" >&2
|
|
|
|
@@ -57,55 +64,37 @@ set +a
|
|
|
|
|
: "${HALOGEN_KV_SLOTS:?HALOGEN_KV_SLOTS must be set}"
|
|
|
|
|
: "${HALOGEN_SPEC_ADAPT:?HALOGEN_SPEC_ADAPT must be set (0 = head always on)}"
|
|
|
|
|
|
|
|
|
|
# Capture podman's output BEFORE grepping: under `set -o pipefail` the
|
|
|
|
|
# `podman images | grep -q` pattern races (grep exits on first match,
|
|
|
|
|
# podman dies SIGPIPE 141, pipefail turns the 141 into "no match" — the
|
|
|
|
|
# 2026-09-30 deploy refusal).
|
|
|
|
|
DIGESTS="$(podman images --format '{{.RepoDigests}}')"
|
|
|
|
|
if ! grep -qF "${FRANK_DRAGOON_IMAGE#*@}" <<<"$DIGESTS"; then
|
|
|
|
|
echo "frank-dragoon: image $FRANK_DRAGOON_IMAGE not in podman storage;" >&2
|
|
|
|
|
echo " pull it: podman pull ${FRANK_DRAGOON_IMAGE%@*}" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
# The image gate is the digest-pinned ref itself: `podman image exists` takes
|
|
|
|
|
# a name@sha256 ref in one command, so a retagged look-alike or a newer
|
|
|
|
|
# latest cannot satisfy it and there is no pipefail/SIGPIPE race with grep
|
|
|
|
|
# (the 2026-09-30 deploy refusal). Runs as dank: the unit's process uses
|
|
|
|
|
# dank's rootless storage.
|
|
|
|
|
require_image "$FRANK_DRAGOON_IMAGE" \
|
|
|
|
|
"pull it: podman pull docker://$FRANK_DRAGOON_IMAGE" || exit 1
|
|
|
|
|
|
|
|
|
|
# Size-exact gates on all three payload files: a truncated curl exits 0 on
|
|
|
|
|
# some failures and a partial .hgn loads far enough to look alive, so the
|
|
|
|
|
# byte counts from the Hub tree API are the contract (fleet fetch law).
|
|
|
|
|
ck() {
|
|
|
|
|
# ck <env-name> <path> <expected-bytes>
|
|
|
|
|
if [ ! -f "$2" ]; then
|
|
|
|
|
echo "frank-dragoon: $1 $2 missing;" >&2
|
|
|
|
|
echo " fetch it: bash scripts/download-frank-dragoon-flashnext.sh" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
local sz
|
|
|
|
|
sz="$(stat -c %s "$2")"
|
|
|
|
|
if [ "$sz" != "$3" ]; then
|
|
|
|
|
echo "frank-dragoon: $1 is $sz bytes, expected $3 (partial download?)" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
ck checkpoint "$FRANK_DRAGOON_MODEL_DIR/$FRANK_DRAGOON_CHECKPOINT" "$FRANK_DRAGOON_CHECKPOINT_BYTES"
|
|
|
|
|
ck n-gram-table "$FRANK_DRAGOON_MODEL_DIR/$FRANK_DRAGOON_NGRAM_TABLE" "$FRANK_DRAGOON_NGRAM_BYTES"
|
|
|
|
|
ck vision-tower "$FRANK_DRAGOON_MODEL_DIR/$FRANK_DRAGOON_VISION_TOWER" "$FRANK_DRAGOON_VISION_BYTES"
|
|
|
|
|
require_bytes "$FRANK_DRAGOON_MODEL_DIR/$FRANK_DRAGOON_CHECKPOINT" \
|
|
|
|
|
checkpoint "$FRANK_DRAGOON_CHECKPOINT_BYTES" \
|
|
|
|
|
"fetch it: bash scripts/download-frank-dragoon-flashnext.sh" || exit 1
|
|
|
|
|
require_bytes "$FRANK_DRAGOON_MODEL_DIR/$FRANK_DRAGOON_NGRAM_TABLE" \
|
|
|
|
|
n-gram-table "$FRANK_DRAGOON_NGRAM_BYTES" \
|
|
|
|
|
"fetch it: bash scripts/download-frank-dragoon-flashnext.sh" || exit 1
|
|
|
|
|
require_bytes "$FRANK_DRAGOON_MODEL_DIR/$FRANK_DRAGOON_VISION_TOWER" \
|
|
|
|
|
vision-tower "$FRANK_DRAGOON_VISION_BYTES" \
|
|
|
|
|
"fetch it: bash scripts/download-frank-dragoon-flashnext.sh" || exit 1
|
|
|
|
|
|
|
|
|
|
# The payload ships the tokenizer as a FLAT DIRECTORY. A HuggingFace cache
|
|
|
|
|
# snapshot symlinks each file into a sibling blobs/, and those symlinks
|
|
|
|
|
# dangle inside the container mount — so the directory must be real and
|
|
|
|
|
# every named file must be a regular file, not a link.
|
|
|
|
|
TOK_DIR="$FRANK_DRAGOON_MODEL_DIR/tokenizer"
|
|
|
|
|
if [ -L "$TOK_DIR" ] || [ ! -d "$TOK_DIR" ]; then
|
|
|
|
|
echo "frank-dragoon: tokenizer dir $TOK_DIR missing or a symlink (HF cache snapshot?);" >&2
|
|
|
|
|
echo " fetch it: bash scripts/download-frank-dragoon-flashnext.sh" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
for TF in $FRANK_DRAGOON_TOKENIZER_FILES; do
|
|
|
|
|
if [ ! -f "$TOK_DIR/$TF" ] || [ -L "$TOK_DIR/$TF" ]; then
|
|
|
|
|
echo "frank-dragoon: tokenizer file $TOK_DIR/$TF missing or a symlink;" >&2
|
|
|
|
|
echo " fetch it: bash scripts/download-frank-dragoon-flashnext.sh" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
# The tokenizer file list is word-separated ON PURPOSE (one argument per
|
|
|
|
|
# file), so the unquoted expansion below is the interface, not a slip.
|
|
|
|
|
# shellcheck disable=SC2086
|
|
|
|
|
require_dir_files "$FRANK_DRAGOON_MODEL_DIR/tokenizer" tokenizer \
|
|
|
|
|
"fetch it: bash scripts/download-frank-dragoon-flashnext.sh" \
|
|
|
|
|
$FRANK_DRAGOON_TOKENIZER_FILES || exit 1
|
|
|
|
|
|
|
|
|
|
# GPU access is mandatory but the node paths are variables so the seam can
|
|
|
|
|
# drive the gate on a box without an AMD GPU.
|
|
|
|
@@ -115,6 +104,7 @@ if [ ! -e "$DEV_KFD" ] || [ ! -d "$DEV_DRI" ]; then
|
|
|
|
|
echo "frank-dragoon: $DEV_KFD or $DEV_DRI missing — no gfx1151 to serve on" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Host-RAM preflight — the engine's pin gate, checked BEFORE podman run.
|
|
|
|
|
# The engine refuses to pin when MemAvailable is below the checkpoint plus
|
|
|
|
|
# a 16 GiB floor: pinned pages cannot be swapped, and under the floor the
|
|
|
|
|