The first cutover crash-looped the unit with 'invalid split file name': the fetch script concatenated the three split parts into a single GGUF, but part 1's header carries ggml.file_count=3, so the byte-correct concatenation still advertises itself as a split and the loader demands a -NNNNN-of-NNNNN filename. - serve-frank-large.sh: -m points at part 1; llama.cpp loads the split natively (parts 2-3 auto-discovered in the same directory) - deploy-remote.sh: LLM_LARGE_MODEL gate path follows - download-frank-large-unsloth.sh: assembly removed; the parts ARE the canonical payload, verified against live Hub LFS oids - docs: runbook model section + troubleshooting note, AGENTS.md, log - the redundant 87 GiB assembled file is staged on dankclaw at ~/.AGENTTRASH/20260921-frank-large-assembled/
131 lines
5.6 KiB
Bash
Executable File
131 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bats
|
|
# Seam: the frank-large unsloth-llama.cpp root unit on dankclaw.
|
|
#
|
|
# The 2026-09-21 cutover moved this service from the CIRU v4.4.1 runtime
|
|
# (jcbtc/Qwen3.8-Flash-CIRU-STRIX-Orca) onto the unsloth llama.cpp prebuilt
|
|
# (b11030-mix-5ff778e, ROCm gfx1151) serving
|
|
# unsloth/Qwen3.8-Flash-Next-GGUF:UD-IQ4_XS, still as a ROOT unit: a
|
|
# prebuilt tarball with its own bundled ROCm has no container image to
|
|
# install and no generated unit to dodge. This seam pins what a flag list
|
|
# cannot show: the operator's one-stream / 200k shape, the payload the
|
|
# serve script refuses to start without, the explicit -md MTP wiring
|
|
# (sidecar auto-discovery does not search the mtp/ subfolder), a deploy
|
|
# handler that gates on the port, and the fact that the sweep for a stray
|
|
# benchmark run cannot take frank-small down with it.
|
|
|
|
load helpers
|
|
|
|
ENV_FILE="$REPO_ROOT/scripts/frank-large.env"
|
|
UNIT="$REPO_ROOT/scripts/frank-large.service"
|
|
SERVE="$REPO_ROOT/scripts/serve-frank-large.sh"
|
|
FETCH="$REPO_ROOT/scripts/download-frank-large-unsloth.sh"
|
|
REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh"
|
|
|
|
@test "the frank-large unit, env and serve files exist where the deployer installs them from" {
|
|
[ -f "$ENV_FILE" ]
|
|
[ -f "$UNIT" ]
|
|
[ -x "$SERVE" ]
|
|
}
|
|
|
|
@test "frank-large is boot-persistent, self-healing and unprivileged" {
|
|
grep -q '^WantedBy=multi-user.target$' "$UNIT"
|
|
grep -q '^Restart=on-failure$' "$UNIT"
|
|
grep -q '^RestartSec=' "$UNIT"
|
|
grep -q '^User=dank$' "$UNIT"
|
|
# The engine pins staging buffers; the memlock budget must be unbounded
|
|
# or the load fails on the RLIMIT.
|
|
grep -q '^LimitMEMLOCK=infinity$' "$UNIT"
|
|
# The unit runs the repo's own serve script with the repo's env.
|
|
grep -q 'ExecStart=/home/dank/serverAppConfigs/scripts/serve-frank-large.sh' "$UNIT"
|
|
grep -q 'FRANK_LARGE_ENV_FILE=/home/dank/serverAppConfigs/scripts/frank-large.env' "$UNIT"
|
|
}
|
|
|
|
@test "the operator's shape is one stream of 200k context with MTP on" {
|
|
local ctx slots
|
|
ctx="$(sed -nE 's/^CONTEXT_SIZE=([0-9]+)$/\1/p' "$ENV_FILE")"
|
|
slots="$(sed -nE 's/^PARALLEL_SLOTS=([0-9]+)$/\1/p' "$ENV_FILE")"
|
|
# 200000 is the operator's requested serving shape (2026-09-21); the
|
|
# model's trained window is 262144.
|
|
[ "$ctx" -eq 200000 ]
|
|
# One stream.
|
|
[ "$slots" -eq 1 ]
|
|
grep -q '^ENABLE_MTP=1$' "$ENV_FILE"
|
|
}
|
|
|
|
@test "frank-large stays text-only until the projector is enabled" {
|
|
grep -q '^ENABLE_VISION=0$' "$ENV_FILE"
|
|
# The fleet's image path is frank-small; the projector is on disk and is
|
|
# one env flip away, but models.yml declares frank-large text-only.
|
|
grep -q -- '--mmproj' "$REPO_ROOT/scripts/serve-frank-small.sh"
|
|
}
|
|
|
|
@test "the serve script pins the prebuilt runtime and refuses a missing build" {
|
|
grep -q 'unsloth-llama-b11030' "$SERVE"
|
|
grep -q 'llama-server' "$SERVE"
|
|
# A missing runtime or payload is a deploy error with a pointer, not a
|
|
# restart loop.
|
|
grep -q 'missing runtime' "$SERVE"
|
|
grep -q 'missing target model' "$SERVE"
|
|
grep -q 'missing MTP head' "$SERVE"
|
|
# The Qwen3.8 template keeps prior thinking blocks across turns only
|
|
# when the kwarg is set.
|
|
grep -q 'preserve_thinking' "$SERVE"
|
|
}
|
|
|
|
@test "the serve script wires the MTP head explicitly and speculates" {
|
|
# Sidecar auto-discovery does not search the mtp/ subfolder, so the head
|
|
# must be passed with -md or the server runs at the main model's speed
|
|
# with no error saying why.
|
|
grep -q -- '-md "$MTP_FILE"' "$SERVE"
|
|
grep -q -- '--spec-type draft-mtp' "$SERVE"
|
|
grep -q 'mtp-Qwen3.8-Flash-Next-shared-Q8_0.gguf' "$SERVE"
|
|
}
|
|
|
|
@test "the payload is fetched resumably and verified against the live Hub tree" {
|
|
grep -q 'unsloth/Qwen3.8-Flash-Next-GGUF' "$FETCH"
|
|
grep -q -- '-fL -C -' "$FETCH"
|
|
# The LFS oids come from the Hub tree API at verify time, not from a
|
|
# file that could drift; each part's local sha256 is compared to its
|
|
# oid and a mismatch fails the run.
|
|
grep -q 'tree/main' "$FETCH"
|
|
grep -q 'SHA256 MISMATCH' "$FETCH"
|
|
}
|
|
|
|
@test "the fleet endpoint and alias survive the cutover" {
|
|
grep -q '^HOST=0.0.0.0$' "$ENV_FILE"
|
|
grep -q '^PORT=7770$' "$ENV_FILE"
|
|
grep -q '^MODEL_DIR=/home/dank/models/Qwen3.8-Flash-Next-UD-IQ4_XS$' "$ENV_FILE"
|
|
}
|
|
|
|
@test "the deploy handler retires the halogen quadlet without deleting it" {
|
|
grep -q 'retire_halogen_large_quadlet' "$REMOTE_DEPLOY"
|
|
grep -q 'HOME/.AGENTTRASH' "$REMOTE_DEPLOY"
|
|
! grep -qE '(^|[^[:alnum:]_])rm (-[a-zA-Z]+ )?.*frank-large' "$REMOTE_DEPLOY"
|
|
}
|
|
|
|
@test "the deploy handler installs the root unit, gates the port and refuses a missing payload" {
|
|
grep -q 'relink_unit frank-large' "$REMOTE_DEPLOY"
|
|
grep -q 'systemctl enable frank-large.service' "$REMOTE_DEPLOY"
|
|
grep -q 'wait_port_free 7770' "$REMOTE_DEPLOY"
|
|
grep -q '127.0.0.1:7770/health' "$REMOTE_DEPLOY"
|
|
grep -q 'LLM_LARGE_MODEL' "$REMOTE_DEPLOY"
|
|
grep -q 'LLM_LARGE_DRAFT' "$REMOTE_DEPLOY"
|
|
}
|
|
|
|
@test "the stray-run sweep cannot take frank-small down" {
|
|
# frank-small on this host is ALSO a llama-server. The sweep must match
|
|
# the model path, not the binary name, or a frank-large deploy restarts
|
|
# the 3090 endpoint.
|
|
grep -q "pkill -f 'Qwen3.8-Flash-Next-UD-IQ4_XS'" "$REMOTE_DEPLOY"
|
|
# ...and only inside the large handler: extract it and check there is no
|
|
# bare binary-name pkill in it.
|
|
local large
|
|
large="$(sed -n '/^deploy_llm_large() {/,/^}/p' "$REMOTE_DEPLOY")"
|
|
! grep -q 'pkill -x llama-server' <<<"$large"
|
|
}
|
|
|
|
@test "the halogen quadlet is archived, not live" {
|
|
[ ! -e "$REPO_ROOT/dankclaw/frank-large/quadlet/frank-large.container" ]
|
|
[ -f "$REPO_ROOT/dankclaw/.AGENTTRASH/frank-large.container.halogen" ]
|
|
}
|