@mlx-bun/inference 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +1475 -2
- package/THIRD_PARTY_NOTICES.md +3404 -0
- package/dist/native/libmlx_bun_ane.dylib +0 -0
- package/dist/native/libmlx_bun_expert_io.dylib +0 -0
- package/dist/native/mlx-bun-frame-extract +0 -0
- package/examples/fit-model.ts +25 -0
- package/examples/qwen3-forward.ts +42 -0
- package/examples/qwen3-generate.ts +28 -0
- package/examples/trellis-expand.ts +23 -0
- package/package.json +129 -4
- package/src/adapters/available.ts +110 -0
- package/src/adapters/index.ts +3 -0
- package/src/adapters/manager.ts +316 -0
- package/src/artifacts/auxiliary-files.ts +45 -0
- package/src/artifacts/config-dialects.ts +94 -0
- package/src/artifacts/config.ts +341 -0
- package/src/artifacts/draft-vocabulary.ts +30 -0
- package/src/artifacts/expert-offload-build.ts +75 -0
- package/src/artifacts/expert-offload.ts +89 -0
- package/src/artifacts/experts/index.ts +3 -0
- package/src/artifacts/experts/io.ts +386 -0
- package/src/artifacts/experts/residency.ts +1134 -0
- package/src/artifacts/experts/usage.ts +415 -0
- package/src/artifacts/fingerprint.ts +44 -0
- package/src/artifacts/glm52/container.ts +305 -0
- package/src/artifacts/glm52/expert-layout.ts +249 -0
- package/src/artifacts/glm52/index.ts +7 -0
- package/src/artifacts/glm52/kernel-layout.ts +124 -0
- package/src/artifacts/glm52/layout.ts +152 -0
- package/src/artifacts/glm52/memory.ts +500 -0
- package/src/artifacts/glm52/resident-weights.ts +135 -0
- package/src/artifacts/glm52/weights.ts +503 -0
- package/src/artifacts/glm52-config.ts +262 -0
- package/src/artifacts/identity.ts +94 -0
- package/src/artifacts/index.ts +15 -0
- package/src/artifacts/mmap.ts +90 -0
- package/src/artifacts/safetensors-writer.ts +249 -0
- package/src/artifacts/safetensors.ts +164 -0
- package/src/artifacts/turboquant-fold.ts +42 -0
- package/src/artifacts/weight-audit.ts +43 -0
- package/src/artifacts/weights.ts +255 -0
- package/src/contracts/index.ts +2 -0
- package/src/contracts/mlx/attention.ts +8 -0
- package/src/contracts/mlx/cache.ts +510 -0
- package/src/contracts/mlx/checkpoint.ts +10 -0
- package/src/contracts/mlx/draft-checkpoint.ts +14 -0
- package/src/contracts/mlx/draft-projection.ts +27 -0
- package/src/contracts/mlx/draft-target.ts +84 -0
- package/src/contracts/mlx/drafter.ts +176 -0
- package/src/contracts/mlx/forward-work.ts +23 -0
- package/src/contracts/mlx/glm52-weights.ts +25 -0
- package/src/contracts/mlx/graph.ts +309 -0
- package/src/contracts/mlx/head.ts +6 -0
- package/src/contracts/mlx/index.ts +15 -0
- package/src/contracts/mlx/kv-codec.ts +9 -0
- package/src/contracts/mlx/media.ts +113 -0
- package/src/contracts/mlx/positions.ts +16 -0
- package/src/contracts/mlx/row-table.ts +2 -0
- package/src/contracts/mlx/trainable.ts +103 -0
- package/src/contracts/mlx/whisper.ts +96 -0
- package/src/contracts/portable/composition.ts +106 -0
- package/src/contracts/portable/denoising.ts +17 -0
- package/src/contracts/portable/execution.ts +79 -0
- package/src/contracts/portable/generation.ts +103 -0
- package/src/contracts/portable/graph.ts +80 -0
- package/src/contracts/portable/index.ts +15 -0
- package/src/contracts/portable/memory-plan.ts +26 -0
- package/src/contracts/portable/object-cache.ts +8 -0
- package/src/contracts/portable/prefill.ts +63 -0
- package/src/contracts/portable/prefix-cache.ts +33 -0
- package/src/contracts/portable/prompt.ts +2 -0
- package/src/contracts/portable/resources.ts +25 -0
- package/src/contracts/portable/rollback.ts +7 -0
- package/src/contracts/portable/sampling.ts +44 -0
- package/src/contracts/portable/scheduling.ts +54 -0
- package/src/contracts/portable/token-delivery.ts +4 -0
- package/src/contracts/portable/tsconfig.json +14 -0
- package/src/embeddings/index.ts +1 -0
- package/src/embeddings/text.ts +85 -0
- package/src/execution/admission.ts +59 -0
- package/src/execution/batch-group.ts +1240 -0
- package/src/execution/batch-types.ts +225 -0
- package/src/execution/cancellation.ts +38 -0
- package/src/execution/continuation-request.ts +45 -0
- package/src/execution/continuation-types.ts +10 -0
- package/src/execution/coordinator.ts +70 -0
- package/src/execution/denoising-group.ts +180 -0
- package/src/execution/engine.ts +56 -0
- package/src/execution/fill-group.ts +505 -0
- package/src/execution/fit.ts +184 -0
- package/src/execution/gateway-binding.ts +178 -0
- package/src/execution/grammar-group.ts +224 -0
- package/src/execution/index.ts +18 -0
- package/src/execution/methods.ts +75 -0
- package/src/execution/mixed-iteration.ts +96 -0
- package/src/execution/plan.ts +65 -0
- package/src/execution/prefill-cohort.ts +100 -0
- package/src/execution/prefill-rows.ts +263 -0
- package/src/execution/request-policy.ts +19 -0
- package/src/execution/row-sampling.ts +49 -0
- package/src/execution/scheduler.ts +92 -0
- package/src/execution/session.ts +247 -0
- package/src/execution/speculative-group.ts +797 -0
- package/src/execution/tasks.ts +44 -0
- package/src/generation/autoregressive.ts +840 -0
- package/src/generation/bindings/autoregressive.ts +104 -0
- package/src/generation/bindings/denoising.ts +60 -0
- package/src/generation/bindings/prefill-policy.ts +36 -0
- package/src/generation/checkpoint-identity.ts +60 -0
- package/src/generation/denoising.ts +85 -0
- package/src/generation/diffusion.ts +583 -0
- package/src/generation/fill/echo-index.ts +188 -0
- package/src/generation/fill/index.ts +3 -0
- package/src/generation/fill/proposal.ts +69 -0
- package/src/generation/fill/schema-rows.ts +506 -0
- package/src/generation/fill/session.ts +464 -0
- package/src/generation/fill/tool-boundary.ts +103 -0
- package/src/generation/fill-append.ts +58 -0
- package/src/generation/generate.ts +44 -0
- package/src/generation/grammar-step.ts +39 -0
- package/src/generation/index.ts +14 -0
- package/src/generation/phase-dispatch.ts +115 -0
- package/src/generation/prefill.ts +33 -0
- package/src/generation/result.ts +49 -0
- package/src/generation/scopes.ts +157 -0
- package/src/generation/speculative/acceptance.ts +41 -0
- package/src/generation/speculative/adaptive-draft-gate.ts +166 -0
- package/src/generation/speculative/bindings/assistant-rows.ts +102 -0
- package/src/generation/speculative/bindings/autoregressive-rows.ts +124 -0
- package/src/generation/speculative/bindings/binding.ts +106 -0
- package/src/generation/speculative/bindings/deepspec-rows.ts +33 -0
- package/src/generation/speculative/bindings/draft-target.ts +10 -0
- package/src/generation/speculative/bindings/glm52-mtp-rows.ts +128 -0
- package/src/generation/speculative/bindings/projected-context-rows.ts +78 -0
- package/src/generation/speculative/bindings/projected-draft-rows.ts +95 -0
- package/src/generation/speculative/bindings/qwen-mtp-rows.ts +336 -0
- package/src/generation/speculative/bindings/two-model-rows.ts +63 -0
- package/src/generation/speculative/cache-identity.ts +16 -0
- package/src/generation/speculative/draft-kind.ts +149 -0
- package/src/generation/speculative/draft-policy.ts +41 -0
- package/src/generation/speculative/draft-registry.ts +118 -0
- package/src/generation/speculative/draft-vocab.ts +110 -0
- package/src/generation/speculative/index.ts +17 -0
- package/src/generation/speculative/output.ts +49 -0
- package/src/generation/speculative/round.ts +307 -0
- package/src/generation/speculative/source.ts +144 -0
- package/src/generation/speculative/sources/assistant-source.ts +64 -0
- package/src/generation/speculative/sources/deepspec-source.ts +70 -0
- package/src/generation/speculative/sources/dflash-source.ts +84 -0
- package/src/generation/speculative/sources/dflash2-source.ts +86 -0
- package/src/generation/speculative/sources/glm52-mtp-source.ts +66 -0
- package/src/generation/speculative/sources/ngram-source.ts +165 -0
- package/src/generation/speculative/sources/qwen-mtp-source.ts +154 -0
- package/src/generation/speculative/sources/two-model.ts +75 -0
- package/src/generation/types.ts +194 -0
- package/src/index.ts +20 -0
- package/src/input/audio/audiotoolbox.ts +112 -0
- package/src/input/audio/decode.ts +133 -0
- package/src/input/audio/features.ts +202 -0
- package/src/input/audio/index.ts +6 -0
- package/src/input/audio/transcode.ts +67 -0
- package/src/input/audio/whisper-mel.ts +185 -0
- package/src/input/audio/whisper-tokenizer.ts +238 -0
- package/src/input/chat-template.ts +389 -0
- package/src/input/index.ts +6 -0
- package/src/input/media-fetch.ts +439 -0
- package/src/input/packed-mixed.ts +43 -0
- package/src/input/special-tokens.ts +75 -0
- package/src/input/token-groups.ts +49 -0
- package/src/input/tokenizer.ts +336 -0
- package/src/input/tool-call.ts +689 -0
- package/src/input/vision/index.ts +3 -0
- package/src/input/vision/preprocess.ts +247 -0
- package/src/input/vision/prompt.ts +398 -0
- package/src/input/vision/qwen3vl-preprocess.ts +409 -0
- package/src/input/vision/qwen3vl-prompt.ts +237 -0
- package/src/input/vision/video-frames.ts +100 -0
- package/src/kernels/ane/fill.ts +101 -0
- package/src/kernels/ane/linear.ts +416 -0
- package/src/kernels/attention/flash.ts +694 -0
- package/src/kernels/attention/kvq4-decode.ts +155 -0
- package/src/kernels/attention/masks.ts +40 -0
- package/src/kernels/attention/multi-query.ts +193 -0
- package/src/kernels/attention/paged.ts +130 -0
- package/src/kernels/delta/gated.ts +306 -0
- package/src/kernels/delta/index.ts +3 -0
- package/src/kernels/delta/silu-rms-heads.ts +69 -0
- package/src/kernels/delta/state.ts +74 -0
- package/src/kernels/gated-rms-norm.ts +64 -0
- package/src/kernels/glm52/layout.ts +37 -0
- package/src/kernels/glm52/streamed-experts.ts +360 -0
- package/src/kernels/logits.ts +38 -0
- package/src/kernels/quantization/affine-row.ts +154 -0
- package/src/kernels/quantization/affine-rows.ts +36 -0
- package/src/kernels/quantization/affine-split-mma.ts +144 -0
- package/src/kernels/quantization/affine3-mma.ts +117 -0
- package/src/kernels/quantization/affine3-rows.ts +103 -0
- package/src/kernels/quantization/affine4-mma.ts +113 -0
- package/src/kernels/quantization/affine4-rows.ts +102 -0
- package/src/kernels/residual-rms-norm.ts +65 -0
- package/src/kernels/sampling/index.ts +2 -0
- package/src/kernels/sampling/normalized-argmax.ts +116 -0
- package/src/kernels/sampling/token-bitmask.ts +52 -0
- package/src/kernels/softcap.ts +35 -0
- package/src/kernels/trellis/codebook.ts +123 -0
- package/src/kernels/trellis/down-codebook.ts +184 -0
- package/src/kernels/trellis/down-factored.ts +180 -0
- package/src/kernels/trellis/down-k3-interleaved-factored.ts +146 -0
- package/src/kernels/trellis/down-mma-wide.ts +195 -0
- package/src/kernels/trellis/down-mma.ts +145 -0
- package/src/kernels/trellis/expand.ts +42 -0
- package/src/kernels/trellis/gate-up-codebook-mma.ts +141 -0
- package/src/kernels/trellis/gate-up-codebook.ts +137 -0
- package/src/kernels/trellis/gate-up-factored.ts +197 -0
- package/src/kernels/trellis/gate-up-mma-wide.ts +147 -0
- package/src/kernels/trellis/gate-up-mma.ts +148 -0
- package/src/kernels/trellis/gate-up.ts +248 -0
- package/src/kernels/trellis/geometry.ts +22 -0
- package/src/kernels/trellis/index.ts +103 -0
- package/src/kernels/trellis/launch.ts +12 -0
- package/src/kernels/trellis/mixed-gate-up-factored.ts +119 -0
- package/src/kernels/trellis/mixed-gate-up.ts +146 -0
- package/src/kernels/trellis/qualified.ts +34 -0
- package/src/kernels/trellis/reduce.ts +150 -0
- package/src/kernels/trellis/scatter.ts +407 -0
- package/src/kernels/trellis/splitk-prefill.ts +152 -0
- package/src/kernels/trellis/tiled-prefill.ts +131 -0
- package/src/kernels/trellis/vector-expand.ts +58 -0
- package/src/kernels/trellis/wide-prefill.ts +89 -0
- package/src/kernels/turboquant/decode.ts +82 -0
- package/src/kernels/turboquant/index.ts +5 -0
- package/src/kernels/turboquant/inverse.ts +122 -0
- package/src/kernels/turboquant/ops.ts +860 -0
- package/src/kernels/turboquant/signs.ts +130 -0
- package/src/kernels/turboquant/tables.ts +124 -0
- package/src/layers/affine-verify-linear.ts +75 -0
- package/src/layers/ane-prefill-split.ts +283 -0
- package/src/layers/compiled-normalization.ts +41 -0
- package/src/layers/delta-heads.ts +23 -0
- package/src/layers/dense-embedding.ts +29 -0
- package/src/layers/dense-linear.ts +32 -0
- package/src/layers/dequantized-linear.ts +72 -0
- package/src/layers/folded-attention.ts +42 -0
- package/src/layers/geglu.ts +130 -0
- package/src/layers/helpers.ts +7 -0
- package/src/layers/index.ts +36 -0
- package/src/layers/kv4-decode-attention.ts +15 -0
- package/src/layers/layer-norm.ts +24 -0
- package/src/layers/loading.ts +92 -0
- package/src/layers/lora.ts +150 -0
- package/src/layers/normalization.ts +9 -0
- package/src/layers/prefix-layout.ts +33 -0
- package/src/layers/quantized-append-attention.ts +32 -0
- package/src/layers/quantized-attention.ts +353 -0
- package/src/layers/quantized-embedding.ts +39 -0
- package/src/layers/quantized-linear.ts +137 -0
- package/src/layers/quantized-switch-linear.ts +37 -0
- package/src/layers/qwen-append.ts +12 -0
- package/src/layers/qwen-mrope.ts +238 -0
- package/src/layers/rope-2d.ts +110 -0
- package/src/layers/rope.ts +389 -0
- package/src/layers/swiglu.ts +23 -0
- package/src/layers/trellis-codebook-down.ts +52 -0
- package/src/layers/trellis-codebook-gate-up.ts +42 -0
- package/src/layers/trellis-down.ts +117 -0
- package/src/layers/trellis-gate-up.ts +71 -0
- package/src/layers/trellis-linear.ts +211 -0
- package/src/layers/trellis-prompt.ts +102 -0
- package/src/layers/trellis-reduce-gate-up.ts +73 -0
- package/src/layers/trellis-scatter-down.ts +98 -0
- package/src/models/ane-install.ts +74 -0
- package/src/models/audio/conformer.ts +656 -0
- package/src/models/cache-composition.ts +44 -0
- package/src/models/capabilities.ts +89 -0
- package/src/models/chat-template.ts +10 -0
- package/src/models/composition.ts +107 -0
- package/src/models/diffusion-gemma/experts.ts +59 -0
- package/src/models/diffusion-gemma/family.ts +21 -0
- package/src/models/diffusion-gemma/model.ts +900 -0
- package/src/models/diffusion-gemma/vision.ts +549 -0
- package/src/models/drafter-loaders.ts +49 -0
- package/src/models/drafters.ts +39 -0
- package/src/models/factory.ts +351 -0
- package/src/models/families.ts +70 -0
- package/src/models/family.ts +125 -0
- package/src/models/gemma4/assistant-target.ts +46 -0
- package/src/models/gemma4/assistant.ts +327 -0
- package/src/models/gemma4/blocks.ts +755 -0
- package/src/models/gemma4/compiled-decode.ts +452 -0
- package/src/models/gemma4/family.ts +39 -0
- package/src/models/gemma4/gemma4-12b.ts +677 -0
- package/src/models/gemma4/gemma4-26b.ts +544 -0
- package/src/models/gemma4/gemma4-e4b.ts +599 -0
- package/src/models/gemma4/graphs.ts +147 -0
- package/src/models/gemma4/media.ts +88 -0
- package/src/models/gemma4/model.ts +860 -0
- package/src/models/gemma4/serving-blocks.ts +72 -0
- package/src/models/gemma4/trainable.ts +186 -0
- package/src/models/gemma4/written-blocks.ts +232 -0
- package/src/models/gemma4/written-forward.ts +159 -0
- package/src/models/gemma4/written-input.ts +29 -0
- package/src/models/glm52/atlas.ts +507 -0
- package/src/models/glm52/coupling.ts +388 -0
- package/src/models/glm52/dsa.ts +601 -0
- package/src/models/glm52/family.ts +20 -0
- package/src/models/glm52/mla.ts +812 -0
- package/src/models/glm52/model.ts +1127 -0
- package/src/models/glm52/moe.ts +223 -0
- package/src/models/glm52/mtp.ts +60 -0
- package/src/models/glm52/pilot.ts +537 -0
- package/src/models/glm52/reference.ts +445 -0
- package/src/models/glm52/residency.ts +557 -0
- package/src/models/glm52/streamed-experts.ts +447 -0
- package/src/models/graph.ts +59 -0
- package/src/models/implementation.ts +230 -0
- package/src/models/index.ts +15 -0
- package/src/models/media-input.ts +15 -0
- package/src/models/memory-plan.ts +39 -0
- package/src/models/minicpm5/family.ts +29 -0
- package/src/models/minicpm5/model.ts +394 -0
- package/src/models/minicpm5/trainable.ts +90 -0
- package/src/models/profile.ts +337 -0
- package/src/models/qwen/blocks.ts +658 -0
- package/src/models/qwen/checkpoint.ts +190 -0
- package/src/models/qwen/families.ts +57 -0
- package/src/models/qwen/media-input.ts +161 -0
- package/src/models/qwen/mtp.ts +101 -0
- package/src/models/qwen/qwen3-moe.ts +355 -0
- package/src/models/qwen/qwen3.ts +323 -0
- package/src/models/qwen/qwen38-27b-trellis-m1max.ts +1532 -0
- package/src/models/qwen/qwen38-27b-trellis-m4pro.ts +1092 -0
- package/src/models/qwen/qwen3_5.ts +433 -0
- package/src/models/qwen/written-layers.ts +285 -0
- package/src/models/row-table.ts +1 -0
- package/src/models/runtime.ts +73 -0
- package/src/models/speculative/deepspec-artifact.ts +10 -0
- package/src/models/speculative/deepspec.ts +744 -0
- package/src/models/speculative/dflash.ts +553 -0
- package/src/models/speculative/dflash2-artifact.ts +9 -0
- package/src/models/speculative/dflash2-operations.ts +1 -0
- package/src/models/speculative/dflash2.ts +576 -0
- package/src/models/speculative/loader.ts +29 -0
- package/src/models/support.ts +79 -0
- package/src/models/universal/archs.ts +365 -0
- package/src/models/universal/blocks.ts +479 -0
- package/src/models/universal/dense.ts +255 -0
- package/src/models/universal/family.ts +17 -0
- package/src/models/vision/qwen3vl.ts +566 -0
- package/src/models/vision/siglip.ts +520 -0
- package/src/models/vision/unified.ts +176 -0
- package/src/models/whisper/family.ts +19 -0
- package/src/models/whisper/fast.ts +734 -0
- package/src/models/whisper/model.ts +430 -0
- package/src/runtime/compiled-trace.ts +16 -0
- package/src/runtime/config.ts +77 -0
- package/src/runtime/expert-trace.ts +71 -0
- package/src/runtime/graph-forward.ts +65 -0
- package/src/runtime/integers.ts +6 -0
- package/src/runtime/native.ts +16 -0
- package/src/runtime/resources.ts +53 -0
- package/src/runtime/row-operations.ts +42 -0
- package/src/runtime/trace.ts +161 -0
- package/src/sampling/curve.ts +122 -0
- package/src/sampling/draft.ts +55 -0
- package/src/sampling/extras.ts +45 -0
- package/src/sampling/filters.ts +134 -0
- package/src/sampling/grammar.ts +630 -0
- package/src/sampling/hlg.ts +290 -0
- package/src/sampling/index.ts +9 -0
- package/src/sampling/processors.ts +142 -0
- package/src/sampling/sampler.ts +120 -0
- package/src/sampling/step.ts +343 -0
- package/src/sampling/types.ts +161 -0
- package/src/sampling/window-filters.ts +46 -0
- package/src/scoring/decisions.ts +96 -0
- package/src/scoring/full-sequence.ts +103 -0
- package/src/scoring/index.ts +9 -0
- package/src/scoring/kl.ts +29 -0
- package/src/scoring/perplexity.ts +165 -0
- package/src/scoring/response-logps.ts +65 -0
- package/src/state/affine-attention.ts +180 -0
- package/src/state/attachment.ts +68 -0
- package/src/state/attention-read.ts +215 -0
- package/src/state/batched-kv.ts +294 -0
- package/src/state/batched-mask.ts +445 -0
- package/src/state/batched-quant.ts +389 -0
- package/src/state/batched-quantized-kv.ts +209 -0
- package/src/state/batched-rotating-quant.ts +457 -0
- package/src/state/batched-rotating-state.ts +149 -0
- package/src/state/batched-rotating.ts +430 -0
- package/src/state/batched-row-storage.ts +179 -0
- package/src/state/batched-ssm.ts +54 -0
- package/src/state/batched-turboquant-kv.ts +144 -0
- package/src/state/bf16-first-kv.ts +663 -0
- package/src/state/cache-service.ts +592 -0
- package/src/state/capabilities.ts +65 -0
- package/src/state/checkpoint.ts +46 -0
- package/src/state/continuation-persistence.ts +126 -0
- package/src/state/decode-step-inputs.ts +31 -0
- package/src/state/decoded-kv-donor.ts +12 -0
- package/src/state/delayed-quantized-kv.ts +141 -0
- package/src/state/delayed-rotating-quantized-kv.ts +178 -0
- package/src/state/delayed-turboquant-kv.ts +123 -0
- package/src/state/durability.ts +214 -0
- package/src/state/encoder-cache.ts +26 -0
- package/src/state/full-kv-row-append.ts +33 -0
- package/src/state/full-kv-row-donor.ts +54 -0
- package/src/state/full-prefill-padding.ts +56 -0
- package/src/state/full-prefill-row.ts +112 -0
- package/src/state/full-transitioning-kv-rows.ts +70 -0
- package/src/state/glm52-cache.ts +783 -0
- package/src/state/grouped-heads-kv4-cache.ts +26 -0
- package/src/state/index.ts +38 -0
- package/src/state/kv-attention-view.ts +77 -0
- package/src/state/kv-budget.ts +14 -0
- package/src/state/kv-scheme.ts +315 -0
- package/src/state/kv-storage.ts +57 -0
- package/src/state/kv-tensor-rows.ts +212 -0
- package/src/state/kv.ts +324 -0
- package/src/state/kv4-head256-cache.ts +46 -0
- package/src/state/latent-legos.ts +895 -0
- package/src/state/layout.ts +277 -0
- package/src/state/leases.ts +26 -0
- package/src/state/paged/cache.ts +689 -0
- package/src/state/paged/codec.ts +36 -0
- package/src/state/paged/rows.ts +223 -0
- package/src/state/persistence-types.ts +173 -0
- package/src/state/persistence-worker.ts +83 -0
- package/src/state/persistence.ts +1036 -0
- package/src/state/persistence.worker.d.ts +3 -0
- package/src/state/persistence.worker.js +264 -0
- package/src/state/prefix-cache.ts +685 -0
- package/src/state/quantized-kv.ts +369 -0
- package/src/state/quantized-tensor.ts +26 -0
- package/src/state/request-policy.ts +48 -0
- package/src/state/retention-policy.ts +37 -0
- package/src/state/rollback.ts +131 -0
- package/src/state/rotating-kv-donor.ts +35 -0
- package/src/state/rotating-kv-layout.ts +341 -0
- package/src/state/rotating-kv.ts +529 -0
- package/src/state/rotating-quantized-kv.ts +616 -0
- package/src/state/rotating-row-transaction.ts +35 -0
- package/src/state/rows.ts +104 -0
- package/src/state/softcap-attention.ts +118 -0
- package/src/state/speculative/glm52-mtp-state.ts +38 -0
- package/src/state/speculative/qwen-mtp-state.ts +34 -0
- package/src/state/speculative-rotating-kv.ts +113 -0
- package/src/state/ssd-cache.ts +434 -0
- package/src/state/ssm-prefill-padding.ts +39 -0
- package/src/state/ssm.ts +422 -0
- package/src/state/target-layout.ts +7 -0
- package/src/state/tiered-prefix-cache.ts +45 -0
- package/src/state/training-cache.ts +300 -0
- package/src/state/transitioning-kv-rows.ts +267 -0
- package/src/state/turboquant-attention.ts +97 -0
- package/src/state/turboquant-codec.ts +90 -0
- package/src/state/turboquant-kv.ts +442 -0
- package/src/state/views.ts +26 -0
- package/src/transcription/index.ts +3 -0
- package/src/transcription/silero-vad.ts +379 -0
- package/src/transcription/whisper/decode.ts +773 -0
- package/src/transcription/whisper/format.ts +74 -0
- package/src/transcription/whisper/index.ts +4 -0
- package/src/transcription/whisper/timing.ts +274 -0
- package/src/transcription/whisper/transcribe.ts +394 -0
- package/src/transcription/whisper/types.ts +22 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
3
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
4
|
+
|
|
5
|
+
/** Per-adapter LoRA weights for one linear (mlx-lm LoRALinear shapes:
|
|
6
|
+
* a [in_features, rank], b [rank, out_features], typically f32). */
|
|
7
|
+
export interface LoraWeights {
|
|
8
|
+
a: MlxArray;
|
|
9
|
+
b: MlxArray;
|
|
10
|
+
scale: number;
|
|
11
|
+
rank: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Active-adapter state, shared by every mounted linear of one model.
|
|
15
|
+
* A plain field, NOT a ContextVar port: under continuous batching requests
|
|
16
|
+
* interleave, so the field is only valid for the forward being built. Each
|
|
17
|
+
* execution group binds its rows' adapter list (`bindAdapterContext`) and
|
|
18
|
+
* sets/restores it around every forward; `adapterScoped` does the same for a
|
|
19
|
+
* single generation. */
|
|
20
|
+
export class LoraState {
|
|
21
|
+
#active: string[] = [];
|
|
22
|
+
readonly #bindings = new Set<(active: readonly string[]) => void>();
|
|
23
|
+
get active(): string[] { return this.#active; }
|
|
24
|
+
set active(ids: string[]) {
|
|
25
|
+
const neutral = this.#active.length === 0 && ids.length === 0;
|
|
26
|
+
this.#active = ids;
|
|
27
|
+
if (!neutral) this.refreshBindings();
|
|
28
|
+
}
|
|
29
|
+
/** Bind request-scoped compound operations before a forward is built. */
|
|
30
|
+
bindOperation(binding: (active: readonly string[]) => void): () => void {
|
|
31
|
+
this.#bindings.add(binding);
|
|
32
|
+
binding(this.#active);
|
|
33
|
+
return () => { this.#bindings.delete(binding); };
|
|
34
|
+
}
|
|
35
|
+
refreshBindings(): void { for (const bind of this.#bindings) bind(this.#active); }
|
|
36
|
+
/** Training-only LoRA-input dropout. `rate` is the drop probability; `seed` is
|
|
37
|
+
* set per micro-step by the trainer and is CONSTANT across that step's forward
|
|
38
|
+
* and any recompute (segmented / gradient-checkpoint), so each layer's mask —
|
|
39
|
+
* keyed by (seed, the linear's dropoutId) — is reproduced exactly in the
|
|
40
|
+
* backward. `seed === null` (the default, and for inference) disables it. */
|
|
41
|
+
dropoutRate = 0;
|
|
42
|
+
dropoutSeed: number | null = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Inverted LoRA-input dropout, keyed by (seed, id) so it is deterministic —
|
|
46
|
+
* the same (seed, id, shape) reproduces the mask, which is what makes the
|
|
47
|
+
* segmented / gradient-checkpoint recompute correct. kept ⇒ x/(1-p),
|
|
48
|
+
* dropped ⇒ 0 (preserves the expectation). Caller owns the result. */
|
|
49
|
+
export function loraInputDropout(x: MlxArray, p: number, seed: number, id: number): MlxArray {
|
|
50
|
+
const key = ops.randomKey(BigInt(seed) * 100003n + BigInt(id));
|
|
51
|
+
const u = ops.randomUniform(x.shape, Dtype.float32, 0, 1, key); // [shape] in [0,1)
|
|
52
|
+
key.dispose();
|
|
53
|
+
const pArr = ops.scalarLike(p, u);
|
|
54
|
+
const keep = ops.less(pArr, u); // u > p ⇒ keep (bool [shape])
|
|
55
|
+
const scaleX = ops.scalarLike(1 / (1 - p), x);
|
|
56
|
+
const zeroX = ops.scalarLike(0, x);
|
|
57
|
+
const mask = ops.where(keep, scaleX, zeroX); // x.dtype
|
|
58
|
+
const xd = ops.mul(x, mask);
|
|
59
|
+
for (const a of [u, pArr, keep, scaleX, zeroX, mask]) a.dispose();
|
|
60
|
+
return xd;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Adapter delta after any projection. Borrows x and consumes the projection
|
|
64
|
+
* output, returning its owned replacement (or the same handle with no delta).
|
|
65
|
+
* Request routing sets the shared active state before the operation runs. */
|
|
66
|
+
export class LoraDelta {
|
|
67
|
+
constructor(readonly inFeatures = 0, readonly outFeatures = 0) {}
|
|
68
|
+
adapters: Map<string, LoraWeights> | null = null;
|
|
69
|
+
#state: LoraState | null = null;
|
|
70
|
+
readonly #stateBindings = new Set<() => void>();
|
|
71
|
+
get loraState(): LoraState | null { return this.#state; }
|
|
72
|
+
set loraState(value: LoraState | null) {
|
|
73
|
+
this.#state = value;
|
|
74
|
+
for (const bind of this.#stateBindings) bind();
|
|
75
|
+
}
|
|
76
|
+
observeState(binding: () => void): () => void {
|
|
77
|
+
this.#stateBindings.add(binding); binding();
|
|
78
|
+
return () => { this.#stateBindings.delete(binding); };
|
|
79
|
+
}
|
|
80
|
+
dropoutId = 0;
|
|
81
|
+
|
|
82
|
+
forward(x: MlxArray, out: MlxArray): MlxArray {
|
|
83
|
+
// LoRA residual — composition is mlx-lm LoRALinear / optiq apply.py:
|
|
84
|
+
// y + (scale · ((x @ A) @ B)).astype(x.dtype)
|
|
85
|
+
// (optiq mount.py omits the astype, leaking the f32 residual into the
|
|
86
|
+
// bf16 stream; the
|
|
87
|
+
// cast form is what the adapters were trained behind.)
|
|
88
|
+
const st = this.loraState;
|
|
89
|
+
if (st && st.active.length > 0 && this.adapters && this.adapters.size > 0) {
|
|
90
|
+
// Training-only LoRA-input dropout (PEFT applies dropout to x before A;
|
|
91
|
+
// the base quantized path is untouched). Keyed by (seed, dropoutId) so the
|
|
92
|
+
// backward recompute reproduces the exact mask.
|
|
93
|
+
let xLora = x;
|
|
94
|
+
let xDrop: MlxArray | null = null;
|
|
95
|
+
if (st.dropoutSeed !== null && st.dropoutRate > 0) {
|
|
96
|
+
xDrop = loraInputDropout(x, st.dropoutRate, st.dropoutSeed, this.dropoutId);
|
|
97
|
+
xLora = xDrop;
|
|
98
|
+
}
|
|
99
|
+
for (const id of st.active) {
|
|
100
|
+
const lw = this.adapters.get(id);
|
|
101
|
+
if (!lw) continue;
|
|
102
|
+
const xa = ops.matmul(xLora, lw.a);
|
|
103
|
+
const z = ops.matmul(xa, lw.b);
|
|
104
|
+
xa.dispose();
|
|
105
|
+
const zs = ops.mulScalar(z, lw.scale);
|
|
106
|
+
z.dispose();
|
|
107
|
+
const zc = zs.astype(x.dtype);
|
|
108
|
+
zs.dispose();
|
|
109
|
+
const previous = out; out = ops.add(out, zc); previous.dispose();
|
|
110
|
+
zc.dispose();
|
|
111
|
+
}
|
|
112
|
+
xDrop?.dispose();
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
/** A compound kernel's adapter operation, bound by the request context before
|
|
120
|
+
* forward. Fused no-adapter arithmetic is retained; the adapted operation
|
|
121
|
+
* exposes projections so their deltas precede activation. */
|
|
122
|
+
export class LoraOperation {
|
|
123
|
+
#forward: (x: MlxArray) => MlxArray;
|
|
124
|
+
readonly #unbind: (() => void)[] = [];
|
|
125
|
+
readonly #states = new Map<LoraState, () => void>();
|
|
126
|
+
constructor(plain: (x: MlxArray) => MlxArray, adapted: (x: MlxArray) => MlxArray,
|
|
127
|
+
state: LoraState | undefined, targets: readonly LoraDelta[]) {
|
|
128
|
+
this.#forward = plain;
|
|
129
|
+
const refresh = () => {
|
|
130
|
+
this.#forward = targets.some(target => (state?.active ?? target.loraState?.active ?? [])
|
|
131
|
+
.some(id => target.adapters?.has(id))) ? adapted : plain;
|
|
132
|
+
};
|
|
133
|
+
const bind = () => {
|
|
134
|
+
const states = new Set(state ? [state] : targets.flatMap(target => target.loraState ? [target.loraState] : []));
|
|
135
|
+
for (const [previous, unbind] of this.#states) if (!states.has(previous)) { unbind(); this.#states.delete(previous); }
|
|
136
|
+
for (const current of states) if (!this.#states.has(current)) this.#states.set(current, current.bindOperation(refresh));
|
|
137
|
+
refresh();
|
|
138
|
+
};
|
|
139
|
+
if (state) bind();
|
|
140
|
+
else for (const target of targets) this.#unbind.push(target.observeState(bind));
|
|
141
|
+
}
|
|
142
|
+
forward(x: MlxArray): MlxArray { return this.#forward(x); }
|
|
143
|
+
dispose(): void {
|
|
144
|
+
for (const unbind of this.#unbind) unbind();
|
|
145
|
+
for (const unbind of this.#states.values()) unbind();
|
|
146
|
+
this.#states.clear();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
|
|
4
|
+
export class RMSNorm {
|
|
5
|
+
constructor(readonly weight: MlxArray | null, readonly eps: number) {}
|
|
6
|
+
forward(x: MlxArray): MlxArray {
|
|
7
|
+
return ops.rmsNorm(x, this.weight, this.eps);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
import type { PrefixLayout } from "../contracts/mlx/trainable";
|
|
4
|
+
|
|
5
|
+
/** Block-wise RoPE for the prefix-shared concat [prompt; chosen; rejected] along
|
|
6
|
+
* the sequence axis (axis 2 of [B,H,T,D]): prompt rotated at offset 0, each
|
|
7
|
+
* response at offset P (reset), so ONE forward over the concat equals the two
|
|
8
|
+
* separate [prompt; response] forwards. RoPE is per-token, so roping each
|
|
9
|
+
* contiguous block at its scalar offset and concatenating == roping with
|
|
10
|
+
* per-token position-ids [0..P-1, P..P+Rc-1, P..P+Rr-1]. `rope` applies the
|
|
11
|
+
* layer's own rotation (its base, frequency table and dims) to one block at a
|
|
12
|
+
* scalar offset. The prefix-shared training cache (`PrefixSharedCache`) owns
|
|
13
|
+
* the layout and ropes through this. Caller disposes the input. */
|
|
14
|
+
export function ropeBlocks(
|
|
15
|
+
x: MlxArray, plan: PrefixLayout, rope: (block: MlxArray, offset: number) => MlxArray,
|
|
16
|
+
): MlxArray {
|
|
17
|
+
const { P, Rc, Rr } = plan;
|
|
18
|
+
const [B, H, , D] = x.shape as [number, number, number, number];
|
|
19
|
+
const blocks = [
|
|
20
|
+
{ start: 0, len: P, off: 0 },
|
|
21
|
+
{ start: P, len: Rc, off: P },
|
|
22
|
+
{ start: P + Rc, len: Rr, off: P },
|
|
23
|
+
].filter((b) => b.len > 0);
|
|
24
|
+
const parts = blocks.map((b) => {
|
|
25
|
+
const sl = x.slice([0, 0, b.start, 0], [B, H, b.start + b.len, D]);
|
|
26
|
+
const r = rope(sl, b.off);
|
|
27
|
+
sl.dispose();
|
|
28
|
+
return r;
|
|
29
|
+
});
|
|
30
|
+
const out = ops.concatAxis(parts, 2);
|
|
31
|
+
for (const p of parts) p.dispose();
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
import { quantizedSdpaUnfused } from "./quantized-attention";
|
|
4
|
+
|
|
5
|
+
/** Committed positions retain single-token attention arithmetic (the unfused
|
|
6
|
+
* port, which one query always takes). Projection work can still share the
|
|
7
|
+
* span; each query sees its own causal KV prefix. */
|
|
8
|
+
export function quantizedAppendAttention(
|
|
9
|
+
q: MlxArray, keys: ops.QuantizedTensor, values: ops.QuantizedTensor,
|
|
10
|
+
scale: number, groupSize: number, bits: number,
|
|
11
|
+
): MlxArray {
|
|
12
|
+
const [batch, heads, length, dim] = q.shape as [number, number, number, number];
|
|
13
|
+
const prefix = keys.packed.shape[2]! - length;
|
|
14
|
+
const outputs: MlxArray[] = [];
|
|
15
|
+
const trim = (tensor: ops.QuantizedTensor, end: number) => {
|
|
16
|
+
const slice = (a: MlxArray) => a.slice([0, 0, 0, 0],
|
|
17
|
+
[a.shape[0]!, a.shape[1]!, end, a.shape[3]!]);
|
|
18
|
+
return { packed: slice(tensor.packed), scales: slice(tensor.scales), biases: slice(tensor.biases) };
|
|
19
|
+
};
|
|
20
|
+
try {
|
|
21
|
+
for (let row = 0; row < length; row++) {
|
|
22
|
+
using queryView = q.slice([0, 0, row, 0], [batch, heads, row + 1, dim]);
|
|
23
|
+
using query = ops.contiguous(queryView);
|
|
24
|
+
const k = trim(keys, prefix + row + 1), v = trim(values, prefix + row + 1);
|
|
25
|
+
try { outputs.push(quantizedSdpaUnfused(query, k, v, scale, { mode: "", arr: null }, groupSize, bits)); }
|
|
26
|
+
finally {
|
|
27
|
+
for (const t of [k, v]) { t.packed.dispose(); t.scales.dispose(); t.biases.dispose(); }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return ops.concatAxis(outputs, 2);
|
|
31
|
+
} finally { for (const output of outputs) output.dispose(); }
|
|
32
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
3
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
4
|
+
import type { Mask } from "../contracts/mlx/cache";
|
|
5
|
+
import { isCompiledTrace } from "../runtime/compiled-trace";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export const FINFO_MIN: Partial<Record<Dtype, number>> = {
|
|
9
|
+
[Dtype.bfloat16]: -3.3895313892515355e38,
|
|
10
|
+
[Dtype.float16]: -65504,
|
|
11
|
+
[Dtype.float32]: -3.4028234663852886e38,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** Port of base.py quantized_scaled_dot_product_attention (stock,
|
|
15
|
+
* non-tiled): scores and output via quantized_matmul against the
|
|
16
|
+
* quantized KV triples; GQA via a 5-d reshape. Exported for the
|
|
17
|
+
* fused-vs-unfused parity tests. */
|
|
18
|
+
export function quantizedSdpaUnfused(
|
|
19
|
+
q: MlxArray, kq: ops.QuantizedTensor, vq: ops.QuantizedTensor,
|
|
20
|
+
scale: number, mask: Mask, groupSize: number, bits: number,
|
|
21
|
+
): MlxArray {
|
|
22
|
+
return unfused(q, kq, vq, scale, mask, groupSize, bits, false);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** `quantizedSdpaUnfused` with the key matmul over three GQA heads per batch:
|
|
26
|
+
* the queries of each KV head enter the key quantized_matmul as [2, 9] rows
|
|
27
|
+
* instead of 18, and the scores are restored to [nRep, L] before the mask,
|
|
28
|
+
* softmax and value matmul. On the M4 Pro GPU (applegpu_g16s) MLX's native
|
|
29
|
+
* key matvec keeps exact arithmetic in that grouping at 8192 or more keys.
|
|
30
|
+
* A kernel for one composition: depth-2 speculative verify (L = 3) on 24
|
|
31
|
+
* query heads, 4 KV heads, head dim 256, B <= 2, a 4-bit group-64 cache and
|
|
32
|
+
* bf16 or f32 queries, built only by the cache composed for it. Other shapes
|
|
33
|
+
* are outside its contract (the [2, 9] split needs nRep * L = 18). */
|
|
34
|
+
export function quantizedSdpaGroupedHeads(
|
|
35
|
+
q: MlxArray, kq: ops.QuantizedTensor, vq: ops.QuantizedTensor,
|
|
36
|
+
scale: number, mask: Mask, groupSize: number, bits: number,
|
|
37
|
+
): MlxArray {
|
|
38
|
+
return unfused(q, kq, vq, scale, mask, groupSize, bits, true);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function unfused(
|
|
42
|
+
q: MlxArray, kq: ops.QuantizedTensor, vq: ops.QuantizedTensor,
|
|
43
|
+
scale: number, mask: Mask, groupSize: number, bits: number, groupHeads: boolean,
|
|
44
|
+
): MlxArray {
|
|
45
|
+
const [B, H, L, D] = q.shape as [number, number, number, number];
|
|
46
|
+
const KV = kq.packed.shape[1]!;
|
|
47
|
+
const nRep = H / KV;
|
|
48
|
+
const N = kq.packed.shape[2]!;
|
|
49
|
+
|
|
50
|
+
// scale is 1.0 for Gemma4 (q/k are RMS-normed) — skip the identity multiply.
|
|
51
|
+
let queries = q;
|
|
52
|
+
const owned: MlxArray[] = [];
|
|
53
|
+
if (scale !== 1.0) { queries = ops.mulScalar(q, scale); owned.push(queries); }
|
|
54
|
+
|
|
55
|
+
let kT = kq;
|
|
56
|
+
let vT = vq;
|
|
57
|
+
if (nRep > 1) {
|
|
58
|
+
const qr = ops.reshape(queries, [B, KV, nRep, L, D]);
|
|
59
|
+
owned.push(qr);
|
|
60
|
+
queries = qr;
|
|
61
|
+
const expand = (t: ops.QuantizedTensor): ops.QuantizedTensor => {
|
|
62
|
+
const e = (a: MlxArray): MlxArray => {
|
|
63
|
+
// expand_dims(axis=-3) like the reference — view-preserving;
|
|
64
|
+
// reshape would copy the strided slice and change kernel paths
|
|
65
|
+
const r = ops.expandDims(a, -3);
|
|
66
|
+
owned.push(r);
|
|
67
|
+
return r;
|
|
68
|
+
};
|
|
69
|
+
return { packed: e(t.packed), scales: e(t.scales), biases: e(t.biases) };
|
|
70
|
+
};
|
|
71
|
+
kT = expand(kq);
|
|
72
|
+
vT = expand(vq);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Grouped heads (quantizedSdpaGroupedHeads): three GQA heads per key
|
|
76
|
+
// matmul batch; restore score geometry before softmax/value work.
|
|
77
|
+
let keyQueries = queries;
|
|
78
|
+
if (groupHeads) {
|
|
79
|
+
keyQueries = ops.reshape(queries, [B, KV, 2, 9, D]);
|
|
80
|
+
owned.push(keyQueries);
|
|
81
|
+
}
|
|
82
|
+
let scores = ops.quantizedMatmulQT(keyQueries, kT, true, groupSize, bits);
|
|
83
|
+
owned.push(scores);
|
|
84
|
+
if (groupHeads) {
|
|
85
|
+
scores = ops.reshape(scores, [B, KV, nRep, L, N]);
|
|
86
|
+
owned.push(scores);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let maskArr: MlxArray | null = null;
|
|
90
|
+
let ownsMask = false;
|
|
91
|
+
if (mask.mode === "causal") {
|
|
92
|
+
const qIdx = ops.arange(N - L, N, 1, Dtype.int32);
|
|
93
|
+
const kIdx = ops.arange(0, N, 1, Dtype.int32);
|
|
94
|
+
const qCol = ops.reshape(qIdx, [L, 1]);
|
|
95
|
+
const kRow = ops.reshape(kIdx, [1, N]);
|
|
96
|
+
maskArr = ops.greaterEqual(qCol, kRow);
|
|
97
|
+
ownsMask = true;
|
|
98
|
+
for (const a of [qIdx, kIdx, qCol, kRow]) a.dispose();
|
|
99
|
+
} else if (mask.mode === "array") {
|
|
100
|
+
maskArr = mask.arr;
|
|
101
|
+
// Batched padding masks are [B,1,L,S] (buildBatchedDecodeMask). When GQA
|
|
102
|
+
// reshaped the scores to 5-D [B,KV,nRep,L,S], trailing-dim broadcasting
|
|
103
|
+
// would misalign the mask's B with KV — insert the unit axis explicitly:
|
|
104
|
+
// [B,1,1,L,S] broadcasts correctly over (KV, nRep).
|
|
105
|
+
if (maskArr && maskArr.ndim === 4 && nRep > 1) {
|
|
106
|
+
maskArr = ops.expandDims(maskArr, 1);
|
|
107
|
+
ownsMask = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (maskArr) {
|
|
111
|
+
let masked: MlxArray;
|
|
112
|
+
if (maskArr.dtype === Dtype.bool) {
|
|
113
|
+
using ninf = ops.scalarLike(FINFO_MIN[scores.dtype] ?? -3.4e38, scores);
|
|
114
|
+
masked = ops.where(maskArr, scores, ninf);
|
|
115
|
+
} else masked = ops.add(scores, maskArr);
|
|
116
|
+
if (ownsMask) maskArr.dispose();
|
|
117
|
+
owned.push(masked);
|
|
118
|
+
scores = masked;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const probs = ops.softmaxAxis(scores, -1, true);
|
|
122
|
+
owned.push(probs);
|
|
123
|
+
let out = ops.quantizedMatmulQT(probs, vT, false, groupSize, bits);
|
|
124
|
+
if (nRep > 1) {
|
|
125
|
+
const r = ops.reshape(out, [B, H, L, D]);
|
|
126
|
+
out.dispose();
|
|
127
|
+
out = r;
|
|
128
|
+
}
|
|
129
|
+
for (const a of owned) a.dispose();
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** GQA folded into rows over a quantized cache: each KV head's rep·L query
|
|
134
|
+
* rows share one quantized_matmul against its packed keys and one against
|
|
135
|
+
* its values, so the cache is read once per KV head instead of once per query
|
|
136
|
+
* head (the broadcast in quantizedSdpaUnfused). B = 1; the L queries are the
|
|
137
|
+
* last L positions (causal), no array mask. Not bit-identical to quantizedSdpa
|
|
138
|
+
* (bf16-level). */
|
|
139
|
+
export function foldedQuantizedSdpaEligible(q: MlxArray, kq: ops.QuantizedTensor, mask: Mask): boolean {
|
|
140
|
+
const [B, H, L] = q.shape as [number, number, number, number];
|
|
141
|
+
const KVH = kq.packed.shape[1]!;
|
|
142
|
+
return B === 1 && H % KVH === 0 && !mask.arr && (L === 1 || mask.mode === "causal") && L <= kq.packed.shape[2]!;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function foldedQuantizedSdpa(
|
|
146
|
+
q: MlxArray, kq: ops.QuantizedTensor, vq: ops.QuantizedTensor,
|
|
147
|
+
scale: number, groupSize: number, bits: number,
|
|
148
|
+
): MlxArray {
|
|
149
|
+
const [, H, L, D] = q.shape as [number, number, number, number];
|
|
150
|
+
const KVH = kq.packed.shape[1]!, N = kq.packed.shape[2]!, rep = H / KVH;
|
|
151
|
+
using q4 = ops.reshape(q, [1, KVH, rep * L, D]); // rows (r, i): head KVH·rep + r, query i
|
|
152
|
+
using qs = ops.mulScalar(q4, scale);
|
|
153
|
+
using scores = ops.quantizedMatmulQT(qs, kq, true, groupSize, bits); // [1, KVH, rep·L, N]
|
|
154
|
+
let probs: MlxArray;
|
|
155
|
+
if (L > 1) {
|
|
156
|
+
using cols = ops.arange(0, N, 1, Dtype.int32);
|
|
157
|
+
using colRow = ops.reshape(cols, [1, N]);
|
|
158
|
+
using limits = ops.fromInt32(Array.from({ length: rep * L }, (_, row) => N - L + (row % L)), [rep * L, 1]);
|
|
159
|
+
using allowed = ops.lessEqual(colRow, limits);
|
|
160
|
+
using ninf = ops.scalarLike(FINFO_MIN[scores.dtype] ?? -3.4e38, scores);
|
|
161
|
+
using masked = ops.where(allowed, scores, ninf);
|
|
162
|
+
probs = ops.softmaxAxis(masked, -1, true);
|
|
163
|
+
} else probs = ops.softmaxAxis(scores, -1, true);
|
|
164
|
+
using p = probs;
|
|
165
|
+
using out = ops.quantizedMatmulQT(p, vq, false, groupSize, bits); // [1, KVH, rep·L, D]
|
|
166
|
+
return ops.reshape(out, [1, H, L, out.shape[3]!]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Tile size for the fused quantized-SDPA prefill path (oracle default). */
|
|
170
|
+
export const FUSED_N_CHUNK = 512;
|
|
171
|
+
|
|
172
|
+
/** Port of optiq fused_quant_sdpa._prefill_flashattn_n_tiled: a
|
|
173
|
+
* FlashAttention-2 loop over the KV N axis with mx.quantized_matmul as
|
|
174
|
+
* the inner kernel. Never materializes the full [..., L, N] scores
|
|
175
|
+
* matrix — the per-tile transient is bounded by FUSED_N_CHUNK, which is
|
|
176
|
+
* the whole point (stock u4 KV prefill peaks ABOVE fp16 KV at long
|
|
177
|
+
* context; see the oracle's module docstring). Op composition order
|
|
178
|
+
* mirrors the oracle exactly: parity is tier a (bit-exact) vs the python
|
|
179
|
+
* fused path; vs quantizedSdpaUnfused it is tier b by construction
|
|
180
|
+
* (online softmax ≠ one-shot precise softmax in bf16). */
|
|
181
|
+
export function quantizedSdpaTiled(
|
|
182
|
+
q: MlxArray, kq: ops.QuantizedTensor, vq: ops.QuantizedTensor,
|
|
183
|
+
scale: number, mask: Mask, groupSize: number, bits: number,
|
|
184
|
+
): MlxArray {
|
|
185
|
+
const [B, H, L, D] = q.shape as [number, number, number, number];
|
|
186
|
+
const KV = kq.packed.shape[1]!;
|
|
187
|
+
const nRep = H / KV;
|
|
188
|
+
const N = kq.packed.shape[2]!;
|
|
189
|
+
|
|
190
|
+
// scale is 1.0 for Gemma4 (q/k are RMS-normed) — skip the identity multiply.
|
|
191
|
+
let queries = q;
|
|
192
|
+
let ownsQueries = false;
|
|
193
|
+
if (scale !== 1.0) { queries = ops.mulScalar(q, scale); ownsQueries = true; }
|
|
194
|
+
|
|
195
|
+
let kT = kq;
|
|
196
|
+
let vT = vq;
|
|
197
|
+
const expanded: MlxArray[] = [];
|
|
198
|
+
if (nRep > 1) {
|
|
199
|
+
const qr = ops.reshape(queries, [B, KV, nRep, L, D]);
|
|
200
|
+
if (ownsQueries) queries.dispose();
|
|
201
|
+
queries = qr;
|
|
202
|
+
ownsQueries = true;
|
|
203
|
+
const expand = (t: ops.QuantizedTensor): ops.QuantizedTensor => {
|
|
204
|
+
const e = (a: MlxArray): MlxArray => {
|
|
205
|
+
const r = ops.expandDims(a, -3);
|
|
206
|
+
expanded.push(r);
|
|
207
|
+
return r;
|
|
208
|
+
};
|
|
209
|
+
return { packed: e(t.packed), scales: e(t.scales), biases: e(t.biases) };
|
|
210
|
+
};
|
|
211
|
+
kT = expand(kq);
|
|
212
|
+
vT = expand(vq);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// The oracle builds a bottom-right-aligned [L, N] bool causal matrix
|
|
216
|
+
// and slices columns per tile; our "array" masks (createCausalMask at
|
|
217
|
+
// offset > 0) are that same matrix already materialized — both slice
|
|
218
|
+
// identically below.
|
|
219
|
+
let maskArr: MlxArray | null = null;
|
|
220
|
+
let ownsMask = false;
|
|
221
|
+
if (mask.mode === "causal") {
|
|
222
|
+
const qIdx = ops.arange(N - L, N, 1, Dtype.int32);
|
|
223
|
+
const kIdx = ops.arange(0, N, 1, Dtype.int32);
|
|
224
|
+
const qCol = ops.reshape(qIdx, [L, 1]);
|
|
225
|
+
const kRow = ops.reshape(kIdx, [1, N]);
|
|
226
|
+
maskArr = ops.greaterEqual(qCol, kRow);
|
|
227
|
+
ownsMask = true;
|
|
228
|
+
for (const a of [qIdx, kIdx, qCol, kRow]) a.dispose();
|
|
229
|
+
} else if (mask.mode === "array") {
|
|
230
|
+
maskArr = mask.arr;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Slice [..., n0:n1, :] (KV triples) / [..., n0:n1] (mask chunks).
|
|
234
|
+
// Under a compiled-decode trace, subrange Slice swaps to DynamicSlice
|
|
235
|
+
// (identical values; shapeless compile rejects Slice — same pattern as
|
|
236
|
+
// the per-layer-input and MoE top-k slices).
|
|
237
|
+
const sliceAxis = (a: MlxArray, axisFromEnd: 1 | 2, n0: number, n1: number): MlxArray => {
|
|
238
|
+
const dims = a.shape;
|
|
239
|
+
const axis = dims.length - axisFromEnd;
|
|
240
|
+
if (isCompiledTrace()) {
|
|
241
|
+
const start = ops.fromInt32([n0], [1]);
|
|
242
|
+
const size = [...dims];
|
|
243
|
+
size[axis] = n1 - n0;
|
|
244
|
+
const out = ops.sliceDynamic(a, start, [axis], size);
|
|
245
|
+
start.dispose();
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
const start = dims.map(() => 0);
|
|
249
|
+
const stop = [...dims];
|
|
250
|
+
start[axis] = n0;
|
|
251
|
+
stop[axis] = n1;
|
|
252
|
+
return a.slice(start, stop);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
let oAcc: MlxArray | null = null;
|
|
256
|
+
let rowMax: MlxArray | null = null;
|
|
257
|
+
let rowSum: MlxArray | null = null;
|
|
258
|
+
|
|
259
|
+
for (let n0 = 0; n0 < N; n0 += FUSED_N_CHUNK) {
|
|
260
|
+
const n1 = Math.min(n0 + FUSED_N_CHUNK, N);
|
|
261
|
+
const kChunk: ops.QuantizedTensor = {
|
|
262
|
+
packed: sliceAxis(kT.packed, 2, n0, n1),
|
|
263
|
+
scales: sliceAxis(kT.scales, 2, n0, n1),
|
|
264
|
+
biases: sliceAxis(kT.biases, 2, n0, n1),
|
|
265
|
+
};
|
|
266
|
+
const vChunk: ops.QuantizedTensor = {
|
|
267
|
+
packed: sliceAxis(vT.packed, 2, n0, n1),
|
|
268
|
+
scales: sliceAxis(vT.scales, 2, n0, n1),
|
|
269
|
+
biases: sliceAxis(vT.biases, 2, n0, n1),
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
let scores = ops.quantizedMatmulQT(queries, kChunk, true, groupSize, bits);
|
|
273
|
+
for (const a of [kChunk.packed, kChunk.scales, kChunk.biases]) a.dispose();
|
|
274
|
+
|
|
275
|
+
if (maskArr) {
|
|
276
|
+
const maskChunk = sliceAxis(maskArr, 1, n0, n1);
|
|
277
|
+
let masked: MlxArray;
|
|
278
|
+
if (maskChunk.dtype === Dtype.bool) {
|
|
279
|
+
const ninf = ops.scalarLike(FINFO_MIN[scores.dtype] ?? -3.4e38, scores);
|
|
280
|
+
masked = ops.where(maskChunk, scores, ninf);
|
|
281
|
+
ninf.dispose();
|
|
282
|
+
} else {
|
|
283
|
+
masked = ops.add(scores, maskChunk);
|
|
284
|
+
}
|
|
285
|
+
maskChunk.dispose();
|
|
286
|
+
scores.dispose();
|
|
287
|
+
scores = masked;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const chunkMax = ops.maxAxis(scores, -1, true);
|
|
291
|
+
if (oAcc === null) {
|
|
292
|
+
rowMax = chunkMax;
|
|
293
|
+
const shifted = ops.sub(scores, rowMax);
|
|
294
|
+
const exps = ops.exp(shifted);
|
|
295
|
+
shifted.dispose();
|
|
296
|
+
rowSum = ops.sumAxis(exps, -1, true);
|
|
297
|
+
oAcc = ops.quantizedMatmulQT(exps, vChunk, false, groupSize, bits);
|
|
298
|
+
exps.dispose();
|
|
299
|
+
} else {
|
|
300
|
+
const newMax = ops.maximum(rowMax!, chunkMax);
|
|
301
|
+
chunkMax.dispose();
|
|
302
|
+
const maxDiff = ops.sub(rowMax!, newMax);
|
|
303
|
+
const factor = ops.exp(maxDiff);
|
|
304
|
+
maxDiff.dispose();
|
|
305
|
+
const shifted = ops.sub(scores, newMax);
|
|
306
|
+
const exps = ops.exp(shifted);
|
|
307
|
+
shifted.dispose();
|
|
308
|
+
// new_sum = factor * row_sum + sum(exps) (association order kept)
|
|
309
|
+
const carried = ops.mul(factor, rowSum!);
|
|
310
|
+
const sumExps = ops.sumAxis(exps, -1, true);
|
|
311
|
+
const newSum = ops.add(carried, sumExps);
|
|
312
|
+
carried.dispose();
|
|
313
|
+
sumExps.dispose();
|
|
314
|
+
const deltaOut = ops.quantizedMatmulQT(exps, vChunk, false, groupSize, bits);
|
|
315
|
+
exps.dispose();
|
|
316
|
+
// o_acc = o_acc * factor + delta_out
|
|
317
|
+
const scaledAcc = ops.mul(oAcc!, factor);
|
|
318
|
+
factor.dispose();
|
|
319
|
+
const nextAcc = ops.add(scaledAcc, deltaOut);
|
|
320
|
+
scaledAcc.dispose();
|
|
321
|
+
deltaOut.dispose();
|
|
322
|
+
oAcc!.dispose();
|
|
323
|
+
oAcc = nextAcc;
|
|
324
|
+
rowMax!.dispose();
|
|
325
|
+
rowMax = newMax;
|
|
326
|
+
rowSum!.dispose();
|
|
327
|
+
rowSum = newSum;
|
|
328
|
+
}
|
|
329
|
+
scores.dispose();
|
|
330
|
+
for (const a of [vChunk.packed, vChunk.scales, vChunk.biases]) a.dispose();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
let out = ops.div(oAcc!, rowSum!);
|
|
334
|
+
oAcc!.dispose();
|
|
335
|
+
rowMax!.dispose();
|
|
336
|
+
rowSum!.dispose();
|
|
337
|
+
if (ownsMask) maskArr!.dispose();
|
|
338
|
+
for (const a of expanded) a.dispose();
|
|
339
|
+
if (ownsQueries) queries.dispose();
|
|
340
|
+
if (nRep > 1) {
|
|
341
|
+
const r = ops.reshape(out, [B, H, L, D]);
|
|
342
|
+
out.dispose();
|
|
343
|
+
out = r;
|
|
344
|
+
}
|
|
345
|
+
return out;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** The (bits, group size, dtype) the tiled kernel takes: the composition's
|
|
349
|
+
* tiled-causal set (`composedAffineKernels`) tiles exactly these. */
|
|
350
|
+
export function fusedSdpaConfigSupported(groupSize: number, bits: number, dtype: Dtype): boolean {
|
|
351
|
+
return (bits === 4 || bits === 8) && (groupSize === 32 || groupSize === 64 || groupSize === 128) &&
|
|
352
|
+
(dtype === Dtype.bfloat16 || dtype === Dtype.float16);
|
|
353
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
import { quantFor,type ModelConfig } from "../artifacts/config";
|
|
4
|
+
import type { Weights } from "../artifacts/weights";
|
|
5
|
+
|
|
6
|
+
export class QuantizedEmbedding {
|
|
7
|
+
constructor(
|
|
8
|
+
readonly w: MlxArray,
|
|
9
|
+
readonly scales: MlxArray,
|
|
10
|
+
readonly biases: MlxArray | null,
|
|
11
|
+
readonly spec: ops.QuantSpec,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
static load(weights: Weights, path: string, config: ModelConfig): QuantizedEmbedding {
|
|
15
|
+
const spec = quantFor(config.quantization, path)!;
|
|
16
|
+
return new QuantizedEmbedding(
|
|
17
|
+
weights.tensor(`${path}.weight`),
|
|
18
|
+
weights.tensor(`${path}.scales`),
|
|
19
|
+
weights.has(`${path}.biases`) ? weights.tensor(`${path}.biases`) : null,
|
|
20
|
+
spec,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** ids [1, L] (int/uint) → embeddings [1, L, hidden]. */
|
|
25
|
+
encode(ids: MlxArray): MlxArray {
|
|
26
|
+
const rows = ops.takeAxis(this.w, ids, 0);
|
|
27
|
+
const scaleRows = ops.takeAxis(this.scales, ids, 0);
|
|
28
|
+
const biasRows = this.biases ? ops.takeAxis(this.biases, ids, 0) : null;
|
|
29
|
+
const out = ops.dequantize(rows, scaleRows, biasRows, this.spec);
|
|
30
|
+
for (const a of [rows, scaleRows]) a.dispose();
|
|
31
|
+
biasRows?.dispose();
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Tied output head: h [1, L, hidden] → logits [1, L, vocab]. */
|
|
36
|
+
asLinear(h: MlxArray): MlxArray {
|
|
37
|
+
return ops.quantizedMatmul(h, this.w, this.scales, this.biases, this.spec, true);
|
|
38
|
+
}
|
|
39
|
+
}
|