File size: 12,094 Bytes
7d3eb8f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | """RAW-text judge inputs, scoring and a channel check for the fix arms on the bug-fixed base
(runs/CSEL/FIX_TRAINING_PREREG.md amendments 3-4): job 184 (FIX 21-23, prefix jr, pools FXR) and job 185 (FIX 10/5/7
of jobs 173/175/176, prefix jq, pools FXQ; replaces job 177's ๆ-written pools).
The judge sees the poem exactly as the model wrote it: NO ๆ write-back (runs/CSEL/FIXED_BASE_PREREG.md amendment 1 -
fixbase_pipe.judged_text contracts any two equal adjacent syllables, which turned model stutters into plausible
reduplication for 6.6% of candidates). scripts/fixon.py (rawjudge/rawpools) still writes ๆ back and is not used here.
rawpools OUT A-B PFX ARM=DIR... judge inputs, seeds A..B: the control (170's FIXED_b0 candidates) + <= 3 arms; per
seed and topic half (fixbase_pipe.halves, 25/24 topics) OUT/{PFX}_{fixbase,ARM...}_s{seed}h{h}.jsonl
= ALL candidates (form-failing ones too, so every arm has the same count per pool and
build_pool's balance assert holds by construction; form enters the endpoint through
the candidate's all_pass), id TOPIC_sSEED, poem = the raw candidate text (asserted 4 lines)
score OUT A-B PREFIX ARM... after judging runs/{PREFIX}_{CLEAN,ALIGN}{seed}{half}/run1.json: per arm vs the control,
paired by (topic, seed) over ALL candidates (every candidate is judged; the endpoint
counts a form-failing candidate as not clean / not aligned; judged-only rates reported): clean, aligned, all-three, topic-clustered 95% CI, decision
(pre-registered rule); a pool whose human anchors are < 70% clean is void -> exit 3
phonuse CTL_MODEL ARM_MODEL row norms of token / rime / tone embeddings and the standard valid loss with rime+tone
zeroed, for both models (reported by job 179; not a gate)
test CPU tests of the pure parts
"""
import os, sys, json, collections
sys.path.insert(0, "."); sys.path.insert(0, "scripts")
FB = "runs/FIXBASE"
def rows(p): return [json.loads(l) for l in open(p, encoding="utf-8") if l.strip()]
def wrows(p, R):
with open(p + ".tmp", "w", encoding="utf-8") as f:
for r in R: f.write(json.dumps(r, ensure_ascii=False) + "\n")
os.replace(p + ".tmp", p)
def halves(ids): # = fixbase_pipe.halves (copied so the pools do not depend on fixbase_pipe)
ids = sorted(ids); h = (len(ids) + 1) // 2
return [ids[:h], ids[h:]]
def arms_of(specs):
A = [("fixbase", f"{FB}/FIXED_b0/cands")] + [tuple(x.split("=", 1)) for x in specs]
assert len({n for n, _ in A}) == len(A) and len(A) <= 4, "arm names must be unique; at most control + 3 arms per pool"
return A
def rawpools(out, spec, pfx, *arms):
assert pfx in ("jr", "jq"), f"prefix {pfx}: jr (job 184) or jq (job 185)"
a, b = map(int, spec.split("-"))
A = arms_of(arms)
os.makedirs(out, exist_ok=True); n = nf = 0
for s in range(a, b + 1):
R = {name: {r["id"]: r for r in rows(f"{d}/c{s}.jsonl")} for name, d in A}
ids = sorted(R["fixbase"])
assert all(set(R[k]) == set(ids) for k in R), f"seed {s}: arms cover different topics"
assert all(R[k][i]["prompt"] == R["fixbase"][i]["prompt"] for k in R for i in ids), f"seed {s}: prompts differ"
for h, part in enumerate(halves(ids)):
for name, _ in A:
J = [{"id": f"{i}_s{s}", "prompt": R[name][i]["prompt"], "poem": R[name][i]["poem"]} for i in part]
bad = [r["id"] for r in J if len(r["poem"].split("\n")) != 4]
assert not bad, f"{name} seed {s}: poems without exactly 4 lines (build_pool would drop them silently): {bad}"
wrows(f"{out}/{pfx}_{name}_s{s}h{h}.jsonl", J); n += len(J); nf += sum(not R[name][i]["all_pass"] for i in part)
print(f" raw pools (no ๆ write-back): arms {[x for x, _ in A]}, seeds {a}-{b}, {n} poems ({nf} form-failing, judged too) in {2 * (b - a + 1)} pool pairs")
def decide(clean, aligned):
"""Pre-registered (FIX_TRAINING_PREREG.md): CONTINUE if clean >= +5pp with the lower 95% bound > 0 and aligned
>= -3pp; KILL if clean < +2pp; otherwise GREY."""
if clean["diff"] >= 5 and clean["ci"][0] > 0 and aligned["diff"] >= -3: return "CONTINUE"
if clean["diff"] < 2: return "KILL"
return "GREY"
def labels(prefix, a, b, root_pools="runs"):
"""{(src, topic, seed): {clean, aligned}} for judged system poems; anchor clean share per clean pool"""
lab = {"clean": {}, "aligned": {}}; anchors = {}
for s in range(a, b + 1):
for h in (0, 1):
for kind, inst in (("clean", "CLEAN"), ("aligned", "ALIGN")):
d = f"{root_pools}/{prefix}_{inst}{s}{h}"
km = json.load(open(f"{d}/keymap.json", encoding="utf-8"))
res = {r["item"]: r for r in json.load(open(f"{d}/run1.json", encoding="utf-8"))}
assert set(res) == set(km), f"{d}: judge output does not cover the pool"
hum = []
for k, v in km.items():
r = res[k]
val = (not any(e.get("severity") in ("critical", "major") for e in r["errors"])) if kind == "clean" else int(r["align"]) >= 4
if v["src"] == "human": hum.append(val); continue
t, ss = v["id"].rsplit("_s", 1); assert int(ss) == s, f"{d}: {v} is not seed {s}"
assert (v["src"], t, s) not in lab[kind], f"{v} judged twice"
lab[kind][(v["src"], t, s)] = val
if kind == "clean": anchors[d] = sum(hum) / max(len(hum), 1)
return lab, anchors
def score(out, spec, prefix, *arm_specs, root_pools="runs"):
import fixbase_pipe
a, b = map(int, spec.split("-"))
A = arms_of(arm_specs)
form = {}
for name, d in A:
for s in range(a, b + 1):
for r in rows(f"{d}/c{s}.jsonl"): form[(name, r["id"], s)] = bool(r["all_pass"])
lab, anchors = labels(prefix, a, b, root_pools)
void = {d: v for d, v in anchors.items() if v < 0.70}
for kind in ("clean", "aligned"):
assert set(lab[kind]) == set(form), f"{kind}: judged poems differ from the candidates"
full = {kind: {k: form[k] and lab[kind][k] for k in form} for kind in ("clean", "aligned")}
full["all-three"] = {k: full["clean"][k] and full["aligned"][k] for k in form}
full["form"] = dict(form); full["judged-clean"] = dict(lab["clean"]); full["judged-aligned"] = dict(lab["aligned"])
R = {"anchor_clean_share": anchors, "void_pools": sorted(void)}
for name, _ in A[1:]:
R[name] = {}
for kind, L in full.items():
c = {k[1:]: v for k, v in L.items() if k[0] == "fixbase"}; t = {k[1:]: v for k, v in L.items() if k[0] == name}
st = R[name][kind] = fixbase_pipe.paired_stats(c, t)
print(f" {name:8s} {kind:9s} control {st['a']:5.1f}% arm {st['b']:5.1f}% diff {st['diff']:+.1f}pp "
f"({st['up']} up / {st['down']} down, n={st['n']}) 95% CI [{st['ci'][0]:+.1f}, {st['ci'][1]:+.1f}]")
R[name]["decision"] = decide(R[name]["clean"], R[name]["aligned"])
print(f" {name}: DECISION (pre-registered) {R[name]['decision']}")
os.makedirs(out, exist_ok=True)
json.dump(R, open(f"{out}/RESULT_{prefix}.json", "w"), indent=1)
if void:
print(f" VOID pools (anchors < 70% clean, judge_frozen §2.2): {void} - re-judge those pools before any decision"); sys.exit(3)
def phonuse(ctl, arm):
import torch
import canvas_bot as B, fixtrain_gates as G
G.RATIOS = (1.0,) # only the standard condition is reported (and r=1.0, which valid_losses needs)
real = B.load_model
def zeroed(path):
m, V = real(path)
with torch.no_grad(): m.rime.weight.zero_(); m.tone.weight.zero_()
return m, V
for d in (ctl, arm):
m, _ = real(d); n = lambda w: float(w.detach().float().norm(dim=1).mean())
print(f" {d}: row norms token {n(m.tok.weight):.3f}, rime {n(m.rime.weight):.3f}, tone {n(m.tone.weight):.3f}")
del m
full = G.valid_losses(d)["standard"]
B.load_model = zeroed
try: z = G.valid_losses(d)["standard"]
finally: B.load_model = real
print(f" {d}: standard valid loss {full:.4f}; rime+tone zeroed {z:.4f} (use = {z - full:+.4f} nats)")
def _test():
import tempfile
global FB
t = tempfile.mkdtemp(); fb0, FB = FB, t
os.makedirs(f"{t}/FIXED_b0/cands"); os.makedirs(f"{t}/x")
stut = "ห้องห้องนี้\nฝนตก\nลมพัด\nใจเย็น" # a model stutter: must reach the judge as written (no ๆ)
for d, bad in ((f"{t}/FIXED_b0/cands", 3), (f"{t}/x", 1)):
wrows(f"{d}/c0.jsonl", [{"id": f"T{i:02d}", "prompt": "ฝน", "poem": stut if i == 0 else "ฝนตก\nลม\nพัด\nมา", "all_pass": i != bad} for i in range(5)])
rawpools(f"{t}/p", "0-0", "jr", f"fix21={t}/x")
h0c, h0a, h1a = rows(f"{t}/p/jr_fixbase_s0h0.jsonl"), rows(f"{t}/p/jr_fix21_s0h0.jsonl"), rows(f"{t}/p/jr_fix21_s0h1.jsonl")
# ALL candidates (form-failing ones too): equal counts per arm and pool by construction
assert [r["id"] for r in h0c] == [r["id"] for r in h0a] == ["T00_s0", "T01_s0", "T02_s0"], (h0c, h0a)
assert [r["id"] for r in h1a] == ["T03_s0", "T04_s0"] and h0c[0]["poem"] == stut and "ๆ" not in h0a[0]["poem"]
# a candidate that is not 4 lines stops the export (build_pool would drop it silently)
os.makedirs(f"{t}/y"); wrows(f"{t}/y/c0.jsonl", [dict(r, poem="ฝน\nตก\nลม") if r["id"] == "T02" else r for r in rows(f"{t}/x/c0.jsonl")])
try: rawpools(f"{t}/q", "0-0", "jq", f"fix5={t}/y"); raise AssertionError("3-line poem not caught")
except AssertionError as e: assert "4 lines" in str(e), e
# scoring: fake judged pools; fix21 clean on everything it passed, control fails T00
for inst in ("CLEAN", "ALIGN"):
for h in (0, 1):
d = f"{t}/runs/FXT_{inst}0{h}"; os.makedirs(d)
km, res = {}, []
for name in ("fixbase", "fix21"):
for r in rows(f"{t}/p/jr_{name}_s0h{h}.jsonl"):
k = f"{name}{r['id']}"; km[k] = {"src": name, "id": r["id"]}
bad = name == "fixbase" and r["id"] == "T00_s0"
res.append({"item": k, "errors": [{"severity": "major"}] if bad else [], "align": 2 if bad else 5})
for j in range(4):
km[f"h{j}"] = {"src": "human", "id": f"H{j}"}
res.append({"item": f"h{j}", "errors": [{"severity": "major"}] if (h == 1 and inst == "CLEAN" and j < 2) else [], "align": 5})
json.dump(km, open(f"{d}/keymap.json", "w")); json.dump(res, open(f"{d}/run1.json", "w"))
try:
score(f"{t}/o", "0-0", "FXT", f"fix21={t}/x", root_pools=f"{t}/runs"); raise AssertionError("void pool not caught")
except SystemExit as e:
assert e.code == 3
R = json.load(open(f"{t}/o/RESULT_FXT.json"))
assert R["void_pools"] == [f"{t}/runs/FXT_CLEAN01"], R["void_pools"]
c = R["fix21"]["clean"] # control clean: T01,T02,T04 (T00 judged bad, T03 form-fail); arm: T00,T02,T03,T04 (T01 form-fail)
assert R["fix21"]["judged-clean"]["a"] == 80 and R["fix21"]["judged-clean"]["b"] == 100 and R["fix21"]["form"]["b"] == 80
assert c["n"] == 5 and abs(c["a"] - 60) < 1e-9 and abs(c["b"] - 80) < 1e-9 and c["up"] == 2 and c["down"] == 1, c
assert decide({"diff": 6, "ci": (1, 11)}, {"diff": -2}) == "CONTINUE" and decide({"diff": 6, "ci": (1, 11)}, {"diff": -4}) == "GREY"
assert decide({"diff": 1.9, "ci": (-3, 7)}, {"diff": 0}) == "KILL" and decide({"diff": 3, "ci": (-3, 9)}, {"diff": 0}) == "GREY"
FB = fb0
print("fixon_raw: tests pass")
if __name__ == "__main__":
cmd, args = sys.argv[1], sys.argv[2:]
{"rawpools": rawpools, "score": score, "phonuse": phonuse, "test": _test}[cmd](*args)
|