mlx-bun 0.0.7 → 0.0.9

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 (138) hide show
  1. package/README.md +112 -20
  2. package/docs/design/batching-v2-plan.md +515 -0
  3. package/docs/design/bucketing-stage.md +196 -0
  4. package/docs/design/compat-cli-surface-design.md +11 -11
  5. package/docs/design/diffusion-gemma-port.md +602 -0
  6. package/docs/design/docs-reorg-plan.md +1 -1
  7. package/docs/design/dreaming-nightly-pipeline.md +291 -0
  8. package/docs/design/dspark-speculative-decoding.md +106 -0
  9. package/docs/design/generic-model-support.md +381 -0
  10. package/docs/design/memory-inference-path.md +422 -0
  11. package/docs/design/memory-synthesis.md +287 -0
  12. package/docs/design/minicpm5-decode-megakernel.md +484 -0
  13. package/docs/design/mlx-lm-tool-parity-plan.md +535 -0
  14. package/docs/design/orpo-dynamic-lambda.md +41 -0
  15. package/docs/design/orpo-training.md +35 -5
  16. package/docs/design/parallel-slots.md +12 -1
  17. package/docs/design/parity-tier-dag.md +13 -5
  18. package/docs/design/spec-decode-larger-targets.md +229 -0
  19. package/docs/design/tauri-desktop-app.md +2 -2
  20. package/docs/design/the-dreaming-handoff.md +141 -0
  21. package/docs/design/the-dreaming-master-plan.md +446 -0
  22. package/docs/design/write-pipeline-entity.md +468 -0
  23. package/docs/investigations/curve-bisector-routing.md +1 -1
  24. package/docs/investigations/decode-roofline-lookagain.md +262 -0
  25. package/docs/investigations/dspark-handoff.md +58 -0
  26. package/docs/investigations/kernel-perf-review-2026-07.md +92 -0
  27. package/docs/planning/PRODUCT_ROADMAP.md +2 -2
  28. package/docs/planning/curve-sampler-research-plan.md +331 -0
  29. package/docs/planning/memory-docs-and-dag-plan.md +185 -0
  30. package/docs/planning/pi-integration-review.md +178 -0
  31. package/docs/planning/release-notes-v0.0.9.md +122 -0
  32. package/docs/planning/web-ui-pass-plan.md +213 -0
  33. package/docs/planning/website-readme-pass-plan.md +224 -0
  34. package/docs/reference/cli.md +255 -0
  35. package/docs/reference/embedding.md +7 -2
  36. package/docs/reference/library-api.md +22 -3
  37. package/docs/reference/memory.md +5 -5
  38. package/docs/reference/models.md +137 -0
  39. package/docs/reference/orpo-quickstart.md +6 -0
  40. package/docs/reference/server-api.md +187 -9
  41. package/docs/reference/server-config.md +40 -9
  42. package/docs/reference/training.md +25 -11
  43. package/package.json +1 -1
  44. package/src/anthropic.ts +22 -0
  45. package/src/assets/curve-designer.html +1 -1
  46. package/src/chat-template.ts +25 -7
  47. package/src/cli.ts +1029 -86
  48. package/src/config.ts +54 -3
  49. package/src/diffusion/diffusion-generate.ts +436 -0
  50. package/src/download.ts +99 -8
  51. package/src/embed.ts +77 -0
  52. package/src/eval/perplexity.ts +164 -0
  53. package/src/eval/runner.ts +19 -7
  54. package/src/eval/tasks/bfcl.ts +1 -1
  55. package/src/generate.ts +196 -19
  56. package/src/harness-pi.ts +43 -17
  57. package/src/index.ts +2 -0
  58. package/src/lora.ts +2 -2
  59. package/src/memory/article.ts +558 -0
  60. package/src/memory/chunk-validation.ts +150 -0
  61. package/src/memory/chunk.ts +305 -12
  62. package/src/memory/cluster.ts +344 -17
  63. package/src/memory/coverage.ts +134 -0
  64. package/src/memory/crosslink.ts +0 -0
  65. package/src/memory/db.ts +184 -45
  66. package/src/memory/entity.ts +203 -0
  67. package/src/memory/gate.ts +187 -0
  68. package/src/memory/history.ts +152 -0
  69. package/src/memory/ingest.ts +324 -11
  70. package/src/memory/model.ts +321 -0
  71. package/src/memory/normalize.ts +671 -0
  72. package/src/memory/parse.ts +22 -0
  73. package/src/memory/pipeline.ts +242 -30
  74. package/src/memory/prompts.ts +42 -0
  75. package/src/memory/query.ts +334 -0
  76. package/src/memory/reconcile.ts +734 -0
  77. package/src/memory/redirect.ts +108 -0
  78. package/src/memory/reindex.ts +325 -0
  79. package/src/memory/resolve.ts +307 -0
  80. package/src/memory/route.ts +272 -0
  81. package/src/memory/schedule.ts +6 -4
  82. package/src/memory/stages.ts +774 -0
  83. package/src/memory/synthesize.ts +1629 -20
  84. package/src/memory/tools.ts +446 -96
  85. package/src/memory/vault.ts +462 -35
  86. package/src/memory/wikify.ts +1057 -16
  87. package/src/mlx/ffi.ts +11 -0
  88. package/src/mlx/ops.ts +113 -0
  89. package/src/model/compiled-decode.ts +43 -8
  90. package/src/model/diffusion-gemma.ts +1026 -0
  91. package/src/model/factory.ts +35 -8
  92. package/src/model/flash-attention.ts +4 -2
  93. package/src/model/fused-decode-kernel.ts +14 -7
  94. package/src/model/fused-mlp-kernel.ts +169 -0
  95. package/src/model/fused-swiglu-kernel.ts +240 -0
  96. package/src/model/gemma4-base.ts +62 -6
  97. package/src/model/gemma4.ts +33 -2
  98. package/src/model/minicpm5.ts +106 -8
  99. package/src/model/qwen3.ts +276 -0
  100. package/src/model/steel-linear-kernel.ts +96 -0
  101. package/src/model/support.ts +45 -7
  102. package/src/model/universal/archs.ts +332 -0
  103. package/src/model/universal/dense.ts +576 -0
  104. package/src/model/universal/modules.ts +163 -0
  105. package/src/model/universal/rope.ts +366 -0
  106. package/src/pi-provider.ts +1 -1
  107. package/src/pi-terminal.ts +29 -8
  108. package/src/pi-web.ts +19 -2
  109. package/src/quantize/config-writer.ts +1 -1
  110. package/src/registry.ts +215 -7
  111. package/src/responses.ts +23 -0
  112. package/src/sampler.ts +220 -9
  113. package/src/serve/batch-scheduler.ts +272 -80
  114. package/src/serve/generation-gateway.ts +90 -18
  115. package/src/server.ts +643 -50
  116. package/src/spec/drafter.ts +81 -49
  117. package/src/spec/dspark/data-dflash.ts +156 -0
  118. package/src/spec/dspark/data.ts +201 -0
  119. package/src/spec/dspark/generate-dflash.ts +193 -0
  120. package/src/spec/dspark/generate.ts +290 -0
  121. package/src/spec/dspark/loss.ts +213 -0
  122. package/src/spec/dspark/module-dflash.ts +361 -0
  123. package/src/spec/dspark/module.ts +540 -0
  124. package/src/spec/dspark/sample.ts +112 -0
  125. package/src/spec/generate.ts +40 -19
  126. package/src/status-page.html +1 -1
  127. package/src/tokenizer.ts +7 -0
  128. package/src/train/diffusion-lora.ts +216 -0
  129. package/src/train/flash-cce.ts +70 -15
  130. package/src/train/fuse.ts +243 -0
  131. package/src/train/index.ts +3 -0
  132. package/src/train/job.ts +8 -0
  133. package/src/train/loss.ts +289 -30
  134. package/src/train/prefix-shared.ts +33 -5
  135. package/src/train/segmented.ts +67 -11
  136. package/src/train/trainer.ts +92 -28
  137. package/src/vision/diffusion-vision.ts +620 -0
  138. package/src/web/skills/memory/SKILL.md +40 -17
