The arena is budget - non-expert - KV reserve - graphs - overhead. With util capped at 0.982 (never 1.00) the graph estimate is the only plan term this repo can shrink: the fork charges an analytic 256 MiB + 52 MiB/graph = 0.55 GiB where the engine's own post-capture line reports 0.31 GiB, so pricing it at 256 + 12/graph returns ~0.24 GiB to the expert arena. MNBT 1024 -> 2048 is arena-free: the registry overhead is base + 0.22 GiB per 1024 ABOVE its nbt 4096 anchor, and the fitted 5.25 GiB was fitted AT 4096.
382 lines
22 KiB
Bash
Executable File
382 lines
22 KiB
Bash
Executable File
#!/usr/bin/env bats
|
|
# Seam: frank-primary — the Dankserver LLM endpoint on :7770.
|
|
#
|
|
# This file asserts the tcclaviger RDNA4 vLLM ROOT-UNIT shape (the
|
|
# 2026-10-01 BF16 cutover, the overnight redo campaign's verdict): the serve
|
|
# script drives `podman run` DIRECTLY (the image entrypoint execs
|
|
# `vllm serve`), with every engine flag qualified by boot probe and carried
|
|
# in the repo env file. The seam pins what the flags alone do not say: the
|
|
# KV qualification law (the night ladder: 262144/245760 OOM at graph
|
|
# capture; 241664 boots at 732,359 tokens / 3.03x, util 0.98;
|
|
# the bf16-KV trade (double the attention-KV bytes/token, ~14% decode cost,
|
|
# clean cache + 262k windows), the automatic-offload law (the fork plans
|
|
# the tiered-expert offload itself; no flag exists to set), the self-healing
|
|
# law (the run returns before the engine is up, so the serve script must
|
|
# wait on the container for Restart=on-failure to mean anything), the
|
|
# memlock law that makes the pinned host rows legal, the deployer's
|
|
# image/payload gates with the false-green MainPID check, and the measured
|
|
# laws: expert prefetch ABSENT (36% precision), NO speculative decode (the
|
|
# no-spec family measured fastest once the prefetch tax was removed; MTP
|
|
# re-measures pending), gather geometry 32x64 PRESENT (+18.7% decode),
|
|
# MNBT 2048, the CUDA-graph plan term pinned to the measured capture cost, and
|
|
# the client contract (reasoning parser qwen3,
|
|
# tool parser qwen3_xml, preserve_thinking on).
|
|
|
|
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"
|
|
DOWNLOAD="$REPO_ROOT/scripts/download-frank-primary.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 tiered-expert
|
|
# 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"
|
|
# Restart=always, not on-failure: a runtime worker OOM makes vLLM's API
|
|
# server exit CLEAN, so ExecStart returns 0 and on-failure never fires
|
|
# (measured 2026-10-01 — the endpoint stayed down until a human noticed).
|
|
grep -q '^Restart=always$' "$UNIT"
|
|
! grep -q '^Restart=on-failure$' "$UNIT"
|
|
# Exiting the engine takes as long as vLLM's drain: ExecStop runs
|
|
# `podman stop -t 120`, so the stop timeout MUST exceed 120 s or systemd
|
|
# kills the stop and records 'Failed with result timeout' (measured
|
|
# 2026-10-01). Guard the relationship, not just the literal.
|
|
stop_t=$(awk -F= '/^TimeoutStopSec=/{print $2}' "$UNIT")
|
|
[ -n "$stop_t" ] && [ "$stop_t" -gt 120 ]
|
|
grep -q '^ExecStop=.*podman stop -t 120' "$UNIT"
|
|
# StartLimitIntervalSec belongs to [Unit]; a copy in [Service] is an
|
|
# 'Unknown key' warning systemd logs on every load.
|
|
[ "$(grep -c '^StartLimitIntervalSec=' "$UNIT")" = "1" ]
|
|
awk '/^\[Service\]/{in_svc=1} /^StartLimitIntervalSec=/{if(in_svc) exit 1}' "$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 unit stops the container child explicitly" {
|
|
# The serve script blocks on podman wait, which cannot stop the
|
|
# container child; systemd stop goes through ExecStop or the engine
|
|
# outlives the unit and holds both GPUs. The container name must match
|
|
# the env file's identity.
|
|
grep -q '^ExecStop=/usr/bin/podman stop' "$UNIT"
|
|
grep -q 'frank-primary-qwen' "$UNIT"
|
|
grep -q '^FRANK_PRIMARY_CONTAINER=frank-primary-qwen$' "$ENV_FILE"
|
|
}
|
|
|
|
@test "the serve shape is direct podman run, not a launcher or shim" {
|
|
# The tcclaviger stack is fully expressed by flags; the R9V launcher,
|
|
# docker->podman shim and setup-state layering are the RETIRED shape.
|
|
grep -q 'podman run' "$SERVE"
|
|
! grep -q 'launch.sh' "$SERVE"
|
|
! grep -q 'podman-shim' "$SERVE"
|
|
! grep -q 'R9V_STATE_FILE' "$SERVE"
|
|
! grep -q -- '--model-draft' "$SERVE"
|
|
! grep -q -- '--spec-type' "$SERVE"
|
|
}
|
|
|
|
@test "the qualified engine shape is in the env file" {
|
|
# 2026-10-02 shape D. History: the 2026-10-01 night ladder put SEQS=3
|
|
# bf16 at 241664 (KV 732,359 / 3.03x); SEQS=6 fp8 does NOT fit (6 x
|
|
# 241664 asked 10.70 GiB against 9.18 GiB free); SEQS=5 fp8 x 262144
|
|
# BOOTED (1,350,182 / 5.15x) but left ~0 bytes free and a real 995-token
|
|
# request killed the worker with `Tried to allocate 50.00 MiB ... 0 bytes
|
|
# free`. UTIL is a REAL arena lever (the fork plans budget = total x util)
|
|
# but a weak one, and Frank caps it at 0.982 — never 1.00 (see
|
|
# FRANK_PRIMARY_UTIL for the two ceiling deaths that justify the margin).
|
|
# SEQS=2 is IMPOSSIBLE on this model, measured 2026-10-02 by
|
|
# single-variable boot probe against the then-deployed shape: SEQS 2 fails
|
|
# at BOTH MNBT 1024 and 2048 with torch.fx ConstraintViolationError
|
|
# (query_start_loc.size()[0] marked dynamic but specialized to a constant
|
|
# (3)); SEQS 3 passes at both. model_runner sizes that buffer
|
|
# max_num_reqs + 1, so the width is SEQS + 1 and MNBT only moves the
|
|
# compile RANGE.
|
|
# So the shape is 3 slots with a pool that admits only ~2 full-length
|
|
# conversations: measured live, KV_BYTES 3.15e9/rank reports 398,658
|
|
# tokens = 1.99x for a 200704-token request (7,892 B/token at THIS shape;
|
|
# the per-token price is shape-dependent, 7,701 at 262144). Shorter
|
|
# requests use the spare blocks; a third LONG request queues.
|
|
# The arena is sized by max_model_len, NOT by KV_BYTES: the planner
|
|
# reserves registry 9190 B/token x max_model_len (2.48 GiB at 262144 ->
|
|
# arena 6,248 slots; 1.91 GiB at 200704 -> 6,496). The pool cut buys
|
|
# runtime headroom, not arena.
|
|
grep -q '^FRANK_PRIMARY_MAXLEN=200704$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_SEQS=3$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_UTIL=0.982$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_KV_BYTES=3150000000$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_MNBT=2048$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_KV_DTYPE=fp8$' "$ENV_FILE"
|
|
# --expert-precise-memory IS set (shape E, 2026-10-02). It was refused
|
|
# while the 2 x 262144 bf16 boot sat 0.13 GiB under the 31.86 GiB ceiling
|
|
# (arena 16.65 device-resident, NRestarts=3 at ~6.7 min each) — there it was
|
|
# the last 2.35 GiB over the line, not the cause. Probed on the shape-D base
|
|
# with production stopped: boots healthy in 420 s, plan reports runtime
|
|
# overhead 5.25 GiB (not 7.60), expert cache 17.22 GiB / 7,522 slots
|
|
# (+1,026 / +16% over 6,496). The pool is NOT cut with it (option A): the
|
|
# pool is not an arena lever, so 3.15e9 keeps the measured 1.99x.
|
|
grep -q '^FRANK_PRIMARY_PRECISE_MEMORY=1$' "$ENV_FILE"
|
|
# GPU_MAX_HW_QUEUES caps HSA hw queues per GPU; real knob (the literal is in
|
|
# libhsa-runtime64.so), passed inside the container as a bare name — the env
|
|
# file keeps only the prefixed form.
|
|
grep -q '^FRANK_PRIMARY_GPU_MAX_HW_QUEUES=2$' "$ENV_FILE"
|
|
# The compile cache is a BOOT-COST fix: the image ships no /root/.cache at
|
|
# all, our boot writes 1.1 GB of vLLM AOT artifacts (4,665 files) plus
|
|
# 229 MB of comgr JIT kernels into the ephemeral layer, and every
|
|
# `podman rm -f` throws them away — re-paying `torch.compile took 181.20 s`
|
|
# (173.19 s of it one graph) on every deploy, restart and reboot.
|
|
grep -q '^FRANK_PRIMARY_CACHE_DIR=/home/dank/.cache/frank-primary$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_GATHER_CHUNKS=32$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_GATHER_LANES=64$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_MODEL_SHARDS=131$' "$ENV_FILE"
|
|
}
|
|
|
|
@test "the serve script wires the engine flags from the env" {
|
|
grep -q -- '--max-num-seqs "$FRANK_PRIMARY_SEQS"' "$SERVE"
|
|
grep -q -- '--max-model-len "$FRANK_PRIMARY_MAXLEN"' "$SERVE"
|
|
grep -q -- '--kv-cache-memory-bytes "$FRANK_PRIMARY_KV_BYTES"' "$SERVE"
|
|
grep -q -- '--kv-cache-dtype "$FRANK_PRIMARY_KV_DTYPE"' "$SERVE"
|
|
grep -q -- '--max-num-batched-tokens "$FRANK_PRIMARY_MNBT"' "$SERVE"
|
|
grep -q -- '-e R4D_LRU_GATHER_CHUNKS="$FRANK_PRIMARY_GATHER_CHUNKS"' "$SERVE"
|
|
grep -q -- '-e R4D_LRU_GATHER_LANES="$FRANK_PRIMARY_GATHER_LANES"' "$SERVE"
|
|
# Speculative decode, the cudagraph capture ladder and the sampling
|
|
# defaults are all ENV-DRIVEN knobs (unset -> the flag is absent, which is
|
|
# the shipped shape today). JSON values carry no shell-special characters
|
|
# that this quoting cannot carry.
|
|
grep -q -- '${FRANK_PRIMARY_SPEC:+--speculative-config "$FRANK_PRIMARY_SPEC"}' "$SERVE"
|
|
grep -q -- '${FRANK_PRIMARY_COMPCONFIG:+--compilation-config "$FRANK_PRIMARY_COMPCONFIG"}' "$SERVE"
|
|
grep -q -- '${FRANK_PRIMARY_GEN_CONFIG:+--override-generation-config "$FRANK_PRIMARY_GEN_CONFIG"}' "$SERVE"
|
|
grep -q -- '--tensor-parallel-size 2' "$SERVE"
|
|
grep -q -- '--enable-prefix-caching' "$SERVE"
|
|
grep -q -- '--trust-remote-code' "$SERVE"
|
|
# DCP is opt-in: the flag is passed only when FRANK_PRIMARY_DCP is set
|
|
# (DCP reuses the TP ranks when PCP is off, so TP=2 + DCP=2 fits two GPUs).
|
|
grep -q -- '--decode-context-parallel-size "$FRANK_PRIMARY_DCP"' "$SERVE"
|
|
! grep -q '^FRANK_PRIMARY_DCP=' "$ENV_FILE"
|
|
# --expert-precise-memory selects the fitted overhead (5.25 GiB) over the
|
|
# default (7.60 GiB = fitted + compile/graph-capture headroom), which only
|
|
# moves the plan's `room` term and therefore the expert arena: ~1,025
|
|
# more slots at 2.344 MiB each.
|
|
grep -q -- '${FRANK_PRIMARY_PRECISE_MEMORY:+--expert-precise-memory}' "$SERVE"
|
|
# Shape E host/CPU wiring: the cache dir is created before it is mounted
|
|
# (a cold boot must have a target) and the mount lands on /root/.cache,
|
|
# whose vllm/ child is the path VLLM_CACHE_ROOT defaults to — the engine log
|
|
# names it as `Using cache directory: /root/.cache/vllm/torch_compile_cache/…`.
|
|
grep -q -- 'mkdir -p "$CACHE_DIR"' "$SERVE"
|
|
grep -q -- '${CACHE_DIR:+-v "$CACHE_DIR":/root/.cache}' "$SERVE"
|
|
grep -q -- '-e GPU_MAX_HW_QUEUES="$FRANK_PRIMARY_GPU_MAX_HW_QUEUES"' "$SERVE"
|
|
# Shape F: the CUDA-graph plan term is carried as CONTAINER env (the fork
|
|
# reads it at call time inside estimate_cudagraph_memory), and both must be
|
|
# present — the graph term is the only plan term this repo can shrink once
|
|
# UTIL is capped, and shrinking it is what grows the arena.
|
|
grep -q -- '-e VLLM_CUDAGRAPH_MEM_BASE_MB="$FRANK_PRIMARY_CUDAGRAPH_BASE_MB"' "$SERVE"
|
|
grep -q -- '-e VLLM_CUDAGRAPH_MEM_PER_GRAPH_MB="$FRANK_PRIMARY_CUDAGRAPH_PER_GRAPH_MB"' "$SERVE"
|
|
grep -q '^FRANK_PRIMARY_CUDAGRAPH_BASE_MB=256$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_CUDAGRAPH_PER_GRAPH_MB=12$' "$ENV_FILE"
|
|
}
|
|
|
|
@test "tools and reasoning are wired — the client contract is served" {
|
|
# 2026-10-01: the rollback-era trio served NO tool dialect and left
|
|
# thinking inline in `content` with a dangling `</think>` and a null
|
|
# `reasoning` field (measured: content 'We need answer...Brief.\n</think>
|
|
# \n\n391') — the malformed thinking block clients choke on. The working
|
|
# values are the ones ecaad36 shipped for this model and this fork's
|
|
# registries confirm: reasoning parser `qwen3`, tool parser `qwen3_xml`.
|
|
# preserve_thinking keeps prior thinking blocks across turns.
|
|
grep -q '^FRANK_PRIMARY_REASONING_PARSER=qwen3$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_TOOL_PARSER=qwen3_xml$' "$ENV_FILE"
|
|
grep -q -- '--reasoning-parser "$FRANK_PRIMARY_REASONING_PARSER"' "$SERVE"
|
|
grep -q -- '--enable-auto-tool-choice' "$SERVE"
|
|
grep -q -- '--tool-call-parser "$FRANK_PRIMARY_TOOL_PARSER"' "$SERVE"
|
|
grep -q -- '--default-chat-template-kwargs "$FRANK_PRIMARY_CHAT_TEMPLATE_KWARGS"' "$SERVE"
|
|
grep -q 'preserve_thinking' "$ENV_FILE"
|
|
grep -q 'enable_thinking' "$ENV_FILE"
|
|
}
|
|
|
|
@test "expert offload is AUTOMATIC — the payload cannot fit VRAM and no flag sets it" {
|
|
# ~86 GiB/rank of FP8 trunk against 32 GiB of VRAM: the fork's tiered-
|
|
# expert planner moves the overflow to pinned host rows by itself (the
|
|
# arena 6,574 slots x 2.344 MiB plus host rows). There is no
|
|
# --enable-expert-offload flag in this arm; the legality of the pin is
|
|
# the unit's unbounded LimitMEMLOCK.
|
|
! grep -q -- '--enable-expert-offload' "$SERVE"
|
|
grep -q '^LimitMEMLOCK=infinity$' "$UNIT"
|
|
grep -q -- '--ipc=host --ulimit memlock=-1:-1' "$SERVE"
|
|
}
|
|
|
|
@test "speculative decode is an ENV KNOB — the old retirement verdict was invalid" {
|
|
# The 2026-10-02 c1248d4 retirement of MTP compared the WRONG PAIR:
|
|
# fp8+MTP4 (37.4-40.6 tok/s, 54% acceptance) against the bf16 no-spec boot
|
|
# (36.4-39.4), then bf16 proved unbootable and fp8 came back WITHOUT MTP —
|
|
# so fp8+spec was never compared against fp8+no-spec on the same shape. The
|
|
# no-spec incumbent runs ~30 tok/s, under the fleet's 35 tok/s target, while
|
|
# EVERY vendor flash-next recipe ships MTP3, and our MTP4 run used the
|
|
# default GREEDY draft sampling (probabilistic is the other fork option and
|
|
# is what the recipes use under temp>0). So spec is a knob, not a law: the
|
|
# shipped value is whatever a matched-pair re-measurement wins.
|
|
grep -q 'THE RETIREMENT VERDICT WAS INVALID' "$ENV_FILE"
|
|
# The knobs are empty in the shipped env, so the `${VAR:+…}` wiring renders
|
|
# NO spec/compilation flag — asserted by rendering, not by grepping the
|
|
# assignment (shape E today has neither flag in its argv).
|
|
run bash -c 'set -a; . "$1" >/dev/null; set +a
|
|
printf %s "${FRANK_PRIMARY_SPEC:+--speculative-config $FRANK_PRIMARY_SPEC}${FRANK_PRIMARY_COMPCONFIG:+--compilation-config $FRANK_PRIMARY_COMPCONFIG}"' _ "$ENV_FILE"
|
|
[ -z "$output" ]
|
|
# The capture-ladder law: the piecewise graph ladder must contain
|
|
# num_speculative_tokens + 1, so an MTP3 shape captures [4, 8, 12] and the
|
|
# 2026-10-02 SEQS=2 failure (query_start_loc specialized to the constant 3)
|
|
# is why SEQS stays a multiple-compatible value.
|
|
grep -q 'num_speculative_tokens + 1' "$ENV_FILE"
|
|
}
|
|
|
|
@test "KV dtype is fp8 — the incumbent, and the only one that boots" {
|
|
# fp8 costs ~7,701 B/token at 262144 (7,892 at 200704 — shape-dependent),
|
|
# decomposed as 6,138 attention + 1,563 mamba/side (dtype-independent);
|
|
# bf16 costs 13,839 (12,276 + 1,563).
|
|
# bf16 was abandoned 2026-10-01: its 2 x 262144 boot died with 0 bytes
|
|
# free on a real request, and the SEQS=2 slot count it was designed
|
|
# around cannot compile this model at all (see the shape test above).
|
|
grep -q '^FRANK_PRIMARY_KV_DTYPE=fp8$' "$ENV_FILE"
|
|
grep -q -- '--kv-cache-dtype "$FRANK_PRIMARY_KV_DTYPE"' "$SERVE"
|
|
}
|
|
|
|
@test "gather geometry 32x64 is PRESENT BY MEASUREMENT" {
|
|
# R4D_LRU_GATHER_CHUNKS=32 / R4D_LRU_GATHER_LANES=64: the overnight
|
|
# sweep's biggest lever, +18.7% decode vs default geometry, and the win
|
|
# GROWS with depth (39.5@166 -> 54.1@1087 -> 49.3@4064 in the fp8 era).
|
|
# g64x128 is past the peak; g8x16 mild. They are container ENV (the
|
|
# fork's knobs), not engine flags — passed through with -e.
|
|
grep -q '^FRANK_PRIMARY_GATHER_CHUNKS=32$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_GATHER_LANES=64$' "$ENV_FILE"
|
|
grep -q -- '-e R4D_LRU_GATHER_CHUNKS="$FRANK_PRIMARY_GATHER_CHUNKS"' "$SERVE"
|
|
}
|
|
|
|
@test "MNBT is 2048 and the CUDA-graph plan term is priced at the measured cost" {
|
|
# MNBT: 512 doubles TTFT at 4064 ctx (2.46 s vs 1.36 s); 1024-4096 are
|
|
# interchangeable on decode; 12288 costs 2.5% plus arena. 2048 is arena-FREE
|
|
# because the registry prices overhead as base + 0.22 GiB per 1024 ABOVE its
|
|
# nbt 4096 anchor — every rung <=4096 charges the same 5.25 GiB, and the
|
|
# fitted 5.25 was fitted AT nbt 4096, so 2048 sits under the anchor.
|
|
# CUDA graphs: the arena is budget - non-expert - KV reserve - graphs -
|
|
# overhead, so every GiB the plan stops charging for graphs is a GiB of
|
|
# expert cache. The fork's analytic default (256 MiB + 52 MiB/graph) charged
|
|
# 0.55 GiB where the engine's post-capture line measured 0.31 GiB on the
|
|
# heavier rank; 256 + 12/graph prices the same 6-graph plan at 0.32 GiB.
|
|
# The pair must stay BELOW the analytic 563 MiB default (that is the win)
|
|
# and ABOVE the measured 317 MiB (under-pricing is what OOMs at capture).
|
|
grep -q '^FRANK_PRIMARY_MNBT=2048$' "$ENV_FILE"
|
|
grep -q -- '--max-num-batched-tokens "$FRANK_PRIMARY_MNBT"' "$SERVE"
|
|
base=$(awk -F= '/^FRANK_PRIMARY_CUDAGRAPH_BASE_MB=/{print $2}' "$ENV_FILE")
|
|
per_graph=$(awk -F= '/^FRANK_PRIMARY_CUDAGRAPH_PER_GRAPH_MB=/{print $2}' "$ENV_FILE")
|
|
[ $((base + 6 * per_graph)) -lt 563 ]
|
|
[ $((base + 6 * per_graph)) -gt 317 ]
|
|
}
|
|
|
|
@test "expert prefetch is absent BY LAW, not by omission" {
|
|
# Telemetry (2026-09-29): the prefetcher served 13,369 of 36,720
|
|
# prefetched slabs (36% precision) — roughly half the gather traffic was
|
|
# waste, and the "savings" would have been installs anyway. Deleting the
|
|
# flag moved decode 26.8 -> 40.6 tok/s. Any future flag must come with a
|
|
# fresh A/B, so the serve script must NOT wire it unconditionally.
|
|
! grep -q -- '--expert-prefetch' "$SERVE"
|
|
! grep -q 'expert_prefetch' "$ENV_FILE"
|
|
grep -q 'PREFETCH IS ABSENT BY LAW' "$ENV_FILE"
|
|
}
|
|
|
|
@test "TunableOp stays off" {
|
|
# CLAV_TUNABLEOP_SWEEP silently skips under expert offload unless
|
|
# CLAV_TUNABLEOP_WITH_OFFLOAD=1, and forcing it corrupts numerics
|
|
# (repetition loops, early halts, invalid measurements).
|
|
! grep -q 'TUNABLEOP' "$ENV_FILE"
|
|
! grep -q 'TUNABLEOP' "$SERVE"
|
|
}
|
|
|
|
@test "the serve script gates the digest-pinned image and the payload" {
|
|
# The image is pinned by OCI manifest digest (the floating :latest moved
|
|
# once already). The payload gate checks config.json and the exact shard
|
|
# count from the env — a missing shard means a guaranteed long-timeout
|
|
# boot instead of a fast failure.
|
|
grep -q 'FRANK_PRIMARY_IMAGE' "$SERVE"
|
|
grep -q 'RepoDigests' "$SERVE"
|
|
grep -q 'FRANK_PRIMARY_MODEL_SHARDS' "$SERVE"
|
|
grep -q 'config.json' "$SERVE"
|
|
grep -q '/dev/kfd' "$SERVE"
|
|
grep -q -- '--device /dev/dri' "$SERVE"
|
|
! grep -q 'tcclaviger/vllm:latest' "$ENV_FILE"
|
|
}
|
|
|
|
@test "the serve script clears stale containers and self-heals via podman wait" {
|
|
# A crashed unit can leave the container name registered forever after;
|
|
# the legacy identities (the retired R9V container and the ad hoc
|
|
# experiment container) hold the same GPUs and die here too. Then block
|
|
# on the container so a crash is a nonzero ExecStart.
|
|
grep -q 'FRANK_PRIMARY_LEGACY_CONTAINERS' "$ENV_FILE"
|
|
grep -q 'frank-primary-r9v' "$ENV_FILE"
|
|
grep -q 'qwen-fp8' "$ENV_FILE"
|
|
grep -q 'exec podman wait "$FRANK_PRIMARY_CONTAINER"' "$SERVE"
|
|
}
|
|
|
|
@test "frank-primary holds the single fleet port under its own alias" {
|
|
# The scratch identity (qwen3.8-flash-next on :7773) must never leak
|
|
# into production: LAN publish of :7770, the harness's model id.
|
|
grep -q '^FRANK_PRIMARY_PUBLISH=7770:7770$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_SERVED_NAME=frank-primary$' "$ENV_FILE"
|
|
}
|
|
|
|
@test "the model id matches the payload's served alias wiring" {
|
|
# The payload dir and its shard count are the fetch script's output
|
|
# layout; the fetch script must write where the env points.
|
|
grep -q '^FRANK_PRIMARY_MODEL_DIR=/home/dank/models/Qwen3.8-Flash-Next-Uncensored-FP8$' "$ENV_FILE"
|
|
grep -q '^FRANK_PRIMARY_MODEL_SHARDS=131$' "$ENV_FILE"
|
|
grep -q 'Qwen3.8-Flash-Next-Uncensored-FP8' "$DOWNLOAD"
|
|
grep -q 'orcarouter/Qwen3.8-Flash-Next-Uncensored-FP8' "$DOWNLOAD"
|
|
}
|
|
|
|
@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. Every key here is FRANK_PRIMARY_-prefixed.
|
|
! grep -qE '^(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$ENV_FILE"
|
|
! grep -qE '^(export )?(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$SERVE"
|
|
}
|
|
|
|
@test "the deployer gates the image digest and payload, then health-checks" {
|
|
# The deployer refuses when the pinned image is absent from rootless
|
|
# storage or the payload is missing/wrong-sharded — then clears stale
|
|
# containers (including the legacy identities), waits out the port,
|
|
# relinks/enables/restarts the root unit and health-gates it.
|
|
grep -q 'LLM_PRIMARY_CONTAINER=frank-primary-qwen' "$REMOTE_DEPLOY"
|
|
grep -q 'FRANK_PRIMARY_IMAGE' "$REMOTE_DEPLOY"
|
|
grep -q "podman images --format '{{.RepoDigests}}'" "$REMOTE_DEPLOY"
|
|
grep -q 'FRANK_PRIMARY_MODEL_SHARDS' "$REMOTE_DEPLOY"
|
|
grep -q 'config.json' "$REMOTE_DEPLOY"
|
|
grep -q 'qwen-fp8' "$REMOTE_DEPLOY"
|
|
grep -q 'glm-probe' "$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"
|
|
}
|