Files
serverAppConfigs/tests/frank_medium_unit.bats
T
Frank e9a01cd013 frank-medium: swap to ParoQuant MXFP6 (Swift Qwen3.8-27B) + Qwen-Sharp v22.5.0
- Model: hugypufy/Swift-Qwen3.8-27B-PARO-MXFP6 (25.16 GiB, 18 shards,
  quant_method=paroquant_mxfp6, bits=6, group_size=128, krot=8, rotation on)
- Image: ggz14/vllm-radiance-mxfp4:0.13.0-e060895 (PR #51 launch6_p kernel,
  built on the published 0.9.3 base; docker->podman via save/load)
- GPU_UTIL 0.98 -> 0.99 (user request); KV_DTYPE=auto (unquantized bf16)
- Entrypoint: mxfp6 branch (17 patches + paroquant kernel compile +
  sitecustomize registration); GPU_UTIL default for set -u robustness
- Quadlet: SNAP/GPU_UTIL/Image/cache updated; new mxfp6 cache dir
- deploy-remote.sh: R4D gate + paroquant_mxfp6; new image/SNAP/cache defaults
- Chat template: Qwen-Sharp v22.5.0 (peculiar-ragdoll); v22.4.0 kept as rollback
- Test 168: dflash branch comment quant-aware (mxfp4 + paroquant_mxfp6)
- Runbook: MXFP6 active shape, MXFP4 rollback

MXFP4 checkpoint remains on disk as the rollback target.
2026-09-21 22:46:44 -07:00