package/src/mlx/ffi.ts CHANGED
@@ -119,6 +119,9 @@ export const C = dlopen(LIBMLXC_PATH, {
119
119
  mlx_divide: { args: [P, u64, u64, u64], returns: i32 },
120
120
  mlx_tanh: { args: [P, u64, u64], returns: i32 },
121
121
  mlx_sigmoid: { args: [P, u64, u64], returns: i32 },
122
+ mlx_erf: { args: [P, u64, u64], returns: i32 },
123
+ // (res, c, a, b, alpha, beta, stream) — alpha·(a@b) + beta·c (nn.Linear bias path)
124
+ mlx_addmm: { args: [P, u64, u64, u64, f32, f32, u64], returns: i32 },
122
125
  mlx_power: { args: [P, u64, u64, u64], returns: i32 },
123
126
  mlx_take_axis: { args: [P, u64, u64, i32, u64], returns: i32 },
124
127
  mlx_reshape: { args: [P, u64, P, u64, u64], returns: i32 },
@@ -142,11 +145,17 @@ export const C = dlopen(LIBMLXC_PATH, {
142
145
  mlx_take_along_axis: { args: [P, u64, u64, i32, u64], returns: i32 },
143
146
  mlx_put_along_axis: { args: [P, u64, u64, u64, i32, u64], returns: i32 },
144
147
  mlx_cumsum: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64], returns: i32 },
148
+ mlx_cummax: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64], returns: i32 },
145
149
  mlx_where: { args: [P, u64, u64, u64, u64], returns: i32 },
