mlx-bun 0.0.10 → 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 (216) hide show
  1. package/README.md +107 -32
  2. package/THIRD_PARTY_LICENSES.md +10 -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 +58 -35
  7. package/docs/design/batching-v2-plan.md +23 -9
  8. package/docs/design/bucketing-stage.md +2 -1
  9. package/docs/design/colibri-glm52-port.md +1479 -0
  10. package/docs/design/compat-cli-surface-design.md +4 -3
  11. package/docs/design/decode-speed-program.md +172 -0
  12. package/docs/design/docs-reorg-plan.md +1 -1
  13. package/docs/design/dspark-serving-program.md +371 -0
  14. package/docs/design/dspark-speculative-decoding.md +167 -5
  15. package/docs/design/faithful-l1-consolidation.md +310 -0
  16. package/docs/design/generic-model-support.md +8 -5
  17. package/docs/design/grammar-spec-batching-integration.md +301 -0
  18. package/docs/design/minicpm5-decode-megakernel.md +16 -11
  19. package/docs/design/mlx-lm-tool-parity-plan.md +11 -1
  20. package/docs/design/omlx-adoption-map.md +14 -6
  21. package/docs/design/orpo-training.md +34 -30
  22. package/docs/design/paged-kv-cache.md +214 -0
  23. package/docs/design/parallel-slots.md +82 -49
  24. package/docs/design/parity-tier-dag.md +43 -33
  25. package/docs/design/pre-colibri-stabilization.md +329 -0
  26. package/docs/design/repo-cleanup-plan.md +189 -0
  27. package/docs/design/runtime-isolation.md +19 -3
  28. package/docs/design/segmented-backward-training.md +3 -2
  29. package/docs/design/ssd-kv-cold-tier.md +130 -0
  30. package/docs/design/structured-output.md +484 -0
  31. package/docs/design/the-dreaming-master-plan.md +2 -3
  32. package/docs/design/turboquant-kv.md +235 -0
  33. package/docs/design/unified-engine-frontier-plan.md +774 -0
  34. package/docs/design/web-chat-beat-matrix.md +315 -0
  35. package/docs/design/web-chat-redesign.md +1112 -0
  36. package/docs/investigations/colibri-oracle-pin.md +1284 -0
  37. package/docs/investigations/dspark-handoff.md +182 -16
  38. package/docs/investigations/indexshare-performance-spike.md +357 -0
  39. package/docs/investigations/lab-build-journal.md +2 -2
  40. package/docs/investigations/openwiki-evaluation.md +249 -0
  41. package/docs/planning/IDEAS.md +14 -0
  42. package/docs/planning/PRODUCT_ROADMAP.md +2 -3
  43. package/docs/planning/chat-ui-vision-memory-first.md +199 -0
  44. package/docs/planning/chat-ui-vision.md +50 -0
  45. package/docs/planning/memory-docs-and-dag-plan.md +15 -6
  46. package/docs/planning/release-notes-v0.0.11.md +239 -0
  47. package/docs/planning/release-notes-v0.0.12.md +80 -0
  48. package/docs/reference/cli.md +63 -18
  49. package/docs/reference/distribution.md +37 -6
  50. package/docs/reference/embedding.md +40 -16
  51. package/docs/reference/features-matrix.md +99 -0
  52. package/docs/reference/library-api.md +69 -20
  53. package/docs/reference/memory.md +26 -6
  54. package/docs/reference/models.md +60 -3
  55. package/docs/reference/orpo-quickstart.md +3 -4
  56. package/docs/reference/server-api.md +717 -24
  57. package/docs/reference/server-config.md +265 -129
  58. package/docs/reference/training.md +24 -16
  59. package/package.json +5 -3
  60. package/src/anthropic.ts +67 -44
  61. package/src/audio/conformer.ts +647 -0
  62. package/src/audio/decode.ts +133 -0
  63. package/src/audio/features.ts +202 -0
  64. package/src/audio/transcode.ts +67 -0
  65. package/src/chat-template.ts +155 -10
  66. package/src/cli.ts +483 -113
  67. package/src/config.ts +116 -24
  68. package/src/diffusion/diffusion-generate.ts +14 -8
  69. package/src/download.ts +143 -12
  70. package/src/eval/README.md +7 -6
  71. package/src/eval/ifeval.ts +63 -120
  72. package/src/eval/kl.ts +59 -3
  73. package/src/eval/quality-db.ts +2 -2
  74. package/src/eval/runner.ts +17 -8
  75. package/src/eval/tasks/ifeval.ts +167 -31
  76. package/src/expert-io.ts +391 -0
  77. package/src/expert-offload.ts +15 -2
  78. package/src/expert-residency.ts +1134 -0
  79. package/src/expert-usage.ts +415 -0
  80. package/src/flags.ts +13 -0
  81. package/src/generate.ts +438 -35
  82. package/src/grammar.ts +585 -0
  83. package/src/hf-push.ts +1 -1
  84. package/src/hub-rest.ts +242 -0
  85. package/src/index.ts +14 -2
  86. package/src/jobs/runner.ts +37 -18
  87. package/src/kv-store.ts +586 -93
  88. package/src/lora.ts +85 -14
  89. package/src/md-modules.d.ts +42 -0
  90. package/src/media-fetch.ts +426 -0
  91. package/src/memory/rest.ts +245 -0
  92. package/src/memory/vault.ts +74 -0
  93. package/src/mlx/array.ts +149 -19
  94. package/src/mlx/autograd.ts +53 -16
  95. package/src/mlx/checkpoint.ts +46 -13
  96. package/src/mlx/custom-vjp.ts +83 -35
  97. package/src/mlx/ffi.ts +27 -3
  98. package/src/mlx/metal-kernel.ts +7 -4
  99. package/src/mlx/ops.ts +153 -23
  100. package/src/mlx/turboquant-ops.ts +831 -0
  101. package/src/mlx/turboquant-tables.ts +237 -0
  102. package/src/model/batched-mask.ts +62 -1
  103. package/src/model/batched-quant.ts +206 -0
  104. package/src/model/batched-rotating-quant.ts +346 -0
  105. package/src/model/batched-rotating.ts +46 -1
  106. package/src/model/compiled-decode.ts +4 -15
  107. package/src/model/factory.ts +103 -6
  108. package/src/model/gemma4-base.ts +395 -39
  109. package/src/model/gemma4.ts +122 -33
  110. package/src/model/generated/gemma4-12b.ts +9 -13
  111. package/src/model/generated/gemma4-26b.ts +10 -5
  112. package/src/model/generated/gemma4-e4b.ts +14 -9
  113. package/src/model/glm52-atlas.ts +511 -0
  114. package/src/model/glm52-cache.ts +706 -0
  115. package/src/model/glm52-config.ts +262 -0
  116. package/src/model/glm52-container.ts +305 -0
  117. package/src/model/glm52-coupling.ts +388 -0
  118. package/src/model/glm52-dsa.ts +602 -0
  119. package/src/model/glm52-expert-layout.ts +253 -0
  120. package/src/model/glm52-layout.ts +152 -0
  121. package/src/model/glm52-memory.ts +473 -0
  122. package/src/model/glm52-mla.ts +786 -0
  123. package/src/model/glm52-moe.ts +223 -0
  124. package/src/model/glm52-pilot.ts +537 -0
  125. package/src/model/glm52-reference.ts +445 -0
  126. package/src/model/glm52-residency.ts +557 -0
  127. package/src/model/glm52-resident-weights.ts +135 -0
  128. package/src/model/glm52-streamed-experts.ts +453 -0
  129. package/src/model/glm52-streamed-metal.ts +610 -0
  130. package/src/model/glm52-weights.ts +503 -0
  131. package/src/model/glm52.ts +1153 -0
  132. package/src/model/minicpm5.ts +58 -102
  133. package/src/model/paged-kv.ts +253 -0
  134. package/src/model/qwen3-delta.ts +76 -15
  135. package/src/model/qwen3-moe.ts +404 -0
  136. package/src/model/qwen3.ts +9 -8
  137. package/src/model/qwen3_5.ts +88 -23
  138. package/src/model/support.ts +19 -1
  139. package/src/model/universal/dense.ts +40 -5
  140. package/src/model/universal/rope.ts +36 -21
  141. package/src/native/expert_io.c +523 -0
  142. package/src/native-pack.ts +6 -4
  143. package/src/pi-web.ts +1299 -82
  144. package/src/preflight.ts +27 -8
  145. package/src/prompt-cache.ts +293 -33
  146. package/src/quantize/quantizer.ts +16 -1
  147. package/src/registry.ts +57 -2
  148. package/src/responses.ts +62 -49
  149. package/src/serve/batch-scheduler.ts +1050 -78
  150. package/src/serve/generation-gateway.ts +252 -24
  151. package/src/serve/isolate.ts +417 -0
  152. package/src/serve/lane-registry.ts +44 -0
  153. package/src/serve/session-search.ts +326 -0
  154. package/src/server.ts +1701 -175
  155. package/src/spec/assistant-source.ts +150 -0
  156. package/src/spec/deepspec-source.ts +143 -0
  157. package/src/spec/dflash-source.ts +124 -0
  158. package/src/spec/dspark/ab-stats.ts +136 -0
  159. package/src/spec/dspark/calibration.ts +112 -0
  160. package/src/spec/dspark/deepspec-module.ts +801 -0
  161. package/src/spec/dspark/generate-dflash.ts +72 -20
  162. package/src/spec/dspark/loader.ts +23 -0
  163. package/src/spec/dspark/module-dflash.ts +278 -36
  164. package/src/spec/dspark/quantize-drafter.ts +91 -0
  165. package/src/spec/generate.ts +59 -25
  166. package/src/spec/glm52-mtp-source.ts +392 -0
  167. package/src/spec/ngram-source.ts +132 -0
  168. package/src/spec/serve-loop.ts +617 -0
  169. package/src/spec/source.ts +118 -0
  170. package/src/spec/two-model.ts +171 -0
  171. package/src/ssd-cache.ts +111 -35
  172. package/src/tokenizer.ts +288 -2
  173. package/src/tool-approvals.ts +131 -0
  174. package/src/tool-call.ts +438 -22
  175. package/src/train/flash-cce.ts +2 -2
  176. package/src/train/job.ts +0 -12
  177. package/src/train/lora-params.ts +17 -6
  178. package/src/train/trainer.ts +60 -30
  179. package/src/vision/prompt.ts +217 -48
  180. package/src/web/app.html +1197 -1791
  181. package/src/web/app.js +5694 -0
  182. package/src/web/icon.svg +13 -0
  183. package/src/web/manifest.webmanifest +24 -0
  184. package/src/web/src/adapters-panel.ts +288 -0
  185. package/src/web/src/api.ts +57 -0
  186. package/src/web/src/assistant.ts +485 -0
  187. package/src/web/src/chat.ts +1050 -0
  188. package/src/web/src/composer.ts +959 -0
  189. package/src/web/src/dataset.ts +132 -0
  190. package/src/web/src/finetune.ts +267 -0
  191. package/src/web/src/hub.ts +428 -0
  192. package/src/web/src/main.ts +98 -0
  193. package/src/web/src/markdown.ts +0 -0
  194. package/src/web/src/memory-panel.ts +572 -0
  195. package/src/web/src/model-picker.ts +159 -0
  196. package/src/web/src/palette.ts +346 -0
  197. package/src/web/src/protocol.ts +62 -0
  198. package/src/web/src/quantize.ts +167 -0
  199. package/src/web/src/rag.ts +274 -0
  200. package/src/web/src/sessions.ts +469 -0
  201. package/src/web/src/shell.ts +804 -0
  202. package/src/web/src/status.ts +300 -0
  203. package/src/web/src/ui-catalog.ts +92 -0
  204. package/src/web/sw.js +71 -0
  205. package/src/web/vendor/README.md +61 -0
  206. package/src/web/vendor/hljs-LICENSE +29 -0
  207. package/src/web/vendor/hljs-theme.css +59 -0
  208. package/src/web/vendor/hljs.js +26 -0
  209. package/src/web-tools.ts +54 -2
  210. package/src/weights.ts +82 -19
  211. package/src/model/fused-decode-kernel.ts +0 -312
  212. package/src/model/fused-geglu-kernel.ts +0 -198
  213. package/src/model/fused-mlp-kernel.ts +0 -169
  214. package/src/model/fused-swiglu-kernel.ts +0 -240
  215. package/src/model/steel-linear-kernel.ts +0 -96
  216. package/src/status-page.html +0 -497
