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,312 +0,0 @@
1
- // Fused decode-SDPA Metal kernel, v2: flash-decoding split-N
2
- // (docs/design/optimization_plan.md Phase E). L=1 attention over a QUANTIZED KV
3
- // cache with the dequant inlined — neither the score row nor the
4
- // dequantized cache is ever materialized. Dispatched from Bun via
5
- // mx.fast.metal_kernel (no MLX fork), always OUTSIDE compiled-decode
6
- // closures (CustomKernel has no output_shapes; compiled segments
7
- // reclassify perf-kernel layers as JS layers instead).
8
- //
9
- // v1 post-mortem (git 48c9b10): numerics landed at the accepted tier-b
10
- // envelope but ONE THREADGROUP PER QUERY HEAD (16 total on the 12B)
11
- // could not occupy the GPU — 0.72× @8k. v2 splits the KV rows into
12
- // G = ceil(N/BLOCK) blocks (grid [128, H, G]):
13
- // kernel A: per-block (max, sumexp) over T-rounded scores
14
- // kernel C: per-block Σ T(exp(s−M)/denom)·dequant(V) partials, with
15
- // the full-row (M, denom) derived inline from A's stats —
16
- // deterministic (no atomics: atomic add order would make
17
- // rounding nondeterministic)
18
- // merge: one mlx sum over G + cast to T (deterministic order)
19
- // Rounding points still MATCH the compat path where it matters (scores
20
- // and normalized probs round to T; f32 accumulation) — per-layer
21
- // divergence stays at the stray-ulp scale the teacher-forced gate is
22
- // calibrated for (tests/perf-kernel-oracle.test.ts).
23
-
24
- import { MlxArray } from "../mlx/array";
25
- import { Dtype } from "../mlx/ffi";
26
- import { MetalKernel } from "../mlx/metal-kernel";
27
- import * as ops from "../mlx/ops";
28
-
29
- /** Perf-mode lever: DEFAULT ON (2026-06-11 cleared-machine pass, ~1.02x @8k)
30
- * — but an L3/explicit-only lever, OFF in the bare --l2 tier. This is an
31
- * mlx-bun ORIGINAL kernel, NOT a port of optiq's fused decode, and the
32
- * quantized-KV parity goldens do NOT track it: the optiq-golden decode
33
- * composition is stock UNFUSED L=1 (scripts/regen-kvq-goldens.ts — the fused
34
- * N-tiled SDPA covers L>1 prefill only, where this kernel never dispatches).
35
- * It is NOT bit-exact vs the -O0 reference (online softmax, tier-b); its gate
36
- * is ENVELOPE quality — ≥56/64 teacher-forced argmax agreement vs mlx-bun's
37
- * own frozen compat trajectory (tests/perf-kernel-oracle.test.ts,
38
- * scripts/freeze-perf-oracle.ts) plus bounded divergence vs the unfused port
39
- * (tests/fused-decode-kernel.test.ts). MLX_BUN_PERF_KERNEL=0 selects the
40
- * compat path we bench against the python libs. (B/C-style bit-exact
41
- * optimizations stay on either way — this lever only toggles the
42
- * parity-breaking decode kernel.) */
43
- export function perfKernelEnabled(): boolean {
44
- return process.env.MLX_BUN_PERF_KERNEL !== "0";
45
- }
46
-
47
- /** Dispatch counter (the oracle gate asserts the kernel actually ran). */
48
- export let fusedKernelCalls = 0;
49
-
50
- const SIMD_GROUPS = 4;
51
- const LANES = 32;
52
- const TG_THREADS = SIMD_GROUPS * LANES;
53
-
54
- // Shared per-row score computation: lane-sliced dot product with the
55
- // dequant FACTORED out of the inner loop (the mlx qdot pattern,
56
- // quantized.h, Apache-2.0 — translated into this kernel's dialect):
57
- // Σ (s·w_e + b)·q_e = s·Σ(w_e·q_e) + b·Σ q_e
58
- // and for 4-bit the nibbles multiply MASKED-BUT-UNSHIFTED against a
59
- // query pre-divided by 16^k — the inner loop is mask+madd only, via
60
- // 16-bit reads. Lane slices are group-aligned for D ∈ {256, 512},
61
- // GS ∈ {32, 64, 128}: one scale/bias per lane per row.
62
- const SCORE_PREAMBLE = String.raw`
63
- constexpr int SG = ${SIMD_GROUPS};
64
- constexpr int ELEMS = D / ${LANES};
65
- constexpr int PER_WORD = 32 / BITS;
66
- constexpr int WORDS = ELEMS / PER_WORD;
67
-
68
- const uint lane = thread_index_in_simdgroup;
69
- const uint sg = simdgroup_index_in_threadgroup;
70
- const uint h = thread_position_in_grid.y;
71
- const uint g = thread_position_in_grid.z;
72
- const uint kv = h / NREP;
73
- const int N = kp_shape[2];
74
- const int G = (N + BLOCK - 1) / BLOCK;
75
- const int nStart = (int)g * BLOCK;
76
- const int nEnd = metal::min(nStart + BLOCK, N);
77
- const int kRowWords = D / PER_WORD;
78
- const int gRow = D / GS;
79
-
80
- // lane's q slice: raw sum for the bias term, and (4-bit) nibble-place
81
- // pre-scaled copies for the no-shift masked dot
82
- float qv[ELEMS];
83
- float qSum = 0.0f;
84
- const uint qBase = h * D + lane * ELEMS;
85
- for (int e = 0; e < ELEMS; e++) {
86
- const float v = float(q[qBase + e]);
87
- qSum += v;
88
- qv[e] = BITS == 4
89
- ? v * ((e & 3) == 0 ? 1.0f : (e & 3) == 1 ? 0.0625f : (e & 3) == 2 ? 0.00390625f : 0.000244140625f)
90
- : v;
91
- }
92
-
93
- const int grpIdx = (lane * ELEMS) / GS;
94
- `;
95
-
96
- const SCORE_ROW = String.raw`
97
- const ulong rowOff = ((ulong)kv * (ulong)N + (ulong)n);
98
- const float kScale = float(ks[rowOff * gRow + grpIdx]);
99
- const float kBias = float(kb[rowOff * gRow + grpIdx]);
100
- float accum = 0.0f;
101
- if (BITS == 4) {
102
- const device uint16_t* kw =
103
- ((const device uint16_t*)(kp + rowOff * kRowWords)) + lane * (ELEMS / 4);
104
- for (int i = 0; i < ELEMS / 4; i++) {
105
- const uint16_t wv = kw[i];
106
- accum += qv[4 * i] * (float)(wv & 0x000f)
107
- + qv[4 * i + 1] * (float)(wv & 0x00f0)
108
- + qv[4 * i + 2] * (float)(wv & 0x0f00)
109
- + qv[4 * i + 3] * (float)(wv & 0xf000);
110
- }
111
- } else {
112
- const device uint8_t* kw8 =
113
- ((const device uint8_t*)(kp + rowOff * kRowWords)) + lane * ELEMS;
114
- for (int e = 0; e < ELEMS; e++) accum += qv[e] * (float)kw8[e];
115
- }
116
- const float partial = kScale * accum + qSum * kBias;
117
- const float s = float(T(simd_sum(partial)));
118
- `;
119
-
120
- // kernel: single-pass per-block ONLINE softmax — K and V each read
121
- // exactly once (the two-pass v2.0 read K twice and measured 0.945);
122
- // per-block (max, sumexp) and unnormalized o land in stats/opart for
123
- // the merge kernel.
124
- const BLOCK_SOURCE = SCORE_PREAMBLE + String.raw`
125
- float mx = -INFINITY;
126
- float sumexp = 0.0f;
127
- // V accumulates RAW masked nibbles per slot (factor 16^k folds once at
128
- // the end) with the bias term collapsed to one scalar: Σ p·b — the
129
- // qdot factoring applied to the value side.
130
- float oacc[ELEMS];
131
- for (int e = 0; e < ELEMS; e++) oacc[e] = 0.0f;
132
- float pbSum = 0.0f;
133
-
134
- for (int n = nStart + (int)sg; n < nEnd; n += SG) {
135
- ${SCORE_ROW}
136
- const float newmax = metal::max(mx, s);
137
- const float factor = metal::exp(mx - newmax);
138
- const float p = metal::exp(s - newmax);
139
- sumexp = sumexp * factor + p;
140
- mx = newmax;
141
-
142
- const float vScale = float(vs[rowOff * gRow + grpIdx]);
143
- const float vBias = float(vb[rowOff * gRow + grpIdx]);
144
- const float ps = p * vScale;
145
- pbSum = pbSum * factor + p * vBias;
146
- if (BITS == 4) {
147
- const device uint16_t* vw =
148
- ((const device uint16_t*)(vp + rowOff * kRowWords)) + lane * (ELEMS / 4);
149
- for (int i = 0; i < ELEMS / 4; i++) {
150
- const uint16_t wv = vw[i];
151
- oacc[4 * i] = oacc[4 * i] * factor + ps * (float)(wv & 0x000f);
152
- oacc[4 * i + 1] = oacc[4 * i + 1] * factor + ps * (float)(wv & 0x00f0);
153
- oacc[4 * i + 2] = oacc[4 * i + 2] * factor + ps * (float)(wv & 0x0f00);
154
- oacc[4 * i + 3] = oacc[4 * i + 3] * factor + ps * (float)(wv & 0xf000);
155
- }
156
- } else {
157
- const device uint8_t* vw8 =
158
- ((const device uint8_t*)(vp + rowOff * kRowWords)) + lane * ELEMS;
159
- for (int e = 0; e < ELEMS; e++)
160
- oacc[e] = oacc[e] * factor + ps * (float)vw8[e];
161
- }
162
- }
163
- // fold the nibble-place factors and the bias sum (once per block)
164
- for (int e = 0; e < ELEMS; e++) {
165
- const float inv = BITS == 4
166
- ? ((e & 3) == 0 ? 1.0f : (e & 3) == 1 ? 0.0625f : (e & 3) == 2 ? 0.00390625f : 0.000244140625f)
167
- : 1.0f;
168
- oacc[e] = oacc[e] * inv + pbSum;
169
- }
170
-
171
- // cross-simdgroup online merge within the block
172
- threadgroup float tgMax[SG];
173
- threadgroup float tgSum[SG];
174
- threadgroup float tgO[SG * D];
175
- if (lane == 0) {
176
- tgMax[sg] = mx;
177
- tgSum[sg] = sumexp;
178
- }
179
- for (int e = 0; e < ELEMS; e++) tgO[sg * D + lane * ELEMS + e] = oacc[e];
180
- threadgroup_barrier(mem_flags::mem_threadgroup);
181
- if (sg == 0) {
182
- float M = -INFINITY;
183
- for (int gg = 0; gg < SG; gg++) M = metal::max(M, tgMax[gg]);
184
- float S = 0.0f;
185
- float wgt[SG];
186
- for (int gg = 0; gg < SG; gg++) {
187
- wgt[gg] = metal::exp(tgMax[gg] - M);
188
- S += tgSum[gg] * wgt[gg];
189
- }
190
- for (int e = 0; e < ELEMS; e++) {
191
- const int d = lane * ELEMS + e;
192
- float o = 0.0f;
193
- for (int gg = 0; gg < SG; gg++) o += tgO[gg * D + d] * wgt[gg];
194
- opart[((ulong)h * (ulong)G + (ulong)g) * (ulong)D + (ulong)d] = o;
195
- }
196
- if (lane == 0) {
197
- stats[(h * (uint)G + g) * 2 + 0] = M;
198
- stats[(h * (uint)G + g) * 2 + 1] = S;
199
- }
200
- }
201
- `;
202
-
203
- // merge kernel: out[h,d] = Σ_g opart[h,g,d]·exp(m_g−M) / denom, cast T.
204
- // Deterministic (fixed g order), one tiny dispatch.
205
- const MERGE_SOURCE = String.raw`
206
- const uint d = thread_position_in_grid.x;
207
- const uint h = thread_position_in_grid.y;
208
- const int G = stats_shape[2];
209
- if ((int)d >= D) return;
210
- float M = -INFINITY;
211
- for (int g = 0; g < G; g++)
212
- M = metal::max(M, stats[(h * (uint)G + (uint)g) * 2 + 0]);
213
- float denom = 0.0f;
214
- for (int g = 0; g < G; g++)
215
- denom += stats[(h * (uint)G + (uint)g) * 2 + 1] *
216
- metal::exp(stats[(h * (uint)G + (uint)g) * 2 + 0] - M);
217
- float o = 0.0f;
218
- for (int g = 0; g < G; g++)
219
- o += opart[((ulong)h * (ulong)G + (ulong)g) * (ulong)D + (ulong)d] *
220
- metal::exp(stats[(h * (uint)G + (uint)g) * 2 + 0] - M);
221
- out[h * (uint)D + d] = T(o / denom);
222
- `;
223
-
224
- let blockKernel: MetalKernel | null = null;
225
- let mergeKernel: MetalKernel | null = null;
226
-
227
- function kernels(): { block: MetalKernel; merge: MetalKernel } {
228
- if (!blockKernel) {
229
- blockKernel = new MetalKernel({
230
- name: "mlx_bun_fqsdpa_block",
231
- inputNames: ["q", "kp", "ks", "kb", "vp", "vs", "vb"],
232
- outputNames: ["opart", "stats"],
233
- source: BLOCK_SOURCE,
234
- ensureRowContiguous: true,
235
- });
236
- mergeKernel = new MetalKernel({
237
- name: "mlx_bun_fqsdpa_merge",
238
- inputNames: ["opart", "stats"],
239
- outputNames: ["out"],
240
- source: MERGE_SOURCE,
241
- ensureRowContiguous: true,
242
- });
243
- }
244
- return { block: blockKernel, merge: mergeKernel! };
245
- }
246
-
247
- /** Split width: enough blocks to occupy the GPU at small N without
248
- * shrinking blocks into per-dispatch overhead at large N. */
249
- function blockSize(n: number): number {
250
- return n <= 2048 ? 128 : 512;
251
- }
252
-
253
- /** Is this dispatch site servable by the fused kernel? (Phase D bakes
254
- * bits/gs at generation time; shape checks guard runtime drift.) */
255
- export function fusedDecodeKernelSupported(
256
- q: MlxArray, bits: number, groupSize: number,
257
- ): boolean {
258
- if (bits !== 4 && bits !== 8) return false;
259
- if (groupSize !== 32 && groupSize !== 64 && groupSize !== 128) return false;
260
- if (q.dtype !== Dtype.bfloat16) return false;
261
- const [B, , L, D] = q.shape as [number, number, number, number];
262
- if (B !== 1 || L !== 1) return false;
263
- if (D % LANES !== 0) return false;
264
- const elems = D / LANES;
265
- if ((elems * bits) % 32 !== 0) return false; // whole u32 words per lane
266
- // lane slice must sit inside one quantization group
267
- if (elems > groupSize || groupSize % elems !== 0) return false;
268
- return true;
269
- }
270
-
271
- /** Fused decode attention over quantized KV (flash-decoding split-N).
272
- * Caller guarantees fusedDecodeKernelSupported and mask mode "" (decode
273
- * attends the whole fetched window). q [1,H,1,D] bf16. */
274
- export function fusedDecodeSdpa(
275
- q: MlxArray, kq: ops.QuantizedTensor, vq: ops.QuantizedTensor,
276
- groupSize: number, bits: number,
277
- ): MlxArray {
278
- const [, H, , D] = q.shape as [number, number, number, number];
279
- const KV = kq.packed.shape[1]!;
280
- const N = kq.packed.shape[2]!;
281
- const nRep = H / KV;
282
- const BLOCK = blockSize(N);
283
- const G = Math.ceil(N / BLOCK);
284
- fusedKernelCalls++;
285
-
286
- const { block, merge } = kernels();
287
- const tmpl = { D, GS: groupSize, BITS: bits, NREP: nRep, BLOCK };
288
-
289
- const [opart, statsArr] = block.apply(
290
- [q, kq.packed, kq.scales, kq.biases, vq.packed, vq.scales, vq.biases],
291
- {
292
- outputs: [
293
- { shape: [1, H, G, D], dtype: Dtype.float32 },
294
- { shape: [1, H, G, 2], dtype: Dtype.float32 },
295
- ],
296
- grid: [TG_THREADS, H, G],
297
- threadGroup: [TG_THREADS, 1, 1],
298
- templateInts: tmpl,
299
- templateDtypes: { T: q.dtype },
300
- },
301
- );
302
- const [out] = merge.apply([opart!, statsArr!], {
303
- outputs: [{ shape: [1, H, 1, D], dtype: q.dtype }],
304
- grid: [D, H, 1],
305
- threadGroup: [Math.min(D, 256), 1, 1],
306
- templateInts: { D },
307
- templateDtypes: { T: q.dtype },
308
- });
309
- opart!.dispose();
310
- statsArr!.dispose();
311
- return out!;
312
- }
@@ -1,198 +0,0 @@
1
- // Fused GeGLU Metal kernel (optimization_plan Phase E, opportunity B/C):
2
- // gelu_approx(a)·b in ONE pass, replacing the ~9 element-wise kernels the
3
- // spelled-out path emits (power, ×3, +, ×, tanh, +, ×, × + a final mul).
4
- // Both GeGLU sites are this shape — MLP (gelu(gate)·up) and the e4b
5
- // per-layer gate (gelu(gate)·pli).
6
- //
7
- // MATCH-COMPAT precision: bf16 in/out, each intermediate rounded to bf16
8
- // op-for-op with the spelled-out path (the mlx-lm parity constraint). A
9
- // first full-f32 version drifted 0.22 KL on e4b (small per-element error
10
- // compounding over 42 MLPs) — match-compat keeps the distribution on the
11
- // trained path; the only residual is the pow/tanh math-lib difference, which
12
- // the KL gate quantifies. Still a perf kernel (not bit-exact) and never
13
- // inside a compiled-decode trace: CustomKernel has no output_shapes (same
14
- // rule as fusedDecodeSdpa) — the caller guards on !isCompiledTrace().
15
-
16
- import { MlxArray } from "../mlx/array";
17
- import { Dtype } from "../mlx/ffi";
18
- import { MetalKernel } from "../mlx/metal-kernel";
19
- import { CustomVjp } from "../mlx/custom-vjp";
20
- import * as ops from "../mlx/ops";
21
-
22
- /** ON by default: the match-compat kernel is BIT-EXACT with the spelled-out
23
- * path (kl --decode = 0 on e4b), so per the bit-parity-floor principle it
24
- * ships on — every optimization we can keep while staying bit-exact.
25
- * MLX_BUN_FUSED_GELU=0 opts out (e.g. the compile arm of the A/B). */
26
- export function fusedGeluEnabled(): boolean {
27
- return process.env.MLX_BUN_FUSED_GELU !== "0";
28
- }
29
-
30
- /** Dispatch counter (a gate test asserts the kernel actually ran). */
31
- export let fusedGeluCalls = 0;
32
-
33
- // Training-mode flag: when set, model GeGLU sites use the DIFFERENTIABLE wrapper
34
- // (Metal-kernel forward + hand-derived vjp) instead of the plain `fusedGeglu`
35
- // (a bare CustomKernel with no vjp — feeding it to autograd throws
36
- // "[Primitive::vjp] Not implemented for CustomKernel"). The differentiable
37
- // wrapper also recomputes the gelu in the backward from the primal `a` instead
38
- // of retaining the ~9 forward intermediates, so it lowers the MLP activation
39
- // memory the backward holds — the dominant resident at long context under
40
- // ops.sdpa (which never materializes the O(T²) scores). Set by the trainer
41
- // around the train loop; cleared in its finally. Off → inference behaviour
42
- // (plain `fusedGeglu`) is byte-for-byte unchanged.
43
- let _trainingGeglu = false;
44
- export function setFusedGeluTraining(on: boolean): void {
45
- _trainingGeglu = on;
46
- }
47
- export function fusedGeluTraining(): boolean {
48
- return _trainingGeglu;
49
- }
50
-
51
- // Pointwise: one thread per element. gelu_approx =
52
- // 0.5 x (1 + tanh(√(2/π)·(x + 0.044715 x³))) — composed in f32.
53
- const SOURCE = String.raw`
54
- const uint i = thread_position_in_grid.x;
55
- if (i >= SIZE) return;
56
- // Match-compat: round each intermediate (and the weak scalars) to T, op
57
- // for op with the spelled-out bf16 path, so the fused distribution tracks
58
- // the TRAINED path rather than drifting to "more accurate but different".
59
- // x^3 via pow (the reference uses mx.power, NOT x*x*x — they round
60
- // differently in bf16). The single remaining residual vs compat is the
61
- // pow/tanh math-lib difference, which the KL gate quantifies.
62
- const float C_CUBE = float(T(0.044715f));
63
- const float C_SQRT = float(T(0.7978845608028654f)); // √(2/π)
64
- const float C_HALF = float(T(0.5f));
65
- const float x = float(a[i]);
66
- const float x3 = float(T(metal::pow(x, 3.0f)));
67
- const float cx3 = float(T(x3 * C_CUBE));
68
- const float inn = float(T(x + cx3));
69
- const float scl = float(T(inn * C_SQRT));
70
- const float tnh = float(T(metal::precise::tanh(scl)));
71
- const float t1 = float(T(1.0f + tnh));
72
- const float hx = float(T(x * C_HALF));
73
- const float g = float(T(hx * t1));
74
- out[i] = T(g * float(b[i]));
75
- `;
76
-
77
- let kernel: MetalKernel | null = null;
78
- function getKernel(): MetalKernel {
79
- if (!kernel)
80
- kernel = new MetalKernel({
81
- name: "mlx_bun_fused_geglu",
82
- inputNames: ["a", "b"],
83
- outputNames: ["out"],
84
- source: SOURCE,
85
- ensureRowContiguous: true,
86
- });
87
- return kernel;
88
- }
89
-
90
- /** bf16/f16 only (the activation dtype the kernel reads/writes). */
91
- export function fusedGeluSupported(a: MlxArray): boolean {
92
- return a.dtype === Dtype.bfloat16 || a.dtype === Dtype.float16;
93
- }
94
-
95
- /** gelu_approx(a) · b, fused. a and b must share shape/dtype; out is a.dtype. */
96
- export function fusedGeglu(a: MlxArray, b: MlxArray): MlxArray {
97
- fusedGeluCalls++;
98
- const size = a.shape.reduce((x, y) => x * y, 1);
99
- const [out] = getKernel().apply([a, b], {
100
- outputs: [{ shape: a.shape, dtype: a.dtype }],
101
- grid: [size, 1, 1],
102
- threadGroup: [Math.min(size, 256), 1, 1],
103
- templateInts: { SIZE: size },
104
- templateDtypes: { T: a.dtype },
105
- });
106
- return out!;
107
- }
108
-
109
- // ---------------------------------------------------------------------------
110
- // Differentiable wrapper: Metal kernel forward + pure-MLX backward
111
- // ---------------------------------------------------------------------------
112
-
113
- // GELU tanh-approx constants
114
- const SQRT_2_PI = 0.7978845608028654;
115
- const C_CUBE = 0.044715;
116
-
117
- /** Compute gelu_approx(a) = 0.5 * a * (1 + tanh(sqrt(2/pi) * (a + 0.044715*a^3)))
118
- * and gelu'(a) using the MLX ops layer. All intermediates are disposed
119
- * before returning; caller owns the two returned arrays. */
120
- function geluAndGrad(a: MlxArray): { gelu: MlxArray; geluGrad: MlxArray } {
121
- // a^3 = a * a * a
122
- const a2 = ops.mul(a, a);
123
- const a3 = ops.mul(a2, a);
124
- // inner = a + 0.044715 * a^3
125
- const cx3 = ops.mulScalar(a3, C_CUBE);
126
- const inner = ops.add(a, cx3);
127
- // z = sqrt(2/pi) * inner
128
- const z = ops.mulScalar(inner, SQRT_2_PI);
129
- // t = tanh(z)
130
- const t = ops.tanh(z);
131
- // gelu = 0.5 * a * (1 + t)
132
- const one = ops.scalarLike(1, a);
133
- const t1 = ops.add(one, t);
134
- const halfA = ops.mulScalar(a, 0.5);
135
- const gelu = ops.mul(halfA, t1);
136
-
137
- // gelu'(a) = 0.5*(1+t) + 0.5*a*(1-t^2)*dz/da
138
- // dz/da = sqrt(2/pi) * (1 + 3*0.044715*a^2)
139
- // 1 - t^2 = sech^2(z)
140
- const halfT1 = ops.mulScalar(t1, 0.5); // 0.5*(1+t)
141
- const t2 = ops.mul(t, t); // t^2
142
- const sech2 = ops.sub(one, t2); // 1 - t^2
143
- const halfASech2 = ops.mul(halfA, sech2); // 0.5*a*(1-t^2)
144
- // dz/da
145
- const a2scaled = ops.mulScalar(a2, 3 * C_CUBE); // 3*0.044715*a^2
146
- const dzda = ops.add(one, a2scaled); // 1 + 3*0.044715*a^2
147
- const dzdaScaled = ops.mulScalar(dzda, SQRT_2_PI);// sqrt(2/pi)*dz_unnorm
148
- // chain: 0.5*a*(1-t^2)*dz/da
149
- const chainTerm = ops.mul(halfASech2, dzdaScaled);
150
- const geluGrad = ops.add(halfT1, chainTerm); // 0.5*(1+t) + chain
151
-
152
- // dispose all intermediates
153
- for (const arr of [a2, a3, cx3, inner, z, t, one, t1, halfA, halfT1, t2, sech2,
154
- halfASech2, a2scaled, dzda, dzdaScaled, chainTerm]) {
155
- arr.dispose();
156
- }
157
-
158
- return { gelu, geluGrad };
159
- }
160
-
161
- let _differentiableKernel: CustomVjp | null = null;
162
-
163
- function getDifferentiableKernel(): CustomVjp {
164
- if (!_differentiableKernel) {
165
- _differentiableKernel = new CustomVjp(
166
- // forward: use the Metal kernel
167
- ([a, b]: MlxArray[]) => [fusedGeglu(a!, b!)],
168
- // backward
169
- (primals: MlxArray[], cotangents: MlxArray[], _outputs: MlxArray[]) => {
170
- const [a, b] = primals as [MlxArray, MlxArray];
171
- const [dc] = cotangents as [MlxArray];
172
-
173
- const { gelu, geluGrad } = geluAndGrad(a);
174
-
175
- // grad_b = dc * gelu(a)
176
- const grad_b = ops.mul(dc, gelu);
177
-
178
- // grad_a = dc * b * gelu'(a)
179
- const dcb = ops.mul(dc, b);
180
- const grad_a = ops.mul(dcb, geluGrad);
181
-
182
- gelu.dispose();
183
- geluGrad.dispose();
184
- dcb.dispose();
185
-
186
- return [grad_a, grad_b];
187
- },
188
- );
189
- }
190
- return _differentiableKernel;
191
- }
192
-
193
- /** gelu_approx(a) · b with a hand-derived vjp. Uses the Metal kernel forward,
194
- * MLX-ops backward. For training only (inference uses the non-differentiable
195
- * fusedGeglu). */
196
- export function fusedGegluDifferentiable(a: MlxArray, b: MlxArray): MlxArray {
197
- return getDifferentiableKernel().apply([a, b])[0]!;
198
- }
@@ -1,169 +0,0 @@
1
- // The WHOLE MLP in ONE dispatch — down = (silu(x·Wgᵀ)·(x·Wuᵀ)) · Wdownᵀ — with no
2
- // intermediate `hidden` ever exposed to MLX. MLX runs this one kernel; there is no
3
- // boundary between our kernels for its planner to alias (the corruption only lives
4
- // at those boundaries). Built from the steel building blocks in steel-qmm-header.ts,
5
- // structured like the flash-CCE backward: each threadgroup owns one (M-block,I-block),
6
- // computes silu·up for that I-block ONCE (no recompute), then atomic-accumulates its
7
- // contribution to every down output — exactly how flash-CCE BWD atomic-adds dh.
8
- //
9
- // grid = (TGN, I/BI, M/BM); each tg: i-block i0 = tid.y*BI, m-block m0 = tid.z*BM
10
- // phase A: gate/up [BM,BI] = x[BM,H] @ {Wg,Wu}[i0,H]ᵀ (reduce H) → silu·up = Hs[BM,BI]
11
- // phase B: for each Ho-block no: D[BM,BN] = Hs @ Wdown[no,i0]ᵀ; atomic-add D → y[M,Ho]
12
- //
13
- // Per-weight quant constants (gate/up/down) are baked literals; H/I/Ho are templateInts;
14
- // M from x_shape. Output is f32 (atomic accumulation); the caller casts to the model
15
- // dtype at the MLP boundary. L3 (atomic-add ordering + bf16-rounded silu); gated by
16
- // teacher-forced KL like the rest of the controlled path.
17
-
18
- import { MlxArray } from "../mlx/array";
19
- import { Dtype } from "../mlx/ffi";
20
- import { MetalKernel } from "../mlx/metal-kernel";
21
- import { STEEL_QMM_HEADER } from "../train/steel-qmm-header";
22
- import * as ops from "../mlx/ops";
23
- import type { QuantSpec } from "../mlx/ops";
24
-
25
- const BM = 32, BN = 32, BK = 32, WM = 2, WN = 2;
26
- const TGN = WM * WN * 32; // 128 threads / threadgroup
27
-
28
- const SOURCE = String.raw`
29
- const uint M=x_shape[0];
30
- constexpr int I=I_T, H=H_T, Ho=HO_T;
31
- constexpr int gsG=GS_G,btsG=BITS_G, gsU=GS_U,btsU=BITS_U, gsD=GS_D,btsD=BITS_D;
32
- constexpr int pfG=get_pack_factor<btsG,8>(), bppG=get_bytes_per_pack<btsG>();
33
- constexpr int pfU=get_pack_factor<btsU,8>(), bppU=get_bytes_per_pack<btsU>();
34
- constexpr int pfD=get_pack_factor<btsD,8>(), bppD=get_bytes_per_pack<btsD>();
35
- constexpr int BKp=BK_T+16/sizeof(T);
36
- threadgroup T Xs[BM_T*BKp];
37
- threadgroup T Wgs[BN_T*BKp];
38
- threadgroup T Wus[BN_T*BKp];
39
- threadgroup T Lg[BM_T*BN_T];
40
- threadgroup T Lu[BM_T*BN_T];
41
- threadgroup T Hs[BM_T*BKp]; // silu·up for this I-block, padded leading dim BKp
42
- threadgroup T Wds[BN_T*BKp]; // a down-weight [BN, BI] tile
43
- using gu_mma=mlx::steel::BlockMMA<T,T,BM_T,BN_T,BK_T,WM_T,WN_T,false,true,BKp,BKp>;
44
- using lx_t =mlx::steel::BlockLoader<T,BM_T,BK_T,BKp,1,WM_T*WN_T*SIMD_SIZE>;
45
- using lwg_t =QuantizedBlockLoader<T,BN_T,BK_T,BKp,1,WM_T*WN_T*SIMD_SIZE,gsG,btsG>;
46
- using lwu_t =QuantizedBlockLoader<T,BN_T,BK_T,BKp,1,WM_T*WN_T*SIMD_SIZE,gsU,btsU>;
47
- using d_mma =mlx::steel::BlockMMA<T,T,BM_T,BN_T,BK_T,WM_T,WN_T,false,true,BKp,BKp>;
48
- using lwd_t =QuantizedBlockLoader<T,BN_T,BK_T,BKp,1,WM_T*WN_T*SIMD_SIZE,gsD,btsD>;
49
- const uint sg=simdgroup_index_in_threadgroup, sl=thread_index_in_simdgroup;
50
- const uint lid=thread_position_in_threadgroup.x;
51
- const int Hh=(int)H;
52
- const int KwG=Hh*bppG/pfG, KgG=Hh/gsG;
53
- const int KwU=Hh*bppU/pfU, KgU=Hh/gsU;
54
- const int KwD=I*bppD/pfD, KgD=I/gsD; // down weight rows are length I
55
- const int m0=(int)threadgroup_position_in_grid.z*BM_T;
56
- const int i0=(int)threadgroup_position_in_grid.y*BN_T;
57
- const short num_els=(short)min(BM_T,(int)M-m0);
58
-
59
- // ---- Phase A: hidden[BM,BI] = silu(x@Wg[i0]ᵀ)·(x@Wu[i0]ᵀ), reduce over H ----
60
- gu_mma mg(sg,sl), mu(sg,sl);
61
- lx_t lx (x + (int64_t)m0*Hh, Hh, Xs, sg, sl);
62
- lwg_t lwg((const device uint8_t*)wg + (int64_t)i0*KwG, sg_w + (int64_t)i0*KgG, bg_w + (int64_t)i0*KgG, Hh, Wgs, sg, sl);
63
- lwu_t lwu((const device uint8_t*)wu + (int64_t)i0*KwU, su_w + (int64_t)i0*KgU, bu_w + (int64_t)i0*KgU, Hh, Wus, sg, sl);
64
- for (int k=0;k<Hh;k+=BK_T){
65
- threadgroup_barrier(mem_flags::mem_threadgroup);
66
- if (num_els<BM_T) lx.load_safe(short2(BK_T,num_els)); else lx.load_unsafe();
67
- lwg.load_unsafe(); lwu.load_unsafe();
68
- threadgroup_barrier(mem_flags::mem_threadgroup);
69
- mg.mma(Xs,Wgs); mu.mma(Xs,Wus);
70
- lx.next(); lwg.next(); lwu.next();
71
- }
72
- threadgroup_barrier(mem_flags::mem_threadgroup);
73
- mg.Ctile.template store<T,WM_T,WN_T,BN_T,1>(Lg + mg.sm*BN_T + mg.sn);
74
- threadgroup_barrier(mem_flags::mem_threadgroup);
75
- mu.Ctile.template store<T,WM_T,WN_T,BN_T,1>(Lu + mu.sm*BN_T + mu.sn);
76
- threadgroup_barrier(mem_flags::mem_threadgroup);
77
- // silu·up into Hs (padded BKp leading dim, so the phase-B BlockMMA can read it as A)
78
- for (uint idx=lid; idx<(uint)(BM_T*BN_T); idx+=TGN_T){
79
- int r=(int)idx/BN_T, c=(int)idx%BN_T;
80
- T gT=Lg[idx], uT=Lu[idx];
81
- T sigT=(T)(1.0f/(1.0f+metal::precise::exp(-(float)gT)));
82
- T siluT=(T)((float)gT*(float)sigT);
83
- Hs[r*BKp + c] = (T)((float)siluT*(float)uT);
84
- }
85
- threadgroup_barrier(mem_flags::mem_threadgroup);
86
-
87
- // ---- Phase B: for each Ho-block, D[BM,BN] = Hs @ Wdown[no,i0]ᵀ; atomic-add → y ----
88
- for (int no=0; no<Ho; no+=BN_T){
89
- lwd_t lwd((const device uint8_t*)wd + (int64_t)no*KwD + (int64_t)i0*bppD/pfD,
90
- sd_w + (int64_t)no*KgD + i0/gsD, bd_w + (int64_t)no*KgD + i0/gsD, I, Wds, sg, sl);
91
- threadgroup_barrier(mem_flags::mem_threadgroup);
92
- lwd.load_unsafe(); // one BI-wide tile (BI==BK, single step)
93
- threadgroup_barrier(mem_flags::mem_threadgroup);
94
- d_mma D(sg,sl);
95
- D.mma(Hs, Wds); // D[BM,BN] = Hs[BM,BI] @ Wds[BN,BI]ᵀ
96
- threadgroup_barrier(mem_flags::mem_threadgroup);
97
- D.Ctile.template store<T,WM_T,WN_T,BN_T,1>(Lg + D.sm*BN_T + D.sn); // reuse Lg to stage D
98
- threadgroup_barrier(mem_flags::mem_threadgroup);
99
- for (uint idx=lid; idx<(uint)(BM_T*BN_T); idx+=TGN_T){
100
- int r=(int)idx/BN_T, c=(int)idx%BN_T;
101
- if (m0+r<(int)M){
102
- const int64_t off=(int64_t)(m0+r)*(int64_t)Ho + (no+c);
103
- // Absorb the residual: add h exactly once (only the i0==0 threadgroups,
104
- // which cover every (m,ho) once) so the kernel outputs h + mlp(x) and
105
- // nothing is left live across it for MLX to plan a buffer around.
106
- float add = (float)Lg[idx];
107
- if (i0==0) add += (float)h[off];
108
- atomic_fetch_add_explicit(&y[off], add, memory_order_relaxed);
109
- }
110
- }
111
- threadgroup_barrier(mem_flags::mem_threadgroup);
112
- }
113
- `;
114
-
115
- const _kernels = new Map<string, MetalKernel>();
116
- function getKernel(
117
- gsG: number, btsG: number, gsU: number, btsU: number, gsD: number, btsD: number,
118
- ): MetalKernel {
119
- const key = `${gsG}:${btsG}:${gsU}:${btsU}:${gsD}:${btsD}`;
120
- let k = _kernels.get(key);
121
- if (!k) {
122
- k = new MetalKernel({
123
- name: "mlx_bun_fused_mlp",
124
- inputNames: ["x", "wg", "sg_w", "bg_w", "wu", "su_w", "bu_w", "wd", "sd_w", "bd_w", "h"],
125
- outputNames: ["y"],
126
- source: SOURCE,
127
- header:
128
- `${STEEL_QMM_HEADER}\n` +
129
- `#define GS_G ${gsG}\n#define BITS_G ${btsG}\n#define GS_U ${gsU}\n#define BITS_U ${btsU}\n` +
130
- `#define GS_D ${gsD}\n#define BITS_D ${btsD}\n` +
131
- `#define BM_T ${BM}\n#define BN_T ${BN}\n#define BK_T ${BK}\n` +
132
- `#define WM_T ${WM}\n#define WN_T ${WN}\n#define TGN_T ${TGN}\n`,
133
- ensureRowContiguous: true,
134
- atomicOutputs: true,
135
- });
136
- _kernels.set(key, k);
137
- }
138
- return k;
139
- }
140
-
141
- /** The whole MLP, fused, as one dispatch. x is [M, H] (bf16/f16). Returns the MLP
142
- * output [M, Ho] in x.dtype (cast from the f32 atomic accumulator). gate/up have
143
- * shape [I, H*bits/32]; down has [Ho, I*bits/32]. Caller owns the result. */
144
- export function fusedMlp(
145
- x: MlxArray,
146
- wg: MlxArray, sg: MlxArray, bg: MlxArray, gateSpec: QuantSpec,
147
- wu: MlxArray, su: MlxArray, bu: MlxArray, upSpec: QuantSpec,
148
- wd: MlxArray, sd: MlxArray, bd: MlxArray, downSpec: QuantSpec,
149
- h: MlxArray, // the residual (layer input) — absorbed: output = h + mlp(x)
150
- ): MlxArray {
151
- const M = x.shape[0]!, I = wg.shape[0]!, H = x.shape[1]!, Ho = wd.shape[0]!;
152
- const [yF] = getKernel(
153
- gateSpec.groupSize, gateSpec.bits, upSpec.groupSize, upSpec.bits, downSpec.groupSize, downSpec.bits,
154
- ).apply([x, wg, sg, bg, wu, su, bu, wd, sd, bd, h], {
155
- // f32 atomic accumulator, zero-initialised; grid tiles (M, I) — every I-block
156
- // contributes to the output via atomic-add.
157
- outputShapeFn: (ins) => [{ shape: [ins[0]!.shape[0]!, Ho], dtype: Dtype.float32 }],
158
- grid: (ins) => [TGN, Math.ceil(I / BN), Math.ceil(ins[0]!.shape[0]! / BM)],
159
- threadGroup: [TGN, 1, 1],
160
- templateInts: { I_T: I, H_T: H, HO_T: Ho },
161
- templateDtypes: { T: x.dtype },
162
- initValue: 0,
163
- });
164
- void M;
165
- if (yF!.dtype === x.dtype) return yF!;
166
- const y = yF!.astype(x.dtype);
167
- yF!.dispose();
168
- return y;
169
- }