""" Wheeler Chat + Dynamics -- a Gradio demo for Wheeler-DeWitt-62M, a language model whose channel mixer is the Wheeler-DeWitt equation of quantum gravity. You chat with the SFT checkpoint AND watch the Wheeler-DeWitt dynamics that produced each reply: the emergent-time mode Psi_0 (the "clock"), the Hamiltonian-constraint residual |H|, and the K minisuperspace modes, captured token-by-token during generation and rendered as an inline-SVG dashboard. Weights are pulled at runtime from the public repo Quazim0t0/Wheeler-DeWitt-62M. Research artifact: the SFT checkpoint learned the FORM of a chat turn but not knowledge -- it hallucinates and does not reliably follow instructions. """ import re, os, json, html, sys try: sys.stdout.reconfigure(line_buffering=True) except Exception: pass print(">> app: importing torch", flush=True) import torch import torch.nn.functional as F print(">> app: torch ok, importing gradio", flush=True) import gradio as gr from huggingface_hub import hf_hub_download print(">> app: gradio ok, importing model chain", flush=True) from model import QuazimotoLM, QuazimotoConfig from spike_tokenizer import SpikeTokenizer import instrument from visualize import HTML_TEMPLATE print(">> app: all imports done, building UI", flush=True) # the inline-SVG Wheeler dashboard REPO = "Quazim0t0/Wheeler-DeWitt-62M" TOKEN = os.environ.get("HF_TOKEN") os.environ.setdefault("HF_HUB_ENABLE_HF_TRANSFER", "1") # faster 755MB pull ROLE = {"system": "<|system|>", "user": "<|user|>", "assistant": "<|assistant|>"} # Lazy load: bind the UI first (so the Space reaches "Running"), then load the # 62.9M checkpoint on the first request. Loading at import time blocks the port # past the startup health-check -> the Space hangs on "starting". model = tok = cfg = CK = VOCAB = IM_END = STOP_IDS = PL = None def _ensure_loaded(): global model, tok, cfg, CK, VOCAB, IM_END, STOP_IDS, PL if model is not None: return ckpt_path = hf_hub_download(REPO, "wheeler-dewitt-62m-sft.pt", token=TOKEN) tok = SpikeTokenizer(vocab_file="tokenizer.json") CK = torch.load(ckpt_path, map_location="cpu", weights_only=False) cfg = QuazimotoConfig(**CK["family_config"]) model = QuazimotoLM(cfg); model.load_state_dict(CK["model"], strict=False); model.eval() VOCAB = tok.get_vocab() IM_END = VOCAB.get("<|im_end|>") STOP_IDS = {VOCAB[n] for n in ("<|im_end|>", "", "<|endoftext|>") if n in VOCAB} PL = cfg.n_layer // 2 def render(history, system, user): parts = [] if system: parts.append(f"<|im_start|>{ROLE['system']}\n{system}<|im_end|>\n") for m in (history or []): if not isinstance(m, dict): # tolerate legacy tuple entries m = {"role": "assistant", "content": m[1] if len(m) > 1 else ""} r, c = m.get("role"), _as_text(m.get("content", "")) if r in ("user", "assistant") and c: parts.append(f"<|im_start|>{ROLE[r]}\n{c}<|im_end|>\n") parts.append(f"<|im_start|>{ROLE['user']}\n{user}<|im_end|>\n<|im_start|>{ROLE['assistant']}\n") return "".join(parts) def _ban3(ids, n=3): """no-repeat-ngram: token ids that would complete an already-seen n-gram. A/B on real weights (5 prompts, 120 tok): rep-4 0.181 -> 0.000, distinct-2 0.745 -> 0.913 at the deployed sampling settings.""" if len(ids) < n: return () tail = tuple(ids[-(n - 1):]) return tuple(ids[i + n - 1] for i in range(len(ids) - n + 1) if tuple(ids[i:i + n - 1]) == tail) @torch.no_grad() def _one_candidate(history, system, user, temperature, top_p, max_new, seed=0): """Generate the reply while recording the per-token Wheeler-DeWitt state (via instrument.Recorder, which WheelerDeWittBlock writes to). Returns (text, frames). KV-cached like generate.py: the prompt is prefilled once, then each new token is a single-position forward (a full recompute per token is O(n^2) -- minutes on CPU). The recorder captures the LAST position of every forward, so each single-token step still yields exactly one clean frame for the token being processed. The first token is sampled from the prefill logits.""" _ensure_loaded() # first call loads the model (lazy) torch.manual_seed(1234 + seed) # reproducible per-candidate sampling if hasattr(model, "reset_ring_memory"): model.reset_ring_memory() # clean per-request fast-weight memory rec = instrument.Recorder(phase_layer=PL, attn_layer=PL) ids = tok.encode(render(history, system, user), add_special_tokens=False) ids = ids[-(cfg.block_size - int(max_new) - 4):] idx = torch.tensor([ids]); out = [] def sample(lg1): lg = torch.nan_to_num(lg1.float(), nan=0.0, posinf=1e4, neginf=-1e4) if out: # repetition penalty 1.15 (reply only) -- nrng seen = torch.tensor(sorted(set(out))) v = lg[0, seen]; lg[0, seen] = torch.where(v > 0, v / 1.15, v * 1.15) for b in _ban3(out): lg[0, b] = -float("inf") lg = lg / max(float(temperature), 1e-6) k = min(40, lg.size(-1)) # top-k 40 -- nrng lg = lg.masked_fill(lg < torch.topk(lg, k).values[..., -1, None], -float("inf")) probs = F.softmax(lg, dim=-1) if float(top_p) < 1.0: sp, si = probs.sort(-1, descending=True) cut = sp.cumsum(-1) > float(top_p) cut[..., 1:] = cut[..., :-1].clone(); cut[..., 0] = False sp = sp.masked_fill(cut, 0.0); sp = sp / sp.sum(-1, keepdim=True) return si.gather(-1, torch.multinomial(sp, 1)) return torch.multinomial(probs, 1) try: logits, _, _, past = model(idx, use_cache=True) # prefill (no capture) nxt = sample(logits[:, -1, :]) for _ in range(int(max_new)): t = int(nxt) if t in STOP_IDS: break out.append(t); idx = torch.cat([idx, nxt], dim=1) # capture the forward that PROCESSES this token -> one frame per token instrument.set_rec(rec); rec.begin() if past[0][0].size(2) < cfg.block_size: logits, _, _, past = model(idx[:, -1:], past_key_values=past, use_cache=True) else: # context full -> windowed re-prime logits, _, _, past = model(idx[:, -cfg.block_size:], use_cache=True) topk = torch.topk(F.softmax(logits[:, -1].float(), -1), 5) rec.end(token=t, char=tok.decode([t], skip_special_tokens=False), top=[[int(i), round(float(p), 3)] for i, p in zip(topk.indices[0], topk.values[0])]) instrument.set_rec(None) nxt = sample(logits[:, -1, :]) finally: instrument.set_rec(None) return tok.decode(out, skip_special_tokens=True), rec.frames # --------------------------------------------------------------------------- # Effort-scaled decoding (candidate sampling + scoring) is adapted from Glint-2 # (https://huggingface.co/Glint-Research/Glint-2), Copyright (c) Glint-Research, # MIT License. # --------------------------------------------------------------------------- EFFORT_LEVELS = {"fast (1 sample)": 1, "balanced (3 samples)": 3, "best (6 samples)": 6} _ETEMPS = [0.30, 0.35, 0.40, 0.45] _STOPW = {"the","a","an","is","are","do","does","how","what","why","of","in","to", "for","and","or","it","its","with","on","at","by","that","this","when", "from","i","you","my","your","can","should","was","were","be","have"} def _as_text(x): """Coerce a history entry / content field to plain text. This Space's Chatbot runs in Gradio's TUPLES format while on_submit appends {"role","content"} dicts, so history arrives mixed: some entries are [user, assistant] lists, some are dicts, and a dict's "content" can itself be a list of parts. Be liberal rather than assume one shape.""" if x is None: return "" if isinstance(x, str): return x if isinstance(x, dict): # gradio message dicts use "content"; multimodal content PARTS use "text" for k in ("content", "text", "value", "alt_text"): if x.get(k) is not None: return _as_text(x[k]) return "" if isinstance(x, (list, tuple)): return " ".join(_as_text(i) for i in x) return str(x) def _content(t): return {w for w in re.findall(r"[a-z]+", _as_text(t).lower()) if w not in _STOPW and len(w) > 3} def _score_reply(text, user, prev): """Rank candidates: penalise self-repetition, reward addressing the CURRENT question, penalise echoing the previous assistant turn.""" words = re.findall(r"[a-z]+", _as_text(text).lower()) if not words: return -1e9 grams = [tuple(words[i:i + 4]) for i in range(len(words) - 3)] rep = 1.0 - len(set(grams)) / len(grams) if grams else 0.0 w = set(words) s = -2.0 * rep + 0.02 * min(len(words), 60) s += 0.6 * min(len(_content(user) & w), 4) s -= 0.5 * min(len(_content(prev) & w), 4) return s def generate_with_capture(history, system, user, effort, top_p, max_new, progress_cb=None): n = EFFORT_LEVELS.get(effort, 3) prev = "" for h in reversed(history or []): if isinstance(h, dict): if h.get("role") == "assistant" and h.get("content"): prev = _as_text(h["content"]); break elif isinstance(h, (list, tuple)) and len(h) > 1 and h[1]: prev = _as_text(h[1]); break # tuples format: [user, assistant] best, best_frames, best_s = "", [], -1e18 for i in range(n): if progress_cb is not None: progress_cb(i, n) temp = 0.7 if n == 1 else _ETEMPS[i % len(_ETEMPS)] text, frames = _one_candidate(history, system, user, temp, top_p, max_new, seed=i) sc = _score_reply(text, user, prev) if sc > best_s: best, best_frames, best_s = text, frames, sc return best, best_frames def dashboard_html(frames, prompt): """Embed the visualize.py inline-SVG dashboard (its JS runs in a sandboxed iframe).""" if not frames: return "
No dynamics captured.
" data = {"meta": {"ckpt": "Wheeler-DeWitt-62M (SFT)", "step": CK.get("step"), "n_layer": cfg.n_layer, "wdw_modes": cfg.wdw_modes, "wdw_steps": cfg.wdw_steps, "phase_layer": PL, "prompt": prompt[:60]}, "prompt_chars": [], "frames": frames} page = HTML_TEMPLATE.replace("/*__DATA__*/", json.dumps(data)) return (f'') def on_submit(user, history, system, effort, top_p, max_new, progress=gr.Progress()): """Ordinary (non-generator) handler. Progress is reported through gradio's Progress bar rather than by yielding chat messages: yielding placeholders into the transcript made them accumulate in the UI instead of replacing. The progress call is wrapped so any API difference degrades to no bar rather than breaking generation.""" history = (history or []) + [{"role": "user", "content": user}] def _tick(i, n): try: progress((i) / max(n, 1), desc="searching" if n == 1 else f"searching {i + 1}/{n}") except Exception: pass reply, frames = generate_with_capture(history[:-1], system, user, effort, top_p, max_new, progress_cb=_tick) history = history + [{"role": "assistant", "content": reply or "…"}] return history, dashboard_html(frames, user), "" CSS = """ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap'); :root { --bg:#04030b; --panel:rgba(160,140,255,.04); --panel2:rgba(160,140,255,.06); --txt:#ece9fb; --muted:#9a94c0; --dim:#5c5680; --border:rgba(160,140,255,.12); --g1:#8b6bff; --g2:#ff5fd0; --g3:#53d6ff; --spectrum:linear-gradient(90deg,#53d6ff,#8b6bff,#ff5fd0,#8b6bff,#53d6ff); } gradio-app { background:transparent !important; } body { background:radial-gradient(1400px 900px at 50% -20%, #120b2e 0%, #04030b 60%) fixed !important; } .gradio-container { background:transparent !important; color:var(--txt) !important; font-family:'Inter','SF Pro Display',ui-sans-serif,-apple-system,'Segoe UI',sans-serif !important; max-width:1240px !important; margin:0 auto !important; position:relative; } .gradio-container::before { content:''; position:fixed; inset:0; z-index:0; pointer-events:none; background:radial-gradient(ellipse 120% 90% at 50% 40%, transparent 55%, rgba(0,0,0,.6) 100%); } .gradio-container::after { content:''; position:fixed; inset:0; z-index:0; pointer-events:none; opacity:.04; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); } .gradio-container > * { position:relative; z-index:1; } /* hero */ #hero { text-align:center; padding:30px 0 4px; } #hero .tag { display:inline-block; font-family:'JetBrains Mono',monospace; font-size:.62rem; letter-spacing:.3em; text-transform:uppercase; color:var(--muted); border:1px solid var(--border); border-radius:999px; padding:5px 16px; margin-bottom:14px; background:rgba(160,140,255,.04); animation:rise .8s cubic-bezier(.16,1,.3,1) both; } #hero .tag b { color:var(--g3); font-weight:500; } #hero h1 { font-size:clamp(2.4rem,6vw,3.8rem); font-weight:200; margin:0; letter-spacing:-.015em; line-height:1.02; color:var(--txt); animation:rise .9s cubic-bezier(.16,1,.3,1) .08s both; } #hero h1 em { font-style:normal; background-image:var(--spectrum) !important; background-size:200% 100% !important; -webkit-background-clip:text !important; background-clip:text !important; -webkit-text-fill-color:transparent; } @keyframes shimmer { to { background-position:200% 0; } } #hero .rule { width:150px; height:1px; margin:16px auto 0; background:var(--spectrum); background-size:200% 100%; opacity:.85; } #hero p { color:var(--muted); margin:.7rem auto 0; font-size:.88rem; font-weight:300; max-width:72ch; animation:rise 1s cubic-bezier(.16,1,.3,1) .16s both; } #hero .warn { display:inline-block; margin-top:.7rem; font-family:'JetBrains Mono',monospace; font-size:.66rem; letter-spacing:.06em; color:#d9a15c; border:1px solid rgba(217,161,92,.25); background:rgba(217,161,92,.06); border-radius:8px; padding:6px 14px; max-width:80ch; animation:rise 1.1s cubic-bezier(.16,1,.3,1) .24s both; } @keyframes rise { from { opacity:0; transform:translateY(16px); } to { opacity:1; transform:none; } } /* glass, compact */ .gradio-container .block { padding:8px 10px !important; } .gradio-container .form { padding:6px !important; gap:6px !important; } .gradio-container .gap, .gradio-container .row, .gradio-container .column { gap:10px !important; } .block, .form, .accordion, .gr-group { background:var(--panel) !important; border:1px solid var(--border) !important; border-radius:16px !important; } div[data-testid='chatbot'], .chatbot, div[data-testid='chatbot'] > div, .bubble-wrap, .message-wrap { background:transparent !important; border:none !important; } [data-testid='block-label'], label.float { display:none !important; } .message, .bubble { border-radius:14px !important; border:1px solid var(--border) !important; background:var(--panel2) !important; color:var(--txt) !important; padding:8px 13px !important; font-size:.88rem !important; line-height:1.45 !important; } .user .message, .user .bubble { background:linear-gradient(92deg,rgba(139,107,255,.16),rgba(255,95,208,.14)) !important; } button.primary, .gr-button-primary { background:linear-gradient(92deg,var(--g1),var(--g2)) !important; border:none !important; color:#fff !important; font-weight:500 !important; border-radius:12px !important; padding:7px 16px !important; transition:filter .2s, box-shadow .2s; } button.primary:hover { filter:brightness(1.12); box-shadow:0 0 26px rgba(139,107,255,.4); } button.secondary { background:var(--panel) !important; border:1px solid var(--border) !important; color:var(--muted) !important; border-radius:12px !important; } textarea, input[type=text], input[type=number] { background:rgba(160,140,255,.05) !important; color:var(--txt) !important; border:1px solid var(--border) !important; border-radius:12px !important; font-size:.88rem !important; } textarea:focus, input[type=text]:focus { border-color:rgba(139,107,255,.55) !important; box-shadow:0 0 0 3px rgba(139,107,255,.14) !important; } label, .label-wrap span, span[data-testid='block-info'] { color:var(--muted) !important; font-family:'JetBrains Mono',monospace !important; font-size:.7rem !important; letter-spacing:.08em; text-transform:uppercase; background:transparent !important; padding:0 !important; } .examples button, button.example { background:var(--panel) !important; border:1px solid var(--border) !important; border-radius:999px !important; color:var(--muted) !important; font-size:.75rem !important; padding:5px 12px !important; transition:all .2s; } .examples button:hover { border-color:rgba(83,214,255,.5) !important; color:var(--txt) !important; } .hero-wrap, .hero-wrap.block, .block:has(#hero) { background:transparent !important; border:none !important; backdrop-filter:none !important; box-shadow:none !important; } footer { display:none !important; } """ HEAD = """ """ HERO = ( "
" "Wheeler-DeWitt-62M  ·  quantum-gravity LM " " ·  live dynamics" "

The Wheeler Universe

" "

A 62.9M-parameter language model whose channel mixer is built from the " "Wheeler–DeWitt equation of quantum gravity — a timeless constraint " "with a Lorentzian supermetric — in place of the usual MLP. Chat below; the dynamics " "panel shows the physics that produced each reply: the emergent-time clock Ψ₀, the " "Hamiltonian constraint |H|, and the minisuperspace modes, captured token-by-token.

" "⚠ research artifact — WheelerV2 tends to follow conversations " "well and understands somewhat, but is not trained on enough data to be reliable on many facts" "
" ) with gr.Blocks(title="Wheeler Chat", css=CSS, head=HEAD) as demo: gr.HTML(elem_classes=["hero-wrap"], value=HERO) with gr.Row(): with gr.Column(scale=1): # NOTE: do not pass type= here. gradio 6 removed the parameter # (messages is the only format); gradio 5 warns without it. The # Space pins sdk_version 6.15.2, so omit it. chatbot = gr.Chatbot(height=380, label="Wheeler-DeWitt-62M") msg = gr.Textbox(placeholder="Ask Wheeler-DeWitt-62M anything…", label="Message") with gr.Accordion("settings", open=False): system = gr.Textbox(value="You are a helpful assistant.", label="System prompt") temperature = gr.Dropdown(choices=list(EFFORT_LEVELS.keys()), value="balanced (3 samples)", label="effort") top_p = gr.Slider(0.1, 1.0, value=0.95, label="top_p") max_new = gr.Slider(16, 160, value=64, step=8, label="max new tokens") with gr.Row(): send = gr.Button("Send", variant="primary") clear = gr.Button("Clear") gr.Examples( examples=[["What is the capital of France?"], ["Explain photosynthesis in one sentence."], ["Tell me a short story."], ["Who described the three laws of motion?"], ["How do you bake a loaf of bread?"]], inputs=msg, label="Try") viz = gr.HTML(label="Wheeler–DeWitt dynamics", value="
Send a message to watch " "the emergent-time clock and the Hamiltonian constraint evolve.
") inp = [msg, chatbot, system, temperature, top_p, max_new] send.click(on_submit, inp, [chatbot, viz, msg]) msg.submit(on_submit, inp, [chatbot, viz, msg]) clear.click(lambda: ([], "", ""), None, [chatbot, viz, msg]) print(">> app: launching gradio", flush=True) if __name__ == "__main__": demo.queue().launch()