mlx-bun 0.4.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.
- package/README.md +1453 -88
- package/THIRD_PARTY_NOTICES.md +882 -0
- package/bin/mlx-bun.convert.mjs +4 -0
- package/bin/mlx-bun.fuse.mjs +4 -0
- package/bin/mlx-bun.generate.mjs +4 -0
- package/bin/mlx-bun.lora.mjs +4 -0
- package/bin/mlx-bun.mjs +13 -21
- package/bin/mlx-bun.server.mjs +4 -0
- package/bin/mlx-bun.upload.mjs +4 -0
- package/dist/web/app.js +9584 -0
- package/package.json +50 -53
- package/src/cli/ane-install.ts +76 -0
- package/src/cli/args.ts +179 -0
- package/src/cli/engine-entry.ts +68 -0
- package/src/cli/inference.ts +214 -0
- package/src/cli/job-entry.ts +97 -0
- package/src/cli/library-host.ts +117 -0
- package/src/cli/main.ts +57 -0
- package/src/cli/memory-engine.ts +163 -0
- package/src/cli/memory.ts +36 -0
- package/src/cli/mlx-lm-aliases.ts +309 -0
- package/src/cli/model-choice.ts +33 -0
- package/src/cli/model-selection.ts +84 -0
- package/src/cli/module-host.ts +11 -0
- package/src/cli/module-verbs.ts +50 -0
- package/src/cli/numerical-policy.ts +12 -0
- package/src/cli/perplexity.ts +89 -0
- package/src/cli/serve-host.ts +423 -0
- package/src/cli/serve-isolated.ts +217 -0
- package/src/cli/serve-options.ts +106 -0
- package/src/cli/serve-state.ts +205 -0
- package/src/cli/serve.ts +500 -0
- package/src/cli/served-model-host.ts +102 -0
- package/src/cli/server-entry.ts +148 -0
- package/src/cli/serving-unit.ts +177 -0
- package/src/{tui.ts → cli/terminal.ts} +13 -8
- package/src/cli/worker-adapters.ts +34 -0
- package/src/cli/worker-entry.ts +272 -0
- package/src/cli/worker-unit.ts +179 -0
- package/src/engine/adapter-operation.ts +26 -0
- package/src/engine/cache-services.ts +114 -0
- package/src/engine/completion.ts +39 -0
- package/src/engine/generation-gateway.ts +358 -0
- package/src/engine/index.ts +59 -0
- package/src/engine/kv-budget.ts +120 -0
- package/src/engine/media-preparation.ts +232 -0
- package/src/engine/model-binding.ts +24 -0
- package/src/engine/model-host.ts +513 -0
- package/src/engine/model-serving.ts +89 -0
- package/src/{serve → engine}/preparation.ts +10 -8
- package/src/{serve → engine}/session-completion-engine.ts +6 -6
- package/src/engine/telemetry.ts +109 -0
- package/src/hub/downloads.ts +116 -0
- package/src/jobs/db.ts +16 -7
- package/src/jobs/events.ts +29 -0
- package/src/jobs/executable.ts +5 -0
- package/src/jobs/host.ts +51 -0
- package/src/jobs/in-process.ts +55 -0
- package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
- package/src/jobs/runner.ts +150 -231
- package/src/jobs/service.ts +75 -0
- package/src/jobs/sse.ts +59 -23
- package/src/jobs/worker-memory.ts +46 -0
- package/src/jobs/worker-process.ts +163 -0
- package/src/jobs/worker-supervisor.ts +258 -0
- package/src/modules.ts +58 -0
- package/src/publishing/catalog-hub.ts +18 -0
- package/src/publishing/credentials.ts +31 -0
- package/src/publishing/upload.ts +37 -0
- package/src/residency/leased-adapters.ts +21 -0
- package/src/residency/local-records.ts +16 -0
- package/src/residency/model-residency.ts +405 -0
- package/src/residency/record-index.ts +28 -0
- package/src/residency/resident-estimate.ts +50 -0
- package/src/{anthropic.ts → server/anthropic.ts} +3 -81
- package/src/server/cache-routes.ts +36 -0
- package/src/{serve → server}/chat-request.ts +12 -10
- package/src/{serve → server}/chat-stage.ts +27 -22
- package/src/{client.ts → server/client.ts} +28 -2
- package/src/{serve → server}/completion-executor.ts +42 -42
- package/src/server/decision-body.ts +55 -0
- package/src/server/decision-routes.ts +134 -0
- package/src/server/decision-wire.ts +228 -0
- package/src/{serve → server}/discovery-routes.ts +42 -83
- package/src/server/generated-token-history.ts +57 -0
- package/src/{serve → server}/http.ts +25 -13
- package/src/{serve → server}/inference-request.ts +5 -5
- package/src/server/job-routes.ts +20 -0
- package/src/server/media-prompt.ts +63 -0
- package/src/server/memory-completion-client.ts +46 -0
- package/src/server/model-routes.ts +192 -0
- package/src/{serve → server}/openai-wire.ts +3 -2
- package/src/{serve → server}/pipeline.ts +1 -1
- package/src/server/prompt-builder.ts +13 -0
- package/src/server/prompt-contracts.ts +17 -0
- package/src/server/proxy-routes.ts +187 -0
- package/src/server/publishing-routes.ts +41 -0
- package/src/{serve → server}/request-plan.ts +8 -8
- package/src/{serve → server}/request-prep.ts +82 -68
- package/src/{serve → server}/responses-client.ts +30 -13
- package/src/{responses.ts → server/responses.ts} +12 -93
- package/src/server/routes.ts +205 -0
- package/src/server/start.ts +72 -0
- package/src/server/status-routes.ts +108 -0
- package/src/{serve → server}/text-completion-stage.ts +25 -4
- package/src/server/text-prompt.ts +12 -0
- package/src/{serve → server}/token-streams.ts +36 -38
- package/src/server/worker-routes.ts +311 -0
- package/src/storage/paths.ts +61 -0
- package/src/web/assets.ts +37 -0
- package/src/web/{src → browser}/api.ts +5 -23
- package/src/web/browser/chat-host.ts +17 -0
- package/src/web/browser/installed-panels.ts +6 -0
- package/src/web/browser/main.ts +55 -0
- package/src/web/browser/markdown.ts +279 -0
- package/src/web/browser/memory-panel.ts +9 -0
- package/src/web/browser/palette.ts +64 -0
- package/src/web/browser/protocol.ts +13 -0
- package/src/web/browser/shell.ts +467 -0
- package/src/web/{src → browser}/status.ts +32 -36
- package/src/web/build.ts +51 -0
- package/src/web/public/app.html +1054 -0
- package/src/web/{sw.js → public/sw.js} +4 -3
- package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
- package/src/web/{vendor → public/vendor}/hljs.js +2 -2
- package/THIRD_PARTY_LICENSES.md +0 -51
- package/docs/README.md +0 -94
- package/docs/archive/hlg-sampling.md +0 -350
- package/docs/archive/investigations/audio-input-plan.md +0 -356
- package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
- package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
- package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
- package/docs/archive/investigations/curve-bisector-routing.md +0 -123
- package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
- package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
- package/docs/archive/investigations/dspark-handoff.md +0 -224
- package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
- package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
- package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
- package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
- package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
- package/docs/archive/investigations/lab-build-journal.md +0 -157
- package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
- package/docs/archive/investigations/openjarvis-fit.md +0 -198
- package/docs/archive/investigations/openwiki-evaluation.md +0 -249
- package/docs/archive/investigations/optimization_plan.md +0 -346
- package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
- package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
- package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
- package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
- package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
- package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
- package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
- package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
- package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
- package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
- package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
- package/docs/archive/planning/chat-ui-vision.md +0 -50
- package/docs/archive/planning/chunk-task-roadmap.md +0 -77
- package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
- package/docs/archive/planning/journal.md +0 -316
- package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
- package/docs/archive/planning/pi-integration-review.md +0 -178
- package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
- package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
- package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
- package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
- package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
- package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
- package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
- package/docs/archive/planning/web-ui-pass-plan.md +0 -213
- package/docs/archive/planning/website-readme-pass-plan.md +0 -224
- package/docs/design/batching.md +0 -1841
- package/docs/design/decode-speed-program.md +0 -2349
- package/docs/design/dreaming-nightly-pipeline.md +0 -684
- package/docs/design/generic-model-support.md +0 -705
- package/docs/design/kv-cache.md +0 -824
- package/docs/design/orpo-training.md +0 -680
- package/docs/design/speculative-decoding.md +0 -1990
- package/docs/design/turboquant.md +0 -2212
- package/docs/design/unified-engine-frontier-plan.md +0 -1880
- package/docs/design/web-chat-redesign.md +0 -695
- package/docs/planning/PRODUCT_ROADMAP.md +0 -594
- package/docs/planning/ResearchTopics.md +0 -119
- package/docs/planning/release-notes-v0.4.0.md +0 -45
- package/docs/planning/video-series.md +0 -421
- package/docs/reference/benchmarks.md +0 -3613
- package/docs/reference/cli.md +0 -585
- package/docs/reference/distribution.md +0 -355
- package/docs/reference/environment.md +0 -398
- package/docs/reference/glossary.md +0 -39
- package/docs/reference/library-api.md +0 -674
- package/docs/reference/memory.md +0 -260
- package/docs/reference/models.md +0 -334
- package/docs/reference/server-api.md +0 -1775
- package/docs/reference/server-config.md +0 -846
- package/docs/reference/training.md +0 -462
- package/docs/reference/troubleshooting.md +0 -158
- package/src/admission-plan.ts +0 -18
- package/src/audio/conformer.ts +0 -647
- package/src/audio/decode.ts +0 -133
- package/src/audio/features.ts +0 -202
- package/src/audio/transcode.ts +0 -67
- package/src/backends/mlx/assistant-target.ts +0 -46
- package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
- package/src/backends/mlx/autoregressive.ts +0 -108
- package/src/backends/mlx/batch-group.ts +0 -1790
- package/src/backends/mlx/cache-attachment.ts +0 -67
- package/src/backends/mlx/cache-layout.ts +0 -31
- package/src/backends/mlx/checkpoint-state.ts +0 -53
- package/src/backends/mlx/continuation-persistence.ts +0 -106
- package/src/backends/mlx/continuation-request.ts +0 -43
- package/src/backends/mlx/continuation.ts +0 -77
- package/src/backends/mlx/diffusion.ts +0 -39
- package/src/backends/mlx/draft-target.ts +0 -32
- package/src/backends/mlx/evaluation.ts +0 -105
- package/src/backends/mlx/fill-append.ts +0 -45
- package/src/backends/mlx/full-kv-row-append.ts +0 -32
- package/src/backends/mlx/full-kv-row-donor.ts +0 -54
- package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
- package/src/backends/mlx/gateway-binding.ts +0 -130
- package/src/backends/mlx/graph.ts +0 -38
- package/src/backends/mlx/kv-codec.ts +0 -9
- package/src/backends/mlx/kv-maintenance.ts +0 -133
- package/src/backends/mlx/kv-tensor-rows.ts +0 -212
- package/src/backends/mlx/memory-client.ts +0 -189
- package/src/backends/mlx/methods.ts +0 -90
- package/src/backends/mlx/model-prompt.ts +0 -164
- package/src/backends/mlx/model-serving.ts +0 -79
- package/src/backends/mlx/prefill-cohort.ts +0 -89
- package/src/backends/mlx/prefill-rows.ts +0 -200
- package/src/backends/mlx/prefill.ts +0 -33
- package/src/backends/mlx/projected-context-rows.ts +0 -72
- package/src/backends/mlx/request-policy.ts +0 -20
- package/src/backends/mlx/request-state-policy.ts +0 -41
- package/src/backends/mlx/rollback.ts +0 -89
- package/src/backends/mlx/row-sampling.ts +0 -49
- package/src/backends/mlx/serial-executor.ts +0 -242
- package/src/backends/mlx/speculative-group.ts +0 -359
- package/src/backends/mlx/speculative-round.ts +0 -161
- package/src/backends/mlx/speculative.ts +0 -114
- package/src/backends/mlx/state-rows.ts +0 -104
- package/src/backends/mlx/state-views.ts +0 -46
- package/src/backends/mlx/target-layout-capability.ts +0 -35
- package/src/backends/mlx/text-engine.ts +0 -73
- package/src/backends/mlx/transitioning-kv-rows.ts +0 -165
- package/src/chat-template.ts +0 -382
- package/src/cli.ts +0 -3168
- package/src/config.ts +0 -399
- package/src/contracts/completion.ts +0 -16
- package/src/contracts/execution.ts +0 -60
- package/src/contracts/generation.ts +0 -103
- package/src/contracts/host.ts +0 -7
- package/src/contracts/pi-provider.ts +0 -30
- package/src/contracts/pi.ts +0 -259
- package/src/contracts/prefix-cache.ts +0 -23
- package/src/contracts/resources.ts +0 -25
- package/src/contracts/sampling.ts +0 -23
- package/src/contracts/scheduling.ts +0 -45
- package/src/contracts/tsconfig.json +0 -14
- package/src/dataset/generators.ts +0 -1087
- package/src/dataset/index.ts +0 -12
- package/src/dataset/job.ts +0 -81
- package/src/dataset/llm.ts +0 -71
- package/src/dataset/registry.ts +0 -528
- package/src/diffusion/diffusion-generate.ts +0 -507
- package/src/download.ts +0 -505
- package/src/embed.ts +0 -77
- package/src/engine/admission.ts +0 -59
- package/src/engine/cancellation.ts +0 -38
- package/src/engine/engine.ts +0 -56
- package/src/engine/execution-coordinator.ts +0 -70
- package/src/engine/execution-plan.ts +0 -53
- package/src/engine/resources.ts +0 -53
- package/src/engine/scheduler.ts +0 -72
- package/src/engine/session.ts +0 -247
- package/src/eval/README.md +0 -130
- package/src/eval/ifeval.ts +0 -81
- package/src/eval/kl-prompts.ts +0 -29
- package/src/eval/kl.ts +0 -590
- package/src/eval/perplexity.ts +0 -164
- package/src/eval/quality-db.ts +0 -92
- package/src/eval/reference-logits.ts +0 -207
- package/src/eval/runner.ts +0 -124
- package/src/eval/score.ts +0 -49
- package/src/eval/tasks/bfcl.ts +0 -535
- package/src/eval/tasks/gsm8k.ts +0 -98
- package/src/eval/tasks/hashhop.ts +0 -139
- package/src/eval/tasks/humaneval.ts +0 -275
- package/src/eval/tasks/ifeval.ts +0 -525
- package/src/eval/tasks/mmlu.ts +0 -157
- package/src/eval/ultrafeedback.ts +0 -60
- package/src/evaldb.ts +0 -199
- package/src/expert-io.ts +0 -393
- package/src/expert-offload-build.ts +0 -77
- package/src/expert-offload.ts +0 -76
- package/src/expert-residency.ts +0 -1134
- package/src/expert-usage.ts +0 -415
- package/src/fill/echo-index.ts +0 -188
- package/src/fill/fill-session.ts +0 -452
- package/src/fill/proposal.ts +0 -68
- package/src/fill/schema-rows.ts +0 -506
- package/src/fill/tool-boundary.ts +0 -103
- package/src/fit.ts +0 -249
- package/src/generate.ts +0 -1346
- package/src/grammar.ts +0 -591
- package/src/harness-pi.ts +0 -184
- package/src/hf-push.ts +0 -628
- package/src/hub-rest.ts +0 -242
- package/src/index.ts +0 -63
- package/src/inference/denoising.ts +0 -17
- package/src/inference/draft-acceptance.ts +0 -41
- package/src/inference/draft-output.ts +0 -49
- package/src/inference/graph.ts +0 -33
- package/src/inference/prefill.ts +0 -39
- package/src/inference/rollback.ts +0 -7
- package/src/jobs/index.ts +0 -31
- package/src/jobs/job-entry.ts +0 -107
- package/src/jobs/task-client.ts +0 -19
- package/src/jobs/types.ts +0 -2
- package/src/kv-scheme.ts +0 -257
- package/src/kv-store.ts +0 -1099
- package/src/lab/README.md +0 -18
- package/src/lab/curve/curve-designer.html +0 -303
- package/src/lab/curve/curve-sampler.ts +0 -122
- package/src/lab/expert-trace/expert-trace.ts +0 -69
- package/src/lab/paged-kv/paged-kv-rows.ts +0 -83
- package/src/lab/paged-kv/paged-kv.ts +0 -270
- package/src/library.ts +0 -40
- package/src/lora.ts +0 -408
- package/src/md-modules.d.ts +0 -49
- package/src/media-fetch.ts +0 -442
- package/src/memory/article.ts +0 -558
- package/src/memory/chunk-validation.ts +0 -150
- package/src/memory/chunk.ts +0 -318
- package/src/memory/cluster.ts +0 -351
- package/src/memory/coverage.ts +0 -134
- package/src/memory/crosslink.ts +0 -0
- package/src/memory/db.ts +0 -253
- package/src/memory/entity.ts +0 -203
- package/src/memory/gate.ts +0 -187
- package/src/memory/history.ts +0 -152
- package/src/memory/ingest.ts +0 -335
- package/src/memory/model.ts +0 -183
- package/src/memory/normalize.ts +0 -671
- package/src/memory/parse.ts +0 -22
- package/src/memory/pipeline.ts +0 -275
- package/src/memory/prompts.ts +0 -65
- package/src/memory/query.ts +0 -334
- package/src/memory/reconcile.ts +0 -734
- package/src/memory/redirect.ts +0 -108
- package/src/memory/reindex.ts +0 -325
- package/src/memory/resolve.ts +0 -307
- package/src/memory/rest.ts +0 -245
- package/src/memory/route.ts +0 -272
- package/src/memory/schedule.ts +0 -165
- package/src/memory/stages.ts +0 -777
- package/src/memory/synthesize.ts +0 -1638
- package/src/memory/tools.ts +0 -749
- package/src/memory/vault.ts +0 -1194
- package/src/memory/wikify.ts +0 -1064
- package/src/mlx/array.ts +0 -388
- package/src/mlx/autograd.ts +0 -304
- package/src/mlx/checkpoint.ts +0 -149
- package/src/mlx/compile.ts +0 -168
- package/src/mlx/custom-vjp.ts +0 -188
- package/src/mlx/ffi.ts +0 -512
- package/src/mlx/materialize.ts +0 -23
- package/src/mlx/metal-kernel.ts +0 -159
- package/src/mlx/ops.ts +0 -1021
- package/src/mlx/quantized-rows.ts +0 -36
- package/src/mlx/token-bitmask.ts +0 -52
- package/src/mlx/turboquant-kv-decode.ts +0 -82
- package/src/mlx/turboquant-kv-inverse.ts +0 -122
- package/src/mlx/turboquant-ops.ts +0 -861
- package/src/mlx/turboquant-tables.ts +0 -237
- package/src/mmap.ts +0 -90
- package/src/model/artifact-identity.ts +0 -73
- package/src/model/batched-kv.ts +0 -235
- package/src/model/batched-mask.ts +0 -279
- package/src/model/batched-quant.ts +0 -212
- package/src/model/batched-quantized-kv.ts +0 -144
- package/src/model/batched-rotating-quant.ts +0 -353
- package/src/model/batched-rotating-state.ts +0 -149
- package/src/model/batched-rotating.ts +0 -338
- package/src/model/batched-row-storage.ts +0 -179
- package/src/model/batched-ssm.ts +0 -52
- package/src/model/batched-turboquant-kv.ts +0 -93
- package/src/model/compiled-decode.ts +0 -712
- package/src/model/decoded-kv-donor.ts +0 -12
- package/src/model/delayed-quantized-kv.ts +0 -72
- package/src/model/delayed-rotating-quantized-kv.ts +0 -93
- package/src/model/delayed-turboquant-kv.ts +0 -55
- package/src/model/diffusion-gemma.ts +0 -1026
- package/src/model/factory.ts +0 -189
- package/src/model/fingerprint.ts +0 -33
- package/src/model/flash-attention.ts +0 -694
- package/src/model/full-prefill-padding.ts +0 -56
- package/src/model/full-prefill-row.ts +0 -105
- package/src/model/gemma4-base.ts +0 -2351
- package/src/model/gemma4.ts +0 -1315
- package/src/model/generated/gemma4-12b.ts +0 -429
- package/src/model/generated/gemma4-26b.ts +0 -448
- package/src/model/generated/gemma4-e4b.ts +0 -717
- package/src/model/generated/index.ts +0 -18
- package/src/model/glm52-atlas.ts +0 -511
- package/src/model/glm52-cache.ts +0 -792
- package/src/model/glm52-config.ts +0 -262
- package/src/model/glm52-container.ts +0 -305
- package/src/model/glm52-coupling.ts +0 -388
- package/src/model/glm52-dsa.ts +0 -606
- package/src/model/glm52-expert-layout.ts +0 -253
- package/src/model/glm52-layout.ts +0 -152
- package/src/model/glm52-memory.ts +0 -500
- package/src/model/glm52-mla.ts +0 -792
- package/src/model/glm52-moe.ts +0 -223
- package/src/model/glm52-pilot.ts +0 -537
- package/src/model/glm52-reference.ts +0 -445
- package/src/model/glm52-residency.ts +0 -557
- package/src/model/glm52-resident-weights.ts +0 -135
- package/src/model/glm52-streamed-experts.ts +0 -453
- package/src/model/glm52-streamed-metal.ts +0 -610
- package/src/model/glm52-weights.ts +0 -503
- package/src/model/glm52.ts +0 -1132
- package/src/model/implementation.ts +0 -69
- package/src/model/kv-attention-view.ts +0 -67
- package/src/model/minicpm5.ts +0 -376
- package/src/model/profile.ts +0 -443
- package/src/model/qwen-append.ts +0 -12
- package/src/model/qwen-conv.ts +0 -52
- package/src/model/qwen3-delta-state.ts +0 -74
- package/src/model/qwen3-delta.ts +0 -451
- package/src/model/qwen3-moe.ts +0 -404
- package/src/model/qwen3-mrope.ts +0 -252
- package/src/model/qwen3.ts +0 -277
- package/src/model/qwen3_5-checkpoint.ts +0 -164
- package/src/model/qwen3_5.ts +0 -819
- package/src/model/rotating-kv-donor.ts +0 -35
- package/src/model/rotating-kv-layout.ts +0 -262
- package/src/model/rotating-row-transaction.ts +0 -35
- package/src/model/speculative-rotating-kv.ts +0 -101
- package/src/model/ssm-prefill-padding.ts +0 -39
- package/src/model/support.ts +0 -102
- package/src/model/trellis-balanced-scatter.ts +0 -51
- package/src/model/trellis-linear.ts +0 -672
- package/src/model/trellis-shared-m.ts +0 -151
- package/src/model/trellis-shared-scatter.ts +0 -144
- package/src/model/trellis-splitk-prefill.ts +0 -158
- package/src/model/trellis-tiled-prefill.ts +0 -136
- package/src/model/trellis-vector-expand.ts +0 -62
- package/src/model/trellis-wide-prefill.ts +0 -94
- package/src/model/turboquant-codec.ts +0 -83
- package/src/model/universal/archs.ts +0 -332
- package/src/model/universal/dense.ts +0 -612
- package/src/model/universal/modules.ts +0 -170
- package/src/model/universal/rope.ts +0 -381
- package/src/native/expert_io.c +0 -523
- package/src/native/frame_extract.swift +0 -140
- package/src/native-pack.ts +0 -162
- package/src/pi-provider.ts +0 -53
- package/src/pi-session.ts +0 -81
- package/src/pi-terminal.ts +0 -274
- package/src/pi-web.ts +0 -1932
- package/src/preflight.ts +0 -158
- package/src/prompt-cache.ts +0 -460
- package/src/quantize/allocator.ts +0 -627
- package/src/quantize/atomic-output.ts +0 -38
- package/src/quantize/calibration.ts +0 -181
- package/src/quantize/config-writer.ts +0 -145
- package/src/quantize/index.ts +0 -84
- package/src/quantize/job.ts +0 -162
- package/src/quantize/quantizer.ts +0 -666
- package/src/quantize/rotate.ts +0 -603
- package/src/quantize/safetensors-writer.ts +0 -249
- package/src/quantize/sensitivity.ts +0 -216
- package/src/quantize/trellis.ts +0 -586
- package/src/quantize/weight-transform.ts +0 -292
- package/src/registry.ts +0 -602
- package/src/runtime-config.ts +0 -81
- package/src/safetensors.ts +0 -158
- package/src/sampler.ts +0 -1011
- package/src/serve/admin-routes.ts +0 -184
- package/src/serve/assets/curve-terrain.html +0 -105
- package/src/serve/assets/training-inference-map.html +0 -575
- package/src/serve/aux-routes.ts +0 -132
- package/src/serve/batch-scheduler.ts +0 -8
- package/src/serve/checkpoint-identity.ts +0 -64
- package/src/serve/generation-gateway.ts +0 -496
- package/src/serve/isolate.ts +0 -609
- package/src/serve/kv-budget.ts +0 -14
- package/src/serve/lab-routes.ts +0 -341
- package/src/serve/lane-registry.ts +0 -45
- package/src/serve/model-admin-routes.ts +0 -155
- package/src/serve/model-binding.ts +0 -43
- package/src/serve/model-host.ts +0 -511
- package/src/serve/parent-application.ts +0 -56
- package/src/serve/prompt-response-trace.ts +0 -147
- package/src/serve/response-lease.ts +0 -24
- package/src/serve/session-search.ts +0 -326
- package/src/serve/static-routes.ts +0 -101
- package/src/server.ts +0 -1314
- package/src/spec/assistant-rows.ts +0 -92
- package/src/spec/assistant-source.ts +0 -126
- package/src/spec/cache-identity.ts +0 -22
- package/src/spec/deepspec-rows.ts +0 -29
- package/src/spec/deepspec-source.ts +0 -145
- package/src/spec/dflash-source.ts +0 -149
- package/src/spec/drafter.ts +0 -304
- package/src/spec/dspark/ab-stats.ts +0 -136
- package/src/spec/dspark/calibration.ts +0 -112
- package/src/spec/dspark/data-dflash.ts +0 -156
- package/src/spec/dspark/data.ts +0 -201
- package/src/spec/dspark/deepspec-module.ts +0 -776
- package/src/spec/dspark/generate-dflash.ts +0 -245
- package/src/spec/dspark/generate.ts +0 -290
- package/src/spec/dspark/loader.ts +0 -23
- package/src/spec/dspark/loss.ts +0 -213
- package/src/spec/dspark/module-dflash.ts +0 -560
- package/src/spec/dspark/module.ts +0 -540
- package/src/spec/dspark/quantize-drafter.ts +0 -91
- package/src/spec/dspark/sample.ts +0 -112
- package/src/spec/generate.ts +0 -276
- package/src/spec/glm52-mtp-graph.ts +0 -29
- package/src/spec/glm52-mtp-rows.ts +0 -132
- package/src/spec/glm52-mtp-source.ts +0 -317
- package/src/spec/glm52-mtp-state.ts +0 -33
- package/src/spec/ngram-source.ts +0 -204
- package/src/spec/projected-draft-rows.ts +0 -73
- package/src/spec/qwen-mtp-module.ts +0 -171
- package/src/spec/qwen-mtp-rows.ts +0 -219
- package/src/spec/qwen-mtp-source.ts +0 -383
- package/src/spec/qwen-mtp-state.ts +0 -30
- package/src/spec/serve-loop.ts +0 -585
- package/src/spec/source.ts +0 -215
- package/src/spec/two-model-rows.ts +0 -61
- package/src/spec/two-model.ts +0 -113
- package/src/ssd-cache.ts +0 -372
- package/src/ssd-durability.ts +0 -211
- package/src/tokenizer.ts +0 -335
- package/src/tool-approvals.ts +0 -131
- package/src/tool-call.ts +0 -695
- package/src/train/dataset.ts +0 -484
- package/src/train/diffusion-lora.ts +0 -216
- package/src/train/export.ts +0 -32
- package/src/train/flash-cce.ts +0 -1234
- package/src/train/forward.ts +0 -266
- package/src/train/fuse.ts +0 -243
- package/src/train/index.ts +0 -46
- package/src/train/job.ts +0 -205
- package/src/train/lora-params.ts +0 -254
- package/src/train/loss.ts +0 -1591
- package/src/train/merge.ts +0 -195
- package/src/train/optimizer.ts +0 -183
- package/src/train/prefix-shared.ts +0 -490
- package/src/train/rank.ts +0 -137
- package/src/train/segmented.ts +0 -1828
- package/src/train/steel-qmm-header.ts +0 -1461
- package/src/train/trainer.ts +0 -1221
- package/src/train/watch.ts +0 -542
- package/src/vision/diffusion-vision.ts +0 -620
- package/src/vision/embedder.ts +0 -168
- package/src/vision/preprocess.ts +0 -247
- package/src/vision/prompt.ts +0 -373
- package/src/vision/qwen3vl-preprocess.ts +0 -409
- package/src/vision/qwen3vl-prompt.ts +0 -196
- package/src/vision/qwen3vl-tower.ts +0 -555
- package/src/vision/siglip.ts +0 -599
- package/src/vision/video-frames.ts +0 -148
- package/src/web/app.html +0 -2143
- package/src/web/app.js +0 -5703
- package/src/web/skills/memory/SKILL.md +0 -120
- package/src/web/skills/web-research/SKILL.md +0 -46
- package/src/web/skills.ts +0 -97
- package/src/web/src/adapters-panel.ts +0 -288
- package/src/web/src/assistant.ts +0 -451
- package/src/web/src/chat.ts +0 -1057
- package/src/web/src/composer.ts +0 -959
- package/src/web/src/dataset.ts +0 -132
- package/src/web/src/finetune.ts +0 -267
- package/src/web/src/hub.ts +0 -428
- package/src/web/src/main.ts +0 -98
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +0 -572
- package/src/web/src/model-picker.ts +0 -159
- package/src/web/src/palette.ts +0 -346
- package/src/web/src/protocol.ts +0 -26
- package/src/web/src/quantize.ts +0 -167
- package/src/web/src/rag.ts +0 -274
- package/src/web/src/sessions.ts +0 -469
- package/src/web/src/shell.ts +0 -804
- package/src/web/src/ui-catalog.ts +0 -78
- package/src/web/vendor/README.md +0 -61
- package/src/web-assets.ts +0 -47
- package/src/web-tools.ts +0 -478
- package/src/weights.ts +0 -255
- /package/src/{serve → server}/completion-sink.ts +0 -0
- /package/src/web/{icon.svg → public/icon.svg} +0 -0
- /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
- /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
package/src/train/flash-cce.ts
DELETED
|
@@ -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
|
-
}
|