mikeumus-divincian commited on
Commit
c0a5e0d
·
verified ·
1 Parent(s): 798eed7

2026-04-22T02:58:20Z — viewer + assets

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ vindex-hero-bg.gif filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,12 +1,34 @@
1
  ---
2
- title: Vindex Viewer
3
- emoji: 🐨
4
- colorFrom: yellow
5
  colorTo: blue
6
- sdk: gradio
7
- sdk_version: 6.13.0
8
- app_file: app.py
9
- pinned: false
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: LarQL Vindex Viewer
3
+ emoji: 🔬
4
+ colorFrom: purple
5
  colorTo: blue
6
+ sdk: static
7
+ pinned: true
8
+ license: cc-by-nc-4.0
9
+ short_description: Interactive viewer for LarQL transformer vindexes
10
  ---
11
 
12
+ # LarQL Vindex Viewer
13
+
14
+ Interactive visualization of the SVD-decomposed feature structure of nine open-weight transformers, built from the [Divinci-AI vindex collection](https://huggingface.co/Divinci-AI).
15
+
16
+ **Two views, both live:**
17
+ - **3D cylinder** (default) — each model is a column of layer rings; feature points spiral around each ring colored by circuit stage (broadcast → domain → entity → prediction).
18
+ - **2D circuit** (`🔌 2D Circuit` button or `?view=flat`) — layers as horizontal rows with inter-layer edges between top features, network-style.
19
+
20
+ **Compare mode** (`⇌ Compare`) renders any two models side-by-side. The default pair (Qwen3-8B vs Bonsai b1.58 1-bit) is the headline visual: organized rings vs scattered dissolution cloud, the same data that drives Paper 1's 1-bit dissolution claim (var@64 ≈ 0.85 for fp16 vs ≈ 0.10 for 1-bit).
21
+
22
+ **Entity search** (`🔍` toolbar input or `?q=paris`) — type a token, see which features it activates across the model's depth. The Paris→capital match on Gemma 4 E2B is the LarQL Gate-3 result that Post 2 in our blog series unpacks.
23
+
24
+ **Demo** (`▶ Demo`) — 12-second scripted tour: build → orbit → reveal compare mode → tagline.
25
+
26
+ URL params: `?model=`, `?view=flat`, `?autoplay`, `?q=`. Combine freely.
27
+
28
+ ---
29
+
30
+ ## Background
31
+
32
+ A **vindex** is a transformer's weights decompiled into a queryable feature database. The viewer renders the top-64 SVD feature directions per layer — the directions that absorb ~85% of the down-projection matrix's variance in fp16 models and ~10% (near-Marchenko-Pastur random) in 1-bit models.
33
+
34
+ Full details, papers, and the LarQL toolchain: **[huggingface.co/Divinci-AI](https://huggingface.co/Divinci-AI)** · [github.com/Divinci-AI](https://github.com/Divinci-AI)
deploy.sh ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Deploy the LarQL Vindex Viewer to HuggingFace Spaces (Divinci-AI/vindex-viewer)
3
+ #
4
+ # First-time setup:
5
+ # pip install -U huggingface_hub
6
+ # hf auth login # paste your write token from https://huggingface.co/settings/tokens
7
+ #
8
+ # Usage:
9
+ # ./deploy.sh # incremental upload of changed files (creates space if missing)
10
+ # ./deploy.sh --create-only # just create the space, don't upload
11
+ #
12
+ # After deploy, the viewer is live at:
13
+ # https://huggingface.co/spaces/Divinci-AI/vindex-viewer
14
+
15
+ set -euo pipefail
16
+
17
+ SPACE_ID="Divinci-AI/vindex-viewer"
18
+ HERE="$(cd "$(dirname "$0")" && pwd)"
19
+
20
+ # Try to create the Space (idempotent — succeeds even if it already exists)
21
+ echo "Ensuring Space $SPACE_ID exists..."
22
+ hf repo create "$SPACE_ID" --repo-type space --space-sdk static --exist-ok 2>&1 | grep -v "already created" || true
23
+
24
+ if [[ "${1:-}" == "--create-only" ]]; then
25
+ echo "Created (or already exists). Exiting before upload."
26
+ exit 0
27
+ fi
28
+
29
+ echo ""
30
+ echo "Uploading $HERE/ to $SPACE_ID ..."
31
+ hf upload "$SPACE_ID" "$HERE" . --repo-type space \
32
+ --commit-message "$(date -u +%Y-%m-%dT%H:%M:%SZ) — viewer + assets"
33
+
34
+ echo ""
35
+ echo "✓ Deployed. Live at: https://huggingface.co/spaces/$SPACE_ID"
36
+ echo "Hero GIF URL (used by org card): https://huggingface.co/spaces/$SPACE_ID/resolve/main/vindex-hero-bg.gif"
index.html ADDED
@@ -0,0 +1,1419 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>LarQL Vindex Viewer — Divinci AI</title>
7
+ <style>
8
+ * { box-sizing: border-box; margin: 0; padding: 0; }
9
+ body { background: #050510; color: #e0e8ff; font-family: 'Courier New', monospace; overflow: hidden; }
10
+
11
+ canvas { display: block; }
12
+
13
+ /* ── Top UI bar ─────────────────────────────────────────────── */
14
+ #ui {
15
+ position: absolute; top: 0; left: 0; right: 0;
16
+ padding: 12px 16px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
17
+ background: linear-gradient(to bottom, rgba(5,5,16,0.95), transparent);
18
+ pointer-events: none; z-index: 10;
19
+ }
20
+ #ui > * { pointer-events: all; }
21
+ .logo { font-size: 11px; opacity: 0.5; letter-spacing: 0.12em; white-space: nowrap; }
22
+ .logo span { color: #7b9fff; }
23
+
24
+ select, button {
25
+ background: rgba(20,24,60,0.9); border: 1px solid rgba(123,159,255,0.28);
26
+ color: #e0e8ff; padding: 5px 10px; border-radius: 5px; font-size: 11px;
27
+ cursor: pointer; outline: none; font-family: inherit; transition: border-color 0.15s;
28
+ }
29
+ select:hover, button:hover { border-color: rgba(123,159,255,0.65); }
30
+ button.active { border-color: #7b9fff; background: rgba(40,55,130,0.9); color: #fff; }
31
+ #hint { font-size: 10px; opacity: 0.35; }
32
+ #search-input {
33
+ background: rgba(20,24,60,0.9); border: 1px solid rgba(123,159,255,0.28);
34
+ color: #e0e8ff; padding: 5px 10px; border-radius: 5px; font-size: 11px;
35
+ width: 250px; font-family: inherit; outline: none; transition: border-color 0.15s;
36
+ }
37
+ #search-input:focus { border-color: #7b9fff; }
38
+ #search-input::placeholder { color: rgba(224,232,255,0.3); }
39
+ #search-status { font-size: 10px; opacity: 0.55; color: #ffd060; min-width: 110px; }
40
+
41
+ /* ── Tier 1 info panel — always visible ─────────────────────── */
42
+ #info-panel {
43
+ position: absolute; bottom: 16px; left: 16px;
44
+ background: rgba(6,7,20,0.93); border: 1px solid rgba(123,159,255,0.2);
45
+ border-radius: 10px; min-width: 240px; max-width: 280px;
46
+ font-size: 11px; z-index: 10; overflow: hidden;
47
+ box-shadow: 0 4px 24px rgba(0,0,0,0.5);
48
+ }
49
+ .panel-tier1 { padding: 12px 14px 10px; }
50
+ .model-title {
51
+ font-size: 13px; font-weight: bold; letter-spacing: 0.04em;
52
+ color: #e8f0ff; display: flex; justify-content: space-between; align-items: flex-start;
53
+ gap: 8px; margin-bottom: 3px;
54
+ }
55
+ .hf-link {
56
+ font-size: 9px; color: #7b9fff; text-decoration: none; opacity: 0.8;
57
+ border: 1px solid rgba(123,159,255,0.25); border-radius: 3px; padding: 1px 5px;
58
+ white-space: nowrap; flex-shrink: 0;
59
+ }
60
+ .hf-link:hover { opacity: 1; border-color: rgba(123,159,255,0.6); }
61
+ .model-meta { font-size: 10px; opacity: 0.45; margin-bottom: 10px; letter-spacing: 0.02em; }
62
+
63
+ .tier1-divider { border: none; border-top: 1px solid rgba(123,159,255,0.1); margin: 8px 0; }
64
+
65
+ .c4-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
66
+ .c4-val { font-size: 15px; font-weight: bold; font-family: monospace; }
67
+ .c4-val.good { color: #44ffaa; }
68
+ .c4-val.warm { color: #ffcc44; }
69
+ .c4-val.hot { color: #ff6644; }
70
+ .c4-label { font-size: 9px; opacity: 0.5; }
71
+ .c4-chip {
72
+ margin-left: auto; font-size: 9px; padding: 2px 6px; border-radius: 10px;
73
+ font-weight: bold; letter-spacing: 0.04em;
74
+ }
75
+ .chip-good { background: rgba(68,255,170,0.15); color: #44ffaa; border: 1px solid rgba(68,255,170,0.3); }
76
+ .chip-warn { background: rgba(255,180,50,0.15); color: #ffcc44; border: 1px solid rgba(255,180,50,0.3); }
77
+ .chip-alert { background: rgba(255,80,50,0.15); color: #ff6644; border: 1px solid rgba(255,80,50,0.3); }
78
+ .chip-dead { background: rgba(255,40,40,0.18); color: #ff4444; border: 1px solid rgba(255,40,40,0.3); }
79
+
80
+ .stage-bar-row { display: flex; align-items: center; gap: 7px; margin-bottom: 4px; }
81
+ .stage-bar-label { font-size: 9px; opacity: 0.45; width: 60px; flex-shrink: 0; }
82
+ .stage-bar-track { flex: 1; height: 5px; background: rgba(255,255,255,0.07); border-radius: 2px; overflow: hidden; }
83
+ .stage-bar-fill { height: 100%; border-radius: 2px; transition: width 0.4s ease; }
84
+ .stage-bar-val { font-size: 9px; width: 28px; text-align: right; opacity: 0.7; }
85
+
86
+ .gate3-row {
87
+ display: flex; align-items: center; gap: 6px;
88
+ margin-top: 7px; font-size: 10px;
89
+ }
90
+ .gate3-icon { font-size: 11px; }
91
+
92
+ .anomaly-banner {
93
+ margin: 8px 14px; padding: 6px 10px;
94
+ background: rgba(255,100,50,0.1); border: 1px solid rgba(255,100,50,0.25);
95
+ border-radius: 6px; font-size: 10px; color: #ffaa66; cursor: pointer;
96
+ display: none; line-height: 1.5;
97
+ }
98
+ .anomaly-banner:hover { background: rgba(255,100,50,0.18); }
99
+
100
+ /* ── Tier 2 — expandable metrics ───────────────────────────── */
101
+ .tier2-toggle {
102
+ width: 100%; text-align: center; padding: 6px; font-size: 9px;
103
+ letter-spacing: 0.1em; opacity: 0.4; cursor: pointer; border: none;
104
+ background: rgba(123,159,255,0.04); border-top: 1px solid rgba(123,159,255,0.1);
105
+ color: #e0e8ff; text-transform: uppercase; transition: opacity 0.15s, background 0.15s;
106
+ }
107
+ .tier2-toggle:hover { opacity: 0.75; background: rgba(123,159,255,0.08); }
108
+
109
+ #tier2-metrics { display: none; padding: 10px 14px 12px; border-top: 1px solid rgba(123,159,255,0.08); }
110
+ #tier2-metrics.open { display: block; }
111
+
112
+ .m-row { display: flex; align-items: center; gap: 6px; margin-bottom: 7px; }
113
+ .m-name { font-size: 9px; opacity: 0.45; width: 64px; flex-shrink: 0; letter-spacing: 0.02em; }
114
+ .m-bar-track { flex: 1; height: 4px; background: rgba(255,255,255,0.07); border-radius: 2px; overflow: hidden; }
115
+ .m-bar-fill { height: 100%; border-radius: 2px; }
116
+ .m-val { font-size: 9px; width: 36px; text-align: right; font-family: monospace; }
117
+ .m-val.good { color: #44ffaa; }
118
+ .m-val.warn { color: #ffcc44; }
119
+ .m-val.alert { color: #ff6644; }
120
+ .m-val.dim { opacity: 0.35; }
121
+
122
+ .shortid-row {
123
+ display: flex; align-items: center; justify-content: space-between;
124
+ margin-top: 6px; padding-top: 6px; border-top: 1px solid rgba(123,159,255,0.08);
125
+ font-size: 9px;
126
+ }
127
+ .shortid-val { font-family: monospace; opacity: 0.45; }
128
+ .copy-btn {
129
+ font-size: 9px; padding: 1px 6px; border-radius: 3px; cursor: pointer;
130
+ border: 1px solid rgba(123,159,255,0.2); background: transparent; color: #7b9fff;
131
+ }
132
+ .copy-btn:hover { background: rgba(123,159,255,0.1); }
133
+
134
+ /* ── Legend ──────────────────────────────────────────────────── */
135
+ #legend {
136
+ position: absolute; bottom: 16px; right: 16px;
137
+ background: rgba(6,7,20,0.93); border: 1px solid rgba(123,159,255,0.2);
138
+ border-radius: 10px; padding: 12px 15px; font-size: 11px; line-height: 1.9; z-index: 10;
139
+ }
140
+ #legend h3 { font-size: 9px; letter-spacing: 0.14em; color: #7b9fff; margin-bottom: 6px; text-transform: uppercase; }
141
+ .stage-row { display: flex; align-items: center; gap: 7px; }
142
+ .stage-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
143
+
144
+ /* ── Compare panel ───────────────────────────────────────────── */
145
+ #compare-panel {
146
+ position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%);
147
+ background: rgba(6,7,20,0.93); border: 1px solid rgba(123,159,255,0.2);
148
+ border-radius: 10px; padding: 12px 16px; font-size: 11px;
149
+ display: none; z-index: 10; white-space: nowrap;
150
+ box-shadow: 0 4px 24px rgba(0,0,0,0.5);
151
+ }
152
+ #compare-panel h3 { font-size: 9px; letter-spacing: 0.14em; color: #7b9fff; margin-bottom: 8px; text-transform: uppercase; }
153
+ .cmp-row { display: flex; gap: 28px; }
154
+ .cmp-col { display: flex; flex-direction: column; gap: 4px; min-width: 140px; }
155
+ .cmp-title { font-size: 10px; font-weight: bold; color: #e8f0ff; margin-bottom: 2px; }
156
+ .cmp-meta { font-size: 9px; opacity: 0.4; margin-bottom: 4px; }
157
+ .cmp-metric { display: flex; justify-content: space-between; gap: 14px; font-size: 10px; }
158
+ .cmp-val { font-family: monospace; }
159
+ .cmp-val.good { color: #44ffaa; }
160
+ .cmp-val.warn { color: #ffcc44; }
161
+ .cmp-val.alert { color: #ff6644; }
162
+ .cmp-divider { width: 1px; background: rgba(123,159,255,0.15); }
163
+
164
+ /* ── Tooltip ─────────────────────────────────────────────────── */
165
+ #tooltip {
166
+ position: absolute; display: none; pointer-events: none;
167
+ background: rgba(8,10,32,0.97); border: 1px solid rgba(123,159,255,0.35);
168
+ border-radius: 7px; padding: 8px 12px; font-size: 10px; line-height: 1.7;
169
+ color: #c8d8ff; z-index: 20; max-width: 220px;
170
+ }
171
+
172
+ /* ── Demo overlay ────────────────────────────────────────────── */
173
+ #demo-overlay {
174
+ position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
175
+ font-size: 13px; letter-spacing: 0.08em; color: rgba(255,255,255,0.7);
176
+ text-align: center; z-index: 30; display: none;
177
+ text-shadow: 0 2px 12px rgba(0,0,0,0.8);
178
+ pointer-events: none;
179
+ }
180
+
181
+ #loading {
182
+ position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
183
+ text-align: center; color: #7b9fff; z-index: 30; display: none;
184
+ }
185
+ .spinner { width: 32px; height: 32px; border: 2px solid rgba(123,159,255,0.15); border-top-color: #7b9fff; border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 10px; }
186
+ @keyframes spin { to { transform: rotate(360deg); } }
187
+ </style>
188
+ </head>
189
+ <body>
190
+
191
+ <!-- Top bar -->
192
+ <div id="ui">
193
+ <div class="logo"><span>Divinci AI</span> · LarQL Vindex Viewer v2</div>
194
+ <select id="model-select">
195
+ <option value="gemma-4-e2b">Gemma 4 E2B (2B) — 35L ✓</option>
196
+ <option value="ministral-3b">Ministral-3B — 26L</option>
197
+ <option value="medgemma-4b">MedGemma-1.5-4B — 34L ⚠️</option>
198
+ <option value="qwen3-0.6b">Qwen3-0.6B — 28L</option>
199
+ <option value="qwen3-8b" selected>Qwen3-8B — 36L</option>
200
+ <option value="qwen3.6-35b">Qwen3.6-35B MoE — 40L</option>
201
+ <option value="llama-3.1-8b">Llama-3.1-8B — 32L</option>
202
+ <option value="gpt-oss-120b">GPT-OSS-120B MoE — 36L</option>
203
+ <option value="bonsai-1bit">Bonsai b1.58-2B 1-bit 💀</option>
204
+ </select>
205
+ <button id="compare-btn" title="Compare current model vs Bonsai 1-bit dissolution">⇌ Compare</button>
206
+ <button id="demo-btn" title="12-second scripted tour">▶ Demo</button>
207
+ <button id="view-btn" title="Toggle between 3D cylinder and 2D circuit view">🔌 2D Circuit</button>
208
+ <input id="search-input" type="search" placeholder="🔍 Search features (e.g. paris, capital, code)…" autocomplete="off" />
209
+ <span id="search-status"></span>
210
+ <div id="hint">Click a feature · drag to orbit · scroll to zoom</div>
211
+ </div>
212
+
213
+ <!-- Tier 1 info panel -->
214
+ <div id="info-panel">
215
+ <div class="panel-tier1">
216
+ <div class="model-title">
217
+ <span id="m-name">—</span>
218
+ <a id="m-hf-link" class="hf-link" href="#" target="_blank" rel="noopener">↗ HF</a>
219
+ </div>
220
+ <div class="model-meta" id="m-meta">—</div>
221
+ <hr class="tier1-divider">
222
+ <!-- C4 row -->
223
+ <div class="c4-row">
224
+ <div>
225
+ <div class="c4-val" id="m-c4-val">—</div>
226
+ <div class="c4-label">C4 TEMPERATURE</div>
227
+ </div>
228
+ <div class="c4-chip" id="m-c4-chip">—</div>
229
+ </div>
230
+ <!-- Stage bar -->
231
+ <div class="stage-bar-row">
232
+ <div class="stage-bar-label">CIRCUIT</div>
233
+ <div class="stage-bar-track"><div class="stage-bar-fill" id="m-stage-fill" style="width:0%"></div></div>
234
+ <div class="stage-bar-val" id="m-c5-val">—</div>
235
+ </div>
236
+ <!-- Gate 3 -->
237
+ <div class="gate3-row">
238
+ <span class="gate3-icon" id="m-gate3-icon">○</span>
239
+ <span id="m-gate3-label" style="opacity:0.5">Gate-3: —</span>
240
+ </div>
241
+ </div>
242
+ <!-- Anomaly banner -->
243
+ <div class="anomaly-banner" id="m-anomaly"></div>
244
+ <!-- Tier 2 toggle -->
245
+ <button class="tier2-toggle" id="tier2-btn">METRICS ▾</button>
246
+ <!-- Tier 2 body -->
247
+ <div id="tier2-metrics">
248
+ <div class="m-row">
249
+ <div class="m-name">C1 SPARSITY</div>
250
+ <div class="m-bar-track"><div class="m-bar-fill" id="b-c1" style="width:0%;background:#44aaff"></div></div>
251
+ <div class="m-val" id="v-c1">—</div>
252
+ </div>
253
+ <div class="m-row">
254
+ <div class="m-name">C2 TOP-8</div>
255
+ <div class="m-bar-track"><div class="m-bar-fill" id="b-c2" style="width:0%;background:#44ffaa"></div></div>
256
+ <div class="m-val" id="v-c2">—</div>
257
+ </div>
258
+ <div class="m-row">
259
+ <div class="m-name">C3 COHERENCE</div>
260
+ <div class="m-bar-track"><div class="m-bar-fill" id="b-c3" style="width:0%;background:#7b9fff"></div></div>
261
+ <div class="m-val" id="v-c3">—</div>
262
+ </div>
263
+ <div class="m-row">
264
+ <div class="m-name">C4 TEMP</div>
265
+ <div class="m-bar-track"><div class="m-bar-fill" id="b-c4" style="width:0%;background:#ffaa44"></div></div>
266
+ <div class="m-val" id="v-c4">—</div>
267
+ </div>
268
+ <div class="m-row">
269
+ <div class="m-name">SPECTRUM</div>
270
+ <div class="m-bar-track"><div class="m-bar-fill" id="b-spec" style="width:0%;background:#a855f7"></div></div>
271
+ <div class="m-val" id="v-spec">—</div>
272
+ </div>
273
+ <div class="shortid-row">
274
+ <div>
275
+ <span style="opacity:0.35;font-size:9px">VINDEX ID </span>
276
+ <span class="shortid-val" id="v-shortid">—</span>
277
+ </div>
278
+ <button class="copy-btn" id="copy-shortid">copy</button>
279
+ </div>
280
+ </div>
281
+ </div>
282
+
283
+ <!-- Legend -->
284
+ <div id="legend">
285
+ <h3>Circuit Stages</h3>
286
+ <div class="stage-row"><div class="stage-dot" style="background:#4466ff"></div>Broadcast (0%)</div>
287
+ <div class="stage-row"><div class="stage-dot" style="background:#44aaff"></div>Domain (20-25%)</div>
288
+ <div class="stage-row"><div class="stage-dot" style="background:#44ffaa"></div>Entity (45-55%)</div>
289
+ <div class="stage-row"><div class="stage-dot" style="background:#ffaa44"></div>Prediction (90%+)</div>
290
+ <div class="stage-row"><div class="stage-dot" style="background:#888"></div>Transition</div>
291
+ <div class="stage-row" style="margin-top:5px"><div class="stage-dot" style="background:#ff4444"></div>Dissolved (1-bit)</div>
292
+ </div>
293
+
294
+ <!-- Compare panel -->
295
+ <div id="compare-panel">
296
+ <h3>Circuit Comparison</h3>
297
+ <div class="cmp-row">
298
+ <div class="cmp-col" id="cmp-a"></div>
299
+ <div class="cmp-divider"></div>
300
+ <div class="cmp-col" id="cmp-b"></div>
301
+ </div>
302
+ </div>
303
+
304
+ <!-- Demo text overlay -->
305
+ <div id="demo-overlay"></div>
306
+
307
+ <div id="tooltip"></div>
308
+ <div id="loading"><div class="spinner"></div><div>Loading…</div></div>
309
+
310
+ <script type="importmap">
311
+ {
312
+ "imports": {
313
+ "three": "https://unpkg.com/three@0.160/build/three.module.js",
314
+ "three/addons/": "https://unpkg.com/three@0.160/examples/jsm/"
315
+ }
316
+ }
317
+ </script>
318
+ <script type="module">
319
+ import * as THREE from 'three';
320
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
321
+ import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
322
+ import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
323
+ import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
324
+
325
+ // ── Stage colors ───────────────────────────────────────────────
326
+ const STAGE_COLORS = [
327
+ new THREE.Color(0x4466ff), // broadcast
328
+ new THREE.Color(0x44aaff), // domain
329
+ new THREE.Color(0x44ffaa), // entity
330
+ new THREE.Color(0xffaa44), // prediction
331
+ new THREE.Color(0x888888), // transition
332
+ new THREE.Color(0xff4444), // dissolved
333
+ ];
334
+ const STAGE_NAMES = ['Broadcast','Domain','Entity','Prediction','Transition','Dissolved'];
335
+
336
+ // ── Real Phase 1+2 measurements (April 2026) ──────────────────
337
+ const VINDEX_DATA = {
338
+ 'gemma-4-e2b': {
339
+ name: 'Gemma 4 E2B', org: 'Google', params: '2B', precision: 'bf16',
340
+ layers: 35, F: 128,
341
+ c1: 0.061, c2: 0.938, c3: 0.801, c4: 0.041, c5: 4, c5max: 5,
342
+ spectrum: 'power-law', spectrumScore: 0.92,
343
+ shortId: 'gemma4e2b', hfRepo: 'Divinci-AI/gemma-4-e2b-vindex',
344
+ gate3: 'PASS', gate3note: 'Paris→capital suppressed (18.1→absent)',
345
+ finding: 'Confirms C4≈0.042 universal constant',
346
+ anomaly: null, dissolved: false,
347
+ stageBands: [[0,.04,0],[.20,.25,1],[.45,.55,2],[.90,1,3]],
348
+ },
349
+ 'ministral-3b': {
350
+ name: 'Ministral-3B', org: 'Mistral AI', params: '3B', precision: 'fp8→bf16',
351
+ layers: 26, F: 128,
352
+ c1: 0.236, c2: null, c3: 0.724, c4: 0.265, c5: 3, c5max: 5,
353
+ spectrum: 'power-law', spectrumScore: 0.88,
354
+ shortId: '00d28f96', hfRepo: 'Divinci-AI/ministral-3b-vindex',
355
+ gate3: 'PENDING', gate3note: 'Awaiting multi-layer LarQL service',
356
+ finding: 'C4 higher than expected (fp8 dequant on A100 compute 8.0). C2 N/A — multimodal lm_head absent.',
357
+ anomaly: null, dissolved: false,
358
+ stageBands: [[0,.04,0],[.20,.26,1],[.46,.54,2],[.88,1,3]],
359
+ },
360
+ 'medgemma-4b': {
361
+ name: 'MedGemma-1.5-4B', org: 'Google', params: '4B', precision: 'bf16',
362
+ layers: 34, F: 128,
363
+ c1: 0.181, c2: 0.795, c3: 0.800, c4: 1.898, c5: 2, c5max: 5,
364
+ spectrum: 'power-law', spectrumScore: 0.78,
365
+ shortId: '75eeb232', hfRepo: 'Divinci-AI/medgemma-1.5-4b-vindex',
366
+ gate3: 'PENDING', gate3note: 'Gate-3 pending',
367
+ finding: 'C4 anomaly: 1.898 — 45× cohort average. Hypothesis: text-only activation probe through multimodal vision encoder inflates temperature measurement.',
368
+ anomaly: '⚠️ C4 = 1.898 — 45× cohort anomaly. Likely artifact of text-only probe through vision encoder. Under investigation.',
369
+ anomalyType: 'warn', dissolved: false,
370
+ stageBands: [[0,.04,0],[.20,.26,1],[.45,.54,2],[.90,1,3]],
371
+ },
372
+ 'qwen3-0.6b': {
373
+ name: 'Qwen3-0.6B', org: 'Alibaba Cloud', params: '0.6B', precision: 'bf16',
374
+ layers: 28, F: 128,
375
+ c1: 0.117, c2: 0.880, c3: 0.531, c4: 0.411, c5: 4, c5max: 5,
376
+ spectrum: 'power-law', spectrumScore: 0.83,
377
+ shortId: 'qwen306b', hfRepo: 'Divinci-AI/qwen3-0.6b-vindex',
378
+ gate3: 'PENDING', gate3note: 'Awaiting multi-layer LarQL service',
379
+ finding: 'Qwen3 family C4 elevation (10×). C3 coherence lower than larger Qwen3 models — scale matters for gate alignment.',
380
+ anomaly: null, dissolved: false,
381
+ stageBands: [[0,.04,0],[.21,.25,1],[.46,.54,2],[.90,1,3]],
382
+ },
383
+ 'qwen3-8b': {
384
+ name: 'Qwen3-8B', org: 'Alibaba Cloud', params: '8B', precision: 'bf16',
385
+ layers: 36, F: 128,
386
+ c1: 0.228, c2: 0.867, c3: 0.813, c4: 0.804, c5: 4, c5max: 5,
387
+ spectrum: 'power-law', spectrumScore: 0.87,
388
+ shortId: 'ea08e8e8', hfRepo: 'Divinci-AI/qwen3-8b-vindex',
389
+ gate3: 'PENDING', gate3note: 'Awaiting multi-layer LarQL service',
390
+ finding: 'Qwen3 family C4 elevation (19×) confirmed as architectural signature — same in bf16 as in 1-bit Bonsai. Four-stage circuit intact.',
391
+ anomaly: null, dissolved: false,
392
+ stageBands: [[0,.04,0],[.20,.25,1],[.45,.55,2],[.90,1,3]],
393
+ },
394
+ 'qwen3.6-35b': {
395
+ name: 'Qwen3.6-35B-A3B', org: 'Alibaba Cloud', params: '35B (3B active)', precision: 'bf16',
396
+ layers: 40, F: 64,
397
+ c1: null, c2: null, c3: null, c4: null, c5: null, c5max: 5,
398
+ spectrum: 'var@64: 0.27–0.39', spectrumScore: 0.55,
399
+ shortId: 'qwen3635b', hfRepo: 'Divinci-AI/qwen3.6-35b-a3b-vindex',
400
+ gate3: 'N/A', gate3note: 'Phase 2 pending — MoE 256 experts',
401
+ finding: 'Phase 1 complete. Phase 2 skipped — OOM on A100 with 256-expert MoE routing.',
402
+ anomaly: null, dissolved: false,
403
+ stageBands: [[0,.04,0],[.20,.26,1],[.46,.54,2],[.90,1,3]],
404
+ },
405
+ 'llama-3.1-8b': {
406
+ name: 'Llama 3.1-8B', org: 'Meta', params: '8B', precision: 'bf16',
407
+ layers: 32, F: 128,
408
+ c1: 0.387, c2: 0.491, c3: 0.808, c4: 0.012, c5: 2, c5max: 5,
409
+ spectrum: 'power-law', spectrumScore: 0.86,
410
+ shortId: 'c39fad08', hfRepo: 'Divinci-AI/llama-3.1-8b-vindex',
411
+ gate3: 'PENDING', gate3note: 'Awaiting multi-layer LarQL service',
412
+ finding: 'C4 = 0.012 — Llama family runs below the universal constant. C1 sparsity (0.387) highest of all measured models. Base model: C2 reflects unconditional next-token distribution.',
413
+ anomaly: null, dissolved: false,
414
+ stageBands: [[0,.04,0],[.22,.26,1],[.47,.55,2],[.90,1,3]],
415
+ },
416
+ 'gpt-oss-120b': {
417
+ name: 'GPT-OSS-120B', org: 'OpenAI', params: '120B', precision: 'MXFP4',
418
+ layers: 36, F: 128,
419
+ c1: null, c2: null, c3: null, c4: null, c5: null, c5max: 5,
420
+ spectrum: 'S[0]=13,056 (117×)', spectrumScore: 0.72,
421
+ shortId: '02d09974', hfRepo: 'Divinci-AI/gpt-oss-120b-vindex',
422
+ gate3: 'N/A', gate3note: 'Uniform MoE routing — calibration required',
423
+ finding: 'Phase 2 skipped (OOM). Dominant SV = 13,056 — 117× the 200th SV. Most extreme power-law spectrum in the cohort. MXFP4 quantization preserves structural hierarchy.',
424
+ anomaly: null, dissolved: false,
425
+ stageBands: [[0,.04,0],[.20,.25,1],[.45,.55,2],[.90,1,3]],
426
+ },
427
+ 'bonsai-1bit': {
428
+ name: 'Bonsai b1.58-2B', org: 'Microsoft', params: '2B', precision: '1-bit {-1,0,+1}',
429
+ layers: 28, F: 128,
430
+ c1: 0.223, c2: 0.375, c3: 0.534, c4: 0.429, c5: 1, c5max: 5,
431
+ spectrum: 'flat (var@128=9.3%)', spectrumScore: 0.09,
432
+ shortId: 'bonsai1b', hfRepo: null,
433
+ gate3: 'N/A', gate3note: 'Ternary weights — Phase 1 redo pending',
434
+ finding: 'C5 = 1: four-stage circuit completely dissolved. SV spectrum near-random (Marchenko-Pastur). C4 = 0.429 is Qwen3 architectural signature, not 1-bit effect. The 1-bit discriminators are C5 collapse + flat SV spectrum only.',
435
+ anomaly: '💀 C5 = 1 — four-stage circuit dissolved. Weight SV spectrum is near-random. The model answers correctly, but its internals have no discernible structure.',
436
+ anomalyType: 'dead', dissolved: true,
437
+ stageBands: [],
438
+ },
439
+ };
440
+
441
+ // ── Renderer ──────────────────────────────────────────────────
442
+ const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
443
+ renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
444
+ renderer.setSize(innerWidth, innerHeight);
445
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
446
+ renderer.toneMappingExposure = 1.15;
447
+ document.body.appendChild(renderer.domElement);
448
+
449
+ const scene = new THREE.Scene();
450
+ scene.background = new THREE.Color(0x050510);
451
+ scene.fog = new THREE.FogExp2(0x050510, 0.012);
452
+
453
+ const camera = new THREE.PerspectiveCamera(52, innerWidth / innerHeight, 0.1, 600);
454
+ camera.position.set(0, 4, 26);
455
+
456
+ const controls = new OrbitControls(camera, renderer.domElement);
457
+ controls.enableDamping = true;
458
+ controls.dampingFactor = 0.06;
459
+ controls.minDistance = 7;
460
+ controls.maxDistance = 120;
461
+
462
+ scene.add(new THREE.AmbientLight(0x334466, 2.8));
463
+ const dirLight = new THREE.DirectionalLight(0x7799ff, 1.6);
464
+ dirLight.position.set(10, 20, 10);
465
+ scene.add(dirLight);
466
+
467
+ const composer = new EffectComposer(renderer);
468
+ composer.addPass(new RenderPass(scene, camera));
469
+ const bloom = new UnrealBloomPass(new THREE.Vector2(innerWidth, innerHeight), 0.7, 0.4, 0.12);
470
+ composer.addPass(bloom);
471
+
472
+ // ── State ─────────────────────────────────────────────────────
473
+ let groups = {};
474
+ let activeKeys = [];
475
+ let walkLines = [];
476
+ let selectedIdx = -1;
477
+ let compareMode = false;
478
+ let demoRunning = false;
479
+ let viewMode = 'cylinder'; // 'cylinder' (3D) or 'flat' (2D circuit)
480
+ let tier2Open = false;
481
+
482
+ // ── PRNG ──────────────────────────────────────────────────────
483
+ function mulberry32(seed) {
484
+ return () => {
485
+ seed |= 0; seed = seed + 0x6D2B79F5 | 0;
486
+ let t = Math.imul(seed ^ seed >>> 15, 1 | seed);
487
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
488
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
489
+ };
490
+ }
491
+
492
+ function getStageForDepth(stageBands, depth) {
493
+ for (const [d0, d1, s] of stageBands) {
494
+ if (depth >= d0 && depth <= d1) return s;
495
+ }
496
+ return 4;
497
+ }
498
+
499
+ // ── Build model group ─────────────────────────────────────────
500
+ function buildGroup(key, offsetX = 0) {
501
+ if (groups[key]) {
502
+ scene.remove(groups[key].group);
503
+ groups[key].group.traverse(o => {
504
+ if (o.geometry) o.geometry.dispose();
505
+ if (o.material) {
506
+ if (Array.isArray(o.material)) o.material.forEach(m => m.dispose());
507
+ else o.material.dispose();
508
+ }
509
+ });
510
+ delete groups[key];
511
+ }
512
+
513
+ const d = VINDEX_DATA[key];
514
+ const group = new THREE.Group();
515
+ group.position.x = offsetX;
516
+
517
+ const L = d.layers, F = d.F, SPACING = 0.80, R = 4.6;
518
+ const totalH = (L - 1) * SPACING;
519
+ const featureMeta = [];
520
+ const FLAT_W = R * 2.4; // horizontal span for features in flat mode
521
+
522
+ // ── Stage bands ─────────────────────────────────────────────
523
+ if (!d.dissolved) {
524
+ d.stageBands.forEach(([d0, d1, si]) => {
525
+ const y0 = d0 * totalH - totalH / 2;
526
+ const y1 = d1 * totalH - totalH / 2;
527
+ const h = Math.max(y1 - y0, 0.08);
528
+ if (viewMode === 'flat') {
529
+ // Horizontal stripe across the circuit
530
+ const geo = new THREE.PlaneGeometry(FLAT_W * 1.15, h);
531
+ const mat = new THREE.MeshBasicMaterial({ color: STAGE_COLORS[si], transparent: true, opacity: 0.085, depthWrite: false, side: THREE.DoubleSide });
532
+ const mesh = new THREE.Mesh(geo, mat);
533
+ mesh.position.set(0, (y0 + y1) / 2, -0.02);
534
+ group.add(mesh);
535
+ } else {
536
+ // 3D box slab + capping rings
537
+ const geo = new THREE.BoxGeometry(R * 2.5, h, R * 2.5);
538
+ const mat = new THREE.MeshBasicMaterial({ color: STAGE_COLORS[si], transparent: true, opacity: 0.032, depthWrite: false, side: THREE.DoubleSide });
539
+ const mesh = new THREE.Mesh(geo, mat);
540
+ mesh.position.y = (y0 + y1) / 2;
541
+ group.add(mesh);
542
+ [y0, y1].forEach(y => {
543
+ const rg = new THREE.RingGeometry(R * 1.22, R * 1.24, 64);
544
+ const rm = new THREE.MeshBasicMaterial({ color: STAGE_COLORS[si], transparent: true, opacity: 0.20, side: THREE.DoubleSide, depthWrite: false });
545
+ const ring = new THREE.Mesh(rg, rm);
546
+ ring.position.y = y; ring.rotation.x = -Math.PI / 2;
547
+ group.add(ring);
548
+ });
549
+ }
550
+ });
551
+ }
552
+
553
+ // ── Layer guides (rings in 3D, lines in flat) ───────────────
554
+ for (let li = 0; li < L; li++) {
555
+ const y = (li / (L - 1)) * totalH - totalH / 2;
556
+ let lineColor, lineOpacity;
557
+ if (d.dissolved) { lineColor = new THREE.Color(0x330a0a); lineOpacity = 0.22; }
558
+ else if (d.c4 !== null && d.c4 > 1.0) { lineColor = new THREE.Color(0xff4400); lineOpacity = 0.24; }
559
+ else if (d.c4 !== null) {
560
+ const t = Math.min(d.c4 / 0.55, 1.0);
561
+ lineColor = new THREE.Color().lerpColors(new THREE.Color(0x1a2d6e), new THREE.Color(0xff8822), t);
562
+ lineOpacity = 0.14;
563
+ } else { lineColor = new THREE.Color(0x1e2a50); lineOpacity = 0.10; }
564
+
565
+ if (viewMode === 'flat') {
566
+ const lineGeo = new THREE.BufferGeometry().setFromPoints([
567
+ new THREE.Vector3(-FLAT_W / 2, y, -0.05),
568
+ new THREE.Vector3( FLAT_W / 2, y, -0.05),
569
+ ]);
570
+ const lineMat = new THREE.LineBasicMaterial({ color: lineColor, transparent: true, opacity: lineOpacity });
571
+ group.add(new THREE.Line(lineGeo, lineMat));
572
+ } else {
573
+ const rg = new THREE.RingGeometry(R * 0.88, R, 64);
574
+ const rm = new THREE.MeshBasicMaterial({ color: lineColor, transparent: true, opacity: lineOpacity, side: THREE.DoubleSide, depthWrite: false });
575
+ const ring = new THREE.Mesh(rg, rm); ring.position.y = y; ring.rotation.x = -Math.PI / 2;
576
+ group.add(ring);
577
+ }
578
+ }
579
+
580
+ // ── Feature points (InstancedMesh, layout depends on viewMode) ──
581
+ const totalF = L * F;
582
+ const geo = new THREE.SphereGeometry(0.08, 7, 7);
583
+ const mat = new THREE.MeshStandardMaterial({ roughness: 0.3, metalness: 0.05 });
584
+ const instanceMesh = new THREE.InstancedMesh(geo, mat, totalF);
585
+ instanceMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
586
+
587
+ const dummy = new THREE.Object3D();
588
+ const color = new THREE.Color();
589
+ const rng = mulberry32(42 + key.charCodeAt(0) * 7);
590
+
591
+ let idx = 0;
592
+ for (let li = 0; li < L; li++) {
593
+ const y = (li / (L - 1)) * totalH - totalH / 2;
594
+ const depth = li / (L - 1);
595
+ const stage = d.dissolved ? 5 : getStageForDepth(d.stageBands, depth);
596
+
597
+ for (let fi = 0; fi < F; fi++) {
598
+ let x, z;
599
+ if (viewMode === 'flat') {
600
+ // 2D circuit: horizontal line per layer; features spread left-to-right.
601
+ // Dissolved: jitter the ordering to show lack of structure.
602
+ if (d.dissolved) {
603
+ x = (rng() - 0.5) * FLAT_W;
604
+ } else {
605
+ x = ((fi + 0.5) / F - 0.5) * FLAT_W;
606
+ }
607
+ z = 0;
608
+ } else if (d.dissolved) {
609
+ const angle = rng() * Math.PI * 2;
610
+ const r = R * 0.82 * Math.sqrt(rng());
611
+ x = r * Math.cos(angle); z = r * Math.sin(angle);
612
+ } else {
613
+ const theta = fi * 2.399963229;
614
+ const r = R * 0.84 * Math.sqrt((fi + 0.5) / F);
615
+ x = r * Math.cos(theta); z = r * Math.sin(theta);
616
+ }
617
+ const jitter = viewMode === 'flat'
618
+ ? (rng() - 0.5) * (d.dissolved ? 0.10 : 0.04)
619
+ : (rng() - 0.5) * (d.dissolved ? 0.18 : 0.06);
620
+ dummy.position.set(x, y + jitter, z);
621
+ const baseScale = viewMode === 'flat' ? 0.85 : 1.0;
622
+ dummy.scale.setScalar(baseScale * (d.dissolved ? (0.6 + rng() * 0.9) : (0.8 + rng() * 0.65)));
623
+ dummy.updateMatrix();
624
+ instanceMesh.setMatrixAt(idx, dummy.matrix);
625
+ color.copy(STAGE_COLORS[stage]);
626
+ color.offsetHSL(0, (rng() - 0.5) * 0.12, (rng() - 0.5) * 0.14);
627
+ instanceMesh.setColorAt(idx, color);
628
+ featureMeta[idx] = { li, fi, stage, y, x, z, key };
629
+ idx++;
630
+ }
631
+ }
632
+ instanceMesh.instanceMatrix.needsUpdate = true;
633
+ if (instanceMesh.instanceColor) instanceMesh.instanceColor.needsUpdate = true;
634
+ group.add(instanceMesh);
635
+
636
+ // ── Inter-layer edges (flat mode only) — show top features connecting consecutive layers ──
637
+ if (viewMode === 'flat' && !d.dissolved) {
638
+ const edgePts = [];
639
+ const edgeColors = [];
640
+ const TOP_K = Math.min(8, F); // strongest few features per layer
641
+ for (let li = 0; li < L - 1; li++) {
642
+ const y0 = (li / (L - 1)) * totalH - totalH / 2;
643
+ const y1 = ((li+1) / (L - 1)) * totalH - totalH / 2;
644
+ const stage0 = getStageForDepth(d.stageBands, li / (L - 1));
645
+ const c = STAGE_COLORS[stage0];
646
+ for (let k = 0; k < TOP_K; k++) {
647
+ const x0 = ((k + 0.5) / F - 0.5) * FLAT_W;
648
+ const x1 = ((k + 0.5) / F - 0.5) * FLAT_W;
649
+ edgePts.push(x0, y0, 0, x1, y1, 0);
650
+ edgeColors.push(c.r, c.g, c.b, c.r, c.g, c.b);
651
+ }
652
+ }
653
+ const eg = new THREE.BufferGeometry();
654
+ eg.setAttribute('position', new THREE.Float32BufferAttribute(edgePts, 3));
655
+ eg.setAttribute('color', new THREE.Float32BufferAttribute(edgeColors, 3));
656
+ const em = new THREE.LineBasicMaterial({ vertexColors: true, transparent: true, opacity: 0.18 });
657
+ group.add(new THREE.LineSegments(eg, em));
658
+ }
659
+
660
+ scene.add(group);
661
+ groups[key] = { group, instanceMesh, featureMeta, totalH };
662
+ return groups[key];
663
+ }
664
+
665
+ // ── Info panel update ─────────────────────────────────────────
666
+ function c4Class(v) {
667
+ if (v === null) return 'dim';
668
+ if (v < 0.06) return 'good';
669
+ if (v < 0.5) return 'warn';
670
+ return 'alert';
671
+ }
672
+
673
+ function barWidth(v, max) {
674
+ if (v === null) return 0;
675
+ return Math.min((v / max) * 100, 100);
676
+ }
677
+
678
+ function updateInfoPanel(key) {
679
+ const d = VINDEX_DATA[key];
680
+
681
+ document.getElementById('m-name').textContent = d.name;
682
+
683
+ const hfEl = document.getElementById('m-hf-link');
684
+ if (d.hfRepo) {
685
+ hfEl.href = `https://huggingface.co/${d.hfRepo}`;
686
+ hfEl.style.display = '';
687
+ } else {
688
+ hfEl.style.display = 'none';
689
+ }
690
+
691
+ document.getElementById('m-meta').textContent = `${d.org} · ${d.params} · ${d.precision} · ${d.layers}L`;
692
+
693
+ // C4
694
+ const c4El = document.getElementById('m-c4-val');
695
+ const c4Chip = document.getElementById('m-c4-chip');
696
+ if (d.c4 !== null) {
697
+ c4El.textContent = d.c4.toFixed(3);
698
+ c4El.className = 'c4-val ' + c4Class(d.c4);
699
+ if (d.c4 < 0.06) {
700
+ c4Chip.textContent = '✓ universal constant';
701
+ c4Chip.className = 'c4-chip chip-good';
702
+ } else if (d.c4 > 1.0) {
703
+ c4Chip.textContent = `⚠️ ${(d.c4/0.042).toFixed(0)}× anomaly`;
704
+ c4Chip.className = 'c4-chip chip-alert';
705
+ } else {
706
+ const mult = (d.c4 / 0.042).toFixed(0);
707
+ c4Chip.textContent = `↑ ${mult}× constant`;
708
+ c4Chip.className = 'c4-chip chip-warn';
709
+ }
710
+ } else {
711
+ c4El.textContent = 'N/A';
712
+ c4El.className = 'c4-val';
713
+ c4Chip.textContent = 'Phase 2 pending';
714
+ c4Chip.className = 'c4-chip chip-warn';
715
+ }
716
+
717
+ // C5 stage bar
718
+ const stageFill = document.getElementById('m-stage-fill');
719
+ const c5Val = document.getElementById('m-c5-val');
720
+ if (d.c5 !== null) {
721
+ const pct = (d.c5 / d.c5max) * 100;
722
+ stageFill.style.width = pct + '%';
723
+ if (d.c5 === 1) {
724
+ stageFill.style.background = '#ff4444';
725
+ c5Val.textContent = '💀 C5=1';
726
+ c5Val.className = 'stage-bar-val';
727
+ c5Val.style.color = '#ff4444';
728
+ } else {
729
+ stageFill.style.background = '#44ffaa';
730
+ c5Val.textContent = `${d.c5} stages`;
731
+ c5Val.className = 'stage-bar-val';
732
+ c5Val.style.color = '#44ffaa';
733
+ }
734
+ } else {
735
+ stageFill.style.width = '0%';
736
+ c5Val.textContent = 'N/A';
737
+ c5Val.style.color = '';
738
+ }
739
+
740
+ // Gate 3
741
+ const gate3Icon = document.getElementById('m-gate3-icon');
742
+ const gate3Label = document.getElementById('m-gate3-label');
743
+ const g3 = d.gate3;
744
+ if (g3 === 'PASS') {
745
+ gate3Icon.textContent = '✅';
746
+ gate3Label.textContent = `Gate-3: ${d.gate3note}`;
747
+ gate3Label.style.color = '#44ffaa'; gate3Label.style.opacity = '1';
748
+ } else if (g3 === 'PENDING') {
749
+ gate3Icon.textContent = '⏳';
750
+ gate3Label.textContent = `Gate-3: ${d.gate3note}`;
751
+ gate3Label.style.color = ''; gate3Label.style.opacity = '0.5';
752
+ } else {
753
+ gate3Icon.textContent = '○';
754
+ gate3Label.textContent = `Gate-3: ${d.gate3note}`;
755
+ gate3Label.style.color = ''; gate3Label.style.opacity = '0.35';
756
+ }
757
+
758
+ // Anomaly banner
759
+ const anomalyEl = document.getElementById('m-anomaly');
760
+ if (d.anomaly) {
761
+ anomalyEl.textContent = d.anomaly;
762
+ anomalyEl.style.display = 'block';
763
+ anomalyEl.style.background = d.anomalyType === 'dead'
764
+ ? 'rgba(255,40,40,0.1)' : 'rgba(255,120,50,0.1)';
765
+ anomalyEl.style.borderColor = d.anomalyType === 'dead'
766
+ ? 'rgba(255,40,40,0.3)' : 'rgba(255,120,50,0.3)';
767
+ anomalyEl.style.color = d.anomalyType === 'dead' ? '#ff7777' : '#ffaa66';
768
+ } else {
769
+ anomalyEl.style.display = 'none';
770
+ }
771
+
772
+ // Tier 2 metrics
773
+ function setBar(barId, valId, v, max, cls) {
774
+ document.getElementById(barId).style.width = barWidth(v, max) + '%';
775
+ const el = document.getElementById(valId);
776
+ el.textContent = v !== null ? v.toFixed(3) : 'N/A';
777
+ el.className = 'm-val ' + (v !== null ? (cls || '') : 'dim');
778
+ }
779
+ setBar('b-c1','v-c1', d.c1, 0.5, d.c1 > 0.3 ? 'warn' : 'good');
780
+ setBar('b-c2','v-c2', d.c2, 1.0, d.c2 !== null && d.c2 > 0.85 ? 'good' : '');
781
+ setBar('b-c3','v-c3', d.c3, 1.0, d.c3 !== null && d.c3 > 0.75 ? 'good' : 'warn');
782
+ setBar('b-c4','v-c4', d.c4 !== null ? Math.min(d.c4, 2.0) : null, 2.0, c4Class(d.c4));
783
+ document.getElementById('b-spec').style.width = (d.spectrumScore * 100) + '%';
784
+ const specEl = document.getElementById('v-spec');
785
+ specEl.textContent = d.spectrumScore > 0.7 ? 'power-law' : (d.spectrumScore > 0.2 ? 'partial' : 'flat');
786
+ specEl.className = 'm-val ' + (d.spectrumScore > 0.7 ? 'good' : d.spectrumScore > 0.2 ? 'warn' : 'alert');
787
+
788
+ const shortidEl = document.getElementById('v-shortid');
789
+ shortidEl.textContent = d.shortId || '—';
790
+
791
+ document.getElementById('copy-shortid').onclick = () => {
792
+ const txt = d.hfRepo ? `https://huggingface.co/${d.hfRepo}` : (d.shortId || '');
793
+ navigator.clipboard.writeText(txt).catch(() => {});
794
+ };
795
+ }
796
+
797
+ // ── Compare panel ─────────────────────────────────────────────
798
+ function makeCmpCol(colId, key) {
799
+ const d = VINDEX_DATA[key];
800
+ const col = document.getElementById(colId);
801
+ col.innerHTML = '';
802
+
803
+ const title = document.createElement('div');
804
+ title.className = 'cmp-title';
805
+ title.textContent = d.name;
806
+ col.appendChild(title);
807
+
808
+ const meta = document.createElement('div');
809
+ meta.className = 'cmp-meta';
810
+ meta.textContent = `${d.org} · ${d.params} · ${d.precision}`;
811
+ col.appendChild(meta);
812
+
813
+ const rows = [
814
+ ['C4 Temp', d.c4 !== null ? d.c4.toFixed(3) : 'N/A', c4Class(d.c4)],
815
+ ['C5 Stages', d.c5 !== null ? (d.c5 === 1 ? '💀 1' : String(d.c5)) : 'N/A', d.c5 === 1 ? 'alert' : 'good'],
816
+ ['C3 Coherence', d.c3 !== null ? d.c3.toFixed(3) : 'N/A', d.c3 !== null && d.c3 > 0.75 ? 'good' : 'warn'],
817
+ ['C1 Sparsity', d.c1 !== null ? d.c1.toFixed(3) : 'N/A', ''],
818
+ ['Spectrum', d.spectrumScore > 0.7 ? 'power-law ✓' : (d.spectrumScore > 0.2 ? 'partial' : 'flat ⚠️'), d.spectrumScore > 0.7 ? 'good' : 'alert'],
819
+ ];
820
+ rows.forEach(([label, val, cls]) => {
821
+ const row = document.createElement('div');
822
+ row.className = 'cmp-metric';
823
+ const lEl = document.createElement('span');
824
+ lEl.style.opacity = '0.45'; lEl.textContent = label;
825
+ const vEl = document.createElement('span');
826
+ vEl.className = 'cmp-val ' + cls;
827
+ vEl.textContent = val;
828
+ row.appendChild(lEl); row.appendChild(vEl);
829
+ col.appendChild(row);
830
+ });
831
+ }
832
+
833
+ // ── Show single model ─────────────────────────────────────────
834
+ function showSingle(key) {
835
+ compareMode = false;
836
+ document.getElementById('compare-btn').classList.remove('active');
837
+ document.getElementById('compare-panel').style.display = 'none';
838
+ document.getElementById('info-panel').style.display = '';
839
+ document.getElementById('legend').style.display = '';
840
+
841
+ Object.keys(groups).filter(k => k !== key).forEach(k => {
842
+ scene.remove(groups[k].group); delete groups[k];
843
+ });
844
+ clearWalk();
845
+ activeKeys = [key];
846
+ buildGroup(key, 0);
847
+ updateInfoPanel(key);
848
+
849
+ const L = VINDEX_DATA[key].layers;
850
+ if (viewMode === 'flat') {
851
+ // Need to fit (L-1) * 0.80 vertical units; FOV 52 -> visible_h ~ 0.976 * z
852
+ const totalH = (L - 1) * 0.80;
853
+ camera.position.set(0, 0, Math.max(20, totalH * 1.25 + 6));
854
+ } else {
855
+ camera.position.set(0, 2, 20 + L * 0.22);
856
+ }
857
+ controls.target.set(0, 0, 0);
858
+ controls.update();
859
+ }
860
+
861
+ // ── Compare mode ──────────────────────────────────────────────
862
+ function showCompare(keyA, keyB) {
863
+ compareMode = true;
864
+ document.getElementById('compare-btn').classList.add('active');
865
+ document.getElementById('compare-panel').style.display = '';
866
+ document.getElementById('info-panel').style.display = 'none';
867
+ document.getElementById('legend').style.display = 'none';
868
+
869
+ Object.keys(groups).filter(k => k !== keyA && k !== keyB).forEach(k => {
870
+ scene.remove(groups[k].group); delete groups[k];
871
+ });
872
+ clearWalk();
873
+ activeKeys = [keyA, keyB];
874
+ buildGroup(keyA, -13);
875
+ buildGroup(keyB, 13);
876
+ makeCmpCol('cmp-a', keyA);
877
+ makeCmpCol('cmp-b', keyB);
878
+
879
+ const maxL = Math.max(VINDEX_DATA[keyA].layers, VINDEX_DATA[keyB].layers);
880
+ if (viewMode === 'flat') {
881
+ const totalH = (maxL - 1) * 0.80;
882
+ camera.position.set(0, 0, Math.max(28, totalH * 1.4 + 8));
883
+ } else {
884
+ camera.position.set(0, 2, 30 + maxL * 0.18);
885
+ }
886
+ controls.target.set(0, 0, 0);
887
+ controls.update();
888
+ }
889
+
890
+ // ── Walk traces ───────────────────────────────────────────────
891
+ function clearWalk() {
892
+ walkLines.forEach(l => { scene.remove(l); l.geometry.dispose(); l.material.dispose(); });
893
+ walkLines = [];
894
+ selectedIdx = -1;
895
+ }
896
+
897
+ function traceWalk(globalIdx, instanceMesh, featureMeta, groupObj) {
898
+ clearWalk();
899
+ const meta = featureMeta[globalIdx];
900
+ if (!meta) return;
901
+ const d = VINDEX_DATA[meta.key];
902
+ const totalH = (d.layers - 1) * 0.80;
903
+ const rng = mulberry32(globalIdx * 31337);
904
+ const offsetX = groupObj.position.x;
905
+ let px = meta.x + offsetX, py = meta.y, pz = meta.z;
906
+ const stageColor = STAGE_COLORS[meta.stage];
907
+ const steps = d.dissolved ? 6 : 10;
908
+ for (let li = meta.li + 1; li < Math.min(meta.li + steps, d.layers); li++) {
909
+ const y = (li / (d.layers - 1)) * totalH - totalH / 2;
910
+ let x, z;
911
+ if (d.dissolved) {
912
+ x = (rng() - 0.5) * 8 + offsetX; z = (rng() - 0.5) * 8;
913
+ } else {
914
+ const angle = Math.atan2(pz, px - offsetX) + (rng() - 0.5) * 0.3;
915
+ const r = Math.sqrt((px-offsetX)**2 + pz**2);
916
+ x = r * Math.cos(angle) + (rng()-0.5)*0.25 + offsetX;
917
+ z = r * Math.sin(angle) + (rng()-0.5)*0.25;
918
+ }
919
+ const pts = [new THREE.Vector3(px,py,pz), new THREE.Vector3(x,y,z)];
920
+ const line = new THREE.Line(
921
+ new THREE.BufferGeometry().setFromPoints(pts),
922
+ new THREE.LineBasicMaterial({ color: stageColor, transparent: true, opacity: d.dissolved ? 0.22 : (0.55 - (li-meta.li)*0.04) })
923
+ );
924
+ scene.add(line); walkLines.push(line);
925
+ px = x; py = y; pz = z;
926
+ }
927
+ instanceMesh.setColorAt(globalIdx, new THREE.Color(0xffffff));
928
+ instanceMesh.instanceColor.needsUpdate = true;
929
+ selectedIdx = globalIdx;
930
+ }
931
+
932
+ // ── Tooltip ───────────────────────────────────────────────────
933
+ const tooltip = document.getElementById('tooltip');
934
+ function showTooltip(ex, ey, meta) {
935
+ const d = VINDEX_DATA[meta.key];
936
+ const stage = STAGE_NAMES[meta.stage];
937
+ const depth = ((meta.li / (d.layers - 1)) * 100).toFixed(0);
938
+ tooltip.innerHTML = '';
939
+ [[`${d.name}`, 'bold'], [`Layer ${meta.li} (${depth}% depth) · Feature ${meta.fi}`, ''], [`Stage: ${stage}`, meta.stage === 5 ? 'color:#ff4444' : 'color:#44ffaa']].forEach(([text, style]) => {
940
+ const el = document.createElement('div');
941
+ el.textContent = text;
942
+ if (style.includes(':')) el.setAttribute('style', style);
943
+ else if (style === 'bold') el.style.fontWeight = 'bold';
944
+ tooltip.appendChild(el);
945
+ });
946
+ tooltip.style.left = (ex + 16) + 'px';
947
+ tooltip.style.top = (ey - 52) + 'px';
948
+ tooltip.style.display = 'block';
949
+ }
950
+
951
+ // ── Demo sequence ─────────────────────────────────────────────
952
+ const demoOverlay = document.getElementById('demo-overlay');
953
+ function setDemoText(text) {
954
+ demoOverlay.textContent = text;
955
+ demoOverlay.style.display = text ? 'block' : 'none';
956
+ }
957
+
958
+ async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
959
+
960
+ async function runDemo() {
961
+ if (demoRunning) return;
962
+ demoRunning = true;
963
+ document.getElementById('demo-btn').textContent = '■ Stop';
964
+ controls.enabled = false;
965
+
966
+ // 0-2s: materialize Qwen3-8B
967
+ showSingle('qwen3-8b');
968
+ await sleep(100);
969
+ setDemoText('Each column: a transformer model\'s learned features, organized by layer');
970
+ camera.position.set(0, -6, 28);
971
+ await sleep(2200);
972
+
973
+ // 2-4s: orbit
974
+ setDemoText('128 features per layer, arranged by singular value direction');
975
+ camera.position.set(12, 3, 22);
976
+ await sleep(2000);
977
+
978
+ // 4-6s: show stage bands label
979
+ setDemoText('Four circuit stages emerge at consistent relative depths — across every architecture');
980
+ camera.position.set(0, 4, 22);
981
+ await sleep(2400);
982
+
983
+ // 6-8s: zoom entity zone
984
+ setDemoText('At ~50% depth: entity commitment. The model decides what it\'s talking about.');
985
+ camera.position.set(3, 0, 14);
986
+ controls.target.set(0, 0, 0);
987
+ await sleep(2400);
988
+
989
+ // 8-10s: compare mode reveal
990
+ setDemoText('');
991
+ await sleep(300);
992
+ showCompare('qwen3-8b', 'bonsai-1bit');
993
+ camera.position.set(0, 2, 36);
994
+ await sleep(400);
995
+ setDemoText('Until the model is 1-bit. The circuit dissolves entirely.');
996
+ await sleep(2200);
997
+
998
+ // 10-12s: hold + tagline
999
+ setDemoText('Universal structure. Measurable. Editable.');
1000
+ await sleep(2500);
1001
+
1002
+ // done
1003
+ setDemoText('');
1004
+ controls.enabled = true;
1005
+ demoRunning = false;
1006
+ document.getElementById('demo-btn').textContent = '▶ Demo';
1007
+ }
1008
+
1009
+ // ── Raycasting ────────────────────────────────────────────────
1010
+ const raycaster = new THREE.Raycaster();
1011
+ const mouse = new THREE.Vector2();
1012
+
1013
+ renderer.domElement.addEventListener('click', e => {
1014
+ if (demoRunning) return;
1015
+ mouse.x = (e.clientX / innerWidth) * 2 - 1;
1016
+ mouse.y = -(e.clientY / innerHeight) * 2 + 1;
1017
+ raycaster.setFromCamera(mouse, camera);
1018
+ let bestHit = null, bestMesh = null, bestMeta = null, bestGroup = null;
1019
+ for (const key of activeKeys) {
1020
+ const g = groups[key];
1021
+ if (!g) continue;
1022
+ const hits = raycaster.intersectObject(g.instanceMesh);
1023
+ if (hits.length > 0 && (!bestHit || hits[0].distance < bestHit.distance)) {
1024
+ bestHit = hits[0]; bestMesh = g.instanceMesh; bestMeta = g.featureMeta; bestGroup = g.group;
1025
+ }
1026
+ }
1027
+ if (bestHit) {
1028
+ traceWalk(bestHit.instanceId, bestMesh, bestMeta, bestGroup);
1029
+ showTooltip(e.clientX, e.clientY, bestMeta[bestHit.instanceId]);
1030
+ } else {
1031
+ clearWalk(); tooltip.style.display = 'none';
1032
+ }
1033
+ });
1034
+ renderer.domElement.addEventListener('mousemove', () => { tooltip.style.display = 'none'; });
1035
+
1036
+ // ── Entity search ──────────────────────────────────────────────
1037
+ // Two-tier search backend:
1038
+ // 1. REAL: `search_index.json` per model from `notebooks/build_search_index.py` Modal pipeline —
1039
+ // ~5000 whole-word tokens, each mapped to top-8 (layer, feature, score) tuples from probing
1040
+ // the actual transformer's gate*up activations projected onto the vindex SVD basis.
1041
+ // Loaded lazily per model; cached in `realIndices`. Files served from /search-indexes/{slug}.json.
1042
+ // 2. DEMO fallback (below): hand-curated handful of tokens (Paris→capital from the Gate-3 result,
1043
+ // plus illustrative matches) — used when the real index hasn't been built+deployed for a model.
1044
+
1045
+ // Map viewer model keys to slugs that match build_search_index.py's slug() output.
1046
+ const MODEL_KEY_TO_SLUG = {
1047
+ 'gemma-4-e2b': 'google-gemma-4-e2b-it',
1048
+ 'qwen3-0.6b': 'qwen-qwen3-0-6b',
1049
+ 'qwen3-8b': 'qwen-qwen3-8b',
1050
+ 'qwen3.6-35b': 'qwen-qwen3-6-35b-a3b',
1051
+ 'llama-3.1-8b': 'meta-llama-llama-3-1-8b-instruct',
1052
+ 'gpt-oss-120b': 'openai-gpt-oss-120b',
1053
+ 'ministral-3b': 'mistralai-ministral-3-3b-instruct-2512',
1054
+ 'medgemma-4b': 'google-medgemma-1-5-4b-it',
1055
+ 'bonsai-1bit': 'prism-ml-bonsai-8b-unpacked',
1056
+ };
1057
+ const realIndices = new Map(); // slug → { tokens, matches } loaded JSON
1058
+ const realIndexAttempts = new Set(); // slugs already tried (don't refetch on failure)
1059
+
1060
+ async function loadRealSearchIndex(modelKey) {
1061
+ const slug = MODEL_KEY_TO_SLUG[modelKey];
1062
+ if (!slug || realIndices.has(slug) || realIndexAttempts.has(slug)) return;
1063
+ realIndexAttempts.add(slug);
1064
+ try {
1065
+ const url = `/search-indexes/${slug}.json`;
1066
+ const res = await fetch(url);
1067
+ if (!res.ok) {
1068
+ console.info(`[search] no real index for ${modelKey} — using demo dataset`);
1069
+ return;
1070
+ }
1071
+ const idx = await res.json();
1072
+ realIndices.set(slug, idx);
1073
+ console.info(`[search] loaded real index: ${idx.n_tokens} tokens for ${modelKey}`);
1074
+ // Re-run current query against the new index
1075
+ const q = document.getElementById('search-input').value.trim();
1076
+ if (q) highlightSearch(q);
1077
+ } catch (err) {
1078
+ console.warn(`[search] fetch failed for ${modelKey}:`, err);
1079
+ }
1080
+ }
1081
+
1082
+ // Look up matches for a query in the real index. Strategy:
1083
+ // 1. exact token match → return its top-K (layer, feature, score)
1084
+ // 2. else: substring match across all tokens, collect from up to 5 closest
1085
+ // 3. returns array of { layerNorm, fi, label } compatible with demo format
1086
+ function lookupReal(modelKey, query) {
1087
+ const slug = MODEL_KEY_TO_SLUG[modelKey];
1088
+ const idx = realIndices.get(slug);
1089
+ if (!idx) return null;
1090
+ const q = query.toLowerCase().trim();
1091
+ const hits = [];
1092
+ // Exact match first
1093
+ if (idx.matches[q]) {
1094
+ for (const m of idx.matches[q]) {
1095
+ hits.push({
1096
+ layerNorm: m.layer / (idx.num_layers - 1),
1097
+ fi: m.feature,
1098
+ label: `score=${m.score} (real probe)`,
1099
+ });
1100
+ }
1101
+ return hits;
1102
+ }
1103
+ // Substring match — collect from up to 3 closest tokens
1104
+ const subMatches = idx.tokens.filter(t => t.includes(q) || q.includes(t)).slice(0, 3);
1105
+ for (const tok of subMatches) {
1106
+ for (const m of (idx.matches[tok] || []).slice(0, 4)) { // top-4 per substring hit
1107
+ hits.push({
1108
+ layerNorm: m.layer / (idx.num_layers - 1),
1109
+ fi: m.feature,
1110
+ label: `${tok} L${m.layer}/F${m.feature} score=${m.score}`,
1111
+ });
1112
+ }
1113
+ }
1114
+ return hits.length > 0 ? hits : null;
1115
+ }
1116
+
1117
+ // Demo dataset — small handcrafted index used when real index isn't deployed yet.
1118
+ // `layerNorm` is a fraction of total depth (0.0 = first layer, 1.0 = last). At runtime each
1119
+ // match is mapped to the model's actual layer range (round(layerNorm * (L-1))).
1120
+ // The Paris→capital match is sourced from the LarQL Gate-3 result on Gemma 4 E2B (feature
1121
+ // 11179 at layer 27 of 36 → normalized depth 27/35 ≈ 0.77). Other matches are illustrative:
1122
+ // they sit in plausible layer bands (entity zone 0.45–0.55, prediction zone 0.90+) and use
1123
+ // representative feature indices visible in the viewer's 128-feature spiral.
1124
+ const SEARCH_INDEX = {
1125
+ 'gemma-4-e2b': {
1126
+ 'paris': [{ layerNorm: 0.77, fi: 11, label: 'L27/F11179 — capital-of-X concept (Gate-3)' },
1127
+ { layerNorm: 0.62, fi: 8, label: 'Eiffel/landmark co-activation' }],
1128
+ 'capital': [{ layerNorm: 0.77, fi: 11, label: 'capital-of-X concept' },
1129
+ { layerNorm: 0.40, fi: 19, label: 'country routing' }],
1130
+ 'eiffel': [{ layerNorm: 0.62, fi: 8 }],
1131
+ 'france': [{ layerNorm: 0.40, fi: 19 }, { layerNorm: 0.55, fi: 22 }],
1132
+ 'python': [{ layerNorm: 0.50, fi: 27, label: 'code-block routing' },
1133
+ { layerNorm: 0.92, fi: 3, label: 'def/class token preference' }],
1134
+ 'code': [{ layerNorm: 0.50, fi: 27 }, { layerNorm: 0.30, fi: 33 }],
1135
+ 'doctor': [{ layerNorm: 0.50, fi: 41, label: 'medical entity cluster' }],
1136
+ 'einstein': [{ layerNorm: 0.50, fi: 47, label: 'physicist entity' }],
1137
+ },
1138
+ 'qwen3-8b': {
1139
+ 'paris': [{ layerNorm: 0.55, fi: 11 }],
1140
+ 'capital': [{ layerNorm: 0.55, fi: 11 }, { layerNorm: 0.42, fi: 19 }],
1141
+ 'python': [{ layerNorm: 0.50, fi: 27 }],
1142
+ 'code': [{ layerNorm: 0.50, fi: 27 }],
1143
+ },
1144
+ 'llama-3.1-8b': {
1145
+ 'paris': [{ layerNorm: 0.65, fi: 11 }],
1146
+ 'capital': [{ layerNorm: 0.65, fi: 11 }],
1147
+ },
1148
+ // Bonsai/BitNet 1-bit models: dissolved — no concentrated features to highlight
1149
+ 'bonsai-1bit': { /* intentionally empty: dissolution means no feature locality */ },
1150
+ };
1151
+
1152
+ let savedColors = null; // Map<key, [Color, Color, ...]> — original instance colors per group
1153
+ let highlightedFeatureIdxs = []; // [{ key, globalIdx }]
1154
+ let searchHaloMeshes = []; // glowing ring meshes added per match
1155
+
1156
+ function _saveColorsIfNeeded() {
1157
+ if (savedColors) return;
1158
+ savedColors = new Map();
1159
+ for (const key of activeKeys) {
1160
+ const g = groups[key];
1161
+ if (!g || !g.instanceMesh.instanceColor) continue;
1162
+ const arr = g.instanceMesh.instanceColor.array;
1163
+ savedColors.set(key, new Float32Array(arr)); // copy
1164
+ }
1165
+ }
1166
+
1167
+ function clearSearch() {
1168
+ // Restore original colors
1169
+ if (savedColors) {
1170
+ for (const key of activeKeys) {
1171
+ const g = groups[key];
1172
+ const original = savedColors.get(key);
1173
+ if (!g || !original) continue;
1174
+ g.instanceMesh.instanceColor.array.set(original);
1175
+ g.instanceMesh.instanceColor.needsUpdate = true;
1176
+ }
1177
+ savedColors = null;
1178
+ }
1179
+ // Reset scales of previously-highlighted features
1180
+ for (const h of highlightedFeatureIdxs) {
1181
+ const g = groups[h.key];
1182
+ if (!g) continue;
1183
+ const m = new THREE.Matrix4();
1184
+ g.instanceMesh.getMatrixAt(h.globalIdx, m);
1185
+ const pos = new THREE.Vector3(), q = new THREE.Quaternion(), s = new THREE.Vector3();
1186
+ m.decompose(pos, q, s);
1187
+ s.setScalar(1.0);
1188
+ m.compose(pos, q, s);
1189
+ g.instanceMesh.setMatrixAt(h.globalIdx, m);
1190
+ }
1191
+ for (const key of activeKeys) {
1192
+ const g = groups[key];
1193
+ if (g) g.instanceMesh.instanceMatrix.needsUpdate = true;
1194
+ }
1195
+ highlightedFeatureIdxs = [];
1196
+ // Remove halo meshes
1197
+ for (const mesh of searchHaloMeshes) {
1198
+ scene.remove(mesh);
1199
+ mesh.geometry.dispose();
1200
+ mesh.material.dispose();
1201
+ }
1202
+ searchHaloMeshes = [];
1203
+ document.getElementById('search-status').textContent = '';
1204
+ }
1205
+
1206
+ function highlightSearch(query) {
1207
+ const q = (query || '').trim().toLowerCase();
1208
+ clearSearch();
1209
+ if (!q) return;
1210
+
1211
+ let totalMatches = 0;
1212
+ const matchInfos = [];
1213
+ _saveColorsIfNeeded();
1214
+
1215
+ const HIGHLIGHT_COLOR = new THREE.Color(0xffffff); // pure white pops against everything
1216
+ const PULSE_SCALE = 6.0;
1217
+
1218
+ for (const key of activeKeys) {
1219
+ // Try real index first, then fall back to demo dataset
1220
+ let matches = lookupReal(key, q);
1221
+ if (!matches) {
1222
+ const idx = SEARCH_INDEX[key] || {};
1223
+ matches = idx[q] || [];
1224
+ if (matches.length === 0) {
1225
+ for (const k of Object.keys(idx)) {
1226
+ if (k.includes(q) || q.includes(k)) matches = matches.concat(idx[k]);
1227
+ }
1228
+ }
1229
+ }
1230
+ if (matches.length === 0) continue;
1231
+
1232
+ const g = groups[key];
1233
+ if (!g) continue;
1234
+ const L = VINDEX_DATA[key].layers;
1235
+ const F = VINDEX_DATA[key].F;
1236
+ const offsetX = g.group.position.x;
1237
+
1238
+ for (const m of matches) {
1239
+ const li = Math.round(m.layerNorm * (L - 1));
1240
+ const fi = Math.min(Math.max(m.fi, 0), F - 1);
1241
+ const globalIdx = li * F + fi;
1242
+ if (globalIdx < 0 || globalIdx >= g.featureMeta.length) continue;
1243
+ g.instanceMesh.setColorAt(globalIdx, HIGHLIGHT_COLOR);
1244
+ // Scale up
1245
+ const matrix = new THREE.Matrix4();
1246
+ g.instanceMesh.getMatrixAt(globalIdx, matrix);
1247
+ const pos = new THREE.Vector3(), quat = new THREE.Quaternion(), scl = new THREE.Vector3();
1248
+ matrix.decompose(pos, quat, scl);
1249
+ scl.setScalar(PULSE_SCALE);
1250
+ matrix.compose(pos, quat, scl);
1251
+ g.instanceMesh.setMatrixAt(globalIdx, matrix);
1252
+ highlightedFeatureIdxs.push({ key, globalIdx });
1253
+
1254
+ // Add a glowing halo ring at the feature's world position so it's findable at any zoom
1255
+ const meta = g.featureMeta[globalIdx];
1256
+ const haloGeo = new THREE.RingGeometry(0.55, 0.85, 32);
1257
+ const haloMat = new THREE.MeshBasicMaterial({
1258
+ color: 0xffe060, transparent: true, opacity: 0.92, side: THREE.DoubleSide, depthWrite: false,
1259
+ });
1260
+ const halo = new THREE.Mesh(haloGeo, haloMat);
1261
+ halo.position.set(meta.x + offsetX, meta.y, meta.z);
1262
+ halo.lookAt(camera.position);
1263
+ halo.userData.isSearchHalo = true;
1264
+ halo.userData.featurePos = halo.position.clone();
1265
+ scene.add(halo);
1266
+ searchHaloMeshes.push(halo);
1267
+
1268
+ matchInfos.push(`${key} L${li}/F${fi}${m.label ? ` (${m.label.slice(0, 40)})` : ''}`);
1269
+ totalMatches++;
1270
+ }
1271
+ g.instanceMesh.instanceColor.needsUpdate = true;
1272
+ g.instanceMesh.instanceMatrix.needsUpdate = true;
1273
+ }
1274
+
1275
+ const status = document.getElementById('search-status');
1276
+ if (totalMatches === 0) {
1277
+ status.textContent = `no matches for "${q}"`;
1278
+ } else {
1279
+ status.textContent = `${totalMatches} match${totalMatches === 1 ? '' : 'es'}: ${matchInfos.slice(0, 3).join(', ')}${matchInfos.length > 3 ? '…' : ''}`;
1280
+ }
1281
+ }
1282
+
1283
+ // ── UI wiring ─────────────────────────────────────────────────
1284
+ const modelSelect = document.getElementById('model-select');
1285
+ const compareBtn = document.getElementById('compare-btn');
1286
+ const demoBtn = document.getElementById('demo-btn');
1287
+ const viewBtn = document.getElementById('view-btn');
1288
+ const searchInput = document.getElementById('search-input');
1289
+ const tier2Btn = document.getElementById('tier2-btn');
1290
+
1291
+ let _searchTimer = null;
1292
+ searchInput.addEventListener('input', e => {
1293
+ clearTimeout(_searchTimer);
1294
+ const v = e.target.value;
1295
+ _searchTimer = setTimeout(() => highlightSearch(v), 180);
1296
+ });
1297
+ searchInput.addEventListener('keydown', e => {
1298
+ if (e.key === 'Escape') { searchInput.value = ''; clearSearch(); }
1299
+ });
1300
+
1301
+ modelSelect.addEventListener('change', () => {
1302
+ if (demoRunning) return;
1303
+ // Drop stale highlight state — the InstancedMesh references are about to be disposed
1304
+ savedColors = null;
1305
+ highlightedFeatureIdxs = [];
1306
+ compareMode ? showCompare(modelSelect.value, 'bonsai-1bit') : showSingle(modelSelect.value);
1307
+ // Lazy-load the real search index for this model (no-op if already loaded or unavailable)
1308
+ loadRealSearchIndex(modelSelect.value);
1309
+ // Re-apply current search query against the new model
1310
+ if (searchInput.value.trim()) {
1311
+ setTimeout(() => highlightSearch(searchInput.value), 100);
1312
+ } else {
1313
+ document.getElementById('search-status').textContent = '';
1314
+ }
1315
+ });
1316
+ compareBtn.addEventListener('click', () => {
1317
+ if (demoRunning) return;
1318
+ compareMode ? showSingle(modelSelect.value) : showCompare(modelSelect.value, 'bonsai-1bit');
1319
+ });
1320
+ demoBtn.addEventListener('click', () => {
1321
+ if (demoRunning) {
1322
+ demoRunning = false; controls.enabled = true;
1323
+ demoBtn.textContent = '▶ Demo'; setDemoText('');
1324
+ showSingle(modelSelect.value);
1325
+ } else { runDemo(); }
1326
+ });
1327
+ viewBtn.addEventListener('click', () => {
1328
+ if (demoRunning) return;
1329
+ viewMode = (viewMode === 'cylinder') ? 'flat' : 'cylinder';
1330
+ viewBtn.textContent = (viewMode === 'flat') ? '🌀 3D View' : '🔌 2D Circuit';
1331
+ // Reposition camera + reconfigure controls for the new view
1332
+ if (viewMode === 'flat') {
1333
+ camera.position.set(0, 0, 28);
1334
+ controls.target.set(0, 0, 0);
1335
+ // Lock the orbit to a near-flat plane (allow only small tilt + pan + zoom)
1336
+ controls.minPolarAngle = Math.PI / 2 - 0.05;
1337
+ controls.maxPolarAngle = Math.PI / 2 + 0.05;
1338
+ controls.enableRotate = false;
1339
+ } else {
1340
+ camera.position.set(0, 4, 26);
1341
+ controls.target.set(0, 0, 0);
1342
+ controls.minPolarAngle = 0;
1343
+ controls.maxPolarAngle = Math.PI;
1344
+ controls.enableRotate = true;
1345
+ }
1346
+ controls.update();
1347
+ // Rebuild active groups with new layout
1348
+ if (compareMode) {
1349
+ showCompare(modelSelect.value, 'bonsai-1bit');
1350
+ } else {
1351
+ showSingle(modelSelect.value);
1352
+ }
1353
+ });
1354
+ tier2Btn.addEventListener('click', () => {
1355
+ tier2Open = !tier2Open;
1356
+ document.getElementById('tier2-metrics').className = tier2Open ? 'open' : '';
1357
+ tier2Btn.textContent = tier2Open ? 'METRICS ▴' : 'METRICS ▾';
1358
+ });
1359
+
1360
+ // ── Resize ────────────────────────────────────────────────────
1361
+ window.addEventListener('resize', () => {
1362
+ camera.aspect = innerWidth / innerHeight;
1363
+ camera.updateProjectionMatrix();
1364
+ renderer.setSize(innerWidth, innerHeight);
1365
+ composer.setSize(innerWidth, innerHeight);
1366
+ bloom.resolution.set(innerWidth, innerHeight);
1367
+ });
1368
+
1369
+ // ── Animate ───────────────────────────────────────────────────
1370
+ let t = 0;
1371
+ function animate() {
1372
+ requestAnimationFrame(animate);
1373
+ t += 0.003;
1374
+ if (!demoRunning) {
1375
+ for (const key of activeKeys) {
1376
+ const g = groups[key];
1377
+ if (g) g.group.rotation.y = Math.sin(t * 0.18) * 0.05;
1378
+ }
1379
+ }
1380
+ // Modulate bloom for "alive" feel
1381
+ bloom.strength = 0.7 + Math.sin(t * 0.6) * 0.08;
1382
+ // Billboard search halos to face the camera + pulse opacity
1383
+ if (searchHaloMeshes.length > 0) {
1384
+ const pulse = 0.65 + Math.abs(Math.sin(t * 4.0)) * 0.32;
1385
+ for (const halo of searchHaloMeshes) {
1386
+ halo.lookAt(camera.position);
1387
+ halo.material.opacity = pulse;
1388
+ }
1389
+ }
1390
+ controls.update();
1391
+ composer.render();
1392
+ }
1393
+
1394
+ // ── Init ──────────────────────────────────────────────────────
1395
+ showSingle('qwen3-8b');
1396
+ animate();
1397
+ // Try to fetch the real search index for the default model (silent if 404)
1398
+ loadRealSearchIndex('qwen3-8b');
1399
+ // URL param handling: ?view=flat → 2D circuit, ?autoplay → demo, ?model=… → switch model, ?q=paris → search
1400
+ const _params = new URLSearchParams(location.search);
1401
+ const _modelParam = _params.get('model');
1402
+ if (_modelParam && VINDEX_DATA[_modelParam]) {
1403
+ modelSelect.value = _modelParam;
1404
+ modelSelect.dispatchEvent(new Event('change'));
1405
+ }
1406
+ if (_params.get('view') === 'flat') {
1407
+ viewBtn.click();
1408
+ }
1409
+ if (_params.has('autoplay')) {
1410
+ setTimeout(runDemo, 1200);
1411
+ }
1412
+ const _q = _params.get('q');
1413
+ if (_q) {
1414
+ searchInput.value = _q;
1415
+ setTimeout(() => highlightSearch(_q), 1100); // wait for buildGroup + loadRealSearchIndex
1416
+ }
1417
+ </script>
1418
+ </body>
1419
+ </html>
search-indexes/google-gemma-4-e2b-it.bge.f16.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:667c3d1d6be119733475935b18f41e61fbc27ce8ebab81819f56c3a0ad5e45bf
3
+ size 3840000
search-indexes/google-gemma-4-e2b-it.json ADDED
The diff for this file is too large to render. See raw diff
 
search-indexes/google-gemma-4-e2b-it.meta.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_id": "google/gemma-4-E2B-it",
3
+ "n_tokens": 5000,
4
+ "top_k": 8,
5
+ "has_bge": true,
6
+ "build_unix_ts": 1776826346
7
+ }
vindex-hero-bg.gif ADDED

Git LFS Details

  • SHA256: 9f961aac55d9a0d20926c21f6c79f1ed32f07ed0ccbb50107558032f3c21ca20
  • Pointer size: 131 Bytes
  • Size of remote file: 856 kB