146
150
  mlx_logsumexp_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
147
151
  mlx_greater_equal: { args: [P, u64, u64, u64], returns: i32 },
148
152
  mlx_less: { args: [P, u64, u64, u64], returns: i32 },
153
+ mlx_less_equal: { args: [P, u64, u64, u64], returns: i32 },
149
154
  mlx_logical_and: { args: [P, u64, u64, u64], returns: i32 },
155
+ mlx_logical_not: { args: [P, u64, u64], returns: i32 },
156
+ mlx_equal: { args: [P, u64, u64, u64], returns: i32 },
157
+ mlx_all: { args: [P, u64, FFIType.bool, u64], returns: i32 },
158
+ mlx_any_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
150
159
  mlx_random_key: { args: [P, u64], returns: i32 },
151
160
  mlx_random_categorical: { args: [P, u64, i32, u64, u64], returns: i32 },
152
161
  mlx_astype: { args: [P, u64, i32, u64], returns: i32 },
@@ -181,6 +190,8 @@ export const C = dlopen(LIBMLXC_PATH, {
181
190
  // (res, low, high, shape*, n, dtype, key may-be-null, stream) — low/high are
182
191
  // scalar mlx_arrays. Used for mlx-lm-faithful LoRA A init (uniform).
183
192
  mlx_random_uniform: { args: [P, u64, u64, P, u64, i32, u64, u64], returns: i32 },
193
+ mlx_random_randint: { args: [P, u64, u64, P, u64, i32, u64, u64], returns: i32 },
194
+ mlx_random_seed: { args: [u64], returns: i32 },
184
195
  mlx_random_split_num: { args: [P, u64, i32, u64], returns: i32 },
185
196
  // --- training: loss/optimizer elementwise + reductions
186
197
  mlx_stop_gradient: { args: [P, u64, u64], returns: i32 },
package/src/mlx/ops.ts CHANGED
@@ -111,6 +111,23 @@ export function rope(
111
111
  );
112
112
  }
113
113
 
114
+ /** fast::rope with every knob exposed (traditional + scale), for the
115
+ * universal rope factory (rope_utils.initialize_rope port). The plain
116
+ * `rope` above stays byte-identical for existing call sites. */
117
+ export function ropeScaled(
118
+ x: MlxArray, dims: number, traditional: boolean, base: number | null,
119
+ scale: number, offset: number, freqs: MlxArray | null, s: S = gpuStream,
120
+ ): MlxArray {
121
+ return new MlxArray(
122
+ outArray("fast_rope", (o) =>
123
+ C.mlx_fast_rope(
124
+ o, x.handle, dims, traditional, optFloat(base), scale, offset,
125
+ freqs?.handle ?? 0n, s,
126
+ ),
127
+ ),
128
+ );
129
+ }
130
+
114
131
  /** fast::rope with the position offset as an ARRAY (int32 scalar) instead
115
132
  * of a baked int — required inside compiled decode graphs, where the
116
133
  * offset changes every step but the graph must not. Same kernel as
@@ -267,6 +284,35 @@ export function geluApprox(x: MlxArray, s: S = gpuStream): MlxArray {
267
284
  return out;
268
285
  }
269
286
 
287
+ export function erf(a: MlxArray, s: S = gpuStream): MlxArray {
288
+ return new MlxArray(outArray("erf", (o) => C.mlx_erf(o, a.handle, s)));
289
+ }
290
+
291
+ /** nn.gelu (PRECISE, erf-based): x · (1 + erf(x/√2)) / 2 — composed exactly
292
+ * as mlx's python source (weak scalars promote to x's dtype). Used by the
293
+ * gemma-1 gated MLP and starcoder2's plain MLP; distinct from geluApprox
294
+ * (gelu_pytorch_tanh). */
295
+ export function geluPrecise(x: MlxArray, s: S = gpuStream): MlxArray {
296
+ const sqrt2 = scalarLike(Math.sqrt(2), x);
297
+ const xs = div(x, sqrt2, s); // x / math.sqrt(2) — division, not mul-by-reciprocal
298
+ const e = erf(xs, s);
299
+ const one = scalarLike(1, x);
300
+ const e1 = add(one, e, s);
301
+ const xe = mul(x, e1, s);
302
+ const two = scalarLike(2, x);
303
+ const out = div(xe, two, s); // (...) / 2
304
+ for (const a of [sqrt2, xs, e, one, e1, xe, two]) a.dispose();
305
+ return out;
306
+ }
307
+
308
+ /** mx.addmm(c, a, b): a@b + c fused (α=β=1) — nn.Linear's bias path
309
+ * (`mx.addmm(bias, x, weight.T)`). */
310
+ export function addmm(c: MlxArray, a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
311
+ return new MlxArray(
312
+ outArray("addmm", (o) => C.mlx_addmm(o, c.handle, a.handle, b.handle, 1.0, 1.0, s)),
313
+ );
314
+ }
315
+
270
316
  export function exp(a: MlxArray, s: S = gpuStream): MlxArray {
271
317
  return new MlxArray(outArray("exp", (o) => C.mlx_exp(o, a.handle, s)));
272
318
  }
@@ -315,6 +361,13 @@ export function cumsum(a: MlxArray, axis: number, s: S = gpuStream): MlxArray {
315
361
  );
316
362
  }
