mlx-bun 0.4.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 +1453 -88
- 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 +10 -8
- 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 +12 -10
- package/src/{serve → server}/chat-stage.ts +27 -22
- package/src/{client.ts → server/client.ts} +28 -2
- package/src/{serve → server}/completion-executor.ts +42 -42
- 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 +42 -83
- package/src/server/generated-token-history.ts +57 -0
- 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 +8 -8
- package/src/{serve → server}/request-prep.ts +82 -68
- 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 -94
- package/docs/archive/hlg-sampling.md +0 -350
- package/docs/archive/investigations/audio-input-plan.md +0 -356
- 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/web-ui-pass-plan.md +0 -213
- package/docs/archive/planning/website-readme-pass-plan.md +0 -224
- package/docs/design/batching.md +0 -1841
- package/docs/design/decode-speed-program.md +0 -2349
- package/docs/design/dreaming-nightly-pipeline.md +0 -684
- package/docs/design/generic-model-support.md +0 -705
- package/docs/design/kv-cache.md +0 -824
- package/docs/design/orpo-training.md +0 -680
- package/docs/design/speculative-decoding.md +0 -1990
- package/docs/design/turboquant.md +0 -2212
- package/docs/design/unified-engine-frontier-plan.md +0 -1880
- package/docs/design/web-chat-redesign.md +0 -695
- package/docs/planning/PRODUCT_ROADMAP.md +0 -594
- package/docs/planning/ResearchTopics.md +0 -119
- package/docs/planning/release-notes-v0.4.0.md +0 -45
- package/docs/planning/video-series.md +0 -421
- package/docs/reference/benchmarks.md +0 -3613
- package/docs/reference/cli.md +0 -585
- package/docs/reference/distribution.md +0 -355
- package/docs/reference/environment.md +0 -398
- package/docs/reference/glossary.md +0 -39
- package/docs/reference/library-api.md +0 -674
- package/docs/reference/memory.md +0 -260
- package/docs/reference/models.md +0 -334
- package/docs/reference/server-api.md +0 -1775
- package/docs/reference/server-config.md +0 -846
- package/docs/reference/training.md +0 -462
- package/docs/reference/troubleshooting.md +0 -158
- package/src/admission-plan.ts +0 -18
- package/src/audio/conformer.ts +0 -647
- package/src/audio/decode.ts +0 -133
- package/src/audio/features.ts +0 -202
- package/src/audio/transcode.ts +0 -67
- 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 -108
- package/src/backends/mlx/batch-group.ts +0 -1790
- 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 -106
- 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/evaluation.ts +0 -105
- package/src/backends/mlx/fill-append.ts +0 -45
- 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 -130
- 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 -133
- 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/model-prompt.ts +0 -164
- package/src/backends/mlx/model-serving.ts +0 -79
- package/src/backends/mlx/prefill-cohort.ts +0 -89
- package/src/backends/mlx/prefill-rows.ts +0 -200
- package/src/backends/mlx/prefill.ts +0 -33
- package/src/backends/mlx/projected-context-rows.ts +0 -72
- package/src/backends/mlx/request-policy.ts +0 -20
- package/src/backends/mlx/request-state-policy.ts +0 -41
- package/src/backends/mlx/rollback.ts +0 -89
- package/src/backends/mlx/row-sampling.ts +0 -49
- package/src/backends/mlx/serial-executor.ts +0 -242
- package/src/backends/mlx/speculative-group.ts +0 -359
- package/src/backends/mlx/speculative-round.ts +0 -161
- package/src/backends/mlx/speculative.ts +0 -114
- 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 -165
- package/src/chat-template.ts +0 -382
- package/src/cli.ts +0 -3168
- package/src/config.ts +0 -399
- package/src/contracts/completion.ts +0 -16
- package/src/contracts/execution.ts +0 -60
- package/src/contracts/generation.ts +0 -103
- package/src/contracts/host.ts +0 -7
- package/src/contracts/pi-provider.ts +0 -30
- package/src/contracts/pi.ts +0 -259
- package/src/contracts/prefix-cache.ts +0 -23
- package/src/contracts/resources.ts +0 -25
- package/src/contracts/sampling.ts +0 -23
- package/src/contracts/scheduling.ts +0 -45
- 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 -53
- package/src/engine/resources.ts +0 -53
- package/src/engine/scheduler.ts +0 -72
- 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 -452
- 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 -1346
- package/src/grammar.ts +0 -591
- 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 -39
- 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 -257
- package/src/kv-store.ts +0 -1099
- package/src/lab/README.md +0 -18
- 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-kv-rows.ts +0 -83
- package/src/lab/paged-kv/paged-kv.ts +0 -270
- package/src/library.ts +0 -40
- package/src/lora.ts +0 -408
- package/src/md-modules.d.ts +0 -49
- 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 -388
- 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 -512
- package/src/mlx/materialize.ts +0 -23
- package/src/mlx/metal-kernel.ts +0 -159
- package/src/mlx/ops.ts +0 -1021
- 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 -93
- 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 -55
- package/src/model/diffusion-gemma.ts +0 -1026
- package/src/model/factory.ts +0 -189
- package/src/model/fingerprint.ts +0 -33
- 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 -2351
- package/src/model/gemma4.ts +0 -1315
- 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 -67
- package/src/model/minicpm5.ts +0 -376
- package/src/model/profile.ts +0 -443
- 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 -252
- package/src/model/qwen3.ts +0 -277
- package/src/model/qwen3_5-checkpoint.ts +0 -164
- package/src/model/qwen3_5.ts +0 -819
- 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 -102
- 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 -83
- 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/native/expert_io.c +0 -523
- package/src/native/frame_extract.swift +0 -140
- package/src/native-pack.ts +0 -162
- 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 -1932
- package/src/preflight.ts +0 -158
- package/src/prompt-cache.ts +0 -460
- 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 -81
- package/src/safetensors.ts +0 -158
- package/src/sampler.ts +0 -1011
- 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/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 -496
- 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 -43
- package/src/serve/model-host.ts +0 -511
- package/src/serve/parent-application.ts +0 -56
- package/src/serve/prompt-response-trace.ts +0 -147
- 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/server.ts +0 -1314
- package/src/spec/assistant-rows.ts +0 -92
- 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 -317
- package/src/spec/glm52-mtp-state.ts +0 -33
- package/src/spec/ngram-source.ts +0 -204
- 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 -219
- package/src/spec/qwen-mtp-source.ts +0 -383
- package/src/spec/qwen-mtp-state.ts +0 -30
- package/src/spec/serve-loop.ts +0 -585
- package/src/spec/source.ts +0 -215
- package/src/spec/two-model-rows.ts +0 -61
- package/src/spec/two-model.ts +0 -113
- package/src/ssd-cache.ts +0 -372
- package/src/ssd-durability.ts +0 -211
- 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 -168
- package/src/vision/preprocess.ts +0 -247
- package/src/vision/prompt.ts +0 -373
- package/src/vision/qwen3vl-preprocess.ts +0 -409
- package/src/vision/qwen3vl-prompt.ts +0 -196
- package/src/vision/qwen3vl-tower.ts +0 -555
- package/src/vision/siglip.ts +0 -599
- package/src/vision/video-frames.ts +0 -148
- package/src/web/app.html +0 -2143
- package/src/web/app.js +0 -5703
- 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 -1057
- package/src/web/src/composer.ts +0 -959
- 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/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/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
package/src/server.ts
DELETED
|
@@ -1,1314 +0,0 @@
|
|
|
1
|
-
import { ContinuationPersistence } from "./backends/mlx/continuation-persistence";
|
|
2
|
-
import { modelServingBinding } from "./backends/mlx/model-serving";
|
|
3
|
-
import type { ServingContext } from "./serve/model-host";
|
|
4
|
-
export { generationCheckpointKey } from "./serve/checkpoint-identity";
|
|
5
|
-
// OpenAI-compatible HTTP server: /v1/chat/completions (+ SSE streaming)
|
|
6
|
-
// and /v1/models. Phase 4 core — tool calling, vision, and the
|
|
7
|
-
// byte-capped prompt cache land on top of this.
|
|
8
|
-
//
|
|
9
|
-
// Original code; behavioral reference: mlx-lm server.py (MIT) for the
|
|
10
|
-
// protocol surface and stop/finish semantics. No code ported — Bun.serve
|
|
11
|
-
// and the generation queue are structurally different.
|
|
12
|
-
//
|
|
13
|
-
// Generation is serialized through a single queue (one GPU, batch=1).
|
|
14
|
-
|
|
15
|
-
import type { Server } from "bun";
|
|
16
|
-
import { STATIC_ROUTE_ASSETS } from "./web-assets";
|
|
17
|
-
import { readFileSync } from "node:fs";
|
|
18
|
-
import { type TurboQuantScheme } from "./config";
|
|
19
|
-
import {
|
|
20
|
-
GLM52_G5_ASPIRATIONAL_DECODE_TPS,
|
|
21
|
-
GLM52_G5_DIRECT_ORACLE_WARM_DECODE_TPS,
|
|
22
|
-
GLM52_G5_MEASURED_AT,
|
|
23
|
-
GLM52_G5_MEASURED_WARM_DECODE_TPS,
|
|
24
|
-
} from "./model/glm52-memory";
|
|
25
|
-
import {
|
|
26
|
-
type GenerateOptions,
|
|
27
|
-
} from "./generate";
|
|
28
|
-
import { createPreparationExecutor } from "./serve/preparation";
|
|
29
|
-
import { cloneKvCaches, legacyCacheCodecs, SpillQueue } from "./kv-store";
|
|
30
|
-
import type { Cache } from "./model/gemma4";
|
|
31
|
-
import { resolveKvScheme } from "./kv-scheme";
|
|
32
|
-
import { runtimeValue } from "./runtime-config";
|
|
33
|
-
import { TURBOQUANT_HEAD_DIMS } from "./mlx/turboquant-tables";
|
|
34
|
-
import type { HlgConfig } from "./sampler";
|
|
35
|
-
import { isMonotone, CURVE_UMIN, type CurveParams } from "./lab/curve/curve-sampler";
|
|
36
|
-
import { GenerationGateway, disposeUnstartedRequest } from "./serve/generation-gateway";
|
|
37
|
-
import { createSessionCompletionEngine } from "./serve/session-completion-engine";
|
|
38
|
-
import {
|
|
39
|
-
CompletionExecutor,
|
|
40
|
-
} from "./serve/completion-executor";
|
|
41
|
-
import {
|
|
42
|
-
createPromptResponseTrace,
|
|
43
|
-
type PromptResponseTrace,
|
|
44
|
-
} from "./serve/prompt-response-trace";
|
|
45
|
-
import { handleAdminRoute } from "./serve/admin-routes";
|
|
46
|
-
import { handleAuxiliaryRoute } from "./serve/aux-routes";
|
|
47
|
-
import { createDiscoveryRoutes } from "./serve/discovery-routes";
|
|
48
|
-
import { handleLabRoute } from "./serve/lab-routes";
|
|
49
|
-
import { handleModelAdminRoute } from "./serve/model-admin-routes";
|
|
50
|
-
import { handleStaticRoute } from "./serve/static-routes";
|
|
51
|
-
|
|
52
|
-
import { PromptCache, cacheBytes } from "./prompt-cache";
|
|
53
|
-
import { SsdCacheStore } from "./ssd-cache";
|
|
54
|
-
import {
|
|
55
|
-
SsdDurabilityCoordinator,
|
|
56
|
-
type DurabilityFlushResult,
|
|
57
|
-
type DurabilitySnapshotStats,
|
|
58
|
-
} from "./ssd-durability";
|
|
59
|
-
import { configFingerprint } from "./model/fingerprint";
|
|
60
|
-
import {
|
|
61
|
-
anthropicToChatBody, chatJsonToAnthropic, createAnthropicStreamProtocol,
|
|
62
|
-
type AnthropicRequest,
|
|
63
|
-
} from "./anthropic";
|
|
64
|
-
import {
|
|
65
|
-
ResponseStore, chatJsonToResponses, resolveResponsesConversation,
|
|
66
|
-
responsesToChatBody, createResponsesStreamProtocol,
|
|
67
|
-
type ResponsesRequest,
|
|
68
|
-
} from "./responses";
|
|
69
|
-
import { fit } from "./fit";
|
|
70
|
-
import { setMemoryLimit } from "./mlx/ffi";
|
|
71
|
-
import { makePiWsHandler, type PiWsData } from "./pi-web";
|
|
72
|
-
import { ChatStage } from "./serve/chat-stage";
|
|
73
|
-
import { TextCompletionStage } from "./serve/text-completion-stage";
|
|
74
|
-
import { InferenceStage } from "./serve/inference-request";
|
|
75
|
-
import { admit, respondJson, respondStream, type ErrorFormatter } from "./serve/http";
|
|
76
|
-
import {
|
|
77
|
-
chatCompletionJson, chatCompletionStream, textCompletionJson, textCompletionStream,
|
|
78
|
-
} from "./serve/openai-wire";
|
|
79
|
-
import { createRequestPrep } from "./serve/request-prep";
|
|
80
|
-
import {
|
|
81
|
-
detectDraftKind,
|
|
82
|
-
loadContext,
|
|
83
|
-
type DraftKind,
|
|
84
|
-
type GenSamplingDefaults,
|
|
85
|
-
type LoadContextOptions,
|
|
86
|
-
type ServerContext,
|
|
87
|
-
} from "./serve/model-host";
|
|
88
|
-
import {
|
|
89
|
-
ChatRequest,
|
|
90
|
-
TextCompletionRequest,
|
|
91
|
-
type ChatRequestParams,
|
|
92
|
-
type TextCompletionParams,
|
|
93
|
-
} from "./serve/chat-request";
|
|
94
|
-
|
|
95
|
-
// The serving facade: createServer's companion loaders stay importable from
|
|
96
|
-
// here (public library API via src/index.ts; bench scripts; parity tests).
|
|
97
|
-
export {
|
|
98
|
-
detectDraftKind,
|
|
99
|
-
loadContext,
|
|
100
|
-
type DraftKind,
|
|
101
|
-
type GenSamplingDefaults,
|
|
102
|
-
type LoadContextOptions,
|
|
103
|
-
type ServerContext,
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
export interface ServerOptions {
|
|
107
|
-
/** Byte cap for the prompt (KV) cache. Default 8 GB. */
|
|
108
|
-
promptCacheBytes?: number;
|
|
109
|
-
/** Aggregate KV-byte budget across concurrently-admitted batch rows
|
|
110
|
-
* (`--kv-budget`, batching-perf-path P3). Joiners whose projected KV
|
|
111
|
-
* (prompt + max_tokens, window-capped) would exceed it QUEUE until rows
|
|
112
|
-
* evict; a request over the budget alone is rejected. Unset = no
|
|
113
|
-
* aggregate cap (an explicit memoryBudget also limits each request). */
|
|
114
|
-
kvBudgetBytes?: number;
|
|
115
|
-
/** KV quantization override. Unset/"off" is bf16. "config" applies the
|
|
116
|
-
* model's declared mixed-precision kv_config; supported per-layer schemes
|
|
117
|
-
* compose with continuous scheduling. A number forces uniform bits
|
|
118
|
-
* (group size 64, start 0) and uses the preserved serial executor. */
|
|
119
|
-
kvQuant?: "off" | "config" | number;
|
|
120
|
-
/** Optional absolute row offset for KV conversion; omitted preserves start-zero server policy. */
|
|
121
|
-
quantizedKvStart?: number;
|
|
122
|
-
/** TurboQuant scheme (docs/design/turboquant.md): a separate axis from
|
|
123
|
-
* kvQuant above, mutually exclusive with it (`--kv-quant turbo[:k<bits>v
|
|
124
|
-
* <bits>]` sets this instead of kvQuant). Solo-only in v1 — see
|
|
125
|
-
* GenerationGateway's explicit refusal. */
|
|
126
|
-
turboQuant?: TurboQuantScheme;
|
|
127
|
-
/** OPTIONAL paged KV cache (`--paged-kv`, docs/design/kv-cache.md):
|
|
128
|
-
* vLLM-style block-pool storage for full-attention layers,
|
|
129
|
-
* gather-to-contiguous before the stock SDPA. Default off (unset = the
|
|
130
|
-
* plain KVCache path, byte-identical). Gemma4-family, bf16, B1/B>1;
|
|
131
|
-
* startup refuses `--kv-quant`,
|
|
132
|
-
* turbo, and `--draft-model` combinations; paged requests bypass the
|
|
133
|
-
* prompt cache and run uncompiled. Gated bit-exact vs the plain path. */
|
|
134
|
-
pagedKv?: { blockSize?: number };
|
|
135
|
-
/** Explicit opt-in process budget. Enforces the fit estimate's context
|
|
136
|
-
* limit and sets the MLX allocator limit. Unset keeps estimates advisory. */
|
|
137
|
-
memoryBudgetBytes?: number;
|
|
138
|
-
/** Who owns this server's lifetime: "serve" (persistent, mlx-bun
|
|
139
|
-
* serve) or "pi-session" (dies with the pi session that started
|
|
140
|
-
* it). Exposed at /stats.server so other mlx-bun processes can
|
|
141
|
-
* warn before attaching to a server that may vanish. */
|
|
142
|
-
owner?: "serve" | "pi-session" | "embedded";
|
|
143
|
-
/** Listen on a UNIX DOMAIN SOCKET instead of a TCP port — the engine-
|
|
144
|
-
* child mode of the isolation architecture (docs/reference/server-config.md): the
|
|
145
|
-
* parent process reverse-proxies HTTP to this socket. Stale socket
|
|
146
|
-
* files are unlinked before bind. When set, `hostname`/port are ignored. */
|
|
147
|
-
unixSocket?: string;
|
|
148
|
-
/** Interface to bind (Bun.serve hostname). Unset ⇒ Bun's default
|
|
149
|
-
* (all interfaces) — embedded/library use. The CLI always passes one:
|
|
150
|
-
* "127.0.0.1" (loopback, mlx_lm.server parity) unless --host says
|
|
151
|
-
* otherwise. */
|
|
152
|
-
hostname?: string;
|
|
153
|
-
/** Server-wide default for the chat template's `enable_thinking`
|
|
154
|
-
* variable (MiniCPM5/CPM and other hybrid-reasoning models). A
|
|
155
|
-
* request's `chat_template_kwargs.enable_thinking` overrides it per
|
|
156
|
-
* call; undefined ⇒ fall back to the model's own default (false for
|
|
157
|
-
* MiniCPM5). Set via `--thinking true|false`. */
|
|
158
|
-
defaultThinking?: boolean;
|
|
159
|
-
/** Server-wide sampling defaults (set via --temperature/--top-p/--top-k).
|
|
160
|
-
* Precedence: an explicit per-request field wins, then these, then the
|
|
161
|
-
* model's generation_config.json, then the built-in fallback. Lets the
|
|
162
|
-
* browser chat (which sends no sampling fields) be steered from the CLI. */
|
|
163
|
-
defaultTemperature?: number;
|
|
164
|
-
defaultTopP?: number;
|
|
165
|
-
defaultTopK?: number;
|
|
166
|
-
/** Completion cap when the request omits max_tokens (`--max-tokens`).
|
|
167
|
-
* mlx_lm.server's flag; its default there is 512 — ours stays 65,536 so
|
|
168
|
-
* thinking traces never truncate. `--max-tokens 512` = mlx-lm behavior. */
|
|
169
|
-
defaultMaxTokens?: number;
|
|
170
|
-
/** Continuous-scheduler concurrency cap (`--batch N`). Default 8; the
|
|
171
|
-
* scheduler specializes active B=1 and B=N with mlx-lm parity at the same
|
|
172
|
-
* composition. `--batch 1` pins the preserved strict serial executor. */
|
|
173
|
-
batch?: number;
|
|
174
|
-
/** HLG tone-curve sampling default (set via --hlg-sampling on + sub-knobs).
|
|
175
|
-
* A per-request `hlg` object overrides it field-by-field. Off when unset. */
|
|
176
|
-
hlg?: HlgConfig;
|
|
177
|
-
/** Adapter id mounted at startup via `serve --adapter <dir>` (mlx-lm's
|
|
178
|
-
* `--adapter-path`). Used when a request sends no `adapter` field; a
|
|
179
|
-
* request's explicit `adapter` (including "none") always wins. Hot-swap
|
|
180
|
-
* via /v1/adapters is unchanged. */
|
|
181
|
-
defaultAdapter?: string;
|
|
182
|
-
/** SSD cold tier for the prompt/KV cache (`--ssd-cache <dir>`): prefix
|
|
183
|
-
* KV survives RAM eviction AND server restarts — the coding-agent
|
|
184
|
-
* long-context TTFT win (docs/design/kv-cache.md). Off unless
|
|
185
|
-
* a directory is given. Serial lane only (where the prompt cache lives). */
|
|
186
|
-
ssdCacheDir?: string;
|
|
187
|
-
/** Optional byte cap for the SSD tier (default unbounded). */
|
|
188
|
-
ssdCacheMaxBytes?: number;
|
|
189
|
-
/** Idle-demotion threshold in seconds (`--ssd-demote-idle`): prompt-cache
|
|
190
|
-
* entries unused this long spill to the SSD tier and free their GPU
|
|
191
|
-
* memory (Layer 0 — RAM drains between bursts, prefixes stay reachable
|
|
192
|
-
* via zero-copy restore). Default 300 when the tier is on; 0 disables. */
|
|
193
|
-
ssdDemoteIdleSec?: number;
|
|
194
|
-
/** Verify every tensor hash on restore (`--ssd-cache-verify`) — reads all
|
|
195
|
-
* bytes eagerly, defeating lazy fault-in; integrity paranoia only. */
|
|
196
|
-
ssdCacheVerify?: boolean;
|
|
197
|
-
/** Periodically persist an in-flight serial generation every N emitted
|
|
198
|
-
* tokens. An identical request after restart replays the saved assistant
|
|
199
|
-
* prefix and continues from the already-sampled next token. Requires the
|
|
200
|
-
* SSD cache and --batch 1. */
|
|
201
|
-
generationCheckpointTokens?: number;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
interface ServerLifecycle {
|
|
205
|
-
stopJobs: () => Promise<void>;
|
|
206
|
-
close: () => Promise<void>;
|
|
207
|
-
flush: () => Promise<DurabilityFlushResult>;
|
|
208
|
-
stats: () => DurabilitySnapshotStats;
|
|
209
|
-
stopTimers: () => void;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export interface ServerShutdownResult {
|
|
213
|
-
stopped: boolean;
|
|
214
|
-
timedOut: boolean;
|
|
215
|
-
durability: DurabilityFlushResult;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const serverLifecycles = new WeakMap<Server<unknown>, ServerLifecycle>();
|
|
219
|
-
|
|
220
|
-
function emptyDurabilityResult(): DurabilityFlushResult {
|
|
221
|
-
return {
|
|
222
|
-
durable: true,
|
|
223
|
-
flushedSnapshots: 0,
|
|
224
|
-
missingSnapshots: 0,
|
|
225
|
-
pendingSnapshots: 0,
|
|
226
|
-
pendingSpills: 0,
|
|
227
|
-
pendingSpillBytes: 0,
|
|
228
|
-
droppedSpills: 0,
|
|
229
|
-
failedSpills: 0,
|
|
230
|
-
elapsedMs: 0,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** Flush prompt-cache snapshots without stopping the HTTP server. */
|
|
235
|
-
export function flushServerCacheDurability(
|
|
236
|
-
server: Server<unknown>,
|
|
237
|
-
): Promise<DurabilityFlushResult> {
|
|
238
|
-
return serverLifecycles.get(server)?.flush() ?? Promise.resolve(emptyDurabilityResult());
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/** Stop admission, flush cache durability, then finish the Bun server. */
|
|
242
|
-
export async function shutdownServer(
|
|
243
|
-
server: Server<unknown>, timeoutMs = 120_000,
|
|
244
|
-
): Promise<ServerShutdownResult> {
|
|
245
|
-
const lifecycle = serverLifecycles.get(server);
|
|
246
|
-
lifecycle?.stopTimers();
|
|
247
|
-
const stopped = Promise.resolve(server.stop(false));
|
|
248
|
-
const work = (async (): Promise<ServerShutdownResult> => {
|
|
249
|
-
await lifecycle?.stopJobs();
|
|
250
|
-
await stopped;
|
|
251
|
-
const durability = lifecycle ? await lifecycle.flush() : emptyDurabilityResult();
|
|
252
|
-
await lifecycle?.close();
|
|
253
|
-
return { stopped: true, timedOut: false, durability };
|
|
254
|
-
})();
|
|
255
|
-
let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
|
|
256
|
-
const timeout = new Promise<ServerShutdownResult>((resolve) => {
|
|
257
|
-
timeoutHandle = setTimeout(() => {
|
|
258
|
-
const stats = lifecycle?.stats();
|
|
259
|
-
resolve({
|
|
260
|
-
stopped: false,
|
|
261
|
-
timedOut: true,
|
|
262
|
-
durability: {
|
|
263
|
-
...emptyDurabilityResult(),
|
|
264
|
-
durable: false,
|
|
265
|
-
...(stats ?? {}),
|
|
266
|
-
elapsedMs: timeoutMs,
|
|
267
|
-
},
|
|
268
|
-
});
|
|
269
|
-
}, timeoutMs);
|
|
270
|
-
timeoutHandle.unref?.();
|
|
271
|
-
});
|
|
272
|
-
try {
|
|
273
|
-
return await Promise.race([work, timeout]);
|
|
274
|
-
} finally {
|
|
275
|
-
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
export interface ContextAdmissionDecision {
|
|
281
|
-
/** Effective completion ceiling after admission. */
|
|
282
|
-
maxTokens: number;
|
|
283
|
-
/** True when admission reduced the client's upper bound to fit. */
|
|
284
|
-
clamped: boolean;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/** Resolve a completion cap against an explicitly enforced context limit.
|
|
288
|
-
* Serving keeps fit estimates advisory unless the operator chooses a budget. */
|
|
289
|
-
export function admitRequestContext(
|
|
290
|
-
promptTokens: number,
|
|
291
|
-
requestedMaxTokens: number,
|
|
292
|
-
maxSafeContext: number,
|
|
293
|
-
): ContextAdmissionDecision | null {
|
|
294
|
-
const available = maxSafeContext - promptTokens;
|
|
295
|
-
if (available < 1) return null;
|
|
296
|
-
if (requestedMaxTokens <= available)
|
|
297
|
-
return { maxTokens: requestedMaxTokens, clamped: false };
|
|
298
|
-
return { maxTokens: available, clamped: true };
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// v2 curve designer: CORS-open so a file:// editor can call a localhost engine.
|
|
304
|
-
const CURVE_CORS: Record<string, string> = {
|
|
305
|
-
"access-control-allow-origin": "*",
|
|
306
|
-
"access-control-allow-methods": "POST, OPTIONS",
|
|
307
|
-
"access-control-allow-headers": "content-type",
|
|
308
|
-
};
|
|
309
|
-
/** coherence flag: non-Latin-letter ratio / replacement char / reserved token. */
|
|
310
|
-
function curveJunk(s: string): boolean {
|
|
311
|
-
const L = s.match(/\p{L}/gu) ?? [];
|
|
312
|
-
const nonLatin = L.length ? L.filter((c) => !/\p{Script=Latin}/u.test(c)).length / L.length : 0;
|
|
313
|
-
return nonLatin >= 0.02 || /�/.test(s) || /<unused\d+>/.test(s);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
export function createServer(
|
|
318
|
-
ctx: ServingContext, port = 0, serverOptions: ServerOptions = {},
|
|
319
|
-
): Server<unknown> {
|
|
320
|
-
// --batch N is a concurrency cap: N===1 pins the strict serial executor;
|
|
321
|
-
// N>1 admits supported execution compositions to the continuous scheduler.
|
|
322
|
-
// The scheduler chooses its B=1 fast path or B=N step from active rows.
|
|
323
|
-
// Both full-attention (CPM) and
|
|
324
|
-
// sliding-window (Gemma) models batch — the scheduler assembles each layer's
|
|
325
|
-
// cache by attention type. Non-batchable requests (vision / adapters /
|
|
326
|
-
// unsupported explicit kv-quant) drain to the serial executor
|
|
327
|
-
// (see GenerationGateway.place). No inference setting is rewritten.
|
|
328
|
-
// DEFAULT 8 (flipped 2026-07-05, Josh's call, after GATE-B1-SPEED): a
|
|
329
|
-
// lone request through the batch lane IS the serial engine (adopted
|
|
330
|
-
// serial-class caches, compiled decode, prompt cache + SSD restore;
|
|
331
|
-
// 0.992-0.996 paired decode ratios, byte-identical output), so the cap
|
|
332
|
-
// only changes behavior when concurrent requests actually arrive — the
|
|
333
|
-
// agentic sub-agent workload. --batch 1 pins strict serial for
|
|
334
|
-
// arrival-independent numerics. 8 = optiq's Mac-safe concurrency.
|
|
335
|
-
const serving = modelServingBinding(ctx);
|
|
336
|
-
ctx = { ...ctx, serving };
|
|
337
|
-
const batch = Math.max(1, Math.floor(serverOptions.batch ?? 8));
|
|
338
|
-
if (serverOptions.generationCheckpointTokens !== undefined) {
|
|
339
|
-
if (!Number.isInteger(serverOptions.generationCheckpointTokens) ||
|
|
340
|
-
serverOptions.generationCheckpointTokens < 1)
|
|
341
|
-
throw new Error("--generation-checkpoint expects a positive integer token interval");
|
|
342
|
-
if (!serverOptions.ssdCacheDir)
|
|
343
|
-
throw new Error("--generation-checkpoint requires --ssd-cache <dir>");
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
const defaultGeneratedTokens =
|
|
347
|
-
serverOptions.defaultMaxTokens ?? ctx.glmMemoryPlan?.maxGenerationTokens;
|
|
348
|
-
|
|
349
|
-
// KV-quant scheme, resolved once. UNSET now means bf16 (flipped
|
|
350
|
-
// 2026-07-05 with the naked-=-L1 default): quantized KV measured 5–20%
|
|
351
|
-
// SLOWER decode than bf16 at ≤16k on every model — on mlx-lm too (its
|
|
352
|
-
// kv8 oracle trails its own bf16) — so it pays only in memory headroom
|
|
353
|
-
// and must be an explicit opt-in (--kv-quant config|4|8, or --l2
|
|
354
|
-
// whose presets pass it explicitly). The CLI always passes kvQuant now;
|
|
355
|
-
// this fallback is the library-user default and matches the CLI's.
|
|
356
|
-
// Mutually exclusive by contract (GenerateOptions.turboQuant doc): a
|
|
357
|
-
// programmatic caller setting both gets turboQuant — say so, like the
|
|
358
|
-
// other risky-combination warnings below.
|
|
359
|
-
if (serverOptions.turboQuant && serverOptions.kvQuant && serverOptions.kvQuant !== "off")
|
|
360
|
-
console.warn(
|
|
361
|
-
`[kv-quant] both turboQuant and --kv-quant ${serverOptions.kvQuant} are set; ` +
|
|
362
|
-
`turboQuant wins (they are mutually exclusive).`,
|
|
363
|
-
);
|
|
364
|
-
const resolvedKvScheme = resolveKvScheme({
|
|
365
|
-
override: serverOptions.kvQuant,
|
|
366
|
-
turboQuant: serverOptions.turboQuant,
|
|
367
|
-
quantizedKvStart: serverOptions.quantizedKvStart,
|
|
368
|
-
config: ctx.kvConfig,
|
|
369
|
-
});
|
|
370
|
-
const kvScheme = resolvedKvScheme.generationOptions;
|
|
371
|
-
// The model execution plan qualifies the method while retaining KV policy.
|
|
372
|
-
if (ctx.draft && (kvScheme.turboQuant || kvScheme.kvBits || kvScheme.kvConfig?.length))
|
|
373
|
-
console.warn(
|
|
374
|
-
`[spec] --draft-model with quantized KV (--kv-quant ${serverOptions.turboQuant ? "turbo" : String(serverOptions.kvQuant)}): ` +
|
|
375
|
-
`requests keep the KV scheme; the model execution plan enables speculation ` +
|
|
376
|
-
`only for a qualified combination. Check response usage for the active method. ` +
|
|
377
|
-
`(docs/design/speculative-decoding.md Phase 4)`,
|
|
378
|
-
);
|
|
379
|
-
// TurboQuant head-dim fail-fast (2026-07-07 review): the cache class only
|
|
380
|
-
// supports {64,128,256,512} (sign-vector + Lloyd-Max table coverage) and
|
|
381
|
-
// used to validate LAZILY on the first append — an unsupported model
|
|
382
|
-
// (e.g. a 72/80/96 head dim) accepted --kv-quant turbo at startup and
|
|
383
|
-
// then 500'd EVERY request from inside prefill. The config knows the
|
|
384
|
-
// full-attention head dim (the only kind that converts; sliding layers
|
|
385
|
-
// stay bf16) — refuse at createServer instead.
|
|
386
|
-
if (kvScheme.turboQuant) {
|
|
387
|
-
const dim = ctx.model.config.text.globalHeadDim;
|
|
388
|
-
if (!(TURBOQUANT_HEAD_DIMS as readonly number[]).includes(dim))
|
|
389
|
-
throw new Error(
|
|
390
|
-
`--kv-quant turbo: this model's full-attention head_dim is ${dim}; ` +
|
|
391
|
-
`TurboQuant supports {${TURBOQUANT_HEAD_DIMS.join(",")}} ` +
|
|
392
|
-
`(docs/design/turboquant.md) — use --kv-quant config|4|8 or omit it`,
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
// Paged KV v1 (docs/design/kv-cache.md): explicit refusals, not
|
|
396
|
-
// silent downgrades — the incompatible combos would otherwise degrade
|
|
397
|
-
// quietly (kv-quant: the swap would drop the scheme; speculative
|
|
398
|
-
// provider composition with paged storage is not implemented).
|
|
399
|
-
if (serverOptions.pagedKv) {
|
|
400
|
-
if (kvScheme.kvBits || kvScheme.kvConfig?.length || kvScheme.turboQuant)
|
|
401
|
-
throw new Error(
|
|
402
|
-
`--paged-kv is bf16-only in v1 — omit --kv-quant (quantized paged ` +
|
|
403
|
-
`blocks are a documented follow-up, docs/design/kv-cache.md).`,
|
|
404
|
-
);
|
|
405
|
-
if (ctx.draft)
|
|
406
|
-
throw new Error(
|
|
407
|
-
`--paged-kv cannot combine with --draft-model in v1 ` +
|
|
408
|
-
`(docs/design/kv-cache.md non-goals).`,
|
|
409
|
-
);
|
|
410
|
-
if (!ctx.model.config.modelType.startsWith("gemma4"))
|
|
411
|
-
throw new Error(
|
|
412
|
-
`--paged-kv v1 supports Gemma4-family models only ` +
|
|
413
|
-
`(this model: ${ctx.model.config.modelType}) — docs/design/kv-cache.md.`,
|
|
414
|
-
);
|
|
415
|
-
const bs = serverOptions.pagedKv.blockSize;
|
|
416
|
-
if (bs !== undefined && (!Number.isInteger(bs) || bs <= 0))
|
|
417
|
-
throw new Error(`--paged-kv-block-size must be a positive integer (got ${bs})`);
|
|
418
|
-
if (serverOptions.ssdCacheDir)
|
|
419
|
-
console.warn(
|
|
420
|
-
"[paged-kv] --ssd-cache has no effect: paged requests bypass the prompt " +
|
|
421
|
-
"cache (v1 non-goal), so nothing reaches the SSD tier.",
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// SSD cold tier (docs/design/kv-cache.md): prefix KV survives RAM
|
|
426
|
-
// eviction and restarts. Compatibility key = configFingerprint (graph
|
|
427
|
-
// shape) + the EFFECTIVE kv scheme (flags pick bf16 vs config vs uniform
|
|
428
|
-
// on the same model — restored caches must match what serving produces) +
|
|
429
|
-
// tokenizer hash (ids must keep meaning the same text). The model binding
|
|
430
|
-
// also identifies the runtime numerics that produced the persisted state.
|
|
431
|
-
// RAM prompt-cache cap: 8 GB default (Josh's call, 2026-07-06). The old
|
|
432
|
-
// flat 2e9 was an anti-OOM reflex sized for a 1B model in 24 GB — a
|
|
433
|
-
// single full-context 12B entry is ~0.6 GB, so it silently shrank to
|
|
434
|
-
// "three entries" on big models. 8 GB holds a dozen 12B contexts; idle
|
|
435
|
-
// entries still demote to SSD and LRU eviction bounds pressure.
|
|
436
|
-
// --prompt-cache <GB> overrides; 0 disables.
|
|
437
|
-
const promptCacheCap = serverOptions.promptCacheBytes ?? 8e9;
|
|
438
|
-
const stateCodecs = ctx.stateCodecs ?? legacyCacheCodecs;
|
|
439
|
-
const cloneState = (caches: Cache[]) => cloneKvCaches(caches, stateCodecs);
|
|
440
|
-
let ssdStore: SsdCacheStore | null = null;
|
|
441
|
-
if (serverOptions.ssdCacheDir) {
|
|
442
|
-
if (promptCacheCap <= 0)
|
|
443
|
-
throw new Error("--ssd-cache requires the RAM prompt cache (--prompt-cache 0 disables it)");
|
|
444
|
-
if (typeof serving.stateCompatibility !== "string" || !serving.stateCompatibility.length)
|
|
445
|
-
throw new Error("--ssd-cache requires the model binding's stateCompatibility identity");
|
|
446
|
-
const schemeKey = resolvedKvScheme.cacheKey;
|
|
447
|
-
const stateKey = Bun.hash(serving.stateCompatibility).toString(16);
|
|
448
|
-
const tokJson = readFileSync(`${ctx.model.config.modelDir}/tokenizer.json`);
|
|
449
|
-
ssdStore = new SsdCacheStore({
|
|
450
|
-
codecs: stateCodecs,
|
|
451
|
-
dir: serverOptions.ssdCacheDir,
|
|
452
|
-
maxBytes: serverOptions.ssdCacheMaxBytes ?? Number.POSITIVE_INFINITY,
|
|
453
|
-
configFingerprint: `${configFingerprint(ctx.model.config)}-${schemeKey}-${stateKey}`,
|
|
454
|
-
tokenizerHash: Bun.hash(tokJson).toString(16),
|
|
455
|
-
modelId: ctx.modelId,
|
|
456
|
-
verify: serverOptions.ssdCacheVerify,
|
|
457
|
-
});
|
|
458
|
-
const recovered = ssdStore.scan();
|
|
459
|
-
const capacity = Number.isFinite(ssdStore.maxBytes)
|
|
460
|
-
? `${(ssdStore.maxBytes / 2 ** 30).toFixed(0)} GiB cap`
|
|
461
|
-
: "unlimited";
|
|
462
|
-
console.log(
|
|
463
|
-
`[ssd-cache] ${serverOptions.ssdCacheDir} — ${recovered} entr${recovered === 1 ? "y" : "ies"} recovered, ` +
|
|
464
|
-
`${(ssdStore.totalBytes / 2 ** 30).toFixed(2)} GiB, ${capacity}`,
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Layer 0 (unified-engine plan): the cold tier is bound INTO the prompt
|
|
469
|
-
// cache — take() itself tiers over it, so the batch scheduler and every
|
|
470
|
-
// other consumer get SSD restores through the same API; eviction AND
|
|
471
|
-
// idle demotion spill into it.
|
|
472
|
-
const coldTier = ssdStore
|
|
473
|
-
? {
|
|
474
|
-
find: (prompt: number[], ns: string) => {
|
|
475
|
-
const h = ssdStore!.find(prompt, ns);
|
|
476
|
-
return h ? { prefixLen: h.prefixLen, handle: h.entry } : null;
|
|
477
|
-
},
|
|
478
|
-
restore: (handle: unknown) => {
|
|
479
|
-
const loaded = serving.restore(ssdStore!, handle as import("./ssd-cache").SsdIndexEntry);
|
|
480
|
-
if (!loaded) return null;
|
|
481
|
-
// Restore is a STREAMED COPY (2026-07-07 A7-restore): the caches
|
|
482
|
-
// own their bytes and no mapping outlives loadKvCache — nothing
|
|
483
|
-
// to pin, nothing to unmap. retain stays in the entry contract
|
|
484
|
-
// as a no-op so callers' dispose ordering is unchanged.
|
|
485
|
-
return { tokens: loaded.tokens, caches: loaded.caches, attachments: loaded.attachments, retain: () => {} };
|
|
486
|
-
},
|
|
487
|
-
store: (tokens: number[], caches: import("./model/gemma4").Cache[], ns: string,
|
|
488
|
-
attachments?: import("./backends/mlx/checkpoint-state").CheckpointAttachment[]) => {
|
|
489
|
-
if (ssdStore!.hasDurablePrefix(tokens, ns)) return true;
|
|
490
|
-
return ssdStore!.store(tokens, caches, ns, attachments);
|
|
491
|
-
},
|
|
492
|
-
}
|
|
493
|
-
: null;
|
|
494
|
-
const promptCache = new PromptCache(
|
|
495
|
-
promptCacheCap,
|
|
496
|
-
// RAM eviction AND idle demotion spill to the cold tier NON-BLOCKING
|
|
497
|
-
// (2026-07-06, same contract as the write-behind snapshot below): the
|
|
498
|
-
// cache hands us OWNED zero-copy clones + copied tokens (made under
|
|
499
|
-
// the generation lock — microseconds; entries are immutable so the
|
|
500
|
-
// clones stay consistent), and the flush goes through the BOUNDED
|
|
501
|
-
// SpillQueue -> storeAsync (generation-locked per tensor, see below) ->
|
|
502
|
-
// clone disposal on every settle/drop path (that dispose is what
|
|
503
|
-
// actually frees the demoted GPU memory; the queue's byte cap keeps
|
|
504
|
-
// starved-gate retention bounded — 2026-07-07 review fix). spillQueue
|
|
505
|
-
// is declared with the snapshot scheduler below; safe to close over
|
|
506
|
-
// here because spills only fire at put()/demoteIdle time, long after
|
|
507
|
-
// init.
|
|
508
|
-
ssdStore
|
|
509
|
-
? {
|
|
510
|
-
spillOwned: (entry) =>
|
|
511
|
-
spillQueue!.enqueue({ tokens: entry.tokens, caches: entry.caches, ns: entry.ns, attachments: entry.attachments }),
|
|
512
|
-
}
|
|
513
|
-
: null,
|
|
514
|
-
coldTier,
|
|
515
|
-
cloneState,
|
|
516
|
-
);
|
|
517
|
-
// Responses-API store for previous_response_id resumption (Phase 11):
|
|
518
|
-
// TTL + byte-capped LRU, port of optiq/response_store.py. Pairs with
|
|
519
|
-
// the prompt cache: a resumed conversation re-renders the same prefix,
|
|
520
|
-
// so its KV prefill is already cached.
|
|
521
|
-
const responseStore = new ResponseStore();
|
|
522
|
-
|
|
523
|
-
// Memory estimates remain available in diagnostics. Only an explicit
|
|
524
|
-
// operator budget or context cap constrains request planning.
|
|
525
|
-
const memoryAdmission = ctx.glmMemoryPlan ?? fit(
|
|
526
|
-
ctx.model.config, ctx.model.weightsBytes, 1,
|
|
527
|
-
undefined, undefined, 0, serverOptions.memoryBudgetBytes,
|
|
528
|
-
resolvedKvScheme.fitOptions,
|
|
529
|
-
);
|
|
530
|
-
const profileContext = runtimeValue("MLX_BUN_RD_CONTEXT_LIMIT");
|
|
531
|
-
const profileLimit = profileContext === undefined ? null : Number(profileContext);
|
|
532
|
-
if (profileLimit !== null && (!Number.isSafeInteger(profileLimit) || profileLimit < 1))
|
|
533
|
-
throw new Error("MLX_BUN_RD_CONTEXT_LIMIT must be a positive integer");
|
|
534
|
-
const budgetLimit = serverOptions.memoryBudgetBytes !== undefined
|
|
535
|
-
? memoryAdmission.maxSafeContext : ctx.glmMemoryPlan?.contextTokens ?? null;
|
|
536
|
-
const contextLimit = profileLimit === null ? budgetLimit
|
|
537
|
-
: Math.min(profileLimit, budgetLimit ?? Infinity);
|
|
538
|
-
const admission = memoryAdmission;
|
|
539
|
-
const allocatorLimit =
|
|
540
|
-
admission.allocatorLimitBytes ?? serverOptions.memoryBudgetBytes;
|
|
541
|
-
if (allocatorLimit) setMemoryLimit(allocatorLimit);
|
|
542
|
-
|
|
543
|
-
let checkpointPersistence: ContinuationPersistence | undefined;
|
|
544
|
-
const executionServices: import("./backends/mlx/serial-executor").MlxSerialServices = {
|
|
545
|
-
get checkpointPersistence() { return checkpointPersistence; },
|
|
546
|
-
promptCache, checkpoints: ssdStore,
|
|
547
|
-
checkpointEveryTokens: serverOptions.generationCheckpointTokens,
|
|
548
|
-
identity: { artifact: ctx.profile.artifact, implementation: ctx.profile.profile.execution,
|
|
549
|
-
stateAbi: "legacy-cache-array-v1", codecs: stateCodecs.id },
|
|
550
|
-
adapterNamespace: (adapters) => ctx.adapters.cacheNamespace(adapters),
|
|
551
|
-
cloneState,
|
|
552
|
-
};
|
|
553
|
-
serving.gateway.configureContinuation?.(executionServices);
|
|
554
|
-
const runGeneration = serving.createSerial(executionServices);
|
|
555
|
-
|
|
556
|
-
// Write-behind persistence (restart survival — the oMLX boundary-snapshot
|
|
557
|
-
// idea at whole-entry granularity, spill-on-evict alone can't survive a
|
|
558
|
-
// clean exit): after a request completes, snapshot its still-RAM-resident
|
|
559
|
-
// entry to SSD off the request path. Debounced + coalesced per namespace —
|
|
560
|
-
// an agent hammering one conversation persists the settled state once
|
|
561
|
-
// things go quiet, not every turn. Runs under the gateway lock (byte
|
|
562
|
-
// extraction must not race a generation mutating the entry); if the entry
|
|
563
|
-
// was evicted (already spilled) or extended (a newer schedule pending)
|
|
564
|
-
// meanwhile, findExact misses and nothing is written.
|
|
565
|
-
// Keyed by namespace and exact tokens: a sub-second final [prompt+gen] put
|
|
566
|
-
// must not cancel the pending boundary snapshot, and unrelated prompts of
|
|
567
|
-
// equal length must not cancel each other. Exact reschedules still
|
|
568
|
-
// coalesce; stale keys self-clean when findExact misses.
|
|
569
|
-
//
|
|
570
|
-
// NON-BLOCKING (2026-07-06, the write-behind persistence contract): the
|
|
571
|
-
// gateway lock is held only for a zero-copy SNAPSHOT (findExact +
|
|
572
|
-
// cloneKvCaches — microseconds; entries are immutable so the clones are
|
|
573
|
-
// consistent forever). The flush itself runs OFF the lock via
|
|
574
|
-
// storeAsync, and writes chain serially so two multi-hundred-MB flushes
|
|
575
|
-
// never overlap.
|
|
576
|
-
//
|
|
577
|
-
// IDLE-GATED (2026-07-07, the decode@ctx fix): "off the lock" was not
|
|
578
|
-
// enough — every per-tensor flush step is a blocking GPU sync on the
|
|
579
|
-
// SAME stream decode uses (ops.contiguous enqueues a kernel, rawBytesView
|
|
580
|
-
// evals) plus a synchronous multi-MB writeSync, and the setImmediate
|
|
581
|
-
// pacing interleaved those slices exactly between decode tokens. A ~16k
|
|
582
|
-
// entry's flush overlapping the bench's ctx repeats depressed decode@ctx
|
|
583
|
-
// ~9% on e4b (mlx-lm runs no equivalent background work). Now every step
|
|
584
|
-
// — including the first — runs through gateway.runWhenIdle(), so the
|
|
585
|
-
// flush only progresses while NOTHING is generating and pauses when a
|
|
586
|
-
// request arrives mid-flush. Tradeoffs, accepted: durability waits for a
|
|
587
|
-
// quiet moment (single-user serving quiesces constantly; sustained
|
|
588
|
-
// hammering defers the flush AND the spill clones' GPU-memory release —
|
|
589
|
-
// bounded by the chain). MLX_BUN_SSD_WRITEBEHIND=0
|
|
590
|
-
// disables write-behind snapshots entirely — the paired-A/B lever + kill
|
|
591
|
-
// switch (restart survival then degrades to spill-on-evict only).
|
|
592
|
-
const writeBehindOn = runtimeValue("MLX_BUN_SSD_WRITEBEHIND") !== "0";
|
|
593
|
-
const ssdFlushStep = <T>(step: () => T): Promise<T> =>
|
|
594
|
-
gateway.runWhenIdle(async () => step());
|
|
595
|
-
// Bounded write-behind queue (2026-07-07 review fix — see SpillQueue in
|
|
596
|
-
// kv-store.ts): pending clones pin their entries' GPU buffers while the
|
|
597
|
-
// idle gate starves under sustained traffic, so QUEUED bytes are capped —
|
|
598
|
-
// over cap the oldest queued spill drops (clone disposed immediately, a
|
|
599
|
-
// future cache miss, never a wrong result) instead of accumulating past
|
|
600
|
-
// the prompt-cache cap. Default 2 GB (a quarter of the 8 GB RAM-cache
|
|
601
|
-
// default); MLX_BUN_SSD_SPILL_QUEUE_GB overrides. The durability
|
|
602
|
-
// coordinator retains a dirty record until the queue confirms the atomic
|
|
603
|
-
// store. Explicit flush and graceful shutdown can therefore retry a drop.
|
|
604
|
-
// `|| 2` would coerce an explicit "0" back to 2 GB — parse so 0 works
|
|
605
|
-
// (cap 0 = keep only the newest + in-flight clone pinned; the soft cap
|
|
606
|
-
// never drops the item just enqueued).
|
|
607
|
-
// The gateway exists before the spill queue because the SSD writer uses
|
|
608
|
-
// its idle boundary to avoid competing with generation.
|
|
609
|
-
const gateway = new GenerationGateway(serving.gateway, batch, runGeneration, {
|
|
610
|
-
kvBudgetBytes: serverOptions.kvBudgetBytes,
|
|
611
|
-
checkpoints: !!(serverOptions.generationCheckpointTokens && ssdStore),
|
|
612
|
-
stateCodecs,
|
|
613
|
-
kvScheme: resolvedKvScheme,
|
|
614
|
-
promptCache,
|
|
615
|
-
adapterNamespace: (adapters) => ctx.adapters.cacheNamespace(adapters),
|
|
616
|
-
});
|
|
617
|
-
const spillQueueGbRaw = Number(runtimeValue("MLX_BUN_SSD_SPILL_QUEUE_GB"));
|
|
618
|
-
const spillQueueCapBytes =
|
|
619
|
-
(Number.isFinite(spillQueueGbRaw) && spillQueueGbRaw >= 0 ? spillQueueGbRaw : 2) * 1024 ** 3;
|
|
620
|
-
// Both queues share the configured total: reserve half for checkpoints
|
|
621
|
-
// only when that service exists. Each retains the existing soft-cap rule.
|
|
622
|
-
const checkpointQueueBytes = ssdStore && serverOptions.generationCheckpointTokens
|
|
623
|
-
? Math.floor(spillQueueCapBytes / 2) : 0;
|
|
624
|
-
if (ssdStore && serverOptions.generationCheckpointTokens)
|
|
625
|
-
checkpointPersistence = new ContinuationPersistence(ssdStore, { maxBytes: checkpointQueueBytes, runStep: ssdFlushStep });
|
|
626
|
-
const spillQueue = ssdStore
|
|
627
|
-
? new SpillQueue(
|
|
628
|
-
spillQueueCapBytes - checkpointQueueBytes,
|
|
629
|
-
cacheBytes,
|
|
630
|
-
(item) => ssdStore!.storeAsync(item.tokens, item.caches, item.ns, ssdFlushStep, item.attachments),
|
|
631
|
-
(caches) => { for (const c of caches) c.dispose(); },
|
|
632
|
-
)
|
|
633
|
-
: null;
|
|
634
|
-
const durability = ssdStore && spillQueue && writeBehindOn
|
|
635
|
-
? new SsdDurabilityCoordinator(
|
|
636
|
-
gateway,
|
|
637
|
-
promptCache,
|
|
638
|
-
spillQueue,
|
|
639
|
-
cloneState,
|
|
640
|
-
(tokens, ns) => ssdStore.hasDurablePrefix(tokens, ns),
|
|
641
|
-
)
|
|
642
|
-
: null;
|
|
643
|
-
const durabilityStats = (): DurabilitySnapshotStats => {
|
|
644
|
-
const stats = durability?.stats ?? {
|
|
645
|
-
pendingSnapshots: 0,
|
|
646
|
-
pendingSpills: spillQueue?.pendingCount ?? 0,
|
|
647
|
-
pendingSpillBytes: spillQueue?.pendingBytes ?? 0,
|
|
648
|
-
droppedSpills: spillQueue?.droppedCount ?? 0,
|
|
649
|
-
failedSpills: spillQueue?.failedCount ?? 0,
|
|
650
|
-
};
|
|
651
|
-
const checkpoints = checkpointPersistence?.stats;
|
|
652
|
-
return { ...stats, pendingSpills: stats.pendingSpills + (checkpoints?.pendingCount ?? 0),
|
|
653
|
-
pendingSpillBytes: stats.pendingSpillBytes + (checkpoints?.pendingBytes ?? 0),
|
|
654
|
-
droppedSpills: stats.droppedSpills + (checkpoints?.dropped ?? 0),
|
|
655
|
-
failedSpills: stats.failedSpills + (checkpoints?.failed ?? 0) };
|
|
656
|
-
};
|
|
657
|
-
const flushDurability = async (): Promise<DurabilityFlushResult> => {
|
|
658
|
-
const started = performance.now();
|
|
659
|
-
const checkpoints = await checkpointPersistence?.flush();
|
|
660
|
-
const result = durability ? await durability.flush() : null;
|
|
661
|
-
if (!result) await spillQueue?.drain();
|
|
662
|
-
const stats = durabilityStats();
|
|
663
|
-
return {
|
|
664
|
-
...stats,
|
|
665
|
-
durable: (result?.durable ?? stats.pendingSpills === 0) && (checkpoints?.durable ?? true) && stats.pendingSpills === 0,
|
|
666
|
-
flushedSnapshots: result?.flushedSnapshots ?? 0,
|
|
667
|
-
missingSnapshots: result?.missingSnapshots ?? 0,
|
|
668
|
-
elapsedMs: performance.now() - started,
|
|
669
|
-
};
|
|
670
|
-
};
|
|
671
|
-
// Every put() — serial lane AND batch scheduler — schedules the snapshot
|
|
672
|
-
// (Layer 0: batch-lane entries survive restarts too, not just evictions).
|
|
673
|
-
promptCache.onPut = durability ? (tokens, ns) => durability.schedule(tokens, ns) : null;
|
|
674
|
-
|
|
675
|
-
// Idle demotion (Layer 0): entries unused for --ssd-demote-idle seconds
|
|
676
|
-
// spill to SSD and free their GPU memory — the RAM tier drains between
|
|
677
|
-
// bursts while every prefix stays reachable (take() restores via
|
|
678
|
-
// zero-copy mmap). The exclusive section below covers only the sweep's
|
|
679
|
-
// zero-copy clone + dispose (spillOwned above); the SSD write itself
|
|
680
|
-
// runs off-lock on ssdWriteChain. Swept only when the engine is TRULY idle: the
|
|
681
|
-
// runExclusive below registers as a serial waiter, which would DRAIN a
|
|
682
|
-
// running batch, so the activity check guards it (a momentary exclusive
|
|
683
|
-
// grab of an idle engine is free). 0 disables.
|
|
684
|
-
const demoteIdleMs = (serverOptions.ssdDemoteIdleSec ?? (ssdStore ? 300 : 0)) * 1000;
|
|
685
|
-
let demoteTimer: ReturnType<typeof setInterval> | null = null;
|
|
686
|
-
if (ssdStore && demoteIdleMs > 0) {
|
|
687
|
-
demoteTimer = setInterval(() => {
|
|
688
|
-
if (gateway.busy) return;
|
|
689
|
-
void gateway.runExclusive(async () => {
|
|
690
|
-
const n = promptCache.demoteIdle(demoteIdleMs);
|
|
691
|
-
if (n > 0) console.log(`[ssd-cache] demoted ${n} idle entr${n === 1 ? "y" : "ies"} to disk`);
|
|
692
|
-
}).catch(() => {});
|
|
693
|
-
}, Math.max(30_000, demoteIdleMs / 4));
|
|
694
|
-
demoteTimer.unref?.();
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
const sessionEngine = createSessionCompletionEngine(gateway, disposeUnstartedRequest);
|
|
698
|
-
const completionExecutor = new CompletionExecutor(sessionEngine);
|
|
699
|
-
|
|
700
|
-
// /library response cache (30 s) — registry + config reads only.
|
|
701
|
-
const startedAt = Date.now();
|
|
702
|
-
const discoveryRoutes = createDiscoveryRoutes(ctx, gateway, startedAt);
|
|
703
|
-
|
|
704
|
-
// Captured so the WebSocket handler can resolve the bound (possibly
|
|
705
|
-
// ephemeral) port lazily for the loopback pi provider.
|
|
706
|
-
let serverRef!: Server<unknown>;
|
|
707
|
-
|
|
708
|
-
// Lab job system (quantize / finetune / dataset), lazily opened so a
|
|
709
|
-
// plain serve with no Lab activity pays nothing. markZombies() recovers
|
|
710
|
-
// rows orphaned by a crashed prior process. The dataset runner is
|
|
711
|
-
// in-process (pure JS + loopback /v1); quantize and finetune run as
|
|
712
|
-
// GPU-leased subprocesses via src/jobs/job-entry.ts.
|
|
713
|
-
let jobStore: import("./jobs").JobStore | null = null;
|
|
714
|
-
const ensureJobs = async () => {
|
|
715
|
-
if (!jobStore) {
|
|
716
|
-
const jobs = await import("./jobs");
|
|
717
|
-
const store = new jobs.JobStore();
|
|
718
|
-
store.markZombies();
|
|
719
|
-
try { (await import("./dataset")).registerDatasetRunner(); } catch {}
|
|
720
|
-
jobStore = store;
|
|
721
|
-
}
|
|
722
|
-
return jobStore;
|
|
723
|
-
};
|
|
724
|
-
|
|
725
|
-
// Per-request preparation (options/template/prompt-ids/grammar/router)
|
|
726
|
-
// and the chat core, each built once with their collaborators injected.
|
|
727
|
-
// The request pipeline, composed once:
|
|
728
|
-
// new ChatRequest(body) → chatStage.run → InferenceRequest
|
|
729
|
-
// → inferenceStage.admit → inferenceStage.run → InferenceResult → wire
|
|
730
|
-
// (/v1/completions substitutes textStage; Anthropic and Responses reuse
|
|
731
|
-
// chatStage with their own wire formats.)
|
|
732
|
-
const prep = createRequestPrep({ ctx, serverOptions, kvScheme, defaultGeneratedTokens });
|
|
733
|
-
const { templateOptionsFor } = prep;
|
|
734
|
-
const preparation = createPreparationExecutor((work, signal) => gateway.runExclusive(work, undefined, signal), batch);
|
|
735
|
-
const chatStage = new ChatStage(
|
|
736
|
-
ctx, prep, promptCache, contextLimit, serverOptions.defaultAdapter,
|
|
737
|
-
preparation, serving.buildPrompt);
|
|
738
|
-
const textStage = new TextCompletionStage(
|
|
739
|
-
ctx, prep, contextLimit, defaultGeneratedTokens, serverOptions.defaultAdapter);
|
|
740
|
-
const inferenceStage = new InferenceStage(completionExecutor);
|
|
741
|
-
const openAiMeta = (id: string) => ({ id, created: Math.floor(Date.now() / 1000), model: ctx.modelId });
|
|
742
|
-
/** Parse the JSON body under the request's trace; a bad body is a 400. */
|
|
743
|
-
const parseBody = async <T,>(request: Request, trace: PromptResponseTrace | undefined): Promise<T | Response> => {
|
|
744
|
-
const closeBodyParse = trace?.begin("request.body_parse");
|
|
745
|
-
try {
|
|
746
|
-
return (await request.json()) as T;
|
|
747
|
-
} catch {
|
|
748
|
-
trace?.finish("error", { stage: "body_parse" });
|
|
749
|
-
return Response.json({ error: { message: "invalid JSON body" } }, { status: 400 });
|
|
750
|
-
} finally {
|
|
751
|
-
closeBodyParse?.();
|
|
752
|
-
}
|
|
753
|
-
};
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
if (serverOptions.unixSocket) {
|
|
758
|
-
try { require("node:fs").unlinkSync(serverOptions.unixSocket); } catch {}
|
|
759
|
-
}
|
|
760
|
-
// Bun's types make unix/port mutually exclusive variants that a spread
|
|
761
|
-
// union can't prove — runtime accepts either; cast once at the call.
|
|
762
|
-
serverRef = Bun.serve({
|
|
763
|
-
...(serverOptions.unixSocket
|
|
764
|
-
? ({ unix: serverOptions.unixSocket } as unknown as Record<string, never>)
|
|
765
|
-
: {
|
|
766
|
-
port,
|
|
767
|
-
...(serverOptions.hostname ? { hostname: serverOptions.hostname } : {}),
|
|
768
|
-
}),
|
|
769
|
-
idleTimeout: 0,
|
|
770
|
-
// Web chat rides pi's AgentSession events over a WebSocket; the embedded
|
|
771
|
-
// pi provider points back at THIS server's own loopback /v1 (port
|
|
772
|
-
// resolved lazily — it may be ephemeral until serve() binds).
|
|
773
|
-
websocket: makePiWsHandler({
|
|
774
|
-
port: () => serverRef.port ?? port,
|
|
775
|
-
modelId: ctx.modelId,
|
|
776
|
-
contextWindow: ctx.model.config.text.maxPositionEmbeddings,
|
|
777
|
-
// capability flags — true if a tower is loaded or loadable (lazy)
|
|
778
|
-
vision: !!(ctx.vision || ctx.loadVision),
|
|
779
|
-
audio: !!(ctx.audio || ctx.loadAudio),
|
|
780
|
-
thinking: ctx.template.supportsThinking,
|
|
781
|
-
// Model-author sampling defaults (generation_config.json, server-CLI
|
|
782
|
-
// overrides applied) for the sampling popover's per-model recommended
|
|
783
|
-
// values (web-ui-pass-plan.md #14 groundwork) — same resolution
|
|
784
|
-
// /v1/models' gen_defaults uses, so the two surfaces agree. The
|
|
785
|
-
// think-mode-vs-not distinction lives in toOptions (per-request); this
|
|
786
|
-
// is the single "model's own defaults" snapshot sent once at ready.
|
|
787
|
-
genDefaults: {
|
|
788
|
-
temperature: serverOptions.defaultTemperature ?? ctx.genDefaults.temperature ?? null,
|
|
789
|
-
topP: serverOptions.defaultTopP ?? ctx.genDefaults.topP ?? null,
|
|
790
|
-
topK: serverOptions.defaultTopK ?? ctx.genDefaults.topK ?? null,
|
|
791
|
-
},
|
|
792
|
-
}),
|
|
793
|
-
async fetch(request, server) {
|
|
794
|
-
const url = new URL(request.url);
|
|
795
|
-
|
|
796
|
-
if (url.pathname === "/admin/cache/flush" && request.method === "POST") {
|
|
797
|
-
const result = await flushDurability();
|
|
798
|
-
return Response.json(
|
|
799
|
-
{
|
|
800
|
-
...result,
|
|
801
|
-
entries: ssdStore?.entries ?? 0,
|
|
802
|
-
longest_durable_prefix_tokens:
|
|
803
|
-
ssdStore?.longestDurablePrefixTokens ?? 0,
|
|
804
|
-
},
|
|
805
|
-
{ status: result.durable ? 200 : 503 },
|
|
806
|
-
);
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
if (url.pathname === "/ws/chat") {
|
|
810
|
-
if (server.upgrade(request, { data: { sessionId: crypto.randomUUID() } as PiWsData }))
|
|
811
|
-
return undefined;
|
|
812
|
-
return new Response("expected websocket", { status: 426 });
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
const auxiliaryResponse = await handleAuxiliaryRoute(url, request);
|
|
816
|
-
if (auxiliaryResponse) return auxiliaryResponse;
|
|
817
|
-
|
|
818
|
-
const staticResponse = handleStaticRoute(url, request, STATIC_ROUTE_ASSETS);
|
|
819
|
-
if (staticResponse) return staticResponse;
|
|
820
|
-
|
|
821
|
-
const discoveryResponse = await discoveryRoutes.handle(url, request);
|
|
822
|
-
if (discoveryResponse) return discoveryResponse;
|
|
823
|
-
|
|
824
|
-
const modelAdminResponse = await handleModelAdminRoute(url, request, ctx, gateway);
|
|
825
|
-
if (modelAdminResponse) return modelAdminResponse;
|
|
826
|
-
|
|
827
|
-
if (url.pathname === "/fit" && request.method === "GET") {
|
|
828
|
-
// Fit assessment for the status page: this-machine report at the
|
|
829
|
-
// admission ceiling + the Apple SKU matrix at a fixed 32k.
|
|
830
|
-
// expertsBytes comes from the registry so MoE models predict on
|
|
831
|
-
// active bytes — same numbers as `mlx-bun fit` and the serve
|
|
832
|
-
// banner (the three surfaces used to disagree). When the eval DB
|
|
833
|
-
// has a real measurement for this snapshot, it rides along:
|
|
834
|
-
// measured beats predicted.
|
|
835
|
-
const { skuMatrix, thisMachine, detectChip } = await import("./fit");
|
|
836
|
-
const machine = thisMachine();
|
|
837
|
-
const chip = detectChip();
|
|
838
|
-
let expertsBytes = 0;
|
|
839
|
-
let artifactDiskBytes: number | null = null;
|
|
840
|
-
let measured: { decodeTps: number; ts: number } | null = null;
|
|
841
|
-
try {
|
|
842
|
-
const { Registry } = await import("./registry");
|
|
843
|
-
const rec = new Registry().list().find((r) => r.repoId === ctx.modelId);
|
|
844
|
-
if (rec) {
|
|
845
|
-
expertsBytes = rec.expertsBytes;
|
|
846
|
-
artifactDiskBytes = rec.sizeBytes;
|
|
847
|
-
const { EvalDB } = await import("./evaldb");
|
|
848
|
-
measured = new EvalDB().latestFor(rec.path);
|
|
849
|
-
}
|
|
850
|
-
} catch {}
|
|
851
|
-
if (ctx.glmMemoryPlan) {
|
|
852
|
-
const plan = ctx.glmMemoryPlan;
|
|
853
|
-
const li = plan.lineItems;
|
|
854
|
-
const kvBytes = li.targetKvBytes + li.mtpKvBytes;
|
|
855
|
-
const transientBytes = plan.plannedProcessBytes -
|
|
856
|
-
li.residentWeightsBytes - li.mainExpertSlabBytes -
|
|
857
|
-
li.mtpExpertSlabBytes - kvBytes;
|
|
858
|
-
return Response.json({
|
|
859
|
-
machine: {
|
|
860
|
-
chip: chip.name,
|
|
861
|
-
ram_bytes: machine.ramBytes,
|
|
862
|
-
bandwidth_gbs: machine.bandwidthGBs,
|
|
863
|
-
},
|
|
864
|
-
context_tokens: plan.contextTokens,
|
|
865
|
-
typical_context_tokens: plan.contextTokens,
|
|
866
|
-
typical_decode_tps: GLM52_G5_MEASURED_WARM_DECODE_TPS,
|
|
867
|
-
measured_decode_tps: GLM52_G5_MEASURED_WARM_DECODE_TPS,
|
|
868
|
-
measured_at: GLM52_G5_MEASURED_AT,
|
|
869
|
-
report: {
|
|
870
|
-
fits: true,
|
|
871
|
-
weights_bytes: li.residentWeightsBytes,
|
|
872
|
-
kv_bytes: kvBytes,
|
|
873
|
-
transient_bytes: transientBytes,
|
|
874
|
-
total_bytes: plan.plannedProcessBytes,
|
|
875
|
-
usable_bytes: plan.processLimitBytes,
|
|
876
|
-
max_safe_context: plan.contextTokens,
|
|
877
|
-
predicted_decode_tps: null,
|
|
878
|
-
},
|
|
879
|
-
glm52: {
|
|
880
|
-
artifact_disk_bytes: artifactDiskBytes,
|
|
881
|
-
main_expert_slab_bytes: li.mainExpertSlabBytes,
|
|
882
|
-
mtp_expert_slab_bytes: li.mtpExpertSlabBytes,
|
|
883
|
-
max_generation_tokens: plan.maxGenerationTokens,
|
|
884
|
-
direct_oracle_warm_decode_tps:
|
|
885
|
-
GLM52_G5_DIRECT_ORACLE_WARM_DECODE_TPS,
|
|
886
|
-
aspirational_decode_tps: GLM52_G5_ASPIRATIONAL_DECODE_TPS,
|
|
887
|
-
},
|
|
888
|
-
sku_matrix_ctx: plan.contextTokens,
|
|
889
|
-
sku_matrix: [{
|
|
890
|
-
sku: chip.name,
|
|
891
|
-
ram_gb: Math.round(machine.ramBytes / 2 ** 30),
|
|
892
|
-
fits: true,
|
|
893
|
-
max_context: plan.contextTokens,
|
|
894
|
-
decode_tps: GLM52_G5_MEASURED_WARM_DECODE_TPS,
|
|
895
|
-
}],
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
const report = fit(
|
|
899
|
-
ctx.model.config, ctx.model.weightsBytes, admission.maxSafeContext,
|
|
900
|
-
machine, undefined, expertsBytes, serverOptions.memoryBudgetBytes,
|
|
901
|
-
resolvedKvScheme.fitOptions,
|
|
902
|
-
);
|
|
903
|
-
return Response.json({
|
|
904
|
-
machine: { chip: chip.name, ram_bytes: machine.ramBytes, bandwidth_gbs: machine.bandwidthGBs },
|
|
905
|
-
context_tokens: admission.maxSafeContext,
|
|
906
|
-
// Headline number: prediction at a TYPICAL context (8k) — the
|
|
907
|
-
// max-context report below is the bandwidth worst case (every
|
|
908
|
-
// decode step re-reads the full KV), not the everyday speed.
|
|
909
|
-
typical_context_tokens: Math.min(8192, admission.maxSafeContext),
|
|
910
|
-
typical_decode_tps: fit(
|
|
911
|
-
ctx.model.config, ctx.model.weightsBytes,
|
|
912
|
-
Math.min(8192, admission.maxSafeContext),
|
|
913
|
-
machine, undefined, expertsBytes, serverOptions.memoryBudgetBytes,
|
|
914
|
-
resolvedKvScheme.fitOptions,
|
|
915
|
-
).predictedDecodeTps,
|
|
916
|
-
measured_decode_tps: measured?.decodeTps ?? null,
|
|
917
|
-
measured_at: measured?.ts ?? null,
|
|
918
|
-
report: {
|
|
919
|
-
fits: report.fits,
|
|
920
|
-
weights_bytes: report.weightsBytes,
|
|
921
|
-
kv_bytes: report.kvBytes,
|
|
922
|
-
transient_bytes: report.transientBytes,
|
|
923
|
-
total_bytes: report.totalBytes,
|
|
924
|
-
usable_bytes: report.usableBytes,
|
|
925
|
-
max_safe_context: report.maxSafeContext,
|
|
926
|
-
predicted_decode_tps: report.predictedDecodeTps,
|
|
927
|
-
},
|
|
928
|
-
sku_matrix_ctx: 32768,
|
|
929
|
-
sku_matrix: skuMatrix(ctx.model.config, ctx.model.weightsBytes, 32768, expertsBytes).map((r) => ({
|
|
930
|
-
sku: r.sku, ram_gb: r.ramGB, fits: r.fits,
|
|
931
|
-
max_context: r.maxContext, decode_tps: r.decodeTps,
|
|
932
|
-
})),
|
|
933
|
-
});
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
if (url.pathname === "/stats" && request.method === "GET") {
|
|
937
|
-
// Active KV scheme across ALL layers. Since Phase 9 rotating
|
|
938
|
-
// (sliding-window) caches quantize too, so every layer the
|
|
939
|
-
// scheme names counts — the old display filtered to
|
|
940
|
-
// full_attention and silently undercounted (e.g. 26B showed
|
|
941
|
-
// 5/30 quantized when its kv_config.json covers all 30).
|
|
942
|
-
const layerTypes = ctx.model.config.text.layerTypes;
|
|
943
|
-
const kvLayers: Record<string, number> = {};
|
|
944
|
-
let kvMode = "bf16";
|
|
945
|
-
if (kvScheme.turboQuant) {
|
|
946
|
-
// v1: full-attention layers only (sliding-window stays bf16 —
|
|
947
|
-
// docs/design/turboquant.md non-goal).
|
|
948
|
-
const fullAttn = layerTypes.filter((l) => l !== "sliding_attention").length;
|
|
949
|
-
kvMode = `turbo k${kvScheme.turboQuant.kBits}v${kvScheme.turboQuant.vBits}`;
|
|
950
|
-
kvLayers[`turbo-k${kvScheme.turboQuant.kBits}v${kvScheme.turboQuant.vBits}`] = fullAttn;
|
|
951
|
-
} else if (kvScheme.kvBits) {
|
|
952
|
-
kvMode = `uniform-kv${kvScheme.kvBits}`;
|
|
953
|
-
kvLayers[`kv${kvScheme.kvBits}`] = layerTypes.length;
|
|
954
|
-
} else if (kvScheme.kvConfig) {
|
|
955
|
-
kvMode = "mixed (kv_config.json)";
|
|
956
|
-
for (const e of kvScheme.kvConfig)
|
|
957
|
-
kvLayers[`kv${e.bits}`] = (kvLayers[`kv${e.bits}`] ?? 0) + 1;
|
|
958
|
-
}
|
|
959
|
-
const bf16Layers = layerTypes.length - Object.values(kvLayers).reduce((a, b) => a + b, 0);
|
|
960
|
-
const slidingLayers = layerTypes.filter((l) => l === "sliding_attention").length;
|
|
961
|
-
return Response.json({
|
|
962
|
-
server: {
|
|
963
|
-
owner: serverOptions.owner ?? "embedded",
|
|
964
|
-
model: ctx.modelId,
|
|
965
|
-
started_at: startedAt,
|
|
966
|
-
},
|
|
967
|
-
prompt_cache: {
|
|
968
|
-
entries: promptCache.size,
|
|
969
|
-
bytes: promptCache.totalBytes,
|
|
970
|
-
max_bytes: promptCache.maxBytes,
|
|
971
|
-
hits: promptCache.hits,
|
|
972
|
-
misses: promptCache.misses,
|
|
973
|
-
},
|
|
974
|
-
...(ssdStore ? {
|
|
975
|
-
ssd_cache: {
|
|
976
|
-
dir: serverOptions.ssdCacheDir,
|
|
977
|
-
entries: ssdStore.entries,
|
|
978
|
-
bytes: ssdStore.totalBytes,
|
|
979
|
-
max_bytes: Number.isFinite(ssdStore.maxBytes) ? ssdStore.maxBytes : null,
|
|
980
|
-
restores: ssdStore.stats.restores,
|
|
981
|
-
spills: ssdStore.stats.spills,
|
|
982
|
-
restore_ms_last: Math.round(ssdStore.stats.restoreMsLast),
|
|
983
|
-
demotions: promptCache.demotions,
|
|
984
|
-
pending_snapshots: durability?.stats.pendingSnapshots ?? 0,
|
|
985
|
-
pending_spills: spillQueue?.pendingCount ?? 0,
|
|
986
|
-
pending_spill_bytes: spillQueue?.pendingBytes ?? 0,
|
|
987
|
-
dropped_spills: spillQueue?.droppedCount ?? 0,
|
|
988
|
-
failed_spills: spillQueue?.failedCount ?? 0,
|
|
989
|
-
longest_durable_prefix_tokens: ssdStore.longestDurablePrefixTokens,
|
|
990
|
-
},
|
|
991
|
-
} : {}),
|
|
992
|
-
response_store: {
|
|
993
|
-
entries: responseStore.size,
|
|
994
|
-
bytes: responseStore.totalBytes,
|
|
995
|
-
max_bytes: responseStore.maxBytes,
|
|
996
|
-
ttl_ms: responseStore.ttlMs,
|
|
997
|
-
},
|
|
998
|
-
kv_quant: {
|
|
999
|
-
mode: kvMode,
|
|
1000
|
-
layers: { ...kvLayers, ...(bf16Layers > 0 ? { bf16: bf16Layers } : {}) },
|
|
1001
|
-
attention: {
|
|
1002
|
-
global: layerTypes.length - slidingLayers,
|
|
1003
|
-
sliding_window: slidingLayers,
|
|
1004
|
-
},
|
|
1005
|
-
},
|
|
1006
|
-
admission: {
|
|
1007
|
-
max_safe_context: admission.maxSafeContext,
|
|
1008
|
-
enforced_context_tokens: contextLimit,
|
|
1009
|
-
memory_budget_bytes:
|
|
1010
|
-
ctx.glmMemoryPlan?.processLimitBytes ??
|
|
1011
|
-
serverOptions.memoryBudgetBytes ?? null,
|
|
1012
|
-
usable_bytes: admission.usableBytes,
|
|
1013
|
-
weights_bytes: ctx.model.weightsBytes,
|
|
1014
|
-
},
|
|
1015
|
-
...serving.diagnostics(),
|
|
1016
|
-
// --batch: configured cap, whether batching is live for this model,
|
|
1017
|
-
// and rows currently decoding in the batch.
|
|
1018
|
-
batch: {
|
|
1019
|
-
configured: batch,
|
|
1020
|
-
mode: gateway.batchMode,
|
|
1021
|
-
batched: gateway.batchingEnabled,
|
|
1022
|
-
active_rows: gateway.activeRows,
|
|
1023
|
-
pending_rows: gateway.pendingRows,
|
|
1024
|
-
submitted_rows: gateway.submittedRows,
|
|
1025
|
-
kv_bytes: gateway.kvBytes.projected,
|
|
1026
|
-
kv_budget_bytes: gateway.kvBytes.budget,
|
|
1027
|
-
},
|
|
1028
|
-
});
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
// A parent-managed GPU job holds this response open. The connection owns
|
|
1032
|
-
// the native lease, so parent death cannot strand the worker lock.
|
|
1033
|
-
if (serverOptions.unixSocket && url.pathname === "/admin/lease" && request.method === "POST") {
|
|
1034
|
-
const lease = await gateway.acquireExecutionLease(request.signal);
|
|
1035
|
-
let released = false;
|
|
1036
|
-
const release = () => {
|
|
1037
|
-
if (released) return;
|
|
1038
|
-
released = true;
|
|
1039
|
-
request.signal.removeEventListener("abort", release);
|
|
1040
|
-
lease.dispose();
|
|
1041
|
-
};
|
|
1042
|
-
request.signal.addEventListener("abort", release, { once: true });
|
|
1043
|
-
if (request.signal.aborted) release();
|
|
1044
|
-
return new Response(new ReadableStream({
|
|
1045
|
-
start(controller) { controller.enqueue(new TextEncoder().encode("leased\n")); },
|
|
1046
|
-
cancel() { release(); },
|
|
1047
|
-
}), { headers: { "content-type": "application/octet-stream" } });
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
// Engine-mode admin (unix-socket children only — never exposed on
|
|
1051
|
-
// TCP): drain = quiesce the gateway + demote the whole prompt cache
|
|
1052
|
-
// to the SSD tier. The pool calls this before evicting a model
|
|
1053
|
-
// child, so its state survives the eviction losslessly.
|
|
1054
|
-
if (
|
|
1055
|
-
serverOptions.unixSocket &&
|
|
1056
|
-
url.pathname === "/admin/drain" &&
|
|
1057
|
-
request.method === "POST"
|
|
1058
|
-
) {
|
|
1059
|
-
await gateway.runExclusive(async () => {
|
|
1060
|
-
promptCache.demoteIdle(0);
|
|
1061
|
-
});
|
|
1062
|
-
return Response.json({ drained: true, demotions: promptCache.demotions });
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
// ---- Curve Designer: POST /signal {prompt} → next-token histogram over the curve's x-axis ----
|
|
1066
|
-
// One prefill forward; bins the real log-prob distribution so the editor can draw the
|
|
1067
|
-
// signal under the curve (you shape where the tokens actually are).
|
|
1068
|
-
if (url.pathname === "/signal" && request.method === "OPTIONS")
|
|
1069
|
-
return new Response(null, { headers: CURVE_CORS });
|
|
1070
|
-
if (url.pathname === "/signal" && request.method === "POST") {
|
|
1071
|
-
let sbody: { prompt?: string };
|
|
1072
|
-
try { sbody = (await request.json()) as typeof sbody; }
|
|
1073
|
-
catch { return Response.json({ error: "invalid JSON" }, { status: 400, headers: CURVE_CORS }); }
|
|
1074
|
-
let sids = ctx.tokenizer.encode(ctx.template.render([{ role: "user", content: typeof sbody.prompt === "string" ? sbody.prompt : "" }], templateOptionsFor({} as ChatRequestParams, null)));
|
|
1075
|
-
if (sids[0] === sids[1] && sids[0] === ctx.tokenizer.bosTokenId) sids = sids.slice(1);
|
|
1076
|
-
try {
|
|
1077
|
-
const NB = 80;
|
|
1078
|
-
// gateway.runExclusive: this raw forward must not run concurrently
|
|
1079
|
-
// with batched decode steps or a serial generation (GPU + shared
|
|
1080
|
-
// model state are single-owner — D3, one lock).
|
|
1081
|
-
const result = await gateway.runExclusive(() => serving.signal(sids, NB, CURVE_UMIN));
|
|
1082
|
-
return Response.json(result, { headers: CURVE_CORS });
|
|
1083
|
-
} catch (e) {
|
|
1084
|
-
return Response.json({ error: `signal failed: ${(e as Error).message}` }, { status: 500, headers: CURVE_CORS });
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
// ---- v2 HLG Curve Designer: POST /generate {prompt, curve, n, max_tokens, seed} ----
|
|
1089
|
-
// The drawn log-prob transfer curve REPLACES temperature+softmax entirely
|
|
1090
|
-
// (src/lab/curve/curve-sampler.ts). The browser editor calls this; same curve object the
|
|
1091
|
-
// tool's "Copy values" emits is the one the sampler consumes — one contract.
|
|
1092
|
-
if (url.pathname === "/generate" && request.method === "OPTIONS")
|
|
1093
|
-
return new Response(null, { headers: CURVE_CORS });
|
|
1094
|
-
if (url.pathname === "/generate" && request.method === "POST") {
|
|
1095
|
-
let body: { prompt?: string; curve?: CurveParams; n?: number; max_tokens?: number; seed?: number; default?: boolean };
|
|
1096
|
-
try { body = (await request.json()) as typeof body; }
|
|
1097
|
-
catch { return Response.json({ error: "invalid JSON" }, { status: 400, headers: CURVE_CORS }); }
|
|
1098
|
-
const curve = body.curve;
|
|
1099
|
-
// Identity / no shaped curve → fall back to the model's DEFAULT chat recipe
|
|
1100
|
-
// (temp + top-p + top-k) — the honest "what you'd get chatting" baseline, which
|
|
1101
|
-
// a smooth curve can't replicate (top-p/top-k are hard truncations).
|
|
1102
|
-
const useCurve = body.default !== true && Array.isArray(curve?.points) && curve.points.length >= 2;
|
|
1103
|
-
if (useCurve && !isMonotone(curve!))
|
|
1104
|
-
return Response.json({ error: "curve is not monotone — all segment slopes must be ≥ 0" }, { status: 400, headers: CURVE_CORS });
|
|
1105
|
-
const recipe = {
|
|
1106
|
-
temperature: serverOptions.defaultTemperature ?? ctx.genDefaults.temperature ?? 0.7,
|
|
1107
|
-
topP: serverOptions.defaultTopP ?? ctx.genDefaults.topP ?? 0,
|
|
1108
|
-
topK: serverOptions.defaultTopK ?? ctx.genDefaults.topK ?? 0,
|
|
1109
|
-
};
|
|
1110
|
-
const prompt = typeof body.prompt === "string" ? body.prompt : "";
|
|
1111
|
-
const n = Math.max(1, Math.min(8, Math.floor(Number(body.n) || 3)));
|
|
1112
|
-
const maxTokens = Math.max(1, Math.min(256, Math.floor(Number(body.max_tokens) || 80)));
|
|
1113
|
-
const baseSeed = Number.isFinite(body.seed) ? Number(body.seed) >>> 0 : (Date.now() & 0xffffffff);
|
|
1114
|
-
let ids = ctx.tokenizer.encode(ctx.template.render([{ role: "user", content: prompt }], templateOptionsFor({} as ChatRequestParams, null)));
|
|
1115
|
-
if (ids[0] === ids[1] && ids[0] === ctx.tokenizer.bosTokenId) ids = ids.slice(1);
|
|
1116
|
-
const samples: { text: string; junk: boolean }[] = [];
|
|
1117
|
-
try {
|
|
1118
|
-
for (let i = 0; i < n; i++) {
|
|
1119
|
-
const toks: number[] = [];
|
|
1120
|
-
const genOpts: GenerateOptions = useCurve
|
|
1121
|
-
? { curve, seed: baseSeed + i, maxTokens, ...kvScheme }
|
|
1122
|
-
: { temperature: recipe.temperature, topP: recipe.topP, topK: recipe.topK, seed: baseSeed + i, maxTokens, ...kvScheme };
|
|
1123
|
-
// The same plan and execution lease cover the Lab comparison endpoint.
|
|
1124
|
-
const shape = { hasVision: false, hasAdapters: false, hasRepetitionPenalty: false,
|
|
1125
|
-
hasLogitsExtras: false, wantsLogprobs: false, userSeed: true,
|
|
1126
|
-
kvQuant: !!(genOpts.kvBits || genOpts.kvConfig?.length), turboQuant: !!genOpts.turboQuant,
|
|
1127
|
-
hasGrammar: false, hasDraft: !!ctx.draft };
|
|
1128
|
-
const placement = gateway.place(shape, genOpts);
|
|
1129
|
-
await gateway.run(ids, genOpts, (t) => { toks.push(t); }, undefined,
|
|
1130
|
-
shape, placement, request.signal);
|
|
1131
|
-
const text = ctx.tokenizer.decode(toks, true).trim();
|
|
1132
|
-
samples.push({ text, junk: curveJunk(text) });
|
|
1133
|
-
}
|
|
1134
|
-
} catch (e) {
|
|
1135
|
-
return Response.json({ error: `generation failed: ${(e as Error).message}` }, { status: 500, headers: CURVE_CORS });
|
|
1136
|
-
}
|
|
1137
|
-
return Response.json({ mode: useCurve ? "curve" : "default", recipe: useCurve ? undefined : recipe, n, seed: baseSeed, samples }, { headers: CURVE_CORS });
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
if (url.pathname === "/v1/chat/completions" && request.method === "POST") {
|
|
1141
|
-
const id = `chatcmpl-${crypto.randomUUID()}`;
|
|
1142
|
-
const trace = createPromptResponseTrace({
|
|
1143
|
-
traceId: request.headers.get("x-mlx-bun-trace-id") ?? id,
|
|
1144
|
-
requestId: id,
|
|
1145
|
-
route: url.pathname,
|
|
1146
|
-
});
|
|
1147
|
-
const body = await parseBody<ChatRequestParams>(request, trace);
|
|
1148
|
-
if (body instanceof Response) return body;
|
|
1149
|
-
const meta = openAiMeta(id);
|
|
1150
|
-
const a = await admit(
|
|
1151
|
-
inferenceStage, () => chatStage.run(new ChatRequest(body), id, request.signal), trace, "chat request");
|
|
1152
|
-
if ("response" in a) return a.response;
|
|
1153
|
-
return body.stream
|
|
1154
|
-
? respondStream(inferenceStage, a.admitted, chatCompletionStream(meta), request.signal, trace)
|
|
1155
|
-
: respondJson(inferenceStage, a.admitted, (r) => chatCompletionJson(r, meta), request.signal, trace);
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
// Raw text completion (mlx_lm.server's /v1/completions): no chat
|
|
1159
|
-
// template — TextCompletionStage tokenizes the prompt string directly.
|
|
1160
|
-
if (url.pathname === "/v1/completions" && request.method === "POST") {
|
|
1161
|
-
const id = `cmpl-${crypto.randomUUID()}`;
|
|
1162
|
-
const trace = createPromptResponseTrace({
|
|
1163
|
-
traceId: request.headers.get("x-mlx-bun-trace-id") ?? id,
|
|
1164
|
-
requestId: id,
|
|
1165
|
-
route: url.pathname,
|
|
1166
|
-
});
|
|
1167
|
-
const body = await parseBody<TextCompletionParams>(request, trace);
|
|
1168
|
-
if (body instanceof Response) return body;
|
|
1169
|
-
const meta = openAiMeta(id);
|
|
1170
|
-
const a = await admit(
|
|
1171
|
-
inferenceStage, () => textStage.run(new TextCompletionRequest(body), id), trace, "text completion");
|
|
1172
|
-
if ("response" in a) return a.response;
|
|
1173
|
-
return body.stream
|
|
1174
|
-
? respondStream(inferenceStage, a.admitted, textCompletionStream(meta), request.signal, trace)
|
|
1175
|
-
: respondJson(inferenceStage, a.admitted, (r) => textCompletionJson(r, meta), request.signal, trace);
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
// Anthropic Messages API (Phase 11) — on by default, mirroring
|
|
1179
|
-
// optiq serve (--anthropic defaults True; the drop-in claim
|
|
1180
|
-
// depends on it). Oracle: optiq/anthropic_shim.py, ported in
|
|
1181
|
-
// src/anthropic.ts. Point Claude Code at this port via
|
|
1182
|
-
// ANTHROPIC_BASE_URL for a fully local backend.
|
|
1183
|
-
if (url.pathname === "/v1/messages" && request.method === "POST") {
|
|
1184
|
-
const anthropicError: ErrorFormatter = (status, message) =>
|
|
1185
|
-
Response.json(
|
|
1186
|
-
{ type: "error", error: { type: status >= 500 ? "api_error" : "invalid_request_error", message } },
|
|
1187
|
-
{ status },
|
|
1188
|
-
);
|
|
1189
|
-
let anthropicBody: AnthropicRequest;
|
|
1190
|
-
try {
|
|
1191
|
-
anthropicBody = (await request.json()) as AnthropicRequest;
|
|
1192
|
-
} catch {
|
|
1193
|
-
return anthropicError(400, "invalid JSON body", {});
|
|
1194
|
-
}
|
|
1195
|
-
let chatBody: ChatRequestParams;
|
|
1196
|
-
try {
|
|
1197
|
-
chatBody = anthropicToChatBody(anthropicBody) as unknown as ChatRequestParams;
|
|
1198
|
-
} catch (e) {
|
|
1199
|
-
return anthropicError(400, (e as Error).message, {});
|
|
1200
|
-
}
|
|
1201
|
-
const id = `chatcmpl-${crypto.randomUUID()}`;
|
|
1202
|
-
const a = await admit(
|
|
1203
|
-
inferenceStage, () => chatStage.run(new ChatRequest(chatBody), id, request.signal), undefined,
|
|
1204
|
-
"anthropic request", anthropicError);
|
|
1205
|
-
if ("response" in a) return a.response;
|
|
1206
|
-
if (anthropicBody.stream)
|
|
1207
|
-
return respondStream(
|
|
1208
|
-
inferenceStage, a.admitted, createAnthropicStreamProtocol(ctx.modelId), request.signal);
|
|
1209
|
-
return respondJson(
|
|
1210
|
-
inferenceStage, a.admitted,
|
|
1211
|
-
(r) => chatJsonToAnthropic(chatCompletionJson(r, openAiMeta(id)), ctx.modelId),
|
|
1212
|
-
request.signal, undefined, anthropicError);
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
// OpenAI Responses API (Phase 11) — Codex/Cursor/Continue speak
|
|
1216
|
-
// this now. Oracle: optiq/responses_shim.py + responses_server.py,
|
|
1217
|
-
// ported in src/responses.ts. previous_response_id resumes a prior
|
|
1218
|
-
// conversation from the in-process store (TTL + byte-capped LRU).
|
|
1219
|
-
if (url.pathname === "/v1/responses" && request.method === "POST") {
|
|
1220
|
-
const responsesError: ErrorFormatter = (status, message) =>
|
|
1221
|
-
Response.json(
|
|
1222
|
-
{
|
|
1223
|
-
error: {
|
|
1224
|
-
message,
|
|
1225
|
-
type: status >= 500 ? "server_error" : "invalid_request_error",
|
|
1226
|
-
param: null, code: null,
|
|
1227
|
-
},
|
|
1228
|
-
},
|
|
1229
|
-
{ status },
|
|
1230
|
-
);
|
|
1231
|
-
let responsesBody: ResponsesRequest;
|
|
1232
|
-
try {
|
|
1233
|
-
responsesBody = (await request.json()) as ResponsesRequest;
|
|
1234
|
-
} catch {
|
|
1235
|
-
return responsesError(400, "invalid JSON body", {});
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
let conversation: ReturnType<typeof resolveResponsesConversation>;
|
|
1239
|
-
try { conversation = resolveResponsesConversation(responsesBody, responseStore); }
|
|
1240
|
-
catch (error) { return responsesError(404, (error as Error).message, {}); }
|
|
1241
|
-
responsesBody = conversation.body;
|
|
1242
|
-
const prevId = conversation.previousId;
|
|
1243
|
-
const capturedInput = conversation.input;
|
|
1244
|
-
const capturedInstructions = conversation.instructions;
|
|
1245
|
-
const storeHere = !(serverOptions.unixSocket && request.headers.get("x-mlx-bun-response-owner") === "parent");
|
|
1246
|
-
const remember = (id: string, output: unknown[]) => {
|
|
1247
|
-
if (storeHere) responseStore.put(id, { input: capturedInput, output, instructions: capturedInstructions });
|
|
1248
|
-
};
|
|
1249
|
-
|
|
1250
|
-
let chatBody: ChatRequestParams;
|
|
1251
|
-
try {
|
|
1252
|
-
chatBody = responsesToChatBody(responsesBody) as unknown as ChatRequestParams;
|
|
1253
|
-
} catch (e) {
|
|
1254
|
-
return responsesError(400, (e as Error).message, {});
|
|
1255
|
-
}
|
|
1256
|
-
const id = `chatcmpl-${crypto.randomUUID()}`;
|
|
1257
|
-
const a = await admit(
|
|
1258
|
-
inferenceStage, () => chatStage.run(new ChatRequest(chatBody), id, request.signal), undefined,
|
|
1259
|
-
"responses request", responsesError);
|
|
1260
|
-
if ("response" in a) return a.response;
|
|
1261
|
-
if (responsesBody.stream)
|
|
1262
|
-
return respondStream(
|
|
1263
|
-
inferenceStage, a.admitted,
|
|
1264
|
-
createResponsesStreamProtocol(
|
|
1265
|
-
ctx.modelId,
|
|
1266
|
-
prevId,
|
|
1267
|
-
(final) =>
|
|
1268
|
-
remember(final.id as string, final.output as unknown[]),
|
|
1269
|
-
),
|
|
1270
|
-
request.signal);
|
|
1271
|
-
return respondJson(
|
|
1272
|
-
inferenceStage, a.admitted,
|
|
1273
|
-
(r) => {
|
|
1274
|
-
const responses = chatJsonToResponses(chatCompletionJson(r, openAiMeta(id)), ctx.modelId, prevId);
|
|
1275
|
-
remember(responses.id as string, responses.output as unknown[]);
|
|
1276
|
-
return responses;
|
|
1277
|
-
},
|
|
1278
|
-
request.signal, undefined, responsesError);
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
const labResponse = await handleLabRoute(url, request, {
|
|
1282
|
-
ensureJobs,
|
|
1283
|
-
acquireGpu: (signal) => gateway.acquireExecutionLease(signal),
|
|
1284
|
-
serverPort: () => server.port,
|
|
1285
|
-
invalidateLibrary: () => discoveryRoutes.invalidateLibrary(),
|
|
1286
|
-
});
|
|
1287
|
-
if (labResponse) return labResponse;
|
|
1288
|
-
|
|
1289
|
-
const adminResponse = await handleAdminRoute(url, request, {
|
|
1290
|
-
ensureJobs,
|
|
1291
|
-
invalidateLibrary: () => discoveryRoutes.invalidateLibrary(),
|
|
1292
|
-
});
|
|
1293
|
-
if (adminResponse) return adminResponse;
|
|
1294
|
-
|
|
1295
|
-
return Response.json({ error: { message: "not found" } }, { status: 404 });
|
|
1296
|
-
},
|
|
1297
|
-
});
|
|
1298
|
-
serverLifecycles.set(serverRef, {
|
|
1299
|
-
stopJobs: async () => {
|
|
1300
|
-
if (jobStore) {
|
|
1301
|
-
const jobs = await import("./jobs");
|
|
1302
|
-
await Promise.all([jobs.closeSubprocessJobs(jobStore), jobs.closeInProcessJobs(jobStore)]);
|
|
1303
|
-
}
|
|
1304
|
-
},
|
|
1305
|
-
close: async () => { preparation.close(); await sessionEngine.close(); await gateway.close(); },
|
|
1306
|
-
flush: flushDurability,
|
|
1307
|
-
stats: durabilityStats,
|
|
1308
|
-
stopTimers: () => {
|
|
1309
|
-
if (demoteTimer) clearInterval(demoteTimer);
|
|
1310
|
-
demoteTimer = null;
|
|
1311
|
-
},
|
|
1312
|
-
});
|
|
1313
|
-
return serverRef;
|
|
1314
|
-
}
|