mlx-bun 0.0.13 → 0.2.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 (224) hide show
  1. package/README.md +31 -21
  2. package/bin/mlx-bun.mjs +1 -1
  3. package/docs/{design → archive}/hlg-sampling.md +3 -3
  4. package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
  5. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
  6. package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
  7. package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
  8. package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
  9. package/docs/design/colibri-glm52-port.md +1 -1
  10. package/docs/design/compat-cli-surface-design.md +1 -1
  11. package/docs/design/decode-speed-program.md +1 -1
  12. package/docs/design/docs-reorg-plan.md +1 -1
  13. package/docs/design/dspark-serving-program.md +1 -1
  14. package/docs/design/dspark-speculative-decoding.md +53 -3
  15. package/docs/design/grammar-spec-batching-integration.md +1 -1
  16. package/docs/design/mlx-lm-serving-execution-seam.md +81 -0
  17. package/docs/design/orpo-dynamic-lambda.md +1 -1
  18. package/docs/design/orpo-future-enhancements.md +1 -1
  19. package/docs/design/orpo-training.md +2 -2
  20. package/docs/design/parity-tier-dag.md +1 -1
  21. package/docs/design/repo-cleanup-plan.md +46 -4
  22. package/docs/design/segmented-backward-training.md +2 -2
  23. package/docs/design/ssd-kv-cold-tier.md +127 -0
  24. package/docs/design/turboquant-weights.md +467 -0
  25. package/docs/design/w4a16-compute-precision-spike.md +1 -1
  26. package/docs/design/web-chat-redesign.md +5 -5
  27. package/docs/planning/chunk-task-roadmap.md +1 -1
  28. package/docs/planning/curve-sampler-research-plan.md +4 -4
  29. package/docs/planning/memory-docs-and-dag-plan.md +1 -1
  30. package/docs/planning/release-notes-v0.2.0.md +49 -0
  31. package/docs/planning/video-series.md +421 -0
  32. package/docs/reference/cli.md +15 -4
  33. package/docs/reference/distribution.md +1 -0
  34. package/docs/reference/features-matrix.md +3 -2
  35. package/docs/reference/library-api.md +41 -1
  36. package/docs/reference/models.md +33 -0
  37. package/docs/reference/server-api.md +69 -14
  38. package/docs/reference/server-config.md +57 -33
  39. package/package.json +2 -2
  40. package/src/anthropic.ts +92 -3
  41. package/src/cli.ts +131 -36
  42. package/src/eval/kl.ts +27 -20
  43. package/src/eval/runner.ts +23 -4
  44. package/src/eval/tasks/bfcl.ts +2 -1
  45. package/src/eval/tasks/gsm8k.ts +2 -1
  46. package/src/eval/tasks/hashhop.ts +2 -1
  47. package/src/eval/tasks/humaneval.ts +5 -4
  48. package/src/eval/tasks/ifeval.ts +2 -1
  49. package/src/eval/tasks/mmlu.ts +2 -1
  50. package/src/expert-io.ts +3 -1
  51. package/src/expert-offload.ts +3 -1
  52. package/src/expert-trace.ts +3 -1
  53. package/src/fit.ts +24 -43
  54. package/src/flags.ts +7 -9
  55. package/src/generate.ts +107 -92
  56. package/src/grammar.ts +2 -1
  57. package/src/harness-pi.ts +1 -1
  58. package/src/index.ts +20 -0
  59. package/src/jobs/job-entry.ts +3 -2
  60. package/src/kv-scheme.ts +199 -0
  61. package/src/kv-store.ts +370 -205
  62. package/src/media-fetch.ts +19 -3
  63. package/src/memory/model.ts +2 -1
  64. package/src/memory/vault.ts +2 -1
  65. package/src/memory-plan.ts +18 -0
  66. package/src/mlx/ffi.ts +8 -11
  67. package/src/mlx/ops.ts +31 -6
  68. package/src/model/batched-mask.ts +1 -1
  69. package/src/model/batched-quant.ts +1 -1
  70. package/src/model/batched-rotating-quant.ts +43 -97
  71. package/src/model/batched-rotating-state.ts +73 -0
  72. package/src/model/batched-rotating.ts +56 -105
  73. package/src/model/batched-row-storage.ts +127 -0
  74. package/src/model/compiled-decode.ts +3 -2
  75. package/src/model/factory.ts +44 -49
  76. package/src/model/gemma4-base.ts +163 -10
  77. package/src/model/generated/gemma4-12b.ts +50 -146
  78. package/src/model/generated/gemma4-26b.ts +32 -92
  79. package/src/model/generated/gemma4-e4b.ts +26 -74
  80. package/src/model/glm52-cache.ts +19 -9
  81. package/src/model/glm52-memory.ts +28 -1
  82. package/src/model/minicpm5.ts +2 -1
  83. package/src/model/profile.ts +433 -0
  84. package/src/model/qwen3-delta.ts +98 -2
  85. package/src/model/qwen3-mrope.ts +252 -0
  86. package/src/model/qwen3_5.ts +217 -25
  87. package/src/native/frame_extract.swift +140 -0
  88. package/src/native-pack.ts +13 -6
  89. package/src/pi-web.ts +6 -5
  90. package/src/prompt-cache.ts +2 -2
  91. package/src/quantize/atomic-output.ts +38 -0
  92. package/src/quantize/calibration.ts +2 -1
  93. package/src/quantize/config-writer.ts +6 -0
  94. package/src/quantize/index.ts +28 -1
  95. package/src/quantize/job.ts +11 -0
  96. package/src/quantize/quantizer.ts +265 -100
  97. package/src/quantize/rotate.ts +603 -0
  98. package/src/quantize/safetensors-writer.ts +79 -1
  99. package/src/quantize/sensitivity.ts +41 -59
  100. package/src/quantize/weight-transform.ts +292 -0
  101. package/src/responses.ts +102 -2
  102. package/src/runtime-config.ts +67 -0
  103. package/src/sampler.ts +272 -0
  104. package/src/serve/admin-routes.ts +184 -0
  105. package/src/serve/aux-routes.ts +132 -0
  106. package/src/serve/batch-scheduler.ts +200 -87
  107. package/src/serve/completion-executor.ts +346 -0
  108. package/src/serve/completion-sink.ts +157 -0
  109. package/src/serve/discovery-routes.ts +205 -0
  110. package/src/serve/generation-gateway.ts +116 -109
  111. package/src/serve/kv-budget.ts +14 -0
  112. package/src/serve/lab-routes.ts +338 -0
  113. package/src/serve/model-admin-routes.ts +153 -0
  114. package/src/serve/prompt-response-trace.ts +147 -0
  115. package/src/serve/request-plan.ts +158 -0
  116. package/src/serve/static-routes.ts +101 -0
  117. package/src/server.ts +704 -1400
  118. package/src/spec/dflash-source.ts +2 -1
  119. package/src/spec/dspark/module-dflash.ts +1 -1
  120. package/src/spec/glm52-mtp-source.ts +8 -0
  121. package/src/spec/qwen-mtp-source.ts +431 -0
  122. package/src/spec/serve-loop.ts +36 -68
  123. package/src/ssd-cache.ts +17 -14
  124. package/src/ssd-durability.ts +195 -0
  125. package/src/train/flash-cce.ts +12 -11
  126. package/src/train/forward.ts +32 -1
  127. package/src/train/loss.ts +2 -1
  128. package/src/train/segmented.ts +4 -3
  129. package/src/train/trainer.ts +3 -2
  130. package/src/vision/preprocess.ts +1 -1
  131. package/src/vision/prompt.ts +47 -1
  132. package/src/vision/qwen3vl-preprocess.ts +409 -0
  133. package/src/vision/qwen3vl-prompt.ts +196 -0
  134. package/src/vision/qwen3vl-tower.ts +513 -0
  135. package/src/vision/video-frames.ts +148 -0
  136. package/src/web/app.js +10 -3
  137. package/src/web/src/chat.ts +12 -5
  138. package/src/weights.ts +81 -26
  139. /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
  140. /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
  141. /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
  142. /package/docs/{investigations → archive/investigations}/curve-runs/bisector-route.json +0 -0
  143. /package/docs/{investigations → archive/investigations}/curve-runs/bisector-trace.json +0 -0
  144. /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.json +0 -0
  145. /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.svg +0 -0
  146. /package/docs/{investigations → archive/investigations}/curve-terrain.html +0 -0
  147. /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
  148. /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
  149. /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
  150. /package/docs/{investigations → archive/investigations}/hlg-ab.html +0 -0
  151. /package/docs/{investigations → archive/investigations}/hlg-explorer.html +0 -0
  152. /package/docs/{investigations → archive/investigations}/hlg-figs/cliff.svg +0 -0
  153. /package/docs/{investigations → archive/investigations}/hlg-figs/cross.svg +0 -0
  154. /package/docs/{investigations → archive/investigations}/hlg-figs/edges.svg +0 -0
  155. /package/docs/{investigations → archive/investigations}/hlg-figs/k20-grids.svg +0 -0
  156. /package/docs/{investigations → archive/investigations}/hlg-figs/k20-peaks.svg +0 -0
  157. /package/docs/{investigations → archive/investigations}/hlg-figs/lw-curve.svg +0 -0
  158. /package/docs/{investigations → archive/investigations}/hlg-figs/terrain.svg +0 -0
  159. /package/docs/{investigations → archive/investigations}/hlg-report.html +0 -0
  160. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-ab.json +0 -0
  161. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind-prompts.md +0 -0
  162. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind.json +0 -0
  163. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cliff.json +0 -0
  164. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cross.json +0 -0
  165. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-edge.json +0 -0
  166. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-eval1.json +0 -0
  167. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-frontier.json +0 -0
  168. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid.json +0 -0
  169. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid2.json +0 -0
  170. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-lw-coarse.json +0 -0
  171. /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-map.json +0 -0
  172. /package/docs/{investigations → archive/investigations}/hlg-runs/run0-original.log +0 -0
  173. /package/docs/{investigations → archive/investigations}/hlg-runs/runA-recommended.log +0 -0
  174. /package/docs/{investigations → archive/investigations}/hlg-runs/runAA-cliff-cpm5.log +0 -0
  175. /package/docs/{investigations → archive/investigations}/hlg-runs/runAB-grid2-e4b.log +0 -0
  176. /package/docs/{investigations → archive/investigations}/hlg-runs/runAC-peak-e4b-partial.log +0 -0
  177. /package/docs/{investigations → archive/investigations}/hlg-runs/runAD-ab-samples.log +0 -0
  178. /package/docs/{investigations → archive/investigations}/hlg-runs/runAE-edge-e4b.log +0 -0
  179. /package/docs/{investigations → archive/investigations}/hlg-runs/runAF-logit-calibration.log +0 -0
  180. /package/docs/{investigations → archive/investigations}/hlg-runs/runAG-eval-config2.log +0 -0
  181. /package/docs/{investigations → archive/investigations}/hlg-runs/runB-gain-variants.log +0 -0
  182. /package/docs/{investigations → archive/investigations}/hlg-runs/runC-pivots.log +0 -0
  183. /package/docs/{investigations → archive/investigations}/hlg-runs/runD-oetf.log +0 -0
  184. /package/docs/{investigations → archive/investigations}/hlg-runs/runE-eotf.log +0 -0
  185. /package/docs/{investigations → archive/investigations}/hlg-runs/runF-shaper-working.log +0 -0
  186. /package/docs/{investigations → archive/investigations}/hlg-runs/runG-shaper-sweep.log +0 -0
  187. /package/docs/{investigations → archive/investigations}/hlg-runs/runH-wsweep.log +0 -0
  188. /package/docs/{investigations → archive/investigations}/hlg-runs/runI-asweep.log +0 -0
  189. /package/docs/{investigations → archive/investigations}/hlg-runs/runJ-knobmap.log +0 -0
  190. /package/docs/{investigations → archive/investigations}/hlg-runs/runK-frontier.log +0 -0
  191. /package/docs/{investigations → archive/investigations}/hlg-runs/runL-widemap.log +0 -0
  192. /package/docs/{investigations → archive/investigations}/hlg-runs/runM-frontier-emb.log +0 -0
  193. /package/docs/{investigations → archive/investigations}/hlg-runs/runN-verify.log +0 -0
  194. /package/docs/{investigations → archive/investigations}/hlg-runs/runO-frontier-seed5000.log +0 -0
  195. /package/docs/{investigations → archive/investigations}/hlg-runs/runP-grid-AxSm.log +0 -0
  196. /package/docs/{investigations → archive/investigations}/hlg-runs/runQ-grid-tgW.log +0 -0
  197. /package/docs/{investigations → archive/investigations}/hlg-runs/runR-blind-transcript.log +0 -0
  198. /package/docs/{investigations → archive/investigations}/hlg-runs/runS-cross-coherence.log +0 -0
  199. /package/docs/{investigations → archive/investigations}/hlg-runs/runT-cross-cpm5.log +0 -0
  200. /package/docs/{investigations → archive/investigations}/hlg-runs/runU-cross-12b.log +0 -0
  201. /package/docs/{investigations → archive/investigations}/hlg-runs/runV-lw-e4b.log +0 -0
  202. /package/docs/{investigations → archive/investigations}/hlg-runs/runW-lw-12b.log +0 -0
  203. /package/docs/{investigations → archive/investigations}/hlg-runs/runX-lw-cpm5.log +0 -0
  204. /package/docs/{investigations → archive/investigations}/hlg-runs/runY-cliff-e4b.log +0 -0
  205. /package/docs/{investigations → archive/investigations}/hlg-runs/runZ-cliff-12b.log +0 -0
  206. /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
  207. /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
  208. /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
  209. /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
  210. /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
  211. /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
  212. /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
  213. /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
  214. /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
  215. /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
  216. /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
  217. /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
  218. /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
  219. /package/docs/{planning → archive/planning}/journal.md +0 -0
  220. /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
  221. /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
  222. /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
  223. /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
  224. /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
