File size: 11,757 Bytes
1272654 | 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 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | # SPDX-License-Identifier: BSD-3-Clause-Clear
# Copyright (c) 2026 Ronald Lee Hampton. All rights reserved.
# No express or implied licenses to any party's patent rights are granted by this license.
#
# read_shuffle_cost.py: re-measures the shuffle cost of every shipped brain and re-counts the switchboards.
#
# Usage (from the package folder):
# python -I reader/read_shuffle_cost.py --brains brains --data data --expect results/per_brain_record.csv
# Needs: Python 3, numpy, torch, safetensors. CPU only, one thread, about a minute for the 80 brains. No network.
# Side effects: none; it prints only (redirect the output to keep it).
#
# THE MEASURE. Each brain is run on the 256 held-out windows (66 tokens each: two context words, then 64 targets), 16,384 positions in
# one batch. The reasoning core's quantized output (qk, one 32-number vector per position) is then shuffled across positions with
# numpy default_rng(0).permutation(16384), everything else held fixed, and the network is run again. The SHUFFLE COST is the mean, over
# the positions whose target is not one of the 40 excluded ids, of (cross-entropy shuffled minus cross-entropy as trained), in nats.
# THE LINE (fixed before the run): a core is a SWITCHBOARD if its shuffle cost is below 0.025 nats, else a CONTEXT WORKER.
# A RESCUE is a seed whose plain (ST-0) core is a switchboard and whose sleep-form (S-SLEEP) core is a context worker; a HARM is the
# reverse. THE BAR (fixed before the run): S-SLEEP ends with fewer switchboards than ST-0, AND at least 5 seeds are discordant, AND the
# exact one-sided sign test on the discordant seeds gives p at or below 0.05.
#
# This file holds only the forward pass the measure needs (no training code). Tensor names keep the run's own prefixes:
# tiny.* the routed base network: two word embeddings summed, a router to one of 48 experts (top 1, its score nudged by the pull
# 0.4 x (mean size minus the expert's size), sizes in "y"), each expert a two-layer block, a layer norm and a shared head;
# rc.* the reasoning core: written from the integer part's hidden state, one gated pass, quantized to whole numbers in -127..127;
# cc.* the small integer part with ternary weights: reads the two context words' ids as 12 bits each, and writes a gain on the
# base network's expert output from its own hidden state and the core's quantized output (zero gain at the start).
# cc.Wv and cc.bv, a two-output head the measure does not use, were removed from the shipped files (results/head_removed_record.csv).
# Every operation below is written in the same order as the run's own code so that the numbers can match it bit for bit on the same
# software and hardware; on other hardware or torch versions the last digits may differ (the --expect check reports by how much).
import sys, os, csv, glob, math, hashlib, argparse
import numpy as np
import torch
import torch.nn.functional as F
V, D, T, CTX, NBITS = 4096, 32, 64, 2, 12
S_PULL, RHO = 0.4, 0.5
LINE = 0.025
FORMS = ("ST-0", "S-SLEEP")
P_BAR, MIN_DISC = 0.05, 5
BITW = 2 ** torch.arange(NBITS)
def sha256_file(p):
h = hashlib.sha256()
with open(p, "rb") as f:
for blk in iter(lambda: f.read(1 << 20), b""):
h.update(blk)
return h.hexdigest()
def sign_p(r, n):
"""exact one-sided p of at least r rescues among n discordant seeds, each a fair coin."""
return float(sum(math.comb(n, k) for k in range(r, n + 1)) / 2 ** n) if n else 1.0
def tern(w):
return (w > 0.5).float() - (w < -0.5).float()
def ste_tern(w): # the training-time expression, kept so the floating-point result is the same
return w + (tern(w) - w)
def ste_round(x):
return x + (torch.round(x) - x)
def ste_floor(x):
return x + (torch.floor(x) - x)
def rms(x, w):
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + 1e-6) * w
def msg(xs):
"""the two context words' token ids as 12 bits each, +1/-1, newest word first."""
parts = [((x[:, None] // BITW) % 2).float() * 2 - 1 for x in xs[:2]]
return torch.cat(parts, dim=-1)
def hidden(P, xs):
"""the integer part's hidden state: ternary weights, whole-number biases, clamps; every value a whole number."""
m = msg(xs)
a1 = m @ ste_tern(P["cc.W1"]).t() + ste_round(P["cc.b1"] * 1.0)
h1 = torch.clamp(a1, 0.0, 31.0)
a2 = h1 @ ste_tern(P["cc.W2"]).t() + ste_round(P["cc.b2"] * 16.0)
return torch.clamp(ste_floor(a2 / 4.0), 0.0, 127.0)
def pull_bias(P):
y = P["y"].numpy()
return torch.tensor(S_PULL * (y.mean() - y), dtype=torch.float32)
def forward(P, xs, bias, h2, perm=None):
"""logits at every position; with perm, the reasoning core's quantized output is shuffled across positions first."""
# the routed base network
x = F.embedding(xs[0], P["tiny.emb.0.weight"])
x = x + F.embedding(xs[1], P["tiny.emb.1.weight"])
r = F.linear(x, P["tiny.router.weight"], P["tiny.router.bias"])
score = r + bias
p = F.softmax(r, dim=-1)
sel = torch.argmax(score, dim=-1)
g = p.gather(1, sel[:, None]).squeeze(1)
h = F.gelu(torch.bmm(x[:, None, :], P["tiny.w1"][sel])).squeeze(1)
y = torch.bmm(h[:, None, :], P["tiny.w2"][sel]).squeeze(1)
contrib = g[:, None] * y
# the reasoning core, written from the integer part's hidden state
K0 = (1 - RHO) * torch.tanh(rms(h2 / 127.0, P["rc.nwc.weight"]) @ P["rc.wkc"])
z = rms(K0, P["rc.ns.weight"])
u = F.silu(z @ P["rc.w1"]) * (z @ P["rc.w3"])
K2 = RHO * K0 + (1 - RHO) * torch.tanh(u @ P["rc.wh"])
qk = torch.clamp(ste_round(K2 * 127.0), -127.0, 127.0)
if perm is not None:
qk = qk[perm] # THE SHUFFLE: the core's output moved across positions, nothing else changed
# the integer part's write: a gain on the expert output
c = h2 @ ste_tern(P["cc.Wcg"]).t()
c = c + qk @ ste_tern(P["cc.Wck"]).t()
gain = 2.0 * torch.sigmoid(c / 256.0) - 1.0
o = x + contrib * (1.0 + gain)
o = F.layer_norm(o, (D,), P["tiny.norm.weight"], P["tiny.norm.bias"], 1e-5)
return F.linear(o, P["tiny.head.weight"], P["tiny.head.bias"])
def heldout(data_dir):
win = np.load(os.path.join(data_dir, "heldout_windows_uint16.npy"), allow_pickle=False).astype(np.int64)
if win.shape != (256, T + CTX):
raise SystemExit("REFUSED: held-out windows have shape %s" % (win.shape,))
xs = [torch.from_numpy(np.ascontiguousarray(win[:, CTX - 1 - k:T + CTX - 1 - k])).reshape(-1) for k in range(CTX)]
tgt = torch.from_numpy(np.ascontiguousarray(win[:, CTX:])).reshape(-1)
ids = [int(l) for l in open(os.path.join(data_dir, "excluded_target_ids.txt"), encoding="utf-8") if l.strip() and not l.startswith("#")]
excl = torch.zeros(V, dtype=torch.bool)
excl[torch.tensor(ids)] = True
allowed = (~excl[tgt]).numpy()
return win, xs, tgt, allowed, ids
def main(a):
from safetensors.torch import load_file
torch.set_num_threads(1)
win, xs, tgt, allowed, ids = heldout(a.data)
perm = torch.from_numpy(np.random.default_rng(0).permutation(len(tgt)))
print("held-out windows %d, positions %d, scored positions %d, excluded ids %d" % (win.shape[0], len(tgt), int(allowed.sum()), len(ids)))
print("torch %s, numpy %s, threads %d" % (torch.__version__, np.__version__, torch.get_num_threads()))
paths = sorted(glob.glob(os.path.join(a.brains, "*.safetensors")))
per = {}
for path in paths:
name = os.path.basename(path)[:-len(".safetensors")]
form, s = name.split("_")[0], int(name.split("_")[1][1:])
P = load_file(path)
with torch.no_grad():
bias = pull_bias(P)
h2 = hidden(P, xs)
lg_on = forward(P, xs, bias, h2)
lg_sh = forward(P, xs, bias, h2, perm)
ce_on = F.cross_entropy(lg_on, tgt, reduction="none").double().numpy()
ce_sh = F.cross_entropy(lg_sh, tgt, reduction="none").double().numpy()
cost = float((ce_sh - ce_on)[allowed].mean())
per[(form, s)] = {"name": name, "cost": cost, "label": "SWITCHBOARD" if cost < LINE else "CONTEXT WORKER",
"ce_on": float(ce_on[allowed].mean()),
"logits_sha256": hashlib.sha256(lg_on.contiguous().numpy().tobytes()).hexdigest(),
"file_sha256": sha256_file(path)}
print("%-8s s%d shuffle cost %.6f %-14s (held-out loss as trained %.6f)" % (form, s, cost, per[(form, s)]["label"], per[(form, s)]["ce_on"]))
seeds = sorted({s for (_f, s) in per})
if not seeds or not all((f, s) in per for f in FORMS for s in seeds):
print("REFUSED: no brains, or a seed is missing one of its two brains")
return 2
sb = {f: [s for s in seeds if per[(f, s)]["label"] == "SWITCHBOARD"] for f in FORMS}
res = [s for s in seeds if per[("ST-0", s)]["label"] == "SWITCHBOARD" and per[("S-SLEEP", s)]["label"] == "CONTEXT WORKER"]
harm = [s for s in seeds if per[("ST-0", s)]["label"] == "CONTEXT WORKER" and per[("S-SLEEP", s)]["label"] == "SWITCHBOARD"]
n = len(res) + len(harm)
p = sign_p(len(res), n)
passed = len(sb["S-SLEEP"]) < len(sb["ST-0"]) and n >= MIN_DISC and p <= P_BAR
print("")
print("seeds %d (%d to %d); THE LINE: switchboard below %.3f nats of shuffle cost" % (len(seeds), seeds[0], seeds[-1], LINE))
for f in FORMS:
print(" %-8s switchboards %d of %d: %s" % (f, len(sb[f]), len(seeds), sb[f]))
print(" rescues %d %s; harms %d %s; discordant %d; exact one-sided sign test p %.5f" % (len(res), res, len(harm), harm, n, p))
print("THE BAR (S-SLEEP fewer, at least %d discordant, p <= %.2f): %s" % (MIN_DISC, P_BAR, "CONFIRMED" if passed else "NOT CONFIRMED"))
near = sorted((abs(v["cost"] - LINE), "%s s%d" % k, round(v["cost"], 6)) for k, v in per.items())[:3]
print(" nearest the line: %s" % [(k, c) for _d, k, c in near])
rc = 0
if a.expect:
rows = {(r["form"], int(r["seed"])): r for r in csv.DictReader(open(a.expect, encoding="utf-8", newline=""))}
keys = [k for k in per if k in rows]
lab_eq = sum(1 for k in keys if rows[k]["label"] == per[k]["label"])
file_eq = sum(1 for k in keys if rows[k]["safetensors_sha256"] == per[k]["file_sha256"])
cost_eq = sum(1 for k in keys if float(rows[k]["shuffle_cost"]) == per[k]["cost"])
ce_eq = sum(1 for k in keys if float(rows[k]["ce_on_allowed_mean"]) == per[k]["ce_on"])
lg_eq = sum(1 for k in keys if rows[k]["logits_sha256_on"] == per[k]["logits_sha256"])
dmax = max(abs(float(rows[k]["shuffle_cost"]) - per[k]["cost"]) for k in keys) if keys else float("nan")
cmax = max(abs(float(rows[k]["ce_on_allowed_mean"]) - per[k]["ce_on"]) for k in keys) if keys else float("nan")
print("")
print("AGAINST %s (%d rows):" % (os.path.basename(a.expect), len(rows)))
print(" brain files equal to the record's sha256: %d of %d" % (file_eq, len(per)))
print(" labels equal: %d of %d" % (lab_eq, len(per)))
print(" shuffle costs equal bit for bit: %d of %d; largest absolute difference %.3g nats" % (cost_eq, len(per), dmax))
print(" held-out loss as trained equal bit for bit: %d of %d; largest absolute difference %.3g nats" % (ce_eq, len(per), cmax))
print(" logits as trained equal bit for bit (sha256): %d of %d" % (lg_eq, len(per)))
if lab_eq != len(per) or file_eq != len(per) or len(rows) != len(per):
rc = 1
return rc
if __name__ == "__main__":
ap = argparse.ArgumentParser()
ap.add_argument("--brains", default="brains")
ap.add_argument("--data", default="data")
ap.add_argument("--expect", default="")
sys.exit(main(ap.parse_args()))
|