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 = '
Asked
Answered
Expected
'
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' was {r["value"]}' if r["new_value"] else ""
cls = "y" if ok else "n"
icon = "✅" if ok else "❌"
rows_html += f'
{r["name"]}
{got}
{exp}{old}
{icon}
'
rows_html += '
'
stats_html = ('
'
f'
{correct}/{len(store_rows)}correct
'
f'
{L:,}words read
'
f'
{dt:.1f}stime
'
f'
{cells:,}memory slots
'
'
')
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'''
{acc:.1%}
{int(ok.sum())} of {ok.numel()} recalled correctly
'''
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"""
Tensorbend Research · Eric E. · Live proof · Real weights
μLattice — the growing memory, running
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.
Paste any text. Define facts as name → value pairs.
Mark any to be changed mid-stream. The model reads your words,
stores the facts, and must return the latest value.
""")
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"""
The model reads your words as the stream, stores your facts as it
passes them, replaces marked ones, then answers from memory. Uses the
forgetting checkpoint (best at overwrite tasks).
""")
# ── Section 02 ──
gr.HTML(f"""
02
The store at scale
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.
""")
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"""
Uses the forgetting checkpoint. On GPU this scales to
8,192 facts in one 133K-token pass. On free-tier CPU, capped at 512.