File size: 10,722 Bytes
20a3a74 5a8cdaf 20a3a74 9b78a90 20a3a74 5a8cdaf 20a3a74 43b947d 9b78a90 43b947d 20a3a74 43b947d 20a3a74 5a8cdaf 43b947d 20a3a74 43b947d 20a3a74 43b947d 20a3a74 9b78a90 20a3a74 43b947d 20a3a74 43b947d 20a3a74 9b78a90 20a3a74 43b947d 20a3a74 43b947d 20a3a74 43b947d 20a3a74 43b947d 20a3a74 43b947d 20a3a74 43b947d 20a3a74 43b947d 20a3a74 43b947d 20a3a74 43b947d 20a3a74 5a8cdaf 43b947d 20a3a74 43b947d 20a3a74 43b947d 20a3a74 43b947d 20a3a74 43b947d 9b78a90 43b947d 9b78a90 43b947d 9b78a90 43b947d 20a3a74 43b947d 9b78a90 43b947d 9b78a90 43b947d 9b78a90 43b947d 9b78a90 43b947d 9b78a90 43b947d 9b78a90 43b947d 9b78a90 43b947d 9b78a90 43b947d 9b78a90 43b947d 9b78a90 43b947d 20a3a74 43b947d 20a3a74 | 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 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 | import gradio as gr
import torch
import sys, os, time, hashlib
sys.path.insert(0, "src")
from mulattice.protocol import SynthCfg, build_model, force_fast_scan, logits_at, synth_batch
DEVICE = "cpu"
CFG = SynthCfg()
print("Loading models...", flush=True)
MODELS = {}
for name in ["capacity8k", "forgetting", "realtext"]:
path = f"checkpoints/{name}.pt"
if os.path.exists(path):
ck = torch.load(path, map_location="cpu", weights_only=False)
c = SynthCfg()
if ck.get("proto_cfg", {}).get("addr_scale"):
c.addr_scale = float(ck["proto_cfg"]["addr_scale"])
m = build_model(c)
m.load_state_dict(ck["model"])
m = m.to(DEVICE).eval()
force_fast_scan(m)
MODELS[name] = m
print(f" {name}.pt loaded", flush=True)
print("All models ready", flush=True)
INK = "#1c1e26"; MUT = "#6b7280"; HAIR = "#e5e7eb"; BG = "#fcfcfa"
ACC = "#2547d0"; OK = "#0e8a5f"; BAD = "#c2413b"; SOFT = "#f4f4f0"; UPD = "#b07807"
with open("demo.css") as f:
DEMO_CSS = f.read()
def run_custom(text, n1, v1, nv1, n2, v2, nv2):
m = MODELS.get("forgetting", MODELS.get("capacity8k"))
seed = int(time.time()) % 100000
pairs = []
for n, v, nv in [(n1, v1, nv1), (n2, v2, nv2)]:
if n.strip() and v.strip():
pairs.append({"name": n.strip(), "value": v.strip(),
"new_value": nv.strip() if nv.strip() else None})
if not pairs:
return "Enter at least one fact (a name and a value).", ""
used = set()
def h(s, span, off):
x = int(hashlib.md5(f"{seed}:{s}".encode()).hexdigest(), 16) % span
while (off + x) in used:
x = (x + 1) % span
used.add(off + x)
return off + x
kmap, vmap = {}, {}
for p in pairs:
kmap[p["name"]] = h("k" + p["name"], CFG.NK, 0)
for p in pairs:
vmap[p["value"]] = h("v" + p["value"], CFG.NV, CFG.NK)
if p["new_value"]:
vmap[p["new_value"]] = h("v2" + p["new_value"], CFG.NV, CFG.NK)
words = [w for w in text.split() if w.strip()][:500] or ["..."]
fid = {w: h("f" + w, CFG.NK, 0) for w in set(words)}
fill = [fid[w] for w in words]
store_rows, upd_rows = [], []
for p in pairs:
vid = vmap[p["value"]]
row = dict(p); row["kid"] = kmap[p["name"]]; row["vid"] = vid
if p["new_value"]:
row["nid"] = vmap[p["new_value"]]
upd_rows.append(row)
store_rows.append(row)
F = 6; block = F + 2
n_upd = len(upd_rows); Q = len(store_rows)
L = (len(store_rows) + n_upd) * block + 2 * Q
toks = torch.zeros(1, L, dtype=torch.long)
def take_f(n):
out = []
while len(out) < n:
i = int(torch.randint(0, len(fill), (1,)))
out.extend(fill[i:i+n-len(out)])
return out[:n]
for i, r in enumerate(store_rows):
s = i * block
toks[0, s:s+F] = torch.tensor(take_f(F))
toks[0, s+F], toks[0, s+F+1] = r["kid"], r["vid"]
for j, r in enumerate(upd_rows):
s = (len(store_rows) + j) * block
toks[0, s:s+F] = torch.tensor(take_f(F))
toks[0, s+F], toks[0, s+F+1] = r["kid"], r["nid"]
S2 = (len(store_rows) + n_upd) * block
ans_l = []
for q, r in enumerate(store_rows):
toks[0, S2+2*q] = r["kid"]
toks[0, S2+2*q+1] = CFG.SEP
ans_l.append(S2 + 2*q + 1)
t0 = time.time()
with torch.no_grad():
pred = logits_at(m, toks, torch.tensor([ans_l])).argmax(-1).cpu()
dt = time.time() - t0
cells = m.blocks[0].mixer.scan.last_n_cells
inv = {v: k for k, v in vmap.items()}
correct = 0
rows_html = '<table><tr><th>Asked</th><th>Answered</th><th>Expected</th><th></th></tr>'
for q, r in enumerate(store_rows):
got = inv.get(int(pred[0, q]), "?")
exp = r["new_value"] or r["value"]
ok = got == exp
correct += ok
old = f' <span class="pill">was {r["value"]}</span>' if r["new_value"] else ""
cls = "y" if ok else "n"
icon = "✅" if ok else "❌"
rows_html += f'<tr><td><b>{r["name"]}</b></td><td class="{cls}">{got}</td><td>{exp}{old}</td><td>{icon}</td></tr>'
rows_html += '</table>'
stats_html = ('<div class="stats" style="display:grid">'
f'<div class="stat"><b>{correct}/{len(store_rows)}</b><span>correct</span></div>'
f'<div class="stat"><b>{L:,}</b><span>words read</span></div>'
f'<div class="stat"><b>{dt:.1f}s</b><span>time</span></div>'
f'<div class="stat"><b>{cells:,}</b><span>memory slots</span></div>'
'</div>')
return rows_html, stats_html
def run_scale(n_pairs, gap, pct_changed):
m = MODELS.get("forgetting", MODELS.get("capacity8k"))
g = torch.Generator().manual_seed(42)
n = int(n_pairs)
b = synth_batch(1, n, min(32, n), CFG,
filler=int(gap), overwrite_frac=float(pct_changed)/100, generator=g)
t0 = time.time()
with torch.no_grad():
pred = logits_at(m, b["tokens"], b["ans_pos"]).argmax(-1).cpu()
dt = time.time() - t0
ok = (pred == b["gold"]).squeeze(0)
up = b["was_updated"].squeeze(0)
cells = m.blocks[0].mixer.scan.last_n_cells
acc = ok.float().mean().item()
acc_up = ok[up].float().mean().item() if bool(up.any()) else 1.0
acc_keep = ok[~up].float().mean().item() if bool((~up).any()) else 1.0
result_html = f'''<div style="text-align:center;padding:28px 0">
<div style="font-size:48px;font-weight:600;color:{OK};margin-bottom:8px">{acc:.1%}</div>
<div style="font-size:15px;color:{MUT};margin:6px 0 16px">{int(ok.sum())} of {ok.numel()} recalled correctly</div>
<div style="font-size:14px;margin:4px 0">Changed facts: <b style="color:{UPD}">{acc_up:.1%}</b> · Unchanged: <b style="color:{OK}">{acc_keep:.1%}</b></div>
</div>
<div class="stats" style="display:grid">
<div class="stat"><b>{b["L"]:,}</b><span>words read</span></div>
<div class="stat"><b>{cells:,}</b><span>memory slots</span></div>
<div class="stat"><b>{dt:.1f}s</b><span>time</span></div>
</div>'''
return result_html
with gr.Blocks(title="μLattice — AI Memory That Grows Without the Cost Growing",
css=DEMO_CSS + f"""
.gradio-container {{ max-width: 980px !important; margin: auto !important;
background: {BG} !important; padding: 0 24px 60px !important;
font-family: -apple-system, BlinkMacSystemFont, Inter, sans-serif !important; }}
footer {{ display: none !important; }}
""") as demo:
gr.HTML(f"""
<header>
<div class="kicker">Tensorbend Research · Eric E. · Live proof · Real weights</div>
<h1 class="serif">μLattice — the growing memory, running</h1>
<p class="dek serif">A model that reads text and keeps every fact in it — stores
associations, replaces them on sight, and answers from memory. Every word costs the
same, whether the model holds ten facts or eight thousand.</p>
<div class="meta">3 trained models loaded: capacity8k · forgetting · realtext · 4.2M parameters</div>
</header>
<main>
""")
# ── Section 01 ──
gr.HTML("""
<section>
<div class="sn"><span class="n">01</span><h2 class="serif">Try it with your own facts</h2></div>
<p class="lead">Paste any text. Define facts as <em>name → value</em> pairs.
Mark any to be <strong>changed mid-stream</strong>. The model reads your words,
stores the facts, and must return the <em>latest</em> value.</p>
<div class="fig">
""")
with gr.Row():
with gr.Column(scale=1):
doc = gr.Textbox(label="Your text (the model reads this)", lines=5,
value="The harbour was quiet that morning. Gulls circled the masts while the tide went out. A clerk stamped papers without looking up. Somewhere a bell rang twice, then stopped. The road east had been washed out again. Dust settled over the market stalls.")
with gr.Column(scale=1):
n1 = gr.Textbox(label="Name", value="the vault password")
v1 = gr.Textbox(label="Value", value="swordfish")
nv1 = gr.Textbox(label="Changed to (optional)", value="bluedragon")
n2 = gr.Textbox(label="Name", value="the meeting time")
v2 = gr.Textbox(label="Value", value="3pm")
nv2 = gr.Textbox(label="Changed to (optional)", value="")
btn1 = gr.Button("Store, overwrite, and answer", variant="primary")
out1 = gr.HTML()
stats1 = gr.HTML()
gr.HTML(f"""
<div class="figcap">The model reads your words as the stream, stores your facts as it
passes them, replaces marked ones, then answers from memory. Uses the
<b>forgetting</b> checkpoint (best at overwrite tasks).</div>
</div>
</section>
""")
# ── Section 02 ──
gr.HTML(f"""
<section>
<div class="sn"><span class="n">02</span><h2 class="serif">The store at scale</h2></div>
<p class="lead">The same mechanism under load. Store facts, change some mid-stream,
then ask for them all back. The cost per word stays flat no matter how many are stored.</p>
<div class="fig">
""")
with gr.Row():
np_s = gr.Slider(64, 512, value=128, step=64, label="How many facts",
info="Capped at 512 for CPU (GPU handles 8,192+)")
gap_s = gr.Slider(0, 14, value=8, step=1, label="Words between each fact")
ow_s = gr.Slider(0, 100, value=50, step=10, label="% of facts changed mid-stream")
btn2 = gr.Button("Run the stress test", variant="primary")
out2 = gr.HTML()
gr.HTML(f"""
<div class="figcap">Uses the <b>forgetting</b> checkpoint. On GPU this scales to
8,192 facts in one 133K-token pass. On free-tier CPU, capped at 512.</div>
</div>
</section>
</main>
<footer style="max-width:980px;margin:60px auto 0;padding:24px 0;color:{MUT};font-size:13px;border-top:1px solid {HAIR};text-align:center">
<p>Every answer is produced by a <b>live forward pass</b> of the trained model.</p>
<p style="margin-top:12px"><a href="https://github.com/eelbaz/mLattice/blob/main/PAPER.md" style="color:{ACC};text-decoration:none">Full paper</a> ·
<a href="https://github.com/eelbaz/mLattice" style="color:{ACC};text-decoration:none">Code & tests</a></p>
<p style="margin-top:12px;font-size:12px">Tensorbend Research · Eric E. · October 2026</p>
</footer>
""")
btn1.click(run_custom, inputs=[doc, n1, v1, nv1, n2, v2, nv2], outputs=[out1, stats1])
btn2.click(run_scale, inputs=[np_s, gap_s, ow_s], outputs=[out2])
demo.launch()
|