Upload source/files/compare_nll_vs_generative.py with huggingface_hub
Browse files
source/files/compare_nll_vs_generative.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
compare_nll_vs_generative.py -- put the NLL proxy and the generative benchmark
|
| 4 |
+
side by side and judge three PRE-REGISTERED predictions against numeric
|
| 5 |
+
thresholds fixed before the numbers were seen.
|
| 6 |
+
|
| 7 |
+
usage: compare_nll_vs_generative.py <longeval_out_dir> <nll_ref_dir> [> EVAL_COMPARISON.md]
|
| 8 |
+
|
| 9 |
+
Why thresholds up front: at 100 prompts per (model, depth) cell the 95% Wilson
|
| 10 |
+
half-width is ~+-10 points, so "arm C beats arm B" is only meaningful relative to
|
| 11 |
+
that resolution. Judging after the fact would be post-hoc.
|
| 12 |
+
|
| 13 |
+
The two metrics are NOT on a common scale -- one is nats of NLL margin, the other
|
| 14 |
+
a hit rate -- so only ORDERINGS are compared, never absolute values.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import csv
|
| 18 |
+
import math
|
| 19 |
+
import os
|
| 20 |
+
import sys
|
| 21 |
+
|
| 22 |
+
# NLL probe dir -> generative checkpoint key
|
| 23 |
+
NLL_MODEL = {
|
| 24 |
+
"probe_run7_yarn4": "base",
|
| 25 |
+
"probe_arm_a": "arm_a",
|
| 26 |
+
"probe_arm_b": "arm_b",
|
| 27 |
+
"probe_arm_c": "arm_c",
|
| 28 |
+
}
|
| 29 |
+
PFRAC_DEPTH = {"p0": 0.0, "p0.25": 0.25, "p0.5": 0.5, "p0.75": 0.75, "p1": 1.0}
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def wilson(h, n):
|
| 33 |
+
if not n:
|
| 34 |
+
return float("nan")
|
| 35 |
+
z, p = 1.96, h / n
|
| 36 |
+
return 100.0 * z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / (1 + z * z / n)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def load_nll(ref):
|
| 40 |
+
"""{model: {depth: margin_nats}} margin = nll_null - nll (higher = better)."""
|
| 41 |
+
out = {}
|
| 42 |
+
for d, key in NLL_MODEL.items():
|
| 43 |
+
p = os.path.join(ref, "logs", d, "reach.csv")
|
| 44 |
+
if not os.path.exists(p):
|
| 45 |
+
continue
|
| 46 |
+
out[key] = {}
|
| 47 |
+
for r in csv.DictReader(open(p)):
|
| 48 |
+
dep = PFRAC_DEPTH.get(r["p_frac"])
|
| 49 |
+
if dep is None:
|
| 50 |
+
continue
|
| 51 |
+
out[key][dep] = float(r["nll_null"]) - float(r["nll"])
|
| 52 |
+
return out
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def load_gen(out_dir):
|
| 56 |
+
"""{model: {depth: (hits, n)}} from depth_summary.csv."""
|
| 57 |
+
p = os.path.join(out_dir, "depth_summary.csv")
|
| 58 |
+
if not os.path.exists(p):
|
| 59 |
+
sys.exit(f"missing {p} -- run run_longeval.sh first")
|
| 60 |
+
g = {}
|
| 61 |
+
for r in csv.DictReader(open(p)):
|
| 62 |
+
g.setdefault(r["model"], {})[float(r["depth"])] = (int(r["hits"]), int(r["n"]))
|
| 63 |
+
return g
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def main():
|
| 67 |
+
if len(sys.argv) < 3:
|
| 68 |
+
sys.exit(__doc__)
|
| 69 |
+
out_dir, ref = sys.argv[1], sys.argv[2]
|
| 70 |
+
nll, gen = load_nll(ref), load_gen(out_dir)
|
| 71 |
+
models = [m for m in ("base", "arm_a", "arm_b", "arm_c") if m in gen]
|
| 72 |
+
depths = sorted({d for m in gen.values() for d in m})
|
| 73 |
+
pct = lambda hn: 100.0 * hn[0] / hn[1] if hn[1] else float("nan")
|
| 74 |
+
G = lambda m, d: pct(gen[m][d]) if m in gen and d in gen[m] else float("nan")
|
| 75 |
+
|
| 76 |
+
print("# NLL proxy vs generative benchmark\n")
|
| 77 |
+
print("Two different measurements: NLL margin in nats (teacher-forced, answer")
|
| 78 |
+
print("already in the input) vs hit rate on generated text. Only the ORDERINGS")
|
| 79 |
+
print("are comparable.\n")
|
| 80 |
+
|
| 81 |
+
print("## Generative accuracy %, pooled by depth across tasks (+-95% Wilson)\n")
|
| 82 |
+
print("| depth | " + " | ".join(models) + " |")
|
| 83 |
+
print("|---" * (len(models) + 1) + "|")
|
| 84 |
+
for d in depths:
|
| 85 |
+
cells = []
|
| 86 |
+
for m in models:
|
| 87 |
+
if m in gen and d in gen[m]:
|
| 88 |
+
h, n = gen[m][d]
|
| 89 |
+
cells.append(f"{pct((h,n)):.1f} ±{wilson(h,n):.1f} (n={n})")
|
| 90 |
+
else:
|
| 91 |
+
cells.append("-")
|
| 92 |
+
print(f"| {d:.2f} | " + " | ".join(cells) + " |")
|
| 93 |
+
|
| 94 |
+
print("\n## NLL margin (nats), same models and depths\n")
|
| 95 |
+
print("| depth | " + " | ".join(models) + " |")
|
| 96 |
+
print("|---" * (len(models) + 1) + "|")
|
| 97 |
+
for d in depths:
|
| 98 |
+
print(f"| {d:.2f} | " + " | ".join(
|
| 99 |
+
(f"{nll[m][d]:.2f}" if m in nll and d in nll[m] else "-") for m in models) + " |")
|
| 100 |
+
|
| 101 |
+
# ---- pre-registered predictions -------------------------------------------
|
| 102 |
+
print("\n## Pre-registered predictions\n")
|
| 103 |
+
verdicts = []
|
| 104 |
+
|
| 105 |
+
# 1. depth 0.0 ~ 0% everywhere; conditional on the positive control
|
| 106 |
+
d0 = {m: G(m, 0.0) for m in models}
|
| 107 |
+
worst = max((v for v in d0.values() if v == v), default=float("nan"))
|
| 108 |
+
ctrl = {m: G(m, 1.0) for m in models}
|
| 109 |
+
ctrl_ok = any(v > 0 for v in ctrl.values() if v == v)
|
| 110 |
+
v1 = ("CONFIRMED" if worst <= 5 else "REFUTED" if worst >= 15 else "INCONCLUSIVE")
|
| 111 |
+
if not ctrl_ok:
|
| 112 |
+
v1 = "UNINTERPRETABLE (positive control failed)"
|
| 113 |
+
verdicts.append(("1. depth 0.0 ~= 0% for every model", v1,
|
| 114 |
+
f"max over models = {worst:.1f}%; positive control (any model >0% at depth 1.0) = "
|
| 115 |
+
f"{'PASS' if ctrl_ok else 'FAIL'}"))
|
| 116 |
+
|
| 117 |
+
# 2. arm C >> arm B at depth 0.25
|
| 118 |
+
if "arm_c" in gen and "arm_b" in gen:
|
| 119 |
+
diff2 = G("arm_c", 0.25) - G("arm_b", 0.25)
|
| 120 |
+
v2 = ("CONFIRMED" if diff2 >= 20 else "REFUTED" if abs(diff2) <= 5 else "INCONCLUSIVE")
|
| 121 |
+
verdicts.append(("2. arm C >> arm B at depth 0.25", v2,
|
| 122 |
+
f"armC {G('arm_c',0.25):.1f}% - armB {G('arm_b',0.25):.1f}% = {diff2:+.1f}pp "
|
| 123 |
+
f"(NLL said 2.80 vs 0.53 nats, ~9.5 sigma)"))
|
| 124 |
+
|
| 125 |
+
# 3. arm C non-monotonicity: depth 0.25 > depth 0.5
|
| 126 |
+
if "arm_c" in gen:
|
| 127 |
+
diff3 = G("arm_c", 0.25) - G("arm_c", 0.5)
|
| 128 |
+
v3 = ("CONFIRMED" if diff3 >= 20 else "REFUTED" if abs(diff3) <= 5 else "INCONCLUSIVE")
|
| 129 |
+
verdicts.append(("3. arm C depth 0.25 > depth 0.5", v3,
|
| 130 |
+
f"{G('arm_c',0.25):.1f}% - {G('arm_c',0.5):.1f}% = {diff3:+.1f}pp "
|
| 131 |
+
f"(NLL said 1.0865 nats at 6.87 sigma)"))
|
| 132 |
+
|
| 133 |
+
print("| prediction | verdict | evidence |")
|
| 134 |
+
print("|---|---|---|")
|
| 135 |
+
for name, v, ev in verdicts:
|
| 136 |
+
print(f"| {name} | **{v}** | {ev} |")
|
| 137 |
+
|
| 138 |
+
# ---- ordering agreement ----------------------------------------------------
|
| 139 |
+
print("\n## Ordering agreement (the only comparable thing)\n")
|
| 140 |
+
for m in models:
|
| 141 |
+
if m not in nll:
|
| 142 |
+
continue
|
| 143 |
+
gd = [(d, G(m, d)) for d in depths if G(m, d) == G(m, d)]
|
| 144 |
+
nd = [(d, nll[m][d]) for d in depths if d in nll[m]]
|
| 145 |
+
go = [d for d, _ in sorted(gd, key=lambda x: -x[1])]
|
| 146 |
+
no = [d for d, _ in sorted(nd, key=lambda x: -x[1])]
|
| 147 |
+
same = "AGREE" if go == no else "DIFFER"
|
| 148 |
+
print(f"- **{m}**: generative best->worst {go}; NLL best->worst {no} -> {same}")
|
| 149 |
+
if all(v == 0.0 for _, v in gd):
|
| 150 |
+
print(f" (all-zero generative: ordering is undefined, not disagreeing)")
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
if __name__ == "__main__":
|
| 154 |
+
main()
|