mlx-bun 0.0.13 → 0.3.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 -578
- package/bin/mlx-bun.mjs +1 -1
- package/docs/README.md +91 -0
- package/docs/{design → archive}/hlg-sampling.md +3 -3
- package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
- package/docs/{design → archive/investigations}/colibri-glm52-port.md +1 -1
- package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
- package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
- package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
- package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
- package/docs/{planning → archive/planning}/chunk-task-roadmap.md +1 -1
- package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +4 -4
- package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +1 -1
- package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
- package/docs/archive/planning/release-notes-v0.2.0.md +49 -0
- package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
- package/docs/design/batching.md +569 -0
- package/docs/design/decode-speed-program.md +3178 -169
- package/docs/design/dreaming-nightly-pipeline.md +675 -282
- package/docs/design/generic-model-support.md +579 -334
- package/docs/design/kv-cache.md +680 -0
- package/docs/design/orpo-training.md +673 -1239
- package/docs/design/speculative-decoding.md +1627 -0
- package/docs/design/turboquant.md +2102 -0
- package/docs/design/unified-engine-frontier-plan.md +1831 -727
- package/docs/design/web-chat-redesign.md +666 -1083
- package/docs/planning/PRODUCT_ROADMAP.md +34 -1
- package/docs/planning/release-notes-v0.3.0.md +35 -0
- package/docs/planning/video-series.md +421 -0
- package/docs/reference/benchmarks.md +1162 -0
- package/docs/reference/cli.md +395 -149
- package/docs/reference/distribution.md +321 -181
- package/docs/reference/environment.md +348 -0
- package/docs/reference/glossary.md +39 -0
- package/docs/reference/library-api.md +580 -142
- package/docs/reference/memory.md +227 -64
- package/docs/reference/models.md +137 -6
- package/docs/reference/server-api.md +938 -422
- package/docs/reference/server-config.md +652 -320
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +166 -0
- package/package.json +10 -5
- package/src/admission-plan.ts +18 -0
- package/src/anthropic.ts +92 -3
- package/src/audio/decode.ts +2 -2
- package/src/audio/features.ts +2 -2
- package/src/backends/mlx/autoregressive.ts +108 -0
- package/src/backends/mlx/batch-group.ts +1646 -0
- package/src/backends/mlx/diffusion.ts +39 -0
- package/src/backends/mlx/draft-target.ts +47 -0
- package/src/backends/mlx/evaluation.ts +105 -0
- package/src/backends/mlx/fill-append.ts +45 -0
- package/src/backends/mlx/gateway-binding.ts +71 -0
- package/src/backends/mlx/graph.ts +38 -0
- package/src/backends/mlx/memory-client.ts +189 -0
- package/src/backends/mlx/memory-guard.ts +40 -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.ts +33 -0
- package/src/backends/mlx/request-policy.ts +20 -0
- package/src/backends/mlx/rollback.ts +51 -0
- package/src/backends/mlx/serial-executor.ts +273 -0
- package/src/backends/mlx/speculative.ts +114 -0
- package/src/backends/mlx/state-views.ts +26 -0
- package/src/backends/mlx/text-engine.ts +73 -0
- package/src/cli.ts +189 -61
- package/src/client.ts +66 -0
- package/src/config.ts +21 -4
- package/src/contracts/completion.ts +16 -0
- package/src/contracts/execution.ts +49 -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/resources.ts +18 -0
- package/src/contracts/scheduling.ts +27 -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 +51 -0
- package/src/engine/resources.ts +42 -0
- package/src/engine/scheduler.ts +67 -0
- package/src/engine/session.ts +247 -0
- package/src/eval/README.md +44 -1
- package/src/eval/kl.ts +263 -23
- package/src/eval/reference-logits.ts +207 -0
- package/src/eval/runner.ts +10 -79
- package/src/eval/tasks/bfcl.ts +2 -1
- package/src/eval/tasks/gsm8k.ts +2 -1
- package/src/eval/tasks/hashhop.ts +2 -1
- package/src/eval/tasks/humaneval.ts +5 -4
- package/src/eval/tasks/ifeval.ts +2 -1
- package/src/eval/tasks/mmlu.ts +2 -1
- package/src/expert-io.ts +3 -1
- package/src/expert-offload.ts +3 -1
- 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 +24 -43
- package/src/generate.ts +740 -337
- package/src/grammar.ts +14 -8
- package/src/harness-pi.ts +15 -34
- package/src/hub-rest.ts +4 -4
- package/src/index.ts +27 -0
- package/src/inference/denoising.ts +17 -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 +15 -14
- 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 +199 -0
- package/src/kv-store.ts +459 -234
- 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} +7 -5
- package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
- package/src/library.ts +40 -0
- package/src/lora.ts +18 -0
- package/src/media-fetch.ts +20 -4
- package/src/memory/article.ts +1 -1
- package/src/memory/chunk.ts +1 -1
- package/src/memory/model.ts +32 -170
- 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 +3 -4
- package/src/mlx/array.ts +9 -10
- package/src/mlx/compile.ts +12 -2
- package/src/mlx/ffi.ts +66 -22
- package/src/mlx/materialize.ts +23 -0
- package/src/mlx/metal-kernel.ts +10 -11
- package/src/mlx/ops.ts +64 -19
- 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/batched-mask.ts +9 -3
- package/src/model/batched-quant.ts +2 -2
- package/src/model/batched-rotating-quant.ts +45 -99
- package/src/model/batched-rotating-state.ts +73 -0
- package/src/model/batched-rotating.ts +57 -106
- package/src/model/batched-row-storage.ts +127 -0
- package/src/model/compiled-decode.ts +4 -3
- package/src/model/diffusion-gemma.ts +8 -8
- package/src/model/factory.ts +87 -49
- package/src/model/fingerprint.ts +1 -1
- package/src/model/flash-attention.ts +3 -3
- package/src/model/gemma4-base.ts +187 -17
- package/src/model/gemma4.ts +6 -6
- package/src/model/generated/gemma4-12b.ts +52 -148
- package/src/model/generated/gemma4-26b.ts +34 -94
- package/src/model/generated/gemma4-e4b.ts +28 -76
- package/src/model/glm52-cache.ts +19 -9
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +28 -1
- package/src/model/implementation.ts +69 -0
- package/src/model/minicpm5.ts +7 -8
- package/src/model/profile.ts +443 -0
- package/src/model/qwen-append.ts +12 -0
- package/src/model/qwen-conv.ts +52 -0
- package/src/model/qwen3-delta.ts +98 -2
- package/src/model/qwen3-mrope.ts +252 -0
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +336 -60
- 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/universal/dense.ts +1 -1
- package/src/model/universal/modules.ts +8 -1
- package/src/native/frame_extract.swift +140 -0
- package/src/native-pack.ts +15 -7
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +14 -320
- package/src/prompt-cache.ts +97 -54
- package/src/quantize/allocator.ts +1 -1
- package/src/quantize/atomic-output.ts +38 -0
- package/src/quantize/calibration.ts +2 -1
- package/src/quantize/config-writer.ts +16 -3
- package/src/quantize/index.ts +28 -1
- package/src/quantize/job.ts +11 -0
- package/src/quantize/quantizer.ts +265 -100
- package/src/quantize/rotate.ts +603 -0
- package/src/quantize/safetensors-writer.ts +79 -1
- package/src/quantize/sensitivity.ts +41 -59
- package/src/quantize/trellis.ts +586 -0
- package/src/quantize/weight-transform.ts +292 -0
- package/src/responses.ts +130 -3
- package/src/runtime-config.ts +81 -0
- package/src/sampler.ts +276 -4
- package/src/serve/admin-routes.ts +184 -0
- package/src/serve/aux-routes.ts +132 -0
- package/src/serve/batch-scheduler.ts +8 -1558
- 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 +354 -0
- package/src/serve/completion-sink.ts +157 -0
- package/src/serve/discovery-routes.ts +204 -0
- package/src/serve/generation-gateway.ts +211 -243
- 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/kv-budget.ts +14 -0
- package/src/serve/lab-routes.ts +341 -0
- package/src/serve/lane-registry.ts +2 -1
- package/src/serve/model-admin-routes.ts +155 -0
- package/src/serve/model-binding.ts +43 -0
- package/src/serve/model-host.ts +512 -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/prompt-response-trace.ts +147 -0
- package/src/serve/request-plan.ts +162 -0
- package/src/serve/request-prep.ts +346 -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 +101 -0
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +523 -3551
- package/src/spec/assistant-source.ts +10 -44
- package/src/spec/deepspec-source.ts +6 -6
- package/src/spec/dflash-source.ts +9 -8
- package/src/spec/dspark/ab-stats.ts +3 -3
- package/src/spec/dspark/calibration.ts +2 -2
- package/src/spec/dspark/deepspec-module.ts +2 -2
- 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 +5 -4
- 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-source.ts +9 -1
- package/src/spec/ngram-source.ts +1 -1
- package/src/spec/prefix-state.ts +45 -0
- package/src/spec/qwen-mtp-source.ts +528 -0
- package/src/spec/serve-loop.ts +179 -213
- package/src/spec/source.ts +42 -10
- package/src/spec/two-model.ts +1 -1
- package/src/ssd-cache.ts +95 -24
- package/src/ssd-durability.ts +207 -0
- package/src/tool-call.ts +51 -14
- package/src/train/flash-cce.ts +14 -13
- package/src/train/forward.ts +38 -1
- package/src/train/loss.ts +3 -2
- package/src/train/prefix-shared.ts +5 -1
- package/src/train/segmented.ts +9 -6
- package/src/train/trainer.ts +9 -8
- package/src/vision/preprocess.ts +1 -1
- package/src/vision/prompt.ts +48 -2
- package/src/vision/qwen3vl-preprocess.ts +409 -0
- package/src/vision/qwen3vl-prompt.ts +196 -0
- package/src/vision/qwen3vl-tower.ts +555 -0
- package/src/vision/video-frames.ts +148 -0
- package/src/web/app.html +1 -1
- package/src/web/app.js +15 -6
- package/src/web/src/adapters-panel.ts +3 -3
- package/src/web/src/assistant.ts +2 -36
- package/src/web/src/chat.ts +16 -9
- 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 +134 -33
- 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 -268
- 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-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 -189
- 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 -288
- 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/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/investigations/curve-runs/bisector-route.json +0 -98
- package/docs/investigations/curve-runs/bisector-trace.json +0 -1393
- package/docs/investigations/curve-runs/curve-terrain.json +0 -1179
- package/docs/investigations/curve-runs/curve-terrain.svg +0 -42
- package/docs/investigations/hlg-ab.html +0 -34
- package/docs/investigations/hlg-explorer.html +0 -238
- package/docs/investigations/hlg-figs/cliff.svg +0 -1
- package/docs/investigations/hlg-figs/cross.svg +0 -1
- package/docs/investigations/hlg-figs/edges.svg +0 -1
- package/docs/investigations/hlg-figs/k20-grids.svg +0 -1
- package/docs/investigations/hlg-figs/k20-peaks.svg +0 -1
- package/docs/investigations/hlg-figs/lw-curve.svg +0 -1
- package/docs/investigations/hlg-figs/terrain.svg +0 -1
- package/docs/investigations/hlg-report.html +0 -262
- package/docs/investigations/hlg-runs/hlg-ab.json +0 -149
- package/docs/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
- package/docs/investigations/hlg-runs/hlg-blind.json +0 -107
- package/docs/investigations/hlg-runs/hlg-cliff.json +0 -197
- package/docs/investigations/hlg-runs/hlg-cross.json +0 -505
- package/docs/investigations/hlg-runs/hlg-edge.json +0 -388
- package/docs/investigations/hlg-runs/hlg-eval1.json +0 -25
- package/docs/investigations/hlg-runs/hlg-frontier.json +0 -86
- package/docs/investigations/hlg-runs/hlg-grid.json +0 -242
- package/docs/investigations/hlg-runs/hlg-grid2.json +0 -343
- package/docs/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
- package/docs/investigations/hlg-runs/hlg-map.json +0 -383
- package/docs/investigations/hlg-runs/run0-original.log +0 -159
- package/docs/investigations/hlg-runs/runA-recommended.log +0 -131
- package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
- package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
- package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
- package/docs/investigations/hlg-runs/runAD-ab-samples.log +0 -8
- package/docs/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
- package/docs/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
- package/docs/investigations/hlg-runs/runAG-eval-config2.log +0 -16
- package/docs/investigations/hlg-runs/runB-gain-variants.log +0 -192
- package/docs/investigations/hlg-runs/runC-pivots.log +0 -162
- package/docs/investigations/hlg-runs/runD-oetf.log +0 -132
- package/docs/investigations/hlg-runs/runE-eotf.log +0 -162
- package/docs/investigations/hlg-runs/runF-shaper-working.log +0 -86
- package/docs/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
- package/docs/investigations/hlg-runs/runH-wsweep.log +0 -176
- package/docs/investigations/hlg-runs/runI-asweep.log +0 -176
- package/docs/investigations/hlg-runs/runJ-knobmap.log +0 -49
- package/docs/investigations/hlg-runs/runK-frontier.log +0 -19
- package/docs/investigations/hlg-runs/runL-widemap.log +0 -65
- package/docs/investigations/hlg-runs/runM-frontier-emb.log +0 -15
- package/docs/investigations/hlg-runs/runN-verify.log +0 -116
- package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
- package/docs/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
- package/docs/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
- package/docs/investigations/hlg-runs/runR-blind-transcript.log +0 -79
- package/docs/investigations/hlg-runs/runS-cross-coherence.log +0 -33
- package/docs/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
- package/docs/investigations/hlg-runs/runU-cross-12b.log +0 -33
- package/docs/investigations/hlg-runs/runV-lw-e4b.log +0 -22
- package/docs/investigations/hlg-runs/runW-lw-12b.log +0 -22
- package/docs/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
- package/docs/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
- package/docs/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
- package/docs/planning/IDEAS.md +0 -69
- package/docs/reference/embedding.md +0 -197
- package/docs/reference/features-matrix.md +0 -100
- package/docs/reference/orpo-quickstart.md +0 -135
- package/src/flags.ts +0 -13
- /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
- /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
- /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
- /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
- /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
- /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
- /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
- /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
- /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
- /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
- /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
- /package/docs/{planning → archive/planning}/journal.md +0 -0
- /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
- /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
- /package/{docs/investigations → src/serve/assets}/curve-terrain.html +0 -0
- /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
|
@@ -0,0 +1,346 @@
|
|
|
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
|
+
// No invented cap: unset means "generate until EOS or the admitted
|
|
96
|
+
// context is exhausted" — admission (planRequest) is the real limit
|
|
97
|
+
// and clamps to maxSafeContext − promptLen. Only an explicit request
|
|
98
|
+
// max_tokens or the operator's --max-tokens narrows it further.
|
|
99
|
+
maxTokens: req.max_completion_tokens ?? req.max_tokens ??
|
|
100
|
+
defaultGeneratedTokens,
|
|
101
|
+
temperature: req.temperature ?? serverOptions.defaultTemperature ?? defaultTemp,
|
|
102
|
+
topP: req.top_p ?? serverOptions.defaultTopP ?? ctx.genDefaults.topP ?? 0,
|
|
103
|
+
topK: req.top_k ?? serverOptions.defaultTopK ?? ctx.genDefaults.topK ?? 0,
|
|
104
|
+
seed: req.seed ?? nextDefaultSeed(),
|
|
105
|
+
repetitionPenalty: req.repetition_penalty ?? ctx.genDefaults.repetitionPenalty,
|
|
106
|
+
// mlx_lm.server sampling extensions (defaults mirror server.py: min_p /
|
|
107
|
+
// xtc off, context windows 20). parseLogitBias throws on non-numeric
|
|
108
|
+
// input — callers surface it as a 400 (mlx-lm's coercion error).
|
|
109
|
+
minP: req.min_p ?? 0,
|
|
110
|
+
xtcProbability: req.xtc_probability ?? 0,
|
|
111
|
+
xtcThreshold: req.xtc_threshold ?? 0,
|
|
112
|
+
...(req.xtc_probability ? { xtcSpecialTokens } : {}),
|
|
113
|
+
logitBias: parseLogitBias(req.logit_bias),
|
|
114
|
+
repetitionContextSize: req.repetition_context_size ?? 20,
|
|
115
|
+
presencePenalty: req.presence_penalty ?? 0,
|
|
116
|
+
presenceContextSize: req.presence_context_size ?? 20,
|
|
117
|
+
frequencyPenalty: req.frequency_penalty ?? 0,
|
|
118
|
+
frequencyContextSize: req.frequency_context_size ?? 20,
|
|
119
|
+
hlg: resolveHlg(req.hlg, serverOptions.hlg),
|
|
120
|
+
// generate() yields token ids; stop sequences match on decoded text
|
|
121
|
+
// (they can span token boundaries), so the StopMatcher sits at the
|
|
122
|
+
// decode layer below and halts the loop via onToken → false.
|
|
123
|
+
stopSequences: (typeof req.stop === "string" ? [req.stop] : req.stop ?? [])
|
|
124
|
+
.filter((s) => typeof s === "string" && s.length > 0),
|
|
125
|
+
...kvScheme,
|
|
126
|
+
// Paged KV is a server-wide mode like the kv scheme above (v1:
|
|
127
|
+
// serial bf16 gemma4 — the createServer refusals hold the invariants).
|
|
128
|
+
...(serverOptions.pagedKv ? { pagedKv: serverOptions.pagedKv } : {}),
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/** Compile a grammar controller from the request's structured-output fields
|
|
133
|
+
* (response_format / guided_* / structured_outputs). Returns null when no
|
|
134
|
+
* constraint is requested OR when compile failed (degrade path). On degrade,
|
|
135
|
+
* `degradeHint` carries a human description for the system-prompt injection
|
|
136
|
+
* + Warning header (oMLX parity — never 500). Honors MLX_BUN_GRAMMAR=0. */
|
|
137
|
+
const compileGrammarForRequest = async (
|
|
138
|
+
req: ChatRequestParams,
|
|
139
|
+
): Promise<{ controller: import("../grammar").GrammarController | null; degradeHint: string | null }> => {
|
|
140
|
+
if (!grammarEnabled()) {
|
|
141
|
+
return {
|
|
142
|
+
controller: null,
|
|
143
|
+
degradeHint: "grammar compilation disabled by MLX_BUN_GRAMMAR=0",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const r = await compileGrammarRequest(
|
|
147
|
+
req as GrammarRequest,
|
|
148
|
+
ctx.tokenizer,
|
|
149
|
+
ctx.model.config.text.vocabSize,
|
|
150
|
+
);
|
|
151
|
+
if (!r) return { controller: null, degradeHint: null };
|
|
152
|
+
return { controller: r.controller, degradeHint: r.degradeHint };
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Map OpenAI reasoning_effort levels onto the Qwen3.8 template's supported
|
|
156
|
+
// set (xhigh|medium|low — the template raises on anything else). "none"
|
|
157
|
+
// means thinking off (handled by resolveEnableThinking), so no depth is
|
|
158
|
+
// passed. Only consumed for templates with readsReasoningEffort.
|
|
159
|
+
const qwenReasoningEffort = (
|
|
160
|
+
effort: ChatRequestParams["reasoning_effort"],
|
|
161
|
+
): "xhigh" | "medium" | "low" | undefined => {
|
|
162
|
+
switch (effort) {
|
|
163
|
+
case "minimal":
|
|
164
|
+
case "low":
|
|
165
|
+
return "low";
|
|
166
|
+
case "medium":
|
|
167
|
+
return "medium";
|
|
168
|
+
case "high":
|
|
169
|
+
case "xhigh":
|
|
170
|
+
return "xhigh";
|
|
171
|
+
default:
|
|
172
|
+
return undefined; // "none" / unset → template default (xhigh)
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const templateOptionsFor = (req: ChatRequestParams, tools: ToolDefinition[] | null) => {
|
|
177
|
+
// enableThinking resolution (and its precedence) lives in
|
|
178
|
+
// resolveEnableThinking so template rendering and sampling stay in sync.
|
|
179
|
+
return {
|
|
180
|
+
tools,
|
|
181
|
+
enableThinking: resolveEnableThinking(req),
|
|
182
|
+
reasoningEffort: qwenReasoningEffort(req.reasoning_effort),
|
|
183
|
+
preserveThinking: req.chat_template_kwargs?.preserve_thinking,
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const promptIdsFor = (
|
|
188
|
+
req: ChatRequestParams,
|
|
189
|
+
tools: ToolDefinition[] | null,
|
|
190
|
+
): { ids: number[]; startInThinking: boolean } => {
|
|
191
|
+
const opts = templateOptionsFor(req, tools);
|
|
192
|
+
const rendered = ctx.template.render(normalizeMessages(req.messages), opts);
|
|
193
|
+
const ids = ctx.tokenizer.encode(rendered);
|
|
194
|
+
// template includes <bos>; tokenizer post-processor also prepends one
|
|
195
|
+
const trimmed = ids[0] === ids[1] && ids[0] === ctx.tokenizer.bosTokenId ? ids.slice(1) : ids;
|
|
196
|
+
return { ids: trimmed, startInThinking: promptEndsInOpenThink(rendered) };
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/** STABLE cache boundary: the prompt's tail is a generation primer (e.g.
|
|
200
|
+
* 12B's `<|channel>thought` tokens) that the NEXT turn's re-render does
|
|
201
|
+
* not contain — a cache entry ending there can never prefix-match again
|
|
202
|
+
* once the rings wrap. Probe by rendering this conversation as if a
|
|
203
|
+
* reply existed; the common prefix is what every future turn preserves.
|
|
204
|
+
* The probe is EXPENSIVE (a second full render + encode — ~150 ms at
|
|
205
|
+
* 16k with our tokenizer; 30-50% of the 12B ctx-repeat TTFT,
|
|
206
|
+
* 2026-07-06b), but the PRIMER LENGTH it measures is constant per
|
|
207
|
+
* template mode: the divergence sits after a special-token turn
|
|
208
|
+
* delimiter, and special tokens break BPE merges, so message content
|
|
209
|
+
* can't shift it. So the full probe runs ONCE per mode and later
|
|
210
|
+
* requests pay a subtraction. (A wrong boundary is a QUALITY knob, not
|
|
211
|
+
* a correctness one — any prefix ≤ len-1 is a valid snapshot point.) */
|
|
212
|
+
const primerLenByMode = new Map<string, number>();
|
|
213
|
+
const stableLenFor = (
|
|
214
|
+
req: ChatRequestParams,
|
|
215
|
+
tools: ToolDefinition[] | null,
|
|
216
|
+
trimmed: number[],
|
|
217
|
+
): number => {
|
|
218
|
+
const opts = templateOptionsFor(req, tools);
|
|
219
|
+
const mode = `${opts.enableThinking}|${!!tools?.length}|${opts.reasoningEffort ?? ""}|${opts.preserveThinking ?? ""}`;
|
|
220
|
+
const primer = primerLenByMode.get(mode);
|
|
221
|
+
if (primer !== undefined) return Math.max(0, trimmed.length - primer);
|
|
222
|
+
let stableLen = trimmed.length;
|
|
223
|
+
try {
|
|
224
|
+
const probe = ctx.tokenizer.encode(
|
|
225
|
+
ctx.template.render(
|
|
226
|
+
[...normalizeMessages(req.messages), { role: "assistant", content: "x" }],
|
|
227
|
+
opts,
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
const probeTrimmed =
|
|
231
|
+
probe[0] === probe[1] && probe[0] === ctx.tokenizer.bosTokenId ? probe.slice(1) : probe;
|
|
232
|
+
let i = 0;
|
|
233
|
+
const n = Math.min(trimmed.length, probeTrimmed.length);
|
|
234
|
+
while (i < n && trimmed[i] === probeTrimmed[i]) i++;
|
|
235
|
+
stableLen = i;
|
|
236
|
+
// Memoize the mode's primer length (sanity-capped: a "primer" longer
|
|
237
|
+
// than 64 tokens means the probe diverged for content reasons —
|
|
238
|
+
// don't generalize that).
|
|
239
|
+
const p = trimmed.length - stableLen;
|
|
240
|
+
if (p >= 0 && p <= 64) primerLenByMode.set(mode, p);
|
|
241
|
+
} catch { /* probe is best-effort; full boundary is the fallback */ }
|
|
242
|
+
return stableLen;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const toolStreamMode = (tools: ToolDefinition[] | null): ToolStreamMode =>
|
|
246
|
+
// Gemma-4 keeps the token-level sentinel path; every other family
|
|
247
|
+
// (MiniCPM5, Qwen3/3.5, Tier-0 generics) emits tool calls as DECODED TEXT
|
|
248
|
+
// — see selectToolStreamMode for why sentinel ids must stay family-gated.
|
|
249
|
+
selectToolStreamMode(ctx.model.config.modelType, !!tools?.length);
|
|
250
|
+
|
|
251
|
+
const toolRouter = (
|
|
252
|
+
tools: ToolDefinition[] | null,
|
|
253
|
+
onParseFailure?: () => void,
|
|
254
|
+
): ToolAwareStream =>
|
|
255
|
+
new ToolAwareStream(ctx.tokenizer, toolStreamMode(tools), tools, onParseFailure);
|
|
256
|
+
|
|
257
|
+
/** Strict fill rows are a function of (template mode, tool signature) only —
|
|
258
|
+
* they are token spans of the assistant turn, which opens with a special
|
|
259
|
+
* token, so message content cannot shift them (the same argument that lets
|
|
260
|
+
* stableLenFor memoize its primer length). Compiling costs several template
|
|
261
|
+
* renders + encodes, so it happens ONCE per mode. */
|
|
262
|
+
const fillRowsByMode = new Map<string, StrictRowPlan>();
|
|
263
|
+
const toolsSignature = (tools: ToolDefinition[]): string =>
|
|
264
|
+
JSON.stringify(tools.map((t) => [
|
|
265
|
+
t.function?.name,
|
|
266
|
+
Object.keys((t.function?.parameters?.properties ?? {}) as Record<string, unknown>),
|
|
267
|
+
(t.function?.parameters?.required as unknown) ?? null,
|
|
268
|
+
(t.function?.parameters?.additionalProperties as unknown) ?? null,
|
|
269
|
+
]));
|
|
270
|
+
|
|
271
|
+
/** Token fast-forwarding (docs/design/speculative-decoding.md "Token
|
|
272
|
+
* fast-forwarding"): compile this request's determined-span table, or null
|
|
273
|
+
* when the feature is off or the request's shape refuses it.
|
|
274
|
+
*
|
|
275
|
+
* Refusals owned here (body-level); the ones only ChatStage can see —
|
|
276
|
+
* a compiled grammar, media prompts, a mounted draft model, a quantized KV
|
|
277
|
+
* scheme — are applied there. Continuous (batch) placement needs no
|
|
278
|
+
* refusal: generate() is the only fill site, so a batched request simply
|
|
279
|
+
* does not fill.
|
|
280
|
+
*
|
|
281
|
+
* MLX_BUN_FILL=echo additionally arms the echo index (K3c, Lab tier): the
|
|
282
|
+
* strict rows keep policy "assert", and copied spans ride the same
|
|
283
|
+
* mechanism under policy "verify". */
|
|
284
|
+
const fillPlanFor = (
|
|
285
|
+
req: ChatRequestParams,
|
|
286
|
+
tools: ToolDefinition[] | null,
|
|
287
|
+
promptIds: number[],
|
|
288
|
+
): FillSession | null => {
|
|
289
|
+
const mode = resolveFillMode();
|
|
290
|
+
if (mode === "off") return null;
|
|
291
|
+
if (!tools?.length) return null;
|
|
292
|
+
// A user-fixed seed means reproducibility: the sampler's step index would
|
|
293
|
+
// skip the injected positions, so an identical request could not replay.
|
|
294
|
+
if (req.seed !== undefined) return null;
|
|
295
|
+
// Injected tokens are never sampled — they have no logprob row.
|
|
296
|
+
if (req.logprobs === true) return null;
|
|
297
|
+
if (typeof req.top_logprobs === "number" && req.top_logprobs > 0) return null;
|
|
298
|
+
// Structured output owns forced-token production (grammar jump-forward).
|
|
299
|
+
if (req.response_format != null || req.guided_grammar || req.guided_regex ||
|
|
300
|
+
req.guided_choice?.length || req.structured_outputs != null) return null;
|
|
301
|
+
|
|
302
|
+
const opts = templateOptionsFor(req, tools);
|
|
303
|
+
const key = `${opts.enableThinking}|${opts.reasoningEffort ?? ""}|` +
|
|
304
|
+
`${opts.preserveThinking ?? ""}|${toolsSignature(tools)}`;
|
|
305
|
+
let plan = fillRowsByMode.get(key);
|
|
306
|
+
if (plan === undefined) {
|
|
307
|
+
plan = compileStrictFillRows({
|
|
308
|
+
template: ctx.template,
|
|
309
|
+
tokenizer: ctx.tokenizer,
|
|
310
|
+
messages: normalizeMessages(req.messages),
|
|
311
|
+
tools,
|
|
312
|
+
renderOptions: opts,
|
|
313
|
+
});
|
|
314
|
+
fillRowsByMode.set(key, plan);
|
|
315
|
+
}
|
|
316
|
+
// The echo tier can carry a request whose template yields no strict rows;
|
|
317
|
+
// strict-only mode cannot.
|
|
318
|
+
if (!plan.rows.length && mode !== "echo") return null;
|
|
319
|
+
return new FillSession(
|
|
320
|
+
{
|
|
321
|
+
rows: plan.rows,
|
|
322
|
+
echo: mode === "echo" ? fillEchoConfig() : null,
|
|
323
|
+
delimiters: new Set(plan.delimiters),
|
|
324
|
+
eos: ctx.model.config.eosTokenIds,
|
|
325
|
+
},
|
|
326
|
+
promptIds,
|
|
327
|
+
{
|
|
328
|
+
maxSpan: fillMaxSpan(), decode: (ids) => ctx.tokenizer.decode([...ids]),
|
|
329
|
+
strictContext: plan.createContext?.(promptIds),
|
|
330
|
+
},
|
|
331
|
+
);
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
resolveEnableThinking,
|
|
336
|
+
toOptions,
|
|
337
|
+
compileGrammarForRequest,
|
|
338
|
+
templateOptionsFor,
|
|
339
|
+
promptIdsFor,
|
|
340
|
+
stableLenFor,
|
|
341
|
+
toolRouter,
|
|
342
|
+
fillPlanFor,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
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
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { cleanupFailure } from "../engine/resources";
|
|
2
|
+
import type { ExecutionPlan, InferenceMethod, Timer } from "../contracts/generation";
|
|
3
|
+
import { CancellationSource, throwIfCancelled } from "../engine/cancellation";
|
|
4
|
+
import { createInferenceEngine } from "../engine/engine";
|
|
5
|
+
import type { GenerateStats } from "../generate";
|
|
6
|
+
import type { CompletionEngine } from "./completion-executor";
|
|
7
|
+
|
|
8
|
+
const timer: Timer = {
|
|
9
|
+
after(ms, callback) {
|
|
10
|
+
const handle = setTimeout(callback, ms);
|
|
11
|
+
return () => clearTimeout(handle);
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type RuntimeArguments = Parameters<CompletionEngine["run"]>;
|
|
16
|
+
|
|
17
|
+
export interface CompletionRunRequest {
|
|
18
|
+
prompt: RuntimeArguments[0]; options: RuntimeArguments[1]; vision?: RuntimeArguments[3];
|
|
19
|
+
shape: RuntimeArguments[4]; placement: RuntimeArguments[5];
|
|
20
|
+
signal?: RuntimeArguments[6]; trace?: RuntimeArguments[7];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Bind one selected runtime operation to the method-neutral session. */
|
|
24
|
+
export function createCompletionMethod(
|
|
25
|
+
runtime: CompletionEngine, request: CompletionRunRequest,
|
|
26
|
+
onStarted?: () => void, onFailure?: (error: unknown) => void,
|
|
27
|
+
): InferenceMethod<GenerateStats> {
|
|
28
|
+
const { prompt, options, vision, shape, placement, signal, trace } = request;
|
|
29
|
+
const maxTokens = options.maxTokens ?? 512;
|
|
30
|
+
return {
|
|
31
|
+
id: placement.execution?.method ?? placement.mechanism,
|
|
32
|
+
async createRun(cancel) {
|
|
33
|
+
throwIfCancelled(cancel);
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const unsubscribe = cancel.subscribe(() => controller.abort(
|
|
36
|
+
signal?.reason ?? new DOMException("generation cancelled", "AbortError"),
|
|
37
|
+
));
|
|
38
|
+
return {
|
|
39
|
+
async execute(output) {
|
|
40
|
+
onStarted?.();
|
|
41
|
+
try {
|
|
42
|
+
const stats = await runtime.run(prompt, options,
|
|
43
|
+
(token, logprobs) => output.commit([token], logprobs ? [logprobs] : undefined),
|
|
44
|
+
vision, shape, placement, controller.signal, trace);
|
|
45
|
+
return {
|
|
46
|
+
finishReason: stats.finishReason ?? (stats.generatedTokens >= maxTokens ? "length" : "stop"),
|
|
47
|
+
metrics: stats,
|
|
48
|
+
};
|
|
49
|
+
} catch (error) { onFailure?.(error); throw error; }
|
|
50
|
+
},
|
|
51
|
+
async close() { unsubscribe(); },
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** HTTP already bounds its output in CompletionSink. Deliver through the same
|
|
58
|
+
* session lifecycle without inserting another queue between sampling and the
|
|
59
|
+
* stop/tool parser. The runtime retains scheduling and native resource ownership. */
|
|
60
|
+
export function createSessionCompletionEngine(
|
|
61
|
+
runtime: CompletionEngine,
|
|
62
|
+
releaseUnstarted: (options: Parameters<CompletionEngine["run"]>[1],
|
|
63
|
+
vision: Parameters<CompletionEngine["run"]>[3]) => void,
|
|
64
|
+
): CompletionEngine & { close(): Promise<void> } {
|
|
65
|
+
const engine = createInferenceEngine<ExecutionPlan<GenerateStats>, GenerateStats>({
|
|
66
|
+
async plan(plan, cancellation) { throwIfCancelled(cancellation); return plan; },
|
|
67
|
+
}, { timer });
|
|
68
|
+
return {
|
|
69
|
+
place: (shape, options) => runtime.place(shape, options),
|
|
70
|
+
async run(prompt, options, onToken, vision, shape, placement, signal, trace) {
|
|
71
|
+
const cancellation = new CancellationSource();
|
|
72
|
+
const abort = () => cancellation.cancel("requested");
|
|
73
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
74
|
+
if (signal?.aborted) abort();
|
|
75
|
+
let started = false;
|
|
76
|
+
let failure: unknown;
|
|
77
|
+
let failed = false;
|
|
78
|
+
const maxTokens = options.maxTokens ?? 512;
|
|
79
|
+
const method = createCompletionMethod(runtime,
|
|
80
|
+
{ prompt, options, vision, shape, placement, signal, trace },
|
|
81
|
+
() => { started = true; },
|
|
82
|
+
(error) => { failed = true; failure = error; });
|
|
83
|
+
try {
|
|
84
|
+
const session = await engine.open({ id: method.id, method, outputTokenLimit: maxTokens }, {
|
|
85
|
+
output: "callback", cancellation,
|
|
86
|
+
async onTokens(ids, logprobs) {
|
|
87
|
+
for (let i = 0; i < ids.length; i++)
|
|
88
|
+
if (await onToken(ids[i]!, logprobs?.[i]) === false) return false;
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
const outcome = await session.result;
|
|
92
|
+
if (outcome.status === "completed") return outcome.result.metrics;
|
|
93
|
+
if (failed) throw failure; // preserve native/HTTP error identity and stack
|
|
94
|
+
if (outcome.status === "cancelled")
|
|
95
|
+
throw signal?.reason ?? new DOMException("generation cancelled", "AbortError");
|
|
96
|
+
throw new Error(outcome.error.message);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (!started) {
|
|
99
|
+
started = true; // release once, preserving the original execution error
|
|
100
|
+
cleanupFailure(error, () => releaseUnstarted(options, vision));
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
} finally {
|
|
104
|
+
signal?.removeEventListener("abort", abort);
|
|
105
|
+
if (!started) releaseUnstarted(options, vision);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
close: () => engine.close(),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -258,7 +258,7 @@ export async function searchSessions(sessionDir: string, query: string): Promise
|
|
|
258
258
|
REST handlers — pure (URL) => Response, no `ctx` dependency, same
|
|
259
259
|
convention as src/memory/rest.ts / src/hub-rest.ts (dispatched from
|
|
260
260
|
server.ts by path+method; unit-tested directly in
|
|
261
|
-
tests/session-search.test.ts without booting a model context).
|
|
261
|
+
tests/serve/session-search.test.ts without booting a model context).
|
|
262
262
|
──────────────────────────────────────────────────────────────────── */
|
|
263
263
|
|
|
264
264
|
function jsonOk<T extends object>(body: T, init?: ResponseInit): Response {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
export interface StaticRouteAssets {
|
|
4
|
+
appPage: string;
|
|
5
|
+
appJs: string;
|
|
6
|
+
hljsJs: string;
|
|
7
|
+
hljsCss: string;
|
|
8
|
+
manifest: string;
|
|
9
|
+
iconSvg: string;
|
|
10
|
+
serviceWorker: string;
|
|
11
|
+
curvePage: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ASSET_HEADERS = { "cache-control": "public, max-age=3600" } as const;
|
|
15
|
+
const LEGACY_PATHS = new Set(["/status", "/chat", "/quantize", "/finetune", "/dataset"]);
|
|
16
|
+
|
|
17
|
+
/** Serve the web shell and its static companion pages. Returns null when the
|
|
18
|
+
* request belongs to another route family. */
|
|
19
|
+
export function handleStaticRoute(
|
|
20
|
+
url: URL,
|
|
21
|
+
request: Request,
|
|
22
|
+
assets: StaticRouteAssets,
|
|
23
|
+
): Response | null {
|
|
24
|
+
if (request.method !== "GET") return null;
|
|
25
|
+
|
|
26
|
+
switch (url.pathname) {
|
|
27
|
+
case "/":
|
|
28
|
+
return new Response(assets.appPage, {
|
|
29
|
+
headers: { "content-type": "text/html; charset=utf-8" },
|
|
30
|
+
});
|
|
31
|
+
case "/assets/hljs.js":
|
|
32
|
+
return new Response(assets.hljsJs, {
|
|
33
|
+
headers: { "content-type": "text/javascript; charset=utf-8", ...ASSET_HEADERS },
|
|
34
|
+
});
|
|
35
|
+
case "/assets/hljs.css":
|
|
36
|
+
return new Response(assets.hljsCss, {
|
|
37
|
+
headers: { "content-type": "text/css; charset=utf-8", ...ASSET_HEADERS },
|
|
38
|
+
});
|
|
39
|
+
case "/assets/app.js":
|
|
40
|
+
return new Response(assets.appJs, {
|
|
41
|
+
headers: { "content-type": "text/javascript; charset=utf-8", ...ASSET_HEADERS },
|
|
42
|
+
});
|
|
43
|
+
case "/manifest.webmanifest":
|
|
44
|
+
return new Response(assets.manifest, {
|
|
45
|
+
headers: { "content-type": "application/manifest+json; charset=utf-8", ...ASSET_HEADERS },
|
|
46
|
+
});
|
|
47
|
+
case "/assets/icon.svg":
|
|
48
|
+
return new Response(assets.iconSvg, {
|
|
49
|
+
headers: { "content-type": "image/svg+xml; charset=utf-8", ...ASSET_HEADERS },
|
|
50
|
+
});
|
|
51
|
+
case "/sw.js":
|
|
52
|
+
return new Response(assets.serviceWorker, {
|
|
53
|
+
headers: { "content-type": "text/javascript; charset=utf-8", "cache-control": "no-store" },
|
|
54
|
+
});
|
|
55
|
+
case "/curves": {
|
|
56
|
+
let html = assets.curvePage;
|
|
57
|
+
try {
|
|
58
|
+
html = readFileSync(new URL("../lab/curve/curve-designer.html", import.meta.url), "utf8");
|
|
59
|
+
} catch {
|
|
60
|
+
// Compiled binaries use the embedded copy.
|
|
61
|
+
}
|
|
62
|
+
return new Response(html, {
|
|
63
|
+
headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
case "/dag":
|
|
67
|
+
try {
|
|
68
|
+
const html = readFileSync(
|
|
69
|
+
new URL("./assets/training-inference-map.html", import.meta.url),
|
|
70
|
+
"utf8",
|
|
71
|
+
);
|
|
72
|
+
return new Response(html, {
|
|
73
|
+
headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" },
|
|
74
|
+
});
|
|
75
|
+
} catch {
|
|
76
|
+
return new Response(
|
|
77
|
+
"DAG map artifact not found",
|
|
78
|
+
{ status: 404 },
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
case "/curve-terrain":
|
|
82
|
+
try {
|
|
83
|
+
const html = readFileSync(
|
|
84
|
+
new URL("./assets/curve-terrain.html", import.meta.url),
|
|
85
|
+
"utf8",
|
|
86
|
+
);
|
|
87
|
+
return new Response(html, {
|
|
88
|
+
headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" },
|
|
89
|
+
});
|
|
90
|
+
} catch {
|
|
91
|
+
return new Response(
|
|
92
|
+
"curve terrain artifact not found",
|
|
93
|
+
{ status: 404 },
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return LEGACY_PATHS.has(url.pathname)
|
|
99
|
+
? Response.redirect(`/#${url.pathname}`, 302)
|
|
100
|
+
: null;
|
|
101
|
+
}
|