mlx-bun 0.2.0 → 0.3.0

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 (364) hide show
  1. package/README.md +71 -588
  2. package/docs/README.md +91 -0
  3. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  4. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  5. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  6. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  7. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  8. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  9. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  10. package/docs/design/batching.md +569 -0
  11. package/docs/design/decode-speed-program.md +3178 -169
  12. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  13. package/docs/design/generic-model-support.md +579 -334
  14. package/docs/design/kv-cache.md +680 -0
  15. package/docs/design/orpo-training.md +673 -1239
  16. package/docs/design/speculative-decoding.md +1627 -0
  17. package/docs/design/turboquant.md +2102 -0
  18. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  19. package/docs/design/web-chat-redesign.md +666 -1083
  20. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  21. package/docs/planning/release-notes-v0.3.0.md +35 -0
  22. package/docs/planning/video-series.md +2 -2
  23. package/docs/reference/benchmarks.md +1162 -0
  24. package/docs/reference/cli.md +394 -159
  25. package/docs/reference/distribution.md +321 -182
  26. package/docs/reference/environment.md +348 -0
  27. package/docs/reference/glossary.md +39 -0
  28. package/docs/reference/library-api.md +566 -168
  29. package/docs/reference/memory.md +227 -64
  30. package/docs/reference/models.md +104 -6
  31. package/docs/reference/server-api.md +886 -425
  32. package/docs/reference/server-config.md +638 -330
  33. package/docs/reference/training.md +393 -254
  34. package/docs/reference/troubleshooting.md +166 -0
  35. package/package.json +9 -4
  36. package/src/audio/decode.ts +2 -2
  37. package/src/audio/features.ts +2 -2
  38. package/src/backends/mlx/autoregressive.ts +108 -0
  39. package/src/backends/mlx/batch-group.ts +1646 -0
  40. package/src/backends/mlx/diffusion.ts +39 -0
  41. package/src/backends/mlx/draft-target.ts +47 -0
  42. package/src/backends/mlx/evaluation.ts +105 -0
  43. package/src/backends/mlx/fill-append.ts +45 -0
  44. package/src/backends/mlx/gateway-binding.ts +71 -0
  45. package/src/backends/mlx/graph.ts +38 -0
  46. package/src/backends/mlx/memory-client.ts +189 -0
  47. package/src/backends/mlx/memory-guard.ts +40 -0
  48. package/src/backends/mlx/methods.ts +90 -0
  49. package/src/backends/mlx/model-prompt.ts +164 -0
  50. package/src/backends/mlx/model-serving.ts +79 -0
  51. package/src/backends/mlx/prefill.ts +33 -0
  52. package/src/backends/mlx/request-policy.ts +20 -0
  53. package/src/backends/mlx/rollback.ts +51 -0
  54. package/src/backends/mlx/serial-executor.ts +273 -0
  55. package/src/backends/mlx/speculative.ts +114 -0
  56. package/src/backends/mlx/state-views.ts +26 -0
  57. package/src/backends/mlx/text-engine.ts +73 -0
  58. package/src/cli.ts +58 -25
  59. package/src/client.ts +66 -0
  60. package/src/config.ts +21 -4
  61. package/src/contracts/completion.ts +16 -0
  62. package/src/contracts/execution.ts +49 -0
  63. package/src/contracts/generation.ts +103 -0
  64. package/src/contracts/host.ts +7 -0
  65. package/src/contracts/jobs.ts +66 -0
  66. package/src/contracts/pi-provider.ts +30 -0
  67. package/src/contracts/pi.ts +259 -0
  68. package/src/contracts/resources.ts +18 -0
  69. package/src/contracts/scheduling.ts +27 -0
  70. package/src/contracts/tsconfig.json +14 -0
  71. package/src/diffusion/diffusion-generate.ts +218 -153
  72. package/src/embed.ts +1 -1
  73. package/src/engine/admission.ts +59 -0
  74. package/src/engine/cancellation.ts +38 -0
  75. package/src/engine/engine.ts +56 -0
  76. package/src/engine/execution-coordinator.ts +70 -0
  77. package/src/engine/execution-plan.ts +51 -0
  78. package/src/engine/resources.ts +42 -0
  79. package/src/engine/scheduler.ts +67 -0
  80. package/src/engine/session.ts +247 -0
  81. package/src/eval/README.md +44 -1
  82. package/src/eval/kl.ts +236 -3
  83. package/src/eval/reference-logits.ts +207 -0
  84. package/src/eval/runner.ts +8 -96
  85. package/src/fill/echo-index.ts +188 -0
  86. package/src/fill/fill-session.ts +452 -0
  87. package/src/fill/proposal.ts +68 -0
  88. package/src/fill/schema-rows.ts +506 -0
  89. package/src/fill/tool-boundary.ts +103 -0
  90. package/src/fit.ts +1 -1
  91. package/src/generate.ts +656 -268
  92. package/src/grammar.ts +12 -7
  93. package/src/harness-pi.ts +14 -33
  94. package/src/hub-rest.ts +4 -4
  95. package/src/index.ts +7 -0
  96. package/src/inference/denoising.ts +17 -0
  97. package/src/inference/graph.ts +33 -0
  98. package/src/inference/prefill.ts +39 -0
  99. package/src/inference/rollback.ts +7 -0
  100. package/src/jobs/db.ts +4 -0
  101. package/src/jobs/index.ts +2 -0
  102. package/src/jobs/job-entry.ts +12 -12
  103. package/src/jobs/runner.ts +98 -13
  104. package/src/jobs/task-client.ts +19 -0
  105. package/src/jobs/types.ts +2 -58
  106. package/src/kv-store.ts +109 -49
  107. package/src/lab/README.md +18 -0
  108. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  109. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  110. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
  111. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  112. package/src/library.ts +40 -0
  113. package/src/lora.ts +18 -0
  114. package/src/media-fetch.ts +1 -1
  115. package/src/memory/article.ts +1 -1
  116. package/src/memory/chunk.ts +1 -1
  117. package/src/memory/model.ts +30 -169
  118. package/src/memory/prompts.ts +1 -1
  119. package/src/memory/rest.ts +1 -1
  120. package/src/memory/schedule.ts +1 -1
  121. package/src/memory/stages.ts +4 -1
  122. package/src/memory/vault.ts +1 -3
  123. package/src/mlx/array.ts +9 -10
  124. package/src/mlx/compile.ts +12 -2
  125. package/src/mlx/ffi.ts +58 -11
  126. package/src/mlx/materialize.ts +23 -0
  127. package/src/mlx/metal-kernel.ts +10 -11
  128. package/src/mlx/ops.ts +33 -13
  129. package/src/mlx/quantized-rows.ts +36 -0
  130. package/src/mlx/token-bitmask.ts +52 -0
  131. package/src/mlx/turboquant-kv-decode.ts +82 -0
  132. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  133. package/src/mlx/turboquant-ops.ts +33 -3
  134. package/src/mlx/turboquant-tables.ts +2 -2
  135. package/src/model/batched-mask.ts +8 -2
  136. package/src/model/batched-quant.ts +1 -1
  137. package/src/model/batched-rotating-quant.ts +2 -2
  138. package/src/model/batched-rotating.ts +1 -1
  139. package/src/model/compiled-decode.ts +1 -1
  140. package/src/model/diffusion-gemma.ts +8 -8
  141. package/src/model/factory.ts +76 -33
  142. package/src/model/fingerprint.ts +1 -1
  143. package/src/model/flash-attention.ts +3 -3
  144. package/src/model/gemma4-base.ts +26 -9
  145. package/src/model/gemma4.ts +6 -6
  146. package/src/model/generated/gemma4-12b.ts +2 -2
  147. package/src/model/generated/gemma4-26b.ts +2 -2
  148. package/src/model/generated/gemma4-e4b.ts +2 -2
  149. package/src/model/glm52-dsa.ts +13 -9
  150. package/src/model/glm52-memory.ts +1 -1
  151. package/src/model/implementation.ts +69 -0
  152. package/src/model/minicpm5.ts +6 -8
  153. package/src/model/profile.ts +12 -2
  154. package/src/model/qwen-append.ts +12 -0
  155. package/src/model/qwen-conv.ts +52 -0
  156. package/src/model/qwen3_5-checkpoint.ts +164 -0
  157. package/src/model/qwen3_5.ts +140 -56
  158. package/src/model/support.ts +1 -1
  159. package/src/model/trellis-balanced-scatter.ts +51 -0
  160. package/src/model/trellis-linear.ts +672 -0
  161. package/src/model/trellis-shared-m.ts +151 -0
  162. package/src/model/trellis-shared-scatter.ts +144 -0
  163. package/src/model/trellis-splitk-prefill.ts +158 -0
  164. package/src/model/trellis-tiled-prefill.ts +136 -0
  165. package/src/model/trellis-vector-expand.ts +62 -0
  166. package/src/model/trellis-wide-prefill.ts +94 -0
  167. package/src/model/universal/dense.ts +1 -1
  168. package/src/model/universal/modules.ts +8 -1
  169. package/src/native-pack.ts +6 -5
  170. package/src/pi-provider.ts +6 -64
  171. package/src/pi-web.ts +8 -315
  172. package/src/prompt-cache.ts +96 -53
  173. package/src/quantize/allocator.ts +1 -1
  174. package/src/quantize/config-writer.ts +10 -3
  175. package/src/quantize/rotate.ts +4 -4
  176. package/src/quantize/trellis.ts +586 -0
  177. package/src/responses.ts +28 -1
  178. package/src/runtime-config.ts +18 -4
  179. package/src/sampler.ts +4 -4
  180. package/src/serve/batch-scheduler.ts +8 -1671
  181. package/src/serve/chat-request.ts +391 -0
  182. package/src/serve/chat-stage.ts +195 -0
  183. package/src/serve/checkpoint-identity.ts +64 -0
  184. package/src/serve/completion-executor.ts +15 -7
  185. package/src/serve/discovery-routes.ts +12 -13
  186. package/src/serve/generation-gateway.ts +154 -193
  187. package/src/serve/http.ts +159 -0
  188. package/src/serve/inference-request.ts +79 -0
  189. package/src/serve/isolate.ts +297 -105
  190. package/src/serve/lab-routes.ts +3 -0
  191. package/src/serve/lane-registry.ts +2 -1
  192. package/src/serve/model-admin-routes.ts +7 -5
  193. package/src/serve/model-binding.ts +43 -0
  194. package/src/serve/model-host.ts +512 -0
  195. package/src/serve/openai-wire.ts +103 -0
  196. package/src/serve/parent-application.ts +56 -0
  197. package/src/serve/pipeline.ts +33 -0
  198. package/src/serve/preparation.ts +33 -0
  199. package/src/serve/request-plan.ts +19 -15
  200. package/src/serve/request-prep.ts +346 -0
  201. package/src/serve/response-lease.ts +24 -0
  202. package/src/serve/responses-client.ts +69 -0
  203. package/src/serve/session-completion-engine.ts +110 -0
  204. package/src/serve/session-search.ts +1 -1
  205. package/src/serve/static-routes.ts +5 -5
  206. package/src/serve/text-completion-stage.ts +104 -0
  207. package/src/serve/token-streams.ts +519 -0
  208. package/src/server.ts +317 -2649
  209. package/src/spec/assistant-source.ts +10 -44
  210. package/src/spec/deepspec-source.ts +6 -6
  211. package/src/spec/dflash-source.ts +8 -8
  212. package/src/spec/dspark/ab-stats.ts +3 -3
  213. package/src/spec/dspark/calibration.ts +2 -2
  214. package/src/spec/dspark/deepspec-module.ts +2 -2
  215. package/src/spec/dspark/generate-dflash.ts +2 -2
  216. package/src/spec/dspark/generate.ts +2 -2
  217. package/src/spec/dspark/loader.ts +1 -1
  218. package/src/spec/dspark/module-dflash.ts +4 -3
  219. package/src/spec/dspark/module.ts +3 -3
  220. package/src/spec/dspark/quantize-drafter.ts +1 -1
  221. package/src/spec/generate.ts +1 -1
  222. package/src/spec/glm52-mtp-source.ts +1 -1
  223. package/src/spec/ngram-source.ts +1 -1
  224. package/src/spec/prefix-state.ts +45 -0
  225. package/src/spec/qwen-mtp-source.ts +166 -69
  226. package/src/spec/serve-loop.ts +154 -156
  227. package/src/spec/source.ts +42 -10
  228. package/src/spec/two-model.ts +1 -1
  229. package/src/ssd-cache.ts +80 -12
  230. package/src/ssd-durability.ts +25 -13
  231. package/src/tool-call.ts +51 -14
  232. package/src/train/flash-cce.ts +2 -2
  233. package/src/train/forward.ts +6 -0
  234. package/src/train/loss.ts +1 -1
  235. package/src/train/prefix-shared.ts +5 -1
  236. package/src/train/segmented.ts +5 -3
  237. package/src/train/trainer.ts +6 -6
  238. package/src/vision/prompt.ts +1 -1
  239. package/src/vision/qwen3vl-tower.ts +52 -10
  240. package/src/web/app.html +1 -1
  241. package/src/web/app.js +5 -3
  242. package/src/web/src/adapters-panel.ts +3 -3
  243. package/src/web/src/assistant.ts +2 -36
  244. package/src/web/src/chat.ts +4 -4
  245. package/src/web/src/composer.ts +4 -4
  246. package/src/web/src/hub.ts +1 -1
  247. package/src/web/src/markdown.ts +0 -0
  248. package/src/web/src/memory-panel.ts +1 -1
  249. package/src/web/src/model-picker.ts +1 -1
  250. package/src/web/src/palette.ts +1 -1
  251. package/src/web/src/protocol.ts +3 -39
  252. package/src/web/src/sessions.ts +1 -1
  253. package/src/web/src/ui-catalog.ts +4 -18
  254. package/src/web-assets.ts +47 -0
  255. package/src/weights.ts +55 -9
  256. package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
  257. package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
  258. package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
  259. package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
  260. package/docs/archive/investigations/hlg-ab.html +0 -34
  261. package/docs/archive/investigations/hlg-explorer.html +0 -238
  262. package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
  263. package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
  264. package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
  265. package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
  266. package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
  267. package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
  268. package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
  269. package/docs/archive/investigations/hlg-report.html +0 -262
  270. package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
  271. package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  272. package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
  273. package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
  274. package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
  275. package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
  276. package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
  277. package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
  278. package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
  279. package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
  280. package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  281. package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
  282. package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
  283. package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
  284. package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  285. package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  286. package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  287. package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  288. package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  289. package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  290. package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  291. package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
  292. package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
  293. package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
  294. package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
  295. package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
  296. package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  297. package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
  298. package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
  299. package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
  300. package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
  301. package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
  302. package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  303. package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
  304. package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  305. package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  306. package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  307. package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  308. package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  309. package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  310. package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
  311. package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  312. package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
  313. package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  314. package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  315. package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  316. package/docs/archive/mlx-bun-lab-report.html +0 -275
  317. package/docs/archive/wwdc-mlx-bun.html +0 -848
  318. package/docs/design/adapters-end-to-end.md +0 -205
  319. package/docs/design/batching-perf-path.md +0 -255
  320. package/docs/design/batching-v2-plan.md +0 -529
  321. package/docs/design/bucketing-stage.md +0 -197
  322. package/docs/design/compat-cli-surface-design.md +0 -230
  323. package/docs/design/docs-reorg-plan.md +0 -165
  324. package/docs/design/dspark-serving-program.md +0 -371
  325. package/docs/design/dspark-speculative-decoding.md +0 -318
  326. package/docs/design/grammar-spec-batching-integration.md +0 -301
  327. package/docs/design/memory-inference-path.md +0 -422
  328. package/docs/design/memory-synthesis.md +0 -287
  329. package/docs/design/memory-system.md +0 -237
  330. package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
  331. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  332. package/docs/design/omlx-adoption-map.md +0 -98
  333. package/docs/design/orpo-dynamic-lambda.md +0 -220
  334. package/docs/design/orpo-future-enhancements.md +0 -340
  335. package/docs/design/paged-kv-cache.md +0 -214
  336. package/docs/design/parallel-slots.md +0 -567
  337. package/docs/design/parity-tier-dag.md +0 -161
  338. package/docs/design/repo-cleanup-plan.md +0 -231
  339. package/docs/design/runtime-isolation.md +0 -263
  340. package/docs/design/segmented-backward-training.md +0 -543
  341. package/docs/design/spec-decode-larger-targets.md +0 -229
  342. package/docs/design/ssd-kv-cold-tier.md +0 -415
  343. package/docs/design/structured-output.md +0 -484
  344. package/docs/design/tauri-desktop-app.md +0 -127
  345. package/docs/design/the-dreaming-master-plan.md +0 -445
  346. package/docs/design/turboquant-kv.md +0 -235
  347. package/docs/design/turboquant-weights.md +0 -467
  348. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  349. package/docs/design/web-chat-beat-matrix.md +0 -315
  350. package/docs/design/write-pipeline-entity.md +0 -468
  351. package/docs/planning/IDEAS.md +0 -69
  352. package/docs/reference/embedding.md +0 -197
  353. package/docs/reference/features-matrix.md +0 -101
  354. package/docs/reference/orpo-quickstart.md +0 -135
  355. package/src/flags.ts +0 -11
  356. /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
  357. /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
  358. /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
  359. /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
  360. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  361. /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
  362. /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
  363. /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
  364. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -1,232 +0,0 @@
