mlx-bun 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -588
- package/docs/README.md +94 -0
- package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
- package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
- package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
- package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
- package/docs/archive/investigations/openjarvis-fit.md +198 -0
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- package/docs/archive/planning/release-notes-v0.3.0.md +35 -0
- package/docs/design/batching.md +1841 -0
- package/docs/design/decode-speed-program.md +2346 -169
- package/docs/design/dreaming-nightly-pipeline.md +675 -282
- package/docs/design/generic-model-support.md +655 -334
- package/docs/design/kv-cache.md +824 -0
- package/docs/design/orpo-training.md +673 -1239
- package/docs/design/speculative-decoding.md +1990 -0
- package/docs/design/turboquant.md +2212 -0
- package/docs/design/unified-engine-frontier-plan.md +1833 -727
- package/docs/design/web-chat-redesign.md +666 -1083
- package/docs/planning/PRODUCT_ROADMAP.md +50 -1
- package/docs/planning/release-notes-v0.4.0.md +45 -0
- package/docs/planning/video-series.md +2 -2
- package/docs/reference/benchmarks.md +3613 -0
- package/docs/reference/cli.md +434 -160
- package/docs/reference/distribution.md +328 -182
- package/docs/reference/environment.md +398 -0
- package/docs/reference/glossary.md +39 -0
- package/docs/reference/library-api.md +566 -168
- package/docs/reference/memory.md +227 -64
- package/docs/reference/models.md +113 -6
- package/docs/reference/server-api.md +944 -429
- package/docs/reference/server-config.md +740 -338
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +158 -0
- package/package.json +9 -4
- package/src/audio/decode.ts +2 -2
- package/src/audio/features.ts +2 -2
- package/src/backends/mlx/assistant-target.ts +46 -0
- package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
- package/src/backends/mlx/autoregressive.ts +108 -0
- package/src/backends/mlx/batch-group.ts +1790 -0
- package/src/backends/mlx/cache-attachment.ts +67 -0
- package/src/backends/mlx/cache-layout.ts +31 -0
- package/src/backends/mlx/checkpoint-state.ts +53 -0
- package/src/backends/mlx/continuation-persistence.ts +106 -0
- package/src/backends/mlx/continuation-request.ts +43 -0
- package/src/backends/mlx/continuation.ts +77 -0
- package/src/backends/mlx/diffusion.ts +39 -0
- package/src/backends/mlx/draft-target.ts +32 -0
- package/src/backends/mlx/evaluation.ts +105 -0
- package/src/backends/mlx/fill-append.ts +45 -0
- package/src/backends/mlx/full-kv-row-append.ts +32 -0
- package/src/backends/mlx/full-kv-row-donor.ts +54 -0
- package/src/backends/mlx/full-transitioning-kv-rows.ts +65 -0
- package/src/backends/mlx/gateway-binding.ts +130 -0
- package/src/backends/mlx/graph.ts +38 -0
- package/src/backends/mlx/kv-codec.ts +9 -0
- package/src/backends/mlx/kv-maintenance.ts +133 -0
- package/src/backends/mlx/kv-tensor-rows.ts +212 -0
- package/src/backends/mlx/memory-client.ts +189 -0
- package/src/backends/mlx/methods.ts +90 -0
- package/src/backends/mlx/model-prompt.ts +164 -0
- package/src/backends/mlx/model-serving.ts +79 -0
- package/src/backends/mlx/prefill-cohort.ts +89 -0
- package/src/backends/mlx/prefill-rows.ts +200 -0
- package/src/backends/mlx/prefill.ts +33 -0
- package/src/backends/mlx/projected-context-rows.ts +72 -0
- package/src/backends/mlx/request-policy.ts +20 -0
- package/src/backends/mlx/request-state-policy.ts +41 -0
- package/src/backends/mlx/rollback.ts +89 -0
- package/src/backends/mlx/row-sampling.ts +49 -0
- package/src/backends/mlx/serial-executor.ts +242 -0
- package/src/backends/mlx/speculative-group.ts +359 -0
- package/src/backends/mlx/speculative-round.ts +161 -0
- package/src/backends/mlx/speculative.ts +114 -0
- package/src/backends/mlx/state-rows.ts +104 -0
- package/src/backends/mlx/state-views.ts +46 -0
- package/src/backends/mlx/target-layout-capability.ts +35 -0
- package/src/backends/mlx/text-engine.ts +73 -0
- package/src/backends/mlx/transitioning-kv-rows.ts +165 -0
- package/src/cli.ts +66 -58
- package/src/client.ts +66 -0
- package/src/config.ts +21 -4
- package/src/contracts/completion.ts +16 -0
- package/src/contracts/execution.ts +60 -0
- package/src/contracts/generation.ts +103 -0
- package/src/contracts/host.ts +7 -0
- package/src/contracts/jobs.ts +66 -0
- package/src/contracts/pi-provider.ts +30 -0
- package/src/contracts/pi.ts +259 -0
- package/src/contracts/prefix-cache.ts +23 -0
- package/src/contracts/resources.ts +25 -0
- package/src/contracts/sampling.ts +23 -0
- package/src/contracts/scheduling.ts +45 -0
- package/src/contracts/tsconfig.json +14 -0
- package/src/diffusion/diffusion-generate.ts +218 -153
- package/src/embed.ts +1 -1
- package/src/engine/admission.ts +59 -0
- package/src/engine/cancellation.ts +38 -0
- package/src/engine/engine.ts +56 -0
- package/src/engine/execution-coordinator.ts +70 -0
- package/src/engine/execution-plan.ts +53 -0
- package/src/engine/resources.ts +53 -0
- package/src/engine/scheduler.ts +72 -0
- package/src/engine/session.ts +247 -0
- package/src/eval/README.md +44 -1
- package/src/eval/kl.ts +236 -3
- package/src/eval/reference-logits.ts +207 -0
- package/src/eval/runner.ts +8 -96
- package/src/fill/echo-index.ts +188 -0
- package/src/fill/fill-session.ts +452 -0
- package/src/fill/proposal.ts +68 -0
- package/src/fill/schema-rows.ts +506 -0
- package/src/fill/tool-boundary.ts +103 -0
- package/src/fit.ts +30 -16
- package/src/generate.ts +709 -417
- package/src/grammar.ts +12 -7
- package/src/harness-pi.ts +14 -33
- package/src/hub-rest.ts +4 -4
- package/src/index.ts +7 -0
- package/src/inference/denoising.ts +17 -0
- package/src/inference/draft-acceptance.ts +41 -0
- package/src/inference/draft-output.ts +49 -0
- package/src/inference/graph.ts +33 -0
- package/src/inference/prefill.ts +39 -0
- package/src/inference/rollback.ts +7 -0
- package/src/jobs/db.ts +4 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-entry.ts +12 -12
- package/src/jobs/runner.ts +98 -13
- package/src/jobs/task-client.ts +19 -0
- package/src/jobs/types.ts +2 -58
- package/src/kv-scheme.ts +70 -12
- package/src/kv-store.ts +177 -63
- package/src/lab/README.md +18 -0
- package/src/{assets → lab/curve}/curve-designer.html +2 -2
- package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
- package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
- package/src/lab/paged-kv/paged-kv-rows.ts +83 -0
- package/src/{model → lab/paged-kv}/paged-kv.ts +32 -15
- package/src/library.ts +40 -0
- package/src/lora.ts +18 -0
- package/src/media-fetch.ts +1 -1
- package/src/memory/article.ts +1 -1
- package/src/memory/chunk.ts +1 -1
- package/src/memory/model.ts +30 -169
- package/src/memory/prompts.ts +1 -1
- package/src/memory/rest.ts +1 -1
- package/src/memory/schedule.ts +1 -1
- package/src/memory/stages.ts +4 -1
- package/src/memory/vault.ts +1 -3
- package/src/mlx/array.ts +11 -11
- package/src/mlx/compile.ts +12 -2
- package/src/mlx/ffi.ts +58 -11
- package/src/mlx/materialize.ts +23 -0
- package/src/mlx/metal-kernel.ts +10 -11
- package/src/mlx/ops.ts +33 -13
- package/src/mlx/quantized-rows.ts +36 -0
- package/src/mlx/token-bitmask.ts +52 -0
- package/src/mlx/turboquant-kv-decode.ts +82 -0
- package/src/mlx/turboquant-kv-inverse.ts +122 -0
- package/src/mlx/turboquant-ops.ts +33 -3
- package/src/mlx/turboquant-tables.ts +2 -2
- package/src/model/artifact-identity.ts +73 -0
- package/src/model/batched-kv.ts +235 -0
- package/src/model/batched-mask.ts +20 -6
- package/src/model/batched-quant.ts +9 -3
- package/src/model/batched-quantized-kv.ts +144 -0
- package/src/model/batched-rotating-quant.ts +78 -17
- package/src/model/batched-rotating-state.ts +76 -0
- package/src/model/batched-rotating.ts +65 -23
- package/src/model/batched-row-storage.ts +52 -0
- package/src/model/batched-ssm.ts +52 -0
- package/src/model/batched-turboquant-kv.ts +93 -0
- package/src/model/compiled-decode.ts +8 -2
- package/src/model/decoded-kv-donor.ts +12 -0
- package/src/model/delayed-quantized-kv.ts +72 -0
- package/src/model/delayed-rotating-quantized-kv.ts +93 -0
- package/src/model/delayed-turboquant-kv.ts +55 -0
- package/src/model/diffusion-gemma.ts +8 -8
- package/src/model/factory.ts +76 -33
- package/src/model/fingerprint.ts +1 -1
- package/src/model/flash-attention.ts +3 -3
- package/src/model/full-prefill-padding.ts +56 -0
- package/src/model/full-prefill-row.ts +105 -0
- package/src/model/gemma4-base.ts +179 -80
- package/src/model/gemma4.ts +40 -32
- package/src/model/generated/gemma4-12b.ts +6 -3
- package/src/model/generated/gemma4-26b.ts +6 -3
- package/src/model/generated/gemma4-e4b.ts +6 -3
- package/src/model/glm52-cache.ts +80 -4
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +1 -1
- package/src/model/glm52-mla.ts +12 -6
- package/src/model/glm52.ts +19 -40
- package/src/model/implementation.ts +69 -0
- package/src/model/kv-attention-view.ts +67 -0
- package/src/model/minicpm5.ts +17 -15
- package/src/model/profile.ts +12 -2
- package/src/model/qwen-append.ts +12 -0
- package/src/model/qwen-conv.ts +52 -0
- package/src/model/qwen3-delta-state.ts +74 -0
- package/src/model/qwen3-delta.ts +59 -36
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +202 -78
- package/src/model/rotating-kv-donor.ts +35 -0
- package/src/model/rotating-kv-layout.ts +262 -0
- package/src/model/rotating-row-transaction.ts +35 -0
- package/src/model/speculative-rotating-kv.ts +101 -0
- package/src/model/ssm-prefill-padding.ts +39 -0
- package/src/model/support.ts +1 -1
- package/src/model/trellis-balanced-scatter.ts +51 -0
- package/src/model/trellis-linear.ts +672 -0
- package/src/model/trellis-shared-m.ts +151 -0
- package/src/model/trellis-shared-scatter.ts +144 -0
- package/src/model/trellis-splitk-prefill.ts +158 -0
- package/src/model/trellis-tiled-prefill.ts +136 -0
- package/src/model/trellis-vector-expand.ts +62 -0
- package/src/model/trellis-wide-prefill.ts +94 -0
- package/src/model/turboquant-codec.ts +83 -0
- package/src/model/universal/dense.ts +15 -14
- package/src/model/universal/modules.ts +8 -1
- package/src/native-pack.ts +6 -5
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +8 -315
- package/src/preflight.ts +31 -15
- package/src/prompt-cache.ts +110 -69
- package/src/quantize/allocator.ts +1 -1
- package/src/quantize/config-writer.ts +10 -3
- package/src/quantize/rotate.ts +4 -4
- package/src/quantize/trellis.ts +586 -0
- package/src/responses.ts +28 -1
- package/src/runtime-config.ts +18 -4
- package/src/sampler.ts +40 -29
- package/src/serve/batch-scheduler.ts +8 -1671
- package/src/serve/chat-request.ts +391 -0
- package/src/serve/chat-stage.ts +195 -0
- package/src/serve/checkpoint-identity.ts +64 -0
- package/src/serve/completion-executor.ts +15 -7
- package/src/serve/discovery-routes.ts +12 -13
- package/src/serve/generation-gateway.ts +183 -213
- package/src/serve/http.ts +159 -0
- package/src/serve/inference-request.ts +79 -0
- package/src/serve/isolate.ts +297 -105
- package/src/serve/lab-routes.ts +3 -0
- package/src/serve/lane-registry.ts +2 -1
- package/src/serve/model-admin-routes.ts +7 -5
- package/src/serve/model-binding.ts +43 -0
- package/src/serve/model-host.ts +511 -0
- package/src/serve/openai-wire.ts +103 -0
- package/src/serve/parent-application.ts +56 -0
- package/src/serve/pipeline.ts +33 -0
- package/src/serve/preparation.ts +33 -0
- package/src/serve/request-plan.ts +23 -18
- package/src/serve/request-prep.ts +344 -0
- package/src/serve/response-lease.ts +24 -0
- package/src/serve/responses-client.ts +69 -0
- package/src/serve/session-completion-engine.ts +110 -0
- package/src/serve/session-search.ts +1 -1
- package/src/serve/static-routes.ts +5 -5
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +335 -2711
- package/src/spec/assistant-rows.ts +92 -0
- package/src/spec/assistant-source.ts +31 -55
- package/src/spec/cache-identity.ts +22 -0
- package/src/spec/deepspec-rows.ts +29 -0
- package/src/spec/deepspec-source.ts +16 -14
- package/src/spec/dflash-source.ts +36 -12
- package/src/spec/drafter.ts +153 -142
- package/src/spec/dspark/ab-stats.ts +3 -3
- package/src/spec/dspark/calibration.ts +2 -2
- package/src/spec/dspark/deepspec-module.ts +97 -122
- package/src/spec/dspark/generate-dflash.ts +2 -2
- package/src/spec/dspark/generate.ts +2 -2
- package/src/spec/dspark/loader.ts +1 -1
- package/src/spec/dspark/module-dflash.ts +129 -172
- package/src/spec/dspark/module.ts +3 -3
- package/src/spec/dspark/quantize-drafter.ts +1 -1
- package/src/spec/generate.ts +1 -1
- package/src/spec/glm52-mtp-graph.ts +29 -0
- package/src/spec/glm52-mtp-rows.ts +132 -0
- package/src/spec/glm52-mtp-source.ts +54 -137
- package/src/spec/glm52-mtp-state.ts +33 -0
- package/src/spec/ngram-source.ts +93 -21
- package/src/spec/projected-draft-rows.ts +73 -0
- package/src/spec/qwen-mtp-module.ts +171 -0
- package/src/spec/qwen-mtp-rows.ts +219 -0
- package/src/spec/qwen-mtp-source.ts +147 -195
- package/src/spec/qwen-mtp-state.ts +30 -0
- package/src/spec/serve-loop.ts +201 -201
- package/src/spec/source.ts +110 -13
- package/src/spec/two-model-rows.ts +61 -0
- package/src/spec/two-model.ts +44 -102
- package/src/ssd-cache.ts +99 -19
- package/src/ssd-durability.ts +36 -20
- package/src/tool-call.ts +51 -14
- package/src/train/flash-cce.ts +2 -2
- package/src/train/forward.ts +6 -0
- package/src/train/loss.ts +1 -1
- package/src/train/prefix-shared.ts +5 -1
- package/src/train/segmented.ts +5 -3
- package/src/train/trainer.ts +6 -6
- package/src/vision/prompt.ts +1 -1
- package/src/vision/qwen3vl-tower.ts +52 -10
- package/src/web/app.html +1 -1
- package/src/web/app.js +62 -60
- package/src/web/src/adapters-panel.ts +3 -3
- package/src/web/src/assistant.ts +2 -36
- package/src/web/src/chat.ts +4 -4
- package/src/web/src/composer.ts +4 -4
- package/src/web/src/hub.ts +1 -1
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +1 -1
- package/src/web/src/model-picker.ts +1 -1
- package/src/web/src/palette.ts +1 -1
- package/src/web/src/protocol.ts +3 -39
- package/src/web/src/sessions.ts +1 -1
- package/src/web/src/ui-catalog.ts +4 -18
- package/src/web-assets.ts +47 -0
- package/src/weights.ts +55 -9
- package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
- package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
- package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
- package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
- package/docs/archive/investigations/hlg-ab.html +0 -34
- package/docs/archive/investigations/hlg-explorer.html +0 -238
- package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
- package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
- package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
- package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
- package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
- package/docs/archive/investigations/hlg-report.html +0 -262
- package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
- package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
- package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
- package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
- package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
- package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
- package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
- package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
- package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
- package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
- package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
- package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
- package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
- package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
- package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
- package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
- package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
- package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
- package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
- package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
- package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
- package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
- package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
- package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
- package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
- package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
- package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
- package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
- package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
- package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
- package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
- package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
- package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
- package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
- package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
- package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
- package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
- package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
- package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
- package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
- package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
- package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
- package/docs/archive/mlx-bun-lab-report.html +0 -275
- package/docs/archive/wwdc-mlx-bun.html +0 -848
- package/docs/design/adapters-end-to-end.md +0 -205
- package/docs/design/batching-perf-path.md +0 -255
- package/docs/design/batching-v2-plan.md +0 -529
- package/docs/design/bucketing-stage.md +0 -197
- package/docs/design/compat-cli-surface-design.md +0 -230
- package/docs/design/docs-reorg-plan.md +0 -165
- package/docs/design/dspark-serving-program.md +0 -371
- package/docs/design/dspark-speculative-decoding.md +0 -318
- package/docs/design/grammar-spec-batching-integration.md +0 -301
- package/docs/design/memory-inference-path.md +0 -422
- package/docs/design/memory-synthesis.md +0 -287
- package/docs/design/memory-system.md +0 -237
- package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
- package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
- package/docs/design/omlx-adoption-map.md +0 -98
- package/docs/design/orpo-dynamic-lambda.md +0 -220
- package/docs/design/orpo-future-enhancements.md +0 -340
- package/docs/design/paged-kv-cache.md +0 -214
- package/docs/design/parallel-slots.md +0 -567
- package/docs/design/parity-tier-dag.md +0 -161
- package/docs/design/repo-cleanup-plan.md +0 -231
- package/docs/design/runtime-isolation.md +0 -263
- package/docs/design/segmented-backward-training.md +0 -543
- package/docs/design/spec-decode-larger-targets.md +0 -229
- package/docs/design/ssd-kv-cold-tier.md +0 -415
- package/docs/design/structured-output.md +0 -484
- package/docs/design/tauri-desktop-app.md +0 -127
- package/docs/design/the-dreaming-master-plan.md +0 -445
- package/docs/design/turboquant-kv.md +0 -235
- package/docs/design/turboquant-weights.md +0 -467
- package/docs/design/w4a16-compute-precision-spike.md +0 -232
- package/docs/design/web-chat-beat-matrix.md +0 -315
- package/docs/design/write-pipeline-entity.md +0 -468
- package/docs/planning/IDEAS.md +0 -69
- package/docs/reference/embedding.md +0 -197
- package/docs/reference/features-matrix.md +0 -101
- package/docs/reference/orpo-quickstart.md +0 -135
- package/src/flags.ts +0 -11
- /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
- /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
- /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
- /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
- /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
- /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
- /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
- /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { handleStaticRoute } from "./static-routes";
|
|
2
|
+
import { STATIC_ROUTE_ASSETS } from "../web-assets";
|
|
3
|
+
import { createResponsesClient } from "./responses-client";
|
|
4
|
+
import type { DisposableResource } from "../contracts/resources";
|
|
5
|
+
import type { JobStore } from "../jobs/db";
|
|
6
|
+
import { handleLabRoute, matchLabRoute } from "./lab-routes";
|
|
7
|
+
import { handleAdminRoute, matchAdminRoute } from "./admin-routes";
|
|
8
|
+
|
|
9
|
+
/** CPU application state stays in the parent when model workers restart/evict.
|
|
10
|
+
* Native inspection/merge/export routes still execute inside a model worker. */
|
|
11
|
+
export function createParentApplication(options: {
|
|
12
|
+
acquireGpu(signal: AbortSignal): Promise<DisposableResource>;
|
|
13
|
+
serverPort(): number | undefined;
|
|
14
|
+
invalidateLibrary(): void;
|
|
15
|
+
createJobStore?(): Promise<JobStore>;
|
|
16
|
+
}) {
|
|
17
|
+
const responses = createResponsesClient();
|
|
18
|
+
let jobs: Promise<JobStore> | undefined;
|
|
19
|
+
let closed = false;
|
|
20
|
+
const ensureJobs = () => {
|
|
21
|
+
if (closed) return Promise.reject(new Error("application host is closed"));
|
|
22
|
+
return jobs ??= (async () => {
|
|
23
|
+
const { JobStore, registerRunner } = await import("../jobs");
|
|
24
|
+
const store = options.createJobStore ? await options.createJobStore() : new JobStore();
|
|
25
|
+
store.markZombies();
|
|
26
|
+
const { datasetRunner } = await import("../dataset/job");
|
|
27
|
+
registerRunner("dataset", datasetRunner);
|
|
28
|
+
return store;
|
|
29
|
+
})();
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
get responseStats() { return responses.stats; },
|
|
33
|
+
async handle(request: Request, forward?: (request: Request) => Promise<Response>): Promise<Response | null> {
|
|
34
|
+
const url = new URL(request.url);
|
|
35
|
+
const staticResponse = handleStaticRoute(url, request, STATIC_ROUTE_ASSETS);
|
|
36
|
+
if (staticResponse) return staticResponse;
|
|
37
|
+
if (forward && request.method === "POST" && url.pathname === "/v1/responses")
|
|
38
|
+
return responses.forward(request, forward);
|
|
39
|
+
const admin = matchAdminRoute(request.method, url.pathname);
|
|
40
|
+
if (admin) return handleAdminRoute(url, request, { ensureJobs, invalidateLibrary: options.invalidateLibrary });
|
|
41
|
+
const route = matchLabRoute(request.method, url.pathname);
|
|
42
|
+
if (route !== "quantize-submit" && route !== "finetune-submit" &&
|
|
43
|
+
route !== "dataset-submit" && route !== "dataset-templates") return null;
|
|
44
|
+
return handleLabRoute(url, request, { ensureJobs, acquireGpu: options.acquireGpu,
|
|
45
|
+
serverPort: options.serverPort, invalidateLibrary: options.invalidateLibrary });
|
|
46
|
+
},
|
|
47
|
+
async close() {
|
|
48
|
+
closed = true;
|
|
49
|
+
if (!jobs) return;
|
|
50
|
+
const store = await jobs;
|
|
51
|
+
const { closeSubprocessJobs, closeInProcessJobs } = await import("../jobs");
|
|
52
|
+
await Promise.all([closeSubprocessJobs(store), closeInProcessJobs(store)]);
|
|
53
|
+
store.close();
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Shared contracts of the request pipeline. Each stage is one configured
|
|
2
|
+
// program — `run(input) → output` — and the server composes them in order,
|
|
3
|
+
// like a shell pipe:
|
|
4
|
+
//
|
|
5
|
+
// new ChatRequest(body) → ChatStage.run → InferenceRequest
|
|
6
|
+
// → InferenceStage.run → InferenceResult → wire JSON/SSE
|
|
7
|
+
//
|
|
8
|
+
// A stage knows only the interface it reads and the one it writes.
|
|
9
|
+
import type { CompletionEvent } from "./completion-sink";
|
|
10
|
+
import type { CompletionUsage } from "./completion-executor";
|
|
11
|
+
import type { PromptResponseTrace } from "./prompt-response-trace";
|
|
12
|
+
|
|
13
|
+
/** A request a stage refuses. `status` is the HTTP status the wire adapter
|
|
14
|
+
* answers with; `body` the protocol-neutral error object (adapters reshape
|
|
15
|
+
* it: OpenAI `{error}`, Anthropic `{type:"error"}`, Responses `{error}`). */
|
|
16
|
+
export class RequestError extends Error {
|
|
17
|
+
constructor(
|
|
18
|
+
readonly status: number,
|
|
19
|
+
message: string,
|
|
20
|
+
readonly body: Record<string, unknown> = { message },
|
|
21
|
+
) {
|
|
22
|
+
super(message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** What flows back to the caller while a request is in flight: cancellation,
|
|
27
|
+
* tracing, and (for streaming surfaces) events + live usage. */
|
|
28
|
+
export interface RunControl {
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
trace?: PromptResponseTrace;
|
|
31
|
+
onEvents?(events: readonly CompletionEvent[]): void | false | Promise<void | false>;
|
|
32
|
+
onUsageProgress?(usage: Readonly<CompletionUsage>): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Host-side admission before grammar/media preparation can allocate native
|
|
2
|
+
* resources. The implementation shares the generation execution domain. */
|
|
3
|
+
export interface PreparationExecutor {
|
|
4
|
+
reserve?(kind: "media" | "constraint", signal?: AbortSignal): Promise<import("../contracts/resources").DisposableResource>;
|
|
5
|
+
run<T>(prepare: () => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
import { AdmissionPool } from "../engine/admission";
|
|
9
|
+
import { CancellationSource } from "../engine/cancellation";
|
|
10
|
+
|
|
11
|
+
export async function acquireReservation(pool: Pick<AdmissionPool, "acquire">, signal?: AbortSignal) {
|
|
12
|
+
signal?.throwIfAborted();
|
|
13
|
+
const cancellation = new CancellationSource();
|
|
14
|
+
const abort = () => cancellation.cancel("requested");
|
|
15
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
16
|
+
try { return await pool.acquire(cancellation); }
|
|
17
|
+
finally { signal?.removeEventListener("abort", abort); }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** One retained media preparation at a time; grammar rows may form a batch. */
|
|
21
|
+
export function createPreparationExecutor(
|
|
22
|
+
run: PreparationExecutor["run"], grammarCapacity: number,
|
|
23
|
+
): PreparationExecutor & { close(): void } {
|
|
24
|
+
const media = new AdmissionPool(1);
|
|
25
|
+
const constraints = new AdmissionPool(Math.max(1, grammarCapacity));
|
|
26
|
+
return {
|
|
27
|
+
run,
|
|
28
|
+
async reserve(kind, signal) {
|
|
29
|
+
return acquireReservation(kind === "media" ? media : constraints, signal);
|
|
30
|
+
},
|
|
31
|
+
close() { media.close(); constraints.close(); },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -1,28 +1,31 @@
|
|
|
1
|
+
import { snapshotGenerationPolicy } from "../backends/mlx/request-policy";
|
|
1
2
|
import type { GenerateOptions } from "../generate";
|
|
2
3
|
import type { RequestShape } from "./generation-gateway";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
4
|
+
import type { DisposableResource } from "../contracts/resources";
|
|
5
|
+
import { disposeResources, ownResource } from "../engine/resources";
|
|
6
|
+
export type { DisposableResource } from "../contracts/resources";
|
|
7
7
|
|
|
8
8
|
export class RequestOwnership {
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
#owner = ownResource(new Set<DisposableResource>(), disposeResources);
|
|
10
|
+
#reservations = ownResource(new Set<DisposableResource>(), disposeResources);
|
|
11
|
+
|
|
12
|
+
/** Reservations live until completion, including after native ownership transfers. */
|
|
13
|
+
retain<T extends DisposableResource>(reservation: T): T {
|
|
14
|
+
this.#reservations.borrow().add(reservation);
|
|
15
|
+
return reservation;
|
|
16
|
+
}
|
|
11
17
|
|
|
12
18
|
own<T extends DisposableResource | null | undefined>(resource: T): T {
|
|
13
|
-
if (resource) this.#
|
|
19
|
+
if (resource) this.#owner.borrow().add(resource);
|
|
14
20
|
return resource;
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
transfer(): void {
|
|
18
|
-
this.#
|
|
19
|
-
this.#resources.clear();
|
|
24
|
+
this.#owner.transfer();
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
dispose(): void {
|
|
23
|
-
|
|
24
|
-
for (const resource of this.#resources) resource.dispose();
|
|
25
|
-
this.#resources.clear();
|
|
28
|
+
disposeResources([{ dispose: () => this.#owner.close() }, { dispose: () => this.#reservations.close() }]);
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -30,7 +33,7 @@ export interface RequestPlanInput {
|
|
|
30
33
|
promptIds: number[];
|
|
31
34
|
options: GenerateOptions & { stopSequences: string[] };
|
|
32
35
|
requestedMaxTokens: number;
|
|
33
|
-
|
|
36
|
+
contextLimit: number | null;
|
|
34
37
|
stream: boolean;
|
|
35
38
|
wantLogprobs: boolean;
|
|
36
39
|
topLogprobs: number;
|
|
@@ -111,11 +114,12 @@ export type PlanRequestResult = RequestPlan | RequestRejection;
|
|
|
111
114
|
* options, adapter selection, and lane shape are derived here once for chat
|
|
112
115
|
* and raw completions. */
|
|
113
116
|
export function planRequest(input: RequestPlanInput): PlanRequestResult {
|
|
114
|
-
const available = input.
|
|
117
|
+
const available = input.contextLimit === null ? Infinity
|
|
118
|
+
: input.contextLimit - input.promptIds.length;
|
|
115
119
|
if (available < 1) {
|
|
116
120
|
return new RequestRejection(
|
|
117
121
|
`prompt is ${input.promptIds.length} tokens but the memory budget caps ` +
|
|
118
|
-
`safe context at ${input.
|
|
122
|
+
`safe context at ${input.contextLimit} — no room to generate; ` +
|
|
119
123
|
"shorten the prompt or raise --memory-budget",
|
|
120
124
|
input.ownership,
|
|
121
125
|
);
|
|
@@ -123,6 +127,7 @@ export function planRequest(input: RequestPlanInput): PlanRequestResult {
|
|
|
123
127
|
|
|
124
128
|
const options = { ...input.options };
|
|
125
129
|
options.maxTokens = Math.min(input.requestedMaxTokens, available);
|
|
130
|
+
options.seedWasExplicit = input.userSeed;
|
|
126
131
|
if (input.adapterIds.length) options.adapters = [...input.adapterIds];
|
|
127
132
|
const captureLogprobs = !input.stream && (input.wantLogprobs || input.topLogprobs > 0);
|
|
128
133
|
if (captureLogprobs) {
|
|
@@ -147,9 +152,9 @@ export function planRequest(input: RequestPlanInput): PlanRequestResult {
|
|
|
147
152
|
};
|
|
148
153
|
|
|
149
154
|
return new RequestPlan({
|
|
150
|
-
promptIds: input.promptIds,
|
|
151
|
-
options,
|
|
152
|
-
shape,
|
|
155
|
+
promptIds: Object.freeze([...input.promptIds]) as number[],
|
|
156
|
+
options: snapshotGenerationPolicy(options),
|
|
157
|
+
shape: Object.freeze(shape),
|
|
153
158
|
captureLogprobs,
|
|
154
159
|
wantLogprobs: input.wantLogprobs,
|
|
155
160
|
topLogprobs: input.topLogprobs,
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
// Per-request preparation shared by every completion surface (chat,
|
|
2
|
+
// raw text, /signal, /generate): request → GenerateOptions with the
|
|
3
|
+
// server/model defaults folded in, chat-template rendering + prompt ids,
|
|
4
|
+
// the stable prompt-cache boundary probe, grammar compilation, and the
|
|
5
|
+
// tool-call stream router. Resolved ONCE per server (createRequestPrep) and
|
|
6
|
+
// captured by the route handlers. Extracted from src/server.ts (repo-taming
|
|
7
|
+
// Phase 4).
|
|
8
|
+
import type { ToolDefinition } from "../chat-template";
|
|
9
|
+
import {
|
|
10
|
+
fillEchoConfig,
|
|
11
|
+
fillMaxSpan,
|
|
12
|
+
FillSession,
|
|
13
|
+
resolveFillMode,
|
|
14
|
+
} from "../fill/fill-session";
|
|
15
|
+
import { compileStrictFillRows, type StrictRowPlan } from "../fill/schema-rows";
|
|
16
|
+
import type { GenerateOptions } from "../generate";
|
|
17
|
+
import { compileGrammarRequest, grammarEnabled, type GrammarRequest } from "../grammar";
|
|
18
|
+
import type { KvSchemeOptions } from "../kv-scheme";
|
|
19
|
+
import { isMiniCPM5Config } from "../model/support";
|
|
20
|
+
import type { HlgConfig } from "../sampler";
|
|
21
|
+
import {
|
|
22
|
+
nextDefaultSeed,
|
|
23
|
+
normalizeMessages,
|
|
24
|
+
parseLogitBias,
|
|
25
|
+
promptEndsInOpenThink,
|
|
26
|
+
resolveHlg,
|
|
27
|
+
type ChatRequestParams,
|
|
28
|
+
} from "./chat-request";
|
|
29
|
+
import type { ServingContext } from "./model-host";
|
|
30
|
+
import { selectToolStreamMode, ToolAwareStream, type ToolStreamMode } from "./token-streams";
|
|
31
|
+
|
|
32
|
+
/** The ServerOptions fields request preparation reads (server-wide
|
|
33
|
+
* defaults a request field overrides). */
|
|
34
|
+
export interface RequestPrepOptions {
|
|
35
|
+
defaultThinking?: boolean;
|
|
36
|
+
defaultTemperature?: number;
|
|
37
|
+
defaultTopP?: number;
|
|
38
|
+
defaultTopK?: number;
|
|
39
|
+
hlg?: HlgConfig;
|
|
40
|
+
pagedKv?: { blockSize?: number };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createRequestPrep(input: {
|
|
44
|
+
ctx: ServingContext;
|
|
45
|
+
serverOptions: RequestPrepOptions;
|
|
46
|
+
/** The server-wide KV scheme (resolveKvScheme(...).generationOptions),
|
|
47
|
+
* spread into every request's GenerateOptions. */
|
|
48
|
+
kvScheme: KvSchemeOptions;
|
|
49
|
+
/** `--max-tokens` / the GLM memory plan's generation cap; undefined = the
|
|
50
|
+
* surface's own default (chat 65 536, raw completion 512). */
|
|
51
|
+
defaultGeneratedTokens: number | undefined;
|
|
52
|
+
}) {
|
|
53
|
+
const { ctx, serverOptions, kvScheme, defaultGeneratedTokens } = input;
|
|
54
|
+
|
|
55
|
+
// XTC never removes EOS or the newline token: mlx_lm.server passes
|
|
56
|
+
// [tokenizer.eos_token_id, tokenizer.encode("\n")] as xtc_special_tokens.
|
|
57
|
+
// We pass the flat equivalent — ALL configured EOS ids plus the bare newline
|
|
58
|
+
// id(s), encoded WITHOUT special tokens (mlx-lm's encode("\n") can drag a
|
|
59
|
+
// BOS along on some tokenizers; protecting BOS from XTC is a no-op, so the
|
|
60
|
+
// flat/no-BOS form is behaviorally identical and cleaner).
|
|
61
|
+
const xtcSpecialTokens = [
|
|
62
|
+
...ctx.model.config.eosTokenIds,
|
|
63
|
+
...ctx.tokenizer.encode("\n", false),
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
// Effective enable_thinking for a request, with the same precedence the chat
|
|
67
|
+
// template uses (extracted so sampling and template rendering can't disagree):
|
|
68
|
+
// explicit chat_template_kwargs.enable_thinking → reasoning_effort ("none" =
|
|
69
|
+
// off) → server --thinking default → model default (MiniCPM5 → off). undefined
|
|
70
|
+
// means "not a switchable-thinking model / leave the template default".
|
|
71
|
+
const resolveEnableThinking = (req: ChatRequestParams): boolean | undefined => {
|
|
72
|
+
const explicit = req.chat_template_kwargs?.enable_thinking;
|
|
73
|
+
if (typeof explicit === "boolean") return explicit;
|
|
74
|
+
const effort = req.reasoning_effort;
|
|
75
|
+
if (effort !== undefined) return effort !== "none";
|
|
76
|
+
if (serverOptions.defaultThinking !== undefined) return serverOptions.defaultThinking;
|
|
77
|
+
return isMiniCPM5Config(ctx.model.config) ? false : undefined;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const toOptions = (req: ChatRequestParams): GenerateOptions & { stopSequences: string[] } => {
|
|
81
|
+
// Sampling follows the thinking state — which the web UI's thinking button
|
|
82
|
+
// drives via enable_thinking. Model authors publish a SINGLE
|
|
83
|
+
// generation_config temperature (the think-mode value) but recommend a
|
|
84
|
+
// cooler one for direct, no-think replies (MiniCPM5 card: 0.9 think / 0.7
|
|
85
|
+
// no-think, top_p 0.95 for both). So with no explicit temperature set, a
|
|
86
|
+
// no-think turn runs at most NO_THINK_TEMPERATURE while a think turn keeps
|
|
87
|
+
// the model's hotter configured default. An explicit request/CLI
|
|
88
|
+
// temperature always wins. top_p is unchanged by mode.
|
|
89
|
+
const NO_THINK_TEMPERATURE = 0.7;
|
|
90
|
+
const genTemp = ctx.genDefaults.temperature ?? 0.7;
|
|
91
|
+
const defaultTemp = resolveEnableThinking(req) === false
|
|
92
|
+
? Math.min(genTemp, NO_THINK_TEMPERATURE)
|
|
93
|
+
: genTemp;
|
|
94
|
+
return {
|
|
95
|
+
// Unset means generate until EOS/stop. Only a client cap or an explicit
|
|
96
|
+
// operator limit constrains completion length; fit estimates do not.
|
|
97
|
+
maxTokens: req.max_completion_tokens ?? req.max_tokens ??
|
|
98
|
+
defaultGeneratedTokens,
|
|
99
|
+
temperature: req.temperature ?? serverOptions.defaultTemperature ?? defaultTemp,
|
|
100
|
+
topP: req.top_p ?? serverOptions.defaultTopP ?? ctx.genDefaults.topP ?? 0,
|
|
101
|
+
topK: req.top_k ?? serverOptions.defaultTopK ?? ctx.genDefaults.topK ?? 0,
|
|
102
|
+
seed: req.seed ?? nextDefaultSeed(),
|
|
103
|
+
repetitionPenalty: req.repetition_penalty ?? ctx.genDefaults.repetitionPenalty,
|
|
104
|
+
// mlx_lm.server sampling extensions (defaults mirror server.py: min_p /
|
|
105
|
+
// xtc off, context windows 20). parseLogitBias throws on non-numeric
|
|
106
|
+
// input — callers surface it as a 400 (mlx-lm's coercion error).
|
|
107
|
+
minP: req.min_p ?? 0,
|
|
108
|
+
xtcProbability: req.xtc_probability ?? 0,
|
|
109
|
+
xtcThreshold: req.xtc_threshold ?? 0,
|
|
110
|
+
...(req.xtc_probability ? { xtcSpecialTokens } : {}),
|
|
111
|
+
logitBias: parseLogitBias(req.logit_bias),
|
|
112
|
+
repetitionContextSize: req.repetition_context_size ?? 20,
|
|
113
|
+
presencePenalty: req.presence_penalty ?? 0,
|
|
114
|
+
presenceContextSize: req.presence_context_size ?? 20,
|
|
115
|
+
frequencyPenalty: req.frequency_penalty ?? 0,
|
|
116
|
+
frequencyContextSize: req.frequency_context_size ?? 20,
|
|
117
|
+
hlg: resolveHlg(req.hlg, serverOptions.hlg),
|
|
118
|
+
// generate() yields token ids; stop sequences match on decoded text
|
|
119
|
+
// (they can span token boundaries), so the StopMatcher sits at the
|
|
120
|
+
// decode layer below and halts the loop via onToken → false.
|
|
121
|
+
stopSequences: (typeof req.stop === "string" ? [req.stop] : req.stop ?? [])
|
|
122
|
+
.filter((s) => typeof s === "string" && s.length > 0),
|
|
123
|
+
...kvScheme,
|
|
124
|
+
// Paged KV is a server-wide mode like the kv scheme above (v1:
|
|
125
|
+
// serial bf16 gemma4 — the createServer refusals hold the invariants).
|
|
126
|
+
...(serverOptions.pagedKv ? { pagedKv: serverOptions.pagedKv } : {}),
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/** Compile a grammar controller from the request's structured-output fields
|
|
131
|
+
* (response_format / guided_* / structured_outputs). Returns null when no
|
|
132
|
+
* constraint is requested OR when compile failed (degrade path). On degrade,
|
|
133
|
+
* `degradeHint` carries a human description for the system-prompt injection
|
|
134
|
+
* + Warning header (oMLX parity — never 500). Honors MLX_BUN_GRAMMAR=0. */
|
|
135
|
+
const compileGrammarForRequest = async (
|
|
136
|
+
req: ChatRequestParams,
|
|
137
|
+
): Promise<{ controller: import("../grammar").GrammarController | null; degradeHint: string | null }> => {
|
|
138
|
+
if (!grammarEnabled()) {
|
|
139
|
+
return {
|
|
140
|
+
controller: null,
|
|
141
|
+
degradeHint: "grammar compilation disabled by MLX_BUN_GRAMMAR=0",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const r = await compileGrammarRequest(
|
|
145
|
+
req as GrammarRequest,
|
|
146
|
+
ctx.tokenizer,
|
|
147
|
+
ctx.model.config.text.vocabSize,
|
|
148
|
+
);
|
|
149
|
+
if (!r) return { controller: null, degradeHint: null };
|
|
150
|
+
return { controller: r.controller, degradeHint: r.degradeHint };
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Map OpenAI reasoning_effort levels onto the Qwen3.8 template's supported
|
|
154
|
+
// set (xhigh|medium|low — the template raises on anything else). "none"
|
|
155
|
+
// means thinking off (handled by resolveEnableThinking), so no depth is
|
|
156
|
+
// passed. Only consumed for templates with readsReasoningEffort.
|
|
157
|
+
const qwenReasoningEffort = (
|
|
158
|
+
effort: ChatRequestParams["reasoning_effort"],
|
|
159
|
+
): "xhigh" | "medium" | "low" | undefined => {
|
|
160
|
+
switch (effort) {
|
|
161
|
+
case "minimal":
|
|
162
|
+
case "low":
|
|
163
|
+
return "low";
|
|
164
|
+
case "medium":
|
|
165
|
+
return "medium";
|
|
166
|
+
case "high":
|
|
167
|
+
case "xhigh":
|
|
168
|
+
return "xhigh";
|
|
169
|
+
default:
|
|
170
|
+
return undefined; // "none" / unset → template default (xhigh)
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const templateOptionsFor = (req: ChatRequestParams, tools: ToolDefinition[] | null) => {
|
|
175
|
+
// enableThinking resolution (and its precedence) lives in
|
|
176
|
+
// resolveEnableThinking so template rendering and sampling stay in sync.
|
|
177
|
+
return {
|
|
178
|
+
tools,
|
|
179
|
+
enableThinking: resolveEnableThinking(req),
|
|
180
|
+
reasoningEffort: qwenReasoningEffort(req.reasoning_effort),
|
|
181
|
+
preserveThinking: req.chat_template_kwargs?.preserve_thinking,
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const promptIdsFor = (
|
|
186
|
+
req: ChatRequestParams,
|
|
187
|
+
tools: ToolDefinition[] | null,
|
|
188
|
+
): { ids: number[]; startInThinking: boolean } => {
|
|
189
|
+
const opts = templateOptionsFor(req, tools);
|
|
190
|
+
const rendered = ctx.template.render(normalizeMessages(req.messages), opts);
|
|
191
|
+
const ids = ctx.tokenizer.encode(rendered);
|
|
192
|
+
// template includes <bos>; tokenizer post-processor also prepends one
|
|
193
|
+
const trimmed = ids[0] === ids[1] && ids[0] === ctx.tokenizer.bosTokenId ? ids.slice(1) : ids;
|
|
194
|
+
return { ids: trimmed, startInThinking: promptEndsInOpenThink(rendered) };
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/** STABLE cache boundary: the prompt's tail is a generation primer (e.g.
|
|
198
|
+
* 12B's `<|channel>thought` tokens) that the NEXT turn's re-render does
|
|
199
|
+
* not contain — a cache entry ending there can never prefix-match again
|
|
200
|
+
* once the rings wrap. Probe by rendering this conversation as if a
|
|
201
|
+
* reply existed; the common prefix is what every future turn preserves.
|
|
202
|
+
* The probe is EXPENSIVE (a second full render + encode — ~150 ms at
|
|
203
|
+
* 16k with our tokenizer; 30-50% of the 12B ctx-repeat TTFT,
|
|
204
|
+
* 2026-07-06b), but the PRIMER LENGTH it measures is constant per
|
|
205
|
+
* template mode: the divergence sits after a special-token turn
|
|
206
|
+
* delimiter, and special tokens break BPE merges, so message content
|
|
207
|
+
* can't shift it. So the full probe runs ONCE per mode and later
|
|
208
|
+
* requests pay a subtraction. (A wrong boundary is a QUALITY knob, not
|
|
209
|
+
* a correctness one — any prefix ≤ len-1 is a valid snapshot point.) */
|
|
210
|
+
const primerLenByMode = new Map<string, number>();
|
|
211
|
+
const stableLenFor = (
|
|
212
|
+
req: ChatRequestParams,
|
|
213
|
+
tools: ToolDefinition[] | null,
|
|
214
|
+
trimmed: number[],
|
|
215
|
+
): number => {
|
|
216
|
+
const opts = templateOptionsFor(req, tools);
|
|
217
|
+
const mode = `${opts.enableThinking}|${!!tools?.length}|${opts.reasoningEffort ?? ""}|${opts.preserveThinking ?? ""}`;
|
|
218
|
+
const primer = primerLenByMode.get(mode);
|
|
219
|
+
if (primer !== undefined) return Math.max(0, trimmed.length - primer);
|
|
220
|
+
let stableLen = trimmed.length;
|
|
221
|
+
try {
|
|
222
|
+
const probe = ctx.tokenizer.encode(
|
|
223
|
+
ctx.template.render(
|
|
224
|
+
[...normalizeMessages(req.messages), { role: "assistant", content: "x" }],
|
|
225
|
+
opts,
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
|
+
const probeTrimmed =
|
|
229
|
+
probe[0] === probe[1] && probe[0] === ctx.tokenizer.bosTokenId ? probe.slice(1) : probe;
|
|
230
|
+
let i = 0;
|
|
231
|
+
const n = Math.min(trimmed.length, probeTrimmed.length);
|
|
232
|
+
while (i < n && trimmed[i] === probeTrimmed[i]) i++;
|
|
233
|
+
stableLen = i;
|
|
234
|
+
// Memoize the mode's primer length (sanity-capped: a "primer" longer
|
|
235
|
+
// than 64 tokens means the probe diverged for content reasons —
|
|
236
|
+
// don't generalize that).
|
|
237
|
+
const p = trimmed.length - stableLen;
|
|
238
|
+
if (p >= 0 && p <= 64) primerLenByMode.set(mode, p);
|
|
239
|
+
} catch { /* probe is best-effort; full boundary is the fallback */ }
|
|
240
|
+
return stableLen;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const toolStreamMode = (tools: ToolDefinition[] | null): ToolStreamMode =>
|
|
244
|
+
// Gemma-4 keeps the token-level sentinel path; every other family
|
|
245
|
+
// (MiniCPM5, Qwen3/3.5, Tier-0 generics) emits tool calls as DECODED TEXT
|
|
246
|
+
// — see selectToolStreamMode for why sentinel ids must stay family-gated.
|
|
247
|
+
selectToolStreamMode(ctx.model.config.modelType, !!tools?.length);
|
|
248
|
+
|
|
249
|
+
const toolRouter = (
|
|
250
|
+
tools: ToolDefinition[] | null,
|
|
251
|
+
onParseFailure?: () => void,
|
|
252
|
+
): ToolAwareStream =>
|
|
253
|
+
new ToolAwareStream(ctx.tokenizer, toolStreamMode(tools), tools, onParseFailure);
|
|
254
|
+
|
|
255
|
+
/** Strict fill rows are a function of (template mode, tool signature) only —
|
|
256
|
+
* they are token spans of the assistant turn, which opens with a special
|
|
257
|
+
* token, so message content cannot shift them (the same argument that lets
|
|
258
|
+
* stableLenFor memoize its primer length). Compiling costs several template
|
|
259
|
+
* renders + encodes, so it happens ONCE per mode. */
|
|
260
|
+
const fillRowsByMode = new Map<string, StrictRowPlan>();
|
|
261
|
+
const toolsSignature = (tools: ToolDefinition[]): string =>
|
|
262
|
+
JSON.stringify(tools.map((t) => [
|
|
263
|
+
t.function?.name,
|
|
264
|
+
Object.keys((t.function?.parameters?.properties ?? {}) as Record<string, unknown>),
|
|
265
|
+
(t.function?.parameters?.required as unknown) ?? null,
|
|
266
|
+
(t.function?.parameters?.additionalProperties as unknown) ?? null,
|
|
267
|
+
]));
|
|
268
|
+
|
|
269
|
+
/** Token fast-forwarding (docs/design/speculative-decoding.md "Token
|
|
270
|
+
* fast-forwarding"): compile this request's determined-span table, or null
|
|
271
|
+
* when the feature is off or the request's shape refuses it.
|
|
272
|
+
*
|
|
273
|
+
* Refusals owned here (body-level); the ones only ChatStage can see —
|
|
274
|
+
* a compiled grammar, media prompts, a mounted draft model, a quantized KV
|
|
275
|
+
* scheme — are applied there. Continuous (batch) placement needs no
|
|
276
|
+
* refusal: generate() is the only fill site, so a batched request simply
|
|
277
|
+
* does not fill.
|
|
278
|
+
*
|
|
279
|
+
* MLX_BUN_FILL=echo additionally arms the echo index (K3c, Lab tier): the
|
|
280
|
+
* strict rows keep policy "assert", and copied spans ride the same
|
|
281
|
+
* mechanism under policy "verify". */
|
|
282
|
+
const fillPlanFor = (
|
|
283
|
+
req: ChatRequestParams,
|
|
284
|
+
tools: ToolDefinition[] | null,
|
|
285
|
+
promptIds: number[],
|
|
286
|
+
): FillSession | null => {
|
|
287
|
+
const mode = resolveFillMode();
|
|
288
|
+
if (mode === "off") return null;
|
|
289
|
+
if (!tools?.length) return null;
|
|
290
|
+
// A user-fixed seed means reproducibility: the sampler's step index would
|
|
291
|
+
// skip the injected positions, so an identical request could not replay.
|
|
292
|
+
if (req.seed !== undefined) return null;
|
|
293
|
+
// Injected tokens are never sampled — they have no logprob row.
|
|
294
|
+
if (req.logprobs === true) return null;
|
|
295
|
+
if (typeof req.top_logprobs === "number" && req.top_logprobs > 0) return null;
|
|
296
|
+
// Structured output owns forced-token production (grammar jump-forward).
|
|
297
|
+
if (req.response_format != null || req.guided_grammar || req.guided_regex ||
|
|
298
|
+
req.guided_choice?.length || req.structured_outputs != null) return null;
|
|
299
|
+
|
|
300
|
+
const opts = templateOptionsFor(req, tools);
|
|
301
|
+
const key = `${opts.enableThinking}|${opts.reasoningEffort ?? ""}|` +
|
|
302
|
+
`${opts.preserveThinking ?? ""}|${toolsSignature(tools)}`;
|
|
303
|
+
let plan = fillRowsByMode.get(key);
|
|
304
|
+
if (plan === undefined) {
|
|
305
|
+
plan = compileStrictFillRows({
|
|
306
|
+
template: ctx.template,
|
|
307
|
+
tokenizer: ctx.tokenizer,
|
|
308
|
+
messages: normalizeMessages(req.messages),
|
|
309
|
+
tools,
|
|
310
|
+
renderOptions: opts,
|
|
311
|
+
});
|
|
312
|
+
fillRowsByMode.set(key, plan);
|
|
313
|
+
}
|
|
314
|
+
// The echo tier can carry a request whose template yields no strict rows;
|
|
315
|
+
// strict-only mode cannot.
|
|
316
|
+
if (!plan.rows.length && mode !== "echo") return null;
|
|
317
|
+
return new FillSession(
|
|
318
|
+
{
|
|
319
|
+
rows: plan.rows,
|
|
320
|
+
echo: mode === "echo" ? fillEchoConfig() : null,
|
|
321
|
+
delimiters: new Set(plan.delimiters),
|
|
322
|
+
eos: ctx.model.config.eosTokenIds,
|
|
323
|
+
},
|
|
324
|
+
promptIds,
|
|
325
|
+
{
|
|
326
|
+
maxSpan: fillMaxSpan(), decode: (ids) => ctx.tokenizer.decode([...ids]),
|
|
327
|
+
strictContext: plan.createContext?.(promptIds),
|
|
328
|
+
},
|
|
329
|
+
);
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
resolveEnableThinking,
|
|
334
|
+
toOptions,
|
|
335
|
+
compileGrammarForRequest,
|
|
336
|
+
templateOptionsFor,
|
|
337
|
+
promptIdsFor,
|
|
338
|
+
stableLenFor,
|
|
339
|
+
toolRouter,
|
|
340
|
+
fillPlanFor,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export type RequestPrep = ReturnType<typeof createRequestPrep>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { DisposableResource } from "../contracts/resources";
|
|
2
|
+
|
|
3
|
+
/** Keep host activity owned through response EOF/error/cancel, without buffering
|
|
4
|
+
* the response. The parent additionally drains worker cleanup before GPU jobs. */
|
|
5
|
+
export function retainResponseLease(response: Response, lease: DisposableResource): Response {
|
|
6
|
+
if (!response.body) { lease.dispose(); return response; }
|
|
7
|
+
const reader = response.body.getReader();
|
|
8
|
+
let closed = false;
|
|
9
|
+
const release = () => {
|
|
10
|
+
if (closed) return;
|
|
11
|
+
closed = true;
|
|
12
|
+
reader.releaseLock(); lease.dispose();
|
|
13
|
+
};
|
|
14
|
+
return new Response(new ReadableStream<Uint8Array>({
|
|
15
|
+
async pull(controller) {
|
|
16
|
+
try {
|
|
17
|
+
const item = await reader.read();
|
|
18
|
+
if (item.done) { release(); controller.close(); }
|
|
19
|
+
else controller.enqueue(item.value);
|
|
20
|
+
} catch (error) { release(); controller.error(error); }
|
|
21
|
+
},
|
|
22
|
+
async cancel(reason) { try { await reader.cancel(reason); } finally { release(); } },
|
|
23
|
+
}), { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
24
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ResponseStore, resolveResponsesConversation, type ResponseHistory, type ResponsesRequest } from "../responses";
|
|
2
|
+
|
|
3
|
+
/** Application-owned conversation history over a data-only HTTP host. Workers
|
|
4
|
+
* translate/run the request; the parent stores only completed response records. */
|
|
5
|
+
export function createResponsesClient(store: ResponseHistory = new ResponseStore()) {
|
|
6
|
+
return {
|
|
7
|
+
get stats() { return { entries: store.size, bytes: store.totalBytes, max_bytes: store.maxBytes, ttl_ms: store.ttlMs }; },
|
|
8
|
+
async forward(request: Request, forward: (request: Request) => Promise<Response>): Promise<Response> {
|
|
9
|
+
const error = (status: number, message: string) => Response.json({ error: {
|
|
10
|
+
message, type: "invalid_request_error", param: null, code: null,
|
|
11
|
+
} }, { status });
|
|
12
|
+
let body: ResponsesRequest;
|
|
13
|
+
try { body = await request.json() as ResponsesRequest; }
|
|
14
|
+
catch { return error(400, "invalid JSON body"); }
|
|
15
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return error(400, "invalid JSON body");
|
|
16
|
+
let conversation: ReturnType<typeof resolveResponsesConversation>;
|
|
17
|
+
try { conversation = resolveResponsesConversation(body, store); }
|
|
18
|
+
catch (e) { return error(404, (e as Error).message); }
|
|
19
|
+
const headers = new Headers(request.headers);
|
|
20
|
+
headers.delete("content-length");
|
|
21
|
+
headers.set("x-mlx-bun-response-owner", "parent");
|
|
22
|
+
const response = await forward(new Request(request.url, {
|
|
23
|
+
method: "POST", headers, signal: request.signal,
|
|
24
|
+
body: JSON.stringify({ ...conversation.body, previous_response_id: undefined }),
|
|
25
|
+
}));
|
|
26
|
+
if (!response.ok) return response;
|
|
27
|
+
const remember = (value: Record<string, unknown>) => {
|
|
28
|
+
if (typeof value.id === "string" && Array.isArray(value.output))
|
|
29
|
+
store.put(value.id, { input: conversation.input, output: value.output, instructions: conversation.instructions });
|
|
30
|
+
};
|
|
31
|
+
const restorePrevious = (value: Record<string, unknown>) => ({ ...value, previous_response_id: conversation.previousId });
|
|
32
|
+
if (!body.stream) {
|
|
33
|
+
const value = await response.json() as Record<string, unknown>;
|
|
34
|
+
remember(value);
|
|
35
|
+
const headers = new Headers(response.headers); headers.delete("content-length");
|
|
36
|
+
return Response.json(restorePrevious(value), { status: response.status, headers });
|
|
37
|
+
}
|
|
38
|
+
if (!response.body) return response;
|
|
39
|
+
const decoder = new TextDecoder(), encoder = new TextEncoder();
|
|
40
|
+
let pending = "";
|
|
41
|
+
const rewrite = (frame: string): string => frame.replace(/^data: (.+)$/m, (line, data: string) => {
|
|
42
|
+
if (data === "[DONE]") return line;
|
|
43
|
+
const event = JSON.parse(data) as Record<string, unknown>;
|
|
44
|
+
if (event.response && typeof event.response === "object") {
|
|
45
|
+
const value = event.response as Record<string, unknown>;
|
|
46
|
+
if (event.type === "response.completed") remember(value);
|
|
47
|
+
event.response = restorePrevious(value);
|
|
48
|
+
}
|
|
49
|
+
return `data: ${JSON.stringify(event)}`;
|
|
50
|
+
});
|
|
51
|
+
const outputHeaders = new Headers(response.headers); outputHeaders.delete("content-length");
|
|
52
|
+
return new Response(response.body.pipeThrough(new TransformStream<Uint8Array, Uint8Array>({
|
|
53
|
+
transform(chunk, controller) {
|
|
54
|
+
pending += decoder.decode(chunk, { stream: true });
|
|
55
|
+
let split: number;
|
|
56
|
+
while ((split = pending.indexOf("\n\n")) !== -1) {
|
|
57
|
+
const frame = pending.slice(0, split);
|
|
58
|
+
pending = pending.slice(split + 2);
|
|
59
|
+
controller.enqueue(encoder.encode(`${rewrite(frame)}\n\n`));
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
flush(controller) {
|
|
63
|
+
pending += decoder.decode();
|
|
64
|
+
if (pending) controller.enqueue(encoder.encode(rewrite(pending)));
|
|
65
|
+
},
|
|
66
|
+
})), { status: response.status, headers: outputHeaders });
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|