Files
serverAppConfigs/tests/frank_primary_unit.bats
T
Frank ecaad36654 frank-primary: vLLM 0.30 UVA-offload shape (pre-deploy commit)
The llama.cpp b11030 trio becomes the vLLM 0.30 trio ahead of the
FP8 cutover on :7770 — same port, same alias, no client moves.

Serve shape: locally built ROCm gfx1201 image
(localhost/frank-primary:vllm030) execs via podman as dank from the
root unit (LimitMEMLOCK=infinity legalizes the pin). UVA offload:
--offload-backend uva pins the PLE family (47.75 GiB, 138 tensors:
131 n-gram table shards + 7 per-layer conv/proj) + routed experts
(114.86 GiB, 150,528 tensors) in host RAM — 162.60 GiB combined,
~81.3 GiB per rank under TP=2, hence --cpu-offload-gb 82 (80/card
strands 2.6 GiB of experts on the cards). Parsers per the host's
own Ornith-era precedent: qwen3_xml tools + qwen3 reasoning (vLLM
0.30 emits the channel as `reasoning` — reasoning_content is
renamed server-side) + enable_thinking/preserve_thinking template
kwargs. Spec decode stays env-wired and OFF at bring-up.

Deployer deploy_llm_primary rewritten: image gate, index-driven
shard-count gate (131), stale-container cleanup, port wait, and the
false-green hardening — health is only green when the unit's own
MainPID is live behind the answer (2026-09-26 lesson).

Seam tests rewritten to the vLLM contract (15 gates): offload
arithmetic, parser wiring, no-llama.cpp-ism, deployer gates. Suite
349/349 green.
2026-09-27 00:16:42 -07:00

181 lines
8.9 KiB
Bash
Executable File

