refact(frank-dragoon): serve gates run on the shared gate library
The hand-written image/ck/tokenizer gates are require_image, require_bytes and require_dir_files from scripts/lib/llm-gates.sh. The serve seam is unchanged and still green (its podman stub now models `podman image exists`, the primitive the gate uses). Behaviour preserved: every refusal names the artifact, the endpoint and the fetch script.
This commit is contained in:
1 parent
acf2a5cee5
commit
028afdeb58
4 files changed
+50
-42
No files matched your search
@@ -69,12 +69,14 @@ require_min_mib() {
|
|||||||
|
|
||||||
# require_dir_files — DIR must be a directory and every named member a
|
# require_dir_files — DIR must be a directory and every named member a
|
||||||
# regular file (the flat-tokenizer law: snapshot symlinks dangle in the
|
# regular file (the flat-tokenizer law: snapshot symlinks dangle in the
|
||||||
# container mount even when the directory itself is real).
|
# container mount even when the directory itself is real. A symlinked
|
||||||
|
# DIRECTORY is refused too: a HF cache snapshot can link the directory
|
||||||
|
# itself, and a link dangles inside the container mount.
|
||||||
require_dir_files() {
|
require_dir_files() {
|
||||||
local dir="$1" label="$2" hint="$3"
|
local dir="$1" label="$2" hint="$3"
|
||||||
shift 3
|
shift 3
|
||||||
local f
|
local f
|
||||||
if [[ ! -d "$dir" ]]; then
|
if [[ -L "$dir" || ! -d "$dir" ]]; then
|
||||||
_gate_fail "$label: missing at $dir" "$hint"
|
_gate_fail "$label: missing at $dir" "$hint"
|
||||||
fi
|
fi
|
||||||
for f in "$@"; do
|
for f in "$@"; do
|
||||||
|
|||||||
@@ -29,7 +29,14 @@
|
|||||||
# exactly like frank-archon's.
|
# exactly like frank-archon's.
|
||||||
set -euo pipefail
|
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)"
|
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}"
|
ENV_FILE="${FRANK_DRAGOON_ENV_FILE:-$SCRIPT_DIR/frank-dragoon.env}"
|
||||||
if [ ! -f "$ENV_FILE" ]; then
|
if [ ! -f "$ENV_FILE" ]; then
|
||||||
echo "frank-dragoon: env file not found: $ENV_FILE" >&2
|
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_KV_SLOTS:?HALOGEN_KV_SLOTS must be set}"
|
||||||
: "${HALOGEN_SPEC_ADAPT:?HALOGEN_SPEC_ADAPT must be set (0 = head always on)}"
|
: "${HALOGEN_SPEC_ADAPT:?HALOGEN_SPEC_ADAPT must be set (0 = head always on)}"
|
||||||
|
|
||||||
# Capture podman's output BEFORE grepping: under `set -o pipefail` the
|
# The image gate is the digest-pinned ref itself: `podman image exists` takes
|
||||||
# `podman images | grep -q` pattern races (grep exits on first match,
|
# a name@sha256 ref in one command, so a retagged look-alike or a newer
|
||||||
# podman dies SIGPIPE 141, pipefail turns the 141 into "no match" — the
|
# latest cannot satisfy it and there is no pipefail/SIGPIPE race with grep
|
||||||
# 2026-09-30 deploy refusal).
|
# (the 2026-09-30 deploy refusal). Runs as dank: the unit's process uses
|
||||||
DIGESTS="$(podman images --format '{{.RepoDigests}}')"
|
# dank's rootless storage.
|
||||||
if ! grep -qF "${FRANK_DRAGOON_IMAGE#*@}" <<<"$DIGESTS"; then
|
require_image "$FRANK_DRAGOON_IMAGE" \
|
||||||
echo "frank-dragoon: image $FRANK_DRAGOON_IMAGE not in podman storage;" >&2
|
"pull it: podman pull docker://$FRANK_DRAGOON_IMAGE" || exit 1
|
||||||
echo " pull it: podman pull ${FRANK_DRAGOON_IMAGE%@*}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Size-exact gates on all three payload files: a truncated curl exits 0 on
|
# 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
|
# 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).
|
# byte counts from the Hub tree API are the contract (fleet fetch law).
|
||||||
ck() {
|
require_bytes "$FRANK_DRAGOON_MODEL_DIR/$FRANK_DRAGOON_CHECKPOINT" \
|
||||||
# ck <env-name> <path> <expected-bytes>
|
checkpoint "$FRANK_DRAGOON_CHECKPOINT_BYTES" \
|
||||||
if [ ! -f "$2" ]; then
|
"fetch it: bash scripts/download-frank-dragoon-flashnext.sh" || exit 1
|
||||||
echo "frank-dragoon: $1 $2 missing;" >&2
|
require_bytes "$FRANK_DRAGOON_MODEL_DIR/$FRANK_DRAGOON_NGRAM_TABLE" \
|
||||||
echo " fetch it: bash scripts/download-frank-dragoon-flashnext.sh" >&2
|
n-gram-table "$FRANK_DRAGOON_NGRAM_BYTES" \
|
||||||
exit 1
|
"fetch it: bash scripts/download-frank-dragoon-flashnext.sh" || exit 1
|
||||||
fi
|
require_bytes "$FRANK_DRAGOON_MODEL_DIR/$FRANK_DRAGOON_VISION_TOWER" \
|
||||||
local sz
|
vision-tower "$FRANK_DRAGOON_VISION_BYTES" \
|
||||||
sz="$(stat -c %s "$2")"
|
"fetch it: bash scripts/download-frank-dragoon-flashnext.sh" || exit 1
|
||||||
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"
|
|
||||||
|
|
||||||
# The payload ships the tokenizer as a FLAT DIRECTORY. A HuggingFace cache
|
# The payload ships the tokenizer as a FLAT DIRECTORY. A HuggingFace cache
|
||||||
# snapshot symlinks each file into a sibling blobs/, and those symlinks
|
# snapshot symlinks each file into a sibling blobs/, and those symlinks
|
||||||
# dangle inside the container mount — so the directory must be real and
|
# dangle inside the container mount — so the directory must be real and
|
||||||
# every named file must be a regular file, not a link.
|
# every named file must be a regular file, not a link.
|
||||||
TOK_DIR="$FRANK_DRAGOON_MODEL_DIR/tokenizer"
|
# The tokenizer file list is word-separated ON PURPOSE (one argument per
|
||||||
if [ -L "$TOK_DIR" ] || [ ! -d "$TOK_DIR" ]; then
|
# file), so the unquoted expansion below is the interface, not a slip.
|
||||||
echo "frank-dragoon: tokenizer dir $TOK_DIR missing or a symlink (HF cache snapshot?);" >&2
|
# shellcheck disable=SC2086
|
||||||
echo " fetch it: bash scripts/download-frank-dragoon-flashnext.sh" >&2
|
require_dir_files "$FRANK_DRAGOON_MODEL_DIR/tokenizer" tokenizer \
|
||||||
exit 1
|
"fetch it: bash scripts/download-frank-dragoon-flashnext.sh" \
|
||||||
fi
|
$FRANK_DRAGOON_TOKENIZER_FILES || exit 1
|
||||||
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
|
|
||||||
|
|
||||||
# GPU access is mandatory but the node paths are variables so the seam can
|
# GPU access is mandatory but the node paths are variables so the seam can
|
||||||
# drive the gate on a box without an AMD GPU.
|
# 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
|
echo "frank-dragoon: $DEV_KFD or $DEV_DRI missing — no gfx1151 to serve on" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Host-RAM preflight — the engine's pin gate, checked BEFORE podman run.
|
# Host-RAM preflight — the engine's pin gate, checked BEFORE podman run.
|
||||||
# The engine refuses to pin when MemAvailable is below the checkpoint plus
|
# 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
|
# a 16 GiB floor: pinned pages cannot be swapped, and under the floor the
|
||||||
|
|||||||
@@ -45,6 +45,13 @@ podman_stub() {
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
case "$1" in
|
case "$1" in
|
||||||
images) printf '%s\n' "${STUB_DIGESTS:-}" ;;
|
images) printf '%s\n' "${STUB_DIGESTS:-}" ;;
|
||||||
|
image)
|
||||||
|
# `podman image exists REF` — exit 1 when storage lacks the ref,
|
||||||
|
# which is exactly the primitive the shared gate library calls.
|
||||||
|
case " ${STUB_DIGESTS:-} " in
|
||||||
|
*" $3 "*) exit 0 ;;
|
||||||
|
*) exit 1 ;;
|
||||||
|
esac ;;
|
||||||
rm) exit 0 ;;
|
rm) exit 0 ;;
|
||||||
run|wait) echo "PODMAN $*"; exit "${STUB_WAIT_RC:-0}" ;;
|
run|wait) echo "PODMAN $*"; exit "${STUB_WAIT_RC:-0}" ;;
|
||||||
*) exit 0 ;;
|
*) exit 0 ;;
|
||||||
|
|||||||
@@ -130,6 +130,15 @@ call() {
|
|||||||
[[ "$output" == *"tokenizer: missing"* ]]
|
[[ "$output" == *"tokenizer: missing"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "the flat-directory gate refuses a symlinked directory" {
|
||||||
|
# The snapshot directory itself can be the link (a HF cache blobs/
|
||||||
|
# sibling), and a link into the container mount dangles there too.
|
||||||
|
ln -s "$TMP/flat" "$TMP/linked-dir"
|
||||||
|
call "require_dir_files '$TMP/linked-dir' tokenizer 'fetch the tokenizer' tokenizer.json"
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
[[ "$output" == *"tokenizer: missing"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
@test "the image gate refuses a ref the storage does not hold" {
|
@test "the image gate refuses a ref the storage does not hold" {
|
||||||
# A name@sha256 ref exists only if THAT manifest was pulled, so the
|
# A name@sha256 ref exists only if THAT manifest was pulled, so the
|
||||||
# gate is the ref itself — one command, no grep race.
|
# gate is the ref itself — one command, no grep race.
|
||||||
|
|||||||
Reference in new issue
Block a user