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
@@ -0,0 +1,610 @@
1
+ import { MlxArray } from "../mlx/array";
2
+ import { Dtype } from "../mlx/ffi";
3
+ import { MetalKernel } from "../mlx/metal-kernel";
4
+ import {
5
+ GLM52_EXPERT_SLOT_ALIGNMENT,
6
+ type Glm52ExpertSlotLayout,
7
+ } from "./glm52-expert-layout";
8
+
9
+ const SIMD_WIDTH = 32;
10
+ const SIMD_GROUPS = 4;
11
+ const THREADS = SIMD_WIDTH * SIMD_GROUPS;
12
+ const ROWS_PER_THREADGROUP = SIMD_GROUPS;
13
+
14
+ /**
15
+ * The subset of the canonical expert-slot layout consumed by the decode
16
+ * kernel. Offsets are bytes from the beginning of one aligned residency slot.
17
+ */
18
+ export interface Glm52CanonicalQ4MetalLayout {
19
+ readonly hiddenSize: number;
20
+ readonly intermediateSize: number;
21
+ readonly slotBytes: number;
22
+ readonly downWeightOffset: number;
23
+ readonly gateWeightOffset: number;
24
+ readonly upWeightOffset: number;
25
+ readonly downScaleOffset: number;
26
+ readonly gateScaleOffset: number;
27
+ readonly upScaleOffset: number;
28
+ }
29
+
30
+ export interface Glm52CanonicalQ8MetalLayout
31
+ extends Glm52CanonicalQ4MetalLayout {}
32
+
33
+ function positiveInteger(value: number, label: string): number {
34
+ if (!Number.isSafeInteger(value) || value <= 0)
35
+ throw new Error(`${label} must be a positive safe integer`);
36
+ return value;
37
+ }
38
+
39
+ function validateRange(
40
+ offset: number,
41
+ byteLength: number,
42
+ slotBytes: number,
43
+ label: string,
44
+ ): void {
45
+ if (!Number.isSafeInteger(offset) || offset < 0)
46
+ throw new Error(`${label} offset must be a non-negative safe integer`);
47
+ if (!Number.isSafeInteger(byteLength) || byteLength < 0)
48
+ throw new Error(`${label} length must be a non-negative safe integer`);
49
+ if (offset + byteLength > slotBytes)
50
+ throw new Error(`${label} exceeds the canonical expert slot`);
51
+ }
52
+
53
+ /**
54
+ * Resolve and validate a production-compatible Q4 descriptor from the
55
+ * artifact-driven slot catalog. Colibri's main routed experts use one F32
56
+ * scale per output row (`groupSize === null`) and affine zero-point 8.
57
+ */
58
+ export function glm52CanonicalQ4MetalLayout(
59
+ layout: Glm52ExpertSlotLayout,
60
+ ): Glm52CanonicalQ4MetalLayout {
61
+ if (layout.bits !== 4)
62
+ throw new Error("GLM Metal streamed experts require a Q4 slot");
63
+ if (layout.groupSize !== null) {
64
+ throw new Error(
65
+ "GLM Metal streamed experts currently require per-row Q4 scales",
66
+ );
67
+ }
68
+
69
+ const down = layout.projections.down;
70
+ const gate = layout.projections.gate;
71
+ const up = layout.projections.up;
72
+ const hiddenSize = positiveInteger(down.tensor.outputRows, "hidden size");
73
+ const intermediateSize = positiveInteger(
74
+ down.tensor.inputColumns,
75
+ "intermediate size",
76
+ );
77
+ const expected = [
78
+ [gate.tensor.outputRows, intermediateSize, "gate output rows"],
79
+ [gate.tensor.inputColumns, hiddenSize, "gate input columns"],
80
+ [up.tensor.outputRows, intermediateSize, "up output rows"],
81
+ [up.tensor.inputColumns, hiddenSize, "up input columns"],
82
+ ] as const;
83
+ for (const [actual, wanted, label] of expected) {
84
+ if (actual !== wanted)
85
+ throw new Error(`${label} must be ${wanted}, got ${actual}`);
86
+ }
87
+
88
+ const slotBytes = positiveInteger(layout.slotBytes, "expert slot bytes");
89
+ for (const [projection, outputRows, inputColumns] of [
90
+ [down, hiddenSize, intermediateSize],
91
+ [gate, intermediateSize, hiddenSize],
92
+ [up, intermediateSize, hiddenSize],
93
+ ] as const) {
94
+ const weightBytes = outputRows * inputColumns / 2;
95
+ const scaleBytes = outputRows * 4;
96
+ if (projection.tensor.weight.byteLength !== weightBytes) {
97
+ throw new Error(
98
+ `${projection.projection} Q4 weight bytes must be ${weightBytes}`,
99
+ );
100
+ }
101
+ if (projection.tensor.scales.byteLength !== scaleBytes) {
102
+ throw new Error(
103
+ `${projection.projection} Q4 scale bytes must be ${scaleBytes}`,
104
+ );
105
+ }
106
+ validateRange(
107
+ projection.weightOffset,
108
+ weightBytes,
109
+ slotBytes,
110
+ `${projection.projection} weights`,
111
+ );
112
+ validateRange(
113
+ projection.scaleOffset,
114
+ scaleBytes,
115
+ slotBytes,
116
+ `${projection.projection} scales`,
117
+ );
118
+ if ((projection.weightOffset & 3) !== 0)
119
+ throw new Error(`${projection.projection} weights must be uint32-aligned`);
120
+ if ((projection.scaleOffset & 3) !== 0)
121
+ throw new Error(`${projection.projection} scales must be F32-aligned`);
122
+ }
123
+
124
+ return {
125
+ hiddenSize,
126
+ intermediateSize,
127
+ slotBytes,
128
+ downWeightOffset: down.weightOffset,
129
+ gateWeightOffset: gate.weightOffset,
130
+ upWeightOffset: up.weightOffset,
131
+ downScaleOffset: down.scaleOffset,
132
+ gateScaleOffset: gate.scaleOffset,
133
+ upScaleOffset: up.scaleOffset,
134
+ };
135
+ }
136
+
137
+ /** Resolve the signed two's-complement, per-output-row int8 layout used by
138
+ * GLM-5.2's native MTP routed experts. */
139
+ export function glm52CanonicalQ8MetalLayout(
140
+ layout: Glm52ExpertSlotLayout,
141
+ ): Glm52CanonicalQ8MetalLayout {
142
+ if (layout.bits !== 8)
143
+ throw new Error("GLM MTP Metal streamed experts require a Q8 slot");
144
+ if (layout.groupSize !== null)
145
+ throw new Error("GLM MTP Metal streamed experts require per-row Q8 scales");
146
+
147
+ const down = layout.projections.down;
148
+ const gate = layout.projections.gate;
149
+ const up = layout.projections.up;
150
+ const hiddenSize = positiveInteger(down.tensor.outputRows, "hidden size");
151
+ const intermediateSize = positiveInteger(
152
+ down.tensor.inputColumns,
153
+ "intermediate size",
154
+ );
155
+ const expected = [
156
+ [gate.tensor.outputRows, intermediateSize, "gate output rows"],
157
+ [gate.tensor.inputColumns, hiddenSize, "gate input columns"],
158
+ [up.tensor.outputRows, intermediateSize, "up output rows"],
159
+ [up.tensor.inputColumns, hiddenSize, "up input columns"],
160
+ ] as const;
161
+ for (const [actual, wanted, label] of expected) {
162
+ if (actual !== wanted)
163
+ throw new Error(`${label} must be ${wanted}, got ${actual}`);
164
+ }
165
+
166
+ const slotBytes = positiveInteger(layout.slotBytes, "expert slot bytes");
167
+ for (const [projection, outputRows, inputColumns] of [
168
+ [down, hiddenSize, intermediateSize],
169
+ [gate, intermediateSize, hiddenSize],
170
+ [up, intermediateSize, hiddenSize],
171
+ ] as const) {
172
+ const weightBytes = outputRows * inputColumns;
173
+ const scaleBytes = outputRows * 4;
174
+ if (projection.tensor.weight.byteLength !== weightBytes)
175
+ throw new Error(
176
+ `${projection.projection} Q8 weight bytes must be ${weightBytes}`,
177
+ );
178
+ if (projection.tensor.scales.byteLength !== scaleBytes)
179
+ throw new Error(
180
+ `${projection.projection} Q8 scale bytes must be ${scaleBytes}`,
181
+ );
182
+ validateRange(
183
+ projection.weightOffset,
184
+ weightBytes,
185
+ slotBytes,
186
+ `${projection.projection} weights`,
187
+ );
188
+ validateRange(
189
+ projection.scaleOffset,
190
+ scaleBytes,
191
+ slotBytes,
192
+ `${projection.projection} scales`,
193
+ );
194
+ if ((projection.scaleOffset & 3) !== 0)
195
+ throw new Error(`${projection.projection} scales must be F32-aligned`);
196
+ }
197
+ return {
198
+ hiddenSize,
199
+ intermediateSize,
200
+ slotBytes,
201
+ downWeightOffset: down.weightOffset,
202
+ gateWeightOffset: gate.weightOffset,
203
+ upWeightOffset: up.weightOffset,
204
+ downScaleOffset: down.scaleOffset,
205
+ gateScaleOffset: gate.scaleOffset,
206
+ upScaleOffset: up.scaleOffset,
207
+ };
208
+ }
209
+
210
+ /** Wrap a live, aligned ExpertIOSlabStore slot without copying it.
211
+ *
212
+ * The residency lease remains owned by the caller and must not be released
213
+ * until the returned expert output has been evaluated and the GPU stream has
214
+ * completed.
215
+ */
216
+ export function glm52CanonicalQ4SlotView(
217
+ pointer: number,
218
+ layout: Glm52CanonicalQ4MetalLayout | Glm52CanonicalQ8MetalLayout,
219
+ ): MlxArray {
220
+ if (!Number.isSafeInteger(pointer) || pointer <= 0)
221
+ throw new Error("expert slot pointer must be a positive safe integer");
222
+ if (pointer % GLM52_EXPERT_SLOT_ALIGNMENT !== 0) {
223
+ throw new Error(
224
+ "expert slot pointer must be page-aligned for zero-copy Metal access",
225
+ );
226
+ }
227
+ return MlxArray.fromPointer(pointer, [layout.slotBytes], Dtype.uint8);
228
+ }
229
+
230
+ export const glm52CanonicalQ8SlotView = glm52CanonicalQ4SlotView;
231
+
232
+ function validateCall(
233
+ input: MlxArray,
234
+ slot: MlxArray,
235
+ layout: Glm52CanonicalQ4MetalLayout,
236
+ ): void {
237
+ const hidden = positiveInteger(layout.hiddenSize, "hidden size");
238
+ const intermediate = positiveInteger(
239
+ layout.intermediateSize,
240
+ "intermediate size",
241
+ );
242
+ positiveInteger(layout.slotBytes, "expert slot bytes");
243
+ if (hidden % 8 !== 0 || intermediate % 8 !== 0)
244
+ throw new Error("Q4 expert dimensions must be divisible by 8");
245
+ if (hidden % ROWS_PER_THREADGROUP !== 0 ||
246
+ intermediate % ROWS_PER_THREADGROUP !== 0) {
247
+ throw new Error(
248
+ `Q4 expert output dimensions must be divisible by ${ROWS_PER_THREADGROUP}`,
249
+ );
250
+ }
251
+ if (
252
+ input.shape.length !== 2 ||
253
+ !Number.isSafeInteger(input.shape[0]) ||
254
+ input.shape[0]! < 1 ||
255
+ input.shape[1] !== hidden
256
+ ) {
257
+ throw new Error(
258
+ `GLM Metal streamed expert decode requires input [M,${hidden}]`,
259
+ );
260
+ }
261
+ if (input.dtype !== Dtype.bfloat16 && input.dtype !== Dtype.float32) {
262
+ throw new Error(
263
+ "GLM Metal streamed expert decode requires bfloat16 or float32 input",
264
+ );
265
+ }
266
+ if (slot.dtype !== Dtype.uint8 || slot.shape.length !== 1)
267
+ throw new Error("canonical expert slot must be a flat uint8 array");
268
+ if (slot.size < layout.slotBytes)
269
+ throw new Error("canonical expert slot array is shorter than its layout");
270
+
271
+ const ranges = [
272
+ [layout.downWeightOffset, hidden * intermediate / 2, "down weights"],
273
+ [layout.gateWeightOffset, intermediate * hidden / 2, "gate weights"],
274
+ [layout.upWeightOffset, intermediate * hidden / 2, "up weights"],
275
+ [layout.downScaleOffset, hidden * 4, "down scales"],
276
+ [layout.gateScaleOffset, intermediate * 4, "gate scales"],
277
+ [layout.upScaleOffset, intermediate * 4, "up scales"],
278
+ ] as const;
279
+ for (const [offset, length, label] of ranges)
280
+ validateRange(offset, length, layout.slotBytes, label);
281
+ }
282
+
283
+ // One simdgroup owns one output row. The packed Q4 byte stream and F32 row
284
+ // scales are read straight from the canonical residency slot. Every product is
285
+ // explicitly dequantized to float and accumulated with an F32 FMA.
286
+ const GATE_UP_SOURCE = String.raw`
287
+ const uint lane = thread_index_in_simdgroup;
288
+ const uint sg = simdgroup_index_in_threadgroup;
289
+ const uint row = thread_position_in_grid.y * (uint)ROWS_TG + sg;
290
+ const uint sample = thread_position_in_grid.z;
291
+ if (row >= (uint)I || sample >= (uint)M) return;
292
+ const device T* sampleX = x + (ulong)sample * (ulong)H;
293
+
294
+ const device uint32_t* gateRow =
295
+ (const device uint32_t*)(slot + GATE_W) + (ulong)row * (ulong)(H / 8);
296
+ const device uint32_t* upRow =
297
+ (const device uint32_t*)(slot + UP_W) + (ulong)row * (ulong)(H / 8);
298
+ const device float* gateScales = (const device float*)(slot + GATE_S);
299
+ const device float* upScales = (const device float*)(slot + UP_S);
300
+ const float gateScale = gateScales[row];
301
+ const float upScale = upScales[row];
302
+
303
+ float gateAcc = 0.0f;
304
+ float upAcc = 0.0f;
305
+ for (uint word = lane; word < (uint)(H / 8); word += 32u) {
306
+ const uint32_t gatePacked = gateRow[word];
307
+ const uint32_t upPacked = upRow[word];
308
+ const uint k0 = word * 8u;
309
+ for (uint place = 0; place < 8u; ++place) {
310
+ const float xv = float(sampleX[k0 + place]);
311
+ const float gateWeight =
312
+ float((gatePacked >> (place * 4u)) & 0xFu) - 8.0f;
313
+ const float upWeight =
314
+ float((upPacked >> (place * 4u)) & 0xFu) - 8.0f;
315
+ gateAcc = metal::fma(gateWeight, xv, gateAcc);
316
+ upAcc = metal::fma(upWeight, xv, upAcc);
317
+ }
318
+ }
319
+
320
+ const float gate = metal::simd_sum(gateAcc) * gateScale;
321
+ const float up = metal::simd_sum(upAcc) * upScale;
322
+ if (lane == 0u) {
323
+ // Match the production BF16 graph's materialization boundaries: gate and
324
+ // up are rounded to T, then SiLU and the product are rounded to T.
325
+ const T gateT = T(gate);
326
+ const T upT = T(up);
327
+ const T sigmoidT = T(1.0f / (1.0f + metal::precise::exp(-float(gateT))));
328
+ const T siluT = T(float(gateT) * float(sigmoidT));
329
+ mid[(ulong)sample * (ulong)I + row] =
330
+ T(float(siluT) * float(upT));
331
+ }
332
+ `;
333
+
334
+ const DOWN_SOURCE = String.raw`
335
+ const uint lane = thread_index_in_simdgroup;
336
+ const uint sg = simdgroup_index_in_threadgroup;
337
+ const uint row = thread_position_in_grid.y * (uint)ROWS_TG + sg;
338
+ const uint sample = thread_position_in_grid.z;
339
+ if (row >= (uint)H || sample >= (uint)M) return;
340
+ const device T* sampleMid = mid + (ulong)sample * (ulong)I;
341
+
342
+ const device uint32_t* downRow =
343
+ (const device uint32_t*)(slot + DOWN_W) + (ulong)row * (ulong)(I / 8);
344
+ const device float* downScales = (const device float*)(slot + DOWN_S);
345
+ const float scale = downScales[row];
346
+ float acc = 0.0f;
347
+ for (uint word = lane; word < (uint)(I / 8); word += 32u) {
348
+ const uint32_t packed = downRow[word];
349
+ const uint k0 = word * 8u;
350
+ for (uint place = 0; place < 8u; ++place) {
351
+ const float xv = float(sampleMid[k0 + place]);
352
+ const float weight =
353
+ float((packed >> (place * 4u)) & 0xFu) - 8.0f;
354
+ acc = metal::fma(weight, xv, acc);
355
+ }
356
+ }
357
+ const float value = metal::simd_sum(acc) * scale;
358
+ if (lane == 0u)
359
+ out[(ulong)sample * (ulong)H + row] = T(value);
360
+ `;
361
+
362
+ /**
363
+ * Row-independent routed-SwiGLU kernel for the GLM-5.2 production geometry.
364
+ * M=1 decode and a pinned speculative verify batch use the identical source
365
+ * and dispatch geometry; only grid.z changes.
366
+ */
367
+ export class Glm52CanonicalQ4MetalExecutor {
368
+ readonly #gateUp = new MetalKernel({
369
+ name: "mlx_bun_glm52_q4_slot_gate_up",
370
+ inputNames: ["x", "slot"],
371
+ outputNames: ["mid"],
372
+ source: GATE_UP_SOURCE,
373
+ ensureRowContiguous: true,
374
+ });
375
+ readonly #down = new MetalKernel({
376
+ name: "mlx_bun_glm52_q4_slot_down",
377
+ inputNames: ["mid", "slot"],
378
+ outputNames: ["out"],
379
+ source: DOWN_SOURCE,
380
+ ensureRowContiguous: true,
381
+ });
382
+ #disposed = false;
383
+
384
+ execute(
385
+ input: MlxArray,
386
+ slot: MlxArray,
387
+ layout: Glm52CanonicalQ4MetalLayout,
388
+ ): MlxArray {
389
+ if (this.#disposed)
390
+ throw new Error("GLM Metal streamed expert executor used after dispose");
391
+ validateCall(input, slot, layout);
392
+ const samples = input.shape[0]!;
393
+ const templateInts = {
394
+ M: samples,
395
+ H: layout.hiddenSize,
396
+ I: layout.intermediateSize,
397
+ DOWN_W: layout.downWeightOffset,
398
+ GATE_W: layout.gateWeightOffset,
399
+ UP_W: layout.upWeightOffset,
400
+ DOWN_S: layout.downScaleOffset,
401
+ GATE_S: layout.gateScaleOffset,
402
+ UP_S: layout.upScaleOffset,
403
+ ROWS_TG: ROWS_PER_THREADGROUP,
404
+ };
405
+ const [mid] = this.#gateUp.apply([input, slot], {
406
+ outputs: [{
407
+ shape: [samples, layout.intermediateSize],
408
+ dtype: input.dtype,
409
+ }],
410
+ grid: [
411
+ THREADS,
412
+ layout.intermediateSize / ROWS_PER_THREADGROUP,
413
+ samples,
414
+ ],
415
+ threadGroup: [THREADS, 1, 1],
416
+ templateDtypes: { T: input.dtype },
417
+ templateInts,
418
+ });
419
+ if (!mid) throw new Error("GLM Metal gate/up kernel returned no output");
420
+ try {
421
+ const [output] = this.#down.apply([mid, slot], {
422
+ outputs: [{
423
+ shape: [samples, layout.hiddenSize],
424
+ dtype: input.dtype,
425
+ }],
426
+ grid: [
427
+ THREADS,
428
+ layout.hiddenSize / ROWS_PER_THREADGROUP,
429
+ samples,
430
+ ],
431
+ threadGroup: [THREADS, 1, 1],
432
+ templateDtypes: { T: input.dtype },
433
+ templateInts,
434
+ });
435
+ if (!output) throw new Error("GLM Metal down kernel returned no output");
436
+ return output;
437
+ } finally {
438
+ mid.dispose();
439
+ }
440
+ }
441
+
442
+ dispose(): void {
443
+ if (this.#disposed) return;
444
+ this.#disposed = true;
445
+ this.#gateUp.dispose();
446
+ this.#down.dispose();
447
+ }
448
+ }
449
+
450
+ const Q8_GATE_UP_SOURCE = String.raw`
451
+ const uint lane = thread_index_in_simdgroup;
452
+ const uint sg = simdgroup_index_in_threadgroup;
453
+ const uint row = thread_position_in_grid.y * (uint)ROWS_TG + sg;
454
+ const uint sample = thread_position_in_grid.z;
455
+ if (row >= (uint)I || sample >= (uint)M) return;
456
+
457
+ const device int8_t* gateRow =
458
+ (const device int8_t*)(slot + GATE_W) + (ulong)row * (ulong)H;
459
+ const device int8_t* upRow =
460
+ (const device int8_t*)(slot + UP_W) + (ulong)row * (ulong)H;
461
+ const device float* gateScales = (const device float*)(slot + GATE_S);
462
+ const device float* upScales = (const device float*)(slot + UP_S);
463
+ const float gateScale = gateScales[row];
464
+ const float upScale = upScales[row];
465
+ float gateAcc = 0.0f;
466
+ float upAcc = 0.0f;
467
+ for (uint k = lane; k < (uint)H; k += 32u) {
468
+ const float xv = float(x[(ulong)sample * (ulong)H + k]);
469
+ gateAcc = metal::fma(float(gateRow[k]), xv, gateAcc);
470
+ upAcc = metal::fma(float(upRow[k]), xv, upAcc);
471
+ }
472
+ const float gate = metal::simd_sum(gateAcc) * gateScale;
473
+ const float up = metal::simd_sum(upAcc) * upScale;
474
+ if (lane == 0u) {
475
+ const T gateT = T(gate);
476
+ const T upT = T(up);
477
+ const T sigmoidT = T(1.0f / (1.0f + metal::precise::exp(-float(gateT))));
478
+ const T siluT = T(float(gateT) * float(sigmoidT));
479
+ mid[(ulong)sample * (ulong)I + row] = T(float(siluT) * float(upT));
480
+ }
481
+ `;
482
+
483
+ const Q8_DOWN_SOURCE = String.raw`
484
+ const uint lane = thread_index_in_simdgroup;
485
+ const uint sg = simdgroup_index_in_threadgroup;
486
+ const uint row = thread_position_in_grid.y * (uint)ROWS_TG + sg;
487
+ const uint sample = thread_position_in_grid.z;
488
+ if (row >= (uint)H || sample >= (uint)M) return;
489
+
490
+ const device int8_t* downRow =
491
+ (const device int8_t*)(slot + DOWN_W) + (ulong)row * (ulong)I;
492
+ const device float* downScales = (const device float*)(slot + DOWN_S);
493
+ const float scale = downScales[row];
494
+ float acc = 0.0f;
495
+ for (uint k = lane; k < (uint)I; k += 32u) {
496
+ const float xv = float(mid[(ulong)sample * (ulong)I + k]);
497
+ acc = metal::fma(float(downRow[k]), xv, acc);
498
+ }
499
+ const float value = metal::simd_sum(acc) * scale;
500
+ if (lane == 0u) out[(ulong)sample * (ulong)H + row] = T(value);
501
+ `;
502
+
503
+ /**
504
+ * Fixed M=1..gamma signed-Q8 kernel family for native MTP draft and accepted
505
+ * token absorption. The row dot-product and materialization boundaries are
506
+ * identical for every batch width; only grid.z changes.
507
+ */
508
+ export class Glm52CanonicalQ8MetalExecutor {
509
+ readonly #gateUp = new MetalKernel({
510
+ name: "mlx_bun_glm52_q8_slot_gate_up",
511
+ inputNames: ["x", "slot"],
512
+ outputNames: ["mid"],
513
+ source: Q8_GATE_UP_SOURCE,
514
+ ensureRowContiguous: true,
515
+ });
516
+ readonly #down = new MetalKernel({
517
+ name: "mlx_bun_glm52_q8_slot_down",
518
+ inputNames: ["mid", "slot"],
519
+ outputNames: ["out"],
520
+ source: Q8_DOWN_SOURCE,
521
+ ensureRowContiguous: true,
522
+ });
523
+ #disposed = false;
524
+
525
+ execute(
526
+ input: MlxArray,
527
+ slot: MlxArray,
528
+ layout: Glm52CanonicalQ8MetalLayout,
529
+ ): MlxArray {
530
+ if (this.#disposed)
531
+ throw new Error("GLM MTP Metal expert executor used after dispose");
532
+ const hidden = positiveInteger(layout.hiddenSize, "hidden size");
533
+ const intermediate = positiveInteger(
534
+ layout.intermediateSize,
535
+ "intermediate size",
536
+ );
537
+ const [rows, width] = input.shape;
538
+ if (
539
+ input.shape.length !== 2 ||
540
+ !rows ||
541
+ width !== hidden ||
542
+ (input.dtype !== Dtype.bfloat16 && input.dtype !== Dtype.float32)
543
+ ) {
544
+ throw new Error(
545
+ `GLM MTP Metal streamed expert requires [M,${hidden}] bf16/f32 input`,
546
+ );
547
+ }
548
+ if (hidden % ROWS_PER_THREADGROUP !== 0 ||
549
+ intermediate % ROWS_PER_THREADGROUP !== 0) {
550
+ throw new Error(
551
+ `Q8 expert dimensions must be divisible by ${ROWS_PER_THREADGROUP}`,
552
+ );
553
+ }
554
+ if (slot.dtype !== Dtype.uint8 || slot.shape.length !== 1 ||
555
+ slot.size < layout.slotBytes) {
556
+ throw new Error("canonical Q8 expert slot is invalid");
557
+ }
558
+ const ranges = [
559
+ [layout.downWeightOffset, hidden * intermediate, "down weights"],
560
+ [layout.gateWeightOffset, intermediate * hidden, "gate weights"],
561
+ [layout.upWeightOffset, intermediate * hidden, "up weights"],
562
+ [layout.downScaleOffset, hidden * 4, "down scales"],
563
+ [layout.gateScaleOffset, intermediate * 4, "gate scales"],
564
+ [layout.upScaleOffset, intermediate * 4, "up scales"],
565
+ ] as const;
566
+ for (const [offset, length, label] of ranges)
567
+ validateRange(offset, length, layout.slotBytes, label);
568
+
569
+ const templateInts = {
570
+ M: rows,
571
+ H: hidden,
572
+ I: intermediate,
573
+ DOWN_W: layout.downWeightOffset,
574
+ GATE_W: layout.gateWeightOffset,
575
+ UP_W: layout.upWeightOffset,
576
+ DOWN_S: layout.downScaleOffset,
577
+ GATE_S: layout.gateScaleOffset,
578
+ UP_S: layout.upScaleOffset,
579
+ ROWS_TG: ROWS_PER_THREADGROUP,
580
+ };
581
+ const [mid] = this.#gateUp.apply([input, slot], {
582
+ outputs: [{ shape: [rows, intermediate], dtype: input.dtype }],
583
+ grid: [THREADS, intermediate / ROWS_PER_THREADGROUP, rows],
584
+ threadGroup: [THREADS, 1, 1],
585
+ templateDtypes: { T: input.dtype },
586
+ templateInts,
587
+ });
588
+ if (!mid) throw new Error("GLM MTP Metal gate/up kernel returned no output");
589
+ try {
590
+ const [output] = this.#down.apply([mid, slot], {
591
+ outputs: [{ shape: [rows, hidden], dtype: input.dtype }],
592
+ grid: [THREADS, hidden / ROWS_PER_THREADGROUP, rows],
593
+ threadGroup: [THREADS, 1, 1],
594
+ templateDtypes: { T: input.dtype },
595
+ templateInts,
596
+ });
597
+ if (!output) throw new Error("GLM MTP Metal down kernel returned no output");
598
+ return output;
599
+ } finally {
600
+ mid.dispose();
601
+ }
602
+ }
603
+
604
+ dispose(): void {
605
+ if (this.#disposed) return;
606
+ this.#disposed = true;
607
+ this.#gateUp.dispose();
608
+ this.#down.dispose();
609
+ }
610
+ }