mlx-bun 0.5.0 → 1.0.0

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 (640) hide show
  1. package/README.md +1449 -109
  2. package/THIRD_PARTY_NOTICES.md +882 -0
  3. package/bin/mlx-bun.convert.mjs +4 -0
  4. package/bin/mlx-bun.fuse.mjs +4 -0
  5. package/bin/mlx-bun.generate.mjs +4 -0
  6. package/bin/mlx-bun.lora.mjs +4 -0
  7. package/bin/mlx-bun.mjs +13 -21
  8. package/bin/mlx-bun.server.mjs +4 -0
  9. package/bin/mlx-bun.upload.mjs +4 -0
  10. package/dist/web/app.js +9584 -0
  11. package/package.json +50 -53
  12. package/src/cli/ane-install.ts +76 -0
  13. package/src/cli/args.ts +179 -0
  14. package/src/cli/engine-entry.ts +68 -0
  15. package/src/cli/inference.ts +214 -0
  16. package/src/cli/job-entry.ts +97 -0
  17. package/src/cli/library-host.ts +117 -0
  18. package/src/cli/main.ts +57 -0
  19. package/src/cli/memory-engine.ts +163 -0
  20. package/src/cli/memory.ts +36 -0
  21. package/src/cli/mlx-lm-aliases.ts +309 -0
  22. package/src/cli/model-choice.ts +33 -0
  23. package/src/cli/model-selection.ts +84 -0
  24. package/src/cli/module-host.ts +11 -0
  25. package/src/cli/module-verbs.ts +50 -0
  26. package/src/cli/numerical-policy.ts +12 -0
  27. package/src/cli/perplexity.ts +89 -0
  28. package/src/cli/serve-host.ts +423 -0
  29. package/src/cli/serve-isolated.ts +217 -0
  30. package/src/cli/serve-options.ts +106 -0
  31. package/src/cli/serve-state.ts +205 -0
  32. package/src/cli/serve.ts +500 -0
  33. package/src/cli/served-model-host.ts +102 -0
  34. package/src/cli/server-entry.ts +148 -0
  35. package/src/cli/serving-unit.ts +177 -0
  36. package/src/{tui.ts → cli/terminal.ts} +13 -8
  37. package/src/cli/worker-adapters.ts +34 -0
  38. package/src/cli/worker-entry.ts +272 -0
  39. package/src/cli/worker-unit.ts +179 -0
  40. package/src/engine/adapter-operation.ts +26 -0
  41. package/src/engine/cache-services.ts +114 -0
  42. package/src/engine/completion.ts +39 -0
  43. package/src/engine/generation-gateway.ts +358 -0
  44. package/src/engine/index.ts +59 -0
  45. package/src/engine/kv-budget.ts +120 -0
  46. package/src/engine/media-preparation.ts +232 -0
  47. package/src/engine/model-binding.ts +24 -0
  48. package/src/engine/model-host.ts +513 -0
  49. package/src/engine/model-serving.ts +89 -0
  50. package/src/{serve → engine}/preparation.ts +7 -5
  51. package/src/{serve → engine}/session-completion-engine.ts +6 -6
  52. package/src/engine/telemetry.ts +109 -0
  53. package/src/hub/downloads.ts +116 -0
  54. package/src/jobs/db.ts +16 -7
  55. package/src/jobs/events.ts +29 -0
  56. package/src/jobs/executable.ts +5 -0
  57. package/src/jobs/host.ts +51 -0
  58. package/src/jobs/in-process.ts +55 -0
  59. package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
  60. package/src/jobs/runner.ts +150 -231
  61. package/src/jobs/service.ts +75 -0
  62. package/src/jobs/sse.ts +59 -23
  63. package/src/jobs/worker-memory.ts +46 -0
  64. package/src/jobs/worker-process.ts +163 -0
  65. package/src/jobs/worker-supervisor.ts +258 -0
  66. package/src/modules.ts +58 -0
  67. package/src/publishing/catalog-hub.ts +18 -0
  68. package/src/publishing/credentials.ts +31 -0
  69. package/src/publishing/upload.ts +37 -0
  70. package/src/residency/leased-adapters.ts +21 -0
  71. package/src/residency/local-records.ts +16 -0
  72. package/src/residency/model-residency.ts +405 -0
  73. package/src/residency/record-index.ts +28 -0
  74. package/src/residency/resident-estimate.ts +50 -0
  75. package/src/{anthropic.ts → server/anthropic.ts} +3 -81
  76. package/src/server/cache-routes.ts +36 -0
  77. package/src/{serve → server}/chat-request.ts +10 -10
  78. package/src/{serve → server}/chat-stage.ts +14 -14
  79. package/src/{client.ts → server/client.ts} +28 -2
  80. package/src/{serve → server}/completion-executor.ts +33 -41
  81. package/src/server/decision-body.ts +55 -0
  82. package/src/server/decision-routes.ts +134 -0
  83. package/src/server/decision-wire.ts +228 -0
  84. package/src/{serve → server}/discovery-routes.ts +25 -82
  85. package/src/{serve → server}/http.ts +25 -13
  86. package/src/{serve → server}/inference-request.ts +5 -5
  87. package/src/server/job-routes.ts +20 -0
  88. package/src/server/media-prompt.ts +63 -0
  89. package/src/server/memory-completion-client.ts +46 -0
  90. package/src/server/model-routes.ts +192 -0
  91. package/src/{serve → server}/openai-wire.ts +3 -2
  92. package/src/{serve → server}/pipeline.ts +1 -1
  93. package/src/server/prompt-builder.ts +13 -0
  94. package/src/server/prompt-contracts.ts +17 -0
  95. package/src/server/proxy-routes.ts +187 -0
  96. package/src/server/publishing-routes.ts +41 -0
  97. package/src/{serve → server}/request-plan.ts +6 -8
  98. package/src/{serve → server}/request-prep.ts +47 -50
  99. package/src/{serve → server}/responses-client.ts +30 -13
  100. package/src/{responses.ts → server/responses.ts} +12 -93
  101. package/src/server/routes.ts +205 -0
  102. package/src/server/start.ts +72 -0
  103. package/src/server/status-routes.ts +108 -0
  104. package/src/{serve → server}/text-completion-stage.ts +25 -4
  105. package/src/server/text-prompt.ts +12 -0
  106. package/src/{serve → server}/token-streams.ts +36 -38
  107. package/src/server/worker-routes.ts +311 -0
  108. package/src/storage/paths.ts +61 -0
  109. package/src/web/assets.ts +37 -0
  110. package/src/web/{src → browser}/api.ts +5 -23
  111. package/src/web/browser/chat-host.ts +17 -0
  112. package/src/web/browser/installed-panels.ts +6 -0
  113. package/src/web/browser/main.ts +55 -0
  114. package/src/web/browser/markdown.ts +279 -0
  115. package/src/web/browser/memory-panel.ts +9 -0
  116. package/src/web/browser/palette.ts +64 -0
  117. package/src/web/browser/protocol.ts +13 -0
  118. package/src/web/browser/shell.ts +467 -0
  119. package/src/web/{src → browser}/status.ts +32 -36
  120. package/src/web/build.ts +51 -0
  121. package/src/web/public/app.html +1054 -0
  122. package/src/web/{sw.js → public/sw.js} +4 -3
  123. package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
  124. package/src/web/{vendor → public/vendor}/hljs.js +2 -2
  125. package/THIRD_PARTY_LICENSES.md +0 -51
  126. package/docs/README.md +0 -107
  127. package/docs/archive/hlg-sampling.md +0 -350
  128. package/docs/archive/investigations/audio-input-plan.md +0 -356
  129. package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +0 -3617
  130. package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
  131. package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
  132. package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
  133. package/docs/archive/investigations/curve-bisector-routing.md +0 -123
  134. package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
  135. package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
  136. package/docs/archive/investigations/dspark-handoff.md +0 -224
  137. package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
  138. package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
  139. package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
  140. package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
  141. package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
  142. package/docs/archive/investigations/lab-build-journal.md +0 -157
  143. package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
  144. package/docs/archive/investigations/openjarvis-fit.md +0 -198
  145. package/docs/archive/investigations/openwiki-evaluation.md +0 -249
  146. package/docs/archive/investigations/optimization_plan.md +0 -346
  147. package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
  148. package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
  149. package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
  150. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
  151. package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
  152. package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
  153. package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
  154. package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
  155. package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
  156. package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
  157. package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
  158. package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
  159. package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
  160. package/docs/archive/planning/chat-ui-vision.md +0 -50
  161. package/docs/archive/planning/chunk-task-roadmap.md +0 -77
  162. package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
  163. package/docs/archive/planning/journal.md +0 -316
  164. package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
  165. package/docs/archive/planning/pi-integration-review.md +0 -178
  166. package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
  167. package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
  168. package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
  169. package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
  170. package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
  171. package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
  172. package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
  173. package/docs/archive/planning/release-notes-v0.4.0.md +0 -45
  174. package/docs/archive/planning/release-notes-v0.5.0.md +0 -37
  175. package/docs/archive/planning/web-ui-pass-plan.md +0 -213
  176. package/docs/archive/planning/website-readme-pass-plan.md +0 -224
  177. package/docs/design/batching.md +0 -2054
  178. package/docs/design/decode-speed-program.md +0 -2404
  179. package/docs/design/dreaming-nightly-pipeline.md +0 -684
  180. package/docs/design/generic-model-support.md +0 -821
  181. package/docs/design/kv-cache.md +0 -993
  182. package/docs/design/orpo-training.md +0 -680
  183. package/docs/design/speculative-decoding.md +0 -2129
  184. package/docs/design/turboquant.md +0 -2225
  185. package/docs/design/unified-engine-frontier-plan.md +0 -1987
  186. package/docs/design/web-chat-redesign.md +0 -695
  187. package/docs/planning/PRODUCT_ROADMAP.md +0 -593
  188. package/docs/planning/ResearchTopics.md +0 -119
  189. package/docs/planning/public-readiness-handoff.md +0 -244
  190. package/docs/planning/public-readiness.md +0 -149
  191. package/docs/planning/video-series.md +0 -421
  192. package/docs/reference/benchmarks.md +0 -3587
  193. package/docs/reference/cli.md +0 -646
  194. package/docs/reference/distribution.md +0 -380
  195. package/docs/reference/environment.md +0 -494
  196. package/docs/reference/glossary.md +0 -39
  197. package/docs/reference/library-api.md +0 -688
  198. package/docs/reference/memory.md +0 -260
  199. package/docs/reference/models.md +0 -346
  200. package/docs/reference/server-api.md +0 -1945
  201. package/docs/reference/server-config.md +0 -973
  202. package/docs/reference/training.md +0 -462
  203. package/docs/reference/troubleshooting.md +0 -158
  204. package/src/admission-plan.ts +0 -18
  205. package/src/audio/audiotoolbox.ts +0 -112
  206. package/src/audio/conformer.ts +0 -655
  207. package/src/audio/decode.ts +0 -133
  208. package/src/audio/features.ts +0 -202
  209. package/src/audio/mic-capture.ts +0 -98
  210. package/src/audio/silero-vad.ts +0 -379
  211. package/src/audio/transcode.ts +0 -67
  212. package/src/audio/whisper-decode.ts +0 -774
  213. package/src/audio/whisper-fast.ts +0 -732
  214. package/src/audio/whisper-format.ts +0 -73
  215. package/src/audio/whisper-mel.ts +0 -185
  216. package/src/audio/whisper-timing.ts +0 -274
  217. package/src/audio/whisper-tokenizer.ts +0 -239
  218. package/src/audio/whisper-transcribe.ts +0 -410
  219. package/src/backends/mlx/assistant-target.ts +0 -46
  220. package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
  221. package/src/backends/mlx/autoregressive.ts +0 -129
  222. package/src/backends/mlx/batch-group.ts +0 -1874
  223. package/src/backends/mlx/cache-attachment.ts +0 -67
  224. package/src/backends/mlx/cache-layout.ts +0 -31
  225. package/src/backends/mlx/checkpoint-state.ts +0 -53
  226. package/src/backends/mlx/continuation-persistence.ts +0 -107
  227. package/src/backends/mlx/continuation-request.ts +0 -43
  228. package/src/backends/mlx/continuation.ts +0 -77
  229. package/src/backends/mlx/diffusion.ts +0 -39
  230. package/src/backends/mlx/draft-target.ts +0 -32
  231. package/src/backends/mlx/encoder-cache.ts +0 -25
  232. package/src/backends/mlx/evaluation.ts +0 -105
  233. package/src/backends/mlx/fill-append.ts +0 -58
  234. package/src/backends/mlx/fill-group.ts +0 -497
  235. package/src/backends/mlx/full-kv-row-append.ts +0 -32
  236. package/src/backends/mlx/full-kv-row-donor.ts +0 -54
  237. package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
  238. package/src/backends/mlx/gateway-binding.ts +0 -174
  239. package/src/backends/mlx/graph.ts +0 -38
  240. package/src/backends/mlx/kv-codec.ts +0 -9
  241. package/src/backends/mlx/kv-maintenance.ts +0 -161
  242. package/src/backends/mlx/kv-tensor-rows.ts +0 -212
  243. package/src/backends/mlx/memory-client.ts +0 -189
  244. package/src/backends/mlx/methods.ts +0 -90
  245. package/src/backends/mlx/mixed-iteration.ts +0 -62
  246. package/src/backends/mlx/model-prompt.ts +0 -178
  247. package/src/backends/mlx/model-serving.ts +0 -80
  248. package/src/backends/mlx/paged-cache-codec.ts +0 -27
  249. package/src/backends/mlx/prefill-cohort.ts +0 -100
  250. package/src/backends/mlx/prefill-policy.ts +0 -26
  251. package/src/backends/mlx/prefill-rows.ts +0 -244
  252. package/src/backends/mlx/prefill.ts +0 -33
  253. package/src/backends/mlx/projected-context-rows.ts +0 -72
  254. package/src/backends/mlx/prompt-input.ts +0 -29
  255. package/src/backends/mlx/qwen-prompt-input.ts +0 -40
  256. package/src/backends/mlx/request-policy.ts +0 -20
  257. package/src/backends/mlx/request-state-policy.ts +0 -54
  258. package/src/backends/mlx/rollback.ts +0 -92
  259. package/src/backends/mlx/row-sampling.ts +0 -49
  260. package/src/backends/mlx/serial-executor.ts +0 -251
  261. package/src/backends/mlx/speculative-group.ts +0 -438
  262. package/src/backends/mlx/speculative-round.ts +0 -161
  263. package/src/backends/mlx/speculative.ts +0 -131
  264. package/src/backends/mlx/state-rows.ts +0 -104
  265. package/src/backends/mlx/state-views.ts +0 -46
  266. package/src/backends/mlx/target-layout-capability.ts +0 -35
  267. package/src/backends/mlx/text-engine.ts +0 -73
  268. package/src/backends/mlx/transitioning-kv-rows.ts +0 -173
  269. package/src/chat-template.ts +0 -382
  270. package/src/cli.ts +0 -3490
  271. package/src/config.ts +0 -399
  272. package/src/contracts/completion.ts +0 -16
  273. package/src/contracts/execution.ts +0 -70
  274. package/src/contracts/generation.ts +0 -103
  275. package/src/contracts/host.ts +0 -7
  276. package/src/contracts/object-cache.ts +0 -8
  277. package/src/contracts/pi-provider.ts +0 -30
  278. package/src/contracts/pi.ts +0 -261
  279. package/src/contracts/prefix-cache.ts +0 -33
  280. package/src/contracts/resources.ts +0 -25
  281. package/src/contracts/sampling.ts +0 -23
  282. package/src/contracts/scheduling.ts +0 -54
  283. package/src/contracts/tsconfig.json +0 -14
  284. package/src/dataset/generators.ts +0 -1087
  285. package/src/dataset/index.ts +0 -12
  286. package/src/dataset/job.ts +0 -81
  287. package/src/dataset/llm.ts +0 -71
  288. package/src/dataset/registry.ts +0 -528
  289. package/src/diffusion/diffusion-generate.ts +0 -507
  290. package/src/download.ts +0 -505
  291. package/src/embed.ts +0 -77
  292. package/src/engine/admission.ts +0 -59
  293. package/src/engine/cancellation.ts +0 -38
  294. package/src/engine/engine.ts +0 -56
  295. package/src/engine/execution-coordinator.ts +0 -70
  296. package/src/engine/execution-plan.ts +0 -64
  297. package/src/engine/execution-tasks.ts +0 -44
  298. package/src/engine/namespaced-cache.ts +0 -13
  299. package/src/engine/resources.ts +0 -53
  300. package/src/engine/scheduler.ts +0 -92
  301. package/src/engine/session.ts +0 -247
  302. package/src/eval/README.md +0 -130
  303. package/src/eval/ifeval.ts +0 -81
  304. package/src/eval/kl-prompts.ts +0 -29
  305. package/src/eval/kl.ts +0 -590
  306. package/src/eval/perplexity.ts +0 -164
  307. package/src/eval/quality-db.ts +0 -92
  308. package/src/eval/reference-logits.ts +0 -207
  309. package/src/eval/runner.ts +0 -124
  310. package/src/eval/score.ts +0 -49
  311. package/src/eval/tasks/bfcl.ts +0 -535
  312. package/src/eval/tasks/gsm8k.ts +0 -98
  313. package/src/eval/tasks/hashhop.ts +0 -139
  314. package/src/eval/tasks/humaneval.ts +0 -275
  315. package/src/eval/tasks/ifeval.ts +0 -525
  316. package/src/eval/tasks/mmlu.ts +0 -157
  317. package/src/eval/ultrafeedback.ts +0 -60
  318. package/src/evaldb.ts +0 -199
  319. package/src/expert-io.ts +0 -393
  320. package/src/expert-offload-build.ts +0 -77
  321. package/src/expert-offload.ts +0 -76
  322. package/src/expert-residency.ts +0 -1134
  323. package/src/expert-usage.ts +0 -415
  324. package/src/fill/echo-index.ts +0 -188
  325. package/src/fill/fill-session.ts +0 -463
  326. package/src/fill/proposal.ts +0 -68
  327. package/src/fill/schema-rows.ts +0 -506
  328. package/src/fill/tool-boundary.ts +0 -103
  329. package/src/fit.ts +0 -249
  330. package/src/generate.ts +0 -1352
  331. package/src/grammar.ts +0 -604
  332. package/src/harness-pi.ts +0 -184
  333. package/src/hf-push.ts +0 -628
  334. package/src/hub-rest.ts +0 -242
  335. package/src/index.ts +0 -63
  336. package/src/inference/denoising.ts +0 -17
  337. package/src/inference/draft-acceptance.ts +0 -41
  338. package/src/inference/draft-output.ts +0 -49
  339. package/src/inference/graph.ts +0 -33
  340. package/src/inference/prefill.ts +0 -45
  341. package/src/inference/rollback.ts +0 -7
  342. package/src/jobs/index.ts +0 -31
  343. package/src/jobs/job-entry.ts +0 -107
  344. package/src/jobs/task-client.ts +0 -19
  345. package/src/jobs/types.ts +0 -2
  346. package/src/kv-scheme.ts +0 -279
  347. package/src/kv-store.ts +0 -1135
  348. package/src/lab/README.md +0 -22
  349. package/src/lab/curve/curve-designer.html +0 -303
  350. package/src/lab/curve/curve-sampler.ts +0 -122
  351. package/src/lab/expert-trace/expert-trace.ts +0 -69
  352. package/src/lab/paged-kv/paged-attention.ts +0 -117
  353. package/src/lab/paged-kv/paged-kv-rows.ts +0 -111
  354. package/src/lab/paged-kv/paged-kv.ts +0 -336
  355. package/src/library.ts +0 -40
  356. package/src/lora.ts +0 -408
  357. package/src/md-modules.d.ts +0 -54
  358. package/src/media-fetch.ts +0 -442
  359. package/src/memory/article.ts +0 -558
  360. package/src/memory/chunk-validation.ts +0 -150
  361. package/src/memory/chunk.ts +0 -318
  362. package/src/memory/cluster.ts +0 -351
  363. package/src/memory/coverage.ts +0 -134
  364. package/src/memory/crosslink.ts +0 -0
  365. package/src/memory/db.ts +0 -253
  366. package/src/memory/entity.ts +0 -203
  367. package/src/memory/gate.ts +0 -187
  368. package/src/memory/history.ts +0 -152
  369. package/src/memory/ingest.ts +0 -335
  370. package/src/memory/model.ts +0 -183
  371. package/src/memory/normalize.ts +0 -671
  372. package/src/memory/parse.ts +0 -22
  373. package/src/memory/pipeline.ts +0 -275
  374. package/src/memory/prompts.ts +0 -65
  375. package/src/memory/query.ts +0 -334
  376. package/src/memory/reconcile.ts +0 -734
  377. package/src/memory/redirect.ts +0 -108
  378. package/src/memory/reindex.ts +0 -325
  379. package/src/memory/resolve.ts +0 -307
  380. package/src/memory/rest.ts +0 -245
  381. package/src/memory/route.ts +0 -272
  382. package/src/memory/schedule.ts +0 -165
  383. package/src/memory/stages.ts +0 -777
  384. package/src/memory/synthesize.ts +0 -1638
  385. package/src/memory/tools.ts +0 -749
  386. package/src/memory/vault.ts +0 -1194
  387. package/src/memory/wikify.ts +0 -1064
  388. package/src/mlx/array.ts +0 -425
  389. package/src/mlx/autograd.ts +0 -304
  390. package/src/mlx/checkpoint.ts +0 -149
  391. package/src/mlx/compile.ts +0 -168
  392. package/src/mlx/custom-vjp.ts +0 -188
  393. package/src/mlx/ffi.ts +0 -531
  394. package/src/mlx/materialize.ts +0 -23
  395. package/src/mlx/metal-kernel.ts +0 -159
  396. package/src/mlx/normalized-argmax.ts +0 -116
  397. package/src/mlx/ops.ts +0 -1108
  398. package/src/mlx/quantized-rows.ts +0 -36
  399. package/src/mlx/token-bitmask.ts +0 -52
  400. package/src/mlx/turboquant-kv-decode.ts +0 -82
  401. package/src/mlx/turboquant-kv-inverse.ts +0 -122
  402. package/src/mlx/turboquant-ops.ts +0 -861
  403. package/src/mlx/turboquant-tables.ts +0 -237
  404. package/src/mmap.ts +0 -90
  405. package/src/model/artifact-identity.ts +0 -73
  406. package/src/model/batched-kv.ts +0 -235
  407. package/src/model/batched-mask.ts +0 -279
  408. package/src/model/batched-quant.ts +0 -212
  409. package/src/model/batched-quantized-kv.ts +0 -144
  410. package/src/model/batched-rotating-quant.ts +0 -353
  411. package/src/model/batched-rotating-state.ts +0 -149
  412. package/src/model/batched-rotating.ts +0 -338
  413. package/src/model/batched-row-storage.ts +0 -179
  414. package/src/model/batched-ssm.ts +0 -52
  415. package/src/model/batched-turboquant-kv.ts +0 -94
  416. package/src/model/compiled-decode.ts +0 -712
  417. package/src/model/decoded-kv-donor.ts +0 -12
  418. package/src/model/delayed-quantized-kv.ts +0 -72
  419. package/src/model/delayed-rotating-quantized-kv.ts +0 -93
  420. package/src/model/delayed-turboquant-kv.ts +0 -57
  421. package/src/model/diffusion-gemma.ts +0 -1026
  422. package/src/model/factory.ts +0 -215
  423. package/src/model/fingerprint.ts +0 -45
  424. package/src/model/flash-attention.ts +0 -694
  425. package/src/model/full-prefill-padding.ts +0 -56
  426. package/src/model/full-prefill-row.ts +0 -105
  427. package/src/model/gemma4-base.ts +0 -2370
  428. package/src/model/gemma4.ts +0 -1395
  429. package/src/model/generated/gemma4-12b.ts +0 -429
  430. package/src/model/generated/gemma4-26b.ts +0 -448
  431. package/src/model/generated/gemma4-e4b.ts +0 -717
  432. package/src/model/generated/index.ts +0 -18
  433. package/src/model/glm52-atlas.ts +0 -511
  434. package/src/model/glm52-cache.ts +0 -792
  435. package/src/model/glm52-config.ts +0 -262
  436. package/src/model/glm52-container.ts +0 -305
  437. package/src/model/glm52-coupling.ts +0 -388
  438. package/src/model/glm52-dsa.ts +0 -606
  439. package/src/model/glm52-expert-layout.ts +0 -253
  440. package/src/model/glm52-layout.ts +0 -152
  441. package/src/model/glm52-memory.ts +0 -500
  442. package/src/model/glm52-mla.ts +0 -792
  443. package/src/model/glm52-moe.ts +0 -223
  444. package/src/model/glm52-pilot.ts +0 -537
  445. package/src/model/glm52-reference.ts +0 -445
  446. package/src/model/glm52-residency.ts +0 -557
  447. package/src/model/glm52-resident-weights.ts +0 -135
  448. package/src/model/glm52-streamed-experts.ts +0 -453
  449. package/src/model/glm52-streamed-metal.ts +0 -610
  450. package/src/model/glm52-weights.ts +0 -503
  451. package/src/model/glm52.ts +0 -1132
  452. package/src/model/implementation.ts +0 -69
  453. package/src/model/kv-attention-view.ts +0 -70
  454. package/src/model/minicpm5.ts +0 -376
  455. package/src/model/profile.ts +0 -468
  456. package/src/model/quantized-append-attention.ts +0 -31
  457. package/src/model/qwen-append.ts +0 -12
  458. package/src/model/qwen-conv.ts +0 -52
  459. package/src/model/qwen3-delta-state.ts +0 -74
  460. package/src/model/qwen3-delta.ts +0 -451
  461. package/src/model/qwen3-moe.ts +0 -404
  462. package/src/model/qwen3-mrope.ts +0 -250
  463. package/src/model/qwen3.ts +0 -277
  464. package/src/model/qwen3_5-checkpoint.ts +0 -164
  465. package/src/model/qwen3_5.ts +0 -881
  466. package/src/model/rotating-kv-donor.ts +0 -35
  467. package/src/model/rotating-kv-layout.ts +0 -262
  468. package/src/model/rotating-row-transaction.ts +0 -35
  469. package/src/model/speculative-rotating-kv.ts +0 -101
  470. package/src/model/ssm-prefill-padding.ts +0 -39
  471. package/src/model/support.ts +0 -110
  472. package/src/model/token-groups.ts +0 -66
  473. package/src/model/trellis-balanced-scatter.ts +0 -51
  474. package/src/model/trellis-linear.ts +0 -672
  475. package/src/model/trellis-shared-m.ts +0 -151
  476. package/src/model/trellis-shared-scatter.ts +0 -144
  477. package/src/model/trellis-splitk-prefill.ts +0 -158
  478. package/src/model/trellis-tiled-prefill.ts +0 -136
  479. package/src/model/trellis-vector-expand.ts +0 -62
  480. package/src/model/trellis-wide-prefill.ts +0 -94
  481. package/src/model/turboquant-codec.ts +0 -89
  482. package/src/model/universal/archs.ts +0 -332
  483. package/src/model/universal/dense.ts +0 -612
  484. package/src/model/universal/modules.ts +0 -170
  485. package/src/model/universal/rope.ts +0 -381
  486. package/src/model/whisper.ts +0 -435
  487. package/src/native/expert_io.c +0 -523
  488. package/src/native/frame_extract.swift +0 -140
  489. package/src/native/mic_capture.swift +0 -92
  490. package/src/native-pack.ts +0 -163
  491. package/src/pi-provider.ts +0 -53
  492. package/src/pi-session.ts +0 -81
  493. package/src/pi-terminal.ts +0 -274
  494. package/src/pi-web.ts +0 -1940
  495. package/src/preflight.ts +0 -158
  496. package/src/prompt-cache.ts +0 -682
  497. package/src/quantize/allocator.ts +0 -627
  498. package/src/quantize/atomic-output.ts +0 -38
  499. package/src/quantize/calibration.ts +0 -181
  500. package/src/quantize/config-writer.ts +0 -145
  501. package/src/quantize/index.ts +0 -84
  502. package/src/quantize/job.ts +0 -162
  503. package/src/quantize/quantizer.ts +0 -666
  504. package/src/quantize/rotate.ts +0 -603
  505. package/src/quantize/safetensors-writer.ts +0 -249
  506. package/src/quantize/sensitivity.ts +0 -216
  507. package/src/quantize/trellis.ts +0 -586
  508. package/src/quantize/weight-transform.ts +0 -292
  509. package/src/registry.ts +0 -602
  510. package/src/runtime-config.ts +0 -77
  511. package/src/safetensors.ts +0 -165
  512. package/src/sampler.ts +0 -1012
  513. package/src/serve/admin-routes.ts +0 -184
  514. package/src/serve/assets/curve-terrain.html +0 -105
  515. package/src/serve/assets/training-inference-map.html +0 -575
  516. package/src/serve/audio-routes.ts +0 -323
  517. package/src/serve/aux-routes.ts +0 -132
  518. package/src/serve/batch-scheduler.ts +0 -8
  519. package/src/serve/checkpoint-identity.ts +0 -64
  520. package/src/serve/generation-gateway.ts +0 -537
  521. package/src/serve/isolate.ts +0 -609
  522. package/src/serve/kv-budget.ts +0 -14
  523. package/src/serve/lab-routes.ts +0 -341
  524. package/src/serve/lane-registry.ts +0 -45
  525. package/src/serve/model-admin-routes.ts +0 -155
  526. package/src/serve/model-binding.ts +0 -49
  527. package/src/serve/model-host.ts +0 -511
  528. package/src/serve/parent-application.ts +0 -56
  529. package/src/serve/prompt-response-trace.ts +0 -161
  530. package/src/serve/response-lease.ts +0 -24
  531. package/src/serve/session-search.ts +0 -326
  532. package/src/serve/static-routes.ts +0 -101
  533. package/src/serve/transcription-server.ts +0 -64
  534. package/src/serve/transcription-service.ts +0 -502
  535. package/src/server.ts +0 -1282
  536. package/src/spec/assistant-rows.ts +0 -93
  537. package/src/spec/assistant-source.ts +0 -126
  538. package/src/spec/cache-identity.ts +0 -22
  539. package/src/spec/deepspec-rows.ts +0 -29
  540. package/src/spec/deepspec-source.ts +0 -145
  541. package/src/spec/dflash-source.ts +0 -149
  542. package/src/spec/drafter.ts +0 -304
  543. package/src/spec/dspark/ab-stats.ts +0 -136
  544. package/src/spec/dspark/calibration.ts +0 -112
  545. package/src/spec/dspark/data-dflash.ts +0 -156
  546. package/src/spec/dspark/data.ts +0 -201
  547. package/src/spec/dspark/deepspec-module.ts +0 -776
  548. package/src/spec/dspark/generate-dflash.ts +0 -245
  549. package/src/spec/dspark/generate.ts +0 -290
  550. package/src/spec/dspark/loader.ts +0 -23
  551. package/src/spec/dspark/loss.ts +0 -213
  552. package/src/spec/dspark/module-dflash.ts +0 -560
  553. package/src/spec/dspark/module.ts +0 -540
  554. package/src/spec/dspark/quantize-drafter.ts +0 -91
  555. package/src/spec/dspark/sample.ts +0 -112
  556. package/src/spec/generate.ts +0 -276
  557. package/src/spec/glm52-mtp-graph.ts +0 -29
  558. package/src/spec/glm52-mtp-rows.ts +0 -132
  559. package/src/spec/glm52-mtp-source.ts +0 -318
  560. package/src/spec/glm52-mtp-state.ts +0 -33
  561. package/src/spec/ngram-source.ts +0 -226
  562. package/src/spec/projected-draft-rows.ts +0 -73
  563. package/src/spec/qwen-mtp-module.ts +0 -171
  564. package/src/spec/qwen-mtp-rows.ts +0 -230
  565. package/src/spec/qwen-mtp-source.ts +0 -386
  566. package/src/spec/qwen-mtp-state.ts +0 -30
  567. package/src/spec/serve-loop.ts +0 -587
  568. package/src/spec/source.ts +0 -228
  569. package/src/spec/two-model-rows.ts +0 -62
  570. package/src/spec/two-model.ts +0 -113
  571. package/src/ssd-cache.ts +0 -431
  572. package/src/ssd-durability.ts +0 -209
  573. package/src/storage/host-buffer.ts +0 -24
  574. package/src/storage/kv-writer.ts +0 -83
  575. package/src/storage/kv-writer.worker.js +0 -264
  576. package/src/storage/retention-policy.ts +0 -37
  577. package/src/tiered-prompt-cache.ts +0 -45
  578. package/src/tokenizer.ts +0 -335
  579. package/src/tool-approvals.ts +0 -131
  580. package/src/tool-call.ts +0 -695
  581. package/src/train/dataset.ts +0 -484
  582. package/src/train/diffusion-lora.ts +0 -216
  583. package/src/train/export.ts +0 -32
  584. package/src/train/flash-cce.ts +0 -1234
  585. package/src/train/forward.ts +0 -266
  586. package/src/train/fuse.ts +0 -243
  587. package/src/train/index.ts +0 -46
  588. package/src/train/job.ts +0 -205
  589. package/src/train/lora-params.ts +0 -254
  590. package/src/train/loss.ts +0 -1591
  591. package/src/train/merge.ts +0 -195
  592. package/src/train/optimizer.ts +0 -183
  593. package/src/train/prefix-shared.ts +0 -490
  594. package/src/train/rank.ts +0 -137
  595. package/src/train/segmented.ts +0 -1828
  596. package/src/train/steel-qmm-header.ts +0 -1461
  597. package/src/train/trainer.ts +0 -1221
  598. package/src/train/watch.ts +0 -542
  599. package/src/vision/diffusion-vision.ts +0 -620
  600. package/src/vision/embedder.ts +0 -176
  601. package/src/vision/preprocess.ts +0 -247
  602. package/src/vision/prompt.ts +0 -409
  603. package/src/vision/qwen3vl-preprocess.ts +0 -409
  604. package/src/vision/qwen3vl-prompt.ts +0 -233
  605. package/src/vision/qwen3vl-tower.ts +0 -566
  606. package/src/vision/siglip.ts +0 -607
  607. package/src/vision/video-frames.ts +0 -148
  608. package/src/web/app.html +0 -2155
  609. package/src/web/app.js +0 -5863
  610. package/src/web/skills/memory/SKILL.md +0 -120
  611. package/src/web/skills/web-research/SKILL.md +0 -46
  612. package/src/web/skills.ts +0 -97
  613. package/src/web/src/adapters-panel.ts +0 -288
  614. package/src/web/src/assistant.ts +0 -451
  615. package/src/web/src/chat.ts +0 -1060
  616. package/src/web/src/composer.ts +0 -962
  617. package/src/web/src/dataset.ts +0 -132
  618. package/src/web/src/finetune.ts +0 -267
  619. package/src/web/src/hub.ts +0 -428
  620. package/src/web/src/main.ts +0 -98
  621. package/src/web/src/markdown.ts +0 -0
  622. package/src/web/src/memory-panel.ts +0 -572
  623. package/src/web/src/model-picker.ts +0 -159
  624. package/src/web/src/palette.ts +0 -346
  625. package/src/web/src/protocol.ts +0 -26
  626. package/src/web/src/quantize.ts +0 -167
  627. package/src/web/src/rag.ts +0 -274
  628. package/src/web/src/sessions.ts +0 -469
  629. package/src/web/src/shell.ts +0 -804
  630. package/src/web/src/ui-catalog.ts +0 -78
  631. package/src/web/src/voice.ts +0 -141
  632. package/src/web/vendor/README.md +0 -61
  633. package/src/web-assets.ts +0 -47
  634. package/src/web-tools.ts +0 -478
  635. package/src/weights.ts +0 -255
  636. /package/src/{serve → server}/completion-sink.ts +0 -0
  637. /package/src/{serve → server}/generated-token-history.ts +0 -0
  638. /package/src/web/{icon.svg → public/icon.svg} +0 -0
  639. /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
  640. /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