1
- # Research spike — W4A16 head GEMM (compute precision, not storage)
2
-
3
- **Status:** proposed (not started) · **Opened:** 2026-06-19 · **Owner:** Josh
4
- **Surface:** `src/train/flash-cce.ts` (steel + SG kernels) · **Gate:** the existing
5
- 1e-5 CCE parity gate + greedy-prefix logit parity.
6
-
7
- > One-line thesis (Josh): *we've maxed **storage** quant (4-bit weights), but we
8
- > dequant into fp32 and run the matmul in fp32 — so the head GEMM is **W4A32**.
9
- > The untouched lever is **compute** precision: dequant 4-bit → half, matmul in
10
- > `half` operands with an fp32 accumulator (**W4A16**), gate on parity. Same 4-bit
11
- > storage, untouched. The only change is the float width we dequant into and
12
- > multiply in.*
13
-
14
- ---
15
-
16
- ## TL;DR
17
-
18
- - The flash-CCE head (forward logit GEMM `E@Wᵀ` and backward phase-2 `coeffᵀ@W`)
19
- currently runs **W4A32**: 4-bit weights are dequantized into **fp32** threadgroup
20
- tiles and multiplied with **fp32** simdgroup matrices. Verified in source (below).
21
- - The 4-bit packing bought the cheap HBM read; the fp32 matmul throws away the
22
- compute advantage. Apple GPUs run **fp16 FMA at ~2× fp32** on the regular SIMD
23
- ALUs (no tensor cores). Both head GEMMs are **compute-bound** (see roofline note),
24
- so half operands are the lever.
25
- - **Industry-standard for the forward** (MLX `quantized_matmul`, llama.cpp, every
26
- on-device 4-bit stack do exactly W4A16). Low risk; do it first.
27
- - **Backward needs the mixed-precision caveat** — fp32 accumulate is non-negotiable
28
- (already planned), and the **operands** (`coeffᵀ`, `h`) need a dynamic-range probe
29
- before trusting fp16, specifically the near-zero coeff tails after the skips fire.
30
- - **M1 Max constraint:** no `bf16` simdgroup_matrix (M2+ only), so **fp16 is the
31
- only half option** — which makes the range-probe matter more here than on M3.
32
- - Gate exactly like the existing skip epsilons: **fp32 stays the default-exact
33
- parity mode**; half is the training default; the 1e-5 gate test runs against fp32.
34
-
35
- ---
36
-
37
- ## Current state — verified W4A32
38
-
39
- The default production path for **both** forward and backward is the **steel**
40
- kernel (MLX's verbatim `BlockMMA` GEMM, `steel-qmm-header.ts`), selected when
41
- `H,V,blockV0 % 32 == 0` (`flash-cce.ts:1034`, `:1130`). The `simdgroup_float8x8`
42
- SG kernels and the scalar/lane kernels are fallbacks for non-32-divisible shapes or
43
- explicit `MLX_BUN_CCE_*` overrides.
44
-
45
- Every one of these instantiations is fp32-operand:
46
-
47
- | path | site | instantiation | operand width |
48
- |---|---|---|---|
49
- | steel fwd (default) | `flash-cce.ts:950` | `BlockMMA<float,float,…>` | fp32 |
50
- | steel bwd phase-1 (default) | `flash-cce.ts:799` | `BlockMMA<float,float,…>` (lmma_t) | fp32 |
51
- | steel bwd phase-2 (default) | `flash-cce.ts:800` | `BlockMMA<float,float,…>` (dmma_t) | fp32 |
52
- | steel staging tiles | `:596–598`, loaders `:801` | `threadgroup float` / `BlockLoader<float,…>` | fp32 |
53
- | SG fwd (fallback) | `:251–254` | `simdgroup_float8x8`, `Csh`/`Esh` = `threadgroup float` | fp32 |
54
- | SG bwd (fallback) | `:617`, `:684–704` | `simdgroup_float8x8`, `Wsh` = `threadgroup float` | fp32 |
55
-
56
- The dequant itself (`Csh[…] = sc * q + bi`, `flash-cce.ts:244`) computes into fp32
57
- and stores fp32. The dh accumulator is already fp32 (`MMATile<float,…>`,
58
- `flash-cce.ts:828`) — i.e. **the accumulator is already where it should be**; only
59
- the matmul *operands* are wider than they need to be.
60
-
61
- ---
62
-
63
- ## The lever — what actually changes
64
-
65
- W4A16 = **same 4-bit storage, half-width operands, fp32 accumulate.** Concretely:
66
-
67
- 1. **Steel operand type** — the `BlockMMA<float,float,…>` first template param is the
68
- GEMM input/operand type; flip operand to `half` while keeping the
69
- accumulator/fragment in `float`. (**Spike task 0: confirm the exact MLX steel
70
- template param that selects operand vs accumulator width** in
71
- `steel-qmm-header.ts` before editing — don't assume the param order.)
72
- 2. **Staging tiles** — `Esh`, `Cstg`, `Wsh`, `Csh` go `threadgroup half`; dequant
73
- arithmetic can stay fp32 then narrow on store, or compute in half.
74
- 3. **Loaders** — `BlockLoader<float,…>` → `BlockLoader<half,…>` to stage half tiles.
75
- 4. **SG fallback** — `simdgroup_float8x8` → `simdgroup_half8x8` for the operands,
76
- keep an `simdgroup_float8x8` accumulator (Apple supports the mixed `half×half→float`
77
- `simdgroup_multiply_accumulate`).
78
-
79
- Everything reduction-shaped (the online-softmax `m`/`d` recurrence, the dh accum,
80
- logsumexp) **stays fp32**.
81
-
82
- ---
83
-
84
- ## First thoughts (mine) — what to add to Josh's analysis
85
-
86
- The analysis is right and the change is well-targeted. Five additions, each grounded
87
- in the current source:
88
-
89
- **1. Aim at the steel kernel, not the SG snippet.** Josh's note led with the SG path
90
- (`simdgroup_float8x8`, `Csh`). That path is a *fallback*. The lever that moves the
91
- measured 180 ms fwd / 754 ms bwd (e4b) numbers is the **steel `BlockMMA<float,float>`**
92
- instantiations (`:950`, `:799`, `:800`) and their `BlockLoader<float>`/staging tiles.
93
- Do the SG path too for completeness, but the steel path is where the production time is.
94
-
95
- **2. The 2× is an upper bound gated by the FMA fraction — get the roofline split
96
- first.** The whole flash-CCE design is `[M,V]`-free and amortizes dequant across
97
- `BLOCK_B` tokens specifically because the **dequant ALU** was "the dominant cost in
98
- v1" (`flash-cce.ts:59`). The runtime splits into (a) nibble-unpack + `sc·q+bi`
99
- dequant and (b) the FMA matmul. **W4A16 only speeds up (b).** The kernel is
100
- unambiguously compute-bound, not BW-bound — **confirmed live** (see the measured box
101
- below), and Josh flagged it from `mactop` before this doc existed. *But* the win
102
- ceiling is the FMA share of that ALU time. The dequant-amortization work was done
103
- precisely to shrink (a)'s share, which *should* leave (b) dominant and favorable — but
104
- **measure the (a)/(b) split before predicting 2×.** The live numbers prove the
105
- *whole kernel* is ALU-bound; they do **not** resolve the intra-kernel (a)/(b) split,
106
- which is what actually bounds the reclaim. If the kernel is still dequant-ALU-bound,
107
- half operands move it less.
108
-
109
- > **Live roofline — measured (M1 Max 32 GB, MiniCPM5-1B ORPO, `--no-segment`, seq 4096):**
110
- > GPU **100% @ 1296 MHz, 81 °C, Nominal** (sustained, not throttling) · memory
111
- > **BW 129.2 GB/s ≈ ⅓ of the M1 Max's ~400 GB/s ceiling** (bus two-thirds idle) ·
112
- > mem **17.37 / 32 GB (54%)**, swap 0 · CPU ~5%, ANE 0%. The training `bun` proc is
113
- > 92.6% GPU. **GPU pegged + memory bus mostly idle = ALU-bound.** This is the regime
114
- > where W4A16 pays; if it were BW-bound (BW near 400, GPU spiky) the spike would be
115
- > moot. The 4-bit storage already did its job — *that's why BW is low* — and the GPU
116
- > is now spending its cycles on the fp32 FMAs.
117
-
118
- **3. Threadgroup memory is a free secondary win — possibly a compounding one.** The
119
- staging tiles (`Esh`/`Cstg`/`Wsh`/`Csh`) are the kernel's threadgroup-memory pressure,
120
- and tile sizing is *already* occupancy-constrained — see the note at `flash-cce.ts:71`
121
- ("the 8KB `Csh` tile measured SLOWER"). Half-width staging **halves that footprint**,
122
- which can either raise occupancy or admit larger tiles. So W4A16 may buy more than the
123
- raw 2× FMA: it could relieve the exact constraint that capped the current tiling.
124
- Worth re-sweeping `BLOCK_B`/tile dims *after* the precision change, not just measuring
125
- the drop-in.
126
-
127
- **4. SDPA in the segmented forward is probably already half — don't chase it.** Josh's
128
- aside ("if your SDPA tiles are also fp32, same logic"). The segmented forward uses
129
- `ops.sdpa` — MLX's own fused flash kernel ([[opssdpa-dk-vjp-bug]]) — which runs in the
130
- model's dtype (bf16), **not** our fp32 tiles. So that lever is likely already pulled by
131
- delegating to MLX. One-line confirm, then drop it; the head GEMM is the real target.
132
-
133
- **5. Free instrumentation hooks already exist for the backward range-probe.** The
134
- backward already computes `cmax = simd_max(coeff)` for the FILTER_EPS skip
135
- (`flash-cce.ts:682`). That same reduction is the natural hook to log the coeff dynamic
136
- range (min nonzero |coeff|) across a real run *before* committing the operand to fp16 —
137
- the underflow-to-zero case Josh flagged. Pair it with a max-abs probe on `h`: at e4b's
138
- `scale=1.0` peaked attention the residual stream can carry outliers
139
- ([[e4b-forward-length-sensitivity]], [[siglip-vision-parity-cross-build]]), and with no
140
- bf16 on M1 Max we can't lean on a wider exponent to eat them.
141
-
142
- ---
143
-
144
- ## Risk analysis (preserving the forward/backward split)
145
-
146
- **Forward — low risk, boring industry default.** Logits are well-scaled; the
147
- online-softmax max-subtraction bounds the `exp` args. W4A16 here is what the dense
148
- path already assumes. Validate against the 1e-5 gate (fp16 operands won't hold 1e-5 —
149
- that's expected; the **fp32 path stays the exact-parity gate**, half is graded by
150
- greedy-prefix logit agreement, same as the vision tier-a bar).
151
-
152
- **Backward — fp16 matmul is mainstream, but as *mixed precision*, with eyes on
153
- operand range.** What makes the forward safe (well-scaled, max-subtracted) is **not**
154
- automatically true here:
155
- - `coeff_v = g·(onehot − softmax)·sech²` — after the skips fire, the survivors are the
156
- **near-zero tails**. Small magnitudes are exactly where fp16's ~3-digit mantissa and
157
- narrow range (subnormals < ~6e-5) bite. **`coeffᵀ` as an fp16 operand is the highest
158
- risk** — tail coeffs underflowing to zero silently drop gradient contribution. Probe
159
- the range first (task 5 above).
160
- - `h` (activations) as fp16 operand — residual outliers; the classic bf16-beats-fp16
161
- case, but bf16 isn't available on M1 Max. Range-check before trusting.
162
- - **fp32 accumulate is non-negotiable** (already planned; `MMATile<float>` already is).
163
- - **Validate grads against autograd, not a hand-derived backward** ([[opssdpa-dk-vjp-bug]]).
164
-
165
- ---
166
-
167
- ## Spike plan
168
-
169
- Sequenced so each step is independently measurable and revertible (Josh: *"Don't flip
170
- both at once — you'll learn more, and if parity drifts you'll know which GEMM did it."*).
171
-
172
- - [ ] **0. Confirm the steel template semantics.** In `steel-qmm-header.ts`, identify
173
- which `BlockMMA`/`BlockLoader` template params are operand vs accumulator width.
174
- Exit: a one-line mapping, no code change.
175
- - [x] **1a. Whole-kernel roofline — DONE (live).** GPU 100% / BW 129 GB/s (~⅓ of 400)
176
- on M1 Max confirms the kernel is ALU-bound, not BW-bound (see the measured box above).
177
- The spike is worth chasing.
178
- - [ ] **1b. Intra-kernel split (still open).** Profile the steel fwd (180 ms) and bwd
179
- (754 ms, e4b) into dequant-ALU vs FMA fractions — 1a says the GPU is the wall, this
180
- says *which part* of the GPU work. Exit: a number that bounds the expected win. *If
181
- FMA < ~40% of either kernel, reset expectations before coding.*
182
- - [ ] **2. Add the precision gate.** New codegen `#define` (e.g. `CCE_A_HALF`) +
183
- env flag (e.g. `MLX_BUN_CCE_A16`), mirroring `CCE_BWD_FILTER_EPS`/`CCE_BWD_BLOCK_EPS`
184
- (`flash-cce.ts:732`,`:739`). Default: fp32 (exact) for the parity suite; half is the
185
- training default. Per [[three-level-fidelity-tree-model]] the exact path must stay
186
- selectable.
187
- - [ ] **3. Forward → half operands.** Steel fwd `BlockMMA`/loader/staging to half.
188
- Validate: 1e-5 gate (fp32 path) + greedy-prefix logit parity (half path) + measure
189
- the fwd GEMM delta. Land as its own change.
190
- - [ ] **4. Backward range probe.** Instrument coeff (via the `simd_max` hook) and `h`
191
- dynamic range across a real MiniCPM5/e4b ORPO step. Exit: min-nonzero-coeff and
192
- max-|h| numbers + a go/no-go on fp16 operands.
193
- - [ ] **5. Backward → half operands** (only if 4 says go). `coeffᵀ`/`h`/`W` operands
194
- half, dh accumulator stays fp32. Validate grads **vs autograd** + measure bwd delta.
195
- - [ ] **6. Re-sweep tiling.** With half-width staging, re-check `BLOCK_B`/tile dims and
196
- occupancy (first-thought #3) — the precision win may unlock a tiling win.
197
-
198
- **Exit criteria for the spike:** a measured fwd/bwd speedup on e4b (V=262144) and
199
- MiniCPM5 (V=130560) on the bench machine (M4 Pro 24 GB; numbers verified against
200
- `~/.cache/mlx-bun/evals.sqlite`, not docs — [[verify-benchmarks-against-evaldb-not-docs]]),
201
- with the fp32 exact path still passing the 1e-5 gate and the half training path inside
202
- the greedy-prefix parity bar. Either it lands as the training default behind the gate,
203
- or the roofline/range data explains why it didn't pay and the spike closes with that.
204
-
205
- ---
206
-
207
- ## Open questions
208
-
209
- - Steel `BlockMMA` mixed operand/accum: does MLX's steel already support
210
- `half`-operand / `float`-accum, or does it need a local template tweak? (task 0)
211
- - Is the dequant cheaper to do **in half** (fewer ALU bits) or **in fp32 then narrow**
212
- (preserves the scale/bias precision)? A/B inside task 3.
213
- - Does the half staging change the `+4-float` padding trick (`flash-cce.ts:197`, the
214
- BK_padded bank-conflict avoidance)? Half-width may re-introduce or shift bank
215
- conflicts — re-derive the pad.
216
-
217
- ---
218
-
219
- ## References
220
-
221
- **Code:** `src/train/flash-cce.ts` — steel fwd `:937–1028`, steel bwd `:763–935`, SG
222
- fwd `:174–270`, SG bwd `:561–726`, gates `:732`/`:739`/`:1034`/`:1130`; staging tiles
223
- `:596–598`; dequant `:244`; coeff `simd_max` hook `:682`; dh fp32 accum `:828`.
224
- `src/train/steel-qmm-header.ts` (the verbatim MLX BlockMMA). `src/mlx/metal-kernel.ts`
225
- (kernel infra).
226
-
227
- **Cross-links:** [[flash-cce-metal-kernel-prototype]] · [[three-level-fidelity-tree-model]]
228
- · [[opssdpa-dk-vjp-bug]] · [[e4b-forward-length-sensitivity]] ·
229
- [[verify-benchmarks-against-evaldb-not-docs]] · `docs/archive/investigations/steel-flash-cce-handoff.md`.
230
-
231
- **Sibling deferred lever** (queued together, post-run): `docs/design/orpo-future-enhancements.md`
232
- → "Deferred levers" (expose `--grad-accum`).
@@ -1,315 +0,0 @@
1
- # The mlx-bun beat matrix
2
-
3
- Purpose: an axis-by-axis competitive requirement table for making mlx-bun's
4
- web chat UI THE SINGLE BEST macOS local-AI chat surface — not best-in-a-niche,
5
- best, period. Every row is a capability judged against the best current
6
- implementation anywhere in the surveyed field (mlx-lm, mlx-optiq, Ollama, Open
7
- WebUI, LM Studio, Odysseus, Jan, Msty, GPT4All, AnythingLLM, Enchanted,
8
- macMLX/mlx-serve, ChatGPT, Claude.ai). Sourced from the 10-agent market-research
9
- corpus plus a direct line-level audit of `src/web/app.html`, `src/pi-web.ts`,
10
- `src/server.ts` and the merged plan at
11
- `docs/design/web-chat-redesign.md`. Supplemented with a fresh read of
12
- mlx-optiq's live Lab docs (mlx-optiq.com/docs/lab/{chat,arena,hub}) — optiq is
13
- the closest-cousin MLX product, so its rows are held to the strictest
14
- standard: the operating rule for this matrix is **zero "I miss how
15
- <competitor> had X" moments**, so every capability below is MATCH, BEAT, or an
16
- explicit SKIP with a stated engine-substrate reason.
17
-
18
- *(Caveat: the Odysseus rows rest on the research agent's public-web
19
- identification of "Odysseus" — Cookbook recommender / Skills /
20
- Observer-Reflector memory / SSRF CVEs. If that's not the Odysseus meant,
21
- those rows describe the wrong product and need a re-run; no verdict in this
22
- matrix flips on them.)*
23
-
24
- Verdict legend:
25
- - **MATCH** — users expect it; parity is sufficient (e.g. code-block copy).
26
- - **BEAT** — an axis where mlx-bun can be clearly, structurally better because
27
- of something we alone have (in-process server, `/fit`, whole-stack control,
28
- MLX-native, memory vault, adapters). The "what beating requires" column says
29
- *how*, not "do it better."
30
- - **SKIP** — the engine genuinely lacks the substrate (image gen, voice). One
31
- line each so silence is never mistaken for an oversight.
32
-
33
- ---
34
-
35
- ## Axis 1 — Chat fundamentals
36
-
37
- | Capability | Best today (who + what makes theirs best) | Verdict | What beating them concretely requires | Backend status |
38
- |---|---|---|---|---|
39
- | Streaming responses with visible Stop | Universal table stakes (ChatGPT/Claude/all local apps) — Stop is prominent, never buried in a menu | MATCH | Already shipped (`app.html` composer) | exists |
40
- | Stop actually kills in-flight tool work | optiq Lab: the stop button SIGKILLs sandboxed tool subprocesses, not just the token stream | MATCH | Ensure our stop aborts running tool calls (bash/web_fetch) through the pi bridge, not only the generation — a half-stop that leaves a shell command running is a visible miss vs. the closest-cousin MLX product | thin addition (verify + wire abort through the pi tool layer) |
41
- | Chat persistence to disk, no account | optiq persists to `~/.optiq/lab/chats/`; universal among local apps | MATCH | Already shipped — pi `SessionManager` disk-persisted sessions with richer lifecycle (fork) than optiq's | exists |
42
- | Regenerate response | ChatGPT/Claude cycle response variants (1/2, 2/2…); Open WebUI same | MATCH | Add a regenerate button per assistant turn, reusing the existing send pipeline | thin addition |
43
- | Edit-and-resend (non-destructive branch) | ChatGPT/Claude: edit a prior user message silently forks; `< 1/2 >` toggle above the edited message | MATCH | Reuse `SessionManager.forkFrom` (already exists server-side) at message granularity instead of whole-session only | thin addition (server primitive exists, needs per-message wiring) |
44
- | Full conversation tree view | Neither ChatGPT nor Claude ships this natively — only a linear toggle; third-party extensions (BranchGPT) exist because users want it | BEAT | Build a genuine node-graph view of every edit-branch, reusing `forkFrom` — a documented gap neither cloud incumbent has closed. Phase 4 stretch, not launch-blocking. | new build (UI only; fork primitive exists) |
45
- | Live steering (type while streaming to redirect) | mlx-bun already has this wired server-side (`queue_update`, distinct `steering`/`followUp` arrays) — no competitor in the survey does this | BEAT | Fix the existing truthy-vs-length-tested array bug (`web-ui-pass-plan.md` #2) so the feature that already exists actually renders | exists (buggy — Phase 0 fix) |
46
- | Follow-up queue while a turn streams | Same as above — server already distinguishes queued follow-ups from live steering | BEAT | Same fix as above; this is a genuine differentiator once rendering is correct | exists (buggy) |
47
- | Stop/regenerate reachable by keyboard | ChatGPT ships a full shortcut sheet (`Cmd+/`) | MATCH | `Cmd/Ctrl+Shift+O` new chat, `Shift+Esc` focus composer, etc. — adopt ChatGPT's vocabulary verbatim, zero cost to originality | thin addition |
48
- | Message-level copy (full assistant text) | Universal — every competitor has full-message copy, not just code-fence copy | MATCH | Add copy button to the message action row | thin addition |
49
- | Delete a single message | Common in Open WebUI, LM Studio | MATCH | Add to message action row | thin addition |
50
- | Multi-turn context management / auto-compaction | mlx-lm's byte-capped LRU prompt cache is the correctness bar; pi already does session compaction | MATCH | Already exists server-side; only gap is a UI seam marking *when* compaction happened | thin addition (mark the seam) |
51
-
52
- ---
53
-
54
- ## Axis 2 — Rendering & content
55
-
56
- | Capability | Best today | Verdict | What beating them concretely requires | Backend status |
57
- |---|---|---|---|---|
58
- | GFM markdown (tables, task lists, blockquotes, autolinks) | mlx-bun's own hand-rolled `mdToHtml` is already competitive with library output | MATCH | Nothing — already good, keep it | exists |
59
- | Syntax-highlighted code blocks | Every mainstream competitor (ChatGPT, Claude, Open WebUI, LM Studio) has this; mlx-bun renders plain monochrome `<pre><code>` | MATCH | Vendor Shiki (or a lighter WASM-free alternative) self-hosted, no CDN, applied only to completed blocks | new build (thin — rendering only) |
60
- | LaTeX/math rendering | Universal via KaTeX in modern chat UIs; mlx-bun has none | MATCH | Vendor KaTeX self-hosted, same completed-block-only pass | new build (thin) |
61
- | Diagram rendering (Mermaid) | Present in several competitors' RAG-heavy stacks | MATCH | Optional — low priority; add if code-heavy audience demands it | new build |
62
- | O(n²) streaming re-render fix | Streamdown/t3.chat's block-memoized architecture (split completed vs. live-tail block, skip re-parse on stable blocks) | BEAT | mlx-bun is the only local competitor whose whole pitch is serving *speed* — a frontend that stutters on a fast, spec-decode-accelerated response undercuts the product's own thesis. Port the block-memoization *idea* into the existing vanilla renderer (no framework). This unblocks every other visual change touching the render path. | new build (architecture fix, no new backend) |
63
- | Unterminated-markdown repair mid-stream | Streamdown's context-aware auto-close of open fences/emphasis | MATCH | Port the repair-pass idea alongside the block-memoization fix | new build (thin, bundled with above) |
64
- | Sandboxed HTML/canvas preview for fenced HTML | optiq Lab's Canvas: fenced HTML blocks render live in a sandboxed allow-scripts iframe with a view-source toggle — shipped today in the closest-cousin MLX product | MATCH | Add the same allow-scripts iframe render + view-source toggle for HTML fences, opt-in-by-heuristic like ChatGPT's Canvas walk-back (not mandatory). Explicit "no I-miss-optiq-Canvas" requirement. | new build |
65
- | Artifacts/Canvas side-panel | Claude Artifacts / ChatGPT Canvas: persistent versioned side panel, highlight-to-scope-edit, version scrubber | MATCH (opt-in, not launch-required) | OpenAI itself partially walked Canvas back in May 2026 for the common case — ship opt-in-by-heuristic (auto-open above ~10 lines / high fence density), not a mandatory pane. Real effort; explicitly Phase 4. | new build |
66
- | logprobs-derived confidence visualization | Nobody in the local-AI survey ships this in-chat at all | BEAT | Server already validates and returns `logprobs`/`top_logprobs` (mlx-lm-matched shape) with zero UI. An opt-in per-token confidence overlay is pure whitespace — no competitor's server even supports it as cleanly. | exists (server), new build (UI) |
67
- | Structured-output / JSON-schema picker | optiq Lab ships JSON mode via lm-format-enforcer (schema-constrained, deliberately pure-Python at a stated ~1ms/token cost); mlx-lm has zero `response_format` support; most local UIs don't expose it even when the server supports it | BEAT | Server already supports `response_format` json_schema/json_object + `guided_grammar`/`guided_regex`/`guided_choice` via **xgrammar token bitmasks** — structurally faster than optiq's ~1ms/token pure-Python enforcer, and broader (grammar/regex/choice, not just JSON schema). Ship a schema/grammar picker with live validation; we beat the closest cousin on both overhead and surface. | exists (server), thin addition (UI) |
68
-
69
- ---
70
-
71
- ## Axis 3 — Model discovery & management
72
-
73
- | Capability | Best today | Verdict | What beating them concretely requires | Backend status |
74
- |---|---|---|---|---|
75
- | Hardware-fit traffic light on model selection | LM Studio: green/yellow/red against detected RAM/VRAM before download commit | BEAT | We already have per-SKU fit prediction measured against *this specific Mac's bandwidth* (`GET /fit`, `src/fit.ts`) — not a generic heuristic like LM Studio's. Wire it into both the model list and the in-chat model switcher. | exists (server), thin addition (UI) |
76
- | One-click model library browse + load + chat (Hub) | optiq Lab's Hub: three sources in one browser — published OptiQ quants auto-discovered from mlx-community (family/size/bit-profile/download counts), HF search of any MLX-compatible model (OptiQ-only filter), and local converted dirs (vision badge when a sidecar is present) — with one-click load-to-server AND new-chat per row | BEAT | Match the three-source browse→load→chat loop, then beat it where optiq is documented weakest: their Hub has **no hardware-fit indicator beyond raw size** — put a `/fit`-computed green/yellow/red badge (predicted decode tok/s + safe context for *this* Mac) on every row, at the exact point of download/load commitment | exists (`/fit`), new build (library UI) |
77
- | Capability badges (vision/tool-use/reasoning) | LM Studio's `model.yaml` badges; Ollama's library tag chips | MATCH | Surface known properties (SigLIP vision, quant tier) as UI chrome — we already know these, just not rendered | thin addition |
78
- | In-chat model switching (no restart) | optiq Lab Hub: hot-swap the serving model in place in ~5-30s without restarting the Lab; Jan's router mode (single process, on-demand load/unload) | MATCH | `#nav-model` is currently a dead `<span>` with no click handler — wire a clickable picker that triggers the same in-place hot-swap; match optiq's 5-30s bar | thin addition |
79
- | Model swap preserves session history + re-primes cleanly | Every competitor surveyed is documented as bad at this (LM Studio has an open bug clearing the selector on swap) | BEAT | Since we control the whole stack (session + KV + adapter layer), a swap that keeps history and re-primes cleanly is a believable, demonstrable win nobody else in the survey has actually shipped correctly | thin addition (UI + verify session continuity) |
80
- | Resident multi-model pinning / LRU pool | macMLX's per-model pin/LRU-evict pool | MATCH | Extend existing adapter-namespaced KV logic to full models for users bouncing between two models | new build |
81
- | Running-models resource table | Ollama's `ollama ps` (PROCESSOR split, UNTIL auto-unload countdown) | BEAT | Build the equivalent Status-tab table PLUS columns no competitor can show: LoRA adapter attached, KV tier/quant, spec-decode draft pairing | new build |
82
- | Live per-layer quantization sensitivity visualization | optiq computes this internally (KL cost per layer per bit-width) but never renders it — a documented gap even in the best-in-class quant tool | BEAT | We already compute or could compute per-layer bit allocation; a heatmap/bar-strip nobody in the entire surveyed field has shipped, turning an opaque flag into a screenshot-able trust signal | thin addition (Quantize tab exists; visualization is new) |
83
- | One-command coding-tool integration (`ollama launch <tool>`) | Ollama: `ollama launch claude --model X` wires Claude Code/Codex/OpenCode at itself with zero config | BEAT | We already speak both OpenAI- and Anthropic-compatible protocols — `mlx-bun launch <tool>` is nearly free to build and makes us infrastructure other daily tools sit on | thin addition (CLI, not chat UI, but a real distribution wedge) |
84
-
85
- ---
86
-
87
- ## Axis 4 — Customization: prompts / presets / sampling
88
-
89
- | Capability | Best today | Verdict | What beating them concretely requires | Backend status |
90
- |---|---|---|---|---|
91
- | Full sampler parameter set (min_p, XTC, penalty families + windows, logit_bias) | mlx-lm's own `/v1/chat/completions` (the oracle) accepts all of these; mlx-bun's composer only exposes 3 of ~11 | MATCH | Server already implements the full set (`src/server.ts`) — wire `min_p`/XTC/penalties/logit_bias/seed into the sampling popover behind an Advanced disclosure. Table-stakes gap against our own drop-in-replacement claim. | exists (server), thin addition (UI, WS `set_sampling` currently hardcodes 3 fields) |
92
- | Per-model correct sampling defaults | LM Studio auto-binds presets per model | MATCH | Fix the hardcoded MiniCPM5-shaped `SAMP_REC` — resolve defaults from `generation_config.json` per served model, which the server already supports | exists (server), thin addition (UI) |
93
- | Per-MESSAGE sampling granularity (temp / max_tokens / thinking per turn, in the composer) | optiq Lab: temperature, max tokens, and enable_thinking overridable per message directly in the chat composer — per-turn granularity, not a per-session settings modal | MATCH | Our per-turn plumbing already exists (`injectSampling` per-turn overrides via `set_sampling`, per-model thinking toggle) — the gap is presentation: keep the popover per-turn-scoped and visible at the composer, not buried, so optiq's per-message granularity has a direct equal | exists (per-turn `injectSampling` + thinking toggle), thin addition (composer placement) |
94
- | Saved/shareable prompt presets (system prompt + params bundle) | LM Studio Presets: exportable, importable, auto-bound per model | MATCH | Build a preset save/load layer over the existing sampling+system-prompt state | new build |
95
- | Slash-command prompt library with variable-filled forms | Open WebUI's `/name` + `{{var}}` popup | MATCH | Well-tested pattern, no meaningful downside; build as a Phase-2/3 item | new build |
96
- | Model-string variant shorthand (`:precise`/`:no-think`) | optiq's `:no-think`/`:precise`/`:creative` suffixes for rigid clients | MATCH | Cheap compatibility hack for agent clients that only set `model` — trivial to add given presets already exist conceptually | new build (thin) |
97
- | Persona/bundle system (prompt + adapter + tools + memory scope, named, switchable) | Msty's Persona Studio + Crew Conversations; AnythingLLM workspace pinning | BEAT | We're the only one who can bind a *named* bundle to an actual LoRA weight swap (not just a system-prompt swap) — Msty/AnythingLLM personas are prompt bundles only. Phase 4 item; data model should be shaped now so it's a thin layer later. | new build |
98
- | Warm/preload a context explicitly | mlx-lm's `cache_prompt` as a first-class CLI verb | MATCH | Expose an explicit "warm this session" action turning a backend perf trick into a felt speed win | new build |
99
-
100
- ---
101
-
102
- ## Axis 5 — Knowledge: documents & RAG
103
-
104
- | Capability | Best today | Verdict | What beating them concretely requires | Backend status |
105
- |---|---|---|---|---|
106
- | Zero-config document attach (point-and-chat) | GPT4All's LocalDocs: point at a folder, fully offline, no pipeline setup | MATCH | Table-stakes risk: our Chat tab currently has no document/RAG story at all. Ship the simplest possible attach-and-answer path first. | new build |
107
- | Transparent inline-vs-RAG mode selection | LM Studio: auto-decide inline (fits context) vs. chunk+embed (doesn't), zero user configuration | MATCH | Copy the zero-config decision engine; then extend the *same* engine to also route into the memory vault (see Axis 6) so "attach a file" and "recall memory" feel like one retrieval system | new build |
108
- | Chat-with-files RAG + citation badges + Sources panel | optiq Lab ships this today in the closest-cousin MLX product: attached non-image files are indexed with a **dependency-free BM25 retriever**, chunks retrieved with inline `[n]` citation markers plus a Sources panel; Open WebUI's SourcePanel adds page-level PDF deep links | MATCH (v1 = optiq's bar) | Match optiq's exact v1 shape — BM25 index over attachments, `[n]` markers, Sources panel (filename+snippet, not full PDF-page rendering) — then extend the same retrieval component to vault articles (Axis 6), which optiq has no concept of | new build (optiq's dependency-free BM25 proves the v1 needs no vector infra) |
109
- | Unified `#`/`@` retrieval mention (files + docs) | Open WebUI's `#` command pulls a KB doc or raw URL as one interaction | MATCH | Extend to both attached files and vault articles as *one* retrieval gesture — this is where the RAG axis and the memory axis (Axis 6) genuinely merge for us | new build |
110
- | Retrieval-mode toggle (chunked search vs. full-context) | Open WebUI's Focused/Full-Context switch | MATCH | Cheap, well-proven; add per-document toggle | new build |
111
- | Hybrid RAG (BM25 + vector + reranking) | Open WebUI's serious RAG maturity (configurable hybrid retrieval, reranking, full-context bypass) | SKIP (full hybrid, for now) | The *hybrid/reranking* tier is what's skipped, not RAG itself — the BM25 v1 above is required (optiq proves it's cheap), and embeddings exist (`src/embed.ts`) for a later vector upgrade; full Open WebUI-grade reranking infrastructure is not warranted at launch | new build (large, deferred tier) |
112
- | Local semantic search over the user's OWN chat history | Nobody in the survey does this — competitors' RAG is document-centric, not "my own past conversations" | BEAT | The memory vault + session history IS a corpus no competitor's RAG concept reaches; making "search my own past" a first-class retrieval target ties directly into the local-to-you thesis | new build |
113
-
114
- ---
115
-
116
- ## Axis 6 — Memory & personalization
117
-
118
- | Capability | Best today | Verdict | What beating them concretely requires | Backend status |
119
- |---|---|---|---|---|
120
- | Auto-extracted, editable memory list (facts/preferences) | AnythingLLM's Observer/Reflector; ChatGPT's Manage Memories (dated rows, per-item delete, search/sort) | BEAT | mlx-bun's memory is architecturally deeper: a git-tracked Markdown vault, read-only at chat time, written only by nightly synthesis — not a flat fact list. The UI's job is making that *visible*: a real Memory panel (vault browser + search), not inventing a new prompt-stuffing mechanism. | exists (`src/memory/tools.ts`, `vaultStatus()`), thin server addition (REST wrappers) + new UI |
121
- | Per-turn provenance ("the assistant looked this up") | ChatGPT/Claude show *what* is remembered but never *how*; nobody shows live per-turn citations for memory reads | BEAT | Memory tool calls already fire silently in web chat with zero distinct rendering. Give them a citation-chip card style (not the generic wrench icon) — the single highest-leverage rendering change in the whole plan. | exists (tools fire), thin addition (card styling) |
122
- | Git-diff / history view of what memory synthesis changed | No competitor's architecture can do this — ChatGPT/Claude synthesis runs on someone else's infrastructure, unauditable even in principle | BEAT | Our synthesis runs locally and is git-tracked. A per-article git-log/diff toggle lets a user *watch* an article evolve after a correction — the sharpest wedge against both cloud incumbents' otherwise-superior memory UX, because ours is mechanistically inspectable and theirs structurally cannot be. | exists (git history is real), thin server addition (log/diff read route) + new UI |
123
- | Wikilink knowledge-graph view | Nobody in the survey has a git-tracked personal wiki at all, so nobody has this | BEAT | `memory_links` already resolves inbound/outbound wikilinks per article — cheap to expose as a graph view; a "knowledge graph of what the model knows about you" nobody else's architecture supports | exists (`memory_links` tool), new build (graph UI) |
124
- | Scope control (this-session / this-surface / global) | AnythingLLM's workspace-vs-global memory scoping | MATCH | Vault is currently single-scope; scope selection is a reasonable v2 addition once the panel exists | new build |
125
- | Pause vs. reset as distinct actions | ChatGPT/Claude: pause (stop reading/writing, keep data) vs. reset (irreversible wipe) as two different stakes levels | MATCH | Cheap UX pattern to copy once settings/memory panel exists | thin addition |
126
- | Memory that graduates into trained weights (not just context) | Nobody — not AnythingLLM, not ChatGPT, not Claude — synthesizes memory into actual model weights; all of them are context-window-bounded fact lists | BEAT | This is mlx-bun's single deepest structural moat: the nightly "dreaming" pipeline can eventually bake a fact into a memory LoRA, not just a growing prompt. A visible marker distinguishing "raw context" vs. "baked into your memory LoRA" is a distinction no competitor's architecture can even express. | partial (synthesis-to-context exists; synthesis-to-LoRA is a further phase per project memory) |
127
- | First-run memory consent flow | AnythingLLM/Odysseus bury consent in settings; nobody makes it a first-60-seconds moment | BEAT | `mlx-bun memory init` today is CLI-only, invisible to anyone who never opens a terminal. A one-line skippable consent card in the hero screen puts personalization consent in the same 60 seconds as "install to chat under a minute" — no competitor frames it this tightly. | exists (CLI), thin addition (hero-screen card) |
128
- | Nightly synthesis schedule status visible | Odysseus runs nightly audits but doesn't surface schedule state in mainstream UI docs | MATCH | `vaultStatus()` already returns schedule state, last-run time — just render it | exists, thin addition |
129
- | Self-evolving procedural memory ("Skills") | Odysseus: auto-distills reusable markdown playbooks from multi-tool-call sessions, teacher-escalation on failure | SKIP (v1) | Genuinely novel mechanic but a large, separate system (distillation trigger, confidence scoring, LLM-as-judge harness) — not core to the memory-vault thesis; defer as a later differentiator, not a launch requirement | new build (large, deferred) |
130
-
131
- ---
132
-
133
- ## Axis 7 — Tools & agents
134
-
135
- | Capability | Best today | Verdict | What beating them concretely requires | Backend status |
136
- |---|---|---|---|---|
137
- | Tool-call visibility as a distinct card (not silent) | Universal table stakes now (Open WebUI, LM Studio, Jan, AnythingLLM all show tool cards) | MATCH | Already shipped (`app.html` running/ok/fail cards) | exists |
138
- | Inline, non-blocking approval cards | Jan v0.8.0: moved away from blocking modal to inline cards embedded in the tool-call bubble, plus global "Allow All" | MATCH | We already have the approval round-trip (`installApprovalGate`) — the presentation layer needs to match Jan's proven inline pattern | exists (backend), thin addition (styling matches Jan's pattern already) |
139
- | Editable arguments before approval | LM Studio: edit the proposed call's arguments, not just accept/deny | MATCH | Extend the existing approval card to allow inline argument editing before execute | thin addition |
140
- | Durable, tool-identity-keyed "always allow" | Universally fragile across the field — Claude Desktop's persistence resets on app update; Cowork doesn't persist across sessions at all | BEAT | This is an easy, concrete bar: store approvals in a real versioned config file keyed by stable tool identity, with a migration test across simulated version bumps — fixing a failure mode every competitor visibly has | new build (small — config file + test) |
141
- | Gated tools (bash/edit/write) actually reachable in web chat | optiq Lab ships a terminal tool (bash one-liners with dangerous-command filtering) live in chat today; mlx-bun's own approval-card UI is fully built but currently dead code — the browser allowlist excludes every `GATED_TOOL` | MATCH (fix our own regression) | Wire `codingTools: true` behind an explicit off-by-default "let it touch files" toggle so the already-shipped UI stops being inert; our per-call approval gate is a *stronger* trust model than optiq's static dangerous-command filter once it's actually reachable | exists (backend + UI both exist, just disconnected) |
142
- | Sandboxed Python execution tool | optiq Lab: built-in Python tool running in a sandbox with AST-level security checks, killed cleanly on Stop | MATCH | Add a Python-interpreter tool to the pi tool surface (or route through the existing gated bash path with an interpreter preset); single-user local means "runs on your machine" is the honest framing, with the approval gate as the control — full AST-sandbox parity is optional, the *capability* is the requirement | new build (moderate; approval plumbing exists) |
143
- | Self-healing tool calls (auto-correct malformed formats) | optiq Lab: auto-corrects six distinct malformed tool-call formats instead of failing the turn — directly addresses the reliability gap mlx-lm's own server logs warn about (truncated/failed parses, no recovery) | MATCH | Add a repair layer in the tool-call parse path (`src/tool-call.ts`) covering the common malformation classes; small local models flub tool-call syntax constantly, so without this every optiq user switching over feels a reliability regression | new build (parser hardening; tool-call parsing exists) |
144
- | Tool-loop hygiene (dedup, retry budget, turn cap, force-finish) | optiq Lab: skips identical consecutive successful calls (dedup), gives 3 failed attempts then forces course-correction (retry budget), caps the loop at 25 turns, and force-generates a text answer if the cap is hit (never ends a turn tool-looping with no answer) | MATCH | Implement the same four guards in the pi agent-loop bridge; the force-finish guarantee ("you always get an answer, never a stuck loop") is the piece users notice most | new build (loop-policy layer over existing agent loop) |
145
- | Argument/diff preview before approval | Neither ChatGPT nor Claude previews the concrete blast radius (file diff, shell command, bytes written) before approval — both show name + yes/no only | BEAT | mlx-bun's approval hook already has the data; showing an actual diff/args preview clears a bar neither cloud incumbent has cleared | thin addition |
146
- | Per-session disk-touch tally (trust log) | Nobody in the survey ships a running "what did the agent touch on disk this session" ledger distinct from tool-call cards | BEAT | Single-user local execution means this is honest, cheap, and uniquely credible — "runs on your machine, with your files" instead of Open WebUI/Odysseus's sandbox theater for multi-tenant deployments we don't need | new build |
147
- | Curated pre-wired tool pack on first run | Cherry Studio: filesystem, web search, GitHub, fetch, memory pre-registered, zero JSON config | MATCH | Ship a default pack including our own wiki-read/vault tool — turns first-run into a memory-flywheel demo instead of a config chore | new build (small) |
148
- | MCP server support | Open WebUI native MCP + mcpo proxy; LM Studio Cursor-compatible `mcp.json`; Jan MCP marketplace | MATCH | Add MCP client support behind the existing tool-call plumbing — real engineering, but table stakes for 2026 agentic chat | new build (moderate-large) |
149
- | Web search + URL fetch, no API key | optiq Lab: built-in DuckDuckGo search + URL fetch requiring zero API keys, on by default in chat; Odysseus/Open WebUI need SearXNG/config | MATCH | `web_search`/`web_fetch` tools already exist, keyless and read-only-safe — but `web_fetch` is currently excluded from the hardcoded `WELCOME_TOOLS` allowlist; widen the toolset per served model and expose an explicit per-turn toggle | exists (tools), thin addition (allowlist + UI toggle) |
150
- | Approval-fatigue-aware defaults | Field-wide complaint (Jan, LM Studio, Claude Desktop users all report fatigue) | MATCH | Ship read-only/local tools (file read, our wiki-read) as always-allow-by-default, reserve prompts for write/network/execute-class tools | thin addition |
151
- | tool_choice forcing a specific function | API-only today (`src/server.ts`); no chat-UI concept | SKIP (chat UI) | Real capability but a power-user/API concern, not a chat-composer affordance — low priority for the redesign | exists (API), not a UI priority |
152
- | No-code visual agent-flow builder | AnythingLLM's Agent Flows canvas; Odysseus admin-gated automation | SKIP | Building a general visual flow builder from scratch is a multi-quarter distraction with no differentiator payoff; if we need automation, scope it narrowly (a "schedule a synthesis-style job" UI) rather than a general canvas | not planned |
153
- | Deep-research-style multi-step trace panel | Odysseus's Deep Research; Open WebUI's Interleaved Thinking | MATCH | Table stakes for agentic 2026 chat UIs; wrap a visible plan/trace UI around existing tool-call infra as a research-flavored preset | new build |
154
-
155
- ---
156
-
157
- ## Axis 8 — Multimodal
158
-
159
- | Capability | Best today | Verdict | What beating them concretely requires | Backend status |
160
- |---|---|---|---|---|
161
- | Vision input (image attach + inference) | Universal now (Ollama desktop, LM Studio, optiq's bf16 sidecar) | MATCH | Already wired end-to-end correctly (capability negotiation via `ready` frame's `vision` flag); only fix needed is stale "switch to 12B" copy now that e2b/e4b/26B/31B are all vision-capable | exists |
162
- | Vision + prompt-cache unification (no perf tax when text-only) | LM Studio's Unified MLX Engine: single code path conditionally attaches a VisionAddOn, ~25x faster follow-up TTFT via shared caching | MATCH | Verify our own vision path already avoids a text-only tax (SigLIP sidecar pattern is architecturally similar); if not already true, this is a real perf investment worth matching | exists (verify) |
163
- | Image generation | Not in scope for any MLX-native serving engine surveyed except bolted-on extras (mlx-serve) | SKIP | No image-generation model/pipeline exists in the engine at all — genuine substrate gap, not a UI decision | not built |
164
- | Speech-to-speech voice mode | ChatGPT Advanced Voice Mode: single model hears tone/emotion, replies in 2-3s | SKIP | No speech/TTS/STT engine exists. This is a hard, expensive model-latency problem, not a UI problem — a naive STT→LLM→TTS bolt-on would read as a visible regression against user expectations set by the cloud bar. Correctly scoped as out of v1. | not built |
165
- | Native OS voice I/O (dictation, TTS readback) | Enchanted's native SwiftUI voice I/O across Apple devices | SKIP | Same substrate gap as above — no TTS/STT pipeline; Enchanted rides macOS system APIs we haven't wired at all | not built |
166
- | Document (PDF/DOCX) attach with inline-vs-RAG | LM Studio's transparent dual mode | MATCH | Covered under Axis 5 (Knowledge) — same capability, cross-referenced here for multimodal completeness | new build |
167
- | Screenshot capture in a floating companion window | ChatGPT macOS's Option+Space + instant screenshot; mlx-serve's Ctrl-Space quick launcher | BEAT | The Tauri shell already wraps the same web UI with zero bespoke chrome; adding a global hotkey + floating window wired to the existing vision pipeline is a concrete way to earn native-app status a browser SPA structurally can't claim | exists (vision pipeline), new build (Tauri-specific, Phase 4) |
168
-
169
- ---
170
-
171
- ## Axis 9 — Performance & transparency
172
-
173
- | Capability | Best today | Verdict | What beating them concretely requires | Backend status |
174
- |---|---|---|---|---|
175
- | Live tok/s + TTFT readout | LM Studio 0.4's ambient context-fullness/tok-s chrome; macMLX's Benchmark tab | MATCH | Already computed (`app.html` post-turn readout) — just under-designed as small dim text | exists |
176
- | Context-window meter | LM Studio's persistent fullness indicator | MATCH | Already exists (`app.html:1696-1703`) | exists |
177
- | Serving-lane badge (serial / spec-decode / batched) | Nobody in the entire surveyed field integrates lane-awareness into the *live chat surface* — macMLX/mlx-serve have a separate Benchmark tab, not live-turn lane visibility | BEAT | This is the one thing no competitor's architecture allows, because none of them control both spec-decode and batching as internally routed lanes AND expose that routing per-turn in the chat itself. Needs a small per-turn `lane` field added server-side (usage payload + pi-web frame) — the single highest-differentiation, lowest-backend-cost item in the whole matrix. | thin server addition (field doesn't exist yet) + UI |
178
- | Prompt-cache-hit telemetry per turn | mlx-lm's LRUPromptCache tracks reuse internally but never surfaces it to a user; nobody in the survey shows "served from cache" per turn | BEAT | We already compute cache-hit counts server-side; surfacing "this turn reused N cached tokens, saved Xms" turns an invisible backend optimization into a felt trust/speed signal | exists (server), thin addition (UI) |
179
- | Built-in Benchmark tab (prefill/decode tok/s, TTFT, peak RSS) | macMLX's Benchmark tab; mlx-serve's "+39% geomean" claim | MATCH | We already discipline every perf claim with `benchmark.sh` (per CLAUDE.md ground rules) — exposing a lightweight in-app version turns internal QA into user-facing proof | exists (harness), new build (in-app UI) |
180
- | Compare/split view of two configs on one prompt | optiq's Model Arena is the closest-cousin implementation: side-by-side 2-model comparison on identical prompts, model B spun up in a second server on port+1 on demand, both panes streaming live with tok/s + token counts, both models staying loaded while Arena is open, thinking force-disabled for reasoning models so comparisons show real answers; Msty's Split Chat adds synced typing across N panes | BEAT | Match the Arena mechanics (two live-streaming panes, tok/s per pane, thinking force-disabled — copy that design decision verbatim), then beat it on two documented Arena gaps: (1) compare things optiq *can't* — base vs. LoRA-adapted variant, bit-exact parity mode vs. L3 fast path — since Arena only compares whole models; (2) add a quality signal (Arena shows only speed + token counts, no voting/ranking — its own docs' weakness), e.g. a memory-grounded check or preference capture wired into the ORPO data pipeline | new build |
181
- | Concurrency / batch-slot visibility | optiq's documented `--max-concurrent-equivalent` guidance | MATCH | Document + add a live concurrency/slot indicator tied to our shipped `--batch N` concurrent serving | thin addition |
182
- | Hardware-fit prediction (`/fit`) surfaced in chat | LM Studio's traffic light is generic-heuristic; we measure actual bandwidth per SKU | BEAT | `GET /fit` already predicts decode tok/s and safe context for *this specific Mac* — surface it in the model switcher and adapter picker, not buried in the Status tab only | exists (server), thin addition (surface beyond Status tab) |
183
- | Auto-sized context window to available memory | Ollama has open feature requests for exactly this (currently a conservative fixed default) | BEAT | Our M4-Pro-tuned memory-headroom knowledge (Metal/unified memory) lets us size this more precisely than Ollama's generic GPU detection; the context-window meter already exists as the display surface | thin addition (logic; UI meter exists) |
184
- | Provable "zero cloud calls" per session | No cloud competitor can make this claim structurally; local competitors don't bother proving it | BEAT | A per-session network-activity indicator (there were zero egress calls) is trivially true for us and impossible for ChatGPT/Claude's "temporary chat" (still retained 30 days per OpenAI's own disclosure) | new build (small) |
185
-
186
- ---
187
-
188
- ## Axis 10 — Polish: onboarding / theming / keyboard / a11y / mobile
189
-
190
- | Capability | Best today | Verdict | What beating them concretely requires | Backend status |
191
- |---|---|---|---|---|
192
- | Dark + light theme, `prefers-color-scheme` | Universal table stakes; mlx-bun is dark-only today (`--bg:#000` hardcoded) | MATCH | Add `prefers-color-scheme` handling + a light theme; audit design tokens | thin addition |
193
- | `prefers-reduced-motion` respect | WCAG baseline; ambient blooms currently unconditional | MATCH | Guard shimmer/bloom animations behind the media query | thin addition |
194
- | Full keyboard shortcut sheet (`Cmd/Ctrl+/`) | ChatGPT's documented vocabulary is the de facto standard | MATCH | Adopt ChatGPT's exact bindings where sensible — zero cost to originality, reuses existing copy-button code for "copy last response" | thin addition |
195
- | Command palette (`Cmd/Ctrl+K`) | ChatGPT/Claude: universal "do anything" entry point | MATCH | Build a palette covering new chat/session search/model-adapter switch/thinking toggle | new build |
196
- | Mobile responsive layout down to 375px | Universal table stakes; mlx-bun's sidebar vanishes at 760px with zero replacement | MATCH | Replace the vanishing sidebar with a slide-over drawer + hamburger — table stakes, not differentiation | new build (small) |
197
- | PWA installability (manifest + service worker) | Open WebUI ships "Install app" as a first-class menu item | MATCH | Add a manifest + minimal cache-first shell service worker; scope explicitly as shell-instant-load + installability, not offline chat (app is useless without the local server running) | new build (small) |
198
- | A11y: `role="log" aria-live="polite"` on the thread | WCAG ARIA23 documented technique; mlx-bun has 11 total `aria-*` attributes across 2737 lines | MATCH | Throttle to paragraph/sentence boundaries, never per-token; verify against actual VoiceOver testing, not just docs | new build (small) |
199
- | Full keyboard tab order + focus rings + Escape-to-close | Baseline a11y expectation; currently no documented strategy | MATCH | Trap focus in popovers, Escape returns focus to trigger | thin addition |
200
- | Progressive disclosure (Simple / Developer mode) | LM Studio's User/Power-User/Developer toggle — one codebase, one switch, not a fork | MATCH | Collapse Quantize/Fine-tune/Build-Dataset/Status/Curves/Routes behind one Developer toggle so Chat is the whole app for a first-time user — directly serves "install to chat under a minute" | new build (IA move, no new backend) |
201
- | Personalized, server-driven onboarding chips | Nobody ties onboarding chips to actual server state — LM Studio/Ollama's onboarding is generic | BEAT | Replace 4 static marketing chips with server-driven ones: one drawn from real vault content, one offering to try an adapter, a first-run memory-consent card — because our onboarding can reflect real personalization state nobody else's architecture has to reflect | thin addition (`vaultStatus()`, `/v1/adapters/available` already exist) |
202
- | Chat export (Markdown/PDF/plain text) | LM Studio exports to PDF/MD/plain text; Open WebUI exports JSON | MATCH | Add export action to session actions; local-only, no server cost concern | new build (small) |
203
- | Chat sharing (shareable link) | Open WebUI: shareable chat links | SKIP | Requires a hosting/sharing surface that conflicts with the single-user, local-only, no-contributors identity — a shareable *link* implies a server reachable by someone else, which is out of scope by design, not a capability gap | not planned (scope decision) |
204
- | Full-text search across message bodies | Claude's own search is title-only — a widely-cited annoyance; local storage removes any indexing-cost excuse | BEAT | No server-cost constraint exists locally the way it does for Claude/ChatGPT's cloud indexing — ship real full-text body search, not just title search, and be visibly better than the cloud incumbents on this specific point | new build (small) |
205
- | Session/conversation folders and pins | Open WebUI folders/tags; ChatGPT pin/star | MATCH | Add pin + lightweight tag/folder grouping to the sidebar | new build |
206
- | Native menu-bar/tray behavior (clean quit, no zombie process) | Ollama gets public complaints for exactly the failure mode to avoid (can't hide dock icon, can't fully quit) | MATCH | Cheap insurance in the Tauri shell — verify clean quit/hide-dock-icon before shipping, avoid inheriting a known competitor failure mode | thin addition (Tauri config) |
207
-
208
- ---
209
-
210
- ## Axis 11 — Sessions & organization
211
-
212
- | Capability | Best today | Verdict | What beating them concretely requires | Backend status |
213
- |---|---|---|---|---|
214
- | New / list / open / fork / delete sessions | mlx-bun already round-trips all of this through pi's `SessionManager` | MATCH | Already shipped, real depth | exists |
215
- | Session search box | Table stakes; mlx-bun's sidebar has none today | MATCH | Add a filter/search input above the session list | thin addition (small) |
216
- | Per-message branch-from-here (not just whole-session fork) | ChatGPT/Claude's edit-creates-branch convention | MATCH | `SessionManager.forkFrom` already exists server-side — expose at message granularity, not just the sidebar | thin addition |
217
- | Projects (scoped knowledge + memory per workspace) | Claude Projects: persistent system instructions + files + isolated memory summary per project | BEAT | A Project is effectively "routed context + adapter scope" in UX terms even though Anthropic implements it as RAG-over-files; we can implement the same UX shape with an actual weight-level adapter swap underneath, something Claude's Projects structurally cannot do since they don't ship adapter weights per customer | new build (persona/bundle concept, Phase 4) |
218
- | Temporary/incognito chat | ChatGPT/Claude: one-click pill in composer header, per-conversation scope | BEAT | Cloud "temporary chat" is still retained 30 days per OpenAI's own disclosure "for safety" — ours can be **provably** never written to disk at all, because we control the whole stack. Pair with a per-session "zero cloud calls" indicator. | new build (small) |
219
- | Multi-model / multi-adapter compare | Msty's Split Chat; optiq's Model Arena; LM Studio's Split View | BEAT | Covered in Axis 9 — cross-referenced here as an organizational pattern (parallel panes as a session-management primitive, not just a perf demo) | new build |
220
- | Conversation export/import (portable JSON) | Open WebUI: JSON export/import | MATCH | Local JSON already backs every session on disk — expose an explicit export/import action | thin addition |
221
-
222
- ---
223
-
224
- ## Axis 12 — App-aware assistant (added 2026-07-06, from the PortfolioManager evaluation)
225
-
226
- The chat agent can see and act on the app it lives in — a structured DOM
227
- snapshot (no screenshots, no vision round-trip), agent-driven navigation,
228
- and element spotlighting. The product theory: guided tours fail on timing
229
- (help before intent → skipped), chatbot help fails on grounding (moment of
230
- intent, but blind — a search bar with a face); this is the only shape with
231
- both — help at the moment of intent, grounded in what's actually on screen.
232
- Acceptance test: it feels like someone is there with you. Verified against
233
- every other axis: **no product in the surveyed field has any of this** — the nearest neighbors are generic
234
- browser-automation agents (wrong tool, heavyweight) and static product
235
- tours (not conversational). Reference implementation:
236
- `~/Code/PortfolioManager` (`server/src/agent/{ui-catalog,portfolio-tools}.ts`,
237
- `client/src/lib/{ui-snapshot,spotlight,resolve-spotlight}.ts`,
238
- `ui-catalog.json`) — same pi SDK as our `src/pi-web.ts`, so the port is
239
- mechanical.
240
-
241
- | Capability | Best today | Verdict | What beating them concretely requires | Backend status |
242
- |---|---|---|---|---|
243
- | Agent sees the live UI (structured snapshot, not pixels) | Nobody in the field; PortfolioManager (private) proves the mechanism: on route change the browser pushes a `uiSnapshot` of visible interactive elements + labeled regions ({ref, label, role, selector}, ~120-element cap, agent chrome excluded) as a WS `context` frame; a `get_current_app_context` tool returns it | BEAT | Add the snapshot builder to the web app + a `context` ClientMessage + the read tool in `pi-web.ts`. Snapshot is DOM-derived (a11y-tree-like) — kills the screenshot→upload→describe round-trip entirely, locally, with no vision model in the loop | new build (small; pattern proven in-house) |
244
- | Agent navigates the app for you ("take me to Quantize" → it clicks the tab) | Nobody; PortfolioManager's `navigate_app` validates against a curated `ui-catalog.json` route map → `ui_navigate` WS frame → browser routes | BEAT | Catalog our routes (incl. Developer-toggle views) + the tool + frame. Navigation is reversible and safe — no approval needed. Synergy: resolves the Developer-toggle discoverability tension (§8) — hidden tabs become conversationally discoverable | new build (small) |
245
- | Agent spotlights a control with a hint ("the source-model field is here") | Nobody; PortfolioManager's `spotlight_ui` locates by ref/label/selector/catalog-id, optionally navigates first, highlights via overlay + popover, auto-dismisses | BEAT | Curated `data-spotlight` anchors on stable controls + a hand-rolled overlay (backdrop cutout + popover, ~80 lines, matches our tokens — no need to vendor driver.js). The agent answers "how do I X here?" by pointing at the actual screen | new build (small) |
246
- | Approval-gated agent UI actions (fill this form, start this job) | Nobody — PortfolioManager deliberately stops at navigate+point; generic computer-use agents act but with no in-app trust surface | BEAT (stretch) | Route `ui_act` tools (set field, submit job) through the EXISTING approval-card gate (`installApprovalGate`) so guided actions render as visible allow/deny cards with argument preview — the trust model no browser-automation layer has. Phase 5; requires the approval surface from Phase 2 to be live first | new build (moderate; approval gate exists) |
247
-
248
- ---
249
-
250
- ## The 10 beats
251
-
252
- Ranked by (visible wow) × (structural defensibility) — the ten rows where
253
- mlx-bun can be clearly, demonstrably better than the current best, not just
254
- equal.
255
-
256
- 1. **Serving-lane badge in live chat (Axis 9).** No competitor surveyed —
257
- not macMLX, not mlx-serve, not optiq — integrates spec-decode/batch/serial
258
- lane-awareness into the actual chat surface; they all bury it in a separate
259
- Benchmark tab. Demo: watch the badge flip from "serial" to "spec-decode
260
- active" mid-conversation on the `dspark` branch and see tok/s visibly jump
261
- in the same breath.
262
- 2. **Memory git-diff / self-healing view (Axis 6).** ChatGPT and Claude show
263
- *what* is remembered but never *how* — their synthesis is a black box on
264
- someone else's infrastructure. Demo: correct a fact mid-chat, run nightly
265
- synthesis, then open the article's git diff and watch the exact line
266
- change.
267
- 3. **Per-turn memory provenance chip (Axis 6).** Memory tools already fire
268
- silently today with zero UI trace — this is a near-free wiring fix with
269
- outsized visible payoff. Demo: ask a personal question, see a small
270
- "read: Photography" citation chip appear and expand to the source
271
- article.
272
- 4. **LoRA adapter routing made legible + stackable (Axis 4/6).** LM Studio's
273
- own bug tracker confirms adapter hot-swap is unsupported industry-wide;
274
- optiq proved the mechanism but shipped it Python-API-only. Demo: swap from
275
- "general-purpose" to a named adapter mid-session with a visible RAM-cost
276
- note, then stack a second adapter (`"a+b"`) live.
277
- 5. **Adapter/parity compare view (Axis 9/11).** optiq's Arena (the
278
- closest-cousin implementation: two live panes, port+1 second server,
279
- tok/s per pane, thinking force-disabled) can only compare whole *models*
280
- and shows no quality signal at all — its own documented gap. Nobody
281
- compares a base model against its own LoRA-adapted variant, or bit-exact
282
- parity mode against the fast path, on the identical prompt. Demo: fire
283
- one prompt at base vs. adapted, side by side, and see the adapter
284
- visibly change the answer's grounding — a comparison Arena literally
285
- cannot express.
286
- 6. **Model swap that actually preserves the session (Axis 3).** Every
287
- competitor surveyed is documented as bad at this (LM Studio's own open
288
- bug). Demo: swap the served model mid-thread and keep scrolling the same
289
- conversation with no reset, no lost history.
290
- 7. **Prompt-cache-hit telemetry per turn (Axis 9).** mlx-lm's LRUPromptCache
291
- tracks this internally and never shows it; nobody in the field surfaces
292
- "served from cache" as a felt signal. Demo: a repeated near-identical
293
- prompt visibly shows "reused 1,400 cached tokens" and a corresponding
294
- TTFT drop.
295
- 8. **Provably-local temporary chat + zero-cloud-calls indicator (Axis 11).**
296
- Cloud "temporary chat" is retained 30 days per OpenAI's own disclosure —
297
- ours is structurally different because we control the whole stack. Demo:
298
- toggle temporary chat, chat normally, then show a network-activity panel
299
- proving zero egress the whole time.
300
- 9. **Tool-call diff/argument preview before approval (Axis 7).** Neither
301
- ChatGPT nor Claude previews the actual blast radius (file diff, shell
302
- command) before approval — both show name + yes/no. Demo: approve a
303
- `write` call and see the literal diff that will land on disk before
304
- clicking allow.
305
- 10. **Full-text search across message bodies (Axis 10/11).** Claude's own
306
- search is title-only, a widely-cited annoyance, purely because
307
- server-side indexing has a cost model we don't have locally. Demo:
308
- search a phrase buried in the middle of a months-old conversation and
309
- jump straight to it.
310
- 11. **The app-aware assistant (Axis 12 — added 2026-07-06).** Nobody's
311
- local chat agent can see the app it lives in; on onboarding wow alone
312
- this ranks top-3. Demo: ask "how do I make a smaller model?" — the
313
- assistant answers from the live screen, reveals the Developer tabs,
314
- navigates to Quantize, and spotlights the source-model field, with
315
- zero screenshots taken.