Compare commits

...
+28 -27
View File
@@ -4,9 +4,9 @@
# went back to the halogen engine on the iGPU. This seam pins what the flags # went back to the halogen engine on the iGPU. This seam pins what the flags
# alone do not say: boot persistence for a NATIVE runtime (no generated quadlet # alone do not say: boot persistence for a NATIVE runtime (no generated quadlet
# is involved), the repo-relative paths the unit executes, the separate client # is involved), the repo-relative paths the unit executes, the separate client
# port, the MTP contract (the head lives in the weights file, and no second copy # port, the MTP contract (the head is a separate sidecar file loaded as the
# of the model is loaded for it), the vision handoff from frank-large, and the # draft model, chained behind ngram-mod), the vision handoff from frank-large,
# KV budget that has to fit a 24 GiB card beside 12 GiB of weights. # and the KV budget: q8_0 quantized KV whose context --fit sizes to the card.
load helpers load helpers
@@ -45,41 +45,42 @@ QUADLET_LARGE="$REPO_ROOT/dankclaw/frank-large/quadlet/frank-large.container"
grep -q -- '--metrics' "$SERVE" grep -q -- '--metrics' "$SERVE"
} }
@test "frank-small's MTP head comes from the weights file, not a second copy" { @test "frank-small drafts with the MTP sidecar chained behind ngram-mod" {
grep -q '^SPEC_TYPE=draft-mtp$' "$ENV_FILE" grep -q '^SPEC_TYPE=draft-mtp,ngram-mod$' "$ENV_FILE"
grep -q -- '--spec-type "$SPEC_TYPE"' "$SERVE" grep -q -- '--spec-type "$SPEC_TYPE"' "$SERVE"
# The -mtp build is the one carrying blk.64.nextn.*; the plain IQ3_S has no # The Gemma4 MTP head is a separate assistant file (gemma4-assistant, 4
# MTP tensors at all, so MTP is a property of the file, not of the flags. # nextn layers); it loads as --model-draft and shares the target's last
grep -q '^MODEL_FILE=.*-mtp\.gguf$' "$ENV_FILE" # layers' KV. ngram-mod is draftless, so the chain is legal.
# A separate draft model would load a second 12 GiB copy of a 12 GiB model. grep -q '^DRAFT_FILE=mtp-gemma-4-12B-it\.gguf$' "$ENV_FILE"
# Only the effective configuration counts: the script's own comment explains grep -q -- '--model-draft "$MODEL_DIR/$DRAFT_FILE"' "$SERVE"
# why there is no draft path, and a textual grep would match that prose.
! grep -vE '^[[:space:]]*#' "$SERVE" | grep -q -- '--model-draft'
} }
@test "frank-small carries the fleet's image path, and only it" { @test "frank-small carries the fleet's image path, and only it" {
grep -q '^MMPROJ_PATH=.*mmproj-Qwen3\.8-27B-BF16\.gguf$' "$ENV_FILE" grep -q '^MMPROJ_PATH=.*mmproj-Gemma4-12B-QAT-Uncensored-HauhauCS-Balanced-BF16\.gguf$' "$ENV_FILE"
grep -q -- '--mmproj "$MMPROJ_PATH"' "$SERVE" grep -q -- '--mmproj "$MMPROJ_PATH"' "$SERVE"
! grep -q 'HALOGEN_VISION_TOWER' "$QUADLET_LARGE" ! grep -q 'HALOGEN_VISION_TOWER' "$QUADLET_LARGE"
} }
@test "frank-small's KV fits the card beside the weights it loads" { @test "frank-small quantizes KV to q8 and lets --fit size the context" {
local ctx parallel parallel_slots cache_k cache_v local parallel cache_k cache_v ctx
ctx="$(sed -nE 's/^CTX_SIZE=([0-9]+)$/\1/p' "$ENV_FILE")"
parallel="$(sed -nE 's/^PARALLEL=([0-9]+)$/\1/p' "$ENV_FILE")" parallel="$(sed -nE 's/^PARALLEL=([0-9]+)$/\1/p' "$ENV_FILE")"
cache_k="$(sed -nE 's/^CACHE_TYPE_K=(.+)$/\1/p' "$ENV_FILE")" cache_k="$(sed -nE 's/^CACHE_TYPE_K=(.+)$/\1/p' "$ENV_FILE")"
cache_v="$(sed -nE 's/^CACHE_TYPE_V=(.+)$/\1/p' "$ENV_FILE")" cache_v="$(sed -nE 's/^CACHE_TYPE_V=(.+)$/\1/p' "$ENV_FILE")"
[ -n "$ctx" ] && [ -n "$parallel" ] && [ -n "$cache_k" ] && [ -n "$cache_v" ] ctx="$(sed -nE 's/^CTX_SIZE=([0-9]+)$/\1/p' "$ENV_FILE")"
[ -n "$parallel" ] && [ -n "$cache_k" ] && [ -n "$cache_v" ]
[ "$parallel" -ge 1 ] [ "$parallel" -ge 1 ]
# qwen35 spends KV on its 16 full-attention layers only: 2 (K,V) x 4 heads x # q8_0 KV is what fits the 24 GiB card beside 7.4 GiB of Q4_K_M weights +
# 256 dim x 2 B = 64 KiB per token at f16. 11.29 GiB of weights + 0.87 of # mmproj + sidecar: Gemma4's 16 kv heads x 512 dim cost 16 KiB per token
# mmproj + ~2 GiB of context/compute/MTP leave under 10 GiB of the card's # per layer at q8, and the model's sliding-window pattern keeps the pool
# 24 GiB for KV, and --parallel divides --ctx-size between the slots. # far below full-ctx x all-layers. The engine itself sizes the context:
[ $((ctx * 64)) -le $((10 * 1024 * 1024)) ] # CTX_SIZE is unset, so the serve script passes --fit on.
# f16 is the deployed type on purpose: a quantized KV needs the [ -z "$ctx" ]
# GGML_CUDA_FA_ALL_QUANTS build option, without which prefill on CUDA [ "$cache_k" = "q8_0" ] && [ "$cache_v" = "q8_0" ]
# collapses about 12x (see the CUDA lesson in AGENTS.md). grep -q -- '--fit on' "$SERVE"
[ "$cache_k" = "f16" ] && [ "$cache_v" = "f16" ] # Quantized KV on CUDA needs the GGML_CUDA_FA_ALL_QUANTS build option,
# without which prefill collapses about 12x (see the CUDA lesson in
# AGENTS.md). The build-cuda tree must carry it.
grep -q '^FLASH_ATTN=on$' "$ENV_FILE"
} }
@test "frank-small runs on its own port beside frank-large's" { @test "frank-small runs on its own port beside frank-large's" {
@@ -98,7 +99,7 @@ QUADLET_LARGE="$REPO_ROOT/dankclaw/frank-large/quadlet/frank-large.container"
grep -q 'relink_unit frank-small' "$REMOTE_DEPLOY" grep -q 'relink_unit frank-small' "$REMOTE_DEPLOY"
grep -q 'systemctl enable frank-small.service' "$REMOTE_DEPLOY" grep -q 'systemctl enable frank-small.service' "$REMOTE_DEPLOY"
grep -q 'systemctl restart frank-small.service' "$REMOTE_DEPLOY" grep -q 'systemctl restart frank-small.service' "$REMOTE_DEPLOY"
grep -q 'Qwen3.8-27B-GSQ-RCO-IQ3_S-mtp' "$REMOTE_DEPLOY" grep -q 'Gemma4-12B-QAT' "$REMOTE_DEPLOY"
grep -q 'refusing to start a unit that cannot load' "$REMOTE_DEPLOY" grep -q 'refusing to start a unit that cannot load' "$REMOTE_DEPLOY"
grep -q '127.0.0.1:7771/health' "$REMOTE_DEPLOY" grep -q '127.0.0.1:7771/health' "$REMOTE_DEPLOY"
} }