@@ -1,6 +1,6 @@
1
1
  // Native autograd via mlx-c value_and_grad over a JS loss closure.
2
2
  //
3
- // Encapsulates the exact FFI lifecycle proven in spikes/phase-train-vag.ts:
3
+ // Encapsulates the exact FFI lifecycle proven in lab/spikes/phase-train-vag.ts:
4
4
  // JS loss closure (JSCallback) → mlx_closure_new_func_payload →
5
5
  // mlx_value_and_grad → mlx_closure_value_and_grad_apply → read grads.
6
6
  //
@@ -15,6 +15,23 @@ import { JSCallback, ptr, read } from "bun:ffi";
15
15
  import { C, takeMlxError } from "./ffi";
16
16
  import { MlxArray } from "./array";
17
17
 
18
+ type CallbackHandle = Pick<JSCallback, "ptr" | "close">;
19
+ type NativeCall<F extends (...args: any[]) => any> =
20
+ (...args: Parameters<F>) => ReturnType<F>;
21
+
22
+ /** Constructor-only dependency seam for deterministic native failure tests. */
23
+ export interface ValueAndGradConstructorHooks {
24
+ callback?: (
25
+ callback: ConstructorParameters<typeof JSCallback>[0],
26
+ definition: ConstructorParameters<typeof JSCallback>[1],
27
+ ) => CallbackHandle;
28
+ closureNew?: NativeCall<typeof C.mlx_closure_new_func_payload>;
29
+ transformedNew?: NativeCall<typeof C.mlx_closure_value_and_grad_new>;
30
+ transform?: NativeCall<typeof C.mlx_value_and_grad>;
31
+ transformedFree?: NativeCall<typeof C.mlx_closure_value_and_grad_free>;
32
+ closureFree?: NativeCall<typeof C.mlx_closure_free>;
33
+ }
34
+
18
35
  /** A reusable value-and-grad over a JS-built loss graph.
19
36
  *
20
37
  * `loss(primals)` receives the input arrays in argument order and must
@@ -28,24 +45,36 @@ import { MlxArray } from "./array";
28
45
  * are owned by mlx — do NOT dispose them; the returned loss IS disposed
29
46
  * of by mlx's graph. */
