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
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { ModelBinding } from "../engine/model-binding";
|
|
2
|
+
import type { LoadedModelContext } from "../engine/model-host";
|
|
3
|
+
import type { PreparationExecutor } from "../engine/preparation";
|
|
4
|
+
import type { ModelPromptBuilder } from "./prompt-contracts";
|
|
5
|
+
import { modelPromptBuilder } from "./prompt-builder";
|
|
6
|
+
import { createRequestPrep } from "./request-prep";
|
|
7
|
+
import { RequestOwnership } from "./request-plan";
|
|
8
|
+
import { RequestError } from "./pipeline";
|
|
9
|
+
import { errorResponse } from "./http";
|
|
10
|
+
import { readDecisionBody } from "./decision-body";
|
|
11
|
+
import { decisionAnswer, decisionCandidates, decisionMessages, DecisionValidationError,
|
|
12
|
+
parseJevDecision, parseOpenAiDecision } from "./decision-wire";
|
|
13
|
+
|
|
14
|
+
export function createDecisionRoutes(engine: {
|
|
15
|
+
context: LoadedModelContext;
|
|
16
|
+
binding: Pick<ModelBinding, "decide"> & Partial<Pick<ModelBinding, "gateway">>;
|
|
17
|
+
preparation: PreparationExecutor;
|
|
18
|
+
}, options: { contextLimit: number | null; defaultAdapter?: string; buildPrompt?: ModelPromptBuilder }) {
|
|
19
|
+
const ctx = engine.context, build = options.buildPrompt ?? modelPromptBuilder(ctx);
|
|
20
|
+
const prep = createRequestPrep({ ctx, serverOptions: { defaultThinking: false }, defaultGeneratedTokens: undefined });
|
|
21
|
+
return {
|
|
22
|
+
async handle(request: Request): Promise<Response | null> {
|
|
23
|
+
const path = new URL(request.url).pathname;
|
|
24
|
+
if (request.method !== "POST") return null;
|
|
25
|
+
const jev = path === "/v1/systemone";
|
|
26
|
+
if (!jev && path !== "/v1/decisions") return null;
|
|
27
|
+
const requestOwnership = new RequestOwnership();
|
|
28
|
+
try {
|
|
29
|
+
const reservation = await engine.preparation.reserve?.("decision", request.signal);
|
|
30
|
+
if (reservation) requestOwnership.retain(reservation);
|
|
31
|
+
const body = await readDecisionBody(request);
|
|
32
|
+
const parsed = jev ? parseJevDecision(body) : parseOpenAiDecision(body);
|
|
33
|
+
const decide = engine.binding.decide?.bind(engine.binding);
|
|
34
|
+
if (!decide) throw new RequestError(501, `model "${ctx.modelId}" declares no decision scoring operation`,
|
|
35
|
+
{ message: `model "${ctx.modelId}" declares no decision scoring operation`, type: "not_implemented", code: "unsupported_execution" });
|
|
36
|
+
const schedule = <T>(work: () => Promise<T>): Promise<T> => engine.preparation.run(work, request.signal);
|
|
37
|
+
request.signal.throwIfAborted();
|
|
38
|
+
const ending = decisionAnswerEnding(ctx, prep);
|
|
39
|
+
const answers: Record<string, unknown>[] = [];
|
|
40
|
+
let inputTokens = 0;
|
|
41
|
+
for (const question of parsed.questions) {
|
|
42
|
+
request.signal.throwIfAborted();
|
|
43
|
+
const candidates = decisionCandidates(ctx.tokenizer, question, ending);
|
|
44
|
+
const ownership = new RequestOwnership();
|
|
45
|
+
try {
|
|
46
|
+
const messages = decisionMessages(parsed.input, question);
|
|
47
|
+
const media = messages.some(message => Array.isArray(message.content) && message.content.some(part => part.type !== "text"));
|
|
48
|
+
if (media) {
|
|
49
|
+
const reservation = await engine.preparation.reserve?.("media", request.signal);
|
|
50
|
+
if (reservation) ownership.retain(reservation);
|
|
51
|
+
}
|
|
52
|
+
let built: Awaited<ReturnType<ModelPromptBuilder>>;
|
|
53
|
+
try {
|
|
54
|
+
built = await build({ messages, reasoning_effort: "none", chat_template_kwargs: { enable_thinking: false } },
|
|
55
|
+
null, ownership, prep, schedule);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (error instanceof RequestError) throw error;
|
|
58
|
+
throw new RequestError(400, `prompt build failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
59
|
+
}
|
|
60
|
+
ownership.own(built.vision?.embeddings);
|
|
61
|
+
ownership.own(built.vision?.imageMask);
|
|
62
|
+
ownership.own(built.vision?.multimodalMask);
|
|
63
|
+
ownership.own(built.diffusionPixels);
|
|
64
|
+
request.signal.throwIfAborted();
|
|
65
|
+
if (built.diffusionPixels) throw new RequestError(501, "pixel-input decision scoring is unavailable for this graph");
|
|
66
|
+
if (options.contextLimit !== null && candidates.some(answer => built.promptIds.length + answer.length - 1 > options.contextLimit!))
|
|
67
|
+
throw new RequestError(400, `decision prompt exceeds the ${options.contextLimit}-token context limit`,
|
|
68
|
+
{ message: "decision prompt exceeds the context limit", type: "invalid_request_error", code: "max_tokens_exceeded" });
|
|
69
|
+
const adapters = options.defaultAdapter ? [options.defaultAdapter] : [];
|
|
70
|
+
const probabilities = await decide({ prompt: built.promptIds, candidates, vision: built.vision, signal: request.signal,
|
|
71
|
+
execute: work => schedule(async () => {
|
|
72
|
+
request.signal.throwIfAborted();
|
|
73
|
+
const restore = engine.binding.gateway?.bindAdapterContext?.(adapters, `decision:${JSON.stringify(adapters)}`).enter();
|
|
74
|
+
try { return await work(); } finally { restore?.(); }
|
|
75
|
+
}) });
|
|
76
|
+
request.signal.throwIfAborted();
|
|
77
|
+
if (probabilities.length !== candidates.length || probabilities.some(p => !Number.isFinite(p) || p < 0 || p > 1) ||
|
|
78
|
+
Math.abs(probabilities.reduce((sum, p) => sum + p, 0) - 1) > 1e-5)
|
|
79
|
+
throw new Error("decision binding returned an invalid probability distribution");
|
|
80
|
+
inputTokens += candidates.reduce((sum, answer) => sum + built.promptIds.length + answer.length - 1, 0);
|
|
81
|
+
answers.push(decisionAnswer(question, probabilities, jev));
|
|
82
|
+
} finally { ownership.dispose(); }
|
|
83
|
+
await new Promise<void>(resolve => setImmediate(resolve));
|
|
84
|
+
}
|
|
85
|
+
request.signal.throwIfAborted();
|
|
86
|
+
return Response.json({ model: ctx.modelId,
|
|
87
|
+
answers: jev ? Object.fromEntries(parsed.questions.map((q, i) => [q.name!, answers[i]])) : answers,
|
|
88
|
+
usage: jev ? { input_tokens: inputTokens, output_tokens: 0 } : {
|
|
89
|
+
input_tokens: inputTokens, input_tokens_details: { cached_tokens: 0, cache_write_tokens: 0 },
|
|
90
|
+
output_tokens: 0, output_tokens_details: { reasoning_tokens: 0 }, total_tokens: inputTokens,
|
|
91
|
+
} });
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error instanceof DecisionValidationError) return jev
|
|
94
|
+
? Response.json({ detail: [{ loc: ["body", ...error.field.split(".")], msg: error.message, type: "value_error" }] }, { status: 422 })
|
|
95
|
+
: Response.json({ error: { message: error.message, type: "invalid_request_error", param: error.field, code: null } }, { status: 400 });
|
|
96
|
+
return errorResponse(error, path, undefined, request.signal);
|
|
97
|
+
} finally { requestOwnership.dispose(); }
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Score the template's completed assistant turn through its first stop marker,
|
|
103
|
+
* excluding layout after the marker. Encode answer + ending together so BPE
|
|
104
|
+
* merges at that boundary match the supplied text. */
|
|
105
|
+
function decisionAnswerEnding(ctx: LoadedModelContext, prep: ReturnType<typeof createRequestPrep>): string {
|
|
106
|
+
const stops = [...new Set([...ctx.model.config.eosTokenIds, ...(ctx.tokenizer.eosTokenId === null ? [] : [ctx.tokenizer.eosTokenId])])]
|
|
107
|
+
.map(id => ctx.tokenizer.decode([id], false)).filter(Boolean);
|
|
108
|
+
if (ctx.template) {
|
|
109
|
+
const marker = "mlx_bun_decision_answer_9d041a";
|
|
110
|
+
const messages = [{ role: "user" as const, content: "x" }, { role: "assistant" as const, content: marker }];
|
|
111
|
+
const options = { ...prep.templateOptionsFor({ messages,
|
|
112
|
+
reasoning_effort: "none", chat_template_kwargs: { enable_thinking: false } }, null), addGenerationPrompt: false };
|
|
113
|
+
const afterAnswer = (turns: typeof messages) => {
|
|
114
|
+
const rendered = ctx.template!.render(turns, options), at = rendered.indexOf(marker);
|
|
115
|
+
if (at < 0 || rendered.indexOf(marker, at + marker.length) >= 0)
|
|
116
|
+
throw new RequestError(501, "decision scoring requires a chat template that preserves assistant answers");
|
|
117
|
+
return rendered.slice(at + marker.length);
|
|
118
|
+
};
|
|
119
|
+
const throughStop = (suffix: string) => {
|
|
120
|
+
const first = stops.map(text => ({ text, at: suffix.indexOf(text) })).filter(stop => stop.at >= 0)
|
|
121
|
+
.sort((a, b) => a.at - b.at)[0];
|
|
122
|
+
return first ? suffix.slice(0, first.at + first.text.length) : undefined;
|
|
123
|
+
};
|
|
124
|
+
const suffix = afterAnswer(messages);
|
|
125
|
+
// GLM-style templates close an assistant answer with the next role marker.
|
|
126
|
+
// Only keep that boundary, never the synthetic next user's content.
|
|
127
|
+
const ending = throughStop(suffix) ?? throughStop(afterAnswer([...messages, { role: "user", content: "x" }]));
|
|
128
|
+
if (ending !== undefined) return ending;
|
|
129
|
+
if (suffix.trim()) return suffix;
|
|
130
|
+
}
|
|
131
|
+
const fallback = ctx.tokenizer.eosTokenId ?? ctx.model.config.eosTokenIds[0];
|
|
132
|
+
if (fallback === undefined || fallback === null) throw new RequestError(501, "decision scoring requires an answer terminator token");
|
|
133
|
+
return ctx.tokenizer.decode([fallback], false);
|
|
134
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type { LoadedTokenizer } from "@mlx-bun/inference/input";
|
|
2
|
+
import type { ChatRequestParams } from "./chat-request";
|
|
3
|
+
|
|
4
|
+
type Messages = ChatRequestParams["messages"];
|
|
5
|
+
export interface DecisionOption { value: string | boolean | number; label: string; description: string; legend?: unknown }
|
|
6
|
+
export interface DecisionQuestion {
|
|
7
|
+
type: "predicate" | "choice" | "score";
|
|
8
|
+
name: string | null;
|
|
9
|
+
instructions: string;
|
|
10
|
+
options: DecisionOption[];
|
|
11
|
+
images?: { reference: string; url: string }[];
|
|
12
|
+
}
|
|
13
|
+
export interface DecisionRequest {
|
|
14
|
+
model: string;
|
|
15
|
+
input: Messages;
|
|
16
|
+
questions: DecisionQuestion[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Carries a field location for both OpenAI errors and Jev's 422 details. */
|
|
20
|
+
export class DecisionValidationError extends Error {
|
|
21
|
+
constructor(readonly field: string, message: string) { super(`${field}: ${message}`); }
|
|
22
|
+
}
|
|
23
|
+
const fail = (field: string, message: string): never => { throw new DecisionValidationError(field, message); };
|
|
24
|
+
function record(value: unknown, field: string): Record<string, unknown> {
|
|
25
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail(field, "must be an object");
|
|
26
|
+
return value as Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
function string(value: unknown, field: string): string {
|
|
29
|
+
if (typeof value !== "string") return fail(field, "must be a string");
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function list(value: unknown, field: string, minimum: number, maximum: number): unknown[] {
|
|
33
|
+
if (!Array.isArray(value) || value.length < minimum || value.length > maximum)
|
|
34
|
+
return fail(field, `must contain ${minimum}–${maximum} entries`);
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
function text(value: unknown, field: string, nullable = false): string {
|
|
38
|
+
if (value === null && nullable) return "";
|
|
39
|
+
if (typeof value !== "string" && (!value || typeof value !== "object")) return fail(field, "must be text, an object, or an array");
|
|
40
|
+
finiteJson(value, field);
|
|
41
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
42
|
+
}
|
|
43
|
+
function finiteJson(value: unknown, field: string, depth = 0): void {
|
|
44
|
+
if (depth > 64) return fail(field, "exceeds 64 nested levels");
|
|
45
|
+
if (typeof value === "number" && !Number.isFinite(value)) return fail(field, "numbers must be finite");
|
|
46
|
+
if (value && typeof value === "object") for (const child of Object.values(value)) finiteJson(child, field, depth + 1);
|
|
47
|
+
}
|
|
48
|
+
const predicateOptions = (): DecisionOption[] => [
|
|
49
|
+
{ value: false, label: "false", description: "The answer is no; the condition is false." },
|
|
50
|
+
{ value: true, label: "true", description: "The answer is yes; the condition is true." },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/** OpenAI Decisions request contract: https://developers.openai.com/api/reference/resources/decisions/methods/create */
|
|
54
|
+
export function parseOpenAiDecision(value: unknown): DecisionRequest {
|
|
55
|
+
const body = record(value, "body");
|
|
56
|
+
const model = string(body.model, "model");
|
|
57
|
+
let imageCount = 0;
|
|
58
|
+
const input: Messages = typeof body.input === "string" ? [{ role: "user", content: body.input }]
|
|
59
|
+
: list(body.input, "input", 1, Number.MAX_SAFE_INTEGER).map((entry, i) => {
|
|
60
|
+
const at = `input.${i}`, message = record(entry, at);
|
|
61
|
+
if (message.role !== "user" || (message.type !== undefined && message.type !== "message"))
|
|
62
|
+
return fail(at, "must be a user message");
|
|
63
|
+
const content = typeof message.content === "string" ? message.content
|
|
64
|
+
: list(message.content, `${at}.content`, 1, Number.MAX_SAFE_INTEGER).map((entry, j) => {
|
|
65
|
+
const part = record(entry, `${at}.content.${j}`);
|
|
66
|
+
if (part.type === "input_text") return { type: "text", text: string(part.text, `${at}.content.${j}.text`) };
|
|
67
|
+
if (part.type === "input_image") {
|
|
68
|
+
imageCount++;
|
|
69
|
+
if (imageCount > 128) return fail("input", "supports at most 128 images");
|
|
70
|
+
const detail = part.detail;
|
|
71
|
+
if (detail != null && !["auto", "low", "high", "original"].includes(String(detail)))
|
|
72
|
+
return fail(`${at}.content.${j}.detail`, "invalid image detail");
|
|
73
|
+
return { type: "image_url", image_url: { url: string(part.image_url, `${at}.content.${j}.image_url`), ...(detail ? { detail } : {}) } };
|
|
74
|
+
}
|
|
75
|
+
return fail(`${at}.content.${j}.type`, "expected input_text or input_image");
|
|
76
|
+
});
|
|
77
|
+
return { role: "user", content };
|
|
78
|
+
});
|
|
79
|
+
const questions = list(body.questions, "questions", 1, 200).map((entry, i): DecisionQuestion => {
|
|
80
|
+
const at = `questions.${i}`, q = record(entry, at), instructions = string(q.instructions, `${at}.instructions`);
|
|
81
|
+
const name = q.name === undefined ? null : string(q.name, `${at}.name`);
|
|
82
|
+
if (q.type === "predicate") return { type: "predicate", name, instructions, options: predicateOptions() };
|
|
83
|
+
if (q.type === "choice") {
|
|
84
|
+
const options = list(q.choices, `${at}.choices`, 2, 255).map((entry, j) => {
|
|
85
|
+
const choice = record(entry, `${at}.choices.${j}`), value = choice.value;
|
|
86
|
+
if (typeof value !== "string" && typeof value !== "boolean") return fail(`${at}.choices.${j}.value`, "must be a string or boolean");
|
|
87
|
+
return { value, label: JSON.stringify(value), description: choice.description === undefined ? "" : string(choice.description, `${at}.choices.${j}.description`) };
|
|
88
|
+
});
|
|
89
|
+
if (new Set(options.map(option => JSON.stringify(option.value))).size !== options.length)
|
|
90
|
+
return fail(`${at}.choices`, "values must be unique (strings and booleans are distinct)");
|
|
91
|
+
return { type: "choice", name, instructions, options };
|
|
92
|
+
}
|
|
93
|
+
if (q.type === "score") {
|
|
94
|
+
const options = list(q.levels, `${at}.levels`, 2, 255).map((entry, j) => {
|
|
95
|
+
const level = record(entry, `${at}.levels.${j}`);
|
|
96
|
+
return { value: j, label: string(level.label, `${at}.levels.${j}.label`),
|
|
97
|
+
description: level.description === undefined ? "" : string(level.description, `${at}.levels.${j}.description`) };
|
|
98
|
+
});
|
|
99
|
+
return { type: "score", name, instructions, options };
|
|
100
|
+
}
|
|
101
|
+
return fail(`${at}.type`, "expected predicate, choice, or score");
|
|
102
|
+
});
|
|
103
|
+
return { model, input, questions };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Jev System One contract: https://docs.typesafe.ai/api */
|
|
107
|
+
export function parseJevDecision(value: unknown): DecisionRequest {
|
|
108
|
+
const body = record(value, "body"), model = string(body.model, "model");
|
|
109
|
+
const state = text(body.state, "state");
|
|
110
|
+
const assets = new Map<string, string>(), used = new Set<string>();
|
|
111
|
+
for (const [reference, value] of Object.entries(body.assets == null ? {} : record(body.assets, "assets"))) {
|
|
112
|
+
if (!reference.trim() || Buffer.byteLength(reference) > 1024) return fail("assets", "invalid image reference");
|
|
113
|
+
const asset = record(value, `assets.${reference}`);
|
|
114
|
+
if (asset.type !== undefined && asset.type !== "image") return fail(`assets.${reference}.type`, "expected image");
|
|
115
|
+
if (!["png", "jpg", "jpeg"].includes(String(asset.format))) return fail(`assets.${reference}.format`, "expected png, jpg, or jpeg");
|
|
116
|
+
const data = string(asset.data_base64, `assets.${reference}.data_base64`);
|
|
117
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(data) || !data.length)
|
|
118
|
+
return fail(`assets.${reference}.data_base64`, "invalid base64 image");
|
|
119
|
+
assets.set(reference, `data:image/${asset.format === "png" ? "png" : "jpeg"};base64,${data}`);
|
|
120
|
+
}
|
|
121
|
+
// Only values may refer to images. IDs and option keys stay text. Matching is
|
|
122
|
+
// left-to-right, with whole-value matches taking precedence over substrings.
|
|
123
|
+
const imagesIn = (value: unknown): { reference: string; url: string }[] => {
|
|
124
|
+
if (value && typeof value === "object") return Object.values(value).flatMap(imagesIn);
|
|
125
|
+
if (typeof value !== "string") return [];
|
|
126
|
+
if (assets.has(value)) { used.add(value); return [{ reference: value, url: assets.get(value)! }]; }
|
|
127
|
+
const found: { reference: string; url: string }[] = [];
|
|
128
|
+
for (let position = 0; position < value.length;) {
|
|
129
|
+
const matches = [...assets.keys()].map(reference => ({ reference, at: value.indexOf(reference, position) }))
|
|
130
|
+
.filter(match => match.at >= 0).sort((a, b) => a.at - b.at || b.reference.length - a.reference.length);
|
|
131
|
+
const match = matches[0];
|
|
132
|
+
if (!match) break;
|
|
133
|
+
found.push({ reference: match.reference, url: assets.get(match.reference)! }); used.add(match.reference);
|
|
134
|
+
position = match.at + match.reference.length;
|
|
135
|
+
}
|
|
136
|
+
return found;
|
|
137
|
+
};
|
|
138
|
+
const stateImages = imagesIn(body.state);
|
|
139
|
+
const entries = Object.entries(record(body.questions, "questions"));
|
|
140
|
+
if (!entries.length) return fail("questions", "must contain at least one question");
|
|
141
|
+
const questions = entries.map(([name, entry]): DecisionQuestion => {
|
|
142
|
+
const at = `questions.${name}`, q = record(entry, at), instructions = text(q.instructions ?? null, `${at}.instructions`, true);
|
|
143
|
+
if (q.type === "noul") {
|
|
144
|
+
const options = predicateOptions();
|
|
145
|
+
if (q.criteria != null) {
|
|
146
|
+
const criteria = record(q.criteria, `${at}.criteria`);
|
|
147
|
+
for (const option of options) if (Object.hasOwn(criteria, String(option.value)))
|
|
148
|
+
option.description = text(criteria[String(option.value)], `${at}.criteria.${option.value}`, true);
|
|
149
|
+
}
|
|
150
|
+
return { type: "predicate", name, instructions, options, images: [...imagesIn(q.instructions), ...imagesIn(q.criteria)] };
|
|
151
|
+
}
|
|
152
|
+
if (q.type === "choice") {
|
|
153
|
+
const choices = Object.entries(record(q.criteria, `${at}.criteria`));
|
|
154
|
+
if (choices.length < 1 || choices.length > 255) return fail(`${at}.criteria`, "requires 1–255 options");
|
|
155
|
+
return { type: "choice", name, instructions, images: [...imagesIn(q.instructions), ...imagesIn(q.criteria)], options: choices.map(([value, description]) => ({
|
|
156
|
+
value, label: JSON.stringify(value), description: text(description, `${at}.criteria.${value}`, true),
|
|
157
|
+
})) };
|
|
158
|
+
}
|
|
159
|
+
if (q.type === "score") return { type: "score", name, instructions, images: [...imagesIn(q.instructions), ...imagesIn(q.criteria)],
|
|
160
|
+
options: list(q.criteria, `${at}.criteria`, 2, 10).map((entry, index) => ({
|
|
161
|
+
value: index, label: text(entry, `${at}.criteria.${index}`, true) || String(index), description: "", legend: entry,
|
|
162
|
+
})) };
|
|
163
|
+
return fail(`${at}.type`, "expected noul, choice, or score");
|
|
164
|
+
});
|
|
165
|
+
if (used.size !== assets.size) return fail("assets", "every image asset must be referenced");
|
|
166
|
+
return { model, input: [{ role: "user", content: stateImages.length ? [
|
|
167
|
+
{ type: "text", text: state }, ...imageParts(stateImages),
|
|
168
|
+
] : state }], questions };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function imageParts(images: { reference: string; url: string }[]): { type: string; [key: string]: unknown }[] {
|
|
172
|
+
return images.flatMap(image => [{ type: "text", text: `Image reference: ${image.reference}` },
|
|
173
|
+
{ type: "image_url", image_url: { url: image.url } }]);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Exact wire values encoded as JSON keep string "true" distinct from boolean
|
|
177
|
+
* true, preserve whitespace and empty strings, and never shorten multi-token
|
|
178
|
+
* answers. Score questions use their supplied labels as the answer text. */
|
|
179
|
+
export function decisionCandidates(tokenizer: Pick<LoadedTokenizer, "encode">, question: DecisionQuestion, ending: string): number[][] {
|
|
180
|
+
return question.options.map(option => tokenizer.encode(
|
|
181
|
+
JSON.stringify(question.type === "score" ? option.label : option.value) + ending, false));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function decisionMessages(input: Messages, question: DecisionQuestion): Messages {
|
|
185
|
+
const menu = question.options.map(option => `${question.type === "score" ? JSON.stringify(option.label) : JSON.stringify(option.value)}${option.description ? ` — ${option.description}` : ""}`).join("\n");
|
|
186
|
+
const questionText = `Evaluate the preceding input.\nQuestion: ${question.instructions}\nAllowed answers${question.type === "score" ? " (ordered from lowest to highest)" : ""}:\n${menu}\nAnswer with exactly one of the JSON values above. Do not explain.`;
|
|
187
|
+
// Both protocols supply user input. Keep it in one turn so templates that
|
|
188
|
+
// enforce user/assistant alternation accept it, retaining media order.
|
|
189
|
+
const content = input.flatMap((message, i) => [
|
|
190
|
+
...(i ? [{ type: "text" as const, text: "\n\n" }] : []),
|
|
191
|
+
...(typeof message.content === "string" ? [{ type: "text" as const, text: message.content }] : message.content ?? []),
|
|
192
|
+
]);
|
|
193
|
+
content.push({ type: "text", text: `\n\n${questionText}` }, ...imageParts(question.images ?? []));
|
|
194
|
+
return [{ role: "user", content: content.every(part => part.type === "text")
|
|
195
|
+
? content.map(part => (part as { text: string }).text).join("") : content }];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function winner(probabilities: readonly number[]): number {
|
|
199
|
+
return probabilities.reduce((best, p, i) => p > probabilities[best]! ? i : best, 0);
|
|
200
|
+
}
|
|
201
|
+
/** Jev's documented distribution-derived confidence, including ordered levels.
|
|
202
|
+
* OpenAI does not publish a confidence formula; the local adapter uses this
|
|
203
|
+
* same statistic. This is a statistic of the likelihoods, with no learned
|
|
204
|
+
* calibration stage. */
|
|
205
|
+
export function decisionConfidence(probabilities: readonly number[], ordered: boolean): number {
|
|
206
|
+
const n = probabilities.length, best = winner(probabilities);
|
|
207
|
+
if (n === 1) return 1;
|
|
208
|
+
if (!ordered) return Math.max(0, Math.min(1, (probabilities[best]! * n - 1) / (n - 1)));
|
|
209
|
+
const spread = probabilities.reduce((sum, p, i) => sum + p * Math.abs(i - best), 0);
|
|
210
|
+
const uniform = probabilities.reduce((sum, _, i) => sum + Math.abs(i - (n - 1) / 2) / n, 0);
|
|
211
|
+
return Math.max(0, Math.min(1, 1 - spread / uniform));
|
|
212
|
+
}
|
|
213
|
+
export function decisionAnswer(question: DecisionQuestion, probabilities: number[], jev: boolean): Record<string, unknown> {
|
|
214
|
+
const { type, name, options } = question;
|
|
215
|
+
if (type === "predicate") return jev ? { type: "noul", noul: probabilities[1] }
|
|
216
|
+
: { type, name, probability: probabilities[1] };
|
|
217
|
+
const confidence = decisionConfidence(probabilities, type === "score");
|
|
218
|
+
if (type === "choice") {
|
|
219
|
+
const choice = options[winner(probabilities)]!.value;
|
|
220
|
+
return jev ? { type, choice, confidence, probabilities: Object.fromEntries(options.map((option, i) => [option.value, probabilities[i]])) }
|
|
221
|
+
: { type, name, choice, confidence, probabilities: options.map((option, i) => ({ value: option.value, probability: probabilities[i] })) };
|
|
222
|
+
}
|
|
223
|
+
const score = probabilities.reduce((sum, p, i) => sum + p * i, 0);
|
|
224
|
+
return jev ? { type, score, confidence,
|
|
225
|
+
legend: Object.fromEntries(options.map((option, i) => [i, Object.hasOwn(option, "legend") ? option.legend : option.label])),
|
|
226
|
+
probabilities: Object.fromEntries(probabilities.map((p, i) => [i, p])) }
|
|
227
|
+
: { type, name, score, confidence, probabilities: options.map((option, i) => ({ value: i, label: option.label, probability: probabilities[i] })) };
|
|
228
|
+
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Registry } from "@mlx-bun/hub/registry";
|
|
2
|
+
import { openRegistry } from "../storage/paths";
|
|
3
|
+
import type { ModelBinding } from "../engine/model-binding";
|
|
2
4
|
import pkgJson from "../../package.json" with { type: "json" };
|
|
3
|
-
import type {
|
|
4
|
-
import type { GenerationGateway } from "./generation-gateway";
|
|
5
|
-
import { fit } from "../fit";
|
|
5
|
+
import type { LoadedModelContext as ModelContext } from "../engine/model-host";
|
|
6
6
|
|
|
7
7
|
const pkgVersion = (pkgJson as { version: string }).version;
|
|
8
8
|
|
|
9
9
|
export type DiscoveryRoute =
|
|
10
|
-
| "library"
|
|
11
|
-
| "downloads"
|
|
12
10
|
| "api-index"
|
|
13
11
|
| "health"
|
|
14
12
|
| "models";
|
|
@@ -16,8 +14,6 @@ export type DiscoveryRoute =
|
|
|
16
14
|
export function matchDiscoveryRoute(method: string, pathname: string): DiscoveryRoute | null {
|
|
17
15
|
if (method !== "GET") return null;
|
|
18
16
|
switch (pathname) {
|
|
19
|
-
case "/library": return "library";
|
|
20
|
-
case "/downloads": return "downloads";
|
|
21
17
|
case "/v1": return "api-index";
|
|
22
18
|
case "/health": return "health";
|
|
23
19
|
case "/v1/models": return "models";
|
|
@@ -27,74 +23,25 @@ export function matchDiscoveryRoute(method: string, pathname: string): Discovery
|
|
|
27
23
|
|
|
28
24
|
export interface DiscoveryRoutes {
|
|
29
25
|
handle(url: URL, request: Request): Promise<Response | null>;
|
|
30
|
-
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface TranscriptionInfo {
|
|
29
|
+
id: string;
|
|
30
|
+
resident: boolean;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export function createDiscoveryRoutes(
|
|
34
|
-
ctx:
|
|
35
|
-
|
|
34
|
+
ctx: ModelContext,
|
|
35
|
+
binding: Pick<ModelBinding, "discovery">,
|
|
36
36
|
startedAt: number,
|
|
37
|
+
transcription: () => Promise<TranscriptionInfo | null> = async () => null,
|
|
38
|
+
createRegistry: () => Pick<Registry, "scan" | "listCanonical" | "close"> = () => openRegistry(),
|
|
39
|
+
/** The server's own sampling defaults (`--temperature`, `--top-p`, `--top-k`): they win over the model's, as they do for a request that names none. */
|
|
40
|
+
serverDefaults: { temperature?: number; topP?: number; topK?: number } = {},
|
|
37
41
|
): DiscoveryRoutes {
|
|
38
|
-
let libraryCache: { at: number; rows: unknown[] } | null = null;
|
|
39
|
-
|
|
40
42
|
return {
|
|
41
|
-
invalidateLibrary() {
|
|
42
|
-
libraryCache = null;
|
|
43
|
-
},
|
|
44
|
-
|
|
45
43
|
async handle(url, request) {
|
|
46
44
|
switch (matchDiscoveryRoute(request.method, url.pathname)) {
|
|
47
|
-
case "library": {
|
|
48
|
-
if (url.searchParams.get("refresh") === "1" || !libraryCache || Date.now() - libraryCache.at > 30_000) {
|
|
49
|
-
const { Registry, visionCapable, audioCapable } = await import("../registry");
|
|
50
|
-
const { loadModelConfig } = await import("../config");
|
|
51
|
-
const { supportTier } = await import("../model/support");
|
|
52
|
-
const registry = new Registry();
|
|
53
|
-
await registry.scan();
|
|
54
|
-
const rows = [];
|
|
55
|
-
for (const model of registry.listCanonical()) {
|
|
56
|
-
const tier = supportTier(model.modelType, model.repoId);
|
|
57
|
-
const supported = tier !== null;
|
|
58
|
-
let assessment = null;
|
|
59
|
-
try {
|
|
60
|
-
const config = await loadModelConfig(model.path);
|
|
61
|
-
const result = fit(
|
|
62
|
-
config,
|
|
63
|
-
model.sizeBytes,
|
|
64
|
-
8192,
|
|
65
|
-
undefined,
|
|
66
|
-
undefined,
|
|
67
|
-
model.expertsBytes,
|
|
68
|
-
);
|
|
69
|
-
assessment = {
|
|
70
|
-
fits: result.fits,
|
|
71
|
-
max_safe_context: result.maxSafeContext,
|
|
72
|
-
predicted_decode_tps: result.predictedDecodeTps,
|
|
73
|
-
};
|
|
74
|
-
} catch {}
|
|
75
|
-
rows.push({
|
|
76
|
-
repo_id: model.repoId,
|
|
77
|
-
model_type: model.modelType,
|
|
78
|
-
size_bytes: model.sizeBytes,
|
|
79
|
-
quant_bits: model.quantBits,
|
|
80
|
-
vision: visionCapable(model),
|
|
81
|
-
audio: audioCapable(model),
|
|
82
|
-
supported,
|
|
83
|
-
support_tier: tier,
|
|
84
|
-
serving: model.repoId === ctx.modelId,
|
|
85
|
-
assessment,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
libraryCache = { at: Date.now(), rows };
|
|
89
|
-
}
|
|
90
|
-
return Response.json({ models: libraryCache.rows });
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
case "downloads": {
|
|
94
|
-
const { downloadsSnapshot } = await import("../download");
|
|
95
|
-
return Response.json({ downloads: downloadsSnapshot() });
|
|
96
|
-
}
|
|
97
|
-
|
|
98
45
|
case "api-index":
|
|
99
46
|
return Response.json({
|
|
100
47
|
name: "mlx-bun",
|
|
@@ -106,8 +53,11 @@ export function createDiscoveryRoutes(
|
|
|
106
53
|
"POST /v1/messages",
|
|
107
54
|
"POST /v1/responses",
|
|
108
55
|
"POST /v1/embeddings",
|
|
56
|
+
"POST /v1/decisions",
|
|
57
|
+
"POST /v1/systemone",
|
|
58
|
+
"POST /v1/audio/transcriptions",
|
|
59
|
+
"POST /v1/audio/translations",
|
|
109
60
|
"GET /v1/models",
|
|
110
|
-
"GET/POST/DELETE /v1/adapters",
|
|
111
61
|
"GET /health",
|
|
112
62
|
"GET /stats",
|
|
113
63
|
"GET /fit",
|
|
@@ -127,30 +77,31 @@ export function createDiscoveryRoutes(
|
|
|
127
77
|
: null;
|
|
128
78
|
const created = Math.floor(startedAt / 1000);
|
|
129
79
|
const genDefaults = {
|
|
130
|
-
temperature: ctx.genDefaults.temperature ?? null,
|
|
131
|
-
top_p: ctx.genDefaults.topP ?? null,
|
|
132
|
-
top_k: ctx.genDefaults.topK ?? null,
|
|
80
|
+
temperature: serverDefaults.temperature ?? ctx.genDefaults.temperature ?? null,
|
|
81
|
+
top_p: serverDefaults.topP ?? ctx.genDefaults.topP ?? null,
|
|
82
|
+
top_k: serverDefaults.topK ?? ctx.genDefaults.topK ?? null,
|
|
133
83
|
};
|
|
134
|
-
const capabilities =
|
|
84
|
+
const capabilities = binding.discovery;
|
|
85
|
+
const stt = await transcription();
|
|
135
86
|
const data: Array<Record<string, unknown>> = [{
|
|
136
87
|
id: ctx.modelId,
|
|
137
88
|
object: "model",
|
|
138
89
|
created,
|
|
139
90
|
owned_by: "mlx-bun",
|
|
140
91
|
context_window:
|
|
141
|
-
ctx.
|
|
92
|
+
ctx.memoryPlan?.contextTokens ??
|
|
142
93
|
ctx.model.config.text.maxPositionEmbeddings,
|
|
143
|
-
reasoning: ctx.template
|
|
94
|
+
reasoning: ctx.template?.supportsThinking ?? false,
|
|
144
95
|
vision: !!(ctx.vision || ctx.loadVision),
|
|
145
96
|
audio: !!(ctx.audio || ctx.loadAudio),
|
|
146
|
-
batch_mode:
|
|
97
|
+
batch_mode: "batch",
|
|
147
98
|
tools: true,
|
|
148
99
|
structured_output: true,
|
|
149
100
|
embeddings: capabilities.embeddings,
|
|
150
101
|
adapters: capabilities.adapters,
|
|
151
102
|
training: capabilities.training,
|
|
152
103
|
dsa: capabilities.dsa,
|
|
153
|
-
mtp: ctx.draft?.
|
|
104
|
+
mtp: ctx.draft?.native === true,
|
|
154
105
|
capabilities: {
|
|
155
106
|
chat_completions: true,
|
|
156
107
|
text_completions: true,
|
|
@@ -161,22 +112,30 @@ export function createDiscoveryRoutes(
|
|
|
161
112
|
structured_output: true,
|
|
162
113
|
logprobs: true,
|
|
163
114
|
embeddings: capabilities.embeddings,
|
|
115
|
+
decisions: binding.discovery.decisions ?? false,
|
|
164
116
|
vision: !!(ctx.vision || ctx.loadVision),
|
|
165
117
|
audio: !!(ctx.audio || ctx.loadAudio),
|
|
166
118
|
adapters: capabilities.adapters,
|
|
167
119
|
training: capabilities.training,
|
|
120
|
+
transcription: stt !== null,
|
|
168
121
|
},
|
|
169
122
|
gen_defaults: genDefaults,
|
|
170
123
|
}];
|
|
124
|
+
if (stt)
|
|
125
|
+
data.push({
|
|
126
|
+
id: stt.id, object: "model", created, owned_by: "mlx-bun",
|
|
127
|
+
transcription: true, resident: stt.resident,
|
|
128
|
+
capabilities: { transcription: true, translation: true, chat_completions: false },
|
|
129
|
+
});
|
|
171
130
|
try {
|
|
172
|
-
const {
|
|
173
|
-
const {
|
|
174
|
-
const registry =
|
|
131
|
+
const { visionCapable } = await import("@mlx-bun/hub/registry");
|
|
132
|
+
const { listedSupportTier } = await import("@mlx-bun/inference/models/support");
|
|
133
|
+
const registry = createRegistry();
|
|
175
134
|
try {
|
|
176
|
-
if (registry.
|
|
135
|
+
if (registry.listCanonical().length === 0) await registry.scan();
|
|
177
136
|
for (const model of registry.listCanonical()) {
|
|
178
|
-
if (model.repoId === ctx.modelId) continue;
|
|
179
|
-
const tier =
|
|
137
|
+
if (model.repoId === ctx.modelId || model.repoId === stt?.id) continue;
|
|
138
|
+
const tier = listedSupportTier(model);
|
|
180
139
|
if (tier === null) continue;
|
|
181
140
|
data.push({
|
|
182
141
|
id: model.repoId,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Token provenance for rendered multi-turn prompts. A generated BPE token
|
|
2
|
+
* sequence need not equal encode(decode(ids)). Preserve the sequence that
|
|
3
|
+
* produced the saved inference state when its rendered text is unchanged.
|
|
4
|
+
* This stores only text and IDs; KV ownership and persistence live elsewhere. */
|
|
5
|
+
export interface PromptTokenHistory {
|
|
6
|
+
remember(tokens: readonly number[]): void;
|
|
7
|
+
resolve(rendered: string, canonical: number[]): number[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface TokenCodec {
|
|
11
|
+
encode(text: string, addSpecialTokens?: boolean): number[];
|
|
12
|
+
decode(ids: number[], skipSpecialTokens?: boolean): string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class GeneratedTokenHistory implements PromptTokenHistory {
|
|
16
|
+
readonly #entries = new Map<string, number[]>();
|
|
17
|
+
#chars = 0;
|
|
18
|
+
constructor(readonly codec: TokenCodec, readonly maxChars = 2 ** 21, readonly maxEntries = 16) {}
|
|
19
|
+
|
|
20
|
+
remember(tokens: readonly number[]): void {
|
|
21
|
+
if (!tokens.length) return;
|
|
22
|
+
// Repeated requests can publish the same generated checkpoint. Its text
|
|
23
|
+
// and IDs are already owned here; refresh recency without decoding the
|
|
24
|
+
// entire conversation again on the request's completion path.
|
|
25
|
+
for (const [text, ids] of this.#entries) {
|
|
26
|
+
if (ids.length !== tokens.length || !ids.every((id, i) => id === tokens[i])) continue;
|
|
27
|
+
this.#entries.delete(text);
|
|
28
|
+
this.#entries.set(text, ids);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const ids = [...tokens], text = this.codec.decode(ids, false);
|
|
32
|
+
// An unfinished UTF-8 token is not a text boundary. A later checkpoint
|
|
33
|
+
// can supply a complete prefix instead.
|
|
34
|
+
if (!text.length || text.endsWith("\uFFFD") || text.length > this.maxChars) return;
|
|
35
|
+
if (this.#entries.delete(text)) this.#chars -= text.length;
|
|
36
|
+
this.#entries.set(text, ids); this.#chars += text.length;
|
|
37
|
+
while (this.#entries.size > this.maxEntries || this.#chars > this.maxChars) {
|
|
38
|
+
const oldest = this.#entries.keys().next().value!;
|
|
39
|
+
this.#entries.delete(oldest); this.#chars -= oldest.length;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
resolve(rendered: string, canonical: number[]): number[] {
|
|
44
|
+
let best: { text: string; ids: number[] } | undefined;
|
|
45
|
+
for (const [text, ids] of this.#entries) {
|
|
46
|
+
if (text.length >= rendered.length || (best && text.length <= best.text.length)) continue;
|
|
47
|
+
if (rendered.startsWith(text)) best = { text, ids };
|
|
48
|
+
}
|
|
49
|
+
if (!best) return canonical;
|
|
50
|
+
// Normal canonical output keeps the existing tokenizer fast path.
|
|
51
|
+
if (best.ids.every((id, i) => canonical[i] === id)) return canonical;
|
|
52
|
+
const ids = [...best.ids, ...this.codec.encode(rendered.slice(best.text.length), false)];
|
|
53
|
+
// Decoder cleanup/normalization (e.g. SentencePiece's leading-space
|
|
54
|
+
// rules) can make a standalone suffix unsafe to append.
|
|
55
|
+
return this.codec.decode(ids, false) === rendered ? ids : canonical;
|
|
56
|
+
}
|
|
57
|
+
}
|