@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,439 @@
|
|
|
1
|
+
// Remote media (image_url / audio_url) fetching with an SSRF/resource-
|
|
2
|
+
// exhaustion policy. A chat request's URL is attacker-controlled input to
|
|
3
|
+
// a server that may hold credentials for its LAN (the classic target:
|
|
4
|
+
// cloud metadata at 169.254.169.254, router admin pages, other local
|
|
5
|
+
// services), so by default the destination of every hop — the original
|
|
6
|
+
// URL and each redirect — must be a public http(s) host, the whole fetch
|
|
7
|
+
// is wall-clock bounded, and the response is size-capped.
|
|
8
|
+
//
|
|
9
|
+
// Callers may allow private/loopback/link-local destinations with the policy's
|
|
10
|
+
// `allowPrivate` option. Scheme/timeout/size limits still apply.
|
|
11
|
+
//
|
|
12
|
+
// DNS names are resolved and every resolved address is checked before the
|
|
13
|
+
// fetch. The request then dials one of those checked IP literals while
|
|
14
|
+
// preserving the logical Host header and HTTPS SNI, so the transport cannot
|
|
15
|
+
// re-resolve to a private address between validation and connect.
|
|
16
|
+
//
|
|
17
|
+
// This module must stay free of mlx imports so packages/inference/tests/input/media-fetch.test.ts
|
|
18
|
+
// runs model-free.
|
|
19
|
+
|
|
20
|
+
import { lookup } from "node:dns/promises";
|
|
21
|
+
import { runtimeValue } from "../runtime/config";
|
|
22
|
+
|
|
23
|
+
export interface MediaFetchPolicy {
|
|
24
|
+
/** Permit private/loopback/link-local destinations (LAN hosts). */
|
|
25
|
+
allowPrivate: boolean;
|
|
26
|
+
/** Optional caller-specific explanation when private destinations fail. */
|
|
27
|
+
privateDestinationHint?: string;
|
|
28
|
+
/** Wall-clock budget for the whole fetch — every redirect hop plus the
|
|
29
|
+
* body read share one AbortSignal.timeout. */
|
|
30
|
+
timeoutMs: number;
|
|
31
|
+
/** Response body cap in bytes (checked against Content-Length up front
|
|
32
|
+
* AND while streaming, since the header is optional/spoofable). */
|
|
33
|
+
maxBytes: number;
|
|
34
|
+
maxRedirects: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type RestrictedFetch = (
|
|
38
|
+
input: string | URL,
|
|
39
|
+
init?: BunFetchRequestInit,
|
|
40
|
+
) => Promise<Response>;
|
|
41
|
+
|
|
42
|
+
export type ResolveHost = (
|
|
43
|
+
hostname: string,
|
|
44
|
+
) => Promise<ReadonlyArray<{ address: string }>>;
|
|
45
|
+
|
|
46
|
+
export interface RestrictedFetchDependencies {
|
|
47
|
+
fetch: RestrictedFetch;
|
|
48
|
+
resolve: ResolveHost;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface RestrictedFetchOptions {
|
|
52
|
+
/** Optional caller cancellation, combined with the policy timeout. */
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
/** Request headers applied on every validated redirect hop. */
|
|
55
|
+
headers?: Record<string, string>;
|
|
56
|
+
/** Test seam for deterministic DNS/redirect/body behavior. */
|
|
57
|
+
dependencies?: Partial<RestrictedFetchDependencies>;
|
|
58
|
+
/** Media callers reject non-2xx before reading; web_fetch returns the body. */
|
|
59
|
+
rejectHttpErrors?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface RestrictedFetchResult {
|
|
63
|
+
ok: boolean;
|
|
64
|
+
status: number;
|
|
65
|
+
finalUrl: string;
|
|
66
|
+
contentType: string;
|
|
67
|
+
bytes: Uint8Array;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function defaultMediaFetchPolicy(): MediaFetchPolicy {
|
|
71
|
+
return {
|
|
72
|
+
allowPrivate: runtimeValue("MLX_BUN_ALLOW_PRIVATE_MEDIA") === "1",
|
|
73
|
+
// 10 s / 64 MB: well above what a 30 s audio clip (~5 MB of 16 kHz
|
|
74
|
+
// WAV, less compressed) or any supported image needs.
|
|
75
|
+
timeoutMs: 10_000,
|
|
76
|
+
maxBytes: 64 * 1024 * 1024,
|
|
77
|
+
maxRedirects: 5,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const PRIVATE_HINT =
|
|
82
|
+
"private/loopback hosts are blocked by default; set MediaFetchPolicy.allowPrivate to true to fetch from LAN hosts";
|
|
83
|
+
|
|
84
|
+
function privateHint(policy: MediaFetchPolicy): string {
|
|
85
|
+
return policy.privateDestinationHint ?? PRIVATE_HINT;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseIpv4(s: string): number[] | null {
|
|
89
|
+
const parts = s.split(".");
|
|
90
|
+
if (parts.length !== 4) return null;
|
|
91
|
+
const out: number[] = [];
|
|
92
|
+
for (const p of parts) {
|
|
93
|
+
if (!/^\d{1,3}$/.test(p)) return null;
|
|
94
|
+
const n = Number(p);
|
|
95
|
+
if (n > 255) return null;
|
|
96
|
+
out.push(n);
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function blockedIpv4(o: number[]): boolean {
|
|
102
|
+
const [a, b] = o as [number, number];
|
|
103
|
+
if (a === 0 || a === 10 || a === 127) return true; // this-net, 10/8, loopback
|
|
104
|
+
if (a === 100 && b >= 64 && b <= 127) return true; // CGNAT 100.64/10
|
|
105
|
+
if (a === 169 && b === 254) return true; // link-local (cloud metadata lives here)
|
|
106
|
+
if (a === 172 && b >= 16 && b <= 31) return true; // 172.16/12
|
|
107
|
+
if (a === 192 && b === 168) return true; // 192.168/16
|
|
108
|
+
if (a >= 224) return true; // multicast + reserved + broadcast
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Expand an IPv6 literal (with `::` compression, an optional zone index,
|
|
113
|
+
* and an optional dotted-quad tail like ::ffff:127.0.0.1) into its 8
|
|
114
|
+
* 16-bit groups. null = not valid IPv6. */
|
|
115
|
+
function parseIpv6(addr: string): number[] | null {
|
|
116
|
+
let s = addr;
|
|
117
|
+
const pct = s.indexOf("%");
|
|
118
|
+
if (pct !== -1) s = s.slice(0, pct);
|
|
119
|
+
if (s.includes(".")) {
|
|
120
|
+
// rewrite the dotted-quad tail as two hex groups
|
|
121
|
+
const i = s.lastIndexOf(":");
|
|
122
|
+
const v4 = parseIpv4(s.slice(i + 1));
|
|
123
|
+
if (!v4) return null;
|
|
124
|
+
s =
|
|
125
|
+
s.slice(0, i + 1) +
|
|
126
|
+
(((v4[0]! << 8) | v4[1]!).toString(16)) + ":" +
|
|
127
|
+
(((v4[2]! << 8) | v4[3]!).toString(16));
|
|
128
|
+
}
|
|
129
|
+
const halves = s.split("::");
|
|
130
|
+
if (halves.length > 2) return null;
|
|
131
|
+
const toGroups = (part: string): number[] | null => {
|
|
132
|
+
if (part === "") return [];
|
|
133
|
+
const out: number[] = [];
|
|
134
|
+
for (const g of part.split(":")) {
|
|
135
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(g)) return null;
|
|
136
|
+
out.push(parseInt(g, 16));
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
};
|
|
140
|
+
const head = toGroups(halves[0]!);
|
|
141
|
+
if (head === null) return null;
|
|
142
|
+
if (halves.length === 1) return head.length === 8 ? head : null;
|
|
143
|
+
const tail = toGroups(halves[1]!);
|
|
144
|
+
if (tail === null) return null;
|
|
145
|
+
if (head.length + tail.length > 7) return null;
|
|
146
|
+
return [...head, ...new Array(8 - head.length - tail.length).fill(0), ...tail];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function blockedIpv6(g: number[]): boolean {
|
|
150
|
+
if (g.every((x) => x === 0)) return true; // :: unspecified
|
|
151
|
+
if (g.slice(0, 7).every((x) => x === 0) && g[7] === 1) return true; // ::1
|
|
152
|
+
if ((g[0]! & 0xffc0) === 0xfe80) return true; // link-local fe80::/10
|
|
153
|
+
if ((g[0]! & 0xfe00) === 0xfc00) return true; // ULA fc00::/7
|
|
154
|
+
if ((g[0]! & 0xff00) === 0xff00) return true; // multicast ff00::/8
|
|
155
|
+
// v4-mapped (::ffff:0:0/96) and NAT64 (64:ff9b::/96) embed an IPv4
|
|
156
|
+
// address in the low 32 bits — judge the embedded address.
|
|
157
|
+
const v4Embedded =
|
|
158
|
+
(g.slice(0, 5).every((x) => x === 0) && g[5] === 0xffff) ||
|
|
159
|
+
(g[0] === 0x64 && g[1] === 0xff9b && g.slice(2, 6).every((x) => x === 0));
|
|
160
|
+
if (v4Embedded)
|
|
161
|
+
return blockedIpv4([g[6]! >> 8, g[6]! & 0xff, g[7]! >> 8, g[7]! & 0xff]);
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** True when `addr` is a private/loopback/link-local/reserved IP literal
|
|
166
|
+
* — or unparseable as an IP at all (fail closed; callers only pass
|
|
167
|
+
* things believed to be addresses). Accepts bracketed IPv6. */
|
|
168
|
+
export function isBlockedAddress(addr: string): boolean {
|
|
169
|
+
let s = addr;
|
|
170
|
+
if (s.startsWith("[") && s.endsWith("]")) s = s.slice(1, -1);
|
|
171
|
+
const v4 = parseIpv4(s);
|
|
172
|
+
if (v4) return blockedIpv4(v4);
|
|
173
|
+
const v6 = parseIpv6(s);
|
|
174
|
+
if (v6) return blockedIpv6(v6);
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Sync per-hop destination check: scheme + hostname-literal policy.
|
|
179
|
+
* Returns a rejection reason or null. DNS names pass here and get their
|
|
180
|
+
* resolved addresses checked in fetchMediaBytes (async). Exported pure
|
|
181
|
+
* so the policy is testable without any network. */
|
|
182
|
+
export function checkMediaUrl(u: URL, policy: MediaFetchPolicy): string | null {
|
|
183
|
+
if (u.protocol !== "http:" && u.protocol !== "https:")
|
|
184
|
+
return `unsupported url scheme "${u.protocol.replace(/:$/, "")}" (only http/https)`;
|
|
185
|
+
const host =
|
|
186
|
+
u.hostname.startsWith("[") && u.hostname.endsWith("]")
|
|
187
|
+
? u.hostname.slice(1, -1)
|
|
188
|
+
: u.hostname;
|
|
189
|
+
if (!host) return "missing host";
|
|
190
|
+
if (policy.allowPrivate) return null;
|
|
191
|
+
const lower = host.toLowerCase();
|
|
192
|
+
if (lower === "localhost" || lower.endsWith(".localhost"))
|
|
193
|
+
return `destination "${host}" — ${privateHint(policy)}`;
|
|
194
|
+
// WHATWG URL parsing already canonicalized numeric IPv4 forms
|
|
195
|
+
// (http://2130706433/, http://0x7f000001/ → 127.0.0.1).
|
|
196
|
+
if (parseIpv4(lower) || parseIpv6(lower)) {
|
|
197
|
+
if (isBlockedAddress(lower))
|
|
198
|
+
return `destination "${host}" — ${privateHint(policy)}`;
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export async function assertResolvesPublic(
|
|
204
|
+
hostname: string,
|
|
205
|
+
kind: string,
|
|
206
|
+
policy: MediaFetchPolicy,
|
|
207
|
+
resolve: ResolveHost = async (host) =>
|
|
208
|
+
await lookup(host, { all: true, verbatim: true }),
|
|
209
|
+
): Promise<void> {
|
|
210
|
+
if (policy.allowPrivate) return;
|
|
211
|
+
const host =
|
|
212
|
+
hostname.startsWith("[") && hostname.endsWith("]")
|
|
213
|
+
? hostname.slice(1, -1)
|
|
214
|
+
: hostname;
|
|
215
|
+
if (parseIpv4(host) || parseIpv6(host)) return; // literal — checked sync
|
|
216
|
+
let addrs: { address: string }[];
|
|
217
|
+
try {
|
|
218
|
+
addrs = [...await resolve(host)];
|
|
219
|
+
} catch {
|
|
220
|
+
throw new Error(`${kind} fetch failed: could not resolve host "${host}"`);
|
|
221
|
+
}
|
|
222
|
+
for (const a of addrs) {
|
|
223
|
+
if (isBlockedAddress(a.address))
|
|
224
|
+
throw new Error(
|
|
225
|
+
`${kind} url rejected: host "${host}" resolves to ${a.address} — ${privateHint(policy)}`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function resolveFetchAddresses(
|
|
231
|
+
hostname: string,
|
|
232
|
+
kind: string,
|
|
233
|
+
policy: MediaFetchPolicy,
|
|
234
|
+
resolve: ResolveHost,
|
|
235
|
+
): Promise<string[]> {
|
|
236
|
+
const host =
|
|
237
|
+
hostname.startsWith("[") && hostname.endsWith("]")
|
|
238
|
+
? hostname.slice(1, -1)
|
|
239
|
+
: hostname;
|
|
240
|
+
if (parseIpv4(host) || parseIpv6(host)) return [host];
|
|
241
|
+
if (policy.allowPrivate) return [];
|
|
242
|
+
let addrs: { address: string }[];
|
|
243
|
+
try {
|
|
244
|
+
addrs = [...await resolve(host)];
|
|
245
|
+
} catch {
|
|
246
|
+
throw new Error(`${kind} fetch failed: could not resolve host "${host}"`);
|
|
247
|
+
}
|
|
248
|
+
if (addrs.length === 0)
|
|
249
|
+
throw new Error(`${kind} fetch failed: host "${host}" resolved to no addresses`);
|
|
250
|
+
for (const a of addrs) {
|
|
251
|
+
if (isBlockedAddress(a.address))
|
|
252
|
+
throw new Error(
|
|
253
|
+
`${kind} url rejected: host "${host}" resolves to ${a.address} — ${privateHint(policy)}`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return addrs.map((a) => a.address);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Fetch an http(s) response through the shared public-destination policy.
|
|
261
|
+
* Every redirect is handled manually and revalidated, caller cancellation
|
|
262
|
+
* shares one signal with the wall-clock timeout, and the body is streamed
|
|
263
|
+
* into a byte-capped buffer.
|
|
264
|
+
*/
|
|
265
|
+
export async function fetchRestrictedHttpBytes(
|
|
266
|
+
url: string,
|
|
267
|
+
kind: string,
|
|
268
|
+
policy: MediaFetchPolicy,
|
|
269
|
+
options: RestrictedFetchOptions = {},
|
|
270
|
+
): Promise<RestrictedFetchResult> {
|
|
271
|
+
let current: URL;
|
|
272
|
+
try {
|
|
273
|
+
current = new URL(url);
|
|
274
|
+
} catch {
|
|
275
|
+
throw new Error(`unsupported ${kind} url scheme: ${url.slice(0, 16)}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const fetchImpl = options.dependencies?.fetch ??
|
|
279
|
+
((input: string | URL, init?: BunFetchRequestInit) => fetch(input, init));
|
|
280
|
+
const resolve = options.dependencies?.resolve ??
|
|
281
|
+
(async (host: string) => await lookup(host, { all: true, verbatim: true }));
|
|
282
|
+
const timeout = AbortSignal.timeout(policy.timeoutMs);
|
|
283
|
+
const signal = options.signal
|
|
284
|
+
? AbortSignal.any([options.signal, timeout])
|
|
285
|
+
: timeout;
|
|
286
|
+
const capMb = Math.round(policy.maxBytes / (1024 * 1024));
|
|
287
|
+
let overCap = false;
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
for (let hop = 0; ; hop++) {
|
|
291
|
+
const reason = checkMediaUrl(current, policy);
|
|
292
|
+
if (reason) throw new Error(`${kind} url rejected: ${reason}`);
|
|
293
|
+
const addresses = await resolveFetchAddresses(
|
|
294
|
+
current.hostname,
|
|
295
|
+
kind,
|
|
296
|
+
policy,
|
|
297
|
+
resolve,
|
|
298
|
+
);
|
|
299
|
+
const logicalHostname = current.hostname.replace(/^\[|\]$/g, "");
|
|
300
|
+
const connectUrl = new URL(current);
|
|
301
|
+
if (!policy.allowPrivate && addresses.length > 0) {
|
|
302
|
+
const address = addresses[0]!;
|
|
303
|
+
connectUrl.hostname = address.includes(":") ? `[${address}]` : address;
|
|
304
|
+
}
|
|
305
|
+
const headers = new Headers(options.headers);
|
|
306
|
+
if (connectUrl.hostname !== current.hostname && !headers.has("host"))
|
|
307
|
+
headers.set("host", current.host);
|
|
308
|
+
const init: BunFetchRequestInit = {
|
|
309
|
+
redirect: "manual",
|
|
310
|
+
signal,
|
|
311
|
+
headers,
|
|
312
|
+
};
|
|
313
|
+
if (current.protocol === "https:" && connectUrl.hostname !== current.hostname) {
|
|
314
|
+
init.tls = { serverName: logicalHostname };
|
|
315
|
+
}
|
|
316
|
+
const res = await fetchImpl(connectUrl, init);
|
|
317
|
+
if (res.status >= 300 && res.status < 400) {
|
|
318
|
+
const loc = res.headers.get("location");
|
|
319
|
+
if (!loc)
|
|
320
|
+
throw new Error(`${kind} fetch failed: ${res.status} redirect without location`);
|
|
321
|
+
if (hop >= policy.maxRedirects)
|
|
322
|
+
throw new Error(`${kind} fetch failed: more than ${policy.maxRedirects} redirects`);
|
|
323
|
+
try {
|
|
324
|
+
await res.body?.cancel();
|
|
325
|
+
} catch {}
|
|
326
|
+
try {
|
|
327
|
+
current = new URL(loc, current);
|
|
328
|
+
} catch {
|
|
329
|
+
throw new Error(`${kind} fetch failed: malformed redirect location`);
|
|
330
|
+
}
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (options.rejectHttpErrors && !res.ok) {
|
|
335
|
+
try {
|
|
336
|
+
await res.body?.cancel();
|
|
337
|
+
} catch {}
|
|
338
|
+
throw new Error(`${kind} fetch failed: ${res.status} ${current}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const declared = Number(res.headers.get("content-length") ?? "0");
|
|
342
|
+
if (Number.isFinite(declared) && declared > policy.maxBytes) {
|
|
343
|
+
overCap = true;
|
|
344
|
+
try {
|
|
345
|
+
await res.body?.cancel();
|
|
346
|
+
} catch {}
|
|
347
|
+
throw new Error(`${kind} response exceeds the ${capMb} MB limit`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let bytes: Uint8Array;
|
|
351
|
+
if (!res.body) {
|
|
352
|
+
bytes = new Uint8Array(await res.arrayBuffer());
|
|
353
|
+
if (bytes.byteLength > policy.maxBytes) {
|
|
354
|
+
overCap = true;
|
|
355
|
+
throw new Error(`${kind} response exceeds the ${capMb} MB limit`);
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
const reader = res.body.getReader();
|
|
359
|
+
const chunks: Uint8Array[] = [];
|
|
360
|
+
let total = 0;
|
|
361
|
+
for (;;) {
|
|
362
|
+
const { done, value } = await reader.read();
|
|
363
|
+
if (done) break;
|
|
364
|
+
total += value.byteLength;
|
|
365
|
+
if (total > policy.maxBytes) {
|
|
366
|
+
overCap = true;
|
|
367
|
+
try {
|
|
368
|
+
await reader.cancel();
|
|
369
|
+
} catch {}
|
|
370
|
+
throw new Error(`${kind} response exceeds the ${capMb} MB limit`);
|
|
371
|
+
}
|
|
372
|
+
chunks.push(value);
|
|
373
|
+
}
|
|
374
|
+
bytes = new Uint8Array(total);
|
|
375
|
+
let offset = 0;
|
|
376
|
+
for (const chunk of chunks) {
|
|
377
|
+
bytes.set(chunk, offset);
|
|
378
|
+
offset += chunk.byteLength;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
ok: res.ok,
|
|
384
|
+
status: res.status,
|
|
385
|
+
finalUrl: current.href,
|
|
386
|
+
contentType: res.headers.get("content-type") ?? "",
|
|
387
|
+
bytes,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
if (timeout.aborted && !options.signal?.aborted && !overCap)
|
|
392
|
+
throw new Error(
|
|
393
|
+
`${kind} fetch timed out after ${policy.timeoutMs} ms: ${url.slice(0, 200)}`,
|
|
394
|
+
);
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** Fetch image/audio bytes for an OpenAI-style image_url/audio_url part.
|
|
400
|
+
* data: URLs decode locally (no policy — the bytes came in the request);
|
|
401
|
+
* http(s) URLs go through the destination policy above, with every
|
|
402
|
+
* redirect hop re-validated, one wall-clock timeout, and a streaming
|
|
403
|
+
* size cap. Throws Error with a client-presentable message — callers
|
|
404
|
+
* surface it as a 400 (the prompt-build catch in
|
|
405
|
+
* apps/mlx-bun/src/server/chat-stage.ts). */
|
|
406
|
+
/** Video clips are legitimately larger than images/audio: same SSRF guard
|
|
407
|
+
* and timeout discipline, quadruple the body cap (a 30 s 1080p H.264 clip
|
|
408
|
+
* runs tens of MB; frame sampling truncates long clips server-side). */
|
|
409
|
+
export function videoMediaFetchPolicy(): MediaFetchPolicy {
|
|
410
|
+
return { ...defaultMediaFetchPolicy(), maxBytes: 256 * 1024 * 1024 };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export async function fetchMediaBytes(
|
|
414
|
+
url: string,
|
|
415
|
+
kind: "image" | "audio" | "video",
|
|
416
|
+
policy: MediaFetchPolicy = kind === "video"
|
|
417
|
+
? videoMediaFetchPolicy()
|
|
418
|
+
: defaultMediaFetchPolicy(),
|
|
419
|
+
): Promise<Uint8Array> {
|
|
420
|
+
if (url.startsWith("data:")) {
|
|
421
|
+
const comma = url.indexOf(",");
|
|
422
|
+
if (comma === -1) throw new Error("malformed data: URL");
|
|
423
|
+
const meta = url.slice(0, comma);
|
|
424
|
+
const body = url.slice(comma + 1);
|
|
425
|
+
if (!meta.includes("base64")) throw new Error("data: URL must be base64");
|
|
426
|
+
// The same body cap as the HTTP path — inline payloads must not become
|
|
427
|
+
// the uncapped route (base64 inflates 4/3, so check pre-decode too).
|
|
428
|
+
if (body.length * 0.75 > policy.maxBytes)
|
|
429
|
+
throw new Error(
|
|
430
|
+
`${kind} data: URL exceeds the ${Math.round(policy.maxBytes / 1024 / 1024)} MB cap`,
|
|
431
|
+
);
|
|
432
|
+
return Uint8Array.from(Buffer.from(body, "base64"));
|
|
433
|
+
}
|
|
434
|
+
return (
|
|
435
|
+
await fetchRestrictedHttpBytes(url, kind, policy, {
|
|
436
|
+
rejectHttpErrors: true,
|
|
437
|
+
})
|
|
438
|
+
).bytes;
|
|
439
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
import type { Cache } from "../contracts/mlx/cache";
|
|
4
|
+
import type { MlxPhaseGroup } from "../contracts/mlx/graph";
|
|
5
|
+
import { mapPackedTokens } from "./token-groups";
|
|
6
|
+
|
|
7
|
+
/** Group geometry and ownership for written packed operations. The graph
|
|
8
|
+
* supplies each layer's direct calls; this scope selects no numerical operation. */
|
|
9
|
+
export class PackedMixedScope {
|
|
10
|
+
readonly #hidden:MlxArray[]=[];
|
|
11
|
+
readonly #bounded:boolean;
|
|
12
|
+
constructor(readonly groups:readonly MlxPhaseGroup[],embed:(ids:MlxArray)=>MlxArray) {
|
|
13
|
+
this.#bounded=groups.some(group=>group.phase==="window" && group.rows>=5);
|
|
14
|
+
try { for (const group of groups) this.#hidden.push(embed(group.ids)); }
|
|
15
|
+
catch (error) { this[Symbol.dispose](); throw error; }
|
|
16
|
+
}
|
|
17
|
+
accept(layer:number,attention:(hidden:MlxArray,cache:Cache,phase:"decode"|"window")=>MlxArray,
|
|
18
|
+
mlp:(hidden:MlxArray)=>MlxArray):void {
|
|
19
|
+
const mids:MlxArray[]=[];
|
|
20
|
+
try {
|
|
21
|
+
for (const [row,group] of this.groups.entries()) mids.push(attention(this.#hidden[row]!,group.state[layer]!,group.phase==="decode"?"decode":"window"));
|
|
22
|
+
const outputs=mapPackedTokens(mids,mlp);
|
|
23
|
+
for (const [row,output] of outputs.entries()) { this.#hidden[row]!.dispose(); this.#hidden[row]=output; }
|
|
24
|
+
if (this.#bounded) {
|
|
25
|
+
const caches=this.groups.map(group=>group.state[layer]!);
|
|
26
|
+
const state=caches.map(cache=>cache.state());
|
|
27
|
+
try { ops.evalAll([...this.#hidden,...state.flat()]); }
|
|
28
|
+
finally { for (const [row,cache] of caches.entries()) if (cache.stateNeedsDispose) for (const array of state[row]!) array.dispose(); }
|
|
29
|
+
}
|
|
30
|
+
for (const [row,group] of this.groups.entries()) group.capture?.(layer,this.#hidden[row]!);
|
|
31
|
+
} finally { for (const mid of mids) mid.dispose(); }
|
|
32
|
+
}
|
|
33
|
+
finish(layer:number,norm:(hidden:MlxArray)=>MlxArray):MlxArray[] {
|
|
34
|
+
const result:MlxArray[]=[];
|
|
35
|
+
try {
|
|
36
|
+
for (const [row,hidden] of this.#hidden.entries()) {
|
|
37
|
+
const output=norm(hidden); result.push(output); this.groups[row]!.capture?.(layer,output);
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
} catch (error) { for (const output of result) output.dispose(); throw error; }
|
|
41
|
+
}
|
|
42
|
+
[Symbol.dispose]():void { for (const hidden of this.#hidden.splice(0)) hidden.dispose(); }
|
|
43
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Special tokens a model's generated text or media prompts depend on. The model
|
|
2
|
+
// profile declares them as token TEXT; the tokenizer in use resolves the ids, so
|
|
3
|
+
// no consumer carries a family's vocabulary ids.
|
|
4
|
+
|
|
5
|
+
import type { AudioTokenIds, VisionTokenIds } from "./vision/prompt";
|
|
6
|
+
import type { LoadedTokenizer } from "./tokenizer";
|
|
7
|
+
|
|
8
|
+
type Encoder = Pick<LoadedTokenizer, "encode">;
|
|
9
|
+
|
|
10
|
+
/** The id of `text` when the tokenizer encodes it as exactly one token. */
|
|
11
|
+
export function singleTokenId(tokenizer: Encoder, text: string): number | null {
|
|
12
|
+
const ids = tokenizer.encode(text, false);
|
|
13
|
+
return ids.length === 1 ? ids[0]! : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Markers that delimit tool calls and the reasoning channel in generated
|
|
17
|
+
* text. They are special tokens the content decoder strips, so a router must
|
|
18
|
+
* see them as ids. */
|
|
19
|
+
export interface SentinelTokenTexts {
|
|
20
|
+
readonly toolCallStart: string;
|
|
21
|
+
readonly toolCallEnd: string;
|
|
22
|
+
readonly channelStart: string;
|
|
23
|
+
readonly channelEnd: string;
|
|
24
|
+
}
|
|
25
|
+
export interface SentinelTokens {
|
|
26
|
+
readonly toolCallStart: number;
|
|
27
|
+
readonly toolCallEnd: number;
|
|
28
|
+
readonly channelStart: number;
|
|
29
|
+
readonly channelEnd: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Resolve declared sentinel texts against a tokenizer. A declared marker that
|
|
33
|
+
* is not one token there means the declaration does not fit this tokenizer. */
|
|
34
|
+
export function resolveSentinelTokens(tokenizer: Encoder, texts: SentinelTokenTexts): SentinelTokens {
|
|
35
|
+
const id = (text: string): number => {
|
|
36
|
+
const found = singleTokenId(tokenizer, text);
|
|
37
|
+
if (found === null) throw new Error(`declared sentinel ${JSON.stringify(text)} is not a single token in this tokenizer`);
|
|
38
|
+
return found;
|
|
39
|
+
};
|
|
40
|
+
return { toolCallStart: id(texts.toolCallStart), toolCallEnd: id(texts.toolCallEnd),
|
|
41
|
+
channelStart: id(texts.channelStart), channelEnd: id(texts.channelEnd) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Soft-token markers of a graph's tower prompts, by role. */
|
|
45
|
+
export interface MediaTokenTexts {
|
|
46
|
+
readonly vision: { readonly image: string; readonly begin: string; readonly end: string };
|
|
47
|
+
readonly audio: { readonly audio: string; readonly begin: string; readonly end: string };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const configId = (raw: Record<string, unknown>, key: string): number | undefined =>
|
|
51
|
+
typeof raw[key] === "number" ? raw[key] as number : undefined;
|
|
52
|
+
|
|
53
|
+
/** Image soft-token ids: the checkpoint's config wins, the declared token text
|
|
54
|
+
* fills what it omits. Null when neither supplies all three. */
|
|
55
|
+
export function resolveVisionTokenIds(
|
|
56
|
+
raw: Record<string, unknown>, tokenizer: Encoder, texts: MediaTokenTexts["vision"] | null,
|
|
57
|
+
): VisionTokenIds | null {
|
|
58
|
+
const from = (key: string, text: string | undefined) =>
|
|
59
|
+
configId(raw, key) ?? (text === undefined ? null : singleTokenId(tokenizer, text));
|
|
60
|
+
const imageTokenId = from("image_token_id", texts?.image), boiTokenId = from("boi_token_id", texts?.begin),
|
|
61
|
+
eoiTokenId = from("eoi_token_id", texts?.end);
|
|
62
|
+
return imageTokenId == null || boiTokenId == null || eoiTokenId == null ? null : { imageTokenId, boiTokenId, eoiTokenId };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Audio soft-token ids, for a checkpoint that declares `audio_config`; same precedence as vision. */
|
|
66
|
+
export function resolveAudioTokenIds(
|
|
67
|
+
raw: Record<string, unknown>, tokenizer: Encoder, texts: MediaTokenTexts["audio"] | null,
|
|
68
|
+
): AudioTokenIds | null {
|
|
69
|
+
if (!raw.audio_config) return null;
|
|
70
|
+
const from = (key: string, text: string | undefined) =>
|
|
71
|
+
configId(raw, key) ?? (text === undefined ? null : singleTokenId(tokenizer, text));
|
|
72
|
+
const audioTokenId = from("audio_token_id", texts?.audio), boaTokenId = from("boa_token_id", texts?.begin),
|
|
73
|
+
eoaTokenId = from("eoa_token_id", texts?.end);
|
|
74
|
+
return audioTokenId == null || boaTokenId == null || eoaTokenId == null ? null : { audioTokenId, boaTokenId, eoaTokenId };
|
|
75
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import * as ops from "@mlx-bun/mlx/ops";
|
|
3
|
+
import type { MlxPhaseGroup } from "../contracts/mlx/graph";
|
|
4
|
+
|
|
5
|
+
/** Apply a tokenwise block over the groups of a mixed forward: the groups that
|
|
6
|
+
* may share it packed into one call of the class the caller named (`packed`),
|
|
7
|
+
* a verify group (which keeps its projection geometry) and every group when
|
|
8
|
+
* `pack` is off on its own rows, at its own class. */
|
|
9
|
+
export function mapTokenGroups(
|
|
10
|
+
groups: readonly MlxPhaseGroup[], inputs: readonly MlxArray[],
|
|
11
|
+
operation: (hidden: MlxArray, width: number) => MlxArray, packed: number, pack = true,
|
|
12
|
+
): MlxArray[] {
|
|
13
|
+
const results: MlxArray[] = [];
|
|
14
|
+
const packedRows = groups.flatMap((group, row) => pack && group.phase !== "verify" ? [row] : []);
|
|
15
|
+
try {
|
|
16
|
+
const outputs = packedRows.length === 1 ? [operation(inputs[packedRows[0]!]!, groups[packedRows[0]!]!.rows)]
|
|
17
|
+
: packedRows.length ? mapPackedTokens(packedRows.map(row => inputs[row]!), hidden => operation(hidden, packed)) : [];
|
|
18
|
+
for (const [index, row] of packedRows.entries()) results[row] = outputs[index]!;
|
|
19
|
+
for (let row = 0; row < groups.length; row++) results[row] ??= operation(inputs[row]!, groups[row]!.rows);
|
|
20
|
+
return results;
|
|
21
|
+
} catch (error) { for (const result of results) result?.dispose(); throw error; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Pack real tokens for a tokenwise operation, then restore each group's
|
|
25
|
+
* geometry. Attention and recurrent operations must run outside this port.
|
|
26
|
+
* No padding, host readback, or evaluation is introduced here. */
|
|
27
|
+
export function mapPackedTokens(
|
|
28
|
+
inputs: readonly MlxArray[], operation: (packed: MlxArray) => MlxArray,
|
|
29
|
+
): MlxArray[] {
|
|
30
|
+
if (inputs.length === 1) return [operation(inputs[0]!)];
|
|
31
|
+
const flat = inputs.map(input => ops.reshape(input, [1, -1, input.shape.at(-1)!]));
|
|
32
|
+
let packed: MlxArray;
|
|
33
|
+
try { packed = ops.concatAxis(flat, 1); }
|
|
34
|
+
finally { for (const input of flat) input.dispose(); }
|
|
35
|
+
using joined = packed;
|
|
36
|
+
using output = operation(joined);
|
|
37
|
+
const results: MlxArray[] = [];
|
|
38
|
+
let position = 0;
|
|
39
|
+
try {
|
|
40
|
+
for (const input of inputs) {
|
|
41
|
+
const [batch, length] = input.shape;
|
|
42
|
+
const end = position + batch! * length!;
|
|
43
|
+
using part = output.slice([0, position, 0], [1, end, output.shape[2]!]);
|
|
44
|
+
results.push(ops.reshape(part, [batch!, length!, output.shape[2]!]));
|
|
45
|
+
position = end;
|
|
46
|
+
}
|
|
47
|
+
return results;
|
|
48
|
+
} catch (error) { for (const result of results) result.dispose(); throw error; }
|
|
49
|
+
}
|