Files
serverAppConfigs/scripts/kld-gate-qwen38.sh
T
Frank c238e32d6f fix(quant): a tap is only a tap when its rig is running
Four gates, all bought with the 2026-10-29 incident: the rig's calibration boot died of an OOM,
the chain's health loop expired without failing, and the pusher fed four rounds of 10M tokens
(86 minutes of prefill) into RESTORED PRODUCTION, recording nothing.

- run-qwen38-bf16.sh: --ngram-placement becomes a knob whose calib default is `disk` (the 95.37
  GiB table pin is what starved that boot: 230.22 GiB pinned of 246, 8.06 GiB available) while
  kldref and score keep `ram`. calib writes .rig-boot into the tap dir and retires it on a
  non-zero exit, so a dead boot cannot leave the tap looking rig-authorized.
- calib-push.py: refuses before the first prompt unless the marker exists, names this container,
  and that container is running. With the driver's existing tap-progress gate, a push can no
  longer spend hours feeding an endpoint that is not the rig.
- qwen38-calib-window.sh: the calibration window as a gated chain — stop on a failed phase
  instead of falling through the health wait, bank the 336 GiB reference container into trash
  only when both phases exit 0 and the tap holds grams, and print the whole plan on --dry-run.
- kld-gate-qwen38.sh: two-report mode (candidate vs the served payload) for the disk where the
  172.8 GiB FP8 anchor cannot live, plus the laws the report always carried and the gate never
  read: a p99.9 tail ceiling and every by_source slice passing mean, tail and top-1.
- frank-archon.md: the pipeline laws (marker, n-gram tier, three-metric acceptance) and a Files
  section that names the live campaign instead of the retired swift15 era.

Tests: 587/0 — 13 new behavioral cases (dry-run argv, marker refusals, two-report gate math).
2026-10-29 16:14:16 -07:00

