mlx-bun 0.0.9 → 0.0.11

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 (179) hide show
  1. package/README.md +66 -21
  2. package/THIRD_PARTY_LICENSES.md +1 -0
  3. package/docs/archive/mlx-bun-lab-report.html +275 -0
  4. package/docs/archive/wwdc-mlx-bun.html +848 -0
  5. package/docs/design/audio-input-plan.md +356 -0
  6. package/docs/design/batching-perf-path.md +255 -0
  7. package/docs/design/batching-v2-plan.md +23 -9
  8. package/docs/design/bucketing-stage.md +2 -1
  9. package/docs/design/compat-cli-surface-design.md +4 -3
  10. package/docs/design/decode-speed-program.md +172 -0
  11. package/docs/design/docs-reorg-plan.md +1 -1
  12. package/docs/design/dspark-serving-program.md +371 -0
  13. package/docs/design/dspark-speculative-decoding.md +167 -5
  14. package/docs/design/faithful-l1-consolidation.md +310 -0
  15. package/docs/design/generic-model-support.md +8 -5
  16. package/docs/design/grammar-spec-batching-integration.md +301 -0
  17. package/docs/design/minicpm5-decode-megakernel.md +16 -11
  18. package/docs/design/mlx-lm-tool-parity-plan.md +11 -1
  19. package/docs/design/omlx-adoption-map.md +98 -0
  20. package/docs/design/orpo-training.md +34 -30
  21. package/docs/design/paged-kv-cache.md +214 -0
  22. package/docs/design/parallel-slots.md +82 -49
  23. package/docs/design/parity-tier-dag.md +43 -33
  24. package/docs/design/repo-cleanup-plan.md +189 -0
  25. package/docs/design/runtime-isolation.md +19 -3
  26. package/docs/design/segmented-backward-training.md +18 -2
  27. package/docs/design/ssd-kv-cold-tier.md +288 -0
  28. package/docs/design/structured-output.md +484 -0
  29. package/docs/design/the-dreaming-master-plan.md +2 -3
  30. package/docs/design/turboquant-kv.md +235 -0
  31. package/docs/design/unified-engine-frontier-plan.md +774 -0
  32. package/docs/design/web-chat-beat-matrix.md +315 -0
  33. package/docs/design/web-chat-redesign.md +1112 -0
  34. package/docs/investigations/decode-roofline-lookagain.md +92 -6
  35. package/docs/investigations/dspark-handoff.md +182 -16
  36. package/docs/investigations/kernel-perf-review-2026-07.md +105 -26
  37. package/docs/investigations/lab-build-journal.md +2 -2
  38. package/docs/investigations/openwiki-evaluation.md +249 -0
  39. package/docs/planning/IDEAS.md +14 -0
  40. package/docs/planning/PRODUCT_ROADMAP.md +2 -3
  41. package/docs/planning/chat-ui-vision-memory-first.md +199 -0
  42. package/docs/planning/chat-ui-vision.md +50 -0
  43. package/docs/planning/memory-docs-and-dag-plan.md +15 -6
  44. package/docs/planning/release-notes-v0.0.10.md +57 -0
  45. package/docs/planning/release-notes-v0.0.11.md +239 -0
  46. package/docs/reference/cli.md +35 -16
  47. package/docs/reference/distribution.md +13 -6
  48. package/docs/reference/embedding.md +35 -14
  49. package/docs/reference/features-matrix.md +97 -0
  50. package/docs/reference/library-api.md +30 -9
  51. package/docs/reference/memory.md +26 -6
  52. package/docs/reference/models.md +11 -1
  53. package/docs/reference/orpo-quickstart.md +3 -4
  54. package/docs/reference/server-api.md +634 -22
  55. package/docs/reference/server-config.md +251 -119
  56. package/docs/reference/training.md +20 -16
  57. package/package.json +5 -3
  58. package/src/anthropic.ts +13 -0
  59. package/src/audio/conformer.ts +647 -0
  60. package/src/audio/decode.ts +133 -0
  61. package/src/audio/features.ts +202 -0
  62. package/src/audio/transcode.ts +67 -0
  63. package/src/cli.ts +402 -107
  64. package/src/config.ts +49 -2
  65. package/src/download.ts +40 -0
  66. package/src/eval/README.md +7 -6
  67. package/src/eval/kl.ts +59 -3
  68. package/src/eval/quality-db.ts +2 -2
  69. package/src/eval/runner.ts +3 -3
  70. package/src/expert-offload.ts +15 -2
  71. package/src/flags.ts +13 -0
  72. package/src/generate.ts +330 -16
  73. package/src/grammar.ts +585 -0
  74. package/src/hub-rest.ts +242 -0
  75. package/src/kv-store.ts +618 -122
  76. package/src/lora.ts +8 -0
  77. package/src/md-modules.d.ts +42 -0
  78. package/src/media-fetch.ts +284 -0
  79. package/src/memory/rest.ts +245 -0
  80. package/src/memory/vault.ts +74 -0
  81. package/src/mlx/array.ts +127 -19
  82. package/src/mlx/autograd.ts +2 -2
  83. package/src/mlx/ffi.ts +27 -3
  84. package/src/mlx/metal-kernel.ts +7 -4
  85. package/src/mlx/ops.ts +179 -23
  86. package/src/mlx/turboquant-ops.ts +831 -0
  87. package/src/mlx/turboquant-tables.ts +237 -0
  88. package/src/model/batched-mask.ts +62 -1
  89. package/src/model/batched-quant.ts +206 -0
  90. package/src/model/batched-rotating-quant.ts +346 -0
  91. package/src/model/batched-rotating.ts +46 -1
  92. package/src/model/compiled-decode.ts +4 -15
  93. package/src/model/factory.ts +10 -4
  94. package/src/model/gemma4-base.ts +391 -34
  95. package/src/model/gemma4.ts +122 -33
  96. package/src/model/generated/gemma4-12b.ts +9 -13
  97. package/src/model/generated/gemma4-26b.ts +10 -5
  98. package/src/model/generated/gemma4-e4b.ts +14 -9
  99. package/src/model/minicpm5.ts +58 -102
  100. package/src/model/paged-kv.ts +253 -0
  101. package/src/model/qwen3-delta.ts +123 -15
  102. package/src/model/qwen3-moe.ts +404 -0
  103. package/src/model/qwen3.ts +9 -8
  104. package/src/model/qwen3_5.ts +99 -25
  105. package/src/model/support.ts +12 -1
  106. package/src/model/universal/dense.ts +40 -5
  107. package/src/model/universal/rope.ts +36 -21
  108. package/src/pi-web.ts +1299 -82
  109. package/src/preflight.ts +27 -8
  110. package/src/prompt-cache.ts +338 -32
  111. package/src/quantize/quantizer.ts +16 -1
  112. package/src/registry.ts +61 -3
  113. package/src/serve/batch-scheduler.ts +1001 -76
  114. package/src/serve/generation-gateway.ts +292 -30
  115. package/src/serve/isolate.ts +417 -0
  116. package/src/serve/lane-registry.ts +44 -0
  117. package/src/serve/session-search.ts +326 -0
  118. package/src/server.ts +1460 -105
  119. package/src/spec/assistant-source.ts +150 -0
  120. package/src/spec/deepspec-source.ts +143 -0
  121. package/src/spec/dflash-source.ts +124 -0
  122. package/src/spec/dspark/ab-stats.ts +136 -0
  123. package/src/spec/dspark/calibration.ts +112 -0
  124. package/src/spec/dspark/deepspec-module.ts +801 -0
  125. package/src/spec/dspark/generate-dflash.ts +72 -20
  126. package/src/spec/dspark/loader.ts +23 -0
  127. package/src/spec/dspark/module-dflash.ts +278 -36
  128. package/src/spec/dspark/quantize-drafter.ts +91 -0
  129. package/src/spec/generate.ts +59 -25
  130. package/src/spec/ngram-source.ts +132 -0
  131. package/src/spec/serve-loop.ts +536 -0
  132. package/src/spec/source.ts +93 -0
  133. package/src/spec/two-model.ts +171 -0
  134. package/src/ssd-cache.ts +285 -0
  135. package/src/tokenizer.ts +288 -2
  136. package/src/tool-approvals.ts +131 -0
  137. package/src/tool-call.ts +408 -20
  138. package/src/train/flash-cce.ts +29 -26
  139. package/src/train/job.ts +0 -2
  140. package/src/train/loss.ts +53 -11
  141. package/src/train/prefix-shared.ts +30 -7
  142. package/src/train/segmented.ts +150 -46
  143. package/src/train/trainer.ts +22 -15
  144. package/src/vision/prompt.ts +217 -48
  145. package/src/web/app.html +1184 -1789
  146. package/src/web/app.js +5667 -0
  147. package/src/web/icon.svg +13 -0
  148. package/src/web/manifest.webmanifest +24 -0
  149. package/src/web/src/adapters-panel.ts +288 -0
  150. package/src/web/src/api.ts +57 -0
  151. package/src/web/src/assistant.ts +485 -0
  152. package/src/web/src/chat.ts +1050 -0
  153. package/src/web/src/composer.ts +959 -0
  154. package/src/web/src/dataset.ts +132 -0
  155. package/src/web/src/finetune.ts +267 -0
  156. package/src/web/src/hub.ts +428 -0
  157. package/src/web/src/main.ts +98 -0
  158. package/src/web/src/markdown.ts +0 -0
  159. package/src/web/src/memory-panel.ts +572 -0
  160. package/src/web/src/model-picker.ts +159 -0
  161. package/src/web/src/palette.ts +346 -0
  162. package/src/web/src/protocol.ts +62 -0
  163. package/src/web/src/quantize.ts +167 -0
  164. package/src/web/src/rag.ts +274 -0
  165. package/src/web/src/sessions.ts +469 -0
  166. package/src/web/src/shell.ts +804 -0
  167. package/src/web/src/status.ts +252 -0
  168. package/src/web/src/ui-catalog.ts +92 -0
  169. package/src/web/sw.js +71 -0
  170. package/src/web/vendor/README.md +61 -0
  171. package/src/web/vendor/hljs-LICENSE +29 -0
  172. package/src/web/vendor/hljs-theme.css +59 -0
  173. package/src/web/vendor/hljs.js +26 -0
  174. package/src/model/fused-decode-kernel.ts +0 -312
  175. package/src/model/fused-geglu-kernel.ts +0 -198
  176. package/src/model/fused-mlp-kernel.ts +0 -169
  177. package/src/model/fused-swiglu-kernel.ts +0 -240
  178. package/src/model/steel-linear-kernel.ts +0 -96
  179. package/src/status-page.html +0 -497
