atom-ai / docs /artifacts /mix_long_memory /probe_recall_alpha.py
thefinalboss's picture
ATOM (atom-ai): code snapshot (100fae0 + batched-train), README with measured numbers, curated checkpoints (part 3)
969c687 verified
Raw History Blame Contribute Delete
2.82 kB
"""Is the name still IN the field at the answer position? Frozen recall-bench checkpoint, eval mode,
stream of recall episodes (held-out filler half); at the first answer byte collect α (32x32 field) and
fit a ridge one-vs-rest linear probe (name, 16-way) on 1200 episodes, test on 600, per distance bucket.
Chance = 6.25 %. Separates 'memory retains the information' from 'readout learned to use it'.
usage: probe_recall_alpha.py OUT.json CKPT [CKPT ...]"""
import json, random, sys
from pathlib import Path
import numpy as np
import torch
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
import longmem_bench as LB # noqa: E402
sys.path.insert(0, str(LB.ROOT / "tools"))
from chat_atom_native import load_model # noqa: E402
@torch.no_grad()
def collect(m, eps):
m.eval(); m.reset_state(reset_atomizer=True); X, y, d = [], [], []; tick = 0
for text, ans, dist, name in eps:
pk = m.atomizer.encode_bytes(text, reset=False)
for i in range(len(pk)):
m.forward_packet(pk[i]); tick += 1
if tick % 64 == 0: LB.MB.flush(m)
if i + 1 == ans:
X.append(m.core.state.alpha.detach().reshape(-1).numpy().copy()); y.append(LB.NAMES.index(name)); d.append(dist)
return np.array(X), np.array(y), np.array(d)
def ridge_probe(Xtr, ytr, Xte, lam=1.0):
mu, sd = Xtr.mean(0), Xtr.std(0) + 1e-6
A, B = (Xtr - mu) / sd, (Xte - mu) / sd
Y = np.eye(16)[ytr]
W = np.linalg.solve(A.T @ A + lam * len(A) * np.eye(A.shape[1]), A.T @ Y)
return (B @ W).argmax(1)
def main():
torch.set_num_threads(1)
out, cks = sys.argv[1], sys.argv[2:]
src = LB.filler_source(); ev = src[len(src) // 2:]
rng = random.Random(4242)
eps = [LB.episode(rng, ev) for _ in range(1800)]
res = {}
for ck in cks:
if ck.startswith("fresh"): # untrained controls: "fresh" (λ .05) / "fresh_slow" (+ slow block .25)
import argparse
a = argparse.Namespace(seed=20260913, leak=0.05, slow_block=0.25 if ck == "fresh_slow" else 0.0,
slow_cap=0.02, slow_gain="ema", k=0, ladrop=0.5)
m = LB.build(a)
else:
m = load_model(ck)
X, y, d = collect(m, eps)
pred = ridge_probe(X[:1200], y[:1200], X[1200:], lam=1e-2)
yt, dt = y[1200:], d[1200:]
r = {"overall_acc": float((pred == yt).mean())}
for lo, hi, b in ((0, 32, "d<32"), (32, 64, "d32-63"), (64, 128, "d64-127"), (128, 999, "d128+")):
sel = (dt >= lo) & (dt < hi)
r[b] = {"n": int(sel.sum()), "acc": float((pred[sel] == yt[sel]).mean()) if sel.any() else None}
res[ck] = r; print(ck, json.dumps(r), flush=True)
Path(out).write_text(json.dumps(res, indent=1))
if __name__ == "__main__":
main()