RegalInvader's picture
Release: Marco-Nano-Instruct 1-, 2- and 3-bit GGUF for phones
e5235fb verified
Raw History Blame Contribute Delete
4.16 kB
#!/usr/bin/env python3
"""D-59b ship rule for a release candidate against the file it would replace: per test group, the score difference
on the same items with its paired standard error (both models answered identical questions), a verdict per group
(drop / gain beyond 2 SE, or within noise), and the KL difference. The D-59b fixed thresholds are printed beside the
paired ones; the verdict uses the paired SE (the exact test of the same rule).
python scripts/ship_rule.py --base marco-3bit-iq3s --cand marco-3bit-b3-20 [--kl-base 0.02854 --kl-cand 0.02773]
Rule: replace only if no group drops beyond 2 SE, KL is not worse by more than 0.0003, and there is a real
improvement: a group gaining beyond 2 SE, or KL better by more than 0.0003 (28 Oct 05:50 reading of the D-59b rule,
for the human to confirm: B3-20 is -0.0008 KL with every benchmark within noise).
"""
from __future__ import annotations
import argparse
import json
import math
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
CMP = REPO / "reports" / "compare"
LANGS, FULL_LANGS = ["zh", "ar", "ko", "hu", "bn"], ["de", "es", "fr", "ja", "ru"]
GROUPS = {"MMLU-Redux": ["mmlu_redux"], "Global-MMLU-Lite (5)": [f"gmmlu_{l}" for l in LANGS],
"Belebele (5)": [f"belebele_{l}" for l in LANGS], "GSM8K-250": ["gsm8k"], "MMLU-Pro": ["mmlu_pro"],
"Global-MMLU (5 more)": [f"gmmlux_{l}" for l in FULL_LANGS], "MATH-500": ["math500"],
"IFEval (prompt)": ["ifeval"], "HumanEval+": ["humanevalplus"], "MBPP+": ["mbppplus"]}
FIXED_2SE = {"MMLU-Redux": 3.1, "Global-MMLU-Lite (5)": 2.2, "Belebele (5)": 2.8, "GSM8K-250": 4.3, "MMLU-Pro": 2.1,
"Global-MMLU (5 more)": 1.4, "MATH-500": 4.1, "IFEval (prompt)": 4.2, "HumanEval+": 7.5, "MBPP+": 4.8}
KL_TOL = 0.0003
def items(name: str) -> dict:
out = {}
for suffix in ("", ".code"):
p = CMP / f"{name}{suffix}.jsonl"
if p.exists():
for line in p.open():
r = json.loads(line)
out[(r["task"], r["id"])] = r
return out
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--base", required=True)
ap.add_argument("--cand", required=True)
ap.add_argument("--kl-base", type=float)
ap.add_argument("--kl-cand", type=float)
a = ap.parse_args()
b, c = items(a.base), items(a.cand)
rows, drops, gains, missing = [], [], [], []
for g, tasks in GROUPS.items():
keys = [k for k in b if k[0] in tasks and k in c]
if not keys:
missing.append(g)
continue
d = [int(c[k]["correct"]) - int(b[k]["correct"]) for k in keys]
n = len(d)
mean = sum(d) / n
se = math.sqrt(sum((x - mean) ** 2 for x in d) / (n - 1) / n) if n > 1 else float("nan")
delta, two_se = 100 * mean, 200 * se
verdict = "drop" if delta < -two_se else "gain" if delta > two_se else "noise"
(drops if verdict == "drop" else gains if verdict == "gain" else []).append(g)
rows.append((g, n, 100 * sum(int(b[k]["correct"]) for k in keys) / n,
100 * sum(int(c[k]["correct"]) for k in keys) / n, delta, two_se, FIXED_2SE[g], verdict))
print(f"{a.cand} vs {a.base}")
print(f"{'test':22s} {'n':>5s} {'base':>6s} {'cand':>6s} {'delta':>6s} {'2SE':>5s} {'fixed':>5s} verdict")
for g, n, sb, sc, dl, ts, fx, v in rows:
print(f"{g:22s} {n:5d} {sb:6.1f} {sc:6.1f} {dl:+6.1f} {ts:5.1f} {fx:5.1f} {v}")
kl_ok = kl_gain = None
if a.kl_base is not None and a.kl_cand is not None:
kl_ok = a.kl_cand <= a.kl_base + KL_TOL
kl_gain = a.kl_cand < a.kl_base - KL_TOL
print(f"KL {a.kl_base:.5f} -> {a.kl_cand:.5f} ({a.kl_cand - a.kl_base:+.5f}): {'ok' if kl_ok else 'WORSE'}")
if missing:
print("not measured yet:", ", ".join(missing))
ok = not drops and (bool(gains) or bool(kl_gain)) and kl_ok is not False
print(json.dumps({"base": a.base, "cand": a.cand, "drops": drops, "gains": gains, "kl_ok": kl_ok, "kl_gain": kl_gain,
"missing": missing, "replace": ok and not missing}))
if __name__ == "__main__":
main()