Files
serverAppConfigs/tests/frank_large_unit.bats
T
Frank 7d3dd33de0 frank-large: swap to the uncensored Q6_0_ROCMFPX_AGENT payload
Frank's order: replace frank-large with
kingjones777/Qwen3.8-27B-Uncensored-ROCmFP4-STRIX-MTP-GGUF's
Qwen3.8-27B-Uncensored-Q6_0_ROCMFPX_AGENT.gguf, following that card.

The file is ftype 114 — 339 GGML_TYPE_Q6_0_ROCMFPX (102) + 159
GGML_TYPE_Q8_0_ROCMFPX (103) + 353 F32 over 851 tensors,
qwen35/64 layers/full_attention_interval 4/4 KV heads/262,144 window —
read out of its own header. The pinned ROCmFPX build already knows both
types (ggml.h 102/103 plus convert/cpy/getrows/mmq/fattn device paths
for 102), so no rebuild: the fork stays at 36e9acd and the payload
directory is the only new host state.

Card compliance, all already our flags: Q4_0 draft head (not Q8_0),
--spec-draft-n-max 4 (the knee on both cards), --spec-draft-ngl 99,
--jinja, --fit off, -fa on, -dio, one slot at full window, thinking with
the card's own sampling triple (which the file also embeds as its
general.sampling defaults). Kept from the deployed shape: the BF16
projector (this repo ships only a Q8_0 one; BF16 is the file verified
4/4 on spatial ground truth here) and the byte-identical MTP head, which
the new script hardlinks instead of re-fetching 1.6 GiB.

Franksdank-Runbook: serverAppConfigs-docs/LLM/frank-large-rocmfpx-q6agent-uncensored-mtp.md
2026-10-03 02:04:22 -07:00

282 lines
13 KiB
Bash
Executable File

