mlx-bun 0.0.7 → 0.0.9

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 (138) hide show
  1. package/README.md +112 -20
  2. package/docs/design/batching-v2-plan.md +515 -0
  3. package/docs/design/bucketing-stage.md +196 -0
  4. package/docs/design/compat-cli-surface-design.md +11 -11
  5. package/docs/design/diffusion-gemma-port.md +602 -0
  6. package/docs/design/docs-reorg-plan.md +1 -1
  7. package/docs/design/dreaming-nightly-pipeline.md +291 -0
  8. package/docs/design/dspark-speculative-decoding.md +106 -0
  9. package/docs/design/generic-model-support.md +381 -0
  10. package/docs/design/memory-inference-path.md +422 -0
  11. package/docs/design/memory-synthesis.md +287 -0
  12. package/docs/design/minicpm5-decode-megakernel.md +484 -0
  13. package/docs/design/mlx-lm-tool-parity-plan.md +535 -0
  14. package/docs/design/orpo-dynamic-lambda.md +41 -0
  15. package/docs/design/orpo-training.md +35 -5
  16. package/docs/design/parallel-slots.md +12 -1
  17. package/docs/design/parity-tier-dag.md +13 -5
  18. package/docs/design/spec-decode-larger-targets.md +229 -0
  19. package/docs/design/tauri-desktop-app.md +2 -2
  20. package/docs/design/the-dreaming-handoff.md +141 -0
  21. package/docs/design/the-dreaming-master-plan.md +446 -0
  22. package/docs/design/write-pipeline-entity.md +468 -0
  23. package/docs/investigations/curve-bisector-routing.md +1 -1
  24. package/docs/investigations/decode-roofline-lookagain.md +262 -0
  25. package/docs/investigations/dspark-handoff.md +58 -0
  26. package/docs/investigations/kernel-perf-review-2026-07.md +92 -0
  27. package/docs/planning/PRODUCT_ROADMAP.md +2 -2
  28. package/docs/planning/curve-sampler-research-plan.md +331 -0
  29. package/docs/planning/memory-docs-and-dag-plan.md +185 -0
  30. package/docs/planning/pi-integration-review.md +178 -0
  31. package/docs/planning/release-notes-v0.0.9.md +122 -0
  32. package/docs/planning/web-ui-pass-plan.md +213 -0
  33. package/docs/planning/website-readme-pass-plan.md +224 -0
  34. package/docs/reference/cli.md +255 -0
  35. package/docs/reference/embedding.md +7 -2
  36. package/docs/reference/library-api.md +22 -3
  37. package/docs/reference/memory.md +5 -5
  38. package/docs/reference/models.md +137 -0
  39. package/docs/reference/orpo-quickstart.md +6 -0
  40. package/docs/reference/server-api.md +187 -9
  41. package/docs/reference/server-config.md +40 -9
  42. package/docs/reference/training.md +25 -11
  43. package/package.json +1 -1
  44. package/src/anthropic.ts +22 -0
  45. package/src/assets/curve-designer.html +1 -1
  46. package/src/chat-template.ts +25 -7
  47. package/src/cli.ts +1029 -86
  48. package/src/config.ts +54 -3
  49. package/src/diffusion/diffusion-generate.ts +436 -0
  50. package/src/download.ts +99 -8
  51. package/src/embed.ts +77 -0
  52. package/src/eval/perplexity.ts +164 -0
  53. package/src/eval/runner.ts +19 -7
  54. package/src/eval/tasks/bfcl.ts +1 -1
  55. package/src/generate.ts +196 -19
  56. package/src/harness-pi.ts +43 -17
  57. package/src/index.ts +2 -0
  58. package/src/lora.ts +2 -2
  59. package/src/memory/article.ts +558 -0
  60. package/src/memory/chunk-validation.ts +150 -0
  61. package/src/memory/chunk.ts +305 -12
  62. package/src/memory/cluster.ts +344 -17
  63. package/src/memory/coverage.ts +134 -0
  64. package/src/memory/crosslink.ts +0 -0
  65. package/src/memory/db.ts +184 -45
  66. package/src/memory/entity.ts +203 -0
  67. package/src/memory/gate.ts +187 -0
  68. package/src/memory/history.ts +152 -0
  69. package/src/memory/ingest.ts +324 -11
  70. package/src/memory/model.ts +321 -0
  71. package/src/memory/normalize.ts +671 -0
  72. package/src/memory/parse.ts +22 -0
  73. package/src/memory/pipeline.ts +242 -30
  74. package/src/memory/prompts.ts +42 -0
  75. package/src/memory/query.ts +334 -0
  76. package/src/memory/reconcile.ts +734 -0
  77. package/src/memory/redirect.ts +108 -0
  78. package/src/memory/reindex.ts +325 -0
  79. package/src/memory/resolve.ts +307 -0
  80. package/src/memory/route.ts +272 -0
  81. package/src/memory/schedule.ts +6 -4
  82. package/src/memory/stages.ts +774 -0
  83. package/src/memory/synthesize.ts +1629 -20
  84. package/src/memory/tools.ts +446 -96
  85. package/src/memory/vault.ts +462 -35
  86. package/src/memory/wikify.ts +1057 -16
  87. package/src/mlx/ffi.ts +11 -0
  88. package/src/mlx/ops.ts +113 -0
  89. package/src/model/compiled-decode.ts +43 -8
  90. package/src/model/diffusion-gemma.ts +1026 -0
  91. package/src/model/factory.ts +35 -8
  92. package/src/model/flash-attention.ts +4 -2
  93. package/src/model/fused-decode-kernel.ts +14 -7
  94. package/src/model/fused-mlp-kernel.ts +169 -0
  95. package/src/model/fused-swiglu-kernel.ts +240 -0
  96. package/src/model/gemma4-base.ts +62 -6
  97. package/src/model/gemma4.ts +33 -2
  98. package/src/model/minicpm5.ts +106 -8
  99. package/src/model/qwen3.ts +276 -0
  100. package/src/model/steel-linear-kernel.ts +96 -0
  101. package/src/model/support.ts +45 -7
  102. package/src/model/universal/archs.ts +332 -0
  103. package/src/model/universal/dense.ts +576 -0
  104. package/src/model/universal/modules.ts +163 -0
  105. package/src/model/universal/rope.ts +366 -0
  106. package/src/pi-provider.ts +1 -1
  107. package/src/pi-terminal.ts +29 -8
  108. package/src/pi-web.ts +19 -2
  109. package/src/quantize/config-writer.ts +1 -1
  110. package/src/registry.ts +215 -7
  111. package/src/responses.ts +23 -0
  112. package/src/sampler.ts +220 -9
  113. package/src/serve/batch-scheduler.ts +272 -80
  114. package/src/serve/generation-gateway.ts +90 -18
  115. package/src/server.ts +643 -50
  116. package/src/spec/drafter.ts +81 -49
  117. package/src/spec/dspark/data-dflash.ts +156 -0
  118. package/src/spec/dspark/data.ts +201 -0
  119. package/src/spec/dspark/generate-dflash.ts +193 -0
  120. package/src/spec/dspark/generate.ts +290 -0
  121. package/src/spec/dspark/loss.ts +213 -0
  122. package/src/spec/dspark/module-dflash.ts +361 -0
  123. package/src/spec/dspark/module.ts +540 -0
  124. package/src/spec/dspark/sample.ts +112 -0
  125. package/src/spec/generate.ts +40 -19
  126. package/src/status-page.html +1 -1
  127. package/src/tokenizer.ts +7 -0
  128. package/src/train/diffusion-lora.ts +216 -0
  129. package/src/train/flash-cce.ts +70 -15
  130. package/src/train/fuse.ts +243 -0
  131. package/src/train/index.ts +3 -0
  132. package/src/train/job.ts +8 -0
  133. package/src/train/loss.ts +289 -30
  134. package/src/train/prefix-shared.ts +33 -5
  135. package/src/train/segmented.ts +67 -11
  136. package/src/train/trainer.ts +92 -28
  137. package/src/vision/diffusion-vision.ts +620 -0
  138. package/src/web/skills/memory/SKILL.md +40 -17
