fix: audioop compat for Python 3.13, plain-language UI
Browse files- app.py +76 -73
- requirements.txt +2 -0
app.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
import torch
|
| 3 |
-
import sys, os,
|
| 4 |
|
| 5 |
sys.path.insert(0, "src")
|
| 6 |
|
| 7 |
from mulattice.protocol import (
|
| 8 |
-
SynthCfg, build_model, force_fast_scan, logits_at, synth_batch
|
| 9 |
)
|
| 10 |
|
| 11 |
-
DEVICE = "cpu"
|
| 12 |
CFG = SynthCfg()
|
| 13 |
|
| 14 |
print("Loading model...", flush=True)
|
|
@@ -21,9 +21,7 @@ force_fast_scan(MODEL)
|
|
| 21 |
print("Model loaded.", flush=True)
|
| 22 |
|
| 23 |
|
| 24 |
-
# --- Section 1: Your own stream ---
|
| 25 |
def run_custom(text, *pair_inputs):
|
| 26 |
-
"""Store user's facts in user's text, overwrite some, query all."""
|
| 27 |
seed = int(time.time()) % 100000
|
| 28 |
pairs = []
|
| 29 |
for i in range(0, len(pair_inputs), 3):
|
|
@@ -32,7 +30,7 @@ def run_custom(text, *pair_inputs):
|
|
| 32 |
pairs.append({"name": name.strip(), "value": val.strip(),
|
| 33 |
"new_value": new_val.strip() if new_val.strip() else None})
|
| 34 |
if not pairs:
|
| 35 |
-
return "Enter at least one fact (name and value).", ""
|
| 36 |
|
| 37 |
used = set()
|
| 38 |
def h(s, span, off):
|
|
@@ -104,111 +102,116 @@ def run_custom(text, *pair_inputs):
|
|
| 104 |
|
| 105 |
inv = {v: k for k, v in vmap.items()}
|
| 106 |
correct = 0
|
| 107 |
-
lines = ["| Asked | Answered | Expected |
|
| 108 |
for q, r in enumerate(store_rows):
|
| 109 |
-
got = inv.get(int(pred[0, q]),
|
| 110 |
exp = r["new_value"] if r["new_value"] else r["value"]
|
| 111 |
ok = got == exp
|
| 112 |
correct += ok
|
| 113 |
-
old = f" (
|
| 114 |
-
lines.append(f"| {r['name']} | **{got}** | {exp}{old} | {'✅' if ok else '❌'} |")
|
| 115 |
|
| 116 |
-
stats = f"\n
|
| 117 |
return "\n".join(lines), stats
|
| 118 |
|
| 119 |
|
| 120 |
-
|
| 121 |
-
def run_scale(n_pairs, filler, overwrite):
|
| 122 |
g = torch.Generator().manual_seed(42)
|
| 123 |
b = synth_batch(1, int(n_pairs), min(32, int(n_pairs)), CFG,
|
| 124 |
-
filler=int(
|
| 125 |
t0 = time.time()
|
| 126 |
with torch.no_grad():
|
| 127 |
pred = logits_at(MODEL, b["tokens"], b["ans_pos"]).argmax(-1).cpu()
|
| 128 |
dt = time.time() - t0
|
| 129 |
ok = (pred == b["gold"]).squeeze(0)
|
| 130 |
n_cells = MODEL.blocks[0].mixer.scan.last_n_cells
|
| 131 |
-
return (f"
|
| 132 |
-
f"
|
| 133 |
-
f"{dt/b['L']*1e6:.0f}μs
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
with gr.Blocks(
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
gr.Markdown("""
|
| 140 |
# μLattice
|
| 141 |
-
##
|
| 142 |
-
|
| 143 |
**Tensorbend Research · Eric E. · October 2026**
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
Access cost doesn't.
|
| 151 |
""")
|
| 152 |
|
| 153 |
-
with gr.Tab("
|
| 154 |
gr.Markdown("""
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
|
|
|
| 158 |
""")
|
| 159 |
with gr.Row():
|
| 160 |
-
with gr.Column():
|
| 161 |
-
doc = gr.Textbox(
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
| 163 |
with gr.Row():
|
| 164 |
-
n1 = gr.Textbox(label="
|
| 165 |
-
v1 = gr.Textbox(label="Value", value="swordfish")
|
| 166 |
-
nv1 = gr.Textbox(label="
|
| 167 |
with gr.Row():
|
| 168 |
-
n2 = gr.Textbox(label="
|
| 169 |
-
v2 = gr.Textbox(label="Value", value="3pm")
|
| 170 |
-
nv2 = gr.Textbox(label="
|
| 171 |
with gr.Row():
|
| 172 |
-
n3 = gr.Textbox(label="
|
| 173 |
-
v3 = gr.Textbox(label="Value", value="")
|
| 174 |
-
nv3 = gr.Textbox(label="
|
| 175 |
-
btn1 = gr.Button("
|
| 176 |
-
with gr.Column():
|
| 177 |
out1 = gr.Markdown()
|
| 178 |
stats1 = gr.Markdown()
|
| 179 |
btn1.click(run_custom, inputs=[doc, n1,v1,nv1, n2,v2,nv2, n3,v3,nv3], outputs=[out1, stats1])
|
| 180 |
|
| 181 |
-
with gr.Tab("
|
| 182 |
gr.Markdown("""
|
| 183 |
-
Test the model at scale
|
| 184 |
-
|
| 185 |
""")
|
| 186 |
with gr.Row():
|
| 187 |
-
np_slider = gr.Slider(64,
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
|
|
|
|
|
|
|
|
|
| 191 |
out2 = gr.Markdown()
|
| 192 |
-
btn2.click(run_scale, inputs=[np_slider,
|
| 193 |
|
| 194 |
gr.Markdown("""
|
| 195 |
---
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
[
|
| 201 |
-
[Code](https://github.com/eelbaz/mLattice) ·
|
| 202 |
-
[
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
@misc{mulattice2026,
|
| 206 |
-
title = {μLattice: Breaking the Quadratic Memory Barrier in AI},
|
| 207 |
-
author = {Eric E. and Tensorbend Research},
|
| 208 |
-
year = {2026},
|
| 209 |
-
url = {github.com/eelbaz/mLattice}
|
| 210 |
-
}
|
| 211 |
-
```
|
| 212 |
""")
|
| 213 |
|
| 214 |
demo.launch()
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import torch
|
| 3 |
+
import sys, os, time, hashlib
|
| 4 |
|
| 5 |
sys.path.insert(0, "src")
|
| 6 |
|
| 7 |
from mulattice.protocol import (
|
| 8 |
+
SynthCfg, build_model, force_fast_scan, logits_at, synth_batch
|
| 9 |
)
|
| 10 |
|
| 11 |
+
DEVICE = "cpu"
|
| 12 |
CFG = SynthCfg()
|
| 13 |
|
| 14 |
print("Loading model...", flush=True)
|
|
|
|
| 21 |
print("Model loaded.", flush=True)
|
| 22 |
|
| 23 |
|
|
|
|
| 24 |
def run_custom(text, *pair_inputs):
|
|
|
|
| 25 |
seed = int(time.time()) % 100000
|
| 26 |
pairs = []
|
| 27 |
for i in range(0, len(pair_inputs), 3):
|
|
|
|
| 30 |
pairs.append({"name": name.strip(), "value": val.strip(),
|
| 31 |
"new_value": new_val.strip() if new_val.strip() else None})
|
| 32 |
if not pairs:
|
| 33 |
+
return "Enter at least one fact (a name and a value).", ""
|
| 34 |
|
| 35 |
used = set()
|
| 36 |
def h(s, span, off):
|
|
|
|
| 102 |
|
| 103 |
inv = {v: k for k, v in vmap.items()}
|
| 104 |
correct = 0
|
| 105 |
+
lines = ["| Asked | Answered | Expected | |", "|---|---|---|---|"]
|
| 106 |
for q, r in enumerate(store_rows):
|
| 107 |
+
got = inv.get(int(pred[0, q]), "?")
|
| 108 |
exp = r["new_value"] if r["new_value"] else r["value"]
|
| 109 |
ok = got == exp
|
| 110 |
correct += ok
|
| 111 |
+
old = f" *(changed from: {r['value']})*" if r["new_value"] else ""
|
| 112 |
+
lines.append(f"| **{r['name']}** | **{got}** | {exp}{old} | {'✅' if ok else '❌'} |")
|
| 113 |
|
| 114 |
+
stats = f"\n**{correct} out of {len(store_rows)} correct** — the model read {L:,} tokens in {dt:.2f} seconds ({dt/L*1e6:.0f} millionths of a second per token)"
|
| 115 |
return "\n".join(lines), stats
|
| 116 |
|
| 117 |
|
| 118 |
+
def run_scale(n_pairs, gap_words, pct_changed):
|
|
|
|
| 119 |
g = torch.Generator().manual_seed(42)
|
| 120 |
b = synth_batch(1, int(n_pairs), min(32, int(n_pairs)), CFG,
|
| 121 |
+
filler=int(gap_words), overwrite_frac=float(pct_changed)/100, generator=g)
|
| 122 |
t0 = time.time()
|
| 123 |
with torch.no_grad():
|
| 124 |
pred = logits_at(MODEL, b["tokens"], b["ans_pos"]).argmax(-1).cpu()
|
| 125 |
dt = time.time() - t0
|
| 126 |
ok = (pred == b["gold"]).squeeze(0)
|
| 127 |
n_cells = MODEL.blocks[0].mixer.scan.last_n_cells
|
| 128 |
+
return (f"### **{ok.float().mean():.1%} correct** ({int(ok.sum())} out of {ok.numel()})\n\n"
|
| 129 |
+
f"The model read **{b['L']:,} words** in one pass and stored **{n_cells:,} memory slots**.\n\n"
|
| 130 |
+
f"Time: {dt:.2f} seconds — that's **{dt/b['L']*1e6:.0f}μs per word**, "
|
| 131 |
+
f"whether it stored {int(n_pairs)} facts or 8,000.")
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
with gr.Blocks(
|
| 135 |
+
title="μLattice — AI Memory That Grows Without the Cost Growing",
|
| 136 |
+
theme=gr.themes.Soft(font=["Inter", "sans-serif"]),
|
| 137 |
+
css="""
|
| 138 |
+
.gradio-container {max-width: 900px !important; margin: auto !important;}
|
| 139 |
+
h1 {font-size: 2.2em !important; font-weight: 300 !important;}
|
| 140 |
+
h2 {color: #2547d0 !important; font-weight: 400 !important;}
|
| 141 |
+
"""
|
| 142 |
+
) as demo:
|
| 143 |
gr.Markdown("""
|
| 144 |
# μLattice
|
| 145 |
+
## AI memory that grows without the cost growing
|
| 146 |
+
|
| 147 |
**Tensorbend Research · Eric E. · October 2026**
|
| 148 |
+
|
| 149 |
+
Today's AI models pay more and more to remember more. μLattice fixes this:
|
| 150 |
+
memory grows as the model reads, but the cost of processing each word stays the same.
|
| 151 |
+
|
| 152 |
+
Every word the model reads touches exactly **9 memory slots** — whether the model
|
| 153 |
+
is storing 10 facts or 8,000. Try it below with your own facts.
|
|
|
|
| 154 |
""")
|
| 155 |
|
| 156 |
+
with gr.Tab("✏️ Try It With Your Own Facts"):
|
| 157 |
gr.Markdown("""
|
| 158 |
+
Type any text below — it becomes the words the model reads.
|
| 159 |
+
Then define facts as **name → value** pairs. If you fill in the
|
| 160 |
+
**"changed to"** field, the fact gets replaced mid-stream and the model
|
| 161 |
+
must remember the **new** value.
|
| 162 |
""")
|
| 163 |
with gr.Row():
|
| 164 |
+
with gr.Column(scale=1):
|
| 165 |
+
doc = gr.Textbox(
|
| 166 |
+
label="Any text (the model reads this)",
|
| 167 |
+
lines=5,
|
| 168 |
+
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.")
|
| 169 |
+
gr.Markdown("**Your facts**")
|
| 170 |
with gr.Row():
|
| 171 |
+
n1 = gr.Textbox(label="Name", value="the vault password", scale=1)
|
| 172 |
+
v1 = gr.Textbox(label="Value", value="swordfish", scale=1)
|
| 173 |
+
nv1 = gr.Textbox(label="Changed to (optional)", value="bluedragon", scale=1)
|
| 174 |
with gr.Row():
|
| 175 |
+
n2 = gr.Textbox(label="Name", value="the meeting time", scale=1)
|
| 176 |
+
v2 = gr.Textbox(label="Value", value="3pm", scale=1)
|
| 177 |
+
nv2 = gr.Textbox(label="Changed to (optional)", value="", scale=1)
|
| 178 |
with gr.Row():
|
| 179 |
+
n3 = gr.Textbox(label="Name", value="", scale=1)
|
| 180 |
+
v3 = gr.Textbox(label="Value", value="", scale=1)
|
| 181 |
+
nv3 = gr.Textbox(label="Changed to (optional)", value="", scale=1)
|
| 182 |
+
btn1 = gr.Button("Store, overwrite, and recall", variant="primary", size="lg")
|
| 183 |
+
with gr.Column(scale=1):
|
| 184 |
out1 = gr.Markdown()
|
| 185 |
stats1 = gr.Markdown()
|
| 186 |
btn1.click(run_custom, inputs=[doc, n1,v1,nv1, n2,v2,nv2, n3,v3,nv3], outputs=[out1, stats1])
|
| 187 |
|
| 188 |
+
with gr.Tab("🔬 Stress Test"):
|
| 189 |
gr.Markdown("""
|
| 190 |
+
Test the model at scale. Store hundreds or thousands of facts in one pass,
|
| 191 |
+
change some of them mid-stream, then ask for them all back.
|
| 192 |
""")
|
| 193 |
with gr.Row():
|
| 194 |
+
np_slider = gr.Slider(64, 2048, value=256, step=64,
|
| 195 |
+
label="How many facts to store")
|
| 196 |
+
gap_slider = gr.Slider(0, 14, value=8, step=1,
|
| 197 |
+
label="Words between each fact")
|
| 198 |
+
ow_slider = gr.Slider(0, 100, value=50, step=10,
|
| 199 |
+
label="% of facts changed mid-stream")
|
| 200 |
+
btn2 = gr.Button("Run the stress test", variant="primary", size="lg")
|
| 201 |
out2 = gr.Markdown()
|
| 202 |
+
btn2.click(run_scale, inputs=[np_slider, gap_slider, ow_slider], outputs=[out2])
|
| 203 |
|
| 204 |
gr.Markdown("""
|
| 205 |
---
|
| 206 |
+
|
| 207 |
+
Every answer above is produced by a **live forward pass** of the trained model.
|
| 208 |
+
Nothing is precomputed or cached.
|
| 209 |
+
|
| 210 |
+
[📄 Full paper](https://github.com/eelbaz/mLattice/blob/main/PAPER.md) ·
|
| 211 |
+
[💻 Code & tests](https://github.com/eelbaz/mLattice) ·
|
| 212 |
+
[📊 All results](https://github.com/eelbaz/mLattice/tree/main/results)
|
| 213 |
+
|
| 214 |
+
*Tensorbend Research · Eric E. · October 2026*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
""")
|
| 216 |
|
| 217 |
demo.launch()
|
requirements.txt
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
|
|
|
|
|
| 1 |
torch>=2.4
|
| 2 |
numpy
|
|
|
|
| 1 |
+
audioop-lts
|
| 2 |
+
pydub
|
| 3 |
torch>=2.4
|
| 4 |
numpy
|