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,545 +0,0 @@
1
- # mlx-lm tool parity — scoping plan
2
-
3
- Scoping for the remaining mlx-lm CLI tools not yet covered by mlx-bun, plus
4
- serve-time speculative decoding (`--draft-model`). Written against the oracle
5
- venv (`/Users/joshrossi/Code/mlx-lm/.venv`, **mlx-lm 0.31.3**, **mlx-optiq
6
- 0.2.4** — note: newer than the 0.2.1 noted in CLAUDE.md), source read directly
7
- from `site-packages/`. Sibling work in flight (not covered here): `fuse` /
8
- `convert` / `perplexity` (another agent), `sft_scope`.
9
-
10
- ## What actually exists in mlx-lm 0.31.3
11
-
12
- From `mlx_lm-0.31.3.dist-info/entry_points.txt` — all six named tools are real
13
- console scripts, plus one this plan folds in because we already cover it:
14
-
15
- | entry point | source file |
16
- | --- | --- |
17
- | `mlx_lm.cache_prompt` | `mlx_lm/cache_prompt.py` |
18
- | `mlx_lm.evaluate` | `mlx_lm/evaluate.py` |
19
- | `mlx_lm.upload` | `mlx_lm/upload.py` |
20
- | `mlx_lm.awq` | `mlx_lm/quant/awq.py` |
21
- | `mlx_lm.dwq` | `mlx_lm/quant/dwq.py` |
22
- | `mlx_lm.gptq` | `mlx_lm/quant/gptq.py` |
23
- | `mlx_lm.dynamic_quant` | `mlx_lm/quant/dynamic_quant.py` (sensitivity-scan + 4/5-bit split — **already covered** by `src/quantize/{sensitivity,allocator}.ts`; no work item) |
24
-
25
- `mlx_lm.server --draft-model/--num-draft-tokens` lives in `mlx_lm/server.py`
26
- (flags at :1782–1791) driving `speculative_generate_step` in
27
- `mlx_lm/generate.py:473`.
28
-
29
- **optiq's quant tooling (the L2 oracle), for calibration of the awq/dwq/gptq
30
- decision:** `optiq convert` is per-layer **KL sensitivity scan + greedy
31
- mixed-precision knapsack** (`optiq/core/sensitivity.py`, `optimizer.py`, with
32
- block-floor/block-run guards and a latency-aware variant), verified by
33
- `optiq/core/verifier.py` (perplexity / KL thresholds). It does **not**
34
- implement AWQ/DWQ/GPTQ-style learned or activation-aware quantization — final
35
- packing is stock `mx.quantize`. So optiq covers the *bit-allocation* half of
36
- quant quality; the *learned-refinement* half (DWQ et al.) is genuinely
37
- uncovered by both mlx-bun and optiq today.
38
-
39
- ---
40
-
41
- ## 1. `cache_prompt`
42
-
43
- **What it does.** Prefills a prompt into a KV cache and saves it to a
44
- safetensors file for later reuse (`mlx_lm/cache_prompt.py`; save/load are
45
- `save_prompt_cache`/`load_prompt_cache` in `mlx_lm/models/cache.py:43,62`).
46
- Consumers: `mlx_lm.generate --prompt-cache-file` (`generate.py:180,1974-1990`,
47
- which also validates that `--kv-bits`/`--kv-group-size` match the loaded
48
- cache) and `mlx_lm.chat`. The chat template is applied with
49
- `add_generation_prompt=False, continue_final_message=True` so the cache ends
50
- mid-conversation.
51
-
52
- **CLI surface** (`cache_prompt.py:17-80`): `--model` (default `mlx_model`),
53
- `--adapter-path`, `--trust-remote-code`, `--eos-token`, `--max-kv-size`,
54
- `--prompt-cache-file` (required), `--prompt` (required, `-` = stdin),
55
- `--kv-bits` (default none), `--kv-group-size` (64), `--quantized-kv-start`
56
- (5000). Saved metadata: model path + tokenizer config.
57
-
58
- **mlx-bun overlap.** `src/prompt-cache.ts` — in-memory, byte-capped
59
- (`--prompt-cache <GiB>`, default 2), LRU, longest-strict-prefix `take()`,
60
- adapter-namespaced. It is server-only and has **no disk serialization**; cache
61
- entries are live MlxArrays and die with the process. `mlx-bun generate` has no
62
- `--prompt-cache-file`. The building blocks exist: safetensors write machinery
63
- in `src/quantize/safetensors-writer.ts`, cache state (K/V tensors + offset)
64
- owned by the `Cache` implementations in `src/model/gemma4.ts`.
65
-
66
- **Port plan.**
67
- - `src/prompt-cache-io.ts` (new): `savePromptCache(file, caches, meta)` /
68
- `loadPromptCache(file)` — serialize each layer's K/V (+ scales/biases for
69
- quantized caches, ring state for rotating caches) plus
70
- `{modelId, tokens, kvBits, kvGroupSize, offset[]}` metadata. Mirror
71
- mlx-lm's per-class `state`/`meta_state` split rather than inventing a format.
72
- - `src/cli.ts`: new `cache-prompt` subcommand (model resolve via `Registry`,
73
- prefill via the serving prompt path — reuse `promptIdsFor`, per the
74
- eval-reuse lesson) and a `--prompt-cache-file` flag on `generate`.
75
- - Optional follow-up: `mlx-bun serve --prompt-cache-file` to pre-warm the
76
- server's `PromptCache` at startup.
77
-
78
- **Oracle / tiers.** L1 bit-exact, two gates: (a) round-trip — greedy
79
- generation from a loaded cache file must be token-identical to single-shot
80
- generation over prompt+continuation; (b) cross-oracle — same prompt cached by
81
- `mlx_lm.cache_prompt` and by us produce token-identical continuations from
82
- each runtime. Byte-level file compatibility with mlx-lm's format is a stretch
83
- goal, not the bar.
84
-
85
- **Effort:** M. **Risky parts:** serializing `RotatingKVCache` ring state and
86
- quantized-cache triples correctly; offset/window metadata; refusing stale
87
- caches (model mismatch) loudly.
88
-
89
- **Recommendation: build later.** The in-memory server cache already covers
90
- the interactive/agent use-case; the file workflow mainly serves scripted CLI
91
- runs. Cheap, real parity, not urgent.
92
-
93
- ---
94
-
95
- ## 2. `evaluate`
96
-
97
- **What it does.** An adapter that registers mlx-lm as an
98
- **lm-evaluation-harness** backend (`mlx_lm/evaluate.py:71` —
99
- `@register_model("mlxlm") class MLXLM(LM)`) implementing `loglikelihood`
100
- (prefix-grouped, batched scoring with KV reuse), `loglikelihood_rolling`, and
101
- `generate_until` (via `batch_generate`), then calls
102
- `lm_eval.simple_evaluate(...)` (`evaluate.py:489`). The task library, few-shot
103
- formatting, and metrics all live in the third-party `lm_eval` package —
104
- that's the bulk of the tool.
105
-
106
- **CLI surface** (`evaluate.py:397-458`): `--model` (req), `--tasks` (req,
107
- nargs+), `--output-dir`, `--batch-size` (16), `--num-shots`, `--max-tokens`,
108
- `--limit`, `--seed` (123), `--fewshot-as-multiturn`, `--apply-chat-template`
109
- (tri-state), `--chat-template-args` (JSON), `--confirm-run-unsafe-code`,
110
- `--trust-remote-code`, `--temp`/`--top-p`/`--top-k`.
111
-
112
- **mlx-bun overlap.** `src/eval/` (runner with `generateText` on the real
113
- serving path + `greedyDecodeBitExact`, task set, KL machinery in
114
- `src/eval/kl.ts`), results in `~/.cache/mlx-bun/evals.sqlite`
115
- (`src/evaldb.ts`), CLI `mlx-bun benchmark` / `mlx-bun evals`. And the L2
116
- oracle already ships a full benchmark suite: `optiq eval`
117
- (kl/gsm8k/mmlu/ifeval/bfcl/humaneval/hashhop + Capability_Score).
118
-
119
- **Port plan.** Do **not** port lm-eval to Bun — reimplementing the task
120
- library is XL and permanently chasing upstream. Two-part coverage instead:
121
- 1. **Shim (S):** document + smoke-test running the oracle venv's `lm_eval`
122
- against a running mlx-bun server via its OpenAI `local-completions` /
123
- `local-chat-completions` backend (mlx-bun's `/v1` already speaks the
124
- contract; needs `logprobs`/`echo` support checked for loglikelihood tasks
125
- — generation tasks work today). One doc page + a
126
- `scripts/lm-eval-shim.sh`.
127
- 2. **Native (only if a gap shows):** add a `loglikelihood` scoring mode to
128
- `src/eval/runner.ts` (one forward, `take_along_axis` on log-softmax — the
129
- same scoring loop as `evaluate.py:107-138`) so our own tasks can do
130
- multiple-choice; that's an S add-on with a direct numeric oracle
131
- (`MLXLM._score_fn` outputs on fixed inputs).
132
-
133
- **Oracle / tiers.** Not a bit-exactness surface end-to-end; gate the scoring
134
- primitive (loglikelihood per token vs oracle `_score_fn`, exact fp32 match on
135
- fixed inputs = L1-style), and gate task *scores* statistically (same task,
136
- same limit, matching accuracy).
137
-
138
- **Effort:** S (shim) / M (native loglikelihood). **Risky parts:**
139
- loglikelihood-via-HTTP needs prompt-logprob echo, which most local /v1 servers
140
- (ours included) don't fully implement — that's the one real feature gap to
141
- check first.
142
-
143
- **Recommendation: cover-via-existing** (own eval suite + optiq eval as
144
- quality oracle) **+ the S-sized lm-eval-over-HTTP shim**; skip a native port.
145
-
146
- ---
147
-
148
- ## 3. `upload`
149
-
150
- **What it does.** 22-line wrapper (`mlx_lm/upload.py`) around
151
- `mlx_lm.utils.upload_to_hub(path, repo)` — creates the repo and uploads a
152
- model folder (with generated model card) via `huggingface_hub`.
153
-
154
- **CLI surface:** `--path` (default `mlx_model`), `--upload-repo`.
155
-
156
- **mlx-bun overlap.** Essentially done: `src/hf-push.ts` implements the whole
157
- HF HTTP protocol natively (create-repo → preupload → LFS batch/S3 → NDJSON
158
- commit), token management (`~/.mlx-bun/hf.json` → `$HF_TOKEN` → HF cache
159
- token), LFS suffix + 10 MB threshold rules. It's wired to the web push routes
160
- (`/api/{quantize,finetune,dataset}/push` in `src/server.ts`) but has **no CLI
161
- command**.
162
-
163
- **Port plan.** One new `case "upload"` in `src/cli.ts`: resolve `--path` (or
164
- a registry query) → `uploadToHub(dir, repo)`; accept `--upload-repo` as the
165
- mlx-lm-named alias for `--repo`. Optional: generate a small model card noting
166
- mlx-bun + quant recipe, as `upload_to_hub` does.
167
-
168
- **Oracle / tiers.** Functional, not numeric: round-trip test (upload a tiny
169
- artifact → `mlx-bun get` it back → hash-compare files). No tier gate.
170
-
171
- **Effort:** S. **Risky parts:** none new — the risky code already runs in
172
- production via the Lab routes.
173
-
174
- **Recommendation: build now.** Trivial, and the in-flight `convert`/`fuse`
175
- work plus `quantize` all produce directories that want a publish verb.
176
-
177
- ---
178
-
179
- ## 4. `awq`
180
-
181
- **What it does.** Activation-aware weight quantization
182
- (`mlx_lm/quant/awq.py`): per transformer block, capture layer inputs with
183
- `Catcher` modules, grid-search (`--n-grid`, default 20) per-channel **scales**
184
- (balancing activation vs weight magnitudes) and **clip** thresholds to
185
- minimize block-output MSE under quantization, with a per-block fallback to
186
- plain quantization if the loss didn't improve (`awq.py:452-505`). Requires a
187
- hand-written per-architecture `AWQConfig` (which norms feed which
188
- projections); registry at `awq.py:143`: llama, mistral, qwen2, qwen3,
189
- gemma3_text, gemma3, deepseek_v2. Calibration data: fixed gist download,
190
- `quant/utils.py:load_data` (`~/.cache/mlx-lm/calibration_v5.txt`). Embeddings
191
- and lm_head quantized separately (`--embed-bits/--embed-group-size`).
192
-
193
- **CLI surface** (`awq.py:533-547`): `--model/-m`, `--mlx-path`, `--bits` (4),
194
- `--group-size` (64), `--embed-bits` (4), `--embed-group-size` (32),
195
- `--num-samples` (128), `--sequence-length` (512), `--n-grid` (20), `--seed`
196
- (123). Distributed-aware (`mx.distributed`).
197
-
198
- **mlx-bun overlap.** `src/quantize/` already does calibration-driven quality
199
- work — per-layer KL sensitivity (`sensitivity.ts`) + mixed-precision knapsack
200
- (`allocator.ts`), i.e. the optiq approach; that attacks the same quality goal
201
- by *bit allocation* rather than *weight conditioning*. No scale/clip search
202
- exists. optiq itself ships no AWQ (verified above), so there is **no L2
203
- oracle path** — the only oracle is `mlx_lm.awq` itself.
204
-
205
- **Port plan (if built).** `src/quantize/awq.ts` (scale/clip grid search —
206
- needs per-block input capture, which means a tapped block forward like
207
- `forwardTapped` in `src/spec/dspark/generate-dflash.ts:34`, generalized),
208
- `src/quantize/awq-configs.ts` (per-arch registry for our ported models:
209
- gemma3/gemma4, qwen, minicpm — note upstream has no gemma4/minicpm configs;
210
- we'd be authoring those, unoracled), calibration loader reusing
211
- `quantize/calibration.ts`, CLI or Lab-API entry. Oracle: run `mlx_lm.awq` in
212
- the oracle venv on a small supported model (fixed seed + shared calibration
213
- file) and compare final weight tensors — deterministic given identical
214
- calibration batches, so weight-level parity is the L1-style gate; then
215
- KL/perplexity via existing `src/eval/kl.ts` for quality claims.
216
-
217
- **Effort:** L. **Risky parts:** per-arch config authoring for models mlx-lm
218
- doesn't cover (no oracle); block-capture instrumentation on our hand-written
219
- (non-module-tree) forwards; grid search runtime on 24–32 GB machines.
220
-
221
- **Recommendation: build later (likely skip).** The L2 mixed-precision path is
222
- our quant-quality story; only revisit if KL evals show our 4-bit uniform tier
223
- losing to community AWQ artifacts.
224
-
225
- ---
226
-
227
- ## 5. `dwq`
228
-
229
- **What it does.** Distilled weight quantization (`mlx_lm/quant/dwq.py`):
230
- quantize the model, then **unfreeze only the quantization `scales`/`biases`**
231
- of sub-8-bit affine layers (`dwq.py:90-100`) and train them against the bf16
232
- teacher with a temperature-2 KL loss on top-1024 teacher logits
233
- (`kl_div_loss`, `dwq.py:108-118`), Adam @ 1e-6, params accumulated in f32.
234
- Teacher logits can be precomputed to disk (`--target-dir`, top-1024 +
235
- indices as safetensors, `compute_dwq_targets`) so teacher and student never
236
- need to be resident together. Warns if final val loss regressed. This is the
237
- method behind most good mlx-community 4-bit artifacts.
238
-
239
- **CLI surface** (`dwq.py:242-304`): `--model/-m` (teacher, req),
240
- `--quantized-model` (optional pre-quantized student), `--mlx-path`, `--bits`
241
- (4), `--group-size` (64), `--num-samples` (2048), `--max-seq-length` (1025),
242
- `--seed`, `--learning-rate` (1e-6), `--batch-size` (4), `--data-path`
243
- (default `allenai/tulu-3-sft-mixture`), `--grad-checkpoint`, `--target-dir`,
244
- `--targets-only`, `--pipeline`.
245
-
246
- **mlx-bun overlap.** Strong: full training stack (`src/train/` — trainer,
247
- gradient checkpointing, segmented backward, flash-CCE), KL machinery
248
- (`src/eval/kl.ts`), quantized-model load/save (`src/quantize/`), dataset
249
- plumbing from the train CLI. Missing pieces are specific: (a) treating
250
- quantized-layer `scales`/`biases` as trainable leaves (trainer is
251
- LoRA-parameter-shaped today), (b) the KL-on-top-k distillation loss +
252
- target-precompute pipeline, (c) f32 master-weights accumulation for bf16
253
- models. Neither optiq nor mlx-bun has this today; optiq's trainer is mlx-lm's
254
- tuner, so the oracle is direct.
255
-
256
- **Port plan.** `src/train/dwq.ts` (loss + loop, reusing trainer infra),
257
- `src/quantize/` hook to enumerate trainable quant params, target-precompute
258
- script writing the same `{logits, indices}` safetensors shards, CLI
259
- `mlx-bun train --method dwq` (or `mlx-bun dwq`). **Memory note for this
260
- hardware:** always use the `--target-dir` two-phase flow (teacher pass, then
261
- student-only training) — teacher+student resident together won't fit for
262
- interesting models on 24/32 GB.
263
-
264
- **Oracle / tiers.** Training-parity style gate (the mlx-lm-tuner-copy
265
- standard): fixed seed/data/targets → first-N-step loss values match
266
- `mlx_lm.dwq` bit-for-bit (or to bf16-accumulation tolerance, documented);
267
- artifact quality gated by KL-vs-teacher and perplexity via existing eval,
268
- plus "final val ≤ initial val" as upstream checks.
269
-
270
- **Effort:** L. **Risky parts:** unfreezing quant params without disturbing
271
- the LoRA-shaped trainer contract; matching `iterate_batches` order for the
272
- step-parity gate; seq-len ceilings during the teacher pass on big models
273
- (same wall as e4b training).
274
-
275
- **Recommendation: build later — but first of the three quant tools.** It's
276
- the one with real artifact-quality payoff, it composes with (rather than
277
- duplicates) the L2 mixed-precision allocator (DWQ-refine an optiq-allocated
278
- model), and it fits the training-first identity. Schedule after `sft_scope`
279
- lands to avoid trainer churn.
280
-
281
- ---
282
-
283
- ## 6. `gptq`
284
-
285
- **What it does.** GPTQ (`mlx_lm/quant/gptq.py`): accumulate per-layer input
286
- Hessians `H = ΣxᵀX` via `Catcher` wrappers on every `nn.Linear`/
287
- `SwitchLinear` (`gptq.py:40-49`), Cholesky-invert on the CPU stream
288
- (`gptq.py:78-86`), then per-group error-propagating rounding
289
- (`gptq.py:95-142`); non-GPTQ layers get `--fallback-bits` (default 6).
290
- Architecture-agnostic (no per-model registry, unlike AWQ). Same calibration
291
- loader as AWQ.
292
-
293
- **CLI surface** (`gptq.py:162-199`): `--model/-m`, `--mlx-path`, `--bits`
294
- (4), `--group-size` (64), `--fallback-bits` (6), `--fallback-group-size`
295
- (64), `--num-samples` (-1 = all), `--sequence-length` (512), `--seed` (123).
296
-
297
- **mlx-bun overlap.** Same as AWQ: `src/quantize/` covers bit-allocation
298
- quality, nothing covers Hessian-conditioned rounding; optiq ships no GPTQ.
299
- The mixed shape is interesting though: GPTQ's *output* is expressible in our
300
- existing per-layer quantization config (`config-writer.ts` /
301
- `quantization` config section) — only the solver is new.
302
-
303
- **Port plan (if built).** `src/quantize/gptq.ts`: per-layer input capture
304
- (shared instrumentation with the AWQ item — build once), Hessian accumulation,
305
- Cholesky via mlx-c on the CPU stream (**verify `mlx_linalg_cholesky` /
306
- `cholesky_inv` are bound in `src/mlx/` first** — if absent, that's new FFI
307
- surface), the rounding loop, then reuse `safetensors-writer.ts`. Being
308
- arch-agnostic, it needs no per-model registry — meaningfully cheaper than AWQ.
309
-
310
- **Oracle / tiers.** `mlx_lm.gptq` on a small model with shared calibration
311
- file + seed → weight-tensor parity (the algorithm is deterministic); then KL /
312
- perplexity quality gate. L1-style, single oracle.
313
-
314
- **Effort:** M–L (M if the capture instrumentation exists from DWQ/AWQ work
315
- and cholesky is already bound; L otherwise). **Risky parts:** cholesky FFI
316
- availability; the inner rounding loop's `mx.eval` cadence (upstream evals per
317
- group element — naive porting is slow); MoE `SwitchLinear` equivalents.
318
-
319
- **Recommendation: skip for now.** Dominated by DWQ on quality-per-effort and
320
- by the L2 allocator on the positioning story; keep on the list only as a
321
- follow-on if we build the shared capture instrumentation anyway.
322
-
323
- ---
324
-
325
- ## 7. `serve --draft-model` / `--num-draft-tokens` (speculative decoding at serve time)
326
-
327
- > **BUILT 2026-07-03** as grammar-spec-batching-integration.md Phase B —
328
- > `src/spec/{source,two-model,serve-loop}.ts`, flags in cli.ts, `hasDraft`
329
- > routing, fit admission, `usage.speculation` telemetry. **L1 gate passed:
330
- > 48/48 token-for-token vs mlx_lm's speculative path** (Llama 3B+1B,
331
- > `scripts/oracle-spec-two-model.py`, `tests/spec-serve.test.ts`). Grammar
332
- > composes (Phase C constrained verify walk). v1 deviations: prompt-cache
333
- > reuse bypassed on the spec path; ring-wrap degrades to plain decode
334
- > (pre-pollution gate) instead of raising. §7.9's assistant/DSpark sources
335
- > remain open behind the same `DraftSource` seam.
336
-
337
- This was the concrete, hand-to-an-implementer plan (kept for the record).
338
-
339
- ### What mlx_lm.server does (read: `server.py`, `generate.py`)
340
-
341
- - **Flags** (`server.py:1782-1791`): `--draft-model` (path/repo, default
342
- None), `--num-draft-tokens` (default **3**; `mlx_lm.generate`'s default is
343
- 2 — copy the server's 3).
344
- - **Loading** (`ModelProvider._load`, `server.py:326-381`): draft loaded with
345
- plain `load()`; **vocab-size mismatch is a warning, not an error**
346
- (`:363-368`); draft path is part of `model_key`, and requests can override
347
- per-request (`draft_model`, `num_draft_tokens` in the body,
348
- `server.py:1164-1166`, validated ≥ 0 at `:1237`). No memory-budget check
349
- (mlx-lm has none; we do — see below).
350
- - **Lane routing:** `is_batchable = draft_model is None` (`server.py:371`) —
351
- a draft model forces every request down `_serve_single`
352
- (`server.py:922-988`), i.e. **spec decode is serial-lane-only upstream
353
- too**. Distributed mode rejects draft models outright (`:327-332`).
354
- - **Prompt cache composition** (`server.py:970-973`): one cache list =
355
- target's caches with the draft's caches **appended**;
356
- `speculative_generate_step` splits it back at `len(model.layers)`
357
- (`generate.py:526-527`). The server's LRU prompt cache therefore stores
358
- both models' KV per entry, keyed by a model_key that includes the draft
359
- path.
360
- - **The loop** (`speculative_generate_step`, `generate.py:473-654`): prefill
361
- both models on the prompt; per round draft `n = min(remaining,
362
- num_draft_tokens)` tokens autoregressively from the draft model (sampled
363
- with the *same request sampler*), run **one target forward over
364
- `[pending, ...drafts]`** (n+1 positions), sample the target per position,
365
- and accept the longest prefix where target token == draft token — **exact
366
- token-match acceptance, not distribution-level rejection sampling** — then
367
- emit the target's token at the first mismatch (or the bonus token if all
368
- accepted), and `trim_prompt_cache` both caches by the rejected count
369
- (`:589-591`). Requires trimmable caches (`:529-533` raises otherwise);
370
- kv-quant composes via `maybe_quantize_kv_cache` on both caches. When all
371
- drafts accept, the last draft token is re-fed to the draft model next round
372
- (`:645-648`). `logits_processors` are threaded through with a `prev_tokens`
373
- replay (`:559-575`).
374
-
375
- ### mlx-bun v1 scope and design
376
-
377
- **Scope:** two-full-model speculative decoding on the **serial lane only**,
378
- Gemma4 + the other ported archs, greedy and sampled; per-request overrides
379
- deferred (server-level flag only in v1 — our single-user model makes
380
- per-request draft switching pointless).
381
-
382
- **1. Drafter abstraction — `src/spec/source.ts` (new).** One interface so the
383
- mlx-lm two-model drafter, the optiq `GemmaAssistantDrafter`
384
- (`src/spec/drafter.ts`), and later DSpark's `DflashDrafter`
385
- (`src/spec/dspark/module-dflash.ts`) sit behind the same verify loop:
386
-
387
- ```ts
388
- interface DraftSource {
389
- /** Process prompt tokens (two-model: prefill the draft model's cache;
390
- * assistant/dflash: no-op — they read the target's state). */
391
- prefill(ids: number[]): void;
392
- /** Propose up to n tokens given the last committed token and the target's
393
- * step context (last hidden slice + donor KV views — unused by the
394
- * two-model source). */
395
- draft(last: number, n: number, ctx: TargetStepCtx): number[];
396
- /** kAccept of n drafts accepted; correction token emitted. Two-model:
397
- * trim draft cache by max(n - kAccept - 1, 0) per mlx-lm's rewind rule
398
- * (generate.py:589-591) and note the all-accepted re-feed. */
399
- commit(n: number, kAccept: number, correction: number): void;
400
- /** Resident bytes for admission accounting. */
401
- readonly weightsBytes: number;
402
- kvBytesAt(ctx: number): number;
403
- dispose(): void;
404
- }
405
- ```
406
-
407
- `TargetStepCtx = { lastHidden?: MlxArray; donors?: SharedKv; position: number }`
408
- — produced by the serial generation loop; the two-model source ignores it,
409
- which is exactly what lets the assistant/dflash drafters slot in later
410
- (they're the ones that need it; `specGenerate` in `src/spec/generate.ts`
411
- already computes both).
412
-
413
- **2. Two-model source — `src/spec/two-model.ts` (new).** Owns a second
414
- `RuntimeModel` (own `Weights.open`, own cache list, own config). `draft()` is
415
- n sequential decode steps sampled with the request sampler (mirror
416
- `_draft_generate`, `generate.py:593-601`, including `async_eval` pipelining).
417
- Sampler parity matters: mlx-lm drafts with the same sampler as the target —
418
- greedy drafting under a temperature>0 request is *not* parity.
419
-
420
- **3. Verify loop — extend the serial lane.** The hook point is the serial
421
- generation function the gateway calls (`serialRun` →
422
- `runGeneration`, `src/server.ts:966-1001`; gateway in
423
- `src/serve/generation-gateway.ts:89-183`). Add a speculative variant of the
424
- decode loop (new `src/spec/serve-loop.ts`, structured like
425
- `specGenerate` at `src/spec/generate.ts:157-232` but sampler-aware and
426
- callback-streaming):
427
-
428
- - per round: `drafts = source.draft(next, n, ctx)`; one target forward over
429
- `[next, ...drafts]`; sample per position with the request sampler over
430
- **batched** lm-head logits (this matches both oracles — mlx-lm and optiq
431
- batch the verify head; see the header comment in `src/spec/generate.ts`);
432
- - accept longest exact-match prefix; emit accepted tokens + correction
433
- **through the existing onToken pipeline one at a time, in order** — the
434
- stop-sequence state machine and detokenizer must see a burst of ≤ n+1
435
- tokens per iteration (today's loop emits exactly 1; audit stop-match
436
- rollback across a burst — this is the subtle part);
437
- - trim the target caches by `n - kAccept` (all our serial caches implement
438
- `isTrimmable()`/`trim()` — see `src/spec/generate.ts:213-220`); on a
439
- rotating cache whose ring has wrapped, **fall back to non-speculative
440
- decode for the rest of the generation** (upstream throws; a serve endpoint
441
- shouldn't).
442
-
443
- **4. Flags + loading.** `serverRuntimeFlags()` in `src/cli.ts` gains
444
- `--draft-model <query>` (registry-resolved like the main model) and
445
- `--num-draft-tokens` (default 3). Load the draft in `loadContext`
446
- (`src/server.ts:177-231`) after the main model. Tokenizer check: compare
447
- vocab sizes; warn like upstream, and additionally hard-fail if tokenizer
448
- families differ (we can detect; upstream's silent garbage mode isn't worth
449
- inheriting).
450
-
451
- **5. Memory admission (`fit`) interaction.** mlx-bun refuses/limits at load
452
- via `fit()` (`src/fit.ts:172-229`; server check `src/server.ts:1012-1022`).
453
- With a draft: effective usable bytes for the target =
454
- `memoryBudget − draftWeightsBytes − draftKvBytesAt(ctx)` (draft KV grows with
455
- the same context; compute with the draft's own config via `kvBytesAt`). Call
456
- `fit()` for the target with that reduced `usableBytes`; refuse startup if
457
- `maxSafeContext < 1` and log the draft-adjusted max context. Also surface the
458
- pair in `mlx-bun fit` later (nice-to-have: `fit <target> --draft <draft>`).
459
-
460
- **6. Prompt cache composition.** Two options; take the mlx-lm-shaped one:
461
- `PromptCache` entries for spec requests store `caches:
462
- [...targetCaches, ...draftCaches]` and the entry namespace gains the draft id
463
- (extend the existing adapter-ns mechanism, `src/server.ts:974`) so
464
- non-spec/spec entries never cross. `take()`/`put()` byte accounting already
465
- sums whatever arrays the entry holds — no change needed there.
466
-
467
- **7. Composition rules (v1):**
468
- - **Serial lane only** — with `--batch N` + `--draft-model`, route requests
469
- like kv-quant does today (explicit spec ⇒ serial lane;
470
- `willBatch` in `generation-gateway.ts:113-127` gains `hasDraft`). Upstream
471
- behaves the same (`is_batchable = draft is None`).
472
- - **kv-quant:** upstream composes (quantized caches are trimmable). v1: allow
473
- `--kv-quant off|4|8` with spec; gate `config` mode behind a follow-up
474
- (per-layer mixed configs make the trim/fallback matrix bigger).
475
- - **Adapters/vision/logprobs:** out of scope v1 — reject the combination
476
- loudly, revisit.
477
- - **Perf levers:** compiled-decode replays a 1-token decode graph — the n+1
478
- verify step won't hit it; run the spec loop eager v1 (flag interplay
479
- documented, not silently wrong).
480
-
481
- **8. Oracle / verification.**
482
- - **L1 gate:** greedy, fixed prompts: token-for-token equality with
483
- `mlx_lm.server --draft-model` / `stream_generate(draft_model=…)` from the
484
- oracle venv, same target/draft pair. Compare **spec-vs-spec**, not
485
- spec-vs-stock: both runtimes' spec paths batch the verify lm-head and
486
- legitimately diverge from stock decode at bf16 knife-edges (documented in
487
- `src/spec/generate.ts` header). Extend `scripts/oracle-spec.py` +
488
- `scripts/spec-bench.ts` for the A/B.
489
- - **Acceptance-rate telemetry:** emit `drafted/accepted/targetCalls` in the
490
- response `usage`-adjacent stats (the `SpecStats` shape already exists) and
491
- into the benchmark path.
492
- - **Perf gate:** `./benchmark.sh`-quality numbers on the current machine
493
- (label host/chip/RAM per the eval-DB rule): target/draft pair from the
494
- local registry, decode tok/s spec vs non-spec at the same flags, recorded
495
- in `benchmarks/RESULTS.md` only if reproduced on an idle machine.
496
- - **Fallback correctness:** rotating-cache-wrap fallback test (long
497
- generation past the sliding window keeps producing = the non-spec
498
- continuation).
499
-
500
- **9. Later: DSpark/assistant behind the same flag.** `--draft-model` value
501
- dispatch by artifact: full model dir ⇒ `TwoModelSource` (L1); a
502
- `gemma4_assistant` artifact (config `model_type` / tensor presence, as
503
- `src/spec/drafter.ts:99-110` already sniffs) ⇒ an `AssistantSource` wrapping
504
- `GemmaAssistantDrafter` (L2 — oracle is optiq's `spec_generate`); a DSpark
505
- checkpoint ⇒ `DflashSource` wrapping `DflashDrafter` (L3 — KL/quality-gated,
506
- per `docs/design/dspark-speculative-decoding.md`; its verify loop in
507
- `src/spec/dspark/generate-dflash.ts` already has the greedy/sampling verdicts
508
- to fold in). The serve loop, admission accounting, prompt-cache namespacing,
509
- and stats do not change — only the source.
510
-
511
- **Effort:** M (≈2 new files + flag/admission/prompt-cache plumbing; the
512
- verify loop is ~200 lines with two in-repo references and a direct oracle).
513
- **Risky parts:** multi-token bursts through the stop-sequence/detokenizer
514
- pipeline; prompt-cache entry shape + namespacing; sampler parity in the draft
515
- step; rotating-cache wrap fallback.
516
-
517
- **Recommendation: build now** (first serving-side item after the in-flight
518
- work): direct mlx-lm parity gap, big single-user win on this hardware class,
519
- and it lays the interface DSpark needs to ever leave research.
520
-
521
- ---
522
-
523
- ## Proposed build order
524
-
525
- Context: `fuse`/`convert`/`perplexity` are being built right now by another
526
- agent; `sft_scope` is in flight. Order chosen to avoid touching the same
527
- files as in-flight work first, and to sequence trainer-touching work after
528
- `sft_scope`.
529
-
530
- 1. **`upload` CLI** — S, zero-risk, unblocks publishing the artifacts the
531
- in-flight `convert`/`fuse` work produces. (`src/cli.ts` +
532
- `src/hf-push.ts`, no overlap with in-flight files beyond a cli.ts case.)
533
- 2. **`serve --draft-model` (two-model, serial lane)** — M, flagship parity
534
- feature; includes the `DraftSource` interface.
535
- 3. **lm-eval shim for `evaluate`** — S, doc + script + a `/v1`
536
- logprobs-echo check; declares the tool covered-via-existing.
537
- 4. **`cache_prompt` + `generate --prompt-cache-file`** — M; KV serialization
538
- also future-proofs prompt-cache persistence for serve.
539
- 5. **`dwq`** — L, **after `sft_scope` lands** (both touch the trainer);
540
- two-phase target-precompute flow mandatory on this hardware.
541
- 6. **Assistant/DSpark sources behind `--draft-model`** — M, once (2) exists;
542
- L2 oracle = optiq spec_generate, L3 = KL-gated.
543
- 7. **`awq` / `gptq`** — L / M–L, parked: build only if KL evals show the
544
- L2 mixed-precision + DWQ stack losing to community AWQ/GPTQ artifacts.
545
- (`dynamic_quant`: covered today by `src/quantize/` — no work item.)
@@ -1,98 +0,0 @@
1
- # oMLX adoption map — every feature, where it slots, status
2
-
3
- Status: LIVING (started 2026-07-02). oMLX (github.com/jundot/omlx, Apache
4
- 2.0) is the systematic port source: read its actual implementation before
5
- building (full source ships locally in
6
- `/Applications/oMLX.app/Contents/Resources/omlx/`), port the IDEA into our
7
- architecture, and keep it only if OUR benchmarks improve (the burst-decode
8
- refutation below is the cautionary tale). Attribution: note derived code in
9
- THIRD_PARTY_LICENSES.md; idea ports get a source comment.
10
-
11
- ## Where oMLX sits in the fidelity-tier model
12
-
13
- It doesn't slot at L1 or L2 — it decomposes. The tiers are NUMERICS
14
- contracts (parity-tier-dag.md); oMLX is a Python appliance ON mlx-lm:
15
-
16
- - Its stock forwards are mlx-lm's numerics = our existing **L1 oracle**
17
- (no new oracle value; mlx-lm itself stays the reference).
18
- - Its own numeric inventions (oQ, custom kernels, DFlash, MTP) are
19
- oracle-less = **L3-class** here, and get our L3 gating (KL/eval/envelope
20
- + kill switch) that they don't have upstream.
21
- - Its product surface (SSD cache, EnginePool, menu bar, MCP, grammar) is
22
- **tier-agnostic serving layer** — keyed by the effective tier/scheme,
23
- never part of the decode-numerics DAG.
24
-
25
- ## Scoreboard
26
-
27
- ### Ported, kept (benchmarked wins)
28
- | Feature | Theirs | Ours | Result |
29
- | --- | --- | --- | --- |
30
- | Continuous batching parity | scheduler on mlx-lm BatchGenerator | `--batch N` lane + P5 SSM port + per-row logits processors (2026-07-02); since: grammar under batching, `--kv-budget` admission, Tier-0 universal (Llama) per-row RoPE (2026-07-03) | cpm5 345 vs 339 (win), e4b −3%, Qwen3.5 −1%, TTFT 2–3× better (batching-perf-path.md) |
31
- | SSD KV cold tier | `cache/paged_ssd_cache.py` (content-hashed blocks) | `src/ssd-cache.ts` + kv-store v2 (whole-entry spill, zero-copy mmap) | restart TTFT 12.1s→0.24s vs their 1–3s; 0% decode overhead vs their ~20% (ssd-kv-cold-tier.md) |
32
-
33
- ### Ported, refuted (do not re-add without new evidence)
34
- | Feature | Why it doesn't transfer |
35
- | --- | --- |
36
- | Adaptive burst decode (`engine_core.py _step_burst`) | Amortizes Python GIL/asyncio ping-pong (~1ms/token). Bun has no GIL; faithful port REGRESSED cpm5 B=4 345→289 + TTFT +100ms. Reverted with breadcrumb (batching-perf-path.md P4). |
37
-
38
- ### Queue (roughly by leverage; ★ = Josh explicitly wants)
39
- 1. **Structured output / JSON-schema constrained decoding** (`api/grammar.py`)
40
- — serving layer. **LANDED 2026-07-02, MERGED to main 2026-07-03** (branch
41
- `feat/structured-output` deleted after merge):
42
- `@mlc-ai/web-xgrammar` (WASM) on `/v1/chat/completions` + `/v1/completions`;
43
- full `response_format`/`guided_*` surface, serial AND batched lanes;
44
- L2-verified vs oMLX (byte-identical
45
- content). See [docs/design/structured-output.md](structured-output.md);
46
- follow-up debt sequenced in
47
- [grammar-spec-batching-integration.md](grammar-spec-batching-integration.md).
48
- 2. **★ Menu bar app** (`apps/omlx-mac/`, native SwiftUI + Sparkle) — product
49
- layer. Their repo is the structural reference; our signed/notarized
50
- single binary is the sidecar. Supersedes tauri-desktop-app.md's Electron
51
- ambivalence: native SwiftUI is the received-well shape.
52
- 3. **Multi-model serving / EnginePool** (`engine_pool.py`: LRU + pinning +
53
- load/unload API) — serving layer. Fits our single-USER framing as model
54
- SWITCHING (one active, LRU-evict the rest), not concurrent tenants.
55
- 4. **oQ-style sensitivity-driven quantization** (`oq.py`,
56
- docs/oQ_Quantization.md) — L3-class, lands in `convert` beside
57
- `--target-bpw`: calibration-measured per-layer sensitivity, boosts on
58
- non-expert tensors only, batched expert GPTQ. Gate: perplexity + 6-task
59
- eval vs our knapsack at equal bpw. (arXiv-lens candidate.)
60
- 5. **DFlash serving wiring** (`engine/dflash.py` + their published -DFlash
61
- drafts) — L3-class; unblocks DSpark (docs/design/dspark-speculative-decoding.md).
62
- Their integration pattern: separate engine class, ctx-length fallback
63
- routing, tape-replay cache rollback. *Note: serve-time speculative
64
- decoding itself LANDED 2026-07-03 (`serve --draft-model`, two-model
65
- serial lane, 48/48 token-for-token vs mlx-lm's spec path —
66
- grammar-spec-batching-integration.md Phase B); DFlash/DSpark would slot
67
- behind the same `DraftSource` seam.*
68
- 6. **Vision feature cache** (`cache/vision_feature_cache.py`) — serving
69
- layer; encoder features for repeated-image agent turns. Natural sibling
70
- of the SSD tier (same keying discipline).
71
- 7. **MCP tool execution in serve** (`--mcp-config`, `mcp_routes.py`) —
72
- serving layer. Note overlap: Pi already owns tool orchestration for our
73
- UIs — scope this to bare-API consumers; don't duplicate Pi.
74
- 8. **Prefill progress observability** (`prefill_progress.py`) — /stats +
75
- web UI surfacing for long prompts. Pairs with the serial-lane hop fix.
76
- 9. **Rerank endpoint** (`/v1/rerank`) — needs a reranker model family
77
- first; park until a Qwen3-Reranker-class port is wanted.
78
- 10. **Document ingestion (MarkItDown)** — product layer; ours would go
79
- through the chat UI / Pi attachments, not the server.
80
- 11. **Admin one-click benchmark with prefix-cache-hit testing** — fold into
81
- bench-serving-load.ts as a `--cache-hit-ratio` mode instead of a UI.
82
-
83
- ### Explicitly not porting
84
- - **Audio (STT/TTS/STS)** — audio tower deferred (project scope).
85
- - **Their SSE burst streaming** — side effect of the GIL workaround;
86
- per-token streaming is strictly better UX at zero cost for us.
87
- - **Python-side scheduler details** (GIL executors, collector reaping) —
88
- runtime-specific.
89
-
90
- ## Porting discipline (learned this week)
91
- 1. Read their real source first (local app bundle) — the README lies less
92
- than the code.
93
- 2. Port the idea into OUR architecture; never transliterate Python.
94
- 3. Benchmark before/after on THIS machine; keep only wins (burst decode
95
- died here). Wall-clock metrics only — their own logs over-report.
96
- 4. Numeric features get L3 gates + kill switches; serving features get
97
- the effective-scheme keying; nothing touches L1/L2 contracts.
98
- 5. Verify the served model id (/v1/models) before trusting any bench.