@@ -0,0 +1,178 @@
1
+ # pi integration review — internal embeds + `harness pi` (2026-07-01)
2
+
3
+ Read-only audit of the pi surface: `mlx-bun pi` (src/pi-terminal.ts, in-process
4
+ InteractiveMode), the shared provider wiring (src/pi-provider.ts), the web
5
+ embed (src/pi-web.ts), `mlx-bun harness pi` (src/harness-pi.ts), and the docs
6
+ that describe them. Every claim below was verified in the current source
7
+ (post-8ee691d, working tree of 2026-07-01); the localhost/loopback behavior was
8
+ verified empirically with Bun on this machine.
9
+
10
+ Stale-memory corrections first (priors that are NO LONGER true):
11
+ - `harness pi` does **not** write `~/.pi/agent/models.json` — it generates a
12
+ self-contained extension at `~/.pi/agent/extensions/mlx-bun-provider.ts`
13
+ that calls `pi.registerProvider()` with live `/v1/models` discovery.
14
+ - Tool-call parsing is **not** Gemma-only anymore: src/tool-call.ts also parses
15
+ OpenAI-JSON `<tool_call>`, Qwen `<function=name><parameter=…>`, and MiniCPM5
16
+ `<function name=…><param name=…>` from decoded text (buffered-text mode).
17
+ - The old `by_bits`/bitsMap and double-BOS issues are fixed and stayed fixed.
18
+
19
+ ## Ranked fix list
20
+
21
+ ### 1. HIGH — Web chat advertises memory tools it filtered out
22
+ `src/pi-web.ts:568` passes `tools: [...WELCOME_TOOLS]` (= `["read","web_search"]`,
23
+ line 68) to `createAgentSessionFromServices`, but `src/pi-web.ts:534` appends
24
+ `surface.memoryHint` to the system prompt and `surface.skillPaths` includes the
25
+ bundled `memory` skill (surface built at :528 with memory auto-detect). pi
26
+ treats the `tools` list as an allowlist, so when a vault exists the prompt and
27
+ skill instruct the model to call `memory_resolve` / `memory_read` /
28
+ `memory_section` — tools pi filtered out of the session. Every such call fails.
29
+ The intent that they be reachable is visible at `src/pi-web.ts:57`
30
+ (`READ_ONLY_TOOLS` already auto-allows `MEMORY_TOOL_NAMES`/`REFERENCE_TOOL_NAMES`,
31
+ imported at :47). The terminal embed does this correctly
32
+ (`src/pi-terminal.ts:157` uses `surface.tools`).
33
+ **Fix (pi-web.ts:568):**
34
+ `tools: [...WELCOME_TOOLS, ...(surface.memoryEnabled ? [...MEMORY_TOOL_NAMES, ...REFERENCE_TOOL_NAMES] : [])]`
35
+ — or, if the 1B welcome assistant must stay two-tool, build the surface with
36
+ `memory: "off"` so the hint and skill disappear too. Add a pi-web test either
37
+ way (tests/pi-web.test.ts has no memory coverage today).
38
+
39
+ ### 2. HIGH — `harness pi` maps EVERY /v1/models row to id `"local"` (duplicate models)
40
+ `/v1/models` now returns the served model FIRST **plus every other servable
41
+ registry model** (src/server.ts:1479–1499). Both consumers still assume the old
42
+ single-entry shape:
43
+ - `src/harness-pi.ts:76–81` (`fetchServerModels`) maps all rows;
44
+ - the generated extension (`renderPiExtension`, src/harness-pi.ts:116–125) and
45
+ the baked fallback (`modelEntry`, :140–151) register one pi model per row,
46
+ **all with id `local`** — N duplicate ids, N−1 of which name models the
47
+ single-model server is not actually serving.
48
+ Side effects: `installPiExtension`'s `bakedModels` and the CLI's
49
+ "server live · …" line print the whole registry; `src/cli.ts:1615`
50
+ ("reusing running server at … (…)") lists every downloaded model as if served.
51
+ `tests/harness-pi.test.ts` stubs a single-row /v1/models, so this is untested.
52
+ **Fix:** in `fetchServerModels`, keep only the served entry — the first row is
53
+ the served model and is the only one carrying the capability extras
54
+ (`context_window`, `reasoning`); e.g. `body.data?.[0]` (or filter rows that
55
+ have `context_window`). Same filter inside the generated extension's live
56
+ fetch. Update cli.ts:1615/1668 to label `models[0]` as served. Extend the
57
+ harness test with a multi-row stub.
58
+
59
+ ### 3. HIGH — Tier-0 generics (and plain Qwen3 chat) get Gemma's token sentinels
60
+ `toolStreamMode` (src/server.ts:1175–1183) returns `"gemma-sentinel"` for every
61
+ model that isn't MiniCPM5/Qwen3.5 — including the new universal-dense generics
62
+ (llama, qwen2, qwen3, gemma1/2, phi3, olmo2, glm4, granite, starcoder2,
63
+ smollm3 — src/model/universal/archs.ts, served via src/model/factory.ts:42–45)
64
+ and plain Qwen3. In that mode `ToolAwareStream.push` (src/server.ts:500–509)
65
+ unconditionally interprets token ids 48/49/100/101 as tool/channel sentinels.
66
+ Those ids are Gemma-4 special tokens (src/tool-call.ts:9–23); on any other
67
+ tokenizer they're ordinary low-id vocab entries, so a generic model emitting
68
+ one silently reroutes all subsequent output into a phantom tool segment or
69
+ "reasoning". It also means generics never reach `parseGeneratedToolCalls`
70
+ (buffered-text), so tool calling is broken for them — and pi always sends
71
+ tools, and `mlx-bun pi -q <generic>` is a supported path now.
72
+ **Fix (src/server.ts:1180–1182):** make gemma-sentinel the *family-gated*
73
+ branch, not the default:
74
+ ```ts
75
+ if (ctx.model.config.modelType.startsWith("gemma4")) return "gemma-sentinel";
76
+ return tools?.length ? "buffered-text" : "plain";
77
+ ```
78
+ (`ctx.template.thinkingFormat === "gemma-channel"` is an alternative key, but
79
+ DiffusionGemma forces it null while still being gemma-family, so prefer
80
+ modelType.) Buffered-text already handles the OpenAI-JSON `<tool_call>` shape
81
+ most generic templates emit; models whose format isn't covered fail soft
82
+ (markup stays in content) instead of corrupting the stream.
83
+
84
+ ### 4. MEDIUM — vision capability never reaches the pi terminal or external pi
85
+ `/v1/models` doesn't advertise vision (src/server.ts:1479–1485: only
86
+ `context_window` + `reasoning`), `RunEmbeddedPiOptions` has no `vision` field
87
+ and `src/cli.ts:1670` passes none, so `mlx-bun pi` on gemma-4-e4b — the
88
+ *default* model, vision-capable — registers `input: ["text"]`
89
+ (src/pi-provider.ts:100) and images are unusable in the TUI. The harness
90
+ extension is hardcoded `input: ["text"]` too (src/harness-pi.ts:121, :146).
91
+ Only pi-web wires it (src/pi-web.ts:491–495, from `ctx.vision || ctx.loadVision`
92
+ via createServer). The `input` flag MUST match the real model (pi-ai
93
+ content-parts vs string serialization — pi-provider.ts:44–49), so this needs
94
+ the real signal, not a default.
95
+ **Fix:** add `vision: !!(ctx.vision || ctx.loadVision)` to the served entry in
96
+ /v1/models; carry it through `ServerModel`/`fetchServerModels`,
97
+ `renderPiExtension`/`modelEntry` (`input: m.vision ? ["text","image"] : ["text"]`),
98
+ `RunEmbeddedPiOptions`, and the cli `pi` case.
99
+
100
+ ### 5. MEDIUM — harness extension drifted from buildPiProvider on compat flags
101
+ The internal provider sets `compat: { supportsDeveloperRole: false, … }`
102
+ (src/pi-provider.ts:89–92); the generated extension sets only `thinkingFormat`
103
+ for reasoning models (src/harness-pi.ts:120, :145). Currently masked because
104
+ the server remaps `developer` → `system` (src/server.ts:743–760), but the two
105
+ paths are documented as "kept aligned" (pi-provider.ts:20–22, harness-pi.ts
106
+ header) and have drifted. **Fix:** emit the same
107
+ `supportsDeveloperRole: false` block in `renderPiExtension`/`modelEntry`.
108
+
109
+ ### 6. MEDIUM — `memory_status` tells the built-in agent synthesis doesn't exist
110
+ `src/memory/tools.ts:571–572` hardcodes
111
+ `"last synthesis: not available yet (M1 synthesis is stubbed)"` and
112
+ `"synthesis: M1 stub (manual/nightly writes are not implemented yet)"`.
113
+ Synthesis is live: `mlx-bun memory synthesize` runs the full DAG
114
+ (src/cli.ts:2508ff) and the nightly launchd job runs it
115
+ (src/memory/schedule.ts:66–72). The pi session will confidently tell the user
116
+ the feature doesn't exist. **Fix:** drop the stub lines; report real state
117
+ (e.g. last synthesis run from the synthesis DB / vault git log, and
118
+ "run: mlx-bun memory synthesize").
119
+
120
+ ### 7. MEDIUM — docs/reference/memory.md contradicts itself
121
+ Line 9: "Synthesis is scaffolded but still a no-op M1 stub" vs line 41
122
+ ("**`memory synthesize` writes to your vault.** It runs the full local
123
+ synthesis DAG …") and line 77 ("Synthesis is live today"). Fix line 9 (and the
124
+ line-53 "last-synthesis placeholder" wording once #6 lands).
125
+
126
+ ### 8. LOW — server-api.md pi section omits `harness pi`
127
+ docs/reference/server-api.md:462–477 documents only the manual
128
+ `~/.pi/agent/models.json` route (which also bakes a concrete model id — the
129
+ staleness problem the `local` id exists to solve). Add: the supported
130
+ one-command path is `mlx-bun harness pi` (extension + live discovery +
131
+ `--remove` undo), manual models.json as the fallback.
132
+
133
+ ### 9. LOW — three base-URL spellings for one loopback server
134
+ pi-web uses `http://127.0.0.1:${port}/v1` (src/pi-web.ts:487); the cli `pi`
135
+ probe/provider uses `http://localhost:${port}/v1` (src/cli.ts:1610, also the
136
+ serve preflight at :1215); harness `DEFAULT_BASE_URL` is
137
+ `http://localhost:8080/v1` (src/harness-pi.ts:25). Functionally coherent today
138
+ — verified empirically: Bun's fetch to `localhost` reaches both a
139
+ 127.0.0.1-bound and a ::1-bound server — and the port sweep from 8ee691d is
140
+ clean (no live `8090` outside historical artifacts). But the harness extension
141
+ runs under the *user's pi* (node/jiti, not Bun), so standardize on
142
+ `127.0.0.1` (the actual default bind) everywhere to be resolver-proof.
143
+
144
+ ### 10. LOW — `mlx-bun pi` arg edges (flag-strip itself is clean)
145
+ Verified complete: `OURS_VAL`/`OURS_BOOL` (src/cli.ts:1575–1582) cover every
146
+ flag read by `serverRuntimeFlags`/`applyDecodeRoute`/`mountStartupAdapter`/
147
+ `resolveModelAuto` (cross-checked flag-by-flag); `opt()` only supports
148
+ space-separated values repo-wide so there's no `--flag=value` bypass; the
149
+ "ignoring …" notice on server reuse works; `harness pi` passes nothing through
150
+ to pi (no leak path). Two edges remain:
151
+ - `parsePiArgs` (src/pi-terminal.ts:237–242) drops an unknown flag but keeps
152
+ its **value** as message text: `mlx-bun pi --resume abc123` starts a chat
153
+ with the message "abc123". Either swallow the next token for unknown
154
+ `--flags` or warn that the flag was ignored.
155
+ - `RunEmbeddedPiOptions.verbose` (src/pi-terminal.ts:97) is never settable —
156
+ parsePiArgs doesn't recognize `--verbose`. Wire it or delete it.
157
+
158
+ ## Verified-good (no action)
159
+ - **Compat flags per model**: `buildPiProvider` derives `reasoning` →
160
+ `thinkingFormat: "qwen-chat-template"` from `ctx.template.supportsThinking`,
161
+ which is detected generically from the template source (`enable_thinking` +
162
+ `<think>`/`<|channel>`, src/chat-template.ts:143–159) — covers Gemma,
163
+ MiniCPM5, Qwen3.5, and Tier-0 generics with think-tag templates alike; the
164
+ server honors `chat_template_kwargs.enable_thinking` and `reasoning_effort`
165
+ with one precedence function (src/server.ts:1101–1108).
166
+ - **Wire contract**: pi's OpenAI `tool_calls` shape is produced for both the
167
+ sentinel and buffered-text parsers (src/server.ts:548–575, 1916–1923,
168
+ 1989–2003), `finish_reason: "tool_calls"` included; `developer` role remapped.
169
+ - **Isolation**: both embeds use in-memory auth/registry (no ~/.pi cross-talk),
170
+ own agent dirs (`~/.mlx-bun/pi`, `~/.mlx-bun/pi-sessions`), `noExtensions/
171
+ noSkills/noPromptTemplates/noThemes`, curated skills only.
172
+ - **Server ownership**: `pi`-started servers are `owner: "pi-session"` and the
173
+ reuse path warns when attaching to another session's server (cli.ts:1624–1631).
174
+ - **Memory read tools vs current vault code**: all imports
175
+ (vault/query/article/schedule) exist and match; terminal surface exposes them
176
+ correctly; FIND→READ contract intact. Only the status text (#6) is stale.
177
+ - **contextWindow fallbacks**: missing `max_position_embeddings` degrades to
178
+ 32768 at every consumer (`?? 32768` / `?? DEFAULT_CONTEXT_WINDOW`).
@@ -0,0 +1,122 @@
1
+ # v0.0.9 release notes (DRAFT — Josh edits + publishes)
2
+
3
+ Everything since v0.0.8 (2026-06-24). The big themes: the mlx-lm drop-in
4
+ surface is now materially complete, ORPO is paper-faithful, mlx-bun runs its
5
+ first generically-supported architectures at verified bit-exact parity, and an
6
+ adversarial review swept the whole engine — fixing 12 confirmed bugs and
7
+ overturning a load-bearing performance assumption.
8
+
9
+ ## Drop-in compatibility with mlx_lm.server (the headline)
10
+
11
+ - **Endpoints:** `POST /v1/completions` (raw text completion, stream +
12
+ non-stream), `GET /health` (byte-exact body), `GET /v1/models` now lists all
13
+ registry models (+ `/v1/models/<id>`).
14
+ - **Request fields:** `min_p`, `xtc_probability`/`xtc_threshold`, `logit_bias`,
15
+ `presence_penalty`/`frequency_penalty` (+ `*_context_size` windows),
16
+ `logprobs`/`top_logprobs` with mlx-lm's EXACT semantics (same distribution,
17
+ same response shape, same [0,11] validation). All L1-faithful ports of
18
+ `sample_utils.py`.
19
+ - **Flags/defaults:** port 8080 + loopback host (matching mlx_lm.server;
20
+ `--host 0.0.0.0` = LAN opt-in), `--temp` alias, `--max-tokens`,
21
+ serve `--adapter <dir>` mounts at startup.
22
+ - **New CLI verbs:** `fuse` (fold LoRA into base — mlx-lm math, untouched
23
+ modules bit-identical), `convert` (wraps the mixed-precision quantizer,
24
+ `--target-bpw` exposed), `perplexity` (mlx_lm.perplexity methodology),
25
+ `upload` (native HF push).
26
+ - Deliberately not ported: `role_mapping` (unreachable — every supported model
27
+ ships a chat template). Remaining: `cache_prompt`, `evaluate` (planned as an
28
+ lm-eval shim), `--draft-model`, awq/dwq/gptq (plan:
29
+ docs/design/mlx-lm-tool-parity-plan.md).
30
+
31
+ ## ORPO: paper-faithful by default
32
+
33
+ - **`sft_scope: full | response`** (default `full`): the chosen-NLL is now the
34
+ token-mean CE over the full prompt+response — matching the ORPO paper, TRL,
35
+ and xfactlab/orpo — implemented across every path (naive/chunked/fused/
36
+ flash-CCE/prefix-shared/segmented). `response` reproduces old runs
37
+ BIT-EXACTLY (regression-pinned). The odds-ratio terms stay response-only in
38
+ both modes (matches TRL). CLI: `mlx-bun train --sft-scope`.
39
+ - The rest of the ORPO stack was adversarially verified correct against
40
+ primary sources (odds-ratio math, gradients vs finite differences,
41
+ prefix-sharing gradient flow, tokenization/label-shift).
42
+
43
+ ## Generic model support (Tier-0)
44
+
45
+ - **New:** `src/model/universal/` — a config-driven UniversalDense module with
46
+ explicit descriptors for llama, qwen2, qwen3, gemma, gemma2, phi3, olmo2,
47
+ glm4, granite, starcoder2, smollm3 (+ mlx-lm's remapping: mistral etc.).
48
+ bf16 (unquantized) checkpoints now load. Rope factory (llama3/yarn/longrope)
49
+ verified bit-exact vs the oracle.
50
+ - **First three archs verified at L1 parity** (bit-exact per-step logits):
51
+ Llama-3.2-1B-Instruct-4bit, Qwen2.5-0.5B-Instruct-4bit, gemma-2-2b-it-4bit.
52
+ - Dispatch ladder: dedicated → generated → generic → reject-with-helpful-error.
53
+ Targeted models keep their optimized paths; a model can graduate.
54
+ Design: docs/design/generic-model-support.md.
55
+
56
+ ## Fixed (the adversarial-review wave)
57
+
58
+ - **`--l2` tier contract restored:** the L2 preset was enabling an
59
+ envelope-gated perf kernel (not bit-exact vs optiq). Bare `--l2` is now
60
+ genuinely optiq-bit-exact; the perf kernel is `--l3`/explicit opt-in.
61
+ `generate --l2/--l3` no longer silently degrades to L1.
62
+ - Batched same-millisecond seed collision (identical concurrent completions);
63
+ streaming per-layer KV-quant conversion (the missing half of optiq's
64
+ tight-RAM fix); segmented compiled-decode mid-step failure KV corruption;
65
+ `MLX_BUN_TRAIN_ATTN=flash` now refused on Gemma (unrevalidated crash path);
66
+ eval-loss error swallowing; wrong tokens_per_sec metric; silent steel-kernel
67
+ fallbacks now warn; flash-attention tests extended (D=256, sliding-window,
68
+ non-tile T).
69
+ - Memory (the Dreaming): batching default flipped to serial (measured 1.7–1.9×
70
+ faster for the real workload); `memory status` truth; third-person voice fix
71
+ merged.
72
+ - CLI audit (14 findings): `fit --ctx` help matched code, `embed` auto-pick
73
+ fixed, pi flag leakage into first message fixed, `--l1/2/3` + `generate` +
74
+ `train-watch` documented, `setup` is a real alias, doc lies corrected.
75
+
76
+ ## Performance truth (research, groundwork for the next release)
77
+
78
+ - The "decode is at the memory floor" claim was **overturned** with measured
79
+ rooflines: only the 12B is at the bandwidth wall (~92%); CPM5/e4b/26B sit at
80
+ 58–70% with the host-side graph build as the top recoverable term. Ranked
81
+ fix plan in-repo; wins land next release.
82
+ - The batching engine was audited (numerics solid, engine naive) — hotfixes +
83
+ containment landing; plan: docs/design/batching-v2-plan.md.
84
+ - Registry duplicates root-caused (snapshot-per-commit accumulation);
85
+ dedupe + `gc` landing.
86
+
87
+ ## Infra
88
+
89
+ - CI gate added (typecheck + model-free tests on push/PR) — previously nothing
90
+ ran on push.
91
+ - Repo hygiene: scratch artifacts untracked, megakernel research relocated,
92
+ STATUS.md rewritten as a true current-state front door, doc map regenerated.
93
+
94
+ ## Late additions (landed after the first draft)
95
+
96
+ - **Batching engine v2 (steps 1–3):** Qwen3.5/SSM models no longer fail under
97
+ `--batch` (capability gate + serial drain); per-row failure containment; no
98
+ serial-lane starvation; pipelined batched decode + sane cache cadence; the
99
+ huge admit transient fixed; all GPU work under one lock.
100
+ - **Model management:** `ls` deduplicates to one row per repo (snapshot
101
+ history via `ls --all-revisions`); new `mlx-bun gc` reclaims superseded
102
+ snapshots (24.7 GB found on the dev machine) with a safety guard for
103
+ unique files; `get` accepts substring queries; download lockfile, streamed
104
+ resume verification, gated-repo auth hints; vision capability detected from
105
+ config (the unified-vision 12B now shows `vision`). New doc:
106
+ docs/reference/models.md.
107
+ - **Pi integration:** web-chat memory tools actually callable; `harness pi`
108
+ registers exactly the served model; tool-calling fixed for Tier-0 generic
109
+ models (Gemma sentinels no longer applied to non-Gemma tokenizers); vision
110
+ advertised to pi; `mlx-bun pi` no longer leaks unknown flags into the first
111
+ message.
112
+ - **Website + README:** six-goals restructure, drop-in guide, memory guide,
113
+ the lab page, full CLI reference from a tracked source; deploy now triggers
114
+ on reference-doc edits. NOTE the release sequence: push and publish in the
115
+ same sitting — the site deploys from the push.
116
+ - **Test infra:** batched-serving goldens are machine-keyed (the M1-Max
117
+ "failures" were M4-Pro fixtures; all code exonerated — mlx-lm reproduces
118
+ mlx-bun token-for-token per machine); CI gate (typecheck + model-free tests).
119
+ - **Research (the lab):** decode-roofline re-measurement overturned the "at
120
+ the floor" assumption (docs/investigations/decode-roofline-lookagain.md);
121
+ curve-sampler distinctness theorem + witness numbers + preregistered
122
+ protocol (docs/planning/curve-sampler-research-plan.md).
@@ -0,0 +1,213 @@
1
+ # Web UI pass — ranked fix list (2026-07-01 audit)
2
+
3
+ Read-only audit of every web surface against the current server contract:
4
+ `src/web/app.html` (unified SPA: chat / quantize / finetune / dataset /
5
+ status+library / routes), `src/assets/curve-designer.html` (/curves),
6
+ `src/pi-web.ts` (WS chat bridge), `src/server.ts` routes, plus the dead
7
+ `src/status-page.html`. Method: diffed every fetch/WS consumer in the pages
8
+ against the actual handler shapes in `server.ts`, `src/jobs/types.ts`,
9
+ `src/train/{job,trainer}.ts`, `src/quantize/job.ts`, `src/download.ts`,
10
+ `src/registry.ts`, `src/model/support.ts`.
11
+
12
+ **Live click-through was skipped deliberately**: GPU-loading test suites
13
+ (`bun test tests/server.test.ts …`, loads MiniCPM5) were running for the whole
14
+ audit window, and starting a second model server would have contended with
15
+ them. Every finding below is source-verified on both sides of the contract.
16
+
17
+ Port sweep: no stale `8090` or hardcoded URLs remain in any html/js.
18
+ `curve-designer.html:92` falls back to `http://localhost:8080` (matches the
19
+ new default) and uses `location.origin` when served at `/curves`. ✓
20
+
21
+ ---
22
+
23
+ ## WRONG (bugs — fix first)
24
+
25
+ 1. **Fine-tune live stats never show LR or tok/s — metric field-name mismatch.**
26
+ `src/web/app.html:2317-2318` reads `e.lr` / `e.tps`, but the trainer emits
27
+ `learning_rate` / `tokens_per_sec` (`src/train/trainer.ts:559-560`, same at
28
+ :724-727 and :966-969; contract comment in `src/jobs/types.ts:23`). The
29
+ "learning rate" and "tok/s" chart stats stay `—` for the entire run.
30
+ Also update the protocol doc-comment at `app.html:1089` (`lr?`, `tps?`).
31
+ **Fix:** read `e.learning_rate` and `e.tokens_per_sec`.
32
+
33
+ 2. **Chat queue bar renders arrays as strings and empty tags.**
34
+ `pi-web.ts:183` sends `queue_update` with `steering: readonly string[]` and
35
+ `followUp: readonly string[]` (arrays). `app.html:1683-1684` does
36
+ `if (q.steering) … esc(String(q.steering))` — an empty array is truthy, so
37
+ every `queue_update` renders empty `steering:`/`queued:` pills, and multiple
38
+ queued messages render comma-joined.
39
+ **Fix:** guard with `.length` and render `q.followUp.map(...)` (or
40
+ `q.steering[q.steering.length-1]`).
41
+
42
+ 3. **Library table loads exactly once per app lifetime — new quants never appear.**
43
+ `app.html:2603` gates `loadLibrary()`/`loadFit()` behind `fitLoaded`, which
44
+ is never reset. The server explicitly invalidates its 30 s `/library` cache
45
+ when a quantize job completes so "the next poll rescans and shows it"
46
+ (`src/server.ts:2510-2513`) — but the UI never re-polls. A model quantized,
47
+ downloaded, or gc'd mid-session never shows until a full page reload.
48
+ **Fix:** refresh the library on its own interval (e.g. every 15 s inside
49
+ `tick()`), and force one refresh when a quantize job reaches `done`.
50
+
51
+ 4. **Adapter mount failure toasts "[object Object]".**
52
+ `POST /v1/adapters` errors use the OpenAI envelope `{error:{message}}`
53
+ (`src/server.ts:1600`), and the `api()` helper (`app.html:1080`) keeps
54
+ `data.error` as that object; `onSelectAdapter` (`app.html:1908-1909`) then
55
+ string-concatenates it. Any mount failure reads
56
+ `adapter: [object Object]`.
57
+ **Fix:** in `api()`, unwrap
58
+ `data.error && typeof data.error === "object" ? data.error.message : …`.
59
+
60
+ 5. **Quantize inspect result parsing targets a response shape the server never sends.**
61
+ `inspectModel` returns `support: boolean` (`src/quantize/job.ts:101`), but
62
+ `app.html:2135-2140` checks `d.support === "supported" | "ok" | "untested"`
63
+ (all dead branches) and, when `support` is false, prints
64
+ `esc(d.support || "unknown architecture")` → always "unknown architecture"
65
+ even when `arch` was resolved. `size_gb` is also rendered raw (unrounded
66
+ float, and `0` when a direct path bypasses the registry).
67
+ **Fix:** treat `support` as boolean; show `arch` + "not quantizable" on
68
+ false; `(+d.size_gb).toFixed(2)` and hide when 0.
69
+
70
+ 6. **Stale vision claim in the chat attach path.**
71
+ `app.html:1848`: "This model can't see images — switch to the **12B** for
72
+ vision." SigLIP landed: e2b/e4b/26B/31B are vision-capable too
73
+ (`src/registry.ts:213-219` `visionCapable`), so the 12B-only advice is
74
+ wrong.
75
+ **Fix:** model-agnostic copy: "This model can't see images — serve a
76
+ vision-capable model (Gemma vision builds show a *vision* tag in the
77
+ Library)."
78
+
79
+ ---
80
+
81
+ ## MISSING (current capabilities the UI doesn't expose)
82
+
83
+ 7. **`sft_scope` absent from the fine-tune form.**
84
+ The finetune job accepts `sft_scope: "full" | "response"` and validates it
85
+ at submit (`src/train/job.ts:54,74-76`); default is now `"full"`
86
+ (paper/TRL-faithful, commit d32fe32). The ORPO panel
87
+ (`app.html:791-800`) has λ + schedule but no scope control, and
88
+ `collectHP()` (`app.html:2276-2289`) never sends it.
89
+ **Fix:** add a segmented control (full · response) to `#f-orpo-extra` with
90
+ a hint ("full = paper/TRL chosen-NLL over the whole sequence; response =
91
+ pre-2026-07 response-only"), include `sft_scope` in `collectHP()` when
92
+ method === "orpo".
93
+
94
+ 8. **`min_p` / `xtc_probability` / `xtc_threshold` / `logprobs` not in any request-builder UI.**
95
+ Server supports them per-request (`src/server.ts:280-304`, wired at
96
+ :1138-1141). The chat sampling popover (`app.html:592-613`) only has
97
+ temperature/top_p/top_k — and the whole chain is width-limited: the
98
+ `set_sampling` WS frame (`pi-web.ts:160`), `SamplingOverrides`
99
+ (`pi-web.ts:408-412`), and `injectSampling` (`pi-web.ts:420-440`) all only
100
+ carry those three fields.
101
+ **Fix (scoped):** add `min_p` (slider 0–0.5) end-to-end: popover row →
102
+ `set_sampling` frame → `SamplingOverrides.min_p` → `injectSampling`. XTC and
103
+ logprobs are power-user/API features; document rather than add sliders.
104
+
105
+ 9. **No gc/reclaim story in Library or Downloads.**
106
+ `mlx-bun gc` exists (CLI `src/cli.ts:1100`; planner
107
+ `src/registry.ts:375-455` `planGc`/`executeGc`) and the download path
108
+ prints "gc reclaims ~X GB" (`cli.ts:1015-1018`) — but the web Library shows
109
+ superseded revisions' disk cost with no affordance. **Needs a new server
110
+ endpoint first** (e.g. `GET /api/gc/plan`, `POST /api/gc/execute {yes}`),
111
+ then a Library footer line: "N superseded snapshots · reclaim X GB —
112
+ [Clean up]". (Separate from the listCanonical wiring below, but pairs
113
+ naturally with it: dedupe hides the stale revisions, gc deletes them.)
114
+
115
+ 10. **Other accepted-but-hidden finetune/quantize knobs** (lower priority):
116
+ `grad_accumulation_steps`, `warm_start_adapter`, adapter naming (submit
117
+ always writes `adapter-<timestamp>`, `src/server.ts:2529-2530`) on the
118
+ finetune form; `reference`, `calibration_mix`, `n_calibration`
119
+ (`src/server.ts:2460-2461`) on the mixed-bpw quantize step. Add an
120
+ "Advanced" disclosure rather than more top-level fields.
121
+
122
+ ---
123
+
124
+ ## BLOCKED on the queued server.ts listCanonical / visionCapable / tier wiring
125
+
126
+ The functions exist and are tested (`Registry.listCanonical`
127
+ `src/registry.ts:181`, `visionCapable` :217, `supportTier`
128
+ `src/model/support.ts:53`) but the server still serves the old view. UI
129
+ consumption points to touch when the wiring lands:
130
+
131
+ 11. **Revision duplicates in /library and /v1/models.**
132
+ `src/server.ts:1263` (`/library`) and :1491 (`/v1/models`) iterate
133
+ `reg.list()` — one row per cached snapshot revision. Both should use
134
+ `reg.listCanonical()`. UI needs no change; the Library table and every
135
+ /v1/models-driven picker (external pi, CLI) currently show dupes.
136
+
137
+ 12. **`vision` flag understates capability.**
138
+ `src/server.ts:1278` sends `vision: m.hasVisionSidecar`, which misses the
139
+ 12B's encoder-free `gemma4_unified_vision` (config-declared, no sidecar
140
+ file). Should be `visionCapable(m)`. UI consumption at `app.html:2663`
141
+ then needs no change.
142
+
143
+ 13. **`supported` is a boolean; tiers are invisible.**
144
+ `src/server.ts:1264` sends `supported: isSupportedModelRecord(...)`.
145
+ Generic Tier-0 models (universal-dense descriptors) now count as
146
+ supported — correct — but the Library can't distinguish "targeted
147
+ (L2/L3 paths)" from "generic (L1 monolith only)". When the server sends
148
+ `tier: "targeted" | "generic" | null` (from `supportTier`), update:
149
+ - `app.html:2659` status cell: keep `unsupported (model_type)` for null;
150
+ add a dim "generic" badge for Tier-0 rows.
151
+ - Keep `supported` as a boolean alongside `tier` for one release so the
152
+ UI change can land independently.
153
+
154
+ ---
155
+
156
+ ## CONFUSING / polish
157
+
158
+ 14. **Sampling popover "recommended" values are hardcoded to MiniCPM5.**
159
+ `app.html:1824-1825` (`SAMP_REC {topP:.95, topK:0}`, `recTemp()` 0.9/0.7)
160
+ claims to "mirror server toOptions defaultTemp", but the server resolves
161
+ defaults from each model's `generation_config.json`
162
+ (`src/server.ts:1120-1132`). For any other served model the dimmed "auto"
163
+ readouts lie. Fix: surface `genDefaults` (e.g. on the `ready` frame or
164
+ `/v1/models` extras) and seed the popover from it.
165
+
166
+ 15. **Adapter dropdown goes stale after training.**
167
+ `refreshAdapters()` runs only at chat init and on `ready` frames
168
+ (`app.html:1789-1792, 2037`). An adapter trained in the Fine-tune tab
169
+ doesn't appear until reconnect/reload. Refresh on finetune `done` and on
170
+ chat `enter()`. Also escape the interpolation at `app.html:1897-1898`
171
+ (`a.id`/`a.path` go into HTML unescaped).
172
+
173
+ 16. **Dead file: `src/status-page.html` (497 lines) + `scripts/status-page-stub.ts`.**
174
+ Nothing imports it since the SPA absorbed status (only
175
+ `scripts/status-page-stub.ts` references it). Delete both, or move the
176
+ stub note into the SPA docs.
177
+
178
+ 17. **Routes tab breaks in the compiled binary.**
179
+ `#/routes` iframes `/dag`, which `readFileSync`s
180
+ `../docs/dag/training-inference-map.html` (`src/server.ts:1231`) — absent
181
+ outside the repo checkout, so the tab shows a bare 404 string. Either
182
+ embed the artifact like app.html, or hide the tab when `/dag` 404s.
183
+
184
+ 18. **CLI banner vs landing route.** `src/cli.ts:64` says "status page at
185
+ http://localhost:8080/", but `/` lands on `#/chat`. Say "web app" or point
186
+ at `/#/status`.
187
+
188
+ 19. **Curves nav tab never highlights.** It's a full-page `href="/curves"`
189
+ (`app.html:520`), outside the hash router, so the active-tab logic
190
+ (`app.html:1390`) can't mark it. Cosmetic; acceptable, but a
191
+ `aria-current` on the curve page's back-link side would be tidier.
192
+
193
+ 20. **Library "too big" rows hide the useful number.** `app.html:2665-2666`
194
+ shows max context/decode only when `a.fits` (fit solved at 8192); a model
195
+ that fits at a smaller context shows `—`. Show `max_safe_context`
196
+ whenever it's > 0.
197
+
198
+ 21. **Downloads visibility is per-process.** `/downloads` reads a
199
+ process-local tracker (`src/download.ts:276-279`) — a `mlx-bun get` in
200
+ another terminal is invisible to the nav pill/status card. Note in the UI
201
+ copy or leave; fixing needs a shared store.
202
+
203
+ ---
204
+
205
+ ## Suggested landing order
206
+
207
+ 1. Bugs #1–#6 (one UI-only PR; no server changes).
208
+ 2. #7 sft_scope + #15 adapter refresh (small, independent).
209
+ 3. The queued server wiring (#11–#13) as its own PR: `listCanonical` +
210
+ `visionCapable` + `tier` in `/library` and `/v1/models`, then the
211
+ one-line UI badge.
212
+ 4. #8 min_p end-to-end, #9 gc endpoint + Library affordance.
213
+ 5. Cleanup: #16 dead files, #17 routes tab, #14 genDefaults surfacing.