@@ -1,1234 +0,0 @@
1
- // Flash Cut-Cross-Entropy Metal kernel for the ORPO log-prob head — the vocab
2
- // loop lives INSIDE the kernel, so neither the [M,V] logits nor a dequantized
3
- // [V,hidden] head ever touch HBM (the residency bound pure MLX can't reach; see
4
- // docs/design/orpo-training.md → "Vocab-blocked online-softmax"). Computes the
5
- // per-token response log-prob `logp = target_logit − logsumexp_v(h·W_v)`.
6
- //
7
- // TRANSPILE SOURCE — this is, in effect, Apple's Cut Cross Entropy + Liger's
8
- // FusedLinearCrossEntropy Triton kernels transpiled Triton → Metal:
9
- // - Apple CCE (github.com/apple/ml-cross-entropy):
10
- // cce_lse_forward.py — forward: tile (BLOCK_B tokens × BLOCK_V vocab),
11
- // accum = tl.dot(E_tile, Cᵀ_tile) over a D-loop, per-block
12
- // this_lse = mx + log(Σ exp(logit−mx)), merged across vocab blocks into a
13
- // per-token LSE via locked atomic logaddexp; mask offs_v<V → −inf.
14
- // cce_backward.py — backward: same (token×vocab) tiling, recompute logits,
15
- // d_accum = exp(accum − lse); d_accum += where(is_target, −1, 0); then
16
- // dE += d_accum @ C and dC += d_accumᵀ @ E via locked atomic adds;
17
- // optional skip when |d_accum| < filter_eps.
18
- // - Liger FusedLinearCrossEntropy — token-chunking + the in-place softmax−onehot
19
- // gradient written back over the logit buffer.
20
- //
21
- // DIVERGENCES (why this isn't a line-for-line port):
22
- // 1. Our classifier C is the QUANTIZED head (4/8-bit affine). Apple loads C
23
- // dense (bf16); we keep the in-Metal dequant for `tl.dot` (the qdot pattern
24
- // from mlx's quantized.h qdot pattern). This is the one substantive change.
25
- // 2. The head is FROZEN (not a LoRA target) → we need only dE (=dh), NOT dC.
26
- // 3. The LSE cross-vocab-block merge is done in cheap MLX ops on per-block
27
- // partials, not Metal atomic-logaddexp spinlocks (simpler, same result).
28
- //
29
- // STATUS: v1 (one threadgroup per token, whole vocab serially) is correct +
30
- // memory-bounded (validated MiniCPM5→M=8192, e4b→M=512) but its single-token
31
- // threadgroup runs the full 262k vocab serially → trips the GPU watchdog on e4b
32
- // at higher M. The vocab-PARALLEL form below (grid over token × vocab blocks, à la
33
- // Apple CCE) bounds per-threadgroup work and is the production path.
34
-
35
- import { MetalKernel } from "../mlx/metal-kernel";
36
- import { MlxArray } from "../mlx/array";
37
- import { Dtype } from "../mlx/ffi";
38
- import * as ops from "../mlx/ops";
39
- import { STEEL_QMM_HEADER } from "./steel-qmm-header";
40
- import { runtimeValue } from "../runtime-config";
41
-
42
- /** The quantized head pieces the kernel needs (packed weight + per-group affine
43
- * scale/bias + bits/group_size). Mirrors HeadQuant in loss.ts. */
44
- export interface FlashCceHead {
45
- w: MlxArray; // packed quantized weight, uint32 [V, hidden*bits/32]
46
- scales: MlxArray; // [V, hidden/groupSize]
47
- biases: MlxArray; // [V, hidden/groupSize] (affine — required)
48
- bits: number; // 4 or 8
49
- groupSize: number; // 64
50
- softcap: number | null;
51
- }
52
-
53
- const H_MAX = 4096; // covers MiniCPM5 (1536) and e4b (2560) hidden sizes
54
- const TG = 128; // threads per token-threadgroup
55
- // Vocab rows per threadgroup (Apple CCE's BLOCK_V analog). Bounds per-threadgroup
56
- // runtime so a single dispatch never loops the whole vocab — the e4b watchdog fix.
57
- const VOCAB_BLOCK = 8192;
58
- // Tokens processed together per threadgroup (Apple CCE's BLOCK_B). Each thread
59
- // dequantizes a vocab row's weights ONCE and reuses them across BLOCK_B tokens,
60
- // amortizing the ALU-bound dequant BLOCK_B× (the dominant cost in the v1
61
- // one-token-per-threadgroup kernel). Forward and backward use different tile
62
- // widths: the backward holds a per-token output accumulator `dacc[BLOCK_B·DPER]`
63
- // in registers, so it is register-bound to a smaller tile (BLOCK_B=8 spills).
64
- const BLOCK_B = 8; // forward (online stats are only ~7·BLOCK_B floats/thread)
65
- const BLOCK_B_BWD = 4; // backward (dacc[BLOCK_B·DPER] — 8 overflows the register file)
66
- // simdgroup_matrix forward GEMM tiles (Apple CCE's BLOCK_B×BLOCK_V×BLOCK_D, mapped
67
- // to Metal 8×8 simdgroup_matrix). BB tokens × BV vocab logit tile, accumulated over
68
- // BD hidden chunks. BV/8 = 4 column-blocks → one per simdgroup (TG/32 = 4).
69
- const SG_BB = 8; // token tile (one 8-row simdgroup block)
70
- const SG_BV = 32; // vocab tile (4 column-blocks)
71
- const SG_BD = 32; // hidden chunk (4 k-blocks). BD=64 (GS-aligned, ½ the barriers)
72
- // measured SLOWER (fwd 844→1010, bwd 3311→3799): the 8KB Csh tile
73
- // cuts occupancy more than fewer barriers help. 32 is the sweet spot.
74
-
75
- // VOCAB-PARALLEL forward (Apple CCE cce_lse_forward, transpiled): one threadgroup
76
- // per (token=grid.z, vocab-block=grid.y) handles a bounded V/NBLK vocab slice → no
77
- // single threadgroup runs the whole vocab (the watchdog fix). Each writes per-block
78
- // partials (blockMax, blockSumexp-relative-to-blockMax, target logit) to [M,NBLK];
79
- // the cross-vocab-block LSE merge is done in cheap MLX ops (vs Apple's locked
80
- // atomic logaddexp). 128 threads stride the slice, per-thread online softmax,
81
- // merged across the threadgroup.
82
- const FWD_SOURCE = String.raw`
83
- const uint tokBlk = threadgroup_position_in_grid.z; // token-block index
84
- const uint vb = threadgroup_position_in_grid.y; // vocab-block index
85
- const uint tid = thread_position_in_threadgroup.x; // 0..TG-1
86
- const uint M = shape[0];
87
- const uint V = shape[1];
88
- const uint H = shape[2];
89
- const uint GS = shape[3];
90
- const uint BITS = shape[4];
91
- const uint WPR = shape[5]; // uint32 words per packed weight row
92
- const uint GR = shape[6]; // groups per row (H/GS)
93
- const uint NBLK = shape[7]; // number of vocab blocks
94
- const float cap = capv[0];
95
- const uint perWord = 32u / BITS;
96
- const uint qmask = (BITS == 32u) ? 0xffffffffu : ((1u << BITS) - 1u);
97
- const uint wordsPerGroup = GS / perWord;
98
- const uint blockV = (V + NBLK - 1u) / NBLK;
99
- const uint nStart = vb * blockV;
100
- const uint nEnd = min(nStart + blockV, V);
101
- const uint tokBase = tokBlk * BLOCK_B;
102
-
103
- // Per-token online state (registers). Each thread keeps BLOCK_B tokens' running
104
- // softmax stats so one vocab-row dequant feeds all BLOCK_B tokens.
105
- float mx[BLOCK_B], sumexp[BLOCK_B], tgtLogit[BLOCK_B];
106
- uint tgtId[BLOCK_B];
107
- for (uint b = 0u; b < BLOCK_B; ++b) {
108
- mx[b] = -INFINITY; sumexp[b] = 0.0f; tgtLogit[b] = 0.0f;
109
- tgtId[b] = (tokBase + b < M) ? targets[tokBase + b] : 0xffffffffu;
110
- }
111
-
112
- for (uint n = nStart + tid; n < nEnd; n += TG) {
113
- const device uint* row = wp + (ulong)n * WPR;
114
- const uint sbBase = n * GR;
115
- float partial[BLOCK_B];
116
- for (uint b = 0u; b < BLOCK_B; ++b) partial[b] = 0.0f;
117
- // qdot factoring per group; the nibble dequant (q) is computed ONCE per
118
- // element and reused across all BLOCK_B tokens (the amortization).
119
- for (uint grp = 0u; grp < GR; ++grp) {
120
- const float sc = scales[sbBase + grp];
121
- const float bi = biases[sbBase + grp];
122
- const uint d0 = grp * GS;
123
- float gacc[BLOCK_B], ghsum[BLOCK_B];
124
- for (uint b = 0u; b < BLOCK_B; ++b) { gacc[b] = 0.0f; ghsum[b] = 0.0f; }
125
- for (uint wi = 0u; wi < wordsPerGroup; ++wi) {
126
- const uint word = row[d0 / perWord + wi];
127
- const uint dbase = d0 + wi * perWord;
128
- for (uint k = 0u; k < perWord; ++k) {
129
- const float q = (float)((word >> (BITS * k)) & qmask);
130
- const uint d = dbase + k;
131
- for (uint b = 0u; b < BLOCK_B; ++b) {
132
- const uint tb = min(tokBase + b, M - 1u); // clamp padding lanes (last block) — result discarded in merge
133
- // h is passed TRANSPOSED [H, M], so the BLOCK_B inner reads hit one
134
- // cache line (h[d, tb..tb+BLOCK_B] contiguous) instead of BLOCK_B
135
- // separate lines — the memory-access fix for the strided pattern.
136
- const float hd = (float)h[(ulong)d * M + tb];
137
- gacc[b] += hd * q; ghsum[b] += hd;
138
- }
139
- }
140
- }
141
- for (uint b = 0u; b < BLOCK_B; ++b) partial[b] += sc * gacc[b] + bi * ghsum[b];
142
- }
143
- for (uint b = 0u; b < BLOCK_B; ++b) {
144
- float logit = partial[b];
145
- if (cap > 0.0f) logit = cap * tanh(logit / cap);
146
- const float nm = max(mx[b], logit);
147
- sumexp[b] = sumexp[b] * exp(mx[b] - nm) + exp(logit - nm);
148
- mx[b] = nm;
149
- if (n == tgtId[b]) tgtLogit[b] = logit;
150
- }
151
- }
152
-
153
- // Merge the TG threads' partials per token → per-(token, vocab-block) output.
154
- threadgroup float mM[TG], mS[TG], mT[TG];
155
- for (uint b = 0u; b < BLOCK_B; ++b) {
156
- if (tokBase + b >= M) break;
157
- mM[tid] = mx[b]; mS[tid] = sumexp[b]; mT[tid] = tgtLogit[b];
158
- threadgroup_barrier(mem_flags::mem_threadgroup);
159
- if (tid == 0u) {
160
- float gm = -INFINITY, gs = 0.0f, gt = 0.0f;
161
- for (uint i = 0u; i < TG; ++i) {
162
- const float im = mM[i];
163
- const float nm = max(gm, im);
164
- if (nm > -INFINITY) { gs = gs * exp(gm - nm) + mS[i] * exp(im - nm); gm = nm; }
165
- gt += mT[i]; // target logit (one thread nonzero), else 0
166
- }
167
- blockMax_out[(tokBase + b) * NBLK + vb] = gm;
168
- blockSum_out[(tokBase + b) * NBLK + vb] = gs;
169
- blockTgt_out[(tokBase + b) * NBLK + vb] = gt;
170
- }
171
- threadgroup_barrier(mem_flags::mem_threadgroup);
172
- }
173
- `;
174
-
175
- // SIMDGROUP_MATRIX forward GEMM (the speed tier): per (token-block, vocab-block)
176
- // threadgroup, loop the vocab slice in BV-chunks; for each chunk compute the
177
- // [BB, BV] logit tile with 8×8 simdgroup_matrix mma over BD hidden chunks, then a
178
- // scalar online-softmax update. The classifier tile C [BV, BD] is dequantized into
179
- // threadgroup once per (vocab-chunk, D-chunk) and reused across all BB tokens by
180
- // the matmul — so the dequant is amortized AND the matmul runs at simdgroup-matrix
181
- // throughput (the scalar madd was the bottleneck after BLOCK_B tiling). BV/8 = 4
182
- // column-blocks → one per simdgroup (TG/32 = 4). logit[b,v] = Σ_d E[b,d]·C[v,d] =
183
- // E @ Cᵀ (the C block is loaded transposed).
184
- const FWD_SG_SOURCE = String.raw`
185
- const uint tokBlk = threadgroup_position_in_grid.z;
186
- const uint vb = threadgroup_position_in_grid.y;
187
- const uint tid = thread_position_in_threadgroup.x;
188
- const uint sg = tid / 32u; // simdgroup 0..3
189
- const uint M=shape[0], V=shape[1], H=shape[2], GS=shape[3], BITS=shape[4], WPR=shape[5], GR=shape[6], NBLK=shape[7];
190
- const float cap = capv[0];
191
- const uint perWord = 32u / BITS;
192
- const uint qmask = (BITS == 32u) ? 0xffffffffu : ((1u << BITS) - 1u);
193
- const uint blockV = (V + NBLK - 1u) / NBLK;
194
- const uint nStart = vb * blockV;
195
- const uint nEnd = min(nStart + blockV, V);
196
- const uint tokBase = tokBlk * BB;
197
-
198
- // +4-float padded leading dims (MLX steel's BK_padded trick): a stride of 32 ==
199
- // 32 banks → 32-way bank conflicts on every simdgroup_load; 36 breaks alignment.
200
- threadgroup float Esh[BB * BDX];
201
- threadgroup float Csh[BV * BDX];
202
- threadgroup float Lsh[BB * BVX];
203
- threadgroup float tgMax[BB], tgSum[BB], tgTgt[BB];
204
- threadgroup uint tgTid[BB];
205
- if (tid < BB) {
206
- tgMax[tid] = -INFINITY; tgSum[tid] = 0.0f; tgTgt[tid] = 0.0f;
207
- tgTid[tid] = (tokBase + tid < M) ? targets[tokBase + tid] : 0xffffffffu;
208
- }
209
- threadgroup_barrier(mem_flags::mem_threadgroup);
210
-
211
- for (uint v0 = nStart; v0 < nEnd; v0 += BV) {
212
- simdgroup_float8x8 acc = simdgroup_float8x8(0);
213
- for (uint d0 = 0u; d0 < H; d0 += BD) {
214
- // E tile [BB, BD] (token hiddens)
215
- for (uint i = tid; i < BB * BD; i += TG) {
216
- const uint b = i / BD, dd = i % BD;
217
- const uint tb = min(tokBase + b, M - 1u);
218
- Esh[b * BDX + dd] = (float)h[(ulong)tb * H + d0 + dd];
219
- }
220
- // C tile [BV, BD] (dequantized head, reused across all BB tokens).
221
- #if CCE_SG_SKIPDQ
222
- // PROBE: no global loads — isolates whether the staging loads are the limiter.
223
- for (uint i = tid; i < BV * BD; i += TG) {
224
- const uint vrow = i / BD, dd = i % BD;
225
- Csh[vrow * BDX + dd] = (v0 + vrow < nEnd) ? ((float)((i & 7u)) * 0.01f + 0.001f) : 0.0f;
226
- }
227
- #else
228
- // ONE THREAD PER PACKED WORD: each word loaded once (was perWord-redundant),
229
- // and scale/bias once per word's group (was per-element — the whole BD chunk
230
- // is one GS group, so this collapses ~perWord× of scale/bias traffic too).
231
- const uint wordsPerRow = BD / perWord;
232
- for (uint wi = tid; wi < BV * wordsPerRow; wi += TG) {
233
- const uint vrow = wi / wordsPerRow;
234
- const uint dloc = (wi % wordsPerRow) * perWord;
235
- const uint n = v0 + vrow;
236
- const uint d = d0 + dloc;
237
- uint word = 0u; float sc = 0.0f, bi = 0.0f;
238
- if (n < nEnd) {
239
- word = wp[(ulong)n * WPR + d / perWord];
240
- const uint grp = d / GS;
241
- sc = scales[n * GR + grp]; bi = biases[n * GR + grp];
242
- }
243
- for (uint k = 0u; k < perWord; ++k) {
244
- const float q = (float)((word >> (BITS * k)) & qmask);
245
- Csh[vrow * BDX + dloc + k] = sc * q + bi;
246
- }
247
- }
248
- #endif
249
- threadgroup_barrier(mem_flags::mem_threadgroup);
250
- // acc(this sg's 8 cols) += E @ Cᵀ over BD/8 k-blocks (padded strides BDX)
251
- for (uint kk = 0u; kk < BD / 8u; ++kk) {
252
- simdgroup_float8x8 me, mc;
253
- simdgroup_load(me, Esh + kk * 8u, BDX);
254
- simdgroup_load(mc, Csh + (ulong)sg * 8u * BDX + kk * 8u, BDX, ulong2(0, 0), true);
255
- simdgroup_multiply_accumulate(acc, me, mc, acc);
256
- }
257
- threadgroup_barrier(mem_flags::mem_threadgroup);
258
- }
259
- simdgroup_store(acc, Lsh + sg * 8u, BVX); // [0:BB, sg*8 : sg*8+8], padded stride
260
- threadgroup_barrier(mem_flags::mem_threadgroup);
261
-
262
- // Online-softmax update for BB tokens over this BV chunk (thread b<BB → token b).
263
- if (tid < BB) {
264
- const uint b = tid;
265
- for (uint v = 0u; v < BV; ++v) {
266
- const uint n = v0 + v;
267
- if (n >= nEnd) break;
268
- float logit = Lsh[b * BVX + v];
269
- if (cap > 0.0f) logit = cap * tanh(logit / cap);
270
- const float nm = max(tgMax[b], logit);
271
- tgSum[b] = tgSum[b] * exp(tgMax[b] - nm) + exp(logit - nm);
272
- tgMax[b] = nm;
273
- if (n == tgTid[b]) tgTgt[b] = logit;
274
- }
275
- }
276
- threadgroup_barrier(mem_flags::mem_threadgroup);
277
- }
278
-
279
- if (tid < BB && tokBase + tid < M) {
280
- blockMax_out[(tokBase + tid) * NBLK + vb] = tgMax[tid];
281
- blockSum_out[(tokBase + tid) * NBLK + vb] = tgSum[tid];
282
- blockTgt_out[(tokBase + tid) * NBLK + vb] = tgTgt[tid];
283
- }
284
- `;
285
-
286
- // LANE-SLICED forward (the real memory fix): instead of one thread per vocab row
287
- // (adjacent threads read rows WPR words apart → uncoalesced weight reads, the
288
- // dominant ~43 GB stream at ~7% bandwidth), a SIMDGROUP of 32 lanes cooperates on
289
- // ONE row — lane l reads words {l, l+32, ...} so the 32 lanes hit CONSECUTIVE
290
- // memory each step (coalesced). The lane-partial dots reduce with simd_sum. The
291
- // threadgroup's 4 simdgroups process 4 rows in parallel; BLOCK_B tokens amortize
292
- // the dequant (each row dequantized once, reused across tokens). Same coalesced
293
- // pattern as mlx's quantized.h qdot. h is passed TRANSPOSED [H, M].
294
- const SGN = TG / 32; // simdgroups per threadgroup (4)
295
- const FWD_LANE_SOURCE = String.raw`
296
- const uint tokBlk = threadgroup_position_in_grid.z;
297
- const uint vb = threadgroup_position_in_grid.y;
298
- const uint lane = thread_index_in_simdgroup; // 0..31 within the simdgroup
299
- const uint sg = thread_position_in_threadgroup.x / 32u; // 0..SGN-1
300
- const uint M=shape[0], V=shape[1], H=shape[2], GS=shape[3], BITS=shape[4], WPR=shape[5], GR=shape[6], NBLK=shape[7];
301
- const float cap = capv[0];
302
- const uint perWord = 32u / BITS;
303
- const uint qmask = (BITS == 32u) ? 0xffffffffu : ((1u << BITS) - 1u);
304
- const uint wordsPerLane = WPR / 32u; // WPR is a multiple of 32
305
- const uint blockV = (V + NBLK - 1u) / NBLK;
306
- const uint nStart = vb * blockV;
307
- const uint nEnd = min(nStart + blockV, V);
308
- const uint tokBase = tokBlk * BLOCK_B;
309
-
310
- float mx[BLOCK_B], sumexp[BLOCK_B], tgtLogit[BLOCK_B];
311
- uint tgtId[BLOCK_B];
312
- for (uint b = 0u; b < BLOCK_B; ++b) {
313
- mx[b] = -INFINITY; sumexp[b] = 0.0f; tgtLogit[b] = 0.0f;
314
- tgtId[b] = (tokBase + b < M) ? targets[tokBase + b] : 0xffffffffu;
315
- }
316
-
317
- // each simdgroup owns rows {nStart+sg, nStart+sg+SGN, ...}; its 32 lanes share the row
318
- for (uint n = nStart + sg; n < nEnd; n += SGN) {
319
- const device uint* row = wp + (ulong)n * WPR;
320
- const uint sbBase = n * GR;
321
- float partial[BLOCK_B];
322
- for (uint b = 0u; b < BLOCK_B; ++b) partial[b] = 0.0f;
323
- for (uint j = 0u; j < wordsPerLane; ++j) {
324
- const uint wi = lane + 32u * j; // coalesced across the 32 lanes
325
- const uint word = row[wi];
326
- const uint dbase = wi * perWord;
327
- for (uint k = 0u; k < perWord; ++k) {
328
- const uint d = dbase + k;
329
- const uint grp = d / GS;
330
- const float w = scales[sbBase + grp] * (float)((word >> (BITS * k)) & qmask) + biases[sbBase + grp];
331
- for (uint b = 0u; b < BLOCK_B; ++b) {
332
- const uint tb = min(tokBase + b, M - 1u);
333
- partial[b] += (float)h[(ulong)d * M + tb] * w; // h transposed [H, M]
334
- }
335
- }
336
- }
337
- // reduce each token's lane-partials across the simdgroup → full logit
338
- for (uint b = 0u; b < BLOCK_B; ++b) {
339
- float logit = simd_sum(partial[b]);
340
- if (cap > 0.0f) logit = cap * tanh(logit / cap);
341
- const float nm = max(mx[b], logit);
342
- sumexp[b] = sumexp[b] * exp(mx[b] - nm) + exp(logit - nm);
343
- mx[b] = nm;
344
- if (n == tgtId[b]) tgtLogit[b] = logit;
345
- }
346
- }
347
-
348
- // simd_sum broadcasts, so each simdgroup's online state is replicated across its
349
- // lanes; lane 0 of each sg publishes, then SGN states merge per token.
350
- threadgroup float mM[SGN * BLOCK_B], mS[SGN * BLOCK_B], mT[SGN * BLOCK_B];
351
- if (lane == 0u) {
352
- for (uint b = 0u; b < BLOCK_B; ++b) {
353
- mM[sg * BLOCK_B + b] = mx[b]; mS[sg * BLOCK_B + b] = sumexp[b]; mT[sg * BLOCK_B + b] = tgtLogit[b];
354
- }
355
- }
356
- threadgroup_barrier(mem_flags::mem_threadgroup);
357
- const uint tid = thread_position_in_threadgroup.x;
358
- if (tid < BLOCK_B && tokBase + tid < M) {
359
- const uint b = tid;
360
- float gm = -INFINITY, gs = 0.0f, gt = 0.0f;
361
- for (uint s = 0u; s < SGN; ++s) {
362
- const float im = mM[s * BLOCK_B + b];
363
- const float nm = max(gm, im);
364
- if (nm > -INFINITY) { gs = gs * exp(gm - nm) + mS[s * BLOCK_B + b] * exp(im - nm); gm = nm; }
365
- gt += mT[s * BLOCK_B + b];
366
- }
367
- blockMax_out[(tokBase + b) * NBLK + vb] = gm;
368
- blockSum_out[(tokBase + b) * NBLK + vb] = gs;
369
- blockTgt_out[(tokBase + b) * NBLK + vb] = gt;
370
- }
371
- `;
372
-
373
- let _fwdLaneKernel: MetalKernel | null = null;
374
- function fwdLaneKernel(): MetalKernel {
375
- if (!_fwdLaneKernel) {
376
- _fwdLaneKernel = new MetalKernel({
377
- name: "flash_cce_fwd_lane",
378
- inputNames: ["h", "wp", "scales", "biases", "targets", "shape", "capv"],
379
- outputNames: ["blockMax_out", "blockSum_out", "blockTgt_out"],
380
- source: FWD_LANE_SOURCE,
381
- header: `#define TG ${TG}\n#define BLOCK_B ${BLOCK_B}\n#define SGN ${SGN}\n`,
382
- ensureRowContiguous: true,
383
- });
384
- }
385
- return _fwdLaneKernel;
386
- }
387
-
388
- let _fwdSgKernel: MetalKernel | null = null;
389
- function fwdSgKernel(): MetalKernel {
390
- if (!_fwdSgKernel) {
391
- _fwdSgKernel = new MetalKernel({
392
- name: "flash_cce_fwd_sg",
393
- inputNames: ["h", "wp", "scales", "biases", "targets", "shape", "capv"],
394
- outputNames: ["blockMax_out", "blockSum_out", "blockTgt_out"],
395
- source: FWD_SG_SOURCE,
396
- header: `#include <metal_simdgroup_matrix>\n#define TG ${TG}\n#define BB ${SG_BB}\n#define BV ${SG_BV}\n#define BD ${SG_BD}\n#define BDX ${SG_BD + 4}\n#define BVX ${SG_BV + 4}\n#define CCE_SG_SKIPDQ ${runtimeValue("MLX_BUN_CCE_SG_SKIPDQ") === "1" ? 1 : 0}\n`,
397
- ensureRowContiguous: true,
398
- });
399
- }
400
- return _fwdSgKernel;
401
- }
402
- // simdgroup_matrix forward is now the DEFAULT (fastest): the earlier "not faster"
403
- // verdict was confounded — the SG path was bound by the Csh dequant STAGING loads
404
- // (packed weight + per-group scale/bias), not the matmul, so it never reached the
405
- // compute-bound regime. Removing the staging redundancy (one thread per packed
406
- // word; scale/bias once per group) cut it 1629→848 ms, beating the scalar
407
- // kernel's 1097 ms. Fall back to scalar (MLX_BUN_CCE_SCALAR=1) when H % SG_BD != 0.
408
- const USE_SIMDGROUP_FWD = runtimeValue("MLX_BUN_CCE_SCALAR") !== "1" && runtimeValue("MLX_BUN_CCE_LANE") !== "1";
409
-
410
- // Backward: dh[t,d] = Σ_v coeff_v · dequant(W_v)[d], coeff_v = g_t·(onehot_v −
411
- // softmax_v)·sech²_v, softmax_v = exp(softcap(logit_v) − lse_t). Two-phase per
412
- // 128-row vocab block, NO atomics: phase 1 each thread computes coeff for one
413
- // block row (full dot → into threadgroup coeff[]); phase 2 each thread owns
414
- // H/TG OUTPUT dims and accumulates them over the block's rows into per-thread
415
- // registers (dacc), so each output dim is written by exactly one thread. lse is
416
- // reused from the forward (no recompute of the global softmax normalizer).
417
- const DPER_MAX = H_MAX / TG; // 32
418
- const BWD_SOURCE = String.raw`
419
- const uint tokBlk = threadgroup_position_in_grid.z; // token-block
420
- const uint vb = threadgroup_position_in_grid.y; // vocab-block
421
- const uint tid = thread_position_in_threadgroup.x;
422
- const uint M = shape[0];
423
- const uint V = shape[1];
424
- const uint H = shape[2];
425
- const uint GS = shape[3];
426
- const uint BITS = shape[4];
427
- const uint WPR = shape[5];
428
- const uint GR = shape[6];
429
- const uint NBLK = shape[7];
430
- const float cap = capv[0];
431
- const uint perWord = 32u / BITS;
432
- const uint qmask = (BITS == 32u) ? 0xffffffffu : ((1u << BITS) - 1u);
433
- const uint wordsPerGroup = GS / perWord;
434
- const uint DPER = H / TG; // output dims this thread owns
435
- const uint dStart = tid * DPER;
436
- const uint blockV = (V + NBLK - 1u) / NBLK;
437
- const uint nStart = vb * blockV;
438
- const uint nEnd = min(nStart + blockV, V);
439
- const uint tokBase = tokBlk * BLOCK_B;
440
-
441
- float lseB[BLOCK_B], gB[BLOCK_B]; uint tgtB[BLOCK_B];
442
- for (uint b = 0u; b < BLOCK_B; ++b) {
443
- const uint tk = min(tokBase + b, M - 1u);
444
- lseB[b] = lse[tk]; gB[b] = gv[tk];
445
- tgtB[b] = (tokBase + b < M) ? targets[tokBase + b] : 0xffffffffu;
446
- }
447
-
448
- threadgroup float coeffB[TG * BLOCK_B]; // [row][token]
449
- // BLOCK_B == 4 → one float4 per output dim holds all 4 tokens, so the phase-2
450
- // accumulation (the backward's ~98% hotspot — a scalar coeffᵀ@W GEMM) runs as
451
- // one vector FMA per (row, dim) instead of 4 scalar ones.
452
- float4 dacc[DACC_N]; // dacc[j] = (tok0..tok3) for dim dStart+j
453
- for (uint i = 0u; i < DPER; ++i) dacc[i] = float4(0.0f);
454
- threadgroup_barrier(mem_flags::mem_threadgroup);
455
-
456
- for (uint blk = nStart; blk < nEnd; blk += TG) {
457
- // --- phase 1: row n = blk + tid → coeff for each of BLOCK_B tokens (the
458
- // dequant-dot is computed once per token via shared nibble q) ---
459
- const uint n = blk + tid;
460
- float cf[BLOCK_B];
461
- for (uint b = 0u; b < BLOCK_B; ++b) cf[b] = 0.0f;
462
- if (n < nEnd) {
463
- const device uint* row = wp + (ulong)n * WPR;
464
- const uint sbBase = n * GR;
465
- float raw[BLOCK_B];
466
- for (uint b = 0u; b < BLOCK_B; ++b) raw[b] = 0.0f;
467
- for (uint grp = 0u; grp < GR; ++grp) {
468
- const float sc = scales[sbBase + grp];
469
- const float bi = biases[sbBase + grp];
470
- const uint d0 = grp * GS;
471
- float gacc[BLOCK_B], ghsum[BLOCK_B];
472
- for (uint b = 0u; b < BLOCK_B; ++b) { gacc[b] = 0.0f; ghsum[b] = 0.0f; }
473
- for (uint wi = 0u; wi < wordsPerGroup; ++wi) {
474
- const uint word = row[d0 / perWord + wi];
475
- const uint dbase = d0 + wi * perWord;
476
- for (uint k = 0u; k < perWord; ++k) {
477
- const float q = (float)((word >> (BITS * k)) & qmask);
478
- const uint d = dbase + k;
479
- for (uint b = 0u; b < BLOCK_B; ++b) {
480
- const uint tk = min(tokBase + b, M - 1u);
481
- const float hd = (float)h[(ulong)tk * H + d]; // phase-1 h reads aren't the bwd bottleneck (phase 2 is)
482
- gacc[b] += hd * q; ghsum[b] += hd;
483
- }
484
- }
485
- }
486
- for (uint b = 0u; b < BLOCK_B; ++b) raw[b] += sc * gacc[b] + bi * ghsum[b];
487
- }
488
- for (uint b = 0u; b < BLOCK_B; ++b) {
489
- float logit = raw[b], sech2 = 1.0f;
490
- if (cap > 0.0f) { const float th = tanh(raw[b] / cap); logit = cap * th; sech2 = 1.0f - th * th; }
491
- const float sm = exp(logit - lseB[b]);
492
- float c = gB[b] * ((n == tgtB[b] ? 1.0f : 0.0f) - sm);
493
- if (cap > 0.0f) c *= sech2;
494
- cf[b] = c;
495
- }
496
- }
497
- for (uint b = 0u; b < BLOCK_B; ++b) coeffB[tid * BLOCK_B + b] = cf[b];
498
- threadgroup_barrier(mem_flags::mem_threadgroup);
499
-
500
- // --- phase 2: the coeffᵀ@W accumulation — the backward's ~98% hotspot. Each
501
- // weight dequant (wd) feeds all BLOCK_B tokens via one float4 FMA. (Loads are
502
- // negligible — proven ~137 ms — so the per-element dequant stays; the win is
503
- // vectorizing the arithmetic.) ---
504
- #if !CCE_BWD_SKIP_P2
505
- for (uint r = 0u; r < TG; ++r) {
506
- const uint n2 = blk + r;
507
- if (n2 >= nEnd) break;
508
- const device uint* row = wp + (ulong)n2 * WPR;
509
- const uint sbBase = n2 * GR;
510
- const float4 cf = *(const threadgroup float4*)(coeffB + r * BLOCK_B); // tok0..3
511
- #if CCE_BWD_SKIP_P2DQ
512
- for (uint j = 0u; j < DPER; ++j) dacc[j] += cf * ((float)((dStart + j) & 7u) * 0.01f);
513
- #else
514
- for (uint j = 0u; j < DPER; ++j) {
515
- const uint d = dStart + j;
516
- const uint grp = d / GS;
517
- const uint word = row[d / perWord];
518
- const float q = (float)((word >> (BITS * (d % perWord))) & qmask);
519
- const float wd = scales[sbBase + grp] * q + biases[sbBase + grp];
520
- dacc[j] += cf * wd;
521
- }
522
- #endif
523
- }
524
- #else
525
- // PROBE: skip phase-2 accumulation but consume coeffB so phase 1 isn't DCE'd —
526
- // isolates phase-1 (the coeff dot) cost from phase-2 accumulation.
527
- dacc[0] += float4(coeffB[tid * BLOCK_B]);
528
- #endif
529
- threadgroup_barrier(mem_flags::mem_threadgroup);
530
- }
531
-
532
- // Atomic-accumulate each token's partial dh across the NBLK vocab-block programs.
533
- // dacc[j] = (tok0..tok3) for dim dStart+j.
534
- for (uint b = 0u; b < BLOCK_B; ++b) {
535
- if (tokBase + b >= M) break;
536
- for (uint j = 0u; j < DPER; ++j)
537
- atomic_fetch_add_explicit(&dh[(ulong)(tokBase + b) * H + dStart + j], dacc[j][b], memory_order_relaxed);
538
- }
539
- `;
540
-
541
- // dacc is sized to the ACTUAL per-thread output dims (ceil(H/TG)) rather than the
542
- // worst-case DPER_MAX — right-sizing the register footprint raises occupancy (the
543
- // backward's limiter). Keyed by daccN so a model with a different H rebuilds.
544
- const _bwdKernels = new Map<number, MetalKernel>();
545
- function bwdKernel(daccN: number): MetalKernel {
546
- let k = _bwdKernels.get(daccN);
547
- if (!k) {
548
- k = new MetalKernel({
549
- name: "flash_cce_bwd",
550
- inputNames: ["h", "wp", "scales", "biases", "targets", "lse", "gv", "shape", "capv"],
551
- outputNames: ["dh"],
552
- source: BWD_SOURCE,
553
- header: `#define TG ${TG}\n#define BLOCK_B ${BLOCK_B_BWD}\n#define DACC_N ${daccN}\n#define CCE_BWD_SKIP_P2DQ ${runtimeValue("MLX_BUN_CCE_BWD_SKIP_P2DQ") === "1" ? 1 : 0}\n#define CCE_BWD_SKIP_P2 ${runtimeValue("MLX_BUN_CCE_BWD_SKIP_P2") === "1" ? 1 : 0}\n`,
554
- ensureRowContiguous: true,
555
- atomicOutputs: true, // dh is summed across the NBLK vocab-block programs
556
- });
557
- _bwdKernels.set(daccN, k);
558
- }
559
- return k;
560
- }
561
-
562
- // SIMDGROUP_MATRIX backward (phase-2 GEMM): the phase-2 accumulation dacc = coeffᵀ@W
563
- // is ~69% of the backward (1733 ms phase-1 + 3779 ms phase-2 at e4b M=512) and is
564
- // compute-bound, NOT register-bound (right-sizing dacc gave 0%) — so we can afford
565
- // simdgroup_matrix's larger accumulator. Phase 1 unchanged (one thread per row →
566
- // coeff into threadgroup, BB=8 tokens). Phase 2: each simdgroup owns H/SGN dims as
567
- // DIMTILES persistent 8×8 acc fragments; per 8-row chunk, load coeffᵀ (transpose)
568
- // and the dequantized W tile, mma. dh atomic-added at the end. Needs H % (SGN*8)==0.
569
- const BB_BWD_SG = 8;
570
- const BWD_SG_SOURCE = String.raw`
571
- const uint tokBlk = threadgroup_position_in_grid.z;
572
- const uint vb = threadgroup_position_in_grid.y;
573
- const uint tid = thread_position_in_threadgroup.x;
574
- const uint lane = thread_index_in_simdgroup;
575
- const uint sg = tid / 32u;
576
- const uint M=shape[0],V=shape[1],H=shape[2],GS=shape[3],BITS=shape[4],WPR=shape[5],GR=shape[6],NBLK=shape[7];
577
- const float cap = capv[0];
578
- const uint perWord = 32u / BITS;
579
- const uint qmask = (BITS == 32u) ? 0xffffffffu : ((1u << BITS) - 1u);
580
- const uint HSG = H / SGN; // dims owned by this simdgroup
581
- const uint d0sg = sg * HSG;
582
- const uint blockV = (V + NBLK - 1u) / NBLK;
583
- const uint nStart = vb * blockV;
584
- const uint nEnd = min(nStart + blockV, V);
585
- const uint tokBase = tokBlk * BB;
586
-
587
- float lseB[BB], gB[BB]; uint tgtB[BB];
588
- for (uint b = 0u; b < BB; ++b) {
589
- const uint tk = min(tokBase + b, M - 1u);
590
- lseB[b] = lse[tk]; gB[b] = gv[tk];
591
- tgtB[b] = (tokBase + b < M) ? targets[tokBase + b] : 0xffffffffu;
592
- }
593
-
594
- threadgroup float coeffB[TG * 8]; // [row][token], 8-wide for the simdgroup tile
595
- threadgroup float Wsh[SGN * 64]; // per-simdgroup 8×8 dequantized W tile
596
- threadgroup float Csh[SGN * 64]; // per-simdgroup 8×8 store-out buffer
597
- threadgroup float Esh[BB * BDP]; // phase-1: token hiddens tile (logit GEMM)
598
- threadgroup float Cstg[BVP * BDP]; // phase-1: dequantized W tile (logit GEMM)
599
- threadgroup float Lsh[BB * BVP]; // phase-1: logit[token, row] tile
600
-
601
- #if CCE_BWD_BLOCKSKIP
602
- // blockMax vocab-block early exit: this (token-block, vocab-block) program does
603
- // ~0 work if, for EVERY token, the block's max softmax prob exp(blockMax−lse) is
604
- // below eps AND the target isn't in this block (so no onehot term lives here).
605
- // On peaked logits ~all but one vocab block per token is cold → skips phase 1+2.
606
- // The early return is AFTER the threadgroup decls (uniform across the TG; declaring
607
- // threadgroup arrays after an early return fails to compile).
608
- bool warm = false;
609
- for (uint b = 0u; b < BB; ++b) {
610
- if (tokBase + b < M) {
611
- if (exp(blockMax[(tokBase + b) * NBLK + vb] - lseB[b]) >= CCE_BWD_BLOCK_EPS) warm = true;
612
- if (tgtB[b] >= nStart && tgtB[b] < nEnd) warm = true;
613
- }
614
- }
615
- if (!warm) return;
616
- #endif
617
-
618
- simdgroup_float8x8 acc[DIMTILES];
619
- for (uint t = 0u; t < DIMTILES; ++t) acc[t] = simdgroup_float8x8(0);
620
-
621
- for (uint blk = nStart; blk < nEnd; blk += TG) {
622
- // --- phase 1 (SG-matrix): logit[tok, row] = E @ Wᵀ over H (the SAME tile GEMM
623
- // as the forward, with the sub-block's TG rows as the "vocab"), then coeff. ---
624
- for (uint vc = 0u; vc < TG; vc += BVP) { // TG rows in BVP-row chunks
625
- simdgroup_float8x8 lacc = simdgroup_float8x8(0);
626
- for (uint d0 = 0u; d0 < H; d0 += BDP) {
627
- for (uint i = tid; i < BB * BDP; i += TG) { // E tile [BB tokens, BDP]
628
- const uint b = i / BDP, dd = i % BDP;
629
- const uint tk = min(tokBase + b, M - 1u);
630
- Esh[i] = (float)h[(ulong)tk * H + d0 + dd];
631
- }
632
- const uint wordsPerRow = BDP / perWord; // C tile [BVP rows, BDP] (one thread/word)
633
- for (uint wi = tid; wi < BVP * wordsPerRow; wi += TG) {
634
- const uint vrow = wi / wordsPerRow;
635
- const uint dloc = (wi % wordsPerRow) * perWord;
636
- const uint n2 = blk + vc + vrow;
637
- const uint d = d0 + dloc;
638
- uint word = 0u; float scl = 0.0f, bia = 0.0f;
639
- if (n2 < nEnd) {
640
- word = wp[(ulong)n2 * WPR + d / perWord];
641
- const uint grp = d / GS; scl = scales[n2 * GR + grp]; bia = biases[n2 * GR + grp];
642
- }
643
- for (uint k = 0u; k < perWord; ++k) Cstg[vrow * BDP + dloc + k] = scl * (float)((word >> (BITS * k)) & qmask) + bia;
644
- }
645
- threadgroup_barrier(mem_flags::mem_threadgroup);
646
- for (uint kk = 0u; kk < BDP / 8u; ++kk) { // lacc(sg's 8 rows) += E @ Cᵀ
647
- simdgroup_float8x8 me, mc;
648
- simdgroup_load(me, Esh + kk * 8u, BDP);
649
- simdgroup_load(mc, Cstg + (ulong)sg * 8u * BDP + kk * 8u, BDP, ulong2(0, 0), true);
650
- simdgroup_multiply_accumulate(lacc, me, mc, lacc);
651
- }
652
- threadgroup_barrier(mem_flags::mem_threadgroup);
653
- }
654
- simdgroup_store(lacc, Lsh + sg * 8u, BVP); // Lsh[tok*BVP + row] = logit[tok][row]
655
- threadgroup_barrier(mem_flags::mem_threadgroup);
656
- for (uint i = tid; i < BB * BVP; i += TG) { // derive coeff[row][tok] → coeffB
657
- const uint tok = i / BVP, row = i % BVP;
658
- const uint n2 = blk + vc + row;
659
- float c = 0.0f;
660
- if (n2 < nEnd && tokBase + tok < M) {
661
- float logit = Lsh[tok * BVP + row], sech2 = 1.0f;
662
- if (cap > 0.0f) { const float th = tanh(logit / cap); logit = cap * th; sech2 = 1.0f - th * th; }
663
- const float sm = exp(logit - lseB[tok]);
664
- c = gB[tok] * ((n2 == tgtB[tok] ? 1.0f : 0.0f) - sm);
665
- if (cap > 0.0f) c *= sech2;
666
- }
667
- coeffB[(vc + row) * 8u + tok] = c;
668
- }
669
- threadgroup_barrier(mem_flags::mem_threadgroup);
670
- }
671
-
672
- // --- phase 2: acc[t][tok][dim] += Σ_row coeff[row][tok] · W[row][dim] ---
673
- for (uint rc = 0u; rc < TG; rc += 8u) {
674
- // Apple-CCE near-zero coeff filter: softmax mass concentrates on a few
675
- // tokens, so coeff ≈ 0 for almost every vocab row → most 8-row chunks
676
- // contribute nothing. Skip the chunk's dim-tile dequant+mma when its whole
677
- // [8 rows × 8 tokens] coeff block is below eps. Safe because the SKIPPED
678
- // softmax mass (not each element) is what's tiny. Uniform across the
679
- // simdgroup (all lanes/sgs see the same coeffB[rc]).
680
- #if CCE_BWD_FILTER
681
- float cmax = 0.0f;
682
- for (uint i = lane; i < 64u; i += 32u) cmax = max(cmax, fabs(coeffB[rc * 8u + i]));
683
- if (simd_max(cmax) < CCE_BWD_FILTER_EPS) continue;
684
- #endif
685
- simdgroup_float8x8 mA;
686
- simdgroup_load(mA, coeffB + rc * 8u, 8, ulong2(0, 0), true); // mA[tok][row]
687
- threadgroup float* ws = Wsh + sg * 64u;
688
- for (uint t = 0u; t < DIMTILES; ++t) {
689
- const uint d0 = d0sg + t * 8u;
690
- for (uint i = lane; i < 64u; i += 32u) { // dequant W[8 rows, 8 dims]
691
- const uint rr = i / 8u, dd = i % 8u;
692
- const uint n2 = blk + rc + rr;
693
- float w = 0.0f;
694
- if (n2 < nEnd) {
695
- const uint d = d0 + dd;
696
- const uint word = wp[(ulong)n2 * WPR + d / perWord];
697
- const float q = (float)((word >> (BITS * (d % perWord))) & qmask);
698
- w = scales[n2 * GR + d / GS] * q + biases[n2 * GR + d / GS];
699
- }
700
- ws[i] = w;
701
- }
702
- simdgroup_barrier(mem_flags::mem_threadgroup);
703
- simdgroup_float8x8 mB;
704
- simdgroup_load(mB, ws, 8); // mB[row][dim]
705
- simdgroup_multiply_accumulate(acc[t], mA, mB, acc[t]);
706
- simdgroup_barrier(mem_flags::mem_threadgroup);
707
- }
708
- }
709
- threadgroup_barrier(mem_flags::mem_threadgroup);
710
- }
711
-
712
- // write each dim-tile's acc → dh (atomic across the NBLK vocab-block programs)
713
- threadgroup float* cs = Csh + sg * 64u;
714
- for (uint t = 0u; t < DIMTILES; ++t) {
715
- simdgroup_store(acc[t], cs, 8);
716
- simdgroup_barrier(mem_flags::mem_threadgroup);
717
- const uint d0 = d0sg + t * 8u;
718
- for (uint i = lane; i < 64u; i += 32u) {
719
- const uint tok = i / 8u, dd = i % 8u;
720
- if (tokBase + tok < M)
721
- atomic_fetch_add_explicit(&dh[(ulong)(tokBase + tok) * H + d0 + dd], cs[i], memory_order_relaxed);
722
- }
723
- simdgroup_barrier(mem_flags::mem_threadgroup);
724
- }
725
- `;
726
-
727
- // Apple-CCE coeff filter epsilon (phase-2 per-tile skip). DEFAULT ON at 1e-5
728
- // (flipped 2026-07-02 — the kernel-perf-review backlog #1 flip). The old
729
- // 0.66%→2.7% dh-accuracy scare was on RANDOM targets (flat softmax, the filter's
730
- // worst case — see PLAN.md "filter-on-real-data"); the real-data gate
731
- // (flash-cce-filter-realdata.ts (deleted 2026-08-23; git history)) measured, on REAL chunk-ORPO
732
- // data (M1 Max, 2026-07-02): CPM5 eps 1e-5 = 0.343% dh for 1.41×; e4b = 0.158% dh
733
- // for 1.70× — and the teacher-forced full-logits fidelity shows the filter's added
734
- // error is far below the flash-vs-full fp-reassociation scatter that already
735
- // exists (CPM5 0.850→0.913% relnorm, e4b 1.207→1.220%, cosine ≥0.99993 both).
736
- // (The ~3.5× figure floating around older notes was the retired SG kernel, not
737
- // steel.) MLX_BUN_CCE_BWD_FILTER_EPS=0 restores exact gradients — eps "0"
738
- // compiles the filter OUT (#if CCE_BWD_FILTER 0), the identical pre-flip kernel.
739
- const BWD_FILTER_EPS = runtimeValue("MLX_BUN_CCE_BWD_FILTER_EPS") ?? "1e-5";
740
- // blockMax vocab-block early-exit eps (skip whole cold (token-block,vocab-block) programs
741
- // — attacks BOTH phases). DEFAULT ON at 1e-5 (flipped 2026-07-02). It is ~LOSSLESS when
742
- // it fires (only skips blocks whose max prob < eps AND which lack the target → ~0
743
- // contribution). The M=512 synthetic probe found nothing cold (pure overhead), but the
744
- // real-data gate proved real text DOES go cold: alone it's 1.23× (CPM5) / 2.02× (e4b)
745
- // at ≤0.004% dh; COMBINED with the coeff filter at 1e-5/1e-5 the backward is 1.71×
746
- // (CPM5) / 3.16× (e4b) vs exact. Fires only when the caller supplies blockMax (the
747
- // production head does); MLX_BUN_CCE_BWD_BLOCK_EPS=0 disables (compiled out).
748
- const BWD_BLOCK_EPS = runtimeValue("MLX_BUN_CCE_BWD_BLOCK_EPS") ?? "1e-5";
749
- const _bwdSgKernels = new Map<string, MetalKernel>();
750
- function bwdSgKernel(dimTiles: number, filterEps: string, blockEps: string): MetalKernel {
751
- const filterOn = Number.parseFloat(filterEps) > 0 ? 1 : 0;
752
- const blockOn = Number.parseFloat(blockEps) > 0 ? 1 : 0;
753
- const key = `${dimTiles}:${filterEps}:${blockEps}`;
754
- let k = _bwdSgKernels.get(key);
755
- if (!k) {
756
- const inputNames = ["h", "wp", "scales", "biases", "targets", "lse", "gv", "shape", "capv"];
757
- if (blockOn) inputNames.push("blockMax"); // [M, NBLK] forward partial, for the skip
758
- k = new MetalKernel({
759
- name: "flash_cce_bwd_sg",
760
- inputNames,
761
- outputNames: ["dh"],
762
- source: BWD_SG_SOURCE,
763
- header: `#include <metal_simdgroup_matrix>\n#define TG ${TG}\n#define BB ${BB_BWD_SG}\n#define SGN ${SGN}\n#define DIMTILES ${dimTiles}\n#define BVP ${SG_BV}\n#define BDP ${SG_BD}\n#define CCE_BWD_FILTER ${filterOn}\n#define CCE_BWD_FILTER_EPS ${filterEps}f\n#define CCE_BWD_BLOCKSKIP ${blockOn}\n#define CCE_BWD_BLOCK_EPS ${blockEps}f\n`,
764
- ensureRowContiguous: true,
765
- atomicOutputs: true,
766
- });
767
- _bwdSgKernels.set(key, k);
768
- }
769
- return k;
770
- }
771
-
772
- // STEEL backward (the production path): MLX's verbatim BlockMMA GEMM (steel-qmm-
773
- // header.ts) for BOTH phases of dh = Σ_v coeff_v·dequant(W_v), at MLX speed AND
774
- // [M,V]-free. Per (token-block z = BM=8 tokens, vocab-block y) threadgroup:
775
- // phase 1 — logit[8,32] = X[8,H] @ Wᵀ[H,32] via the SAME steel logit GEMM as the
776
- // forward (mma_t + QuantizedBlockLoader, K-pipelined over H) → coeff epilogue.
777
- // phase 2 — dh[8,H] += coeff[8,32] @ W[32,H], accumulated over the vocab tiles in a
778
- // persistent register accumulator D = MMATile<1, H/32>. To keep threadgroup
779
- // bounded (the full Wd[32,H] is 320 KB at e4b), H is tiled into HTw=128 columns:
780
- // per H-tile a TEMP BlockMMA over a bounded Wd[32,HTw] dequant, whose Ctile is
781
- // accumulated into D's frags LANE-LOCALLY (frag += frag — no get_coord math, the
782
- // per-lane element mapping is identical between temp and D). dh atomic-added
783
- // across the NBLK vocab-block programs. Validated dh ~1e-4 rel vs autograd.
784
- // (Design + parity: steel-bwd-htile-test.ts (deleted 2026-08-23; git history).)
785
- // PHASE-2 DEQUANT: the Wd[32,HTw] staging uses MLX's vectorized+fused
786
- // QuantizedBlockLoader (the SAME loader the forward uses), NOT a manual scalar
787
- // dequant. The wrinkle: phase-2's GEMM contracts over vocab (K=32) and outputs H
788
- // (the dim the quant groups run along), so the loader must run with reduction_dim=0
789
- // and one group per H-tile → HTw is pinned to group_size (so each H-tile is exactly
790
- // one quant group, satisfying BCOLS<=group_size && group_size%BCOLS==0). The loader
791
- // is built fresh per H-tile (correct src/scales/biases group offsets) + a single
792
- // load_unsafe (no next()). TN = HTw/32 = group_size/32 frags per H-tile. The earlier
793
- // manual scalar dequant (recomputing grp/scale/bias per element) was the bottleneck;
794
- // this matches the forward's qmm staging. (Validated: steel-bwd-htile-test.ts.)
795
- // HTw (the phase-2 H-tile width) is pinned to the quant group_size in-kernel (HTw=GS_T),
796
- // so it is not a standalone constant here — the JS side derives NHT_T/NHT4_T from gs.
797
- const BWD_STEEL_SOURCE = String.raw`
798
- const uint M=shape[0], V=shape[1], H=shape[2], NBLK=shape[7], WPR=shape[5];
799
- const uint tokBlk=threadgroup_position_in_grid.z, vb=threadgroup_position_in_grid.y;
800
- // HTw is pinned to the quant group_size so each phase-2 H-tile is exactly one group
801
- // (lets QuantizedBlockLoader stage Wd with reduction_dim=0). TNv = HTw/32 frags/tile;
802
- // NHTc = H/HTw tiles; total accumulator frags = NHTc*TNv = H/32 (= NHT4_T from JS).
803
- constexpr int BM=8,BNv=32,BK=32,WM=1,WN=4,gs=GS_T,bts=BITS_T,HTw=GS_T,NHTc=NHT_T,TNv=GS_T/32;
804
- constexpr int pf=get_pack_factor<bts,8>(), bpp=get_bytes_per_pack<bts>(), BKp=BK+16/sizeof(float);
805
- threadgroup float Xs[BM*BKp]; threadgroup float Ws[BNv*BKp]; threadgroup float Ls[BM*BNv];
806
- threadgroup float coeffS[BM*BNv]; threadgroup float Wd[BNv*HTw];
807
- threadgroup float redMax[4]; threadgroup float tileMaxSh; // coeff-filter reduction scratch
808
- using lmma_t=mlx::steel::BlockMMA<float,float,BM,BNv,BK,WM,WN,false,true,BKp,BKp>;
809
- using dmma_t=mlx::steel::BlockMMA<float,float,BM,HTw,BNv,WM,WN,false,false,BNv,HTw>;
810
- using lx_t=mlx::steel::BlockLoader<float,BM,BK,BKp,1,WM*WN*SIMD_SIZE>;
811
- using lw_t=QuantizedBlockLoader<float,BNv,BK,BKp,1,WM*WN*SIMD_SIZE,gs,bts>;
812
- // phase-2 W loader: BROWS=32 vocab (GEMM K), BCOLS=HTw=group_size (one group/tile),
813
- // reduction_dim=0, dst_ld=HTw — built fresh per H-tile, single load_unsafe.
814
- using lw2_t=QuantizedBlockLoader<float,BNv,HTw,HTw,0,WM*WN*SIMD_SIZE,gs,bts>;
815
- const uint sg=simdgroup_index_in_threadgroup, sl=thread_index_in_simdgroup, lid=thread_position_in_threadgroup.x;
816
- const float cap=capv[0];
817
- const int Hh=(int)H, K_w=Hh*bpp/pf, K_g=Hh/gs, tokBase=(int)tokBlk*BM;
818
- const int blockV=((int)V+(int)NBLK-1)/(int)NBLK, nStart=(int)vb*blockV, nEnd=min(nStart+blockV,(int)V);
819
- const short num_els=(short)min(BM,(int)M-tokBase);
820
- short2 sc2=mlx::steel::BaseMMAFrag<float,8,8>::get_coord(sl); const short fm=sc2.y, fn=sc2.x;
821
- const uint perWord=32u/bts, qmask=(bts==32u)?0xffffffffu:((1u<<bts)-1u);
822
-
823
- #if CCE_BWD_BLOCKSKIP
824
- // Apple-CCE vocab-block early exit: this (token-block, vocab-block) program does ~0
825
- // work if, for EVERY token, the block's max softmax prob exp(blockMax−lse) is below
826
- // eps AND the target isn't in this block (no onehot term lives here). On peaked
827
- // logits ~all but a couple of vocab blocks per token are cold → skips BOTH phases.
828
- // Uniform across the threadgroup (all threads read the same blockMax/lse/targets), so
829
- // the early return can't desync barriers; it is AFTER the threadgroup decls (required).
830
- bool warm = false;
831
- for (int b=0; b<BM; ++b){ int tk=tokBase+b; if (tk<(int)M){
832
- if (exp(blockMax[tk*(int)NBLK + (int)vb] - lse[tk]) >= CCE_BWD_BLOCK_EPS) warm=true;
833
- int tg2=(int)targets[tk]; if (tg2>=nStart && tg2<nEnd) warm=true; } }
834
- if (!warm) return;
835
- #endif
836
-
837
- mlx::steel::MMATile<float,1,NHT4_T> D; // persistent dh accumulator (H/32 frags/sg)
838
-
839
- for (int v0=nStart; v0<nEnd; v0+=BNv){
840
- // phase 1: logit tile → coeff
841
- lmma_t lmma(sg,sl);
842
- lx_t loader_x(h+(int64_t)tokBase*Hh, Hh, Xs, sg, sl);
843
- lw_t loader_w((const device uint8_t*)wp+v0*K_w, scales+v0*K_g, biases+v0*K_g, Hh, Ws, sg, sl);
844
- for(int k=0;k<Hh;k+=BK){ threadgroup_barrier(mem_flags::mem_threadgroup);
845
- if(num_els<BM) loader_x.load_safe(short2(BK,num_els)); else loader_x.load_unsafe();
846
- loader_w.load_unsafe();
847
- threadgroup_barrier(mem_flags::mem_threadgroup); lmma.mma(Xs,Ws); loader_x.next(); loader_w.next(); }
848
- threadgroup_barrier(mem_flags::mem_threadgroup);
849
- lmma.Ctile.store<float,WM,WN,BNv,1>(Ls + lmma.sm*BNv + lmma.sn);
850
- threadgroup_barrier(mem_flags::mem_threadgroup);
851
- if (lid<(uint)BM){ uint b=lid;
852
- if(tokBase+(int)b<(int)M){ const float lseb=lse[tokBase+b], gb=gv[tokBase+b]; const uint tgt=targets[tokBase+b];
853
- for(int v=0;v<BNv;++v){ int n=v0+v; float logit=Ls[b*BNv+v],sech2=1.0f; if(cap>0.0f){float th=tanh(logit/cap);logit=cap*th;sech2=1.0f-th*th;} float sm=exp(logit-lseb); float c=(n<nEnd)?gb*(((uint)n==tgt?1.0f:0.0f)-sm):0.0f; if(cap>0.0f)c*=sech2; coeffS[b*BNv+v]=c; } }
854
- else { for(int v=0;v<BNv;++v) coeffS[b*BNv+v]=0.0f; } }
855
- threadgroup_barrier(mem_flags::mem_threadgroup);
856
-
857
- #if CCE_BWD_FILTER
858
- // Apple-CCE coeff filter: softmax mass concentrates on a few tokens, so coeff ≈ 0 for
859
- // almost every vocab tile even within a warm block → skip the whole phase-2 (Wd staging
860
- // + temp BlockMMA × NHT) when the entire coeff[8,32] tile is below eps. Parallel max
861
- // over the 256 coeff elems (each thread 2, simd_max per simdgroup, combine 4). Uniform
862
- // continue (all threads read the same tileMaxSh) so there is no barrier desync.
863
- { float lm=max(fabs(coeffS[lid]), fabs(coeffS[lid+128u])); lm=simd_max(lm);
864
- if (sl==0u) redMax[sg]=lm; }
865
- threadgroup_barrier(mem_flags::mem_threadgroup);
866
- if (lid==0u) tileMaxSh=max(max(redMax[0],redMax[1]),max(redMax[2],redMax[3]));
867
- threadgroup_barrier(mem_flags::mem_threadgroup);
868
- if (tileMaxSh < CCE_BWD_FILTER_EPS) continue;
869
- #endif
870
-
871
- // phase 2: per H-tile stage Wd[32,HTw] via QuantizedBlockLoader, temp BlockMMA, accum into D
872
- for (int ht=0; ht<NHTc; ++ht){
873
- const int hbase=ht*HTw;
874
- // src at byte addr of W[v0, hbase]; scales/biases at group ht of vocab row v0.
875
- lw2_t loader_w2((const device uint8_t*)wp + (int64_t)v0*K_w + (int64_t)hbase*bpp/pf,
876
- scales + (int64_t)v0*K_g + ht, biases + (int64_t)v0*K_g + ht, Hh, Wd, sg, sl);
877
- loader_w2.load_unsafe();
878
- threadgroup_barrier(mem_flags::mem_threadgroup);
879
- dmma_t tmp(sg,sl); tmp.mma(coeffS, Wd);
880
- for(int j=0;j<TNv;++j) D.frag_at(0, ht*TNv+j) += tmp.Ctile.frag_at(0,j);
881
- threadgroup_barrier(mem_flags::mem_threadgroup);
882
- }
883
- }
884
-
885
- // store D → dh (atomic-add across the NBLK vocab-block programs)
886
- for(int gj=0; gj<NHT4_T; ++gj){
887
- int ht=gj/TNv, j=gj%TNv; int row=tokBase+(int)fm; int col=ht*HTw + 8*(int)sg + (int)fn + j*32;
888
- if(row<(int)M && col+1<=Hh){ thread auto& fr=D.frag_at(0,gj);
889
- atomic_fetch_add_explicit(&dh[(int64_t)row*Hh + col], fr[0], memory_order_relaxed);
890
- atomic_fetch_add_explicit(&dh[(int64_t)row*Hh + col+1], fr[1], memory_order_relaxed); }
891
- }
892
- `;
893
- const BWD_STEEL_BM = 8;
894
- const _bwdSteelKernels = new Map<string, MetalKernel>();
895
- function bwdSteelKernel(gs: number, bits: number, H: number, filterEps: string, blockEps: string): MetalKernel {
896
- // HTw == group_size (one quant group per phase-2 H-tile → QuantizedBlockLoader).
897
- const nht = H / gs; // # H-tiles
898
- const nFrags = nht * (gs / 32); // total accumulator frags/sg = H/32 (NHT4_T name kept)
899
- // Apple-CCE skips: coeff filter (phase-2 per-tile) + blockMax vocab-block early exit
900
- // (whole-program). Both compile out when eps=0 → zero overhead, exact dh (the parity
901
- // gate). blockMax skip needs the forward's [M,NBLK] blockMax as an extra input.
902
- const filterOn = Number.parseFloat(filterEps) > 0 ? 1 : 0;
903
- const blockOn = Number.parseFloat(blockEps) > 0 ? 1 : 0;
904
- const key = `${gs}:${bits}:${H}:${filterEps}:${blockEps}`;
905
- let k = _bwdSteelKernels.get(key);
906
- if (!k) {
907
- const inputNames = ["h", "wp", "scales", "biases", "targets", "lse", "gv", "shape", "capv"];
908
- if (blockOn) inputNames.push("blockMax"); // [M, NBLK] forward partial, for the vocab-block skip
909
- k = new MetalKernel({
910
- name: "flash_cce_bwd_steel",
911
- inputNames,
912
- outputNames: ["dh"],
913
- source: BWD_STEEL_SOURCE,
914
- header: `${STEEL_QMM_HEADER}\n#define GS_T ${gs}\n#define BITS_T ${bits}\n#define NHT_T ${nht}\n#define NHT4_T ${nFrags}\n#define CCE_BWD_FILTER ${filterOn}\n#define CCE_BWD_FILTER_EPS ${filterEps}f\n#define CCE_BWD_BLOCKSKIP ${blockOn}\n#define CCE_BWD_BLOCK_EPS ${blockEps}f\n`,
915
- ensureRowContiguous: true,
916
- atomicOutputs: true,
917
- });
918
- _bwdSteelKernels.set(key, k);
919
- }
920
- return k;
921
- }
922
-
923
- let _fwdKernel: MetalKernel | null = null;
924
- function fwdKernel(): MetalKernel {
925
- if (!_fwdKernel) {
926
- _fwdKernel = new MetalKernel({
927
- name: "flash_cce_fwd",
928
- inputNames: ["h", "wp", "scales", "biases", "targets", "shape", "capv"],
929
- outputNames: ["blockMax_out", "blockSum_out", "blockTgt_out"],
930
- source: FWD_SOURCE,
931
- header: `#define TG ${TG}\n#define BLOCK_B ${BLOCK_B}\n`,
932
- ensureRowContiguous: true,
933
- });
934
- }
935
- return _fwdKernel;
936
- }
937
-
938
- function u32(values: number[]): MlxArray {
939
- // COPY into an mlx-owned array (fromBytesCopy = mlx_array_new_data), NOT the
940
- // pinning fromView. These tiny kernel-arg arrays (shape, targets) feed a LAZY
941
- // kernel and are disposed before it evaluates; pinning leaks one host buffer
942
- // per call, never released (unbounded `pinned` map + a deferred native
943
- // use-after-free under memory pressure — the seg/accum crash). A few-int copy
944
- // is free; correctness is not.
945
- return MlxArray.fromBytesCopy(new Uint8Array(new Uint32Array(values).buffer), [values.length], Dtype.uint32);
946
- }
947
-
948
- /** Flash-CCE forward: per-token response log-prob [M] (f32) + lse [M] (f32),
949
- * computing logits in-kernel from the quantized head — no [M,V] materialized.
950
- * `hResp` is [M, hidden] (any float dtype; cast to f32 for the kernel). `targets`
951
- * are the response target token ids [M]. Caller owns the returned arrays. */
952
- // STEEL forward: MLX's verbatim quantized BlockMMA GEMM (steel-qmm-header.ts) for the
953
- // logit tile (BM=BN=BK=32, WM=WN=2 → 4 simdgroups × 16×16 register tiles, BK_padded),
954
- // with ONLY the ORPO epilogue diverging — softcap + online-softmax over each BN vocab
955
- // tile (no [M,V]). Per (token-block z, vocab-block y) threadgroup; partials [M,NBLK]
956
- // merged in MLX. Same logit math as quantizedMatmul (validated 1e-5) at MLX speed
957
- // (e4b logits 101 ms vs MLX 106 ms; 8.4× our old hand-rolled forward).
958
- const FWD_STEEL_SOURCE = String.raw`
959
- const uint M=shape[0], V=shape[1], H=shape[2], NBLK=shape[7];
960
- const uint tokBlk=threadgroup_position_in_grid.z, vb=threadgroup_position_in_grid.y;
961
- constexpr int BM=32,BK=32,BN=32,WM=2,WN=2,gs=GS_T,bts=BITS_T;
962
- constexpr int pf=get_pack_factor<bts,8>(), bpp=get_bytes_per_pack<bts>(), BKp=BK+16/sizeof(float);
963
- threadgroup float Xs[BM*BKp]; threadgroup float Ws[BN*BKp]; threadgroup float Ls[BM*BN];
964
- threadgroup float tgMax[BM], tgSum[BM], tgTgt[BM]; threadgroup uint tgTid[BM];
965
- using mma_t=mlx::steel::BlockMMA<float,float,BM,BN,BK,WM,WN,false,true,BKp,BKp>;
966
- using lx_t=mlx::steel::BlockLoader<float,BM,BK,BKp,1,WM*WN*SIMD_SIZE>;
967
- using lw_t=QuantizedBlockLoader<float,BN,BK,BKp,1,WM*WN*SIMD_SIZE,gs,bts>;
968
- const uint sg=simdgroup_index_in_threadgroup, sl=thread_index_in_simdgroup, lid=thread_position_in_threadgroup.x;
969
- const float cap=capv[0];
970
- const int Hh=(int)H, K_w=Hh*bpp/pf, K_g=Hh/gs;
971
- const int blockV=((int)V+(int)NBLK-1)/(int)NBLK;
972
- const int nStart=(int)vb*blockV, nEnd=min(nStart+blockV,(int)V), tokBase=(int)tokBlk*BM;
973
- const short num_els=(short)min(BM,(int)M-tokBase);
974
- if (lid<(uint)BM){ tgMax[lid]=-INFINITY; tgSum[lid]=0.0f; tgTgt[lid]=0.0f; tgTid[lid]=(tokBase+(int)lid<(int)M)?targets[tokBase+lid]:0xffffffffu; }
975
- threadgroup_barrier(mem_flags::mem_threadgroup);
976
- for (int v0=nStart; v0<nEnd; v0+=BN){
977
- mma_t mma_op(sg,sl);
978
- lx_t loader_x(h+(int64_t)tokBase*Hh, Hh, Xs, sg, sl);
979
- lw_t loader_w((const device uint8_t*)wp+v0*K_w, scales+v0*K_g, biases+v0*K_g, Hh, Ws, sg, sl);
980
- for(int k=0;k<Hh;k+=BK){ threadgroup_barrier(mem_flags::mem_threadgroup);
981
- if(num_els<BM) loader_x.load_safe(short2(BK,num_els)); else loader_x.load_unsafe();
982
- loader_w.load_unsafe();
983
- threadgroup_barrier(mem_flags::mem_threadgroup); mma_op.mma(Xs,Ws); loader_x.next(); loader_w.next(); }
984
- threadgroup_barrier(mem_flags::mem_threadgroup);
985
- mma_op.Ctile.store<float,WM,WN,BN,1>(Ls + mma_op.sm*BN + mma_op.sn);
986
- threadgroup_barrier(mem_flags::mem_threadgroup);
987
- if (lid<(uint)BM && tokBase+(int)lid<(int)M){ uint b=lid;
988
- for (int v=0; v<BN; ++v){ int n=v0+v; if(n>=nEnd) break;
989
- float logit=Ls[b*BN+v]; if(cap>0.0f) logit=cap*tanh(logit/cap);
990
- float nm=max(tgMax[b],logit); tgSum[b]=tgSum[b]*exp(tgMax[b]-nm)+exp(logit-nm); tgMax[b]=nm;
991
- if((uint)n==tgTid[b]) tgTgt[b]=logit; }
992
- }
993
- threadgroup_barrier(mem_flags::mem_threadgroup);
994
- }
995
- if (lid<(uint)BM && tokBase+(int)lid<(int)M){ uint t=tokBase+lid;
996
- blockMax_out[t*NBLK+vb]=tgMax[lid]; blockSum_out[t*NBLK+vb]=tgSum[lid]; blockTgt_out[t*NBLK+vb]=tgTgt[lid]; }
997
- `;
998
- const STEEL_BM = 32;
999
- const _fwdSteelKernels = new Map<string, MetalKernel>();
1000
- function fwdSteelKernel(gs: number, bits: number): MetalKernel {
1001
- const key = `${gs}:${bits}`;
1002
- let k = _fwdSteelKernels.get(key);
1003
- if (!k) {
1004
- k = new MetalKernel({
1005
- name: "flash_cce_fwd_steel",
1006
- inputNames: ["h", "wp", "scales", "biases", "targets", "shape", "capv"],
1007
- outputNames: ["blockMax_out", "blockSum_out", "blockTgt_out"],
1008
- source: FWD_STEEL_SOURCE,
1009
- header: `${STEEL_QMM_HEADER}\n#define GS_T ${gs}\n#define BITS_T ${bits}\n`,
1010
- ensureRowContiguous: true,
1011
- });
1012
- _fwdSteelKernels.set(key, k);
1013
- }
1014
- return k;
1015
- }
1016
-
1017
- // One-time dispatch-route warnings. A failed tiling guard silently reroutes the
1018
- // whole flash head onto the legacy kernels (~5× slower class: e4b M=512 backward
1019
- // 754 → 3687+ ms) with no observable signal — the exact "forward passes are
1020
- // flag-gated, verify in code" hazard. Name the failed guard once per route so a
1021
- // new model port that misses steel is visible in the training log.
1022
- const _steelFallbackWarned = new Set<string>();
1023
- function warnSteelFallback(which: "forward" | "backward", detail: string, fallback: string): void {
1024
- if (_steelFallbackWarned.has(which)) return;
1025
- _steelFallbackWarned.add(which);
1026
- console.warn(
1027
- `[flash-cce] ${which}: steel kernel guard failed (${detail}) — dispatching the ` +
1028
- `${fallback} instead (~5x-slower class). If this model should be steel-capable, ` +
1029
- `check the tiling guards in flash-cce.ts.`,
1030
- );
1031
- }
1032
-
1033
- export function flashCceForward(
1034
- hResp: MlxArray, head: FlashCceHead, targets: number[],
1035
- ): { logp: MlxArray; lse: MlxArray; blockMax: MlxArray } {
1036
- const M = hResp.shape[0]!;
1037
- const H = hResp.shape[1]!;
1038
- const V = head.scales.shape[0]!;
1039
- const GR = head.scales.shape[1]!;
1040
- const GS = head.groupSize;
1041
- const WPR = head.w.shape[1]!;
1042
- if (H > H_MAX) throw new Error(`flashCceForward: hidden ${H} > H_MAX ${H_MAX}`);
1043
- if (GR * GS !== H) throw new Error(`flashCceForward: GR*GS ${GR * GS} != H ${H}`);
1044
-
1045
- // Vocab blocks sized so each threadgroup handles a bounded slice (≈ VOCAB_BLOCK
1046
- // rows) — independent of V, so a single threadgroup never runs the whole vocab
1047
- // (the e4b watchdog fix). NBLK = ceil(V / VOCAB_BLOCK).
1048
- const NBLK = Math.max(1, Math.ceil(V / VOCAB_BLOCK));
1049
- const hF = hResp.dtype === Dtype.float32 ? hResp : hResp.astype(Dtype.float32);
1050
- const scF = head.scales.dtype === Dtype.float32 ? head.scales : head.scales.astype(Dtype.float32);
1051
- const biF = head.biases.dtype === Dtype.float32 ? head.biases : head.biases.astype(Dtype.float32);
1052
- const tgtArr = u32(targets);
1053
- const shape = u32([M, V, H, GS, head.bits, WPR, GR, NBLK]);
1054
- const capv = MlxArray.fromFloat32(new Float32Array([head.softcap ?? 0]), [1]);
1055
-
1056
- // DEFAULT = simdgroup_matrix kernel with efficient dequant staging (848 ms, the
1057
- // fastest measured — matmul throughput once the staging loads stopped bounding
1058
- // it). MLX_BUN_CCE_SCALAR=1 → one-thread-per-row scalar+coalesced-h (1097 ms;
1059
- // also the H % SG_BD != 0 fallback). MLX_BUN_CCE_LANE=1 → lane-sliced (2.6×
1060
- // slower; scatters the h broadcast — recorded dead end).
1061
- // DEFAULT = STEEL (verbatim MLX BlockMMA GEMM, 8.4× the old SG forward) when the
1062
- // tiling is clean (H,V,blockV all % 32). MLX_BUN_CCE_NOSTEEL=1 falls back to the
1063
- // old kernels (simdgroup / scalar / lane).
1064
- const blockV0 = Math.ceil(V / NBLK);
1065
- const useSteel = runtimeValue("MLX_BUN_CCE_NOSTEEL") !== "1" && H % 32 === 0 && V % 32 === 0 && blockV0 % 32 === 0;
1066
- const useSimd = !useSteel && USE_SIMDGROUP_FWD && H % SG_BD === 0;
1067
- if (!useSteel && runtimeValue("MLX_BUN_CCE_NOSTEEL") !== "1") {
1068
- // NOTE blockV0 = ceil(V/NBLK) can violate %32 even when V%32==0 (e.g. V=50304)
1069
- const fails = [
1070
- H % 32 !== 0 ? `H%32!=0 (H=${H})` : null,
1071
- V % 32 !== 0 ? `V%32!=0 (V=${V})` : null,
1072
- blockV0 % 32 !== 0 ? `blockV0%32!=0 (blockV0=${blockV0}, V=${V}, NBLK=${NBLK})` : null,
1073
- ].filter(Boolean).join(", ");
1074
- warnSteelFallback("forward", fails, useSimd ? "simdgroup kernel" : "scalar/lane kernel");
1075
- }
1076
- const useLane = !useSteel && !useSimd && runtimeValue("MLX_BUN_CCE_LANE") === "1" && WPR % 32 === 0;
1077
- const kernel = useSteel ? fwdSteelKernel(GS, head.bits) : useSimd ? fwdSgKernel() : useLane ? fwdLaneKernel() : fwdKernel();
1078
- const tokTile = useSteel ? STEEL_BM : useSimd ? SG_BB : BLOCK_B;
1079
- // steel + simdgroup read h as [M, H] (M on axis 0); lane + scalar read h TRANSPOSED
1080
- // [H, M] (M on axis 1) — they stage E into threadgroup themselves.
1081
- const transposedH = !useSteel && !useSimd;
1082
- const hIn = transposedH ? ops.transposeAxes(hF, [1, 0]) : hF;
1083
- let bMax: MlxArray | undefined, bSum: MlxArray | undefined, bTgt: MlxArray | undefined;
1084
- try {
1085
- const outs = kernel.apply([hIn, head.w, scF, biF, tgtArr, shape, capv], {
1086
- // Derive outputs + grid from the input shapes (hResp = inputs[0]) so the
1087
- // kernel composes inside an mx.compile'd closure: [M,NBLK] f32 partials, one
1088
- // threadgroup per (token-block, vocab-block). NBLK is fixed (from V).
1089
- outputShapeFn: (ins) => {
1090
- const m = transposedH ? ins[0]!.shape[1]! : ins[0]!.shape[0]!;
1091
- return [
1092
- { shape: [m, NBLK], dtype: Dtype.float32 },
1093
- { shape: [m, NBLK], dtype: Dtype.float32 },
1094
- { shape: [m, NBLK], dtype: Dtype.float32 },
1095
- ];
1096
- },
1097
- grid: (ins) => [TG, NBLK, Math.ceil((transposedH ? ins[0]!.shape[1]! : ins[0]!.shape[0]!) / tokTile)],
1098
- threadGroup: [TG, 1, 1],
1099
- });
1100
- [bMax, bSum, bTgt] = outs as [MlxArray, MlxArray, MlxArray];
1101
- // Cross-vocab-block merge in MLX: gMax = max_b; gSum = Σ_b sum_b·exp(max_b−gMax);
1102
- // lse = gMax + log(gSum); logp = target − lse. (Apple CCE does this with a
1103
- // locked atomic logaddexp in-kernel; the partials are tiny [M,NBLK] so an MLX
1104
- // reduction is simpler and just as exact.)
1105
- const gMax = ops.maxAxis(bMax, 1, false); // [M]
1106
- const gMaxCol = ops.reshape(gMax, [M, 1]);
1107
- // Bind every nested intermediate so its MlxArray wrapper gets disposed — a
1108
- // bare `ops.exp(ops.sub(...))` leaks the inner `sub` (stays live in `active`
1109
- // until the GC finalizer backstop), and this runs TWICE per step (chosen +
1110
- // rejected). gMax was also missing from the dispose list below.
1111
- const sub = ops.sub(bMax, gMaxCol);
1112
- const shifted = ops.exp(sub); // [M, NBLK]
1113
- const prod = ops.mul(bSum, shifted);
1114
- const gSum = ops.sumAxis(prod, 1, false); // [M]
1115
- const logG = ops.log(gSum);
1116
- const lse = ops.add(gMax, logG); // [M]
1117
- const tgt = ops.sumAxis(bTgt, 1, false); // [M] (owning block only is nonzero)
1118
- const logp = ops.sub(tgt, lse);
1119
- for (const a of [gMax, gMaxCol, sub, shifted, prod, gSum, logG, tgt]) a.dispose();
1120
- const blockMax = bMax!; bMax = undefined; // hand off to caller (backward block-skip)
1121
- return { logp, lse, blockMax };
1122
- } finally {
1123
- bMax?.dispose(); bSum?.dispose(); bTgt?.dispose();
1124
- if (hIn !== hF) hIn.dispose();
1125
- if (hF !== hResp) hF.dispose();
1126
- if (scF !== head.scales) scF.dispose();
1127
- if (biF !== head.biases) biF.dispose();
1128
- tgtArr.dispose();
1129
- shape.dispose();
1130
- capv.dispose();
1131
- }
1132
- }
1133
-
1134
- /** Flash-CCE backward: dh [M, hidden] (f32) = ∂(Σ_t cotangent_t · logp_t)/∂h.
1135
- * `lse` is the forward's saved log-sum-exp [M]; `cot` is the per-token cotangent
1136
- * ∂loss/∂logp_t [M]. Recomputes logits in-kernel (no [M,V]); writes dh directly
1137
- * (no atomics). Caller owns the result. */
1138
- export function flashCceBackward(
1139
- hResp: MlxArray, head: FlashCceHead, targets: number[], lse: MlxArray, cot: number[],
1140
- // Apple-CCE coeff filter eps. Defaults to BWD_FILTER_EPS — ON at 1e-5 since
1141
- // 2026-07-02 (real-data gates in BWD_FILTER_EPS's note). Pass "0" explicitly
1142
- // for guaranteed-exact gradients (the parity gate does).
1143
- filterEps: string = BWD_FILTER_EPS,
1144
- // The forward's `blockMax [M, NBLK]` enables the vocab-block early exit (skips
1145
- // whole cold programs — attacks BOTH phases); ON at 1e-5 by default when
1146
- // blockMax is supplied. Omit blockMax (or set blockEps "0") for exact.
1147
- blockMax?: MlxArray, blockEps: string = BWD_BLOCK_EPS,
1148
- ): MlxArray {
1149
- const M = hResp.shape[0]!;
1150
- const H = hResp.shape[1]!;
1151
- const V = head.scales.shape[0]!;
1152
- const GR = head.scales.shape[1]!;
1153
- const GS = head.groupSize;
1154
- const WPR = head.w.shape[1]!;
1155
- if (H > H_MAX) throw new Error(`flashCceBackward: hidden ${H} > H_MAX ${H_MAX}`);
1156
- // NOTE: H % TG is a LEGACY-SCALAR-kernel requirement only (it owns DPER = H/TG
1157
- // dims per thread — floor division, so a remainder would leave dims silently
1158
- // zero). The steel/simdgroup kernels have their own (weaker) guards below; the
1159
- // throw is applied only when dispatch actually lands on the scalar kernel.
1160
-
1161
- const hF = hResp.dtype === Dtype.float32 ? hResp : hResp.astype(Dtype.float32);
1162
- const scF = head.scales.dtype === Dtype.float32 ? head.scales : head.scales.astype(Dtype.float32);
1163
- const biF = head.biases.dtype === Dtype.float32 ? head.biases : head.biases.astype(Dtype.float32);
1164
- const lseF = lse.dtype === Dtype.float32 ? lse : lse.astype(Dtype.float32);
1165
- const NBLK = Math.max(1, Math.ceil(V / VOCAB_BLOCK));
1166
- const tgtArr = u32(targets);
1167
- const gv = MlxArray.fromFloat32(new Float32Array(cot), [M]);
1168
- const shape = u32([M, V, H, GS, head.bits, WPR, GR, NBLK]);
1169
- const capv = MlxArray.fromFloat32(new Float32Array([head.softcap ?? 0]), [1]);
1170
-
1171
- // STEEL is the DEFAULT (verbatim MLX BlockMMA GEMM for both phases, [M,V]-free) when
1172
- // the tiling is clean (H,V,blockV all %32 AND H % HTw for the H-tiled accumulator).
1173
- // MLX_BUN_CCE_BWD_NOSTEEL=1 falls back to the old kernels. The steel backward carries
1174
- // the SAME Apple-CCE skips as the SG kernel — coeff filter (phase-2 per-tile) +
1175
- // blockMax vocab-block early exit (whole-program) — both compiled out at eps=0.
1176
- // BOTH skips default ON at 1e-5 (2026-07-02 flip; measured gates in the
1177
- // BWD_FILTER_EPS / BWD_BLOCK_EPS notes). MLX_BUN_CCE_BWD_{FILTER,BLOCK}_EPS=0
1178
- // restores exact gradients (identical pre-flip kernels).
1179
- const blockV0 = Math.ceil(V / NBLK);
1180
- // HTw == group_size for the phase-2 QuantizedBlockLoader (one group per H-tile); needs
1181
- // H % GS == 0 (always, GR*GS=H) and GS % 32 == 0 (TNv = GS/32 frags per H-tile integral).
1182
- const useSteel = runtimeValue("MLX_BUN_CCE_BWD_NOSTEEL") !== "1" && H % 32 === 0 && V % 32 === 0 && blockV0 % 32 === 0 && H % GS === 0 && GS % 32 === 0;
1183
- // simdgroup_matrix phase-2 GEMM is the next tier (5512→3913 ms, 1.41×) when the H
1184
- // tiling is clean; MLX_BUN_CCE_BWD_SCALAR=1 forces the float4 scalar kernel.
1185
- const useSg = !useSteel && runtimeValue("MLX_BUN_CCE_BWD_SCALAR") !== "1" && H % (SGN * 8) === 0 && H % SG_BD === 0;
1186
- if (!useSteel && runtimeValue("MLX_BUN_CCE_BWD_NOSTEEL") !== "1") {
1187
- // NOTE blockV0 = ceil(V/NBLK) can violate %32 even when V%32==0 (e.g. V=50304)
1188
- const fails = [
1189
- H % 32 !== 0 ? `H%32!=0 (H=${H})` : null,
1190
- V % 32 !== 0 ? `V%32!=0 (V=${V})` : null,
1191
- blockV0 % 32 !== 0 ? `blockV0%32!=0 (blockV0=${blockV0}, V=${V}, NBLK=${NBLK})` : null,
1192
- H % GS !== 0 ? `H%GS!=0 (H=${H}, GS=${GS})` : null,
1193
- GS % 32 !== 0 ? `GS%32!=0 (GS=${GS})` : null,
1194
- ].filter(Boolean).join(", ");
1195
- warnSteelFallback("backward", fails, useSg ? "simdgroup kernel" : "scalar kernel");
1196
- }
1197
- // Legacy scalar kernel: each thread owns DPER = H/TG dims (floor) — a remainder
1198
- // would leave the last H%TG dims silently zero, so it hard-requires H % TG == 0.
1199
- // Only enforced when dispatch actually lands there (steel/SG have weaker guards).
1200
- if (!useSteel && !useSg && H % TG !== 0)
1201
- throw new Error(
1202
- `flashCceBackward: hidden ${H} not divisible by TG ${TG} — required by the legacy ` +
1203
- `scalar kernel, which is the selected route because the steel/simdgroup tiling ` +
1204
- `guards also failed for this shape (see the [flash-cce] warning above).`,
1205
- );
1206
- // Block-skip is on (either kernel) only when blockMax is supplied AND blockEps>0 (the
1207
- // kernel reads it as an extra input). The coeff filter needs no extra input (just eps).
1208
- const useBlockSkip = (useSteel || useSg) && blockMax != null && Number.parseFloat(blockEps) > 0;
1209
- const bmF = useBlockSkip ? (blockMax!.dtype === Dtype.float32 ? blockMax! : blockMax!.astype(Dtype.float32)) : null;
1210
- const kernel = useSteel
1211
- ? bwdSteelKernel(GS, head.bits, H, filterEps, useBlockSkip ? blockEps : "0")
1212
- : useSg ? bwdSgKernel(H / SGN / 8, filterEps, useBlockSkip ? blockEps : "0") : bwdKernel(Math.ceil(H / TG));
1213
- const tokTile = useSteel ? BWD_STEEL_BM : useSg ? BB_BWD_SG : BLOCK_B_BWD;
1214
- const baseInputs = [hF, head.w, scF, biF, tgtArr, lseF, gv, shape, capv];
1215
- try {
1216
- const outs = kernel.apply(useBlockSkip ? [...baseInputs, bmF!] : baseInputs, {
1217
- outputShapeFn: (ins) => [{ shape: [ins[0]!.shape[0]!, H], dtype: Dtype.float32 }], // dh [M, hidden]
1218
- grid: (ins) => [TG, NBLK, Math.ceil(ins[0]!.shape[0]! / tokTile)],
1219
- threadGroup: [TG, 1, 1],
1220
- initValue: 0, // dh starts at 0; vocab-block programs atomic-add into it
1221
- });
1222
- return outs[0]!;
1223
- } finally {
1224
- if (hF !== hResp) hF.dispose();
1225
- if (bmF && bmF !== blockMax) bmF.dispose();
1226
- if (scF !== head.scales) scF.dispose();
1227
- if (biF !== head.biases) biF.dispose();
1228
- if (lseF !== lse) lseF.dispose();
1229
- tgtArr.dispose();
1230
- gv.dispose();
1231
- shape.dispose();
1232
- capv.dispose();
1233
- }
1234
- }