@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,900 @@
|
|
|
1
|
+
import type { MlxCacheLayer, MlxDenoisingPrefillRange, MlxDenoisingDecodeRange, MlxDenoisingAppendRange } from "../../contracts/mlx/graph";
|
|
2
|
+
import { cleanupFailure, disposeResources } from "../../runtime/resources";
|
|
3
|
+
// DiffusionGemma-26B-A4B-it (model_type "diffusion_gemma") — the first
|
|
4
|
+
// NON-autoregressive model in the codebase. Instead of a left-to-right AR loop
|
|
5
|
+
// it runs an ENCODER prefill over the prompt (builds a KV cache) and then a
|
|
6
|
+
// DECODER pass over a fixed 256-token "canvas" that it denoises over a few
|
|
7
|
+
// steps. This file is the D1 static graph: one full forward
|
|
8
|
+
// encoder(prompt) -> cache ; decoder(canvas, cache) -> hidden -> tied head
|
|
9
|
+
// held bit-exact (KL/argmax where the quantized GEMV is 1-ULP off) against the
|
|
10
|
+
// mlx-optiq reference (optiq IS the oracle here — stock mlx-lm can't load it).
|
|
11
|
+
//
|
|
12
|
+
// Ported verbatim from optiq/vlm/_mlxvlm/models/diffusion_gemma/language.py.
|
|
13
|
+
// Reuses mlx-bun's quantized primitives (QuantizedLinear/Embedding/SwitchLinear,
|
|
14
|
+
// RMSNorm, the composed cache legos) from packages/inference/src/layers/ and
|
|
15
|
+
// packages/inference/src/state/. Architecture deltas vs
|
|
16
|
+
// gemma4 (verified against the checkpoint, see `02d723a:docs/design/generic-model-support.md` §6.5):
|
|
17
|
+
// - attention scale=1.0, NO attention softcap (only final-logit softcap 30 fp32)
|
|
18
|
+
// - QK/V-norm POST-proj / PRE-RoPE; v_norm is RMSNormNoScale; V gets no RoPE
|
|
19
|
+
// - full-attention layers (5,11,17,23,29) reuse k as v (no v_proj), hd=512 kv=2,
|
|
20
|
+
// partial-rotary 0.25; sliding layers hd=256 kv=8
|
|
21
|
+
// - per layer: parallel dense-MLP + 128-expert top-8 MoE, summed via 7 RMSNorms
|
|
22
|
+
// and a per-layer `layer_scalar`
|
|
23
|
+
// - experts use a FUSED gate_up_proj SwitchLinear (split at moe_intermediate)
|
|
24
|
+
// - plain nn.RMSNorm everywhere (NO Gemma 1+weight offset) — weights used as-is
|
|
25
|
+
// - tied LM head via the 4-bit QuantizedEmbedding.asLinear
|
|
26
|
+
// - SelfConditioning MLP wraps the canvas embeddings every step (zero signal on
|
|
27
|
+
// the first step still applies its RMSNormNoScale post-norm)
|
|
28
|
+
|
|
29
|
+
import type { ModelConfig } from "../../artifacts/config";
|
|
30
|
+
import type { Weights } from "../../artifacts/weights";
|
|
31
|
+
import { MlxArray } from "@mlx-bun/mlx/array";
|
|
32
|
+
import { Dtype } from "@mlx-bun/mlx/ffi";
|
|
33
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
34
|
+
import { disposing } from "../../layers/helpers";
|
|
35
|
+
import { LoraState } from "../../layers/lora";
|
|
36
|
+
import type { GraphDeclaration } from "../../contracts/portable/graph";
|
|
37
|
+
import type { MlxDeclaredGraph, MlxDenoisingOperations } from "../../contracts/mlx/graph";
|
|
38
|
+
import type { TrainableGraph } from "../../contracts/mlx/trainable";
|
|
39
|
+
import { declareGraph } from "../capabilities";
|
|
40
|
+
import type { PixelInput } from "../../contracts/mlx/media";
|
|
41
|
+
import { geglu, GegluMLP, CompiledGeglu, CompiledGegluMLP } from "../../layers/geglu";
|
|
42
|
+
import { SortedDiffExperts, UnsortedDiffExperts } from "./experts";
|
|
43
|
+
import { compiledLogitSoftcap } from "../../kernels/softcap";
|
|
44
|
+
import { QuantizedEmbedding } from "../../layers/quantized-embedding";
|
|
45
|
+
import { QuantizedLinear } from "../../layers/quantized-linear";
|
|
46
|
+
import { QuantizedSwitchLinear } from "../../layers/quantized-switch-linear";
|
|
47
|
+
import { RMSNorm } from "../../layers/normalization";
|
|
48
|
+
import { AttentionMasks } from "../../state/attention-read";
|
|
49
|
+
import { composedFullAttentionCache, composedSlidingAttentionCache } from "../../state/bf16-first-kv";
|
|
50
|
+
import { maybePageKv } from "../../state/request-policy";
|
|
51
|
+
import { deferredTurboQuantKernels } from "../../state/turboquant-attention";
|
|
52
|
+
import type { AttentionRead, BlockAttentionCache, Cache, EncoderAttentionCache } from "../../contracts/mlx/cache";
|
|
53
|
+
import { DiffusionVisionTower, spliceImageTokens } from "./vision";
|
|
54
|
+
|
|
55
|
+
/** The caches `makeCache` composes: each serves the window read (encoder
|
|
56
|
+
* prefill over prompt tokens or an accepted canvas block), the block read (the
|
|
57
|
+
* decoder canvas pass) and the encoder window read (the vision prompt). */
|
|
58
|
+
type DiffusionCache = BlockAttentionCache & EncoderAttentionCache;
|
|
59
|
+
|
|
60
|
+
/** The read one pass makes on a layer's cache with that layer's keys and
|
|
61
|
+
* values. The pass names it; no layer inspects its input or its cache. */
|
|
62
|
+
type PassRead = (cache: Cache, k: MlxArray, v: MlxArray) => AttentionRead;
|
|
63
|
+
|
|
64
|
+
/** Encoder prefill over tokens: append, causal, limited to the layer's sliding window. */
|
|
65
|
+
const windowRead: PassRead = (cache, k, v) => (cache as DiffusionCache).appendWindow(k, v);
|
|
66
|
+
/** Decoder canvas pass: the canvas attends the stored encoder positions (the
|
|
67
|
+
* newest sliding_window - 1 on sliding layers) and itself, storing nothing. */
|
|
68
|
+
const blockRead: PassRead = (cache, k, v) => (cache as DiffusionCache).readBlock(k, v);
|
|
69
|
+
/** Vision encoder prefill: append, causal over the whole prompt with no sliding
|
|
70
|
+
* window, image positions (`media`, bool [L]) attending each other both ways. */
|
|
71
|
+
const encoderRead = (media: MlxArray): PassRead =>
|
|
72
|
+
(cache, k, v) => (cache as DiffusionCache).appendEncoderWindow(k, v, media);
|
|
73
|
+
|
|
74
|
+
/** Router: pre-projection RMSNorm (no-scale) * scale * hidden**-0.5, top-8
|
|
75
|
+
* argpartition, softmax(precise) * per_expert_scale[idx]. Local, not gemma4's Router:
|
|
76
|
+
* the reference applies scale and hidden**-0.5 as separate bf16 ops after a no-scale
|
|
77
|
+
* norm (gemma4's folds them into the norm weight, which rounds differently), softmaxes
|
|
78
|
+
* in precise mode, and this one stops gradient on the indices. */
|
|
79
|
+
class DiffRouter {
|
|
80
|
+
readonly proj: QuantizedLinear;
|
|
81
|
+
readonly scale: MlxArray; // per-hidden-dim scale (applied AFTER the no-scale norm)
|
|
82
|
+
readonly rootSize: number; // hidden**-0.5
|
|
83
|
+
readonly perExpertScale: MlxArray;
|
|
84
|
+
readonly eps: number;
|
|
85
|
+
readonly numExperts: number;
|
|
86
|
+
readonly topK: number;
|
|
87
|
+
constructor(weights: Weights, config: ModelConfig, prefix: string) {
|
|
88
|
+
const t = config.text;
|
|
89
|
+
this.proj = QuantizedLinear.load(weights, `${prefix}.proj`, config);
|
|
90
|
+
this.scale = weights.tensor(`${prefix}.scale`);
|
|
91
|
+
this.rootSize = Math.pow(t.hiddenSize, -0.5);
|
|
92
|
+
this.perExpertScale = weights.tensor(`${prefix}.per_expert_scale`);
|
|
93
|
+
this.eps = t.rmsNormEps;
|
|
94
|
+
this.numExperts = t.numExperts;
|
|
95
|
+
this.topK = t.topKExperts;
|
|
96
|
+
}
|
|
97
|
+
/** x: [T, H] -> { indices: [T, k], weights: [T, k] }. */
|
|
98
|
+
forward(x: MlxArray): { indices: MlxArray; weights: MlxArray } {
|
|
99
|
+
// Match the reference EXACTLY (do NOT fold the post-scale into the norm
|
|
100
|
+
// weight — that changes the bf16 rounding and shifts the softmax weights):
|
|
101
|
+
// x = rms_norm(x, None, eps); x = x * scale * hidden**-0.5
|
|
102
|
+
let normed = ops.rmsNorm(x, null, this.eps);
|
|
103
|
+
normed = disposing(normed, ops.mul(normed, this.scale));
|
|
104
|
+
normed = disposing(normed, ops.mulScalar(normed, this.rootSize));
|
|
105
|
+
const scores = this.proj.forward(normed);
|
|
106
|
+
normed.dispose();
|
|
107
|
+
const part = ops.argpartitionAxis(scores, this.numExperts - this.topK, -1);
|
|
108
|
+
const [T] = scores.shape as [number, number];
|
|
109
|
+
const sliced = part.slice([0, this.numExperts - this.topK], [T, this.numExperts]);
|
|
110
|
+
part.dispose();
|
|
111
|
+
// The top-k indices derive from `scores`, so under value_and_grad they sit
|
|
112
|
+
// on the tape and MLX's gather VJP refuses a cotangent for indices
|
|
113
|
+
// ("Cannot calculate VJP with respect to indices"). Integer indices carry
|
|
114
|
+
// no gradient; stopping them here keeps the forward bit-identical and lets
|
|
115
|
+
// the value gather still backpropagate into `scores`. (Main's copy of this
|
|
116
|
+
// router lacks the stop and fails the same way under MLX 0.32.2.)
|
|
117
|
+
const indices = ops.stopGradient(sliced);
|
|
118
|
+
sliced.dispose();
|
|
119
|
+
let w = ops.takeAlongAxis(scores, indices, -1);
|
|
120
|
+
scores.dispose();
|
|
121
|
+
w = disposing(w, ops.softmaxAxis(w, -1, true));
|
|
122
|
+
const gathered = ops.takeAxis(this.perExpertScale, indices, 0);
|
|
123
|
+
w = disposing(w, ops.mul(w, gathered));
|
|
124
|
+
gathered.dispose();
|
|
125
|
+
return { indices, weights: w };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** 128-expert top-8 MoE with a FUSED gate_up SwitchLinear (split at
|
|
130
|
+
* moe_intermediate_size) + a down SwitchLinear (gemma4's SwitchGLU holds separate
|
|
131
|
+
* gate and up projections; the geglu between them is the shared one). _gather_sort / _scatter_unsort
|
|
132
|
+
* exactly as mlx_lm.models.switch_layers (sort threshold idx.size >= 64). */
|
|
133
|
+
class DiffExperts {
|
|
134
|
+
readonly gateUp: QuantizedSwitchLinear;
|
|
135
|
+
readonly down: QuantizedSwitchLinear;
|
|
136
|
+
readonly hiddenDims: number; // moe_intermediate_size (split point)
|
|
137
|
+
constructor(weights: Weights, config: ModelConfig, prefix: string, readonly compiledGeglu: boolean) {
|
|
138
|
+
this.gateUp = QuantizedSwitchLinear.load(weights, `${prefix}.gate_up_proj`, config);
|
|
139
|
+
this.down = QuantizedSwitchLinear.load(weights, `${prefix}.down_proj`, config);
|
|
140
|
+
this.hiddenDims = config.text.moeIntermediateSize;
|
|
141
|
+
}
|
|
142
|
+
/** x: [T, H], indices/weights: [T, k] -> [T, H]. */
|
|
143
|
+
forward(x: MlxArray, indices: MlxArray, weights: MlxArray): MlxArray {
|
|
144
|
+
let h = ops.expandDims(x, -2); // [T, 1, H]
|
|
145
|
+
h = disposing(h, ops.expandDims(h, -3)); // [T, 1, 1, H]
|
|
146
|
+
const doSort = indices.size >= 64;
|
|
147
|
+
let idx = indices;
|
|
148
|
+
let order: MlxArray | null = null;
|
|
149
|
+
let invOrder: MlxArray | null = null;
|
|
150
|
+
if (doSort) {
|
|
151
|
+
const k = indices.shape[indices.ndim - 1]!;
|
|
152
|
+
const idxFlat = ops.reshape(indices, [indices.size]);
|
|
153
|
+
order = ops.argsortAxis(idxFlat, 0);
|
|
154
|
+
invOrder = ops.argsortAxis(order, 0);
|
|
155
|
+
const kS = ops.scalarLike(k, order);
|
|
156
|
+
const rowIdx = ops.floorDivide(order, kS);
|
|
157
|
+
kS.dispose();
|
|
158
|
+
const H = h.shape[h.ndim - 1]!;
|
|
159
|
+
const flat = ops.reshape(h, [-1, 1, H]);
|
|
160
|
+
h.dispose();
|
|
161
|
+
h = ops.takeAxis(flat, rowIdx, 0);
|
|
162
|
+
flat.dispose();
|
|
163
|
+
rowIdx.dispose();
|
|
164
|
+
idx = ops.takeAxis(idxFlat, order, 0);
|
|
165
|
+
idxFlat.dispose();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const gateUp = this.gateUp.forward(h, idx, doSort);
|
|
169
|
+
h.dispose();
|
|
170
|
+
const parts = ops.split(gateUp, [this.hiddenDims], -1);
|
|
171
|
+
gateUp.dispose();
|
|
172
|
+
const gate = parts[0]!;
|
|
173
|
+
const up = parts[1]!;
|
|
174
|
+
const mid = geglu(gate, up, this.compiledGeglu);
|
|
175
|
+
let y = this.down.forward(mid, idx, doSort);
|
|
176
|
+
mid.dispose();
|
|
177
|
+
if (idx !== indices) idx.dispose();
|
|
178
|
+
|
|
179
|
+
if (doSort) {
|
|
180
|
+
y = disposing(y, ops.takeAxis(y, invOrder!, 0));
|
|
181
|
+
invOrder!.dispose();
|
|
182
|
+
order!.dispose();
|
|
183
|
+
const shape = [...indices.shape, 1, y.shape[y.ndim - 1]!]; // [T, k, 1, H]
|
|
184
|
+
y = disposing(y, ops.reshape(y, shape));
|
|
185
|
+
}
|
|
186
|
+
// squeeze(-2): [T, k, 1, H] -> [T, k, H]
|
|
187
|
+
{
|
|
188
|
+
const shape = y.shape;
|
|
189
|
+
shape.splice(shape.length - 2, 1);
|
|
190
|
+
y = disposing(y, ops.reshape(y, shape));
|
|
191
|
+
}
|
|
192
|
+
// (y * weights[..., None]).sum(axis=-2) -> [T, H]
|
|
193
|
+
const w = ops.expandDims(weights, -1);
|
|
194
|
+
const wy = ops.mul(y, w);
|
|
195
|
+
w.dispose();
|
|
196
|
+
y.dispose();
|
|
197
|
+
return disposing(wy, ops.sumAxis(wy, -2, false));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** SelfConditioning: post_norm(inputs_embeds + down(geglu(gate(pre_norm(signal)),
|
|
202
|
+
* up(pre_norm(signal))))). pre_norm is RMSNorm, post_norm is RMSNormNoScale. */
|
|
203
|
+
class SelfConditioning {
|
|
204
|
+
readonly activation = new CompiledGeglu();
|
|
205
|
+
readonly preNorm: RMSNorm;
|
|
206
|
+
readonly postNorm: RMSNorm; // RMSNormNoScale (null weight)
|
|
207
|
+
readonly gate: QuantizedLinear;
|
|
208
|
+
readonly up: QuantizedLinear;
|
|
209
|
+
readonly down: QuantizedLinear;
|
|
210
|
+
constructor(weights: Weights, config: ModelConfig, prefix: string, readonly compiledGeglu: boolean) {
|
|
211
|
+
const eps = config.text.rmsNormEps;
|
|
212
|
+
this.preNorm = new RMSNorm(weights.tensor(`${prefix}.pre_norm.weight`), eps);
|
|
213
|
+
this.postNorm = new RMSNorm(null, eps);
|
|
214
|
+
this.gate = QuantizedLinear.load(weights, `${prefix}.gate_proj`, config);
|
|
215
|
+
this.up = QuantizedLinear.load(weights, `${prefix}.up_proj`, config);
|
|
216
|
+
this.down = QuantizedLinear.load(weights, `${prefix}.down_proj`, config);
|
|
217
|
+
}
|
|
218
|
+
forward(inputsEmbeds: MlxArray, signal: MlxArray): MlxArray {
|
|
219
|
+
const normed = this.preNorm.forward(signal);
|
|
220
|
+
const m = this.activation.forward(this.gate.forward(normed), this.up.forward(normed));
|
|
221
|
+
normed.dispose();
|
|
222
|
+
const s = this.down.forward(m);
|
|
223
|
+
m.dispose();
|
|
224
|
+
const sum = ops.add(inputsEmbeds, s);
|
|
225
|
+
s.dispose();
|
|
226
|
+
const out = this.postNorm.forward(sum);
|
|
227
|
+
sum.dispose();
|
|
228
|
+
return out;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Attention through the read its pass names: the encoder passes append to the
|
|
233
|
+
* cache, the decoder canvas pass reads the stored encoder K/V with the canvas
|
|
234
|
+
* K/V without storing them. The cache builds the mask. scale=1.0, no softcap. */
|
|
235
|
+
class DiffAttention {
|
|
236
|
+
readonly isSliding: boolean;
|
|
237
|
+
readonly headDim: number;
|
|
238
|
+
readonly nHeads: number;
|
|
239
|
+
readonly nKvHeads: number;
|
|
240
|
+
readonly qProj: QuantizedLinear;
|
|
241
|
+
readonly kProj: QuantizedLinear;
|
|
242
|
+
readonly vProj: QuantizedLinear | null;
|
|
243
|
+
readonly oProj: QuantizedLinear;
|
|
244
|
+
readonly qNorm: RMSNorm;
|
|
245
|
+
readonly kNorm: RMSNorm;
|
|
246
|
+
readonly vNorm: RMSNorm; // RMSNormNoScale (null weight)
|
|
247
|
+
readonly ropeBase: number | null;
|
|
248
|
+
readonly ropeFreqs: MlxArray | null;
|
|
249
|
+
|
|
250
|
+
constructor(weights: Weights, config: ModelConfig, prefix: string, layerType: string) {
|
|
251
|
+
const t = config.text;
|
|
252
|
+
this.isSliding = layerType === "sliding_attention";
|
|
253
|
+
this.headDim = this.isSliding ? t.headDim : t.globalHeadDim;
|
|
254
|
+
this.nHeads = t.numAttentionHeads;
|
|
255
|
+
this.nKvHeads = this.isSliding ? t.numKeyValueHeads : t.numGlobalKeyValueHeads;
|
|
256
|
+
|
|
257
|
+
this.qProj = QuantizedLinear.load(weights, `${prefix}.q_proj`, config);
|
|
258
|
+
this.kProj = QuantizedLinear.load(weights, `${prefix}.k_proj`, config);
|
|
259
|
+
// Full-attention layers have no v_proj — they reuse the (pre-norm) k tensor.
|
|
260
|
+
this.vProj = this.isSliding
|
|
261
|
+
? QuantizedLinear.load(weights, `${prefix}.v_proj`, config)
|
|
262
|
+
: null;
|
|
263
|
+
this.oProj = QuantizedLinear.load(weights, `${prefix}.o_proj`, config);
|
|
264
|
+
this.qNorm = new RMSNorm(weights.tensor(`${prefix}.q_norm.weight`), t.rmsNormEps);
|
|
265
|
+
this.kNorm = new RMSNorm(weights.tensor(`${prefix}.k_norm.weight`), t.rmsNormEps);
|
|
266
|
+
this.vNorm = new RMSNorm(null, t.rmsNormEps);
|
|
267
|
+
|
|
268
|
+
// RoPE: default (sliding, theta 1e4) or proportional (full, partial 0.25,
|
|
269
|
+
// theta 1e6). Proportional rotates only floor(head_dim*factor) dims; the
|
|
270
|
+
// unrotated tail is freq=Infinity (cos=1, sin=0 → identity). Mirrors
|
|
271
|
+
// packages/inference/src/models/gemma4/model.ts initialize_rope.
|
|
272
|
+
const rp = t.ropeParameters[this.isSliding ? "sliding_attention" : "full_attention"]!;
|
|
273
|
+
if (rp.ropeType === "default") {
|
|
274
|
+
this.ropeBase = rp.ropeTheta;
|
|
275
|
+
this.ropeFreqs = null;
|
|
276
|
+
} else {
|
|
277
|
+
const rotated = Math.floor(this.headDim * rp.partialRotaryFactor);
|
|
278
|
+
const n = this.headDim / 2;
|
|
279
|
+
const exponentsRaw = ops.arange(0, rotated, 2, Dtype.float32);
|
|
280
|
+
const dims = ops.scalarLike(this.headDim, exponentsRaw);
|
|
281
|
+
const exponents = ops.div(exponentsRaw, dims);
|
|
282
|
+
exponentsRaw.dispose();
|
|
283
|
+
dims.dispose();
|
|
284
|
+
const base = ops.scalarLike(rp.ropeTheta, exponents);
|
|
285
|
+
let rotFreqs = ops.pow(base, exponents);
|
|
286
|
+
base.dispose();
|
|
287
|
+
exponents.dispose();
|
|
288
|
+
if (rp.factor !== 1.0) {
|
|
289
|
+
const f = ops.scalarLike(rp.factor, rotFreqs);
|
|
290
|
+
rotFreqs = disposing(rotFreqs, ops.mul(f, rotFreqs));
|
|
291
|
+
f.dispose();
|
|
292
|
+
}
|
|
293
|
+
this.ropeBase = null;
|
|
294
|
+
const tailLen = n - rotated / 2;
|
|
295
|
+
if (tailLen > 0) {
|
|
296
|
+
const tail = MlxArray.fromFloat32(new Float32Array(tailLen).fill(Infinity), [tailLen]);
|
|
297
|
+
this.ropeFreqs = ops.concatAxis([rotFreqs, tail], 0);
|
|
298
|
+
rotFreqs.dispose();
|
|
299
|
+
tail.dispose();
|
|
300
|
+
} else {
|
|
301
|
+
this.ropeFreqs = rotFreqs;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#rope(x: MlxArray, offset: number): MlxArray {
|
|
307
|
+
return ops.rope(x, this.headDim, this.ropeBase, offset, this.ropeFreqs);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** `offset` is the RoPE position of `x`'s first token, which the pass chooses
|
|
311
|
+
* (the reference encoder ropes every pass from 0). */
|
|
312
|
+
forward(x: MlxArray, cache: Cache, read: PassRead, offset: number): MlxArray {
|
|
313
|
+
const [B, L] = x.shape as [number, number, number];
|
|
314
|
+
|
|
315
|
+
const qFlat = this.qProj.forward(x);
|
|
316
|
+
let q = ops.reshape(qFlat, [B, L, this.nHeads, this.headDim]);
|
|
317
|
+
qFlat.dispose();
|
|
318
|
+
q = disposing(q, this.qNorm.forward(q));
|
|
319
|
+
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
320
|
+
q = disposing(q, this.#rope(q, offset));
|
|
321
|
+
|
|
322
|
+
const kFlat = this.kProj.forward(x);
|
|
323
|
+
const kReshaped = ops.reshape(kFlat, [B, L, this.nKvHeads, this.headDim]);
|
|
324
|
+
kFlat.dispose();
|
|
325
|
+
// v reuses the pre-norm k tensor on full layers (v_proj is None there).
|
|
326
|
+
let vReshaped: MlxArray;
|
|
327
|
+
if (this.vProj) {
|
|
328
|
+
const vFlat = this.vProj.forward(x);
|
|
329
|
+
vReshaped = ops.reshape(vFlat, [B, L, this.nKvHeads, this.headDim]);
|
|
330
|
+
vFlat.dispose();
|
|
331
|
+
} else {
|
|
332
|
+
vReshaped = kReshaped;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let k = this.kNorm.forward(kReshaped);
|
|
336
|
+
k = disposing(k, ops.transposeAxes(k, [0, 2, 1, 3]));
|
|
337
|
+
k = disposing(k, this.#rope(k, offset));
|
|
338
|
+
let v = this.vNorm.forward(vReshaped); // no RoPE on V
|
|
339
|
+
v = disposing(v, ops.transposeAxes(v, [0, 2, 1, 3]));
|
|
340
|
+
if (vReshaped !== kReshaped) vReshaped.dispose();
|
|
341
|
+
kReshaped.dispose();
|
|
342
|
+
|
|
343
|
+
let attended: AttentionRead;
|
|
344
|
+
try { attended = read(cache, k, v); } finally { k.dispose(); v.dispose(); }
|
|
345
|
+
let attn: MlxArray;
|
|
346
|
+
try { attn = attended.attend(q, 1.0); } finally { attended.dispose(); q.dispose(); }
|
|
347
|
+
|
|
348
|
+
let out = ops.transposeAxes(attn, [0, 2, 1, 3]);
|
|
349
|
+
attn.dispose();
|
|
350
|
+
out = disposing(out, ops.reshape(out, [B, L, this.nHeads * this.headDim]));
|
|
351
|
+
const o = this.oProj.forward(out);
|
|
352
|
+
out.dispose();
|
|
353
|
+
return o;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** One decoder layer: attention residual + a PARALLEL dense-MLP + MoE branch,
|
|
358
|
+
* combined through 7 RMSNorms and scaled by `layer_scalar` (the encoder pass
|
|
359
|
+
* overrides it with the per-layer encoder scalar). */
|
|
360
|
+
class DiffDecoderLayer {
|
|
361
|
+
readonly layerType: string;
|
|
362
|
+
readonly attn: DiffAttention;
|
|
363
|
+
readonly mlp: GegluMLP;
|
|
364
|
+
readonly router: DiffRouter;
|
|
365
|
+
readonly experts: DiffExperts;
|
|
366
|
+
readonly inputNorm: RMSNorm;
|
|
367
|
+
readonly postAttnNorm: RMSNorm;
|
|
368
|
+
readonly preFfNorm: RMSNorm;
|
|
369
|
+
readonly postFfNorm: RMSNorm;
|
|
370
|
+
readonly postFfNorm1: RMSNorm;
|
|
371
|
+
readonly postFfNorm2: RMSNorm;
|
|
372
|
+
readonly preFfNorm2: RMSNorm;
|
|
373
|
+
readonly layerScalar: MlxArray;
|
|
374
|
+
|
|
375
|
+
constructor(weights: Weights, config: ModelConfig, idx: number, compiledGeglu: boolean) {
|
|
376
|
+
const t = config.text;
|
|
377
|
+
const prefix = `model.decoder.layers.${idx}`;
|
|
378
|
+
this.layerType = t.layerTypes[idx]!;
|
|
379
|
+
const norm = (n: string) => new RMSNorm(weights.tensor(`${prefix}.${n}.weight`), t.rmsNormEps);
|
|
380
|
+
this.attn = new DiffAttention(weights, config, `${prefix}.self_attn`, this.layerType);
|
|
381
|
+
this.mlp = new GegluMLP(weights, config, `${prefix}.mlp`, compiledGeglu);
|
|
382
|
+
this.router = new DiffRouter(weights, config, `${prefix}.router`);
|
|
383
|
+
this.experts = new DiffExperts(weights, config, `${prefix}.experts`, compiledGeglu);
|
|
384
|
+
this.inputNorm = norm("input_layernorm");
|
|
385
|
+
this.postAttnNorm = norm("post_attention_layernorm");
|
|
386
|
+
this.preFfNorm = norm("pre_feedforward_layernorm");
|
|
387
|
+
this.postFfNorm = norm("post_feedforward_layernorm");
|
|
388
|
+
this.postFfNorm1 = norm("post_feedforward_layernorm_1");
|
|
389
|
+
this.postFfNorm2 = norm("post_feedforward_layernorm_2");
|
|
390
|
+
this.preFfNorm2 = norm("pre_feedforward_layernorm_2");
|
|
391
|
+
this.layerScalar = weights.tensor(`${prefix}.layer_scalar`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
forward(
|
|
395
|
+
x: MlxArray,
|
|
396
|
+
cache: Cache,
|
|
397
|
+
read: PassRead,
|
|
398
|
+
offset: number,
|
|
399
|
+
layerScalar: MlxArray | null,
|
|
400
|
+
): MlxArray { return this.forwardWith(x,cache,read,offset,layerScalar,this.mlp,this.experts); }
|
|
401
|
+
|
|
402
|
+
forwardWith(x: MlxArray, cache: Cache, read: PassRead, offset: number, layerScalar: MlxArray | null,
|
|
403
|
+
mlp: {forward(x:MlxArray):MlxArray}, experts: {forward(x:MlxArray,indices:MlxArray,weights:MlxArray):MlxArray}): MlxArray {
|
|
404
|
+
// attention
|
|
405
|
+
let residual = x;
|
|
406
|
+
let h = this.inputNorm.forward(x);
|
|
407
|
+
h = disposing(h, this.attn.forward(h, cache, read, offset));
|
|
408
|
+
h = disposing(h, this.postAttnNorm.forward(h));
|
|
409
|
+
h = disposing(h, ops.add(residual, h));
|
|
410
|
+
|
|
411
|
+
// parallel dense-MLP + MoE feedforward
|
|
412
|
+
residual = h;
|
|
413
|
+
const dense = this.preFfNorm.forward(h);
|
|
414
|
+
let h1 = mlp.forward(dense);
|
|
415
|
+
dense.dispose();
|
|
416
|
+
h1 = disposing(h1, this.postFfNorm1.forward(h1));
|
|
417
|
+
|
|
418
|
+
const shape = residual.shape;
|
|
419
|
+
const hidden = shape[shape.length - 1]!;
|
|
420
|
+
const flat = ops.reshape(residual, [-1, hidden]);
|
|
421
|
+
const { indices: rawIdx, weights: topw } = this.router.forward(flat);
|
|
422
|
+
// stop_gradient the routing indices so the MoE backward (gather_qmm vjp)
|
|
423
|
+
// doesn't try to differentiate the non-differentiable top-k indices (the
|
|
424
|
+
// reference does this in training mode). Identity in the forward, so D1/D2
|
|
425
|
+
// stay bit-exact; required for D5 LoRA grads to flow through the experts.
|
|
426
|
+
const indices = ops.stopGradient(rawIdx);
|
|
427
|
+
rawIdx.dispose();
|
|
428
|
+
let h2 = this.preFfNorm2.forward(flat);
|
|
429
|
+
flat.dispose();
|
|
430
|
+
h2 = disposing(h2, experts.forward(h2, indices, topw));
|
|
431
|
+
indices.dispose();
|
|
432
|
+
topw.dispose();
|
|
433
|
+
h2 = disposing(h2, ops.reshape(h2, shape));
|
|
434
|
+
h2 = disposing(h2, this.postFfNorm2.forward(h2));
|
|
435
|
+
|
|
436
|
+
const sum = ops.add(h1, h2);
|
|
437
|
+
h1.dispose();
|
|
438
|
+
h2.dispose();
|
|
439
|
+
h = this.postFfNorm.forward(sum);
|
|
440
|
+
sum.dispose();
|
|
441
|
+
h = disposing(h, ops.add(residual, h));
|
|
442
|
+
residual.dispose();
|
|
443
|
+
|
|
444
|
+
const scalar = layerScalar ?? this.layerScalar;
|
|
445
|
+
h = disposing(h, ops.mul(h, scalar));
|
|
446
|
+
return h;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export class DiffusionGemmaModel implements MlxDeclaredGraph, MlxDenoisingOperations {
|
|
451
|
+
readonly config: ModelConfig;
|
|
452
|
+
readonly weightsBytes: number;
|
|
453
|
+
readonly embed: QuantizedEmbedding;
|
|
454
|
+
readonly embedScale: number;
|
|
455
|
+
readonly layers: DiffDecoderLayer[];
|
|
456
|
+
readonly finalNorm: RMSNorm;
|
|
457
|
+
readonly selfConditioning: SelfConditioning;
|
|
458
|
+
readonly encoderLayerScalars: MlxArray[];
|
|
459
|
+
readonly canvasLength: number;
|
|
460
|
+
readonly softcap: number;
|
|
461
|
+
readonly slidingWindow: number;
|
|
462
|
+
// Shared RuntimeModel surface. LoRA mounts onto the (quantized) decoder
|
|
463
|
+
// linears like the AR models; the diffusion engine activates it per request.
|
|
464
|
+
readonly loraState = new LoraState();
|
|
465
|
+
/** Denoising rows interleave through their own grouped method; images enter as
|
|
466
|
+
* the method's pixel prefill input. */
|
|
467
|
+
get graphCapabilities(): GraphDeclaration {
|
|
468
|
+
return declareGraph({ method: "denoising", media: { input: "pixels", video: false } });
|
|
469
|
+
}
|
|
470
|
+
/** The denoising objective: LoRA on the decoder blocks (the encoder reuses them, weight-tied). */
|
|
471
|
+
get trainable(): TrainableGraph {
|
|
472
|
+
return {
|
|
473
|
+
denoising: {
|
|
474
|
+
vocabSize: this.config.text.vocabSize,
|
|
475
|
+
layerModulePaths: this.layers.map((_, i) => `model.decoder.layers.${i}`),
|
|
476
|
+
canvasLogits: (promptIds, canvas) => (this as unknown as DiffusionGemmaModel & { makeCache(): Cache[] }).forwardCanvasLogitsArr(promptIds, canvas),
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
pixelInput(): PixelInput | null {
|
|
481
|
+
const tower = this.visionTower;
|
|
482
|
+
return tower ? { preprocess: bytes => tower.preprocess(bytes), spliceTokens: (ids, softTokens, tokenIds) => spliceImageTokens(ids, [softTokens], tokenIds) } : null;
|
|
483
|
+
}
|
|
484
|
+
/** Dedicated DiffusionGemma SigLIP vision tower (image-text-to-text). */
|
|
485
|
+
readonly visionTower: DiffusionVisionTower | null;
|
|
486
|
+
readonly imageTokenId: number;
|
|
487
|
+
|
|
488
|
+
/** The graph binds compiled GeGLU and softcap closures, matching the
|
|
489
|
+
* reference's mx.compile'd operations. */
|
|
490
|
+
readonly makeCache!: () => Cache[];
|
|
491
|
+
readonly denoisingDecode: readonly MlxDenoisingDecodeRange[];
|
|
492
|
+
readonly denoisingAppend: readonly MlxDenoisingAppendRange[];
|
|
493
|
+
readonly denoisingPrefill: readonly MlxDenoisingPrefillRange[];
|
|
494
|
+
readonly #canvasLayers: readonly {forward: DiffDecoderLayer["forward"]}[];
|
|
495
|
+
readonly #unsortedLayers: readonly {forward: DiffDecoderLayer["forward"]}[];
|
|
496
|
+
readonly #sortedLayers: readonly {forward: DiffDecoderLayer["forward"]}[];
|
|
497
|
+
/** The composition this graph was built for: its KV scheme and paging are its caches' (`makeCache`). */
|
|
498
|
+
|
|
499
|
+
constructor(weights: Weights, config: ModelConfig) {
|
|
500
|
+
const t = config.text;
|
|
501
|
+
this.config = config;
|
|
502
|
+
|
|
503
|
+
this.weightsBytes = [...weights.shards.files.values()].reduce((a, f) => a + f.mmap.size, 0);
|
|
504
|
+
this.embed = QuantizedEmbedding.load(weights, "model.decoder.embed_tokens", config);
|
|
505
|
+
this.embedScale = Math.pow(t.hiddenSize, 0.5);
|
|
506
|
+
this.layers = Array.from(
|
|
507
|
+
{ length: t.numHiddenLayers },
|
|
508
|
+
(_, i) => new DiffDecoderLayer(weights, config, i, true),
|
|
509
|
+
);
|
|
510
|
+
this.finalNorm = new RMSNorm(weights.tensor("model.decoder.norm.weight"), t.rmsNormEps);
|
|
511
|
+
this.selfConditioning = new SelfConditioning(weights, config, "model.decoder.self_conditioning", true);
|
|
512
|
+
this.encoderLayerScalars = this.layers.map((_, i) =>
|
|
513
|
+
weights.tensor(`model.encoder.language_model.layers.${i}.layer_scalar`),
|
|
514
|
+
);
|
|
515
|
+
this.canvasLength = t.canvasLength ?? 256;
|
|
516
|
+
this.#unsortedLayers = this.layers.map(layer=>{
|
|
517
|
+
const mlp = new CompiledGegluMLP(layer.mlp), experts = new UnsortedDiffExperts(layer.experts);
|
|
518
|
+
return {forward:(x:MlxArray,cache:Cache,read:PassRead,offset:number,scalar:MlxArray|null)=>layer.forwardWith(x,cache,read,offset,scalar,mlp,experts)};
|
|
519
|
+
});
|
|
520
|
+
this.#sortedLayers = this.layers.map(layer=>{
|
|
521
|
+
const mlp = new CompiledGegluMLP(layer.mlp), experts = new SortedDiffExperts(layer.experts);
|
|
522
|
+
return {forward:(x:MlxArray,cache:Cache,read:PassRead,offset:number,scalar:MlxArray|null)=>layer.forwardWith(x,cache,read,offset,scalar,mlp,experts)};
|
|
523
|
+
});
|
|
524
|
+
this.#canvasLayers = this.canvasLength * t.topKExperts < 64 ? this.#unsortedLayers : this.#sortedLayers;
|
|
525
|
+
const boundary = Math.ceil(64/t.topKExperts);
|
|
526
|
+
const ranges = [{from:1,to:boundary-1,layers:this.#unsortedLayers},{from:boundary,to:Infinity,layers:this.#sortedLayers}].filter(range=>range.from<=range.to);
|
|
527
|
+
this.denoisingDecode = ranges.map(range=>({from:range.from,to:range.to,operation:(canvas,cache,feedback)=>this.decoderLogits(canvas,cache,feedback,range.layers)}));
|
|
528
|
+
this.denoisingAppend = ranges.map(range=>({from:range.from,to:range.to,operation:(tokens,cache)=>this.extendPrefill(tokens,cache,range.layers)}));
|
|
529
|
+
this.denoisingPrefill = ranges.map(range=>({from:range.from,to:range.to,operation:(ids,pixels)=>{
|
|
530
|
+
const cache = this.makeCache();
|
|
531
|
+
try {
|
|
532
|
+
if (pixels) this.encodeVisionPrefill(ids,pixels,cache,range.layers);
|
|
533
|
+
else this.#encodePrompt(ids,cache,range.layers);
|
|
534
|
+
return cache;
|
|
535
|
+
} catch(error) { cleanupFailure(error,()=>disposeResources(cache)); }
|
|
536
|
+
}}));
|
|
537
|
+
this.softcap = t.finalLogitSoftcapping ?? 30.0;
|
|
538
|
+
this.slidingWindow = t.slidingWindow;
|
|
539
|
+
const raw = (config as unknown as { raw: Record<string, any> }).raw;
|
|
540
|
+
this.imageTokenId = (raw.image_token_id as number) ?? 258880;
|
|
541
|
+
this.visionTower = raw.vision_config ? new DiffusionVisionTower(weights, config) : null;
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
this.cacheLayers = this.layers.map(layer=>layer.layerType === "sliding_attention" ? {kind:"sliding" as const,window:this.slidingWindow}:{kind:"full" as const,turboQuant:"deferred" as const});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** Build the merged prompt embeddings for an image+text turn: embed the text
|
|
548
|
+
* ids (image positions → pad) * embed_scale, then scatter the SigLIP vision
|
|
549
|
+
* features in at the contiguous <|image|> run. `pixels` is channel-first
|
|
550
|
+
* [1,3,H,W]. Reference EncoderModel._embed_inputs + get_image_features. */
|
|
551
|
+
#embedInputsVision(splicedIds: number[], pixels: MlxArray): MlxArray {
|
|
552
|
+
if (!this.visionTower) throw new Error("this checkpoint has no vision tower");
|
|
553
|
+
const padId = 0; // pad_token_id (reference where(vision_mask, pad, ids))
|
|
554
|
+
const llm = splicedIds.map((id) => (id === this.imageTokenId ? padId : id));
|
|
555
|
+
const ids = MlxArray.fromInt32(Int32Array.from(llm), [1, llm.length]);
|
|
556
|
+
let embeds = this.embed.encode(ids);
|
|
557
|
+
ids.dispose();
|
|
558
|
+
embeds = disposing(embeds, ops.mulScalar(embeds, this.embedScale)); // [1, L, hidden]
|
|
559
|
+
|
|
560
|
+
const feats = this.visionTower.getImageFeatures(pixels); // [1, numSoft, hidden]
|
|
561
|
+
const featsCast = feats.astype(embeds.dtype);
|
|
562
|
+
feats.dispose();
|
|
563
|
+
// image tokens are one contiguous run (boi + image*N + eoi)
|
|
564
|
+
const start = splicedIds.indexOf(this.imageTokenId);
|
|
565
|
+
const count = splicedIds.filter((id) => id === this.imageTokenId).length;
|
|
566
|
+
const hidden = embeds.shape[embeds.shape.length - 1]!;
|
|
567
|
+
embeds = disposing(
|
|
568
|
+
embeds,
|
|
569
|
+
ops.sliceUpdate(embeds, featsCast, [0, start, 0], [1, start + count, hidden]),
|
|
570
|
+
);
|
|
571
|
+
featsCast.dispose();
|
|
572
|
+
return embeds;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/** The vision prompt's image positions, bool [N]: every <|image|> token. The
|
|
576
|
+
* encoder window read lets them attend each other both ways (reference
|
|
577
|
+
* _vision_block_overlay). */
|
|
578
|
+
#imagePositions(splicedIds: number[]): MlxArray {
|
|
579
|
+
const flags = splicedIds.map((id) => (id === this.imageTokenId ? 1 : 0));
|
|
580
|
+
using ints = MlxArray.fromInt32(Int32Array.from(flags), [flags.length]);
|
|
581
|
+
return ints.astype(Dtype.bool);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/** Image-text-to-text encoder prefill: merged embeddings through all layers,
|
|
585
|
+
* each reading through the encoder window (causal over the whole prompt, no
|
|
586
|
+
* sliding window, image positions both ways; reference _make_encoder_masks +
|
|
587
|
+
* _vision_block_overlay, one mask for every layer), filling `cache`. The
|
|
588
|
+
* canvas decoder then reads it like the text path. */
|
|
589
|
+
encodeVisionPrefill(splicedIds: number[], pixels: MlxArray, cache: Cache[], layers: readonly {forward:DiffDecoderLayer["forward"]}[] = this.layers): void {
|
|
590
|
+
let h = this.#embedInputsVision(splicedIds, pixels); // [1, L, hidden] (*embed_scale)
|
|
591
|
+
using media = this.#imagePositions(splicedIds);
|
|
592
|
+
const read = encoderRead(media);
|
|
593
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
594
|
+
const layer = layers[i]!;
|
|
595
|
+
const next = layer.forward(h, cache[i]!, read, 0, this.encoderLayerScalars[i]!);
|
|
596
|
+
h.dispose();
|
|
597
|
+
h = next;
|
|
598
|
+
}
|
|
599
|
+
h.dispose();
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/** Releases the rope frequency tables its layers built; the weights are its owner's. */
|
|
603
|
+
dispose(): void {
|
|
604
|
+
disposeResources(this.layers.flatMap(layer => layer.attn.ropeFreqs ? [layer.attn.ropeFreqs] : []));
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** One cache per layer, the lego the composition's KV scheme names for it
|
|
608
|
+
* (bf16, bf16-first affine, per listed layer under a per-layer scheme, or
|
|
609
|
+
* TurboQuant on full-attention layers, read deferred as Gemma 4 reads it), a
|
|
610
|
+
* ring of the sliding window on sliding layers, all sharing one mask memo;
|
|
611
|
+
* under the composition's paging the full-attention layers hold pages of
|
|
612
|
+
* what the scheme names (`maybePageKv`, growing from one block). The encoder
|
|
613
|
+
* prefill fills them, the decoder canvas pass reads them. */
|
|
614
|
+
readonly cachePaging = true;
|
|
615
|
+
readonly cacheLayers: readonly MlxCacheLayer[];
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
/** Encoder prefill: run all layers over the prompt through the window read,
|
|
619
|
+
* filling the cache. Discards the hidden. */
|
|
620
|
+
encodePrompt(promptIds: number[], cache: Cache[]): void { this.#encodePrompt(promptIds,cache,this.layers); }
|
|
621
|
+
|
|
622
|
+
#encodePrompt(promptIds: number[], cache: Cache[], layers: readonly {forward:DiffDecoderLayer["forward"]}[]): void {
|
|
623
|
+
const ids = MlxArray.fromInt32(Int32Array.from(promptIds), [1, promptIds.length]);
|
|
624
|
+
let h = this.embed.encode(ids);
|
|
625
|
+
ids.dispose();
|
|
626
|
+
h = disposing(h, ops.mulScalar(h, this.embedScale));
|
|
627
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
628
|
+
const next = layers[i]!.forward(h, cache[i]!, windowRead, 0, this.encoderLayerScalars[i]!);
|
|
629
|
+
h.dispose();
|
|
630
|
+
h = next;
|
|
631
|
+
}
|
|
632
|
+
h.dispose();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** Diagnostic: run the encoder, invoking `onLayer(i, h)` after each layer
|
|
636
|
+
* (h is the raw layer output, pre-final-norm) and `onEmbed(h)` on the
|
|
637
|
+
* embedding. Returns the final pre-norm hidden. Fills `cache`. */
|
|
638
|
+
encoderTrace(
|
|
639
|
+
promptIds: number[],
|
|
640
|
+
cache: Cache[],
|
|
641
|
+
onEmbed: (h: MlxArray) => void,
|
|
642
|
+
onLayer: (i: number, h: MlxArray) => void,
|
|
643
|
+
): MlxArray {
|
|
644
|
+
const ids = MlxArray.fromInt32(Int32Array.from(promptIds), [1, promptIds.length]);
|
|
645
|
+
let h = this.embed.encode(ids);
|
|
646
|
+
ids.dispose();
|
|
647
|
+
h = disposing(h, ops.mulScalar(h, this.embedScale));
|
|
648
|
+
onEmbed(h);
|
|
649
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
650
|
+
const next = this.layers[i]!.forward(h, cache[i]!, windowRead, 0, this.encoderLayerScalars[i]!);
|
|
651
|
+
h.dispose();
|
|
652
|
+
h = next;
|
|
653
|
+
onLayer(i, h);
|
|
654
|
+
}
|
|
655
|
+
return h;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** Diagnostic: run encoder layer 0 sub-components (attention vs parallel
|
|
659
|
+
* dense/MoE) and dump each via `cb(name, h)`. */
|
|
660
|
+
diagLayer0(promptIds: number[], cache: Cache[], cb: (name: string, h: MlxArray) => void): void {
|
|
661
|
+
const ids = MlxArray.fromInt32(Int32Array.from(promptIds), [1, promptIds.length]);
|
|
662
|
+
let emb = this.embed.encode(ids);
|
|
663
|
+
ids.dispose();
|
|
664
|
+
emb = disposing(emb, ops.mulScalar(emb, this.embedScale));
|
|
665
|
+
const layer = this.layers[0]!;
|
|
666
|
+
const ls0 = this.encoderLayerScalars[0]!;
|
|
667
|
+
|
|
668
|
+
const residual = emb;
|
|
669
|
+
const hn = layer.inputNorm.forward(emb);
|
|
670
|
+
const attnOut = layer.attn.forward(hn, cache[0]!, windowRead, 0);
|
|
671
|
+
hn.dispose();
|
|
672
|
+
cb("attn_out", attnOut);
|
|
673
|
+
const postAttn = layer.postAttnNorm.forward(attnOut);
|
|
674
|
+
attnOut.dispose();
|
|
675
|
+
const hMid = ops.add(residual, postAttn);
|
|
676
|
+
postAttn.dispose();
|
|
677
|
+
cb("h_mid", hMid);
|
|
678
|
+
|
|
679
|
+
const dense = layer.preFfNorm.forward(hMid);
|
|
680
|
+
let h1 = layer.mlp.forward(dense);
|
|
681
|
+
dense.dispose();
|
|
682
|
+
h1 = disposing(h1, layer.postFfNorm1.forward(h1));
|
|
683
|
+
cb("h1", h1);
|
|
684
|
+
h1.dispose();
|
|
685
|
+
|
|
686
|
+
const shape = hMid.shape;
|
|
687
|
+
const hidden = shape[shape.length - 1]!;
|
|
688
|
+
const flat = ops.reshape(hMid, [-1, hidden]);
|
|
689
|
+
const { indices, weights } = layer.router.forward(flat);
|
|
690
|
+
cb("router_idx", indices.astype(Dtype.float32));
|
|
691
|
+
cb("router_w", weights.astype(Dtype.float32));
|
|
692
|
+
let h2 = layer.preFfNorm2.forward(flat);
|
|
693
|
+
flat.dispose();
|
|
694
|
+
h2 = disposing(h2, layer.experts.forward(h2, indices, weights));
|
|
695
|
+
indices.dispose();
|
|
696
|
+
weights.dispose();
|
|
697
|
+
h2 = disposing(h2, ops.reshape(h2, shape));
|
|
698
|
+
cb("experts_raw", h2);
|
|
699
|
+
h2 = disposing(h2, layer.postFfNorm2.forward(h2));
|
|
700
|
+
cb("h2", h2);
|
|
701
|
+
h2.dispose();
|
|
702
|
+
void ls0;
|
|
703
|
+
emb.dispose();
|
|
704
|
+
hMid.dispose();
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/** Diagnostic: encoder prefill then finalNorm(h) — matches the reference
|
|
708
|
+
* EncoderModel.__call__ output (decoder.norm(h)) for per-stage localization. */
|
|
709
|
+
encodePromptHidden(promptIds: number[], cache: Cache[]): MlxArray {
|
|
710
|
+
const ids = MlxArray.fromInt32(Int32Array.from(promptIds), [1, promptIds.length]);
|
|
711
|
+
let h = this.embed.encode(ids);
|
|
712
|
+
ids.dispose();
|
|
713
|
+
h = disposing(h, ops.mulScalar(h, this.embedScale));
|
|
714
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
715
|
+
const next = this.layers[i]!.forward(h, cache[i]!, windowRead, 0, this.encoderLayerScalars[i]!);
|
|
716
|
+
h.dispose();
|
|
717
|
+
h = next;
|
|
718
|
+
}
|
|
719
|
+
return disposing(h, this.finalNorm.forward(h));
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/** Embed the canvas + self-conditioning. For the first step both signals are
|
|
723
|
+
* None → zero signal (which still applies SelfConditioning's post RMSNorm). */
|
|
724
|
+
#embedCanvas(
|
|
725
|
+
canvasIds: MlxArray,
|
|
726
|
+
scLogits: MlxArray | null,
|
|
727
|
+
scEmbeddings: MlxArray | null,
|
|
728
|
+
): MlxArray {
|
|
729
|
+
let inputsEmbeds = this.embed.encode(canvasIds);
|
|
730
|
+
inputsEmbeds = disposing(inputsEmbeds, ops.mulScalar(inputsEmbeds, this.embedScale));
|
|
731
|
+
let soft: MlxArray;
|
|
732
|
+
let softOwned = true;
|
|
733
|
+
if (scEmbeddings) {
|
|
734
|
+
soft = scEmbeddings.astype(inputsEmbeds.dtype);
|
|
735
|
+
} else if (scLogits === null) {
|
|
736
|
+
soft = ops.zeros(inputsEmbeds.shape, inputsEmbeds.dtype);
|
|
737
|
+
} else {
|
|
738
|
+
// probs @ embed_tokens.weight (quantized, transpose=false), * embed_scale.
|
|
739
|
+
const probs = ops.softmaxAxis(scLogits, -1, true);
|
|
740
|
+
const probsCast = probs.astype(inputsEmbeds.dtype);
|
|
741
|
+
probs.dispose();
|
|
742
|
+
const se = ops.quantizedMatmul(
|
|
743
|
+
probsCast,
|
|
744
|
+
this.embed.w,
|
|
745
|
+
this.embed.scales,
|
|
746
|
+
this.embed.biases,
|
|
747
|
+
this.embed.spec,
|
|
748
|
+
false,
|
|
749
|
+
);
|
|
750
|
+
probsCast.dispose();
|
|
751
|
+
soft = ops.mulScalar(se, this.embedScale);
|
|
752
|
+
se.dispose();
|
|
753
|
+
}
|
|
754
|
+
const out = this.selfConditioning.forward(inputsEmbeds, soft);
|
|
755
|
+
inputsEmbeds.dispose();
|
|
756
|
+
if (softOwned) soft.dispose();
|
|
757
|
+
return out;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/** Decoder canvas pass over an on-device canvas: embed canvas -> 30 layers,
|
|
761
|
+
* each reading its cache through the block read (the cache builds the
|
|
762
|
+
* reference _make_decoder_masks sliding mask and keeps the newest
|
|
763
|
+
* sliding_window - 1 encoder positions) -> final norm. Returns hidden
|
|
764
|
+
* [1, canvasLength, hidden]. The D2 engine drives this per denoising step
|
|
765
|
+
* (canvas stays on-device). */
|
|
766
|
+
decodeCanvasArr(
|
|
767
|
+
canvasArr: MlxArray,
|
|
768
|
+
cache: Cache[],
|
|
769
|
+
scLogits: MlxArray | null = null,
|
|
770
|
+
scEmbeddings: MlxArray | null = null,
|
|
771
|
+
layers: readonly {forward:DiffDecoderLayer["forward"]}[] = this.layers,
|
|
772
|
+
): MlxArray {
|
|
773
|
+
let h = this.#embedCanvas(canvasArr, scLogits, scEmbeddings);
|
|
774
|
+
const offset = cache[0]!.offset;
|
|
775
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
776
|
+
const next = layers[i]!.forward(h, cache[i]!, blockRead, offset, null);
|
|
777
|
+
h.dispose();
|
|
778
|
+
h = next;
|
|
779
|
+
}
|
|
780
|
+
h = disposing(h, this.finalNorm.forward(h));
|
|
781
|
+
return h;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/** Decoder canvas pass from host ids (D1 path). */
|
|
785
|
+
decodeCanvas(
|
|
786
|
+
canvasIds: number[],
|
|
787
|
+
cache: Cache[],
|
|
788
|
+
scLogits: MlxArray | null = null,
|
|
789
|
+
scEmbeddings: MlxArray | null = null,
|
|
790
|
+
): MlxArray {
|
|
791
|
+
const ids = MlxArray.fromInt32(Int32Array.from(canvasIds), [1, canvasIds.length]);
|
|
792
|
+
const h = this.decodeCanvasArr(ids, cache, scLogits, scEmbeddings);
|
|
793
|
+
ids.dispose();
|
|
794
|
+
return h;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// ---- D2 denoising-engine surface ----
|
|
798
|
+
|
|
799
|
+
/** One denoising-step decoder pass -> softcapped logits [1, canvas, vocab]. */
|
|
800
|
+
decoderLogits(canvasArr: MlxArray, cache: Cache[], scEmbeddings: MlxArray | null, layers = this.#canvasLayers): MlxArray {
|
|
801
|
+
const h = this.decodeCanvasArr(canvasArr, cache, null, scEmbeddings,layers);
|
|
802
|
+
try { return this.logitsFromHidden(h); }
|
|
803
|
+
finally { h.dispose(); }
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/** Dequantized embedding table (once per call), for self-conditioning soft
|
|
807
|
+
* embeddings — matches optiq's `_diffusion_soft_embedding_weight`. */
|
|
808
|
+
dequantEmbedWeight(): MlxArray {
|
|
809
|
+
return ops.dequantize(this.embed.w, this.embed.scales, this.embed.biases, this.embed.spec);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/** Self-conditioning soft embeddings for the NEXT step (confidence sampler):
|
|
813
|
+
* softmax(processed, precise) @ dequant(embed) * embed_scale. */
|
|
814
|
+
softEmbeddings(processedLogits: MlxArray, dequantWeight: MlxArray): MlxArray {
|
|
815
|
+
const probs = ops.softmaxAxis(processedLogits, -1, true);
|
|
816
|
+
const probsCast = probs.astype(dequantWeight.dtype);
|
|
817
|
+
probs.dispose();
|
|
818
|
+
const se = ops.matmul(probsCast, dequantWeight);
|
|
819
|
+
probsCast.dispose();
|
|
820
|
+
const cast = se.astype(dequantWeight.dtype);
|
|
821
|
+
se.dispose();
|
|
822
|
+
const out = ops.mulScalar(cast, this.embedScale);
|
|
823
|
+
cast.dispose();
|
|
824
|
+
return out;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/** Re-prefill the encoder over an accepted canvas block (continuation), so the
|
|
828
|
+
* next block's decoder sees it as context. Appends to the existing cache. */
|
|
829
|
+
extendPrefill(canvasArr: MlxArray, cache: Cache[], layers: readonly {forward:DiffDecoderLayer["forward"]}[] = this.layers): void {
|
|
830
|
+
let h = this.embed.encode(canvasArr);
|
|
831
|
+
h = disposing(h, ops.mulScalar(h, this.embedScale));
|
|
832
|
+
for (let i = 0; i < this.layers.length; i++) {
|
|
833
|
+
const next = layers[i]!.forward(h, cache[i]!, windowRead, 0, this.encoderLayerScalars[i]!);
|
|
834
|
+
h.dispose();
|
|
835
|
+
h = next;
|
|
836
|
+
}
|
|
837
|
+
h.dispose();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/** Tied quantized head + fp32 softcap. hidden [1, L, hidden] -> [1, L, vocab]. */
|
|
841
|
+
logitsFromHidden(h: MlxArray): MlxArray {
|
|
842
|
+
const logits = this.embed.asLinear(h);
|
|
843
|
+
// fp32-upcast tanh softcap, fused like the reference's mx.compile'd make_compiled_softcap
|
|
844
|
+
// (stays f32; the golden is dumped f32). The flag-off arm spells out the same math.
|
|
845
|
+
const wide = logits.astype(Dtype.float32);
|
|
846
|
+
logits.dispose();
|
|
847
|
+
const capped = compiledLogitSoftcap(wide, this.softcap);
|
|
848
|
+
wide.dispose();
|
|
849
|
+
return capped;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/** D1 entry point: prompt + canvas ids -> softcapped logits [1, canvas, vocab].
|
|
853
|
+
* Builds a fresh cache, prefills the encoder, runs one decoder pass. */
|
|
854
|
+
forwardCanvasLogits(this: DiffusionGemmaModel & { makeCache(): Cache[] }, promptIds: number[], canvasIds: number[]): MlxArray {
|
|
855
|
+
const cache = this.makeCache();
|
|
856
|
+
try {
|
|
857
|
+
this.encodePrompt(promptIds, cache);
|
|
858
|
+
const h = this.decodeCanvas(canvasIds, cache);
|
|
859
|
+
const logits = this.logitsFromHidden(h);
|
|
860
|
+
h.dispose();
|
|
861
|
+
return logits;
|
|
862
|
+
} finally {
|
|
863
|
+
for (const c of cache) c.dispose();
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/** Training forward over an on-device canvas (D5 LoRA): prompt + canvas array
|
|
868
|
+
* -> softcapped logits [1, L, vocab], differentiable w.r.t. mounted LoRA. */
|
|
869
|
+
forwardCanvasLogitsArr(this: DiffusionGemmaModel & { makeCache(): Cache[] }, promptIds: number[], canvasArr: MlxArray): MlxArray {
|
|
870
|
+
const cache = this.makeCache();
|
|
871
|
+
try {
|
|
872
|
+
this.encodePrompt(promptIds, cache);
|
|
873
|
+
const h = this.decodeCanvasArr(canvasArr, cache);
|
|
874
|
+
const logits = this.logitsFromHidden(h);
|
|
875
|
+
h.dispose();
|
|
876
|
+
return logits;
|
|
877
|
+
} finally {
|
|
878
|
+
for (const c of cache) c.dispose();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
readonly prefixBase = "model.decoder";
|
|
883
|
+
|
|
884
|
+
/** LoRA-able decoder linears (the optiq diffusion DEFAULT_LORA_KEYS: attention
|
|
885
|
+
* q/k/v/o + dense MLP gate/up/down per layer; full layers have no v_proj). */
|
|
886
|
+
loraTargets(): Map<string, QuantizedLinear> {
|
|
887
|
+
const m = new Map<string, QuantizedLinear>();
|
|
888
|
+
this.layers.forEach((layer, i) => {
|
|
889
|
+
const p = `model.decoder.layers.${i}`;
|
|
890
|
+
m.set(`${p}.self_attn.q_proj`, layer.attn.qProj);
|
|
891
|
+
m.set(`${p}.self_attn.k_proj`, layer.attn.kProj);
|
|
892
|
+
if (layer.attn.vProj) m.set(`${p}.self_attn.v_proj`, layer.attn.vProj);
|
|
893
|
+
m.set(`${p}.self_attn.o_proj`, layer.attn.oProj);
|
|
894
|
+
m.set(`${p}.mlp.gate_proj`, layer.mlp.gate);
|
|
895
|
+
m.set(`${p}.mlp.up_proj`, layer.mlp.up);
|
|
896
|
+
m.set(`${p}.mlp.down_proj`, layer.mlp.down);
|
|
897
|
+
});
|
|
898
|
+
return m;
|
|
899
|
+
}
|
|
900
|
+
}
|