317
363
 
364
+ /** Cumulative maximum along `axis` (inclusive, forward) — mlx.core.cummax. */
365
+ export function cummax(a: MlxArray, axis: number, s: S = gpuStream): MlxArray {
366
+ return new MlxArray(
367
+ outArray("cummax", (o) => C.mlx_cummax(o, a.handle, axis, false, true, s)),
368
+ );
369
+ }
370
+
318
371
  export function where(cond: MlxArray, x: MlxArray, y: MlxArray, s: S = gpuStream): MlxArray {
319
372
  return new MlxArray(
320
373
  outArray("where", (o) => C.mlx_where(o, cond.handle, x.handle, y.handle, s)),
@@ -370,6 +423,38 @@ export function less(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
370
423
  return new MlxArray(outArray("less", (o) => C.mlx_less(o, a.handle, b.handle, s)));
371
424
  }
372
425
 
426
+ export function lessEqual(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
427
+ return new MlxArray(outArray("less_equal", (o) => C.mlx_less_equal(o, a.handle, b.handle, s)));
428
+ }
429
+
430
+ export function logicalNot(a: MlxArray, s: S = gpuStream): MlxArray {
431
+ return new MlxArray(outArray("logical_not", (o) => C.mlx_logical_not(o, a.handle, s)));
432
+ }
433
+
434
+ export function equal(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
435
+ return new MlxArray(outArray("equal", (o) => C.mlx_equal(o, a.handle, b.handle, s)));
436
+ }
437
+
438
+ /** Reduce-all to a scalar (mlx.core.all). */
439
+ export function allReduce(a: MlxArray, s: S = gpuStream): MlxArray {
440
+ return new MlxArray(outArray("all", (o) => C.mlx_all(o, a.handle, false, s)));
441
+ }
442
+
443
+ /** any along one axis (mlx.core.any). */
444
+ export function anyAxis(a: MlxArray, axis: number, keepdims: boolean, s: S = gpuStream): MlxArray {
445
+ return new MlxArray(outArray("any_axis", (o) => C.mlx_any_axis(o, a.handle, axis, keepdims, s)));
446
+ }
447
+
448
+ /** Read a 0-d/1-elem boolean array to host (forces eval). */
449
+ export function itemBool(a: MlxArray): boolean {
450
+ const u = a.astype(Dtype.uint32);
451
+ try {
452
+ return itemUint32(u) !== 0;
453
+ } finally {
454
+ u.dispose();
455
+ }
456
+ }
457
+
373
458
  export function logicalAnd(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
374
459
  return new MlxArray(
375
460
  outArray("logical_and", (o) => C.mlx_logical_and(o, a.handle, b.handle, s)),
@@ -443,6 +528,34 @@ export function randomKey(seed: bigint): MlxArray {
443
528
  return new MlxArray(outArray("random_key", (o) => C.mlx_random_key(o, seed)));
444
529
  }
445
530
 
531
+ /** Set the GLOBAL mlx PRNG seed (mlx.core.random.seed). Calls to randint/etc
532
+ * with a null key thread the global key, so seeding then calling in the same
533
+ * order as the reference reproduces its draws bit-for-bit. */
534
+ export function randomSeed(seed: bigint): void {
535
+ if (C.mlx_random_seed(seed) !== 0) throw new Error(`mlx_random_seed failed: ${takeMlxError() ?? ""}`);
536
+ }
537
+
538
+ /** Uniform integers in [low, high) — mlx.core.random.randint. With key=null it
539
+ * uses (and advances) the global key, matching mx.random.randint exactly. */
540
+ export function randint(
541
+ low: number, high: number, shape: number[], dtype: Dtype,
542
+ key: MlxArray | null = null, s: S = gpuStream,
543
+ ): MlxArray {
544
+ const buf = new Int32Array(shape);
545
+ const lo = MlxArray.fromInt32(new Int32Array([low]), []);
546
+ const hi = MlxArray.fromInt32(new Int32Array([high]), []);
547
+ try {
548
+ return new MlxArray(
549
+ outArray("random_randint", (o) =>
550
+ C.mlx_random_randint(o, lo.handle, hi.handle, ptr(buf), BigInt(shape.length), dtype, key?.handle ?? 0n, s),
551
+ ),
552
+ );
553
+ } finally {
554
+ lo.dispose();
555
+ hi.dispose();
556
+ }
557
+ }
558
+
446
559
  export function randomCategorical(logits: MlxArray, key: MlxArray | null, s: S = gpuStream): MlxArray {
447
560
  return new MlxArray(
448
561
  outArray("categorical", (o) =>
@@ -590,6 +590,29 @@ export class CompiledDecode {
590
590
  const evalWith: MlxArray[] = [];
591
591
  let carried = cur; // seg 0 consumes the token array; later, hidden state
592
592
  let carriedOwned = false; // cur belongs to the caller
593
+ // Mid-step failure safety (a later segment's closure.apply can throw at
594
+ // first trace AFTER earlier layers already advanced): the step must be
595
+ // TRANSACTIONAL, or the caller's uncompiled re-forward of the same token
596
+ // double-writes the committed layers (+1 offset skew forever, silently
597
+ // corrupted attention). Two mechanisms, chosen by what each phase allows:
598
+ // - ring segments: adoptDecodeStep is a pure host-side handle swap with
599
+ // no reader between here and step end (each cache is read only by its
600
+ // own layer, inside the closure that produced the update), so the
601
+ // adopts are STAGED and committed only once every segment and js layer
602
+ // has succeeded. On failure the staged buffers are disposed and the
603
+ // ring caches are exactly at their pre-step state (prepareDecodeStep's
604
+ // growth/trim/rotate bookkeeping is benign for the uncompiled path —
605
+ // the same invariant #stepWhole and generate.ts's fallback rely on).
606
+ // - js (concat) layers: layer.forward commits via the cache's real
607
+ // updateAndFetch mid-loop, so on failure the commit is ROLLED BACK by
608
+ // reverting offset (and ring index) — trim(1) semantics. The written
609
+ // KV row beyond the reverted offset is dead data the re-forward
610
+ // overwrites at the same position (updateAndFetch writes at offset/
611
+ // ringIdx), so post-recovery state equals a single clean write. This
612
+ // holds for the post-wrap quantized-under-perf-kernel case too: the
613
+ // in-place S=1 write slot is re-derived from the reverted ringIdx.
614
+ const stagedAdopts: { c: AnyCache; bufs: MlxArray[] }[] = [];
615
+ const committedJs: { c: AnyCache; offsetBefore: number }[] = [];
593
616
  try {
594
617
  for (let s = 0; s < segs.length; s++) {
595
618
  const { from, to } = segs[s]!;
@@ -636,17 +659,14 @@ export class CompiledDecode {
636
659
  carried = outs[0]!;
637
660
  carriedOwned = true;
638
661
  // ring updates are ancestors of the carried hidden state (the
639
- // in-graph fetch reads the updated buffer): no eval roots
662
+ // in-graph fetch reads the updated buffer): no eval roots. The
663
+ // host-side adoption is deferred to the end of the step (see above).
640
664
  let oi = 1;
641
665
  for (let i = from; i < to; i++) {
642
666
  const c = anyCaches[i]!;
643
- if (c instanceof RotatingQuantizedKVCache) {
644
- c.adoptDecodeStep(outs.slice(oi, oi + 6));
645
- oi += 6;
646
- } else {
647
- (c as RotatingKVCache).adoptDecodeStep(outs[oi]!, outs[oi + 1]!);
648
- oi += 2;
649
- }
667
+ const width = c instanceof RotatingQuantizedKVCache ? 6 : 2;
668
+ stagedAdopts.push({ c, bufs: outs.slice(oi, oi + width) });
669
+ oi += width;
650
670
  }
651
671
  }
652
672
  if (s < jsLayers.length) {
@@ -655,6 +675,7 @@ export class CompiledDecode {
655
675
  const c = anyCaches[li]!;
656
676
  const window = layer.layerType === "sliding_attention" ? this.model.windowSize : null;
657
677
  const mask = c.makeMask(1, window); // N=1 → mode ""
678
+ committedJs.push({ c, offsetBefore: c.offset });
658
679
  const { h: next, shared } = layer.forward(carried, mask, c, null, null);
659
680
  if (carriedOwned) carried.dispose();
660
681
  carried = next;
@@ -663,6 +684,20 @@ export class CompiledDecode {
663
684
  mask.arr?.dispose();
664
685
  }
665
686
  }
687
+ // every segment and js layer succeeded — commit the staged ring adopts
688
+ for (const { c, bufs } of stagedAdopts) {
689
+ if (c instanceof RotatingQuantizedKVCache) c.adoptDecodeStep(bufs);
690
+ else (c as RotatingKVCache).adoptDecodeStep(bufs[0]!, bufs[1]!);
691
+ }
692
+ } catch (e) {
693
+ // unwind: free staged (never-adopted) ring buffers and the owned hidden
694
+ // state, and revert the js layers' committed writes, so the caller's
695
+ // uncompiled re-forward of this token starts from the pre-step state.
696
+ for (const { bufs } of stagedAdopts) for (const b of bufs) b.dispose();
697
+ for (const { c, offsetBefore } of committedJs)
698
+ if (c.offset > offsetBefore) c.trim(c.offset - offsetBefore);
699
+ if (carriedOwned) carried.dispose();
700
+ throw e;
666
701
  } finally {
667
702
  for (const t of stepTemps) t.dispose();
668
703
  }