mlx-bun 0.0.4

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 (210) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +362 -0
  3. package/THIRD_PARTY_LICENSES.md +41 -0
  4. package/bin/mlx-bun.mjs +32 -0
  5. package/docs/curve-designer.html +303 -0
  6. package/docs/design/adapters-end-to-end.md +201 -0
  7. package/docs/design/compat-cli-surface-design.md +227 -0
  8. package/docs/design/docs-reorg-plan.md +152 -0
  9. package/docs/design/hlg-sampling.md +340 -0
  10. package/docs/design/memory-system.md +339 -0
  11. package/docs/design/optimization_plan.md +344 -0
  12. package/docs/design/parallel-slots.md +515 -0
  13. package/docs/design/runtime-isolation.md +247 -0
  14. package/docs/design/segmented-backward-training.md +508 -0
  15. package/docs/investigations/chunk-finetune-experiment.md +84 -0
  16. package/docs/investigations/curve-bisector-routing.md +123 -0
  17. package/docs/investigations/curve-runs/bisector-route.json +98 -0
  18. package/docs/investigations/curve-runs/bisector-trace.json +1393 -0
  19. package/docs/investigations/curve-runs/curve-terrain.json +1179 -0
  20. package/docs/investigations/curve-runs/curve-terrain.svg +42 -0
  21. package/docs/investigations/curve-terrain.html +105 -0
  22. package/docs/investigations/expert-offload-single-user-moe.md +292 -0
  23. package/docs/investigations/hlg-ab.html +34 -0
  24. package/docs/investigations/hlg-explorer.html +238 -0
  25. package/docs/investigations/hlg-figs/cliff.svg +1 -0
  26. package/docs/investigations/hlg-figs/cross.svg +1 -0
  27. package/docs/investigations/hlg-figs/edges.svg +1 -0
  28. package/docs/investigations/hlg-figs/k20-grids.svg +1 -0
  29. package/docs/investigations/hlg-figs/k20-peaks.svg +1 -0
  30. package/docs/investigations/hlg-figs/lw-curve.svg +1 -0
  31. package/docs/investigations/hlg-figs/terrain.svg +1 -0
  32. package/docs/investigations/hlg-report.html +262 -0
  33. package/docs/investigations/hlg-runs/hlg-ab.json +149 -0
  34. package/docs/investigations/hlg-runs/hlg-blind-prompts.md +87 -0
  35. package/docs/investigations/hlg-runs/hlg-blind.json +107 -0
  36. package/docs/investigations/hlg-runs/hlg-cliff.json +197 -0
  37. package/docs/investigations/hlg-runs/hlg-cross.json +505 -0
  38. package/docs/investigations/hlg-runs/hlg-edge.json +388 -0
  39. package/docs/investigations/hlg-runs/hlg-eval1.json +25 -0
  40. package/docs/investigations/hlg-runs/hlg-frontier.json +86 -0
  41. package/docs/investigations/hlg-runs/hlg-grid.json +242 -0
  42. package/docs/investigations/hlg-runs/hlg-grid2.json +343 -0
  43. package/docs/investigations/hlg-runs/hlg-lw-coarse.json +110 -0
  44. package/docs/investigations/hlg-runs/hlg-map.json +383 -0
  45. package/docs/investigations/hlg-runs/run0-original.log +159 -0
  46. package/docs/investigations/hlg-runs/runA-recommended.log +131 -0
  47. package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +26 -0
  48. package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +18 -0
  49. package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +25 -0
  50. package/docs/investigations/hlg-runs/runAD-ab-samples.log +8 -0
  51. package/docs/investigations/hlg-runs/runAE-edge-e4b.log +51 -0
  52. package/docs/investigations/hlg-runs/runAF-logit-calibration.log +10 -0
  53. package/docs/investigations/hlg-runs/runAG-eval-config2.log +16 -0
  54. package/docs/investigations/hlg-runs/runB-gain-variants.log +192 -0
  55. package/docs/investigations/hlg-runs/runC-pivots.log +162 -0
  56. package/docs/investigations/hlg-runs/runD-oetf.log +132 -0
  57. package/docs/investigations/hlg-runs/runE-eotf.log +162 -0
  58. package/docs/investigations/hlg-runs/runF-shaper-working.log +86 -0
  59. package/docs/investigations/hlg-runs/runG-shaper-sweep.log +101 -0
  60. package/docs/investigations/hlg-runs/runH-wsweep.log +176 -0
  61. package/docs/investigations/hlg-runs/runI-asweep.log +176 -0
  62. package/docs/investigations/hlg-runs/runJ-knobmap.log +49 -0
  63. package/docs/investigations/hlg-runs/runK-frontier.log +19 -0
  64. package/docs/investigations/hlg-runs/runL-widemap.log +65 -0
  65. package/docs/investigations/hlg-runs/runM-frontier-emb.log +15 -0
  66. package/docs/investigations/hlg-runs/runN-verify.log +116 -0
  67. package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +15 -0
  68. package/docs/investigations/hlg-runs/runP-grid-AxSm.log +15 -0
  69. package/docs/investigations/hlg-runs/runQ-grid-tgW.log +15 -0
  70. package/docs/investigations/hlg-runs/runR-blind-transcript.log +79 -0
  71. package/docs/investigations/hlg-runs/runS-cross-coherence.log +33 -0
  72. package/docs/investigations/hlg-runs/runT-cross-cpm5.log +33 -0
  73. package/docs/investigations/hlg-runs/runU-cross-12b.log +33 -0
  74. package/docs/investigations/hlg-runs/runV-lw-e4b.log +22 -0
  75. package/docs/investigations/hlg-runs/runW-lw-12b.log +22 -0
  76. package/docs/investigations/hlg-runs/runX-lw-cpm5.log +22 -0
  77. package/docs/investigations/hlg-runs/runY-cliff-e4b.log +26 -0
  78. package/docs/investigations/hlg-runs/runZ-cliff-12b.log +26 -0
  79. package/docs/investigations/hlg-sampling-investigation.md +970 -0
  80. package/docs/investigations/lab-build-journal.md +157 -0
  81. package/docs/investigations/pi-builtin-investigation.md +308 -0
  82. package/docs/investigations/starter-model-port-handoff.md +323 -0
  83. package/docs/planning/IDEAS.md +26 -0
  84. package/docs/planning/PRODUCT_ROADMAP.md +426 -0
  85. package/docs/planning/ResearchTopics.md +119 -0
  86. package/docs/planning/chunk-task-roadmap.md +77 -0
  87. package/docs/planning/journal.md +155 -0
  88. package/docs/reference/distribution.md +121 -0
  89. package/docs/reference/embedding.md +168 -0
  90. package/docs/reference/library-api.md +153 -0
  91. package/docs/reference/server-api.md +224 -0
  92. package/docs/reference/server-config.md +243 -0
  93. package/docs/segmented-backward-handoff.md +148 -0
  94. package/package.json +68 -0
  95. package/src/anthropic.ts +491 -0
  96. package/src/chat-template.ts +186 -0
  97. package/src/cli.ts +1075 -0
  98. package/src/config.ts +229 -0
  99. package/src/curve-sampler.ts +122 -0
  100. package/src/dataset/generators.ts +1087 -0
  101. package/src/dataset/index.ts +12 -0
  102. package/src/dataset/job.ts +81 -0
  103. package/src/dataset/llm.ts +71 -0
  104. package/src/dataset/registry.ts +528 -0
  105. package/src/download.ts +283 -0
  106. package/src/eval/README.md +86 -0
  107. package/src/eval/kl-prompts.ts +29 -0
  108. package/src/eval/kl.ts +294 -0
  109. package/src/eval/quality-db.ts +92 -0
  110. package/src/eval/runner.ts +102 -0
  111. package/src/eval/score.ts +49 -0
  112. package/src/eval/tasks/bfcl.ts +500 -0
  113. package/src/eval/tasks/gsm8k.ts +85 -0
  114. package/src/eval/tasks/hashhop.ts +109 -0
  115. package/src/eval/tasks/humaneval.ts +252 -0
  116. package/src/eval/tasks/ifeval.ts +354 -0
  117. package/src/eval/tasks/mmlu.ts +145 -0
  118. package/src/evaldb.ts +199 -0
  119. package/src/expert-offload-build.ts +77 -0
  120. package/src/expert-offload.ts +61 -0
  121. package/src/expert-trace.ts +67 -0
  122. package/src/fit.ts +254 -0
  123. package/src/generate.ts +451 -0
  124. package/src/harness-pi.ts +177 -0
  125. package/src/hf-push.ts +628 -0
  126. package/src/index.ts +22 -0
  127. package/src/jobs/db.ts +139 -0
  128. package/src/jobs/index.ts +29 -0
  129. package/src/jobs/job-entry.ts +106 -0
  130. package/src/jobs/runner.ts +304 -0
  131. package/src/jobs/sse.ts +114 -0
  132. package/src/jobs/types.ts +58 -0
  133. package/src/kv-store.ts +185 -0
  134. package/src/lora.ts +314 -0
  135. package/src/md-modules.d.ts +7 -0
  136. package/src/mlx/array.ts +237 -0
  137. package/src/mlx/autograd.ts +267 -0
  138. package/src/mlx/checkpoint.ts +116 -0
  139. package/src/mlx/compile.ts +158 -0
  140. package/src/mlx/custom-vjp.ts +140 -0
  141. package/src/mlx/ffi.ts +433 -0
  142. package/src/mlx/metal-kernel.ts +130 -0
  143. package/src/mlx/ops.ts +707 -0
  144. package/src/mmap.ts +90 -0
  145. package/src/model/batched-mask.ts +204 -0
  146. package/src/model/batched-rotating.ts +300 -0
  147. package/src/model/compiled-decode.ts +681 -0
  148. package/src/model/factory.ts +27 -0
  149. package/src/model/fingerprint.ts +33 -0
  150. package/src/model/flash-attention.ts +674 -0
  151. package/src/model/fused-decode-kernel.ts +305 -0
  152. package/src/model/fused-geglu-kernel.ts +87 -0
  153. package/src/model/gemma4-base.ts +1616 -0
  154. package/src/model/gemma4.ts +1012 -0
  155. package/src/model/generated/gemma4-12b.ts +526 -0
  156. package/src/model/generated/gemma4-26b.ts +500 -0
  157. package/src/model/generated/gemma4-e4b.ts +757 -0
  158. package/src/model/generated/index.ts +18 -0
  159. package/src/model/minicpm5.ts +278 -0
  160. package/src/model/qwen3-delta.ts +224 -0
  161. package/src/model/qwen3_5.ts +429 -0
  162. package/src/model/support.ts +46 -0
  163. package/src/native-pack.ts +152 -0
  164. package/src/pi-provider.ts +95 -0
  165. package/src/pi-terminal.ts +264 -0
  166. package/src/pi-web.ts +924 -0
  167. package/src/preflight.ts +123 -0
  168. package/src/prompt-cache.ts +113 -0
  169. package/src/quantize/allocator.ts +627 -0
  170. package/src/quantize/calibration.ts +180 -0
  171. package/src/quantize/config-writer.ts +132 -0
  172. package/src/quantize/index.ts +57 -0
  173. package/src/quantize/job.ts +136 -0
  174. package/src/quantize/quantizer.ts +462 -0
  175. package/src/quantize/safetensors-writer.ts +171 -0
  176. package/src/quantize/sensitivity.ts +234 -0
  177. package/src/registry.ts +336 -0
  178. package/src/responses.ts +774 -0
  179. package/src/safetensors.ts +158 -0
  180. package/src/sampler.ts +517 -0
  181. package/src/serve/batch-scheduler.ts +362 -0
  182. package/src/serve/generation-gateway.ts +160 -0
  183. package/src/server.ts +1891 -0
  184. package/src/spec/drafter.ts +261 -0
  185. package/src/spec/generate.ts +221 -0
  186. package/src/status-page.html +497 -0
  187. package/src/tokenizer.ts +38 -0
  188. package/src/tool-call.ts +242 -0
  189. package/src/train/dataset.ts +461 -0
  190. package/src/train/export.ts +32 -0
  191. package/src/train/forward.ts +229 -0
  192. package/src/train/index.ts +43 -0
  193. package/src/train/job.ts +148 -0
  194. package/src/train/lora-params.ts +194 -0
  195. package/src/train/loss.ts +342 -0
  196. package/src/train/merge.ts +195 -0
  197. package/src/train/optimizer.ts +176 -0
  198. package/src/train/rank.ts +101 -0
  199. package/src/train/segmented.ts +520 -0
  200. package/src/train/trainer.ts +600 -0
  201. package/src/tui.ts +161 -0
  202. package/src/vision/embedder.ts +168 -0
  203. package/src/vision/preprocess.ts +247 -0
  204. package/src/vision/prompt.ts +158 -0
  205. package/src/vision/siglip.ts +599 -0
  206. package/src/web/app.html +2471 -0
  207. package/src/web/skills/web-research/SKILL.md +44 -0
  208. package/src/web/skills.ts +59 -0
  209. package/src/web-tools.ts +426 -0
  210. package/src/weights.ts +91 -0
