@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,100 @@
|
|
|
1
|
+
// Video frame extraction using the bundled AVFoundation helper.
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { rm } from "node:fs/promises";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { runtimeValue } from "../../runtime/config";
|
|
8
|
+
import { FRAME_EXTRACT_BINARY } from "../../runtime/native";
|
|
9
|
+
import { decodeImage,type RGBImage } from "./preprocess";
|
|
10
|
+
|
|
11
|
+
export const VIDEO_SAMPLE_FPS = 2;
|
|
12
|
+
export const VIDEO_MAX_FRAMES = 768; // Qwen3VLVideoProcessor max_frames
|
|
13
|
+
|
|
14
|
+
/** Locate the bundled extractor or an explicit caller override. */
|
|
15
|
+
export async function resolveFrameExtract(): Promise<string | null> {
|
|
16
|
+
const explicit = runtimeValue("MLX_BUN_FRAME_EXTRACT");
|
|
17
|
+
if (explicit) return explicit;
|
|
18
|
+
return existsSync(FRAME_EXTRACT_BINARY) ? FRAME_EXTRACT_BINARY : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Decode a video container to sampled RGB frames. Throws with the
|
|
22
|
+
* sidecar's diagnostic on failure (the serve layer surfaces it as a 400,
|
|
23
|
+
* the afconvert pattern — timeout kill included). */
|
|
24
|
+
export async function extractVideoFrames(
|
|
25
|
+
bytes: Uint8Array,
|
|
26
|
+
opts: { fps?: number; maxFrames?: number } = {},
|
|
27
|
+
): Promise<RGBImage[]> {
|
|
28
|
+
const bin = await resolveFrameExtract();
|
|
29
|
+
if (!bin)
|
|
30
|
+
throw new Error(
|
|
31
|
+
"video input needs the frame-extraction sidecar (mlx-bun-frame-extract) " +
|
|
32
|
+
"— ships beside the release binary and in the native pack; from a " +
|
|
33
|
+
"source tree build it with `bun run --cwd packages/inference build:native` " +
|
|
34
|
+
"(Xcode CLT swiftc) or point MLX_BUN_FRAME_EXTRACT at it",
|
|
35
|
+
);
|
|
36
|
+
const base = join(tmpdir(), `mlx-bun-video-${randomUUID()}`);
|
|
37
|
+
// AVFoundation infers the container from the path EXTENSION — sniff the
|
|
38
|
+
// ISO-BMFF `ftyp` box (mp4/mov share it; the `qt ` major brand is
|
|
39
|
+
// QuickTime) so the temp file opens. Unknown magic defaults to .mp4.
|
|
40
|
+
const isFtyp = bytes.length >= 12 &&
|
|
41
|
+
bytes[4] === 0x66 && bytes[5] === 0x74 && bytes[6] === 0x79 && bytes[7] === 0x70;
|
|
42
|
+
const brand = isFtyp
|
|
43
|
+
? String.fromCharCode(bytes[8]!, bytes[9]!, bytes[10]!, bytes[11]!)
|
|
44
|
+
: "";
|
|
45
|
+
const ext = brand.startsWith("qt") ? "mov" : "mp4";
|
|
46
|
+
const inPath = `${base}.${ext}`;
|
|
47
|
+
const outDir = `${base}.frames`;
|
|
48
|
+
try {
|
|
49
|
+
// Inside the try so a part-way write failure (ENOSPC on a large clip)
|
|
50
|
+
// still hits the finally's cleanup instead of stranding the temp file.
|
|
51
|
+
await Bun.write(inPath, bytes);
|
|
52
|
+
const proc = Bun.spawn(
|
|
53
|
+
[
|
|
54
|
+
bin, inPath, outDir,
|
|
55
|
+
String(opts.fps ?? VIDEO_SAMPLE_FPS),
|
|
56
|
+
String(opts.maxFrames ?? VIDEO_MAX_FRAMES),
|
|
57
|
+
],
|
|
58
|
+
{ stdout: "pipe", stderr: "pipe" },
|
|
59
|
+
);
|
|
60
|
+
// Decoding is fast (hardware) but long clips take real time; a hung
|
|
61
|
+
// decoder must not pin the request forever — kill → nonzero → 400.
|
|
62
|
+
const killer = setTimeout(() => proc.kill(), 120_000);
|
|
63
|
+
const exit = await proc.exited;
|
|
64
|
+
clearTimeout(killer);
|
|
65
|
+
if (exit !== 0) {
|
|
66
|
+
const err = (await new Response(proc.stderr).text()).trim();
|
|
67
|
+
throw new Error(
|
|
68
|
+
`video decode failed (frame-extract exit ${exit}): ` +
|
|
69
|
+
`${err.split("\n").at(-1) || "unknown error"} — supported containers: ` +
|
|
70
|
+
`whatever AVFoundation decodes on this macOS (mp4/mov H.264/HEVC/ProRes/…)`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const head = (await new Response(proc.stdout).text()).trim();
|
|
74
|
+
const n = Number(/^N (\d+)/.exec(head)?.[1] ?? 0);
|
|
75
|
+
if (!n) throw new Error(`video decode produced no frames (${head})`);
|
|
76
|
+
// Aggregate decoded-RGB budget (belt over the sidecar's 1024px
|
|
77
|
+
// longest-edge cap): a valid low-bitrate clip must not balloon into
|
|
78
|
+
// gigabytes of frame buffers before preprocessing.
|
|
79
|
+
const w = Number(/W (\d+)/.exec(head)?.[1] ?? 0);
|
|
80
|
+
const hh = Number(/H (\d+)/.exec(head)?.[1] ?? 0);
|
|
81
|
+
const DECODED_BUDGET = 1536 * 1024 * 1024; // 1.5 GiB
|
|
82
|
+
if (n * w * hh * 3 > DECODED_BUDGET)
|
|
83
|
+
throw new Error(
|
|
84
|
+
`video decodes to ${(n * w * hh * 3 / 1024 / 1024).toFixed(0)} MB of ` +
|
|
85
|
+
`frames (${n} × ${w}x${hh}) — over the ${DECODED_BUDGET / 1024 / 1024} MB ` +
|
|
86
|
+
`budget; send a shorter or smaller clip`,
|
|
87
|
+
);
|
|
88
|
+
const frames: RGBImage[] = [];
|
|
89
|
+
for (let i = 0; i < n; i++) {
|
|
90
|
+
const f = join(outDir, `frame-${String(i).padStart(4, "0")}.png`);
|
|
91
|
+
frames.push(await decodeImage(new Uint8Array(await Bun.file(f).arrayBuffer())));
|
|
92
|
+
}
|
|
93
|
+
return frames;
|
|
94
|
+
} finally {
|
|
95
|
+
await Promise.allSettled([
|
|
96
|
+
rm(inPath, { force: true }),
|
|
97
|
+
rm(outDir, { recursive: true, force: true }),
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
3
|
+
import { MetalKernel } from "@mlx-bun/mlx/metal-kernel";
|
|
4
|
+
import { HEADER } from "../trellis/codebook";
|
|
5
|
+
import type { TrellisGeometry } from "../trellis/geometry";
|
|
6
|
+
|
|
7
|
+
// Fill an ANE weight buffer straight from Trellis codes: the vector-expand
|
|
8
|
+
// decode (four weights from one circular word window, the same
|
|
9
|
+
// trellis_val_rcp · scale values) of stored rows [0, ROWS) written as fp16 at
|
|
10
|
+
// OFFSET into `dst`, a zero-copy MLX wrap of the ANE IOSurface. Each thread
|
|
11
|
+
// decodes PER consecutive weights of a row, each four from one window and
|
|
12
|
+
// stored as one half4 (OFFSET a multiple of 4). Reads the full code tensor, so
|
|
13
|
+
// block-interleaved axis-0 codes need no slicing copy. `dst` is an MLX input
|
|
14
|
+
// the kernel writes through; `done` only carries the dependency. One kernel
|
|
15
|
+
// per code layout: row-major [rows, cols·K/32] and the 3-bit block interleave
|
|
16
|
+
// [cols/512, rows, 48].
|
|
17
|
+
|
|
18
|
+
/** The kernel body for one code layout, `blockWords` the address of the PER weights' code block. */
|
|
19
|
+
const source = (blockWords: string) => String.raw`
|
|
20
|
+
const uint c = thread_position_in_grid.x * PER;
|
|
21
|
+
const uint r = thread_position_in_grid.y;
|
|
22
|
+
if (c >= (uint)C || r >= (uint)ROWS) return;
|
|
23
|
+
constexpr uint wpb = BT * BITS / 32;
|
|
24
|
+
const uint block = c / BT, t = c % BT;
|
|
25
|
+
const device uint* bw = ${blockWords};
|
|
26
|
+
const float scale = float(scales[r]);
|
|
27
|
+
device half4* out = (device half4*)((device half*)dst + (ulong)OFFSET + (ulong)r * (uint)C + c);
|
|
28
|
+
#pragma clang loop unroll(full)
|
|
29
|
+
for (uint q = 0; q < PER / 4; q++) {
|
|
30
|
+
const uint pos = (BT - 1 - (t + 4 * q + 3)) * BITS, wi = pos >> 5, off = pos & 31u;
|
|
31
|
+
const ulong window = (ulong(bw[wi]) | (ulong(bw[wi + 1 == wpb ? 0 : wi + 1]) << 32)) >> off;
|
|
32
|
+
half4 v;
|
|
33
|
+
#pragma clang loop unroll(full)
|
|
34
|
+
for (uint i = 0; i < 4u; i++) v[i] = half(trellis_val_rcp(uint(window >> ((3 - i) * BITS)) & ((1u << L) - 1)) * scale);
|
|
35
|
+
out[q] = v;
|
|
36
|
+
}
|
|
37
|
+
if (c == 0u && r == 0u) done[0] = 1;
|
|
38
|
+
`;
|
|
39
|
+
const ROW_MAJOR_SOURCE = source("codes + (ulong)r * (C / BT) * wpb + block * wpb");
|
|
40
|
+
const K3_INTERLEAVED_SOURCE = source("codes + (ulong)(block / 2) * (ulong)R * 2 * wpb + (ulong)r * 2 * wpb + (block % 2) * wpb");
|
|
41
|
+
|
|
42
|
+
let rowMajor: MetalKernel | undefined, interleaved: MetalKernel | undefined;
|
|
43
|
+
|
|
44
|
+
export function trellisFillEligible(g: TrellisGeometry): boolean {
|
|
45
|
+
return g.L === 12 && g.T === 256 && (g.k === 2 || g.k === 3 || g.k === 4) && g.cols % 256 === 0 && !g.blockInterleave;
|
|
46
|
+
}
|
|
47
|
+
export function trellisFillK3InterleavedEligible(g: TrellisGeometry): boolean {
|
|
48
|
+
return g.L === 12 && g.T === 256 && g.k === 3 && g.cols % 512 === 0 && g.blockInterleave === 2;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Weights per thread by code width, measured on the M1 Max over one layer's 5888 rows: at 16, the 2-bit gate fills
|
|
52
|
+
* in 0.45 ms, the 3-bit gate in 0.79 and the 3-bit interleaved down in 0.72, against 0.57, 0.95 and 1.01 at four
|
|
53
|
+
* weights and four half stores per thread (the same values); the 4-bit gate in 0.51 at 8, against 0.97 at four and
|
|
54
|
+
* 0.70 at 16. */
|
|
55
|
+
const perThread = (g: TrellisGeometry) => g.k === 4 ? 8 : 16;
|
|
56
|
+
|
|
57
|
+
function launch(kernel: MetalKernel, codes: MlxArray, scales: MlxArray, g: TrellisGeometry, rows: number, dst: MlxArray, offset: number): MlxArray {
|
|
58
|
+
if (offset % 4) throw new Error(`ANE fill: offset ${offset} is not a multiple of 4 (half4 stores)`);
|
|
59
|
+
const per = perThread(g);
|
|
60
|
+
const [done] = kernel.apply([codes, scales, dst], {
|
|
61
|
+
outputs: [{ shape: [1], dtype: Dtype.int32 }],
|
|
62
|
+
grid: [g.cols / per, rows, 1], threadGroup: [128, 1, 1],
|
|
63
|
+
templateInts: { R: g.rows, ROWS: rows, C: g.cols, BT: g.T, BITS: g.k, L: g.L, OFFSET: offset, PER: per },
|
|
64
|
+
});
|
|
65
|
+
return done!;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Decode stored rows [0, rows) of row-major codes into dst[OFFSET + r·C + c] (fp16). */
|
|
69
|
+
export function trellisFillHalf(codes: MlxArray, scales: MlxArray, g: TrellisGeometry, rows: number, dst: MlxArray, offset: number): MlxArray {
|
|
70
|
+
if (!trellisFillEligible(g)) throw new Error("trellisFillHalf: needs row-major codes (L 12, T 256)");
|
|
71
|
+
rowMajor ??= new MetalKernel({ name: "mlx_bun_ane_trellis_fill", inputNames: ["codes", "scales", "dst"], outputNames: ["done"],
|
|
72
|
+
source: ROW_MAJOR_SOURCE, header: HEADER, ensureRowContiguous: true });
|
|
73
|
+
return launch(rowMajor, codes, scales, g, rows, dst, offset);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** trellisFillHalf for 3-bit block-interleaved codes [cols/512, rows, 48]. */
|
|
77
|
+
export function trellisFillHalfK3Interleaved(codes: MlxArray, scales: MlxArray, g: TrellisGeometry, rows: number, dst: MlxArray, offset: number): MlxArray {
|
|
78
|
+
if (!trellisFillK3InterleavedEligible(g)) throw new Error("trellisFillHalfK3Interleaved: needs 3-bit block-interleaved codes");
|
|
79
|
+
interleaved ??= new MetalKernel({ name: "mlx_bun_ane_trellis_fill_k3i", inputNames: ["codes", "scales", "dst"], outputNames: ["done"],
|
|
80
|
+
source: K3_INTERLEAVED_SOURCE, header: HEADER, ensureRowContiguous: true });
|
|
81
|
+
return launch(interleaved, codes, scales, g, rows, dst, offset);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Copy src (any float dtype) as fp16 into dst at OFFSET (elements): the
|
|
85
|
+
// activation and dequantized-weight writes into ANE input buffers.
|
|
86
|
+
const WRITE_SOURCE = String.raw`
|
|
87
|
+
const uint i = thread_position_in_grid.x;
|
|
88
|
+
if (i < (uint)N) ((device half*)dst)[OFFSET + i] = half(float(src[i]));
|
|
89
|
+
if (i == 0u) done[0] = 1;
|
|
90
|
+
`;
|
|
91
|
+
let writer: MetalKernel | undefined;
|
|
92
|
+
|
|
93
|
+
/** dst[OFFSET + i] = half(src[i]) for every element of src; `done` carries the dependency. */
|
|
94
|
+
export function writeHalf(dst: MlxArray, src: MlxArray, offset: number): MlxArray {
|
|
95
|
+
writer ??= new MetalKernel({ name: "mlx_bun_ane_write_half", inputNames: ["src", "dst"], outputNames: ["done"],
|
|
96
|
+
source: WRITE_SOURCE, ensureRowContiguous: true });
|
|
97
|
+
const [done] = writer.apply([src, dst], { outputs: [{ shape: [1], dtype: Dtype.int32 }],
|
|
98
|
+
grid: [Math.ceil(src.size / 256) * 256, 1, 1], threadGroup: [256, 1, 1],
|
|
99
|
+
templateDtypes: { T: src.dtype }, templateInts: { N: src.size, OFFSET: offset } });
|
|
100
|
+
return done!;
|
|
101
|
+
}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { dlopen, FFIType, ptr, toArrayBuffer, type Pointer } from "bun:ffi";
|
|
2
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
3
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
4
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
5
|
+
import { ANE_LIBRARY as LIBRARY } from "../../runtime/native";
|
|
6
|
+
|
|
7
|
+
// Apple Neural Engine sidecar (native/ane-bridge.m): programs compiled from MIL
|
|
8
|
+
// text through the private AppleNeuralEngine.framework, with IOSurface
|
|
9
|
+
// inputs/outputs the host or the GPU (zero-copy MLX wraps) fill. Compiling is
|
|
10
|
+
// the install step (`AneProgram.compile`, run for every shipped composition by
|
|
11
|
+
// `compileAnePrograms` in models/ane-install); it leaves each program in the
|
|
12
|
+
// system compile cache. A graph binding Neural Engine layers
|
|
13
|
+
// loads its programs from that cache when it is
|
|
14
|
+
// constructed and never compiles one: a bridge that does not load, or a program
|
|
15
|
+
// the cache lacks, fails that load with `AneBridgeError`.
|
|
16
|
+
|
|
17
|
+
/** The Neural Engine bridge did not load, a program is not compiled on this Mac,
|
|
18
|
+
* or the compiler refused one: loading a graph that binds the bridge fails
|
|
19
|
+
* with it. */
|
|
20
|
+
export class AneBridgeError extends Error {
|
|
21
|
+
override readonly name = "AneBridgeError";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** What a program missing from the compile cache asks for. */
|
|
25
|
+
const INSTALL_STEP = "run the install step, `mlx-bun compile-ane`, once " +
|
|
26
|
+
"(a library caller: compileAnePrograms from @mlx-bun/inference/models, under the executable that serves)";
|
|
27
|
+
|
|
28
|
+
/** One ANE program as the compiler takes it. Programs with the same MIL and
|
|
29
|
+
* weights are one program: one compile, and one loaded model for the layers
|
|
30
|
+
* that share it. */
|
|
31
|
+
export interface AneProgramSpec {
|
|
32
|
+
/** Names the program in messages, e.g. `MLP slice 5120×9600, 2048 rows`. */
|
|
33
|
+
readonly label: string;
|
|
34
|
+
readonly mil: string;
|
|
35
|
+
/** IOSurface bytes of each MIL input, in symbol order (input names sorted). */
|
|
36
|
+
readonly inputBytes: readonly number[];
|
|
37
|
+
readonly outputBytes: number;
|
|
38
|
+
/** BLOBFILE payloads by file name (already in blob format). */
|
|
39
|
+
readonly weights?: Readonly<Record<string, Uint8Array>>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A key equal for two specs exactly when they are one program (same MIL and weights). */
|
|
43
|
+
export function aneProgramKey(spec: AneProgramSpec): string {
|
|
44
|
+
const weights = Object.entries(spec.weights ?? {}).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)
|
|
45
|
+
.map(([name, data]) => `${name}:${data.byteLength}:${Bun.hash(data).toString(16)}`);
|
|
46
|
+
return `${Bun.hash(spec.mil).toString(16)}:${spec.mil.length}|${weights.join(",")}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** `compiled`: the compiler ran now; `cached`: the system compile cache already held the program. */
|
|
50
|
+
export type AneCompileStatus = "compiled" | "cached";
|
|
51
|
+
|
|
52
|
+
type Lib = ReturnType<typeof open>;
|
|
53
|
+
function open() {
|
|
54
|
+
const source = [FFIType.ptr, FFIType.i32, FFIType.ptr, FFIType.ptr, FFIType.ptr] as const;
|
|
55
|
+
return dlopen(LIBRARY, {
|
|
56
|
+
mbane_program_create: { args: [...source, FFIType.i32, FFIType.ptr, FFIType.u64, FFIType.ptr, FFIType.ptr, FFIType.ptr,
|
|
57
|
+
FFIType.i32], returns: FFIType.ptr },
|
|
58
|
+
mbane_program_compile: { args: [...source, FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
|
|
59
|
+
mbane_program_cached: { args: [...source, FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
|
|
60
|
+
mbane_program_purge: { args: [...source, FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
|
|
61
|
+
mbane_input: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.ptr },
|
|
62
|
+
mbane_output: { args: [FFIType.ptr], returns: FFIType.ptr },
|
|
63
|
+
mbane_eval_async: { args: [FFIType.ptr], returns: FFIType.i32 },
|
|
64
|
+
mbane_wait: { args: [FFIType.ptr, FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
|
|
65
|
+
mbane_free: { args: [FFIType.ptr], returns: FFIType.void },
|
|
66
|
+
}).symbols;
|
|
67
|
+
}
|
|
68
|
+
let lib: Lib | undefined;
|
|
69
|
+
/** The bridge, loaded on first use. */
|
|
70
|
+
function native(): Lib {
|
|
71
|
+
if (lib) return lib;
|
|
72
|
+
try { lib = open(); }
|
|
73
|
+
catch (error) { throw new AneBridgeError(`the Neural Engine bridge did not load (${LIBRARY})`, { cause: error }); }
|
|
74
|
+
return lib;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const errorBuffer = new Uint8Array(512);
|
|
78
|
+
const errorText = () => new TextDecoder().decode(errorBuffer.subarray(0, errorBuffer.indexOf(0)));
|
|
79
|
+
const cstring = (text: string) => new TextEncoder().encode(text + "\0");
|
|
80
|
+
|
|
81
|
+
/** A spec's MIL and weights as the bridge's leading five arguments. The buffers
|
|
82
|
+
* live in the returned array, so it must stay reachable for the call. */
|
|
83
|
+
function source(spec: AneProgramSpec): [Pointer, number, Pointer | null, Pointer | null, Pointer | null, ...unknown[]] {
|
|
84
|
+
const entries = Object.entries(spec.weights ?? {});
|
|
85
|
+
const names = entries.map(([name]) => cstring(name));
|
|
86
|
+
const namePtrs = BigUint64Array.from(names.map(n => BigInt(ptr(n))));
|
|
87
|
+
const dataPtrs = BigUint64Array.from(entries.map(([, data]) => BigInt(ptr(data))));
|
|
88
|
+
const dataLens = BigUint64Array.from(entries.map(([, data]) => BigInt(data.byteLength)));
|
|
89
|
+
const text = cstring(spec.mil);
|
|
90
|
+
const some = entries.length > 0;
|
|
91
|
+
return [ptr(text), entries.length, some ? ptr(namePtrs) : null, some ? ptr(dataPtrs) : null, some ? ptr(dataLens) : null,
|
|
92
|
+
text, names, namePtrs, dataPtrs, dataLens];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Bytes the bridge allocates for a buffer of `bytes` (native/ane-bridge.m make_surface: whole 64 KiB units), so a
|
|
96
|
+
* wrap of the allocation is a page multiple, which Metal maps without a copy. */
|
|
97
|
+
const allocated = (bytes: number) => Math.max(65536, Math.ceil(bytes / 65536) * 65536);
|
|
98
|
+
|
|
99
|
+
/** The MLX wraps of one set of program buffers (IOSurfaces), each a no-copy
|
|
100
|
+
* Metal buffer that MLX also enters in its residency set: made once per
|
|
101
|
+
* buffer, on first use, and shared by every program loaded on the buffers, the
|
|
102
|
+
* last of which releases them before the buffers. Also the one array that may
|
|
103
|
+
* still read the output lazily (`defer`), evaluated before the next run. */
|
|
104
|
+
class Surfaces {
|
|
105
|
+
holders = 1;
|
|
106
|
+
readonly #inputs = new Map<number, MlxArray>();
|
|
107
|
+
#output: MlxArray | undefined;
|
|
108
|
+
#reader: MlxArray | undefined;
|
|
109
|
+
/** Byte sizes of the buffers: the first program's (any program loaded on them needs no more). */
|
|
110
|
+
constructor(readonly inputBytes: readonly number[], readonly outputBytes: number) {}
|
|
111
|
+
input(i: number, address: number): MlxArray {
|
|
112
|
+
let wrap = this.#inputs.get(i);
|
|
113
|
+
if (!wrap) this.#inputs.set(i, wrap = MlxArray.fromPointer(address, [allocated(this.inputBytes[i]!) / 2], Dtype.float16));
|
|
114
|
+
return wrap;
|
|
115
|
+
}
|
|
116
|
+
output(address: number): MlxArray {
|
|
117
|
+
return this.#output ??= MlxArray.fromPointer(address, [allocated(this.outputBytes) / 2], Dtype.float16);
|
|
118
|
+
}
|
|
119
|
+
defer(reader: MlxArray): void { this.settle(); this.#reader = reader; }
|
|
120
|
+
/** Evaluates and releases the deferred reader, if any (nothing to wait for once its graph ran). */
|
|
121
|
+
settle(): void {
|
|
122
|
+
const reader = this.#reader;
|
|
123
|
+
if (!reader) return;
|
|
124
|
+
this.#reader = undefined;
|
|
125
|
+
try { reader.eval(); } finally { reader.dispose(); }
|
|
126
|
+
}
|
|
127
|
+
dispose(): void {
|
|
128
|
+
this.#reader?.dispose();
|
|
129
|
+
this.#reader = undefined;
|
|
130
|
+
for (const wrap of this.#inputs.values()) wrap.dispose();
|
|
131
|
+
this.#inputs.clear();
|
|
132
|
+
this.#output?.dispose();
|
|
133
|
+
this.#output = undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** One loaded ANE program. Inputs follow MIL symbol order (input names sorted).
|
|
138
|
+
* A loaded program holds one of the ANE driver's direct-path clients, about a
|
|
139
|
+
* thousand on the whole machine, until its last holder is disposed; creating
|
|
140
|
+
* one while none is free blocks (native/ane-bridge.m). */
|
|
141
|
+
export class AneProgram implements Disposable {
|
|
142
|
+
readonly #lib: Lib;
|
|
143
|
+
#handle: Pointer | null;
|
|
144
|
+
readonly #surfaces: Surfaces;
|
|
145
|
+
|
|
146
|
+
private constructor(lib: Lib, handle: Pointer, surfaces: Surfaces, readonly inputBytes: readonly number[], readonly outputBytes: number) {
|
|
147
|
+
this.#lib = lib;
|
|
148
|
+
this.#handle = handle;
|
|
149
|
+
this.#surfaces = surfaces;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Loads `spec` from the system compile cache (about 4 ms); never compiles.
|
|
153
|
+
* `share`: a program that never runs at the same time as this one. With the
|
|
154
|
+
* same MIL and weights the result holds that same loaded program; otherwise
|
|
155
|
+
* a new program loads on its buffers (each at least as large). Either way
|
|
156
|
+
* the shared resources live until every holder is disposed. Throws
|
|
157
|
+
* `AneBridgeError` when the bridge does not load, when the program is not
|
|
158
|
+
* compiled on this Mac (naming the install step, {@link AneProgram.compile}),
|
|
159
|
+
* or when it does not load. */
|
|
160
|
+
static create(spec: AneProgramSpec, share?: AneProgram): AneProgram {
|
|
161
|
+
const l = native(), args = source(spec), uncompiled = new Int32Array(1);
|
|
162
|
+
const sizes = BigUint64Array.from(spec.inputBytes.map(BigInt));
|
|
163
|
+
errorBuffer.fill(0);
|
|
164
|
+
const handle = l.mbane_program_create(args[0], args[1], args[2], args[3], args[4], spec.inputBytes.length, ptr(sizes),
|
|
165
|
+
spec.outputBytes, share ? share.#handle : null, ptr(uncompiled), ptr(errorBuffer), errorBuffer.length);
|
|
166
|
+
if (!handle) throw new AneBridgeError(uncompiled[0]
|
|
167
|
+
? `Neural Engine program ${spec.label} is not compiled on this Mac; ${INSTALL_STEP}`
|
|
168
|
+
: `${spec.label}: ${errorText() || "ANE program creation failed"}`);
|
|
169
|
+
const surfaces = share ? share.#surfaces : new Surfaces(spec.inputBytes, spec.outputBytes);
|
|
170
|
+
if (share) surfaces.holders++;
|
|
171
|
+
return new AneProgram(l, handle, surfaces, spec.inputBytes, spec.outputBytes);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The install step for one program: compiles `spec` into the system compile
|
|
175
|
+
* cache unless it is there already. Loads nothing and holds no ANE driver
|
|
176
|
+
* client. The cache is per code-signing identity, so the executable that will
|
|
177
|
+
* serve is the one that compiles. Throws `AneBridgeError` when the bridge does
|
|
178
|
+
* not load or the compiler refuses the program. */
|
|
179
|
+
static compile(spec: AneProgramSpec): AneCompileStatus {
|
|
180
|
+
const args = source(spec);
|
|
181
|
+
errorBuffer.fill(0);
|
|
182
|
+
const status = native().mbane_program_compile(args[0], args[1], args[2], args[3], args[4], ptr(errorBuffer), errorBuffer.length);
|
|
183
|
+
if (status === 0) throw new AneBridgeError(`${spec.label}: ${errorText() || "ANE compile failed"}`);
|
|
184
|
+
return status === 1 ? "compiled" : "cached";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Whether the system compile cache holds `spec` for this process (the framework's `compiledModelExists`). */
|
|
188
|
+
static cached(spec: AneProgramSpec): boolean {
|
|
189
|
+
const args = source(spec);
|
|
190
|
+
errorBuffer.fill(0);
|
|
191
|
+
const status = native().mbane_program_cached(args[0], args[1], args[2], args[3], args[4], ptr(errorBuffer), errorBuffer.length);
|
|
192
|
+
if (status < 0) throw new AneBridgeError(`${spec.label}: ${errorText()}`);
|
|
193
|
+
return status === 1;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Removes `spec` from the system compile cache (the framework's
|
|
197
|
+
* `purgeCompiledModel`): `create` refuses it until it is compiled again.
|
|
198
|
+
* Programs already loaded keep running. */
|
|
199
|
+
static purge(spec: AneProgramSpec): void {
|
|
200
|
+
const args = source(spec);
|
|
201
|
+
errorBuffer.fill(0);
|
|
202
|
+
const status = native().mbane_program_purge(args[0], args[1], args[2], args[3], args[4], ptr(errorBuffer), errorBuffer.length);
|
|
203
|
+
if (status !== 1) throw new AneBridgeError(`${spec.label}: ${status < 0 ? errorText() : "still in the compile cache after the purge"}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Base address of input i, locked for writes until the next evaluation. */
|
|
207
|
+
input(i: number): number { return Number(this.#lib.mbane_input(this.#handle!, i)); }
|
|
208
|
+
inputView(i: number): Uint8Array { return new Uint8Array(toArrayBuffer(this.#lib.mbane_input(this.#handle!, i)!, 0, this.inputBytes[i]!)); }
|
|
209
|
+
/** Zero-copy fp16 MLX wrap of input i's whole buffer (at least its
|
|
210
|
+
* `inputBytes`), locked for writes until the next evaluation (the GPU writes
|
|
211
|
+
* it through a kernel). Borrowed: the buffers own it, and every program
|
|
212
|
+
* loaded on them returns the same array, so a caller never disposes it. */
|
|
213
|
+
inputArray(i: number): MlxArray { return this.#surfaces.input(i, this.input(i)); }
|
|
214
|
+
/** Zero-copy fp16 MLX wrap of the whole output buffer, valid until the next
|
|
215
|
+
* evaluation; borrowed as {@link inputArray}. */
|
|
216
|
+
outputArray(): MlxArray { return this.#surfaces.output(Number(this.#lib.mbane_output(this.#handle!))); }
|
|
217
|
+
/** The output's first `shape` elements as an owned fp16 view (row-major). */
|
|
218
|
+
output(shape: number[]): MlxArray {
|
|
219
|
+
using front = this.outputArray().slice([0], [shape.reduce((a, b) => a * b, 1)]);
|
|
220
|
+
return ops.reshape(front, shape);
|
|
221
|
+
}
|
|
222
|
+
/** Takes `reader` (owned), an array that reads the output lazily: the next
|
|
223
|
+
* evaluation of any program on these buffers evaluates it first, so an
|
|
224
|
+
* output is never overwritten before it is read, and then releases it. */
|
|
225
|
+
deferOutput(reader: MlxArray): void { this.#surfaces.defer(reader); }
|
|
226
|
+
|
|
227
|
+
/** Starts the evaluation, once a deferred output reader (`deferOutput`) has run. */
|
|
228
|
+
evalAsync(): void {
|
|
229
|
+
if (this.#handle) this.#surfaces.settle();
|
|
230
|
+
if (!this.#lib.mbane_eval_async(this.#handle!)) throw new Error("ANE evaluation already pending");
|
|
231
|
+
}
|
|
232
|
+
wait(): void {
|
|
233
|
+
errorBuffer.fill(0);
|
|
234
|
+
if (!this.#lib.mbane_wait(this.#handle!, ptr(errorBuffer), errorBuffer.length)) throw new Error(errorText());
|
|
235
|
+
}
|
|
236
|
+
[Symbol.dispose](): void { this.dispose(); }
|
|
237
|
+
dispose(): void {
|
|
238
|
+
if (!this.#handle) return;
|
|
239
|
+
if (--this.#surfaces.holders === 0) this.#surfaces.dispose();
|
|
240
|
+
this.#lib.mbane_free(this.#handle);
|
|
241
|
+
this.#handle = null;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---- MIL builders ----------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
const MIL_HEADER = `program(1.3)
|
|
248
|
+
[buildInfo = dict<string, string>({{"coremlc-component-MIL", "3510.2.1"}, {"coremlc-version", "3505.4.1"}, {"coremltools-component-milinternal", ""}, {"coremltools-version", "9.0"}})]
|
|
249
|
+
{
|
|
250
|
+
`;
|
|
251
|
+
|
|
252
|
+
/** BLOBFILE payload (offset 64 holds the data header, data at 128). */
|
|
253
|
+
export function weightBlob(bytes: Uint8Array): Uint8Array {
|
|
254
|
+
const out = new Uint8Array(128 + bytes.byteLength);
|
|
255
|
+
const view = new DataView(out.buffer);
|
|
256
|
+
out[0] = 0x01; out[4] = 0x02;
|
|
257
|
+
out.set([0xef, 0xbe, 0xad, 0xde, 0x01], 64);
|
|
258
|
+
view.setUint32(72, bytes.byteLength, true);
|
|
259
|
+
view.setUint32(80, 128, true);
|
|
260
|
+
out.set(bytes, 128);
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const BOOLS = [` bool ff = const()[name=string("ff"), val=bool(false)];`, ` bool tt = const()[name=string("tt"), val=bool(true)];`];
|
|
265
|
+
|
|
266
|
+
/** Reduction slices summed inside a streamed-weight program unless its caller
|
|
267
|
+
* names others: 8 runs ~2.5x faster than one on M4 Pro. A reduction not
|
|
268
|
+
* divisible by the slices stays whole. */
|
|
269
|
+
const CHUNKS = 8;
|
|
270
|
+
|
|
271
|
+
/** a[1,1,rows,red] · b[1,1,cols,red]ᵀ → out, the reduction split into `slices`
|
|
272
|
+
* slices summed inside the program. `tag` keeps value names unique. */
|
|
273
|
+
function chunkedMatmulT(lines: string[], out: string, a: string, b: string, rows: number, cols: number, red: number, tag: string,
|
|
274
|
+
slices = CHUNKS): void {
|
|
275
|
+
const chunks = red % slices ? 1 : slices;
|
|
276
|
+
const cc = red / chunks;
|
|
277
|
+
let last = "";
|
|
278
|
+
for (let c = 0; c < chunks; c++) {
|
|
279
|
+
lines.push(` tensor<int32, [4]> ${tag}b${c} = const()[name=string("${tag}b${c}"), val=tensor<int32, [4]>([0, 0, 0, ${c * cc}])];`,
|
|
280
|
+
` tensor<int32, [4]> ${tag}ea${c} = const()[name=string("${tag}ea${c}"), val=tensor<int32, [4]>([1, 1, ${rows}, ${(c + 1) * cc}])];`,
|
|
281
|
+
` tensor<int32, [4]> ${tag}eb${c} = const()[name=string("${tag}eb${c}"), val=tensor<int32, [4]>([1, 1, ${cols}, ${(c + 1) * cc}])];`,
|
|
282
|
+
` tensor<fp16, [1, 1, ${rows}, ${cc}]> ${tag}sa${c} = slice_by_index(x=${a}, begin=${tag}b${c}, end=${tag}ea${c})[name=string("${tag}sa${c}")];`,
|
|
283
|
+
` tensor<fp16, [1, 1, ${cols}, ${cc}]> ${tag}sb${c} = slice_by_index(x=${b}, begin=${tag}b${c}, end=${tag}eb${c})[name=string("${tag}sb${c}")];`,
|
|
284
|
+
` tensor<fp16, [1, 1, ${rows}, ${cols}]> ${tag}p${c} = matmul(transpose_x=ff, transpose_y=tt, x=${tag}sa${c}, y=${tag}sb${c})[name=string("${tag}p${c}")];`);
|
|
285
|
+
const p = `${tag}p${c}`;
|
|
286
|
+
if (last) {
|
|
287
|
+
lines.push(` tensor<fp16, [1, 1, ${rows}, ${cols}]> ${tag}s${c} = add(x=${last}, y=${p})[name=string("${tag}s${c}")];`);
|
|
288
|
+
last = `${tag}s${c}`;
|
|
289
|
+
} else last = p;
|
|
290
|
+
}
|
|
291
|
+
lines.push(` tensor<fp16, [1, 1, ${rows}, ${cols}]> ${out} = identity(x=${last})[name=string("${out}")];`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** y[S, Cout] = x[S, Cin] · w[Cout, Cin]ᵀ, w streamed (inputs: w=0, x=1), the
|
|
295
|
+
* reduction in `slices` slices (default CHUNKS). */
|
|
296
|
+
export function streamedMatmulProgram(cin: number, cout: number, seq: number, slices = CHUNKS): AneProgramSpec {
|
|
297
|
+
const lines = [` func main<ios18>(tensor<fp16, [1, 1, ${seq}, ${cin}]> x, tensor<fp16, [1, 1, ${cout}, ${cin}]> w) {`, ...BOOLS];
|
|
298
|
+
chunkedMatmulT(lines, "y", "x", "w", seq, cout, cin, "m", slices);
|
|
299
|
+
return { label: `projection ${cin}→${cout}, ${seq} rows${slices === CHUNKS ? "" : `, ${slices} slices`}`,
|
|
300
|
+
mil: MIL_HEADER + lines.join("\n") + `\n } -> (y);\n}\n`,
|
|
301
|
+
inputBytes: [cout * cin * 2, seq * cin * 2], outputBytes: seq * cout * 2 };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** One MLP channel slice with streamed weights:
|
|
305
|
+
* y[S, D] = (silu(x·wgᵀ) ⊙ (x·wuᵀ)) · wd, wg/wu/wd [N, D] (wd: the down
|
|
306
|
+
* projection's rows for the same N channels), each matmul's reduction in
|
|
307
|
+
* `slices` slices (default CHUNKS). Inputs in symbol order: wd=0, wg=1, wu=2,
|
|
308
|
+
* x=3. Measured 10.2 TFLOPS (D 5120, N 9600, S 2048, 8 slices, M4 Pro). */
|
|
309
|
+
export function mlpSliceProgram(D: number, N: number, S: number, slices = CHUNKS): AneProgramSpec {
|
|
310
|
+
return { label: `MLP slice ${D}×${N}, ${S} rows${slices === CHUNKS ? "" : `, ${slices} slices`}`, mil: mlpSliceMil(D, N, S, slices),
|
|
311
|
+
inputBytes: [N * D * 2, N * D * 2, N * D * 2, S * D * 2], outputBytes: S * D * 2 };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function mlpSliceMil(D: number, N: number, S: number, slices: number): string {
|
|
315
|
+
const lines = [` func main<ios18>(tensor<fp16, [1, 1, ${N}, ${D}]> wd, tensor<fp16, [1, 1, ${N}, ${D}]> wg, ` +
|
|
316
|
+
`tensor<fp16, [1, 1, ${N}, ${D}]> wu, tensor<fp16, [1, 1, ${S}, ${D}]> x) {`, ...BOOLS];
|
|
317
|
+
chunkedMatmulT(lines, "g", "x", "wg", S, N, D, "G", slices);
|
|
318
|
+
chunkedMatmulT(lines, "u", "x", "wu", S, N, D, "U", slices);
|
|
319
|
+
lines.push(` tensor<fp16, [1, 1, ${S}, ${N}]> sg = silu(x=g)[name=string("sg")];`,
|
|
320
|
+
` tensor<fp16, [1, 1, ${S}, ${N}]> h = mul(x=sg, y=u)[name=string("h")];`);
|
|
321
|
+
const chunksDown = N % slices ? 1 : slices;
|
|
322
|
+
const cc = N / chunksDown;
|
|
323
|
+
let last = "";
|
|
324
|
+
for (let c = 0; c < chunksDown; c++) {
|
|
325
|
+
lines.push(` tensor<int32, [4]> Hb${c} = const()[name=string("Hb${c}"), val=tensor<int32, [4]>([0, 0, 0, ${c * cc}])];`,
|
|
326
|
+
` tensor<int32, [4]> He${c} = const()[name=string("He${c}"), val=tensor<int32, [4]>([1, 1, ${S}, ${(c + 1) * cc}])];`,
|
|
327
|
+
` tensor<int32, [4]> Wb${c} = const()[name=string("Wb${c}"), val=tensor<int32, [4]>([0, 0, ${c * cc}, 0])];`,
|
|
328
|
+
` tensor<int32, [4]> We${c} = const()[name=string("We${c}"), val=tensor<int32, [4]>([1, 1, ${(c + 1) * cc}, ${D}])];`,
|
|
329
|
+
` tensor<fp16, [1, 1, ${S}, ${cc}]> hs${c} = slice_by_index(x=h, begin=Hb${c}, end=He${c})[name=string("hs${c}")];`,
|
|
330
|
+
` tensor<fp16, [1, 1, ${cc}, ${D}]> ws${c} = slice_by_index(x=wd, begin=Wb${c}, end=We${c})[name=string("ws${c}")];`,
|
|
331
|
+
` tensor<fp16, [1, 1, ${S}, ${D}]> d${c} = matmul(transpose_x=ff, transpose_y=ff, x=hs${c}, y=ws${c})[name=string("d${c}")];`);
|
|
332
|
+
if (last) { lines.push(` tensor<fp16, [1, 1, ${S}, ${D}]> ds${c} = add(x=${last}, y=d${c})[name=string("ds${c}")];`); last = `ds${c}`; }
|
|
333
|
+
else last = `d${c}`;
|
|
334
|
+
}
|
|
335
|
+
return MIL_HEADER + lines.join("\n") + `\n } -> (${last});\n}\n`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** INT8 const-weight linear over channel-major activations: y[1, Cout, 1, S],
|
|
339
|
+
* weights `q` [Cout, Cin] with one fp16 scale (bits) per output row. */
|
|
340
|
+
export function int8LinearProgram(cin: number, cout: number, seq: number, q: Int8Array, scales: Uint16Array): AneProgramSpec {
|
|
341
|
+
return { label: `INT8 linear ${cin}→${cout}, ${seq} rows`, mil: int8LinearMil(cin, cout, seq),
|
|
342
|
+
inputBytes: [cin * seq * 2], outputBytes: cout * seq * 2,
|
|
343
|
+
weights: { "weight_data.bin": weightBlob(new Uint8Array(q.buffer, q.byteOffset, q.byteLength)),
|
|
344
|
+
"weight_scale.bin": weightBlob(new Uint8Array(scales.buffer, scales.byteOffset, scales.byteLength)) } };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function int8LinearMil(cin: number, cout: number, seq: number): string {
|
|
348
|
+
return MIL_HEADER + [
|
|
349
|
+
` func main<ios18>(tensor<fp16, [1, ${cin}, 1, ${seq}]> x) {`,
|
|
350
|
+
` tensor<int8, [${cout}, ${cin}, 1, 1]> wd = const()[name=string("wd"), val=tensor<int8, [${cout}, ${cin}, 1, 1]>(BLOBFILE(path=string("@model_path/weights/weight_data.bin"), offset=uint64(64)))];`,
|
|
351
|
+
` tensor<fp16, [${cout}, 1, 1, 1]> ws = const()[name=string("ws"), val=tensor<fp16, [${cout}, 1, 1, 1]>(BLOBFILE(path=string("@model_path/weights/weight_scale.bin"), offset=uint64(64)))];`,
|
|
352
|
+
` tensor<fp16, [${cout}, ${cin}, 1, 1]> w = constexpr_blockwise_shift_scale(data=wd, scale=ws)[name=string("dequant")];`,
|
|
353
|
+
` string pt = const()[name=string("pt"), val=string("valid")];`,
|
|
354
|
+
` tensor<int32, [2]> st = const()[name=string("st"), val=tensor<int32, [2]>([1,1])];`,
|
|
355
|
+
` tensor<int32, [4]> pd = const()[name=string("pd"), val=tensor<int32, [4]>([0,0,0,0])];`,
|
|
356
|
+
` tensor<int32, [2]> dl = const()[name=string("dl"), val=tensor<int32, [2]>([1,1])];`,
|
|
357
|
+
` int32 gr = const()[name=string("gr"), val=int32(1)];`,
|
|
358
|
+
` tensor<fp16, [1, ${cout}, 1, ${seq}]> y = conv(dilations=dl, groups=gr, pad=pd, pad_type=pt, strides=st, weight=w, x=x)[name=string("conv")];`,
|
|
359
|
+
].join("\n") + `\n } -> (y);\n}\n`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ---- Convenience wrappers --------------------------------------------------
|
|
363
|
+
|
|
364
|
+
/** INT8 linear with resident weights; channel-major [C, S] fp16 buffers (S % 32 == 0). */
|
|
365
|
+
export class AneLinear implements Disposable {
|
|
366
|
+
private constructor(readonly program: AneProgram, readonly cin: number, readonly cout: number, readonly seq: number) {}
|
|
367
|
+
/** Loads `int8LinearProgram(cin, cout, seq, q, scales)`, compiled by the install step. */
|
|
368
|
+
static create(cin: number, cout: number, seq: number, q: Int8Array, scales: Uint16Array, share?: AneLinear): AneLinear {
|
|
369
|
+
if (seq % 32 !== 0) throw new Error(`AneLinear: S=${seq} must be a multiple of 32`);
|
|
370
|
+
return new AneLinear(AneProgram.create(int8LinearProgram(cin, cout, seq, q, scales), share?.program), cin, cout, seq);
|
|
371
|
+
}
|
|
372
|
+
writeInput(bytes: Uint8Array): void { this.program.inputView(0).set(bytes); }
|
|
373
|
+
evalAsync(): void { this.program.evalAsync(); }
|
|
374
|
+
wait(): void { this.program.wait(); }
|
|
375
|
+
output(): MlxArray { return this.program.output([this.cout, this.seq]); }
|
|
376
|
+
[Symbol.dispose](): void { this.dispose(); }
|
|
377
|
+
dispose(): void { this.program.dispose(); }
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Streamed-weight matmul y[S, Cout] = x[S, Cin] · w[Cout, Cin]ᵀ (row-major fp16). */
|
|
381
|
+
export class AneStreamedMatmul implements Disposable {
|
|
382
|
+
private constructor(readonly program: AneProgram, readonly cin: number, readonly cout: number, readonly seq: number) {}
|
|
383
|
+
/** Loads `streamedMatmulProgram(cin, cout, seq, slices)`, compiled by the install step. */
|
|
384
|
+
static create(cin: number, cout: number, seq: number, share?: AneStreamedMatmul, slices?: number): AneStreamedMatmul {
|
|
385
|
+
return new AneStreamedMatmul(AneProgram.create(streamedMatmulProgram(cin, cout, seq, slices), share?.program), cin, cout, seq);
|
|
386
|
+
}
|
|
387
|
+
/** The weight buffer [Cout·Cin] and the input buffer [S·Cin], borrowed fp16 wraps (AneProgram.inputArray). */
|
|
388
|
+
weights(): MlxArray { return this.program.inputArray(0); }
|
|
389
|
+
input(): MlxArray { return this.program.inputArray(1); }
|
|
390
|
+
evalAsync(): void { this.program.evalAsync(); }
|
|
391
|
+
wait(): void { this.program.wait(); }
|
|
392
|
+
/** The first `rows` rows of y [S, Cout], an owned fp16 view; valid until the next evaluation. */
|
|
393
|
+
output(rows = this.seq): MlxArray { return this.program.output([rows, this.cout]); }
|
|
394
|
+
[Symbol.dispose](): void { this.dispose(); }
|
|
395
|
+
dispose(): void { this.program.dispose(); }
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Per-output-row symmetric INT8 of a dense [rows, cols] weight (GPU), as the
|
|
399
|
+
* ANE program's int8 data and fp16 scale bits. */
|
|
400
|
+
export function quantizeRowsInt8(w: MlxArray): { q: Int8Array; scales: Uint16Array } {
|
|
401
|
+
using w32 = w.astype(Dtype.float32);
|
|
402
|
+
using a = ops.abs(w32);
|
|
403
|
+
using amax = ops.maxAxis(a, 1, true);
|
|
404
|
+
using raw = ops.mulScalar(amax, 1 / 127);
|
|
405
|
+
using floor = ops.scalarLike(1e-8, raw);
|
|
406
|
+
using scale = ops.maximum(raw, floor);
|
|
407
|
+
using scaled = ops.div(w32, scale);
|
|
408
|
+
using rounded = ops.round(scaled);
|
|
409
|
+
using lo = ops.scalarLike(-127, rounded), hi = ops.scalarLike(127, rounded);
|
|
410
|
+
using floored = ops.maximum(rounded, lo);
|
|
411
|
+
using clipped = ops.minimum(floored, hi);
|
|
412
|
+
using q8 = clipped.astype(Dtype.int8);
|
|
413
|
+
using s16 = scale.astype(Dtype.float16);
|
|
414
|
+
q8.eval(); s16.eval();
|
|
415
|
+
return { q: new Int8Array(q8.rawBytes().buffer), scales: new Uint16Array(s16.rawBytes().buffer) };
|
|
416
|
+
}
|