feat(qwen38): the w5nl64 heavier-quant campaign toolchain (fetch, BF16 reference, arms, FP8-anchored gate) #76
No files matched your search
@@ -1,27 +1,45 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# apply-protected-cast.sh — RUNS ON DANKSERVER. Rewrite the recipe's
|
# apply-protected-cast.sh — RUNS ON DANKSERVER. Rewrite a recipe's
|
||||||
# PROTECTED-BEGIN/END section from Swift's OWN calibration grams: run
|
# PROTECTED-BEGIN/END section from the campaign's OWN calibration grams: run
|
||||||
# derive-protected-experts.py over the calib dir, splice its cast lines into
|
# derive-protected-experts.py over the calib dir, splice its cast lines into
|
||||||
# the marked section of the recipe in place, keep the pre-edit recipe in the
|
# the marked section of the recipe in place, keep the pre-edit recipe in the
|
||||||
# campaign trash. Refuses to run twice (marker text says derived).
|
# campaign trash. Refuses to run twice (marker text says derived), and refuses
|
||||||
|
# when a requested target count was not reached. Run it on a COPY of the
|
||||||
|
# template so the committed recipe stays the clean, reviewable baseline.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
CALIB=${1:-/home/dank/corpora/calib}
|
SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
RECIPE=${2:-/home/dank/serverAppConfigs-swift15/scripts/recipes/qwen4exp-swift15-w4dn8-i8-hc8m.recipe}
|
CALIB=${1:-/home/dank/campaign/qwen38-w5nl64/calib}
|
||||||
PY=/home/dank/venvs/radcorpus/bin/python
|
RECIPE=${2:-$SELF_DIR/recipes/qwen4exp-qwen38-w5nl64-bf16trunk.recipe}
|
||||||
DERIVE=/home/dank/serverAppConfigs-swift15/scripts/derive-protected-experts.py
|
TARGET_COUNT=${3:-0}
|
||||||
|
PY=${PY:-/home/dank/venvs/radcorpus/bin/python}
|
||||||
|
DERIVE=${DERIVE:-$SELF_DIR/derive-protected-experts.py}
|
||||||
TS=$(date +%Y%m%d-%H%M%S)
|
TS=$(date +%Y%m%d-%H%M%S)
|
||||||
|
# OUT and TRASH_DIR are overridable so the seam can drive the whole script in a
|
||||||
|
# scratch directory; on the box they are the campaign's usual places.
|
||||||
|
OUT=${OUT:-/tmp/protected-cast-$TS.txt}
|
||||||
|
TRASH_DIR=${TRASH_DIR:-/home/dank/.AGENTTRASH}
|
||||||
|
|
||||||
grep -q "derive-protected-experts.py rewrote" "$RECIPE" && {
|
grep -q "derive-protected-experts.py rewrote" "$RECIPE" && {
|
||||||
echo "recipe already carries derived cast lines; refusing to re-derive" >&2; exit 1; }
|
echo "recipe already carries derived cast lines; refusing to re-derive" >&2; exit 1; }
|
||||||
grep -q "PROTECTED-BEGIN" "$RECIPE" || { echo "no PROTECTED markers in $RECIPE" >&2; exit 1; }
|
grep -q "PROTECTED-BEGIN" "$RECIPE" || { echo "no PROTECTED markers in $RECIPE" >&2; exit 1; }
|
||||||
|
|
||||||
"$PY" "$DERIVE" "$CALIB" > "/tmp/protected-cast-$TS.txt"
|
ARGS=("$DERIVE" "$CALIB")
|
||||||
N=$(grep -c 'cast dtype=bf16' "/tmp/protected-cast-$TS.txt")
|
if [ "$TARGET_COUNT" -gt 0 ]; then
|
||||||
[ "$N" -ge 2 ] || { echo "derive produced $N cast lines; refusing" >&2; exit 1; }
|
ARGS+=(--target-count "$TARGET_COUNT")
|
||||||
echo "derived $N protected experts -> /tmp/protected-cast-$TS.txt"
|
fi
|
||||||
|
"$PY" "${ARGS[@]}" > "$OUT"
|
||||||
|
N=$(grep -c 'cast dtype=bf16' "$OUT")
|
||||||
|
[ "$N" -ge 2 ] || { echo "derive produced $N cast lines; refusing (see $OUT)" >&2; exit 1; }
|
||||||
|
if [ "$TARGET_COUNT" -gt 0 ] && [ "$N" -lt "$TARGET_COUNT" ]; then
|
||||||
|
echo "derive reached $N protected experts, target was $TARGET_COUNT; refusing to convert" >&2
|
||||||
|
echo " (the bar sweep bottomed out: the recording is too thin or the target too high)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "derived $N protected experts -> $OUT"
|
||||||
|
|
||||||
cp -f "$RECIPE" "/home/dank/.AGENTTRASH/$TS-qwen4exp-swift15-w4dn8-i8-hc8m.recipe"
|
mkdir -p "$TRASH_DIR"
|
||||||
"$PY" - "$RECIPE" "/tmp/protected-cast-$TS.txt" <<'PYEOF'
|
cp -f "$RECIPE" "$TRASH_DIR/$TS-$(basename "$RECIPE")"
|
||||||
|
"$PY" - "$RECIPE" "$OUT" <<'PYEOF'
|
||||||
import sys
|
import sys
|
||||||
recipe, castfile = sys.argv[1], sys.argv[2]
|
recipe, castfile = sys.argv[1], sys.argv[2]
|
||||||
lines = open(recipe).read().splitlines()
|
lines = open(recipe).read().splitlines()
|
||||||
@@ -35,7 +53,7 @@ for l in lines:
|
|||||||
elif 'PROTECTED-END' in l:
|
elif 'PROTECTED-END' in l:
|
||||||
insec = False
|
insec = False
|
||||||
out.append(l)
|
out.append(l)
|
||||||
out.append('# (derive-protected-experts.py rewrote this section from Swift\'s own grams)')
|
out.append('# (derive-protected-experts.py rewrote this section from the campaign grams)')
|
||||||
elif not insec:
|
elif not insec:
|
||||||
out.append(l)
|
out.append(l)
|
||||||
open(recipe, 'w').write('\n'.join(out) + '\n')
|
open(recipe, 'w').write('\n'.join(out) + '\n')
|
||||||
|
|||||||
Executable
+220
@@ -0,0 +1,220 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# build-qwen38-bf16-rad.sh — RUNS ON DANKSERVER. rad-convert the vendor BF16
|
||||||
|
# checkpoint /home/dank/models/Qwen3.8-Flash-Next into
|
||||||
|
# /home/dank/models/rad/qwen38-bf16.rad with NO recipe and NO --reuse: a weight
|
||||||
|
# no rule matches keeps the checkpoint's encoding, so every tensor stays the
|
||||||
|
# bf16 it is in the safetensors and the container is the checkpoint's own
|
||||||
|
# numbers behind a .rad header.
|
||||||
|
#
|
||||||
|
# This file is the campaign's measuring stick, twice over: scripts/
|
||||||
|
# run-qwen38-bf16.sh kldref records the KLD reference logits from it (every
|
||||||
|
# arm's --kld-ref is scored against that record) and the same container serves
|
||||||
|
# with the calibration tap to write the Grams that derive the protected-expert
|
||||||
|
# list and feed GPTQ. A reference that is not the vendor checkpoint, byte for
|
||||||
|
# byte in bf16, silently moves every gate in the campaign — hence no recipe, no
|
||||||
|
# --reuse, and the four refusal gates below.
|
||||||
|
#
|
||||||
|
# Why a container at all: a checkpoint dir has no mapped/file tiers, and a
|
||||||
|
# weight only a host kernel reads (the 47.7 GiB PLE n-gram table) cannot be
|
||||||
|
# served from one; rad-convert also bakes the tokenizer. The convert runs on
|
||||||
|
# the HOST backend — no --device /dev/kfd, no /dev/dri — so declare resolves
|
||||||
|
# kernels on CPU and production serving is untouched for the whole run (the
|
||||||
|
# precedent that proved it: scripts/convert-swift15-w4dn8.sh).
|
||||||
|
#
|
||||||
|
# Gates, every one a refusal with exit 2 and a named reason, BEFORE anything is
|
||||||
|
# written, cheapest first — the verifier runs last of them because its Hub gate
|
||||||
|
# hashes 343 GiB and must not be paid for by a run that was going to refuse on
|
||||||
|
# the output file anyway.
|
||||||
|
# checkpoint the tree must be on disk; an absent dir names the command that
|
||||||
|
# makes one (scripts/download-qwen38-bf16.sh --fetch) and stops.
|
||||||
|
# free space the convert writes ~343,323 MiB next to the ~343,323 MiB
|
||||||
|
# checkpoint; QWEN38_MIN_FREE_MIB (default 350000 = that figure
|
||||||
|
# plus 6.5 GiB of slack) is the floor. The volume that cannot
|
||||||
|
# hold both is discovered at minute 2, not hour 5.
|
||||||
|
# output the campaign converts the reference ONCE. An existing
|
||||||
|
# qwen38-bf16.rad is refused; --force retires it to
|
||||||
|
# ~/.AGENTTRASH/<utc>-qwen38-bf16.rad (trash law: nothing is
|
||||||
|
# deleted, and the move is a rename on the same volume).
|
||||||
|
# verified scripts/download-qwen38-bf16.sh's verify mode must pass. A
|
||||||
|
# failed verify exits 1, not 2: the tree being the wrong payload
|
||||||
|
# is a result, a missing environment is a refusal.
|
||||||
|
# plan rad-convert --plan-only -v first — the oracle for "does this
|
||||||
|
# shape boot". A refused plan costs seconds and saves a convert.
|
||||||
|
#
|
||||||
|
# usage: scripts/build-qwen38-bf16-rad.sh [--dry-run] [--plan-only] [--force]
|
||||||
|
# --dry-run print the argv it would exec (podman wrapping rad-convert),
|
||||||
|
# one shell-quoted argument a line, and exit 0. No gate runs, no
|
||||||
|
# container starts, nothing is written — the offline test seam.
|
||||||
|
# It does resolve the checkpoint and output paths through the
|
||||||
|
# /models mount, because an argv that carries host paths would
|
||||||
|
# not run either.
|
||||||
|
# --plan-only run the plan and every gate except the disk and output ones —
|
||||||
|
# those two protect bytes the plan never writes, and this is the
|
||||||
|
# probe to run when the disk gate itself is what refuses
|
||||||
|
# --force retire an existing output container to the trash dir first
|
||||||
|
#
|
||||||
|
# knobs: FRANK_QWEN38_MODELS the host dir mounted at /models (default
|
||||||
|
# /home/dank/models); FRANK_QWEN38_CKPT (default $MODELS/
|
||||||
|
# Qwen3.8-Flash-Next, the same knob the verifier reads);
|
||||||
|
# FRANK_QWEN38_BF16_OUT (default $MODELS/rad/qwen38-bf16.rad);
|
||||||
|
# QWEN38_MIN_FREE_MIB; QWEN38_TRASH (default $HOME/.AGENTTRASH). The
|
||||||
|
# verifier's own census knobs (FRANK_QWEN38_SHARDS / _BYTES / _REPO)
|
||||||
|
# reach it through the environment untouched.
|
||||||
|
# exit: 0 converted (or planned), 1 the checkpoint failed verification,
|
||||||
|
# 2 refused before anything was written
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# ONE owner of the engine pin: the digest frank-archon serves on. The reference
|
||||||
|
# must be built by the SAME engine build that scores it, or a KLD delta is
|
||||||
|
# partly a version delta.
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/frank-archon.env"
|
||||||
|
IMAGE="${FRANK_ARCHON_IMAGE:?FRANK_ARCHON_IMAGE must name the radiance image}"
|
||||||
|
|
||||||
|
MODELS="${FRANK_QWEN38_MODELS:-/home/dank/models}"
|
||||||
|
CKPT="${FRANK_QWEN38_CKPT:-$MODELS/Qwen3.8-Flash-Next}"
|
||||||
|
OUT="${FRANK_QWEN38_BF16_OUT:-$MODELS/rad/qwen38-bf16.rad}"
|
||||||
|
TRASH="${QWEN38_TRASH:-$HOME/.AGENTTRASH}"
|
||||||
|
MIN_FREE_MIB="${QWEN38_MIN_FREE_MIB:-350000}"
|
||||||
|
LOG="/home/dank/logs/qwen38_bf16_convert.log"
|
||||||
|
|
||||||
|
DRY=0
|
||||||
|
PLAN_ONLY=0
|
||||||
|
FORCE=0
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--dry-run) DRY=1 ;;
|
||||||
|
--plan-only) PLAN_ONLY=1 ;;
|
||||||
|
--force) FORCE=1 ;;
|
||||||
|
*) echo "usage: ${0##*/} [--dry-run] [--plan-only] [--force]" >&2
|
||||||
|
exit 2 ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# The convert sees the tree only through the /models mount, so both paths must
|
||||||
|
# be under it; the container-side spelling is what goes into the argv.
|
||||||
|
case "$CKPT" in
|
||||||
|
"$MODELS"/*) CKPT_IN="/models/${CKPT#"$MODELS"/}" ;;
|
||||||
|
*) echo "REFUSED: checkpoint $CKPT is not under the mounted models dir $MODELS — the convert cannot see it" >&2
|
||||||
|
exit 2 ;;
|
||||||
|
esac
|
||||||
|
case "$OUT" in
|
||||||
|
"$MODELS"/*) OUT_IN="/models/${OUT#"$MODELS"/}" ;;
|
||||||
|
*) echo "REFUSED: output $OUT is not under the mounted models dir $MODELS — rad-convert could not write it" >&2
|
||||||
|
exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Declare with NO recipe line — that is what keeps every tensor bf16 — and at the
|
||||||
|
# bound this container will actually be served at. Both flags come off the pinned
|
||||||
|
# image's own help: --kv-cache-dtype at convert "only picks the kernels it
|
||||||
|
# checks" and the container is identical either way, so naming fp8 is free
|
||||||
|
# insurance — if the fp8 KV kernels the fleet serves would not resolve, the
|
||||||
|
# convert refuses here instead of the boot failing on a drained window.
|
||||||
|
# --max-ctx is the bound declare is run at, whose default is the checkpoint's
|
||||||
|
# training context (262,144 here, whose fp8 cache is 258.8 GiB against a 192 GiB
|
||||||
|
# machine, so the default is not an option). 32,768 is set by the corpus, not by
|
||||||
|
# taste: kld.jsonl's longest record is 38,263 characters — ~9,600 tokens at the
|
||||||
|
# pessimistic chars/4, ~7,700 at the chars/4.97 this repo measured on the K3
|
||||||
|
# corpus — so the window the rig boots at clears every record 2.6x over and the
|
||||||
|
# reference boot cannot die on a sequence that will not fit. The rig's window is
|
||||||
|
# one knob (FRANK_QWEN38_MAX_MODEL_LEN in scripts/run-qwen38-bf16.sh, default
|
||||||
|
# 32,768) and that script refuses a corpus its window cannot hold, so raising one
|
||||||
|
# without the other is caught before the drained window, not inside it. The
|
||||||
|
# payload containers the gate scores declare at the training context because
|
||||||
|
# production must hold the 200k shape; a declared bound is a declare-time check,
|
||||||
|
# not a weight change, so the reference and the payloads carry the same numbers
|
||||||
|
# whatever each declared at — which is why one can be measured against the other.
|
||||||
|
CONVERT_ARGS=("$CKPT_IN" -o "$OUT_IN" --max-ctx 32768 --kv-cache-dtype fp8)
|
||||||
|
PODMAN=(podman run --rm --entrypoint rad-convert -v "$MODELS:/models")
|
||||||
|
PLAN_CMD=("${PODMAN[@]}" --name qwen38-bf16-plan "$IMAGE" "${CONVERT_ARGS[@]}" --plan-only -v)
|
||||||
|
CONV_CMD=("${PODMAN[@]}" --name qwen38-bf16-convert "$IMAGE" "${CONVERT_ARGS[@]}")
|
||||||
|
|
||||||
|
if [ "$DRY" = "1" ]; then
|
||||||
|
echo "dry run: no gate checked, no container started, nothing written"
|
||||||
|
echo "# $CKPT -> $OUT (the convert sees them as $CKPT_IN and $OUT_IN)"
|
||||||
|
echo "# plan argv:"
|
||||||
|
printf '%q\n' "${PLAN_CMD[@]}"
|
||||||
|
echo "# convert argv:"
|
||||||
|
printf '%q\n' "${CONV_CMD[@]}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- gate: the checkpoint is on disk ---------------------------------------
|
||||||
|
if [ ! -d "$CKPT" ]; then
|
||||||
|
echo "REFUSED: no checkpoint at $CKPT — fetch and verify it first: scripts/download-qwen38-bf16.sh --fetch" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- gate: peak disk --------------------------------------------------------
|
||||||
|
# The deepest existing ancestor is the right thing to ask: a fresh
|
||||||
|
# /home/dank/models/rad has no free space of its own to report.
|
||||||
|
OUT_DIR="$(dirname "$OUT")"
|
||||||
|
PROBE="$OUT_DIR"
|
||||||
|
while [ ! -d "$PROBE" ] && [ "$PROBE" != "/" ]; do PROBE="$(dirname "$PROBE")"; done
|
||||||
|
FREE_MIB="$(df --output=avail -m "$PROBE" | tail -1 | tr -d ' ')"
|
||||||
|
if ! [[ "$FREE_MIB" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "REFUSED: cannot read the free space under $PROBE (df said: $FREE_MIB)" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if [ "$PLAN_ONLY" != "1" ] && [ "$FREE_MIB" -lt "$MIN_FREE_MIB" ]; then
|
||||||
|
echo "REFUSED peak-disk: $FREE_MIB MiB free under $PROBE, $MIN_FREE_MIB MiB wanted (QWEN38_MIN_FREE_MIB) — the bf16 container rewrites the checkpoint's own 343,323 MiB of tensors beside it" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- gate: one conversion ---------------------------------------------------
|
||||||
|
if [ -e "$OUT" ]; then
|
||||||
|
if [ "$PLAN_ONLY" = "1" ]; then
|
||||||
|
echo "note: $OUT already exists; --plan-only writes nothing, so it stays"
|
||||||
|
elif [ "$FORCE" = "1" ]; then
|
||||||
|
mkdir -p "$TRASH"
|
||||||
|
DEST="$TRASH/$(date -u +%Y%m%dT%H%M%SZ)-$(basename "$OUT")"
|
||||||
|
mv "$OUT" "$DEST"
|
||||||
|
echo "TRASHED: retired the existing reference container $OUT -> $DEST"
|
||||||
|
else
|
||||||
|
echo "REFUSED: $OUT already exists — the campaign converts the reference once; re-run with --force to retire it to $TRASH first" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- gate: the tree really is the checkpoint (the full verify) -------------
|
||||||
|
echo "== verifying $CKPT (scripts/download-qwen38-bf16.sh, full gates)"
|
||||||
|
rc=0
|
||||||
|
bash "$SCRIPT_DIR/download-qwen38-bf16.sh" --dir "$CKPT" || rc=$?
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
if [ "$rc" -eq 2 ]; then
|
||||||
|
echo "REFUSED: the checkpoint verifier refused the environment (rc=2); nothing was converted" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "REFUSED: $CKPT failed verification (rc=$rc) — a half tree converts to a half reference" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- plan, then convert ------------------------------------------------------
|
||||||
|
mkdir -p "$(dirname "$LOG")"
|
||||||
|
# rad-convert opens -o during declare as well, so the output directory exists
|
||||||
|
# before the plan; that empty directory is all this creates ahead of it.
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
echo "== rad-convert --plan-only -v (declare + plan; writes nothing)"
|
||||||
|
"${PLAN_CMD[@]}" 2>&1 | tee -a "$LOG"
|
||||||
|
rc=${PIPESTATUS[0]}
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
echo "REFUSED plan: rad-convert --plan-only exited rc=$rc — the engine refuses this encoding/shape, no container was written (log: $LOG)" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if [ "$PLAN_ONLY" = "1" ]; then
|
||||||
|
echo "PLAN_OK: $CKPT_IN declares for $OUT_IN; no container written (log: $LOG)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== convert: $CKPT -> $OUT ($(date -Is))"
|
||||||
|
"${CONV_CMD[@]}" 2>&1 | tee -a "$LOG"
|
||||||
|
rc=${PIPESTATUS[0]}
|
||||||
|
if [ "$rc" -ne 0 ]; then
|
||||||
|
echo "rad-convert rc=$rc; log: $LOG" >&2
|
||||||
|
exit "$rc"
|
||||||
|
fi
|
||||||
|
MIB=$(( $(stat -c %s "$OUT") / 1048576 ))
|
||||||
|
echo "BUILD_DONE: $OUT is ${MIB} MiB (log: $LOG)"
|
||||||
|
echo "NEXT: scripts/run-qwen38-bf16.sh kldref records the reference logits from it"
|
||||||
Executable
+221
@@ -0,0 +1,221 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# convert-qwen38-w5nl64.sh — RUNS ON DANKSERVER. The CPU half of the Qwen3.8-Flash-Next five-bit
|
||||||
|
# campaign: convert the vendor BF16 checkpoint into the campaign's arms, each one the whole
|
||||||
|
# `w5nl64a8h` rung on BOTH expert projections with the protected experts kept bf16.
|
||||||
|
#
|
||||||
|
# Nothing here touches a GPU: the container is started without /dev/kfd or /dev/dri, so the
|
||||||
|
# converter runs on the host backend and the fleet can keep serving through the whole script.
|
||||||
|
# The engine pin is the digest that serves frank-archon, so a winning arm crosses no version
|
||||||
|
# boundary between measurement and serving.
|
||||||
|
#
|
||||||
|
# Three arms, because the campaign's question is which search knob pays for its hours
|
||||||
|
# (recipes/qwen4exp-qwen38-w5nl64-bf16trunk.recipe):
|
||||||
|
#
|
||||||
|
# cd3 the recipe as written — coordinate descent three sweeps. The control.
|
||||||
|
# cd6 six sweeps: the same search, twice the passes.
|
||||||
|
# ao1 act_order=1 instead of cd: GPTQ sweeps columns in activation-importance order and
|
||||||
|
# stores them permuted. This is a SEPARATE arm, not a fourth sweep — libquant refuses
|
||||||
|
# cd= together with act_order=1 ("cd= sweeps the columns in their stored order, and
|
||||||
|
# act_order=1 stores them permuted; one or the other", libquant/lq_registry.cpp), so
|
||||||
|
# the two knobs cannot be combined into a best-of-both container.
|
||||||
|
#
|
||||||
|
# The arms are generated from ONE template with `--quant` overrides, not three near-duplicate
|
||||||
|
# recipes: --quant rules are inserted BEFORE the recipe file and the first rule whose glob matches
|
||||||
|
# a weight decides it (core/format/recipe.cpp: Recipe::match), so an override cleanly replaces the
|
||||||
|
# expert line and the other 60 lines cannot drift between arms.
|
||||||
|
#
|
||||||
|
# Protected experts: apply-protected-cast.sh derives the bf16 set from THIS campaign's grams
|
||||||
|
# (--target-count 48, two per layer so each rank's 256-expert half stays even) and splices it into
|
||||||
|
# a per-run COPY of the template; the committed recipe keeps its markers and stays reviewable.
|
||||||
|
#
|
||||||
|
# Gates, in the order they bite — each one is a lesson the swift15 campaign paid for:
|
||||||
|
# inputs checkpoint (dir + index), recipe, grams, imatrix, tokenizer. A missing grams
|
||||||
|
# directory converts UNCALIBRATED with one stderr note, which is silently worse
|
||||||
|
# than failing here.
|
||||||
|
# reference the 335.3 GiB BF16 reference container must be GONE before the converts start.
|
||||||
|
# It and the checkpoint together eat the volume; its job (the KLD recording and
|
||||||
|
# the calibration grams) is finished by then, and both survive on disk without the
|
||||||
|
# container beside it. The FP8-native anchor is FETCHED after the converts (disk
|
||||||
|
# sequencing), so nothing here waits on it.
|
||||||
|
# plan --plan-only runs FIRST, per arm, and prints "about X would be written". The
|
||||||
|
# number is the bill: free space must cover it plus the 128k-prompt overshoot
|
||||||
|
# margin, and the finished artifact is checked against it.
|
||||||
|
# outputs no arm overwrites anything; move the old one to trash.
|
||||||
|
#
|
||||||
|
# Usage: convert-qwen38-w5nl64.sh [--arm cd3|cd6|ao1]... [--dry-run] [--plan-only]
|
||||||
|
# [--target-count N] [--keep-recipe]
|
||||||
|
set -u -o pipefail
|
||||||
|
|
||||||
|
SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
IMAGE=${IMAGE:-stilldeadcode/radiance@sha256:d590e300234bf6d2f942a0b8d456d183153fd808f28820e6c2fa69c9389178ed}
|
||||||
|
CKPT=${CKPT:-/home/dank/models/Qwen3.8-Flash-Next}
|
||||||
|
CAMPAIGN=${CAMPAIGN:-/home/dank/campaign/qwen38-w5nl64}
|
||||||
|
RECIPE=${RECIPE:-$SELF/recipes/qwen4exp-qwen38-w5nl64-bf16trunk.recipe}
|
||||||
|
CALIB=${CALIB:-$CAMPAIGN/calib}
|
||||||
|
IMATRIX=${IMATRIX:-$CAMPAIGN/imatrix.gguf}
|
||||||
|
OUTDIR=${OUTDIR:-/home/dank/models/rad}
|
||||||
|
BF16_RAD=${BF16_RAD:-$OUTDIR/qwen38-bf16.rad} # the reference container, not an arm output
|
||||||
|
STATE=${STATE:-$CAMPAIGN} # the dir the container mounts read-only: the
|
||||||
|
# spliced recipe and the imatrix live here
|
||||||
|
TARGET_COUNT=${TARGET_COUNT:-48}
|
||||||
|
MIN_GRAMS=${MIN_GRAMS:-24576} # per-expert DOWN grams: 48 recorded layers x 512
|
||||||
|
# experts. The checkpoint has 49 MoE layers, but the
|
||||||
|
# MTP layer is never recorded, so its 512 experts are
|
||||||
|
# absent from the tap's output by design
|
||||||
|
MARGIN_MIB=${MARGIN_MIB:-40960} # the long-prompt overshoot, off the plan number
|
||||||
|
TS=$(date +%Y%m%d-%H%M%S)
|
||||||
|
|
||||||
|
ARMS=()
|
||||||
|
DRY=0
|
||||||
|
PLAN_ONLY=0
|
||||||
|
KEEP_RECIPE=0
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--arm) ARMS+=("$2"); shift 2 ;;
|
||||||
|
--dry-run) DRY=1; shift ;;
|
||||||
|
--plan-only) PLAN_ONLY=1; shift ;;
|
||||||
|
--target-count) TARGET_COUNT="$2"; shift 2 ;;
|
||||||
|
--keep-recipe) KEEP_RECIPE=1; shift ;;
|
||||||
|
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[ "${#ARMS[@]}" -gt 0 ] || ARMS=(cd3 cd6 ao1)
|
||||||
|
for a in "${ARMS[@]}"; do
|
||||||
|
case "$a" in cd3|cd6|ao1) ;; *) echo "unknown arm: $a (cd3|cd6|ao1)" >&2; exit 2 ;; esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# The expert rule's option set, spelled once. --quant replaces the template's line wholesale, so
|
||||||
|
# every option has to be repeated here; `calib=$CALIB` is expanded by the recipe reader from the
|
||||||
|
# container environment (core/format/recipe.cpp: expand refuses an unset name rather than taking
|
||||||
|
# it for empty).
|
||||||
|
expert_opts='table=w5nl,group=64,scale=fp8_e4m3,scale2=f32,block2=*x*,scale2_value=0.0001220703125,transform=fwht128,rule=search,calib=$CALIB'
|
||||||
|
arm_quant() {
|
||||||
|
case "$1" in
|
||||||
|
# The control is the template itself: no override, so the recipe's own line decides.
|
||||||
|
cd3) return 0 ;;
|
||||||
|
cd6) echo "blk.*.ffn_*_exps.*.weight=gptq:${expert_opts},cd=6" ;;
|
||||||
|
# act_order=1 WITHOUT cd: libquant refuses the pair (lq_registry.cpp:105, "cd= sweeps the
|
||||||
|
# columns in their stored order, and act_order=1 stores them permuted; one or the other"), so
|
||||||
|
# this arm is the interleave knob alone, not cd=3 plus it.
|
||||||
|
ao1) echo "blk.*.ffn_*_exps.*.weight=gptq:${expert_opts},act_order=1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- gates ------------------------------------------------------------------------------------
|
||||||
|
[ -d "$CKPT" ] || { echo "no checkpoint at $CKPT; run scripts/download-qwen38-bf16.sh --fetch" >&2; exit 2; }
|
||||||
|
[ -f "$CKPT/model.safetensors.index.json" ] || { echo "checkpoint at $CKPT has no safetensors index — a torn fetch" >&2; exit 2; }
|
||||||
|
[ -f "$CKPT/tokenizer.json" ] || { echo "no tokenizer.json in $CKPT; the .rad cannot carry one without it" >&2; exit 2; }
|
||||||
|
[ -f "$RECIPE" ] || { echo "no recipe at $RECIPE" >&2; exit 2; }
|
||||||
|
GRAMS=$(find "$CALIB" -maxdepth 1 -name 'gram.r*.bin' 2>/dev/null | wc -l)
|
||||||
|
[ "$GRAMS" -ge "$MIN_GRAMS" ] || {
|
||||||
|
echo "calib dir $CALIB holds $GRAMS gram files (<$MIN_GRAMS); run $0 after run-qwen38-bf16.sh calib" >&2; exit 2; }
|
||||||
|
[ -f "$IMATRIX" ] || { echo "no imatrix at $IMATRIX; run scripts/grams-to-imatrix.py over $CALIB" >&2; exit 2; }
|
||||||
|
# The container sees the campaign dir at one fixed mount point, so an imatrix kept elsewhere has
|
||||||
|
# no path inside it: copy it in (or point IMATRIX at the campaign dir) rather than let the
|
||||||
|
# converter fail halfway through a planned arm.
|
||||||
|
[ "$(dirname "$IMATRIX")" = "$STATE" ] || {
|
||||||
|
echo "imatrix $IMATRIX is not in the campaign dir $STATE the container mounts; move it there" >&2; exit 2; }
|
||||||
|
if [ -e "$BF16_RAD" ]; then
|
||||||
|
echo "the 335.3 GiB BF16 reference container $BF16_RAD is still on disk; its recording and" \
|
||||||
|
"the calibration grams are already taken, so move it to trash — the converts need its" \
|
||||||
|
"space (peak-disk law)" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
for a in "${ARMS[@]}"; do
|
||||||
|
[ -e "$OUTDIR/qwen38-w5nl64-$a.rad" ] && {
|
||||||
|
echo "$OUTDIR/qwen38-w5nl64-$a.rad already exists; move it to trash first" >&2; exit 2; }
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- the protected plane, spliced into a per-run copy of the template --------------------------
|
||||||
|
mkdir -p "$STATE"
|
||||||
|
WORK="$STATE/recipe-$TS.recipe"
|
||||||
|
cp "$RECIPE" "$WORK"
|
||||||
|
if [ "$DRY" = 1 ]; then
|
||||||
|
echo "would run: $SELF/apply-protected-cast.sh $CALIB $WORK $TARGET_COUNT"
|
||||||
|
else
|
||||||
|
# Positional, because the tool reads its recipe and calib dir by position: passing them as
|
||||||
|
# environment variables would be silently ignored and it would rewrite the COMMITTED
|
||||||
|
# template in place instead of this run's copy.
|
||||||
|
"$SELF/apply-protected-cast.sh" "$CALIB" "$WORK" "$TARGET_COUNT" \
|
||||||
|
|| { echo "the protected-expert derivation refused; the arms would convert unprotected" >&2; exit 1; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_convert() { # run_convert ARM [extra flags...]
|
||||||
|
local arm="$1"; shift
|
||||||
|
local out="$OUTDIR/qwen38-w5nl64-$arm.rad" log="$STATE/convert-$arm-$TS.log"
|
||||||
|
local argv=(--rm --name "qwen38-convert-$arm"
|
||||||
|
-e "CALIB=/data/calib" -e "OMP_NUM_THREADS=$(nproc)"
|
||||||
|
-v "$(dirname "$CKPT"):/models:ro"
|
||||||
|
-v "$CALIB:/data/calib:ro"
|
||||||
|
-v "$STATE:/campaign:ro"
|
||||||
|
-v "$OUTDIR:/out")
|
||||||
|
local conv=(/models/"${CKPT##*/}" --recipe "/campaign/$(basename "$WORK")"
|
||||||
|
--tokenizer "/models/${CKPT##*/}/tokenizer.json"
|
||||||
|
--imatrix /campaign/"${IMATRIX##*/}"
|
||||||
|
# No --max-ctx: the context declare then runs at the checkpoint's training context
|
||||||
|
# (262144), which is what the shipped container did — rad-info --meta on it carries
|
||||||
|
# no override. A container declared at a smaller bound cannot be expected to serve
|
||||||
|
# the 200k window D2 holds, and the convert is not where to discover that.
|
||||||
|
# --kv-cache-dtype changes nothing in the artifact; it picks the kernels the declare
|
||||||
|
# checks, and the fleet serves fp8 KV, so an unresolvable fp8 kernel must be said no
|
||||||
|
# to by the convert, not by a boot on a drained window.
|
||||||
|
--kv-cache-dtype fp8 -o "/out/$(basename "$out")")
|
||||||
|
local q; q=$(arm_quant "$arm")
|
||||||
|
[ -n "$q" ] && conv+=(--quant "$q")
|
||||||
|
if [ "$DRY" = 1 ]; then
|
||||||
|
echo "podman run ${argv[*]} \$IMAGE --entrypoint /opt/radiance/bin/rad-convert ${conv[*]} $*"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
podman run "${argv[@]}" --entrypoint /opt/radiance/bin/rad-convert "$IMAGE" "${conv[@]}" "$@" \
|
||||||
|
2>&1 | tee "$log"
|
||||||
|
return "${PIPESTATUS[0]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- plan, then bill, then convert -------------------------------------------------------------
|
||||||
|
overall=0
|
||||||
|
for arm in "${ARMS[@]}"; do
|
||||||
|
echo "== arm $arm =="
|
||||||
|
if [ "$DRY" = 1 ]; then
|
||||||
|
# Both lines, in order: the plan runs before the convert, and the convert is what the
|
||||||
|
# operator is about to pay an afternoon for.
|
||||||
|
run_convert "$arm" --plan-only
|
||||||
|
run_convert "$arm"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
rc=0; plan_log=$(run_convert "$arm" --plan-only) || rc=$?
|
||||||
|
[ "$rc" -eq 0 ] || { echo "arm $arm: --plan-only failed (rc=$rc); nothing was written" >&2; overall=1; continue; }
|
||||||
|
echo "$plan_log"
|
||||||
|
# "about 141.2 GiB would be written" is the converter's own bill; parse it rather than
|
||||||
|
# hardcoding a size, so the gate still works after any recipe edit.
|
||||||
|
want=$(printf '%s' "$plan_log" | sed -n 's/.*about \([0-9.]*\) \([KMGT]iB\) would be written.*/\1 \2/p' | tail -1)
|
||||||
|
[ -n "$want" ] || { echo "arm $arm: the plan printed no size; refusing to convert blind" >&2; overall=1; continue; }
|
||||||
|
need_mib=$(awk -v n="${want% *}" -v u="${want#* }" \
|
||||||
|
'BEGIN{ f = (u=="KiB") ? 2^-10 : (u=="MiB") ? 1 : (u=="GiB") ? 1024 : (u=="TiB") ? 1048576 : 0
|
||||||
|
if (f == 0) { exit 1 }; printf "%d", n*f + 0.5 }') || {
|
||||||
|
echo "arm $arm: the plan printed an unknown unit in '$want'" >&2; overall=1; continue; }
|
||||||
|
free_mib=$(df --output=avail -m "$OUTDIR" | tail -1 | tr -d ' ')
|
||||||
|
echo "plan: $want ($need_mib MiB); free ${free_mib} MiB on $OUTDIR (margin $MARGIN_MIB MiB)"
|
||||||
|
if [ "$free_mib" -lt $((need_mib + MARGIN_MIB)) ]; then
|
||||||
|
echo "arm $arm: needs ${need_mib} MiB + ${MARGIN_MIB} margin, ${free_mib} free; free space and re-run" >&2
|
||||||
|
overall=1; continue
|
||||||
|
fi
|
||||||
|
if [ "$PLAN_ONLY" = 1 ]; then echo "planned, not converted (--plan-only)"; continue; fi
|
||||||
|
rc=0; run_convert "$arm" || rc=$?
|
||||||
|
[ "$rc" -eq 0 ] || { echo "arm $arm: rad-convert rc=$rc; log: $STATE/convert-$arm-$TS.log" >&2; overall=1; continue; }
|
||||||
|
out="$OUTDIR/qwen38-w5nl64-$arm.rad"
|
||||||
|
got_mib=$(( $(stat -c %s "$out") / 1048576 ))
|
||||||
|
# The receipt: the artifact is what the plan predicted, within the metadata the plan does not
|
||||||
|
# count. An arm that came out materially smaller lost a plane somewhere.
|
||||||
|
lo=$(( need_mib * 98 / 100 )); hi=$(( need_mib * 103 / 100 ))
|
||||||
|
if [ "$got_mib" -lt "$lo" ] || [ "$got_mib" -gt "$hi" ]; then
|
||||||
|
echo "arm $arm: $out is ${got_mib} MiB, the plan said ${need_mib} MiB (allowed ${lo}-${hi})" >&2
|
||||||
|
overall=1; continue
|
||||||
|
fi
|
||||||
|
echo "CONVERT_DONE $arm ${got_mib} MiB -> $out"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$KEEP_RECIPE" = 0 ] && [ "$DRY" = 0 ]; then
|
||||||
|
mkdir -p "$CAMPAIGN/.AGENTTRASH"
|
||||||
|
mv "$WORK" "$CAMPAIGN/.AGENTTRASH/$(basename "$WORK")" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
exit "$overall"
|
||||||
@@ -20,9 +20,16 @@ of the Gram is its down-input energy. Rank halves: 0-255, 256-511
|
|||||||
Output: the cast-rule lines (recipe format) on stdout, a report on stderr.
|
Output: the cast-rule lines (recipe format) on stdout, a report on stderr.
|
||||||
|
|
||||||
Usage: derive-protected-experts.py <calib dir> [--min-share 0.03]
|
Usage: derive-protected-experts.py <calib dir> [--min-share 0.03]
|
||||||
[--min-route-mult 3.0]
|
[--min-route-mult 3.0] [--target-count 0]
|
||||||
|
--target-count N fills the byte bill: the bars pick the base set exactly as
|
||||||
|
the bar-only pass does, then the energy ranking admits experts one at a time
|
||||||
|
(each with its parity partner) until the count lands. The energy bar NEVER
|
||||||
|
relaxes, so asking for 48 bf16 experts never quietly buys 80; the ratio bar
|
||||||
|
walks down only when the ranking cannot produce that many candidates.
|
||||||
|
0 takes the bars as given.
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
|
import collections
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -32,6 +39,7 @@ import sys
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
NAME_RE = re.compile(r"^gram\.r(\d)\.blk\.(\d+)\.ffn_down_exps\.(\d+)\.bin$")
|
NAME_RE = re.compile(r"^gram\.r(\d)\.blk\.(\d+)\.ffn_down_exps\.(\d+)\.bin$")
|
||||||
|
CAST_RE = re.compile(r"^blk\.(\d+)\.ffn_\*_exps\.(\d+)\.weight")
|
||||||
|
|
||||||
|
|
||||||
def read_gram(path):
|
def read_gram(path):
|
||||||
@@ -49,27 +57,8 @@ def read_gram(path):
|
|||||||
return k, rank, int(rows), tr
|
return k, rank, int(rows), tr
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def select(data, min_share, min_route_mult):
|
||||||
ap = argparse.ArgumentParser()
|
"""(cast lines, report lines, count) for one pair of bars."""
|
||||||
ap.add_argument("calib_dir")
|
|
||||||
ap.add_argument("--min-share", type=float, default=0.03)
|
|
||||||
ap.add_argument("--min-route-mult", type=float, default=3.0)
|
|
||||||
a = ap.parse_args()
|
|
||||||
|
|
||||||
# layer -> global expert -> (rank, rows, energy)
|
|
||||||
data = {}
|
|
||||||
for path in sorted(glob.glob(os.path.join(
|
|
||||||
a.calib_dir, "gram.r*.ffn_down_exps.*.bin"))):
|
|
||||||
m = NAME_RE.match(os.path.basename(path))
|
|
||||||
if not m:
|
|
||||||
continue
|
|
||||||
rank, layer, expert = int(m.group(1)), int(m.group(2)), int(m.group(3))
|
|
||||||
k, _, rows, tr = read_gram(path)
|
|
||||||
data.setdefault(layer, {})[expert] = (rank, rows, tr)
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
raise SystemExit("no per-expert down grams found")
|
|
||||||
|
|
||||||
lines, report = [], []
|
lines, report = [], []
|
||||||
total = 0
|
total = 0
|
||||||
for layer in sorted(data):
|
for layer in sorted(data):
|
||||||
@@ -83,7 +72,7 @@ def main():
|
|||||||
for e, (rank, rows, tr) in experts.items():
|
for e, (rank, rows, tr) in experts.items():
|
||||||
es, rs = tr / tot_e, rows / max(tot_r, 1)
|
es, rs = tr / tot_e, rows / max(tot_r, 1)
|
||||||
rec = (e, rank, es, rs)
|
rec = (e, rank, es, rs)
|
||||||
if es >= a.min_share and es >= a.min_route_mult * rs:
|
if es >= min_share and es >= min_route_mult * rs:
|
||||||
qual.append(rec)
|
qual.append(rec)
|
||||||
elif es > 0:
|
elif es > 0:
|
||||||
pool.append(rec)
|
pool.append(rec)
|
||||||
@@ -107,10 +96,148 @@ def main():
|
|||||||
f"energy {es*100:5.2f}%, routing {rs*100:5.3f}%, "
|
f"energy {es*100:5.2f}%, routing {rs*100:5.3f}%, "
|
||||||
f"ratio {es/max(rs,1e-12):6.1f}")
|
f"ratio {es/max(rs,1e-12):6.1f}")
|
||||||
total += len(chosen)
|
total += len(chosen)
|
||||||
|
return lines, report, total
|
||||||
|
|
||||||
|
|
||||||
|
def parity_repair(chosen, by_half, report, only=None):
|
||||||
|
"""Add the next-best candidate to every half — or to `only` — holding an odd count.
|
||||||
|
|
||||||
|
The four-bit GEMM takes each half's remainder as two equal tables, so an odd count
|
||||||
|
of bf16 experts on a half is not a style problem: the two tables would not match.
|
||||||
|
`by_half` is energy-descending, so the first unchosen candidate is the next most
|
||||||
|
energetic, which is the expert worth keeping. Returns how many were added.
|
||||||
|
"""
|
||||||
|
added = 0
|
||||||
|
for key in ([only] if only is not None else sorted(by_half)):
|
||||||
|
layer, half = key
|
||||||
|
n = sum(1 for (l, x) in chosen if l == layer and x // 256 == half)
|
||||||
|
if n % 2 == 0:
|
||||||
|
continue
|
||||||
|
for e, es, rs in by_half.get(key, []):
|
||||||
|
if (layer, e) not in chosen:
|
||||||
|
chosen[(layer, e)] = (es, rs)
|
||||||
|
added += 1
|
||||||
|
report.append(f"blk.{layer}: half {half} odd -> added e{e} "
|
||||||
|
f"(energy {es*100:.2f}%)")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
report.append(f"blk.{layer}: half {half} holds {n} and no candidate is "
|
||||||
|
f"left to make it even")
|
||||||
|
return added
|
||||||
|
|
||||||
|
|
||||||
|
def select_to_count(data, min_share, min_route_mult, target):
|
||||||
|
"""(lines, report, total, cutoff) for the set that LANDS the count.
|
||||||
|
|
||||||
|
The bars decide the base set; the count is then filled from the energy ranking,
|
||||||
|
one expert at a time, each addition taking its half's next-best candidate with it
|
||||||
|
so every rank's half stays even. Only the RATIO bar walks down to find candidates
|
||||||
|
— it is the law that says an expert is disproportionate and not merely busy, and
|
||||||
|
the caller's byte bill has to be filled — while the ENERGY bar holds, because that
|
||||||
|
bar is what the extra GiB of bf16 is supposed to buy. `cutoff` is the energy share
|
||||||
|
the set bottoms out at: the bar this campaign actually converted with.
|
||||||
|
"""
|
||||||
|
report = []
|
||||||
|
shares, ranked = {}, []
|
||||||
|
for layer in sorted(data):
|
||||||
|
experts = data[layer]
|
||||||
|
tot_e = sum(v[2] for v in experts.values())
|
||||||
|
tot_r = sum(v[1] for v in experts.values())
|
||||||
|
if tot_e <= 0 or tot_r <= 0:
|
||||||
|
report.append(f"blk.{layer}: empty recording, skipped")
|
||||||
|
continue
|
||||||
|
for e, (_rank, rows, tr) in experts.items():
|
||||||
|
es, rs = tr / tot_e, rows / max(tot_r, 1)
|
||||||
|
if es <= 0:
|
||||||
|
continue
|
||||||
|
shares[(layer, e)] = (es, rs)
|
||||||
|
ranked.append((es, layer, e, rs))
|
||||||
|
ranked.sort(key=lambda r: (-r[0], r[1], r[2]))
|
||||||
|
by_half = {}
|
||||||
|
for es, layer, e, rs in ranked:
|
||||||
|
by_half.setdefault((layer, e // 256), []).append((e, es, rs))
|
||||||
|
|
||||||
|
chosen = {k: v for k, v in shares.items()
|
||||||
|
if v[0] >= min_share and v[0] >= min_route_mult * v[1]}
|
||||||
|
total = len(chosen)
|
||||||
|
total += parity_repair(chosen, by_half, report)
|
||||||
|
|
||||||
|
mult = min_route_mult
|
||||||
|
while True:
|
||||||
|
pool = [(es, l, e, rs) for es, l, e, rs in ranked
|
||||||
|
if es >= mult * rs and (l, e) not in chosen]
|
||||||
|
if len(pool) + total >= target or mult <= 0.0:
|
||||||
|
break
|
||||||
|
mult -= 0.5
|
||||||
|
report.append(f"ratio bar {mult + 0.5:g} leaves {len(pool)} fill candidates "
|
||||||
|
f"for {target - total} slots; relaxing toward 0")
|
||||||
|
for es, layer, e, rs in pool:
|
||||||
|
if total >= target:
|
||||||
|
break
|
||||||
|
if (layer, e) in chosen:
|
||||||
|
continue
|
||||||
|
chosen[(layer, e)] = (es, rs)
|
||||||
|
total += 1
|
||||||
|
report.append(f"blk.{layer}: count fill -> added e{e} (energy {es*100:.2f}%)")
|
||||||
|
total += parity_repair(chosen, by_half, report, only=(layer, e // 256))
|
||||||
|
lines, cutoff = [], None
|
||||||
|
for (layer, e) in sorted(chosen):
|
||||||
|
es, rs = chosen[(layer, e)]
|
||||||
|
cutoff = es if cutoff is None else min(cutoff, es)
|
||||||
|
lines.append(f"blk.{layer}.ffn_*_exps.{e}.weight cast dtype=bf16")
|
||||||
|
return lines, report, total, cutoff
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser()
|
||||||
|
ap.add_argument("calib_dir")
|
||||||
|
ap.add_argument("--min-share", type=float, default=0.03)
|
||||||
|
ap.add_argument("--min-route-mult", type=float, default=3.0)
|
||||||
|
ap.add_argument("--target-count", type=int, default=0)
|
||||||
|
a = ap.parse_args()
|
||||||
|
|
||||||
|
# layer -> global expert -> (rank, rows, energy)
|
||||||
|
data = {}
|
||||||
|
for path in sorted(glob.glob(os.path.join(
|
||||||
|
a.calib_dir, "gram.r*.ffn_down_exps.*.bin"))):
|
||||||
|
m = NAME_RE.match(os.path.basename(path))
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
rank, layer, expert = int(m.group(1)), int(m.group(2)), int(m.group(3))
|
||||||
|
k, _, rows, tr = read_gram(path)
|
||||||
|
data.setdefault(layer, {})[expert] = (rank, rows, tr)
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
raise SystemExit("no per-expert down grams found")
|
||||||
|
|
||||||
|
if a.target_count > 0:
|
||||||
|
lines, report, total, cutoff = select_to_count(
|
||||||
|
data, a.min_share, a.min_route_mult, a.target_count)
|
||||||
|
bar = f"energy cutoff {cutoff:.3g}" if cutoff is not None else "no candidates"
|
||||||
|
else:
|
||||||
|
lines, report, total = select(data, a.min_share, a.min_route_mult)
|
||||||
|
bar = f"min-share {a.min_share:.3g}"
|
||||||
|
|
||||||
|
# The emitted lines ARE the contract with the four-bit GEMM: every rank's half of
|
||||||
|
# every layer must hold an even number of bf16 experts. Judged on the output and
|
||||||
|
# not on the internal sets, so a slip in either pass is caught here rather than at
|
||||||
|
# engine load time.
|
||||||
|
halves = collections.Counter()
|
||||||
|
for rule in lines:
|
||||||
|
m = CAST_RE.match(rule)
|
||||||
|
if m:
|
||||||
|
halves[(int(m.group(1)), int(m.group(2)) // 256)] += 1
|
||||||
|
odd = {key: n for key, n in halves.items() if n % 2}
|
||||||
|
if odd:
|
||||||
|
print("PARITY LAW BROKEN: " + ", ".join(
|
||||||
|
f"blk.{lay} half {half} holds {n} protected experts"
|
||||||
|
for (lay, half), n in sorted(odd.items())), file=sys.stderr)
|
||||||
|
raise SystemExit(2)
|
||||||
|
|
||||||
print("\n".join(lines))
|
print("\n".join(lines))
|
||||||
print("\n".join(report), file=sys.stderr)
|
print("\n".join(report), file=sys.stderr)
|
||||||
print(f"total protected: {total}", file=sys.stderr)
|
print(f"total protected: {total} (target {a.target_count or 'none'}, {bar}, "
|
||||||
|
f"min-route-mult {a.min_route_mult:g})", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Executable
+249
@@ -0,0 +1,249 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# download-qwen38-bf16.sh — RUNS ON DANKSERVER. Fetch and verify the vendor
|
||||||
|
# BF16 checkpoint Qwen/Qwen3.8-Flash-Next: the source the five-bit campaign
|
||||||
|
# converts, and the source the KLD reference is recorded from.
|
||||||
|
#
|
||||||
|
# Why the base and not a finetune: the campaign converts to compare against the
|
||||||
|
# publisher's own iq4r container, and that container was built from this
|
||||||
|
# checkpoint. Base-in, base-out; anything else confounds the gate. (The
|
||||||
|
# predecessor campaign ran ukisai/Swift-1.5-Qwen3.8-Flash-Next-BF16, whose key
|
||||||
|
# set is identical to this repo's but whose weights are not — none of its
|
||||||
|
# calibration grams, KLD reference or protected list carries over.)
|
||||||
|
#
|
||||||
|
# Why re-fetch instead of reusing: every BF16 copy of this family was destroyed
|
||||||
|
# in the 2026-10-15 storage purge — only the 113.6 GiB served .rad survived.
|
||||||
|
# 335.3 GiB of safetensors, 131 shards, ungated (the predecessor campaign
|
||||||
|
# verified the key set against this repo, so no gate letter is needed).
|
||||||
|
#
|
||||||
|
# Verification is four gates deep, because the failure this repo actually lived
|
||||||
|
# through is a fetch that reported success: `curl -o FILE URL` exits 0 on an
|
||||||
|
# HTTP 401/403 and writes the error body as the artifact — three shards of
|
||||||
|
# identical tiny size and a cheerful DONE in 20 seconds for 104 GiB.
|
||||||
|
#
|
||||||
|
# 1. shard census: the file count must equal the expected shard count, so a
|
||||||
|
# partially fetched fragment tree (the .partNofM trap) cannot pass.
|
||||||
|
# 2. per-shard completeness, from the shard's OWN safetensors header: the
|
||||||
|
# header declares where the tensor data ends, so every shard's true size is
|
||||||
|
# known offline. A torn tail is named here, by file, instead of surfacing
|
||||||
|
# as a load error five hours later.
|
||||||
|
# 3. index coverage: every shard model.safetensors.index.json references
|
||||||
|
# exists and is named.
|
||||||
|
# 4. total bytes against the number the Hub declared, then, unless --no-hub,
|
||||||
|
# every file's sha256 against the Hub's LFS record — including the shards
|
||||||
|
# already on disk, so a previous partial download is never trusted.
|
||||||
|
#
|
||||||
|
# The token never enters a command line (the 2026-09-16 leak law): it is read
|
||||||
|
# into HF_TOKEN in the environment, where a local process listing is the only
|
||||||
|
# reader. This repo is ungated, so the fetch simply runs without one when no
|
||||||
|
# token file exists. scripts/hfget.sh is curl+jq only — no Python, no venv, no
|
||||||
|
# hf_transfer — and it drops the pickle/ONNX weight duplicates by itself.
|
||||||
|
#
|
||||||
|
# usage: download-qwen38-bf16.sh [--fetch] [--no-hub] [--dir DIR]
|
||||||
|
# [--shards N] [--bytes B] [--repo ID]
|
||||||
|
# --fetch download first (resumable: re-run to continue), then verify
|
||||||
|
# --no-hub skip the Hub-side sha256 sweep; the other gates always run
|
||||||
|
# exit: 0 verified, 1 verification failed, 2 environment / usage error
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO="${FRANK_QWEN38_HF_REPO:-Qwen/Qwen3.8-Flash-Next}"
|
||||||
|
LOCAL_DIR="${FRANK_QWEN38_CKPT:-/home/dank/models/Qwen3.8-Flash-Next}"
|
||||||
|
# Hub census of 2026-10-27: 144 files, 131 of them .safetensors, together
|
||||||
|
# 360,023,351,514 bytes; the shards alone are 360,000,192,888. This gate counts
|
||||||
|
# shards only, so a config file edited on the Hub cannot fail it.
|
||||||
|
EXPECTED_SHARDS="${FRANK_QWEN38_SHARDS:-131}"
|
||||||
|
EXPECTED_BYTES="${FRANK_QWEN38_BYTES:-360000192888}"
|
||||||
|
MIN_FREE_MIB="${FRANK_QWEN38_MIN_FREE_MIB:-350000}"
|
||||||
|
|
||||||
|
FETCH=0
|
||||||
|
HUB=1
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--fetch) FETCH=1 ;;
|
||||||
|
--no-hub) HUB=0 ;;
|
||||||
|
--dir) LOCAL_DIR="$2"; shift ;;
|
||||||
|
--shards) EXPECTED_SHARDS="$2"; shift ;;
|
||||||
|
--bytes) EXPECTED_BYTES="$2"; shift ;;
|
||||||
|
--repo) REPO="$2"; shift ;;
|
||||||
|
*) echo "usage: ${0##*/} [--fetch] [--no-hub] [--dir DIR] [--shards N] [--bytes B] [--repo ID]" >&2
|
||||||
|
exit 2 ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
FAILS=0
|
||||||
|
note() { echo "$@"; FAILS=$((FAILS + 1)); }
|
||||||
|
|
||||||
|
if [ "$FETCH" = "1" ]; then
|
||||||
|
# Every fetch gate is a hard stop: skipping the download silently and then
|
||||||
|
# verifying a half tree is how a campaign ships a stale payload.
|
||||||
|
HFGET="$SELF_DIR/hfget.sh"
|
||||||
|
if [ ! -f "$HFGET" ]; then
|
||||||
|
echo "REFUSED: the fetch helper is not here: $HFGET" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
echo "REFUSED: hfget.sh needs jq on PATH" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
mkdir -p "$LOCAL_DIR"
|
||||||
|
# The campaign's peak-disk law starts at the fetch: the checkpoint and the
|
||||||
|
# reference container built from it are ~335 GiB each, and a tree that does
|
||||||
|
# not fit is discovered hours into a transfer, when it costs the most.
|
||||||
|
free_mib=$(df -m --output=avail "$LOCAL_DIR" | tail -1 | tr -dc '0-9')
|
||||||
|
if [ "${free_mib:-0}" -lt "$MIN_FREE_MIB" ]; then
|
||||||
|
echo "REFUSED: $free_mib MiB free at $LOCAL_DIR; the fetch wants $MIN_FREE_MIB MiB free" >&2
|
||||||
|
echo " (retire the peak-disk predecessors first — nothing is downloaded, so" >&2
|
||||||
|
echo " nothing is left half-written)" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "fetch: $REPO -> $LOCAL_DIR ($free_mib MiB free)"
|
||||||
|
if [ -z "${HF_TOKEN:-}" ]; then
|
||||||
|
CREDSTORE="$HOME/.config/huggingface/credentials/https://huggingface.co"
|
||||||
|
if [ -f "$CREDSTORE" ]; then
|
||||||
|
HF_TOKEN="$(awk '/^ OauthToken: / {print $2; exit}' "$CREDSTORE")"
|
||||||
|
elif [ -f "$HOME/.cache/huggingface/token" ]; then
|
||||||
|
# The legacy single-line token file the hub library writes.
|
||||||
|
HF_TOKEN="$(tr -d '[:space:]' < "$HOME/.cache/huggingface/token")"
|
||||||
|
elif [ -f "$HOME/.hf_token" ]; then
|
||||||
|
HF_TOKEN="$(tr -d '[:space:]' < "$HOME/.hf_token")"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -n "${HF_TOKEN:-}" ]; then
|
||||||
|
export HF_TOKEN
|
||||||
|
fi
|
||||||
|
bash "$HFGET" "$REPO" "$LOCAL_DIR" main
|
||||||
|
unset HF_TOKEN
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$LOCAL_DIR" ]; then
|
||||||
|
echo "REFUSED: $LOCAL_DIR does not exist (run with --fetch, or pass --dir)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
INDEX="$LOCAL_DIR/model.safetensors.index.json"
|
||||||
|
if [ ! -f "$INDEX" ]; then
|
||||||
|
echo "REFUSED: $INDEX is missing — the checkpoint tree is not addressable without it" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== gate 1: shard census ($LOCAL_DIR)"
|
||||||
|
SHARDS=$(find "$LOCAL_DIR" -maxdepth 1 -type f -name '*.safetensors' | wc -l)
|
||||||
|
if [ "$SHARDS" != "$EXPECTED_SHARDS" ]; then
|
||||||
|
note "FAIL SHARD_COUNT: $SHARDS safetensors files on disk, expected $EXPECTED_SHARDS"
|
||||||
|
else
|
||||||
|
echo " ok: $SHARDS shards"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== gate 2: every shard is whole, judged by its own header"
|
||||||
|
# A safetensors file is [u64 header length][JSON header][tensor data]. The JSON
|
||||||
|
# declares where the data ends, so the file's true size is computable offline:
|
||||||
|
# 8 + header + the largest end offset. Truncation and error bodies both fall
|
||||||
|
# here, and each is reported with the file that caused it.
|
||||||
|
while IFS= read -r f; do
|
||||||
|
state=$(python3 - "$f" <<'PY'
|
||||||
|
import json, os, struct, sys
|
||||||
|
path = sys.argv[1]
|
||||||
|
size = os.path.getsize(path)
|
||||||
|
try:
|
||||||
|
with open(path, "rb") as fh:
|
||||||
|
(hlen,) = struct.unpack("<Q", fh.read(8))
|
||||||
|
if hlen == 0 or hlen > size:
|
||||||
|
print("NOT_SAFETENSORS")
|
||||||
|
raise SystemExit
|
||||||
|
header = json.loads(fh.read(hlen))
|
||||||
|
except Exception:
|
||||||
|
print("NOT_SAFETENSORS")
|
||||||
|
raise SystemExit
|
||||||
|
if not header:
|
||||||
|
print("NOT_SAFETENSORS")
|
||||||
|
raise SystemExit
|
||||||
|
end = max((v["data_offsets"][1] for v in header.values()
|
||||||
|
if isinstance(v, dict) and "data_offsets" in v), default=0)
|
||||||
|
want = 8 + hlen + end
|
||||||
|
print("OK" if want == size else f"TRUNCATED want={want} have={size}")
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
case "$state" in
|
||||||
|
OK) ;;
|
||||||
|
NOT_SAFETENSORS*) note "FAIL NOT_SAFETENSORS: $(basename "$f") is not a safetensors file (an error body written as an artifact?)" ;;
|
||||||
|
TRUNCATED*) note "FAIL TRUNCATED: $(basename "$f") $state" ;;
|
||||||
|
esac
|
||||||
|
done < <(find "$LOCAL_DIR" -maxdepth 1 -type f -name '*.safetensors' | sort)
|
||||||
|
if [ "$FAILS" -eq 0 ]; then
|
||||||
|
echo " ok: every shard's data extent matches its size on disk"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== gate 3: the index's shards are all present"
|
||||||
|
INDEX_SHARDS=$(python3 - "$INDEX" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
map_ = json.load(open(sys.argv[1]))["weight_map"]
|
||||||
|
print("\n".join(sorted(set(map_.values()))))
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
while IFS= read -r name; do
|
||||||
|
[ -n "$name" ] || continue
|
||||||
|
if [ ! -f "$LOCAL_DIR/$name" ]; then
|
||||||
|
note "FAIL MISSING: $name is named by the index and is not on disk"
|
||||||
|
fi
|
||||||
|
done <<< "$INDEX_SHARDS"
|
||||||
|
if [ "$FAILS" -eq 0 ]; then
|
||||||
|
echo " ok: $(printf '%s\n' "$INDEX_SHARDS" | grep -c .) referenced shards on disk"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "== gate 4: total bytes vs the Hub's declaration"
|
||||||
|
TOTAL=$(find "$LOCAL_DIR" -maxdepth 1 -type f -name '*.safetensors' -printf '%s\n' | awk '{s+=$1} END{print s+0}')
|
||||||
|
if [ "$TOTAL" != "$EXPECTED_BYTES" ]; then
|
||||||
|
note "FAIL TOTAL_BYTES: shards total $TOTAL bytes, expected $EXPECTED_BYTES"
|
||||||
|
else
|
||||||
|
echo " ok: $TOTAL bytes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$HUB" = "1" ]; then
|
||||||
|
echo "== gate 5: every file's sha256 vs the Hub's LFS record"
|
||||||
|
# size = file size; lfs.sha256 = sha256 of the file itself (LFS or not).
|
||||||
|
TMPD="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$TMPD"' EXIT
|
||||||
|
MANIFEST="$TMPD/tree.json"
|
||||||
|
PLAN="$TMPD/plan"
|
||||||
|
curl -sf "https://huggingface.co/api/models/$REPO/tree/main?recursive=true&expand=true" \
|
||||||
|
-o "$MANIFEST" || { echo "FAIL: the Hub API call failed — refusing to report success" >&2; exit 1; }
|
||||||
|
python3 - "$MANIFEST" > "$PLAN" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
for e in json.load(open(sys.argv[1])):
|
||||||
|
if e.get("type") != "file":
|
||||||
|
continue
|
||||||
|
sha = (e.get("lfs") or {}).get("sha256") or e.get("sha") or ""
|
||||||
|
if sha:
|
||||||
|
print(e["path"], e["size"], sha)
|
||||||
|
PY
|
||||||
|
BAD=0
|
||||||
|
while read -r rel size sha; do
|
||||||
|
[ -n "${rel:-}" ] || continue
|
||||||
|
if [ ! -f "$LOCAL_DIR/$rel" ]; then
|
||||||
|
note "FAIL MISSING: $rel is on the Hub and not on disk"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
have=$(stat -c %s "$LOCAL_DIR/$rel")
|
||||||
|
if [ "$have" != "$size" ]; then
|
||||||
|
note "FAIL SIZE: $rel is $have bytes, Hub says $size"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
got=$(sha256sum "$LOCAL_DIR/$rel" | awk '{print $1}')
|
||||||
|
if [ "$got" != "$sha" ]; then
|
||||||
|
note "FAIL SHA256: $rel got $got want $sha"
|
||||||
|
BAD=$((BAD + 1))
|
||||||
|
fi
|
||||||
|
done < "$PLAN"
|
||||||
|
if [ "$BAD" -eq 0 ] && [ "$FAILS" -eq 0 ]; then
|
||||||
|
echo " ok: sha256 matches for every file"
|
||||||
|
fi
|
||||||
|
rm -rf "$TMPD"
|
||||||
|
trap - EXIT
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$FAILS" -gt 0 ]; then
|
||||||
|
echo "VERIFY_FAILED: $FAILS problem(s) in $LOCAL_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "VERIFY_OK: $SHARDS shards, $TOTAL bytes of safetensors in $LOCAL_DIR"
|
||||||
|
echo "NEXT: build the BF16 reference container with scripts/build-qwen38-bf16-rad.sh"
|
||||||
Executable
+418
@@ -0,0 +1,418 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""grams-to-imatrix.py — the engine's own calibration recording, re-voiced as a
|
||||||
|
llama.cpp importance matrix for `rad-convert --imatrix F`. RUNS ON DANKSERVER
|
||||||
|
beside the recording. Python 3 stdlib only: numpy must NOT be required, because
|
||||||
|
this runs in the plain container next to a 335 GiB checkpoint, not in a venv.
|
||||||
|
|
||||||
|
WHY. `rad-convert` takes its per-column activation second moments and its
|
||||||
|
expert-popularity profile from ONE input, an imatrix (core/format/imatrix.h).
|
||||||
|
Only the GGUF form carries a per-expert dimension — with the legacy flat form,
|
||||||
|
or with none, every expert in a layer is weighted by the layer's aggregate,
|
||||||
|
which is exactly the wrong thing on a 512-expert MoE. The calibration tap has
|
||||||
|
already measured everything; this tool only re-voices it, so the imatrix and the
|
||||||
|
protected-expert census (derive-protected-experts.py) are the SAME recording:
|
||||||
|
one measurement, two consumers, neither able to disagree about which experts are
|
||||||
|
hot.
|
||||||
|
|
||||||
|
INPUT. RADGRAM2 files in a RADIANCE_CALIB_DIR output directory
|
||||||
|
(arch/common/rad_arch.h, calib_write). Header law, same parse as
|
||||||
|
scripts/derive-protected-experts.py — 32 bytes, `<8sIIQI`: magic `RADGRAM2`,
|
||||||
|
u32 k, u32 rank, u64 rows, u32 domain, a pad word, then k*k f32 row-major. Two
|
||||||
|
kinds of name, both GGUF-style, both with the checkpoint's GLOBAL expert
|
||||||
|
numbering (rad_block_moe_fp8.h: ge = expert_base + e):
|
||||||
|
|
||||||
|
gram.r<rank>.blk.L.ffn_down_exps.bin the layer's pooled gram
|
||||||
|
gram.r<rank>.blk.L.ffn_down_exps.<e>.bin expert e's own gram
|
||||||
|
|
||||||
|
`rows` is that file's routing/token count and the matrix is Σ x xᵀ over those
|
||||||
|
rows, so **the diagonal is exactly the per-column activation second moment an
|
||||||
|
imatrix wants**, and `rows` is the count the reader hands back from `count()`.
|
||||||
|
Every off-diagonal term is dropped on purpose: the GGUF format has nowhere to
|
||||||
|
put a full matrix, and the quantiser asks for columns.
|
||||||
|
|
||||||
|
OUTPUT, per layer L (core/format/imatrix.h: names WITHOUT `.weight`, a MoE
|
||||||
|
layer's experts folded into ONE entry, so expert 9 is slice 9 of
|
||||||
|
`blk.L.ffn_down_exps`):
|
||||||
|
|
||||||
|
blk.L.ffn_down_exps in_sum2 [k, n_expert] expert-major, counts [1, n_expert]
|
||||||
|
blk.L.ffn_gate_up_exps the SAME matrices at the SAME offsets. A routed expert's
|
||||||
|
gate_up input is the same hidden-state rows as its down
|
||||||
|
input, so the second moment over them is identical and
|
||||||
|
so is the routing count. Two tensor infos over one
|
||||||
|
payload: the reader maps each half independently
|
||||||
|
(imatrix.cpp joins `<base>.in_sum2` with `<base>.counts`
|
||||||
|
by base name), and copying ~200 MB to state one fact
|
||||||
|
twice buys no correctness.
|
||||||
|
blk.L.ssm_inz [k] and [1], from the pooled gram: that row's input is
|
||||||
|
the same hidden state, so its columns take the pooled
|
||||||
|
diagonal and its count is the layer's pooled rows.
|
||||||
|
|
||||||
|
Then STOP. Nothing whose input width is not the gram's k — no ssm_out, no
|
||||||
|
attn_output, no mtp.*, no ple_ngram. importance_for() answers false when the
|
||||||
|
entry's cols do not equal the weight's width and the convert proceeds without
|
||||||
|
it: an entry the reader silently ignores is a lie in the artifact, worse than
|
||||||
|
an absent one. The corollary to check before trusting the two hidden-width
|
||||||
|
entries: the tap writes the gate_up gram at g.n_embd and the down gram at the
|
||||||
|
expert width, so the gram's k must equal the checkpoint's hidden width (its
|
||||||
|
config.json). Where they differ, gate_up/ssm_inz are inert, not wrong.
|
||||||
|
|
||||||
|
INHERITANCE. An expert with no own gram carries the layer's POOLED diagonal, and
|
||||||
|
its count stays its own routing rows — the rule the engine itself applies when it
|
||||||
|
picks a Hessian (libquant/lq_hess.h, calib_resolve: an expert's own gram only
|
||||||
|
once its rank files hold kOwnRowsPerK * K rows; below that a fit to so few rows
|
||||||
|
is worse than round-to-nearest, and those experts carry a fraction of a percent
|
||||||
|
of the routing). The count never inherits: popularity is `count`'s job. The count
|
||||||
|
of inherited experts is reported on stderr.
|
||||||
|
|
||||||
|
RANK FOLDING. TP=2 writes gram.r0.* and gram.r1.* with GLOBAL expert numbering,
|
||||||
|
so the ranks describe DISJOINT experts and the fold is a UNION into the one
|
||||||
|
[k, n_expert] entry. A pooled gram is the opposite case: every rank summed the
|
||||||
|
same stem over its own rows, so the fold is a SUM, rows included (calib_sum,
|
||||||
|
calib_rows). The same global expert in two per-expert files is legal only when
|
||||||
|
the numbers agree exactly; otherwise the recording is corrupt and the entry would
|
||||||
|
be whichever file sorted last, so refuse.
|
||||||
|
|
||||||
|
GGUF v3 (the only version imatrix.cpp accepts — version != 3 is a refusal):
|
||||||
|
`general.alignment` u32, `imatrix.chunk_size` u32, `imatrix.chunk_count` u32,
|
||||||
|
`imatrix.datasets` array of strings; tensors f32 (ggml type 0) with offsets
|
||||||
|
relative to the aligned data section. Every tensor offset is padded up to
|
||||||
|
`general.alignment` and the data section itself starts aligned. That is the
|
||||||
|
classic hand-rolled-writer bug: an unaligned offset makes the reader's extent
|
||||||
|
arithmetic point into the middle of the previous tensor's floats, and it reads
|
||||||
|
them without complaint.
|
||||||
|
|
||||||
|
chunk_count DERIVATION. The corpus row count is the LARGEST single-layer pooled
|
||||||
|
row total, not a sum over layers: every layer's tap accumulated over the whole
|
||||||
|
corpus, so adding the layers counts each token once per layer and inflates the
|
||||||
|
figure by the layer count. chunk_count = that row total // chunk_size, the
|
||||||
|
"chunks of chunk_size" line imatrix.cpp prints for itself.
|
||||||
|
|
||||||
|
REFUSALS, each exit 2 with a message naming what is missing:
|
||||||
|
NO_GRAMS the directory holds no RADGRAM2 down-projection gram
|
||||||
|
NOT_RADGRAM2 a gram whose magic is not RADGRAM2
|
||||||
|
SHORT a gram whose length is not 32 + k*k*4
|
||||||
|
NO_POOLED a layer with expert grams and no pooled gram to inherit from
|
||||||
|
K_MISMATCH a layer whose files disagree about k (rank pooled grams, or an
|
||||||
|
expert gram against its layer's pooled gram)
|
||||||
|
EXPERT_CONFLICT a global expert recorded twice with different numbers
|
||||||
|
EXISTS the -o path already holds a file (the trash law: move it)
|
||||||
|
CHUNK_COUNT the derived chunk count does not fit the u32 metadata field
|
||||||
|
|
||||||
|
USAGE. grams-to-imatrix.py <calib-dir> -o <out.gguf>
|
||||||
|
[--datasets NAME,...] [--chunk-size N] [--min-rows N] [-v]
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
MAGIC = b"RADGRAM2"
|
||||||
|
HDR = 32 # 8s I I Q I + pad, rad_arch.h
|
||||||
|
KOWN_ROWS_PER_K = 4 # libquant/lq_hess.h kOwnRowsPerK
|
||||||
|
ALIGNMENT = 32 # general.alignment, and the pad unit
|
||||||
|
|
||||||
|
POOLED_RE = re.compile(r"^gram\.r(\d+)\.blk\.(\d+)\.ffn_down_exps\.bin$")
|
||||||
|
EXPERT_RE = re.compile(r"^gram\.r(\d+)\.blk\.(\d+)\.ffn_down_exps\.(\d+)\.bin$")
|
||||||
|
|
||||||
|
GT_U32, GT_STR, GT_ARR = 4, 8, 9
|
||||||
|
GGML_F32 = 0
|
||||||
|
|
||||||
|
|
||||||
|
def refuse(msg):
|
||||||
|
sys.stderr.write("REFUSED %s\n" % msg)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
|
||||||
|
def read_gram(path):
|
||||||
|
"""(k, rank, rows, domain, diagonal) from one RADGRAM2 file."""
|
||||||
|
try:
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
raw = f.read()
|
||||||
|
except OSError as e:
|
||||||
|
refuse("UNREADABLE %s: %s" % (path, e))
|
||||||
|
if len(raw) < HDR:
|
||||||
|
refuse("SHORT %s: %d bytes, under the %d-byte RADGRAM2 header"
|
||||||
|
% (path, len(raw), HDR))
|
||||||
|
magic, k, rank, rows, domain = struct.unpack_from("<8sIIQI", raw, 0)
|
||||||
|
if magic != MAGIC:
|
||||||
|
refuse("NOT_RADGRAM2 %s: magic %r, expected b'RADGRAM2'" % (path, magic))
|
||||||
|
if k <= 0:
|
||||||
|
refuse("SHORT %s: header claims k=%d, which describes no matrix" % (path, k))
|
||||||
|
need = HDR + k * k * 4
|
||||||
|
if len(raw) != need:
|
||||||
|
refuse("SHORT %s: %d bytes, expected %d (32 + k*k*4 for k=%d)"
|
||||||
|
% (path, len(raw), need, k))
|
||||||
|
# The diagonal only, read by stride out of the row-major matrix.
|
||||||
|
diag = [struct.unpack_from("<f", raw, HDR + (i * k + i) * 4)[0]
|
||||||
|
for i in range(k)]
|
||||||
|
return k, rank, rows, domain, diag
|
||||||
|
|
||||||
|
|
||||||
|
def load_dir(d, verbose):
|
||||||
|
"""{layer: {"pooled": gram|None, "experts": {global e: gram}}} + rotated count.
|
||||||
|
|
||||||
|
Files outside the ffn_down_exps stem are not ours to fold: the tap names a
|
||||||
|
pooled gram after every projection it taps, and those carry other widths."""
|
||||||
|
if not os.path.isdir(d):
|
||||||
|
refuse("NO_GRAMS %s: not a directory" % d)
|
||||||
|
layers, rotated = {}, 0
|
||||||
|
for name in sorted(os.listdir(d)):
|
||||||
|
m = POOLED_RE.match(name) or EXPERT_RE.match(name)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
path = os.path.join(d, name)
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
continue
|
||||||
|
pooled = bool(POOLED_RE.match(name))
|
||||||
|
layer = int(m.group(2))
|
||||||
|
k, rank, rows, domain, diag = read_gram(path)
|
||||||
|
if domain != 0:
|
||||||
|
rotated += 1
|
||||||
|
L = layers.setdefault(layer, {"pooled": None, "experts": {}})
|
||||||
|
if pooled:
|
||||||
|
P = L["pooled"]
|
||||||
|
if P is None:
|
||||||
|
L["pooled"] = {"k": k, "rows": rows, "diag": diag, "paths": [path]}
|
||||||
|
elif P["k"] != k:
|
||||||
|
refuse("K_MISMATCH layer %d: pooled grams %s (k=%d) and %s (k=%d)"
|
||||||
|
" disagree about the width"
|
||||||
|
% (layer, P["paths"][0], P["k"], path, k))
|
||||||
|
else:
|
||||||
|
# calib_sum: each rank summed the same stem over its own rows.
|
||||||
|
P["rows"] += rows
|
||||||
|
P["paths"].append(path)
|
||||||
|
for i in range(k):
|
||||||
|
P["diag"][i] += diag[i]
|
||||||
|
if verbose:
|
||||||
|
sys.stderr.write("pooled layer %d: %s k=%d rows=%d\n"
|
||||||
|
% (layer, name, k, rows))
|
||||||
|
continue
|
||||||
|
e = int(m.group(3))
|
||||||
|
own = L["experts"].get(e)
|
||||||
|
if own is None:
|
||||||
|
L["experts"][e] = {"k": k, "rows": rows, "diag": diag,
|
||||||
|
"path": path, "ranks": [rank]}
|
||||||
|
if verbose:
|
||||||
|
sys.stderr.write("expert layer %d r%d e%d: rows=%d\n"
|
||||||
|
% (layer, rank, e, rows))
|
||||||
|
continue
|
||||||
|
if own["rows"] != rows or own["diag"] != diag:
|
||||||
|
refuse("EXPERT_CONFLICT layer %d expert %d: %s (rows=%d) and %s"
|
||||||
|
" (rows=%d) record the same global expert with different"
|
||||||
|
" numbers" % (layer, e, own["path"], own["rows"], path, rows))
|
||||||
|
own["ranks"].append(rank)
|
||||||
|
sys.stderr.write("note: layer %d expert %d recorded identically by rank %s"
|
||||||
|
" and %s; folded once\n"
|
||||||
|
% (layer, e, own["path"], name))
|
||||||
|
if not layers:
|
||||||
|
refuse("NO_GRAMS %s: no RADGRAM2 gram.r<rank>.blk.<L>.ffn_down_exps"
|
||||||
|
"[.<expert>].bin files in the directory" % d)
|
||||||
|
return layers, rotated
|
||||||
|
|
||||||
|
|
||||||
|
def build_entries(layers, min_rows, verbose):
|
||||||
|
"""([ (name, dims, payload|None, key) ], census, layers emitted)."""
|
||||||
|
entries = []
|
||||||
|
census = {"inherited": 0, "experts": 0, "routing": 0, "rows": 0, "layers": 0}
|
||||||
|
for layer in sorted(layers):
|
||||||
|
P, E = layers[layer]["pooled"], layers[layer]["experts"]
|
||||||
|
if E and P is None:
|
||||||
|
refuse("NO_POOLED layer %d: %d expert gram(s) but no pooled gram (an"
|
||||||
|
" expected gram.r<rank>.blk.%d.ffn_down_exps.bin), so an expert"
|
||||||
|
" under the row threshold would have nothing to inherit"
|
||||||
|
% (layer, len(E), layer))
|
||||||
|
if P is None:
|
||||||
|
continue
|
||||||
|
k = P["k"]
|
||||||
|
census["layers"] += 1
|
||||||
|
census["rows"] = max(census["rows"], P["rows"])
|
||||||
|
dn = "blk.%d.ffn_down_exps" % layer
|
||||||
|
# A staging key names ONE payload. A gate_up entry reuses the down
|
||||||
|
# layer's bytes by carrying the same key with no payload of its own;
|
||||||
|
# in_sum2 and counts are different bytes and must never share a key.
|
||||||
|
ksum, kcnt = (layer, "sum2"), (layer, "counts")
|
||||||
|
kpgm, kpcnt = (layer, "pooled_sum2"), (layer, "pooled_counts")
|
||||||
|
# One entry holds one width: an expert gram recorded at another k would
|
||||||
|
# inherit (or be inherited by) a diagonal of the wrong length, and the
|
||||||
|
# reader would then answer `cols` from whichever file set dims[0].
|
||||||
|
for e in sorted(E):
|
||||||
|
if E[e]["k"] != k:
|
||||||
|
refuse("K_MISMATCH layer %d expert %d: %s holds k=%d and this"
|
||||||
|
" layer's pooled gram is k=%d" % (layer, e, E[e]["path"],
|
||||||
|
E[e]["k"], k))
|
||||||
|
|
||||||
|
if not E:
|
||||||
|
# No per-expert recording for this layer: emit the dense form
|
||||||
|
# ([k], [1]) rather than 512 identical slices, which would claim a
|
||||||
|
# per-expert measurement that was never taken.
|
||||||
|
entries.append((dn + ".in_sum2", [k],
|
||||||
|
struct.pack("<%df" % k, *P["diag"]), ksum))
|
||||||
|
entries.append((dn + ".counts", [1],
|
||||||
|
struct.pack("<f", float(P["rows"])), kcnt))
|
||||||
|
sys.stderr.write("note: layer %d has no per-expert grams; %s is"
|
||||||
|
" dense over the pooled gram\n" % (layer, dn))
|
||||||
|
else:
|
||||||
|
floor = min_rows if min_rows > 0 else KOWN_ROWS_PER_K * k
|
||||||
|
n_expert = max(E) + 1
|
||||||
|
sum2, counts = bytearray(), bytearray()
|
||||||
|
for e in range(n_expert):
|
||||||
|
own = E.get(e)
|
||||||
|
if own is not None and own["rows"] >= floor:
|
||||||
|
src = own["diag"]
|
||||||
|
rows = own["rows"]
|
||||||
|
else:
|
||||||
|
src = P["diag"]
|
||||||
|
rows = own["rows"] if own is not None else 0
|
||||||
|
census["inherited"] += 1
|
||||||
|
sum2 += struct.pack("<%df" % k, *src)
|
||||||
|
counts += struct.pack("<f", float(rows))
|
||||||
|
census["experts"] += 1
|
||||||
|
census["routing"] += rows
|
||||||
|
if min(E) != 0:
|
||||||
|
sys.stderr.write("note: layer %d has no gram for experts 0..%d;"
|
||||||
|
" those slices carry the pooled gram at count 0\n"
|
||||||
|
% (layer, min(E) - 1))
|
||||||
|
entries.append((dn + ".in_sum2", [k, n_expert], bytes(sum2), ksum))
|
||||||
|
entries.append((dn + ".counts", [1, n_expert], bytes(counts), kcnt))
|
||||||
|
gu = "blk.%d.ffn_gate_up_exps" % layer
|
||||||
|
entries.append((gu + ".in_sum2", [k, n_expert], None, ksum))
|
||||||
|
entries.append((gu + ".counts", [1, n_expert], None, kcnt))
|
||||||
|
if verbose:
|
||||||
|
sys.stderr.write("layer %d: k=%d n_expert=%d pooled_rows=%d"
|
||||||
|
" own-gram floor=%d\n"
|
||||||
|
% (layer, k, n_expert, P["rows"], floor))
|
||||||
|
|
||||||
|
inz = "blk.%d.ssm_inz" % layer
|
||||||
|
entries.append((inz + ".in_sum2", [k],
|
||||||
|
struct.pack("<%df" % k, *P["diag"]), kpgm))
|
||||||
|
entries.append((inz + ".counts", [1],
|
||||||
|
struct.pack("<f", float(P["rows"])), kpcnt))
|
||||||
|
return entries, census
|
||||||
|
|
||||||
|
|
||||||
|
def enc_str(v):
|
||||||
|
b = v.encode("utf-8")
|
||||||
|
return struct.pack("<Q", len(b)) + b
|
||||||
|
|
||||||
|
|
||||||
|
def write_gguf(path, entries, datasets, chunk_size, chunk_count):
|
||||||
|
"""Write GGUF v3 and return its size in bytes.
|
||||||
|
|
||||||
|
One payload per key: entries that share a key share the bytes and the
|
||||||
|
offset, so a second tensor info states the same fact without copying it."""
|
||||||
|
kv = bytearray()
|
||||||
|
kv += enc_str("general.alignment") + struct.pack("<I", GT_U32) \
|
||||||
|
+ struct.pack("<I", ALIGNMENT)
|
||||||
|
kv += enc_str("imatrix.chunk_size") + struct.pack("<I", GT_U32) \
|
||||||
|
+ struct.pack("<I", chunk_size)
|
||||||
|
kv += enc_str("imatrix.chunk_count") + struct.pack("<I", GT_U32) \
|
||||||
|
+ struct.pack("<I", chunk_count)
|
||||||
|
kv += enc_str("imatrix.datasets") + struct.pack("<I", GT_ARR) \
|
||||||
|
+ struct.pack("<I", GT_STR) + struct.pack("<Q", len(datasets))
|
||||||
|
for d in datasets:
|
||||||
|
kv += enc_str(d)
|
||||||
|
|
||||||
|
head = bytearray()
|
||||||
|
head += b"GGUF" + struct.pack("<I", 3)
|
||||||
|
head += struct.pack("<Q", len(entries)) + struct.pack("<Q", 4)
|
||||||
|
head += kv
|
||||||
|
|
||||||
|
payloads = {}
|
||||||
|
for name, _dims, payload, key in entries:
|
||||||
|
if payload is None:
|
||||||
|
continue
|
||||||
|
prev = payloads.get(key)
|
||||||
|
if prev is not None and prev != payload:
|
||||||
|
# Two different payloads on one key would ship whichever was
|
||||||
|
# appended last while BOTH tensor infos pointed at it: the counts
|
||||||
|
# payload landing over the in_sum2 payload is exactly how a
|
||||||
|
# hand-rolled GGUF writer loses half its data without an error.
|
||||||
|
refuse("INTERNAL %s: two different payloads share one staging key"
|
||||||
|
% name)
|
||||||
|
payloads.setdefault(key, payload)
|
||||||
|
infos = bytearray()
|
||||||
|
offsets, order, size = {}, [], 0
|
||||||
|
for name, dims, payload, key in entries:
|
||||||
|
if key not in payloads:
|
||||||
|
refuse("INTERNAL %s: no payload staged for its key" % name)
|
||||||
|
if key not in offsets:
|
||||||
|
offsets[key] = (size + ALIGNMENT - 1) // ALIGNMENT * ALIGNMENT
|
||||||
|
size = offsets[key] + len(payloads[key])
|
||||||
|
order.append(key)
|
||||||
|
infos += enc_str(name) + struct.pack("<I", len(dims))
|
||||||
|
for d in dims:
|
||||||
|
infos += struct.pack("<Q", d)
|
||||||
|
infos += struct.pack("<I", GGML_F32) + struct.pack("<Q", offsets[key])
|
||||||
|
|
||||||
|
body = bytes(head) + bytes(infos)
|
||||||
|
data_start = (len(body) + ALIGNMENT - 1) // ALIGNMENT * ALIGNMENT
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(body)
|
||||||
|
f.write(b"\0" * (data_start - len(body)))
|
||||||
|
# The offsets in the tensor infos are the file's law, not a hint: each
|
||||||
|
# payload must land at its own padded offset. Writing them back to back
|
||||||
|
# instead leaves every reader that trusts the info — imatrix.cpp does —
|
||||||
|
# reading its neighbour's floats: plausible values, no error, wrong
|
||||||
|
# quantisation.
|
||||||
|
here = data_start
|
||||||
|
for key in order:
|
||||||
|
f.write(b"\0" * (data_start + offsets[key] - here))
|
||||||
|
f.write(payloads[key])
|
||||||
|
here = data_start + offsets[key] + len(payloads[key])
|
||||||
|
return f.tell()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(
|
||||||
|
description="Fold a radiance calibration recording into a GGUF v3 imatrix.")
|
||||||
|
ap.add_argument("calib_dir")
|
||||||
|
ap.add_argument("-o", "--out", required=True)
|
||||||
|
ap.add_argument("--datasets", default="",
|
||||||
|
help="comma-separated names for imatrix.datasets")
|
||||||
|
ap.add_argument("--chunk-size", type=int, default=4096,
|
||||||
|
help="rows per chunk for imatrix.chunk_size (default 4096)")
|
||||||
|
ap.add_argument("--min-rows", type=int, default=0,
|
||||||
|
help="own-gram row threshold (default 4*k, the engine's"
|
||||||
|
" kOwnRowsPerK law)")
|
||||||
|
ap.add_argument("-v", "--verbose", action="store_true")
|
||||||
|
a = ap.parse_args()
|
||||||
|
|
||||||
|
if os.path.exists(a.out):
|
||||||
|
# The repo's deletion law: an artifact that exists is moved to trash by
|
||||||
|
# a human, never silently overwritten by a re-run of the tool that made
|
||||||
|
# it, so a second convert cannot destroy the imatrix the first one fed.
|
||||||
|
refuse("EXISTS %s: an imatrix is already there; move it to"
|
||||||
|
" .AGENTTRASH/ or name another -o" % a.out)
|
||||||
|
if a.chunk_size <= 0:
|
||||||
|
refuse("BAD_CHUNK_SIZE --chunk-size %d is not positive" % a.chunk_size)
|
||||||
|
layers, rotated = load_dir(a.calib_dir, a.verbose)
|
||||||
|
entries, census = build_entries(layers, a.min_rows, a.verbose)
|
||||||
|
if not entries:
|
||||||
|
refuse("NO_GRAMS %s: no layer holds a pooled gram to fold" % a.calib_dir)
|
||||||
|
if rotated:
|
||||||
|
sys.stderr.write("note: %d gram(s) recorded in a rotated domain: their"
|
||||||
|
" diagonal is the rotated activation's second moment,"
|
||||||
|
" not the model's own\n" % rotated)
|
||||||
|
|
||||||
|
chunk_count = census["rows"] // a.chunk_size
|
||||||
|
if chunk_count > 0xFFFFFFFF:
|
||||||
|
# imatrix.chunk_count is a u32 in the reader's grammar, so there is no
|
||||||
|
# legal way to record a bigger one. Reaching this means either a chunk
|
||||||
|
# size picked for a far larger run or a row total read out of the wrong
|
||||||
|
# header offset — rows is a u64 and a u32 parse is off by 2**32 — and
|
||||||
|
# the reader would trust whatever number landed in the field.
|
||||||
|
refuse("CHUNK_COUNT %d = %d rows // %d does not fit the u32"
|
||||||
|
" imatrix.chunk_count field; raise --chunk-size"
|
||||||
|
% (chunk_count, census["rows"], a.chunk_size))
|
||||||
|
nbytes = write_gguf(a.out, entries, [d for d in a.datasets.split(",") if d],
|
||||||
|
a.chunk_size, chunk_count)
|
||||||
|
sys.stderr.write("IMATRIX_OK entries=%d layers=%d experts=%d inherited=%d"
|
||||||
|
" rows=%d routing=%d chunks=%d/%d bytes=%d out=%s\n"
|
||||||
|
% (len(entries), census["layers"], census["experts"],
|
||||||
|
census["inherited"], census["rows"], census["routing"],
|
||||||
|
chunk_count, a.chunk_size, nbytes, a.out))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Executable
+122
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# kld-gate-qwen38.sh — RUNS ON DANKSERVER (or anywhere the reports are). The campaign's
|
||||||
|
# acceptance gate: three KLD reports against ONE reference recording, one verdict.
|
||||||
|
#
|
||||||
|
# The reference is the BF16 container built from the vendor checkpoint (run-qwen38-bf16.sh
|
||||||
|
# kldref, in the same drained window that records the calibration grams). Every scored
|
||||||
|
# container is measured against that one recording, so the KL scale runs from 0 = the
|
||||||
|
# checkpoint itself downward in faithfulness. Three containers ride it:
|
||||||
|
#
|
||||||
|
# fp8.json the published FP8-native release — the achievable end of the interval
|
||||||
|
# iq4r.json the payload frank-archon serves — the bar to beat
|
||||||
|
# cand.json the arm under test — the subject
|
||||||
|
#
|
||||||
|
# The FP8 number is what makes the interval absolute instead of relative: "half the gap"
|
||||||
|
# is half of the distance between the 4-bit payload and the near-lossless release, not half
|
||||||
|
# of an invented distance to the checkpoint. That is also why the FP8 payload is FETCHED and
|
||||||
|
# scored, never converted: it is a second opinion about what this checkpoint tolerates, not a
|
||||||
|
# step of the campaign. It lands LAST (disk sequencing) — by then the 335 GiB bf16 container
|
||||||
|
# is gone, so its 172.8 GiB fits.
|
||||||
|
#
|
||||||
|
# The law, in order of severity, on the mean KL of one slice (--slice all | a corpus source):
|
||||||
|
# 1. KL(fp8) < KL(iq4r) the anchors are ordered. If the near-lossless
|
||||||
|
# release does NOT sit closer to the checkpoint than
|
||||||
|
# the 4-bit payload, the corpus, the tokenizer or the
|
||||||
|
# boot shape is wrong and no arm number means anything.
|
||||||
|
# 2. KL(fp8) < KL(cand) < KL(iq4r) the candidate lies strictly inside the interval —
|
||||||
|
# outside it, something is mis-measured, not better
|
||||||
|
# 3. KL(cand) <= KL(iq4r) - R x gap it closes at least R (--recover, default 0.5) of the
|
||||||
|
# gap = KL(iq4r) - KL(fp8): at least half the way from
|
||||||
|
# what is served toward the release (agreed 2026-10-27)
|
||||||
|
# 4. top-1(cand) >= top-1(iq4r) the agreement the harness keys on does not fall
|
||||||
|
#
|
||||||
|
# Booting the containers is NOT this script's job: `run-qwen38-bf16.sh score RAD OUT.json`
|
||||||
|
# writes the reports and owns the single engine argv (same pin, --tp 2, --kv-cache-dtype fp8,
|
||||||
|
# same step size, --deterministic, no speculation) that makes the three numbers comparable.
|
||||||
|
# This script is arithmetic, so it re-runs against old reports and is testable without a GPU.
|
||||||
|
#
|
||||||
|
# Usage: kld-gate-qwen38.sh FP8.json IQ4R.json CAND.json [--slice NAME] [--recover F]
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
[ "$#" -ge 3 ] || { echo "usage: kld-gate-qwen38.sh FP8.json IQ4R.json CAND.json [--slice NAME] [--recover F]" >&2; exit 2; }
|
||||||
|
python3 - "$@" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
|
||||||
|
a = sys.argv[1:]
|
||||||
|
opts, files = [], []
|
||||||
|
i = 0
|
||||||
|
while i < len(a):
|
||||||
|
if a[i].startswith("--"):
|
||||||
|
opts += [a[i], a[i + 1]]
|
||||||
|
i += 2
|
||||||
|
else:
|
||||||
|
files.append(a[i])
|
||||||
|
i += 1
|
||||||
|
if len(files) != 3:
|
||||||
|
sys.exit("usage: kld-gate-qwen38.sh FP8.json IQ4R.json CAND.json [--slice NAME] [--recover F]")
|
||||||
|
o = dict(zip(opts[0::2], opts[1::2]))
|
||||||
|
slice_ = o.get("--slice", "all")
|
||||||
|
recover = float(o.get("--recover", 0.5))
|
||||||
|
if not 0.0 < recover <= 1.0:
|
||||||
|
sys.exit(f"--recover must be in (0,1], got {recover}")
|
||||||
|
|
||||||
|
|
||||||
|
def die(msg):
|
||||||
|
print(msg, file=sys.stderr)
|
||||||
|
raise SystemExit(2)
|
||||||
|
|
||||||
|
|
||||||
|
def read(path):
|
||||||
|
try:
|
||||||
|
d = json.load(open(path))
|
||||||
|
except Exception as e:
|
||||||
|
die(f"cannot read the KLD report {path}: {e}")
|
||||||
|
if slice_ == "all":
|
||||||
|
g = d.get("all")
|
||||||
|
else:
|
||||||
|
g = (d.get("by_source") or {}).get(slice_)
|
||||||
|
if g is None:
|
||||||
|
die(f"{path} has no slice {slice_!r}; it has: "
|
||||||
|
f"{', '.join(sorted(d.get('by_source') or {})) or '(none)'}")
|
||||||
|
if g is None or "kld" not in g or "top1_agreement" not in g:
|
||||||
|
die(f"{path} has no {slice_!r} summary — wrong report version?")
|
||||||
|
return d, g
|
||||||
|
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for label, path in zip(("fp8", "iq4r", "cand"), files):
|
||||||
|
d, g = read(path)
|
||||||
|
rows.append((label, path, d.get("model", "?"), g["kld"]["mean"], g["kld"]["p99_9"],
|
||||||
|
g["top1_agreement"], g["positions"]))
|
||||||
|
|
||||||
|
print(f"reference: BF16 container; slice: {slice_}")
|
||||||
|
print(f"{'':6} {'model':38} {'KL mean':>10} {'KL p99.9':>10} {'top-1':>8} positions")
|
||||||
|
for label, path, model, kl, p999, top1, n in rows:
|
||||||
|
print(f"{label:6} {str(model)[:38]:38} {kl:10.6f} {p999:10.4f} {100*top1:7.2f}% {n}")
|
||||||
|
r = {label: (kl, top1) for label, _p, _m, kl, _q, top1, _n in rows}
|
||||||
|
kl_fp8, kl_iq4r, kl_cand = r["fp8"][0], r["iq4r"][0], r["cand"][0]
|
||||||
|
t_iq4r, t_cand = r["iq4r"][1], r["cand"][1]
|
||||||
|
gap = kl_iq4r - kl_fp8
|
||||||
|
ok = True
|
||||||
|
|
||||||
|
|
||||||
|
def law(n, text, passed):
|
||||||
|
global ok
|
||||||
|
ok = ok and passed
|
||||||
|
print(f" [{'PASS' if passed else 'FAIL'}] {n}. {text}")
|
||||||
|
|
||||||
|
|
||||||
|
law(1, f"anchors ordered: fp8 {kl_fp8:.6f} < iq4r {kl_iq4r:.6f} (gap {gap:.6f})",
|
||||||
|
gap > 0)
|
||||||
|
law(2, f"candidate inside the interval: {kl_fp8:.6f} < {kl_cand:.6f} < {kl_iq4r:.6f}",
|
||||||
|
kl_fp8 < kl_cand < kl_iq4r)
|
||||||
|
need = kl_iq4r - recover * gap
|
||||||
|
law(3, f"candidate closes {100*max((kl_iq4r - kl_cand)/gap, 0):.1f}% of the "
|
||||||
|
f"{gap:.6f} gap (needs {100*recover:.0f}%, i.e. KL <= {need:.6f})"
|
||||||
|
if gap > 0 else "gap is not positive; cannot score recovery",
|
||||||
|
gap > 0 and kl_cand <= need)
|
||||||
|
law(4, f"candidate top-1 {100*t_cand:.2f}% >= served iq4r top-1 {100*t_iq4r:.2f}%",
|
||||||
|
t_cand >= t_iq4r)
|
||||||
|
print("KLD GATE " + ("PASS" if ok else "FAIL"))
|
||||||
|
sys.exit(0 if ok else 1)
|
||||||
|
PY
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# qwen4exp-qwen38-w5nl64-bf16trunk.recipe — every routed expert, BOTH projections, at five bits:
|
||||||
|
# u5 codes into libquant's 32-level w5nl table, an E4M3 scale a group of 64 under the fixed 2^-13
|
||||||
|
# second level, rotated by the 128-wide Hadamard, chosen by GPTQ against $CALIB and swept three
|
||||||
|
# times by coordinate descent. This is `w5nl64a8h` in the engine's served-encoding table — the top of
|
||||||
|
# the four-bit class — applied wider than any published radiance container of this checkpoint: the
|
||||||
|
# author's own w5 rung (`qwen4exp-w5dn64-i8-hc8m.recipe`) moves only `down`, because that rung was
|
||||||
|
# priced as a byte-for-error trade against prefill; this campaign buys the whole rung and pays for
|
||||||
|
# it out of the pinned host pool (+15.3 GiB of expert plane, `--host-pool-mib 61440` per rank), not
|
||||||
|
# out of the KV pool.
|
||||||
|
#
|
||||||
|
# Every other line is the served container's recipe (dump it with
|
||||||
|
# `rad-info --recipe /home/dank/models/rad/qwen3.8-next-flash-fp8-iq4r-moe.rad`), minus the rows this
|
||||||
|
# campaign leaves at the checkpoint:
|
||||||
|
#
|
||||||
|
# * the trunk's block linears, the shared expert and the hyper-connection mixers are NOT listed —
|
||||||
|
# dropping their i8/fp8 lines leaves them bf16, which is what the checkpoint holds. Measured bill
|
||||||
|
# +1.978 GiB of always-resident weight = ~131k tokens of KV off the pool.
|
||||||
|
# * `ssm_inz`/`ssm_out` STAY int8: the delta-net gates are the largest trunk rows (+1.9 GiB, 126k
|
||||||
|
# tokens) and the least sensitive per byte.
|
||||||
|
# * `mtp.draft_head` STAYS 2-bit and the PLE n-gram table STAYS one fixed E4M3 scale: the draft
|
||||||
|
# head's rung is cheap and its acceptance cost is a decode question this campaign does not touch,
|
||||||
|
# and bf16 for the n-gram table would pin +47.69 GiB of host RAM for a lookup.
|
||||||
|
#
|
||||||
|
# Protected experts: none in this file. `scripts/apply-protected-cast.sh` appends the derived set
|
||||||
|
# (48, two per layer so each rank's 256-expert half stays even — the four-bit GEMM takes the rest of
|
||||||
|
# a half as two equal tables by parity) from the fresh 12M-token energy/routing census, and they must
|
||||||
|
# sit ABOVE the expert row because the first rule that matches a weight decides it.
|
||||||
|
#
|
||||||
|
# CALIB=/home/dank/campaign/qwen38-w5nl64/calib \
|
||||||
|
# rad-convert /home/dank/models/Qwen3.8-Flash-Next \
|
||||||
|
# --recipe this.file --imatrix .../imatrix.gguf -o out.rad
|
||||||
|
#
|
||||||
|
# The arm driver (`scripts/convert-qwen38-w5nl64.sh`) generates the per-arm variants of the expert
|
||||||
|
# row from this template — cd=3 (control), cd=6, and `act_order=1` with NO cd= (libquant refuses the
|
||||||
|
# pair, lq_registry.cpp:105) — rather than keeping three near-duplicate recipes that can drift apart.
|
||||||
|
#
|
||||||
|
# The whole campaign, in the order that fits on one disk (every step runs on dankserver):
|
||||||
|
#
|
||||||
|
# 1. scripts/download-qwen38-bf16.sh --fetch the vendor BF16 checkpoint, 335.3 GiB
|
||||||
|
# 2. scripts/build-qwen38-bf16-rad.sh the BF16 reference container, another 335.3 GiB
|
||||||
|
# 3. one drained window, two boots:
|
||||||
|
# scripts/run-qwen38-bf16.sh kldref the KLD reference recording (the zero of scale)
|
||||||
|
# scripts/run-qwen38-bf16.sh calib the 12M-position calibration tap -> grams
|
||||||
|
# 4. trash qwen38-bf16.rad the peak-disk law: the converts need its room
|
||||||
|
# 5. scripts/grams-to-imatrix.py grams -> imatrix.gguf in the campaign dir
|
||||||
|
# 6. scripts/convert-qwen38-w5nl64.sh the arms, planned and billed before each one runs
|
||||||
|
# 7. fetch the published FP8-native release, then per container:
|
||||||
|
# scripts/run-qwen38-bf16.sh score RAD OUT fp8 anchor, served iq4r, each arm
|
||||||
|
# then scripts/kld-gate-qwen38.sh fp8.json iq4r.json cand.json
|
||||||
|
# 8. a winner is a trio edit plus `scripts/deploy.sh dankserver --service frank-archon`; the
|
||||||
|
# rollback is the commit before it. If the win needs the bigger pinned plane, raise
|
||||||
|
# FRANK_ARCHON_HOST_POOL_MIB and FRANK_ARCHON_TTM_TARGET_MIB in the SAME commit: the preflight
|
||||||
|
# floor is 2xHOST_POOL + 48,824 + prefix cache + MNBT + the store, and a pool raised without
|
||||||
|
# the TTM target is a boot refusal that Restart=always repeats forever, invisible to deploy.sh.
|
||||||
|
|
||||||
|
# PROTECTED-BEGIN — scripts/apply-protected-cast.sh splices the derived cast lines in here.
|
||||||
|
# They MUST sit above the expert rule: the first rule whose glob matches a weight decides it, so a
|
||||||
|
# cast line below the five-bit rule would never win. The set is derived from the campaign's own
|
||||||
|
# 12M-token recording (`derive-protected-experts.py --target-count 48` over the fresh grams), and the
|
||||||
|
# driver splices it into a per-arm COPY so this template stays the clean, reviewable baseline.
|
||||||
|
# PROTECTED-END
|
||||||
|
|
||||||
|
# Every routed expert, trunk and MTP layer alike, both projections, at five bits. The experts' scales
|
||||||
|
# sit between 2^-15 and 2^-10.6 under w4nl's top level of 22 and half a binade lower under w5nl's 30
|
||||||
|
# — inside E4M3's normal range under 2^-13 either way. The MTP layer has no recording and is rounded
|
||||||
|
# to nearest, which libquant says on stderr; it is one layer of 49 and only ever changes drafts.
|
||||||
|
blk.*.ffn_*_exps.*.weight gptq table=w5nl group=64 scale=fp8_e4m3 scale2=f32 block2=*x* scale2_value=0.0001220703125 transform=fwht128 rule=search cd=3 calib=$CALIB
|
||||||
|
|
||||||
|
# The delta-net's in/out gates, int8 with a bf16 scale a group of 128 searched for least error — the
|
||||||
|
# served recipe's lines, unchanged.
|
||||||
|
blk.*.ssm_inz.weight rtn codes=i8 group=128 scale=bf16 clamp=sym rule=search
|
||||||
|
blk.*.ssm_out.weight rtn codes=i8 group=128 scale=bf16 clamp=sym rule=search
|
||||||
|
|
||||||
|
# The MTP head's two mixing rows, E4M3 as served. They only ever change the drafts.
|
||||||
|
mtp.fc_hidden.weight rtn codes=fp8_e4m3 group=128 scale=f32
|
||||||
|
mtp.fc_embedding.weight rtn codes=fp8_e4m3 group=128 scale=f32
|
||||||
|
|
||||||
|
# The n-gram table at one fixed E4M3 scale, and the 2-bit draft head — both the served lines.
|
||||||
|
blk.*.ple_ngram.weight rtn codes=fp8_e4m3 block=*x* scale=bf16 rule=fixed scale_value=1.99317932128906e-4
|
||||||
|
mtp.draft_head.weight rtn codes=u2 zero=u8 group=128 scale=f16
|
||||||
Executable
+329
@@ -0,0 +1,329 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# run-qwen38-bf16.sh — RUNS ON DANKSERVER. Boot a container over the campaign's
|
||||||
|
# ONE measuring shape for the phases that need both R9700s. The engine owns the
|
||||||
|
# cards, so frank-archon is stopped first and restored on every exit path — the
|
||||||
|
# autoresearch pattern: stop, never mask (the unit file in /etc/systemd/system
|
||||||
|
# is a symlink into the checkout and mask fails on a relinked unit; the deploy
|
||||||
|
# restores it).
|
||||||
|
#
|
||||||
|
# kldref --kld-record the reference log-probabilities: the engine
|
||||||
|
# runs /home/dank/corpora/out/kld.jsonl (197 prompts, 50,432
|
||||||
|
# scored positions, score tail 256) through prefill, writes
|
||||||
|
# /home/dank/campaign/qwen38-w5nl64/kld-ref, and EXITS. The
|
||||||
|
# model behind it is the BF16 container from
|
||||||
|
# scripts/build-qwen38-bf16-rad.sh — the vendor checkpoint's
|
||||||
|
# own numbers, which is what makes it the zero point.
|
||||||
|
# score RAD OUT --kld-ref score ONE container against that recording and
|
||||||
|
# EXIT: the FP8-native anchor, the served iq4r, and every
|
||||||
|
# arm. Deliberately the SAME engine pin, the same TP, the
|
||||||
|
# same step size, the same fp8 KV, --deterministic and no
|
||||||
|
# speculation as kldref — KL and top-1 agreement are only
|
||||||
|
# comparable across boots when everything except the weights
|
||||||
|
# is identical, so this mode is one argv with --model and
|
||||||
|
# the two --kld-* flags changed, never a second shape.
|
||||||
|
# RAD must sit under the models dir (the container reads it
|
||||||
|
# through the ro /models mount); OUT lands on a writable
|
||||||
|
# mount because --kld-out F also writes F.rows beside it.
|
||||||
|
# calib serve on :7770 with the calibration tap open
|
||||||
|
# (RADIANCE_CALIB_DIR + RADIANCE_CALIB_TOKENS=12000000). A
|
||||||
|
# separate driver — scripts/calib-push.py --port 7770
|
||||||
|
# --container qwen38-bf16 --corpus
|
||||||
|
# /home/dank/corpora/out/calib.jsonl — pushes the corpus; at
|
||||||
|
# 12M pooled positions the engine writes the Grams and the
|
||||||
|
# tap switches itself off (calib-push prints TAP_DONE). Stop
|
||||||
|
# this script after that; the trap brings frank-archon back.
|
||||||
|
#
|
||||||
|
# Shape: the vendor's bf16 calibration lines — expert_tiered placement with the
|
||||||
|
# weights disk tier, deterministic (the resident set pinned to the loader's plan,
|
||||||
|
# so a recorded reference and a scored candidate see the same one), no
|
||||||
|
# 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 —
|
||||||
|
# every scored container is asked the same tokenised questions the reference
|
||||||
|
# answered. The window is 32,768 because this corpus is long: measured on
|
||||||
|
# Dankserver 2026-10-27, kld.jsonl is 197 records with a longest of 38,263
|
||||||
|
# characters (longest prompt FIELD 36,855, median record 27,228), which is ~9,600
|
||||||
|
# tokens at the pessimistic chars/4 and ~7,700 at the ratio this repo actually
|
||||||
|
# measured on the K3 corpus (380,143 chars = 76,568 tokens, chars/4.97). A 16,384
|
||||||
|
# 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
|
||||||
|
# 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
|
||||||
|
# model needs." The budget resolver derives the per-card split from
|
||||||
|
# --host-pool-mib and --gpu-headroom-mib, and those two are deliberately NOT
|
||||||
|
# production's 49,152 / 1,536 (frank-archon.env:77): the pool decides WHICH
|
||||||
|
# experts stay resident, so it buys speed and never logits — the shape the gate
|
||||||
|
# has to hold identical is the kv dtype, the slot count, the step size, the spec
|
||||||
|
# depth and the window. It is also the only figure that fits: a 335 GiB bf16
|
||||||
|
# container cannot be booted against a 49 GiB resident set on a 192 GiB machine,
|
||||||
|
# and a prefill-only pass does not care that the disk tier paces at ~3 GiB/s.
|
||||||
|
#
|
||||||
|
# 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,
|
||||||
|
# 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
|
||||||
|
# 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.
|
||||||
|
# Recording the reference at 32,768 would therefore measure a shape the payloads
|
||||||
|
# cannot reproduce; at 2,048 the 50,432 positions cost more steps, which is still
|
||||||
|
# one drained window. 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 (a disk-tier step costs the same at 1 token as at 64k).
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
# dir, OUT the report path whose directory is created if missing)
|
||||||
|
# --dry-run print the exact `podman run` argv, one shell-quoted argument a
|
||||||
|
# line, and exit 0: the service is NOT stopped, nothing is
|
||||||
|
# mounted, nothing is written — the offline test seam. It still
|
||||||
|
# validates the argv's own shape (paths through the /models mount,
|
||||||
|
# a step size that is a number), because an argv that could not
|
||||||
|
# boot is not worth printing.
|
||||||
|
# --image REF override the digest pin (a probe boot, or the test seam)
|
||||||
|
#
|
||||||
|
# knobs: FRANK_QWEN38_MODELS (the dir mounted at /models), FRANK_QWEN38_RAD (the
|
||||||
|
# bf16 container, kldref only), FRANK_QWEN38_CAMPAIGN (holds kld-ref/ and
|
||||||
|
# calib/), FRANK_QWEN38_KLD_CORPUS, FRANK_QWEN38_TPL (the template copy
|
||||||
|
# the container reads through /models), FRANK_QWEN38_LOG_DIR, and
|
||||||
|
# FRANK_QWEN38_MNBT — the step size shared by kldref and score (2,048 is
|
||||||
|
# the file tier's ceiling and production's own value, four slots and all),
|
||||||
|
# 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).
|
||||||
|
# kldref refuses (exit 2) when the longest record of the corpus it is about to
|
||||||
|
# read is longer than MAX_MODEL_LEN x 3 characters — chars/3 being the
|
||||||
|
# pessimistic token:char ratio, so the script says no before the engine finds out
|
||||||
|
# inside a drained window, after the checkpoint work is already done. score
|
||||||
|
# refuses when the reference dir is absent, when RAD is not on disk or not under
|
||||||
|
# the models mount, or when OUT already exists: the trash law means a second
|
||||||
|
# score over the same name is a deliberate act, and two numbers in one file is
|
||||||
|
# how a campaign loses its own history.
|
||||||
|
# Engine stdout goes to $LOG_DIR/qwen38_bf16_<mode>.log — the boot ledger is the
|
||||||
|
# record of what the resolver actually gave the cards; the journal never sees it.
|
||||||
|
# exit: the engine's code; 2 = refused before the service was touched
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$SCRIPT_DIR/frank-archon.env"
|
||||||
|
IMAGE="${FRANK_ARCHON_IMAGE:?FRANK_ARCHON_IMAGE must name the radiance image}"
|
||||||
|
|
||||||
|
MODE=""
|
||||||
|
POS=()
|
||||||
|
DRY=0
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
kldref|calib|score) MODE="$1" ;;
|
||||||
|
--dry-run) DRY=1 ;;
|
||||||
|
--image) IMAGE="$2"; shift ;;
|
||||||
|
*)
|
||||||
|
# score's two positional arguments, and nothing else may be bare.
|
||||||
|
if [ "$MODE" = score ] && [ "${#POS[@]}" -lt 2 ]; then
|
||||||
|
POS+=("$1")
|
||||||
|
else
|
||||||
|
echo "usage: ${0##*/} kldref|calib|score RAD OUT [--dry-run] [--image REF] (unknown argument: $1)" >&2
|
||||||
|
exit 2
|
||||||
|
fi ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
if [ -z "$MODE" ]; then
|
||||||
|
echo "usage: ${0##*/} kldref|calib|score RAD OUT [--dry-run] [--image REF] (no mode given)" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if [ "$MODE" = score ] && [ "${#POS[@]}" -ne 2 ]; then
|
||||||
|
echo "usage: ${0##*/} score RAD OUT — RAD is the container to score, OUT the report to write (got ${#POS[@]} argument(s))" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODELS="${FRANK_QWEN38_MODELS:-/home/dank/models}"
|
||||||
|
CAMPAIGN="${FRANK_QWEN38_CAMPAIGN:-/home/dank/campaign/qwen38-w5nl64}"
|
||||||
|
KLD_CORPUS="${FRANK_QWEN38_KLD_CORPUS:-/home/dank/corpora/out/kld.jsonl}"
|
||||||
|
RAD="${FRANK_QWEN38_RAD:-$MODELS/rad/qwen38-bf16.rad}"
|
||||||
|
TPL="${FRANK_QWEN38_TPL:-$MODELS/tpl.jinja}"
|
||||||
|
TEMPLATE="$SCRIPT_DIR/qwen38-froggeric-v22.5.jinja"
|
||||||
|
LOG_DIR="${FRANK_QWEN38_LOG_DIR:-/home/dank/logs}"
|
||||||
|
LOG="$LOG_DIR/qwen38_bf16_${MODE}.log"
|
||||||
|
KLD_REF_DIR="$CAMPAIGN/kld-ref"
|
||||||
|
CALIB_DIR="$CAMPAIGN/calib"
|
||||||
|
C=qwen38-bf16
|
||||||
|
SERVICE=frank-archon
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# reference, so the reference is recorded at the ceiling the payloads can meet —
|
||||||
|
# never two numbers that a KL between them would have to forgive.
|
||||||
|
MNBT="${FRANK_QWEN38_MNBT:-2048}"
|
||||||
|
if ! [[ "$MNBT" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "REFUSED: FRANK_QWEN38_MNBT must be a token count (got: $MNBT)" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The window every mode boots at. Over-length is a REFUSAL, not a truncation (the
|
||||||
|
# env file says so for production), so this is the number the corpus must fit in.
|
||||||
|
MAX_MODEL_LEN="${FRANK_QWEN38_MAX_MODEL_LEN:-32768}"
|
||||||
|
if ! [[ "$MAX_MODEL_LEN" =~ ^[0-9]+$ ]]; then
|
||||||
|
echo "REFUSED: FRANK_QWEN38_MAX_MODEL_LEN must be a token count (got: $MAX_MODEL_LEN)" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Every container this campaign boots is read through the ro /models mount, so
|
||||||
|
# the host path must live under it; the container-side spelling is what goes
|
||||||
|
# into the argv. Same rule for the container a score pass opens.
|
||||||
|
under_models() {
|
||||||
|
case "$1" in
|
||||||
|
"$MODELS"/*) printf '/models/%s' "${1#"$MODELS"/}" ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# same_dir A B — the two name one directory, judged only when BOTH exist: a dry
|
||||||
|
# run has no fixtures on disk, and two absent paths are not the same place.
|
||||||
|
same_dir() {
|
||||||
|
[ -d "$1" ] && [ -d "$2" ] && [ "$(cd "$1" && pwd)" = "$(cd "$2" && pwd)" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$MODE" = score ]; then
|
||||||
|
SCORE_RAD="${POS[0]}"
|
||||||
|
SCORE_OUT="${POS[1]}"
|
||||||
|
OUT_DIR="$(dirname "$SCORE_OUT")"
|
||||||
|
MODEL_IN="$(under_models "$SCORE_RAD")" || {
|
||||||
|
echo "REFUSED: container to score $SCORE_RAD is not under the mounted models dir $MODELS — the engine cannot read it" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
else
|
||||||
|
MODEL_IN="$(under_models "$RAD")" || {
|
||||||
|
echo "REFUSED: reference container $RAD is not under the mounted models dir $MODELS — the engine cannot read it" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The measuring shape. Identical for kldref and score by construction — this
|
||||||
|
# array is the only place the campaign's KL numbers are defined.
|
||||||
|
ARGS=(--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 "$MAX_MODEL_LEN"
|
||||||
|
--ngram-placement ram
|
||||||
|
--override-chat-template /models/tpl.jinja)
|
||||||
|
MOUNTS=(-v "$MODELS:/models:ro")
|
||||||
|
TAPENV=()
|
||||||
|
case "$MODE" in
|
||||||
|
kldref)
|
||||||
|
# Recording is prefill-only over four slots and the shared step, which is
|
||||||
|
# production's own operating point (frank-archon.env: FRANK_ARCHON_MAX_SEQS=4
|
||||||
|
# with FRANK_ARCHON_MAX_BATCHED_TOKENS=2048). Corpus read-only, record dir rw.
|
||||||
|
ARGS+=(--model "$MODEL_IN"
|
||||||
|
--kld-record /data/kld-ref --kld-corpus /data/kld.jsonl
|
||||||
|
--max-num-seqs 4 --max-num-batched-tokens "$MNBT")
|
||||||
|
MOUNTS+=(-v "$KLD_CORPUS:/data/kld.jsonl:ro"
|
||||||
|
-v "$KLD_REF_DIR:/data/kld-ref")
|
||||||
|
;;
|
||||||
|
score)
|
||||||
|
# The same argv with three words changed: which container, and that this
|
||||||
|
# time the engine reads a recording and writes a report instead of the
|
||||||
|
# reverse. The reference dir mounts READ-ONLY — a scoring pass must not be
|
||||||
|
# able to touch the zero point — and the report gets its own writable bind.
|
||||||
|
if [ "$OUT_DIR" = "$KLD_REF_DIR" ] || same_dir "$OUT_DIR" "$KLD_REF_DIR"; then
|
||||||
|
echo "REFUSED: score output $SCORE_OUT is inside the reference dir $KLD_REF_DIR, which this mode mounts read-only" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
ARGS+=(--model "$MODEL_IN"
|
||||||
|
--kld-ref /data/kld-ref --kld-out "/data/out/$(basename "$SCORE_OUT")"
|
||||||
|
--max-num-seqs 4 --max-num-batched-tokens "$MNBT")
|
||||||
|
MOUNTS+=(-v "$KLD_REF_DIR:/data/kld-ref:ro"
|
||||||
|
-v "$OUT_DIR:/data/out")
|
||||||
|
;;
|
||||||
|
calib)
|
||||||
|
# Calibration is exempt from the shared step: the tap counts pooled
|
||||||
|
# positions and nothing downstream is compared against a step size, so the
|
||||||
|
# wide step is free throughput (a disk-tier step costs the same at 1 token
|
||||||
|
# as at 64k).
|
||||||
|
ARGS+=(--model "$MODEL_IN" --port 7770
|
||||||
|
--max-num-seqs 8 --max-num-batched-tokens 65536)
|
||||||
|
MOUNTS+=(-v "$CALIB_DIR:/data/calib")
|
||||||
|
TAPENV=(-e RADIANCE_CALIB_DIR=/data/calib
|
||||||
|
-e RADIANCE_CALIB_TOKENS=12000000)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CMD=(podman run --rm --name "$C"
|
||||||
|
--network host --init
|
||||||
|
--security-opt seccomp=unconfined
|
||||||
|
--ulimit memlock=-1:-1
|
||||||
|
--device /dev/kfd --device /dev/dri
|
||||||
|
"${MOUNTS[@]}" "${TAPENV[@]}"
|
||||||
|
"$IMAGE" "${ARGS[@]}")
|
||||||
|
|
||||||
|
# ---- gate: the window must hold the corpus (kldref alone can meet it) --------
|
||||||
|
# score reads a recording instead of a corpus, and every arm declares at the
|
||||||
|
# checkpoint's training context, so neither can meet a sequence too long to boot.
|
||||||
|
# Measured on the raw JSON line — the pessimistic direction, and the same figure
|
||||||
|
# the census in the header quotes. It runs on a dry run too, because a refusal
|
||||||
|
# this cheap should never have to wait for the drained window to be discovered.
|
||||||
|
if [ "$MODE" = kldref ] && [ -f "$KLD_CORPUS" ]; then
|
||||||
|
read -r REC_NO REC_CHARS < <(awk '{ if (length($0) > best) { best = length($0); where = NR } } END { print where + 0, best + 0 }' "$KLD_CORPUS")
|
||||||
|
LIMIT_CHARS=$(( MAX_MODEL_LEN * 3 ))
|
||||||
|
if [ "$REC_CHARS" -gt "$LIMIT_CHARS" ]; then
|
||||||
|
echo "REFUSED: record $REC_NO of $KLD_CORPUS is $REC_CHARS chars, past the $LIMIT_CHARS a $MAX_MODEL_LEN-token window promises at the pessimistic chars/3 — raise FRANK_QWEN38_MAX_MODEL_LEN, and the container's declared --max-ctx with it, before booting the reference" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "corpus fits: longest record $REC_CHARS chars (number $REC_NO) inside the $MAX_MODEL_LEN-token window (limit $LIMIT_CHARS chars)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$DRY" = 1 ]; then
|
||||||
|
echo "dry run: $MODE — the production unit is left running, nothing is mounted, nothing is written"
|
||||||
|
printf '%q\n' "${CMD[@]}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- gates that only a real run needs ---------------------------------------
|
||||||
|
if [ "$MODE" = score ]; then
|
||||||
|
if [ ! -d "$KLD_REF_DIR" ]; then
|
||||||
|
echo "REFUSED: no KLD reference at $KLD_REF_DIR — record it first in a drained window: scripts/run-qwen38-bf16.sh kldref" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if [ ! -f "$SCORE_RAD" ]; then
|
||||||
|
echo "REFUSED: no container to score at $SCORE_RAD" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if [ -e "$SCORE_OUT" ]; then
|
||||||
|
echo "REFUSED: $SCORE_OUT already exists — one report per name; move the old one to trash before re-scoring" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
else
|
||||||
|
if [ ! -f "$RAD" ]; then
|
||||||
|
echo "REFUSED: no reference container at $RAD — build it first: scripts/build-qwen38-bf16-rad.sh" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
[ "$MODE" = kldref ] && [ ! -f "$KLD_CORPUS" ] && {
|
||||||
|
echo "REFUSED: no KLD corpus at $KLD_CORPUS — the record would be empty" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
if [ ! -f "$TEMPLATE" ]; then
|
||||||
|
echo "REFUSED: no chat template at $TEMPLATE" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
# The prod template, staged where the ro /models mount reaches it: a reference
|
||||||
|
# recorded with the container's baked template scores a different tokenisation
|
||||||
|
# than the arms served with froggeric's, and the KL would be measuring that.
|
||||||
|
cp -f "$TEMPLATE" "$TPL" || {
|
||||||
|
echo "REFUSED: cannot stage the chat template at $TPL (the container reads it through /models)" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
[ "$MODE" = kldref ] && mkdir -p "$KLD_REF_DIR"
|
||||||
|
[ "$MODE" = calib ] && mkdir -p "$CALIB_DIR"
|
||||||
|
|
||||||
|
restore() { sudo systemctl start "$SERVICE" >/dev/null 2>&1 || true; }
|
||||||
|
trap restore EXIT
|
||||||
|
echo "run-qwen38-bf16 $MODE: stopping $SERVICE ($(date -Is)); log: $LOG"
|
||||||
|
sudo systemctl stop "$SERVICE" >/dev/null 2>&1 || true
|
||||||
|
podman rm -f "$C" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
"${CMD[@]}" 2>&1 | tee "$LOG"
|
||||||
|
rc=${PIPESTATUS[0]}
|
||||||
|
echo "run-qwen38-bf16 $MODE exited rc=$rc; $SERVICE restored; log: $LOG"
|
||||||
|
exit "$rc"
|
||||||
Executable
+527
@@ -0,0 +1,527 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
# Seam: the Qwen3.8-Flash-Next five-bit campaign (feat/qwen38-w5nl64).
|
||||||
|
#
|
||||||
|
# The campaign converts the vendor BF16 checkpoint into a w5nl64a8h .rad on
|
||||||
|
# dankserver while frank-archon keeps serving. Every gate that protects the box
|
||||||
|
# or the result lives in a script under scripts/, and each gate is tested here
|
||||||
|
# through that script's own exit status and printed output — no engine, no GPU,
|
||||||
|
# no network. Fixtures are synthetic; the expected numbers are worked examples,
|
||||||
|
# not values recomputed the way the script computes them.
|
||||||
|
#
|
||||||
|
# Seams under test (the campaign's public boundaries):
|
||||||
|
# scripts/download-qwen38-bf16.sh fetch/verify of the BF16 checkpoint
|
||||||
|
# scripts/grams-to-imatrix.py calibration recording -> imatrix GGUF
|
||||||
|
# scripts/derive-protected-experts.py --target-count sweep + the parity law
|
||||||
|
# scripts/build-qwen38-bf16-rad.sh the reference container's convert argv
|
||||||
|
# scripts/run-qwen38-bf16.sh the drained-serve argv, mode selection
|
||||||
|
# scripts/convert-qwen38-w5nl64.sh the arm driver's refusal gates
|
||||||
|
# scripts/kld-gate-qwen38.sh the three-anchor interval law
|
||||||
|
# scripts/deploy-plan.py campaign files must deploy nothing
|
||||||
|
|
||||||
|
load helpers
|
||||||
|
|
||||||
|
DOWNLOAD="$REPO_ROOT/scripts/download-qwen38-bf16.sh"
|
||||||
|
PLANNER="$REPO_ROOT/scripts/deploy-plan.py"
|
||||||
|
|
||||||
|
# make_shard <dir> <name> <total-bytes> — a REAL safetensors file whose size on
|
||||||
|
# disk is exactly <total-bytes>: 8-byte header length, JSON header, tensor data.
|
||||||
|
# Real, because the verifier reads each shard's own header to learn how many
|
||||||
|
# bytes it claims to be, which is the only per-file truth available offline.
|
||||||
|
make_shard() {
|
||||||
|
mkdir -p "$1"
|
||||||
|
python3 - "$1/$2" "$3" <<'PY'
|
||||||
|
import json, struct, sys
|
||||||
|
path, want = sys.argv[1], int(sys.argv[2])
|
||||||
|
payload = max(0, want - 96)
|
||||||
|
header = b""
|
||||||
|
for _ in range(16):
|
||||||
|
# The header is part of the file, so its own length moves the target: solve
|
||||||
|
# payload = want - 8 - len(header(payload)) by fixed-point iteration.
|
||||||
|
header = json.dumps({"w": {"dtype": "F32", "shape": [payload // 4],
|
||||||
|
"data_offsets": [0, payload]}},
|
||||||
|
separators=(",", ":")).encode()
|
||||||
|
want_payload = want - 8 - len(header)
|
||||||
|
if want_payload == payload:
|
||||||
|
break
|
||||||
|
if want_payload < 0:
|
||||||
|
raise SystemExit("fixture too small to carry a header")
|
||||||
|
payload = want_payload
|
||||||
|
else:
|
||||||
|
raise SystemExit("fixture header never converged")
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(struct.pack("<Q", len(header)) + header + b"\x00" * payload)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
# make_index <dir> <shard>... — a safetensors index that references them
|
||||||
|
make_index() {
|
||||||
|
local dir="$1"; shift
|
||||||
|
python3 - "$dir/model.safetensors.index.json" "$@" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
out, names = sys.argv[1], sys.argv[2:]
|
||||||
|
json.dump({"metadata": {"total_size": 0},
|
||||||
|
"weight_map": {f"model.layers.{i}.attn.q.weight": n
|
||||||
|
for i, n in enumerate(names)}},
|
||||||
|
open(out, "w"))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
# three_fixture <dir> — three valid 1000-byte shards plus the index naming them
|
||||||
|
three_fixture() {
|
||||||
|
make_shard "$1" model-00001-of-00003.safetensors 1000
|
||||||
|
make_shard "$1" model-00002-of-00003.safetensors 1000
|
||||||
|
make_shard "$1" model-00003-of-00003.safetensors 1000
|
||||||
|
make_index "$1" model-00001-of-00003.safetensors \
|
||||||
|
model-00002-of-00003.safetensors \
|
||||||
|
model-00003-of-00003.safetensors
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- download / verify -------------------------------------------------------
|
||||||
|
|
||||||
|
@test "the checkpoint verifier accepts a complete checkpoint and reports its footprint" {
|
||||||
|
C="$BATS_TEST_TMPDIR/complete"
|
||||||
|
three_fixture "$C"
|
||||||
|
# 3 x 1000 bytes of shards. The index file itself is deliberately not
|
||||||
|
# counted, so a Hub-side edit to a config file cannot fail this gate.
|
||||||
|
run bash "$DOWNLOAD" --no-hub --dir "$C" --shards 3 --bytes 3000
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"VERIFY_OK"*"3 shards"*"3000 bytes"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the verifier names the shard whose own header disagrees with its size on disk" {
|
||||||
|
C="$BATS_TEST_TMPDIR/truncated"
|
||||||
|
three_fixture "$C"
|
||||||
|
# Cut the tail, keep the header: that is a torn transfer, which is what the
|
||||||
|
# box actually gets from an interrupted fetch. Zero-filling instead would
|
||||||
|
# destroy the header and be caught as "not a safetensors file" — a different
|
||||||
|
# gate, and it would prove nothing about the size check.
|
||||||
|
python3 -c 'import sys
|
||||||
|
p = sys.argv[1]
|
||||||
|
keep = open(p, "rb").read(400)
|
||||||
|
open(p, "wb").write(keep)' "$C/model-00002-of-00003.safetensors"
|
||||||
|
run bash "$DOWNLOAD" --no-hub --dir "$C" --shards 3 --bytes 3000
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"TRUNCATED"*"model-00002-of-00003.safetensors"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the verifier refuses a missing shard and names it" {
|
||||||
|
C="$BATS_TEST_TMPDIR/missing"
|
||||||
|
three_fixture "$C"
|
||||||
|
mv "$C/model-00003-of-00003.safetensors" "$C/hidden.bin"
|
||||||
|
run bash "$DOWNLOAD" --no-hub --dir "$C" --shards 3 --bytes 3000
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"MISSING"*"model-00003-of-00003.safetensors"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the verifier refuses when the shard census disagrees with the expected count" {
|
||||||
|
# The fragment law this repo learned the hard way: large quants ship as
|
||||||
|
# .partNofM fragments and a naive glob reports success on a partial tree.
|
||||||
|
C="$BATS_TEST_TMPDIR/count"
|
||||||
|
three_fixture "$C"
|
||||||
|
make_shard "$C" model-00004-of-00004.safetensors 1000
|
||||||
|
run bash "$DOWNLOAD" --no-hub --dir "$C" --shards 3 --bytes 3000
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"SHARD_COUNT"*"4"*"3"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the verifier refuses a shard the index names that never landed" {
|
||||||
|
C="$BATS_TEST_TMPDIR/orphan-index"
|
||||||
|
three_fixture "$C"
|
||||||
|
rm "$C/model-00003-of-00003.safetensors"
|
||||||
|
make_index "$C" model-00001-of-00003.safetensors \
|
||||||
|
model-00002-of-00003.safetensors \
|
||||||
|
model-00003-of-00003.safetensors
|
||||||
|
run bash "$DOWNLOAD" --no-hub --dir "$C" --shards 2 --bytes 2000
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"MISSING"*"model-00003-of-00003.safetensors"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the verifier treats an HTTP error body written as a shard as a failure, not a download" {
|
||||||
|
# curl -o FILE exits 0 on 401/403 and writes the error body; three shards
|
||||||
|
# of identical tiny size must never look like a checkpoint.
|
||||||
|
C="$BATS_TEST_TMPDIR/errorbody"
|
||||||
|
mkdir -p "$C"
|
||||||
|
for n in 1 2 3; do
|
||||||
|
printf '{"error":"You are not logged in"}' \
|
||||||
|
> "$C/model-0000${n}-of-00003.safetensors"
|
||||||
|
done
|
||||||
|
make_index "$C" model-00001-of-00003.safetensors \
|
||||||
|
model-00002-of-00003.safetensors \
|
||||||
|
model-00003-of-00003.safetensors
|
||||||
|
run bash "$DOWNLOAD" --no-hub --dir "$C" --shards 3 --bytes 3000
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"NOT_SAFETENSORS"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the verifier refuses a shard census whose bytes do not total what the Hub declared" {
|
||||||
|
# Every shard header can be self-consistent and the tree still be the wrong
|
||||||
|
# payload: the total is the cross-check against the Hub's declared size.
|
||||||
|
C="$BATS_TEST_TMPDIR/wrongtotal"
|
||||||
|
three_fixture "$C"
|
||||||
|
run bash "$DOWNLOAD" --no-hub --dir "$C" --shards 3 --bytes 2500
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"TOTAL_BYTES"*"3000"*"2500"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the fetch path refuses when the destination cannot hold the checkpoint" {
|
||||||
|
# The campaign's peak-disk law starts at the fetch: the BF16 checkpoint and
|
||||||
|
# the reference container built from it are both ~335 GiB, and the tree that
|
||||||
|
# does not fit is discovered hours into a transfer.
|
||||||
|
C="$BATS_TEST_TMPDIR/tiny"
|
||||||
|
three_fixture "$C"
|
||||||
|
run env FRANK_QWEN38_MIN_FREE_MIB=999999999 \
|
||||||
|
bash "$DOWNLOAD" --fetch --no-hub --dir "$C" --shards 3 --bytes 3000
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"REFUSED"*"free"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- deployment scoping ------------------------------------------------------
|
||||||
|
|
||||||
|
@test "the campaign tooling deploys nothing: merging it cannot drain frank-archon" {
|
||||||
|
# Existence is asserted first: a planner that plans nothing because the
|
||||||
|
# files vanished would otherwise pass.
|
||||||
|
[ -f "$REPO_ROOT/scripts/download-qwen38-bf16.sh" ]
|
||||||
|
run python3 "$PLANNER" --target dankserver --paths \
|
||||||
|
scripts/download-qwen38-bf16.sh \
|
||||||
|
scripts/build-qwen38-bf16-rad.sh \
|
||||||
|
scripts/run-qwen38-bf16.sh \
|
||||||
|
scripts/convert-qwen38-w5nl64.sh \
|
||||||
|
scripts/kld-gate-qwen38.sh \
|
||||||
|
scripts/grams-to-imatrix.py \
|
||||||
|
scripts/recipes/qwen4exp-qwen38-w5nl64-bf16trunk.recipe \
|
||||||
|
tests/qwen38_campaign.bats
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ -z "$output" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- protected experts -------------------------------------------------------
|
||||||
|
|
||||||
|
DERIVE="$REPO_ROOT/scripts/derive-protected-experts.py"
|
||||||
|
APPLY="$REPO_ROOT/scripts/apply-protected-cast.sh"
|
||||||
|
CONVERT="$REPO_ROOT/scripts/convert-qwen38-w5nl64.sh"
|
||||||
|
GATE="$REPO_ROOT/scripts/kld-gate-qwen38.sh"
|
||||||
|
TEMPLATE="$REPO_ROOT/scripts/recipes/qwen4exp-qwen38-w5nl64-bf16trunk.recipe"
|
||||||
|
|
||||||
|
# make_gram <dir> <layer> <expert> <energy> <rows> — a RADGRAM2 down-Gram whose
|
||||||
|
# trace (its down-input energy) is <energy> and whose routing count is <rows>.
|
||||||
|
make_gram() {
|
||||||
|
python3 - "$@" <<'PY'
|
||||||
|
import os, struct, sys
|
||||||
|
d, layer, e, energy, rows = sys.argv[1:6]
|
||||||
|
os.makedirs(d, exist_ok=True)
|
||||||
|
hdr = struct.pack("<8sIIQI4x", b"RADGRAM2", 2, 0, int(rows), 0)
|
||||||
|
mat = struct.pack("<4f", float(energy), 0.0, 0.0, 0.0)
|
||||||
|
with open(os.path.join(d, f"gram.r0.blk.{layer}.ffn_down_exps.{e}.bin"), "wb") as f:
|
||||||
|
f.write(hdr + mat)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
# eight_grams <dir> — one layer, eight experts inside rank half 0, energies
|
||||||
|
# 60/15/10/8/4/2/1/0.5 against equal routing. Under the default bars (3% of the
|
||||||
|
# layer's energy AND 3x its routing share) exactly ONE expert is disproportionate,
|
||||||
|
# so the parity law has to hand it a partner.
|
||||||
|
eight_grams() {
|
||||||
|
local d="$1"
|
||||||
|
make_gram "$d" 0 0 60 100
|
||||||
|
make_gram "$d" 0 1 15 100
|
||||||
|
make_gram "$d" 0 2 10 100
|
||||||
|
make_gram "$d" 0 3 8 100
|
||||||
|
make_gram "$d" 0 4 4 100
|
||||||
|
make_gram "$d" 0 5 2 100
|
||||||
|
make_gram "$d" 0 6 1 100
|
||||||
|
make_gram "$d" 0 7 0.5 100
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_count() { grep -c 'cast dtype=bf16' <<< "$output"; }
|
||||||
|
|
||||||
|
@test "the bar-only pass keeps the disproportionate expert and repairs the odd rank half" {
|
||||||
|
C="$BATS_TEST_TMPDIR/calib"
|
||||||
|
eight_grams "$C"
|
||||||
|
run python3 "$DERIVE" "$C"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
# e0 holds 59.7% of the layer's energy on 12.5% of the routing: the only expert
|
||||||
|
# that clears three times its routing share. e1 joins it because half 0 was odd.
|
||||||
|
[ "$(cast_count)" -eq 2 ]
|
||||||
|
[[ "$output" == *"blk.0.ffn_*_exps.0.weight"* ]]
|
||||||
|
[[ "$output" == *"blk.0.ffn_*_exps.1.weight"* ]]
|
||||||
|
[[ "$output" != *"ffn_*_exps.2.weight"* ]]
|
||||||
|
[[ "$output" == *"half 0 odd -> added e1"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "--target-count lands the byte bill that was asked for, and never buys more" {
|
||||||
|
# The count is a disk and VRAM decision (each protected expert costs bf16 twice,
|
||||||
|
# once per rank half's copy), so landing it matters as much as not missing it.
|
||||||
|
C="$BATS_TEST_TMPDIR/calib"
|
||||||
|
eight_grams "$C"
|
||||||
|
run python3 "$DERIVE" "$C" --target-count 6
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ "$(cast_count)" -eq 6 ]
|
||||||
|
[[ "$output" == *"count fill -> added e2"* ]]
|
||||||
|
[[ "$output" != *"ffn_*_exps.6.weight"* ]]
|
||||||
|
# the energy share the set bottoms out at is the bar the campaign paid for
|
||||||
|
[[ "$output" == *"total protected: 6 (target 6, energy cutoff"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "--target-count cannot invent experts: the pool's ceiling is reported, not padded" {
|
||||||
|
C="$BATS_TEST_TMPDIR/calib"
|
||||||
|
eight_grams "$C"
|
||||||
|
run python3 "$DERIVE" "$C" --target-count 20
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ "$(cast_count)" -eq 8 ]
|
||||||
|
[[ "$output" == *"total protected: 8 (target 20"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a protected set that cannot be made even is refused instead of emitted" {
|
||||||
|
# One disproportionate expert in half 0, its only companion in half 1: no
|
||||||
|
# candidate could make half 0 even, and an odd half hands the four-bit GEMM two
|
||||||
|
# tables that are not equal. Refusing is the only honest answer.
|
||||||
|
C="$BATS_TEST_TMPDIR/calib"
|
||||||
|
make_gram "$C" 0 0 100 100
|
||||||
|
make_gram "$C" 0 300 1 1000
|
||||||
|
run python3 "$DERIVE" "$C"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"PARITY LAW BROKEN"* ]]
|
||||||
|
[[ "$output" == *"blk.0 half 0 holds 1"* ]]
|
||||||
|
[ "$(cast_count)" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the splice rewrites the run's copy above the expert rule and refuses a second run" {
|
||||||
|
C="$BATS_TEST_TMPDIR/calib"
|
||||||
|
eight_grams "$C"
|
||||||
|
W="$BATS_TEST_TMPDIR/recipe-work.recipe"
|
||||||
|
cp "$TEMPLATE" "$W"
|
||||||
|
run env PY=python3 TRASH_DIR="$BATS_TEST_TMPDIR/trash" bash "$APPLY" "$C" "$W" 0
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
grep -q "derive-protected-experts.py rewrote" "$W"
|
||||||
|
# The derived lines must precede the expert rule: the first rule whose glob
|
||||||
|
# matches a weight decides it, so a cast line below the expert line never fires.
|
||||||
|
cast_line=$(grep -n 'cast dtype=bf16' "$W" | head -1 | cut -d: -f1)
|
||||||
|
expert_line=$(grep -n 'ffn_\*_exps\.\*\.weight' "$W" | head -1 | cut -d: -f1)
|
||||||
|
[ "$cast_line" -lt "$expert_line" ]
|
||||||
|
# The committed template stays the clean, reviewable baseline.
|
||||||
|
! grep -q "derive-protected-experts.py rewrote" "$TEMPLATE"
|
||||||
|
# A second run on the same copy would derive from a recording it already spent.
|
||||||
|
run env PY=python3 TRASH_DIR="$BATS_TEST_TMPDIR/trash" bash "$APPLY" "$C" "$W" 0
|
||||||
|
[ "$status" -ne 0 ]
|
||||||
|
[[ "$output" == *"refusing to re-derive"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the splice refuses before touching the recipe when the count is not reached" {
|
||||||
|
C="$BATS_TEST_TMPDIR/calib"
|
||||||
|
eight_grams "$C"
|
||||||
|
W="$BATS_TEST_TMPDIR/recipe-work.recipe"
|
||||||
|
cp "$TEMPLATE" "$W"
|
||||||
|
run env PY=python3 TRASH_DIR="$BATS_TEST_TMPDIR/trash" bash "$APPLY" "$C" "$W" 20
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"target was 20"* ]]
|
||||||
|
! grep -q "derive-protected-experts.py rewrote" "$W"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- the arm driver ----------------------------------------------------------
|
||||||
|
|
||||||
|
# drv_fixture <dir> — the smallest tree the driver's gates accept: a checkpoint
|
||||||
|
# with its index and tokenizer, three grams, an imatrix inside the campaign dir
|
||||||
|
# the container mounts, and an empty output dir.
|
||||||
|
drv_fixture() {
|
||||||
|
local d="$1"
|
||||||
|
mkdir -p "$d/ck/Qwen3.8-Flash-Next" "$d/campaign" "$d/calib" "$d/out"
|
||||||
|
: > "$d/ck/Qwen3.8-Flash-Next/model.safetensors.index.json"
|
||||||
|
: > "$d/ck/Qwen3.8-Flash-Next/tokenizer.json"
|
||||||
|
make_gram "$d/calib" 0 0 60 100
|
||||||
|
make_gram "$d/calib" 0 1 15 100
|
||||||
|
make_gram "$d/calib" 0 2 10 100
|
||||||
|
# four experts, not three: a thin pool that cannot be made even trips the parity
|
||||||
|
# audit, and this fixture is meant to reach the later gates
|
||||||
|
make_gram "$d/calib" 0 3 8 100
|
||||||
|
: > "$d/campaign/imatrix.gguf"
|
||||||
|
}
|
||||||
|
|
||||||
|
# run_drv <driver args and K=V overrides, any order> — the driver with every path
|
||||||
|
# pointed at the fixture. Override words are separated out because `env` only
|
||||||
|
# accepts assignments ahead of the command; later assignments win, so a test can
|
||||||
|
# break exactly one input.
|
||||||
|
run_drv() {
|
||||||
|
local d="$BATS_TEST_TMPDIR/drv" assigns=() flags=() w
|
||||||
|
for w in "$@"; do
|
||||||
|
if [[ "$w" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]]; then assigns+=("$w"); else flags+=("$w"); fi
|
||||||
|
done
|
||||||
|
run env CKPT="$d/ck/Qwen3.8-Flash-Next" CAMPAIGN="$d/campaign" STATE="$d/campaign" \
|
||||||
|
CALIB="$d/calib" OUTDIR="$d/out" IMATRIX="$d/campaign/imatrix.gguf" \
|
||||||
|
MIN_GRAMS=2 TARGET_COUNT=0 ${assigns[@]+"${assigns[@]}"} \
|
||||||
|
bash "$CONVERT" ${flags[@]+"${flags[@]}"}
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the dry run prints the plan and then the convert for every arm" {
|
||||||
|
drv_fixture "$BATS_TEST_TMPDIR/drv"
|
||||||
|
run_drv --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"== arm cd3 =="* ]]
|
||||||
|
[[ "$output" == *"== arm cd6 =="* ]]
|
||||||
|
[[ "$output" == *"== arm ao1 =="* ]]
|
||||||
|
# one --plan-only bill per arm, and one convert line per arm beside it
|
||||||
|
[ "$(grep -c -- '--plan-only' <<< "$output")" -eq 3 ]
|
||||||
|
[ "$(grep -c 'rad-convert' <<< "$output")" -eq 6 ]
|
||||||
|
awk '/arm cd3/ {next}
|
||||||
|
/qwen38-w5nl64-cd3\.rad/ { if ($0 ~ /--plan-only/) p = NR; else c = NR }
|
||||||
|
END { exit !(p && c && p < c) }' <<< "$output"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the cd6 arm re-sends the whole expert option set with cd=6; the control sends none" {
|
||||||
|
# --quant replaces the recipe line wholesale, so an override that forgot an
|
||||||
|
# option would quietly convert with the default for it.
|
||||||
|
drv_fixture "$BATS_TEST_TMPDIR/drv"
|
||||||
|
run_drv --arm cd3 --arm cd6 --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
cd6line=$(grep -- '-cd6\.rad --quant' <<< "$output" | head -1)
|
||||||
|
[[ "$cd6line" == *"table=w5nl"* ]]
|
||||||
|
[[ "$cd6line" == *"group=64"* ]]
|
||||||
|
[[ "$cd6line" == *"scale=fp8_e4m3"* ]]
|
||||||
|
[[ "$cd6line" == *"transform=fwht128"* ]]
|
||||||
|
[[ "$cd6line" == *"calib=\$CALIB"* ]]
|
||||||
|
[[ "$cd6line" == *",cd=6"* ]]
|
||||||
|
cd3line=$(grep -- '-cd3\.rad' <<< "$output" | grep -v -- '--plan-only' | head -1)
|
||||||
|
[[ "$cd3line" != *"--quant"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the ao1 arm carries act_order=1 and no cd=, because libquant refuses the pair" {
|
||||||
|
drv_fixture "$BATS_TEST_TMPDIR/drv"
|
||||||
|
run_drv --arm ao1 --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
line=$(grep -- '-ao1\.rad --quant' <<< "$output" | head -1)
|
||||||
|
[[ "$line" == *"act_order=1"* ]]
|
||||||
|
[[ "$line" != *",cd="* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the driver splices the protected plane into its own copy, never the template" {
|
||||||
|
drv_fixture "$BATS_TEST_TMPDIR/drv"
|
||||||
|
run_drv --dry-run
|
||||||
|
[[ "$output" == *"apply-protected-cast.sh $BATS_TEST_TMPDIR/drv/calib "* ]]
|
||||||
|
[[ "$output" == *"/campaign/recipe-"* ]]
|
||||||
|
[[ "$output" != *"$TEMPLATE"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the driver refuses while the BF16 reference container still holds the volume" {
|
||||||
|
drv_fixture "$BATS_TEST_TMPDIR/drv"
|
||||||
|
: > "$BATS_TEST_TMPDIR/drv/out/qwen38-bf16.rad"
|
||||||
|
run_drv --dry-run
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"peak-disk"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a thin recording is refused: the driver never converts uncalibrated" {
|
||||||
|
# rad-convert converts WITHOUT a calibration recording with one stderr note,
|
||||||
|
# which is silently worse than a GPTQ run that failed.
|
||||||
|
drv_fixture "$BATS_TEST_TMPDIR/drv"
|
||||||
|
run_drv MIN_GRAMS=9 --dry-run
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"run-qwen38-bf16.sh calib"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "an imatrix outside the mounted campaign dir is refused before any convert" {
|
||||||
|
drv_fixture "$BATS_TEST_TMPDIR/drv"
|
||||||
|
mv "$BATS_TEST_TMPDIR/drv/campaign/imatrix.gguf" "$BATS_TEST_TMPDIR/imatrix.gguf"
|
||||||
|
run_drv IMATRIX="$BATS_TEST_TMPDIR/imatrix.gguf" --dry-run
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"not in the campaign dir"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the driver never overwrites an arm that already exists" {
|
||||||
|
drv_fixture "$BATS_TEST_TMPDIR/drv"
|
||||||
|
: > "$BATS_TEST_TMPDIR/drv/out/qwen38-w5nl64-cd6.rad"
|
||||||
|
run_drv --arm cd6 --dry-run
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"move it to trash first"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "an unknown arm name is refused before the gates run" {
|
||||||
|
# No fixture built: the name check must not need a checkpoint to exist.
|
||||||
|
run_drv --arm cd9 --dry-run
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"unknown arm: cd9"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the driver stops when the protected derivation cannot land the target" {
|
||||||
|
drv_fixture "$BATS_TEST_TMPDIR/drv"
|
||||||
|
run_drv PY=python3 TARGET_COUNT=20 --arm cd3
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"target was 20"* ]]
|
||||||
|
[[ "$output" == *"would convert unprotected"* ]]
|
||||||
|
[[ "$output" != *"--entrypoint"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- the acceptance gate -----------------------------------------------------
|
||||||
|
|
||||||
|
# kld_report <path> <model> <kl mean> <top1 fraction> — one score report, in the
|
||||||
|
# shape `run-qwen38-bf16.sh score` writes: an `all` summary plus a per-source one.
|
||||||
|
kld_report() {
|
||||||
|
python3 - "$@" <<'PY'
|
||||||
|
import json, sys
|
||||||
|
path, model, kl, top1 = sys.argv[1:5]
|
||||||
|
kl, top1 = float(kl), float(top1)
|
||||||
|
group = {"kld": {"mean": kl, "p99_9": kl * 4}, "top1_agreement": top1, "positions": 50432}
|
||||||
|
json.dump({"model": model, "all": group,
|
||||||
|
"by_source": {"ops": dict(group, positions=20000)}}, open(path, "w"))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
gate_case() { # gate_case <kl fp8> <kl iq4r> <kl cand> <top1 cand> [extra flags...]
|
||||||
|
D="$BATS_TEST_TMPDIR/kl"
|
||||||
|
mkdir -p "$D"
|
||||||
|
kld_report "$D/fp8.json" fp8-native "$1" 0.981
|
||||||
|
kld_report "$D/iq4r.json" iq4r-moe "$2" 0.962
|
||||||
|
kld_report "$D/cand.json" w5nl64 "$3" "$4"
|
||||||
|
run bash "$GATE" "$D/fp8.json" "$D/iq4r.json" "$D/cand.json" "${@:5}"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "an arm strictly inside the interval that closes half the gap passes" {
|
||||||
|
gate_case 0.0021 0.0190 0.0090 0.971
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"closes 59.2%"* ]]
|
||||||
|
[[ "$output" == *"KLD GATE PASS"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "an arm worse than the served payload fails the interval and the recovery" {
|
||||||
|
gate_case 0.0021 0.0190 0.0210 0.971
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"[FAIL] 2."* ]]
|
||||||
|
[[ "$output" == *"[FAIL] 3."* ]]
|
||||||
|
[[ "$output" == *"KLD GATE FAIL"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "anchors that are not ordered stop the gate: the instrument is wrong, not the arm" {
|
||||||
|
# If the near-lossless FP8 release does not sit CLOSER to the bf16 reference
|
||||||
|
# than the 4-bit payload does, the corpus, tokenizer or boot shape is broken and
|
||||||
|
# no arm number can mean anything.
|
||||||
|
gate_case 0.0300 0.0190 0.0120 0.971
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"[FAIL] 1."* ]]
|
||||||
|
[[ "$output" == *"gap is not positive"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "an arm measured outside the interval fails even when it looks better" {
|
||||||
|
# Closer to the reference than the achievable anchor is a measurement fault.
|
||||||
|
gate_case 0.0021 0.0190 0.0010 0.971
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"[FAIL] 2."* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "an arm that buys KL with top-1 agreement fails" {
|
||||||
|
gate_case 0.0021 0.0190 0.0090 0.940
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"[PASS] 3."* ]]
|
||||||
|
[[ "$output" == *"[FAIL] 4."* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "--recover tightens the same numbers until they fail" {
|
||||||
|
gate_case 0.0021 0.0190 0.0090 0.971 --recover 0.8
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"needs 80%"* ]]
|
||||||
|
[[ "$output" == *"[FAIL] 3."* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a slice the report does not carry is named, not guessed at" {
|
||||||
|
gate_case 0.0021 0.0190 0.0090 0.971 --slice code
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"has no slice 'code'"* ]]
|
||||||
|
[[ "$output" == *"it has: ops"* ]]
|
||||||
|
}
|
||||||
Executable
+445
@@ -0,0 +1,445 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
# Seam: scripts/grams-to-imatrix.py — the calibration recording (RADGRAM2 grams)
|
||||||
|
# folded into the GGUF v3 imatrix `rad-convert --imatrix F` reads.
|
||||||
|
#
|
||||||
|
# The only contract that matters is the reader's: core/format/imatrix.cpp joins
|
||||||
|
# `<base>.in_sum2` with `<base>.counts`, takes cols from in_sum2 dims[0] and
|
||||||
|
# n_expert from dims[1], demands ggml f32 tensors and GGUF version 3, and
|
||||||
|
# silently IGNORES an entry whose width does not match the weight it is asked
|
||||||
|
# about. So every test here parses the artifact back with its own tiny GGUF
|
||||||
|
# reader and asserts bytes — a file that merely exists and exits 0 proves
|
||||||
|
# nothing about a quantiser that will read it hours later on another machine.
|
||||||
|
#
|
||||||
|
# Fixtures are synthetic: k=4 grams with real RADGRAM2 headers (rows is a u64
|
||||||
|
# at offset 16 — pack it as u32 and 100 rows read back as 4294967296000) and
|
||||||
|
# real f32 payloads whose OFF-DIAGONAL is filled with 99.0, so an entry built
|
||||||
|
# from anything but the diagonal is instantly visible. Expected numbers are
|
||||||
|
# worked examples from the fixture tables below, never recomputed the way the
|
||||||
|
# tool computes them.
|
||||||
|
|
||||||
|
load helpers
|
||||||
|
|
||||||
|
TOOL="$REPO_ROOT/scripts/grams-to-imatrix.py"
|
||||||
|
|
||||||
|
# make_gram <path> <k> <rank> <rows> <domain> <diag,csv>
|
||||||
|
# A RADGRAM2 file: 32-byte header <8sIIQI (magic, k, rank, rows, domain, pad)
|
||||||
|
# then k*k f32 row-major. Off-diagonals are 99.0 — a sentinel, not data.
|
||||||
|
make_gram() {
|
||||||
|
python3 - "$@" <<'PY'
|
||||||
|
import struct, sys
|
||||||
|
path, k, rank, rows, domain = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), \
|
||||||
|
int(sys.argv[4]), int(sys.argv[5])
|
||||||
|
diag = [float(x) for x in sys.argv[6].split(",")]
|
||||||
|
assert len(diag) == k, "diagonal must hold k values"
|
||||||
|
vals = [99.0] * (k * k)
|
||||||
|
for i, d in enumerate(diag):
|
||||||
|
vals[i * k + i] = d
|
||||||
|
hdr = struct.pack("<8sIIQI", b"RADGRAM2", k, rank, rows, domain) + struct.pack("<I", 0)
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(hdr + struct.pack("<%df" % (k * k), *vals))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
# full_recording <dir> — the fixture every happy-path test reads.
|
||||||
|
#
|
||||||
|
# layer 0 (k=4, own-gram floor 4*k = 16 rows):
|
||||||
|
# pooled r0 rows 40 diag 1,2,3,4 + r1 rows 8 diag .5,.5,.5,.5
|
||||||
|
# -> summed gram (the engine's calib_sum law): diag 1.5,2.5,3.5,4.5,
|
||||||
|
# pooled rows 48
|
||||||
|
# expert 0 r0 rows 100 diag 10,20,30,40 (>= 16: own gram kept)
|
||||||
|
# expert 1 r0 rows 3 diag 7,7,7,7 (< 16: inherits pooled)
|
||||||
|
# expert 2 r1 rows 64 diag 1,1,1,2 (>= 16: own gram kept)
|
||||||
|
# expert 3 no file at all (inherits pooled, count 0)
|
||||||
|
# expert 4 r1 rows 100 diag 5,6,7,8 (>= 16: own gram kept)
|
||||||
|
# -> n_expert 5, counts 100 3 64 0 100, inherited 2, routing 267
|
||||||
|
# layer 1: pooled r0 rows 48 diag 2,2,2,2 ; expert 0 on r1 rows 48 diag 1,1,1,1
|
||||||
|
# -> n_expert 1, counts 48, inherited 0, routing 48
|
||||||
|
# Corpus rows = the largest single-layer pooled rows = 48.
|
||||||
|
full_recording() {
|
||||||
|
local d="$1"
|
||||||
|
mkdir -p "$d"
|
||||||
|
make_gram "$d/gram.r0.blk.0.ffn_down_exps.bin" 4 0 40 0 "1,2,3,4"
|
||||||
|
make_gram "$d/gram.r1.blk.0.ffn_down_exps.bin" 4 1 8 0 "0.5,0.5,0.5,0.5"
|
||||||
|
make_gram "$d/gram.r0.blk.0.ffn_down_exps.0.bin" 4 0 100 0 "10,20,30,40"
|
||||||
|
make_gram "$d/gram.r0.blk.0.ffn_down_exps.1.bin" 4 0 3 0 "7,7,7,7"
|
||||||
|
make_gram "$d/gram.r1.blk.0.ffn_down_exps.2.bin" 4 1 64 0 "1,1,1,2"
|
||||||
|
make_gram "$d/gram.r1.blk.0.ffn_down_exps.4.bin" 4 1 100 0 "5,6,7,8"
|
||||||
|
make_gram "$d/gram.r0.blk.1.ffn_down_exps.bin" 4 0 48 0 "2,2,2,2"
|
||||||
|
make_gram "$d/gram.r1.blk.1.ffn_down_exps.0.bin" 4 1 48 0 "1,1,1,1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# gguf_read <file> [vals <tensor> <float-index> <count>] — an independent GGUF
|
||||||
|
# v3 reader: KV block, tensor infos, and (on request) the f32 values behind a
|
||||||
|
# tensor's own declared offset. Prints KV/HDR/TENSOR lines. Nothing here shares
|
||||||
|
# code with the writer, which is the only reason the byte assertions mean
|
||||||
|
# anything: imatrix.cpp walks the same three structures.
|
||||||
|
gguf_read() {
|
||||||
|
python3 - "$@" <<'PY'
|
||||||
|
import struct, sys
|
||||||
|
d = open(sys.argv[1], "rb").read()
|
||||||
|
def u32(o): return struct.unpack_from("<I", d, o)[0]
|
||||||
|
def u64(o): return struct.unpack_from("<Q", d, o)[0]
|
||||||
|
def st(o):
|
||||||
|
n = u64(o)
|
||||||
|
return d[o + 8:o + 8 + n].decode(), o + 8 + n
|
||||||
|
assert d[:4] == b"GGUF", "magic is %r" % d[:4]
|
||||||
|
ver, n_t, n_kv = u32(4), u64(8), u64(16)
|
||||||
|
p, align, out = 24, 32, []
|
||||||
|
for _ in range(n_kv):
|
||||||
|
key, p = st(p)
|
||||||
|
ty = u32(p); p += 4
|
||||||
|
if ty == 4: # uint32
|
||||||
|
v = u32(p); p += 4
|
||||||
|
if key == "general.alignment":
|
||||||
|
align = v
|
||||||
|
elif ty == 8: # string
|
||||||
|
v, p = st(p)
|
||||||
|
elif ty == 9: # array
|
||||||
|
et = u32(p); n = u64(p + 4); p += 12
|
||||||
|
got = []
|
||||||
|
for _i in range(n):
|
||||||
|
if et != 8:
|
||||||
|
raise SystemExit("kv array element type %d" % et)
|
||||||
|
s, p = st(p)
|
||||||
|
got.append(s)
|
||||||
|
v = ",".join(got)
|
||||||
|
else:
|
||||||
|
raise SystemExit("kv type %d is not steppable" % ty)
|
||||||
|
out.append("KV %s %s" % (key, v))
|
||||||
|
tens = []
|
||||||
|
for _ in range(n_t):
|
||||||
|
name, p = st(p)
|
||||||
|
nd = u32(p); p += 4
|
||||||
|
dims = [u64(p + 8 * i) for i in range(nd)]; p += 8 * nd
|
||||||
|
ty = u32(p); off = u64(p + 4); p += 12
|
||||||
|
tens.append((name, dims, ty, off))
|
||||||
|
ds = (p + align - 1) // align * align
|
||||||
|
out.append("HDR version=%d n_tensors=%d n_kv=%d alignment=%d data_start=%d"
|
||||||
|
% (ver, n_t, n_kv, align, ds))
|
||||||
|
for name, dims, ty, off in tens:
|
||||||
|
out.append("TENSOR %s %s %d %d" % (name, "x".join(str(x) for x in dims), ty, off))
|
||||||
|
if len(sys.argv) > 2 and sys.argv[2] == "vals":
|
||||||
|
name, i0, n = sys.argv[3], int(sys.argv[4]), int(sys.argv[5])
|
||||||
|
for nm, dims, ty, off in tens:
|
||||||
|
if nm == name:
|
||||||
|
base = ds + off
|
||||||
|
vals = [("%d" % v) if float(v).is_integer() else repr(v)
|
||||||
|
for v in (struct.unpack_from("<f", d, base + 4 * (i0 + i))[0]
|
||||||
|
for i in range(n))]
|
||||||
|
out.append("VALS %s %s" % (name, " ".join(vals)))
|
||||||
|
print("\n".join(out))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
# aligned_offsets <file> — exit 0 iff the data section starts aligned AND every
|
||||||
|
# tensor's absolute data offset (data_start + t.off) is a multiple of
|
||||||
|
# general.alignment. This is the writer bug that no value assertion can see: an
|
||||||
|
# unaligned offset makes the reader's extent arithmetic land mid-float in the
|
||||||
|
# previous tensor, and it reads the result without complaining.
|
||||||
|
aligned_offsets() {
|
||||||
|
python3 - "$1" <<'PY'
|
||||||
|
import struct, sys
|
||||||
|
d = open(sys.argv[1], "rb").read()
|
||||||
|
def u32(o): return struct.unpack_from("<I", d, o)[0]
|
||||||
|
def u64(o): return struct.unpack_from("<Q", d, o)[0]
|
||||||
|
def st(o):
|
||||||
|
n = u64(o)
|
||||||
|
return d[o + 8:o + 8 + n].decode(), o + 8 + n
|
||||||
|
ver, n_t, n_kv = u32(4), u64(8), u64(16)
|
||||||
|
p, align = 24, 32
|
||||||
|
for _ in range(n_kv):
|
||||||
|
key, p = st(p)
|
||||||
|
ty = u32(p); p += 4
|
||||||
|
if ty == 4:
|
||||||
|
v = u32(p); p += 4
|
||||||
|
if key == "general.alignment":
|
||||||
|
align = v
|
||||||
|
if ty == 8:
|
||||||
|
_v, p = st(p)
|
||||||
|
elif ty == 9:
|
||||||
|
et = u32(p); n = u64(p + 4); p += 12
|
||||||
|
for _i in range(n):
|
||||||
|
if et == 8:
|
||||||
|
_v, p = st(p)
|
||||||
|
elif et == 4:
|
||||||
|
p += 4
|
||||||
|
else:
|
||||||
|
raise SystemExit("kv array element type %d" % et)
|
||||||
|
elif ty in (0, 1):
|
||||||
|
p += 1
|
||||||
|
elif ty in (2, 3):
|
||||||
|
p += 2
|
||||||
|
elif ty in (5, 6):
|
||||||
|
p += 4
|
||||||
|
elif ty in (10, 11, 12):
|
||||||
|
p += 8
|
||||||
|
tens = []
|
||||||
|
for _ in range(n_t):
|
||||||
|
name, p = st(p)
|
||||||
|
nd = u32(p); p += 4
|
||||||
|
p += 8 * nd
|
||||||
|
ty = u32(p); off = u64(p + 4); p += 12
|
||||||
|
tens.append((name, ty, off))
|
||||||
|
ds = (p + align - 1) // align * align
|
||||||
|
bad = [] if ds % align == 0 else ["data_start=%d" % ds]
|
||||||
|
bad += ["%s=%d" % (nm, ds + off) for nm, _ty, off in tens if (ds + off) % align]
|
||||||
|
if bad:
|
||||||
|
print("UNALIGNED", ";".join(bad))
|
||||||
|
sys.exit(1)
|
||||||
|
print("ALIGNED %d tensor(s), data_start=%d, alignment=%d" % (len(tens), ds, align))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- the artifact -----------------------------------------------------------
|
||||||
|
|
||||||
|
@test "a complete recording becomes a GGUF v3 file and the run reports itself" {
|
||||||
|
C="$BATS_TEST_TMPDIR/full"
|
||||||
|
full_recording "$C"
|
||||||
|
run python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/im.gguf" \
|
||||||
|
--datasets calib,chat --chunk-size 16
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"IMATRIX_OK"* ]]
|
||||||
|
head -c 4 "$BATS_TEST_TMPDIR/im.gguf" | grep -q "GGUF"
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/im.gguf"
|
||||||
|
[[ "$output" == *"HDR version=3"* ]]
|
||||||
|
# The reader refuses anything but version 3, so this is the whole gate.
|
||||||
|
[[ "$output" != *"version=2"* ]]
|
||||||
|
# Everything the reader looks for by key, and the honesty of the chunk math:
|
||||||
|
# 48 corpus rows over 16-row chunks is 3 chunks.
|
||||||
|
[[ "$output" == *"KV general.alignment 32"* ]]
|
||||||
|
[[ "$output" == *"KV imatrix.chunk_size 16"* ]]
|
||||||
|
[[ "$output" == *"KV imatrix.chunk_count 3"* ]]
|
||||||
|
[[ "$output" == *"KV imatrix.datasets calib,chat"* ]]
|
||||||
|
[[ "$output" == *"n_tensors=12"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the reader finds the folded MoE entry: one per layer, expert-major, f32" {
|
||||||
|
C="$BATS_TEST_TMPDIR/full"
|
||||||
|
full_recording "$C"
|
||||||
|
run python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/im.gguf"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/im.gguf"
|
||||||
|
# dims (k, n_expert) with k fastest, ggml type 0 (f32) or the entry is
|
||||||
|
# warned away, and 5 experts because the fixture's largest global id is 4.
|
||||||
|
[[ "$output" == *"TENSOR blk.0.ffn_down_exps.in_sum2 4x5 0 "* ]]
|
||||||
|
[[ "$output" == *"TENSOR blk.0.ffn_down_exps.counts 1x5 0 "* ]]
|
||||||
|
# counts are the routing rows, not the energy trace: 100 3 64 0 100.
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/im.gguf" vals blk.0.ffn_down_exps.counts 0 5
|
||||||
|
[[ "$output" == *"VALS blk.0.ffn_down_exps.counts 100 3 64 0 100"* ]]
|
||||||
|
# Expert 0 (floats 0..3) and expert 4 (floats 16..19) carry their own
|
||||||
|
# matrices' diagonals; the 99.0 off-diagonal fill appears nowhere.
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/im.gguf" vals blk.0.ffn_down_exps.in_sum2 0 4
|
||||||
|
[[ "$output" == *"VALS blk.0.ffn_down_exps.in_sum2 10 20 30 40"* ]]
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/im.gguf" vals blk.0.ffn_down_exps.in_sum2 16 4
|
||||||
|
[[ "$output" == *"VALS blk.0.ffn_down_exps.in_sum2 5 6 7 8"* ]]
|
||||||
|
[[ "$output" != *"99"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "an expert with no own gram carries the pooled diagonal, and the count of them is reported" {
|
||||||
|
C="$BATS_TEST_TMPDIR/full"
|
||||||
|
full_recording "$C"
|
||||||
|
run python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/im.gguf"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"inherited=2"* ]]
|
||||||
|
# Expert 1 has a gram (3 rows, under the 4*k floor) and expert 3 has none at
|
||||||
|
# all; both slices must be the POOLED diag 1+0.5, 2+0.5, 3+0.5, 4+0.5.
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/im.gguf" vals blk.0.ffn_down_exps.in_sum2 4 4
|
||||||
|
[[ "$output" == *"VALS blk.0.ffn_down_exps.in_sum2 1.5 2.5 3.5 4.5"* ]]
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/im.gguf" vals blk.0.ffn_down_exps.in_sum2 12 4
|
||||||
|
[[ "$output" == *"VALS blk.0.ffn_down_exps.in_sum2 1.5 2.5 3.5 4.5"* ]]
|
||||||
|
# Their counts stay their own routing: 3 for the under-threshold expert, 0
|
||||||
|
# for the unrecorded one — popularity is count's job, never the gram's.
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/im.gguf" vals blk.0.ffn_down_exps.counts 1 3
|
||||||
|
[[ "$output" == *"VALS blk.0.ffn_down_exps.counts 3 64 0"* ]]
|
||||||
|
# The census is a report for the operator, so it belongs on stderr. `run`
|
||||||
|
# folds both streams into $output, so a second run into a different -o (an
|
||||||
|
# existing output is refused by design) captures stderr alone: without the
|
||||||
|
# stderr write there is no file to grep and this assertion goes red.
|
||||||
|
python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/im2.gguf" 2>"$BATS_TEST_TMPDIR/err"
|
||||||
|
[ -s "$BATS_TEST_TMPDIR/err" ]
|
||||||
|
grep -q "inherited=2" "$BATS_TEST_TMPDIR/err"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the hidden-width entries are the same bytes, and nothing else is emitted" {
|
||||||
|
C="$BATS_TEST_TMPDIR/full"
|
||||||
|
full_recording "$C"
|
||||||
|
run python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/im.gguf"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/im.gguf"
|
||||||
|
# gate_up shares the down matrices (same hidden-state rows), so its slice 4
|
||||||
|
# must read 5 6 7 8 at its own tensor info's offset.
|
||||||
|
[[ "$output" == *"TENSOR blk.0.ffn_gate_up_exps.in_sum2 4x5 0 "* ]]
|
||||||
|
[[ "$output" == *"TENSOR blk.0.ssm_inz.in_sum2 4 0 "* ]]
|
||||||
|
[[ "$output" == *"TENSOR blk.0.ssm_inz.counts 1 0 "* ]]
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/im.gguf" vals blk.0.ffn_gate_up_exps.in_sum2 16 4
|
||||||
|
[[ "$output" == *"VALS blk.0.ffn_gate_up_exps.in_sum2 5 6 7 8"* ]]
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/im.gguf" vals blk.0.ssm_inz.in_sum2 0 4
|
||||||
|
[[ "$output" == *"VALS blk.0.ssm_inz.in_sum2 1.5 2.5 3.5 4.5"* ]]
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/im.gguf" vals blk.0.ssm_inz.counts 0 1
|
||||||
|
[[ "$output" == *"VALS blk.0.ssm_inz.counts 48"* ]]
|
||||||
|
# The stop rule: importance_for() false-on-a-width-mismatch means an entry
|
||||||
|
# for a projection whose input is NOT the gram's width is a silent lie.
|
||||||
|
[[ "$output" != *"ssm_out"* ]]
|
||||||
|
[[ "$output" != *"attn"* ]]
|
||||||
|
[[ "$output" != *"mtp."* ]]
|
||||||
|
[[ "$output" != *"ple_ngram"* ]]
|
||||||
|
[[ "$output" != *"output.weight"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "rows is a u64: a corpus too big for u32 survives the header" {
|
||||||
|
# The trap this fixture law exists for: pack rows as u32 at offset 16 and a
|
||||||
|
# 100-row file reads back as 4294967296000. 2**32 is exactly representable
|
||||||
|
# in f32, so the count round-trips only if the tool read 8 bytes.
|
||||||
|
C="$BATS_TEST_TMPDIR/wide"
|
||||||
|
mkdir -p "$C"
|
||||||
|
make_gram "$C/gram.r0.blk.0.ffn_down_exps.bin" 4 0 4294967296 0 "1,1,1,1"
|
||||||
|
make_gram "$C/gram.r0.blk.0.ffn_down_exps.0.bin" 4 0 4294967296 0 "3,3,3,3"
|
||||||
|
run python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/wide.gguf" --chunk-size 4096
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/wide.gguf" vals blk.0.ffn_down_exps.counts 0 1
|
||||||
|
[[ "$output" == *"VALS blk.0.ffn_down_exps.counts 4294967296"* ]]
|
||||||
|
[[ "$output" != *"4294967296000"* ]]
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/wide.gguf"
|
||||||
|
[[ "$output" == *"KV imatrix.chunk_count 1048576"* ]]
|
||||||
|
# imatrix.chunk_count is a u32 field, so 2**32 rows in 1-row chunks has no
|
||||||
|
# legal encoding: wrapping it would put a number downstream believes.
|
||||||
|
run python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/wide2.gguf" --chunk-size 1
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"REFUSED"*"CHUNK_COUNT"* ]]
|
||||||
|
[ ! -e "$BATS_TEST_TMPDIR/wide2.gguf" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "every tensor's data offset is a multiple of general.alignment" {
|
||||||
|
C="$BATS_TEST_TMPDIR/full"
|
||||||
|
full_recording "$C"
|
||||||
|
run python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/im.gguf"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
run aligned_offsets "$BATS_TEST_TMPDIR/im.gguf"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == "ALIGNED 12 tensor(s),"*"alignment=32" ]]
|
||||||
|
|
||||||
|
# Red proof, in the same test so the assertion cannot rot: the same file
|
||||||
|
# with one tensor's declared offset nudged by 4. A counts tensor is 4 bytes,
|
||||||
|
# so a writer that forgets to pad between tensors produces exactly this
|
||||||
|
# shape of file — and every value assertion above still passes on it.
|
||||||
|
python3 - "$BATS_TEST_TMPDIR/im.gguf" "$BATS_TEST_TMPDIR/unaligned.gguf" <<'PY'
|
||||||
|
import struct, sys
|
||||||
|
src, dst = sys.argv[1], sys.argv[2]
|
||||||
|
d = bytearray(open(src, "rb").read())
|
||||||
|
def u32(o): return struct.unpack_from("<I", d, o)[0]
|
||||||
|
def u64(o): return struct.unpack_from("<Q", d, o)[0]
|
||||||
|
def st(o):
|
||||||
|
n = u64(o)
|
||||||
|
return d[o + 8:o + 8 + n].decode(), o + 8 + n
|
||||||
|
p = 24
|
||||||
|
for _ in range(u64(16)):
|
||||||
|
key, p = st(p)
|
||||||
|
ty = u32(p); p += 4
|
||||||
|
if ty == 4:
|
||||||
|
p += 4
|
||||||
|
elif ty == 8:
|
||||||
|
_v, p = st(p)
|
||||||
|
elif ty == 9:
|
||||||
|
et = u32(p); n = u64(p + 4); p += 12
|
||||||
|
for _i in range(n):
|
||||||
|
_v, p = st(p)
|
||||||
|
for _ in range(u64(8)):
|
||||||
|
name, p = st(p)
|
||||||
|
nd = u32(p); p += 4
|
||||||
|
p += 8 * nd
|
||||||
|
off_at = p + 4 # the u64 offset field of this tensor
|
||||||
|
if name == "blk.0.ssm_inz.counts":
|
||||||
|
struct.pack_into("<Q", d, off_at, u64(off_at) + 4)
|
||||||
|
p += 12
|
||||||
|
open(dst, "wb").write(bytes(d))
|
||||||
|
PY
|
||||||
|
run aligned_offsets "$BATS_TEST_TMPDIR/unaligned.gguf"
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
[[ "$output" == *"UNALIGNED"*"blk.0.ssm_inz.counts"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- the refusals -----------------------------------------------------------
|
||||||
|
|
||||||
|
@test "a gram short by one float is refused and the file is named" {
|
||||||
|
C="$BATS_TEST_TMPDIR/short"
|
||||||
|
full_recording "$C"
|
||||||
|
# Cut the last 4 bytes of one expert gram: a torn write, which is what a
|
||||||
|
# killed tap actually leaves behind.
|
||||||
|
p="$C/gram.r0.blk.0.ffn_down_exps.0.bin"
|
||||||
|
python3 -c 'import sys
|
||||||
|
p = sys.argv[1]
|
||||||
|
open(p, "wb").write(open(p, "rb").read()[:-4])' "$p"
|
||||||
|
run python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/no.gguf"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"REFUSED"*"SHORT"* ]]
|
||||||
|
[[ "$output" == *"gram.r0.blk.0.ffn_down_exps.0.bin"* ]]
|
||||||
|
[ ! -e "$BATS_TEST_TMPDIR/no.gguf" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "an empty recording directory is refused" {
|
||||||
|
mkdir -p "$BATS_TEST_TMPDIR/empty"
|
||||||
|
run python3 "$TOOL" "$BATS_TEST_TMPDIR/empty" -o "$BATS_TEST_TMPDIR/no.gguf"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"REFUSED"*"NO_GRAMS"* ]]
|
||||||
|
[[ "$output" == *"$BATS_TEST_TMPDIR/empty"* ]]
|
||||||
|
[ ! -e "$BATS_TEST_TMPDIR/no.gguf" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "expert grams with no pooled gram for the layer are refused" {
|
||||||
|
# Without the pooled gram there is nothing for an under-threshold expert to
|
||||||
|
# inherit, and the tool would quietly invent a zero diagonal for it.
|
||||||
|
C="$BATS_TEST_TMPDIR/nopooled"
|
||||||
|
mkdir -p "$C"
|
||||||
|
make_gram "$C/gram.r0.blk.3.ffn_down_exps.7.bin" 4 0 500 0 "1,2,3,4"
|
||||||
|
run python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/no.gguf"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"REFUSED"*"NO_POOLED"*"layer 3"* ]]
|
||||||
|
[[ "$output" == *"gram.r<rank>.blk.3.ffn_down_exps.bin"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the same global expert recorded twice with different numbers is refused" {
|
||||||
|
# Rank files carry GLOBAL expert numbering and so describe disjoint experts;
|
||||||
|
# a collision means the recording is corrupt, and whichever file sorted last
|
||||||
|
# would win.
|
||||||
|
C="$BATS_TEST_TMPDIR/conflict"
|
||||||
|
full_recording "$C"
|
||||||
|
make_gram "$C/gram.r1.blk.0.ffn_down_exps.0.bin" 4 1 77 0 "10,20,30,40"
|
||||||
|
run python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/no.gguf"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"REFUSED"*"EXPERT_CONFLICT"*"expert 0"* ]]
|
||||||
|
[[ "$output" == *"gram.r0.blk.0.ffn_down_exps.0.bin"* ]]
|
||||||
|
[[ "$output" == *"gram.r1.blk.0.ffn_down_exps.0.bin"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "an existing output is refused by name rather than overwritten" {
|
||||||
|
# The trash law: a re-run must not destroy the imatrix a convert is reading.
|
||||||
|
C="$BATS_TEST_TMPDIR/full"
|
||||||
|
full_recording "$C"
|
||||||
|
: > "$BATS_TEST_TMPDIR/keep.gguf"
|
||||||
|
printf 'do not clobber me\n' > "$BATS_TEST_TMPDIR/keep.gguf"
|
||||||
|
run python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/keep.gguf"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"REFUSED"*"EXISTS"*"keep.gguf"* ]]
|
||||||
|
run cat "$BATS_TEST_TMPDIR/keep.gguf"
|
||||||
|
[[ "$output" == *"do not clobber me"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "--min-rows moves the threshold and the bytes follow it" {
|
||||||
|
# The default threshold is the engine's own law (4*k rows, lq_hess.h), but
|
||||||
|
# the operator has to be able to raise it when a corpus is thin. Asserted on
|
||||||
|
# the slices, not on a report line: a flag that only changes prose is a
|
||||||
|
# flag that changes nothing.
|
||||||
|
C="$BATS_TEST_TMPDIR/floor"
|
||||||
|
full_recording "$C"
|
||||||
|
run python3 "$TOOL" "$C" -o "$BATS_TEST_TMPDIR/far.gguf" --min-rows 200
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
# 200 is above every expert's routing (100, 3, 64, 0, 100 and layer 1's 48),
|
||||||
|
# so all six slices now inherit.
|
||||||
|
[[ "$output" == *"inherited=6"* ]]
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/far.gguf" vals blk.0.ffn_down_exps.in_sum2 0 4
|
||||||
|
[[ "$output" == *"VALS blk.0.ffn_down_exps.in_sum2 1.5 2.5 3.5 4.5"* ]]
|
||||||
|
# The counts do not move with the threshold: routing is a measurement, not a
|
||||||
|
# judgement about how much data is enough.
|
||||||
|
run gguf_read "$BATS_TEST_TMPDIR/far.gguf" vals blk.0.ffn_down_exps.counts 0 5
|
||||||
|
[[ "$output" == *"VALS blk.0.ffn_down_exps.counts 100 3 64 0 100"* ]]
|
||||||
|
}
|
||||||
Executable
+337
@@ -0,0 +1,337 @@
|
|||||||
|
#!/usr/bin/env bats
|
||||||
|
# Seam: the BF16 REFERENCE container of the Qwen3.8-Flash-Next five-bit
|
||||||
|
# campaign (feat/qwen38-w5nl64) — scripts/build-qwen38-bf16-rad.sh and
|
||||||
|
# scripts/run-qwen38-bf16.sh.
|
||||||
|
#
|
||||||
|
# The reference is what every later stage is judged or derived from (the KLD
|
||||||
|
# record scores the arms, the Grams derive the protected experts), so the
|
||||||
|
# deliverable here is the set of refusals: a half checkpoint, a second convert,
|
||||||
|
# a volume that cannot hold the convert, an engine-refused plan, and any run —
|
||||||
|
# including a dry one — that would take the GPUs from frank-archon. Each test
|
||||||
|
# drives the script's own exit status and printed argv. Offline: no engine, no
|
||||||
|
# GPU, no container, no systemctl, no network. Path knobs aim at
|
||||||
|
# $BATS_TEST_TMPDIR.
|
||||||
|
#
|
||||||
|
# The third mode, score, carries the campaign's comparability law: its engine
|
||||||
|
# argv must equal kldref's except for --model and the --kld-* pair, or the KL
|
||||||
|
# numbers of the FP8-native anchor, the served payload and the arms are not one
|
||||||
|
# measurement series. The seam proves that by diffing the two dry runs.
|
||||||
|
|
||||||
|
# The 1.8.1 law asserted in two places below: --vram-kv-mib was REMOVED at
|
||||||
|
# 1.3.0, so naming it is a boot refusal — the reference serve must carry the
|
||||||
|
# budget-resolver knobs (--host-pool-mib / --gpu-headroom-mib) instead.
|
||||||
|
|
||||||
|
load helpers
|
||||||
|
|
||||||
|
BUILD="$REPO_ROOT/scripts/build-qwen38-bf16-rad.sh"
|
||||||
|
RUNNER="$REPO_ROOT/scripts/run-qwen38-bf16.sh"
|
||||||
|
|
||||||
|
# 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 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"
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
oneline() { local s="${output//$'\n'/ }"; printf '%s' "$s"; }
|
||||||
|
|
||||||
|
# engine_argv — the arguments AFTER the image line of a dry run, with --model
|
||||||
|
# and every --kld-* flag and its value removed: what is left is the measuring
|
||||||
|
# shape, and it must be identical between kldref and score.
|
||||||
|
engine_argv() {
|
||||||
|
local seen=0 skip=0 a
|
||||||
|
while IFS= read -r a; do
|
||||||
|
if [ "$seen" = 0 ]; then
|
||||||
|
[[ "$a" == *"radiance@sha256:"* ]] && seen=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
case "$a" in
|
||||||
|
--model|--kld-record|--kld-corpus|--kld-ref|--kld-out) skip=1; continue ;;
|
||||||
|
esac
|
||||||
|
if [ "$skip" = 1 ]; then skip=0; continue; fi
|
||||||
|
printf '%s\n' "$a"
|
||||||
|
done <<< "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# flag_value FLAG — the value a dry run printed for FLAG (the argv is one
|
||||||
|
# argument a line, so the value is the line after it). Lets a test ask what
|
||||||
|
# number the script chose instead of re-declaring the literal it guards.
|
||||||
|
flag_value() {
|
||||||
|
local want="$1" prev="" a
|
||||||
|
while IFS= read -r a; do
|
||||||
|
if [ "$prev" = "$want" ]; then printf '%s' "$a"; return 0; fi
|
||||||
|
prev="$a"
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the build dry run prints the rad-convert argv for the checkpoint and the bf16 output" {
|
||||||
|
M="$BATS_TEST_TMPDIR/models"
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" bash "$BUILD" --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"rad-convert"* ]]
|
||||||
|
# the argv is the container's view: the models dir rides the /models mount
|
||||||
|
[[ "$output" == *"$M:/models"* ]]
|
||||||
|
flat="$(oneline)"
|
||||||
|
[[ "$flat" == *"/models/Qwen3.8-Flash-Next -o /models/rad/qwen38-bf16.rad"* ]]
|
||||||
|
# a reference is the checkpoint re-headed, not re-encoded, and it declares a
|
||||||
|
# window the long KLD corpus can be read inside of
|
||||||
|
[[ "$output" != *"--recipe"* ]]
|
||||||
|
[[ "$output" != *"--reuse"* ]]
|
||||||
|
[[ "$flat" == *"--max-ctx 32768"* ]]
|
||||||
|
# and a dry run looked at nothing: the fixture dirs were never created
|
||||||
|
[ ! -e "$M" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the build refuses a checkpoint that is not on disk and names the fetch command" {
|
||||||
|
M="$BATS_TEST_TMPDIR/models"
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" bash "$BUILD"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"$M/Qwen3.8-Flash-Next"* ]]
|
||||||
|
[[ "$output" == *"scripts/download-qwen38-bf16.sh --fetch"* ]]
|
||||||
|
[ ! -e "$M/rad/qwen38-bf16.rad" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the build refuses an existing reference container and names --force as the way past it" {
|
||||||
|
M="$BATS_TEST_TMPDIR/models"
|
||||||
|
mkdir -p "$M/Qwen3.8-Flash-Next" "$M/rad"
|
||||||
|
echo "not a container" > "$M/rad/qwen38-bf16.rad"
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" QWEN38_MIN_FREE_MIB=1 \
|
||||||
|
QWEN38_TRASH="$BATS_TEST_TMPDIR/trash" bash "$BUILD"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"$M/rad/qwen38-bf16.rad"* ]]
|
||||||
|
[[ "$output" == *"--force"* ]]
|
||||||
|
# refusing retires nothing: the trash law means the file survives a refusal
|
||||||
|
[ -f "$M/rad/qwen38-bf16.rad" ]
|
||||||
|
[ ! -e "$BATS_TEST_TMPDIR/trash" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the build refuses when the output filesystem cannot hold a second 335 GiB" {
|
||||||
|
# The campaign's peak-disk law: 343,323 MiB of container written next to
|
||||||
|
# 343,323 MiB of checkpoint. The wanted figure is set above whatever this
|
||||||
|
# box really has, which is what the gate reads.
|
||||||
|
M="$BATS_TEST_TMPDIR/models"
|
||||||
|
mkdir -p "$M/Qwen3.8-Flash-Next" "$M/rad"
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" QWEN38_MIN_FREE_MIB=999999999 bash "$BUILD"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"REFUSED"* ]]
|
||||||
|
[[ "$output" =~ [0-9]+" MiB free" ]]
|
||||||
|
[[ "$output" == *"999999999 MiB wanted"* ]]
|
||||||
|
# the disk gate is the cheap one: it refuses long before the Hub sweep runs
|
||||||
|
[[ "$output" != *"VERIFY_OK"* ]]
|
||||||
|
[ ! -e "$M/rad/qwen38-bf16.rad" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the reference serve rejects an unknown mode and names every valid mode" {
|
||||||
|
# `probe` is the swift15 era's third mode; it is retired and must not boot
|
||||||
|
# a container here either.
|
||||||
|
run bash "$RUNNER" probe
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"probe"* ]]
|
||||||
|
[[ "$output" == *"kldref"* ]]
|
||||||
|
[[ "$output" == *"calib"* ]]
|
||||||
|
[[ "$output" == *"score"* ]]
|
||||||
|
[[ "$output" != *"podman"* ]]
|
||||||
|
run bash "$RUNNER"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"kldref"* ]]
|
||||||
|
[[ "$output" == *"calib"* ]]
|
||||||
|
[[ "$output" == *"score"* ]]
|
||||||
|
# score without its two positionals is the same usage refusal
|
||||||
|
run bash "$RUNNER" score
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"score RAD OUT"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the kldref dry run carries the record flags at the reference shape and no --vram-kv-mib" {
|
||||||
|
run bash "$RUNNER" kldref --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
flat="$(oneline)"
|
||||||
|
[[ "$flat" == *"--kld-record /data/kld-ref"* ]]
|
||||||
|
[[ "$flat" == *"--kld-corpus /data/kld.jsonl"* ]]
|
||||||
|
# the corpus arrives read-only, the record dir writable
|
||||||
|
[[ "$flat" == *":/data/kld.jsonl:ro"* ]]
|
||||||
|
[[ "$flat" == *":/data/kld-ref "* ]]
|
||||||
|
[[ "$flat" == *"$SHAPE"* ]]
|
||||||
|
# 1.8.1: the flag does not exist — naming it is a boot refusal
|
||||||
|
[[ "$output" != *"--vram-kv-mib"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the calib dry run opens the tap at 12M tokens and serves on 7770" {
|
||||||
|
run bash "$RUNNER" calib --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
flat="$(oneline)"
|
||||||
|
[[ "$flat" == *"-e RADIANCE_CALIB_TOKENS=12000000"* ]]
|
||||||
|
[[ "$flat" == *"-e RADIANCE_CALIB_DIR=/data/calib"* ]]
|
||||||
|
[[ "$flat" == *"--port 7770"* ]]
|
||||||
|
[[ "$flat" == *":/data/calib "* ]]
|
||||||
|
# calib serves; it does not record
|
||||||
|
[[ "$output" != *"--kld-record"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "a dry run prints argv only: no drain, no container, nothing written" {
|
||||||
|
L="$BATS_TEST_TMPDIR/logs"
|
||||||
|
run env FRANK_QWEN38_LOG_DIR="$L" \
|
||||||
|
bash "$RUNNER" kldref --dry-run --image example.invalid/radiance@sha256:0000000000000000000000000000000000000000000000000000000000000000
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" != *"systemctl"* ]]
|
||||||
|
[[ "$output" != *"frank-archon stopped"* ]]
|
||||||
|
[[ "$output" != *"podman rm"* ]]
|
||||||
|
# not vacuous: the podman argv WAS printed, and --image replaced the pin
|
||||||
|
[[ "$output" == *"podman"* ]]
|
||||||
|
[[ "$output" == *"example.invalid/radiance@sha256:000"* ]]
|
||||||
|
[[ "$output" != *"stilldeadcode/radiance"* ]]
|
||||||
|
[ ! -d "$L" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the score dry run is the kldref argv with only --model and the --kld-* pair changed" {
|
||||||
|
M="$BATS_TEST_TMPDIR/models"
|
||||||
|
C="$BATS_TEST_TMPDIR/campaign"
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" \
|
||||||
|
bash "$RUNNER" kldref --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
ref_argv="$(engine_argv "$output")"
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" \
|
||||||
|
bash "$RUNNER" score "$M/rad/arm-w5nl64.rad" "$C/scores/arm1.json" --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
score_argv="$(engine_argv "$output")"
|
||||||
|
# The whole value of the campaign's three KL numbers is that they rode this
|
||||||
|
# one shape: same pin, --tp 2, fp8 KV, step size, --deterministic, no spec.
|
||||||
|
if [[ "$score_argv" != "$ref_argv" ]]; then
|
||||||
|
echo "--- kldref engine argv ---"; echo "$ref_argv"
|
||||||
|
echo "--- score engine argv ---"; echo "$score_argv"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
flat="$(oneline)"
|
||||||
|
[[ "$flat" == *"--model /models/rad/arm-w5nl64.rad"* ]]
|
||||||
|
[[ "$flat" == *"--kld-ref /data/kld-ref --kld-out /data/out/arm1.json"* ]]
|
||||||
|
# the zero point mounts read-only; the report needs a writable bind because
|
||||||
|
# --kld-out F also writes F.rows beside it
|
||||||
|
[[ "$flat" == *":/data/kld-ref:ro "* ]]
|
||||||
|
[[ "$flat" == *":/data/out "* ]]
|
||||||
|
[[ "$output" != *"--kld-record"* ]]
|
||||||
|
[[ "$output" != *"--kld-corpus"* ]]
|
||||||
|
[[ "$output" != *"--vram-kv-mib"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "score refuses a missing reference, a missing container and an existing report" {
|
||||||
|
M="$BATS_TEST_TMPDIR/models"
|
||||||
|
C="$BATS_TEST_TMPDIR/campaign"
|
||||||
|
L="$BATS_TEST_TMPDIR/logs"
|
||||||
|
mkdir -p "$M/rad"
|
||||||
|
: > "$M/rad/arm-w5nl64.rad"
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" FRANK_QWEN38_LOG_DIR="$L" \
|
||||||
|
bash "$RUNNER" score "$M/rad/arm-w5nl64.rad" "$C/scores/arm1.json"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"$C/kld-ref"* ]]
|
||||||
|
[[ "$output" == *"run-qwen38-bf16.sh kldref"* ]]
|
||||||
|
# refused before the log dir, so before anything that could reach systemctl
|
||||||
|
[ ! -d "$L" ]
|
||||||
|
|
||||||
|
mkdir -p "$C/kld-ref"
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" FRANK_QWEN38_LOG_DIR="$L" \
|
||||||
|
bash "$RUNNER" score "$M/rad/gone.rad" "$C/scores/arm1.json"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"$M/rad/gone.rad"* ]]
|
||||||
|
[ ! -d "$L" ]
|
||||||
|
|
||||||
|
mkdir -p "$C/scores"
|
||||||
|
: > "$C/scores/arm1.json"
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" FRANK_QWEN38_LOG_DIR="$L" \
|
||||||
|
bash "$RUNNER" score "$M/rad/arm-w5nl64.rad" "$C/scores/arm1.json"
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"$C/scores/arm1.json"* ]]
|
||||||
|
[[ "$output" == *"trash"* ]]
|
||||||
|
# the trash law: a refusal keeps the old report, it does not overwrite it
|
||||||
|
[ -f "$C/scores/arm1.json" ]
|
||||||
|
[ ! -d "$L" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "the reference and a score pass ride ONE step size, legal on the file tier" {
|
||||||
|
# The gate's claim is one shape; that shape's ceiling is set by the tightest
|
||||||
|
# payload it boots, and the served iq4r and every arm are file-tier
|
||||||
|
# containers, which stream only at <=2048. A reference recorded above that is
|
||||||
|
# a KL between two shapes, not two weights.
|
||||||
|
M="$BATS_TEST_TMPDIR/models"
|
||||||
|
C="$BATS_TEST_TMPDIR/campaign"
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" bash "$RUNNER" kldref --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
ref_step="$(flag_value --max-num-batched-tokens <<< "$output")"
|
||||||
|
[ -n "$ref_step" ]
|
||||||
|
[ "$ref_step" -le 2048 ]
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" \
|
||||||
|
bash "$RUNNER" score "$M/rad/arm-w5nl64.rad" "$C/scores/arm1.json" --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
score_step="$(flag_value --max-num-batched-tokens <<< "$output")"
|
||||||
|
[ "$ref_step" = "$score_step" ]
|
||||||
|
# one knob moves both, so the operator can lower it at 2 a.m. without an edit
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" FRANK_QWEN38_MNBT=1024 \
|
||||||
|
bash "$RUNNER" kldref --dry-run
|
||||||
|
[ "$(flag_value --max-num-batched-tokens <<< "$output")" = "1024" ]
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" FRANK_QWEN38_MNBT=1024 \
|
||||||
|
bash "$RUNNER" score "$M/rad/arm-w5nl64.rad" "$C/scores/arm1.json" --dry-run
|
||||||
|
[ "$(flag_value --max-num-batched-tokens <<< "$output")" = "1024" ]
|
||||||
|
# calibration is exempt: grams are not logits, so the tap keeps the wide step
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_CAMPAIGN="$C" FRANK_QWEN38_MNBT=1024 \
|
||||||
|
bash "$RUNNER" calib --dry-run
|
||||||
|
[ "$(flag_value --max-num-batched-tokens <<< "$output")" = "65536" ]
|
||||||
|
# a step size that is not a token count is refused, never handed to the engine
|
||||||
|
run env FRANK_QWEN38_MNBT=broad bash "$RUNNER" kldref --dry-run
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"FRANK_QWEN38_MNBT"* ]]
|
||||||
|
[[ "$output" == *"must be a token count"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "kldref refuses a corpus its window cannot hold, before the service is touched" {
|
||||||
|
M="$BATS_TEST_TMPDIR/models" R="$M/rad"
|
||||||
|
mkdir -p "$M/tpl" "$R" "$BATS_TEST_TMPDIR/out"
|
||||||
|
: > "$M/tpl/qwen38-froggeric-v22.5.jinja"
|
||||||
|
: > "$R/qwen38-bf16.rad"
|
||||||
|
CORPUS="$BATS_TEST_TMPDIR/long.jsonl"
|
||||||
|
{
|
||||||
|
printf '{"prompt":"short one"}\n'
|
||||||
|
printf '{"prompt":"%s"}\n' "$(head -c 120000 /dev/zero | tr '\0' 'x')"
|
||||||
|
printf '{"prompt":"short three"}\n'
|
||||||
|
} > "$CORPUS"
|
||||||
|
# 32,768 tokens x 3 chars is the pessimistic limit; the 120,002-char line
|
||||||
|
# that opens record 2 is well past it, so the campaign says no up front.
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_KLD_CORPUS="$CORPUS" \
|
||||||
|
bash "$RUNNER" kldref --dry-run
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[[ "$output" == *"REFUSED"* ]]
|
||||||
|
[[ "$output" == *"record 2"* ]]
|
||||||
|
[[ "$output" == *"120013 chars"* ]]
|
||||||
|
[[ "$output" == *"past the 98304 a 32768-token window"* ]]
|
||||||
|
[[ "$output" == *"FRANK_QWEN38_MAX_MODEL_LEN"* ]]
|
||||||
|
# the same corpus is fine once the window is raised past it, and the raised
|
||||||
|
# window is what the boot is asked for
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_KLD_CORPUS="$CORPUS" \
|
||||||
|
FRANK_QWEN38_MAX_MODEL_LEN=65536 FRANK_QWEN38_LOG_DIR="$BATS_TEST_TMPDIR/logs" \
|
||||||
|
bash "$RUNNER" kldref --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"corpus fits"* ]]
|
||||||
|
flat="$(oneline)"
|
||||||
|
[[ "$flat" == *"--max-model-len 65536"* ]]
|
||||||
|
# the shipped corpus of three short records fits the shipped window
|
||||||
|
SHORT="$BATS_TEST_TMPDIR/short.jsonl"
|
||||||
|
printf '{"prompt":"tiny"}\n{"prompt":"also tiny"}\n' > "$SHORT"
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_KLD_CORPUS="$SHORT" \
|
||||||
|
FRANK_QWEN38_LOG_DIR="$BATS_TEST_TMPDIR/logs" bash "$RUNNER" kldref --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"corpus fits: longest record 22 chars (number 2)"* ]]
|
||||||
|
# a real kldref run refuses at the same place: no log dir, no drain, and the
|
||||||
|
# service would still be up when the script gave up
|
||||||
|
L="$BATS_TEST_TMPDIR/logs-real"
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_KLD_CORPUS="$CORPUS" \
|
||||||
|
FRANK_QWEN38_LOG_DIR="$L" bash "$RUNNER" kldref
|
||||||
|
[ "$status" -eq 2 ]
|
||||||
|
[ ! -d "$L" ]
|
||||||
|
# and it is kldref that reads the corpus: score and calib never consult it
|
||||||
|
run env FRANK_QWEN38_MODELS="$M" FRANK_QWEN38_KLD_CORPUS="$CORPUS" \
|
||||||
|
FRANK_QWEN38_KLD_REF_DIR="$BATS_TEST_TMPDIR/ref" \
|
||||||
|
FRANK_QWEN38_LOG_DIR="$BATS_TEST_TMPDIR/logs" \
|
||||||
|
bash "$RUNNER" score "$R/qwen38-bf16.rad" "$BATS_TEST_TMPDIR/out/arm.json" --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" != *"corpus fits"* ]]
|
||||||
|
[[ "$output" != *"REFUSED"* ]]
|
||||||
|
}
|
||||||
Reference in new issue
Block a user