#!/usr/bin/env bats
# Seam: frank-large serves kingjones777's UNCENSORED Qwen3.8-27B
# Q6_0_ROCMFPX_AGENT (a ftype-114 ROCmFPX file) with the repo's MTP draft head
# on the Strix Halo iGPU, via the ROCmFPX fork of llama.cpp — one session at
# the native 262,144 window.
#
# This seam exists because the format is the constraint: GGML_TYPE_Q6_0_ROCMFPX
# (tensor type 102) and GGML_TYPE_Q8_0_ROCMFPX (103) are understood by the
# ROCmFPX fork and by nothing else on this host, so the runtime, the payload
# and the deployer's gate have to agree
# on paths that no other endpoint uses. On 2026-10-01 a serve-script gate and
# its deployer twin disagreed during a cutover and the endpoint crash-looped;
# both sides are pinned here.
#
# Everything is tested from what an operator or a supervisor can observe — the
# command line, the exit code, the deployer's error text — never from anything
# inside llama.cpp.
setup() {
load helpers
SERVE="$REPO_ROOT/scripts/serve-frank-large.sh"
UNIT="$REPO_ROOT/scripts/frank-large.service"
ENV_FILE="$REPO_ROOT/scripts/frank-large.env"
DEPLOYER="$REPO_ROOT/scripts/deploy-remote.sh"
BUILD="$REPO_ROOT/scripts/build-rocmfpx.sh"
RUNTIME_STUB="/home/dank/GitPackages/kingjones-ROCmFPX/build/bin/llama-server"
MODEL_STUB="/home/dank/models/kingjones-Qwen3.8-27B-Uncensored-ROCmFP4-STRIX-MTP/Qwen3.8-27B-Uncensored-Q6_0_ROCMFPX_AGENT.gguf"
DRAFT_STUB="/home/dank/models/kingjones-Qwen3.8-27B-Uncensored-ROCmFP4-STRIX-MTP/mtp-Qwen3.8-27B-Q4_0.gguf"
MMPROJ_STUB="/home/dank/models/kingjones-Qwen3.8-27B-Uncensored-ROCmFP4-STRIX-MTP/mmproj-Qwen3.8-27B-BF16.gguf"
TEST_TMP="$(mktemp -d)"
SRC="$TEST_TMP/src"
mkdir -p "$SRC"
git -C "$SRC" init -q
git -C "$SRC" -c user.email=t -c user.name=t commit -q --allow-empty -m x
}
teardown() {
rm -rf "$TEST_TMP"
}
# make_stub — an executable that prints its whole argv on one line, so
# "what command line would the supervisor run" is observable output.
make_stub() {
cat > "$TEST_TMP/bin" <<'STUB'
#!/bin/sh
printf '%s\n' "$*"
STUB
chmod +x "$TEST_TMP/bin"
}
@test "the env file defines the deployment values" {
grep -qE '^LLAMA_BIN=/' "$ENV_FILE"
grep -qE '^MODEL_FILE=/' "$ENV_FILE"
grep -qE '^DRAFT_FILE=/' "$ENV_FILE"
grep -qE '^ALIAS=[a-z0-9-]+$' "$ENV_FILE"
grep -qE '^LISTEN_PORT=[0-9]+$' "$ENV_FILE"
grep -qE '^CTX_SIZE=[0-9]+$' "$ENV_FILE"
}
@test "the serve script fails when the runtime is missing" {
# Only the model, the head and the projector exist; the fork was never
# built. llama.cpp is not reached, so this fails fast and says what to
# run instead of failing at bind time.
touch "$TEST_TMP/model.gguf" "$TEST_TMP/draft.gguf" "$TEST_TMP/mmproj.gguf"
run env "LLAMA_BIN=$TEST_TMP/nope/llama-server" \
"MODEL_FILE=$TEST_TMP/model.gguf" "DRAFT_FILE=$TEST_TMP/draft.gguf" \
"MMPROJ_FILE=$TEST_TMP/mmproj.gguf" "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"build it with scripts/build-rocmfpx.sh"* ]]
}
@test "the serve script refuses to start without the model file" {
make_stub
touch "$TEST_TMP/draft.gguf" "$TEST_TMP/mmproj.gguf"
run env "LLAMA_BIN=$TEST_TMP/bin" "MODEL_FILE=$TEST_TMP/absent.gguf" \
"DRAFT_FILE=$TEST_TMP/draft.gguf" "MMPROJ_FILE=$TEST_TMP/mmproj.gguf" "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"is not readable"* ]]
[[ "$output" == *"download-frank-large-kingjones-uncensored.sh"* ]]
}
@test "the serve script refuses to start without the MTP draft head" {
make_stub
touch "$TEST_TMP/model.gguf" "$TEST_TMP/mmproj.gguf"
run env "LLAMA_BIN=$TEST_TMP/bin" "MODEL_FILE=$TEST_TMP/model.gguf" \
"DRAFT_FILE=$TEST_TMP/absent.gguf" "MMPROJ_FILE=$TEST_TMP/mmproj.gguf" "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"MTP draft head"* ]]
}
@test "the serve script refuses a missing mmproj unless vision is opted out" {
make_stub
touch "$TEST_TMP/model.gguf" "$TEST_TMP/draft.gguf"
run env "LLAMA_BIN=$TEST_TMP/bin" "MODEL_FILE=$TEST_TMP/model.gguf" \
"DRAFT_FILE=$TEST_TMP/draft.gguf" "MMPROJ_FILE=$TEST_TMP/absent.gguf" "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"set MMPROJ_FILE='' for text-only"* ]]
# The documented opt-out must actually start, without a --mmproj argument.
run env "LLAMA_BIN=$TEST_TMP/bin" "MODEL_FILE=$TEST_TMP/model.gguf" \
"DRAFT_FILE=$TEST_TMP/draft.gguf" "MMPROJ_FILE=" "$SERVE"
[ "$status" -eq 0 ]
[[ "$output" != *"--mmproj"* ]]
}
@test "the serve script fails when LLAMA_BIN is empty" {
touch "$TEST_TMP/model.gguf" "$TEST_TMP/draft.gguf" "$TEST_TMP/mmproj.gguf"
run env LLAMA_BIN= "MODEL_FILE=$TEST_TMP/model.gguf" \
"DRAFT_FILE=$TEST_TMP/draft.gguf" "MMPROJ_FILE=$TEST_TMP/mmproj.gguf" "$SERVE"
[ "$status" -ne 0 ]
[[ "$output" == *"LLAMA_BIN is empty"* ]]
}
@test "the shipped env points at the ROCmFPX runtime and the AGENT payload" {
grep -qE "^LLAMA_BIN=$RUNTIME_STUB$" "$ENV_FILE"
grep -qE "^MODEL_FILE=$MODEL_STUB$" "$ENV_FILE"
grep -qE "^DRAFT_FILE=$DRAFT_STUB$" "$ENV_FILE"
grep -qE "^MMPROJ_FILE=$MMPROJ_STUB$" "$ENV_FILE"
# HIP reads bare HOST/TEMP/GPU_MAX_HEAP_SIZE out of the environment that
# `set -a` creates; a tunable named HOST leaks into the runtime.
! grep -qE '^(HOST|TEMP|GPU_MAX_HEAP_SIZE)=' "$ENV_FILE"
}
@test "the argv contract keeps the fleet's port and alias" {
make_stub
touch "$TEST_TMP/model.gguf" "$TEST_TMP/draft.gguf" "$TEST_TMP/mmproj.gguf"
run env "LLAMA_BIN=$TEST_TMP/bin" "MODEL_FILE=$TEST_TMP/model.gguf" \
"DRAFT_FILE=$TEST_TMP/draft.gguf" "MMPROJ_FILE=$TEST_TMP/mmproj.gguf" \
ALIAS=frank-large BIND_ADDR=127.0.0.1 LISTEN_PORT=7770 "$SERVE"
[ "$status" -eq 0 ]
# The caller's values must survive the env file, or a single-run operator
# override silently does nothing.
[[ "$output" == *"--host 127.0.0.1"* ]]
[[ "$output" == *"--port 7770"* ]]
[[ "$output" == *"--alias frank-large"* ]]
# Vision is wired: the projector is the whole vision tower.
[[ "$output" == *"--mmproj $TEST_TMP/mmproj.gguf"* ]]
}
@test "the shipped argv is one full native window on one session" {
grep -qE '^CTX_SIZE=262144$' "$ENV_FILE"
grep -qE '^PARALLEL=1$' "$ENV_FILE"
# 262144 IS the model's trained window (qwen35.context_length in the GGUF),
# so 1 slot gets the whole thing — no truncation, no yarn scaling.
#
# Budget, from the GGUF header: 23.15 GiB of weights + 1.57 GiB draft head
# + 0.87 GiB projector + 16 full-attention layers x 4 kv heads x 256 dim x
# 2 (K+V) x 2 B = 64 KiB/token of f16 KV (~16 GiB at 262,144) + ~0.14 GiB
# of DeltaNet state + a couple GiB of compute buffers, against the iGPU's
# ~114 GiB GTT aperture. Change CTX_SIZE and this arithmetic, not a boot
# log, is what tells you it no longer fits.
ctx="$(sed -nE 's/^CTX_SIZE=([0-9]+)$/\1/p' "$ENV_FILE")"
kv_gib=$((ctx * 65536 / 1073741824))
total_gib=$((24 + 2 + 1 + kv_gib + 3))
[ "$total_gib" -le 114 ]
}
@test "the MTP draft contract matches the model card's measured optimum" {
make_stub
touch "$TEST_TMP/model.gguf" "$TEST_TMP/draft.gguf" "$TEST_TMP/mmproj.gguf"
run env "LLAMA_BIN=$TEST_TMP/bin" "MODEL_FILE=$TEST_TMP/model.gguf" \
"DRAFT_FILE=$TEST_TMP/draft.gguf" "MMPROJ_FILE=$TEST_TMP/mmproj.gguf" "$SERVE"
[ "$status" -eq 0 ]
# The head is the model's own nextn layer, loaded as a separate draft
# model because this checkpoint ships it standalone (no blk.<n>.nextn.*
# tensors in the target file).
[[ "$output" == *"--spec-type draft-mtp"* ]]
[[ "$output" == *"--model-draft $TEST_TMP/draft.gguf"* ]]
# n-max 4 is the measured optimum; n-max 8 measured WORSE (acceptance
# fell to 0.757 and the extra verification cost outran the savings), so
# the flag is a measurement, not a wish.
[[ "$output" == *"--spec-draft-n-max 4"* ]]
[[ "$output" == *"--spec-draft-n-min 0"* ]]
[[ "$output" == *"--spec-draft-p-min 0.0"* ]]
# The draft runs on the iGPU beside the target: no device-to-device hop
# on a link that measures single-digit GB/s.
[[ "$output" == *"--spec-draft-device ROCm0"* ]]
[[ "$output" == *"--spec-draft-ngl 99"* ]]
}
@test "KV and placement stay unquantized with flash attention on" {
make_stub
touch "$TEST_TMP/model.gguf" "$TEST_TMP/draft.gguf" "$TEST_TMP/mmproj.gguf"
run env "LLAMA_BIN=$TEST_TMP/bin" "MODEL_FILE=$TEST_TMP/model.gguf" \
"DRAFT_FILE=$TEST_TMP/draft.gguf" "MMPROJ_FILE=$TEST_TMP/mmproj.gguf" "$SERVE"
[ "$status" -eq 0 ]
[[ "$output" == *"--cache-type-k f16"* ]]
[[ "$output" == *"--cache-type-v f16"* ]]
# ROCmFPx quantized KV has no kernel on this branch, and the endpoint has
# the aperture to keep KV unquantized anyway.
[[ "$output" == *"--flash-attn on"* ]]
# --fit's placement heuristics do not know that 311 tensors of type 103
# belong on the iGPU, so the whole stack is placed explicitly instead.
[[ "$output" == *"--fit off"* ]]
[[ "$output" == *"--n-gpu-layers 999"* ]]
}
@test "the client contract wires thinking and template kwargs" {
make_stub
touch "$TEST_TMP/model.gguf" "$TEST_TMP/draft.gguf" "$TEST_TMP/mmproj.gguf"
run env "LLAMA_BIN=$TEST_TMP/bin" "MODEL_FILE=$TEST_TMP/model.gguf" \
"DRAFT_FILE=$TEST_TMP/draft.gguf" "MMPROJ_FILE=$TEST_TMP/mmproj.gguf" "$SERVE"
[ "$status" -eq 0 ]
# Without --jinja, llama.cpp silently ignores the harness's
# chat_template_kwargs, so thinking control disappears from the API.
[[ "$output" == *"--jinja"* ]]
# The harness reads `reasoning_content` for this endpoint, which is what
# the deepseek emission format produces (vLLM's `reasoning` is not it).
[[ "$output" == *"--reasoning-format deepseek"* ]]
# The template needs an unbounded thinking budget.
[[ "$output" == *"--reasoning-budget -1"* ]]
}
@test "the deployer gates exactly the paths the serve script opens" {
# A gate that checks a different file than the engine opens is the
# 2026-10-01 crash-loop: deploy passed, the unit restart-looped. Read the
# engine's own paths out of the env file and require the deployer's
# defaults to name the same ones.
for pair in "LLAMA_BIN:LLM_LARGE_RUNTIME" \
"MODEL_FILE:LLM_LARGE_MODEL" \
"DRAFT_FILE:LLM_LARGE_DRAFT" \
"MMPROJ_FILE:LLM_LARGE_MMPROJ"; do
env_key="${pair%%:*}"
dep_key="${pair##*:}"
want="$(sed -nE "s|^${env_key}=(.*)\$|\1|p" "$ENV_FILE")"
[ -n "$want" ]
got="$(grep -oE "${dep_key}:-[^}]+" "$DEPLOYER" | head -1 | sed "s|^${dep_key}:-||")"
[ "$got" = "$want" ]
done
}
@test "the deployer points a missing runtime at the fork build script" {
grep -qE 'build it: bash scripts/build-rocmfpx\.sh' "$DEPLOYER"
grep -qE 'fetch it: bash scripts/download-frank-large-kingjones-uncensored\.sh' "$DEPLOYER"
}
@test "the stray-process sweep cannot take frank-small down with frank-large" {
# frank-small serves a different GGUF over llama-server on the SAME host.
# A sweep keyed on the binary name (`pkill -f llama-server`) would kill
# the 3090 endpoint too, so it is keyed on frank-large's own payload file.
grep -q "pkill -f 'Qwen3.8-27B-Uncensored-Q6_0_ROCMFPX_AGENT'" "$DEPLOYER"
! grep -qE "pkill -f 'llama-server'" "$DEPLOYER"
}
@test "the build script refuses a source tree that is not the pinned commit" {
# The pin is load-bearing: the older builds of this fork line segfault in
# ggml_compute_forward_set_rows on this exact payload. A runtime that
# differs from what was qualified must be heard about at deploy time.
wrong="$(git -C "$SRC" rev-parse HEAD)"
run env ROCMFPX_SRC_DIR="$SRC" ROCMFPX_COMMIT="ffff$wrong" \
"$BUILD" --verify
[ "$status" -ne 0 ]
[[ "$output" == *"ROCMFPX_COMMIT"*mismatch* ]]
}
@test "the build script pins a real commit and the gfx1151 target" {
grep -qE 'ROCMFPX_COMMIT=.*[0-9a-f]{40}' "$BUILD"
grep -qE 'GPU_TARGET=.*gfx1151' "$BUILD"
grep -qE 'DGPU_TARGETS=' "$BUILD"
# The webui asset fetch half-failed on this box and aborted the link at
# 98%; the build must not depend on it.
grep -qE 'DLLAMA_BUILD_WEBUI=OFF' "$BUILD"
}
@test "the deployed unit keeps the fleet's restart and boot-persistence shape" {
grep -qE '^Restart=(always|on-failure)$' "$UNIT"
grep -qE '^WantedBy=' "$UNIT"
grep -qE '^ExecStart=.*/serve-frank-large\.sh$' "$UNIT"
grep -qE '^User=dank$' "$UNIT"
# The payload is 26 GiB of mmap'd weights plus pinned staging buffers; an
# RLIMIT smaller than the pinned set fails the load in a way that looks
# like an engine crash.
grep -qE '^LimitMEMLOCK=infinity$' "$UNIT"
}
@test "the unit points at no retired frank-large runtime path" {
# The host keeps every retired payload and build on disk, so a stale path
# in a unit still resolves — to a runtime nobody has measured in weeks.
! grep -qE 'GitPackages/(ROCmFPX-kairic|llama.cpp-ik_llama|llama-builds|ciru-orca)' "$UNIT"
! grep -q 'halogen' "$UNIT"
}