30
47
  export class ValueAndGrad {
31
- readonly #cb: JSCallback;
48
+ readonly #cb: CallbackHandle;
32
49
  readonly #closure: bigint;
33
50
  readonly #vag: bigint;
34
51
  readonly #argIdx: Int32Array;
52
+ readonly #freeClosure: NativeCall<typeof C.mlx_closure_free>;
53
+ readonly #freeVag: NativeCall<typeof C.mlx_closure_value_and_grad_free>;
35
54
  #closureError: string | null = null;
36
55
  #disposed = false;
37
56
 
38
- constructor(loss: (primals: MlxArray[]) => MlxArray, argIdx: number[]) {
57
+ constructor(
58
+ loss: (primals: MlxArray[]) => MlxArray,
59
+ argIdx: number[],
60
+ hooks: ValueAndGradConstructorHooks = {},
61
+ ) {
39
62
  if (argIdx.length === 0) throw new Error("ValueAndGrad: argIdx must be non-empty");
40
63
  this.#argIdx = new Int32Array(argIdx);
64
+ const makeCallback = hooks.callback ?? ((callback, definition) => new JSCallback(callback, definition));
65
+ const closureNew = hooks.closureNew ?? C.mlx_closure_new_func_payload;
66
+ const transformedNew = hooks.transformedNew ?? C.mlx_closure_value_and_grad_new;
67
+ const transform = hooks.transform ?? C.mlx_value_and_grad;
68
+ this.#freeClosure = hooks.closureFree ?? C.mlx_closure_free;
69
+ this.#freeVag = hooks.transformedFree ?? C.mlx_closure_value_and_grad_free;
41
70
 
42
- this.#cb = new JSCallback(
71
+ const cb = makeCallback(
43
72
  (outPtr: number, inVec: bigint, _payload: number): number => {
44
73
  try {
45
74
  // Read the primals out of the input vector_array. mlx_vector_array_get
46
75
  // returns a fresh (ref-counted) handle, so disposing the wrapper
47
76
  // afterward is correct — it drops our reference, not mlx's graph
48
- // reference (proven in spikes/phase-train-vag.ts).
77
+ // reference (proven in lab/spikes/phase-train-vag.ts).
49
78
  const n = Number(C.mlx_vector_array_size(inVec));
50
79
  const primals: MlxArray[] = [];
51
80
  for (let i = 0; i < n; i++) {
@@ -73,16 +102,24 @@ export class ValueAndGrad {
73
102
  { args: ["ptr", "u64", "ptr"], returns: "i32" },
74
103
  );
75
104
 
76
- this.#closure = C.mlx_closure_new_func_payload(this.#cb.ptr as never, null, null);
77
-
78
- const vagSlot = new BigUint64Array([C.mlx_closure_value_and_grad_new()]);
79
- if (C.mlx_value_and_grad(ptr(vagSlot), this.#closure, ptr(this.#argIdx), BigInt(this.#argIdx.length)) !== 0) {
80
- // best-effort cleanup before throwing
81
- C.mlx_closure_free(this.#closure);
82
- this.#cb.close();
83
- throw new Error("mlx_value_and_grad failed");
105
+ let closure: bigint | null = null;
106
+ let vagSlot: BigUint64Array | null = null;
107
+ try {
108
+ closure = closureNew(cb.ptr as never, null, null);
109
+ vagSlot = new BigUint64Array([transformedNew()]);
110
+ if (transform(ptr(vagSlot), closure, ptr(this.#argIdx), BigInt(this.#argIdx.length)) !== 0)
111
+ throw new Error("mlx_value_and_grad failed");
112
+ this.#cb = cb;
113
+ this.#closure = closure;
114
+ this.#vag = read.u64(ptr(vagSlot), 0);
115
+ } catch (e) {
116
+ // mlx-c out-params are initialized live handles. On failure the slot
117
+ // still owns whichever handle it contains and must be freed explicitly.
118
+ if (vagSlot) this.#freeVag(read.u64(ptr(vagSlot), 0));
119
+ if (closure !== null) this.#freeClosure(closure);
120
+ cb.close();
121
+ throw e;
84
122
  }
85
- this.#vag = read.u64(ptr(vagSlot), 0);
86
123
  }
87
124
 
88
125
  /** Run the loss + backward on `primals` (in argument order). Returns the
@@ -136,8 +173,8 @@ export class ValueAndGrad {
136
173
  dispose(): void {
137
174
  if (this.#disposed) return;
138
175
  this.#disposed = true;
139
- C.mlx_closure_value_and_grad_free(this.#vag);
140
- C.mlx_closure_free(this.#closure);
176
+ this.#freeVag(this.#vag);
177
+ this.#freeClosure(this.#closure);
141
178
  this.#cb.close();
142
179
  }
143
180
 
@@ -15,6 +15,22 @@ import { JSCallback, ptr, read } from "bun:ffi";
15
15
  import { C, takeMlxError } from "./ffi";
16
16
  import { MlxArray } from "./array";
17
17
 
18
+ type CallbackHandle = Pick<JSCallback, "ptr" | "close">;
19
+ type NativeCall<F extends (...args: any[]) => any> =
20
+ (...args: Parameters<F>) => ReturnType<F>;
21
+
22
+ /** Constructor-only dependency seam for deterministic native failure tests. */
23
+ export interface CheckpointConstructorHooks {
24
+ callback?: (
25
+ callback: ConstructorParameters<typeof JSCallback>[0],
26
+ definition: ConstructorParameters<typeof JSCallback>[1],
27
+ ) => CallbackHandle;
28
+ closureNew?: NativeCall<typeof C.mlx_closure_new_func_payload>;
29
+ transformedNew?: NativeCall<typeof C.mlx_closure_new>;
30
+ transform?: NativeCall<typeof C.mlx_checkpoint>;
31
+ closureFree?: NativeCall<typeof C.mlx_closure_free>;
32
+ }
33
+
18
34
  /** A reusable checkpointed wrapper over a JS-built subgraph.
19
35
  *
20
36
  * `fn(inputs)` receives the input arrays (in order) and returns one or more
@@ -24,14 +40,24 @@ import { MlxArray } from "./array";
24
40
  * AND to any outer-trace leaves captured by `fn` (e.g. swapped-in LoRA
25
41
  * primals), exactly as if `fn` had been inlined. */
26
42
  export class Checkpoint {
27
- readonly #cb: JSCallback;
43
+ readonly #cb: CallbackHandle;
28
44
  readonly #closure: bigint;
29
45
  readonly #ckpt: bigint;
46
+ readonly #freeClosure: NativeCall<typeof C.mlx_closure_free>;
30
47
  #closureError: string | null = null;
31
48
  #disposed = false;
32
49
 
33
- constructor(fn: (inputs: MlxArray[]) => MlxArray[]) {
34
- this.#cb = new JSCallback(
50
+ constructor(
51
+ fn: (inputs: MlxArray[]) => MlxArray[],
52
+ hooks: CheckpointConstructorHooks = {},
53
+ ) {
54
+ const makeCallback = hooks.callback ?? ((callback, definition) => new JSCallback(callback, definition));
55
+ const closureNew = hooks.closureNew ?? C.mlx_closure_new_func_payload;
56
+ const transformedNew = hooks.transformedNew ?? C.mlx_closure_new;
57
+ const transform = hooks.transform ?? C.mlx_checkpoint;
58
+ this.#freeClosure = hooks.closureFree ?? C.mlx_closure_free;
59
+
60
+ const cb = makeCallback(
35
61
  (outPtr: number, inVec: bigint, _payload: number): number => {
36
62
  try {
37
63
  const n = Number(C.mlx_vector_array_size(inVec));
@@ -58,15 +84,22 @@ export class Checkpoint {
58
84
  { args: ["ptr", "u64", "ptr"], returns: "i32" },
59
85
  );
60
86
 
61
- this.#closure = C.mlx_closure_new_func_payload(this.#cb.ptr as never, null, null);
62
-
63
- const slot = new BigUint64Array([C.mlx_closure_new()]);
64
- if (C.mlx_checkpoint(ptr(slot), this.#closure) !== 0) {
65
- C.mlx_closure_free(this.#closure);
66
- this.#cb.close();
67
- throw new Error(`mlx_checkpoint failed: ${takeMlxError() ?? ""}`);
87
+ let closure: bigint | null = null;
88
+ let slot: BigUint64Array | null = null;
89
+ try {
90
+ closure = closureNew(cb.ptr as never, null, null);
91
+ slot = new BigUint64Array([transformedNew()]);
92
+ if (transform(ptr(slot), closure) !== 0)
93
+ throw new Error(`mlx_checkpoint failed: ${takeMlxError() ?? ""}`);
94
+ this.#cb = cb;
95
+ this.#closure = closure;
96
+ this.#ckpt = read.u64(ptr(slot), 0);
97
+ } catch (e) {
98
+ if (slot) this.#freeClosure(read.u64(ptr(slot), 0));
99
+ if (closure !== null) this.#freeClosure(closure);
100
+ cb.close();
101
+ throw e;
68
102
  }
69
- this.#ckpt = read.u64(ptr(slot), 0);
70
103
  }
71
104
 
72
105
  /** Apply the checkpointed fn. Returns its outputs as owned MlxArrays. */
@@ -105,8 +138,8 @@ export class Checkpoint {
105
138
  dispose(): void {
106
139
  if (this.#disposed) return;
107
140
  this.#disposed = true;
108
- C.mlx_closure_free(this.#ckpt);
109
- C.mlx_closure_free(this.#closure);
141
+ this.#freeClosure(this.#ckpt);
142
+ this.#freeClosure(this.#closure);
110
143
  this.#cb.close();
111
144
  }
112
145
 
@@ -15,6 +15,24 @@ import { JSCallback, ptr, read } from "bun:ffi";
15
15
  import { C, takeMlxError } from "./ffi";
16
16
  import { MlxArray } from "./array";
17
17
 
18
+ type CallbackHandle = Pick<JSCallback, "ptr" | "close">;
19
+ type NativeCall<F extends (...args: any[]) => any> =
20
+ (...args: Parameters<F>) => ReturnType<F>;
21
+
22
+ /** Constructor-only dependency seam for deterministic native failure tests. */
23
+ export interface CustomVjpConstructorHooks {
24
+ callback?: (
25
+ callback: ConstructorParameters<typeof JSCallback>[0],
26
+ definition: ConstructorParameters<typeof JSCallback>[1],
27
+ ) => CallbackHandle;
28
+ forwardClosureNew?: NativeCall<typeof C.mlx_closure_new_func_payload>;
29
+ vjpClosureNew?: NativeCall<typeof C.mlx_closure_custom_new_func_payload>;
30
+ transformedNew?: NativeCall<typeof C.mlx_closure_new>;
31
+ transform?: NativeCall<typeof C.mlx_custom_vjp>;
32
+ closureFree?: NativeCall<typeof C.mlx_closure_free>;
33
+ customClosureFree?: NativeCall<typeof C.mlx_closure_custom_free>;
34
+ }
35
+
18
36
  function readVec(vec: bigint): MlxArray[] {
19
37
  const n = Number(C.mlx_vector_array_size(vec));
20
38
  const out: MlxArray[] = [];
@@ -41,19 +59,30 @@ function setVec(outPtr: number, arrays: MlxArray[]): void {
41
59
  * pure functions of their arguments — the vjp re-reads everything from the
42
60
  * graph during the enclosing backward. */
43
61
  export class CustomVjp {
44
- readonly #fwdCb: JSCallback;
45
- readonly #vjpCb: JSCallback;
62
+ readonly #fwdCb: CallbackHandle;
63
+ readonly #vjpCb: CallbackHandle;
46
64
  readonly #fwdClosure: bigint;
47
65
  readonly #vjpClosure: bigint;
48
66
  readonly #combined: bigint;
67
+ readonly #freeClosure: NativeCall<typeof C.mlx_closure_free>;
68
+ readonly #freeCustomClosure: NativeCall<typeof C.mlx_closure_custom_free>;
49
69
  #error: string | null = null;
50
70
  #disposed = false;
51
71
 
52
72
  constructor(
53
73
  fwd: (inputs: MlxArray[]) => MlxArray[],
54
74
  vjp: (primals: MlxArray[], cotangents: MlxArray[], outputs: MlxArray[]) => MlxArray[],
75
+ hooks: CustomVjpConstructorHooks = {},
55
76
  ) {
56
- this.#fwdCb = new JSCallback(
77
+ const makeCallback = hooks.callback ?? ((callback, definition) => new JSCallback(callback, definition));
78
+ const forwardClosureNew = hooks.forwardClosureNew ?? C.mlx_closure_new_func_payload;
79
+ const vjpClosureNew = hooks.vjpClosureNew ?? C.mlx_closure_custom_new_func_payload;
80
+ const transformedNew = hooks.transformedNew ?? C.mlx_closure_new;
81
+ const transform = hooks.transform ?? C.mlx_custom_vjp;
82
+ this.#freeClosure = hooks.closureFree ?? C.mlx_closure_free;
83
+ this.#freeCustomClosure = hooks.customClosureFree ?? C.mlx_closure_custom_free;
84
+
85
+ const fwdCb = makeCallback(
57
86
  (outPtr: number, inVec: bigint, _payload: number): number => {
58
87
  try {
59
88
  const inputs = readVec(inVec);
@@ -70,37 +99,56 @@ export class CustomVjp {
70
99
  { args: ["ptr", "u64", "ptr"], returns: "i32" },
71
100
  );
72
101
 
73
- this.#vjpCb = new JSCallback(
74
- (resPtr: number, primalsVec: bigint, cotsVec: bigint, outsVec: bigint, _payload: number): number => {
75
- try {
76
- const primals = readVec(primalsVec);
77
- const cots = readVec(cotsVec);
78
- const outs = readVec(outsVec);
79
- const grads = vjp(primals, cots, outs);
80
- setVec(resPtr, grads);
81
- for (const a of [...primals, ...cots, ...outs]) a.dispose();
82
- for (const g of grads) g.dispose();
83
- return 0;
84
- } catch (e) {
85
- this.#error = e instanceof Error ? e.message : String(e);
86
- return 1;
87
- }
88
- },
89
- { args: ["ptr", "u64", "u64", "u64", "ptr"], returns: "i32" },
90
- );
91
-
92
- this.#fwdClosure = C.mlx_closure_new_func_payload(this.#fwdCb.ptr as never, null, null);
93
- this.#vjpClosure = C.mlx_closure_custom_new_func_payload(this.#vjpCb.ptr as never, null, null);
102
+ let vjpCb: CallbackHandle;
103
+ try {
104
+ vjpCb = makeCallback(
105
+ (resPtr: number, primalsVec: bigint, cotsVec: bigint, outsVec: bigint, _payload: number): number => {
106
+ try {
107
+ const primals = readVec(primalsVec);
108
+ const cots = readVec(cotsVec);
109
+ const outs = readVec(outsVec);
110
+ const grads = vjp(primals, cots, outs);
111
+ setVec(resPtr, grads);
112
+ for (const a of [...primals, ...cots, ...outs]) a.dispose();
113
+ for (const g of grads) g.dispose();
114
+ return 0;
115
+ } catch (e) {
116
+ this.#error = e instanceof Error ? e.message : String(e);
117
+ return 1;
118
+ }
119
+ },
120
+ { args: ["ptr", "u64", "u64", "u64", "ptr"], returns: "i32" },
121
+ );
122
+ } catch (e) {
123
+ // Callback construction is itself fallible. The native-closure guard
124
+ // below starts only after both callbacks exist, so this partial state
125
+ // must release the already-created forward callback here.
126
+ fwdCb.close();
127
+ throw e;
128
+ }
94
129
 
95
- const slot = new BigUint64Array([C.mlx_closure_new()]);
96
- if (C.mlx_custom_vjp(ptr(slot), this.#fwdClosure, this.#vjpClosure) !== 0) {
97
- C.mlx_closure_free(this.#fwdClosure);
98
- C.mlx_closure_custom_free(this.#vjpClosure);
99
- this.#fwdCb.close();
100
- this.#vjpCb.close();
101
- throw new Error(`mlx_custom_vjp failed: ${takeMlxError() ?? ""}`);
130
+ let fwdClosure: bigint | null = null;
131
+ let vjpClosure: bigint | null = null;
132
+ let slot: BigUint64Array | null = null;
133
+ try {
134
+ fwdClosure = forwardClosureNew(fwdCb.ptr as never, null, null);
135
+ vjpClosure = vjpClosureNew(vjpCb.ptr as never, null, null);
136
+ slot = new BigUint64Array([transformedNew()]);
137
+ if (transform(ptr(slot), fwdClosure, vjpClosure) !== 0)
138
+ throw new Error(`mlx_custom_vjp failed: ${takeMlxError() ?? ""}`);
139
+ this.#fwdCb = fwdCb;
140
+ this.#vjpCb = vjpCb;
141
+ this.#fwdClosure = fwdClosure;
142
+ this.#vjpClosure = vjpClosure;
143
+ this.#combined = read.u64(ptr(slot), 0);
144
+ } catch (e) {
145
+ if (slot) this.#freeClosure(read.u64(ptr(slot), 0));
146
+ if (fwdClosure !== null) this.#freeClosure(fwdClosure);
147
+ if (vjpClosure !== null) this.#freeCustomClosure(vjpClosure);
148
+ fwdCb.close();
149
+ vjpCb.close();
150
+ throw e;
102
151
  }
103
- this.#combined = read.u64(ptr(slot), 0);
104
152
  }
105
153
 
106
154
  apply(inputs: MlxArray[]): MlxArray[] {
@@ -127,9 +175,9 @@ export class CustomVjp {
127
175
  dispose(): void {
128
176
  if (this.#disposed) return;
129
177
  this.#disposed = true;
130
- C.mlx_closure_free(this.#combined);
131
- C.mlx_closure_free(this.#fwdClosure);
132
- C.mlx_closure_custom_free(this.#vjpClosure);
178
+ this.#freeClosure(this.#combined);
179
+ this.#freeClosure(this.#fwdClosure);
180
+ this.#freeCustomClosure(this.#vjpClosure);
133
181
  this.#fwdCb.close();
134
182
  this.#vjpCb.close();
135
183
  }
package/src/mlx/ffi.ts CHANGED
@@ -15,7 +15,7 @@ import { existsSync } from "node:fs";
15
15
  import { dirname, join } from "node:path";
16
16
  import { nativePackDir } from "../native-pack";
17
17
 
18
- const { ptr: P, i32, u64, f32, cstring } = FFIType;
18
+ const { ptr: P, i32, u64, f32, f64, cstring } = FFIType;
19
19
 
20
20
  /** libmlxc resolution, in order: explicit env override → next to the
21
21
  * executable (the `bun build --compile` sidecar layout, where
@@ -57,6 +57,9 @@ export const C = dlopen(LIBMLXC_PATH, {
57
57
  mlx_array_data_float16: { args: [u64], returns: P },
58
58
  mlx_array_data_bfloat16: { args: [u64], returns: P },
59
59
  mlx_array_data_uint32: { args: [u64], returns: P },
60
+ mlx_array_data_int32: { args: [u64], returns: P },
61
+ mlx_array_data_uint8: { args: [u64], returns: P },
62
+ mlx_array_data_int8: { args: [u64], returns: P },
60
63
  mlx_array_item_float32: { args: [P, u64], returns: i32 },
61
64
  // streams
62
65
  mlx_default_gpu_stream_new: { args: [], returns: u64 },
@@ -142,6 +145,7 @@ export const C = dlopen(LIBMLXC_PATH, {
142
145
  mlx_negative: { args: [P, u64, u64], returns: i32 },
143
146
  mlx_argsort_axis: { args: [P, u64, i32, u64], returns: i32 },
144
147
  mlx_argpartition_axis: { args: [P, u64, i32, i32, u64], returns: i32 },
148
+ mlx_arange: { args: [P, f64, f64, f64, i32, u64], returns: i32 },
145
149
  mlx_take_along_axis: { args: [P, u64, u64, i32, u64], returns: i32 },
146
150
  mlx_put_along_axis: { args: [P, u64, u64, u64, i32, u64], returns: i32 },
147
151
  mlx_cumsum: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64], returns: i32 },
@@ -169,7 +173,17 @@ export const C = dlopen(LIBMLXC_PATH, {
169
173
  // (res, input, weight, stride, padding, dilation, groups, stream) — depthwise
170
174
  // causal conv1d for Qwen3.5 gated-DeltaNet (groups == channels, padding 0).
171
175
  mlx_conv1d: { args: [P, u64, u64, i32, i32, i32, i32, u64], returns: i32 },
172
- // --- training: autograd (value_and_grad) — proven in spikes/phase-train-vag.ts.
176
+ // C signature: (res, input, weight, stride_0, stride_1, padding_0,
177
+ // padding_1, dilation_0, dilation_1, groups, stream) — NHWC conv2d for the
178
+ // gemma-4 audio SSCP subsampler.
179
+ // HAZARD (lab/repro/bun-ffi-stack-args): Apple arm64 packs sub-8-byte STACK
180
+ // args at natural size (dilation_1@sp+0, groups@sp+4, stream@sp+8) but
181
+ // bun:ffi 1.3.14 writes one 8-byte slot per arg — the callee reads shifted
182
+ // garbage and mlx segfaults on a bogus stream. Workaround: declare the two
183
+ // stack ints as ONE u64 (dilation_1 | groups<<32) so Bun's slot layout
184
+ // byte-matches the Apple ABI. ops.conv2d does the packing.
185
+ mlx_conv2d: { args: [P, u64, u64, i32, i32, i32, i32, i32, u64, u64], returns: i32 },
186
+ // --- training: autograd (value_and_grad) — proven in lab/spikes/phase-train-vag.ts.
173
187
  // mlx_value_and_grad(res: mlx_closure_value_and_grad*, fun: mlx_closure,
174
188
  // argnums: int*, n) → builds a value+grad closure differentiating the
175
189
  // selected inputs of `fun` (a mlx_closure_new_func_payload over a JS loss).
@@ -260,6 +274,16 @@ export const C = dlopen(LIBMLXC_PATH, {
260
274
  // GPU trace capture (Phase E step 2: size the prize before the kernel)
261
275
  mlx_metal_start_capture: { args: [cstring], returns: i32 },
262
276
  mlx_metal_stop_capture: { args: [], returns: i32 },
277
+ // TurboQuant KV codec (Phase 13): FWHT rotation + per-group affine/Lloyd-Max
278
+ // (res, a, opt scale, stream) — 1/sqrt(dim) normalization when scale is null.
279
+ mlx_hadamard_transform: { args: [P, u64, u64, u64], returns: i32 },
280
+ mlx_min_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
281
+ mlx_round: { args: [P, u64, i32, u64], returns: i32 },
282
+ mlx_greater: { args: [P, u64, u64, u64], returns: i32 },
283
+ mlx_bitwise_and: { args: [P, u64, u64, u64], returns: i32 },
284
+ mlx_bitwise_or: { args: [P, u64, u64, u64], returns: i32 },
285
+ mlx_left_shift: { args: [P, u64, u64, u64], returns: i32 },
286
+ mlx_right_shift: { args: [P, u64, u64, u64], returns: i32 },
263
287
  }).symbols;
264
288
 
265
289
  export type MlxHandle = bigint;
@@ -329,7 +353,7 @@ export const NULL_HANDLE = 0n;
329
353
  // RULE: never read a typed array that a bun:ffi call wrote through a
330
354
  // pointer — once the calling function is DFG-compiled, the JIT eliminates
331
355
  // the load across the native call and `buf[i]` returns stale values
332
- // (repro/bun-ffi-f64/ISSUE.md; PLAN.md Phase 4 findings). Out-param slots
356
+ // (lab/repro/bun-ffi-f64/ISSUE.md; PLAN.md Phase 4 findings). Out-param slots
333
357
  // must be read back with bun:ffi `read.*` (verified safe). Initializing the
334
358
  // slot via the typed-array *constructor* is fine: that store happens in
335
359
  // host code the JIT can't elide.
@@ -89,15 +89,18 @@ export class MetalKernel {
89
89
  C.mlx_fast_metal_kernel_config_set_thread_group(cfg, call.threadGroup[0], call.threadGroup[1], call.threadGroup[2]);
90
90
  if (call.initValue !== undefined)
91
91
  C.mlx_fast_metal_kernel_config_set_init_value(cfg, call.initValue);
92
- for (const [k, v] of Object.entries(call.templateInts ?? {})) {
92
+ // Dtypes-first, then ints, to MATCH mlx's own specialization-name order
93
+ // (mlx-lm's gated_delta_step passes [("InT",…),("StT",…),("Dk",…),…]).
94
+ // Order affects only the generated name/cache-key, not binding (named args).
95
+ for (const [k, v] of Object.entries(call.templateDtypes ?? {})) {
93
96
  const kb = Buffer.from(k + "\0", "utf8");
94
97
  keepAlive.push(kb);
95
- C.mlx_fast_metal_kernel_config_add_template_arg_int(cfg, ptr(kb), v);
98
+ C.mlx_fast_metal_kernel_config_add_template_arg_dtype(cfg, ptr(kb), v);
96
99
  }
97
- for (const [k, v] of Object.entries(call.templateDtypes ?? {})) {
100
+ for (const [k, v] of Object.entries(call.templateInts ?? {})) {
98
101
  const kb = Buffer.from(k + "\0", "utf8");
99
102
  keepAlive.push(kb);
100
- C.mlx_fast_metal_kernel_config_add_template_arg_dtype(cfg, ptr(kb), v);
103
+ C.mlx_fast_metal_kernel_config_add_template_arg_int(cfg, ptr(kb), v);
101
104
  }
102
105
 
103
106
  const handles = new BigUint64Array(inputs.map((a) => a.handle));