Files
serverAppConfigs/tests/frank_zealot_unit.bats
T
Frank 55f87fcdce llm: retire the trio service names — fleet services renamed to frank-archon/frank-dragoon/frank-zealot
The 2026-10-09 wire aliases become the service names everywhere:
systemd units, serve scripts, env files and FRANK_* variables,
deploy-plan/deploy-remote service keys, container names, Prometheus
job names + alert rules, Grafana dashboard queries, bats seams,
runbook filenames, homepage/AGENTS lore, and historical prose.
Rollback tags renamed in lockstep. Residue in the tree: zero.
Retired frank-medium/small/npu names are history and stay.

Known casualty: scripts/patches/*.patch context lines were rewritten;
those retired-era vLLM-fork patches no longer apply byte-exact.
2026-10-10 17:03:14 -07:00

286 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Seam: the frank-zealot llama.cpp root unit on dankclaw.
# frank-zealot is the RTX 3090 endpoint; since 2026-10-07 it serves
# HauhauCS's Qwen3.5-9B-Uncensored-HauhauCS-Aggressive (Q6_K, qwen35 hybrid:
# 32 layers, 8 full-attention + 24 Gated DeltaNet, no MTP tensors in the file)
# via the CUDA (sm_86) build of llama.cpp master: TWO 200,704-token
# conversations (--ctx-size 401408 --parallel 2), exact f16 K + f16 V under
# flash attention, the publisher's own BF16 vision projector, and MTP off —
# the file carries no nextn/mtp tensors, so there is no in-file head to draft
# with. Log verbosity 4 ships so the VRAM-fit forensics (KV layout, buffer
# sizes, graph capture) land in the journal.
# This seam runs the serve script against a stub engine and asserts on the
# argv it actually execs, so what is pinned is the command the operator gets.
load helpers
UNIT="$REPO_ROOT/scripts/frank-zealot.service"
SERVE="$REPO_ROOT/scripts/serve-frank-zealot.sh"
ENV_FILE="$REPO_ROOT/scripts/frank-zealot.env"
REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
ENV_LARGE="$REPO_ROOT/scripts/frank-dragoon.env"
setup() {
STAGE="$(mktemp -d)"
RUNTIME_DIR="$STAGE/llama"
MODEL_DIR="$STAGE/models"
mkdir -p "$RUNTIME_DIR/bin" "$MODEL_DIR"
cat > "$RUNTIME_DIR/bin/llama-server" <<'STUB'
#!/usr/bin/env bash
printf '%s\n' "$@"
STUB
chmod +x "$RUNTIME_DIR/bin/llama-server" "$RUNTIME_DIR/bin" 2>/dev/null || chmod +x "$RUNTIME_DIR/bin/llama-server"
# The stub payload files carry the SAME basenames as the shipped env, so
# the seam exercises the real paths, and a payload rename moves one file
# (the env) instead of silently forking the test's filename constants.
MODEL_STUB="$(basename "$(sed -nE 's/^MODEL_FILE=(.+)$/\1/p' "$ENV_FILE")")"
MM_STUB="$(basename "$(sed -nE 's/^MMPROJ_FILE=(.+)$/\1/p' "$ENV_FILE")")"
DRAFT_STUB="draft-assistant.gguf"
touch "$MODEL_DIR/$MODEL_STUB" "$MODEL_DIR/$MM_STUB" "$MODEL_DIR/$DRAFT_STUB"
}
teardown() {
rm -rf "$STAGE"
}
# serve_argv [env-file] — run the serve script and capture the engine's
# argv, one token per line, in $output.
serve_argv() {
local envf="${1:-$STAGE/test.env}"
cat > "$envf" <<EOF
LLAMA_BIN=$RUNTIME_DIR/bin/llama-server
MODEL_FILE=$MODEL_DIR/$MODEL_STUB
MMPROJ_FILE=$MODEL_DIR/$MM_STUB
ALIAS=frank-zealot
BIND_ADDR=0.0.0.0
LISTEN_PORT=7771
CTX_SIZE=$(sed -nE 's/^CTX_SIZE=([0-9]+)$/\1/p' "$ENV_FILE")
PARALLEL=$(sed -nE 's/^PARALLEL=([0-9]+)$/\1/p' "$ENV_FILE")
BATCH_SIZE=$(sed -nE 's/^BATCH_SIZE=([0-9]+)$/\1/p' "$ENV_FILE")
UBATCH_SIZE=$(sed -nE 's/^UBATCH_SIZE=([0-9]+)$/\1/p' "$ENV_FILE")
CACHE_TYPE_K=$(sed -nE 's/^CACHE_TYPE_K=(.+)$/\1/p' "$ENV_FILE")
CACHE_TYPE_V=$(sed -nE 's/^CACHE_TYPE_V=(.+)$/\1/p' "$ENV_FILE")
SPEC_TYPE=$(sed -nE 's/^SPEC_TYPE=(.+)$/\1/p' "$ENV_FILE")
SPEC_DRAFT_N_MAX=$(sed -nE 's/^SPEC_DRAFT_N_MAX=([0-9]+)$/\1/p' "$ENV_FILE")
VERBOSITY=$(sed -nE 's/^VERBOSITY=([0-9]+)$/\1/p' "$ENV_FILE")
NGL=$(sed -nE 's/^NGL=([0-9]+)$/\1/p' "$ENV_FILE")
EOF
run env FRANK_ZEALOT_ENV_FILE="$envf" nvidia-modprobe=true bash "$SERVE"
}
argv_value() {
grep -A1 -xF -- "$1" <<<"$output" | tail -1
}
# run_stage — exec the CURRENT $STAGE/test.env as-is. serve_argv REWRITES
# that file from the shipped env, so any test that mutates it (drop a line,
# append a drafter) must exec through this, not serve_argv — which silently
# undoes the mutation and makes the assertion a tautology.
run_stage() {
run env FRANK_ZEALOT_ENV_FILE="$STAGE/test.env" nvidia-modprobe=true bash "$SERVE"
}
@test "frank-zealot unit, serve script and env exist in scripts/" {
[ -f "$UNIT" ]
[ -f "$SERVE" ]
[ -f "$ENV_FILE" ]
}
@test "frank-zealot is boot-persistent, self-healing and unprivileged" {
grep -q '^WantedBy=multi-user.target$' "$UNIT"
grep -q '^Restart=' "$UNIT"
grep -q '^RestartSec=' "$UNIT"
grep -q '^User=dank$' "$UNIT"
grep -q '^LimitMEMLOCK=infinity$' "$UNIT"
grep -q '^ExecStart=/home/dank/serverAppConfigs/scripts/serve-frank-zealot.sh$' "$UNIT"
grep -q 'FRANK_ZEALOT_ENV_FILE=/home/dank/serverAppConfigs/scripts/frank-zealot.env' "$UNIT"
}
@test "the shipped env points at the llama.cpp runtime and the Qwen3.5-9B Q6_K GGUF" {
# The payload path IS the endpoint's identity.
grep -qE '^LLAMA_BIN=/home/dank/GitPackages/llama-master/build-cuda/bin/llama-server$' "$ENV_FILE"
grep -qE '^MODEL_FILE=/home/dank/models/Qwen35-9B-HauhauCS-Q6K/Qwen3\.5-9B-Uncensored-HauhauCS-Aggressive-Q6_K\.gguf$' "$ENV_FILE"
}
@test "the deployer gates on exactly the payload paths the env file ships" {
# Anti-drift: the deploy handler checks files by its own defaults. If
# those ever differ from what the unit actually loads, a green deploy
# would be gating on a file nobody reads — and a missing projector would
# ship a blind endpoint.
local env_v dep_v path pair
for pair in "MODEL_FILE LLM_ZEALOT_MODEL_FILE" "MMPROJ_FILE LLM_ZEALOT_MMPROJ"; do
env_v="${pair%% *}"
dep_v="${pair##* }"
path="$(sed -nE "s/^${env_v}=(.+)$/\1/p" "$ENV_FILE")"
grep -qF -- "${dep_v}=\"\${${dep_v}:-${path}}\"" "$REMOTE_DEPLOY"
done
}
@test "two conversations share the pool at a 200704 window each" {
serve_argv
[ "$status" -eq 0 ]
[ "$(argv_value --alias)" = "frank-zealot" ]
[ "$(argv_value --port)" = "7771" ]
[ "$(argv_value --host)" = "0.0.0.0" ]
[ "$(argv_value --model)" = "$MODEL_DIR/$MODEL_STUB" ]
[ "$(argv_value --n-gpu-layers)" -gt 0 ]
# llama.cpp divides --ctx-size across slots, so the observable contract
# is the per-slot window. Frank's 200k cap is a per-conversation cap, so
# the pair is 401,408 / 2 — and the harness contextWindow must quote
# 200704, not the pool and not the model's native 262,144.
[ "$(argv_value --parallel)" -eq 2 ]
[ "$(argv_value --ctx-size)" -eq 401408 ]
local pool slots
pool="$(argv_value --ctx-size)"; slots="$(argv_value --parallel)"
[ $((pool % slots)) -eq 0 ]
[ $((pool / slots)) -eq 200704 ]
}
@test "the KV contract is exact f16 K + f16 V, flash-attn on" {
serve_argv
[ "$status" -eq 0 ]
# Frank, 2026-10-07: the 9B rung funds unquantized KV (the 26B MoE era
# shipped q8_0 V because the card was full). qwen35 bills the pool on the
# 8 full-attention layers only (8 × 4 KV heads × 256 dim = 8,192
# elements/token ⇒ 32 KiB/token at f16/f16, 12.25 GiB at the 401,408
# pool); the 24 DeltaNet layers cost a fixed recurrent state per slot.
# The boot ledger, not this file, is the referee. flash-attn stays pinned:
# any quantized-V fallback (q8_0 = this build's FP8 class) requires it, and
# the build carries GGML_CUDA_FA_ALL_QUANTS=ON.
[ "$(argv_value --cache-type-k)" = "f16" ]
[ "$(argv_value --cache-type-v)" = "f16" ]
[ "$(argv_value --flash-attn)" = "on" ]
}
@test "log verbosity follows the env file" {
serve_argv
[ "$status" -eq 0 ]
# The shipped 4 (debug) is what makes VRAM-fit forensics possible from
# the journal (KV layout, buffer sizes, graph capture).
[ "$(argv_value --log-verbosity)" = "4" ]
# And an env that names no verbosity still ships the engine default
# explicitly — the script's written-down 3, never a blank.
sed -i '/^VERBOSITY=/d' "$STAGE/test.env"
run_stage
[ "$status" -eq 0 ]
[ "$(argv_value --log-verbosity)" = "3" ]
}
@test "the compute-buffer pair follows the env file and stays self-consistent" {
# The contract is: whatever rung the repo carries is the rung the engine
# is exec'd with — the number a benchmark attributes a prefill rate to
# must be a repo value, never an engine default nobody wrote down.
# Asserted against the env rather than a literal so a retune moves one
# file, not two.
local env_batch env_ubatch
env_batch="$(sed -nE 's/^BATCH_SIZE=([0-9]+)$/\1/p' "$ENV_FILE")"
env_ubatch="$(sed -nE 's/^UBATCH_SIZE=([0-9]+)$/\1/p' "$ENV_FILE")"
serve_argv
[ "$status" -eq 0 ]
[ "$(argv_value --batch-size)" = "$env_batch" ]
[ "$(argv_value --ubatch-size)" = "$env_ubatch" ]
# llama.cpp refuses ubatch > batch, so a retune that inverts the pair has
# to fail here instead of at boot on a live endpoint.
[ "$env_batch" -ge "$env_ubatch" ]
# And an env that names neither flag still ships an explicit pair: the
# serve script's fallbacks are written-down values, not blanks the engine
# fills in silently.
sed -i '/^BATCH_SIZE=/d;/^UBATCH_SIZE=/d' "$STAGE/test.env"
run_stage
[ "$status" -eq 0 ]
argv_value --batch-size | grep -qE '^[0-9]+$'
argv_value --ubatch-size | grep -qE '^[0-9]+$'
}
@test "speculation is off: no drafter flag reaches the argv" {
# Frank, 2026-10-07: MTP is off because it CANNOT be on — the Q6_K file
# carries no nextn/mtp tensors (verified from its tensor-name list), and
# MTP is a property of the file, not of the flags. The flags move as a
# set: SPEC_TYPE=none strips --spec-type, --spec-draft-n-max AND
# --spec-draft-model together, so a no-spec boot cannot carry half a
# spec pair (the ROCmFPX-era baseline arm segfaulted at load doing that).
serve_argv
[ "$status" -eq 0 ]
! grep -qxF -- '--spec-type' <<<"$output"
! grep -qxF -- '--spec-draft-n-max' <<<"$output"
! grep -qxF -- '--spec-draft-model' <<<"$output"
grep -qE '^SPEC_TYPE=none$' "$ENV_FILE"
}
@test "naming a drafter ships the full MTP trio" {
# The opt-back-in path (e.g. a future Qwen3.5 -mtp build): SPEC_TYPE +
# SPEC_DRAFT_FILE must reach the engine as a complete set — a draft-mtp
# flag without its model file boots a drafter against the main weights and
# drafts nothing.
serve_argv
{
echo 'SPEC_TYPE=draft-mtp'
echo 'SPEC_DRAFT_N_MAX=3'
echo "SPEC_DRAFT_FILE=$MODEL_DIR/$DRAFT_STUB"
} >> "$STAGE/test.env"
run_stage
[ "$(argv_value --spec-type)" = "draft-mtp" ]
[ "$(argv_value --spec-draft-n-max)" = "3" ]
[ "$(argv_value --spec-draft-model)" = "$MODEL_DIR/$DRAFT_STUB" ]
}
@test "vision ships by default and MMPROJ_FILE empty opts out of the image path" {
# Qwen3.5-9B is natively multimodal, but the Q6_K file carries no vision
# tensors — the publisher's projector argument IS the vision capability:
# no flag, no eyes.
serve_argv
[ "$status" -eq 0 ]
[ "$(argv_value --mmproj)" = "$MODEL_DIR/$MM_STUB" ]
# The documented opt-out must reach the engine as ABSENCE of the flag,
# not as an argument pointing at an empty string.
sed -i 's|^MMPROJ_FILE=.*|MMPROJ_FILE=|' "$STAGE/test.env"
run env FRANK_ZEALOT_ENV_FILE="$STAGE/test.env" nvidia-modprobe=true bash "$SERVE"
[ "$status" -eq 0 ]
! grep -qxF -- '--mmproj' <<<"$output"
}
@test "the fleet reasoning contract reaches the argv" {
serve_argv
[ "$status" -eq 0 ]
# deepseek format = thinking lands in reasoning_content, the field the
# harness reads on this endpoint; --jinja is what makes
# chat_template_kwargs reach the template at all.
[ "$(argv_value --reasoning-format)" = "deepseek" ]
argv_value --jinja >/dev/null
grep -qxF -- '--jinja' <<<"$output"
}
@test "the serve script refuses a missing runtime, GGUF or projector" {
rm -f "$MODEL_DIR/$MM_STUB"
serve_argv
[ "$status" -ne 0 ]
[[ "$output" == *"frank-zealot: missing"* ]]
rm -f "$RUNTIME_DIR/bin/llama-server"
serve_argv
[ "$status" -ne 0 ]
[[ "$output" == *"frank-zealot: missing"* ]]
}
@test "the deploy handler gates on binary, GGUF and projector, then MainPID-hardens health" {
grep -q 'deploy_llm_zealot' "$REMOTE_DEPLOY"
grep -q 'LLM_ZEALOT_RUNTIME' "$REMOTE_DEPLOY"
grep -q 'LLM_ZEALOT_MODEL_FILE' "$REMOTE_DEPLOY"
grep -q 'LLM_ZEALOT_MMPROJ' "$REMOTE_DEPLOY"
grep -q 'relink_unit frank-zealot' "$REMOTE_DEPLOY"
grep -q 'systemctl enable frank-zealot.service' "$REMOTE_DEPLOY"
grep -q '127.0.0.1:7771/health' "$REMOTE_DEPLOY"
grep -q 'frank-zealot.service has no MainPID' "$REMOTE_DEPLOY"
}
@test "the two endpoints keep separate ports and the large handler never pkills llama-server" {
# frank-dragoon's sweeps must not take down frank-zealot, whose
# process is llama-server on the SAME host.
grep -q '^LISTEN_PORT=7771$' "$ENV_FILE"
local large
large="$(sed -n '/^deploy_llm_dragoon() {/,/^}/p' "$REMOTE_DEPLOY")"
! grep -q 'pkill -x llama-server' <<<"$large"
}