mlx-bun 0.5.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1449 -109
- package/THIRD_PARTY_NOTICES.md +882 -0
- package/bin/mlx-bun.convert.mjs +4 -0
- package/bin/mlx-bun.fuse.mjs +4 -0
- package/bin/mlx-bun.generate.mjs +4 -0
- package/bin/mlx-bun.lora.mjs +4 -0
- package/bin/mlx-bun.mjs +13 -21
- package/bin/mlx-bun.server.mjs +4 -0
- package/bin/mlx-bun.upload.mjs +4 -0
- package/dist/web/app.js +9584 -0
- package/package.json +50 -53
- package/src/cli/ane-install.ts +76 -0
- package/src/cli/args.ts +179 -0
- package/src/cli/engine-entry.ts +68 -0
- package/src/cli/inference.ts +214 -0
- package/src/cli/job-entry.ts +97 -0
- package/src/cli/library-host.ts +117 -0
- package/src/cli/main.ts +57 -0
- package/src/cli/memory-engine.ts +163 -0
- package/src/cli/memory.ts +36 -0
- package/src/cli/mlx-lm-aliases.ts +309 -0
- package/src/cli/model-choice.ts +33 -0
- package/src/cli/model-selection.ts +84 -0
- package/src/cli/module-host.ts +11 -0
- package/src/cli/module-verbs.ts +50 -0
- package/src/cli/numerical-policy.ts +12 -0
- package/src/cli/perplexity.ts +89 -0
- package/src/cli/serve-host.ts +423 -0
- package/src/cli/serve-isolated.ts +217 -0
- package/src/cli/serve-options.ts +106 -0
- package/src/cli/serve-state.ts +205 -0
- package/src/cli/serve.ts +500 -0
- package/src/cli/served-model-host.ts +102 -0
- package/src/cli/server-entry.ts +148 -0
- package/src/cli/serving-unit.ts +177 -0
- package/src/{tui.ts → cli/terminal.ts} +13 -8
- package/src/cli/worker-adapters.ts +34 -0
- package/src/cli/worker-entry.ts +272 -0
- package/src/cli/worker-unit.ts +179 -0
- package/src/engine/adapter-operation.ts +26 -0
- package/src/engine/cache-services.ts +114 -0
- package/src/engine/completion.ts +39 -0
- package/src/engine/generation-gateway.ts +358 -0
- package/src/engine/index.ts +59 -0
- package/src/engine/kv-budget.ts +120 -0
- package/src/engine/media-preparation.ts +232 -0
- package/src/engine/model-binding.ts +24 -0
- package/src/engine/model-host.ts +513 -0
- package/src/engine/model-serving.ts +89 -0
- package/src/{serve → engine}/preparation.ts +7 -5
- package/src/{serve → engine}/session-completion-engine.ts +6 -6
- package/src/engine/telemetry.ts +109 -0
- package/src/hub/downloads.ts +116 -0
- package/src/jobs/db.ts +16 -7
- package/src/jobs/events.ts +29 -0
- package/src/jobs/executable.ts +5 -0
- package/src/jobs/host.ts +51 -0
- package/src/jobs/in-process.ts +55 -0
- package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
- package/src/jobs/runner.ts +150 -231
- package/src/jobs/service.ts +75 -0
- package/src/jobs/sse.ts +59 -23
- package/src/jobs/worker-memory.ts +46 -0
- package/src/jobs/worker-process.ts +163 -0
- package/src/jobs/worker-supervisor.ts +258 -0
- package/src/modules.ts +58 -0
- package/src/publishing/catalog-hub.ts +18 -0
- package/src/publishing/credentials.ts +31 -0
- package/src/publishing/upload.ts +37 -0
- package/src/residency/leased-adapters.ts +21 -0
- package/src/residency/local-records.ts +16 -0
- package/src/residency/model-residency.ts +405 -0
- package/src/residency/record-index.ts +28 -0
- package/src/residency/resident-estimate.ts +50 -0
- package/src/{anthropic.ts → server/anthropic.ts} +3 -81
- package/src/server/cache-routes.ts +36 -0
- package/src/{serve → server}/chat-request.ts +10 -10
- package/src/{serve → server}/chat-stage.ts +14 -14
- package/src/{client.ts → server/client.ts} +28 -2
- package/src/{serve → server}/completion-executor.ts +33 -41
- package/src/server/decision-body.ts +55 -0
- package/src/server/decision-routes.ts +134 -0
- package/src/server/decision-wire.ts +228 -0
- package/src/{serve → server}/discovery-routes.ts +25 -82
- package/src/{serve → server}/http.ts +25 -13
- package/src/{serve → server}/inference-request.ts +5 -5
- package/src/server/job-routes.ts +20 -0
- package/src/server/media-prompt.ts +63 -0
- package/src/server/memory-completion-client.ts +46 -0
- package/src/server/model-routes.ts +192 -0
- package/src/{serve → server}/openai-wire.ts +3 -2
- package/src/{serve → server}/pipeline.ts +1 -1
- package/src/server/prompt-builder.ts +13 -0
- package/src/server/prompt-contracts.ts +17 -0
- package/src/server/proxy-routes.ts +187 -0
- package/src/server/publishing-routes.ts +41 -0
- package/src/{serve → server}/request-plan.ts +6 -8
- package/src/{serve → server}/request-prep.ts +47 -50
- package/src/{serve → server}/responses-client.ts +30 -13
- package/src/{responses.ts → server/responses.ts} +12 -93
- package/src/server/routes.ts +205 -0
- package/src/server/start.ts +72 -0
- package/src/server/status-routes.ts +108 -0
- package/src/{serve → server}/text-completion-stage.ts +25 -4
- package/src/server/text-prompt.ts +12 -0
- package/src/{serve → server}/token-streams.ts +36 -38
- package/src/server/worker-routes.ts +311 -0
- package/src/storage/paths.ts +61 -0
- package/src/web/assets.ts +37 -0
- package/src/web/{src → browser}/api.ts +5 -23
- package/src/web/browser/chat-host.ts +17 -0
- package/src/web/browser/installed-panels.ts +6 -0
- package/src/web/browser/main.ts +55 -0
- package/src/web/browser/markdown.ts +279 -0
- package/src/web/browser/memory-panel.ts +9 -0
- package/src/web/browser/palette.ts +64 -0
- package/src/web/browser/protocol.ts +13 -0
- package/src/web/browser/shell.ts +467 -0
- package/src/web/{src → browser}/status.ts +32 -36
- package/src/web/build.ts +51 -0
- package/src/web/public/app.html +1054 -0
- package/src/web/{sw.js → public/sw.js} +4 -3
- package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
- package/src/web/{vendor → public/vendor}/hljs.js +2 -2
- package/THIRD_PARTY_LICENSES.md +0 -51
- package/docs/README.md +0 -107
- package/docs/archive/hlg-sampling.md +0 -350
- package/docs/archive/investigations/audio-input-plan.md +0 -356
- package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +0 -3617
- package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
- package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
- package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
- package/docs/archive/investigations/curve-bisector-routing.md +0 -123
- package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
- package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
- package/docs/archive/investigations/dspark-handoff.md +0 -224
- package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
- package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
- package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
- package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
- package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
- package/docs/archive/investigations/lab-build-journal.md +0 -157
- package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
- package/docs/archive/investigations/openjarvis-fit.md +0 -198
- package/docs/archive/investigations/openwiki-evaluation.md +0 -249
- package/docs/archive/investigations/optimization_plan.md +0 -346
- package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
- package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
- package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
- package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
- package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
- package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
- package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
- package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
- package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
- package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
- package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
- package/docs/archive/planning/chat-ui-vision.md +0 -50
- package/docs/archive/planning/chunk-task-roadmap.md +0 -77
- package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
- package/docs/archive/planning/journal.md +0 -316
- package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
- package/docs/archive/planning/pi-integration-review.md +0 -178
- package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
- package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
- package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
- package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
- package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
- package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
- package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
- package/docs/archive/planning/release-notes-v0.4.0.md +0 -45
- package/docs/archive/planning/release-notes-v0.5.0.md +0 -37
- package/docs/archive/planning/web-ui-pass-plan.md +0 -213
- package/docs/archive/planning/website-readme-pass-plan.md +0 -224
- package/docs/design/batching.md +0 -2054
- package/docs/design/decode-speed-program.md +0 -2404
- package/docs/design/dreaming-nightly-pipeline.md +0 -684
- package/docs/design/generic-model-support.md +0 -821
- package/docs/design/kv-cache.md +0 -993
- package/docs/design/orpo-training.md +0 -680
- package/docs/design/speculative-decoding.md +0 -2129
- package/docs/design/turboquant.md +0 -2225
- package/docs/design/unified-engine-frontier-plan.md +0 -1987
- package/docs/design/web-chat-redesign.md +0 -695
- package/docs/planning/PRODUCT_ROADMAP.md +0 -593
- package/docs/planning/ResearchTopics.md +0 -119
- package/docs/planning/public-readiness-handoff.md +0 -244
- package/docs/planning/public-readiness.md +0 -149
- package/docs/planning/video-series.md +0 -421
- package/docs/reference/benchmarks.md +0 -3587
- package/docs/reference/cli.md +0 -646
- package/docs/reference/distribution.md +0 -380
- package/docs/reference/environment.md +0 -494
- package/docs/reference/glossary.md +0 -39
- package/docs/reference/library-api.md +0 -688
- package/docs/reference/memory.md +0 -260
- package/docs/reference/models.md +0 -346
- package/docs/reference/server-api.md +0 -1945
- package/docs/reference/server-config.md +0 -973
- package/docs/reference/training.md +0 -462
- package/docs/reference/troubleshooting.md +0 -158
- package/src/admission-plan.ts +0 -18
- package/src/audio/audiotoolbox.ts +0 -112
- package/src/audio/conformer.ts +0 -655
- package/src/audio/decode.ts +0 -133
- package/src/audio/features.ts +0 -202
- package/src/audio/mic-capture.ts +0 -98
- package/src/audio/silero-vad.ts +0 -379
- package/src/audio/transcode.ts +0 -67
- package/src/audio/whisper-decode.ts +0 -774
- package/src/audio/whisper-fast.ts +0 -732
- package/src/audio/whisper-format.ts +0 -73
- package/src/audio/whisper-mel.ts +0 -185
- package/src/audio/whisper-timing.ts +0 -274
- package/src/audio/whisper-tokenizer.ts +0 -239
- package/src/audio/whisper-transcribe.ts +0 -410
- package/src/backends/mlx/assistant-target.ts +0 -46
- package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
- package/src/backends/mlx/autoregressive.ts +0 -129
- package/src/backends/mlx/batch-group.ts +0 -1874
- package/src/backends/mlx/cache-attachment.ts +0 -67
- package/src/backends/mlx/cache-layout.ts +0 -31
- package/src/backends/mlx/checkpoint-state.ts +0 -53
- package/src/backends/mlx/continuation-persistence.ts +0 -107
- package/src/backends/mlx/continuation-request.ts +0 -43
- package/src/backends/mlx/continuation.ts +0 -77
- package/src/backends/mlx/diffusion.ts +0 -39
- package/src/backends/mlx/draft-target.ts +0 -32
- package/src/backends/mlx/encoder-cache.ts +0 -25
- package/src/backends/mlx/evaluation.ts +0 -105
- package/src/backends/mlx/fill-append.ts +0 -58
- package/src/backends/mlx/fill-group.ts +0 -497
- package/src/backends/mlx/full-kv-row-append.ts +0 -32
- package/src/backends/mlx/full-kv-row-donor.ts +0 -54
- package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
- package/src/backends/mlx/gateway-binding.ts +0 -174
- package/src/backends/mlx/graph.ts +0 -38
- package/src/backends/mlx/kv-codec.ts +0 -9
- package/src/backends/mlx/kv-maintenance.ts +0 -161
- package/src/backends/mlx/kv-tensor-rows.ts +0 -212
- package/src/backends/mlx/memory-client.ts +0 -189
- package/src/backends/mlx/methods.ts +0 -90
- package/src/backends/mlx/mixed-iteration.ts +0 -62
- package/src/backends/mlx/model-prompt.ts +0 -178
- package/src/backends/mlx/model-serving.ts +0 -80
- package/src/backends/mlx/paged-cache-codec.ts +0 -27
- package/src/backends/mlx/prefill-cohort.ts +0 -100
- package/src/backends/mlx/prefill-policy.ts +0 -26
- package/src/backends/mlx/prefill-rows.ts +0 -244
- package/src/backends/mlx/prefill.ts +0 -33
- package/src/backends/mlx/projected-context-rows.ts +0 -72
- package/src/backends/mlx/prompt-input.ts +0 -29
- package/src/backends/mlx/qwen-prompt-input.ts +0 -40
- package/src/backends/mlx/request-policy.ts +0 -20
- package/src/backends/mlx/request-state-policy.ts +0 -54
- package/src/backends/mlx/rollback.ts +0 -92
- package/src/backends/mlx/row-sampling.ts +0 -49
- package/src/backends/mlx/serial-executor.ts +0 -251
- package/src/backends/mlx/speculative-group.ts +0 -438
- package/src/backends/mlx/speculative-round.ts +0 -161
- package/src/backends/mlx/speculative.ts +0 -131
- package/src/backends/mlx/state-rows.ts +0 -104
- package/src/backends/mlx/state-views.ts +0 -46
- package/src/backends/mlx/target-layout-capability.ts +0 -35
- package/src/backends/mlx/text-engine.ts +0 -73
- package/src/backends/mlx/transitioning-kv-rows.ts +0 -173
- package/src/chat-template.ts +0 -382
- package/src/cli.ts +0 -3490
- package/src/config.ts +0 -399
- package/src/contracts/completion.ts +0 -16
- package/src/contracts/execution.ts +0 -70
- package/src/contracts/generation.ts +0 -103
- package/src/contracts/host.ts +0 -7
- package/src/contracts/object-cache.ts +0 -8
- package/src/contracts/pi-provider.ts +0 -30
- package/src/contracts/pi.ts +0 -261
- package/src/contracts/prefix-cache.ts +0 -33
- package/src/contracts/resources.ts +0 -25
- package/src/contracts/sampling.ts +0 -23
- package/src/contracts/scheduling.ts +0 -54
- package/src/contracts/tsconfig.json +0 -14
- package/src/dataset/generators.ts +0 -1087
- package/src/dataset/index.ts +0 -12
- package/src/dataset/job.ts +0 -81
- package/src/dataset/llm.ts +0 -71
- package/src/dataset/registry.ts +0 -528
- package/src/diffusion/diffusion-generate.ts +0 -507
- package/src/download.ts +0 -505
- package/src/embed.ts +0 -77
- package/src/engine/admission.ts +0 -59
- package/src/engine/cancellation.ts +0 -38
- package/src/engine/engine.ts +0 -56
- package/src/engine/execution-coordinator.ts +0 -70
- package/src/engine/execution-plan.ts +0 -64
- package/src/engine/execution-tasks.ts +0 -44
- package/src/engine/namespaced-cache.ts +0 -13
- package/src/engine/resources.ts +0 -53
- package/src/engine/scheduler.ts +0 -92
- package/src/engine/session.ts +0 -247
- package/src/eval/README.md +0 -130
- package/src/eval/ifeval.ts +0 -81
- package/src/eval/kl-prompts.ts +0 -29
- package/src/eval/kl.ts +0 -590
- package/src/eval/perplexity.ts +0 -164
- package/src/eval/quality-db.ts +0 -92
- package/src/eval/reference-logits.ts +0 -207
- package/src/eval/runner.ts +0 -124
- package/src/eval/score.ts +0 -49
- package/src/eval/tasks/bfcl.ts +0 -535
- package/src/eval/tasks/gsm8k.ts +0 -98
- package/src/eval/tasks/hashhop.ts +0 -139
- package/src/eval/tasks/humaneval.ts +0 -275
- package/src/eval/tasks/ifeval.ts +0 -525
- package/src/eval/tasks/mmlu.ts +0 -157
- package/src/eval/ultrafeedback.ts +0 -60
- package/src/evaldb.ts +0 -199
- package/src/expert-io.ts +0 -393
- package/src/expert-offload-build.ts +0 -77
- package/src/expert-offload.ts +0 -76
- package/src/expert-residency.ts +0 -1134
- package/src/expert-usage.ts +0 -415
- package/src/fill/echo-index.ts +0 -188
- package/src/fill/fill-session.ts +0 -463
- package/src/fill/proposal.ts +0 -68
- package/src/fill/schema-rows.ts +0 -506
- package/src/fill/tool-boundary.ts +0 -103
- package/src/fit.ts +0 -249
- package/src/generate.ts +0 -1352
- package/src/grammar.ts +0 -604
- package/src/harness-pi.ts +0 -184
- package/src/hf-push.ts +0 -628
- package/src/hub-rest.ts +0 -242
- package/src/index.ts +0 -63
- package/src/inference/denoising.ts +0 -17
- package/src/inference/draft-acceptance.ts +0 -41
- package/src/inference/draft-output.ts +0 -49
- package/src/inference/graph.ts +0 -33
- package/src/inference/prefill.ts +0 -45
- package/src/inference/rollback.ts +0 -7
- package/src/jobs/index.ts +0 -31
- package/src/jobs/job-entry.ts +0 -107
- package/src/jobs/task-client.ts +0 -19
- package/src/jobs/types.ts +0 -2
- package/src/kv-scheme.ts +0 -279
- package/src/kv-store.ts +0 -1135
- package/src/lab/README.md +0 -22
- package/src/lab/curve/curve-designer.html +0 -303
- package/src/lab/curve/curve-sampler.ts +0 -122
- package/src/lab/expert-trace/expert-trace.ts +0 -69
- package/src/lab/paged-kv/paged-attention.ts +0 -117
- package/src/lab/paged-kv/paged-kv-rows.ts +0 -111
- package/src/lab/paged-kv/paged-kv.ts +0 -336
- package/src/library.ts +0 -40
- package/src/lora.ts +0 -408
- package/src/md-modules.d.ts +0 -54
- package/src/media-fetch.ts +0 -442
- package/src/memory/article.ts +0 -558
- package/src/memory/chunk-validation.ts +0 -150
- package/src/memory/chunk.ts +0 -318
- package/src/memory/cluster.ts +0 -351
- package/src/memory/coverage.ts +0 -134
- package/src/memory/crosslink.ts +0 -0
- package/src/memory/db.ts +0 -253
- package/src/memory/entity.ts +0 -203
- package/src/memory/gate.ts +0 -187
- package/src/memory/history.ts +0 -152
- package/src/memory/ingest.ts +0 -335
- package/src/memory/model.ts +0 -183
- package/src/memory/normalize.ts +0 -671
- package/src/memory/parse.ts +0 -22
- package/src/memory/pipeline.ts +0 -275
- package/src/memory/prompts.ts +0 -65
- package/src/memory/query.ts +0 -334
- package/src/memory/reconcile.ts +0 -734
- package/src/memory/redirect.ts +0 -108
- package/src/memory/reindex.ts +0 -325
- package/src/memory/resolve.ts +0 -307
- package/src/memory/rest.ts +0 -245
- package/src/memory/route.ts +0 -272
- package/src/memory/schedule.ts +0 -165
- package/src/memory/stages.ts +0 -777
- package/src/memory/synthesize.ts +0 -1638
- package/src/memory/tools.ts +0 -749
- package/src/memory/vault.ts +0 -1194
- package/src/memory/wikify.ts +0 -1064
- package/src/mlx/array.ts +0 -425
- package/src/mlx/autograd.ts +0 -304
- package/src/mlx/checkpoint.ts +0 -149
- package/src/mlx/compile.ts +0 -168
- package/src/mlx/custom-vjp.ts +0 -188
- package/src/mlx/ffi.ts +0 -531
- package/src/mlx/materialize.ts +0 -23
- package/src/mlx/metal-kernel.ts +0 -159
- package/src/mlx/normalized-argmax.ts +0 -116
- package/src/mlx/ops.ts +0 -1108
- package/src/mlx/quantized-rows.ts +0 -36
- package/src/mlx/token-bitmask.ts +0 -52
- package/src/mlx/turboquant-kv-decode.ts +0 -82
- package/src/mlx/turboquant-kv-inverse.ts +0 -122
- package/src/mlx/turboquant-ops.ts +0 -861
- package/src/mlx/turboquant-tables.ts +0 -237
- package/src/mmap.ts +0 -90
- package/src/model/artifact-identity.ts +0 -73
- package/src/model/batched-kv.ts +0 -235
- package/src/model/batched-mask.ts +0 -279
- package/src/model/batched-quant.ts +0 -212
- package/src/model/batched-quantized-kv.ts +0 -144
- package/src/model/batched-rotating-quant.ts +0 -353
- package/src/model/batched-rotating-state.ts +0 -149
- package/src/model/batched-rotating.ts +0 -338
- package/src/model/batched-row-storage.ts +0 -179
- package/src/model/batched-ssm.ts +0 -52
- package/src/model/batched-turboquant-kv.ts +0 -94
- package/src/model/compiled-decode.ts +0 -712
- package/src/model/decoded-kv-donor.ts +0 -12
- package/src/model/delayed-quantized-kv.ts +0 -72
- package/src/model/delayed-rotating-quantized-kv.ts +0 -93
- package/src/model/delayed-turboquant-kv.ts +0 -57
- package/src/model/diffusion-gemma.ts +0 -1026
- package/src/model/factory.ts +0 -215
- package/src/model/fingerprint.ts +0 -45
- package/src/model/flash-attention.ts +0 -694
- package/src/model/full-prefill-padding.ts +0 -56
- package/src/model/full-prefill-row.ts +0 -105
- package/src/model/gemma4-base.ts +0 -2370
- package/src/model/gemma4.ts +0 -1395
- package/src/model/generated/gemma4-12b.ts +0 -429
- package/src/model/generated/gemma4-26b.ts +0 -448
- package/src/model/generated/gemma4-e4b.ts +0 -717
- package/src/model/generated/index.ts +0 -18
- package/src/model/glm52-atlas.ts +0 -511
- package/src/model/glm52-cache.ts +0 -792
- package/src/model/glm52-config.ts +0 -262
- package/src/model/glm52-container.ts +0 -305
- package/src/model/glm52-coupling.ts +0 -388
- package/src/model/glm52-dsa.ts +0 -606
- package/src/model/glm52-expert-layout.ts +0 -253
- package/src/model/glm52-layout.ts +0 -152
- package/src/model/glm52-memory.ts +0 -500
- package/src/model/glm52-mla.ts +0 -792
- package/src/model/glm52-moe.ts +0 -223
- package/src/model/glm52-pilot.ts +0 -537
- package/src/model/glm52-reference.ts +0 -445
- package/src/model/glm52-residency.ts +0 -557
- package/src/model/glm52-resident-weights.ts +0 -135
- package/src/model/glm52-streamed-experts.ts +0 -453
- package/src/model/glm52-streamed-metal.ts +0 -610
- package/src/model/glm52-weights.ts +0 -503
- package/src/model/glm52.ts +0 -1132
- package/src/model/implementation.ts +0 -69
- package/src/model/kv-attention-view.ts +0 -70
- package/src/model/minicpm5.ts +0 -376
- package/src/model/profile.ts +0 -468
- package/src/model/quantized-append-attention.ts +0 -31
- package/src/model/qwen-append.ts +0 -12
- package/src/model/qwen-conv.ts +0 -52
- package/src/model/qwen3-delta-state.ts +0 -74
- package/src/model/qwen3-delta.ts +0 -451
- package/src/model/qwen3-moe.ts +0 -404
- package/src/model/qwen3-mrope.ts +0 -250
- package/src/model/qwen3.ts +0 -277
- package/src/model/qwen3_5-checkpoint.ts +0 -164
- package/src/model/qwen3_5.ts +0 -881
- package/src/model/rotating-kv-donor.ts +0 -35
- package/src/model/rotating-kv-layout.ts +0 -262
- package/src/model/rotating-row-transaction.ts +0 -35
- package/src/model/speculative-rotating-kv.ts +0 -101
- package/src/model/ssm-prefill-padding.ts +0 -39
- package/src/model/support.ts +0 -110
- package/src/model/token-groups.ts +0 -66
- package/src/model/trellis-balanced-scatter.ts +0 -51
- package/src/model/trellis-linear.ts +0 -672
- package/src/model/trellis-shared-m.ts +0 -151
- package/src/model/trellis-shared-scatter.ts +0 -144
- package/src/model/trellis-splitk-prefill.ts +0 -158
- package/src/model/trellis-tiled-prefill.ts +0 -136
- package/src/model/trellis-vector-expand.ts +0 -62
- package/src/model/trellis-wide-prefill.ts +0 -94
- package/src/model/turboquant-codec.ts +0 -89
- package/src/model/universal/archs.ts +0 -332
- package/src/model/universal/dense.ts +0 -612
- package/src/model/universal/modules.ts +0 -170
- package/src/model/universal/rope.ts +0 -381
- package/src/model/whisper.ts +0 -435
- package/src/native/expert_io.c +0 -523
- package/src/native/frame_extract.swift +0 -140
- package/src/native/mic_capture.swift +0 -92
- package/src/native-pack.ts +0 -163
- package/src/pi-provider.ts +0 -53
- package/src/pi-session.ts +0 -81
- package/src/pi-terminal.ts +0 -274
- package/src/pi-web.ts +0 -1940
- package/src/preflight.ts +0 -158
- package/src/prompt-cache.ts +0 -682
- package/src/quantize/allocator.ts +0 -627
- package/src/quantize/atomic-output.ts +0 -38
- package/src/quantize/calibration.ts +0 -181
- package/src/quantize/config-writer.ts +0 -145
- package/src/quantize/index.ts +0 -84
- package/src/quantize/job.ts +0 -162
- package/src/quantize/quantizer.ts +0 -666
- package/src/quantize/rotate.ts +0 -603
- package/src/quantize/safetensors-writer.ts +0 -249
- package/src/quantize/sensitivity.ts +0 -216
- package/src/quantize/trellis.ts +0 -586
- package/src/quantize/weight-transform.ts +0 -292
- package/src/registry.ts +0 -602
- package/src/runtime-config.ts +0 -77
- package/src/safetensors.ts +0 -165
- package/src/sampler.ts +0 -1012
- package/src/serve/admin-routes.ts +0 -184
- package/src/serve/assets/curve-terrain.html +0 -105
- package/src/serve/assets/training-inference-map.html +0 -575
- package/src/serve/audio-routes.ts +0 -323
- package/src/serve/aux-routes.ts +0 -132
- package/src/serve/batch-scheduler.ts +0 -8
- package/src/serve/checkpoint-identity.ts +0 -64
- package/src/serve/generation-gateway.ts +0 -537
- package/src/serve/isolate.ts +0 -609
- package/src/serve/kv-budget.ts +0 -14
- package/src/serve/lab-routes.ts +0 -341
- package/src/serve/lane-registry.ts +0 -45
- package/src/serve/model-admin-routes.ts +0 -155
- package/src/serve/model-binding.ts +0 -49
- package/src/serve/model-host.ts +0 -511
- package/src/serve/parent-application.ts +0 -56
- package/src/serve/prompt-response-trace.ts +0 -161
- package/src/serve/response-lease.ts +0 -24
- package/src/serve/session-search.ts +0 -326
- package/src/serve/static-routes.ts +0 -101
- package/src/serve/transcription-server.ts +0 -64
- package/src/serve/transcription-service.ts +0 -502
- package/src/server.ts +0 -1282
- package/src/spec/assistant-rows.ts +0 -93
- package/src/spec/assistant-source.ts +0 -126
- package/src/spec/cache-identity.ts +0 -22
- package/src/spec/deepspec-rows.ts +0 -29
- package/src/spec/deepspec-source.ts +0 -145
- package/src/spec/dflash-source.ts +0 -149
- package/src/spec/drafter.ts +0 -304
- package/src/spec/dspark/ab-stats.ts +0 -136
- package/src/spec/dspark/calibration.ts +0 -112
- package/src/spec/dspark/data-dflash.ts +0 -156
- package/src/spec/dspark/data.ts +0 -201
- package/src/spec/dspark/deepspec-module.ts +0 -776
- package/src/spec/dspark/generate-dflash.ts +0 -245
- package/src/spec/dspark/generate.ts +0 -290
- package/src/spec/dspark/loader.ts +0 -23
- package/src/spec/dspark/loss.ts +0 -213
- package/src/spec/dspark/module-dflash.ts +0 -560
- package/src/spec/dspark/module.ts +0 -540
- package/src/spec/dspark/quantize-drafter.ts +0 -91
- package/src/spec/dspark/sample.ts +0 -112
- package/src/spec/generate.ts +0 -276
- package/src/spec/glm52-mtp-graph.ts +0 -29
- package/src/spec/glm52-mtp-rows.ts +0 -132
- package/src/spec/glm52-mtp-source.ts +0 -318
- package/src/spec/glm52-mtp-state.ts +0 -33
- package/src/spec/ngram-source.ts +0 -226
- package/src/spec/projected-draft-rows.ts +0 -73
- package/src/spec/qwen-mtp-module.ts +0 -171
- package/src/spec/qwen-mtp-rows.ts +0 -230
- package/src/spec/qwen-mtp-source.ts +0 -386
- package/src/spec/qwen-mtp-state.ts +0 -30
- package/src/spec/serve-loop.ts +0 -587
- package/src/spec/source.ts +0 -228
- package/src/spec/two-model-rows.ts +0 -62
- package/src/spec/two-model.ts +0 -113
- package/src/ssd-cache.ts +0 -431
- package/src/ssd-durability.ts +0 -209
- package/src/storage/host-buffer.ts +0 -24
- package/src/storage/kv-writer.ts +0 -83
- package/src/storage/kv-writer.worker.js +0 -264
- package/src/storage/retention-policy.ts +0 -37
- package/src/tiered-prompt-cache.ts +0 -45
- package/src/tokenizer.ts +0 -335
- package/src/tool-approvals.ts +0 -131
- package/src/tool-call.ts +0 -695
- package/src/train/dataset.ts +0 -484
- package/src/train/diffusion-lora.ts +0 -216
- package/src/train/export.ts +0 -32
- package/src/train/flash-cce.ts +0 -1234
- package/src/train/forward.ts +0 -266
- package/src/train/fuse.ts +0 -243
- package/src/train/index.ts +0 -46
- package/src/train/job.ts +0 -205
- package/src/train/lora-params.ts +0 -254
- package/src/train/loss.ts +0 -1591
- package/src/train/merge.ts +0 -195
- package/src/train/optimizer.ts +0 -183
- package/src/train/prefix-shared.ts +0 -490
- package/src/train/rank.ts +0 -137
- package/src/train/segmented.ts +0 -1828
- package/src/train/steel-qmm-header.ts +0 -1461
- package/src/train/trainer.ts +0 -1221
- package/src/train/watch.ts +0 -542
- package/src/vision/diffusion-vision.ts +0 -620
- package/src/vision/embedder.ts +0 -176
- package/src/vision/preprocess.ts +0 -247
- package/src/vision/prompt.ts +0 -409
- package/src/vision/qwen3vl-preprocess.ts +0 -409
- package/src/vision/qwen3vl-prompt.ts +0 -233
- package/src/vision/qwen3vl-tower.ts +0 -566
- package/src/vision/siglip.ts +0 -607
- package/src/vision/video-frames.ts +0 -148
- package/src/web/app.html +0 -2155
- package/src/web/app.js +0 -5863
- package/src/web/skills/memory/SKILL.md +0 -120
- package/src/web/skills/web-research/SKILL.md +0 -46
- package/src/web/skills.ts +0 -97
- package/src/web/src/adapters-panel.ts +0 -288
- package/src/web/src/assistant.ts +0 -451
- package/src/web/src/chat.ts +0 -1060
- package/src/web/src/composer.ts +0 -962
- package/src/web/src/dataset.ts +0 -132
- package/src/web/src/finetune.ts +0 -267
- package/src/web/src/hub.ts +0 -428
- package/src/web/src/main.ts +0 -98
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +0 -572
- package/src/web/src/model-picker.ts +0 -159
- package/src/web/src/palette.ts +0 -346
- package/src/web/src/protocol.ts +0 -26
- package/src/web/src/quantize.ts +0 -167
- package/src/web/src/rag.ts +0 -274
- package/src/web/src/sessions.ts +0 -469
- package/src/web/src/shell.ts +0 -804
- package/src/web/src/ui-catalog.ts +0 -78
- package/src/web/src/voice.ts +0 -141
- package/src/web/vendor/README.md +0 -61
- package/src/web-assets.ts +0 -47
- package/src/web-tools.ts +0 -478
- package/src/weights.ts +0 -255
- /package/src/{serve → server}/completion-sink.ts +0 -0
- /package/src/{serve → server}/generated-token-history.ts +0 -0
- /package/src/web/{icon.svg → public/icon.svg} +0 -0
- /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
- /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// resolved generation settings, ready for a model. InferenceStage first
|
|
3
3
|
// ADMITS it (memory ceiling → reject or clamp; the resolved settings become
|
|
4
4
|
// an opaque, single-use plan) and then RUNS it — the gateway routes it to
|
|
5
|
-
//
|
|
5
|
+
// continuous scheduling with the selected generation method and the executor returns
|
|
6
6
|
// the terminal InferenceResult. Every completion surface — chat, raw text,
|
|
7
7
|
// Anthropic, Responses — produces one of these and reads one of those.
|
|
8
8
|
import {
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
type PreparedCompletion,
|
|
15
15
|
} from "./completion-executor";
|
|
16
16
|
import { createTimedFlowControl } from "./completion-sink";
|
|
17
|
-
import { runtimeValue } from "
|
|
17
|
+
import { runtimeValue } from "@mlx-bun/inference/runtime/config";
|
|
18
18
|
import { RequestError, type RunControl } from "./pipeline";
|
|
19
19
|
|
|
20
20
|
export interface InferenceRequest
|
|
@@ -37,7 +37,7 @@ export interface AdmittedRequest {
|
|
|
37
37
|
export type InferenceResult = CompletionSummary;
|
|
38
38
|
|
|
39
39
|
export class InferenceStage {
|
|
40
|
-
constructor(private readonly executor: Pick<CompletionExecutor, "execute">) {}
|
|
40
|
+
constructor(private readonly executor: Pick<CompletionExecutor, "place" | "execute">) {}
|
|
41
41
|
|
|
42
42
|
/** Admission: throws RequestError(400, memory_admission) when the prompt
|
|
43
43
|
* leaves no room to generate inside the safe context; otherwise clamps
|
|
@@ -49,8 +49,7 @@ export class InferenceStage {
|
|
|
49
49
|
...preparation,
|
|
50
50
|
...(stream
|
|
51
51
|
? {
|
|
52
|
-
createFlowControl: (
|
|
53
|
-
createTimedFlowControl(mechanism === "serial"),
|
|
52
|
+
createFlowControl: () => createTimedFlowControl(false),
|
|
54
53
|
}
|
|
55
54
|
: {}),
|
|
56
55
|
onPlacement: ({ mechanism, shape }) => {
|
|
@@ -61,6 +60,7 @@ export class InferenceStage {
|
|
|
61
60
|
);
|
|
62
61
|
},
|
|
63
62
|
});
|
|
63
|
+
this.executor.place(prepared);
|
|
64
64
|
return { requestId: request.requestId, warnings, prepared };
|
|
65
65
|
} catch (error) {
|
|
66
66
|
if (!(error instanceof CompletionRejected)) throw error;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { JobStore } from "../jobs/db";
|
|
2
|
+
import { streamJobResponse } from "../jobs/sse";
|
|
3
|
+
|
|
4
|
+
export function createJobRoutes(host: { ensureStore(): JobStore; signal: AbortSignal }) {
|
|
5
|
+
return { async handle(request: Request): Promise<Response | null> {
|
|
6
|
+
const url = new URL(request.url);
|
|
7
|
+
if (request.method !== "GET") return null;
|
|
8
|
+
if (url.pathname === "/api/jobs") {
|
|
9
|
+
const limit = Number(url.searchParams.get("limit") ?? "50");
|
|
10
|
+
const kind = url.searchParams.get("kind") ?? undefined;
|
|
11
|
+
return Response.json({ ok: true, jobs: host.ensureStore().recent(limit, kind) });
|
|
12
|
+
}
|
|
13
|
+
const match = url.pathname.match(/^\/api\/jobs\/([^/]+?)(\/stream)?$/);
|
|
14
|
+
if (!match) return null;
|
|
15
|
+
const store = host.ensureStore(), jobId = match[1]!;
|
|
16
|
+
if (match[2]) return streamJobResponse(store, jobId, AbortSignal.any([host.signal, request.signal]));
|
|
17
|
+
const job = store.get(jobId);
|
|
18
|
+
return job ? Response.json({ ok: true, job }) : Response.json({ ok: false, error: "job not found" }, { status: 404 });
|
|
19
|
+
} };
|
|
20
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { normalizeMessages, type ChatRequestParams } from "./chat-request";
|
|
2
|
+
import { requireChatTemplate, type LoadedModelContext as ModelContext } from "../engine/model-host";
|
|
3
|
+
import { mediaRefusalMessage } from "../engine/media-preparation";
|
|
4
|
+
import { RequestError } from "./pipeline";
|
|
5
|
+
import type { RequestOwnership } from "./request-plan";
|
|
6
|
+
import type { RequestPrep } from "./request-prep";
|
|
7
|
+
import type { BuiltPrompt, PromptNativeWork } from "./prompt-contracts";
|
|
8
|
+
|
|
9
|
+
export async function buildModelPrompt(
|
|
10
|
+
ctx: ModelContext,
|
|
11
|
+
prep: RequestPrep,
|
|
12
|
+
body: ChatRequestParams,
|
|
13
|
+
tools: ChatRequestParams["tools"] | null,
|
|
14
|
+
_ownership: RequestOwnership,
|
|
15
|
+
nativeWork: PromptNativeWork = (work) => work(),
|
|
16
|
+
objects?: import("@mlx-bun/inference/contracts/portable").ObjectCache<import("@mlx-bun/inference/state").CheckpointAttachment[]>,
|
|
17
|
+
): Promise<BuiltPrompt> {
|
|
18
|
+
requireChatTemplate(ctx);
|
|
19
|
+
const toolList = tools ?? null;
|
|
20
|
+
const partOf = (types: string[]) => body.messages.some(
|
|
21
|
+
(m) => Array.isArray(m.content) &&
|
|
22
|
+
m.content.some((p: any) => types.includes(p.type)),
|
|
23
|
+
);
|
|
24
|
+
const hasImages = partOf(["image_url", "image"]);
|
|
25
|
+
// Same shapes extractAudio accepts: OpenAI-canonical input_audio plus
|
|
26
|
+
// optiq's audio / audio_url aliases (`02d723a:docs/design/generic-model-support.md` §6.6).
|
|
27
|
+
const hasAudio = partOf(["input_audio", "audio", "audio_url"]);
|
|
28
|
+
// Video content parts (video_url / video with base64 data) — accepted only
|
|
29
|
+
// by graphs whose media route declares video (decoded to sampled frames via
|
|
30
|
+
// the AVFoundation sidecar, packages/inference/src/input/vision/video-frames.ts).
|
|
31
|
+
const hasVideos = partOf(["video_url", "video"]);
|
|
32
|
+
|
|
33
|
+
// Video never composes with audio — one early guard so no downstream
|
|
34
|
+
// branch can silently drop a video part.
|
|
35
|
+
if (hasVideos && (hasAudio || !ctx.media.video))
|
|
36
|
+
throw new RequestError(400, mediaRefusalMessage(hasAudio ? "video-with-audio" : "video-unsupported", ctx.modelId));
|
|
37
|
+
if (hasImages || hasAudio || hasVideos) {
|
|
38
|
+
const prepared = await ctx.media.prepare({
|
|
39
|
+
messages: normalizeMessages(body.messages),
|
|
40
|
+
parts: { images: hasImages, audio: hasAudio, videos: hasVideos },
|
|
41
|
+
tools: toolList,
|
|
42
|
+
templateOptions: () => prep.templateOptionsFor(body, toolList),
|
|
43
|
+
nativeWork,
|
|
44
|
+
objects,
|
|
45
|
+
});
|
|
46
|
+
if ("rejected" in prepared) throw new RequestError(400, mediaRefusalMessage(prepared.rejected, ctx.modelId));
|
|
47
|
+
return {
|
|
48
|
+
promptIds: prepared.promptIds,
|
|
49
|
+
vision: prepared.vision,
|
|
50
|
+
startInThinking: false,
|
|
51
|
+
probeStableLen: false,
|
|
52
|
+
diffusionPixels: prepared.pixels ?? null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const text = prep.promptIdsFor(body, toolList);
|
|
56
|
+
return {
|
|
57
|
+
promptIds: text.ids,
|
|
58
|
+
vision: undefined,
|
|
59
|
+
startInThinking: text.startInThinking,
|
|
60
|
+
probeStableLen: true,
|
|
61
|
+
diffusionPixels: null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TaskCompletionClient as MemoryCompletionClient } from "@mlx-bun/app-core";
|
|
2
|
+
import type { WorkerMemoryCall } from "./worker-routes";
|
|
3
|
+
export interface MemoryTarget { worker: { fetch(url: string, init?: RequestInit): Promise<Response> }; snapshot: string }
|
|
4
|
+
/** The isolated parent's client for one synthesis run: each `complete` or
|
|
5
|
+
* `completeBatch` is one POST of the rows and the selected snapshot to the
|
|
6
|
+
* private `/admin/memory/complete` of the worker `select` returns (the
|
|
7
|
+
* model worker), answered with the ordered raw outputs. A failed
|
|
8
|
+
* selection fails the call as it is. The worker's task model runs the call
|
|
9
|
+
* under the worker's own execution lease; none is taken here (one
|
|
10
|
+
* would wait on that worker). The run's signal aborts the request, and the
|
|
11
|
+
* worker then aborts and joins every row. A call is never retried: a worker
|
|
12
|
+
* that stops mid-call fails it rather than replay a POST. */
|
|
13
|
+
export function createWorkerMemoryClient(select: (signal: AbortSignal) => Promise<MemoryTarget>, signal: AbortSignal): MemoryCompletionClient {
|
|
14
|
+
const send = async (call: Omit<WorkerMemoryCall, "snapshot">): Promise<string[]> => {
|
|
15
|
+
signal.throwIfAborted();
|
|
16
|
+
const { worker, snapshot } = await select(signal);
|
|
17
|
+
const body: WorkerMemoryCall = { call: call.call, snapshot, requests: call.requests };
|
|
18
|
+
let response: Response;
|
|
19
|
+
try {
|
|
20
|
+
response = await worker.fetch("http://engine/admin/memory/complete", { method: "POST",
|
|
21
|
+
headers: { "content-type": "application/json" }, body: JSON.stringify(body), signal });
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (signal.aborted) throw signal.reason;
|
|
24
|
+
throw new Error(`memory: the model worker did not complete the task model ${body.call} (${error instanceof Error ? error.message : String(error)}); it is not retried`);
|
|
25
|
+
}
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const detail = await response.text().catch(() => "");
|
|
28
|
+
let message = detail;
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(detail) as { error?: { message?: unknown } } | null;
|
|
31
|
+
if (typeof parsed?.error?.message === "string") message = parsed.error.message;
|
|
32
|
+
} catch { /* not JSON: the text itself */ }
|
|
33
|
+
throw new Error(`memory: the model worker's task model ${body.call} failed (${response.status}): ${message.slice(0, 400)}`);
|
|
34
|
+
}
|
|
35
|
+
const { outputs } = await response.json() as { outputs?: unknown };
|
|
36
|
+
signal.throwIfAborted();
|
|
37
|
+
if (!Array.isArray(outputs) || outputs.length !== body.requests.length || !outputs.every(output => typeof output === "string"))
|
|
38
|
+
throw new Error(`memory: the model worker's task model ${body.call} answered no ordered outputs`);
|
|
39
|
+
return outputs as string[];
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
complete: async request => (await send({ call: "complete", requests: [request] }))[0]!,
|
|
43
|
+
// An empty batch is answered here, as the task model answers it: without a call.
|
|
44
|
+
completeBatch: async requests => requests.length ? send({ call: "completeBatch", requests: [...requests] }) : [],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// Routes model-scoped requests to the model that should answer them. The
|
|
2
|
+
// inference endpoints carry a `model` id in the JSON body: an exact local id
|
|
3
|
+
// (the ones `/v1/models` lists) leases that model from the model host, loading
|
|
4
|
+
// it (and draining another) when it is not resident; anything else (no id, an
|
|
5
|
+
// alias such as Pi's `local`, a name another server would know) is answered by
|
|
6
|
+
// the current model. Every other model-scoped path (`/stats`, `/fit`, cache
|
|
7
|
+
// administration) belongs to the current model too. The response
|
|
8
|
+
// body holds the lease until it ends, so a model is never released under a
|
|
9
|
+
// stream. `/v1/models` lists every local model with which are
|
|
10
|
+
// resident and which is current; `/stats` adds the host's residency.
|
|
11
|
+
import type { ResidencyHost, ResidentUnit } from "../residency/model-residency";
|
|
12
|
+
import { ResidencyError } from "../residency/model-residency";
|
|
13
|
+
import { errorResponse } from "./http";
|
|
14
|
+
import { RequestError } from "./pipeline";
|
|
15
|
+
import { readDecisionBody } from "./decision-body";
|
|
16
|
+
|
|
17
|
+
/** Inference endpoints whose JSON body carries the routing `model` field. */
|
|
18
|
+
export const MODEL_ROUTED = new Set(["/v1/chat/completions", "/v1/completions", "/v1/messages", "/v1/responses", "/v1/embeddings", "/v1/decisions", "/v1/systemone"]);
|
|
19
|
+
|
|
20
|
+
const decoder = new TextDecoder();
|
|
21
|
+
/** The routing key from a buffered JSON body. A body that is not a JSON object with a string `model` names no model; the current model answers it and its own parser rejects a malformed body. */
|
|
22
|
+
export function modelField(body: Uint8Array | string): string | undefined {
|
|
23
|
+
try {
|
|
24
|
+
const value = JSON.parse(typeof body === "string" ? body : decoder.decode(body)) as { model?: unknown } | null;
|
|
25
|
+
return value && typeof value === "object" && typeof value.model === "string" ? value.model : undefined;
|
|
26
|
+
} catch { return undefined; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A unit whose model-scoped routes this router can delegate to. */
|
|
30
|
+
export interface RoutedUnit extends ResidentUnit {
|
|
31
|
+
readonly routes: { handle(request: Request): Promise<Response | null> };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ModelRoutesOptions<U extends RoutedUnit> {
|
|
35
|
+
host: Pick<ResidencyHost<U>, "acquire" | "peek" | "resident" | "policy">;
|
|
36
|
+
/** The model that answers requests naming no local model. */
|
|
37
|
+
current(): string;
|
|
38
|
+
/** Whether `id` is an exact id this host can serve (the ids `/v1/models` lists), resident or not. */
|
|
39
|
+
serves(id: string): Promise<boolean>;
|
|
40
|
+
/** The companion model (Whisper) that answers this path, when it is one of its own; the router leases it like any model. */
|
|
41
|
+
companion?(pathname: string): Promise<string | undefined>;
|
|
42
|
+
/** The configured transcription companion, including before its worker starts. */
|
|
43
|
+
transcription?(): Promise<string | undefined>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A response that keeps its lease until the body has ended, been cancelled, or failed. */
|
|
47
|
+
function holding(response: Response, release: () => void): Response {
|
|
48
|
+
if (!response.body) { release(); return response; }
|
|
49
|
+
const reader = response.body.getReader();
|
|
50
|
+
let done = false;
|
|
51
|
+
const finish = () => { if (!done) { done = true; release(); } };
|
|
52
|
+
const body = new ReadableStream<Uint8Array>({
|
|
53
|
+
async pull(controller) {
|
|
54
|
+
try {
|
|
55
|
+
const chunk = await reader.read();
|
|
56
|
+
if (chunk.done) { finish(); controller.close(); } else controller.enqueue(chunk.value);
|
|
57
|
+
} catch (error) { finish(); controller.error(error); }
|
|
58
|
+
},
|
|
59
|
+
cancel(reason) { finish(); return reader.cancel(reason).catch(() => {}); },
|
|
60
|
+
});
|
|
61
|
+
return new Response(body, { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The typed answer for a model that could not be leased. */
|
|
65
|
+
function unavailable(error: unknown, context: string, signal: AbortSignal): Response {
|
|
66
|
+
if (error instanceof ResidencyError) {
|
|
67
|
+
const status = error.code === "load-failed" ? 502 : error.code === "closed" ? 503 : 400;
|
|
68
|
+
const type = error.code === "load-failed" ? "model_load_failed" : error.code === "closed" ? "server_closing" : "invalid_request_error";
|
|
69
|
+
return errorResponse(new RequestError(status, error.message, { message: error.message, type, code: error.code }), context, undefined, signal);
|
|
70
|
+
}
|
|
71
|
+
return errorResponse(error, context, undefined, signal);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function createModelRoutes<U extends RoutedUnit>(options: ModelRoutesOptions<U>) {
|
|
75
|
+
const { host } = options;
|
|
76
|
+
/** The unit that answers model-scoped requests that name no model, without loading anything. */
|
|
77
|
+
const active = (): U | undefined => {
|
|
78
|
+
const current = host.peek(options.current());
|
|
79
|
+
if (current) return current;
|
|
80
|
+
const latest = host.resident().filter(model => model.state === "ready" && model.role === "primary").sort((a, b) => b.lastUsedAt - a.lastUsedAt)[0];
|
|
81
|
+
return latest ? host.peek(latest.id) : undefined;
|
|
82
|
+
};
|
|
83
|
+
const listing = async (request: Request, path: string): Promise<Response | null> => {
|
|
84
|
+
const unit = active();
|
|
85
|
+
return unit ? await unit.routes.handle(new Request(new URL(path, request.url), { headers: request.headers, signal: request.signal })) : null;
|
|
86
|
+
};
|
|
87
|
+
const residentIds = () => new Set(host.resident().filter(model => model.state === "ready").map(model => model.id));
|
|
88
|
+
|
|
89
|
+
/** Lease `id`, run `serve` on its unit, and hold the lease until the response has been delivered. */
|
|
90
|
+
async function leased(id: string, request: Request, serve: (unit: U, lease: { release(): void }) => Promise<Response | null>): Promise<Response | null> {
|
|
91
|
+
const path = new URL(request.url).pathname;
|
|
92
|
+
let lease;
|
|
93
|
+
try { lease = await host.acquire(id, { signal: request.signal }); }
|
|
94
|
+
catch (error) { return unavailable(error, path, request.signal); }
|
|
95
|
+
try {
|
|
96
|
+
const response = await serve(lease.unit, lease);
|
|
97
|
+
return response ? holding(response, () => lease.release()) : (lease.release(), null);
|
|
98
|
+
} catch (error) { lease.release(); return errorResponse(error, path, undefined, request.signal); }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
async handle(request: Request): Promise<Response | null> {
|
|
103
|
+
const url = new URL(request.url);
|
|
104
|
+
const { pathname } = url;
|
|
105
|
+
if (request.method === "POST" && MODEL_ROUTED.has(pathname)) {
|
|
106
|
+
// The body is read once here and handed on as bytes; the model's own parser answers a malformed one.
|
|
107
|
+
const headers = new Headers(request.headers);
|
|
108
|
+
const body = new Uint8Array(await request.arrayBuffer());
|
|
109
|
+
let named = modelField(body);
|
|
110
|
+
if (pathname === "/v1/systemone" || pathname === "/v1/decisions") {
|
|
111
|
+
try {
|
|
112
|
+
const parsed = await readDecisionBody(new Request(request.url, { method: "POST", headers, body }));
|
|
113
|
+
if (parsed && typeof parsed === "object" && typeof (parsed as { model?: unknown }).model === "string")
|
|
114
|
+
named = (parsed as { model: string }).model;
|
|
115
|
+
} catch { /* The model's parser supplies the protocol's error. */ }
|
|
116
|
+
}
|
|
117
|
+
const id = named !== undefined && await options.serves(named) ? named : options.current();
|
|
118
|
+
return leased(id, request, unit => unit.operationsFor().generate!(new Request(request.url, {
|
|
119
|
+
method: request.method, headers, body, signal: request.signal })));
|
|
120
|
+
}
|
|
121
|
+
const listsModels = pathname === "/v1/models" || pathname.startsWith("/v1/models/");
|
|
122
|
+
if (request.method === "GET" && listsModels) {
|
|
123
|
+
// `/v1/models/{id}` lists that one model; `/v1/models` lists them all.
|
|
124
|
+
const filter = decodeURIComponent(pathname.slice("/v1/models/".length)) || null;
|
|
125
|
+
const own = filter === null ? undefined : options.host.peek(filter);
|
|
126
|
+
if (own && !own.operations.includes("transcribe")) return own.routes.handle(request);
|
|
127
|
+
const base = await listing(request, "/v1/models");
|
|
128
|
+
if (!base) return null;
|
|
129
|
+
// A unit that cannot answer (a worker that is down) is the answer, not something to decorate.
|
|
130
|
+
if (!base.ok) return base;
|
|
131
|
+
const list = await base.json() as { object: string; data: Record<string, unknown>[] };
|
|
132
|
+
// Chat workers have no companion of their own. Discovery belongs to this host's
|
|
133
|
+
// resolved companion, not whichever Whisper snapshot a worker discovers locally.
|
|
134
|
+
if (options.transcription) {
|
|
135
|
+
const id = await options.transcription();
|
|
136
|
+
list.data = list.data.filter(entry => entry.transcription !== true && entry.id !== id);
|
|
137
|
+
for (const entry of list.data) {
|
|
138
|
+
if (entry.id === options.current() && entry.capabilities)
|
|
139
|
+
entry.capabilities = { ...(entry.capabilities as Record<string, unknown>), transcription: id !== undefined };
|
|
140
|
+
}
|
|
141
|
+
if (id !== undefined) list.data.push({ id, object: "model", owned_by: "mlx-bun", transcription: true,
|
|
142
|
+
resident: false, capabilities: { transcription: true, translation: true, chat_completions: false } });
|
|
143
|
+
}
|
|
144
|
+
const resident = residentIds(), current = options.current();
|
|
145
|
+
// Every resident model answers for itself; the rest are listed as the registry knows them.
|
|
146
|
+
const full = await Promise.all([...resident].map(async id => {
|
|
147
|
+
const unit = host.peek(id);
|
|
148
|
+
const companion = unit?.operations.includes("transcribe");
|
|
149
|
+
const path = companion ? "/v1/models" : `/v1/models/${encodeURIComponent(id)}`;
|
|
150
|
+
const entry = unit ? await unit.routes.handle(new Request(new URL(path, request.url))) : null;
|
|
151
|
+
const rows = entry?.ok ? ((await entry.json()) as { data?: Record<string, unknown>[] }).data : undefined;
|
|
152
|
+
const row = companion ? rows?.find(row => row.transcription === true) : rows?.[0];
|
|
153
|
+
return [id, row ? { ...row, id } : undefined] as const;
|
|
154
|
+
}));
|
|
155
|
+
const bySelf = new Map(full.flatMap(([id, entry]) => entry ? [[id, entry] as const] : []));
|
|
156
|
+
const data: Record<string, unknown>[] = list.data.map(entry => {
|
|
157
|
+
const id = entry.id as string, row = bySelf.get(id) ?? entry;
|
|
158
|
+
// A companion's own row says whether it is loaded; the host knows when it holds it resident.
|
|
159
|
+
return entry.transcription === true ? row : { ...row, resident: resident.has(id), current: id === current };
|
|
160
|
+
});
|
|
161
|
+
const selected = filter === null ? data : data.filter(entry => entry.id === filter);
|
|
162
|
+
return Response.json({ ...list, data: selected,
|
|
163
|
+
// Jev clients use `models`, OpenAI clients use `data`; one discovery
|
|
164
|
+
// response can carry both views without changing either contract.
|
|
165
|
+
models: selected.filter(entry => entry.transcription !== true).map(entry => ({ name: entry.id,
|
|
166
|
+
description: "Local mlx-bun inference model", release_date: "" })) });
|
|
167
|
+
}
|
|
168
|
+
if (request.method === "GET" && pathname === "/stats") {
|
|
169
|
+
const unit = active();
|
|
170
|
+
const base = unit ? await unit.routes.handle(request) : null;
|
|
171
|
+
if (!base) return null;
|
|
172
|
+
if (!base.ok) return base;
|
|
173
|
+
const stats = await base.json() as Record<string, unknown>;
|
|
174
|
+
const models = host.resident();
|
|
175
|
+
return Response.json({ ...stats, models: { current: options.current(), budget_bytes: host.policy.budgetBytes,
|
|
176
|
+
resident_bytes: models.reduce((sum, model) => sum + model.bytes, 0),
|
|
177
|
+
resident: models.map(model => ({ id: model.id, state: model.state, bytes: model.bytes, pinned: model.pinned, leases: model.leases,
|
|
178
|
+
last_used_at: model.lastUsedAt })) } });
|
|
179
|
+
}
|
|
180
|
+
if (request.method === "GET" && pathname === "/health")
|
|
181
|
+
return await active()?.routes.handle(request) ?? Response.json({ status: "ok" });
|
|
182
|
+
const companion = await options.companion?.(pathname);
|
|
183
|
+
if (companion !== undefined) return leased(companion, request, served => served.routes.handle(request));
|
|
184
|
+
// Everything else model-scoped is the current model's, when one is resident: a read answers as it is, and
|
|
185
|
+
// a change (flushing a cache) holds the model resident until it is done.
|
|
186
|
+
const unit = active();
|
|
187
|
+
if (!unit) return null;
|
|
188
|
+
if (["GET", "HEAD"].includes(request.method)) return unit.routes.handle(request);
|
|
189
|
+
return leased(unit.id, request, served => served.routes.handle(request));
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// OpenAI wire formats: InferenceResult → chat.completion / text_completion
|
|
2
2
|
// JSON, and the SSE chunk protocols for both. Anthropic and Responses have
|
|
3
|
-
// their own (src/anthropic.ts, src/responses.ts);
|
|
3
|
+
// their own (apps/mlx-bun/src/server/anthropic.ts, apps/mlx-bun/src/server/responses.ts);
|
|
4
|
+
// all three implement the
|
|
4
5
|
// same CompletionStreamProtocol so the HTTP writer (http.ts) is shared.
|
|
5
6
|
import type { CompletionSummary, CompletionUsage } from "./completion-executor";
|
|
6
7
|
import type { CompletionEvent, CompletionStreamProtocol } from "./completion-sink";
|
|
@@ -22,7 +23,7 @@ export const openAiUsage = (usage: CompletionUsage) => ({
|
|
|
22
23
|
...(usage.fill ? { fill: usage.fill } : {}),
|
|
23
24
|
});
|
|
24
25
|
|
|
25
|
-
/** Terminal usage: the wire block plus mlx-bun's `lane` (batch
|
|
26
|
+
/** Terminal usage: the wire block plus mlx-bun's `lane` (always `batched`: one request is a batch of one). */
|
|
26
27
|
export const openAiSummaryUsage = (summary: CompletionSummary) => ({
|
|
27
28
|
...openAiUsage(summary.usage),
|
|
28
29
|
lane: summary.lane,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// A stage knows only the interface it reads and the one it writes.
|
|
9
9
|
import type { CompletionEvent } from "./completion-sink";
|
|
10
10
|
import type { CompletionUsage } from "./completion-executor";
|
|
11
|
-
import type { PromptResponseTrace } from "
|
|
11
|
+
import type { PromptResponseTrace } from "@mlx-bun/inference/runtime/trace";
|
|
12
12
|
|
|
13
13
|
/** A request a stage refuses. `status` is the HTTP status the wire adapter
|
|
14
14
|
* answers with; `body` the protocol-neutral error object (adapters reshape
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LoadedModelContext as ModelContext } from "../engine/model-host";
|
|
2
|
+
import type { ModelPromptBuilder } from "./prompt-contracts";
|
|
3
|
+
export function modelPromptBuilder(context: ModelContext): ModelPromptBuilder {
|
|
4
|
+
return async (body, tools, ownership, prep, nativeWork, objects) => {
|
|
5
|
+
const media = body.messages.some(message => Array.isArray(message.content) && message.content.some(part => part.type !== "text"));
|
|
6
|
+
if (media) {
|
|
7
|
+
const { buildModelPrompt } = await import("./media-prompt");
|
|
8
|
+
return buildModelPrompt(context, prep, body, tools, ownership, nativeWork, objects);
|
|
9
|
+
}
|
|
10
|
+
const text = prep.promptIdsFor(body, tools ?? null);
|
|
11
|
+
return { promptIds: text.ids, vision: undefined, startInThinking: text.startInThinking, probeStableLen: true, diffusionPixels: null };
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MlxArray } from "@mlx-bun/mlx/array";
|
|
2
|
+
import type { ObjectCache } from "@mlx-bun/inference/contracts/portable";
|
|
3
|
+
import type { CheckpointAttachment } from "@mlx-bun/inference/state";
|
|
4
|
+
import type { Vision } from "../engine/completion";
|
|
5
|
+
import type { ChatRequestParams } from "./chat-request";
|
|
6
|
+
import type { RequestOwnership } from "./request-plan";
|
|
7
|
+
import type { RequestPrep } from "./request-prep";
|
|
8
|
+
export interface BuiltPrompt {
|
|
9
|
+
promptIds: number[]; vision: Vision | undefined; startInThinking: boolean;
|
|
10
|
+
probeStableLen: boolean; diffusionPixels: MlxArray | null;
|
|
11
|
+
}
|
|
12
|
+
export type PromptNativeWork = <T>(work: () => Promise<T>) => Promise<T>;
|
|
13
|
+
export interface ModelPromptBuilder {
|
|
14
|
+
(body: ChatRequestParams, tools: ChatRequestParams["tools"] | null,
|
|
15
|
+
ownership: RequestOwnership, prep: RequestPrep, nativeWork?: PromptNativeWork,
|
|
16
|
+
objects?: ObjectCache<CheckpointAttachment[]>): Promise<BuiltPrompt>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// The isolated host's route group: what the parent owns, and the way a request
|
|
2
|
+
// reaches a model worker. Each resident model runs in its own worker process;
|
|
3
|
+
// the model router (model-routes.ts) picks the model a request names and leases
|
|
4
|
+
// its worker, and `forwardToWorker` sends the request over the worker's Unix
|
|
5
|
+
// socket with the body streaming through (hop-by-hop headers stripped, a client
|
|
6
|
+
// abort aborting the proxied fetch so the worker sees the disconnect). The
|
|
7
|
+
// parent answers `/engine`, `/health` and `/stats` (with the workers'
|
|
8
|
+
// contribution) and `/v1/responses` through the history client. The workers' private admin paths never forward.
|
|
9
|
+
import type { WorkerMemory } from "../jobs/worker-memory";
|
|
10
|
+
import { describeExit, EngineUnavailableError, type WorkerSupervisor, type WorkerSupervisorState } from "../jobs/worker-supervisor";
|
|
11
|
+
import type { createResponsesClient } from "./responses-client";
|
|
12
|
+
|
|
13
|
+
const HOP_BY_HOP = new Set([
|
|
14
|
+
"connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
|
|
15
|
+
"te", "trailer", "transfer-encoding", "upgrade",
|
|
16
|
+
]);
|
|
17
|
+
/** Never forwarded to a worker: its private socket surface (which only the
|
|
18
|
+
* parent itself calls; unmatched here, the listener answers 404) and the
|
|
19
|
+
* parent's own synthesis route (served by its earlier group). */
|
|
20
|
+
const PARENT_ONLY = new Set(["/admin/lease", "/admin/drain", "/admin/memory/complete", "/admin/events", "/admin/adapters", "/v1/memory/synthesize"]);
|
|
21
|
+
|
|
22
|
+
const encoder = new TextEncoder();
|
|
23
|
+
/** A copy without the hop-by-hop headers and the ones `Connection` names
|
|
24
|
+
* (also used by the library host, `cli/library-host.ts`). */
|
|
25
|
+
export const stripHopByHop = (headers: Headers) => {
|
|
26
|
+
const excluded = new Set(HOP_BY_HOP);
|
|
27
|
+
for (const name of (headers.get("connection") ?? "").split(",")) excluded.add(name.trim().toLowerCase());
|
|
28
|
+
const out = new Headers();
|
|
29
|
+
headers.forEach((value, key) => { if (!excluded.has(key.toLowerCase())) out.set(key, value); });
|
|
30
|
+
return out;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** The SSE error frame each protocol's own error path emits, so a client
|
|
34
|
+
* parser ends the stream with an error instead of a silent truncation. */
|
|
35
|
+
export function unavailableFrame(pathname: string, message: string): string {
|
|
36
|
+
if (pathname === "/v1/messages")
|
|
37
|
+
return `event: error\ndata: ${JSON.stringify({ type: "error", error: { type: "api_error", message } })}\n\n`;
|
|
38
|
+
if (pathname === "/v1/responses")
|
|
39
|
+
return `event: error\ndata: ${JSON.stringify({ type: "error", code: "engine_unavailable", message, param: null })}\n\n`;
|
|
40
|
+
return `data: ${JSON.stringify({ error: { message, type: "engine_unavailable" } })}\n\n`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Pass an event stream through unchanged; when the worker dies under it,
|
|
44
|
+
* end with the protocol's error frame instead of dropping the connection. */
|
|
45
|
+
function guardEventStream(body: ReadableStream<Uint8Array>, failure: () => string): ReadableStream<Uint8Array> {
|
|
46
|
+
const reader = body.getReader();
|
|
47
|
+
return new ReadableStream<Uint8Array>({
|
|
48
|
+
async pull(controller) {
|
|
49
|
+
let item: Awaited<ReturnType<typeof reader.read>>;
|
|
50
|
+
try { item = await reader.read(); }
|
|
51
|
+
catch { controller.enqueue(encoder.encode(failure())); controller.close(); return; }
|
|
52
|
+
if (item.done) controller.close(); else controller.enqueue(item.value);
|
|
53
|
+
},
|
|
54
|
+
cancel(reason) { return reader.cancel(reason).catch(() => {}); },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface WorkerEngine {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly role: "primary" | "companion";
|
|
61
|
+
readonly supervisor: WorkerSupervisor;
|
|
62
|
+
/** The MLX memory the worker last reported; undefined before its first report and while it is down. */
|
|
63
|
+
measured?(): WorkerMemory | undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ProxyRoutesOptions {
|
|
67
|
+
/** The resident workers, and the id of the model requests that name none are answered by. */
|
|
68
|
+
workers(): readonly WorkerEngine[];
|
|
69
|
+
current(): string;
|
|
70
|
+
/** The model router: it leases the worker a request belongs to and forwards to it. */
|
|
71
|
+
models: { handle(request: Request): Promise<Response | null> };
|
|
72
|
+
responses: ReturnType<typeof createResponsesClient>;
|
|
73
|
+
/** The model the server started with. */
|
|
74
|
+
modelId: string;
|
|
75
|
+
startedAt: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** `memory` is the worker's own MLX reading (`active_bytes` and `cache_bytes` are what residency counts), null until it reports. */
|
|
79
|
+
export interface WorkerReport { id: string; role: "primary" | "companion"; pid: number | null; state: WorkerSupervisorState; restarts: number; socket: string;
|
|
80
|
+
memory: { active_bytes: number; cache_bytes: number; peak_bytes: number } | null }
|
|
81
|
+
|
|
82
|
+
/** The current model's worker fields, then every resident worker. `state` is `none` while no worker is resident. */
|
|
83
|
+
export interface EngineReport {
|
|
84
|
+
isolated: true;
|
|
85
|
+
state: WorkerSupervisorState | "none";
|
|
86
|
+
pid: number | null;
|
|
87
|
+
restarts: number | null;
|
|
88
|
+
socket: string | null;
|
|
89
|
+
model: string;
|
|
90
|
+
last_exit: { code: number | null; signal: string | null } | null;
|
|
91
|
+
response_store: { entries: number; bytes: number; max_bytes: number; ttl_ms: number };
|
|
92
|
+
workers: WorkerReport[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const unavailableMessage = (error: unknown) => {
|
|
96
|
+
if (error instanceof EngineUnavailableError) return `inference engine unavailable: ${error.message}${error.state === "restarting" ? " — retry shortly" : ""}`;
|
|
97
|
+
return "inference engine unavailable: the engine worker stopped mid-request; respawning — retry shortly";
|
|
98
|
+
};
|
|
99
|
+
/** The 502 a request gets when its worker cannot take it. */
|
|
100
|
+
export function workerUnavailable(error: unknown, worker: WorkerSupervisor): Response {
|
|
101
|
+
return Response.json({ error: { message: unavailableMessage(error), type: "engine_unavailable",
|
|
102
|
+
state: error instanceof EngineUnavailableError ? error.state : worker.state } }, { status: 502 });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The request goes to the worker as it came, its body streaming through. A worker that cannot take it, or dies under
|
|
106
|
+
* it, answers with the protocol's own unavailable error; nothing is retried. */
|
|
107
|
+
export async function forwardToWorker(worker: WorkerSupervisor, request: Request): Promise<Response> {
|
|
108
|
+
const { pathname } = new URL(request.url);
|
|
109
|
+
try {
|
|
110
|
+
request.signal.throwIfAborted();
|
|
111
|
+
const upstream = await worker.fetch(request.url, {
|
|
112
|
+
method: request.method, headers: stripHopByHop(request.headers), body: request.body,
|
|
113
|
+
signal: request.signal, redirect: "manual", duplex: "half",
|
|
114
|
+
});
|
|
115
|
+
const headers = stripHopByHop(upstream.headers);
|
|
116
|
+
const failure = () => {
|
|
117
|
+
const exit = worker.lastExit;
|
|
118
|
+
return `inference engine unavailable: the engine worker stopped while streaming this response${exit ? ` (${describeExit(exit)})` : ""}; it is being respawned`;
|
|
119
|
+
};
|
|
120
|
+
const stream = upstream.body && (upstream.headers.get("content-type") ?? "").startsWith("text/event-stream")
|
|
121
|
+
? guardEventStream(upstream.body, () => unavailableFrame(pathname, failure()))
|
|
122
|
+
: upstream.body;
|
|
123
|
+
return new Response(stream, { status: upstream.status, statusText: upstream.statusText, headers });
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (request.signal.aborted) return new Response(null, { status: 499 });
|
|
126
|
+
return workerUnavailable(error, worker);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function createProxyRoutes(options: ProxyRoutesOptions) {
|
|
131
|
+
const { responses } = options;
|
|
132
|
+
const currentWorker = () => options.workers().find(worker => worker.id === options.current() && worker.role === "primary")?.supervisor;
|
|
133
|
+
const report = (): EngineReport => {
|
|
134
|
+
const engine = currentWorker();
|
|
135
|
+
return {
|
|
136
|
+
isolated: true, state: engine?.state ?? "none", pid: engine?.pid ?? null, restarts: engine?.restarts ?? null,
|
|
137
|
+
socket: engine?.socketPath ?? null, model: options.current(), last_exit: engine?.lastExit ?? null, response_store: responses.stats,
|
|
138
|
+
workers: options.workers().map(({ id, role, supervisor, measured }) => {
|
|
139
|
+
const memory = measured?.();
|
|
140
|
+
return { id, role, pid: supervisor.pid, state: supervisor.state, restarts: supervisor.restarts, socket: supervisor.socketPath,
|
|
141
|
+
memory: memory ? { active_bytes: memory.activeBytes, cache_bytes: memory.cacheBytes, peak_bytes: memory.peakBytes } : null };
|
|
142
|
+
}),
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
const health = async (): Promise<Response> => {
|
|
146
|
+
const engine = currentWorker();
|
|
147
|
+
let contribution: Record<string, unknown> = {};
|
|
148
|
+
if (engine?.state === "ready") {
|
|
149
|
+
try {
|
|
150
|
+
const upstream = await engine.fetch("http://engine/health", { signal: AbortSignal.timeout(2_000) });
|
|
151
|
+
const body = await upstream.json() as Record<string, unknown>;
|
|
152
|
+
if (upstream.ok) contribution = { state: body.state, in_flight: body.in_flight, leases: body.leases };
|
|
153
|
+
else contribution = { state: "unreachable" };
|
|
154
|
+
} catch { contribution = { state: "unreachable" }; }
|
|
155
|
+
}
|
|
156
|
+
const { response_store: _store, isolated: _isolated, workers, ...current } = report();
|
|
157
|
+
return Response.json({ status: "ok", isolated: true, engine: { ...current, ...contribution }, workers });
|
|
158
|
+
};
|
|
159
|
+
const stats = async (request: Request): Promise<Response> => {
|
|
160
|
+
const own = { response_store: responses.stats, engine: report() };
|
|
161
|
+
const upstream = await options.models.handle(request).catch(() => null);
|
|
162
|
+
if (upstream?.ok && (upstream.headers.get("content-type") ?? "").includes("json"))
|
|
163
|
+
return Response.json({ ...(await upstream.json() as Record<string, unknown>), ...own });
|
|
164
|
+
if (upstream && !upstream.ok && upstream.status !== 502) return upstream;
|
|
165
|
+
// The worker's own reason (a 502 from the forward), else that no worker is resident.
|
|
166
|
+
const reason = await upstream?.json().then((body: { error?: { message?: string } }) => body.error?.message).catch(() => undefined);
|
|
167
|
+
if (request.signal.aborted) return new Response(null, { status: 499 });
|
|
168
|
+
return Response.json({ server: { owner: "serve", model: options.current(), started_at: options.startedAt }, ...own,
|
|
169
|
+
unavailable: reason ?? "inference engine unavailable: no model worker is resident" });
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
report,
|
|
174
|
+
async handle(request: Request): Promise<Response | null> {
|
|
175
|
+
const { pathname } = new URL(request.url);
|
|
176
|
+
if (PARENT_ONLY.has(pathname)) return null;
|
|
177
|
+
if (pathname === "/engine") return request.method === "GET" ? Response.json(report()) : null;
|
|
178
|
+
if (request.method === "GET") {
|
|
179
|
+
if (pathname === "/health") return health();
|
|
180
|
+
if (pathname === "/stats") return stats(request);
|
|
181
|
+
}
|
|
182
|
+
if (pathname === "/v1/responses" && request.method === "POST")
|
|
183
|
+
return responses.forward(request, async inner => await options.models.handle(inner) ?? Response.json({ error: { message: "Not found" } }, { status: 404 }));
|
|
184
|
+
return options.models.handle(request);
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|