File size: 9,949 Bytes
da49872
 
 
 
7a0131a
da49872
 
 
 
 
68f08c1
da49872
 
 
68f08c1
da49872
 
 
7a0131a
 
da49872
 
68f08c1
da49872
 
 
 
 
 
 
7a0131a
 
 
 
 
 
 
 
da49872
 
 
 
68f08c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7a0131a
68f08c1
 
 
 
 
 
 
 
 
 
da49872
 
7a0131a
 
 
 
 
 
 
 
 
 
 
 
 
da49872
68f08c1
 
 
 
da49872
 
 
 
 
 
 
 
7a0131a
 
da49872
7a0131a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
da49872
 
 
7a0131a
 
 
 
da49872
68f08c1
7a0131a
 
68f08c1
da49872
 
7a0131a
 
 
 
 
 
 
 
 
 
 
 
da49872
7a0131a
da49872
 
68f08c1
 
7a0131a
da49872
 
 
7a0131a
 
da49872
7a0131a
 
da49872
7a0131a
da49872
 
68f08c1
 
 
 
 
7a0131a
68f08c1
 
7a0131a
68f08c1
7a0131a
 
68f08c1
7a0131a
 
 
 
68f08c1
 
7a0131a
68f08c1
7a0131a
68f08c1
7a0131a
68f08c1
 
7a0131a
 
 
 
 
68f08c1
 
da49872
68f08c1
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
#!/usr/bin/env python3
"""
app.py — HuggingFace Spaces demo for Competitive Docking Memory (CDM) Language Model
DuoNeural — Archon + Jesse Caldwell + Aura — 2026
Gradio 5 compatible. Routing gate visualization (not Logit Lens).
"""

import json
import torch
import gradio as gr
from huggingface_hub import hf_hub_download

model = None
tokenizer = None
DEVICE = "cpu"
MODEL_REPO = "DuoNeural/CDM-V2-TinyStories-37M"

SLOT_COLORS = [
    "#FF6B6B","#4ECDC4","#45B7D1","#96CEB4","#FECA57","#FF9FF3","#54A0FF","#5F27CD",
    "#00D2D3","#FF9F43","#C8D6E5","#8395A7","#EE5A24","#009432","#C4E538","#A3CB38",
]

EXAMPLES = [
    "Once upon a time there was a little girl named Lily.",
    "Tom loved trains more than anything else in the world.",
    "The rabbit hopped through the sunny meadow looking for",
    "In a small village there lived a clever fox named",
    "She wanted to play outside but the weather was",
]

SLOT_ROLES = {
    11: "PUNCT",   # 0-indexed — slot 11 specializes on punctuation (step5000: 71%, step30k: 88%)
    9:  "AGENCY",  # character agency verbs
    0:  "IDENTITY",# character names / pronouns
    15: "VERBS",   # action verbs
    5:  "ARTICLES",# articles & modifiers
}


def load_model():
    global model, tokenizer
    if model is not None:
        return

    from transformers import GPT2TokenizerFast
    from cdm_model_v2 import CDMConfigV2, CDMLanguageModelV2

    tokenizer = GPT2TokenizerFast.from_pretrained("gpt2")
    tokenizer.pad_token = tokenizer.eos_token

    model_pt_path = hf_hub_download(repo_id=MODEL_REPO, filename="model.pt")
    config_path   = hf_hub_download(repo_id=MODEL_REPO, filename="config.json")

    with open(config_path) as f:
        cfg_dict = json.load(f)

    cfg = CDMConfigV2(
        vocab_size=cfg_dict.get("vocab_size", 50257),
        d_model=cfg_dict.get("d_model", 384),
        n_layers=cfg_dict.get("n_layers", 8),
        n_heads=cfg_dict.get("n_heads", 8),
        n_kv_heads=cfg_dict.get("n_kv_heads", 4),
        d_ff=cfg_dict.get("d_ff", 1024),
        K=cfg_dict.get("K", 16),
        max_len=cfg_dict.get("max_len", 512),
    )

    ckpt = torch.load(model_pt_path, map_location=DEVICE, weights_only=False)
    model = CDMLanguageModelV2(cfg)
    model.load_state_dict(ckpt.get("model_state", ckpt))
    model = model.to(DEVICE)
    model.eval()


