- env: STATE_FILE -> r9v-state/q8xl; add dev-overlay adapter mount (quantized inject shard dequant, fork 363d606), FULL_MUTABLE_CACHE=0 (80-slot tiered vs the overlay's uncensored 160-slot injection), and the qwen-sharp-v22.4.0.jinja chat template via the launcher's new R9V_CHAT_TEMPLATE_FILE/KWARGS passthrough (fork 30d4a32) - serve/deploy: shard gates extended to SHARD4/5/6 (unset-safe, the uncensored rollback state carries none) - seams: STATE_FILE/LLM_PRIMARY_STATE -> q8xl
146 lines
7.1 KiB
Bash
Executable File
146 lines
7.1 KiB
Bash
Executable File
#!/usr/bin/env bats
|
|
# Seam: frank-primary — the Dankserver LLM endpoint on :7770.
|
|
#
|
|
# This file asserts the R9V radiance-vLLM ROOT-UNIT shape (the 2026-09-27
|
|
# swap that replaced the llama.cpp b11030 endpoint): the R9V fork's own
|
|
# launcher (scripts/launch.sh in the pinned fork checkout) driven through a
|
|
# docker->podman shim, with the qualified on-host setup state layered under
|
|
# the repo's production overrides. The seam pins what the flags alone do not
|
|
# say: the state-layering contract (104 tuned R9V_* keys live on-host; the
|
|
# repo env carries only production overrides), the self-healing law (the
|
|
# launcher detaches, so the serve script must wait on the container for
|
|
# Restart=on-failure to mean anything), the memlock law that makes the
|
|
# tiered-expert pin legal, and the deployer's state/payload/image 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 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"
|
|
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 unit stops the container child explicitly" {
|
|
# The serve script blocks on docker wait, which cannot stop the
|
|
# container child; systemd stop goes through ExecStop or the engine
|
|
# outlives the unit and holds both GPUs.
|
|
grep -q '^ExecStop=/usr/bin/podman stop' "$UNIT"
|
|
grep -q 'frank-primary-r9v' "$UNIT"
|
|
}
|
|
|
|
@test "the serve shape is the R9V launcher, not a frozen podman run" {
|
|
# The launcher owns the resolved container args (binds, overlays, device
|
|
# passthrough); a frozen copy of its argv would drift on every fork
|
|
# update. The llama.cpp era's flags would also silently no-op here.
|
|
grep -q 'launch.sh' "$SERVE"
|
|
! grep -q -- '--model-draft' "$SERVE"
|
|
! grep -q -- '--spec-type' "$SERVE"
|
|
! grep -q -- '--mmproj' "$SERVE"
|
|
! grep -q -- '--override-tensor' "$SERVE"
|
|
! grep -q 'podman run' "$SERVE"
|
|
}
|
|
|
|
@test "the tuned engine shape comes from the on-host setup state, not the repo" {
|
|
# The profile setup qualified the tuned shape (tiered-expert placement,
|
|
# per-rank offload budgets, MTP4, parser wiring). Copying it into
|
|
# the repo would drift; the serve script layers it and skips keys the
|
|
# env file defines, so production overrides always win.
|
|
grep -q 'R9V_STATE_FILE=/home/dank/r9v-state/q8xl/setup.json' "$ENV_FILE"
|
|
grep -q 'state must carry R9V_MODEL_DIR' "$SERVE"
|
|
grep -q 'is not ready' "$SERVE"
|
|
grep -q 'key not in env_keys' "$SERVE"
|
|
}
|
|
|
|
@test "the serve script gates the pinned image, PLE bytes and GGUF shards" {
|
|
# The image is answered by its OCI manifest digest (podman keeps the
|
|
# manifest digest in RepoDigests, not as the image ID — the shim's ID
|
|
# law). The PLE payload is checked at its pinned byte count
|
|
# (stat -c %s, never wc -c). A missing shard means a guaranteed
|
|
# long-timeout boot instead of a fast failure.
|
|
grep -q 'R9V_PLE_EXPECTED_BYTES' "$SERVE"
|
|
grep -q 'stat -c %s' "$SERVE"
|
|
grep -q 'R9V_TARGET_SHARD2_REL' "$SERVE"
|
|
grep -q 'R9V_MMPROJ_REL' "$SERVE"
|
|
grep -q 'RepoDigests' "$SERVE"
|
|
grep -q '/dev/kfd' "$SERVE"
|
|
}
|
|
|
|
@test "the serve script clears the stale container and self-heals via docker wait" {
|
|
# launch.sh detaches and refuses an existing container name; a crashed
|
|
# unit can leave the name registered forever after. Clear our own name
|
|
# first (idempotent), then block on the container so a crash is a
|
|
# nonzero ExecStart.
|
|
grep -q 'docker rm -f "$R9V_CONTAINER_NAME"' "$SERVE"
|
|
grep -q 'exec docker wait "$R9V_CONTAINER_NAME"' "$SERVE"
|
|
}
|
|
|
|
@test "frank-primary holds the single fleet port under its own alias" {
|
|
# The scratch identity (r9v-qwen38-flash-next on 127.0.0.1:7772) must
|
|
# never leak into production: LAN bind, :7770, the harness's model id.
|
|
grep -q '^R9V_HOST_BIND=0.0.0.0$' "$ENV_FILE"
|
|
grep -q '^R9V_HOST_PORT=7770$' "$ENV_FILE"
|
|
grep -q '^R9V_SERVED_MODEL_NAME=frank-primary$' "$ENV_FILE"
|
|
grep -q '^R9V_CONTAINER_NAME=frank-primary-r9v$' "$ENV_FILE"
|
|
grep -q '^R9V_EXPECTED_GPU_BDFS=0000:03:00.0,0000:07:00.0$' "$ENV_FILE"
|
|
}
|
|
|
|
@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 R9V_HOST_BIND, not HOST.
|
|
! 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 setup state, image digest, PLE bytes and shards" {
|
|
# The deployer refuses when the on-host setup state is missing or not
|
|
# ready, when the pinned image is absent from rootless storage, when
|
|
# the PLE payload is off its pinned byte count, or when any GGUF shard
|
|
# or the mmproj is missing — then clears stale containers, waits out
|
|
# the port, relinks/enables/restarts the root unit and health-gates it.
|
|
grep -q 'LLM_PRIMARY_STATE=/home/dank/r9v-state/q8xl/setup.json' "$REMOTE_DEPLOY"
|
|
grep -q 'LLM_PRIMARY_CONTAINER=frank-primary-r9v' "$REMOTE_DEPLOY"
|
|
grep -q 'is not ready' "$REMOTE_DEPLOY"
|
|
grep -q "podman images --format '{{.RepoDigests}}'" "$REMOTE_DEPLOY"
|
|
grep -q 'stat -c %s' "$REMOTE_DEPLOY"
|
|
grep -q 'R9V_TARGET_SHARD2_REL' "$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"
|
|
}
|