mlx-bun 0.0.13 → 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 -578
- package/bin/mlx-bun.mjs +1 -1
- package/docs/README.md +91 -0
- package/docs/{design → archive}/hlg-sampling.md +3 -3
- package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
- package/docs/{design → archive/investigations}/colibri-glm52-port.md +1 -1
- package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
- package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
- package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
- package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
- package/docs/{planning → archive/planning}/chunk-task-roadmap.md +1 -1
- package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +4 -4
- package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +1 -1
- package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
- package/docs/archive/planning/release-notes-v0.2.0.md +49 -0
- package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
- 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 +421 -0
- package/docs/reference/benchmarks.md +1162 -0
- package/docs/reference/cli.md +395 -149
- package/docs/reference/distribution.md +321 -181
- package/docs/reference/environment.md +348 -0
- package/docs/reference/glossary.md +39 -0
- package/docs/reference/library-api.md +580 -142
- package/docs/reference/memory.md +227 -64
- package/docs/reference/models.md +137 -6
- package/docs/reference/server-api.md +938 -422
- package/docs/reference/server-config.md +652 -320
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +166 -0
- package/package.json +10 -5
- package/src/admission-plan.ts +18 -0
- package/src/anthropic.ts +92 -3
- 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 +189 -61
- 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 +263 -23
- package/src/eval/reference-logits.ts +207 -0
- package/src/eval/runner.ts +10 -79
- package/src/eval/tasks/bfcl.ts +2 -1
- package/src/eval/tasks/gsm8k.ts +2 -1
- package/src/eval/tasks/hashhop.ts +2 -1
- package/src/eval/tasks/humaneval.ts +5 -4
- package/src/eval/tasks/ifeval.ts +2 -1
- package/src/eval/tasks/mmlu.ts +2 -1
- package/src/expert-io.ts +3 -1
- package/src/expert-offload.ts +3 -1
- 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 +24 -43
- package/src/generate.ts +740 -337
- package/src/grammar.ts +14 -8
- package/src/harness-pi.ts +15 -34
- package/src/hub-rest.ts +4 -4
- package/src/index.ts +27 -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 +15 -14
- 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-scheme.ts +199 -0
- package/src/kv-store.ts +459 -234
- 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} +7 -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 +20 -4
- package/src/memory/article.ts +1 -1
- package/src/memory/chunk.ts +1 -1
- package/src/memory/model.ts +32 -170
- 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 +3 -4
- package/src/mlx/array.ts +9 -10
- package/src/mlx/compile.ts +12 -2
- package/src/mlx/ffi.ts +66 -22
- package/src/mlx/materialize.ts +23 -0
- package/src/mlx/metal-kernel.ts +10 -11
- package/src/mlx/ops.ts +64 -19
- 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 +9 -3
- package/src/model/batched-quant.ts +2 -2
- package/src/model/batched-rotating-quant.ts +45 -99
- package/src/model/batched-rotating-state.ts +73 -0
- package/src/model/batched-rotating.ts +57 -106
- package/src/model/batched-row-storage.ts +127 -0
- package/src/model/compiled-decode.ts +4 -3
- package/src/model/diffusion-gemma.ts +8 -8
- package/src/model/factory.ts +87 -49
- package/src/model/fingerprint.ts +1 -1
- package/src/model/flash-attention.ts +3 -3
- package/src/model/gemma4-base.ts +187 -17
- package/src/model/gemma4.ts +6 -6
- package/src/model/generated/gemma4-12b.ts +52 -148
- package/src/model/generated/gemma4-26b.ts +34 -94
- package/src/model/generated/gemma4-e4b.ts +28 -76
- package/src/model/glm52-cache.ts +19 -9
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +28 -1
- package/src/model/implementation.ts +69 -0
- package/src/model/minicpm5.ts +7 -8
- package/src/model/profile.ts +443 -0
- package/src/model/qwen-append.ts +12 -0
- package/src/model/qwen-conv.ts +52 -0
- package/src/model/qwen3-delta.ts +98 -2
- package/src/model/qwen3-mrope.ts +252 -0
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +336 -60
- 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/frame_extract.swift +140 -0
- package/src/native-pack.ts +15 -7
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +14 -320
- package/src/prompt-cache.ts +97 -54
- package/src/quantize/allocator.ts +1 -1
- package/src/quantize/atomic-output.ts +38 -0
- package/src/quantize/calibration.ts +2 -1
- package/src/quantize/config-writer.ts +16 -3
- package/src/quantize/index.ts +28 -1
- package/src/quantize/job.ts +11 -0
- package/src/quantize/quantizer.ts +265 -100
- package/src/quantize/rotate.ts +603 -0
- package/src/quantize/safetensors-writer.ts +79 -1
- package/src/quantize/sensitivity.ts +41 -59
- package/src/quantize/trellis.ts +586 -0
- package/src/quantize/weight-transform.ts +292 -0
- package/src/responses.ts +130 -3
- package/src/runtime-config.ts +81 -0
- package/src/sampler.ts +276 -4
- package/src/serve/admin-routes.ts +184 -0
- package/src/serve/aux-routes.ts +132 -0
- package/src/serve/batch-scheduler.ts +8 -1558
- 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 +354 -0
- package/src/serve/completion-sink.ts +157 -0
- package/src/serve/discovery-routes.ts +204 -0
- package/src/serve/generation-gateway.ts +211 -243
- 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/kv-budget.ts +14 -0
- package/src/serve/lab-routes.ts +341 -0
- package/src/serve/lane-registry.ts +2 -1
- package/src/serve/model-admin-routes.ts +155 -0
- 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/prompt-response-trace.ts +147 -0
- package/src/serve/request-plan.ts +162 -0
- 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 +101 -0
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +523 -3551
- package/src/spec/assistant-source.ts +10 -44
- package/src/spec/deepspec-source.ts +6 -6
- package/src/spec/dflash-source.ts +9 -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 +5 -4
- 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 +9 -1
- package/src/spec/ngram-source.ts +1 -1
- package/src/spec/prefix-state.ts +45 -0
- package/src/spec/qwen-mtp-source.ts +528 -0
- package/src/spec/serve-loop.ts +179 -213
- package/src/spec/source.ts +42 -10
- package/src/spec/two-model.ts +1 -1
- package/src/ssd-cache.ts +95 -24
- package/src/ssd-durability.ts +207 -0
- package/src/tool-call.ts +51 -14
- package/src/train/flash-cce.ts +14 -13
- package/src/train/forward.ts +38 -1
- package/src/train/loss.ts +3 -2
- package/src/train/prefix-shared.ts +5 -1
- package/src/train/segmented.ts +9 -6
- package/src/train/trainer.ts +9 -8
- package/src/vision/preprocess.ts +1 -1
- package/src/vision/prompt.ts +48 -2
- package/src/vision/qwen3vl-preprocess.ts +409 -0
- package/src/vision/qwen3vl-prompt.ts +196 -0
- package/src/vision/qwen3vl-tower.ts +555 -0
- package/src/vision/video-frames.ts +148 -0
- package/src/web/app.html +1 -1
- package/src/web/app.js +15 -6
- package/src/web/src/adapters-panel.ts +3 -3
- package/src/web/src/assistant.ts +2 -36
- package/src/web/src/chat.ts +16 -9
- 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 +134 -33
- 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 -268
- 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-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 -189
- 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 -288
- 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/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/investigations/curve-runs/bisector-route.json +0 -98
- package/docs/investigations/curve-runs/bisector-trace.json +0 -1393
- package/docs/investigations/curve-runs/curve-terrain.json +0 -1179
- package/docs/investigations/curve-runs/curve-terrain.svg +0 -42
- package/docs/investigations/hlg-ab.html +0 -34
- package/docs/investigations/hlg-explorer.html +0 -238
- package/docs/investigations/hlg-figs/cliff.svg +0 -1
- package/docs/investigations/hlg-figs/cross.svg +0 -1
- package/docs/investigations/hlg-figs/edges.svg +0 -1
- package/docs/investigations/hlg-figs/k20-grids.svg +0 -1
- package/docs/investigations/hlg-figs/k20-peaks.svg +0 -1
- package/docs/investigations/hlg-figs/lw-curve.svg +0 -1
- package/docs/investigations/hlg-figs/terrain.svg +0 -1
- package/docs/investigations/hlg-report.html +0 -262
- package/docs/investigations/hlg-runs/hlg-ab.json +0 -149
- package/docs/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
- package/docs/investigations/hlg-runs/hlg-blind.json +0 -107
- package/docs/investigations/hlg-runs/hlg-cliff.json +0 -197
- package/docs/investigations/hlg-runs/hlg-cross.json +0 -505
- package/docs/investigations/hlg-runs/hlg-edge.json +0 -388
- package/docs/investigations/hlg-runs/hlg-eval1.json +0 -25
- package/docs/investigations/hlg-runs/hlg-frontier.json +0 -86
- package/docs/investigations/hlg-runs/hlg-grid.json +0 -242
- package/docs/investigations/hlg-runs/hlg-grid2.json +0 -343
- package/docs/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
- package/docs/investigations/hlg-runs/hlg-map.json +0 -383
- package/docs/investigations/hlg-runs/run0-original.log +0 -159
- package/docs/investigations/hlg-runs/runA-recommended.log +0 -131
- package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
- package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
- package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
- package/docs/investigations/hlg-runs/runAD-ab-samples.log +0 -8
- package/docs/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
- package/docs/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
- package/docs/investigations/hlg-runs/runAG-eval-config2.log +0 -16
- package/docs/investigations/hlg-runs/runB-gain-variants.log +0 -192
- package/docs/investigations/hlg-runs/runC-pivots.log +0 -162
- package/docs/investigations/hlg-runs/runD-oetf.log +0 -132
- package/docs/investigations/hlg-runs/runE-eotf.log +0 -162
- package/docs/investigations/hlg-runs/runF-shaper-working.log +0 -86
- package/docs/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
- package/docs/investigations/hlg-runs/runH-wsweep.log +0 -176
- package/docs/investigations/hlg-runs/runI-asweep.log +0 -176
- package/docs/investigations/hlg-runs/runJ-knobmap.log +0 -49
- package/docs/investigations/hlg-runs/runK-frontier.log +0 -19
- package/docs/investigations/hlg-runs/runL-widemap.log +0 -65
- package/docs/investigations/hlg-runs/runM-frontier-emb.log +0 -15
- package/docs/investigations/hlg-runs/runN-verify.log +0 -116
- package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
- package/docs/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
- package/docs/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
- package/docs/investigations/hlg-runs/runR-blind-transcript.log +0 -79
- package/docs/investigations/hlg-runs/runS-cross-coherence.log +0 -33
- package/docs/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
- package/docs/investigations/hlg-runs/runU-cross-12b.log +0 -33
- package/docs/investigations/hlg-runs/runV-lw-e4b.log +0 -22
- package/docs/investigations/hlg-runs/runW-lw-12b.log +0 -22
- package/docs/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
- package/docs/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
- package/docs/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
- package/docs/planning/IDEAS.md +0 -69
- package/docs/reference/embedding.md +0 -197
- package/docs/reference/features-matrix.md +0 -100
- package/docs/reference/orpo-quickstart.md +0 -135
- package/src/flags.ts +0 -13
- /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
- /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
- /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
- /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
- /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
- /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
- /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
- /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
- /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
- /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
- /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
- /package/docs/{planning → archive/planning}/journal.md +0 -0
- /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
- /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
- /package/{docs/investigations → src/serve/assets}/curve-terrain.html +0 -0
- /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
|
@@ -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
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// TurboQuant KV-cache codec —
|
|
1
|
+
// TurboQuant KV-cache codec — reference operations ported op-for-op
|
|
2
2
|
// from the vendored vllm-metal reference
|
|
3
3
|
// (lab/repro/vllm-metal-turboquant/turboquant_reference.py). See
|
|
4
|
-
// docs/design/turboquant
|
|
5
|
-
// goldens/turboquant.json (via tests/turboquant-ops.test.ts) for the
|
|
4
|
+
// docs/design/turboquant.md for the algorithm writeup and
|
|
5
|
+
// goldens/turboquant.json (via tests/research/turboquant-ops.test.ts) for the
|
|
6
6
|
// bit-exactness oracle. Quantization groups run along the LAST axis
|
|
7
7
|
// (head_dim) only — never the token axis, per the standing invariant.
|
|
8
8
|
//
|
|
@@ -27,6 +27,10 @@ import {
|
|
|
27
27
|
type TurboQuantHeadDim, type TurboQuantValueBits,
|
|
28
28
|
} from "./turboquant-tables";
|
|
29
29
|
|
|
30
|
+
import { tryJointDecodePackedKv, type PackedKvArrays } from "./turboquant-kv-decode";
|
|
31
|
+
import { tryDecodePackedKvInverse256 } from "./turboquant-kv-inverse";
|
|
32
|
+
import { isShapelessTracing } from "./compile";
|
|
33
|
+
|
|
30
34
|
type S = MlxHandle;
|
|
31
35
|
|
|
32
36
|
const BLOCK_SIZE = 32;
|
|
@@ -421,6 +425,32 @@ export function decodeValuesRotated(
|
|
|
421
425
|
return outBf16;
|
|
422
426
|
}
|
|
423
427
|
|
|
428
|
+
/** Optional exact packed K/V decode. Inputs stay borrowed; the caller owns
|
|
429
|
+
* the returned pair. The eager path keeps the reference inverse rotation. */
|
|
430
|
+
export function tryDecodePackedKv(
|
|
431
|
+
inputs: PackedKvArrays, kBits: number, vBits: number, headDim: number,
|
|
432
|
+
deferV = false, s: S = gpuStream,
|
|
433
|
+
): [MlxArray, MlxArray] | null {
|
|
434
|
+
// Reject unsupported value widths before requesting their centroid table.
|
|
435
|
+
if (![2, 3, 4, 5, 8].includes(vBits)) return null;
|
|
436
|
+
if (!deferV && headDim === 256 && kBits === 8 && vBits === 3 &&
|
|
437
|
+
s === gpuStream && !isShapelessTracing() && inputs[0].shape[2]! >= 8192) {
|
|
438
|
+
const decoded = tryDecodePackedKvInverse256(
|
|
439
|
+
inputs, centroidsFor(vBits), signArray(256), kBits, vBits, headDim, s,
|
|
440
|
+
);
|
|
441
|
+
if (decoded) return decoded;
|
|
442
|
+
}
|
|
443
|
+
const decoded = tryJointDecodePackedKv(inputs, centroidsFor(vBits), kBits, vBits, headDim, deferV, s);
|
|
444
|
+
if (!decoded || deferV) return decoded;
|
|
445
|
+
const [key, rotated] = decoded;
|
|
446
|
+
try {
|
|
447
|
+
const value = fwht(rotated, false, s);
|
|
448
|
+
try { return [key, value.astype(Dtype.bfloat16, s)]; }
|
|
449
|
+
finally { value.dispose(); }
|
|
450
|
+
} catch (error) { key.dispose(); throw error; }
|
|
451
|
+
finally { rotated.dispose(); }
|
|
452
|
+
}
|
|
453
|
+
|
|
424
454
|
/** Undo the value rotation on an attention output computed against
|
|
425
455
|
* decodeValuesRotated values. f32 through the transform (matching the
|
|
426
456
|
* eager decode path's precision), returned in the input's dtype. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// GENERATED by scripts/regen
|
|
2
|
-
// Regenerate: bun scripts/regen
|
|
1
|
+
// GENERATED by scripts/regen.ts turboquant — DO NOT EDIT.
|
|
2
|
+
// Regenerate: bun scripts/regen.ts turboquant
|
|
3
3
|
//
|
|
4
4
|
// Source: lab/repro/vllm-metal-turboquant/turboquant_reference.py (vendored
|
|
5
5
|
// vllm-metal oracle, see PROVENANCE.md in that directory), executed under
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Companion to src/train/forward.ts's buildBatchedPadMask, which builds the
|
|
4
4
|
// PREFILL mask ([B,1,L,L], offset 0, right-padded — the training case, already
|
|
5
|
-
// parity-proven by tests/train-batch-e2e.test.ts). This module covers the
|
|
5
|
+
// parity-proven by tests/parity/train-batch-e2e.test.ts). This module covers the
|
|
6
6
|
// DECODE case for batched serving: left-padded rows sharing one growing
|
|
7
7
|
// [B,H,S,D] KV buffer, where a step forwards N new query tokens at a nonzero
|
|
8
8
|
// offset. (The two builders should be consolidated when batched prefill is
|
|
9
|
-
// wired into the serving path — phase S1a, docs/design/
|
|
9
|
+
// wired into the serving path — phase S1a, docs/design/batching.md.)
|
|
10
10
|
//
|
|
11
11
|
// Why left-padding for decode: with rows right-aligned in the KV buffer, every
|
|
12
12
|
// row's next write lands in the SAME column, so one advancing offset serves
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// thing the mask must encode is (a) causality, (b) each row's left padding,
|
|
16
16
|
// and (c) the sliding window, if any.
|
|
17
17
|
|
|
18
|
+
import { cacheSignature } from "./gemma4-base";
|
|
18
19
|
import { MlxArray } from "../mlx/array";
|
|
19
20
|
import * as ops from "../mlx/ops";
|
|
20
21
|
import { createCausalMask, KVCache, type Cache, type Mask } from "./gemma4-base";
|
|
@@ -92,6 +93,11 @@ export class BatchedDecodeMaskCache implements Cache {
|
|
|
92
93
|
#ropeArr: MlxArray | null = null;
|
|
93
94
|
#ropeForOffset = -1;
|
|
94
95
|
|
|
96
|
+
/** A decorator, not a storage kind: never claims the inner cache's
|
|
97
|
+
* identity (isPlainKvCache(wrapper) must stay false — it is not a
|
|
98
|
+
* KVCache), but records what it wraps for diagnostics/persistence. */
|
|
99
|
+
signature(): string { return `kv:batched-mask:${cacheSignature(this.inner)}`; }
|
|
100
|
+
|
|
95
101
|
constructor(
|
|
96
102
|
private readonly inner: Cache,
|
|
97
103
|
private readonly B: number,
|
|
@@ -242,7 +248,7 @@ export function extractKVRow(cache: KVCache, leftPad: number, i: number): KVCach
|
|
|
242
248
|
const cut = (a: MlxArray): MlxArray => {
|
|
243
249
|
const [, H, , D] = a.shape as [number, number, number, number];
|
|
244
250
|
const view = a.slice([i, 0, leftPad, 0], [i + 1, H, S, D]);
|
|
245
|
-
const own = ops.contiguous(view)
|
|
251
|
+
const own = ops.copyOf(view); // TRUE copy: contiguous(view) is a no-op VIEW when already contiguous — pins the source buffer (2026-08-20 DeltaNet conv leak class)
|
|
246
252
|
view.dispose();
|
|
247
253
|
return own;
|
|
248
254
|
};
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// (maybeQuantizeKv at chunk boundaries — see BatchScheduler.#quantizeSolo),
|
|
18
18
|
// so a row's quantized bytes are bit-exact vs serial `--kv-quant config` by
|
|
19
19
|
// construction; this module only re-arranges those bytes across the batch
|
|
20
|
-
// axis. Gates: tests/batched-kv-quant-parity.test.ts.
|
|
20
|
+
// axis. Gates: tests/parity/batched-kv-quant-parity.test.ts.
|
|
21
21
|
|
|
22
22
|
import { MlxArray } from "../mlx/array";
|
|
23
23
|
import * as ops from "../mlx/ops";
|
|
@@ -122,7 +122,7 @@ export function extractQuantRow(
|
|
|
122
122
|
tripleMap(t, (a) => {
|
|
123
123
|
const [, H, , D] = a.shape as [number, number, number, number];
|
|
124
124
|
const view = a.slice([i, 0, leftPad, 0], [i + 1, H, S, D]);
|
|
125
|
-
const own = ops.contiguous(view)
|
|
125
|
+
const own = ops.copyOf(view); // TRUE copy: contiguous(view) is a no-op VIEW when already contiguous — pins the source buffer (2026-08-20 DeltaNet conv leak class)
|
|
126
126
|
view.dispose();
|
|
127
127
|
return own;
|
|
128
128
|
});
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
// every step; it self-tracks per-row offset/leftPad and the scalar ring
|
|
23
23
|
// state. N=1 decode updates only (rows solo-prefill serially, then merge).
|
|
24
24
|
//
|
|
25
|
-
// Gates: tests/batched-rotating-quant.test.ts (model-free byte-identity vs
|
|
25
|
+
// Gates: tests/unit/batched-rotating-quant.test.ts (model-free byte-identity vs
|
|
26
26
|
// the serial oracle per row, through ring wrap, B=1 and B=2) + the gemma
|
|
27
|
-
// scheduler gate in tests/batched-kv-quant-parity.test.ts.
|
|
27
|
+
// scheduler gate in tests/parity/batched-kv-quant-parity.test.ts.
|
|
28
28
|
|
|
29
29
|
import { MlxArray } from "../mlx/array";
|
|
30
30
|
import * as ops from "../mlx/ops";
|
|
@@ -32,6 +32,12 @@ import { Dtype } from "../mlx/ffi";
|
|
|
32
32
|
import { RotatingQuantizedKVCache, type Mask } from "./gemma4-base";
|
|
33
33
|
import { buildBatchedRotatingMask } from "./batched-rotating";
|
|
34
34
|
import type { QuantRow } from "./batched-quant";
|
|
35
|
+
import { BatchedRotatingState } from "./batched-rotating-state";
|
|
36
|
+
import {
|
|
37
|
+
mergeStorageRows,
|
|
38
|
+
quantizedRowStorage,
|
|
39
|
+
temporalStorageView,
|
|
40
|
+
} from "./batched-row-storage";
|
|
35
41
|
|
|
36
42
|
const STEP = 256; // shared with RotatingQuantizedKVCache.STEP and the bf16 twin
|
|
37
43
|
|
|
@@ -49,9 +55,7 @@ const mapTriple = (
|
|
|
49
55
|
* storage. Scalar ring state (`ringIdx`/`offset` reuse the base fields);
|
|
50
56
|
* per-row `offsetArr` (absolute positions → RoPE) and `leftPad`. */
|
|
51
57
|
export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
52
|
-
|
|
53
|
-
leftPad: number[];
|
|
54
|
-
#rotated = false;
|
|
58
|
+
readonly #rows: BatchedRotatingState;
|
|
55
59
|
/** Per-row RoPE positions. STABLE ACROSS A STEP — refreshed only at
|
|
56
60
|
* releaseRopeArr() (the scheduler's post-dispatch hook), never inside
|
|
57
61
|
* the update: the monolith CAPTURES it pre-update and ropes Q after
|
|
@@ -67,13 +71,16 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
67
71
|
maxSize: number, groupSize: number, bits: number, leftPad: number[], offsets: number[],
|
|
68
72
|
) {
|
|
69
73
|
super(maxSize, groupSize, bits);
|
|
70
|
-
this
|
|
71
|
-
this.offsetArr = [...offsets];
|
|
74
|
+
this.#rows = new BatchedRotatingState(maxSize, leftPad, offsets);
|
|
72
75
|
this.ropeOffsetArr = MlxArray.fromInt32(Int32Array.from(offsets), [offsets.length]);
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
get offsetArr(): number[] { return this.#rows.offsets; }
|
|
79
|
+
get leftPad(): number[] { return this.#rows.leftPad; }
|
|
80
|
+
get batchSize(): number { return this.#rows.batchSize; }
|
|
81
|
+
|
|
75
82
|
get #B(): number {
|
|
76
|
-
return this.
|
|
83
|
+
return this.#rows.batchSize;
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
#seqLen2(): number {
|
|
@@ -104,7 +111,7 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
104
111
|
mode: "array",
|
|
105
112
|
arr: buildBatchedRotatingMask(
|
|
106
113
|
this.#B, N, this.leftPad, this.maxSize, window,
|
|
107
|
-
this.
|
|
114
|
+
this.#rows.ringIndex, this.#rows.totalOffset, this.#rows.rotated,
|
|
108
115
|
),
|
|
109
116
|
};
|
|
110
117
|
}
|
|
@@ -117,7 +124,7 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
117
124
|
const vD = v.shape[3]!;
|
|
118
125
|
if (S !== 1)
|
|
119
126
|
throw new Error("BatchedRotatingQuantCache supports N=1 decode updates only (solo-prefill then merge)");
|
|
120
|
-
const prev = this.
|
|
127
|
+
const prev = this.#rows.totalOffset;
|
|
121
128
|
|
|
122
129
|
// Grow the buffer (in STEP chunks) until it reaches maxSize.
|
|
123
130
|
if (!this.keys || (prev >= this.#seqLen2() && this.#seqLen2() < this.maxSize)) {
|
|
@@ -144,7 +151,8 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
144
151
|
this.keys = newK;
|
|
145
152
|
this.values = newV;
|
|
146
153
|
}
|
|
147
|
-
this.
|
|
154
|
+
this.#rows.markGrown(prev);
|
|
155
|
+
this.ringIdx = this.#rows.ringIndex;
|
|
148
156
|
}
|
|
149
157
|
|
|
150
158
|
// Trim any overshoot past maxSize (decrements left padding persistently).
|
|
@@ -159,16 +167,12 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
159
167
|
});
|
|
160
168
|
this.keys = cutFrom(this.keys!);
|
|
161
169
|
this.values = cutFrom(this.values!);
|
|
162
|
-
this.
|
|
163
|
-
this.
|
|
170
|
+
this.#rows.trimOvershoot(trimSize);
|
|
171
|
+
this.ringIdx = this.#rows.ringIndex;
|
|
164
172
|
}
|
|
165
173
|
|
|
166
174
|
// Rotate when the write head reaches the end of the ring.
|
|
167
|
-
|
|
168
|
-
this.#rotated = true;
|
|
169
|
-
this.ringIdx = 0;
|
|
170
|
-
}
|
|
171
|
-
if (this.#rotated) this.leftPad = this.leftPad.map((x) => x - S);
|
|
175
|
+
this.ringIdx = this.#rows.beginWrite(S);
|
|
172
176
|
|
|
173
177
|
// Quantize + write the new K/V at the ring head (six sliceUpdates).
|
|
174
178
|
const kq = ops.quantize(k, this.groupSize, this.bits);
|
|
@@ -184,9 +188,9 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
184
188
|
disposeTriple(kq);
|
|
185
189
|
disposeTriple(vq);
|
|
186
190
|
|
|
187
|
-
this.
|
|
188
|
-
this.
|
|
189
|
-
this.ringIdx
|
|
191
|
+
this.#rows.commitWrite(S);
|
|
192
|
+
this.offset = this.#rows.totalOffset;
|
|
193
|
+
this.ringIdx = this.#rows.ringIndex;
|
|
190
194
|
|
|
191
195
|
// Return the populated prefix (ring not yet full) or the whole buffer.
|
|
192
196
|
const upTo = this.offset < this.maxSize ? this.offset : this.#seqLen2();
|
|
@@ -209,27 +213,10 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
209
213
|
* overrides the base to use the BATCHED ring state. */
|
|
210
214
|
override temporalView(): [ops.QuantizedTensor, ops.QuantizedTensor] {
|
|
211
215
|
if (!this.keys || !this.values) throw new Error("cache is empty");
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
let tt: MlxArray;
|
|
217
|
-
if (this.ringIdx === Sbuf) {
|
|
218
|
-
tt = a.slice([0, 0, 0, 0], [B, H, Sbuf, D]);
|
|
219
|
-
} else if (this.ringIdx < this.offset) {
|
|
220
|
-
const tail = a.slice([0, 0, this.ringIdx, 0], [B, H, Sbuf, D]);
|
|
221
|
-
const head = a.slice([0, 0, 0, 0], [B, H, this.ringIdx, D]);
|
|
222
|
-
tt = ops.concatAxis([tail, head], 2);
|
|
223
|
-
tail.dispose();
|
|
224
|
-
head.dispose();
|
|
225
|
-
} else {
|
|
226
|
-
tt = a.slice([0, 0, 0, 0], [B, H, this.ringIdx, D]);
|
|
227
|
-
}
|
|
228
|
-
const cut = tt.slice([0, 0, 0, 0], [B, H, valid, D]);
|
|
229
|
-
tt.dispose();
|
|
230
|
-
return cut;
|
|
231
|
-
});
|
|
232
|
-
return [order(this.keys), order(this.values)];
|
|
216
|
+
return [
|
|
217
|
+
temporalStorageView(quantizedRowStorage, this.keys, this.#rows),
|
|
218
|
+
temporalStorageView(quantizedRowStorage, this.values, this.#rows),
|
|
219
|
+
];
|
|
233
220
|
}
|
|
234
221
|
|
|
235
222
|
/** Quantized twin of BatchedRotatingCache.extractRow (mlx-lm
|
|
@@ -244,34 +231,13 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
244
231
|
extractRow(i: number): RotatingQuantizedKVCache | null {
|
|
245
232
|
if (!this.keys || !this.values) return null;
|
|
246
233
|
const pad = Math.max(0, this.leftPad[i]!);
|
|
247
|
-
const valid = Math.min(this.offset, this.maxSize);
|
|
248
|
-
const cut = (t: ops.QuantizedTensor): ops.QuantizedTensor =>
|
|
249
|
-
mapTriple(t, (a) => {
|
|
250
|
-
const [, H, Sbuf, D] = a.shape as [number, number, number, number];
|
|
251
|
-
const row = a.slice([i, 0, 0, 0], [i + 1, H, Sbuf, D]);
|
|
252
|
-
let tt: MlxArray;
|
|
253
|
-
if (this.ringIdx === Sbuf) {
|
|
254
|
-
tt = row;
|
|
255
|
-
} else if (this.ringIdx < this.offset) {
|
|
256
|
-
const tail = row.slice([0, 0, this.ringIdx, 0], [1, H, Sbuf, D]);
|
|
257
|
-
const head = row.slice([0, 0, 0, 0], [1, H, this.ringIdx, D]);
|
|
258
|
-
tt = ops.concatAxis([tail, head], 2);
|
|
259
|
-
tail.dispose();
|
|
260
|
-
head.dispose();
|
|
261
|
-
row.dispose();
|
|
262
|
-
} else {
|
|
263
|
-
tt = row.slice([0, 0, 0, 0], [1, H, this.ringIdx, D]);
|
|
264
|
-
row.dispose();
|
|
265
|
-
}
|
|
266
|
-
const cutV = tt.slice([0, 0, pad, 0], [1, H, valid, D]);
|
|
267
|
-
tt.dispose();
|
|
268
|
-
const own = ops.contiguous(cutV);
|
|
269
|
-
cutV.dispose();
|
|
270
|
-
return own;
|
|
271
|
-
});
|
|
272
234
|
const c = new RotatingQuantizedKVCache(this.maxSize, this.groupSize, this.bits);
|
|
273
|
-
const k =
|
|
274
|
-
|
|
235
|
+
const k = temporalStorageView(quantizedRowStorage, this.keys, this.#rows, {
|
|
236
|
+
row: i, from: pad, copy: true,
|
|
237
|
+
});
|
|
238
|
+
const v = temporalStorageView(quantizedRowStorage, this.values, this.#rows, {
|
|
239
|
+
row: i, from: pad, copy: true,
|
|
240
|
+
});
|
|
275
241
|
c.restoreState(k, v, this.offsetArr[i]!, k.packed.shape[2]!);
|
|
276
242
|
return c;
|
|
277
243
|
}
|
|
@@ -279,22 +245,19 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
279
245
|
/** Keep only `keep` rows along the batch axis (eviction), in place. */
|
|
280
246
|
filter(keep: number[]): void {
|
|
281
247
|
if (this.keys && this.values) {
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
mapTriple(t, (a) => ops.takeAxis(a, idxArr, 0));
|
|
285
|
-
const k = take(this.keys);
|
|
286
|
-
const v = take(this.values);
|
|
287
|
-
idxArr.dispose();
|
|
248
|
+
const k = quantizedRowStorage.takeRows(this.keys, keep);
|
|
249
|
+
const v = quantizedRowStorage.takeRows(this.values, keep);
|
|
288
250
|
disposeTriple(this.keys);
|
|
289
251
|
disposeTriple(this.values);
|
|
290
252
|
this.keys = k;
|
|
291
253
|
this.values = v;
|
|
292
254
|
}
|
|
293
|
-
this.
|
|
294
|
-
this.leftPad = keep.map((i) => this.leftPad[i]!);
|
|
255
|
+
this.#rows.filter(keep);
|
|
295
256
|
this.releaseRopeArr(); // safe here: filter runs between steps
|
|
296
257
|
}
|
|
297
258
|
|
|
259
|
+
filterRows(keep: readonly number[]): void { this.filter([...keep]); }
|
|
260
|
+
|
|
298
261
|
override isTrimmable(): boolean {
|
|
299
262
|
return false; // batched rows never re-enter the prompt cache directly
|
|
300
263
|
}
|
|
@@ -317,30 +280,13 @@ export class BatchedRotatingQuantCache extends RotatingQuantizedKVCache {
|
|
|
317
280
|
const width = Math.max(...lens, 0);
|
|
318
281
|
const leftPad = lens.map((l) => width - l);
|
|
319
282
|
const cache = new BatchedRotatingQuantCache(maxSize, groupSize, bits, leftPad, offsets);
|
|
283
|
+
cache.#rows.restoreMerged(width, offsets);
|
|
284
|
+
cache.offset = cache.#rows.totalOffset;
|
|
285
|
+
cache.ringIdx = cache.#rows.ringIndex;
|
|
320
286
|
if (width === 0) return cache;
|
|
321
287
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const [B, H, , D] = a.shape as [number, number, number, number];
|
|
325
|
-
const z = ops.zeros([B, H, pad, D], a.dtype);
|
|
326
|
-
const out = ops.concatAxis([z, a], 2);
|
|
327
|
-
z.dispose();
|
|
328
|
-
return out;
|
|
329
|
-
};
|
|
330
|
-
const padded = rows.map((r, i) => ({
|
|
331
|
-
keys: mapTriple(r.keys, (a) => padTo(a, leftPad[i]!)),
|
|
332
|
-
values: mapTriple(r.values, (a) => padTo(a, leftPad[i]!)),
|
|
333
|
-
}));
|
|
334
|
-
const cat = (pick: (r: QuantRow) => ops.QuantizedTensor): ops.QuantizedTensor => ({
|
|
335
|
-
packed: ops.concatAxis(padded.map((r) => pick(r).packed), 0),
|
|
336
|
-
scales: ops.concatAxis(padded.map((r) => pick(r).scales), 0),
|
|
337
|
-
biases: ops.concatAxis(padded.map((r) => pick(r).biases), 0),
|
|
338
|
-
});
|
|
339
|
-
cache.keys = cat((r) => r.keys);
|
|
340
|
-
cache.values = cat((r) => r.values);
|
|
341
|
-
for (const r of padded) { disposeTriple(r.keys); disposeTriple(r.values); }
|
|
342
|
-
cache.ringIdx = width;
|
|
343
|
-
cache.offset = width;
|
|
288
|
+
cache.keys = mergeStorageRows(quantizedRowStorage, rows.map((row) => row.keys), leftPad);
|
|
289
|
+
cache.values = mergeStorageRows(quantizedRowStorage, rows.map((row) => row.values), leftPad);
|
|
344
290
|
return cache;
|
|
345
291
|
}
|
|
346
292
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Host-side row and ring bookkeeping shared by plain and quantized storage. */
|
|
2
|
+
export class BatchedRotatingState {
|
|
3
|
+
readonly maxSize: number;
|
|
4
|
+
offsets: number[];
|
|
5
|
+
leftPad: number[];
|
|
6
|
+
ringIndex = 0;
|
|
7
|
+
totalOffset = 0;
|
|
8
|
+
rotated = false;
|
|
9
|
+
|
|
10
|
+
constructor(maxSize: number, leftPad: readonly number[], offsets?: readonly number[]) {
|
|
11
|
+
this.maxSize = maxSize;
|
|
12
|
+
this.leftPad = [...leftPad];
|
|
13
|
+
this.offsets = offsets ? [...offsets] : leftPad.map((pad) => -pad);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get batchSize(): number { return this.leftPad.length; }
|
|
17
|
+
get validLength(): number { return Math.min(this.totalOffset, this.maxSize); }
|
|
18
|
+
get trimmable(): boolean { return this.totalOffset < this.maxSize; }
|
|
19
|
+
|
|
20
|
+
markGrown(previousOffset: number): void {
|
|
21
|
+
this.ringIndex = previousOffset;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
trimOvershoot(tokens: number): void {
|
|
25
|
+
if (tokens <= 0) return;
|
|
26
|
+
this.ringIndex = this.maxSize;
|
|
27
|
+
this.leftPad = this.leftPad.map((pad) => pad - tokens);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Prepare an N-token write and return its physical ring column. */
|
|
31
|
+
beginWrite(tokens: number): number {
|
|
32
|
+
if (this.ringIndex === this.maxSize) {
|
|
33
|
+
this.rotated = true;
|
|
34
|
+
this.ringIndex = 0;
|
|
35
|
+
}
|
|
36
|
+
if (this.rotated) this.leftPad = this.leftPad.map((pad) => pad - tokens);
|
|
37
|
+
return this.ringIndex;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
commitWrite(tokens: number): void {
|
|
41
|
+
this.totalOffset += tokens;
|
|
42
|
+
this.offsets = this.offsets.map((offset) => offset + tokens);
|
|
43
|
+
this.ringIndex += tokens;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
filter(keep: readonly number[]): void {
|
|
47
|
+
this.offsets = keep.map((row) => this.offsets[row]!);
|
|
48
|
+
this.leftPad = keep.map((row) => this.leftPad[row]!);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
trim(tokens: number): number {
|
|
52
|
+
const amount = Math.min(this.totalOffset, tokens);
|
|
53
|
+
this.totalOffset -= amount;
|
|
54
|
+
this.ringIndex -= amount;
|
|
55
|
+
this.offsets = this.offsets.map((offset) => offset - amount);
|
|
56
|
+
return amount;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
restoreMerged(width: number, offsets: readonly number[]): void {
|
|
60
|
+
this.totalOffset = width;
|
|
61
|
+
this.ringIndex = width;
|
|
62
|
+
this.offsets = [...offsets];
|
|
63
|
+
this.rotated = false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Physical ranges that expose a ring buffer in temporal order. */
|
|
67
|
+
temporalRanges(bufferLength: number): readonly [number, number][] {
|
|
68
|
+
if (this.ringIndex === bufferLength) return [[0, bufferLength]];
|
|
69
|
+
if (this.ringIndex < this.totalOffset)
|
|
70
|
+
return [[this.ringIndex, bufferLength], [0, this.ringIndex]];
|
|
71
|
+
return [[0, this.ringIndex]];
|
|
72
|
+
}
|
|
73
|
+
}
|