print("Loading CDM model on startup...")
try:
    load_model()
    print("Model loaded OK")
except Exception as e:
    print(f"Startup load failed (will retry on first request): {e}")


def gate_to_css_color(base_hex: str, intensity: float) -> str:
    """Blend base color with dark background by intensity (0-1)."""
    r = int(base_hex[1:3], 16)
    g = int(base_hex[3:5], 16)
    b = int(base_hex[5:7], 16)
    bg = 10
    r2 = int(bg + (r - bg) * intensity)
    g2 = int(bg + (g - bg) * intensity)
    b2 = int(bg + (b - bg) * intensity)
    return f"#{r2:02x}{g2:02x}{b2:02x}"


def generate_and_visualize(prompt: str, max_new_tokens: int, temperature: float, top_k: int):
    if model is None:
        try:
            load_model()
        except Exception as e:
            return f"[Model load failed: {e}]", "<p>Model not loaded.</p>", ""

    ids = tokenizer.encode(prompt, return_tensors="pt").to(DEVICE)
    generated_text, snapshots = model.generate_with_slots(
        ids, max_new=int(max_new_tokens), tokenizer=tokenizer,
        temperature=float(temperature), top_k=int(top_k),
    )

    K = model.cfg.K

    # ── Routing gate heatmap table ────────────────────────────────────────────
    rows = []
    slot_token_counts = [[] for _ in range(K)]  # track which tokens route to each slot

    for tok_str, all_layer_gates, winner in snapshots:
        tok_display = tok_str.replace("<","&lt;").replace(">","&gt;").replace(" ","·").strip() or "⏎"
        last_gates = all_layer_gates[-1]  # final layer gate distribution

        # Track routing for summary
        slot_token_counts[winner].append(tok_str.strip())

        # Winner badge
        win_color = SLOT_COLORS[winner % len(SLOT_COLORS)]
        winner_label = SLOT_ROLES.get(winner, f"S{winner+1}")
        tok_cell = (
            f'<td style="background:#1a1a2e;color:#e0e0e0;padding:3px 8px;'
            f'font-size:13px;font-weight:bold;border-right:2px solid #333;white-space:nowrap;">'
            f'{tok_display} '
            f'<span style="color:{win_color};font-size:10px;font-weight:normal;">→{winner_label}</span>'
            f'</td>'
        )

        gate_cells = []
        for k, gv in enumerate(last_gates):
            intensity = min(1.0, gv * K)  # scale up since each slot avg ~1/K
            bg = gate_to_css_color(SLOT_COLORS[k % len(SLOT_COLORS)], intensity)
            pct = f"{gv*100:.0f}%" if gv > 0.08 else ""
            border = f"2px solid {SLOT_COLORS[k%len(SLOT_COLORS)]}" if k == winner else "1px solid #111"
            gate_cells.append(
                f'<td style="background:{bg};color:#fff;padding:2px 5px;'
                f'font-size:10px;font-family:monospace;border:{border};'
                f'text-align:center;min-width:28px;">{pct}</td>'
            )
        rows.append(f'<tr style="border-bottom:1px solid #111;">{tok_cell}{"".join(gate_cells)}</tr>')

    # Header
    slot_headers = "".join(
        f'<th style="background:{SLOT_COLORS[k%len(SLOT_COLORS)]};color:#000;'
        f'padding:4px 3px;font-size:10px;font-weight:bold;text-align:center;min-width:28px;">'
        f'{"★" if k in SLOT_ROLES else f"S{k+1}"}</th>'
        for k in range(K)
    )
    table_html = f"""<div style="overflow-x:auto;background:#050510;border-radius:8px;padding:8px;">
<p style="color:#888;font-size:11px;margin:0 0 6px 0;">
Cell brightness = routing affinity. ★ = slots with known specialization (probe-confirmed).
→ label = winning slot for each token. <b>Watch S12 light up for punctuation!</b> (slot 11, 0-indexed)
</p>
<table style="border-collapse:collapse;width:100%;font-family:monospace;">
<thead><tr>
<th style="background:#1a1a2e;color:#aaa;padding:4px 7px;font-size:11px;text-align:left;border-right:2px solid #333;white-space:nowrap;">Token → Winner</th>
{slot_headers}</tr></thead>
<tbody>{"".join(rows)}</tbody>
</table></div>"""

    # ── Routing summary ───────────────────────────────────────────────────────
    summary_lines = ["Slot routing summary (final layer, generated tokens):", ""]
    for k in range(K):
        tokens = slot_token_counts[k]
        role = SLOT_ROLES.get(k, "")
        role_str = f" [{role}]" if role else ""
        if tokens:
            top_toks = ", ".join(f'"{t}"' for t in tokens[:6] if t)
            summary_lines.append(f"S{k+1:2d}{role_str}: {len(tokens)} wins — {top_toks}")
        else:
            summary_lines.append(f"S{k+1:2d}{role_str}: 0 wins")
    summary = "\n".join(summary_lines)

    return generated_text, table_html, summary


