mlx-bun 0.5.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1449 -109
- package/THIRD_PARTY_NOTICES.md +882 -0
- package/bin/mlx-bun.convert.mjs +4 -0
- package/bin/mlx-bun.fuse.mjs +4 -0
- package/bin/mlx-bun.generate.mjs +4 -0
- package/bin/mlx-bun.lora.mjs +4 -0
- package/bin/mlx-bun.mjs +13 -21
- package/bin/mlx-bun.server.mjs +4 -0
- package/bin/mlx-bun.upload.mjs +4 -0
- package/dist/web/app.js +9584 -0
- package/package.json +50 -53
- package/src/cli/ane-install.ts +76 -0
- package/src/cli/args.ts +179 -0
- package/src/cli/engine-entry.ts +68 -0
- package/src/cli/inference.ts +214 -0
- package/src/cli/job-entry.ts +97 -0
- package/src/cli/library-host.ts +117 -0
- package/src/cli/main.ts +57 -0
- package/src/cli/memory-engine.ts +163 -0
- package/src/cli/memory.ts +36 -0
- package/src/cli/mlx-lm-aliases.ts +309 -0
- package/src/cli/model-choice.ts +33 -0
- package/src/cli/model-selection.ts +84 -0
- package/src/cli/module-host.ts +11 -0
- package/src/cli/module-verbs.ts +50 -0
- package/src/cli/numerical-policy.ts +12 -0
- package/src/cli/perplexity.ts +89 -0
- package/src/cli/serve-host.ts +423 -0
- package/src/cli/serve-isolated.ts +217 -0
- package/src/cli/serve-options.ts +106 -0
- package/src/cli/serve-state.ts +205 -0
- package/src/cli/serve.ts +500 -0
- package/src/cli/served-model-host.ts +102 -0
- package/src/cli/server-entry.ts +148 -0
- package/src/cli/serving-unit.ts +177 -0
- package/src/{tui.ts → cli/terminal.ts} +13 -8
- package/src/cli/worker-adapters.ts +34 -0
- package/src/cli/worker-entry.ts +272 -0
- package/src/cli/worker-unit.ts +179 -0
- package/src/engine/adapter-operation.ts +26 -0
- package/src/engine/cache-services.ts +114 -0
- package/src/engine/completion.ts +39 -0
- package/src/engine/generation-gateway.ts +358 -0
- package/src/engine/index.ts +59 -0
- package/src/engine/kv-budget.ts +120 -0
- package/src/engine/media-preparation.ts +232 -0
- package/src/engine/model-binding.ts +24 -0
- package/src/engine/model-host.ts +513 -0
- package/src/engine/model-serving.ts +89 -0
- package/src/{serve → engine}/preparation.ts +7 -5
- package/src/{serve → engine}/session-completion-engine.ts +6 -6
- package/src/engine/telemetry.ts +109 -0
- package/src/hub/downloads.ts +116 -0
- package/src/jobs/db.ts +16 -7
- package/src/jobs/events.ts +29 -0
- package/src/jobs/executable.ts +5 -0
- package/src/jobs/host.ts +51 -0
- package/src/jobs/in-process.ts +55 -0
- package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
- package/src/jobs/runner.ts +150 -231
- package/src/jobs/service.ts +75 -0
- package/src/jobs/sse.ts +59 -23
- package/src/jobs/worker-memory.ts +46 -0
- package/src/jobs/worker-process.ts +163 -0
- package/src/jobs/worker-supervisor.ts +258 -0
- package/src/modules.ts +58 -0
- package/src/publishing/catalog-hub.ts +18 -0
- package/src/publishing/credentials.ts +31 -0
- package/src/publishing/upload.ts +37 -0
- package/src/residency/leased-adapters.ts +21 -0
- package/src/residency/local-records.ts +16 -0
- package/src/residency/model-residency.ts +405 -0
- package/src/residency/record-index.ts +28 -0
- package/src/residency/resident-estimate.ts +50 -0
- package/src/{anthropic.ts → server/anthropic.ts} +3 -81
- package/src/server/cache-routes.ts +36 -0
- package/src/{serve → server}/chat-request.ts +10 -10
- package/src/{serve → server}/chat-stage.ts +14 -14
- package/src/{client.ts → server/client.ts} +28 -2
- package/src/{serve → server}/completion-executor.ts +33 -41
- package/src/server/decision-body.ts +55 -0
- package/src/server/decision-routes.ts +134 -0
- package/src/server/decision-wire.ts +228 -0
- package/src/{serve → server}/discovery-routes.ts +25 -82
- package/src/{serve → server}/http.ts +25 -13
- package/src/{serve → server}/inference-request.ts +5 -5
- package/src/server/job-routes.ts +20 -0
- package/src/server/media-prompt.ts +63 -0
- package/src/server/memory-completion-client.ts +46 -0
- package/src/server/model-routes.ts +192 -0
- package/src/{serve → server}/openai-wire.ts +3 -2
- package/src/{serve → server}/pipeline.ts +1 -1
- package/src/server/prompt-builder.ts +13 -0
- package/src/server/prompt-contracts.ts +17 -0
- package/src/server/proxy-routes.ts +187 -0
- package/src/server/publishing-routes.ts +41 -0
- package/src/{serve → server}/request-plan.ts +6 -8
- package/src/{serve → server}/request-prep.ts +47 -50
- package/src/{serve → server}/responses-client.ts +30 -13
- package/src/{responses.ts → server/responses.ts} +12 -93
- package/src/server/routes.ts +205 -0
- package/src/server/start.ts +72 -0
- package/src/server/status-routes.ts +108 -0
- package/src/{serve → server}/text-completion-stage.ts +25 -4
- package/src/server/text-prompt.ts +12 -0
- package/src/{serve → server}/token-streams.ts +36 -38
- package/src/server/worker-routes.ts +311 -0
- package/src/storage/paths.ts +61 -0
- package/src/web/assets.ts +37 -0
- package/src/web/{src → browser}/api.ts +5 -23
- package/src/web/browser/chat-host.ts +17 -0
- package/src/web/browser/installed-panels.ts +6 -0
- package/src/web/browser/main.ts +55 -0
- package/src/web/browser/markdown.ts +279 -0
- package/src/web/browser/memory-panel.ts +9 -0
- package/src/web/browser/palette.ts +64 -0
- package/src/web/browser/protocol.ts +13 -0
- package/src/web/browser/shell.ts +467 -0
- package/src/web/{src → browser}/status.ts +32 -36
- package/src/web/build.ts +51 -0
- package/src/web/public/app.html +1054 -0
- package/src/web/{sw.js → public/sw.js} +4 -3
- package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
- package/src/web/{vendor → public/vendor}/hljs.js +2 -2
- package/THIRD_PARTY_LICENSES.md +0 -51
- package/docs/README.md +0 -107
- package/docs/archive/hlg-sampling.md +0 -350
- package/docs/archive/investigations/audio-input-plan.md +0 -356
- package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +0 -3617
- package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
- package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
- package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
- package/docs/archive/investigations/curve-bisector-routing.md +0 -123
- package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
- package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
- package/docs/archive/investigations/dspark-handoff.md +0 -224
- package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
- package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
- package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
- package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
- package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
- package/docs/archive/investigations/lab-build-journal.md +0 -157
- package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
- package/docs/archive/investigations/openjarvis-fit.md +0 -198
- package/docs/archive/investigations/openwiki-evaluation.md +0 -249
- package/docs/archive/investigations/optimization_plan.md +0 -346
- package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
- package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
- package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
- package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
- package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
- package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
- package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
- package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
- package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
- package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
- package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
- package/docs/archive/planning/chat-ui-vision.md +0 -50
- package/docs/archive/planning/chunk-task-roadmap.md +0 -77
- package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
- package/docs/archive/planning/journal.md +0 -316
- package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
- package/docs/archive/planning/pi-integration-review.md +0 -178
- package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
- package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
- package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
- package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
- package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
- package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
- package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
- package/docs/archive/planning/release-notes-v0.4.0.md +0 -45
- package/docs/archive/planning/release-notes-v0.5.0.md +0 -37
- package/docs/archive/planning/web-ui-pass-plan.md +0 -213
- package/docs/archive/planning/website-readme-pass-plan.md +0 -224
- package/docs/design/batching.md +0 -2054
- package/docs/design/decode-speed-program.md +0 -2404
- package/docs/design/dreaming-nightly-pipeline.md +0 -684
- package/docs/design/generic-model-support.md +0 -821
- package/docs/design/kv-cache.md +0 -993
- package/docs/design/orpo-training.md +0 -680
- package/docs/design/speculative-decoding.md +0 -2129
- package/docs/design/turboquant.md +0 -2225
- package/docs/design/unified-engine-frontier-plan.md +0 -1987
- package/docs/design/web-chat-redesign.md +0 -695
- package/docs/planning/PRODUCT_ROADMAP.md +0 -593
- package/docs/planning/ResearchTopics.md +0 -119
- package/docs/planning/public-readiness-handoff.md +0 -244
- package/docs/planning/public-readiness.md +0 -149
- package/docs/planning/video-series.md +0 -421
- package/docs/reference/benchmarks.md +0 -3587
- package/docs/reference/cli.md +0 -646
- package/docs/reference/distribution.md +0 -380
- package/docs/reference/environment.md +0 -494
- package/docs/reference/glossary.md +0 -39
- package/docs/reference/library-api.md +0 -688
- package/docs/reference/memory.md +0 -260
- package/docs/reference/models.md +0 -346
- package/docs/reference/server-api.md +0 -1945
- package/docs/reference/server-config.md +0 -973
- package/docs/reference/training.md +0 -462
- package/docs/reference/troubleshooting.md +0 -158
- package/src/admission-plan.ts +0 -18
- package/src/audio/audiotoolbox.ts +0 -112
- package/src/audio/conformer.ts +0 -655
- package/src/audio/decode.ts +0 -133
- package/src/audio/features.ts +0 -202
- package/src/audio/mic-capture.ts +0 -98
- package/src/audio/silero-vad.ts +0 -379
- package/src/audio/transcode.ts +0 -67
- package/src/audio/whisper-decode.ts +0 -774
- package/src/audio/whisper-fast.ts +0 -732
- package/src/audio/whisper-format.ts +0 -73
- package/src/audio/whisper-mel.ts +0 -185
- package/src/audio/whisper-timing.ts +0 -274
- package/src/audio/whisper-tokenizer.ts +0 -239
- package/src/audio/whisper-transcribe.ts +0 -410
- package/src/backends/mlx/assistant-target.ts +0 -46
- package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
- package/src/backends/mlx/autoregressive.ts +0 -129
- package/src/backends/mlx/batch-group.ts +0 -1874
- package/src/backends/mlx/cache-attachment.ts +0 -67
- package/src/backends/mlx/cache-layout.ts +0 -31
- package/src/backends/mlx/checkpoint-state.ts +0 -53
- package/src/backends/mlx/continuation-persistence.ts +0 -107
- package/src/backends/mlx/continuation-request.ts +0 -43
- package/src/backends/mlx/continuation.ts +0 -77
- package/src/backends/mlx/diffusion.ts +0 -39
- package/src/backends/mlx/draft-target.ts +0 -32
- package/src/backends/mlx/encoder-cache.ts +0 -25
- package/src/backends/mlx/evaluation.ts +0 -105
- package/src/backends/mlx/fill-append.ts +0 -58
- package/src/backends/mlx/fill-group.ts +0 -497
- package/src/backends/mlx/full-kv-row-append.ts +0 -32
- package/src/backends/mlx/full-kv-row-donor.ts +0 -54
- package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
- package/src/backends/mlx/gateway-binding.ts +0 -174
- package/src/backends/mlx/graph.ts +0 -38
- package/src/backends/mlx/kv-codec.ts +0 -9
- package/src/backends/mlx/kv-maintenance.ts +0 -161
- package/src/backends/mlx/kv-tensor-rows.ts +0 -212
- package/src/backends/mlx/memory-client.ts +0 -189
- package/src/backends/mlx/methods.ts +0 -90
- package/src/backends/mlx/mixed-iteration.ts +0 -62
- package/src/backends/mlx/model-prompt.ts +0 -178
- package/src/backends/mlx/model-serving.ts +0 -80
- package/src/backends/mlx/paged-cache-codec.ts +0 -27
- package/src/backends/mlx/prefill-cohort.ts +0 -100
- package/src/backends/mlx/prefill-policy.ts +0 -26
- package/src/backends/mlx/prefill-rows.ts +0 -244
- package/src/backends/mlx/prefill.ts +0 -33
- package/src/backends/mlx/projected-context-rows.ts +0 -72
- package/src/backends/mlx/prompt-input.ts +0 -29
- package/src/backends/mlx/qwen-prompt-input.ts +0 -40
- package/src/backends/mlx/request-policy.ts +0 -20
- package/src/backends/mlx/request-state-policy.ts +0 -54
- package/src/backends/mlx/rollback.ts +0 -92
- package/src/backends/mlx/row-sampling.ts +0 -49
- package/src/backends/mlx/serial-executor.ts +0 -251
- package/src/backends/mlx/speculative-group.ts +0 -438
- package/src/backends/mlx/speculative-round.ts +0 -161
- package/src/backends/mlx/speculative.ts +0 -131
- package/src/backends/mlx/state-rows.ts +0 -104
- package/src/backends/mlx/state-views.ts +0 -46
- package/src/backends/mlx/target-layout-capability.ts +0 -35
- package/src/backends/mlx/text-engine.ts +0 -73
- package/src/backends/mlx/transitioning-kv-rows.ts +0 -173
- package/src/chat-template.ts +0 -382
- package/src/cli.ts +0 -3490
- package/src/config.ts +0 -399
- package/src/contracts/completion.ts +0 -16
- package/src/contracts/execution.ts +0 -70
- package/src/contracts/generation.ts +0 -103
- package/src/contracts/host.ts +0 -7
- package/src/contracts/object-cache.ts +0 -8
- package/src/contracts/pi-provider.ts +0 -30
- package/src/contracts/pi.ts +0 -261
- package/src/contracts/prefix-cache.ts +0 -33
- package/src/contracts/resources.ts +0 -25
- package/src/contracts/sampling.ts +0 -23
- package/src/contracts/scheduling.ts +0 -54
- package/src/contracts/tsconfig.json +0 -14
- package/src/dataset/generators.ts +0 -1087
- package/src/dataset/index.ts +0 -12
- package/src/dataset/job.ts +0 -81
- package/src/dataset/llm.ts +0 -71
- package/src/dataset/registry.ts +0 -528
- package/src/diffusion/diffusion-generate.ts +0 -507
- package/src/download.ts +0 -505
- package/src/embed.ts +0 -77
- package/src/engine/admission.ts +0 -59
- package/src/engine/cancellation.ts +0 -38
- package/src/engine/engine.ts +0 -56
- package/src/engine/execution-coordinator.ts +0 -70
- package/src/engine/execution-plan.ts +0 -64
- package/src/engine/execution-tasks.ts +0 -44
- package/src/engine/namespaced-cache.ts +0 -13
- package/src/engine/resources.ts +0 -53
- package/src/engine/scheduler.ts +0 -92
- package/src/engine/session.ts +0 -247
- package/src/eval/README.md +0 -130
- package/src/eval/ifeval.ts +0 -81
- package/src/eval/kl-prompts.ts +0 -29
- package/src/eval/kl.ts +0 -590
- package/src/eval/perplexity.ts +0 -164
- package/src/eval/quality-db.ts +0 -92
- package/src/eval/reference-logits.ts +0 -207
- package/src/eval/runner.ts +0 -124
- package/src/eval/score.ts +0 -49
- package/src/eval/tasks/bfcl.ts +0 -535
- package/src/eval/tasks/gsm8k.ts +0 -98
- package/src/eval/tasks/hashhop.ts +0 -139
- package/src/eval/tasks/humaneval.ts +0 -275
- package/src/eval/tasks/ifeval.ts +0 -525
- package/src/eval/tasks/mmlu.ts +0 -157
- package/src/eval/ultrafeedback.ts +0 -60
- package/src/evaldb.ts +0 -199
- package/src/expert-io.ts +0 -393
- package/src/expert-offload-build.ts +0 -77
- package/src/expert-offload.ts +0 -76
- package/src/expert-residency.ts +0 -1134
- package/src/expert-usage.ts +0 -415
- package/src/fill/echo-index.ts +0 -188
- package/src/fill/fill-session.ts +0 -463
- package/src/fill/proposal.ts +0 -68
- package/src/fill/schema-rows.ts +0 -506
- package/src/fill/tool-boundary.ts +0 -103
- package/src/fit.ts +0 -249
- package/src/generate.ts +0 -1352
- package/src/grammar.ts +0 -604
- package/src/harness-pi.ts +0 -184
- package/src/hf-push.ts +0 -628
- package/src/hub-rest.ts +0 -242
- package/src/index.ts +0 -63
- package/src/inference/denoising.ts +0 -17
- package/src/inference/draft-acceptance.ts +0 -41
- package/src/inference/draft-output.ts +0 -49
- package/src/inference/graph.ts +0 -33
- package/src/inference/prefill.ts +0 -45
- package/src/inference/rollback.ts +0 -7
- package/src/jobs/index.ts +0 -31
- package/src/jobs/job-entry.ts +0 -107
- package/src/jobs/task-client.ts +0 -19
- package/src/jobs/types.ts +0 -2
- package/src/kv-scheme.ts +0 -279
- package/src/kv-store.ts +0 -1135
- package/src/lab/README.md +0 -22
- package/src/lab/curve/curve-designer.html +0 -303
- package/src/lab/curve/curve-sampler.ts +0 -122
- package/src/lab/expert-trace/expert-trace.ts +0 -69
- package/src/lab/paged-kv/paged-attention.ts +0 -117
- package/src/lab/paged-kv/paged-kv-rows.ts +0 -111
- package/src/lab/paged-kv/paged-kv.ts +0 -336
- package/src/library.ts +0 -40
- package/src/lora.ts +0 -408
- package/src/md-modules.d.ts +0 -54
- package/src/media-fetch.ts +0 -442
- package/src/memory/article.ts +0 -558
- package/src/memory/chunk-validation.ts +0 -150
- package/src/memory/chunk.ts +0 -318
- package/src/memory/cluster.ts +0 -351
- package/src/memory/coverage.ts +0 -134
- package/src/memory/crosslink.ts +0 -0
- package/src/memory/db.ts +0 -253
- package/src/memory/entity.ts +0 -203
- package/src/memory/gate.ts +0 -187
- package/src/memory/history.ts +0 -152
- package/src/memory/ingest.ts +0 -335
- package/src/memory/model.ts +0 -183
- package/src/memory/normalize.ts +0 -671
- package/src/memory/parse.ts +0 -22
- package/src/memory/pipeline.ts +0 -275
- package/src/memory/prompts.ts +0 -65
- package/src/memory/query.ts +0 -334
- package/src/memory/reconcile.ts +0 -734
- package/src/memory/redirect.ts +0 -108
- package/src/memory/reindex.ts +0 -325
- package/src/memory/resolve.ts +0 -307
- package/src/memory/rest.ts +0 -245
- package/src/memory/route.ts +0 -272
- package/src/memory/schedule.ts +0 -165
- package/src/memory/stages.ts +0 -777
- package/src/memory/synthesize.ts +0 -1638
- package/src/memory/tools.ts +0 -749
- package/src/memory/vault.ts +0 -1194
- package/src/memory/wikify.ts +0 -1064
- package/src/mlx/array.ts +0 -425
- package/src/mlx/autograd.ts +0 -304
- package/src/mlx/checkpoint.ts +0 -149
- package/src/mlx/compile.ts +0 -168
- package/src/mlx/custom-vjp.ts +0 -188
- package/src/mlx/ffi.ts +0 -531
- package/src/mlx/materialize.ts +0 -23
- package/src/mlx/metal-kernel.ts +0 -159
- package/src/mlx/normalized-argmax.ts +0 -116
- package/src/mlx/ops.ts +0 -1108
- package/src/mlx/quantized-rows.ts +0 -36
- package/src/mlx/token-bitmask.ts +0 -52
- package/src/mlx/turboquant-kv-decode.ts +0 -82
- package/src/mlx/turboquant-kv-inverse.ts +0 -122
- package/src/mlx/turboquant-ops.ts +0 -861
- package/src/mlx/turboquant-tables.ts +0 -237
- package/src/mmap.ts +0 -90
- package/src/model/artifact-identity.ts +0 -73
- package/src/model/batched-kv.ts +0 -235
- package/src/model/batched-mask.ts +0 -279
- package/src/model/batched-quant.ts +0 -212
- package/src/model/batched-quantized-kv.ts +0 -144
- package/src/model/batched-rotating-quant.ts +0 -353
- package/src/model/batched-rotating-state.ts +0 -149
- package/src/model/batched-rotating.ts +0 -338
- package/src/model/batched-row-storage.ts +0 -179
- package/src/model/batched-ssm.ts +0 -52
- package/src/model/batched-turboquant-kv.ts +0 -94
- package/src/model/compiled-decode.ts +0 -712
- package/src/model/decoded-kv-donor.ts +0 -12
- package/src/model/delayed-quantized-kv.ts +0 -72
- package/src/model/delayed-rotating-quantized-kv.ts +0 -93
- package/src/model/delayed-turboquant-kv.ts +0 -57
- package/src/model/diffusion-gemma.ts +0 -1026
- package/src/model/factory.ts +0 -215
- package/src/model/fingerprint.ts +0 -45
- package/src/model/flash-attention.ts +0 -694
- package/src/model/full-prefill-padding.ts +0 -56
- package/src/model/full-prefill-row.ts +0 -105
- package/src/model/gemma4-base.ts +0 -2370
- package/src/model/gemma4.ts +0 -1395
- package/src/model/generated/gemma4-12b.ts +0 -429
- package/src/model/generated/gemma4-26b.ts +0 -448
- package/src/model/generated/gemma4-e4b.ts +0 -717
- package/src/model/generated/index.ts +0 -18
- package/src/model/glm52-atlas.ts +0 -511
- package/src/model/glm52-cache.ts +0 -792
- package/src/model/glm52-config.ts +0 -262
- package/src/model/glm52-container.ts +0 -305
- package/src/model/glm52-coupling.ts +0 -388
- package/src/model/glm52-dsa.ts +0 -606
- package/src/model/glm52-expert-layout.ts +0 -253
- package/src/model/glm52-layout.ts +0 -152
- package/src/model/glm52-memory.ts +0 -500
- package/src/model/glm52-mla.ts +0 -792
- package/src/model/glm52-moe.ts +0 -223
- package/src/model/glm52-pilot.ts +0 -537
- package/src/model/glm52-reference.ts +0 -445
- package/src/model/glm52-residency.ts +0 -557
- package/src/model/glm52-resident-weights.ts +0 -135
- package/src/model/glm52-streamed-experts.ts +0 -453
- package/src/model/glm52-streamed-metal.ts +0 -610
- package/src/model/glm52-weights.ts +0 -503
- package/src/model/glm52.ts +0 -1132
- package/src/model/implementation.ts +0 -69
- package/src/model/kv-attention-view.ts +0 -70
- package/src/model/minicpm5.ts +0 -376
- package/src/model/profile.ts +0 -468
- package/src/model/quantized-append-attention.ts +0 -31
- package/src/model/qwen-append.ts +0 -12
- package/src/model/qwen-conv.ts +0 -52
- package/src/model/qwen3-delta-state.ts +0 -74
- package/src/model/qwen3-delta.ts +0 -451
- package/src/model/qwen3-moe.ts +0 -404
- package/src/model/qwen3-mrope.ts +0 -250
- package/src/model/qwen3.ts +0 -277
- package/src/model/qwen3_5-checkpoint.ts +0 -164
- package/src/model/qwen3_5.ts +0 -881
- package/src/model/rotating-kv-donor.ts +0 -35
- package/src/model/rotating-kv-layout.ts +0 -262
- package/src/model/rotating-row-transaction.ts +0 -35
- package/src/model/speculative-rotating-kv.ts +0 -101
- package/src/model/ssm-prefill-padding.ts +0 -39
- package/src/model/support.ts +0 -110
- package/src/model/token-groups.ts +0 -66
- package/src/model/trellis-balanced-scatter.ts +0 -51
- package/src/model/trellis-linear.ts +0 -672
- package/src/model/trellis-shared-m.ts +0 -151
- package/src/model/trellis-shared-scatter.ts +0 -144
- package/src/model/trellis-splitk-prefill.ts +0 -158
- package/src/model/trellis-tiled-prefill.ts +0 -136
- package/src/model/trellis-vector-expand.ts +0 -62
- package/src/model/trellis-wide-prefill.ts +0 -94
- package/src/model/turboquant-codec.ts +0 -89
- package/src/model/universal/archs.ts +0 -332
- package/src/model/universal/dense.ts +0 -612
- package/src/model/universal/modules.ts +0 -170
- package/src/model/universal/rope.ts +0 -381
- package/src/model/whisper.ts +0 -435
- package/src/native/expert_io.c +0 -523
- package/src/native/frame_extract.swift +0 -140
- package/src/native/mic_capture.swift +0 -92
- package/src/native-pack.ts +0 -163
- package/src/pi-provider.ts +0 -53
- package/src/pi-session.ts +0 -81
- package/src/pi-terminal.ts +0 -274
- package/src/pi-web.ts +0 -1940
- package/src/preflight.ts +0 -158
- package/src/prompt-cache.ts +0 -682
- package/src/quantize/allocator.ts +0 -627
- package/src/quantize/atomic-output.ts +0 -38
- package/src/quantize/calibration.ts +0 -181
- package/src/quantize/config-writer.ts +0 -145
- package/src/quantize/index.ts +0 -84
- package/src/quantize/job.ts +0 -162
- package/src/quantize/quantizer.ts +0 -666
- package/src/quantize/rotate.ts +0 -603
- package/src/quantize/safetensors-writer.ts +0 -249
- package/src/quantize/sensitivity.ts +0 -216
- package/src/quantize/trellis.ts +0 -586
- package/src/quantize/weight-transform.ts +0 -292
- package/src/registry.ts +0 -602
- package/src/runtime-config.ts +0 -77
- package/src/safetensors.ts +0 -165
- package/src/sampler.ts +0 -1012
- package/src/serve/admin-routes.ts +0 -184
- package/src/serve/assets/curve-terrain.html +0 -105
- package/src/serve/assets/training-inference-map.html +0 -575
- package/src/serve/audio-routes.ts +0 -323
- package/src/serve/aux-routes.ts +0 -132
- package/src/serve/batch-scheduler.ts +0 -8
- package/src/serve/checkpoint-identity.ts +0 -64
- package/src/serve/generation-gateway.ts +0 -537
- package/src/serve/isolate.ts +0 -609
- package/src/serve/kv-budget.ts +0 -14
- package/src/serve/lab-routes.ts +0 -341
- package/src/serve/lane-registry.ts +0 -45
- package/src/serve/model-admin-routes.ts +0 -155
- package/src/serve/model-binding.ts +0 -49
- package/src/serve/model-host.ts +0 -511
- package/src/serve/parent-application.ts +0 -56
- package/src/serve/prompt-response-trace.ts +0 -161
- package/src/serve/response-lease.ts +0 -24
- package/src/serve/session-search.ts +0 -326
- package/src/serve/static-routes.ts +0 -101
- package/src/serve/transcription-server.ts +0 -64
- package/src/serve/transcription-service.ts +0 -502
- package/src/server.ts +0 -1282
- package/src/spec/assistant-rows.ts +0 -93
- package/src/spec/assistant-source.ts +0 -126
- package/src/spec/cache-identity.ts +0 -22
- package/src/spec/deepspec-rows.ts +0 -29
- package/src/spec/deepspec-source.ts +0 -145
- package/src/spec/dflash-source.ts +0 -149
- package/src/spec/drafter.ts +0 -304
- package/src/spec/dspark/ab-stats.ts +0 -136
- package/src/spec/dspark/calibration.ts +0 -112
- package/src/spec/dspark/data-dflash.ts +0 -156
- package/src/spec/dspark/data.ts +0 -201
- package/src/spec/dspark/deepspec-module.ts +0 -776
- package/src/spec/dspark/generate-dflash.ts +0 -245
- package/src/spec/dspark/generate.ts +0 -290
- package/src/spec/dspark/loader.ts +0 -23
- package/src/spec/dspark/loss.ts +0 -213
- package/src/spec/dspark/module-dflash.ts +0 -560
- package/src/spec/dspark/module.ts +0 -540
- package/src/spec/dspark/quantize-drafter.ts +0 -91
- package/src/spec/dspark/sample.ts +0 -112
- package/src/spec/generate.ts +0 -276
- package/src/spec/glm52-mtp-graph.ts +0 -29
- package/src/spec/glm52-mtp-rows.ts +0 -132
- package/src/spec/glm52-mtp-source.ts +0 -318
- package/src/spec/glm52-mtp-state.ts +0 -33
- package/src/spec/ngram-source.ts +0 -226
- package/src/spec/projected-draft-rows.ts +0 -73
- package/src/spec/qwen-mtp-module.ts +0 -171
- package/src/spec/qwen-mtp-rows.ts +0 -230
- package/src/spec/qwen-mtp-source.ts +0 -386
- package/src/spec/qwen-mtp-state.ts +0 -30
- package/src/spec/serve-loop.ts +0 -587
- package/src/spec/source.ts +0 -228
- package/src/spec/two-model-rows.ts +0 -62
- package/src/spec/two-model.ts +0 -113
- package/src/ssd-cache.ts +0 -431
- package/src/ssd-durability.ts +0 -209
- package/src/storage/host-buffer.ts +0 -24
- package/src/storage/kv-writer.ts +0 -83
- package/src/storage/kv-writer.worker.js +0 -264
- package/src/storage/retention-policy.ts +0 -37
- package/src/tiered-prompt-cache.ts +0 -45
- package/src/tokenizer.ts +0 -335
- package/src/tool-approvals.ts +0 -131
- package/src/tool-call.ts +0 -695
- package/src/train/dataset.ts +0 -484
- package/src/train/diffusion-lora.ts +0 -216
- package/src/train/export.ts +0 -32
- package/src/train/flash-cce.ts +0 -1234
- package/src/train/forward.ts +0 -266
- package/src/train/fuse.ts +0 -243
- package/src/train/index.ts +0 -46
- package/src/train/job.ts +0 -205
- package/src/train/lora-params.ts +0 -254
- package/src/train/loss.ts +0 -1591
- package/src/train/merge.ts +0 -195
- package/src/train/optimizer.ts +0 -183
- package/src/train/prefix-shared.ts +0 -490
- package/src/train/rank.ts +0 -137
- package/src/train/segmented.ts +0 -1828
- package/src/train/steel-qmm-header.ts +0 -1461
- package/src/train/trainer.ts +0 -1221
- package/src/train/watch.ts +0 -542
- package/src/vision/diffusion-vision.ts +0 -620
- package/src/vision/embedder.ts +0 -176
- package/src/vision/preprocess.ts +0 -247
- package/src/vision/prompt.ts +0 -409
- package/src/vision/qwen3vl-preprocess.ts +0 -409
- package/src/vision/qwen3vl-prompt.ts +0 -233
- package/src/vision/qwen3vl-tower.ts +0 -566
- package/src/vision/siglip.ts +0 -607
- package/src/vision/video-frames.ts +0 -148
- package/src/web/app.html +0 -2155
- package/src/web/app.js +0 -5863
- package/src/web/skills/memory/SKILL.md +0 -120
- package/src/web/skills/web-research/SKILL.md +0 -46
- package/src/web/skills.ts +0 -97
- package/src/web/src/adapters-panel.ts +0 -288
- package/src/web/src/assistant.ts +0 -451
- package/src/web/src/chat.ts +0 -1060
- package/src/web/src/composer.ts +0 -962
- package/src/web/src/dataset.ts +0 -132
- package/src/web/src/finetune.ts +0 -267
- package/src/web/src/hub.ts +0 -428
- package/src/web/src/main.ts +0 -98
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +0 -572
- package/src/web/src/model-picker.ts +0 -159
- package/src/web/src/palette.ts +0 -346
- package/src/web/src/protocol.ts +0 -26
- package/src/web/src/quantize.ts +0 -167
- package/src/web/src/rag.ts +0 -274
- package/src/web/src/sessions.ts +0 -469
- package/src/web/src/shell.ts +0 -804
- package/src/web/src/ui-catalog.ts +0 -78
- package/src/web/src/voice.ts +0 -141
- package/src/web/vendor/README.md +0 -61
- package/src/web-assets.ts +0 -47
- package/src/web-tools.ts +0 -478
- package/src/weights.ts +0 -255
- /package/src/{serve → server}/completion-sink.ts +0 -0
- /package/src/{serve → server}/generated-token-history.ts +0 -0
- /package/src/web/{icon.svg → public/icon.svg} +0 -0
- /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
- /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
package/src/jobs/runner.ts
CHANGED
|
@@ -1,167 +1,16 @@
|
|
|
1
|
-
import { CancellationSource } from "../engine/cancellation";
|
|
2
|
-
// Job runner: turns a registered `JobRunner` into a tracked, logged job.
|
|
3
|
-
// Two execution modes — both create a row, both stream NDJSON events to the
|
|
4
|
-
// log, both own terminal status:
|
|
5
|
-
//
|
|
6
|
-
// submitInProcess — runs the runner as a fire-and-forget async task in
|
|
7
|
-
// THIS process. For pure-JS work (dataset builds) with
|
|
8
|
-
// no MLX/GPU state to isolate.
|
|
9
|
-
// submitSubprocess — spawns `bun job-entry.ts <jobId>` so MLX state is
|
|
10
|
-
// isolated and an uncatchable crash (segfault,
|
|
11
|
-
// process.exit) can't take the server down. Gated by a
|
|
12
|
-
// single global GPU lease: only one MLX job runs at a
|
|
13
|
-
// time; the rest wait 'queued' and drain in order.
|
|
14
|
-
//
|
|
15
|
-
// Port of optiq lab jobs.py `submit` + `_job_main`, split across this file
|
|
16
|
-
// and job-entry.ts.
|
|
17
|
-
|
|
18
|
-
import type { DisposableResource } from "../contracts/resources";
|
|
19
|
-
import { createJobTaskClient } from "./task-client";
|
|
20
1
|
import { appendFileSync } from "node:fs";
|
|
21
|
-
import {
|
|
22
|
-
import type {
|
|
23
|
-
import type
|
|
24
|
-
|
|
25
|
-
/** Absolute path to the child-process entry (`bun job-entry.ts <jobId>`). */
|
|
26
|
-
export const JOB_ENTRY_PATH = fileURLToPath(new URL("./job-entry.ts", import.meta.url));
|
|
27
|
-
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// Runner registry
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
|
|
32
|
-
const RUNNERS = new Map<string, JobRunner>();
|
|
33
|
-
|
|
34
|
-
export function registerRunner(kind: string, run: JobRunner): void {
|
|
35
|
-
RUNNERS.set(kind, run);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function getRunner(kind: string): JobRunner | undefined {
|
|
39
|
-
return RUNNERS.get(kind);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Built-in test kinds. Real kinds (quantize/finetune) register their runner
|
|
43
|
-
// on import of their module (see job-entry.ts KIND_MODULES).
|
|
44
|
-
|
|
45
|
-
/** Emits a couple of stage events then resolves — exercises the happy path. */
|
|
46
|
-
const noopRunner: JobRunner = async (emit) => {
|
|
47
|
-
emit({ type: "stage", stage: "warmup", progress: 0.25, message: "starting up" });
|
|
48
|
-
emit({ type: "stage", stage: "work", progress: 0.75, message: "doing the thing" });
|
|
49
|
-
return { outputPath: undefined };
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/** Hard-exits the process — simulates an uncatchable crash (segfault-class).
|
|
53
|
-
* Only meaningful under submitSubprocess: the child dies, the parent lives. */
|
|
54
|
-
const crashRunner: JobRunner = async (emit) => {
|
|
55
|
-
emit({ type: "stage", stage: "boom", message: "about to crash" });
|
|
56
|
-
process.exit(1);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
registerRunner("noop", noopRunner);
|
|
60
|
-
registerRunner("crash", crashRunner);
|
|
61
|
-
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
// Emit factory
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
|
|
66
|
-
/** Build the `emit` sink for a job: appends each event as one NDJSON line
|
|
67
|
-
* (line-buffered via appendFileSync for crash durability) and mirrors
|
|
68
|
-
* progress/message from stage/metric events onto the row. Never throws — a
|
|
69
|
-
* logging failure must not kill a job. The terminal `done`/`failed` status
|
|
70
|
-
* is owned by the submit wrapper / job-entry, NOT here. */
|
|
71
|
-
export function makeEmit(store: JobStore, jobId: string, logPath: string): Emit {
|
|
72
|
-
return (e: JobEvent) => {
|
|
73
|
-
try {
|
|
74
|
-
appendFileSync(logPath, JSON.stringify(e) + "\n");
|
|
75
|
-
} catch {
|
|
76
|
-
// swallow: a job must survive a logging hiccup
|
|
77
|
-
}
|
|
78
|
-
if (e.type === "stage" || e.type === "metric") {
|
|
79
|
-
const progress = typeof e.progress === "number" ? e.progress : undefined;
|
|
80
|
-
const message = typeof e.message === "string" ? e.message : undefined;
|
|
81
|
-
if (progress !== undefined || message !== undefined) {
|
|
82
|
-
try {
|
|
83
|
-
if (progress !== undefined) store.setProgress(jobId, progress, message);
|
|
84
|
-
else if (message !== undefined) {
|
|
85
|
-
// message-only update preserves current progress
|
|
86
|
-
store.db.prepare("UPDATE jobs SET message = ? WHERE id = ?").run(message, jobId);
|
|
87
|
-
}
|
|
88
|
-
} catch {
|
|
89
|
-
// swallow: DB contention must not kill a job
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// In-process submit
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
|
|
100
|
-
export interface SubmitResult {
|
|
101
|
-
jobId: string;
|
|
102
|
-
outputPath?: string;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/** Run a registered runner as a fire-and-forget async task in this process.
|
|
106
|
-
* Sets 'running' + emits 'started' synchronously, then schedules the runner;
|
|
107
|
-
* on success sets 'done'/progress 1/ended_at + emits 'done', on throw sets
|
|
108
|
-
* 'failed'/error/ended_at + emits 'failed'. Returns once the task is
|
|
109
|
-
* scheduled (does not await completion — poll the row / tail the log). */
|
|
110
|
-
interface InProcessWork { cancellation: CancellationSource; done: Promise<void>; }
|
|
111
|
-
const inProcess = new WeakMap<JobStore, Set<InProcessWork>>();
|
|
112
|
-
|
|
113
|
-
export function submitInProcess(
|
|
114
|
-
store: JobStore,
|
|
115
|
-
kind: string,
|
|
116
|
-
config: Record<string, unknown>,
|
|
117
|
-
outputPath?: string,
|
|
118
|
-
): SubmitResult {
|
|
119
|
-
if (closedStores.has(store)) throw new Error("job host is closed");
|
|
120
|
-
const row = store.create(kind, config, outputPath);
|
|
121
|
-
const emit = makeEmit(store, row.id, row.log_path);
|
|
122
|
-
store.setStatus(row.id, "running");
|
|
123
|
-
emit({ type: "started", ts: Date.now() });
|
|
2
|
+
import type { DisposableResource } from "@mlx-bun/inference/contracts/portable";
|
|
3
|
+
import type { JobEvent } from "./protocol";
|
|
4
|
+
import { jobError, nowIso, type JobStore } from "./db";
|
|
5
|
+
import { executablePath } from "./executable";
|
|
124
6
|
|
|
125
|
-
|
|
126
|
-
const cancellation = new CancellationSource();
|
|
127
|
-
const running = inProcess.get(store) ?? new Set<InProcessWork>();
|
|
128
|
-
inProcess.set(store, running);
|
|
129
|
-
let finish!: () => void;
|
|
130
|
-
const work = { cancellation, done: new Promise<void>((resolve) => { finish = resolve; }) };
|
|
131
|
-
running.add(work);
|
|
132
|
-
void (async () => {
|
|
133
|
-
if (!runner) {
|
|
134
|
-
const error = `no runner registered for kind "${kind}"`;
|
|
135
|
-
store.setStatus(row.id, "failed", { error, endedAt: nowIso() });
|
|
136
|
-
emit({ type: "failed", error, ts: Date.now() });
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
try {
|
|
140
|
-
const result = await createJobTaskClient(runner).run(JSON.parse(row.config_json), emit, cancellation);
|
|
141
|
-
const out = result?.outputPath;
|
|
142
|
-
if (out) store.setOutputPath(row.id, out);
|
|
143
|
-
store.setProgress(row.id, 1);
|
|
144
|
-
store.setStatus(row.id, "done", { endedAt: nowIso() });
|
|
145
|
-
emit({ type: "done", ts: Date.now(), output_dir: out ?? outputPath });
|
|
146
|
-
} catch (e) {
|
|
147
|
-
const error = errString(e);
|
|
148
|
-
store.setStatus(row.id, "failed", { error, endedAt: nowIso() });
|
|
149
|
-
emit({ type: "failed", error, ts: Date.now() });
|
|
150
|
-
}
|
|
151
|
-
})().finally(() => { running.delete(work); finish(); });
|
|
152
|
-
|
|
153
|
-
return { jobId: row.id, outputPath };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ---------------------------------------------------------------------------
|
|
157
|
-
// Subprocess submit + single global GPU lease
|
|
158
|
-
// ---------------------------------------------------------------------------
|
|
7
|
+
export interface SubmitResult { jobId: string; outputPath?: string; }
|
|
159
8
|
|
|
160
9
|
interface QueuedSpawn {
|
|
161
|
-
acquire
|
|
10
|
+
acquire: (signal: AbortSignal) => Promise<DisposableResource>;
|
|
162
11
|
lease?: DisposableResource;
|
|
163
12
|
abort: AbortController;
|
|
164
|
-
proc?:
|
|
13
|
+
proc?: JobProcess;
|
|
165
14
|
finished: Promise<void>;
|
|
166
15
|
finish(): void;
|
|
167
16
|
store: JobStore;
|
|
@@ -170,34 +19,33 @@ interface QueuedSpawn {
|
|
|
170
19
|
bin: string;
|
|
171
20
|
compiled: boolean;
|
|
172
21
|
spawn: typeof Bun.spawn;
|
|
22
|
+
graceMs: number;
|
|
23
|
+
/** The submitter's temporary directory for the child (`TMPDIR`). */
|
|
24
|
+
scratchDir?: string;
|
|
25
|
+
/** Set when a caller stopped this job on purpose; recorded as the failure instead of the exit code. */
|
|
26
|
+
cancelReason?: string;
|
|
173
27
|
onComplete?: (jobId: string, code: number) => void;
|
|
174
28
|
}
|
|
175
29
|
|
|
30
|
+
type JobProcess = Bun.Subprocess<"pipe", "pipe", "pipe">;
|
|
31
|
+
|
|
176
32
|
let gpuLeaseHolder: string | null = null;
|
|
177
33
|
const spawnQueue: QueuedSpawn[] = [];
|
|
178
34
|
let activeSpawn: QueuedSpawn | undefined;
|
|
179
35
|
const closedStores = new WeakSet<JobStore>();
|
|
180
36
|
|
|
181
|
-
/** True while a GPU (subprocess) job is running — the server gates token
|
|
182
|
-
* generation on this so quantize/finetune don't fight live inference. */
|
|
183
|
-
export function isGpuBusy(): boolean {
|
|
184
|
-
return gpuLeaseHolder !== null;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/** The job id currently holding the GPU lease, or null. */
|
|
188
|
-
export function currentGpuJob(): string | null {
|
|
189
|
-
return gpuLeaseHolder;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
37
|
export interface SubprocessOpts {
|
|
193
|
-
/** Host execution lease, held until child exit (including crashes). */
|
|
194
|
-
acquire
|
|
195
|
-
/**
|
|
196
|
-
entry
|
|
197
|
-
/**
|
|
38
|
+
/** Host execution lease, held until child exit and log drain (including crashes). */
|
|
39
|
+
acquire: (signal: AbortSignal) => Promise<DisposableResource>;
|
|
40
|
+
/** Child entry supplied by application composition. */
|
|
41
|
+
entry: string;
|
|
42
|
+
/** Defaults to the executable captured at startup (Bun for source execution). */
|
|
198
43
|
bin?: string;
|
|
199
44
|
/** Override process creation for deterministic failure-path tests. */
|
|
200
45
|
spawn?: typeof Bun.spawn;
|
|
46
|
+
/** SIGTERM first; SIGKILL for whatever of the job's process group is still
|
|
47
|
+
* alive after this (default 3 s, as main). */
|
|
48
|
+
graceMs?: number;
|
|
201
49
|
/** Called on the server (parent) after the child exits — used to invalidate
|
|
202
50
|
* caches (e.g. the Library) so a finished quantize surfaces immediately. */
|
|
203
51
|
onComplete?: (jobId: string, code: number) => void;
|
|
@@ -210,8 +58,9 @@ export function submitSubprocess(
|
|
|
210
58
|
store: JobStore,
|
|
211
59
|
kind: string,
|
|
212
60
|
config: Record<string, unknown>,
|
|
213
|
-
outputPath
|
|
214
|
-
opts: SubprocessOpts
|
|
61
|
+
outputPath: string | undefined,
|
|
62
|
+
opts: SubprocessOpts,
|
|
63
|
+
scratchDir?: string,
|
|
215
64
|
): SubmitResult {
|
|
216
65
|
if (closedStores.has(store)) throw new Error("job host is closed");
|
|
217
66
|
const row = store.create(kind, config, outputPath);
|
|
@@ -221,20 +70,24 @@ export function submitSubprocess(
|
|
|
221
70
|
abort: new AbortController(), finished, finish,
|
|
222
71
|
store,
|
|
223
72
|
jobId: row.id,
|
|
224
|
-
entry: opts.entry
|
|
225
|
-
compiled: opts.entry
|
|
226
|
-
bin: opts.bin ??
|
|
73
|
+
entry: opts.entry,
|
|
74
|
+
compiled: opts.entry.includes("$bunfs"),
|
|
75
|
+
bin: opts.bin ?? executablePath,
|
|
227
76
|
spawn: opts.spawn ?? Bun.spawn,
|
|
77
|
+
graceMs: opts.graceMs ?? 3000,
|
|
228
78
|
onComplete: opts.onComplete,
|
|
229
79
|
acquire: opts.acquire,
|
|
80
|
+
...(scratchDir ? { scratchDir } : {}),
|
|
230
81
|
};
|
|
231
82
|
spawnQueue.push(item);
|
|
232
83
|
drainQueue();
|
|
233
84
|
return { jobId: row.id, outputPath };
|
|
234
85
|
}
|
|
235
86
|
|
|
236
|
-
/** Spawn the child
|
|
237
|
-
*
|
|
87
|
+
/** Spawn the child as the leader of its own process group, stream its
|
|
88
|
+
* stdout/stderr into the log, reconcile terminal status on exit, then release
|
|
89
|
+
* the lease and drain. Everything that can fail runs before the spawn, so a
|
|
90
|
+
* child that exists is always joined before its lease is released. */
|
|
238
91
|
function spawnNow(item: QueuedSpawn): void {
|
|
239
92
|
const { store, jobId, entry, bin, spawn } = item;
|
|
240
93
|
gpuLeaseHolder = jobId;
|
|
@@ -242,26 +95,35 @@ function spawnNow(item: QueuedSpawn): void {
|
|
|
242
95
|
// The child opens its OWN JobStore over the SAME DB/logs — a sqlite
|
|
243
96
|
// connection can't cross the process boundary, so we hand the paths via
|
|
244
97
|
// env. A :memory: DB can't be shared with a child; subprocess jobs require
|
|
245
|
-
// a file-backed DB.
|
|
246
|
-
|
|
98
|
+
// a file-backed DB. `detached` makes the child a process-group leader, so
|
|
99
|
+
// stopping the job stops everything it started; a group leader no longer
|
|
100
|
+
// receives the terminal's signals, so the child watches the parent pipe on
|
|
101
|
+
// its stdin instead (cli/job-entry.ts) and stops when this process is gone.
|
|
102
|
+
let proc: JobProcess;
|
|
103
|
+
let logPath: string | undefined;
|
|
247
104
|
try {
|
|
248
|
-
|
|
105
|
+
logPath = store.get(jobId)?.log_path;
|
|
106
|
+
proc = spawn(item.compiled ? [bin, "__job", jobId] : [bin, entry, jobId], {
|
|
107
|
+
stdin: "pipe",
|
|
249
108
|
stdout: "pipe",
|
|
250
109
|
stderr: "pipe",
|
|
110
|
+
detached: true,
|
|
251
111
|
env: {
|
|
252
112
|
...process.env,
|
|
253
113
|
MLX_BUN_JOBS_DB: store.dbPath,
|
|
254
114
|
MLX_BUN_JOBS_DIR: store.logsDir,
|
|
115
|
+
MLX_BUN_JOB_PARENT_PIPE: "1",
|
|
116
|
+
...(item.scratchDir ? { TMPDIR: item.scratchDir } : {}),
|
|
255
117
|
},
|
|
256
118
|
});
|
|
257
119
|
} catch (e) {
|
|
258
120
|
try {
|
|
259
121
|
store.setStatus(jobId, "failed", {
|
|
260
|
-
error:
|
|
122
|
+
error: jobError(e),
|
|
261
123
|
endedAt: nowIso(),
|
|
262
124
|
});
|
|
263
125
|
} catch (error) {
|
|
264
|
-
console.error(`[jobs] failed to record spawn failure: ${
|
|
126
|
+
console.error(`[jobs] failed to record spawn failure: ${jobError(error)}`);
|
|
265
127
|
} finally {
|
|
266
128
|
releaseLease(item);
|
|
267
129
|
}
|
|
@@ -269,18 +131,24 @@ function spawnNow(item: QueuedSpawn): void {
|
|
|
269
131
|
}
|
|
270
132
|
|
|
271
133
|
item.proc = proc;
|
|
272
|
-
const logPath = store.get(jobId)?.log_path;
|
|
273
134
|
const logLine = (line: string) => {
|
|
274
135
|
if (!line || !logPath) return;
|
|
275
136
|
const ev: JobEvent = { type: "log", line };
|
|
276
137
|
try { appendFileSync(logPath, JSON.stringify(ev) + "\n"); } catch {}
|
|
277
138
|
};
|
|
278
139
|
|
|
279
|
-
|
|
280
|
-
void pumpLines(proc.stderr, logLine);
|
|
140
|
+
const logs = Promise.all([pumpLines(proc.stdout, logLine), pumpLines(proc.stderr, logLine)]);
|
|
281
141
|
|
|
282
142
|
void (async () => {
|
|
283
143
|
const code = await proc.exited;
|
|
144
|
+
// Descendants that outlived the child still hold its work and its log
|
|
145
|
+
// pipes: the job is joined once its whole process group is gone and its
|
|
146
|
+
// output has drained. Both waits are bounded: a process surviving SIGKILL
|
|
147
|
+
// (uninterruptible sleep) or one that left the group holding the pipes is
|
|
148
|
+
// reported and left behind rather than holding the lease forever.
|
|
149
|
+
const joined = await stopGroup(proc, item.graceMs) && await settles(logs, item.graceMs);
|
|
150
|
+
if (!joined) console.error(`[jobs] ${jobId}: a process of the job outlived SIGKILL or its output; releasing the lease without it`);
|
|
151
|
+
try { proc.stdin?.end(); } catch { /* already closed */ }
|
|
284
152
|
// code 0 ⇒ trust the child's terminal status (it set done/failed itself).
|
|
285
153
|
// non-zero ⇒ if the row never reached terminal (crash before the wrapper
|
|
286
154
|
// could write), force it failed.
|
|
@@ -288,12 +156,12 @@ function spawnNow(item: QueuedSpawn): void {
|
|
|
288
156
|
const cur = store.get(jobId);
|
|
289
157
|
if (cur && (cur.status === "queued" || cur.status === "running")) {
|
|
290
158
|
store.setStatus(jobId, "failed", {
|
|
291
|
-
error: `exited ${code}`,
|
|
159
|
+
error: item.cancelReason ?? `exited ${code}`,
|
|
292
160
|
endedAt: nowIso(),
|
|
293
161
|
});
|
|
294
162
|
}
|
|
295
163
|
} } catch (error) {
|
|
296
|
-
console.error(`[jobs] failed to record child exit: ${
|
|
164
|
+
console.error(`[jobs] failed to record child exit: ${jobError(error)}`);
|
|
297
165
|
} finally {
|
|
298
166
|
releaseLease(item);
|
|
299
167
|
try { item.onComplete?.(jobId, code); } catch {}
|
|
@@ -302,9 +170,49 @@ function spawnNow(item: QueuedSpawn): void {
|
|
|
302
170
|
})();
|
|
303
171
|
}
|
|
304
172
|
|
|
173
|
+
/** Whether any process of the group the child leads is still alive. False for
|
|
174
|
+
* a child that leads no group (a supplied spawn without process groups). */
|
|
175
|
+
function groupAlive(proc: JobProcess): boolean {
|
|
176
|
+
if (!Number.isInteger(proc.pid) || proc.pid <= 0) return false;
|
|
177
|
+
try { process.kill(-proc.pid, 0); return true; } catch { return false; }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Signal the child's process group, or the child alone when it leads none. */
|
|
181
|
+
function signalJob(proc: JobProcess, signal: NodeJS.Signals): void {
|
|
182
|
+
if (groupAlive(proc)) {
|
|
183
|
+
try { process.kill(-proc.pid, signal); return; } catch { /* the group just ended */ }
|
|
184
|
+
}
|
|
185
|
+
if (proc.exitCode === null && proc.signalCode == null) {
|
|
186
|
+
try { proc.kill(signal); } catch { /* already gone */ }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** After the leader's exit: SIGTERM what is left of its group, SIGKILL it after
|
|
191
|
+
* the grace, and wait one more grace for it to go. Whether the group is gone. */
|
|
192
|
+
async function stopGroup(proc: JobProcess, graceMs: number): Promise<boolean> {
|
|
193
|
+
if (!groupAlive(proc)) return true;
|
|
194
|
+
signalJob(proc, "SIGTERM");
|
|
195
|
+
const force = Date.now() + graceMs, giveUp = force + graceMs;
|
|
196
|
+
let killed = false;
|
|
197
|
+
while (groupAlive(proc)) {
|
|
198
|
+
if (Date.now() >= giveUp) return false;
|
|
199
|
+
if (!killed && Date.now() >= force) { killed = true; signalJob(proc, "SIGKILL"); }
|
|
200
|
+
await Bun.sleep(20);
|
|
201
|
+
}
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Whether `work` settles within `ms`. */
|
|
206
|
+
async function settles(work: Promise<unknown>, ms: number): Promise<boolean> {
|
|
207
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
208
|
+
const timeout = new Promise<false>((resolve) => { timer = setTimeout(() => resolve(false), ms); });
|
|
209
|
+
try { return await Promise.race([work.then(() => true, () => true), timeout]); }
|
|
210
|
+
finally { clearTimeout(timer); }
|
|
211
|
+
}
|
|
212
|
+
|
|
305
213
|
function releaseLease(item: QueuedSpawn): void {
|
|
306
214
|
try { item.lease?.dispose(); }
|
|
307
|
-
catch (error) { console.error(`[jobs] execution lease cleanup failed: ${
|
|
215
|
+
catch (error) { console.error(`[jobs] execution lease cleanup failed: ${jobError(error)}`); }
|
|
308
216
|
finally {
|
|
309
217
|
item.lease = undefined;
|
|
310
218
|
if (gpuLeaseHolder === item.jobId) gpuLeaseHolder = null;
|
|
@@ -322,7 +230,7 @@ export function drainQueue(): void {
|
|
|
322
230
|
// iteratively so a run of bad queue entries cannot recurse or wedge the
|
|
323
231
|
// first startable job behind them.
|
|
324
232
|
activeSpawn = next;
|
|
325
|
-
|
|
233
|
+
{
|
|
326
234
|
gpuLeaseHolder = next.jobId; // reserve FIFO order while inference drains
|
|
327
235
|
void Promise.resolve().then(() => next.acquire!(next.abort.signal)).then((lease) => {
|
|
328
236
|
next.lease = lease;
|
|
@@ -330,47 +238,73 @@ export function drainQueue(): void {
|
|
|
330
238
|
spawnNow(next);
|
|
331
239
|
}).catch((error) => {
|
|
332
240
|
try {
|
|
333
|
-
next.store.setStatus(next.jobId, "failed", { error:
|
|
241
|
+
next.store.setStatus(next.jobId, "failed", { error: jobError(error), endedAt: nowIso() });
|
|
334
242
|
} catch (recordError) {
|
|
335
|
-
console.error(`[jobs] failed to record admission failure: ${
|
|
243
|
+
console.error(`[jobs] failed to record admission failure: ${jobError(recordError)}`);
|
|
336
244
|
} finally { releaseLease(next); }
|
|
337
245
|
}).finally(drainQueue);
|
|
338
|
-
}
|
|
246
|
+
}
|
|
339
247
|
}
|
|
340
248
|
}
|
|
341
249
|
|
|
342
|
-
/**
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const active = [...inProcess.get(store) ?? []];
|
|
346
|
-
for (const work of active) work.cancellation.cancel("engine_closed");
|
|
347
|
-
await Promise.all(active.map((work) => work.done));
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/** Stop only this host's managed GPU jobs. Await process death before releasing
|
|
351
|
-
* the execution lease; queued and admission-waiting jobs never spawn. */
|
|
250
|
+
/** Stop only this host's managed GPU jobs. Await the death of the active child's
|
|
251
|
+
* whole process group before releasing the execution lease, bounded at one
|
|
252
|
+
* grace after SIGKILL; queued and admission-waiting jobs never spawn. */
|
|
352
253
|
export async function closeSubprocessJobs(store: JobStore): Promise<void> {
|
|
353
254
|
closedStores.add(store);
|
|
255
|
+
const errors: unknown[] = [];
|
|
354
256
|
for (let i = spawnQueue.length - 1; i >= 0; i--) {
|
|
355
257
|
const item = spawnQueue[i]!;
|
|
356
258
|
if (item.store !== store) continue;
|
|
357
259
|
spawnQueue.splice(i, 1);
|
|
358
|
-
store.setStatus(item.jobId, "failed", { error: "job host closed", endedAt: nowIso() });
|
|
359
|
-
|
|
260
|
+
try { store.setStatus(item.jobId, "failed", { error: "job host closed", endedAt: nowIso() }); }
|
|
261
|
+
catch (error) { errors.push(error); }
|
|
262
|
+
finally { item.finish(); }
|
|
360
263
|
}
|
|
361
264
|
const active = activeSpawn;
|
|
362
|
-
if (
|
|
363
|
-
|
|
265
|
+
if (active?.store === store) await stopActive(active, "job host closed");
|
|
266
|
+
if (errors.length) throw new AggregateError(errors, "Failed to persist cancelled jobs");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Stop the active job: no child is spawned if it is still waiting for the lease, otherwise the child gets SIGTERM, then SIGKILL after
|
|
270
|
+
* the grace, and the lease is released once its process group is gone (bounded at one more grace). Resolves when the job has ended. */
|
|
271
|
+
async function stopActive(active: QueuedSpawn, reason: string): Promise<void> {
|
|
272
|
+
active.abort.abort(new Error(reason));
|
|
364
273
|
const proc = active.proc;
|
|
365
|
-
if (proc)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
274
|
+
if (proc) {
|
|
275
|
+
signalJob(proc, "SIGTERM");
|
|
276
|
+
const force = setTimeout(() => signalJob(proc, "SIGKILL"), active.graceMs);
|
|
277
|
+
try {
|
|
278
|
+
// SIGKILL at the grace, then one more grace for the child to die.
|
|
279
|
+
if (!(await settles(proc.exited, 2 * active.graceMs))) {
|
|
280
|
+
console.error(`[jobs] ${active.jobId}: pid ${proc.pid} outlived SIGKILL; releasing the lease without it`);
|
|
281
|
+
releaseLease(active);
|
|
282
|
+
}
|
|
283
|
+
} finally { clearTimeout(force); }
|
|
284
|
+
}
|
|
285
|
+
await active.finished;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Stop one managed job of this store: a queued job never spawns, the active one is stopped as `closeSubprocessJobs` stops it. A job that
|
|
289
|
+
* has finished but whose process group is still being joined is waited for; anything else is left alone. Resolves when the job's process is gone. */
|
|
290
|
+
export async function cancelSubprocessJob(store: JobStore, jobId: string): Promise<void> {
|
|
291
|
+
const queued = spawnQueue.findIndex(item => item.store === store && item.jobId === jobId);
|
|
292
|
+
if (queued >= 0) {
|
|
293
|
+
const [item] = spawnQueue.splice(queued, 1);
|
|
294
|
+
store.setStatus(jobId, "failed", { error: "job cancelled", endedAt: nowIso() });
|
|
295
|
+
item!.finish();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const active = activeSpawn;
|
|
299
|
+
if (active?.store !== store || active.jobId !== jobId) return;
|
|
300
|
+
const row = store.get(jobId);
|
|
301
|
+
if (row && row.status !== "done" && row.status !== "failed") { active.cancelReason = "job cancelled"; await stopActive(active, "job cancelled"); }
|
|
302
|
+
else await active.finished;
|
|
369
303
|
}
|
|
370
304
|
|
|
371
305
|
/** Read a child stream line-by-line, buffering partial trailing lines, and
|
|
372
|
-
* hand each complete line to `sink`. */
|
|
373
|
-
async function pumpLines(
|
|
306
|
+
* hand each complete line to `sink`. Shared with the isolation worker owner. */
|
|
307
|
+
export async function pumpLines(
|
|
374
308
|
stream: ReadableStream<Uint8Array> | undefined,
|
|
375
309
|
sink: (line: string) => void,
|
|
376
310
|
): Promise<void> {
|
|
@@ -397,18 +331,3 @@ async function pumpLines(
|
|
|
397
331
|
reader.releaseLock();
|
|
398
332
|
}
|
|
399
333
|
}
|
|
400
|
-
|
|
401
|
-
// ---------------------------------------------------------------------------
|
|
402
|
-
// helpers
|
|
403
|
-
// ---------------------------------------------------------------------------
|
|
404
|
-
|
|
405
|
-
function nowIso(): string {
|
|
406
|
-
// SQLite datetime('now') is "YYYY-MM-DD HH:MM:SS" in UTC — match it so the
|
|
407
|
-
// ended_at column is uniform whether set here or by a column default.
|
|
408
|
-
return new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function errString(e: unknown): string {
|
|
412
|
-
if (e instanceof Error) return `${e.name}: ${e.message}`;
|
|
413
|
-
return String(e);
|
|
414
|
-
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// The `jobs` core service over this app's job host and store: what a module's
|
|
2
|
+
// job runners run on. A module submits by kind and the loaded module's runner
|
|
3
|
+
// spec decides how it runs, recorded in the same job store (and streamed by
|
|
4
|
+
// the same `/api/jobs` routes) as every other job: a `task` runs in this
|
|
5
|
+
// process; a `process` runs as a child that stops with its parent, under the
|
|
6
|
+
// host's execution lease (the child activates the module itself,
|
|
7
|
+
// `cli/job-entry.ts`).
|
|
8
|
+
import type { JobEmit, JobEvent, JobRecord, JobRunner, JobRunnerSpec, JobService } from "@mlx-bun/app-core";
|
|
9
|
+
import type { DisposableResource } from "@mlx-bun/inference/contracts/portable";
|
|
10
|
+
import type { JobStore } from "./db";
|
|
11
|
+
import type { JobRow, JobRunner as AppJobRunner } from "./protocol";
|
|
12
|
+
import { tailJob } from "./sse";
|
|
13
|
+
|
|
14
|
+
/** The runners of the loaded modules, keyed by job kind (`LoadedModules.jobs`). */
|
|
15
|
+
export type JobRunners = ReadonlyMap<string, { readonly spec: JobRunnerSpec; readonly runner: JobRunner }>;
|
|
16
|
+
|
|
17
|
+
/** The part of the job host the service drives. */
|
|
18
|
+
export interface JobTasks {
|
|
19
|
+
ensureStore(): JobStore;
|
|
20
|
+
submitTask(kind: string, config: Record<string, unknown>, runner: AppJobRunner, outputPath?: string): { jobId: string };
|
|
21
|
+
cancelTask(jobId: string): Promise<void>;
|
|
22
|
+
/** Queue a child process for the kind under the host's execution lease. */
|
|
23
|
+
submit(kind: string, config: Record<string, unknown>, outputPath?: string, scratchDir?: string): { jobId: string };
|
|
24
|
+
cancelProcess(jobId: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AppJobService extends JobService {
|
|
28
|
+
/** Serve the job kinds of the loaded modules; before this, every submission is rejected. */
|
|
29
|
+
serve(runners: JobRunners): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const recordOf = (row: JobRow): JobRecord => ({ id: row.id, kind: row.kind, status: row.status, progress: row.progress, message: row.message,
|
|
33
|
+
outputPath: row.output_path, error: row.error, startedAt: row.started_at, endedAt: row.ended_at });
|
|
34
|
+
|
|
35
|
+
export interface JobServiceOptions {
|
|
36
|
+
/** The engine's execution lease: a runner that declared `gpu: "exclusive"` holds it for its whole run, so no model generates meanwhile. */
|
|
37
|
+
acquire?(signal: AbortSignal): Promise<DisposableResource>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createJobService(host: JobTasks, options: JobServiceOptions = {}): AppJobService {
|
|
41
|
+
let runners: JobRunners = new Map();
|
|
42
|
+
const store = () => host.ensureStore();
|
|
43
|
+
return {
|
|
44
|
+
serve(served) { runners = served; },
|
|
45
|
+
async submit(submission) {
|
|
46
|
+
const registered = runners.get(submission.kind);
|
|
47
|
+
if (!registered) throw new Error(`no installed module runs job kind "${submission.kind}"`);
|
|
48
|
+
const { isolation, gpu } = registered.spec;
|
|
49
|
+
if (isolation === "process") {
|
|
50
|
+
// The host's children run under its execution lease, so no model generates meanwhile.
|
|
51
|
+
if (gpu !== "exclusive") throw new Error(`job kind "${submission.kind}": this host runs process jobs only with the exclusive GPU lease`);
|
|
52
|
+
const { jobId } = host.submit(submission.kind, { ...submission.config }, submission.outputPath, submission.scratchDir);
|
|
53
|
+
return recordOf(store().get(jobId)!);
|
|
54
|
+
}
|
|
55
|
+
// The module's runner speaks the contract's event type; the store records every event the same way.
|
|
56
|
+
const exclusive = registered.spec.gpu === "exclusive" && options.acquire;
|
|
57
|
+
const run: AppJobRunner = async (emit, config, signal) => {
|
|
58
|
+
const stop = signal ?? new AbortController().signal;
|
|
59
|
+
if (!exclusive) return registered.runner(emit as JobEmit, config, stop);
|
|
60
|
+
emit({ type: "stage", stage: "waiting", progress: 0, message: "waiting for in-flight generation to drain" });
|
|
61
|
+
const lease = await exclusive(stop);
|
|
62
|
+
try { return await registered.runner(emit as JobEmit, config, stop); } finally { lease.dispose(); }
|
|
63
|
+
};
|
|
64
|
+
const { jobId } = host.submitTask(submission.kind, { ...submission.config }, run, submission.outputPath);
|
|
65
|
+
return recordOf(store().get(jobId)!);
|
|
66
|
+
},
|
|
67
|
+
async get(id) { const row = store().get(id); return row ? recordOf(row) : undefined; },
|
|
68
|
+
async list(filter) {
|
|
69
|
+
// The store's newest-first window; a status filter applies within it.
|
|
70
|
+
return store().recent(1000, filter?.kind).filter(row => !filter?.status || row.status === filter.status).map(recordOf);
|
|
71
|
+
},
|
|
72
|
+
async cancel(id) { await Promise.all([host.cancelTask(id), host.cancelProcess(id)]); },
|
|
73
|
+
events(id, signal) { return tailJob(store(), id, { follow: true, ...(signal ? { signal } : {}) }) as AsyncIterable<JobEvent>; },
|
|
74
|
+
};
|
|
75
|
+
}
|