220 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bats
# Seam: the frank-medium vllm-radiance container on dankserver.
# frank-medium became a CONTAINER again on 2026-09-20, when the board swap put
# both R9700s on CPU root ports and a measured three-arm comparison retired the
# mainline-llama.cpp layer split (scripts/.AGENTTRASH/20260920-llama-layersplit/).
# What this seam pins is what the flags alone do not say: boot persistence for a
# GENERATED unit (this host's v1 quadlet generator cannot enable units, so the
# [Install] section is the only thing that survives a reboot), that BOTH cards
# work on ONE model through tensor parallelism — the thing the pre-2026-09-20
# hardware could not do — the KV pin's coupling to the batch shape it was
# calibrated at, and that the pool still holds one full-window request.
load helpers
UNIT="$REPO_ROOT/dankserver/frank-medium/quadlet/frank-medium.container"
ENTRY="$REPO_ROOT/dankserver/frank-medium/radiance-entrypoint.sh"
HEALTH="$REPO_ROOT/dankserver/frank-medium/frank-medium-healthcheck.py"
REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
LLAMA_TRASH="$REPO_ROOT/scripts/.AGENTTRASH/20260920-llama-layersplit"
REPLICA_TRASH="$REPO_ROOT/dankserver/.AGENTTRASH/20260916-vllm-radiance"
# Measured constants, used by the fit arithmetic.
CARD_BYTES=34208743424 # one R9700 as rocm-smi reports it (32624 MiB)
KV_BYTES_PER_TOKEN=70656 # --kv-cache-memory 18563072000 => "262,720 tokens"
env_value() {
sed -nE "s/^Environment=$1=(-?[0-9]+)$/\1/p" "$UNIT" | head -n1
}
# GPU_UTIL is a fraction, not a count; the integer reader would drop it.
env_ratio() {
sed -nE "s/^Environment=$1=([0-9.]+)$/\1/p" "$UNIT" | head -n1
}
# SPEC_METHOD is a string (dflash|mtp), not a count; the integer reader drops it.
env_str() {
sed -nE "s/^Environment=$1=(.+)$/\1/p" "$UNIT" | head -n1
}
@test "frank-medium's unit, entrypoint and healthcheck exist" {
[ -f "$UNIT" ]
[ -x "$ENTRY" ]
[ -f "$HEALTH" ]
}
@test "frank-medium is boot-persistent and self-healing" {
# Generated units cannot be enabled on this host, so WantedBy in the file is
# the whole boot story (AGENTS.md: Boot Persistence & Self-Healing).
grep -q '^WantedBy=default.target$' "$UNIT"
grep -q '^Restart=on-failure$' "$UNIT"
grep -q '^RestartSec=' "$UNIT"
# First start compiles Triton/inductor kernels before the engine loads ~9.5
# GiB per card; a shorter timeout kills a healthy boot.
grep -q '^TimeoutStartSec=1800$' "$UNIT"
grep -q '^HealthCmd=python3 /usr/local/bin/frank-medium-healthcheck.py$' "$UNIT"
grep -q '^HealthStartPeriod=1800s$' "$UNIT"
# Boots must not depend on the registry.
grep -q '^Pull=never$' "$UNIT"
}
@test "both R9700s serve ONE model as one tensor-parallel process" {
grep -q '^Environment=TP=2$' "$UNIT"
grep -q '^Environment=ROCR_VISIBLE_DEVICES=0,1$' "$UNIT"
grep -q '^Environment=HIP_VISIBLE_DEVICES=0,1$' "$UNIT"
grep -q -- '--tensor-parallel-size "$TP"' "$ENTRY"
# The iGPU (index 2, 2 GiB) must stay out of the visible set: a third rank
# would take layers onto system RAM.
! grep -q '^Environment=.*VISIBLE_DEVICES=0,1,2' "$UNIT"
# TP needs the all-reduce to actually run. R4D is the collective-free path
# this host measured; the entrypoint must still select it, and the RCCL
# fallback must not be re-disabled by the pre-board-swap workarounds.
grep -q 'RADIANCE_USE_R4D_AR' "$ENTRY"
grep -q -- '--attention-backend "$ATTN"' "$ENTRY"
! grep -q 'NCCL_P2P_DISABLE' "$UNIT"
! grep -q 'RCCL_MSCCL_ENABLE' "$UNIT"
}
@test "HIP never reads one of these tunables as its own setting" {
# HIP reads bare TEMP, HOST and GPU_MAX_HEAP_SIZE (2026-08-13 incident:
# TEMP=1.0 sent the JIT cache to path "1.0" and segfaulted every load).
# Serve scripts export env files with `set -a`; a container inherits the
# same hazard from Environment=.
! grep -qE '^Environment=(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$UNIT"
}
@test "the speculative drafter matches the serving shape" {
local spec_method spec maxseqs chunk
spec_method="$(env_str SPEC_METHOD)"
spec="$(env_value SPEC)"
maxseqs="$(env_value MAXSEQS)"
chunk="$(env_value CHUNK)"
[ -n "$spec_method" ] && [ -n "$spec" ] && [ -n "$maxseqs" ] && [ -n "$chunk" ]
# The chunk is the AR size gate's input (CHUNK x hidden x 2 bytes); a chunk
# larger than this row's silently changes which all-reduce path runs.
[ "$chunk" -le 8192 ]
case "$spec_method" in
dflash)
# DFlash2 drafts at most 7 tokens per step; deeper is a different
# drafter's contract and costs acceptance, not just VRAM.
[ "$spec" -eq 7 ]
# The MXFP4 / ParoQuant-MXFP6 decode kernel band covers MAXSEQS x
# (SPEC+1) rows, and the shipped default covers 64 (the mxfp6 profile
# block exports the same RADIANCE_MXFP4_DECODE_MAX_M=64). Exceeding it
# without the entrypoint's >8 bump is a launch failure at the worst
# moment (first concurrent request).
[ $(( maxseqs * (spec + 1) )) -le 64 ]
grep -q 'RADIANCE_MXFP4_DECODE_MAX_M' "$ENTRY"
;;
mtp)
# MTP self-speculation at depth 8 (the documented plain-FP8 path; the
# model's own single-layer MTP head, applied autoregressively).
[ "$spec" -eq 8 ]
;;
*)
false "unknown SPEC_METHOD: $spec_method"
;;
esac
}
@test "the KV pool holds a full window and fits beside the weights" {
local kv_mem maxseqs chunk maxlen spec kv_dtype
kv_mem="$(env_value KV_MEM)"
maxseqs="$(env_value MAXSEQS)"
chunk="$(env_value CHUNK)"
maxlen="$(env_value MAXLEN)"
spec="$(env_value SPEC)"
kv_dtype="$(env_str KV_DTYPE)"
[ -n "$kv_mem" ] && [ -n "$maxseqs" ] && [ -n "$chunk" ] && [ -n "$maxlen" ]
# KV_MEM=0 = profile the shape (the documented plain-FP8 path's default;
# the upstream docker-compose leaves the pin commented out). The pool is
# auto-sized to fit, so there is no pin to verify — only that the shape
# is set and the model's weights fit beside an auto-sized pool.
if [ "$kv_mem" -eq 0 ]; then
# MXFP4 + unquantized (KV_DTYPE=auto -> bf16) KV: MXFP4's freed weights
# (19.37 vs 30.9 GB) buy the pool back, so the full 262,144 native window
# still fits clean (~290k f16 pool); vLLM boots or the pool is too small.
[ "$maxseqs" -eq 8 ] && [ "$chunk" -eq 8192 ] && [ "$maxlen" -eq 262144 ]
[ "$kv_dtype" = "auto" ]
return 0
fi
# Pinned pool: the pin must hold at least ONE full-window request or the
# endpoint refuses its own --max-model-len, and it must fit on one card
# beside the weights.
grep -q 'kv-profiles.tsv' "$UNIT"
[ $(( kv_mem / KV_BYTES_PER_TOKEN )) -ge "$maxlen" ]
# Weights per card: MXFP4 ~9.5 GiB (19 GB / 2), FP8 ~15.5 GiB (30.9 GB / 2).
local weights util util_pct floor
case "$(env_str SPEC_METHOD)" in
dflash) weights=10200547328 ;;
*) weights=16646128640 ;;
esac
util="$(env_ratio GPU_UTIL)"
[ -n "$util" ]
util_pct="${util#0.}"
floor=$(( 1024 * 1024 * 1024 )) # 1 GiB per card
[ $(( weights + kv_mem / 2 )) -le $(( CARD_BYTES * util_pct / 100 - floor )) ]
}
@test "the fleet endpoint contract survives the runtime swap" {
grep -q '^Environment=PORT=7770$' "$UNIT"
grep -q '^Environment=SERVED_NAMES=frank-medium$' "$UNIT"
grep -q -- '--port "$PORT"' "$ENTRY"
grep -q -- '--served-model-name $SERVED_NAMES' "$ENTRY"
# Host network: vLLM binds the port itself, so no PublishPort (and no
# pasta bind, which is what made the retired proxy's port replacement
# fail at container create).
grep -q -- '--network=host' "$UNIT"
! grep -q '^PublishPort=' "$UNIT"
# Tools and reasoning are part of the harness contract; both parsers must
# be selected, and thinking must land in its own field.
grep -q -- '--enable-auto-tool-choice' "$ENTRY"
grep -q -- '--tool-call-parser qwen3_coder' "$ENTRY"
grep -q -- '--reasoning-parser qwen3' "$ENTRY"
grep -q -- '--enable-prefix-caching' "$ENTRY"
# Requests that send no sampler fields get the predecessor's defaults.
grep -q '"temperature":0.7' "$ENTRY"
grep -q '"top_p":0.95' "$ENTRY"
}
@test "the healthcheck proves the engine serves, not merely that a process lives" {
grep -q '"/health"' "$HEALTH" || grep -q '/health' "$HEALTH"
grep -q 'status == 200' "$HEALTH"
# PORT-aware, and it must not hard-code a replica port.
grep -q 'os.environ.get("PORT"' "$HEALTH"
! grep -qE '7771|7772' "$HEALTH"
}
@test "the deploy handler gates on host assets and installs the quadlet" {
grep -q 'deploy_llm_medium' "$REMOTE_DEPLOY"
grep -q 'install_quadlet frank-medium dankserver/frank-medium/quadlet frank-medium' "$REMOTE_DEPLOY"
grep -q 'wait_container_healthy frank-medium' "$REMOTE_DEPLOY"
grep -q 'wait_port_free 7770' "$REMOTE_DEPLOY"
grep -q '\[\[ "$missing" -eq 0 \]\] || return 1' "$REMOTE_DEPLOY"
# The image is only pulled when absent, and re-tagged to the name the unit
# actually runs (the quadlet path cannot resolve registry refs at create).
grep -q 'podman image exists "$LLM_MEDIUM_IMAGE_LOCAL"' "$REMOTE_DEPLOY"
grep -q 'podman tag "$LLM_MEDIUM_IMAGE" "$LLM_MEDIUM_IMAGE_LOCAL"' "$REMOTE_DEPLOY"
# The patched libr4d is not optional (the image's own build NaNs this model).
grep -q 'patched libr4d absent' "$REMOTE_DEPLOY"
}
@test "a generated unit is never asked to enable, and the llama unit is gone" {
# podman 5.x v1 generator: generated units cannot be enabled; asking makes
# the deploy fail on a unit that is already boot-persistent via [Install].
! grep -q 'systemctl --user enable frank-medium' "$REMOTE_DEPLOY"
# ...and the root llama unit must be retired, not relinked: relinking would
# resurrect a service whose repo file no longer exists.
! grep -q 'relink_unit frank-medium' "$REMOTE_DEPLOY"
grep -q 'retire_llama_medium_unit' "$REMOTE_DEPLOY"
}
@test "the llama layer split and the replica trio are archived, not deleted" {
[ -f "$LLAMA_TRASH/frank-medium.service.llamacpp-layersplit" ]
[ -f "$LLAMA_TRASH/frank-medium.env.llamacpp-layersplit" ]
[ -f "$LLAMA_TRASH/serve-frank-medium.sh.llamacpp-layersplit" ]
[ ! -f "$REPO_ROOT/scripts/frank-medium.env" ]
[ -f "$REPLICA_TRASH/frank-medium-a.container" ]
[ -f "$REPLICA_TRASH/frank-medium-proxy.container" ]
# The replicas stay retired: the handler stops them but never installs them.
grep -q 'systemctl --user stop frank-medium-a.service frank-medium-b.service' "$REMOTE_DEPLOY"
! grep -q 'install_quadlet frank-medium-a' "$REMOTE_DEPLOY"
}