fix(qwen38): the rig's card split is inherited from frank-archon.env, not invented #77

Merged
dank merged 2 commits from feat/qwen38-w5nl64 into main 2026-10-27 23:53:12 -07:00
2 changed files with 49 additions and 32 deletions

No files matched your search

+48 -31
View File
@@ -39,37 +39,45 @@
# speculation, fp8 KV, a 32,768-token window, the PLE n-gram table pinned to RAM, # speculation, fp8 KV, a 32,768-token window, the PLE n-gram table pinned to RAM,
# and the production chat template rather than the one baked into the container — # and the production chat template rather than the one baked into the container —
# every scored container is asked the same tokenised questions the reference # every scored container is asked the same tokenised questions the reference
# answered. The window is 32,768 because this corpus is long: measured on # answered. The window is 32,768: kld.jsonl is 197 records with a longest of
# Dankserver 2026-10-27, kld.jsonl is 197 records with a longest of 38,263 # 38,263 characters (longest prompt FIELD 36,855, median record 27,228). Its
# characters (longest prompt FIELD 36,855, median record 27,228), which is ~9,600 # token length — 10,554 for that longest record, counted by the engine itself —
# tokens at the pessimistic chars/4 and ~7,700 at the ratio this repo actually # is in the funding paragraph below; the kldref gate re-checks the file against
# measured on the K3 corpus (380,143 chars = 76,568 tokens, chars/4.97). A 16,384 # the window before it boots anything.
# window cleared that by 1.7x; 32,768 clears every record of both corpora by at
# least 2.6x, and the kldref gate below re-checks it against the file on disk.
# #
# NO --vram-kv-mib: it was REMOVED at 1.3.0 and naming it is a boot refusal, not # NO --vram-kv-mib: it was REMOVED at 1.3.0 and naming it is a boot refusal, not
# a no-op — verified on the pinned image 2026-10-27: "E --vram-kv-mib was # a no-op — verified on the pinned image 2026-10-27: "E --vram-kv-mib was
# removed: the card is split between the weights and the cache from what the # removed: the card is split between the weights and the cache from what the
# model needs." The budget resolver derives the per-card split from # model needs." The resolver derives the split from --host-pool-mib,
# --host-pool-mib and --gpu-headroom-mib, and those two are deliberately NOT # --gpu-headroom-mib and the activation arena; the rig inherits the first two from
# production's 49,152 / 1,536 (frank-archon.env:77): the pool decides WHICH # frank-archon.env, the file that owns them. On THIS payload the host pool is not
# experts stay resident, so it buys speed and never logits — the shape the gate # the cache's supplier: the boot lands in vram_budget.cpp's `no lender` branch,
# has to hold identical is the kv dtype, the slot count, the step size, the spec # where the experts keep their plan-fixed 25.29 GiB and the cache gets the residue.
# depth and the window. It is also the only figure that fits: a 335 GiB bf16 # Measured twice — 12,288 and 49,152 MiB of host pool both printed the same
# container cannot be booted against a 49 GiB resident set on a 192 GiB machine, # "kv cache 271.25 MiB ... 7,436"; only --gpu-headroom-mib moved anything
# and a prefill-only pass does not care that the disk tier paces at ~3 GiB/s. # (768 -> 96 grew vram_weights 29.41 -> 30.06 GiB). The supplier the source names
# is the arena: vram_budget.cpp:117, "lower --max-num-batched-tokens (the arena
# scales with it)" — which is what the step size below now does.
#
# The pool, not the window, decides what the recorder accepts: it refuses any
# document longer than the pool, so the 32,768-token window was decorative while the
# cache held 7,436 — "E kld: document 196 is 8507 tokens and the KV pool holds
# 7436, so it can never be scored" on all 197 records, exit 1. The longest record
# is 10,554 tokens by the engine's own count (chars/3.6 for this file, not the
# chars/4.97 this repo measured on the K3 corpus).
# #
# The step size is ONE value for the recording and every scoring pass: # The step size is ONE value for the recording and every scoring pass:
# FRANK_QWEN38_MNBT, default 2,048. The gate's whole claim is that the reference, # FRANK_QWEN38_MNBT, default 1,024. The gate's whole claim is that the reference,
# the served iq4r and every arm rode one shape, so the ceiling is set by the # the served iq4r and every arm rode one shape, so the ceiling is set by the
# TIGHTEST payload the gate boots rather than by the bf16 reference: the quantised # TIGHTEST payload the gate boots rather than by the bf16 reference: the quantised
# expert FILE tiers (the served iq4r, the arms) stream only at MNBT<=2048 and # expert FILE tiers (the served iq4r, the arms) stream only at MNBT<=2048 and
# refuse the widened form above it, and 2,048 is what production holds anyway. # refuse the widened form above it. 2,048 was still the wrong default on this
# Recording the reference at 32,768 would therefore measure a shape the payloads # payload, because the arena the engine charges for a step comes straight out of
# cannot reproduce; at 2,048 the 50,432 positions cost more steps, which is still # the cache — 1.08 GiB at a 2,048-token step, 504.39 MiB at a 1,024-token one, and
# one drained window. Calibration keeps the wide 65,536 — grams are not logits, # the experts keep their share either way. At 1,024 the cache is 866 MiB: 24,300
# nothing downstream is compared against a step size, and that is where a big # tokens, which scores every one of the 197 documents (longest 11,970).
# step pays (a disk-tier step costs the same at 1 token as at 64k). # Calibration keeps the wide 65,536 — grams are not logits, nothing downstream is
# compared against a step size, and that is where a big step pays.
# #
# usage: scripts/run-qwen38-bf16.sh kldref|calib|score RAD OUT [--dry-run] [--image REF] # usage: scripts/run-qwen38-bf16.sh kldref|calib|score RAD OUT [--dry-run] [--image REF]
# (RAD and OUT are score's two positionals: RAD is a container under the models # (RAD and OUT are score's two positionals: RAD is a container under the models
@@ -86,8 +94,9 @@
# bf16 container, kldref only), FRANK_QWEN38_CAMPAIGN (holds kld-ref/ and # bf16 container, kldref only), FRANK_QWEN38_CAMPAIGN (holds kld-ref/ and
# calib/), FRANK_QWEN38_KLD_CORPUS, FRANK_QWEN38_TPL (the template copy # calib/), FRANK_QWEN38_KLD_CORPUS, FRANK_QWEN38_TPL (the template copy
# the container reads through /models), FRANK_QWEN38_LOG_DIR, and # the container reads through /models), FRANK_QWEN38_LOG_DIR, and
# FRANK_QWEN38_MNBT — the step size shared by kldref and score (2,048 is # FRANK_QWEN38_MNBT — the step size shared by kldref and score (default
# the file tier's ceiling and production's own value, four slots and all), # 1,024: the file tier's ceiling is 2,048 and production runs there, but on
# this payload the arena charged for a wider step is taken from the cache),
# and FRANK_QWEN38_MAX_MODEL_LEN — the window every mode boots at (32,768; # and FRANK_QWEN38_MAX_MODEL_LEN — the window every mode boots at (32,768;
# see the corpus census above, and raise it together with the corpus). # see the corpus census above, and raise it together with the corpus).
# kldref refuses (exit 2) when the longest record of the corpus it is about to # kldref refuses (exit 2) when the longest record of the corpus it is about to
@@ -148,12 +157,19 @@ KLD_REF_DIR="$CAMPAIGN/kld-ref"
CALIB_DIR="$CAMPAIGN/calib" CALIB_DIR="$CAMPAIGN/calib"
C=qwen38-bf16 C=qwen38-bf16
SERVICE=frank-archon SERVICE=frank-archon
# The card split, taken from the file that owns it instead of a private pair:
# --host-pool-mib also funds the KV cache (see the header), so hardcoding it
# here once starved the recorder. The `:?` guards keep an empty env fatal.
HOST_POOL_MIB="${FRANK_ARCHON_HOST_POOL_MIB:?FRANK_ARCHON_HOST_POOL_MIB must be set}"
GPU_HEADROOM_MIB="${FRANK_ARCHON_GPU_HEADROOM_MIB:?FRANK_ARCHON_GPU_HEADROOM_MIB must be set}"
# ONE step size for both logits modes. The file tier streams only at <=2,048 and # ONE step size for both logits modes. The file tier streams only at <=2,048 and
# the gate scores file-tier payloads (the served iq4r, the arms) against the # the gate scores file-tier payloads (the served iq4r, the arms) against the
# reference, so the reference is recorded at the ceiling the payloads can meet — # reference, so the reference is recorded at a step the payloads can meet — never
# never two numbers that a KL between them would have to forgive. # two numbers that a KL between them would have to forgive. 1,024 rather than the
MNBT="${FRANK_QWEN38_MNBT:-2048}" # 2,048 ceiling: on this payload the arena charged for the step is the cache's only
# remaining supplier, and 1,024 is the rung that funds every corpus document.
MNBT="${FRANK_QWEN38_MNBT:-1024}"
if ! [[ "$MNBT" =~ ^[0-9]+$ ]]; then if ! [[ "$MNBT" =~ ^[0-9]+$ ]]; then
echo "REFUSED: FRANK_QWEN38_MNBT must be a token count (got: $MNBT)" >&2 echo "REFUSED: FRANK_QWEN38_MNBT must be a token count (got: $MNBT)" >&2
exit 2 exit 2
@@ -201,7 +217,7 @@ fi
# The measuring shape. Identical for kldref and score by construction — this # The measuring shape. Identical for kldref and score by construction — this
# array is the only place the campaign's KL numbers are defined. # array is the only place the campaign's KL numbers are defined.
ARGS=(--tp 2 --placement expert_tiered --weights-disk-tier ARGS=(--tp 2 --placement expert_tiered --weights-disk-tier
--host-pool-mib 12288 --gpu-headroom-mib 768 --host-pool-mib "$HOST_POOL_MIB" --gpu-headroom-mib "$GPU_HEADROOM_MIB"
--deterministic --num-speculative-tokens 0 --deterministic --num-speculative-tokens 0
--kv-cache-dtype fp8 --max-model-len "$MAX_MODEL_LEN" --kv-cache-dtype fp8 --max-model-len "$MAX_MODEL_LEN"
--ngram-placement ram --ngram-placement ram
@@ -210,9 +226,10 @@ MOUNTS=(-v "$MODELS:/models:ro")
TAPENV=() TAPENV=()
case "$MODE" in case "$MODE" in
kldref) kldref)
# Recording is prefill-only over four slots and the shared step, which is # Recording is prefill-only over four slots at the campaign's single step size
# production's own operating point (frank-archon.env: FRANK_ARCHON_MAX_SEQS=4 # — 1,024, not production's 2,048 (frank-archon.env: FRANK_ARCHON_MAX_SEQS=4,
# with FRANK_ARCHON_MAX_BATCHED_TOKENS=2048). Corpus read-only, record dir rw. # FRANK_ARCHON_MAX_BATCHED_TOKENS=2048): the wider step costs the cache the
# room the corpus needs. Corpus read-only, record dir rw.
ARGS+=(--model "$MODEL_IN" ARGS+=(--model "$MODEL_IN"
--kld-record /data/kld-ref --kld-corpus /data/kld.jsonl --kld-record /data/kld-ref --kld-corpus /data/kld.jsonl
--max-num-seqs 4 --max-num-batched-tokens "$MNBT") --max-num-seqs 4 --max-num-batched-tokens "$MNBT")
+1 -1
View File
@@ -29,7 +29,7 @@ RUNNER="$REPO_ROOT/scripts/run-qwen38-bf16.sh"
# The reference shape, spelled exactly as run-qwen38-bf16.sh must pass it: this # The reference shape, spelled exactly as run-qwen38-bf16.sh must pass it: this
# is the contract the recorded reference is measured at, so a silent edit here # is the contract the recorded reference is measured at, so a silent edit here
# is a silent edit to every KLD number in the campaign. # is a silent edit to every KLD number in the campaign.
SHAPE="--tp 2 --placement expert_tiered --weights-disk-tier --host-pool-mib 12288 --gpu-headroom-mib 768 --deterministic --num-speculative-tokens 0 --kv-cache-dtype fp8 --max-model-len 32768 --ngram-placement ram --override-chat-template /models/tpl.jinja" SHAPE="--tp 2 --placement expert_tiered --weights-disk-tier --host-pool-mib 49152 --gpu-headroom-mib 96 --deterministic --num-speculative-tokens 0 --kv-cache-dtype fp8 --max-model-len 32768 --ngram-placement ram --override-chat-template /models/tpl.jinja"
# oneline — both dry runs print one shell-quoted argument a line; joining them # oneline — both dry runs print one shell-quoted argument a line; joining them
# lets an option and its value be asserted as the pair they are. # lets an option and its value be asserted as the pair they are.