@@ -0,0 +1,346 @@
1
+ import type {
2
+ GenerateOptions,
3
+ GenerateStats,
4
+ TokenLogprobs,
5
+ } from "../generate";
6
+ import type {
7
+ GenerationMechanism,
8
+ GenerationPlacement,
9
+ OnToken,
10
+ RequestShape,
11
+ Vision,
12
+ } from "./generation-gateway";
13
+ import {
14
+ type CompletionEvent,
15
+ type CompletionFinishReason,
16
+ type CompletionFlowControl,
17
+ type CompletionResult,
18
+ type CompletionSinkOptions,
19
+ CompletionSink,
20
+ type CompletionToolCall,
21
+ } from "./completion-sink";
22
+ import { recordLane, type Lane } from "./lane-registry";
23
+ import {
24
+ planRequest,
25
+ type RequestPlan,
26
+ type RequestPlanInput,
27
+ type RequestRejection,
28
+ } from "./request-plan";
29
+ import type { PromptResponseTrace } from "./prompt-response-trace";
30
+
31
+ export interface CompletionEngine {
32
+ place(shape: RequestShape): GenerationPlacement;
33
+ run(
34
+ promptIds: number[],
35
+ options: GenerateOptions & { stopSequences?: string[] },
36
+ onToken: OnToken,
37
+ vision: Vision | undefined,
38
+ shape: RequestShape,
39
+ placement: GenerationPlacement,
40
+ signal?: AbortSignal,
41
+ trace?: PromptResponseTrace,
42
+ ): Promise<GenerateStats>;
43
+ }
44
+
45
+ export interface CompletionPreparation {
46
+ requestId: string;
47
+ plan: RequestPlanInput;
48
+ vision?: Vision;
49
+ pipeline: Omit<CompletionSinkOptions, "flowControl">;
50
+ createFlowControl?(placement: CompletionPlacement):
51
+ | CompletionSinkOptions["flowControl"];
52
+ onPlacement?(placement: CompletionPlacement): void;
53
+ idToToken(id: number): string;
54
+ }
55
+
56
+ export interface CompletionPlacement {
57
+ mechanism: GenerationMechanism;
58
+ lane: Lane;
59
+ shape: RequestShape;
60
+ }
61
+
62
+ interface PreparedData extends Omit<CompletionPreparation, "plan"> {
63
+ plan: RequestPlan;
64
+ stream: boolean;
65
+ requestedMaxTokens: number;
66
+ }
67
+
68
+ const preparedBrand: unique symbol = Symbol("PreparedCompletion");
69
+
70
+ /** A single-use completion declaration. Protocol adapters can create one but
71
+ * cannot inspect or rewrite the resolved execution plan after preparation. */
72
+ export interface PreparedCompletion {
73
+ readonly [preparedBrand]: true;
74
+ }
75
+
76
+ const preparedData = new WeakMap<PreparedCompletion, PreparedData>();
77
+
78
+ export function prepareCompletion(data: CompletionPreparation): PreparedCompletion {
79
+ const planned = planRequest(data.plan);
80
+ if (!planned.ok) {
81
+ planned.dispose();
82
+ throw new CompletionRejected(planned);
83
+ }
84
+ const prepared = Object.freeze({ [preparedBrand]: true }) as PreparedCompletion;
85
+ preparedData.set(prepared, {
86
+ requestId: data.requestId,
87
+ plan: planned,
88
+ stream: data.plan.stream,
89
+ requestedMaxTokens: data.plan.requestedMaxTokens,
90
+ ...(data.vision ? { vision: data.vision } : {}),
91
+ pipeline: data.pipeline,
92
+ ...(data.createFlowControl
93
+ ? { createFlowControl: data.createFlowControl }
94
+ : {}),
95
+ ...(data.onPlacement ? { onPlacement: data.onPlacement } : {}),
96
+ idToToken: data.idToToken,
97
+ });
98
+ return prepared;
99
+ }
100
+
101
+ export interface CompletionControl {
102
+ signal?: AbortSignal;
103
+ trace?: PromptResponseTrace;
104
+ onEvents?(events: readonly CompletionEvent[]):
105
+ | void
106
+ | false
107
+ | Promise<void | false>;
108
+ /** Receives one live, read-only usage view before execution. Retain the
109
+ * reference to close an errored protocol stream with accumulated usage.
110
+ * The executor updates it in place, avoiding per-token callback/allocation
111
+ * work on the decode path. */
112
+ onUsageProgress?(usage: Readonly<CompletionUsage>): void;
113
+ }
114
+
115
+ export interface CompletionUsage {
116
+ promptTokens: number;
117
+ cachedTokens: number;
118
+ completionTokens: number;
119
+ totalTokens: number;
120
+ speculation?: GenerateStats["spec"];
121
+ }
122
+
123
+ export interface CompletionSummary {
124
+ content: string;
125
+ reasoning: string;
126
+ toolCalls: CompletionToolCall[];
127
+ stopped: boolean;
128
+ finishReason: CompletionFinishReason;
129
+ lane: Lane;
130
+ usage: CompletionUsage;
131
+ logprobs: { content: Record<string, unknown>[] } | null;
132
+ }
133
+
134
+ export class CompletionRejected extends Error {
135
+ readonly status: number;
136
+ readonly error: RequestRejection["error"];
137
+
138
+ constructor(rejection: RequestRejection) {
139
+ super(rejection.error.message);
140
+ this.name = "CompletionRejected";
141
+ this.status = rejection.status;
142
+ this.error = rejection.error;
143
+ }
144
+ }
145
+
146
+ class LogprobsCollector {
147
+ readonly #tokens: number[] = [];
148
+ readonly #tokenLogprobs: number[] = [];
149
+ readonly #topTokens: { id: number; token: string; logprob: number }[][] = [];
150
+
151
+ constructor(
152
+ private readonly wantLogprobs: boolean,
153
+ private readonly topK: number,
154
+ private readonly idToToken: (id: number) => string,
155
+ ) {}
156
+
157
+ push(token: number, info?: TokenLogprobs): void {
158
+ this.#tokens.push(token);
159
+ if (this.wantLogprobs) this.#tokenLogprobs.push(info?.logprob ?? NaN);
160
+ if (this.topK > 0) {
161
+ this.#topTokens.push(
162
+ (info?.top ?? []).map((candidate) => ({
163
+ id: candidate.id,
164
+ token: this.idToToken(candidate.id),
165
+ logprob: candidate.logprob,
166
+ })),
167
+ );
168
+ }
169
+ }
170
+
171
+ payload(): { content: Record<string, unknown>[] } | null {
172
+ if (this.#topTokens.length) {
173
+ return {
174
+ content: this.#topTokens.map((tokens) =>
175
+ tokens.length ? { ...tokens[0]!, top_logprobs: tokens } : {},
176
+ ),
177
+ };
178
+ }
179
+ if (this.#tokenLogprobs.length) {
180
+ return {
181
+ content: this.#tokens.map((id, index) => ({
182
+ id,
183
+ logprob: this.#tokenLogprobs[index]!,
184
+ })),
185
+ };
186
+ }
187
+ return null;
188
+ }
189
+ }
190
+
191
+ function initialLane(mechanism: GenerationMechanism, shape: RequestShape): Lane {
192
+ if (mechanism === "continuous") return "batched";
193
+ return shape.hasDraft ? "serial+spec" : "serial";
194
+ }
195
+
196
+ function finalLane(
197
+ mechanism: GenerationMechanism,
198
+ lane: Lane,
199
+ stats: GenerateStats,
200
+ ): Lane {
201
+ if (mechanism === "continuous") return "batched";
202
+ return stats.spec ? "serial+spec" : lane;
203
+ }
204
+
205
+ function finishReason(
206
+ result: CompletionResult,
207
+ stats: GenerateStats,
208
+ maxTokens: number,
209
+ ): CompletionFinishReason {
210
+ if (result.toolCalls.length) return "tool_calls";
211
+ if (result.stopped) return "stop";
212
+ return stats.generatedTokens >= maxTokens ? "length" : "stop";
213
+ }
214
+
215
+ function combineControl(
216
+ sinkControl: CompletionFlowControl,
217
+ eventControl: void | false | Promise<void | false>,
218
+ ): void | false | Promise<void | false> {
219
+ if (sinkControl === false || eventControl === false) return false;
220
+ if (!(sinkControl instanceof Promise) && !(eventControl instanceof Promise))
221
+ return undefined;
222
+ return Promise.all([sinkControl, eventControl]).then((values) =>
223
+ values.includes(false) ? false : undefined,
224
+ );
225
+ }
226
+
227
+ /** Owns one completion attempt from admission through terminal accounting.
228
+ * Model math and scheduling policy remain in the supplied CompletionEngine. */
229
+ export class CompletionExecutor {
230
+ constructor(private readonly engine: CompletionEngine) {}
231
+
232
+ async execute(
233
+ prepared: PreparedCompletion,
234
+ control: CompletionControl = {},
235
+ ): Promise<CompletionSummary> {
236
+ const input = preparedData.get(prepared);
237
+ if (!input) throw new Error("prepared completion has already been executed");
238
+ preparedData.delete(prepared);
239
+
240
+ const planned = input.plan;
241
+ const closeCompletion = control.trace?.begin("completion.total");
242
+ const usageProgress: CompletionUsage | null = control.onUsageProgress
243
+ ? {
244
+ promptTokens: planned.promptIds.length,
245
+ cachedTokens: 0,
246
+ completionTokens: 0,
247
+ totalTokens: planned.promptIds.length,
248
+ }
249
+ : null;
250
+
251
+ try {
252
+ if (usageProgress) control.onUsageProgress!(usageProgress);
253
+ const closePlacement = control.trace?.begin("completion.placement");
254
+ const enginePlacement = this.engine.place(planned.shape);
255
+ closePlacement?.();
256
+ if (enginePlacement.shape !== planned.shape)
257
+ throw new Error("generation placement does not belong to this request shape");
258
+ const mechanism = enginePlacement.mechanism;
259
+ const lane = initialLane(mechanism, planned.shape);
260
+ recordLane(input.requestId, lane);
261
+ const placement = { mechanism, lane, shape: planned.shape };
262
+ input.onPlacement?.(placement);
263
+ const flowControl = input.createFlowControl?.(placement);
264
+ const sink = new CompletionSink({
265
+ ...input.pipeline,
266
+ ...(flowControl ? { flowControl } : {}),
267
+ });
268
+ const logprobs = planned.captureLogprobs
269
+ ? new LogprobsCollector(
270
+ planned.wantLogprobs,
271
+ planned.topLogprobs,
272
+ input.idToToken,
273
+ )
274
+ : null;
275
+ control.signal?.throwIfAborted();
276
+ planned.transferOwnership();
277
+
278
+ const consumeToken: OnToken = (token, tokenLogprobs) => {
279
+ logprobs?.push(token, tokenLogprobs);
280
+ const pushed = sink.push(token);
281
+ const observed = pushed.events.length
282
+ ? control.onEvents?.(pushed.events)
283
+ : undefined;
284
+ return combineControl(pushed.control, observed);
285
+ };
286
+ const onToken: OnToken = usageProgress
287
+ ? (token, tokenLogprobs) => {
288
+ usageProgress.completionTokens++;
289
+ usageProgress.totalTokens++;
290
+ return consumeToken(token, tokenLogprobs);
291
+ }
292
+ : consumeToken;
293
+ const stats = await this.engine.run(
294
+ planned.promptIds,
295
+ planned.options,
296
+ onToken,
297
+ input.vision,
298
+ planned.shape,
299
+ enginePlacement,
300
+ control.signal,
301
+ control.trace,
302
+ );
303
+
304
+ if (input.stream) control.signal?.throwIfAborted();
305
+ const result = sink.finish();
306
+ if (result.events.length) await control.onEvents?.(result.events);
307
+
308
+ const resolvedLane = finalLane(mechanism, lane, stats);
309
+ if (resolvedLane !== lane) recordLane(input.requestId, resolvedLane);
310
+ if (usageProgress) {
311
+ usageProgress.promptTokens = stats.promptTokens;
312
+ usageProgress.cachedTokens = stats.cachedTokens;
313
+ usageProgress.completionTokens = stats.generatedTokens;
314
+ usageProgress.totalTokens = stats.promptTokens + stats.generatedTokens;
315
+ if (stats.spec) usageProgress.speculation = stats.spec;
316
+ else delete usageProgress.speculation;
317
+ }
318
+ const summary = {
319
+ content: result.content,
320
+ reasoning: result.reasoning,
321
+ toolCalls: result.toolCalls,
322
+ stopped: result.stopped,
323
+ finishReason: finishReason(
324
+ result,
325
+ stats,
326
+ planned.options.maxTokens ?? input.requestedMaxTokens,
327
+ ),
328
+ lane: resolvedLane,
329
+ usage: {
330
+ promptTokens: stats.promptTokens,
331
+ cachedTokens: stats.cachedTokens,
332
+ completionTokens: stats.generatedTokens,
333
+ totalTokens: stats.promptTokens + stats.generatedTokens,
334
+ ...(stats.spec ? { speculation: stats.spec } : {}),
335
+ },
336
+ logprobs: logprobs?.payload() ?? null,
337
+ };
338
+ closeCompletion?.();
339
+ return summary;
340
+ } catch (error) {
341
+ closeCompletion?.();
342
+ planned.dispose();
343
+ throw error;
344
+ }
345
+ }
346
+ }
@@ -0,0 +1,157 @@
1
+ export interface CompletionToolCall {
2
+ id: string;
3
+ type: "function";
4
+ function: { name: string; arguments: string };
5
+ }
6
+
7
+ export type CompletionEvent =
8
+ | { type: "content"; text: string }
9
+ | { type: "reasoning"; text: string }
10
+ | { type: "tool_calls"; calls: CompletionToolCall[] };
11
+
12
+ export type CompletionFlowControl = false | Promise<void> | undefined;
13
+
14
+ export interface CompletionPush {
15
+ events: CompletionEvent[];
16
+ control: CompletionFlowControl;
17
+ }
18
+
19
+ export interface CompletionResult {
20
+ events: CompletionEvent[];
21
+ content: string;
22
+ reasoning: string;
23
+ toolCalls: CompletionToolCall[];
24
+ stopped: boolean;
25
+ }
26
+
27
+ export type CompletionFinishReason = "stop" | "length" | "tool_calls";
28
+
29
+ export interface CompletionStreamProtocol {
30
+ start(): string[];
31
+ addEvents(events: CompletionEvent[]): string[];
32
+ finish(reason: CompletionFinishReason, usage: Record<string, unknown>): string[];
33
+ error(message: string): string[];
34
+ }
35
+
36
+ export interface TokenTextRouter {
37
+ push(token: number): string;
38
+ flush(): string;
39
+ takeReasoning(): string;
40
+ toolCalls(): CompletionToolCall[];
41
+ }
42
+
43
+ export interface TextStopper {
44
+ readonly stopped: boolean;
45
+ push(text: string): string;
46
+ flush(): string;
47
+ }
48
+
49
+ export interface ThinkingSplitter {
50
+ push(text: string): { content: string; reasoning: string };
51
+ flush(): { content: string; reasoning: string };
52
+ }
53
+
54
+ export interface CompletionSinkOptions {
55
+ router: TokenTextRouter;
56
+ stopper: TextStopper;
57
+ thinking: ThinkingSplitter;
58
+ collectToolCalls: boolean;
59
+ flowControl?: (emitted: boolean) => CompletionFlowControl;
60
+ }
61
+
62
+ /** One generated-token sink for every text-serving surface.
63
+ *
64
+ * Chat uses the full router → stop matcher → thinking splitter stack.
65
+ * Text completions use the same sink with a plain router, thinking disabled,
66
+ * and tool-call collection off. `push()` returns both semantic events and the
67
+ * generation callback's control signal: false halts decode; a Promise yields
68
+ * before the next token. */
69
+ export class CompletionSink {
70
+ readonly #router: TokenTextRouter;
71
+ readonly #stopper: TextStopper;
72
+ readonly #thinking: ThinkingSplitter;
73
+ readonly #collectToolCalls: boolean;
74
+ readonly #flowControl?: (emitted: boolean) => CompletionFlowControl;
75
+ #content = "";
76
+ #reasoning = "";
77
+
78
+ constructor(options: CompletionSinkOptions) {
79
+ this.#router = options.router;
80
+ this.#stopper = options.stopper;
81
+ this.#thinking = options.thinking;
82
+ this.#collectToolCalls = options.collectToolCalls;
83
+ this.#flowControl = options.flowControl;
84
+ }
85
+
86
+ get stopped(): boolean {
87
+ return this.#stopper.stopped;
88
+ }
89
+
90
+ push(token: number): CompletionPush {
91
+ const events: CompletionEvent[] = [];
92
+ const rawContent = this.#router.push(token);
93
+ this.#emitReasoning(events, this.#router.takeReasoning());
94
+ this.#emitParts(events, this.#thinking.push(this.#stopper.push(rawContent)));
95
+ return {
96
+ events,
97
+ control: this.#stopper.stopped
98
+ ? false
99
+ : this.#flowControl?.(events.length > 0),
100
+ };
101
+ }
102
+
103
+ finish(): CompletionResult {
104
+ const events: CompletionEvent[] = [];
105
+ if (!this.#stopper.stopped) {
106
+ const flushed = this.#router.flush();
107
+ this.#emitReasoning(events, this.#router.takeReasoning());
108
+ let tail = this.#stopper.push(flushed);
109
+ if (!this.#stopper.stopped) tail += this.#stopper.flush();
110
+ this.#emitParts(events, this.#thinking.push(tail));
111
+ }
112
+ this.#emitParts(events, this.#thinking.flush());
113
+
114
+ const toolCalls = this.#collectToolCalls ? this.#router.toolCalls() : [];
115
+ if (toolCalls.length) events.push({ type: "tool_calls", calls: toolCalls });
116
+ return {
117
+ events,
118
+ content: this.#content,
119
+ reasoning: this.#reasoning,
120
+ toolCalls,
121
+ stopped: this.#stopper.stopped,
122
+ };
123
+ }
124
+
125
+ #emitParts(
126
+ events: CompletionEvent[],
127
+ parts: { content: string; reasoning: string },
128
+ ): void {
129
+ this.#emitReasoning(events, parts.reasoning);
130
+ if (parts.content) {
131
+ this.#content += parts.content;
132
+ events.push({ type: "content", text: parts.content });
133
+ }
134
+ }
135
+
136
+ #emitReasoning(events: CompletionEvent[], text: string): void {
137
+ if (!text) return;
138
+ this.#reasoning += text;
139
+ events.push({ type: "reasoning", text });
140
+ }
141
+ }
142
+
143
+ export function createTimedFlowControl(
144
+ enabled: boolean,
145
+ intervalMs = 25,
146
+ now: () => number = () => performance.now(),
147
+ yieldTask: () => Promise<void> = () => new Promise<void>((resolve) => setImmediate(resolve)),
148
+ ): (emitted: boolean) => CompletionFlowControl {
149
+ let lastYield = now();
150
+ return (emitted) => {
151
+ if (!enabled || !emitted) return undefined;
152
+ const current = now();
153
+ if (current - lastYield < intervalMs) return undefined;
154
+ lastYield = current;
155
+ return yieldTask();
156
+ };
157
+ }
@@ -0,0 +1,205 @@
1
+ import pkgJson from "../../package.json" with { type: "json" };
2
+ import type { ServerContext } from "../server";
3
+ import type { GenerationGateway } from "./generation-gateway";
4
+ import { fit } from "../fit";
5
+ import { isEmbeddingModel } from "../embed";
6
+ import { Glm52Model } from "../model/glm52";
7
+
8
+ const pkgVersion = (pkgJson as { version: string }).version;
9
+
10
+ export type DiscoveryRoute =
11
+ | "library"
12
+ | "downloads"
13
+ | "api-index"
14
+ | "health"
15
+ | "models";
16
+
17
+ export function matchDiscoveryRoute(method: string, pathname: string): DiscoveryRoute | null {
18
+ if (method !== "GET") return null;
19
+ switch (pathname) {
20
+ case "/library": return "library";
21
+ case "/downloads": return "downloads";
22
+ case "/v1": return "api-index";
23
+ case "/health": return "health";
24
+ case "/v1/models": return "models";
25
+ default: return pathname.startsWith("/v1/models/") ? "models" : null;
26
+ }
27
+ }
28
+
29
+ export interface DiscoveryRoutes {
30
+ handle(url: URL, request: Request): Promise<Response | null>;
31
+ invalidateLibrary(): void;
32
+ }
33
+
34
+ export function createDiscoveryRoutes(
35
+ ctx: ServerContext,
36
+ gateway: Pick<GenerationGateway, "batchMode">,
37
+ startedAt: number,
38
+ ): DiscoveryRoutes {
39
+ let libraryCache: { at: number; rows: unknown[] } | null = null;
40
+
41
+ return {
42
+ invalidateLibrary() {
43
+ libraryCache = null;
44
+ },
45
+
46
+ async handle(url, request) {
47
+ switch (matchDiscoveryRoute(request.method, url.pathname)) {
48
+ case "library": {
49
+ if (!libraryCache || Date.now() - libraryCache.at > 30_000) {
50
+ const { Registry, visionCapable, audioCapable } = await import("../registry");
51
+ const { loadModelConfig } = await import("../config");
52
+ const { supportTier } = await import("../model/support");
53
+ const registry = new Registry();
54
+ await registry.scan();
55
+ const rows = [];
56
+ for (const model of registry.listCanonical()) {
57
+ const tier = supportTier(model.modelType, model.repoId);
58
+ const supported = tier !== null;
59
+ let assessment = null;
60
+ try {
61
+ const config = await loadModelConfig(model.path);
62
+ const result = fit(
63
+ config,
64
+ model.sizeBytes,
65
+ 8192,
66
+ undefined,
67
+ undefined,
68
+ model.expertsBytes,
69
+ );
70
+ assessment = {
71
+ fits: result.fits,
72
+ max_safe_context: result.maxSafeContext,
73
+ predicted_decode_tps: result.predictedDecodeTps,
74
+ };
75
+ } catch {}
76
+ rows.push({
77
+ repo_id: model.repoId,
78
+ model_type: model.modelType,
79
+ size_bytes: model.sizeBytes,
80
+ quant_bits: model.quantBits,
81
+ vision: visionCapable(model),
82
+ audio: audioCapable(model),
83
+ supported,
84
+ support_tier: tier,
85
+ serving: model.repoId === ctx.modelId,
86
+ assessment,
87
+ });
88
+ }
89
+ libraryCache = { at: Date.now(), rows };
90
+ }
91
+ return Response.json({ models: libraryCache.rows });
92
+ }
93
+
94
+ case "downloads": {
95
+ const { downloadsSnapshot } = await import("../download");
96
+ return Response.json({ downloads: downloadsSnapshot() });
97
+ }
98
+
99
+ case "api-index":
100
+ return Response.json({
101
+ name: "mlx-bun",
102
+ version: pkgVersion,
103
+ model: ctx.modelId,
104
+ endpoints: [
105
+ "POST /v1/chat/completions",
106
+ "POST /v1/completions",
107
+ "POST /v1/messages",
108
+ "POST /v1/responses",
109
+ "POST /v1/embeddings",
110
+ "GET /v1/models",
111
+ "GET/POST/DELETE /v1/adapters",
112
+ "GET /health",
113
+ "GET /stats",
114
+ "GET /fit",
115
+ "GET /library",
116
+ "GET /downloads",
117
+ ],
118
+ });
119
+
120
+ case "health":
121
+ return new Response('{"status": "ok"}', {
122
+ headers: { "content-type": "application/json" },
123
+ });
124
+
125
+ case "models": {
126
+ const filterId = url.pathname.length > "/v1/models/".length - 1
127
+ ? decodeURIComponent(url.pathname.slice("/v1/models/".length))
128
+ : null;
129
+ const created = Math.floor(startedAt / 1000);
130
+ const genDefaults = {
131
+ temperature: ctx.genDefaults.temperature ?? null,
132
+ top_p: ctx.genDefaults.topP ?? null,
133
+ top_k: ctx.genDefaults.topK ?? null,
134
+ };
135
+ const isGlm52 = ctx.model instanceof Glm52Model;
136
+ const data: Array<Record<string, unknown>> = [{
137
+ id: ctx.modelId,
138
+ object: "model",
139
+ created,
140
+ owned_by: "mlx-bun",
141
+ context_window:
142
+ ctx.glmMemoryPlan?.contextTokens ??
143
+ ctx.model.config.text.maxPositionEmbeddings,
144
+ reasoning: ctx.template.supportsThinking,
145
+ vision: !!(ctx.vision || ctx.loadVision),
146
+ audio: !!(ctx.audio || ctx.loadAudio),
147
+ batch_mode: gateway.batchMode,
148
+ tools: true,
149
+ structured_output: true,
150
+ embeddings: isEmbeddingModel(ctx.model),
151
+ adapters: !isGlm52,
152
+ training: !isGlm52,
153
+ dsa: ctx.model instanceof Glm52Model && ctx.model.capabilities.dsa,
154
+ mtp: ctx.draft?.provider.id === "glm52-native-mtp",
155
+ capabilities: {
156
+ chat_completions: true,
157
+ text_completions: true,
158
+ anthropic_messages: true,
159
+ responses: true,
160
+ streaming: true,
161
+ tools: true,
162
+ structured_output: true,
163
+ logprobs: true,
164
+ embeddings: isEmbeddingModel(ctx.model),
165
+ vision: !!(ctx.vision || ctx.loadVision),
166
+ audio: !!(ctx.audio || ctx.loadAudio),
167
+ adapters: !isGlm52,
168
+ training: !isGlm52,
169
+ },
170
+ gen_defaults: genDefaults,
171
+ }];
172
+ try {
173
+ const { Registry, visionCapable } = await import("../registry");
174
+ const { supportTier } = await import("../model/support");
175
+ const registry = new Registry();
176
+ try {
177
+ if (registry.list().length === 0) await registry.scan();
178
+ for (const model of registry.listCanonical()) {
179
+ if (model.repoId === ctx.modelId) continue;
180
+ const tier = supportTier(model.modelType, model.repoId);
181
+ if (tier === null) continue;
182
+ data.push({
183
+ id: model.repoId,
184
+ object: "model",
185
+ created,
186
+ vision: visionCapable(model),
187
+ tier,
188
+ });
189
+ }
190
+ } finally {
191
+ registry.close();
192
+ }
193
+ } catch {}
194
+ return Response.json({
195
+ object: "list",
196
+ data: filterId ? data.filter((model) => model.id === filterId) : data,
197
+ });
198
+ }
199
+
200
+ default:
201
+ return null;
202
+ }
203
+ },
204
+ };
205
+ }