with gr.Blocks(title="CDM V2 — Competitive Docking Memory | DuoNeural") as demo:
    gr.Markdown("""
# 🧠 Competitive Docking Memory (CDM) — Routing Gate Visualization

**[DuoNeural](https://huggingface.co/DuoNeural) 2026 | Archon · Jesse Caldwell · Aura**

This 37M-parameter model maintains **K=16 persistent memory slots** per layer that compete to process each token.
Slots **spontaneously specialize** without supervision: slot 11 (S12) dominates punctuation, slot 0 (S1) tracks character names, etc.

The heatmap shows **routing gate affinity** — how strongly each slot claims each token.
Bright cell = that slot "owns" the token. The `→ label` shows the winning slot.

> *K_eff ≈ 15.9/16 · 99.8% Shannon Capacity Saturation · val CE 1.5934 vs 1.6516 vanilla GPT baseline*
""")

    with gr.Row():
        with gr.Column(scale=3):
            prompt_in = gr.Textbox(label="Story Prompt", placeholder="Once upon a time...",
                                   lines=2, value="Once upon a time there was a little girl named Lily.")
            with gr.Row():
                max_tokens  = gr.Slider(10, 80, value=50, step=5,    label="Max new tokens")
                temperature = gr.Slider(0.1, 1.4, value=0.85, step=0.05, label="Temperature")
                top_k       = gr.Slider(5, 80,  value=40, step=5,    label="Top-k")
            btn = gr.Button("⚡ Generate + Show Routing Gates", variant="primary")
        with gr.Column(scale=2):
            routing_summary = gr.Textbox(label="Routing Summary — which tokens each slot claimed",
                                         lines=12, interactive=False)

    generated_out = gr.Textbox(label="Generated Story", lines=4, interactive=False)
    gr.Markdown("""### Routing Gate Heatmap
*Each row = one generated token. Columns S1–S16 = memory slots (★ = probe-confirmed specializations).
Cell brightness = gate affinity. `→ label` = winning slot. Try a prompt with punctuation to watch S12!*""")
    slot_table = gr.HTML()

    gr.Examples(examples=EXAMPLES, inputs=prompt_in, label="Try these prompts")

    btn.click(fn=generate_and_visualize,
              inputs=[prompt_in, max_tokens, temperature, top_k],
              outputs=[generated_out, slot_table, routing_summary])

    gr.Markdown("""---
**Known slot specializations** (from routing probe, step 30000, last layer):
S12 (slot 11) = PUNCT · S10 (slot 9) = CHARACTER AGENCY · S1 (slot 0) = CHARACTER IDENTITY ·
S16 (slot 15) = ACTION VERBS · S6 (slot 5) = ARTICLES

**Architecture:** 37.1M params · d=384 · 8 layers · K=16 competitive memory slots · GQA · SwiGLU FFN  
**Model:** [DuoNeural/CDM-V2-TinyStories-37M](https://huggingface.co/DuoNeural/CDM-V2-TinyStories-37M)  
**Paper:** *Competitive Docking Memory: Emergent Slot Specialization in Language Models* — DuoNeural 2026""")

demo.launch()