package/src/mlx/ops.ts ADDED
@@ -0,0 +1,707 @@
1
+ // Functional op layer over MlxArray. Parity-critical conventions:
2
+ // - Python-float scalars in mlx promote weakly (bf16 array × float →
3
+ // bf16, scalar cast to bf16 first). `scalarLike` replicates that.
4
+ // - Op composition order mirrors mlx-lm exactly; see model/gemma4.ts.
5
+
6
+ import { ptr, read } from "bun:ffi";
7
+ import { C, Dtype, type MlxHandle, optFloat, optInt, outArray, takeMlxError } from "./ffi";
8
+ import { MlxArray, gpuStream } from "./array";
9
+
10
+ const cstrCache = new Map<string, Buffer>();
11
+ function cstr(s: string): Buffer {
12
+ let b = cstrCache.get(s);
13
+ if (!b) {
14
+ b = Buffer.from(s + "\0", "utf8");
15
+ cstrCache.set(s, b);
16
+ }
17
+ return b;
18
+ }
19
+
20
+ type S = MlxHandle;
21
+
22
+ export function add(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
23
+ return new MlxArray(outArray("add", (o) => C.mlx_add(o, a.handle, b.handle, s)));
24
+ }
25
+
26
+ export function sub(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
27
+ return new MlxArray(outArray("subtract", (o) => C.mlx_subtract(o, a.handle, b.handle, s)));
28
+ }
29
+
30
+ export function mul(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
31
+ return new MlxArray(outArray("multiply", (o) => C.mlx_multiply(o, a.handle, b.handle, s)));
32
+ }
33
+
34
+ export function div(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
35
+ return new MlxArray(outArray("divide", (o) => C.mlx_divide(o, a.handle, b.handle, s)));
36
+ }
37
+
38
+ export function tanh(a: MlxArray, s: S = gpuStream): MlxArray {
39
+ return new MlxArray(outArray("tanh", (o) => C.mlx_tanh(o, a.handle, s)));
40
+ }
41
+
42
+ export function sigmoid(a: MlxArray, s: S = gpuStream): MlxArray {
43
+ return new MlxArray(outArray("sigmoid", (o) => C.mlx_sigmoid(o, a.handle, s)));
44
+ }
45
+
46
+ /** Scalar constant with the same dtype as `like` (python weak-scalar semantics). */
47
+ export function scalarLike(value: number, like: MlxArray): MlxArray {
48
+ const f = MlxArray.fromFloat32(new Float32Array([value]), []);
49
+ if (like.dtype === Dtype.float32) return f;
50
+ const cast = f.astype(like.dtype);
51
+ f.dispose();
52
+ return cast;
53
+ }
54
+
55
+ export function mulScalar(a: MlxArray, value: number, s: S = gpuStream): MlxArray {
56
+ const k = scalarLike(value, a);
57
+ const r = mul(a, k, s);
58
+ k.dispose();
59
+ return r;
60
+ }
61
+
62
+ export function rmsNorm(
63
+ x: MlxArray, weight: MlxArray | null, eps: number, s: S = gpuStream,
64
+ ): MlxArray {
65
+ return new MlxArray(
66
+ outArray("fast_rms_norm", (o) =>
67
+ C.mlx_fast_rms_norm(o, x.handle, weight?.handle ?? 0n, eps, s),
68
+ ),
69
+ );
70
+ }
71
+
72
+ export function layerNorm(
73
+ x: MlxArray, weight: MlxArray | null, bias: MlxArray | null, eps: number,
74
+ s: S = gpuStream,
75
+ ): MlxArray {
76
+ return new MlxArray(
77
+ outArray("fast_layer_norm", (o) =>
78
+ C.mlx_fast_layer_norm(o, x.handle, weight?.handle ?? 0n, bias?.handle ?? 0n, eps, s),
79
+ ),
80
+ );
81
+ }
82
+
83
+ export function matmul(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
84
+ return new MlxArray(outArray("matmul", (o) => C.mlx_matmul(o, a.handle, b.handle, s)));
85
+ }
86
+
87
+ /** Materialize a row-major contiguous copy (needed before rawBytes on views). */
88
+ export function contiguous(a: MlxArray, s: S = gpuStream): MlxArray {
89
+ return new MlxArray(
90
+ outArray("contiguous", (o) => C.mlx_contiguous(o, a.handle, false, s)),
91
+ );
92
+ }
93
+
94
+ export function logicalOr(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
95
+ return new MlxArray(
96
+ outArray("logical_or", (o) => C.mlx_logical_or(o, a.handle, b.handle, s)),
97
+ );
98
+ }
99
+
100
+ export function rope(
101
+ x: MlxArray, dims: number, base: number | null, offset: number,
102
+ freqs: MlxArray | null, s: S = gpuStream,
103
+ ): MlxArray {
104
+ return new MlxArray(
105
+ outArray("fast_rope", (o) =>
106
+ C.mlx_fast_rope(
107
+ o, x.handle, dims, false, optFloat(base), 1.0, offset,
108
+ freqs?.handle ?? 0n, s,
109
+ ),
110
+ ),
111
+ );
112
+ }
113
+
114
+ /** fast::rope with the position offset as an ARRAY (int32 scalar) instead
115
+ * of a baked int — required inside compiled decode graphs, where the
116
+ * offset changes every step but the graph must not. Same kernel as
117
+ * `rope`; bit-exactness vs the static form is asserted in
118
+ * tests/compile.test.ts. */
119
+ export function ropeDynamic(
120
+ x: MlxArray, dims: number, base: number | null, offset: MlxArray,
121
+ freqs: MlxArray | null, s: S = gpuStream,
122
+ ): MlxArray {
123
+ return new MlxArray(
124
+ outArray("fast_rope_dynamic", (o) =>
125
+ C.mlx_fast_rope_dynamic(
126
+ o, x.handle, dims, false, optFloat(base), 1.0, offset.handle,
127
+ freqs?.handle ?? 0n, s,
128
+ ),
129
+ ),
130
+ );
131
+ }
132
+
133
+ export function sdpa(
134
+ q: MlxArray, k: MlxArray, v: MlxArray, scale: number,
135
+ maskMode: "" | "causal" | "array", maskArr: MlxArray | null = null,
136
+ s: S = gpuStream,
137
+ ): MlxArray {
138
+ return new MlxArray(
139
+ outArray("fast_sdpa", (o) =>
140
+ C.mlx_fast_scaled_dot_product_attention(
141
+ o, q.handle, k.handle, v.handle, scale, ptr(cstr(maskMode)),
142
+ maskArr?.handle ?? 0n, 0n, s,
143
+ ),
144
+ ),
145
+ );
146
+ }
147
+
148
+ export interface QuantSpec {
149
+ bits: number;
150
+ groupSize: number;
151
+ mode: string;
152
+ }
153
+
154
+ export function quantizedMatmul(
155
+ x: MlxArray, w: MlxArray, scales: MlxArray, biases: MlxArray | null,
156
+ spec: QuantSpec, transpose = true, s: S = gpuStream,
157
+ ): MlxArray {
158
+ return new MlxArray(
159
+ outArray("quantized_matmul", (o) =>
160
+ C.mlx_quantized_matmul(
161
+ o, x.handle, w.handle, scales.handle, biases?.handle ?? 0n,
162
+ transpose, optInt(spec.groupSize), optInt(spec.bits),
163
+ ptr(cstr(spec.mode)), s,
164
+ ),
165
+ ),
166
+ );
167
+ }
168
+
169
+ /** Gathered quantized matmul over stacked expert weights (MoE).
170
+ * x [..., 1, K] with rhs_indices [...] selecting the expert per row;
171
+ * port of mx.gather_qmm as QuantizedSwitchLinear uses it. */
172
+ export function gatherQmm(
173
+ x: MlxArray, w: MlxArray, scales: MlxArray, biases: MlxArray | null,
174
+ rhsIndices: MlxArray, spec: QuantSpec, sortedIndices: boolean,
175
+ transpose = true, s: S = gpuStream,
176
+ ): MlxArray {
177
+ return new MlxArray(
178
+ outArray("gather_qmm", (o) =>
179
+ C.mlx_gather_qmm(
180
+ o, x.handle, w.handle, scales.handle, biases?.handle ?? 0n,
181
+ 0n /* lhs_indices */, rhsIndices.handle, transpose,
182
+ optInt(spec.groupSize), optInt(spec.bits), ptr(cstr(spec.mode)),
183
+ sortedIndices, s,
184
+ ),
185
+ ),
186
+ );
187
+ }
188
+
189
+ export function floorDivide(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
190
+ return new MlxArray(
191
+ outArray("floor_divide", (o) => C.mlx_floor_divide(o, a.handle, b.handle, s)),
192
+ );
193
+ }
194
+
195
+ export function dequantize(
196
+ w: MlxArray, scales: MlxArray, biases: MlxArray | null, spec: QuantSpec,
197
+ s: S = gpuStream,
198
+ ): MlxArray {
199
+ return new MlxArray(
200
+ outArray("dequantize", (o) =>
201
+ C.mlx_dequantize(
202
+ o, w.handle, scales.handle, biases?.handle ?? 0n,
203
+ optInt(spec.groupSize), optInt(spec.bits), ptr(cstr(spec.mode)),
204
+ 0n, optInt(null), s,
205
+ ),
206
+ ),
207
+ );
208
+ }
209
+
210
+ export function takeAxis(a: MlxArray, indices: MlxArray, axis: number, s: S = gpuStream): MlxArray {
211
+ return new MlxArray(
212
+ outArray("take_axis", (o) => C.mlx_take_axis(o, a.handle, indices.handle, axis, s)),
213
+ );
214
+ }
215
+
216
+ export function reshape(a: MlxArray, shape: number[], s: S = gpuStream): MlxArray {
217
+ const buf = new Int32Array(shape);
218
+ return new MlxArray(
219
+ outArray("reshape", (o) => C.mlx_reshape(o, a.handle, ptr(buf), BigInt(shape.length), s)),
220
+ );
221
+ }
222
+
223
+ export function transposeAxes(a: MlxArray, axes: number[], s: S = gpuStream): MlxArray {
224
+ const buf = new Int32Array(axes);
225
+ return new MlxArray(
226
+ outArray("transpose", (o) => C.mlx_transpose_axes(o, a.handle, ptr(buf), BigInt(axes.length), s)),
227
+ );
228
+ }
229
+
230
+ export function argmaxAxis(a: MlxArray, axis: number, s: S = gpuStream): MlxArray {
231
+ return new MlxArray(
232
+ outArray("argmax_axis", (o) => C.mlx_argmax_axis(o, a.handle, axis, false, s)),
233
+ );
234
+ }
235
+
236
+ export function concatAxis(arrays: MlxArray[], axis: number, s: S = gpuStream): MlxArray {
237
+ const handles = new BigUint64Array(arrays.map((a) => a.handle));
238
+ const vec = C.mlx_vector_array_new_data(ptr(handles), BigInt(arrays.length));
239
+ try {
240
+ return new MlxArray(
241
+ outArray("concatenate", (o) => C.mlx_concatenate_axis(o, vec, axis, s)),
242
+ );
243
+ } finally {
244
+ C.mlx_vector_array_free(vec);
245
+ }
246
+ }
247
+
248
+ export function pow(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
249
+ return new MlxArray(outArray("power", (o) => C.mlx_power(o, a.handle, b.handle, s)));
250
+ }
251
+
252
+ /** nn.gelu_approx: 0.5 x (1 + tanh(√(2/π) (x + 0.044715 x³))) — composed
253
+ * exactly as mlx's python source: x**3 is mx.power (NOT x·x·x — they
254
+ * round differently in bf16), scalars promote weakly to x's dtype. */
255
+ export function geluApprox(x: MlxArray, s: S = gpuStream): MlxArray {
256
+ const three = scalarLike(3, x);
257
+ const x3 = pow(x, three, s);
258
+ const cx3 = mulScalar(x3, 0.044715, s);
259
+ const inner = add(x, cx3, s);
260
+ const scaled = mulScalar(inner, Math.sqrt(2 / Math.PI), s);
261
+ const t = tanh(scaled, s);
262
+ const one = scalarLike(1, x);
263
+ const t1 = add(one, t, s);
264
+ const halfx = mulScalar(x, 0.5, s);
265
+ const out = mul(halfx, t1, s);
266
+ for (const a of [three, x3, cx3, inner, scaled, t, one, t1, halfx]) a.dispose();
267
+ return out;
268
+ }
269
+
270
+ export function exp(a: MlxArray, s: S = gpuStream): MlxArray {
271
+ return new MlxArray(outArray("exp", (o) => C.mlx_exp(o, a.handle, s)));
272
+ }
273
+
274
+ export function cos(a: MlxArray, s: S = gpuStream): MlxArray {
275
+ return new MlxArray(outArray("cos", (o) => C.mlx_cos(o, a.handle, s)));
276
+ }
277
+
278
+ export function sin(a: MlxArray, s: S = gpuStream): MlxArray {
279
+ return new MlxArray(outArray("sin", (o) => C.mlx_sin(o, a.handle, s)));
280
+ }
281
+
282
+ export function neg(a: MlxArray, s: S = gpuStream): MlxArray {
283
+ return new MlxArray(outArray("negative", (o) => C.mlx_negative(o, a.handle, s)));
284
+ }
285
+
286
+ export function argsortAxis(a: MlxArray, axis: number, s: S = gpuStream): MlxArray {
287
+ return new MlxArray(outArray("argsort", (o) => C.mlx_argsort_axis(o, a.handle, axis, s)));
288
+ }
289
+
290
+ export function argpartitionAxis(a: MlxArray, kth: number, axis: number, s: S = gpuStream): MlxArray {
291
+ return new MlxArray(
292
+ outArray("argpartition", (o) => C.mlx_argpartition_axis(o, a.handle, kth, axis, s)),
293
+ );
294
+ }
295
+
296
+ export function takeAlongAxis(a: MlxArray, idx: MlxArray, axis: number, s: S = gpuStream): MlxArray {
297
+ return new MlxArray(
298
+ outArray("take_along_axis", (o) => C.mlx_take_along_axis(o, a.handle, idx.handle, axis, s)),
299
+ );
300
+ }
301
+
302
+ export function putAlongAxis(
303
+ a: MlxArray, idx: MlxArray, values: MlxArray, axis: number, s: S = gpuStream,
304
+ ): MlxArray {
305
+ return new MlxArray(
306
+ outArray("put_along_axis", (o) =>
307
+ C.mlx_put_along_axis(o, a.handle, idx.handle, values.handle, axis, s),
308
+ ),
309
+ );
310
+ }
311
+
312
+ export function cumsum(a: MlxArray, axis: number, s: S = gpuStream): MlxArray {
313
+ return new MlxArray(
314
+ outArray("cumsum", (o) => C.mlx_cumsum(o, a.handle, axis, false, true, s)),
315
+ );
316
+ }
317
+
318
+ export function where(cond: MlxArray, x: MlxArray, y: MlxArray, s: S = gpuStream): MlxArray {
319
+ return new MlxArray(
320
+ outArray("where", (o) => C.mlx_where(o, cond.handle, x.handle, y.handle, s)),
321
+ );
322
+ }
323
+
324
+ // arange is built host-side and uploaded. The original mlx_arange binding
325
+ // broke once the calling path got JIT-optimized; a standalone repro
326
+ // (repro/bun-ffi-f64/) traced the root cause to a Bun bug: after DFG
327
+ // tier-up, typed-array reads following a bun:ffi call return stale values
328
+ // (the JIT eliminates the load across the native call). Not f64 marshaling
329
+ // — args reach C intact. See PLAN.md Phase 4 findings. Large constant
330
+ // ranges (topP's vocab arange) are cached for the process lifetime.
331
+ const arangeCache = new Map<string, MlxArray>();
332
+ const ARANGE_CACHE_MIN = 65536;
333
+
334
+ export function arange(start: number, stop: number, step: number, dtype: Dtype, s: S = gpuStream): MlxArray {
335
+ if (!Number.isInteger(start) || !Number.isInteger(step) || step === 0)
336
+ throw new Error(`arange: integer start/step required (${start}, ${stop}, ${step})`);
337
+ const n = Math.max(0, Math.ceil((stop - start) / step));
338
+ const key = `${start}|${step}|${n}|${dtype}`;
339
+ const cached = arangeCache.get(key);
340
+ if (cached) return cached.slice([0], [n], s);
341
+
342
+ const data = new Int32Array(n);
343
+ for (let i = 0; i < n; i++) data[i] = start + i * step;
344
+ let arr = MlxArray.fromInt32(data, [n]);
345
+ if (dtype !== Dtype.int32) {
346
+ const cast = arr.astype(dtype, s);
347
+ arr.dispose();
348
+ arr = cast;
349
+ }
350
+ if (n >= ARANGE_CACHE_MIN) {
351
+ arangeCache.set(key, arr);
352
+ return arr.slice([0], [n], s);
353
+ }
354
+ return arr;
355
+ }
356
+
357
+ export function logsumexpAxis(a: MlxArray, axis: number, keepdims: boolean, s: S = gpuStream): MlxArray {
358
+ return new MlxArray(
359
+ outArray("logsumexp", (o) => C.mlx_logsumexp_axis(o, a.handle, axis, keepdims, s)),
360
+ );
361
+ }
362
+
363
+ export function greaterEqual(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
364
+ return new MlxArray(
365
+ outArray("greater_equal", (o) => C.mlx_greater_equal(o, a.handle, b.handle, s)),
366
+ );
367
+ }
368
+
369
+ export function less(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
370
+ return new MlxArray(outArray("less", (o) => C.mlx_less(o, a.handle, b.handle, s)));
371
+ }
372
+
373
+ export function logicalAnd(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
374
+ return new MlxArray(
375
+ outArray("logical_and", (o) => C.mlx_logical_and(o, a.handle, b.handle, s)),
376
+ );
377
+ }
378
+
379
+ export function zeros(shape: number[], dtype: Dtype, s: S = gpuStream): MlxArray {
380
+ const buf = new Int32Array(shape);
381
+ return new MlxArray(
382
+ outArray("zeros", (o) => C.mlx_zeros(o, ptr(buf), BigInt(shape.length), dtype, s)),
383
+ );
384
+ }
385
+
386
+ export function sliceUpdate(
387
+ src: MlxArray, update: MlxArray, start: number[], stop: number[], s: S = gpuStream,
388
+ ): MlxArray {
389
+ const st = new Int32Array(start);
390
+ const sp = new Int32Array(stop);
391
+ const strides = new Int32Array(start.map(() => 1));
392
+ return new MlxArray(
393
+ outArray("slice_update", (o) =>
394
+ C.mlx_slice_update(
395
+ o, src.handle, update.handle,
396
+ ptr(st), BigInt(st.length), ptr(sp), BigInt(sp.length),
397
+ ptr(strides), BigInt(strides.length), s,
398
+ ),
399
+ ),
400
+ );
401
+ }
402
+
403
+ /** mx.slice with start as an ARRAY and the output shape as constants —
404
+ * the shapeless-compile-safe slice (mlx's Slice primitive cannot infer
405
+ * output shapes; DynamicSlice's output shape IS sliceSize). Used only
406
+ * inside compiled-decode traces for subrange slices whose bounds are
407
+ * per-layer constants (full-range slices get simplified away and need
408
+ * no substitute). Values identical to `slice`. */
409
+ export function sliceDynamic(
410
+ a: MlxArray, start: MlxArray, axes: number[], sliceSize: number[], s: S = gpuStream,
411
+ ): MlxArray {
412
+ const ax = new Int32Array(axes);
413
+ const sz = new Int32Array(sliceSize);
414
+ return new MlxArray(
415
+ outArray("slice_dynamic", (o) =>
416
+ C.mlx_slice_dynamic(
417
+ o, a.handle, start.handle,
418
+ ptr(ax), BigInt(ax.length), ptr(sz), BigInt(sz.length), s,
419
+ ),
420
+ ),
421
+ );
422
+ }
423
+
424
+ /** slice_update with the start index as an ARRAY (one int32 per entry in
425
+ * `axes`) — the compiled-decode form of the per-step cache write. Same
426
+ * write as `sliceUpdate`; bit-exactness vs the static form is asserted
427
+ * in tests/compile.test.ts. */
428
+ export function sliceUpdateDynamic(
429
+ src: MlxArray, update: MlxArray, start: MlxArray, axes: number[], s: S = gpuStream,
430
+ ): MlxArray {
431
+ const ax = new Int32Array(axes);
432
+ return new MlxArray(
433
+ outArray("slice_update_dynamic", (o) =>
434
+ C.mlx_slice_update_dynamic(
435
+ o, src.handle, update.handle, start.handle,
436
+ ptr(ax), BigInt(ax.length), s,
437
+ ),
438
+ ),
439
+ );
440
+ }
441
+
442
+ export function randomKey(seed: bigint): MlxArray {
443
+ return new MlxArray(outArray("random_key", (o) => C.mlx_random_key(o, seed)));
444
+ }
445
+
446
+ export function randomCategorical(logits: MlxArray, key: MlxArray | null, s: S = gpuStream): MlxArray {
447
+ return new MlxArray(
448
+ outArray("categorical", (o) =>
449
+ C.mlx_random_categorical(o, logits.handle, -1, key?.handle ?? 0n, s),
450
+ ),
451
+ );
452
+ }
453
+
454
+ /** Eval several arrays in one call (e.g. cache state after a prefill chunk). */
455
+ export function evalAll(arrays: MlxArray[]): void {
456
+ const handles = new BigUint64Array(arrays.map((a) => a.handle));
457
+ const vec = C.mlx_vector_array_new_data(ptr(handles), BigInt(arrays.length));
458
+ try {
459
+ if (C.mlx_eval(vec) !== 0) throw new Error(`mlx_eval failed: ${takeMlxError() ?? ""}`);
460
+ } finally {
461
+ C.mlx_vector_array_free(vec);
462
+ }
463
+ }
464
+
465
+ /** Kick off async evaluation (decode pipelining). */
466
+ export function asyncEvalAll(arrays: MlxArray[]): void {
467
+ const handles = new BigUint64Array(arrays.map((a) => a.handle));
468
+ const vec = C.mlx_vector_array_new_data(ptr(handles), BigInt(arrays.length));
469
+ try {
470
+ if (C.mlx_async_eval(vec) !== 0) throw new Error(`mlx_async_eval failed: ${takeMlxError() ?? ""}`);
471
+ } finally {
472
+ C.mlx_vector_array_free(vec);
473
+ }
474
+ }
475
+
476
+ /** Read a scalar uint32 (forces eval). Hot per-token path: the out-param
477
+ * is read back via read.u32, never out[0] (DFG stale-read bug — see
478
+ * outArray in ffi.ts). */
479
+ export function itemUint32(a: MlxArray): number {
480
+ a.eval();
481
+ const out = new Uint32Array(1);
482
+ const p = ptr(out);
483
+ if (C.mlx_array_item_uint32(p, a.handle) !== 0)
484
+ throw new Error("mlx_array_item_uint32 failed");
485
+ return read.u32(p, 0);
486
+ }
487
+
488
+ export function fromInt32(data: number[], shape: number[]): MlxArray {
489
+ return MlxArray.fromInt32(new Int32Array(data), shape);
490
+ }
491
+
492
+ // --- quantized KV support (Phase 6) ---------------------------------------
493
+
494
+ export interface QuantizedTensor {
495
+ packed: MlxArray;
496
+ scales: MlxArray;
497
+ biases: MlxArray;
498
+ }
499
+
500
+ /** mx.quantize: w → (packed u32, scales, biases). `mode` defaults to the
501
+ * mlx-standard "affine" scheme; the quantizer seam may pass others. */
502
+ export function quantize(
503
+ w: MlxArray, groupSize: number, bits: number, mode = "affine", s: S = gpuStream,
504
+ ): QuantizedTensor {
505
+ const slot = new BigUint64Array([C.mlx_vector_array_new()]);
506
+ const slotPtr = ptr(slot);
507
+ const status = C.mlx_quantize(
508
+ slotPtr, w.handle, optInt(groupSize), optInt(bits), ptr(cstr(mode)), 0n, s,
509
+ );
510
+ if (status !== 0) throw new Error(`quantize failed: ${takeMlxError() ?? ""}`);
511
+ const vec = read.u64(slotPtr, 0);
512
+ const get = (i: number): MlxArray => {
513
+ const aSlot = new BigUint64Array([C.mlx_array_new()]);
514
+ const aPtr = ptr(aSlot);
515
+ if (C.mlx_vector_array_get(aPtr, vec, BigInt(i)) !== 0)
516
+ throw new Error("vector_array_get failed");
517
+ return new MlxArray(read.u64(aPtr, 0));
518
+ };
519
+ try {
520
+ return { packed: get(0), scales: get(1), biases: get(2) };
521
+ } finally {
522
+ C.mlx_vector_array_free(vec);
523
+ }
524
+ }
525
+
526
+ // --- training: elementwise / reductions / random ------------------------
527
+
528
+ /** Stops gradient flow through `a` (identity forward; ∂=0). DPO reference
529
+ * logprobs are stop_gradient'd before the policy forward. */
530
+ export function stopGradient(a: MlxArray, s: S = gpuStream): MlxArray {
531
+ return new MlxArray(outArray("stop_gradient", (o) => C.mlx_stop_gradient(o, a.handle, s)));
532
+ }
533
+
534
+ export function log(a: MlxArray, s: S = gpuStream): MlxArray {
535
+ return new MlxArray(outArray("log", (o) => C.mlx_log(o, a.handle, s)));
536
+ }
537
+
538
+ export function logaddexp(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
539
+ return new MlxArray(outArray("logaddexp", (o) => C.mlx_logaddexp(o, a.handle, b.handle, s)));
540
+ }
541
+
542
+ export function sqrt(a: MlxArray, s: S = gpuStream): MlxArray {
543
+ return new MlxArray(outArray("sqrt", (o) => C.mlx_sqrt(o, a.handle, s)));
544
+ }
545
+
546
+ export function rsqrt(a: MlxArray, s: S = gpuStream): MlxArray {
547
+ return new MlxArray(outArray("rsqrt", (o) => C.mlx_rsqrt(o, a.handle, s)));
548
+ }
549
+
550
+ export function square(a: MlxArray, s: S = gpuStream): MlxArray {
551
+ return new MlxArray(outArray("square", (o) => C.mlx_square(o, a.handle, s)));
552
+ }
553
+
554
+ export function abs(a: MlxArray, s: S = gpuStream): MlxArray {
555
+ return new MlxArray(outArray("abs", (o) => C.mlx_abs(o, a.handle, s)));
556
+ }
557
+
558
+ /** Reduce-mean over all elements → scalar (keepdims=false) or [1,…]. */
559
+ export function meanAll(a: MlxArray, keepdims = false, s: S = gpuStream): MlxArray {
560
+ return new MlxArray(outArray("mean", (o) => C.mlx_mean(o, a.handle, keepdims, s)));
561
+ }
562
+
563
+ export function meanAxis(a: MlxArray, axis: number, keepdims: boolean, s: S = gpuStream): MlxArray {
564
+ return new MlxArray(outArray("mean_axis", (o) => C.mlx_mean_axis(o, a.handle, axis, keepdims, s)));
565
+ }
566
+
567
+ export function clip(a: MlxArray, lo: MlxArray | null, hi: MlxArray | null, s: S = gpuStream): MlxArray {
568
+ return new MlxArray(
569
+ outArray("clip", (o) => C.mlx_clip(o, a.handle, lo?.handle ?? 0n, hi?.handle ?? 0n, s)),
570
+ );
571
+ }
572
+
573
+ /** mx.random.normal(shape, dtype, loc, scale, key). key may be null. */
574
+ export function randomNormal(
575
+ shape: number[], dtype: Dtype, loc: number, scale: number,
576
+ key: MlxArray | null, s: S = gpuStream,
577
+ ): MlxArray {
578
+ const buf = new Int32Array(shape);
579
+ return new MlxArray(
580
+ outArray("random_normal", (o) =>
581
+ C.mlx_random_normal(o, ptr(buf), BigInt(shape.length), dtype, loc, scale, key?.handle ?? 0n, s),
582
+ ),
583
+ );
584
+ }
585
+
586
+ /** mx.random.uniform(low, high, shape, dtype, key). low/high are scalars.
587
+ * Matches mlx-lm LoRALinear A init: uniform(-1/√in, +1/√in). */
588
+ export function randomUniform(
589
+ shape: number[], dtype: Dtype, low: number, high: number,
590
+ key: MlxArray | null, s: S = gpuStream,
591
+ ): MlxArray {
592
+ const buf = new Int32Array(shape);
593
+ const lo = MlxArray.fromFloat32(new Float32Array([low]), []);
594
+ const hi = MlxArray.fromFloat32(new Float32Array([high]), []);
595
+ try {
596
+ return new MlxArray(
597
+ outArray("random_uniform", (o) =>
598
+ C.mlx_random_uniform(o, lo.handle, hi.handle, ptr(buf), BigInt(shape.length), dtype, key?.handle ?? 0n, s),
599
+ ),
600
+ );
601
+ } finally {
602
+ lo.dispose();
603
+ hi.dispose();
604
+ }
605
+ }
606
+
607
+ /** mx.random.split(key, num) → [num, 2] uint32 subkeys. */
608
+ export function randomSplitNum(key: MlxArray, num: number, s: S = gpuStream): MlxArray {
609
+ return new MlxArray(
610
+ outArray("random_split", (o) => C.mlx_random_split_num(o, key.handle, num, s)),
611
+ );
612
+ }
613
+
614
+ export function softmaxAxis(a: MlxArray, axis: number, precise: boolean, s: S = gpuStream): MlxArray {
615
+ return new MlxArray(
616
+ outArray("softmax", (o) => C.mlx_softmax_axis(o, a.handle, axis, precise, s)),
617
+ );
618
+ }
619
+
620
+ export function maxAxis(a: MlxArray, axis: number, keepdims: boolean, s: S = gpuStream): MlxArray {
621
+ return new MlxArray(
622
+ outArray("max_axis", (o) => C.mlx_max_axis(o, a.handle, axis, keepdims, s)),
623
+ );
624
+ }
625
+
626
+ export function sumAxis(a: MlxArray, axis: number, keepdims: boolean, s: S = gpuStream): MlxArray {
627
+ return new MlxArray(
628
+ outArray("sum_axis", (o) => C.mlx_sum_axis(o, a.handle, axis, keepdims, s)),
629
+ );
630
+ }
631
+
632
+ /** View-preserving expand_dims (reshape on a non-contiguous view copies —
633
+ * different kernel path, different reduction rounding; see Phase 6). */
634
+ export function expandDims(a: MlxArray, axis: number, s: S = gpuStream): MlxArray {
635
+ return new MlxArray(
636
+ outArray("expand_dims", (o) => C.mlx_expand_dims(o, a.handle, axis, s)),
637
+ );
638
+ }
639
+
640
+ export function maximum(a: MlxArray, b: MlxArray, s: S = gpuStream): MlxArray {
641
+ return new MlxArray(
642
+ outArray("maximum", (o) => C.mlx_maximum(o, a.handle, b.handle, s)),
643
+ );
644
+ }
645
+
646
+ /** quantized_matmul against a quantized tensor (x @ qt^T or x @ qt). */
647
+ export function quantizedMatmulQT(
648
+ x: MlxArray, qt: QuantizedTensor, transpose: boolean,
649
+ groupSize: number, bits: number, s: S = gpuStream,
650
+ ): MlxArray {
651
+ return new MlxArray(
652
+ outArray("quantized_matmul", (o) =>
653
+ C.mlx_quantized_matmul(
654
+ o, x.handle, qt.packed.handle, qt.scales.handle, qt.biases.handle,
655
+ transpose, optInt(groupSize), optInt(bits), ptr(cstr("affine")), s,
656
+ ),
657
+ ),
658
+ );
659
+ }
660
+
661
+ /** nn.Conv1d: input [B, L, C_in], weight [C_out, K, C_in/groups] → [B, L', C_out].
662
+ * Depthwise (groups == channels) for the gated-DeltaNet causal conv; the model
663
+ * left-pads with the conv-state cache, so padding is 0 here. */
664
+ export function conv1d(
665
+ input: MlxArray, weight: MlxArray, stride = 1, padding = 0, dilation = 1,
666
+ groups = 1, s: S = gpuStream,
667
+ ): MlxArray {
668
+ return new MlxArray(
669
+ outArray("conv1d", (o) =>
670
+ C.mlx_conv1d(o, input.handle, weight.handle, stride, padding, dilation, groups, s),
671
+ ),
672
+ );
673
+ }
674
+
675
+ /** mx.split(a, indices, axis): split at the given boundary offsets along `axis`
676
+ * into N+1 contiguous slices (mirrors numpy/mlx split-by-indices). */
677
+ export function split(a: MlxArray, indices: number[], axis: number): MlxArray[] {
678
+ const shape = a.shape;
679
+ const ax = axis < 0 ? shape.length + axis : axis;
680
+ const dim = shape[ax]!;
681
+ const bounds = [0, ...indices, dim];
682
+ const out: MlxArray[] = [];
683
+ for (let i = 0; i < bounds.length - 1; i++) {
684
+ const start = shape.map(() => 0);
685
+ const stop = [...shape];
686
+ start[ax] = bounds[i]!;
687
+ stop[ax] = bounds[i + 1]!;
688
+ out.push(a.slice(start, stop));
689
+ }
690
+ return out;
691
+ }
692
+
693
+ /** nn.softplus: log(1 + exp(x)) == logaddexp(x, 0). */
694
+ export function softplus(a: MlxArray, s: S = gpuStream): MlxArray {
695
+ const zero = scalarLike(0, a);
696
+ const r = logaddexp(a, zero, s);
697
+ zero.dispose();
698
+ return r;
699
+ }
700
+
701
+ /** nn.silu: x * sigmoid(x). */
702
+ export function silu(a: MlxArray, s: S = gpuStream): MlxArray {
703
+ const sig = sigmoid(a, s);
704
+ const r = mul(a, sig, s);
705
+ sig.dispose();
706
+ return r;
707
+ }