#!/usr/bin/env bats
# Seam: frank-primary — the Dankserver LLM endpoint on :7770.
#
# This file asserts the vLLM 0.30 ROOT-UNIT shape (the 2026-09-27 swap that
# replaced the llama.cpp b11030 endpoint): a locally built ROCm gfx1201
# image (localhost/frank-primary:vllm030) serving Qwen3.8-Flash-Next-FP8
# (Qwen4ExpForConditionalGeneration, 131 shards, 172.78 GiB) on the two
# R9700s with the UVA offload shape. The seam pins what the flags alone do
# not say: the offload budget arithmetic (the bandwidth-huge tensors — the
# 47.75 GiB PLE family and the 114.86 GiB routed experts — are pinned in
# host RAM and read in place, never streamed), the memlock law that makes
# the pin legal, the parser contract that keeps the harness wire formats
# (tool_calls + the reasoning channel) working, HIP env hygiene, and the
# deployer's image/payload gates with the false-green MainPID check.
load helpers
UNIT="$REPO_ROOT/scripts/frank-primary.service"
SERVE="$REPO_ROOT/scripts/serve-frank-primary.sh"
ENV_FILE="$REPO_ROOT/scripts/frank-primary.env"
REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
@test "frank-primary unit, serve script and env exist in scripts/" {
[ -f "$UNIT" ]
[ -x "$SERVE" ]
[ -f "$ENV_FILE" ]
}
@test "frank-primary is boot-persistent, self-healing and unprivileged" {
# Root unit parity with frank-large/frank-small: enable is the [Install]
# section, the restart policy survives a crash, and the ~164 GiB UVA pin
# needs an unbounded memlock (a root unit grants it via PID1; the dank
# session's own hard limit is 8 MiB and cannot be raised without
# restarting user@1000.service).
grep -q '^WantedBy=multi-user.target$' "$UNIT"
grep -q '^Restart=on-failure$' "$UNIT"
grep -q '^RestartSec=' "$UNIT"
grep -q '^User=dank$' "$UNIT"
grep -q '^LimitMEMLOCK=infinity$' "$UNIT"
}
@test "frank-primary runs the repo's own serve script with the repo's env" {
grep -q '^ExecStart=/home/dank/serverAppConfigs/scripts/serve-frank-primary.sh$' "$UNIT"
grep -q 'FRANK_PRIMARY_ENV_FILE=/home/dank/serverAppConfigs/scripts/frank-primary.env' "$UNIT"
}
@test "the serve shape is vLLM: no llama.cpp-ism survives the cutover" {
# Clean cutover: the GGUF era's flags would silently no-op or crash a vLLM
# server, so their absence is part of the contract.
! grep -q -- '--model-draft' "$SERVE"
! grep -q -- '--spec-type' "$SERVE"
! grep -q -- '--mmproj' "$SERVE"
! grep -q -- '--override-tensor' "$SERVE"
! grep -q '\.gguf' "$SERVE"
! grep -q '\.gguf' "$ENV_FILE"
}
@test "the bandwidth-huge tensors are pinned in host RAM, not streamed" {
# The UVA shape (the x4-pair law: streaming experts over PCIe loses,
# computing them in host RAM does not care): --offload-backend uva pins
# the matched weights with zero-copy reads. Measured from the payload
# headers (2026-09-26): `ple` matches 138 tensors (131 n-gram table
# shards + 7 per-layer conv/proj weights) = 47.75 GiB; `experts` matches
# 150,528 tensors = 114.86 GiB; combined 162.60 GiB. cpu-offload-gb is
# PER GPU: under TP=2 each rank needs ~81.3 GiB, hence 82 — 80/card
# would strand 2.6 GiB of experts on the cards.
grep -q '^OFFLOAD_BACKEND=uva$' "$ENV_FILE"
grep -q '^OFFLOAD_GB=82$' "$ENV_FILE"
grep -q '^OFFLOAD_PARAMS=ple,experts$' "$ENV_FILE"
grep -q -- '--offload-backend "$OFFLOAD_BACKEND"' "$SERVE"
grep -q -- '--cpu-offload-gb "$OFFLOAD_GB"' "$SERVE"
grep -q -- '--cpu-offload-params "$OFFLOAD_PARAMS"' "$SERVE"
# auto/prefetch are the copy paths: this deployment is only qualified
# for uva, so the serve script refuses anything else.
grep -q 'only qualified for uva' "$SERVE"
}
@test "the engine shape: TP=2 over both R9700s, full native window, batch 8" {
grep -q '^TP=2$' "$ENV_FILE"
grep -q '^GPU_UTIL=0.97$' "$ENV_FILE"
grep -q '^CTX_SIZE=262144$' "$ENV_FILE"
grep -q '^MAX_SEQS=8$' "$ENV_FILE"
grep -q -- '--tensor-parallel-size "$TP"' "$SERVE"
grep -q -- '--gpu-memory-utilization "$GPU_UTIL"' "$SERVE"
grep -q -- '--max-model-len "$CTX_SIZE"' "$SERVE"
grep -q -- '--max-num-seqs "$MAX_SEQS"' "$SERVE"
}
@test "the payload is the FP8 tree, vision served from the model's own config" {
# Qwen4ExpForConditionalGeneration: vLLM auto-loads the ViT and the
# mtp.* weights from the payload config — no explicit projector flag
# exists in this runtime (the llama.cpp --mmproj trap does not apply).
grep -q '^MODEL_DIR=/models/Qwen3.8-Flash-Next-FP8$' "$ENV_FILE"
grep -q '^IMAGE=localhost/frank-primary:vllm030$' "$ENV_FILE"
}
@test "frank-primary holds the single fleet port under its own alias" {
grep -q '^PORT=7770$' "$ENV_FILE"
grep -q '^BIND_HOST=0.0.0.0$' "$ENV_FILE"
grep -q '^SERVED_NAME=frank-primary$' "$ENV_FILE"
grep -q -- '--served-model-name "$SERVED_NAME"' "$SERVE"
grep -q -- '--host "$BIND_HOST"' "$SERVE"
grep -q -- '--port "$PORT"' "$SERVE"
}
@test "tool_calls and the reasoning channel are wired for the harness" {
# The wire contract: vLLM 0.30 emits the thinking channel as `reasoning`
# (the deprecated `reasoning_content` is renamed server-side), the qwen3
# parser extracts the template's think block into it, and qwen3_xml +
# auto-tool-choice structure tool calls. Precedent: the host's own
# Ornith-era scripts (~/vllm/failedAttempts/OrnithFP8.sh); the FP8
# template consumes enable_thinking/preserve_thinking (verified in
# tokenizer_config.json).
grep -q '^TOOL_PARSER=qwen3_xml$' "$ENV_FILE"
grep -q '^REASONING_PARSER=qwen3$' "$ENV_FILE"
grep -q -- '--enable-auto-tool-choice --tool-call-parser "$TOOL_PARSER"' "$SERVE"
grep -q -- '--reasoning-parser "$REASONING_PARSER"' "$SERVE"
# The template kwargs are JSON: the env value must be single-quoted or
# bash splits it and CHAT_TEMPLATE_KWARGS arrives empty (the -ot lesson).
grep -q "^CHAT_TEMPLATE_KWARGS='{\"enable_thinking\": true, \"preserve_thinking\": true}'$" "$ENV_FILE"
grep -q -- '--default-chat-template-kwargs "$CHAT_TEMPLATE_KWARGS"' "$SERVE"
}
@test "speculative decode is env-wired and OFF at bring-up" {
# MTP drafting comes from the model's own mtp.* weights via
# --speculative-config; bring-up measures the base shape clean, so
# SPEC_CONFIG is empty and the flag is only added when non-empty.
grep -q '^SPEC_CONFIG=$' "$ENV_FILE"
grep -q -- '--speculative-config "$SPEC_CONFIG"' "$SERVE"
}
@test "both R9700s are visible and the Raphael iGPU is not" {
grep -q '^ROCR_VISIBLE_DEVICES=0,1$' "$ENV_FILE"
grep -q '^HIP_VISIBLE_DEVICES=0,1$' "$ENV_FILE"
grep -q -- '-e ROCR_VISIBLE_DEVICES="$ROCR_VISIBLE_DEVICES"' "$SERVE"
grep -q -- '-e HIP_VISIBLE_DEVICES="$HIP_VISIBLE_DEVICES"' "$SERVE"
}
@test "no bare HIP-reserved env var is defined" {
# HIP reads bare TEMP for its JIT cache path (2026-08-13: TEMP=1.0
# segfaulted every load), and HOST/GPU_MAX_HEAP_SIZE are the same
# class. The bind address is BIND_HOST, not HOST.
! grep -qE '^(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$ENV_FILE"
grep -q '^BIND_HOST=' "$ENV_FILE"
! grep -qE '^(export )?(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$SERVE"
}
@test "the serve script refuses to start on a half-finished payload or missing image" {
# A payload missing its wrapper config or shard index turns the
# container start into a guaranteed 20-minute timeout; the checks fail
# fast with a pointer at the fetch script instead.
grep -q 'config.json or index missing' "$SERVE"
grep -q 'download-frank-primary-fp8.sh' "$SERVE"
grep -q 'podman image inspect' "$SERVE"
}
@test "the deployer gates the image and the full shard set before starting" {
# The deployer refuses when the image or any index-named shard is
# absent (never serve a partial download), clears any stale container
# that would break `podman run --name`, and waits out the port before
# the new engine binds it.
grep -q 'LLM_PRIMARY_IMAGE=localhost/frank-primary:vllm030' "$REMOTE_DEPLOY"
grep -q 'LLM_PRIMARY_ROOT=/home/dank/models/Qwen3.8-Flash-Next-FP8' "$REMOTE_DEPLOY"
grep -q 'weight_map' "$REMOTE_DEPLOY"
grep -q 'download-frank-primary-fp8.sh' "$REMOTE_DEPLOY"
grep -q "podman rm -f \"\$LLM_PRIMARY_CONTAINER\"" "$REMOTE_DEPLOY"
grep -q 'wait_port_free 7770' "$REMOTE_DEPLOY"
grep -q 'relink_unit frank-primary' "$REMOTE_DEPLOY"
grep -q 'systemctl enable frank-primary.service' "$REMOTE_DEPLOY"
grep -q 'systemctl restart frank-primary.service' "$REMOTE_DEPLOY"
grep -q 'wait_for_health frank-primary http://127.0.0.1:7770/health 1800' "$REMOTE_DEPLOY"
}
@test "the deployer will not report green on a stray port-holder" {
# The 2026-09-26 false green: a leftover probe held :7770 and satisfied
# the health curl while the real unit was stopped. The deploy is only
# green when the unit's own MainPID is live behind the health answer.
grep -q 'systemctl show -p MainPID --value frank-primary.service' "$REMOTE_DEPLOY"
grep -q 'stray process holds the port' "$REMOTE_DEPLOY"
}