@@ -0,0 +1,242 @@
1
+ // Model Hub REST wrappers — thin loopback JSON routes for the web chat's
2
+ // Model Hub panel (docs/design/web-chat-redesign.md §9 Phase 3, beat-matrix
3
+ // Axis 3 "Hub" row). Same posture as src/memory/rest.ts: pure (request|url)
4
+ // => Response handlers with no `ctx` dependency, dispatched from
5
+ // src/server.ts by path+method, so they're unit-testable without booting a
6
+ // model context (see tests/hub-rest.test.ts).
7
+ //
8
+ // Three sources, same as optiq Lab's Hub, plus the hardware-fit column optiq
9
+ // lacks:
10
+ // - GET /api/hub/local — registry's canonical downloaded models, each
11
+ // with a /fit-computed verdict (predicted decode tok/s + fits at a
12
+ // default context) — the beat-matrix "real hardware verdict per row".
13
+ // - GET /api/hub/search — server-side HF model search, filtered to
14
+ // MLX-compatible results, normalized. NEVER auto-downloads.
15
+ // - POST /api/hub/download — kicks off a background download via the
16
+ // existing downloadModel() + process tracker (src/download.ts);
17
+ // progress is already visible via GET /downloads. Refuses a duplicate
18
+ // in-flight download for the same repo.
19
+ // - POST /api/hub/serve — model-swap investigation (see the doc header
20
+ // above the handler): today this ALWAYS returns
21
+ // { ok:false, restart_required:true, command } — see
22
+ // docs/design/runtime-isolation.md for why no in-process swap seam is
23
+ // reachable from the web chat's serving path today.
24
+
25
+ import { loadModelConfig } from "./config";
26
+ import { fit } from "./fit";
27
+ import { downloadModel, hfToken, isDownloadActive, type DownloadOptions } from "./download";
28
+ import { Registry, visionCapable, type ModelRecord } from "./registry";
29
+ import { supportTier } from "./model/support";
30
+
31
+ function jsonOk<T extends object>(body: T, init?: ResponseInit): Response {
32
+ return Response.json({ ok: true, ...body }, init);
33
+ }
34
+
35
+ function jsonErr(error: string, status = 400): Response {
36
+ return Response.json({ ok: false, error }, { status });
37
+ }
38
+
39
+ // ---- GET /api/hub/local -------------------------------------------------
40
+
41
+ /** One row per canonical repo, mirroring /library's shape but scoped to
42
+ * what the Hub panel needs: size/quant/capabilities + a fit verdict at a
43
+ * fixed 8k context (the same "typical" context /fit and /library use, so
44
+ * the three surfaces never disagree). `reg`/`hubDir` are injectable so
45
+ * tests can point at a synthetic hub dir instead of the real
46
+ * ~/.cache/huggingface/hub (matching tests/registry-fit.test.ts's pattern);
47
+ * the live server route omits both and gets the real registry. */
48
+ export async function handleHubLocal(
49
+ opts: { reg?: Registry; hubDir?: string } = {},
50
+ ): Promise<Response> {
51
+ const reg = opts.reg ?? new Registry();
52
+ const ownReg = !opts.reg;
53
+ try {
54
+ await reg.scan(opts.hubDir);
55
+ const rows = [];
56
+ for (const m of reg.listCanonical()) {
57
+ rows.push(await hubLocalRow(m));
58
+ }
59
+ return jsonOk({ models: rows });
60
+ } finally {
61
+ if (ownReg) reg.close();
62
+ }
63
+ }
64
+
65
+ async function hubLocalRow(m: ModelRecord) {
66
+ const tier = supportTier(m.modelType, m.repoId);
67
+ let assessment: {
68
+ fits: boolean;
69
+ max_safe_context: number;
70
+ predicted_decode_tps: number;
71
+ } | null = null;
72
+ try {
73
+ const config = await loadModelConfig(m.path);
74
+ const r = fit(config, m.sizeBytes, 8192, undefined, undefined, m.expertsBytes);
75
+ assessment = {
76
+ fits: r.fits,
77
+ max_safe_context: r.maxSafeContext,
78
+ predicted_decode_tps: r.predictedDecodeTps,
79
+ };
80
+ } catch {
81
+ /* unreadable config — assessment stays null, row still lists */
82
+ }
83
+ return {
84
+ repo_id: m.repoId,
85
+ model_type: m.modelType,
86
+ size_bytes: m.sizeBytes,
87
+ quant_bits: m.quantBits,
88
+ quant_group_size: m.quantGroupSize,
89
+ vision: visionCapable(m),
90
+ supported: tier !== null,
91
+ support_tier: tier,
92
+ assessment,
93
+ };
94
+ }
95
+
96
+ // ---- GET /api/hub/search -------------------------------------------------
97
+
98
+ export interface HubSearchResult {
99
+ id: string;
100
+ downloads: number;
101
+ likes: number;
102
+ size_estimate: number | null;
103
+ }
104
+
105
+ /** The HF Hub's model-search API, filtered to the `mlx` library tag (the
106
+ * tag mlx-community and every other MLX-converted repo carries) so results
107
+ * are MLX-compatible by construction — same convention the download path
108
+ * already speaks HTTPS against (huggingface.co/api/*). Injectable `fetchFn`
109
+ * for tests; defaults to the real global fetch. */
110
+ export async function searchHub(
111
+ query: string,
112
+ opts: { fetchFn?: typeof fetch; limit?: number; endpoint?: string } = {},
113
+ ): Promise<
114
+ | { ok: true; offline: false; results: HubSearchResult[] }
115
+ | { ok: false; offline: true; error: string }
116
+ > {
117
+ const fetchFn = opts.fetchFn ?? fetch;
118
+ const endpoint = opts.endpoint ?? "https://huggingface.co";
119
+ const limit = opts.limit ?? 30;
120
+ const token = hfToken();
121
+ const url =
122
+ `${endpoint}/api/models?search=${encodeURIComponent(query)}` +
123
+ `&filter=mlx&sort=downloads&direction=-1&limit=${limit}`;
124
+ let res: Response;
125
+ try {
126
+ res = await fetchFn(url, {
127
+ headers: token ? { authorization: `Bearer ${token}` } : {},
128
+ signal: AbortSignal.timeout(10_000),
129
+ });
130
+ } catch (e) {
131
+ // Network unreachable (offline, DNS failure, timeout) — never surface
132
+ // this as a 500; the Hub panel shows an explicit offline state instead.
133
+ return { ok: false, offline: true, error: (e as Error).message };
134
+ }
135
+ if (!res.ok) {
136
+ return { ok: false, offline: true, error: `HF search ${res.status}` };
137
+ }
138
+ let body: unknown;
139
+ try {
140
+ body = await res.json();
141
+ } catch (e) {
142
+ return { ok: false, offline: true, error: `HF search: bad response (${(e as Error).message})` };
143
+ }
144
+ const rows = Array.isArray(body) ? body : [];
145
+ const results: HubSearchResult[] = rows
146
+ // Belt-and-suspenders: the API's filter=mlx already restricts to the
147
+ // tag, but a defensive re-check keeps a future upstream filter change
148
+ // from silently widening results to non-MLX repos.
149
+ .filter((r) => Array.isArray((r as Record<string, unknown>).tags)
150
+ ? ((r as { tags: string[] }).tags).includes("mlx")
151
+ : true)
152
+ .map((r) => {
153
+ const row = r as Record<string, unknown>;
154
+ return {
155
+ id: String(row.id ?? row.modelId ?? ""),
156
+ downloads: typeof row.downloads === "number" ? row.downloads : 0,
157
+ likes: typeof row.likes === "number" ? row.likes : 0,
158
+ // The search endpoint doesn't return file sizes (that's a second
159
+ // per-repo call this v1 skips to keep search fast) — null, not a
160
+ // fabricated number; the frontend's fit badge lands after download.
161
+ size_estimate: null,
162
+ };
163
+ })
164
+ .filter((r) => r.id.length > 0);
165
+ return { ok: true, offline: false, results };
166
+ }
167
+
168
+ export async function handleHubSearch(url: URL): Promise<Response> {
169
+ const q = url.searchParams.get("q")?.trim() ?? "";
170
+ if (!q) return jsonErr("missing ?q=");
171
+ const result = await searchHub(q);
172
+ if (!result.ok) return jsonOk({ offline: true, error: result.error, results: [] });
173
+ return jsonOk({ offline: false, results: result.results });
174
+ }
175
+
176
+ // ---- POST /api/hub/download -----------------------------------------------
177
+
178
+ // downloadModel()'s own DownloadStatus row (src/download.ts's downloadLog,
179
+ // read via isDownloadActive) only exists once listRepoFiles' network round
180
+ // trip resolves — a rapid double-submit of this route can both pass that
181
+ // check before either row is written. Route-local admission closes that
182
+ // window: a repo is marked in-flight synchronously, before the first
183
+ // `await`, and cleared in a `finally` once downloadModel settles either way.
184
+ const inFlightHubDownloads = new Set<string>();
185
+
186
+ export async function handleHubDownload(request: Request): Promise<Response> {
187
+ const body = (await request.json().catch(() => ({}))) as { repo?: string };
188
+ const repo = body.repo?.trim();
189
+ if (!repo) return jsonErr("missing \"repo\"");
190
+ if (inFlightHubDownloads.has(repo) || isDownloadActive(repo))
191
+ return jsonErr(`a download for ${repo} is already in progress`, 409);
192
+ inFlightHubDownloads.add(repo);
193
+ // Fire-and-forget, mirroring the CLI's background-download pattern
194
+ // (cli.ts's `downloadModel(recRepo).then(...).catch(...)`) — progress is
195
+ // already visible via GET /downloads (the process-global tracker
196
+ // downloadModel() itself writes to); this route just starts it and
197
+ // returns immediately rather than blocking on a multi-GB transfer.
198
+ const opts: DownloadOptions = {};
199
+ downloadModel(repo, opts)
200
+ .then(async () => {
201
+ try {
202
+ const reg = new Registry();
203
+ try { await reg.scan(); } finally { reg.close(); }
204
+ } catch { /* best-effort refresh; /downloads still shows completion */ }
205
+ })
206
+ .catch(() => { /* status recorded on the tracker; /downloads shows the error */ })
207
+ .finally(() => inFlightHubDownloads.delete(repo));
208
+ return jsonOk({ repo, started: true });
209
+ }
210
+
211
+ // ---- POST /api/hub/serve --------------------------------------------------
212
+
213
+ /** Model-swap investigation (docs/design/runtime-isolation.md): the web
214
+ * chat's `/ws/chat` is explicitly NOT proxied even under `--isolate`
215
+ * (src/serve/isolate.ts serves it a 501 with a pointer back to the direct,
216
+ * non-isolated server) and `--isolate` itself is opt-in, not the default —
217
+ * so the process the web chat actually talks to always has exactly one
218
+ * model loaded in-process with no drop-weights-and-reload seam. The
219
+ * isolate.ts `ModelPool` (P2, spawn-overlap child-per-model) IS a real,
220
+ * working live-swap mechanism, but it lives entirely on the `--isolate`
221
+ * reverse-proxy's `/v1/*` HTTP surface — not reachable from the WebSocket
222
+ * chat path this Hub panel serves. Faking a swap here (killing/reloading
223
+ * the in-process model while `/ws/chat` sessions are attached) would leave
224
+ * the server in a half-state the ground rules forbid ("the AI may crash,
225
+ * the UI never may" — a self-inflicted crash is not better). Honest
226
+ * answer: refuse, and hand back the exact restart command. Follow-up:
227
+ * wire /ws/chat through the isolate proxy (or move session state above the
228
+ * engine boundary) so ModelPool's spawn-overlap swap is actually reachable
229
+ * from the web chat — tracked as the Hub's live-swap follow-up. */
230
+ export async function handleHubServe(request: Request): Promise<Response> {
231
+ const body = (await request.json().catch(() => ({}))) as { model?: string };
232
+ const model = body.model?.trim();
233
+ if (!model) return jsonErr("missing \"model\"");
234
+ // ok:false is deliberate — this is not an error the caller mishandled, it
235
+ // is the honest capability answer ("no live swap today"), but it is also
236
+ // not success: the frontend must not treat this as "now serving `model`".
237
+ return Response.json({
238
+ ok: false,
239
+ restart_required: true,
240
+ command: `mlx-bun serve ${model}`,
241
+ });
242
+ }