187 lines
7.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# kld-gate-qwen38.sh — RUNS ON DANKSERVER (or anywhere the reports are). The campaign's
# acceptance gate: one verdict from the KLD reports of the containers that ride one reference
# recording.
#
# 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.
#
# TWO MODES, because the disk decides how many anchors can exist at once:
#
# gate IQ4R.json CAND.json candidate vs the served payload
# gate FP8.json IQ4R.json CAND.json the same, inside the absolute interval
#
# iq4r.json is the payload frank-archon serves — the bar to beat. When the FP8-native release
# (172.8 GiB, fetched last because of the peak-disk law) is on disk, pass its report first:
# "half the gap" is then half of the distance between the 4-bit payload and the near-lossless
# release, not half of an invented distance to the checkpoint. Without it the gate still
# answers the campaign's question — does the candidate beat what is served — and says so.
#
# The law, on the summary of one slice (--slice all | a corpus source):
#
# candidate vs served (2 reports)
# 1. mean KL(cand) < mean KL(iq4r) the served payload is beaten on the corpus
# 2. p99.9(cand) <= p99.9(iq4r) and the tail is no worse — a mean can hide a few
# positions taking the whole hit
# 3. top-1(cand) >= top-1(iq4r) the agreement the harness keys on does not fall
# 4. every source slice passes 1-3 a win bought by regressing one distribution
# (code, prose, tools) is not a win
#
# inside the interval (3 reports), laws 1-4 above (1-3 renumbered to the interval and the
# recovery fraction, as agreed 2026-10-27) plus:
# 5. p99.9(cand) <= p99.9(iq4r) the tail law
# 6. every source slice passes the slice law
#
# 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 numbers comparable.
# This script is arithmetic, so it re-runs against old reports and is testable without a GPU.
#
# Usage: kld-gate-qwen38.sh IQ4R.json CAND.json [--slice NAME]
# kld-gate-qwen38.sh FP8.json IQ4R.json CAND.json [--slice NAME] [--recover F]
set -uo pipefail
if [ "$#" -lt 2 ]; then
echo "usage: kld-gate-qwen38.sh IQ4R.json CAND.json [--slice NAME]" >&2
echo " kld-gate-qwen38.sh FP8.json IQ4R.json CAND.json [--slice NAME] [--recover F]" >&2
exit 2
fi
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) not in (2, 3):
sys.exit("usage: kld-gate-qwen38.sh IQ4R.json CAND.json [--slice NAME]\n"
" 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}")
interval = len(files) == 3
def die(msg):
print(msg, file=sys.stderr)
raise SystemExit(2)
def load(path):
try:
return json.load(open(path))
except Exception as e:
die(f"cannot read the KLD report {path}: {e}")
def summary(path, d):
"""The slice this gate grades: 'all', or one corpus source."""
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 g
docs = {label: load(path) for label, path in
zip(("fp8", "iq4r", "cand") if interval else ("iq4r", "cand"), files)}
rows = []
for label, path in zip(("fp8", "iq4r", "cand") if interval else ("iq4r", "cand"), files):
g = summary(path, docs[label])
rows.append((label, path, docs[label].get("model", "?"), g["kld"]["mean"],
g["kld"]["p99_9"], g["top1_agreement"], g["positions"]))
print(f"reference: BF16 container; slice: {slice_}; "
+ ("inside the FP8 interval" if interval else "candidate vs served (no FP8 anchor)"))
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, p999, top1) for label, _p, _m, kl, p999, top1, _n in rows}
kl_cand, p_cand, t_cand = r["cand"]
kl_iq4r, p_iq4r, t_iq4r = r["iq4r"]
ok = True
def law(n, text, passed):
global ok
ok = ok and passed
print(f" [{'PASS' if passed else 'FAIL'}] {n}. {text}")
def tail_law(n):
law(n, f"candidate KL p99.9 {p_cand:.4f} <= served iq4r {p_iq4r:.4f}",
p_cand <= p_iq4r)
# --- the slice law: every corpus source must pass the metric laws -------------
def slice_law(n):
"""A win that regresses one source class is not a win: check them all."""
mine = docs["cand"].get("by_source") or {}
theirs = docs["iq4r"].get("by_source") or {}
if not mine or not theirs:
law(n, "no per-source summaries to check", True)
return
missing = sorted(set(theirs) - set(mine))
extra = sorted(set(mine) - set(theirs))
if missing or extra:
law(n, "same source slices in both reports"
+ (f" (missing from cand: {', '.join(missing)})" if missing else "")
+ (f" (only in cand: {', '.join(extra)})" if extra else ""), False)
return
bad = []
for src in sorted(theirs):
c, q = mine[src], theirs[src]
worse_mean = c["kld"]["mean"] >= q["kld"]["mean"]
worse_tail = c["kld"]["p99_9"] > q["kld"]["p99_9"]
worse_top1 = c["top1_agreement"] < q["top1_agreement"]
if worse_mean or worse_tail or worse_top1:
why = ",".join(m for m, w in (("mean", worse_mean), ("p99.9", worse_tail),
("top-1", worse_top1)) if w)
bad.append(f"{src} ({why})")
law(n, "every source slice passes (mean, p99.9, top-1)"
+ (f" — regressed: {'; '.join(bad)}" if bad else ""), not bad)
if interval:
kl_fp8 = r["fp8"][0]
gap = kl_iq4r - kl_fp8
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)
tail_law(5)
slice_law(6)
else:
law(1, f"candidate beats the served payload: KL {kl_cand:.6f} < iq4r {kl_iq4r:.6f}",
kl_cand < kl_iq4r)
tail_law(2)
law(3, f"candidate top-1 {100*t_cand:.2f}% >= served iq4r top-1 {100*t_iq4r:.2f}%",
t_cand >= t_iq4r)
slice_law(4)
print("KLD GATE " + ("PASS" if ok else "FAIL"))
sys.exit(0 if ok else 1)
PY