mlx-bun 0.0.11 → 0.0.12

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 (81) hide show
  1. package/README.md +43 -13
  2. package/THIRD_PARTY_LICENSES.md +9 -0
  3. package/docs/design/colibri-glm52-port.md +1479 -0
  4. package/docs/design/pre-colibri-stabilization.md +329 -0
  5. package/docs/investigations/colibri-oracle-pin.md +1284 -0
  6. package/docs/investigations/indexshare-performance-spike.md +357 -0
  7. package/docs/planning/release-notes-v0.0.12.md +80 -0
  8. package/docs/reference/cli.md +28 -2
  9. package/docs/reference/distribution.md +25 -1
  10. package/docs/reference/embedding.md +5 -2
  11. package/docs/reference/features-matrix.md +4 -2
  12. package/docs/reference/library-api.md +41 -13
  13. package/docs/reference/models.md +49 -2
  14. package/docs/reference/server-api.md +90 -9
  15. package/docs/reference/server-config.md +21 -14
  16. package/docs/reference/training.md +10 -1
  17. package/package.json +1 -1
  18. package/src/anthropic.ts +54 -44
  19. package/src/chat-template.ts +155 -10
  20. package/src/cli.ts +127 -8
  21. package/src/config.ts +71 -26
  22. package/src/diffusion/diffusion-generate.ts +14 -8
  23. package/src/download.ts +103 -12
  24. package/src/eval/ifeval.ts +63 -120
  25. package/src/eval/runner.ts +14 -5
  26. package/src/eval/tasks/ifeval.ts +167 -31
  27. package/src/expert-io.ts +391 -0
  28. package/src/expert-residency.ts +1134 -0
  29. package/src/expert-usage.ts +415 -0
  30. package/src/generate.ts +114 -17
  31. package/src/hf-push.ts +1 -1
  32. package/src/index.ts +14 -2
  33. package/src/jobs/runner.ts +37 -18
  34. package/src/kv-store.ts +147 -8
  35. package/src/lora.ts +77 -14
  36. package/src/media-fetch.ts +198 -56
  37. package/src/mlx/array.ts +43 -0
  38. package/src/mlx/autograd.ts +51 -14
  39. package/src/mlx/checkpoint.ts +46 -13
  40. package/src/mlx/custom-vjp.ts +83 -35
  41. package/src/model/factory.ts +96 -5
  42. package/src/model/gemma4-base.ts +32 -0
  43. package/src/model/glm52-atlas.ts +511 -0
  44. package/src/model/glm52-cache.ts +706 -0
  45. package/src/model/glm52-config.ts +262 -0
  46. package/src/model/glm52-container.ts +305 -0
  47. package/src/model/glm52-coupling.ts +388 -0
  48. package/src/model/glm52-dsa.ts +602 -0
  49. package/src/model/glm52-expert-layout.ts +253 -0
  50. package/src/model/glm52-layout.ts +152 -0
  51. package/src/model/glm52-memory.ts +473 -0
  52. package/src/model/glm52-mla.ts +786 -0
  53. package/src/model/glm52-moe.ts +223 -0
  54. package/src/model/glm52-pilot.ts +537 -0
  55. package/src/model/glm52-reference.ts +445 -0
  56. package/src/model/glm52-residency.ts +557 -0
  57. package/src/model/glm52-resident-weights.ts +135 -0
  58. package/src/model/glm52-streamed-experts.ts +453 -0
  59. package/src/model/glm52-streamed-metal.ts +610 -0
  60. package/src/model/glm52-weights.ts +503 -0
  61. package/src/model/glm52.ts +1153 -0
  62. package/src/model/support.ts +8 -1
  63. package/src/native/expert_io.c +523 -0
  64. package/src/native-pack.ts +6 -4
  65. package/src/responses.ts +62 -49
  66. package/src/serve/batch-scheduler.ts +95 -16
  67. package/src/serve/generation-gateway.ts +41 -16
  68. package/src/server.ts +362 -81
  69. package/src/spec/glm52-mtp-source.ts +392 -0
  70. package/src/spec/serve-loop.ts +99 -18
  71. package/src/spec/source.ts +31 -6
  72. package/src/ssd-cache.ts +6 -2
  73. package/src/tool-call.ts +30 -2
  74. package/src/train/job.ts +0 -10
  75. package/src/train/lora-params.ts +17 -6
  76. package/src/train/trainer.ts +59 -26
  77. package/src/web/app.html +13 -2
  78. package/src/web/app.js +30 -3
  79. package/src/web/src/status.ts +51 -3
  80. package/src/web-tools.ts +54 -2
  81. package/src/weights.ts +82 -19
