mlx-bun 0.2.0 → 0.3.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 +71 -588
- package/docs/README.md +91 -0
- package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
- package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
- package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
- package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- package/docs/design/batching.md +569 -0
- package/docs/design/decode-speed-program.md +3178 -169
- package/docs/design/dreaming-nightly-pipeline.md +675 -282
- package/docs/design/generic-model-support.md +579 -334
- package/docs/design/kv-cache.md +680 -0
- package/docs/design/orpo-training.md +673 -1239
- package/docs/design/speculative-decoding.md +1627 -0
- package/docs/design/turboquant.md +2102 -0
- package/docs/design/unified-engine-frontier-plan.md +1831 -727
- package/docs/design/web-chat-redesign.md +666 -1083
- package/docs/planning/PRODUCT_ROADMAP.md +34 -1
- package/docs/planning/release-notes-v0.3.0.md +35 -0
- package/docs/planning/video-series.md +2 -2
- package/docs/reference/benchmarks.md +1162 -0
- package/docs/reference/cli.md +394 -159
- package/docs/reference/distribution.md +321 -182
- package/docs/reference/environment.md +348 -0
- package/docs/reference/glossary.md +39 -0
- package/docs/reference/library-api.md +566 -168
- package/docs/reference/memory.md +227 -64
- package/docs/reference/models.md +104 -6
- package/docs/reference/server-api.md +886 -425
- package/docs/reference/server-config.md +638 -330
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +166 -0
- package/package.json +9 -4
- package/src/audio/decode.ts +2 -2
- package/src/audio/features.ts +2 -2
- package/src/backends/mlx/autoregressive.ts +108 -0
- package/src/backends/mlx/batch-group.ts +1646 -0
- package/src/backends/mlx/diffusion.ts +39 -0
- package/src/backends/mlx/draft-target.ts +47 -0
- package/src/backends/mlx/evaluation.ts +105 -0
- package/src/backends/mlx/fill-append.ts +45 -0
- package/src/backends/mlx/gateway-binding.ts +71 -0
- package/src/backends/mlx/graph.ts +38 -0
- package/src/backends/mlx/memory-client.ts +189 -0
- package/src/backends/mlx/memory-guard.ts +40 -0
- package/src/backends/mlx/methods.ts +90 -0
- package/src/backends/mlx/model-prompt.ts +164 -0
- package/src/backends/mlx/model-serving.ts +79 -0
- package/src/backends/mlx/prefill.ts +33 -0
- package/src/backends/mlx/request-policy.ts +20 -0
- package/src/backends/mlx/rollback.ts +51 -0
- package/src/backends/mlx/serial-executor.ts +273 -0
- package/src/backends/mlx/speculative.ts +114 -0
- package/src/backends/mlx/state-views.ts +26 -0
- package/src/backends/mlx/text-engine.ts +73 -0
- package/src/cli.ts +58 -25
- package/src/client.ts +66 -0
- package/src/config.ts +21 -4
- package/src/contracts/completion.ts +16 -0
- package/src/contracts/execution.ts +49 -0
- package/src/contracts/generation.ts +103 -0
- package/src/contracts/host.ts +7 -0
- package/src/contracts/jobs.ts +66 -0
- package/src/contracts/pi-provider.ts +30 -0
- package/src/contracts/pi.ts +259 -0
- package/src/contracts/resources.ts +18 -0
- package/src/contracts/scheduling.ts +27 -0
- package/src/contracts/tsconfig.json +14 -0
- package/src/diffusion/diffusion-generate.ts +218 -153
- package/src/embed.ts +1 -1
- package/src/engine/admission.ts +59 -0
- package/src/engine/cancellation.ts +38 -0
- package/src/engine/engine.ts +56 -0
- package/src/engine/execution-coordinator.ts +70 -0
- package/src/engine/execution-plan.ts +51 -0
- package/src/engine/resources.ts +42 -0
- package/src/engine/scheduler.ts +67 -0
- package/src/engine/session.ts +247 -0
- package/src/eval/README.md +44 -1
- package/src/eval/kl.ts +236 -3
- package/src/eval/reference-logits.ts +207 -0
- package/src/eval/runner.ts +8 -96
- package/src/fill/echo-index.ts +188 -0
- package/src/fill/fill-session.ts +452 -0
- package/src/fill/proposal.ts +68 -0
- package/src/fill/schema-rows.ts +506 -0
- package/src/fill/tool-boundary.ts +103 -0
- package/src/fit.ts +1 -1
- package/src/generate.ts +656 -268
- package/src/grammar.ts +12 -7
- package/src/harness-pi.ts +14 -33
- package/src/hub-rest.ts +4 -4
- package/src/index.ts +7 -0
- package/src/inference/denoising.ts +17 -0
- package/src/inference/graph.ts +33 -0
- package/src/inference/prefill.ts +39 -0
- package/src/inference/rollback.ts +7 -0
- package/src/jobs/db.ts +4 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-entry.ts +12 -12
- package/src/jobs/runner.ts +98 -13
- package/src/jobs/task-client.ts +19 -0
- package/src/jobs/types.ts +2 -58
- package/src/kv-store.ts +109 -49
- package/src/lab/README.md +18 -0
- package/src/{assets → lab/curve}/curve-designer.html +2 -2
- package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
- package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
- package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
- package/src/library.ts +40 -0
- package/src/lora.ts +18 -0
- package/src/media-fetch.ts +1 -1
- package/src/memory/article.ts +1 -1
- package/src/memory/chunk.ts +1 -1
- package/src/memory/model.ts +30 -169
- package/src/memory/prompts.ts +1 -1
- package/src/memory/rest.ts +1 -1
- package/src/memory/schedule.ts +1 -1
- package/src/memory/stages.ts +4 -1
- package/src/memory/vault.ts +1 -3
- package/src/mlx/array.ts +9 -10
- package/src/mlx/compile.ts +12 -2
- package/src/mlx/ffi.ts +58 -11
- package/src/mlx/materialize.ts +23 -0
- package/src/mlx/metal-kernel.ts +10 -11
- package/src/mlx/ops.ts +33 -13
- package/src/mlx/quantized-rows.ts +36 -0
- package/src/mlx/token-bitmask.ts +52 -0
- package/src/mlx/turboquant-kv-decode.ts +82 -0
- package/src/mlx/turboquant-kv-inverse.ts +122 -0
- package/src/mlx/turboquant-ops.ts +33 -3
- package/src/mlx/turboquant-tables.ts +2 -2
- package/src/model/batched-mask.ts +8 -2
- package/src/model/batched-quant.ts +1 -1
- package/src/model/batched-rotating-quant.ts +2 -2
- package/src/model/batched-rotating.ts +1 -1
- package/src/model/compiled-decode.ts +1 -1
- package/src/model/diffusion-gemma.ts +8 -8
- package/src/model/factory.ts +76 -33
- package/src/model/fingerprint.ts +1 -1
- package/src/model/flash-attention.ts +3 -3
- package/src/model/gemma4-base.ts +26 -9
- package/src/model/gemma4.ts +6 -6
- package/src/model/generated/gemma4-12b.ts +2 -2
- package/src/model/generated/gemma4-26b.ts +2 -2
- package/src/model/generated/gemma4-e4b.ts +2 -2
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +1 -1
- package/src/model/implementation.ts +69 -0
- package/src/model/minicpm5.ts +6 -8
- package/src/model/profile.ts +12 -2
- package/src/model/qwen-append.ts +12 -0
- package/src/model/qwen-conv.ts +52 -0
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +140 -56
- package/src/model/support.ts +1 -1
- package/src/model/trellis-balanced-scatter.ts +51 -0
- package/src/model/trellis-linear.ts +672 -0
- package/src/model/trellis-shared-m.ts +151 -0
- package/src/model/trellis-shared-scatter.ts +144 -0
- package/src/model/trellis-splitk-prefill.ts +158 -0
- package/src/model/trellis-tiled-prefill.ts +136 -0
- package/src/model/trellis-vector-expand.ts +62 -0
- package/src/model/trellis-wide-prefill.ts +94 -0
- package/src/model/universal/dense.ts +1 -1
- package/src/model/universal/modules.ts +8 -1
- package/src/native-pack.ts +6 -5
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +8 -315
- package/src/prompt-cache.ts +96 -53
- package/src/quantize/allocator.ts +1 -1
- package/src/quantize/config-writer.ts +10 -3
- package/src/quantize/rotate.ts +4 -4
- package/src/quantize/trellis.ts +586 -0
- package/src/responses.ts +28 -1
- package/src/runtime-config.ts +18 -4
- package/src/sampler.ts +4 -4
- package/src/serve/batch-scheduler.ts +8 -1671
- package/src/serve/chat-request.ts +391 -0
- package/src/serve/chat-stage.ts +195 -0
- package/src/serve/checkpoint-identity.ts +64 -0
- package/src/serve/completion-executor.ts +15 -7
- package/src/serve/discovery-routes.ts +12 -13
- package/src/serve/generation-gateway.ts +154 -193
- package/src/serve/http.ts +159 -0
- package/src/serve/inference-request.ts +79 -0
- package/src/serve/isolate.ts +297 -105
- package/src/serve/lab-routes.ts +3 -0
- package/src/serve/lane-registry.ts +2 -1
- package/src/serve/model-admin-routes.ts +7 -5
- package/src/serve/model-binding.ts +43 -0
- package/src/serve/model-host.ts +512 -0
- package/src/serve/openai-wire.ts +103 -0
- package/src/serve/parent-application.ts +56 -0
- package/src/serve/pipeline.ts +33 -0
- package/src/serve/preparation.ts +33 -0
- package/src/serve/request-plan.ts +19 -15
- package/src/serve/request-prep.ts +346 -0
- package/src/serve/response-lease.ts +24 -0
- package/src/serve/responses-client.ts +69 -0
- package/src/serve/session-completion-engine.ts +110 -0
- package/src/serve/session-search.ts +1 -1
- package/src/serve/static-routes.ts +5 -5
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +317 -2649
- package/src/spec/assistant-source.ts +10 -44
- package/src/spec/deepspec-source.ts +6 -6
- package/src/spec/dflash-source.ts +8 -8
- package/src/spec/dspark/ab-stats.ts +3 -3
- package/src/spec/dspark/calibration.ts +2 -2
- package/src/spec/dspark/deepspec-module.ts +2 -2
- package/src/spec/dspark/generate-dflash.ts +2 -2
- package/src/spec/dspark/generate.ts +2 -2
- package/src/spec/dspark/loader.ts +1 -1
- package/src/spec/dspark/module-dflash.ts +4 -3
- package/src/spec/dspark/module.ts +3 -3
- package/src/spec/dspark/quantize-drafter.ts +1 -1
- package/src/spec/generate.ts +1 -1
- package/src/spec/glm52-mtp-source.ts +1 -1
- package/src/spec/ngram-source.ts +1 -1
- package/src/spec/prefix-state.ts +45 -0
- package/src/spec/qwen-mtp-source.ts +166 -69
- package/src/spec/serve-loop.ts +154 -156
- package/src/spec/source.ts +42 -10
- package/src/spec/two-model.ts +1 -1
- package/src/ssd-cache.ts +80 -12
- package/src/ssd-durability.ts +25 -13
- package/src/tool-call.ts +51 -14
- package/src/train/flash-cce.ts +2 -2
- package/src/train/forward.ts +6 -0
- package/src/train/loss.ts +1 -1
- package/src/train/prefix-shared.ts +5 -1
- package/src/train/segmented.ts +5 -3
- package/src/train/trainer.ts +6 -6
- package/src/vision/prompt.ts +1 -1
- package/src/vision/qwen3vl-tower.ts +52 -10
- package/src/web/app.html +1 -1
- package/src/web/app.js +5 -3
- package/src/web/src/adapters-panel.ts +3 -3
- package/src/web/src/assistant.ts +2 -36
- package/src/web/src/chat.ts +4 -4
- package/src/web/src/composer.ts +4 -4
- package/src/web/src/hub.ts +1 -1
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +1 -1
- package/src/web/src/model-picker.ts +1 -1
- package/src/web/src/palette.ts +1 -1
- package/src/web/src/protocol.ts +3 -39
- package/src/web/src/sessions.ts +1 -1
- package/src/web/src/ui-catalog.ts +4 -18
- package/src/web-assets.ts +47 -0
- package/src/weights.ts +55 -9
- package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
- package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
- package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
- package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
- package/docs/archive/investigations/hlg-ab.html +0 -34
- package/docs/archive/investigations/hlg-explorer.html +0 -238
- package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
- package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
- package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
- package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
- package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
- package/docs/archive/investigations/hlg-report.html +0 -262
- package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
- package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
- package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
- package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
- package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
- package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
- package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
- package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
- package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
- package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
- package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
- package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
- package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
- package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
- package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
- package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
- package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
- package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
- package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
- package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
- package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
- package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
- package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
- package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
- package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
- package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
- package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
- package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
- package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
- package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
- package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
- package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
- package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
- package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
- package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
- package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
- package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
- package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
- package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
- package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
- package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
- package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
- package/docs/archive/mlx-bun-lab-report.html +0 -275
- package/docs/archive/wwdc-mlx-bun.html +0 -848
- package/docs/design/adapters-end-to-end.md +0 -205
- package/docs/design/batching-perf-path.md +0 -255
- package/docs/design/batching-v2-plan.md +0 -529
- package/docs/design/bucketing-stage.md +0 -197
- package/docs/design/compat-cli-surface-design.md +0 -230
- package/docs/design/docs-reorg-plan.md +0 -165
- package/docs/design/dspark-serving-program.md +0 -371
- package/docs/design/dspark-speculative-decoding.md +0 -318
- package/docs/design/grammar-spec-batching-integration.md +0 -301
- package/docs/design/memory-inference-path.md +0 -422
- package/docs/design/memory-synthesis.md +0 -287
- package/docs/design/memory-system.md +0 -237
- package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
- package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
- package/docs/design/omlx-adoption-map.md +0 -98
- package/docs/design/orpo-dynamic-lambda.md +0 -220
- package/docs/design/orpo-future-enhancements.md +0 -340
- package/docs/design/paged-kv-cache.md +0 -214
- package/docs/design/parallel-slots.md +0 -567
- package/docs/design/parity-tier-dag.md +0 -161
- package/docs/design/repo-cleanup-plan.md +0 -231
- package/docs/design/runtime-isolation.md +0 -263
- package/docs/design/segmented-backward-training.md +0 -543
- package/docs/design/spec-decode-larger-targets.md +0 -229
- package/docs/design/ssd-kv-cold-tier.md +0 -415
- package/docs/design/structured-output.md +0 -484
- package/docs/design/tauri-desktop-app.md +0 -127
- package/docs/design/the-dreaming-master-plan.md +0 -445
- package/docs/design/turboquant-kv.md +0 -235
- package/docs/design/turboquant-weights.md +0 -467
- package/docs/design/w4a16-compute-precision-spike.md +0 -232
- package/docs/design/web-chat-beat-matrix.md +0 -315
- package/docs/design/write-pipeline-entity.md +0 -468
- package/docs/planning/IDEAS.md +0 -69
- package/docs/reference/embedding.md +0 -197
- package/docs/reference/features-matrix.md +0 -101
- package/docs/reference/orpo-quickstart.md +0 -135
- package/src/flags.ts +0 -11
- /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
- /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
- /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
- /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
- /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
- /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
- /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
- /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
package/src/mlx/ffi.ts
CHANGED
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
// - mlx_optional_int is `{ int32 value; bool has_value }` = 8 bytes, by
|
|
11
11
|
// value in one register: value in bits 0..31, has_value at bit 32.
|
|
12
12
|
|
|
13
|
-
import { dlopen, FFIType, ptr, read } from "bun:ffi";
|
|
13
|
+
import { dlopen, FFIType, ptr, read, type Pointer } from "bun:ffi";
|
|
14
14
|
import { existsSync } from "node:fs";
|
|
15
15
|
import { dirname, join } from "node:path";
|
|
16
|
-
import { nativePackDir } from "../native-pack";
|
|
16
|
+
import { MLX_CORE_VERSION, nativePackDir } from "../native-pack";
|
|
17
17
|
import { runtimeValue } from "../runtime-config";
|
|
18
18
|
|
|
19
19
|
const { ptr: P, i32, u64, f32, f64, cstring } = FFIType;
|
|
@@ -21,7 +21,7 @@ const { ptr: P, i32, u64, f32, f64, cstring } = FFIType;
|
|
|
21
21
|
/** libmlxc resolution, in order: explicit env override → next to the
|
|
22
22
|
* executable (the `bun build --compile` sidecar layout, where
|
|
23
23
|
* libmlxc.dylib + libmlx.dylib + mlx.metallib ship beside the binary —
|
|
24
|
-
* see docs/reference/
|
|
24
|
+
* see docs/reference/distribution.md) → the downloaded native-pack cache
|
|
25
25
|
* (src/native-pack.ts; populated on first run by the CLI) → homebrew
|
|
26
26
|
* (arm64, then Intel prefix). Keep in sync with nativeRuntimeDir(). */
|
|
27
27
|
function resolveLibmlxc(): string {
|
|
@@ -38,8 +38,12 @@ function resolveLibmlxc(): string {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export const LIBMLXC_PATH = resolveLibmlxc();
|
|
41
|
-
|
|
42
41
|
export const C = dlopen(LIBMLXC_PATH, {
|
|
42
|
+
// linked MLX core version, independent of the C API family
|
|
43
|
+
mlx_version: { args: [P], returns: i32 },
|
|
44
|
+
mlx_string_new: { args: [], returns: u64 },
|
|
45
|
+
mlx_string_data: { args: [u64], returns: cstring },
|
|
46
|
+
mlx_string_free: { args: [u64], returns: i32 },
|
|
43
47
|
// array lifecycle
|
|
44
48
|
mlx_array_new: { args: [], returns: u64 },
|
|
45
49
|
mlx_array_new_data: { args: [P, P, i32, i32], returns: u64 },
|
|
@@ -78,6 +82,7 @@ export const C = dlopen(LIBMLXC_PATH, {
|
|
|
78
82
|
mlx_device_info_get: { args: [P, u64], returns: i32 },
|
|
79
83
|
mlx_device_info_free: { args: [u64], returns: i32 },
|
|
80
84
|
mlx_device_info_get_size: { args: [P, u64, cstring], returns: i32 },
|
|
85
|
+
mlx_device_info_get_string: { args: [P, u64, cstring], returns: i32 },
|
|
81
86
|
mlx_get_default_device: { args: [P], returns: i32 },
|
|
82
87
|
mlx_device_free: { args: [u64], returns: i32 },
|
|
83
88
|
mlx_synchronize: { args: [u64], returns: i32 },
|
|
@@ -98,6 +103,10 @@ export const C = dlopen(LIBMLXC_PATH, {
|
|
|
98
103
|
mlx_fast_rms_norm: { args: [P, u64, u64, f32, u64], returns: i32 },
|
|
99
104
|
// (res, x, weight may-be-null, bias may-be-null, eps, stream)
|
|
100
105
|
mlx_fast_layer_norm: { args: [P, u64, u64, u64, f32, u64], returns: i32 },
|
|
106
|
+
// MLX 0.32.2: mask, sinks, force_fused, stream.
|
|
107
|
+
mlx_fast_scaled_dot_product_attention: {
|
|
108
|
+
args: [P, u64, u64, u64, f32, cstring, u64, u64, FFIType.bool, u64], returns: i32,
|
|
109
|
+
},
|
|
101
110
|
mlx_matmul: { args: [P, u64, u64, u64], returns: i32 },
|
|
102
111
|
mlx_contiguous: { args: [P, u64, FFIType.bool, u64], returns: i32 },
|
|
103
112
|
mlx_copy: { args: [P, u64, u64], returns: i32 },
|
|
@@ -108,15 +117,14 @@ export const C = dlopen(LIBMLXC_PATH, {
|
|
|
108
117
|
mlx_softmax_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
|
|
109
118
|
mlx_max_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
|
|
110
119
|
mlx_sum_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
|
|
120
|
+
// Optional dtype uses the eight-byte value/has_value layout of optional int.
|
|
121
|
+
mlx_cumsum_axis: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64, u64], returns: i32 },
|
|
122
|
+
mlx_cummax_axis: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64], returns: i32 },
|
|
111
123
|
mlx_maximum: { args: [P, u64, u64, u64], returns: i32 },
|
|
112
124
|
mlx_expand_dims: { args: [P, u64, i32, u64], returns: i32 },
|
|
113
125
|
mlx_logical_or: { args: [P, u64, u64, u64], returns: i32 },
|
|
114
126
|
// (res, x, dims, traditional, opt base, scale, offset, freqs may-be-null, stream)
|
|
115
127
|
mlx_fast_rope: { args: [P, u64, i32, FFIType.bool, u64, f32, i32, u64, u64], returns: i32 },
|
|
116
|
-
// (res, q, k, v, scale, mask_mode, mask may-be-null, sinks may-be-null, stream)
|
|
117
|
-
mlx_fast_scaled_dot_product_attention: {
|
|
118
|
-
args: [P, u64, u64, u64, f32, cstring, u64, u64, u64], returns: i32,
|
|
119
|
-
},
|
|
120
128
|
// ops (grown as phases need them)
|
|
121
129
|
mlx_add: { args: [P, u64, u64, u64], returns: i32 },
|
|
122
130
|
mlx_subtract: { args: [P, u64, u64, u64], returns: i32 },
|
|
@@ -150,8 +158,6 @@ export const C = dlopen(LIBMLXC_PATH, {
|
|
|
150
158
|
mlx_arange: { args: [P, f64, f64, f64, i32, u64], returns: i32 },
|
|
151
159
|
mlx_take_along_axis: { args: [P, u64, u64, i32, u64], returns: i32 },
|
|
152
160
|
mlx_put_along_axis: { args: [P, u64, u64, u64, i32, u64], returns: i32 },
|
|
153
|
-
mlx_cumsum: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64], returns: i32 },
|
|
154
|
-
mlx_cummax: { args: [P, u64, i32, FFIType.bool, FFIType.bool, u64], returns: i32 },
|
|
155
161
|
mlx_where: { args: [P, u64, u64, u64, u64], returns: i32 },
|
|
156
162
|
mlx_logsumexp_axis: { args: [P, u64, i32, FFIType.bool, u64], returns: i32 },
|
|
157
163
|
mlx_greater_equal: { args: [P, u64, u64, u64], returns: i32 },
|
|
@@ -220,7 +226,7 @@ export const C = dlopen(LIBMLXC_PATH, {
|
|
|
220
226
|
mlx_save_safetensors: { args: [cstring, u64, u64], returns: i32 },
|
|
221
227
|
mlx_map_string_to_array_insert: { args: [u64, cstring, u64], returns: i32 },
|
|
222
228
|
mlx_map_string_to_string_insert: { args: [u64, cstring, cstring], returns: i32 },
|
|
223
|
-
// compile via closures (Phase A compiled decode — docs/
|
|
229
|
+
// compile via closures (Phase A compiled decode — docs/archive/investigations/optimization_plan.md).
|
|
224
230
|
// mlx_closure is the usual one-pointer struct; the func-payload variant
|
|
225
231
|
// carries an id we use to route the trace callback back to JS.
|
|
226
232
|
mlx_closure_new: { args: [], returns: u64 },
|
|
@@ -314,6 +320,22 @@ export function takeMlxError(): string | null {
|
|
|
314
320
|
return e;
|
|
315
321
|
}
|
|
316
322
|
|
|
323
|
+
function readRuntimeVersion(): string {
|
|
324
|
+
const slot = new BigUint64Array([C.mlx_string_new()]);
|
|
325
|
+
const p = ptr(slot);
|
|
326
|
+
try {
|
|
327
|
+
if (C.mlx_version(p) !== 0)
|
|
328
|
+
throw new Error(`mlx_version failed: ${takeMlxError() ?? "(no mlx message)"}`);
|
|
329
|
+
return C.mlx_string_data(read.u64(p, 0)).toString();
|
|
330
|
+
} finally { C.mlx_string_free(read.u64(p, 0)); }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Read once at startup; kernel eligibility must follow the linked core. */
|
|
334
|
+
export const MLX_VERSION = readRuntimeVersion();
|
|
335
|
+
if (MLX_VERSION !== MLX_CORE_VERSION) {
|
|
336
|
+
throw new Error(`mlx-bun requires its bundled MLX ${MLX_CORE_VERSION}; ${LIBMLXC_PATH} loaded MLX ${MLX_VERSION}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
317
339
|
/** mlx_dtype enum values (array.h). */
|
|
318
340
|
export const enum Dtype {
|
|
319
341
|
bool = 0, uint8 = 1, uint16 = 2, uint32 = 3, uint64 = 4,
|
|
@@ -405,6 +427,31 @@ export function clearCache(): void {
|
|
|
405
427
|
C.mlx_clear_cache();
|
|
406
428
|
}
|
|
407
429
|
|
|
430
|
+
let cachedDeviceArchitecture: string | undefined;
|
|
431
|
+
|
|
432
|
+
/** Native architecture identifier, used to match MLX's hardware dispatch. */
|
|
433
|
+
export function deviceArchitecture(): string {
|
|
434
|
+
if (cachedDeviceArchitecture !== undefined) return cachedDeviceArchitecture;
|
|
435
|
+
const devSlot = new BigUint64Array(1), devPtr = ptr(devSlot);
|
|
436
|
+
if (C.mlx_get_default_device(devPtr) !== 0)
|
|
437
|
+
throw new Error(`mlx_get_default_device failed: ${takeMlxError() ?? ""}`);
|
|
438
|
+
const dev = read.u64(devPtr, 0);
|
|
439
|
+
const infoSlot = new BigUint64Array([C.mlx_device_info_new()]), infoPtr = ptr(infoSlot);
|
|
440
|
+
try {
|
|
441
|
+
if (C.mlx_device_info_get(infoPtr, dev) !== 0)
|
|
442
|
+
throw new Error(`mlx_device_info_get failed: ${takeMlxError() ?? ""}`);
|
|
443
|
+
const out = new BigUint64Array(1), outPtr = ptr(out);
|
|
444
|
+
const key = Buffer.from("architecture\0", "utf8");
|
|
445
|
+
if (C.mlx_device_info_get_string(outPtr, read.u64(infoPtr, 0), ptr(key)) !== 0)
|
|
446
|
+
throw new Error("architecture not in device info");
|
|
447
|
+
// Copy before the device-info object releases the native string.
|
|
448
|
+
return cachedDeviceArchitecture = new CString(Number(read.u64(outPtr, 0)) as Pointer).toString();
|
|
449
|
+
} finally {
|
|
450
|
+
C.mlx_device_info_free(read.u64(infoPtr, 0));
|
|
451
|
+
C.mlx_device_free(dev);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
408
455
|
/** Metal's recommended max working-set size for the default device. */
|
|
409
456
|
export function maxRecommendedWorkingSetSize(): number {
|
|
410
457
|
const devSlot = new BigUint64Array(1);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Explicit compact storage for a value that must release a larger backing
|
|
2
|
+
// buffer. MLX 0.31.2 Copy::eval shares storage; contiguous() may also alias.
|
|
3
|
+
// MLX's dynamic slice allocates the logical output size and has a native VJP.
|
|
4
|
+
import { MlxArray } from "./array";
|
|
5
|
+
import * as ops from "./ops";
|
|
6
|
+
|
|
7
|
+
let zero: MlxArray | undefined;
|
|
8
|
+
|
|
9
|
+
/** Borrow an array; return an owned, row-contiguous copy on the GPU.
|
|
10
|
+
* Values and dtype are unchanged. Evaluation stays lazy and never reads data
|
|
11
|
+
* back to the host. Use only when independent compact storage is required;
|
|
12
|
+
* immutable array aliases normally avoid this allocation and dispatch.
|
|
13
|
+
* Slice sizes are concrete, so use shape-specialized compiled graphs. */
|
|
14
|
+
export function materializeCopy(input: MlxArray): MlxArray {
|
|
15
|
+
zero ??= MlxArray.fromInt32(new Int32Array([0]), []);
|
|
16
|
+
if (input.ndim > 0) return ops.sliceDynamic(input, zero, [0], input.shape);
|
|
17
|
+
const vector = ops.reshape(input, [1]);
|
|
18
|
+
try {
|
|
19
|
+
const copy = ops.sliceDynamic(vector, zero, [0], [1]);
|
|
20
|
+
try { return ops.reshape(copy, []); }
|
|
21
|
+
finally { copy.dispose(); }
|
|
22
|
+
} finally { vector.dispose(); }
|
|
23
|
+
}
|
package/src/mlx/metal-kernel.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// mx.fast.metal_kernel from Bun (docs/
|
|
1
|
+
// mx.fast.metal_kernel from Bun (docs/archive/investigations/optimization_plan.md Phase E): the
|
|
2
2
|
// intended MLX extension point for custom Metal kernels — no fork. The
|
|
3
3
|
// kernel body is Metal Shading Language; mlx generates the signature
|
|
4
4
|
// from input/output names (inputs by name, outputs by name, plus
|
|
@@ -29,15 +29,15 @@ export interface MetalKernelSpec {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export interface MetalKernelCall {
|
|
32
|
-
/** Concrete output shapes/dtypes. Provide this OR `outputShapeFn
|
|
33
|
-
*
|
|
34
|
-
* primitive — required to compose inside an mx.compile'd / shapeless closure,
|
|
35
|
-
* where the wrapper must answer "given these input shapes, what comes out?"). */
|
|
32
|
+
/** Concrete output shapes/dtypes. Provide this OR `outputShapeFn`.
|
|
33
|
+
* Both are captured when apply() builds the custom primitive. */
|
|
36
34
|
outputs?: { shape: number[]; dtype: Dtype }[];
|
|
37
|
-
/** Derive
|
|
35
|
+
/** Derive outputs during eager construction or a shape-specific compile trace.
|
|
36
|
+
* This JS callback is not native shape inference: MLX's CustomKernel lacks
|
|
37
|
+
* output_shapes(), so it cannot participate in shapeless replay. */
|
|
38
38
|
outputShapeFn?: (inputs: MlxArray[]) => { shape: number[]; dtype: Dtype }[];
|
|
39
|
-
/** Concrete launch grid, OR derive it
|
|
40
|
-
*
|
|
39
|
+
/** Concrete launch grid, OR derive it during eager construction or tracing.
|
|
40
|
+
* Native replay reuses the captured grid without invoking this callback. */
|
|
41
41
|
grid: [number, number, number] | ((inputs: MlxArray[]) => [number, number, number]);
|
|
42
42
|
threadGroup: [number, number, number];
|
|
43
43
|
templateInts?: Record<string, number>;
|
|
@@ -73,9 +73,8 @@ export class MetalKernel {
|
|
|
73
73
|
// flash-CCE pin bug). Pure-additive: holds references, changes no logic.
|
|
74
74
|
const keepAlive: unknown[] = [];
|
|
75
75
|
try {
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
// during a compile/replay trace, instead of baking in one call's shapes.
|
|
76
|
+
// Resolve callbacks once for this eager call or shape-specific trace.
|
|
77
|
+
// A compiled graph captures these values; replay does not call JS again.
|
|
79
78
|
const outputs = call.outputShapeFn ? call.outputShapeFn(inputs) : call.outputs;
|
|
80
79
|
if (!outputs) throw new Error("metal_kernel: provide outputs or outputShapeFn");
|
|
81
80
|
const grid = typeof call.grid === "function" ? call.grid(inputs) : call.grid;
|
package/src/mlx/ops.ts
CHANGED
|
@@ -170,7 +170,7 @@ export function ropeScaled(
|
|
|
170
170
|
* of a baked int — required inside compiled decode graphs, where the
|
|
171
171
|
* offset changes every step but the graph must not. Same kernel as
|
|
172
172
|
* `rope`; bit-exactness vs the static form is asserted in
|
|
173
|
-
* tests/compile.test.ts. */
|
|
173
|
+
* tests/unit/compile.test.ts. */
|
|
174
174
|
export function ropeDynamic(
|
|
175
175
|
x: MlxArray, dims: number, base: number | null, offset: MlxArray,
|
|
176
176
|
freqs: MlxArray | null, s: S = gpuStream,
|
|
@@ -189,7 +189,7 @@ export function ropeDynamic(
|
|
|
189
189
|
* the dynamic twin of `ropeScaled`, for the universal rope factory's
|
|
190
190
|
* batched-decode path (per-row [B] offsets under left-padding). Same
|
|
191
191
|
* kernel as `ropeDynamic`; static/dynamic bit-exactness is asserted in
|
|
192
|
-
* tests/compile.test.ts. */
|
|
192
|
+
* tests/unit/compile.test.ts. */
|
|
193
193
|
export function ropeScaledDynamic(
|
|
194
194
|
x: MlxArray, dims: number, traditional: boolean, base: number | null,
|
|
195
195
|
scale: number, offset: MlxArray, freqs: MlxArray | null, s: S = gpuStream,
|
|
@@ -213,7 +213,7 @@ export function sdpa(
|
|
|
213
213
|
outArray("fast_sdpa", (o) =>
|
|
214
214
|
C.mlx_fast_scaled_dot_product_attention(
|
|
215
215
|
o, q.handle, k.handle, v.handle, scale, ptr(cstr(maskMode)),
|
|
216
|
-
maskArr?.handle ?? 0n, 0n, s,
|
|
216
|
+
maskArr?.handle ?? 0n, 0n, false, s,
|
|
217
217
|
),
|
|
218
218
|
),
|
|
219
219
|
);
|
|
@@ -314,7 +314,8 @@ export function takeAxis(a: MlxArray, indices: MlxArray, axis: number, s: S = gp
|
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
export function reshape(a: MlxArray, shape: number[], s: S = gpuStream): MlxArray {
|
|
317
|
-
|
|
317
|
+
// bun:ffi cannot take a pointer to an empty view; ndim=0 ignores the dummy.
|
|
318
|
+
const buf = new Int32Array(shape.length ? shape : [0]);
|
|
318
319
|
return new MlxArray(
|
|
319
320
|
outArray("reshape", (o) => C.mlx_reshape(o, a.handle, ptr(buf), BigInt(shape.length), s)),
|
|
320
321
|
);
|
|
@@ -454,14 +455,14 @@ export function putAlongAxis(
|
|
|
454
455
|
|
|
455
456
|
export function cumsum(a: MlxArray, axis: number, s: S = gpuStream): MlxArray {
|
|
456
457
|
return new MlxArray(
|
|
457
|
-
outArray("cumsum", (o) => C.
|
|
458
|
+
outArray("cumsum", (o) => C.mlx_cumsum_axis(o, a.handle, axis, false, true, 0n, s)),
|
|
458
459
|
);
|
|
459
460
|
}
|
|
460
461
|
|
|
461
462
|
/** Cumulative maximum along `axis` (inclusive, forward) — mlx.core.cummax. */
|
|
462
463
|
export function cummax(a: MlxArray, axis: number, s: S = gpuStream): MlxArray {
|
|
463
464
|
return new MlxArray(
|
|
464
|
-
outArray("cummax", (o) => C.
|
|
465
|
+
outArray("cummax", (o) => C.mlx_cummax_axis(o, a.handle, axis, false, true, s)),
|
|
465
466
|
);
|
|
466
467
|
}
|
|
467
468
|
|
|
@@ -476,10 +477,13 @@ export function where(cond: MlxArray, x: MlxArray, y: MlxArray, s: S = gpuStream
|
|
|
476
477
|
// (lab/repro/bun-ffi-f64/) traced the root cause to a Bun bug: after DFG
|
|
477
478
|
// tier-up, typed-array reads following a bun:ffi call return stale values
|
|
478
479
|
// (the JIT eliminates the load across the native call). Not f64 marshaling
|
|
479
|
-
// — args reach C intact. See PLAN.md Phase 4 findings.
|
|
480
|
-
//
|
|
481
|
-
|
|
480
|
+
// — args reach C intact. See PLAN.md Phase 4 findings. Reuse large ranges
|
|
481
|
+
// across attention layers and sampling, but bound their retained storage:
|
|
482
|
+
// causal masks request a new range on every round once context exceeds 64K.
|
|
483
|
+
const arangeCache = new Map<string, { array: MlxArray; bytes: number }>();
|
|
482
484
|
const ARANGE_CACHE_MIN = 65536;
|
|
485
|
+
const ARANGE_CACHE_BYTES = 8 * 1024 * 1024;
|
|
486
|
+
let arangeCacheBytes = 0;
|
|
483
487
|
|
|
484
488
|
export function arange(start: number, stop: number, step: number, dtype: Dtype, s: S = gpuStream): MlxArray {
|
|
485
489
|
if (!Number.isInteger(start) || !Number.isInteger(step) || step === 0)
|
|
@@ -487,7 +491,11 @@ export function arange(start: number, stop: number, step: number, dtype: Dtype,
|
|
|
487
491
|
const n = Math.max(0, Math.ceil((stop - start) / step));
|
|
488
492
|
const key = `${start}|${step}|${n}|${dtype}`;
|
|
489
493
|
const cached = arangeCache.get(key);
|
|
490
|
-
if (cached)
|
|
494
|
+
if (cached) {
|
|
495
|
+
arangeCache.delete(key);
|
|
496
|
+
arangeCache.set(key, cached);
|
|
497
|
+
return cached.array.slice([0], [n], s);
|
|
498
|
+
}
|
|
491
499
|
|
|
492
500
|
let arr: MlxArray;
|
|
493
501
|
if (dtype === Dtype.int32) {
|
|
@@ -503,8 +511,20 @@ export function arange(start: number, stop: number, step: number, dtype: Dtype,
|
|
|
503
511
|
arr = new MlxArray(outArray("arange", (o) => C.mlx_arange(o, start, stop, step, dtype, s)));
|
|
504
512
|
}
|
|
505
513
|
if (n >= ARANGE_CACHE_MIN) {
|
|
506
|
-
|
|
507
|
-
|
|
514
|
+
const bytes = arr.nbytes;
|
|
515
|
+
if (bytes <= ARANGE_CACHE_BYTES) {
|
|
516
|
+
while (arangeCacheBytes + bytes > ARANGE_CACHE_BYTES) {
|
|
517
|
+
const [oldKey, old] = arangeCache.entries().next().value!;
|
|
518
|
+
arangeCache.delete(oldKey);
|
|
519
|
+
arangeCacheBytes -= old.bytes;
|
|
520
|
+
// Returned slices retain their own native reference, including lazy
|
|
521
|
+
// views that have not been evaluated when this owner is evicted.
|
|
522
|
+
old.array.dispose();
|
|
523
|
+
}
|
|
524
|
+
arangeCache.set(key, { array: arr, bytes });
|
|
525
|
+
arangeCacheBytes += bytes;
|
|
526
|
+
return arr.slice([0], [n], s);
|
|
527
|
+
}
|
|
508
528
|
}
|
|
509
529
|
return arr;
|
|
510
530
|
}
|
|
@@ -611,7 +631,7 @@ export function sliceDynamic(
|
|
|
611
631
|
/** slice_update with the start index as an ARRAY (one int32 per entry in
|
|
612
632
|
* `axes`) — the compiled-decode form of the per-step cache write. Same
|
|
613
633
|
* write as `sliceUpdate`; bit-exactness vs the static form is asserted
|
|
614
|
-
* in tests/compile.test.ts. */
|
|
634
|
+
* in tests/unit/compile.test.ts. */
|
|
615
635
|
export function sliceUpdateDynamic(
|
|
616
636
|
src: MlxArray, update: MlxArray, start: MlxArray, axes: number[], s: S = gpuStream,
|
|
617
637
|
): MlxArray {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { MlxArray } from "./array";
|
|
2
|
+
import * as ops from "./ops";
|
|
3
|
+
|
|
4
|
+
/** Run independent affine M=1 matrix-vector products in one native batched call.
|
|
5
|
+
* Weight/scales/bias views share their original buffers. MLX 0.32.2's
|
|
6
|
+
* flattened M>=2 path uses different arithmetic, so appending known tokens
|
|
7
|
+
* must explicitly select this layout when preserving decode state.
|
|
8
|
+
*/
|
|
9
|
+
export function quantizedMatmulRows(
|
|
10
|
+
x: MlxArray, w: MlxArray, scales: MlxArray, biases: MlxArray | null,
|
|
11
|
+
spec: ops.QuantSpec,
|
|
12
|
+
): MlxArray {
|
|
13
|
+
const shape = x.shape, width = shape[shape.length - 1]!;
|
|
14
|
+
const rows = shape.reduce((a, b) => a * b, 1) / width;
|
|
15
|
+
if (rows === 1) return ops.quantizedMatmul(x, w, scales, biases, spec);
|
|
16
|
+
if (spec.mode !== "affine")
|
|
17
|
+
throw new Error("Independent-row batched matmul requires affine quantization");
|
|
18
|
+
if (w.shape.length !== 2 || scales.shape.length !== 2 || (biases && biases.shape.length !== 2))
|
|
19
|
+
throw new Error("Independent-row matmul requires shared two-dimensional weights");
|
|
20
|
+
const views: MlxArray[] = [];
|
|
21
|
+
const view = (value: MlxArray, newShape: number[]) => {
|
|
22
|
+
const result = ops.reshape(value, newShape); views.push(result); return result;
|
|
23
|
+
};
|
|
24
|
+
let output: MlxArray | undefined;
|
|
25
|
+
try {
|
|
26
|
+
const input = view(x, [rows, 1, width]);
|
|
27
|
+
const weight = view(w, [1, ...w.shape]);
|
|
28
|
+
const scale = view(scales, [1, ...scales.shape]);
|
|
29
|
+
const bias = biases ? view(biases, [1, ...biases.shape]) : null;
|
|
30
|
+
output = ops.quantizedMatmul(input, weight, scale, bias, spec);
|
|
31
|
+
return ops.reshape(output, [...shape.slice(0, -1), output.shape.at(-1)!]);
|
|
32
|
+
} finally {
|
|
33
|
+
output?.dispose();
|
|
34
|
+
for (const value of views) value.dispose();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// XGrammar's additive 0/-Infinity mask contract, decoded from packed bits.
|
|
2
|
+
// Reference: mlc-ai/xgrammar b5048784, apply_token_bitmask_mlx.py (Apache-2.0).
|
|
3
|
+
import { MlxArray } from "./array";
|
|
4
|
+
import { CompiledFunction } from "./compile";
|
|
5
|
+
import { Dtype } from "./ffi";
|
|
6
|
+
import { MetalKernel } from "./metal-kernel";
|
|
7
|
+
|
|
8
|
+
let compiled: CompiledFunction | undefined;
|
|
9
|
+
let kernel: MetalKernel | undefined;
|
|
10
|
+
|
|
11
|
+
function maskGraph(logits: MlxArray, bits: MlxArray): MlxArray {
|
|
12
|
+
kernel ??= new MetalKernel({
|
|
13
|
+
name: "mlx_bun_token_bitmask",
|
|
14
|
+
inputNames: ["logits", "bits"],
|
|
15
|
+
outputNames: ["out"],
|
|
16
|
+
source: String.raw`
|
|
17
|
+
const uint i = thread_position_in_grid.x;
|
|
18
|
+
if (i >= SIZE) return;
|
|
19
|
+
const uint id = i % V;
|
|
20
|
+
const uint word = uint(bits[id >> 5]);
|
|
21
|
+
const bool valid = ((word >> (id & 31)) & 1u) != 0;
|
|
22
|
+
out[i] = logits[i] + T(valid ? 0.0f : -INFINITY);
|
|
23
|
+
`,
|
|
24
|
+
});
|
|
25
|
+
return kernel.apply([logits, bits], {
|
|
26
|
+
outputs: [{ shape: logits.shape, dtype: logits.dtype }],
|
|
27
|
+
grid: [logits.size, 1, 1],
|
|
28
|
+
threadGroup: [128, 1, 1],
|
|
29
|
+
templateDtypes: { T: logits.dtype },
|
|
30
|
+
templateInts: { V: logits.shape.at(-1)!, SIZE: logits.size },
|
|
31
|
+
})[0]!;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Apply one little-endian token bitmask to every row along the final axis.
|
|
35
|
+
* Borrow logits; return an owned lazy result. Copy the mutable host bits now,
|
|
36
|
+
* so a matcher can advance before GPU evaluation. Missing words reject tokens.
|
|
37
|
+
* Preserve the additive-mask expression, including NaNs and signed zero.
|
|
38
|
+
* Shape-specialized compilation handles bf16/f16/f32 and strided inputs. */
|
|
39
|
+
export function applyTokenBitmask(logits: MlxArray, bits: Int32Array): MlxArray {
|
|
40
|
+
const vocab = logits.shape.at(-1);
|
|
41
|
+
if (!vocab || logits.size === 0 || logits.size > 0x7fffffff)
|
|
42
|
+
throw new Error("token bitmask requires nonempty logits with at most 2^31-1 elements");
|
|
43
|
+
if (![Dtype.bfloat16, Dtype.float16, Dtype.float32].includes(logits.dtype))
|
|
44
|
+
throw new Error("token bitmask requires bf16, f16 or f32 logits");
|
|
45
|
+
const copy = new Int32Array(Math.ceil(vocab / 32));
|
|
46
|
+
copy.set(bits.subarray(0, copy.length));
|
|
47
|
+
const deviceBits = MlxArray.fromInt32(copy, [copy.length]);
|
|
48
|
+
try {
|
|
49
|
+
compiled ??= new CompiledFunction(([x, mask]) => [maskGraph(x!, mask!)], false);
|
|
50
|
+
return compiled.apply([logits, deviceBits])[0]!;
|
|
51
|
+
} finally { deviceBits.dispose(); }
|
|
52
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Exact byte-packed TurboQuant K/V decoding. The caller owns output arrays.
|
|
2
|
+
// Rotation and cache policy belong to the existing codec/cache layers.
|
|
3
|
+
import { type MlxArray, gpuStream } from './array';
|
|
4
|
+
import { Dtype, type MlxHandle } from './ffi';
|
|
5
|
+
import { isShapelessTracing } from './compile';
|
|
6
|
+
import { MetalKernel } from './metal-kernel';
|
|
7
|
+
|
|
8
|
+
export type PackedKvArrays = readonly [
|
|
9
|
+
keyIndices: MlxArray, keyScales: MlxArray, keyZeros: MlxArray,
|
|
10
|
+
valueIndices: MlxArray, valueScales: MlxArray,
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const source = `
|
|
14
|
+
uint col = thread_position_in_grid.x, pos = thread_position_in_grid.y;
|
|
15
|
+
uint bh = thread_position_in_grid.z;
|
|
16
|
+
if (col >= D || pos >= vp_shape[2] || bh >= vp_shape[0] * vp_shape[1]) return;
|
|
17
|
+
uint b = bh / vp_shape[1], h = bh % vp_shape[1];
|
|
18
|
+
uint ko = b * kp_strides[0] + h * kp_strides[1] + pos * kp_strides[2];
|
|
19
|
+
uint vpo = b * vp_strides[0] + h * vp_strides[1] + pos * vp_strides[2];
|
|
20
|
+
uint kso = b * ks_strides[0] + h * ks_strides[1] + pos * ks_strides[2] + (col / KG) * ks_strides[3];
|
|
21
|
+
uint kzo = b * kz_strides[0] + h * kz_strides[1] + pos * kz_strides[2] + (col / KG) * kz_strides[3];
|
|
22
|
+
uint vso = b * vs_strides[0] + h * vs_strides[1] + pos * vs_strides[2] + (col / VG) * vs_strides[3];
|
|
23
|
+
int key_code;
|
|
24
|
+
if (KBITS == 8) key_code = int(kp[ko + col * kp_strides[3]]);
|
|
25
|
+
else {
|
|
26
|
+
uint bit = col * KBITS, byte = bit / 8u, shift = bit % 8u;
|
|
27
|
+
uint word = uint(kp[ko + byte * kp_strides[3]]);
|
|
28
|
+
if (shift + KBITS > 8u) word |= uint(kp[ko + (byte + 1u) * kp_strides[3]]) << 8u;
|
|
29
|
+
key_code = int((word >> shift) & ((1u << KBITS) - 1u));
|
|
30
|
+
}
|
|
31
|
+
// Keep the incumbent f32 addition before multiplication.
|
|
32
|
+
float shifted = float(key_code) + float(kz[kzo]);
|
|
33
|
+
uint i = (bh * vp_shape[2] + pos) * D + col;
|
|
34
|
+
outK[i] = OutK(shifted * float(ks[kso]));
|
|
35
|
+
uint bit = col * VBITS, byte = bit / 8u, shift = bit % 8u;
|
|
36
|
+
uint word = uint(vp[vpo + byte * vp_strides[3]]);
|
|
37
|
+
if (shift + VBITS > 8u) word |= uint(vp[vpo + (byte + 1u) * vp_strides[3]]) << 8u;
|
|
38
|
+
uint code = (word >> shift) & ((1u << VBITS) - 1u);
|
|
39
|
+
outV[i] = OutV(centroids[code] * float(vs[vso]));
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
let kernel: MetalKernel | undefined;
|
|
43
|
+
function jointKernel(): MetalKernel {
|
|
44
|
+
return kernel ??= new MetalKernel({
|
|
45
|
+
name: 'turboquant_kv_decode',
|
|
46
|
+
inputNames: ['kp', 'ks', 'kz', 'vp', 'vs', 'centroids'],
|
|
47
|
+
outputNames: ['outK', 'outV'], source, ensureRowContiguous: false,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Return bf16 K and rotated V (f32 for eager inverse rotation, bf16 for
|
|
52
|
+
* deferred rotation). Null means the ordinary codec must handle this input.
|
|
53
|
+
* Input arrays and the shared centroid table remain borrowed. */
|
|
54
|
+
export function tryJointDecodePackedKv(
|
|
55
|
+
inputs: PackedKvArrays, centroids: MlxArray, kBits: number, vBits: number,
|
|
56
|
+
headDim: number, deferV: boolean, stream: MlxHandle = gpuStream,
|
|
57
|
+
): [MlxArray, MlxArray] | null {
|
|
58
|
+
if (stream !== gpuStream || isShapelessTracing() ||
|
|
59
|
+
![64, 128, 256, 512].includes(headDim) ||
|
|
60
|
+
![2, 4, 5, 8].includes(kBits) || ![2, 3, 4, 5, 8].includes(vBits)) return null;
|
|
61
|
+
const [kp, ks, kz, vp, vs] = inputs;
|
|
62
|
+
if (kp.dtype !== (kBits === 8 ? Dtype.int8 : Dtype.uint8) ||
|
|
63
|
+
vp.dtype !== Dtype.uint8 || centroids.dtype !== Dtype.float32 ||
|
|
64
|
+
centroids.ndim !== 1 || centroids.size !== (1 << vBits)) return null;
|
|
65
|
+
const shapes = inputs.map(a => a.shape);
|
|
66
|
+
if (shapes.some(shape => shape.length !== 4)) return null;
|
|
67
|
+
const [b, h, n] = shapes[3]!;
|
|
68
|
+
if (!b || !h || !n || b * h * n * headDim >= 2 ** 32 ||
|
|
69
|
+
shapes.some(shape => shape[0] !== b || shape[1] !== h || shape[2] !== n) ||
|
|
70
|
+
shapes[0]![3] !== headDim * kBits / 8 || shapes[3]![3] !== headDim * vBits / 8 ||
|
|
71
|
+
shapes[1]![3] !== shapes[2]![3]) return null;
|
|
72
|
+
const keyGroup = headDim / shapes[1]![3]!, valueGroup = headDim / shapes[4]![3]!;
|
|
73
|
+
if (![32, 64].includes(keyGroup) || ![32, 64].includes(valueGroup) ||
|
|
74
|
+
[ks, kz, vs].some(a => ![Dtype.float16, Dtype.bfloat16, Dtype.float32].includes(a.dtype))) return null;
|
|
75
|
+
const shape = [b, h, n, headDim], valueDtype = deferV ? Dtype.bfloat16 : Dtype.float32;
|
|
76
|
+
return jointKernel().apply([...inputs, centroids], {
|
|
77
|
+
templateDtypes: { OutK: Dtype.bfloat16, OutV: valueDtype },
|
|
78
|
+
templateInts: { D: headDim, KBITS: kBits, VBITS: vBits, KG: keyGroup, VG: valueGroup },
|
|
79
|
+
grid: [headDim, n, b * h], threadGroup: [Math.min(headDim, 256), Math.max(1, 256 / headDim), 1],
|
|
80
|
+
outputs: [{ shape, dtype: Dtype.bfloat16 }, { shape, dtype: valueDtype }], stream,
|
|
81
|
+
}) as [MlxArray, MlxArray];
|
|
82
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Joint packed K/V decode with eager inverse rotation in the measured D256 regime.
|
|
2
|
+
// Hadamard butterfly/layout follows MLX 0.31.2,
|
|
3
|
+
// mlx/backend/metal/kernels/hadamard.h, Copyright © 2024 Apple Inc., MIT.
|
|
4
|
+
import { type MlxArray, gpuStream } from "./array";
|
|
5
|
+
import { Dtype, type MlxHandle } from "./ffi";
|
|
6
|
+
import { isShapelessTracing } from "./compile";
|
|
7
|
+
import { MetalKernel } from "./metal-kernel";
|
|
8
|
+
import { type PackedKvArrays } from "./turboquant-kv-decode";
|
|
9
|
+
|
|
10
|
+
const header = `
|
|
11
|
+
template <short R> inline void tq_inverse_radix(thread float* x) {
|
|
12
|
+
constexpr short logR = __builtin_ctz(R);
|
|
13
|
+
short h = 1;
|
|
14
|
+
#pragma unroll
|
|
15
|
+
for (short s = 0; s < logR; s++) {
|
|
16
|
+
#pragma unroll
|
|
17
|
+
for (short i = 0; i < R / 2; i++) {
|
|
18
|
+
short k = i & (h - 1), j = ((i - k) << 1) + k;
|
|
19
|
+
float a = x[j], b = x[j + h];
|
|
20
|
+
x[j] = a + b; x[j + h] = a - b;
|
|
21
|
+
}
|
|
22
|
+
h <<= 1;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
const source = `
|
|
27
|
+
constexpr short D = 256, R = 16, NT = 16, LOG_R = 4;
|
|
28
|
+
uint pos = thread_position_in_grid.y, bh = thread_position_in_grid.z;
|
|
29
|
+
short i = thread_position_in_grid.x;
|
|
30
|
+
bool valid = pos < vp_shape[2];
|
|
31
|
+
uint b = bh / vp_shape[1], hidx = bh % vp_shape[1];
|
|
32
|
+
uint ko = b * kp_strides[0] + hidx * kp_strides[1] + pos * kp_strides[2];
|
|
33
|
+
uint vo = b * vp_strides[0] + hidx * vp_strides[1] + pos * vp_strides[2];
|
|
34
|
+
uint kso = b * ks_strides[0] + hidx * ks_strides[1] + pos * ks_strides[2];
|
|
35
|
+
uint kzo = b * kz_strides[0] + hidx * kz_strides[1] + pos * kz_strides[2];
|
|
36
|
+
uint vso = b * vs_strides[0] + hidx * vs_strides[1] + pos * vs_strides[2];
|
|
37
|
+
uint row = (bh * vp_shape[2] + pos) * D;
|
|
38
|
+
threadgroup float storage[D * 16];
|
|
39
|
+
threadgroup float* buf = storage + thread_position_in_threadgroup.y * D;
|
|
40
|
+
#pragma unroll
|
|
41
|
+
for (short j = 0; j < R / 4; j++) {
|
|
42
|
+
short index = j * 4 * NT + i * 4;
|
|
43
|
+
#pragma unroll
|
|
44
|
+
for (short r = 0; r < 4; r++) {
|
|
45
|
+
uint col = index + r;
|
|
46
|
+
if (!valid) { buf[col] = 0.0f; continue; }
|
|
47
|
+
int key_code = int(kp[ko + col * kp_strides[3]]);
|
|
48
|
+
float shifted = float(key_code) + float(kz[kzo + (col / 32) * kz_strides[3]]);
|
|
49
|
+
outK[row + col] = OutK(shifted * float(ks[kso + (col / 32) * ks_strides[3]]));
|
|
50
|
+
uint bit = col * 3u, byte = bit / 8u, shift = bit % 8u;
|
|
51
|
+
uint word = uint(vp[vo + byte * vp_strides[3]]);
|
|
52
|
+
if (shift + 3u > 8u) word |= uint(vp[vo + (byte + 1u) * vp_strides[3]]) << 8u;
|
|
53
|
+
uint code = (word >> shift) & 7u;
|
|
54
|
+
buf[col] = centroids[code * centroids_strides[0]] * float(vs[vso + (col / 32) * vs_strides[3]]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
58
|
+
float x[R]; short h = 1;
|
|
59
|
+
#pragma unroll
|
|
60
|
+
for (short s = 0; s < 2; s++) {
|
|
61
|
+
short k = i & (h - 1), j = ((i - k) << LOG_R) + k;
|
|
62
|
+
#pragma unroll
|
|
63
|
+
for (short r = 0; r < R; r++) x[r] = buf[j + h * r];
|
|
64
|
+
tq_inverse_radix<R>(x);
|
|
65
|
+
#pragma unroll
|
|
66
|
+
for (short r = 0; r < R; r++) buf[j + h * r] = x[r];
|
|
67
|
+
h <<= LOG_R;
|
|
68
|
+
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
69
|
+
}
|
|
70
|
+
#pragma unroll
|
|
71
|
+
for (short j = 0; j < R / 4; j++) {
|
|
72
|
+
short index = j * 4 * NT + i * 4;
|
|
73
|
+
#pragma unroll
|
|
74
|
+
for (short r = 0; r < 4; r++) {
|
|
75
|
+
uint col = index + r;
|
|
76
|
+
float normalized = buf[col] * 0.0625f;
|
|
77
|
+
if (valid) outV[row + col] = OutV(normalized * signs[col * signs_strides[0]]);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
`;
|
|
81
|
+
|
|
82
|
+
let kernel: MetalKernel | undefined;
|
|
83
|
+
function inverseKernel(): MetalKernel {
|
|
84
|
+
return kernel ??= new MetalKernel({
|
|
85
|
+
name: "turboquant_kv_inverse256",
|
|
86
|
+
inputNames: ["kp", "ks", "kz", "vp", "vs", "centroids", "signs"],
|
|
87
|
+
outputNames: ["outK", "outV"], header, source, ensureRowContiguous: false,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Borrow packed inputs and codec tables; return owned bf16 K/V or null.
|
|
92
|
+
* Only the measured eager D256/k8v3/B1/H4/fp16/group32 regime is eligible.
|
|
93
|
+
* Other regimes continue through the existing joint decoder or original codec.
|
|
94
|
+
* N affects dispatch size but is never a shader template parameter. */
|
|
95
|
+
export function tryDecodePackedKvInverse256(
|
|
96
|
+
inputs: PackedKvArrays, centroids: MlxArray, signs: MlxArray,
|
|
97
|
+
kBits: number, vBits: number, headDim: number, s: MlxHandle = gpuStream,
|
|
98
|
+
): [MlxArray, MlxArray] | null {
|
|
99
|
+
if (s !== gpuStream || isShapelessTracing() || headDim !== 256 || kBits !== 8 || vBits !== 3)
|
|
100
|
+
return null;
|
|
101
|
+
const [kp, ks, kz, vp, vs] = inputs;
|
|
102
|
+
const shape = kp.shape;
|
|
103
|
+
if (shape.length !== 4 || shape[0] !== 1 || shape[1] !== 4 || shape[3] !== 256)
|
|
104
|
+
return null;
|
|
105
|
+
const n = shape[2]!;
|
|
106
|
+
if (n < 8192 || n * 1024 >= 2 ** 32) return null;
|
|
107
|
+
const samePrefix = (a: MlxArray, width: number) => {
|
|
108
|
+
const x = a.shape;
|
|
109
|
+
return x.length === 4 && x[0] === 1 && x[1] === 4 && x[2] === n && x[3] === width;
|
|
110
|
+
};
|
|
111
|
+
if (kp.dtype !== Dtype.int8 || vp.dtype !== Dtype.uint8 || !samePrefix(vp, 96)) return null;
|
|
112
|
+
if (![ks, kz, vs].every(a => a.dtype === Dtype.float16 && samePrefix(a, 8))) return null;
|
|
113
|
+
if (centroids.dtype !== Dtype.float32 || centroids.shape.length !== 1 || centroids.shape[0] !== 8)
|
|
114
|
+
return null;
|
|
115
|
+
if (signs.dtype !== Dtype.float32 || signs.shape.length !== 1 || signs.shape[0] !== 256)
|
|
116
|
+
return null;
|
|
117
|
+
return inverseKernel().apply([...inputs, centroids, signs], {
|
|
118
|
+
templateDtypes: { OutK: Dtype.bfloat16, OutV: Dtype.bfloat16 },
|
|
119
|
+
grid: [16, Math.ceil(n / 16) * 16, 4], threadGroup: [16, 16, 1],
|
|
120
|
+
outputs: [{ shape, dtype: Dtype.bfloat16 }, { shape, dtype: Dtype.bfloat16 }], stream: s,
|
|
121
|
+
}) as [MlxArray, MlxArray];
|
|
122
|
+
}
|