mlx-bun 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +11 -2
  2. package/docs/dag/training-inference-map.html +575 -0
  3. package/docs/design/memory-system.md +193 -295
  4. package/docs/design/orpo-dynamic-lambda.md +179 -0
  5. package/docs/design/orpo-future-enhancements.md +42 -0
  6. package/docs/design/orpo-training.md +13 -0
  7. package/docs/design/parity-tier-dag.md +143 -0
  8. package/docs/design/w4a16-compute-precision-spike.md +232 -0
  9. package/docs/investigations/orpo-base-uf-experiment-and-directions.md +120 -0
  10. package/docs/investigations/orpo-flash-cce-pin-leak.md +167 -0
  11. package/docs/investigations/orpo-uf-testing-handoff.md +126 -0
  12. package/docs/investigations/steel-flash-cce-handoff.md +58 -0
  13. package/docs/investigations/trainer-validation-experiment.md +173 -0
  14. package/docs/reference/memory.md +77 -0
  15. package/docs/reference/orpo-quickstart.md +44 -5
  16. package/docs/reference/training.md +9 -5
  17. package/package.json +1 -1
  18. package/src/cli.ts +651 -18
  19. package/src/eval/runner.ts +76 -6
  20. package/src/eval/tasks/bfcl.ts +49 -15
  21. package/src/eval/tasks/gsm8k.ts +21 -9
  22. package/src/eval/tasks/hashhop.ts +33 -4
  23. package/src/eval/tasks/humaneval.ts +30 -8
  24. package/src/eval/tasks/ifeval.ts +52 -18
  25. package/src/eval/tasks/mmlu.ts +85 -74
  26. package/src/jobs/runner.ts +6 -0
  27. package/src/memory/chunk.ts +25 -0
  28. package/src/memory/cluster.ts +24 -0
  29. package/src/memory/db.ts +114 -0
  30. package/src/memory/ingest.ts +22 -0
  31. package/src/memory/pipeline.ts +63 -0
  32. package/src/memory/prompts.ts +23 -0
  33. package/src/memory/schedule.ts +163 -0
  34. package/src/memory/synthesize.ts +29 -0
  35. package/src/memory/tools.ts +399 -0
  36. package/src/memory/vault.ts +694 -0
  37. package/src/memory/wikify.ts +23 -0
  38. package/src/mlx/metal-kernel.ts +13 -0
  39. package/src/model/flash-attention.ts +5 -3
  40. package/src/pi-provider.ts +29 -13
  41. package/src/pi-session.ts +81 -0
  42. package/src/pi-terminal.ts +13 -24
  43. package/src/pi-web.ts +246 -166
  44. package/src/quantize/job.ts +17 -2
  45. package/src/quantize/quantizer.ts +26 -2
  46. package/src/server.ts +213 -44
  47. package/src/tokenizer.ts +6 -2
  48. package/src/train/dataset.ts +11 -3
  49. package/src/train/flash-cce.ts +18 -5
  50. package/src/train/job.ts +41 -4
  51. package/src/train/loss.ts +10 -4
  52. package/src/train/trainer.ts +62 -3
  53. package/src/train/watch.ts +542 -0
  54. package/src/web/app.html +264 -14
  55. package/src/web/skills/memory/SKILL.md +97 -0
  56. package/src/web/skills/web-research/SKILL.md +3 -1
  57. package/src/web/skills.ts +46 -8
package/README.md CHANGED
@@ -134,6 +134,14 @@ back in responses).
134
134
  - **The background agent runtime** — leave a local OpenAI-compatible endpoint
135
135
  available for scripts, recurring jobs, app integrations, and per-request
136
136
  adapter routing.
137
+ - **The local memory layer** — `mlx-bun memory init` creates a git-tracked
138
+ Markdown wiki at `~/.mlx-bun/wiki`; `Reference/` is seeded with read-only
139
+ symlinks to mlx-bun's own docs, and the built-in pi agents read both those
140
+ docs and your articles quietly (`memory_search`, `memory_section`, etc.) and can open it in Obsidian for you
141
+ (`mlx-bun memory open [article]`). Chat-time memory is read-only; synthesis is
142
+ the separate writer path. The planned synthesis pipeline uses the same local
143
+ models with stage-specific LoRA adapters (chunk, bucket, synthesize, edit)
144
+ trained by the project creator and distributed from Hugging Face.
137
145
 
138
146
  The default experience is intentionally opinionated. Power users can override
139
147
  models, budgets, KV modes, adapters, and flags; the average user should not
@@ -407,8 +415,9 @@ prompt cache); registry / fit / KV persistence; quantized +
407
415
  mixed-precision KV serving (rotating-cache KV-quant, Phase 9) with fused
408
416
  quantized prefill (Phase 10); LoRA hot-swap with per-request selection;
409
417
  segmented-backward LoRA training (SFT / DPO / ORPO, with an `[M,vocab]`-free
410
- flash-CCE head + prefix-sharing for long-context preference data — see
411
- [training guide](docs/reference/training.md)); resumable
418
+ flash-CCE head + prefix-sharing for long-context preference data — `mlx-bun
419
+ train <model> --data <dir>`, see [training guide](docs/reference/training.md));
420
+ resumable
412
421
  verified downloads (`mlx-bun get`); memory admission control; the
413
422
  head-to-head benchmark harness; the decode-gap root-cause fix