@@ -0,0 +1,453 @@
1
+ import { ExpertIOSlabStore } from "../expert-io";
2
+ import {
3
+ ExpertResidencyManager,
4
+ summarizeExpertLatencies,
5
+ type ExpertLatencySummary,
6
+ type ExpertResidencyLeaseEntry,
7
+ } from "../expert-residency";
8
+ import { MlxArray, gpuStream } from "../mlx/array";
9
+ import { Dtype, synchronize } from "../mlx/ffi";
10
+ import * as ops from "../mlx/ops";
11
+ import type {
12
+ Glm52ExpertProjectionLayout,
13
+ Glm52ExpertSlotLayout,
14
+ } from "./glm52-expert-layout";
15
+ import type {
16
+ Glm52MoeBatchPlan,
17
+ Glm52MoeExpertJob,
18
+ } from "./glm52-moe";
19
+ import {
20
+ Glm52CanonicalQ4MetalExecutor,
21
+ Glm52CanonicalQ8MetalExecutor,
22
+ glm52CanonicalQ4MetalLayout,
23
+ glm52CanonicalQ4SlotView,
24
+ glm52CanonicalQ8MetalLayout,
25
+ glm52CanonicalQ8SlotView,
26
+ } from "./glm52-streamed-metal";
27
+
28
+ export interface Glm52ExpertExecutionArgs {
29
+ readonly layer: number;
30
+ readonly input: MlxArray;
31
+ readonly plan: Glm52MoeBatchPlan;
32
+ readonly shared: MlxArray | null;
33
+ }
34
+
35
+ export interface Glm52ExpertExecutionBackend {
36
+ execute(args: Glm52ExpertExecutionArgs): Promise<MlxArray>;
37
+ }
38
+
39
+ export interface Glm52StreamedExpertExecutorOptions {
40
+ readonly manager: ExpertResidencyManager;
41
+ readonly store: ExpertIOSlabStore;
42
+ readonly layout: (layer: number, expertId: number) => Glm52ExpertSlotLayout;
43
+ readonly hiddenSize: number;
44
+ /** Explicit candidate selection; stock remains default until the G1 matrix. */
45
+ readonly decodeKernel?: "stock" | "metal";
46
+ }
47
+
48
+ interface MaterializedJob {
49
+ readonly job: Glm52MoeExpertJob;
50
+ readonly output: MlxArray;
51
+ }
52
+
53
+ function projectionDimensions(
54
+ projection: Glm52ExpertProjectionLayout,
55
+ ): readonly [outputRows: number, inputColumns: number] {
56
+ return [
57
+ projection.tensor.outputRows,
58
+ projection.tensor.inputColumns,
59
+ ];
60
+ }
61
+
62
+ /**
63
+ * Correctness-first stock MLX consumer for a canonical Colibri expert slot.
64
+ * Packed Q4 bytes are already in MLX's little-endian uint32 lane order.
65
+ */
66
+ function slotLinear(
67
+ store: ExpertIOSlabStore,
68
+ lease: ExpertResidencyLeaseEntry,
69
+ layout: Glm52ExpertSlotLayout,
70
+ projection: Glm52ExpertProjectionLayout,
71
+ input: MlxArray,
72
+ ): MlxArray {
73
+ const [outputRows, inputColumns] = projectionDimensions(projection);
74
+ const base = store.pointer(lease.slot, lease.generation);
75
+ if ((base + projection.weightOffset) % layout.alignment !== 0 ||
76
+ (base + projection.scaleOffset) % layout.alignment !== 0) {
77
+ throw new Error(
78
+ `${projection.projection}: streamed MLX pointer is not ` +
79
+ `${layout.alignment}-byte aligned`,
80
+ );
81
+ }
82
+ if (layout.bits === 4) {
83
+ const rowBytes = Math.ceil(inputColumns / 2);
84
+ if (rowBytes % 4 !== 0)
85
+ throw new Error("streamed Q4 projection row must occupy whole uint32 lanes");
86
+ const packed = MlxArray.fromPointer(
87
+ base + projection.weightOffset,
88
+ [outputRows, rowBytes / 4],
89
+ Dtype.uint32,
90
+ );
91
+ const scaleGroups =
92
+ projection.tensor.scales.byteLength / 4 / outputRows;
93
+ if (!Number.isSafeInteger(scaleGroups) || scaleGroups < 1)
94
+ throw new Error("streamed Q4 projection has invalid scale geometry");
95
+ const rawScales = MlxArray.fromPointer(
96
+ base + projection.scaleOffset,
97
+ [outputRows, scaleGroups],
98
+ Dtype.float32,
99
+ );
100
+ let scales = rawScales;
101
+ let groupSize = layout.groupSize ?? inputColumns;
102
+ if (layout.groupSize === null && inputColumns > 32) {
103
+ if (inputColumns % 32 !== 0)
104
+ throw new Error("per-row streamed Q4 requires a 32-divisible input width");
105
+ groupSize = 32;
106
+ const zeros = ops.zeros(
107
+ [outputRows, inputColumns / groupSize],
108
+ Dtype.float32,
109
+ );
110
+ scales = ops.add(rawScales, zeros);
111
+ zeros.dispose();
112
+ }
113
+ const biases = ops.mulScalar(scales, -8);
114
+ const output = ops.quantizedMatmul(
115
+ input,
116
+ packed,
117
+ scales,
118
+ biases,
119
+ { bits: 4, groupSize, mode: "affine" },
120
+ true,
121
+ );
122
+ packed.dispose();
123
+ rawScales.dispose();
124
+ if (scales !== rawScales) scales.dispose();
125
+ biases.dispose();
126
+ return output;
127
+ }
128
+
129
+ // MTP uses signed two's-complement Q8. G4 will replace this materializing
130
+ // reference path with its fixed draft/verify kernel family.
131
+ if (layout.groupSize !== null)
132
+ throw new Error("streamed Q8 grouped scales are unsupported");
133
+ const packed = MlxArray.fromPointer(
134
+ base + projection.weightOffset,
135
+ [outputRows, inputColumns],
136
+ Dtype.int8,
137
+ );
138
+ const rawScales = MlxArray.fromPointer(
139
+ base + projection.scaleOffset,
140
+ [outputRows, 1],
141
+ Dtype.float32,
142
+ );
143
+ const values = packed.astype(Dtype.float32);
144
+ const zeros = ops.zeros([outputRows, inputColumns], Dtype.float32);
145
+ const scales = ops.add(rawScales, zeros);
146
+ const weights = ops.mul(values, scales);
147
+ const transposed = ops.transposeAxes(weights, [1, 0]);
148
+ const output = ops.matmul(input, transposed);
149
+ packed.dispose();
150
+ rawScales.dispose();
151
+ values.dispose();
152
+ zeros.dispose();
153
+ scales.dispose();
154
+ weights.dispose();
155
+ transposed.dispose();
156
+ return output;
157
+ }
158
+
159
+ function slotSwiGlu(
160
+ store: ExpertIOSlabStore,
161
+ lease: ExpertResidencyLeaseEntry,
162
+ layout: Glm52ExpertSlotLayout,
163
+ input: MlxArray,
164
+ ): MlxArray {
165
+ let gate: MlxArray | null = null;
166
+ let up: MlxArray | null = null;
167
+ let activated: MlxArray | null = null;
168
+ let product: MlxArray | null = null;
169
+ try {
170
+ gate = slotLinear(
171
+ store, lease, layout, layout.projections.gate, input,
172
+ );
173
+ up = slotLinear(
174
+ store, lease, layout, layout.projections.up, input,
175
+ );
176
+ activated = ops.silu(gate);
177
+ product = ops.mul(activated, up);
178
+ return slotLinear(
179
+ store, lease, layout, layout.projections.down, product,
180
+ );
181
+ } finally {
182
+ gate?.dispose();
183
+ up?.dispose();
184
+ activated?.dispose();
185
+ product?.dispose();
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Stable batch-union executor. Resident graphs and the shared expert are
191
+ * submitted before miss reads; every wave is materialized and fenced before
192
+ * its scratch slots are promoted/reused.
193
+ */
194
+ export class Glm52StockStreamedExpertExecutor
195
+ implements Glm52ExpertExecutionBackend {
196
+ readonly manager: ExpertResidencyManager;
197
+ readonly store: ExpertIOSlabStore;
198
+ readonly hiddenSize: number;
199
+ #layout: Glm52StreamedExpertExecutorOptions["layout"];
200
+ #metal: Glm52CanonicalQ4MetalExecutor | null;
201
+ #mtpMetal: Glm52CanonicalQ8MetalExecutor | null;
202
+ #fixedKernelFamily = false;
203
+ #forwardMs: number[] = [];
204
+
205
+ constructor(options: Glm52StreamedExpertExecutorOptions) {
206
+ this.manager = options.manager;
207
+ this.store = options.store;
208
+ this.#layout = options.layout;
209
+ this.hiddenSize = options.hiddenSize;
210
+ this.#metal = options.decodeKernel === "metal"
211
+ ? new Glm52CanonicalQ4MetalExecutor()
212
+ : null;
213
+ this.#mtpMetal = options.decodeKernel === "metal"
214
+ ? new Glm52CanonicalQ8MetalExecutor()
215
+ : null;
216
+ }
217
+
218
+ setFixedKernelFamily(enabled: boolean): void {
219
+ this.#fixedKernelFamily = enabled;
220
+ }
221
+
222
+ drainForwardTelemetry(): ExpertLatencySummary {
223
+ const summary = summarizeExpertLatencies(this.#forwardMs);
224
+ this.#forwardMs = [];
225
+ return summary;
226
+ }
227
+
228
+ async execute(args: Glm52ExpertExecutionArgs): Promise<MlxArray> {
229
+ const [batch, tokens, hidden] = args.input.shape;
230
+ if (args.input.shape.length !== 3 || hidden !== this.hiddenSize)
231
+ throw new Error(
232
+ `streamed GLM experts require [B,T,${this.hiddenSize}] input`,
233
+ );
234
+ const rowCount = batch! * tokens!;
235
+ if (args.plan.routes.length !== rowCount)
236
+ throw new Error(
237
+ `streamed GLM plan has ${args.plan.routes.length} rows; input has ${rowCount}`,
238
+ );
239
+ this.manager.recordRoutes(args.layer, args.plan.routes);
240
+
241
+ const forwardStarted = performance.now();
242
+ const flattened = ops.reshape(args.input, [rowCount, this.hiddenSize]);
243
+ const sharedFlat = args.shared
244
+ ? ops.reshape(args.shared, [rowCount, this.hiddenSize])
245
+ : null;
246
+ const materialized = new Map<number, MaterializedJob>();
247
+ let sharedSubmitted = false;
248
+ try {
249
+ for (const wave of args.plan.waves) {
250
+ const jobs = new Map(wave.jobs.map((job) => [job.expertId, job]));
251
+ const waveOutputs: MlxArray[] = [];
252
+ let lease = await this.manager.acquireBlock(
253
+ args.layer,
254
+ wave.jobs.map((job) => job.expertId),
255
+ (resident) => {
256
+ for (const entry of resident) {
257
+ const job = jobs.get(entry.expertId)!;
258
+ const output = this.#executeJob(
259
+ flattened,
260
+ job,
261
+ entry,
262
+ this.#layout(args.layer, entry.expertId),
263
+ );
264
+ materialized.set(entry.expertId, { job, output });
265
+ waveOutputs.push(output);
266
+ }
267
+ const submit = sharedFlat && !sharedSubmitted
268
+ ? [...waveOutputs, sharedFlat]
269
+ : waveOutputs;
270
+ if (submit.length) ops.asyncEvalAll(submit);
271
+ if (sharedFlat && !sharedSubmitted) sharedSubmitted = true;
272
+ },
273
+ );
274
+ let releaseAttempted = false;
275
+ try {
276
+ for (const entry of lease.entries) {
277
+ if (entry.hit) continue;
278
+ const job = jobs.get(entry.expertId)!;
279
+ const output = this.#executeJob(
280
+ flattened,
281
+ job,
282
+ entry,
283
+ this.#layout(args.layer, entry.expertId),
284
+ );
285
+ materialized.set(entry.expertId, { job, output });
286
+ waveOutputs.push(output);
287
+ }
288
+ const fence = sharedFlat && !sharedSubmitted
289
+ ? [...waveOutputs, sharedFlat]
290
+ : waveOutputs;
291
+ if (sharedFlat && !sharedSubmitted) sharedSubmitted = true;
292
+ if (fence.length) ops.evalAll(fence);
293
+ synchronize(gpuStream);
294
+ releaseAttempted = true;
295
+ lease.releaseFenced();
296
+ } catch (error) {
297
+ if (releaseAttempted) throw error;
298
+ const cleanupErrors: unknown[] = [];
299
+ try {
300
+ synchronize(gpuStream);
301
+ } catch (cleanupError) {
302
+ cleanupErrors.push(cleanupError);
303
+ }
304
+ if (cleanupErrors.length === 0) {
305
+ releaseAttempted = true;
306
+ try {
307
+ lease.releaseFenced();
308
+ } catch (cleanupError) {
309
+ cleanupErrors.push(cleanupError);
310
+ }
311
+ }
312
+ if (cleanupErrors.length) {
313
+ throw new AggregateError(
314
+ [error, ...cleanupErrors],
315
+ "streamed expert wave failed and fenced cleanup was incomplete",
316
+ );
317
+ }
318
+ throw error;
319
+ }
320
+ }
321
+ this.manager.correctForPressure();
322
+ return this.#compose(
323
+ args.plan,
324
+ materialized,
325
+ sharedFlat,
326
+ [batch!, tokens!, hidden!],
327
+ );
328
+ } finally {
329
+ flattened.dispose();
330
+ sharedFlat?.dispose();
331
+ for (const value of materialized.values()) value.output.dispose();
332
+ this.#forwardMs.push(performance.now() - forwardStarted);
333
+ }
334
+ }
335
+
336
+ #executeJob(
337
+ flattened: MlxArray,
338
+ job: Glm52MoeExpertJob,
339
+ lease: ExpertResidencyLeaseEntry,
340
+ layout: Glm52ExpertSlotLayout,
341
+ ): MlxArray {
342
+ const indices = MlxArray.fromInt32(job.rows, [job.rows.length]);
343
+ let rows: MlxArray | null = null;
344
+ try {
345
+ rows = ops.takeAxis(flattened, indices, 0);
346
+ if (this.#mtpMetal && layout.bits === 8) {
347
+ const descriptor = glm52CanonicalQ8MetalLayout(layout);
348
+ const slot = glm52CanonicalQ8SlotView(
349
+ this.store.pointer(lease.slot, lease.generation),
350
+ descriptor,
351
+ );
352
+ try {
353
+ return this.#mtpMetal.execute(rows, slot, descriptor);
354
+ } finally {
355
+ slot.dispose();
356
+ }
357
+ }
358
+ if (
359
+ this.#metal &&
360
+ layout.bits === 4 &&
361
+ (rows.shape[0] === 1 || this.#fixedKernelFamily)
362
+ ) {
363
+ const descriptor = glm52CanonicalQ4MetalLayout(layout);
364
+ const slot = glm52CanonicalQ4SlotView(
365
+ this.store.pointer(lease.slot, lease.generation),
366
+ descriptor,
367
+ );
368
+ try {
369
+ return this.#metal.execute(rows, slot, descriptor);
370
+ } finally {
371
+ slot.dispose();
372
+ }
373
+ }
374
+ return slotSwiGlu(this.store, lease, layout, rows);
375
+ } finally {
376
+ indices.dispose();
377
+ rows?.dispose();
378
+ }
379
+ }
380
+
381
+ dispose(): void {
382
+ this.#metal?.dispose();
383
+ this.#metal = null;
384
+ this.#mtpMetal?.dispose();
385
+ this.#mtpMetal = null;
386
+ }
387
+
388
+ #compose(
389
+ plan: Glm52MoeBatchPlan,
390
+ materialized: ReadonlyMap<number, MaterializedJob>,
391
+ shared: MlxArray | null,
392
+ shape: readonly [number, number, number],
393
+ ): MlxArray {
394
+ const [batch, tokens, hidden] = shape;
395
+ const consumers = new Map<string, { value: MlxArray; index: number }>();
396
+ for (const { job, output } of materialized.values()) {
397
+ for (let index = 0; index < job.rows.length; index++)
398
+ consumers.set(`${job.rows[index]}:${job.ranks[index]}`, {
399
+ value: output,
400
+ index,
401
+ });
402
+ }
403
+
404
+ const rows: MlxArray[] = [];
405
+ try {
406
+ for (let row = 0; row < plan.routes.length; row++) {
407
+ const route = plan.routes[row]!;
408
+ let sum: MlxArray | null = null;
409
+ for (let rank = 0; rank < route.indices.length; rank++) {
410
+ const consumer = consumers.get(`${row}:${rank}`);
411
+ if (!consumer)
412
+ throw new Error(`missing streamed output for row ${row} rank ${rank}`);
413
+ const view = consumer.value.slice(
414
+ [consumer.index, 0],
415
+ [consumer.index + 1, hidden],
416
+ );
417
+ const scaled = ops.mulScalar(view, route.executionWeights[rank]!);
418
+ view.dispose();
419
+ if (sum === null) {
420
+ sum = scaled;
421
+ } else {
422
+ const next = ops.add(sum, scaled);
423
+ sum.dispose();
424
+ scaled.dispose();
425
+ sum = next;
426
+ }
427
+ }
428
+ if (shared) {
429
+ const sharedRow = shared.slice([row, 0], [row + 1, hidden]);
430
+ if (sum === null) {
431
+ sum = ops.mulScalar(sharedRow, 1);
432
+ } else {
433
+ const next = ops.add(sum, sharedRow);
434
+ sum.dispose();
435
+ sum = next;
436
+ }
437
+ sharedRow.dispose();
438
+ }
439
+ if (!sum) throw new Error(`streamed GLM row ${row} has no expert output`);
440
+ rows.push(sum);
441
+ }
442
+ const flat = rows.length === 1
443
+ ? rows[0]!
444
+ : ops.concatAxis(rows, 0);
445
+ const output = ops.reshape(flat, [batch, tokens, hidden]);
446
+ flat.dispose();
447
+ return output;
448
+ } finally {
449
+ if (rows.length > 1)
450
+ for (const row of rows) row.dispose();
451
+ }
452
+ }
453
+ }