@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,755 @@
|
|
|
1
|
+
// The Gemma 4 decoder blocks (port target: mlx-lm's gemma4_text.py and the
|
|
2
|
+
// switch_layers.py SwitchGLU it uses): the attention block with the read it was
|
|
3
|
+
// built with, the dense and mixture-of-experts feed-forward pieces, the
|
|
4
|
+
// per-layer-input pieces (e2b/e4b), the KV-sharing pieces (a donor layer's read
|
|
5
|
+
// and the donor each sharer attends) and the decoder layer composing them. The
|
|
6
|
+
// generic graph (`Gemma4Model`, model.ts) and the written graphs
|
|
7
|
+
// (gemma4-e4b.ts, gemma4-12b.ts, gemma4-26b.ts) compose these blocks.
|
|
8
|
+
// Each block is built once from the weights and the config and reads its cache
|
|
9
|
+
// through the read its caller's phase names: no block inspects its input to
|
|
10
|
+
// choose a phase, builds a mask or chooses an attention kernel. The training
|
|
11
|
+
// forward, which fetches plain keys and values from training caches under masks
|
|
12
|
+
// it builds, stays with the generic graph that declares training (model.ts).
|
|
13
|
+
|
|
14
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
15
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
16
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
17
|
+
import type { ModelConfig } from "../../artifacts/config";
|
|
18
|
+
import type { Weights } from "../../artifacts/weights";
|
|
19
|
+
import type { AttentionCache, AttentionRead, BidirectionalAttentionCache, Cache } from "../../contracts/mlx/cache";
|
|
20
|
+
import { geglu, GegluMLP } from "../../layers/geglu";
|
|
21
|
+
import { disposing } from "../../layers/helpers";
|
|
22
|
+
import { RMSNorm } from "../../layers/normalization";
|
|
23
|
+
import { QuantizedEmbedding } from "../../layers/quantized-embedding";
|
|
24
|
+
import { QuantizedLinear, QuantizedLinearPlain } from "../../layers/quantized-linear";
|
|
25
|
+
import { QuantizedSwitchLinear } from "../../layers/quantized-switch-linear";
|
|
26
|
+
import { isCompiledTrace } from "../../runtime/compiled-trace";
|
|
27
|
+
import { isExpertTracing, recordRouting } from "../../runtime/expert-trace";
|
|
28
|
+
import { captureRopeOffsets } from "../../state/attention-read";
|
|
29
|
+
import type { ReadPhase } from "../graph";
|
|
30
|
+
|
|
31
|
+
/** The phase a Gemma 4 forward runs, named by its caller: one new position per
|
|
32
|
+
* row (`decode`), a causal window of positions (`window`: prefill chunks and
|
|
33
|
+
* tails, speculative verify windows), or the first window of a prompt whose
|
|
34
|
+
* image tokens attend each other in both directions (`bidirectional`: a
|
|
35
|
+
* borrowed bool [L] flagging them, for every row). Each names a read of the
|
|
36
|
+
* cache: `AttentionCache.appendDecode`, `appendWindow`,
|
|
37
|
+
* `BidirectionalAttentionCache.appendBidirectional`. Gemma 4 composes no
|
|
38
|
+
* committed append. */
|
|
39
|
+
export type Gemma4Phase = Exclude<ReadPhase, "committed"> | { readonly bidirectional: MlxArray };
|
|
40
|
+
|
|
41
|
+
/** How an attention block reads its cache, fixed when the block is built.
|
|
42
|
+
* `read` appends `k` and `v` (borrowed) for `phase` and returns the read the
|
|
43
|
+
* block, and the layers sharing its keys and values, attend. */
|
|
44
|
+
export interface Gemma4ReadStrategy {
|
|
45
|
+
read(cache: Cache, phase: Gemma4Phase, k: MlxArray, v: MlxArray): AttentionRead;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const appendFor: Readonly<Record<Exclude<ReadPhase, "committed">, (cache: Cache, k: MlxArray, v: MlxArray) => AttentionRead>> = {
|
|
49
|
+
decode: (cache, k, v) => (cache as AttentionCache).appendDecode(k, v),
|
|
50
|
+
window: (cache, k, v) => (cache as AttentionCache).appendWindow(k, v),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** The cache's own read for the phase: the default strategy. */
|
|
54
|
+
export const gemma4CacheRead: Gemma4ReadStrategy = Object.freeze({
|
|
55
|
+
read: (cache: Cache, phase: Gemma4Phase, k: MlxArray, v: MlxArray): AttentionRead => typeof phase === "string"
|
|
56
|
+
? appendFor[phase](cache, k, v)
|
|
57
|
+
: (cache as BidirectionalAttentionCache).appendBidirectional(k, v, phase.bidirectional),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/** A donor layer's read for one forward and the positions its queries take: the
|
|
61
|
+
* donor cache's offset before the append and, where the cache carries per-row
|
|
62
|
+
* positions (a batched layout, a compiled-decode trace), an owned copy of them,
|
|
63
|
+
* held because advancing the cache may release its own array. The layers
|
|
64
|
+
* sharing the donor's keys and values attend the same read with their own
|
|
65
|
+
* queries (`AttentionRead.attend` is repeatable); the forward that made it
|
|
66
|
+
* disposes it once, after its last sharer. */
|
|
67
|
+
export interface DonorRead {
|
|
68
|
+
readonly read: AttentionRead;
|
|
69
|
+
readonly offset: number;
|
|
70
|
+
readonly offsetArr?: MlxArray;
|
|
71
|
+
dispose(): void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The donor each layer's attention reads (reference Gemma4TextModel's
|
|
75
|
+
* `previous_kvs`): itself for the first `numDonors` layers, which own caches;
|
|
76
|
+
* for each later layer, the last donor of its own attention type, whose read
|
|
77
|
+
* that layer attends in the same forward. */
|
|
78
|
+
export function kvDonors(layerTypes: readonly string[], numDonors: number): number[] {
|
|
79
|
+
const donors = layerTypes.map((_, i) => i);
|
|
80
|
+
const lastByType: Record<string, number> = {};
|
|
81
|
+
for (let i = 0; i < numDonors; i++) lastByType[layerTypes[i]!] = i;
|
|
82
|
+
for (let j = numDonors; j < layerTypes.length; j++) {
|
|
83
|
+
const donor = lastByType[layerTypes[j]!];
|
|
84
|
+
if (donor === undefined) throw new Error(`no donor layer of type ${layerTypes[j]}`);
|
|
85
|
+
donors[j] = donor;
|
|
86
|
+
}
|
|
87
|
+
return donors;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The attention block of one layer: the query projection and norm, and for a
|
|
91
|
+
* layer that owns a cache the key and value projections and norms, RoPE on the
|
|
92
|
+
* layer type's frequencies, and the read it was built with (`reads`, the cache's
|
|
93
|
+
* own by default); a KV-sharing layer attends its donor's read instead. */
|
|
94
|
+
export class Attention {
|
|
95
|
+
readonly isSliding: boolean;
|
|
96
|
+
readonly windowSize: number;
|
|
97
|
+
readonly useKEqV: boolean;
|
|
98
|
+
readonly hasKv: boolean;
|
|
99
|
+
readonly headDim: number;
|
|
100
|
+
readonly nHeads: number;
|
|
101
|
+
readonly nKvHeads: number;
|
|
102
|
+
readonly ropeBase: number | null;
|
|
103
|
+
readonly ropeFreqs: MlxArray | null;
|
|
104
|
+
readonly qProj: QuantizedLinear;
|
|
105
|
+
readonly kProj: QuantizedLinear | null;
|
|
106
|
+
readonly vProj: QuantizedLinear | null;
|
|
107
|
+
readonly oProj: QuantizedLinear;
|
|
108
|
+
readonly #qPlain: QuantizedLinearPlain;
|
|
109
|
+
readonly #kPlain: QuantizedLinearPlain | null;
|
|
110
|
+
readonly #vPlain: QuantizedLinearPlain | null;
|
|
111
|
+
readonly #oPlain: QuantizedLinearPlain;
|
|
112
|
+
readonly qNorm: RMSNorm;
|
|
113
|
+
readonly kNorm: RMSNorm | null;
|
|
114
|
+
readonly vNorm: RMSNorm | null;
|
|
115
|
+
|
|
116
|
+
constructor(
|
|
117
|
+
weights: Weights, config: ModelConfig, prefix: string, layerType: string,
|
|
118
|
+
hasKv = true,
|
|
119
|
+
/** How a donor layer reads its cache (the cache's own read). */
|
|
120
|
+
readonly reads: Gemma4ReadStrategy = gemma4CacheRead,
|
|
121
|
+
kEqV?: boolean,
|
|
122
|
+
) {
|
|
123
|
+
const t = config.text;
|
|
124
|
+
this.hasKv = hasKv;
|
|
125
|
+
this.isSliding = layerType === "sliding_attention";
|
|
126
|
+
this.windowSize = t.slidingWindow;
|
|
127
|
+
this.headDim = this.isSliding ? t.headDim : t.globalHeadDim;
|
|
128
|
+
this.nHeads = t.numAttentionHeads;
|
|
129
|
+
this.useKEqV = kEqV ?? (t.attentionKEqV && !this.isSliding);
|
|
130
|
+
this.nKvHeads = this.useKEqV ? t.numGlobalKeyValueHeads : t.numKeyValueHeads;
|
|
131
|
+
|
|
132
|
+
const rp = t.ropeParameters[this.isSliding ? "sliding_attention" : "full_attention"]!;
|
|
133
|
+
if (rp.ropeType === "default") {
|
|
134
|
+
this.ropeBase = rp.ropeTheta;
|
|
135
|
+
this.ropeFreqs = null;
|
|
136
|
+
} else if (rp.ropeType === "proportional") {
|
|
137
|
+
// Port of rope_utils.ProportionalRoPE.__init__. The rotated freqs
|
|
138
|
+
// MUST be computed on-device in f32 (arange/dims, then base**x)
|
|
139
|
+
// exactly like the reference — computing them host-side in f64 and
|
|
140
|
+
// rounding to f32 lands 17/64 of them 1 ulp off (f64 pow ≠ f32
|
|
141
|
+
// powf), a latent knife-edge that tiled values exposed
|
|
142
|
+
// as a 1-ulp q-rope divergence at layer 11 (porting rule:
|
|
143
|
+
// replicate the helper's IMPLEMENTATION, not its formula).
|
|
144
|
+
const rotated = Math.floor(this.headDim * rp.partialRotaryFactor);
|
|
145
|
+
const n = this.headDim / 2;
|
|
146
|
+
const exponentsRaw = ops.arange(0, rotated, 2, Dtype.float32);
|
|
147
|
+
const dims = ops.scalarLike(this.headDim, exponentsRaw);
|
|
148
|
+
const exponents = ops.div(exponentsRaw, dims);
|
|
149
|
+
const base = ops.scalarLike(rp.ropeTheta, exponents);
|
|
150
|
+
let rotFreqs = ops.pow(base, exponents);
|
|
151
|
+
if (rp.factor !== 1.0) {
|
|
152
|
+
const f = ops.scalarLike(rp.factor, rotFreqs);
|
|
153
|
+
rotFreqs = disposing(rotFreqs, ops.mul(f, rotFreqs));
|
|
154
|
+
f.dispose();
|
|
155
|
+
}
|
|
156
|
+
this.ropeBase = null;
|
|
157
|
+
const tailLen = n - rotated / 2;
|
|
158
|
+
if (tailLen > 0) {
|
|
159
|
+
const tail = MlxArray.fromFloat32(
|
|
160
|
+
new Float32Array(tailLen).fill(Infinity), [tailLen],
|
|
161
|
+
);
|
|
162
|
+
this.ropeFreqs = ops.concatAxis([rotFreqs, tail], 0);
|
|
163
|
+
tail.dispose();
|
|
164
|
+
} else {
|
|
165
|
+
this.ropeFreqs = rotFreqs;
|
|
166
|
+
}
|
|
167
|
+
for (const a of [exponentsRaw, dims, exponents, base])
|
|
168
|
+
a.dispose();
|
|
169
|
+
if (this.ropeFreqs !== rotFreqs) rotFreqs.dispose();
|
|
170
|
+
} else {
|
|
171
|
+
throw new Error(`unsupported rope_type ${rp.ropeType}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.qProj = QuantizedLinear.load(weights, `${prefix}.q_proj`, config);
|
|
175
|
+
this.kProj = hasKv ? QuantizedLinear.load(weights, `${prefix}.k_proj`, config) : null;
|
|
176
|
+
this.vProj = hasKv && !this.useKEqV
|
|
177
|
+
? QuantizedLinear.load(weights, `${prefix}.v_proj`, config) : null;
|
|
178
|
+
this.oProj = QuantizedLinear.load(weights, `${prefix}.o_proj`, config);
|
|
179
|
+
this.#qPlain = new QuantizedLinearPlain(this.qProj);
|
|
180
|
+
this.#kPlain = this.kProj ? new QuantizedLinearPlain(this.kProj) : null;
|
|
181
|
+
this.#vPlain = this.vProj ? new QuantizedLinearPlain(this.vProj) : null;
|
|
182
|
+
this.#oPlain = new QuantizedLinearPlain(this.oProj);
|
|
183
|
+
this.qNorm = new RMSNorm(weights.tensor(`${prefix}.q_norm.weight`), t.rmsNormEps);
|
|
184
|
+
this.kNorm = hasKv
|
|
185
|
+
? new RMSNorm(weights.tensor(`${prefix}.k_norm.weight`), t.rmsNormEps) : null;
|
|
186
|
+
this.vNorm = hasKv ? new RMSNorm(null, t.rmsNormEps) : null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** A number offset takes the static fast::rope; an array offset (set by
|
|
190
|
+
* compiled-decode trace adapters) takes the dynamic variant — same
|
|
191
|
+
* kernel, offset read from the array (bit-exactness asserted in
|
|
192
|
+
* `02d723a:tests/unit/compile.test.ts`). */
|
|
193
|
+
rope(x: MlxArray, offset: number | MlxArray): MlxArray {
|
|
194
|
+
return typeof offset === "number"
|
|
195
|
+
? ops.rope(x, this.headDim, this.ropeBase, offset, this.ropeFreqs)
|
|
196
|
+
: ops.ropeDynamic(x, this.headDim, this.ropeBase, offset, this.ropeFreqs);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** `x` [B, L, hidden] (borrowed) to an owned [B, L, hidden]. A donor layer
|
|
200
|
+
* appends its keys and values to `cache` through the read `phase` names and
|
|
201
|
+
* returns that read (`donor`, owned by the caller, who keeps it for the
|
|
202
|
+
* layers that share it); a KV-sharing layer attends `donor`, the read its
|
|
203
|
+
* donor layer made in this forward, and returns null. */
|
|
204
|
+
forward(
|
|
205
|
+
x: MlxArray, cache: Cache | null, phase: Gemma4Phase, donor: DonorRead | null,
|
|
206
|
+
): { out: MlxArray; donor: DonorRead | null } {
|
|
207
|
+
const q = this.queries(x);
|
|
208
|
+
const own = this.hasKv ? this.#append(x, cache!, phase) : null;
|
|
209
|
+
return this.attendQueries(q, own ?? donor!, own, x.shape[0]!, x.shape[1]!);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
forwardSeparate(x: MlxArray, cache: Cache, phase: Gemma4Phase): { out: MlxArray; donor: DonorRead } {
|
|
213
|
+
const q = this.queries(x);
|
|
214
|
+
const [B, L] = x.shape;
|
|
215
|
+
let k = this.#kPlain!.forward(x);
|
|
216
|
+
k = disposing(k, ops.reshape(k, [B!, L!, this.nKvHeads, this.headDim]));
|
|
217
|
+
let v = this.#vPlain!.forward(x);
|
|
218
|
+
v = disposing(v, ops.reshape(v, [B!, L!, this.nKvHeads, this.headDim]));
|
|
219
|
+
const source = this.appendValues(k, v, cache, phase);
|
|
220
|
+
return this.attendQueries(q, source, source, B!, L!) as { out: MlxArray; donor: DonorRead };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
forwardKEqV(x: MlxArray, cache: Cache, phase: Gemma4Phase): { out: MlxArray; donor: DonorRead } {
|
|
224
|
+
const q = this.queries(x);
|
|
225
|
+
const [B, L] = x.shape;
|
|
226
|
+
let k = this.#kPlain!.forward(x);
|
|
227
|
+
k = disposing(k, ops.reshape(k, [B!, L!, this.nKvHeads, this.headDim]));
|
|
228
|
+
const source = this.appendValues(k, k, cache, phase);
|
|
229
|
+
return this.attendQueries(q, source, source, B!, L!) as { out: MlxArray; donor: DonorRead };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
forwardShared(x: MlxArray, donor: DonorRead): { out: MlxArray; donor: null } {
|
|
233
|
+
return this.attendQueries(this.queries(x), donor, null, x.shape[0]!, x.shape[1]!) as { out: MlxArray; donor: null };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private queries(x: MlxArray): MlxArray {
|
|
237
|
+
const [B, L] = x.shape;
|
|
238
|
+
let q = this.#qPlain.forward(x);
|
|
239
|
+
q = disposing(q, ops.reshape(q, [B!, L!, this.nHeads, this.headDim]));
|
|
240
|
+
return disposing(q, this.qNorm.forward(q));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private attendQueries(q: MlxArray, source: DonorRead, own: DonorRead | null, B: number, L: number): { out: MlxArray; donor: DonorRead | null } {
|
|
244
|
+
let attn: MlxArray;
|
|
245
|
+
try {
|
|
246
|
+
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
247
|
+
q = disposing(q, this.rope(q, source.offsetArr ?? source.offset));
|
|
248
|
+
attn = source.read.attend(q, 1.0);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
own?.dispose();
|
|
251
|
+
throw error;
|
|
252
|
+
} finally { q.dispose(); }
|
|
253
|
+
const attnT = ops.transposeAxes(attn, [0, 2, 1, 3]);
|
|
254
|
+
attn.dispose();
|
|
255
|
+
const merged = ops.reshape(attnT, [B, L, -1]);
|
|
256
|
+
attnT.dispose();
|
|
257
|
+
const out = this.#oPlain.forward(merged);
|
|
258
|
+
merged.dispose();
|
|
259
|
+
return { out, donor: own };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** The donor half of `forward`: this layer's keys and values, normalized and
|
|
263
|
+
* positioned at the cache's offset, appended through the read `phase`
|
|
264
|
+
* names. The caller owns the returned read. */
|
|
265
|
+
#append(x: MlxArray, cache: Cache, phase: Gemma4Phase): DonorRead {
|
|
266
|
+
const [B, L] = x.shape as [number, number, number];
|
|
267
|
+
let k = this.kProj!.forward(x);
|
|
268
|
+
k = disposing(k, ops.reshape(k, [B, L, this.nKvHeads, this.headDim]));
|
|
269
|
+
let v: MlxArray;
|
|
270
|
+
if (this.vProj) {
|
|
271
|
+
v = this.vProj.forward(x);
|
|
272
|
+
v = disposing(v, ops.reshape(v, [B, L, this.nKvHeads, this.headDim]));
|
|
273
|
+
} else {
|
|
274
|
+
v = k; // attention_k_eq_v: shared projection; norms differ below
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return this.appendValues(k, v, cache, phase);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private appendValues(k: MlxArray, v: MlxArray, cache: Cache, phase: Gemma4Phase): DonorRead {
|
|
281
|
+
const offset = cache.offset;
|
|
282
|
+
// Own the pre-write positions through every query that attends this read.
|
|
283
|
+
// Advancing a row cache may replace and release its borrowed offset array
|
|
284
|
+
// before the queries' RoPE is built.
|
|
285
|
+
const offsetArr = captureRopeOffsets(cache);
|
|
286
|
+
|
|
287
|
+
const kNormed = this.kNorm!.forward(k);
|
|
288
|
+
const kT = ops.transposeAxes(kNormed, [0, 2, 1, 3]);
|
|
289
|
+
kNormed.dispose();
|
|
290
|
+
const kRoped = this.rope(kT, offsetArr ?? offset);
|
|
291
|
+
kT.dispose();
|
|
292
|
+
|
|
293
|
+
const vNormed = this.vNorm!.forward(v);
|
|
294
|
+
const vT = ops.transposeAxes(vNormed, [0, 2, 1, 3]);
|
|
295
|
+
vNormed.dispose();
|
|
296
|
+
if (v !== k) v.dispose();
|
|
297
|
+
k.dispose();
|
|
298
|
+
|
|
299
|
+
let read: AttentionRead;
|
|
300
|
+
try {
|
|
301
|
+
read = this.reads.read(cache, phase, kRoped, vT);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
offsetArr?.dispose();
|
|
304
|
+
throw error;
|
|
305
|
+
} finally {
|
|
306
|
+
kRoped.dispose();
|
|
307
|
+
vT.dispose();
|
|
308
|
+
}
|
|
309
|
+
return { read, offset, offsetArr, dispose() { read.dispose(); offsetArr?.dispose(); } };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// --- MoE block (26B-A4B) — port of reference Router/Experts/SwitchGLU ----
|
|
314
|
+
// (gemma4_text.py + switch_layers.py in the oracle venv). The checkpoint
|
|
315
|
+
// ships pre-stacked switch_glu tensors [experts, out, in/packed]; only the
|
|
316
|
+
// quantized path exists here (all our targets are OptiQ quants).
|
|
317
|
+
|
|
318
|
+
/** Port of reference Router: norm -> scale -> project -> top-k -> renormalize. */
|
|
319
|
+
export class Router {
|
|
320
|
+
readonly proj: QuantizedLinear;
|
|
321
|
+
/** scale · hidden_size^-0.5, precomputed (identical ops to the per-call
|
|
322
|
+
* product in the reference: bf16 array × weak scalar). */
|
|
323
|
+
readonly normWeight: MlxArray;
|
|
324
|
+
readonly perExpertScale: MlxArray;
|
|
325
|
+
readonly eps: number;
|
|
326
|
+
readonly numExperts: number;
|
|
327
|
+
readonly topK: number;
|
|
328
|
+
/** Decoder layer index (parsed from the weight prefix) — for E0 tracing. */
|
|
329
|
+
readonly layerIdx: number;
|
|
330
|
+
|
|
331
|
+
constructor(weights: Weights, config: ModelConfig, prefix: string) {
|
|
332
|
+
const t = config.text;
|
|
333
|
+
this.layerIdx = Number(prefix.match(/layers\.(\d+)/)?.[1] ?? -1);
|
|
334
|
+
this.proj = QuantizedLinear.load(weights, `${prefix}.proj`, config);
|
|
335
|
+
// weights.tensor() returns the Weights-owned cached array — do NOT
|
|
336
|
+
// dispose it (a second model over the same Weights would get a dead
|
|
337
|
+
// handle; Weights.dispose() frees it at end of life)
|
|
338
|
+
const scale = weights.tensor(`${prefix}.scale`);
|
|
339
|
+
this.normWeight = ops.mulScalar(scale, Math.pow(t.hiddenSize, -0.5));
|
|
340
|
+
this.perExpertScale = weights.tensor(`${prefix}.per_expert_scale`);
|
|
341
|
+
this.eps = t.rmsNormEps;
|
|
342
|
+
this.numExperts = t.numExperts;
|
|
343
|
+
this.topK = t.topKExperts;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** x [B, L, H] → { indices [B, L, k] (uint32), weights [B, L, k] }. */
|
|
347
|
+
forward(x: MlxArray): { indices: MlxArray; weights: MlxArray } {
|
|
348
|
+
const normed = ops.rmsNorm(x, this.normWeight, this.eps);
|
|
349
|
+
const scores = this.proj.forward(normed);
|
|
350
|
+
normed.dispose();
|
|
351
|
+
|
|
352
|
+
const part = ops.argpartitionAxis(scores, this.numExperts - this.topK, -1);
|
|
353
|
+
const [B, L] = scores.shape as [number, number, number];
|
|
354
|
+
let indices: MlxArray;
|
|
355
|
+
if (isCompiledTrace()) {
|
|
356
|
+
const start = ops.fromInt32([this.numExperts - this.topK], [1]);
|
|
357
|
+
indices = ops.sliceDynamic(part, start, [2], [B, L, this.topK]);
|
|
358
|
+
start.dispose();
|
|
359
|
+
} else {
|
|
360
|
+
indices = part.slice([0, 0, this.numExperts - this.topK], [B, L, this.numExperts]);
|
|
361
|
+
}
|
|
362
|
+
part.dispose();
|
|
363
|
+
|
|
364
|
+
let w = ops.takeAlongAxis(scores, indices, -1);
|
|
365
|
+
scores.dispose();
|
|
366
|
+
w = disposing(w, ops.softmaxAxis(w, -1, false));
|
|
367
|
+
const gathered = ops.takeAxis(this.perExpertScale, indices, 0);
|
|
368
|
+
w = disposing(w, ops.mul(w, gathered));
|
|
369
|
+
gathered.dispose();
|
|
370
|
+
if (isExpertTracing()) recordRouting(this.layerIdx, indices);
|
|
371
|
+
return { indices, weights: w };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** Port of switch_layers.SwitchGLU (geglu activation), quantized path, with the
|
|
376
|
+
* activation form its owner chose at construction (`compiled`). */
|
|
377
|
+
export class SwitchGLU {
|
|
378
|
+
readonly gate: QuantizedSwitchLinear;
|
|
379
|
+
readonly up: QuantizedSwitchLinear;
|
|
380
|
+
readonly down: QuantizedSwitchLinear;
|
|
381
|
+
|
|
382
|
+
constructor(weights: Weights, config: ModelConfig, prefix: string, readonly compiled: boolean) {
|
|
383
|
+
this.gate = QuantizedSwitchLinear.load(weights, `${prefix}.gate_proj`, config);
|
|
384
|
+
this.up = QuantizedSwitchLinear.load(weights, `${prefix}.up_proj`, config);
|
|
385
|
+
this.down = QuantizedSwitchLinear.load(weights, `${prefix}.down_proj`, config);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** x [B, L, H], indices [B, L, k] → [B, L, k, H]. */
|
|
389
|
+
forward(x: MlxArray, indices: MlxArray): MlxArray {
|
|
390
|
+
// x → [B, L, 1, 1, H] (reference expand_dims(x, (-2, -3)))
|
|
391
|
+
let h = ops.expandDims(x, -2);
|
|
392
|
+
h = disposing(h, ops.expandDims(h, -3));
|
|
393
|
+
|
|
394
|
+
// Sort tokens by expert when there are many rows (reference threshold).
|
|
395
|
+
const doSort = indices.size >= 64;
|
|
396
|
+
let idx = indices;
|
|
397
|
+
let invOrder: MlxArray | null = null;
|
|
398
|
+
let order: MlxArray | null = null;
|
|
399
|
+
if (doSort) {
|
|
400
|
+
// _gather_sort: flatten indices, argsort, gather rows by order // k
|
|
401
|
+
const k = indices.shape[indices.ndim - 1]!;
|
|
402
|
+
const idxFlat = ops.reshape(indices, [indices.size]);
|
|
403
|
+
order = ops.argsortAxis(idxFlat, 0);
|
|
404
|
+
invOrder = ops.argsortAxis(order, 0);
|
|
405
|
+
const kScalar = ops.scalarLike(k, order);
|
|
406
|
+
const rowIdx = ops.floorDivide(order, kScalar);
|
|
407
|
+
kScalar.dispose();
|
|
408
|
+
const [, , , , H] = h.shape as number[];
|
|
409
|
+
const flat = ops.reshape(h, [-1, 1, H!]);
|
|
410
|
+
h.dispose();
|
|
411
|
+
h = ops.takeAxis(flat, rowIdx, 0);
|
|
412
|
+
flat.dispose();
|
|
413
|
+
rowIdx.dispose();
|
|
414
|
+
idx = ops.takeAxis(idxFlat, order, 0);
|
|
415
|
+
idxFlat.dispose();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const xUp = this.up.forward(h, idx, doSort);
|
|
419
|
+
const xGate = this.gate.forward(h, idx, doSort);
|
|
420
|
+
h.dispose();
|
|
421
|
+
// GeGLU activation: gelu_approx(gate) · up. The oracle SwitchGLU uses the SAME
|
|
422
|
+
// @mx.compile'd geglu as the dense MLP (activation=GeGLU(), switch_layers.py).
|
|
423
|
+
const mid = geglu(xGate, xUp, this.compiled);
|
|
424
|
+
let y = this.down.forward(mid, idx, doSort);
|
|
425
|
+
mid.dispose();
|
|
426
|
+
if (idx !== indices) idx.dispose();
|
|
427
|
+
|
|
428
|
+
if (doSort) {
|
|
429
|
+
// _scatter_unsort: restore row order, unflatten to indices.shape
|
|
430
|
+
y = disposing(y, ops.takeAxis(y, invOrder!, 0));
|
|
431
|
+
invOrder!.dispose();
|
|
432
|
+
order!.dispose();
|
|
433
|
+
const [B, L, k] = indices.shape as number[];
|
|
434
|
+
const H = y.shape[y.ndim - 1]!;
|
|
435
|
+
y = disposing(y, ops.reshape(y, [B!, L!, k!, 1, H]));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// squeeze(-2)
|
|
439
|
+
const shape = y.shape;
|
|
440
|
+
shape.splice(shape.length - 2, 1);
|
|
441
|
+
return disposing(y, ops.reshape(y, shape));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** Port of reference Experts: switch_glu then top-k weighted sum. */
|
|
446
|
+
export class Experts {
|
|
447
|
+
readonly switchGlu: SwitchGLU;
|
|
448
|
+
|
|
449
|
+
constructor(weights: Weights, config: ModelConfig, prefix: string, compiledGeglu: boolean) {
|
|
450
|
+
this.switchGlu = new SwitchGLU(weights, config, `${prefix}.switch_glu`, compiledGeglu);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
forward(x: MlxArray, indices: MlxArray, weights: MlxArray): MlxArray {
|
|
454
|
+
const w = ops.expandDims(weights, -1);
|
|
455
|
+
const y = this.switchGlu.forward(x, indices);
|
|
456
|
+
const wy = ops.mul(w, y);
|
|
457
|
+
w.dispose();
|
|
458
|
+
y.dispose();
|
|
459
|
+
return disposing(wy, ops.sumAxis(wy, -2, false));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** One decoder layer: the attention sub-block (`forwardAttn`) and the row-wise
|
|
464
|
+
* pieces after it (`denseFeedforward` or `moeFeedforward`, `perLayerGated`,
|
|
465
|
+
* `scaled`), which the generic graph composes through `forwardMlp` and a
|
|
466
|
+
* written graph composes as its layer table names them. */
|
|
467
|
+
export class DecoderLayer {
|
|
468
|
+
readonly attn: Attention;
|
|
469
|
+
readonly mlp: GegluMLP;
|
|
470
|
+
readonly inputNorm: RMSNorm;
|
|
471
|
+
readonly postAttnNorm: RMSNorm;
|
|
472
|
+
readonly preFfNorm: RMSNorm;
|
|
473
|
+
readonly postFfNorm: RMSNorm;
|
|
474
|
+
readonly layerScalar: MlxArray | null;
|
|
475
|
+
readonly layerType: string;
|
|
476
|
+
// 26B-A4B MoE block (dense MLP + routed experts in parallel branches)
|
|
477
|
+
readonly router: Router | null;
|
|
478
|
+
readonly experts: Experts | null;
|
|
479
|
+
readonly postFfNorm1: RMSNorm | null;
|
|
480
|
+
readonly postFfNorm2: RMSNorm | null;
|
|
481
|
+
readonly preFfNorm2: RMSNorm | null;
|
|
482
|
+
// e2b/e4b per-layer input gating
|
|
483
|
+
readonly perLayerGate: QuantizedLinear | null;
|
|
484
|
+
readonly perLayerProjection: QuantizedLinear | null;
|
|
485
|
+
readonly postPerLayerNorm: RMSNorm | null;
|
|
486
|
+
|
|
487
|
+
/** `compiledGeglu` is the graph's construction-time choice of the GeGLU
|
|
488
|
+
* activation form for the dense MLP and the experts (`layers/geglu.ts`). */
|
|
489
|
+
constructor(weights: Weights, config: ModelConfig, prefixBase: string, idx: number, hasKv: boolean, compiledGeglu: boolean) {
|
|
490
|
+
const prefix = `${prefixBase}.layers.${idx}`;
|
|
491
|
+
const t = config.text;
|
|
492
|
+
this.layerType = t.layerTypes[idx]!;
|
|
493
|
+
this.attn = new Attention(weights, config, `${prefix}.self_attn`, this.layerType, hasKv);
|
|
494
|
+
this.mlp = new GegluMLP(weights, config, `${prefix}.mlp`, compiledGeglu);
|
|
495
|
+
const norm = (n: string) => new RMSNorm(weights.tensor(`${prefix}.${n}.weight`), t.rmsNormEps);
|
|
496
|
+
this.inputNorm = norm("input_layernorm");
|
|
497
|
+
this.postAttnNorm = norm("post_attention_layernorm");
|
|
498
|
+
this.preFfNorm = norm("pre_feedforward_layernorm");
|
|
499
|
+
this.postFfNorm = norm("post_feedforward_layernorm");
|
|
500
|
+
this.layerScalar = weights.has(`${prefix}.layer_scalar`)
|
|
501
|
+
? weights.tensor(`${prefix}.layer_scalar`)
|
|
502
|
+
: null;
|
|
503
|
+
if (t.enableMoeBlock) {
|
|
504
|
+
this.router = new Router(weights, config, `${prefix}.router`);
|
|
505
|
+
this.experts = new Experts(weights, config, `${prefix}.experts`, compiledGeglu);
|
|
506
|
+
this.postFfNorm1 = norm("post_feedforward_layernorm_1");
|
|
507
|
+
this.postFfNorm2 = norm("post_feedforward_layernorm_2");
|
|
508
|
+
this.preFfNorm2 = norm("pre_feedforward_layernorm_2");
|
|
509
|
+
} else {
|
|
510
|
+
this.router = this.experts = null;
|
|
511
|
+
this.postFfNorm1 = this.postFfNorm2 = this.preFfNorm2 = null;
|
|
512
|
+
}
|
|
513
|
+
if (t.hiddenSizePerLayerInput > 0) {
|
|
514
|
+
this.perLayerGate = QuantizedLinear.load(weights, `${prefix}.per_layer_input_gate`, config);
|
|
515
|
+
this.perLayerProjection = QuantizedLinear.load(weights, `${prefix}.per_layer_projection`, config);
|
|
516
|
+
this.postPerLayerNorm = norm("post_per_layer_input_norm");
|
|
517
|
+
} else {
|
|
518
|
+
this.perLayerGate = this.perLayerProjection = null;
|
|
519
|
+
this.postPerLayerNorm = null;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** `x` [B, L, hidden] (borrowed) to an owned [B, L, hidden], the attention
|
|
524
|
+
* block reading `cache` for `phase` (a donor) or attending `donor` (a
|
|
525
|
+
* KV-sharing layer). Returns a donor layer's read, which the caller owns. */
|
|
526
|
+
forward(
|
|
527
|
+
x: MlxArray, cache: Cache | null, phase: Gemma4Phase,
|
|
528
|
+
donor: DonorRead | null, perLayerInput: MlxArray | null,
|
|
529
|
+
): { h: MlxArray; donor: DonorRead | null } {
|
|
530
|
+
// Composed from the two sub-blocks (the intra-layer min-cut is the post-attn
|
|
531
|
+
// residual `hMid` [1,T,hidden]). `forwardMlp` borrows hMid.
|
|
532
|
+
const { h: hMid, donor: made } = this.forwardAttn(x, cache, phase, donor);
|
|
533
|
+
let h: MlxArray;
|
|
534
|
+
try { h = this.forwardMlp(hMid, perLayerInput); }
|
|
535
|
+
catch (error) { made?.dispose(); throw error; }
|
|
536
|
+
finally { hMid.dispose(); }
|
|
537
|
+
return { h, donor: made };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** Attention sub-block: inputNorm → attn → postAttnNorm → residual add.
|
|
541
|
+
* Returns the post-attn residual stream `h` [B,T,hidden] (the intra-layer
|
|
542
|
+
* boundary) and a donor layer's read (null for a sharer). `x` is borrowed
|
|
543
|
+
* (never disposed). The e4b donor threading lives entirely here. */
|
|
544
|
+
forwardAttn(
|
|
545
|
+
x: MlxArray, cache: Cache | null, phase: Gemma4Phase, donor: DonorRead | null,
|
|
546
|
+
): { h: MlxArray; donor: DonorRead | null } {
|
|
547
|
+
let h = this.inputNorm.forward(x);
|
|
548
|
+
const { out, donor: made } = this.attn.forward(h, cache, phase, donor);
|
|
549
|
+
h.dispose();
|
|
550
|
+
h = out;
|
|
551
|
+
h = disposing(h, this.postAttnNorm.forward(h));
|
|
552
|
+
h = disposing(h, ops.add(x, h));
|
|
553
|
+
return { h, donor: made };
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** MLP sub-block: FFN (dense or MoE) + postFfNorm + residual add, then the
|
|
557
|
+
* e4b per-layer-input gate and layer scalar. `hMid` (the attn sub-block's
|
|
558
|
+
* output) is BORROWED — the caller owns/disposes it (it is the checkpoint
|
|
559
|
+
* boundary leaf), so this never disposes it. The generic graph's composition
|
|
560
|
+
* of the pieces below; a written graph composes the ones its layer has. */
|
|
561
|
+
forwardMlp(hMid: MlxArray, perLayerInput: MlxArray | null): MlxArray {
|
|
562
|
+
let h = this.router && this.experts ? this.moeFeedforward(hMid) : this.denseFeedforward(hMid);
|
|
563
|
+
if (this.perLayerGate && this.perLayerProjection && this.postPerLayerNorm && perLayerInput)
|
|
564
|
+
h = this.perLayerGated(h, perLayerInput);
|
|
565
|
+
if (this.layerScalar) h = this.scaled(h);
|
|
566
|
+
return h;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** The dense feed-forward with its residual: preFfNorm → GeGLU MLP →
|
|
570
|
+
* postFfNorm, added to `hMid` (borrowed). Returns an owned output. */
|
|
571
|
+
denseFeedforward(hMid: MlxArray): MlxArray {
|
|
572
|
+
let f = this.preFfNorm.forward(hMid);
|
|
573
|
+
f = disposing(f, this.mlp.forward(f));
|
|
574
|
+
f = disposing(f, this.postFfNorm.forward(f));
|
|
575
|
+
const h = ops.add(hMid, f); // hMid borrowed — NOT disposed here
|
|
576
|
+
f.dispose();
|
|
577
|
+
return h;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** The MoE feed-forward with its residual (26B-A4B): the dense MLP and the
|
|
581
|
+
* routed experts as parallel branches off `hMid` (borrowed; reference
|
|
582
|
+
* DecoderLayer, enable_moe path), summed, postFfNorm, added to `hMid`.
|
|
583
|
+
* Returns an owned output. */
|
|
584
|
+
moeFeedforward(hMid: MlxArray): MlxArray {
|
|
585
|
+
let h1 = this.preFfNorm.forward(hMid);
|
|
586
|
+
h1 = disposing(h1, this.mlp.forward(h1));
|
|
587
|
+
h1 = disposing(h1, this.postFfNorm1!.forward(h1));
|
|
588
|
+
|
|
589
|
+
const { indices, weights: topKWeights } = this.router!.forward(hMid);
|
|
590
|
+
let h2 = this.preFfNorm2!.forward(hMid);
|
|
591
|
+
h2 = disposing(h2, this.experts!.forward(h2, indices, topKWeights));
|
|
592
|
+
h2 = disposing(h2, this.postFfNorm2!.forward(h2));
|
|
593
|
+
indices.dispose();
|
|
594
|
+
topKWeights.dispose();
|
|
595
|
+
|
|
596
|
+
let f = ops.add(h1, h2);
|
|
597
|
+
h1.dispose();
|
|
598
|
+
h2.dispose();
|
|
599
|
+
f = disposing(f, this.postFfNorm.forward(f));
|
|
600
|
+
const h = ops.add(hMid, f); // hMid borrowed — NOT disposed here
|
|
601
|
+
f.dispose();
|
|
602
|
+
return h;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/** The per-layer input gate (e2b/e4b; reference gemma4_text DecoderLayer):
|
|
606
|
+
* `h` (consumed) plus postPerLayerNorm(projection(gelu(gate(h)) ·
|
|
607
|
+
* `perLayerInput`)), the input borrowed. gelu(gate)·pli is the second GeGLU
|
|
608
|
+
* site — the bit-exact spelled-out gelu+mul. Returns an owned output. */
|
|
609
|
+
perLayerGated(h: MlxArray, perLayerInput: MlxArray): MlxArray {
|
|
610
|
+
let gate = this.perLayerGate!.forward(h);
|
|
611
|
+
gate = disposing(gate, ops.geluApprox(gate));
|
|
612
|
+
gate = disposing(gate, ops.mul(gate, perLayerInput));
|
|
613
|
+
gate = disposing(gate, this.perLayerProjection!.forward(gate));
|
|
614
|
+
gate = disposing(gate, this.postPerLayerNorm!.forward(gate));
|
|
615
|
+
const out = ops.add(h, gate);
|
|
616
|
+
h.dispose();
|
|
617
|
+
gate.dispose();
|
|
618
|
+
return out;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/** `h` (consumed) times the layer scalar. */
|
|
622
|
+
scaled(h: MlxArray): MlxArray {
|
|
623
|
+
return disposing(h, ops.mul(h, this.layerScalar!));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** The per-layer inputs of a per-layer-input model (e2b/e4b; reference
|
|
628
|
+
* `_get_per_layer_inputs` + `_project_per_layer_inputs`): every layer's input
|
|
629
|
+
* from the token ids and the scaled embeddings, and each layer's slice. */
|
|
630
|
+
export class PerLayerInputs {
|
|
631
|
+
readonly embed: QuantizedEmbedding;
|
|
632
|
+
readonly modelProjection: QuantizedLinear;
|
|
633
|
+
readonly projectionNorm: RMSNorm;
|
|
634
|
+
/** Each layer's input width. */
|
|
635
|
+
readonly width: number;
|
|
636
|
+
readonly #layers: number;
|
|
637
|
+
readonly #hiddenSize: number;
|
|
638
|
+
|
|
639
|
+
constructor(weights: Weights, config: ModelConfig, prefixBase: string) {
|
|
640
|
+
const t = config.text;
|
|
641
|
+
this.embed = QuantizedEmbedding.load(weights, `${prefixBase}.embed_tokens_per_layer`, config);
|
|
642
|
+
this.modelProjection = QuantizedLinear.load(weights, `${prefixBase}.per_layer_model_projection`, config);
|
|
643
|
+
this.projectionNorm = new RMSNorm(weights.tensor(`${prefixBase}.per_layer_projection_norm.weight`), t.rmsNormEps);
|
|
644
|
+
this.width = t.hiddenSizePerLayerInput;
|
|
645
|
+
this.#layers = t.numHiddenLayers;
|
|
646
|
+
this.#hiddenSize = t.hiddenSize;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/** Every layer's input, [B, L, nLayers, width] (owned), from `ids` [B, L]
|
|
650
|
+
* and the scaled embeddings `hScaled` [B, L, hidden] (both borrowed). */
|
|
651
|
+
compute(ids: MlxArray, hScaled: MlxArray): MlxArray {
|
|
652
|
+
const B = ids.shape[0]!;
|
|
653
|
+
const L = ids.shape[1]!;
|
|
654
|
+
let pli = this.embed.encode(ids); // [B, L, nLayers*width]
|
|
655
|
+
pli = disposing(pli, ops.mulScalar(pli, Math.sqrt(this.width)));
|
|
656
|
+
pli = disposing(pli, ops.reshape(pli, [B, L, this.#layers, this.width]));
|
|
657
|
+
|
|
658
|
+
let proj = this.modelProjection.forward(hScaled);
|
|
659
|
+
proj = disposing(proj, ops.mulScalar(proj, 1 / Math.sqrt(this.#hiddenSize)));
|
|
660
|
+
proj = disposing(proj, ops.reshape(proj, [B, L, this.#layers, this.width]));
|
|
661
|
+
proj = disposing(proj, this.projectionNorm.forward(proj));
|
|
662
|
+
|
|
663
|
+
let combined = ops.add(proj, pli);
|
|
664
|
+
proj.dispose();
|
|
665
|
+
pli.dispose();
|
|
666
|
+
combined = disposing(combined, ops.mulScalar(combined, Math.SQRT1_2));
|
|
667
|
+
return combined;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/** Layer `i`'s input [B, L, width] (owned) from the [B, L, nLayers, width]
|
|
671
|
+
* tensor `compute` returned (borrowed). A compiled-decode trace slices at an
|
|
672
|
+
* array start, which replays at every step. */
|
|
673
|
+
ordinaryLayer(perLayer: MlxArray, i: number, B: number, L: number): MlxArray {
|
|
674
|
+
using slice = perLayer.slice([0, 0, i, 0], [B, L, i + 1, this.width]);
|
|
675
|
+
return ops.reshape(slice, [B, L, this.width]);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
traceLayer(perLayer: MlxArray, i: number, B: number, L: number): MlxArray {
|
|
679
|
+
using start = ops.fromInt32([i], [1]);
|
|
680
|
+
using slice = ops.sliceDynamic(perLayer, start, [2], [B, L, 1, this.width]);
|
|
681
|
+
return ops.reshape(slice, [B, L, this.width]);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
layer(perLayer: MlxArray, i: number, B: number, L: number): MlxArray {
|
|
685
|
+
let pls: MlxArray;
|
|
686
|
+
if (isCompiledTrace()) {
|
|
687
|
+
const start = ops.fromInt32([i], [1]);
|
|
688
|
+
pls = ops.sliceDynamic(perLayer, start, [2], [B, L, 1, this.width]);
|
|
689
|
+
start.dispose();
|
|
690
|
+
} else {
|
|
691
|
+
pls = perLayer.slice([0, 0, i, 0], [B, L, i + 1, this.width]);
|
|
692
|
+
}
|
|
693
|
+
const r = ops.reshape(pls, [B, L, this.width]);
|
|
694
|
+
pls.dispose();
|
|
695
|
+
return r;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
/** Unsorted expert gather. The graph binds this below the reference threshold. */
|
|
701
|
+
export class UnsortedSwitchGLU {
|
|
702
|
+
constructor(readonly base: SwitchGLU,
|
|
703
|
+
readonly activation: { forward(gate: MlxArray, up: MlxArray): MlxArray }) {}
|
|
704
|
+
forward(x: MlxArray, indices: MlxArray): MlxArray {
|
|
705
|
+
using expanded = ops.expandDims(x, -2);
|
|
706
|
+
using h = ops.expandDims(expanded, -3);
|
|
707
|
+
const up = this.base.up.forward(h, indices, false);
|
|
708
|
+
const gate = this.base.gate.forward(h, indices, false);
|
|
709
|
+
using mid = this.activation.forward(gate, up);
|
|
710
|
+
using y = this.base.down.forward(mid, indices, false);
|
|
711
|
+
const shape = y.shape;
|
|
712
|
+
shape.splice(shape.length - 2, 1);
|
|
713
|
+
return ops.reshape(y, shape);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/** Sorted expert gather and inverse permutation. No token-count selector. */
|
|
718
|
+
export class SortedSwitchGLU {
|
|
719
|
+
constructor(readonly base: SwitchGLU,
|
|
720
|
+
readonly activation: { forward(gate: MlxArray, up: MlxArray): MlxArray }) {}
|
|
721
|
+
forward(x: MlxArray, indices: MlxArray): MlxArray {
|
|
722
|
+
using expanded = ops.expandDims(x, -2);
|
|
723
|
+
using h = ops.expandDims(expanded, -3);
|
|
724
|
+
const k = indices.shape[indices.ndim - 1]!;
|
|
725
|
+
using idxFlat = ops.reshape(indices, [indices.size]);
|
|
726
|
+
using order = ops.argsortAxis(idxFlat, 0);
|
|
727
|
+
using invOrder = ops.argsortAxis(order, 0);
|
|
728
|
+
using kScalar = ops.scalarLike(k, order);
|
|
729
|
+
using rowIdx = ops.floorDivide(order, kScalar);
|
|
730
|
+
using flat = ops.reshape(h, [-1, 1, h.shape[4]!]);
|
|
731
|
+
using sorted = ops.takeAxis(flat, rowIdx, 0);
|
|
732
|
+
using idx = ops.takeAxis(idxFlat, order, 0);
|
|
733
|
+
const up = this.base.up.forward(sorted, idx, true);
|
|
734
|
+
const gate = this.base.gate.forward(sorted, idx, true);
|
|
735
|
+
using mid = this.activation.forward(gate, up);
|
|
736
|
+
using projected = this.base.down.forward(mid, idx, true);
|
|
737
|
+
using unsorted = ops.takeAxis(projected, invOrder, 0);
|
|
738
|
+
const [B, L, topK] = indices.shape as number[];
|
|
739
|
+
using y = ops.reshape(unsorted, [B!, L!, topK!, 1, unsorted.shape.at(-1)!]);
|
|
740
|
+
const shape = y.shape;
|
|
741
|
+
shape.splice(shape.length - 2, 1);
|
|
742
|
+
return ops.reshape(y, shape);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/** The explicit expert operation, with top-k weighted sum in reference order. */
|
|
747
|
+
export class FixedExperts {
|
|
748
|
+
constructor(readonly switchGlu: { forward(x: MlxArray, indices: MlxArray): MlxArray }) {}
|
|
749
|
+
forward(x: MlxArray, indices: MlxArray, weights: MlxArray): MlxArray {
|
|
750
|
+
using w = ops.expandDims(weights, -1);
|
|
751
|
+
using y = this.switchGlu.forward(x, indices);
|
|
752
|
+
using wy = ops.mul(w, y);
|
|
753
|
+
return ops.sumAxis(wy, -2, false);
|
|
754
|
+
}
|
|
755
|
+
}
|