414
423
  (2026-06-11); Anthropic Messages + Responses API (`/v1/messages` and
@@ -0,0 +1,575 @@
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" />
6
+ <title>mlx-bun · control-flow DAG (MiniCPM5-1B)</title>
7
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.28.1/cytoscape.min.js"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/dagre/0.8.5/dagre.min.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/cytoscape-dagre@2.5.0/cytoscape-dagre.min.js"></script>
10
+ <style>
11
+ :root{--bg:#0b0f14;--panel:#11161d;--line:#243040;--txt:#cdd9e5;--dim:#7d8aa0;--cond:#39c5cf;}
12
+ *{box-sizing:border-box;}
13
+ html,body{margin:0;height:100%;background:var(--bg);color:var(--txt);font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;}
14
+ #top{display:flex;align-items:center;gap:12px;padding:8px 14px;flex-wrap:wrap;border-bottom:1px solid var(--line);background:var(--panel);}
15
+ #top b{color:#fff;font-size:14px;}
16
+ .seg{display:flex;gap:2px;background:#0b0f14;border:1px solid var(--line);border-radius:6px;}
17
+ .seg button,.btn{background:transparent;color:var(--dim);border:0;padding:5px 11px;cursor:pointer;font:inherit;border-radius:5px;}
18
+ .seg button.on{background:#388bfd;color:#fff;}
19
+ .btn{border:1px solid var(--line);}
20
+ .legend{margin-left:auto;display:flex;gap:11px;color:var(--dim);align-items:center;flex-wrap:wrap;font-size:11px;}
21
+ .legend span{display:flex;align-items:center;gap:5px;}
22
+ .dot{width:9px;height:9px;border-radius:50%;display:inline-block;}
23
+ .dash{width:15px;border-top:2px dashed var(--cond);display:inline-block;}
24
+ #wrap{display:flex;height:calc(100% - 46px);}
25
+ #cy{flex:1;height:100%;}
26
+ #side{width:340px;border-left:1px solid var(--line);background:var(--panel);padding:14px;overflow:auto;}
27
+ #side h2{margin:0 0 4px;font-size:14px;color:#fff;}
28
+ #side .file{color:#388bfd;font-size:12px;word-break:break-all;margin-bottom:8px;}
29
+ #side .k{color:var(--dim);text-transform:uppercase;font-size:10.5px;letter-spacing:.06em;margin-top:10px;}
30
+ #side .v{white-space:pre-wrap;}
31
+ .hint{color:var(--dim);font-size:12px;margin-top:6px;}
32
+ code{color:#e3b341;}
33
+ </style>
34
+ </head>
35
+ <body>
36
+ <div id="top">
37
+ <b>mlx-bun control-flow DAG</b><span style="color:var(--dim);font-size:11px">MiniCPM5-1B</span>
38
+ <div class="seg" id="graph">
39
+ <button data-g="infer" class="on">inference</button>
40
+ <button data-g="train">training</button>
41
+ </div>
42
+ <button class="btn" id="fit">⤢ fit</button>
43
+ <div class="legend" id="legend"></div>
44
+ </div>
45
+ <div id="wrap">
46
+ <div id="cy"></div>
47
+ <div id="side">
48
+ <h2 id="s-title">Click a step</h2>
49
+ <div class="file" id="s-file"></div>
50
+ <div class="hint" id="hint">Every node = one actual step the code runs. Solid arrows = the next step in execution order. <b style="color:var(--cond)">Dashed labelled arrows</b> = a branch: the code can go several ways, and the label is the exact condition (flag / variable / check) that takes that edge. Loops are back-edges. Scroll to zoom, drag to pan. Colour = subsystem (legend). Traced from the actual source — file:line on each node.</div>
51
+ <div id="s-body" style="display:none">
52
+ <div class="k">file</div><div class="v" id="s-fileb"></div>
53
+ <div class="k">subsystem</div><div class="v" id="s-grp"></div>
54
+ <div class="k">outgoing</div><div class="v" id="s-out"></div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ <script>
59
+ // node = [id, label, group, file]. edge = [from, to, condition('' = unconditional)].
60
+ // A node with several edges of different conditions IS a branch point.
61
+ const GRP={ // subsystem → colour
62
+ drive:'#388bfd', fwd:'#2ea043', layer:'#3fb950', attn:'#d29922', sdpa:'#a371f7',
63
+ unfused:'#6e7b91', tiled:'#39c5cf', mlp:'#56d364', kv:'#f0883e', samp:'#db61a2',
64
+ setup:'#6e7b91', loss:'#d29922', seg:'#a371f7', accum:'#388bfd', bwd:'#f85149',
65
+ norm:'#39c5cf', opt:'#f0883e', metrics:'#db61a2' };
66
+
67
+ const INFER={ n:[
68
+ ['n1','generate() entry','drive','generate.ts:168'],
69
+ ['n2','generateInner() entry','drive','generate.ts:208'],
70
+ ['n3','? wired-scope: FORCE_WIRE ∨ weights>thresh','drive','generate.ts:175'],
71
+ ['n4','enter wired scope','drive','generate.ts:200'],
72
+ ['n5','? adapters?.length','drive','generate.ts:174'],
73
+ ['n6','set loraState.active','drive','generate.ts:187'],
74
+ ['n7','makeCache/Sampler/LogitsProcessors','drive','generate.ts:218'],
75
+ ['n8','maybeQuantizeKv (prefill)','drive','generate.ts:281'],
76
+ ['n9','? prompt-pos > prefillChunkSize','drive','generate.ts:298'],
77
+ ['n10','chunked prefill: loop chunk','drive','generate.ts:299'],
78
+ ['n11','forwardHidden(chunk, cache)','drive','generate.ts:301'],
79
+ ['n12','evalAll(cache) after chunk','drive','generate.ts:304'],
80
+ ['n13','clearCache() after chunk','drive','generate.ts:312'],
81
+ ['n14','? options.promptEmbeddings','drive','generate.ts:284'],
82
+ ['n15','forwardEmbeddings(...)','drive','generate.ts:292'],
83
+ ['n16','forwardHidden(lastChunk, cache)','drive','generate.ts:320'],
84
+ ['n17','? needsTokenHistory init','drive','generate.ts:315'],
85
+ ['n18','slice last position h0[:,L-1]','drive','generate.ts:324'],
86
+ ['n19','logitsFromHidden(hLast)','drive','generate.ts:326'],
87
+ ['n20','sampleStep(logits0, 0)','drive','generate.ts:328'],
88
+ ['n21','asyncEvalAll([pending])','drive','generate.ts:338'],
89
+ ['n22','? CompiledDecode.for (env·adapters·gemma4)','drive','generate.ts:352'],
90
+ ['n23','decode loop: while !stop','drive','generate.ts:360'],
91
+ ['n24','? generated+1 < maxTokens','drive','generate.ts:364'],
92
+ ['n25','maybeQuantizeKv (decode)','drive','generate.ts:365'],
93
+ ['n26','pushHistory(cur)','drive','generate.ts:366'],
94
+ ['n27','? compiled && supports(cache)','drive','generate.ts:369'],
95
+ ['n28','compiled.step(cur, cache)','drive','generate.ts:371'],
96
+ ['n29','catch → compiled=null (fallback)','drive','generate.ts:374'],
97
+ ['n30','forwardHidden(ids, cache)','drive','generate.ts:383'],
98
+ ['n31','logitsFromHidden(h)','drive','generate.ts:385'],
99
+ ['n32','sampleStep(logits, n+1)','drive','generate.ts:388'],
100
+ ['n33','asyncEvalAll([nextPending,…])','drive','generate.ts:390'],
101
+ ['n34','itemUint32(cur) sync-read token','drive','generate.ts:394'],
102
+ ['n35','? first token: prefillMs/tDecode','drive','generate.ts:399'],
103
+ ['n36','cur.dispose; generated++','drive','generate.ts:402'],
104
+ ['n37','? nextPending !== null','drive','generate.ts:406'],
105
+ ['n38','? eosTokenIds.includes(token)','drive','generate.ts:408'],
106
+ ['n39','yield {token, index}','drive','generate.ts:413'],
107
+ ['n40','? clearCache every 256','drive','generate.ts:416'],
108
+ ['n42','stop = true (no next)','drive','generate.ts:418'],
109
+ ['n43','pending = nextPending','drive','generate.ts:420'],
110
+ ['n44','stop = true (EOS / done)','drive','generate.ts:411'],
111
+ ['n45','decodeMs','drive','generate.ts:425'],
112
+ ['n46','return makeStats()','drive','generate.ts:427'],
113
+ ['n47','finally: dispose pending/cache/history','drive','generate.ts:432'],
114
+ ['n49','loraState.active=[] (scope exit)','drive','generate.ts:191'],
115
+ ['n50','exitWiredScope()','drive','generate.ts:204'],
116
+ // model forward
117
+ ['n51','embed.encode(ids)','fwd','minicpm5.ts:250'],
118
+ ['n52','forwardLayers(h, cache)','fwd','minicpm5.ts:258'],
119
+ ['n53','runLayerRange(h,0,N,cache)','fwd','minicpm5.ts:259'],
120
+ ['n54','? makeMask(L) (N==1? offset==0?)','fwd','minicpm5.ts:275'],
121
+ ['n55','for i in [0,N): layers','fwd','minicpm5.ts:277'],
122
+ ['n56','layers[i].forward(cur,mask,cache[i])','fwd','minicpm5.ts:278'],
123
+ ['n57','? dispose(cur) if i>0','fwd','minicpm5.ts:279'],
124
+ ['n58','mask.arr?.dispose()','fwd','minicpm5.ts:282'],
125
+ ['n59','finalNorm.forward(h)','fwd','minicpm5.ts:261'],
126
+ ['n60','lmHead.forward(h) → logits','fwd','minicpm5.ts:287'],
127
+ // layer
128
+ ['n61','inputNorm RMSNorm','layer','minicpm5.ts:189'],
129
+ ['n62','attn.forward(xn,mask,cache)','layer','minicpm5.ts:190'],
130
+ ['n63','add(x, attnOut) residual','layer','minicpm5.ts:192'],
131
+ ['n64','postAttnNorm RMSNorm','layer','minicpm5.ts:194'],
132
+ ['n65','mlp.forward(hn)','layer','minicpm5.ts:195'],
133
+ ['n66','add(h, mlpOut) residual','layer','minicpm5.ts:197'],
134
+ // attention
135
+ ['n67','q_proj quantized_matmul','attn','minicpm5.ts:89'],
136
+ ['n68','k_proj quantized_matmul','attn','minicpm5.ts:90'],
137
+ ['n69','v_proj quantized_matmul','attn','minicpm5.ts:91'],
138
+ ['n70','reshape q [B,L,H,D]','attn','minicpm5.ts:93'],
139
+ ['n71','reshape k [B,L,Kv,D]','attn','minicpm5.ts:94'],
140
+ ['n72','reshape v [B,L,Kv,D]','attn','minicpm5.ts:95'],
141
+ ['n73','transpose q →[B,H,L,D]','attn','minicpm5.ts:97'],
142
+ ['n74','transpose k','attn','minicpm5.ts:98'],
143
+ ['n75','transpose v','attn','minicpm5.ts:99'],
144
+ ['n76','? rope: prefixPlan / offsetArr / static','attn','minicpm5.ts:106'],
145
+ ['n77','ropeBlocks(q) (prefix)','attn','minicpm5.ts:108'],
146
+ ['n78','ropeDynamic(q) (offsetArr)','attn','minicpm5.ts:110'],
147
+ ['n79','rope(q, cache.offset)','attn','minicpm5.ts:111'],
148
+ ['n80','rope k (same offset)','attn','minicpm5.ts:113'],
149
+ ['n81','? cache instanceof QuantizedKVCache','attn','minicpm5.ts:115'],
150
+ ['n82','updateAndFetchQuantized(k,v)','attn','minicpm5.ts:116'],
151
+ ['n83','quantizedSdpa(q,kq,vq,…)','attn','minicpm5.ts:119'],
152
+ ['n84','updateAndFetch(k,v) bf16','attn','minicpm5.ts:123'],
153
+ ['n85','? getTrainingAttn()=="flash"','attn','minicpm5.ts:128'],
154
+ ['n86','flashAttention(...)','attn','minicpm5.ts:130'],
155
+ ['n87','ops.sdpa(q,keys,values,…)','attn','minicpm5.ts:132'],
156
+ ['n88','transpose attn →[B,L,H,D]','attn','minicpm5.ts:138'],
157
+ ['n89','reshape merged [B,L,H*D]','attn','minicpm5.ts:140'],
158
+ ['n90','o_proj quantized_matmul','attn','minicpm5.ts:142'],
159
+ // sdpa dispatch
160
+ ['n91','? mask=="" && scale==1.0','sdpa','gemma4-base.ts:1514'],
161
+ ['n92','? perfKernelEnabled()','sdpa','fused-decode-kernel.ts:36'],
162
+ ['n93','? !isCompiledTrace()','sdpa','gemma4-base.ts:1515'],
163
+ ['n94','? fusedDecodeKernelSupported','sdpa','gemma4-base.ts:1515'],
164
+ ['n95','⚙ fusedDecodeSdpa (L=1)','sdpa','gemma4-base.ts:1517'],
165
+ ['n96','? tile: L>1 ∨ FUSED_DECODE','sdpa','gemma4-base.ts:1518'],
166
+ ['n97','? fusedSdpaSupported','sdpa','gemma4-base.ts:1519'],
167
+ ['n98','⚙ quantizedSdpaTiled','sdpa','gemma4-base.ts:1520'],
168
+ ['n99','quantizedSdpaUnfused','sdpa','gemma4-base.ts:1521'],
169
+ // unfused internals
170
+ ['n100','dims [B,H,L,D,KV,nRep,N]','unfused','gemma4-base.ts:1221'],
171
+ ['n101','? scale !== 1.0','unfused','gemma4-base.ts:1229'],
172
+ ['n102','mulScalar(q, scale)','unfused','gemma4-base.ts:1229'],
173
+ ['n103','? nRep > 1 (GQA)','unfused','gemma4-base.ts:1233'],
174
+ ['n104','reshape q [B,KV,nRep,L,D]','unfused','gemma4-base.ts:1234'],
175
+ ['n105','expandDims(k.packed,-3)','unfused','gemma4-base.ts:1245'],
176
+ ['n106','quantizedMatmulQT(q,kᵀ) → scores','unfused','gemma4-base.ts:1251'],
177
+ ['n107','? mask.mode=="causal"','unfused','gemma4-base.ts:1256'],
178
+ ['n108','greaterEqual → causal mask','unfused','gemma4-base.ts:1261'],
179
+ ['n109','? mask.mode=="array"','unfused','gemma4-base.ts:1264'],
180
+ ['n110','where(mask, scores, -inf)','unfused','gemma4-base.ts:1269'],
181
+ ['n111','softmaxAxis(scores,-1)','unfused','gemma4-base.ts:1276'],
182
+ ['n112','quantizedMatmulQT(probs,vᵀ) → out','unfused','gemma4-base.ts:1278'],
183
+ ['n113','? nRep > 1','unfused','gemma4-base.ts:1279'],
184
+ ['n114','reshape out [B,H,L,D]','unfused','gemma4-base.ts:1280'],
185
+ // tiled internals
186
+ ['n115','dims [B,H,L,D,KV,nRep,N]','tiled','gemma4-base.ts:1304'],
187
+ ['n116','? scale !== 1.0','tiled','gemma4-base.ts:1312'],
188
+ ['n117','mulScalar(q, scale)','tiled','gemma4-base.ts:1312'],
189
+ ['n118','? nRep > 1 (GQA)','tiled','gemma4-base.ts:1317'],
190
+ ['n119','reshape q [B,KV,nRep,L,D]','tiled','gemma4-base.ts:1318'],
191
+ ['n120','expandDims(k.packed,-3)','tiled','gemma4-base.ts:1324'],
192
+ ['n121','? mask.mode=="causal"','tiled','gemma4-base.ts:1340'],
193
+ ['n122','greaterEqual → causal mask','tiled','gemma4-base.ts:1345'],
194
+ ['n123','? mask.mode=="array"','tiled','gemma4-base.ts:1348'],
195
+ ['n124','for n0=0..N step 512 (KV chunks)','tiled','gemma4-base.ts:1378'],
196
+ ['n125','slice kᵀ.packed chunk','tiled','gemma4-base.ts:1381'],
197
+ ['n126','slice kᵀ.scales chunk','tiled','gemma4-base.ts:1382'],
198
+ ['n127','slice kᵀ.biases chunk','tiled','gemma4-base.ts:1383'],
199
+ ['n128','slice vᵀ.packed chunk','tiled','gemma4-base.ts:1386'],
200
+ ['n129','quantizedMatmulQT(q,kChunk) → scores','tiled','gemma4-base.ts:1391'],
201
+ ['n130','? maskChunk dtype bool','tiled','gemma4-base.ts:1397'],
202
+ ['n131','where(mask, scores, -inf)','tiled','gemma4-base.ts:1399'],
203
+ ['n132','add(scores, maskChunk) float','tiled','gemma4-base.ts:1402'],
204
+ ['n133','maxAxis(scores) → chunkMax','tiled','gemma4-base.ts:1409'],
205
+ ['n134','? oAcc===null (first tile)','tiled','gemma4-base.ts:1410'],
206
+ ['n135','init rowMax/rowSum/oAcc','tiled','gemma4-base.ts:1411'],
207
+ ['n136','maximum(rowMax,chunkMax)=newMax','tiled','gemma4-base.ts:1419'],
208
+ ['n137','exp(rowMax-newMax)=factor','tiled','gemma4-base.ts:1422'],
209
+ ['n138','exp(scores-newMax)=exps','tiled','gemma4-base.ts:1425'],
210
+ ['n139','newSum = factor·rowSum + Σexps','tiled','gemma4-base.ts:1428'],
211
+ ['n140','nextAcc = oAcc·factor + exps·vChunk','tiled','gemma4-base.ts:1436'],
212
+ ['n141','rowMax/rowSum = new','tiled','gemma4-base.ts:1443'],
213
+ ['n142','div(oAcc, rowSum) → out','tiled','gemma4-base.ts:1452'],
214
+ ['n143','? nRep > 1','tiled','gemma4-base.ts:1459'],
215
+ ['n144','reshape out [B,H,L,D]','tiled','gemma4-base.ts:1460'],
216
+ // mlp
217
+ ['n145','gate_proj quantized_matmul','mlp','minicpm5.ts:160'],
218
+ ['n146','up_proj quantized_matmul','mlp','minicpm5.ts:161'],
219
+ ['n147','sigmoid(gate)','mlp','minicpm5.ts:162'],
220
+ ['n148','mul → SiLU (gate·σ(gate))','mlp','minicpm5.ts:163'],
221
+ ['n149','mul(silu, up)','mlp','minicpm5.ts:166'],
222
+ ['n150','down_proj quantized_matmul','mlp','minicpm5.ts:169'],
223
+ // KV bf16
224
+ ['n151','offset=prev, L=k.shape[2]','kv','gemma4-base.ts:252'],
225
+ ['n152','? capacity: !keys ∨ prev+L>cap','kv','gemma4-base.ts:254'],
226
+ ['n153','alloc newK/newV zeros','kv','gemma4-base.ts:257'],
227
+ ['n154','? cache exists','kv','gemma4-base.ts:260'],
228
+ ['n155','? prev%STEP!=0 (trim)','kv','gemma4-base.ts:263'],
229
+ ['n156','slice old, dispose','kv','gemma4-base.ts:264'],
230
+ ['n157','concat([old,new]) grow','kv','gemma4-base.ts:271'],
231
+ ['n158','sliceUpdate keys←k','kv','gemma4-base.ts:283'],
232
+ ['n159','sliceUpdate values←v','kv','gemma4-base.ts:284'],
233
+ ['n160','slice fetch keys','kv','gemma4-base.ts:290'],
234
+ ['n161','slice fetch values','kv','gemma4-base.ts:291'],
235
+ // KV quantized
236
+ ['n162','prev/L/elPerInt','kv','gemma4-base.ts:443'],
237
+ ['n163','? capacity (packed)','kv','gemma4-base.ts:446'],
238
+ ['n164','grow cache (6 comps)','kv','gemma4-base.ts:447'],
239
+ ['n165','? prev%STEP!=0 (trim)','kv','gemma4-base.ts:448'],
240
+ ['n166','trimTo (slice 6)','kv','gemma4-base.ts:449'],
241
+ ['n167','quantize(k) → kq','kv','gemma4-base.ts:467'],
242
+ ['n168','quantize(v) → vq','kv','gemma4-base.ts:468'],
243
+ ['n169','write k packed/scales/biases','kv','gemma4-base.ts:479'],
244
+ ['n175','fetch keys (3 comps)','kv','gemma4-base.ts:488'],
245
+ ['n176','fetch values (3 comps)','kv','gemma4-base.ts:488'],
246
+ // sampler
247
+ ['n177','? curve sampler active','samp','sampler.ts:418'],
248
+ ['n178','applyCurve(lp, curve)','samp','sampler.ts:420'],
249
+ ['n179','randomCategorical (curve)','samp','sampler.ts:421'],
250
+ ['n180','? temperature === 0','samp','sampler.ts:429'],
251
+ ['n181','argmaxAxis(lp) greedy','samp','sampler.ts:430'],
252
+ ['n182','? hlg?.enabled','samp','sampler.ts:437'],
253
+ ['n183','applyHlgShaper','samp','sampler.ts:453'],
254
+ ['n184','applyHlgPipeline','samp','sampler.ts:454'],
255
+ ['n185','applyHlgEotf','samp','sampler.ts:454'],
256
+ ['n186','applyHlgOetf','samp','sampler.ts:455'],
257
+ ['n187','applyHlg (parametric)','samp','sampler.ts:455'],
258
+ ['n188','randomCategorical (HLG)','samp','sampler.ts:457'],
259
+ ['n189','? topP in (0,1)','samp','sampler.ts:467'],
260
+ ['n190','applyTopP(lp, topP)','samp','sampler.ts:467'],
261
+ ['n191','? topK > 0','samp','sampler.ts:468'],
262
+ ['n192','applyTopK(lp, topK)','samp','sampler.ts:468'],
263
+ ['n193','mulScalar(lp, 1/temp)','samp','sampler.ts:469'],
264
+ ['n194','randomCategorical (temp)','samp','sampler.ts:471'],
265
+ ],e:[
266
+ ['n1','n2',''],['n2','n3',''],
267
+ ['n3','n4','FORCE_WIRE ∨ weights>thresh'],['n3','n5','else'],
268
+ ['n4','n5',''],
269
+ ['n5','n6','adapters?.length'],['n5','n7','else'],
270
+ ['n6','n7',''],['n7','n8',''],['n8','n9',''],
271
+ ['n9','n10','pos > prefillChunkSize'],['n9','n14','else (one-shot)'],
272
+ ['n10','n11',''],['n11','n51','call forward'],['n11','n12',''],['n12','n13',''],
273
+ ['n13','n10','more chunks'],['n13','n14','done'],
274
+ ['n14','n15','promptEmbeddings'],['n14','n16','else'],
275
+ ['n15','n17',''],['n16','n51','call forward'],['n16','n17',''],
276
+ ['n17','n18',''],['n18','n19',''],['n19','n60','call lmHead'],['n19','n20',''],['n20','n21',''],['n21','n22',''],['n22','n23',''],
277
+ ['n23','n24',''],
278
+ ['n24','n25','generated+1<maxTokens'],['n24','n34','else'],
279
+ ['n25','n26',''],['n26','n27',''],
280
+ ['n27','n28','compiled && supports'],['n27','n30','else (uncompiled)'],
281
+ ['n28','n29','throws'],['n28','n31','ok'],['n29','n30',''],
282
+ ['n30','n51','call forward'],['n30','n31',''],['n31','n60','call lmHead'],['n31','n32',''],['n32','n33',''],['n33','n34',''],
283
+ ['n34','n35','first token'],['n34','n36','else'],['n35','n36',''],['n36','n37',''],
284
+ ['n37','n38','nextPending!=null'],['n37','n42','else'],
285
+ ['n38','n39','!EOS'],['n38','n44','EOS'],
286
+ ['n39','n40',''],['n40','n43','nextPending'],['n40','n42','none'],
287
+ ['n42','n44',''],['n43','n23','loop'],
288
+ ['n44','n45',''],['n45','n46',''],['n46','n47',''],['n47','n49',''],['n49','n50',''],
289
+ // forward
290
+ ['n51','n52',''],['n52','n53',''],['n53','n54',''],['n54','n55',''],
291
+ ['n55','n56','i<N'],['n56','n57',''],['n57','n55','i++'],['n57','n58','done'],
292
+ ['n58','n59',''],['n59','n12','return (prefill)'],['n59','n17','return (prefill)'],['n59','n31','return (decode)'],
293
+ // layer (called by n56)
294
+ ['n56','n61','call layer'],
295
+ ['n61','n62',''],['n62','n63',''],['n63','n64',''],['n64','n65',''],['n65','n66',''],
296
+ // attention (called by n62)
297
+ ['n62','n67','call attn'],
298
+ ['n67','n68',''],['n68','n69',''],['n69','n70',''],['n70','n71',''],['n71','n72',''],['n72','n73',''],['n73','n74',''],['n74','n75',''],['n75','n76',''],
299
+ ['n76','n77','_prefixPlan'],['n76','n78','offsetArr'],['n76','n79','else (static)'],
300
+ ['n77','n80',''],['n78','n80',''],['n79','n80',''],['n80','n81',''],
301
+ ['n81','n82','QuantizedKVCache'],['n81','n84','else (bf16)'],
302
+ ['n82','n83',''],['n83','n88',''],
303
+ ['n84','n85',''],
304
+ ['n85','n86','flash & supported'],['n85','n87','else'],
305
+ ['n86','n88',''],['n87','n88',''],['n88','n89',''],['n89','n90',''],
306
+ // sdpa dispatch (called by n83)
307
+ ['n83','n91','call quantizedSdpa'],
308
+ ['n91','n92','mask=="" & scale==1'],['n91','n96','else'],
309
+ ['n92','n93','perfKernel'],['n92','n96','else'],
310
+ ['n93','n94','!compiledTrace'],['n93','n96','else'],
311
+ ['n94','n95','supported'],['n94','n96','else'],
312
+ ['n96','n97','tile (L>1 ∨ FUSED_DECODE)'],['n96','n99','else'],
313
+ ['n97','n98','fusedSdpaSupported'],['n97','n99','else'],
314
+ // unfused (n99)
315
+ ['n99','n100','call'],['n100','n101',''],
316
+ ['n101','n102','scale!=1'],['n101','n103','else'],['n102','n103',''],
317
+ ['n103','n104','nRep>1'],['n103','n106','else'],['n104','n105',''],['n105','n106',''],
318
+ ['n106','n107',''],['n107','n108','causal'],['n107','n109','else'],
319
+ ['n108','n110',''],['n109','n110','array'],['n109','n111','none'],['n110','n111',''],
320
+ ['n111','n112',''],['n112','n113',''],['n113','n114','nRep>1'],
321
+ // tiled (n98)
322
+ ['n98','n115','call'],['n115','n116',''],
323
+ ['n116','n117','scale!=1'],['n116','n118','else'],['n117','n118',''],
324
+ ['n118','n119','nRep>1'],['n118','n121','else'],['n119','n120',''],['n120','n121',''],
325
+ ['n121','n122','causal'],['n121','n123','else'],['n122','n124',''],['n123','n124',''],
326
+ ['n124','n125','n0<N'],['n125','n126',''],['n126','n127',''],['n127','n128',''],['n128','n129',''],
327
+ ['n129','n130',''],['n130','n131','bool'],['n130','n132','float'],['n131','n133',''],['n132','n133',''],
328
+ ['n133','n134',''],['n134','n135','first'],['n134','n136','else'],
329
+ ['n135','n124','next chunk'],
330
+ ['n136','n137',''],['n137','n138',''],['n138','n139',''],['n139','n140',''],['n140','n141',''],['n141','n124','next chunk'],
331
+ ['n124','n142','n0>=N'],['n142','n143',''],['n143','n144','nRep>1'],
332
+ // mlp (called by n65)
333
+ ['n65','n145','call mlp'],
334
+ ['n145','n146',''],['n146','n147',''],['n147','n148',''],['n148','n149',''],['n149','n150',''],
335
+ // KV bf16 (called by n84)
336
+ ['n84','n151','call updateAndFetch'],
337
+ ['n151','n152',''],['n152','n153','grow'],['n152','n158','fits'],
338
+ ['n153','n154',''],['n154','n155','exists'],['n154','n157','first'],
339
+ ['n155','n156','prev%STEP!=0'],['n155','n157','aligned'],['n156','n157',''],['n157','n158',''],
340
+ ['n158','n159',''],['n159','n160',''],['n160','n161',''],
341
+ // KV quant (called by n82)
342
+ ['n82','n162','call updateAndFetchQuantized'],
343
+ ['n162','n163',''],['n163','n164','grow'],['n163','n167','fits'],
344
+ ['n164','n165',''],['n165','n166','prev%STEP!=0'],['n165','n167','aligned'],['n166','n167',''],
345
+ ['n167','n168',''],['n168','n169',''],['n169','n175',''],['n175','n176',''],
346
+ // sampler (called by n20, n32)
347
+ ['n20','n177','call sampleStep'],['n32','n177','call sampleStep'],
348
+ ['n177','n178','curve'],['n177','n180','else'],['n178','n179',''],
349
+ ['n180','n181','temp==0'],['n180','n182','else'],
350
+ ['n182','n183','shaper'],['n182','n184','pipeline'],['n182','n185','eotf'],['n182','n186','oetf'],['n182','n187','parametric'],['n182','n189','hlg off'],
351
+ ['n183','n188',''],['n184','n188',''],['n185','n188',''],['n186','n188',''],['n187','n188',''],
352
+ ['n189','n190','topP'],['n189','n191','else'],['n190','n191',''],
353
+ ['n191','n192','topK'],['n191','n193','else'],['n192','n193',''],['n193','n194',''],
354
+ ]};
355
+
356
+ const TRAIN={ n:[
357
+ ['t1','step loop begins','setup','trainer.ts:479'],
358
+ ['t2','per-step dropout seed','setup','trainer.ts:876'],
359
+ ['t3','enter accumulation loop','accum','trainer.ts:487'],
360
+ ['t4','fetch micro-batch','accum','trainer.ts:490'],
361
+ ['t5','? segmentSize > 0','setup','trainer.ts:409'],
362
+ ['t6','segmented forward start','seg','segmented.ts:196'],
363
+ ['t7','embed.encode(ids)','seg','segmented.ts:196'],
364
+ ['t8','detach embedding boundary','seg','segmented.ts:196'],
365
+ ['t9','for segment k=0..nSeg','seg','segmented.ts:197'],
366
+ ['t10','runLayerRange[lo:hi] (swap LoRA)','seg','segmented.ts:199'],
367
+ ['t11','detach output boundary k','seg','segmented.ts:199'],
368
+ ['t12','? ORPO prefix-shared & segmented','seg','trainer.ts:880'],
369
+ ['t13','splitPrefixBatch','seg','trainer.ts:881'],
370
+ ['t14','single forward [P;C;R] block-mask','seg','prefix-shared.ts:159'],
371
+ ['t15','prefix fallback (two-forward)','seg','trainer.ts:884'],
372
+ ['t16','non-segmented forward start','accum','trainer.ts:492'],
373
+ ['t17','value_and_grad: swap primals','accum','trainer.ts:432'],
374
+ ['t18','? B==1 response-only vs full','loss','loss.ts:123'],
375
+ ['t19','forwardHidden → [B,T,hid]','fwd','forward.ts:72'],
376
+ ['t20','embedding forward','fwd','forward.ts:92'],
377
+ ['t21','layer-stack forward','fwd','forward.ts:92'],
378
+ ['t22','? MLX_BUN_TRAIN_ATTN=="flash"','fwd','trainer.ts:469'],
379
+ ['t23','sdpa attention (O(L²) bwd)','fwd','loss.ts:131'],
380
+ ['t24','flash attention (O(L) bwd)','fwd','trainer.ts:469'],
381
+ ['t25','final norm','fwd','forward.ts:113'],
382
+ ['t26','lm-head → logits','fwd','loss.ts:131'],
383
+ ['t27','? B==1 responseCe vs B>1 maskedCe','loss','loss.ts:123'],
384
+ ['t28','slice hidden to response span','loss','loss.ts:160'],
385
+ ['t29','head at response positions','loss','loss.ts:161'],
386
+ ['t30','gather target logits','loss','loss.ts:170'],
387
+ ['t31','logsumexp vocab','loss','loss.ts:169'],
388
+ ['t32','CE = lse - target','loss','loss.ts:172'],
389
+ ['t33','sum/normalize response CE','loss','loss.ts:174'],
390
+ ['t34','build response mask [B,T]','loss','loss.ts:233'],
391
+ ['t35','reshape logits [B*T,V]','loss','loss.ts:251'],
392
+ ['t36','gather targets','loss','loss.ts:252'],
393
+ ['t37','logsumexp','loss','loss.ts:256'],
394
+ ['t38','CE * mask','loss','loss.ts:262'],
395
+ ['t39','sum masked CE / norm','loss','loss.ts:264'],
396
+ ['t40','DPO ref logps (scale=0, detach)','loss','loss.ts:361'],
397
+ ['t41','DPO policy logps','loss','loss.ts:389'],
398
+ ['t42','logr_c = piC - refC','loss','loss.ts:392'],
399
+ ['t43','logr_r = piR - refR','loss','loss.ts:393'],
400
+ ['t44','adv = β(logr_c - logr_r)','loss','loss.ts:394'],
401
+ ['t45','-logσ(adv) = softplus; mean','loss','loss.ts:399'],
402
+ ['t46','ORPO chosen logp','loss','loss.ts:1242'],
403
+ ['t47','? orpoChunkSize > 0','loss','loss.ts:481'],
404
+ ['t48','? orpoFusedCe ∨ orpoFlashCe','loss','loss.ts:482'],
405
+ ['t49','chunked head (Checkpoint)','loss','loss.ts:567'],
406
+ ['t50','fused linear-CE head (CustomVjp)','loss','loss.ts:726'],
407
+ ['t51','vocab-blocked head','loss','loss.ts:822'],
408
+ ['t52','flash-CCE Metal head','loss','loss.ts:1009'],
409
+ ['t53','whole-vocab head (oracle)','loss','loss.ts:1087'],
410
+ ['t54','ORPO rejected logp','loss','loss.ts:1243'],
411
+ ['t55','NLL = mean(-ℓw)','loss','loss.ts:1225'],
412
+ ['t56','log_odds calc','loss','loss.ts:1207'],
413
+ ['t57','OR = mean softplus(-log_odds)','loss','loss.ts:1228'],
414
+ ['t58','L = NLL + λ·OR','loss','loss.ts:1231'],
415
+ ['t59','seg loss-head VJP (cot=1)','seg','segmented.ts:141'],
416
+ ['t60','seg loss scalar (detached)','seg','segmented.ts:204'],
417
+ ['t61','seg dh_out = dLoss/dhidden','seg','segmented.ts:205'],
418
+ ['t62','evalAll([loss, …grads])','accum','trainer.ts:1027'],
419
+ ['t63','loss scalar toFloat32','accum','trainer.ts:1028'],
420
+ ['t64','scale grad by 1/accum','accum','trainer.ts:1042'],
421
+ ['t65','accumulate grad','accum','trainer.ts:1048'],
422
+ ['t66','dispose checkpoint closures','accum','trainer.ts:496'],
423
+ ['t67','sum micro losses','accum','trainer.ts:1061'],
424
+ ['t69','value_and_grad backward','bwd','trainer.ts:492'],
425
+ ['t70','MLX reverse-mode AD','bwd','trainer.ts:492'],
426
+ ['t71','restore LoRA leaves','bwd','trainer.ts:436'],
427
+ ['t72','seg backward: k=nSeg-1..0','bwd','segmented.ts:210'],
428
+ ['t73','vjp segment k (cot=dh_out)','bwd','segmented.ts:215'],
429
+ ['t74','scatter per-seg LoRA grads','bwd','segmented.ts:220'],
430
+ ['t75','dh_in → cotangent for k-1','bwd','segmented.ts:225'],
431
+ ['t76','dispose boundary k','bwd','segmented.ts:226'],
432
+ ['t80','globalNorm(grads)','norm','trainer.ts:1076'],
433
+ ['t81','square each grad','norm','trainer.ts:1078'],
434
+ ['t82','sum squares, sqrt','norm','trainer.ts:1086'],
435
+ ['t83','? ‖g‖ > maxNorm','norm','trainer.ts:1099'],
436
+ ['t84','scale g *= max/‖g‖','norm','trainer.ts:1103'],
437
+ ['t85','AdamW.step(grads)','opt','trainer.ts:504'],
438
+ ['t86','t++ step counter','opt','optimizer.ts:84'],
439
+ ['t87','bias-correct β1^t','opt','optimizer.ts:85'],
440
+ ['t88','bias-correct β2^t','opt','optimizer.ts:86'],
441
+ ['t89','for each param i (A,B)','opt','optimizer.ts:88'],
442
+ ['t90','grad → float32','opt','optimizer.ts:92'],
443
+ ['t91','m ← β1·m + (1-β1)g','opt','optimizer.ts:95'],
444
+ ['t92','v ← β2·v + (1-β2)g²','opt','optimizer.ts:105'],
445
+ ['t93','? weight decay','opt','optimizer.ts:119'],
446
+ ['t94','m̂ = m/(1-β1^t)','opt','optimizer.ts:125'],
447
+ ['t95','v̂ = v/(1-β2^t)','opt','optimizer.ts:126'],
448
+ ['t96','√v̂ + ε','opt','optimizer.ts:127'],
449
+ ['t97','m̂/(√v̂+ε)','opt','optimizer.ts:130'],
450
+ ['t98','scale by lr (LoRA+ per-param)','opt','optimizer.ts:131'],
451
+ ['t99','p = p - lr·update','opt','optimizer.ts:132'],
452
+ ['t100','writeParam → swap into LoRA','opt','trainer.ts:402'],
453
+ ['t101','opt.evalState()','opt','trainer.ts:505'],
454
+ ['t102','clearCache()','metrics','trainer.ts:511'],
455
+ ['t103','emit step metrics','metrics','trainer.ts:516'],
456
+ ['t104','? step%report==0 ∨ step==1','metrics','trainer.ts:514'],
457
+ ['t105','recompute dpo/orpo metrics','metrics','trainer.ts:900'],
458
+ ['t106','? step%eval==0 & valid>0','metrics','trainer.ts:529'],
459
+ ['t107','disable grad-ckpt for eval','metrics','trainer.ts:532'],
460
+ ['t108','loop validation examples','metrics','trainer.ts:554'],
461
+ ['t109','eval loss per example','metrics','trainer.ts:564'],
462
+ ['t110','reduce eval (mean/acc/margin)','metrics','trainer.ts:684'],
463
+ ['t111','re-enable grad-ckpt','metrics','trainer.ts:534'],
464
+ ['t112','step iteration completes','setup','trainer.ts:538'],
465
+ ],e:[
466
+ ['t1','t2',''],['t2','t3',''],['t3','t4',''],['t4','t5',''],
467
+ ['t5','t6','segmentSize>0'],['t5','t16','else'],
468
+ ['t6','t7',''],['t7','t8',''],['t8','t9',''],
469
+ ['t9','t10','k<nSeg'],['t10','t11',''],['t11','t9','k++'],
470
+ ['t11','t12','done (ORPO)'],['t11','t59','done (SFT seg)'],
471
+ ['t12','t13','prefixShared & segmented'],['t12','t59','else'],
472
+ ['t13','t14','split non-null'],['t13','t15','null (mismatch)'],
473
+ ['t14','t59',''],['t15','t72',''],
474
+ ['t16','t17',''],['t17','t18',''],
475
+ ['t18','t19','B==1'],['t18','t20','full-logits'],
476
+ ['t19','t20',''],['t20','t21',''],['t21','t22',''],
477
+ ['t22','t24','flash'],['t22','t23','else'],
478
+ ['t23','t25',''],['t24','t25',''],['t25','t26',''],
479
+ ['t19','t27','SFT B==1'],['t26','t27','SFT B>1'],
480
+ ['t27','t28','B==1'],['t27','t34','B>1'],
481
+ ['t28','t29',''],['t29','t30',''],['t30','t31',''],['t31','t32',''],['t32','t33',''],
482
+ ['t34','t35',''],['t35','t36',''],['t36','t37',''],['t37','t38',''],['t38','t39',''],
483
+ // DPO
484
+ ['t4','t40','method==dpo'],['t40','t41',''],['t41','t42',''],['t42','t43',''],['t43','t44',''],['t44','t45',''],
485
+ // ORPO
486
+ ['t4','t12','method==orpo & prefixShared'],['t12','t46','else'],['t13','t46','null → two-forward'],
487
+ ['t46','t47',''],
488
+ ['t47','t48','chunkSize>0'],['t47','t54','batched'],
489
+ ['t48','t49','neither'],['t48','t50','fused'],['t48','t52','flash'],
490
+ ['t50','t51','vocabBlock>0'],['t50','t53','else'],
491
+ ['t49','t54',''],['t51','t54',''],['t52','t54',''],['t53','t54',''],
492
+ ['t14','t55','prefix-shared done'],['t54','t55',''],
493
+ ['t55','t56',''],['t56','t57',''],['t57','t58',''],
494
+ // seg head
495
+ ['t59','t60',''],['t60','t61',''],
496
+ // accumulation
497
+ ['t33','t62','SFT B==1'],['t39','t62','SFT B>1'],['t45','t62','DPO'],['t58','t62','ORPO'],['t60','t62','seg'],
498
+ ['t62','t63',''],
499
+ ['t63','t64','accum>1'],['t63','t66','else'],
500
+ ['t64','t65',''],['t65','t66',''],
501
+ ['t66','t64','next micro'],['t66','t67','last micro'],
502
+ // backward
503
+ ['t62','t69','non-seg'],['t69','t70',''],['t70','t71',''],
504
+ ['t61','t72','seg'],['t72','t73','k>=0'],['t73','t74',''],['t74','t75',''],['t75','t76',''],['t76','t72','k--'],
505
+ // norm/clip
506
+ ['t71','t80',''],['t72','t80','seg done'],['t67','t80','accum done'],
507
+ ['t80','t81',''],['t81','t82',''],['t82','t83',''],
508
+ ['t83','t84','‖g‖>max'],['t83','t85','else'],['t84','t85',''],
509
+ // optimizer
510
+ ['t85','t86',''],['t86','t87',''],['t87','t88',''],['t88','t89',''],
511
+ ['t89','t90','i<len'],['t90','t91',''],['t91','t92',''],['t92','t93',''],
512
+ ['t93','t94','wd>0 / else'],['t94','t95',''],['t95','t96',''],['t96','t97',''],['t97','t98',''],['t98','t99',''],['t99','t100',''],['t100','t89','i++'],
513
+ ['t89','t101','done'],
514
+ // metrics
515
+ ['t101','t102',''],['t102','t103',''],['t103','t104',''],
516
+ ['t104','t105','report'],['t104','t106','skip'],['t105','t106',''],
517
+ ['t106','t107','eval'],['t106','t112','skip'],
518
+ ['t107','t108',''],['t108','t109','ex'],['t109','t108','next'],['t108','t110','done'],['t110','t111',''],['t111','t112',''],
519
+ ['t112','t1','step++'],
520
+ ]};
521
+
522
+ let MODE_G='infer';
523
+ const cy=cytoscape({container:document.getElementById('cy'),minZoom:0.06,maxZoom:3,wheelSensitivity:0.25,
524
+ autoungrabify:true, boxSelectionEnabled:false, selectionType:'single',
525
+ style:[
526
+ {selector:'node',style:{'background-color':d=>GRP[d.data('grp')]||'#6e7b91','label':'data(label)',
527
+ 'color':'#0b0f14','font-family':'ui-monospace, Menlo, monospace','font-size':'11px','font-weight':'600',
528
+ 'text-valign':'center','text-halign':'center','shape':'round-rectangle','width':'label','height':22,
529
+ 'padding':'7px','text-wrap':'wrap','text-max-width':'210px','border-width':1,'border-color':'#0b0f14'}},
530
+ {selector:'node[branch]',style:{'border-width':2.5,'border-color':'#39c5cf'}},
531
+ {selector:'node:selected',style:{'border-width':3,'border-color':'#fff'}},
532
+ {selector:'edge',style:{'width':1.6,'line-color':'#3a4760','target-arrow-color':'#3a4760',
533
+ 'target-arrow-shape':'triangle','curve-style':'bezier','arrow-scale':0.8}},
534
+ {selector:'edge[cond]',style:{'line-color':'#39c5cf','target-arrow-color':'#39c5cf','line-style':'dashed',
535
+ 'label':'data(cond)','font-size':'8px','font-family':'ui-monospace, Menlo, monospace','color':'#39c5cf',
536
+ 'text-background-color':'#0b0f14','text-background-opacity':0.85,'text-background-padding':'1px','text-rotation':'autorotate'}},
537
+ ]});
538
+
539
+ function build(g){
540
+ // count outgoing per node to mark branch points (>1 distinct outgoing edge)
541
+ const out={}; g.e.forEach(([a])=>out[a]=(out[a]||0)+1);
542
+ const nodes=g.n.map(([id,label,grp,file])=>({data:{id,label,grp,file,branch:out[id]>1?1:undefined}}));
543
+ const edges=g.e.map(([a,b,c])=>({data:{id:a+'>'+b+(c||''),source:a,target:b,...(c?{cond:c}:{})}}));
544
+ return {nodes,edges};
545
+ }
546
+ function load(g){const el=build(g);cy.elements().remove();cy.add(el.nodes);cy.add(el.edges);
547
+ cy.layout({name:'dagre',rankDir:'TB',nodeSep:18,rankSep:30,padding:30}).run();}
548
+
549
+ function show(d){
550
+ document.getElementById('s-title').textContent=d.label;
551
+ document.getElementById('s-file').textContent=d.file||'';
552
+ document.getElementById('hint').style.display='none';
553
+ document.getElementById('s-body').style.display='block';
554
+ document.getElementById('s-fileb').textContent=d.file||'';
555
+ document.getElementById('s-grp').textContent=d.grp||'';
556
+ const outs=cy.getElementById(d.id).outgoers('edge').map(e=>{
557
+ const t=e.target().data('label'); const c=e.data('cond'); return c?`• [${c}] → ${t}`:`• → ${t}`;}).join('\n');
558
+ document.getElementById('s-out').textContent=outs||'(terminal)';
559
+ }
560
+ cy.on('tap','node',e=>show(e.target.data()));
561
+ document.getElementById('fit').onclick=()=>cy.fit(undefined,40);
562
+ const LABELS={drive:'generate',fwd:'forward',layer:'layer',attn:'attention',sdpa:'sdpa dispatch',unfused:'unfused sdpa',tiled:'tiled sdpa',mlp:'mlp',kv:'kv cache',samp:'sampler',
563
+ setup:'setup/loop',loss:'loss head',seg:'segmented',accum:'accum',bwd:'backward',norm:'grad norm/clip',opt:'optimizer',metrics:'metrics/eval'};
564
+ function legend(){
565
+ const groups = MODE_G==='infer' ? ['drive','fwd','attn','sdpa','tiled','unfused','mlp','kv','samp'] : ['setup','fwd','loss','seg','accum','bwd','norm','opt','metrics'];
566
+ document.getElementById('legend').innerHTML='<span><i class="dash"></i>branch (condition)</span>'+
567
+ groups.map(g=>`<span><i class="dot" style="background:${GRP[g]}"></i>${LABELS[g]}</span>`).join('');
568
+ }
569
+ document.querySelectorAll('#graph button').forEach(b=>b.onclick=()=>{
570
+ document.querySelectorAll('#graph button').forEach(x=>x.classList.remove('on'));b.classList.add('on');
571
+ MODE_G=b.dataset.g;load(MODE_G==='infer'?INFER:TRAIN);legend();});
572
+ legend();load(INFER);
573
+ </script>
574
+ </body>
575
+ </html>