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/generate.ts
DELETED
|
@@ -1,1352 +0,0 @@
|
|
|
1
|
-
// Streaming generation — port of mlx-lm's generate_step:
|
|
2
|
-
// - prefill in chunks; cache state evaluated per chunk (bounded transient
|
|
3
|
-
// memory), logits never computed for non-final prefill positions
|
|
4
|
-
// - decode pipelining via mx.async_eval: step n+1's graph is built and
|
|
5
|
-
// dispatched before step n's token is read back, so the GPU never idles
|
|
6
|
-
// on the JS round-trip
|
|
7
|
-
// - opt-in early token-zero yield reduces latency before that pipeline starts
|
|
8
|
-
// - sampling stays on-device; only the chosen token id crosses to JS
|
|
9
|
-
|
|
10
|
-
import { appendFileSync } from "node:fs";
|
|
11
|
-
import { MlxArray, gpuStream } from "./mlx/array";
|
|
12
|
-
import {
|
|
13
|
-
Dtype,
|
|
14
|
-
activeMemory,
|
|
15
|
-
clearCache,
|
|
16
|
-
maxRecommendedWorkingSetSize,
|
|
17
|
-
peakMemory,
|
|
18
|
-
setWiredLimit,
|
|
19
|
-
synchronize,
|
|
20
|
-
} from "./mlx/ffi";
|
|
21
|
-
import * as ops from "./mlx/ops";
|
|
22
|
-
import { KVCache, RotatingKVCache, type Cache } from "./model/gemma4";
|
|
23
|
-
import { maybePageKv } from "./backends/mlx/request-state-policy";
|
|
24
|
-
export { maybePageKv } from "./backends/mlx/request-state-policy";
|
|
25
|
-
import { DiffusionGemmaModel } from "./model/diffusion-gemma";
|
|
26
|
-
import { denoiseAsync } from "./diffusion/diffusion-generate";
|
|
27
|
-
import { bindLegacyDenoisingModel, type MlxDenoisingBinding } from "./backends/mlx/diffusion";
|
|
28
|
-
import type { RuntimeModel } from "./model/factory";
|
|
29
|
-
import {
|
|
30
|
-
assertMlxAutoregressiveBinding, bindLegacyAutoregressiveModel, supportsCommittedAppendCache,
|
|
31
|
-
type MlxAutoregressiveBinding, type MlxModelMemory, type MlxDecodeStep, type MlxTokenAppend,
|
|
32
|
-
} from "./backends/mlx/autoregressive";
|
|
33
|
-
import { createKvMaintenance } from "./backends/mlx/kv-maintenance";
|
|
34
|
-
import { nextPrefillStep } from "./inference/prefill";
|
|
35
|
-
import { evalCacheState, executeMlxPrefillStep } from "./backends/mlx/prefill";
|
|
36
|
-
import { appendFillHidden } from "./backends/mlx/fill-append";
|
|
37
|
-
export { evalCacheState } from "./backends/mlx/prefill";
|
|
38
|
-
import type { TokenLogprobs } from "./contracts/generation";
|
|
39
|
-
export type { TokenLogprobs } from "./contracts/generation";
|
|
40
|
-
import type { PromptResponseTrace } from "./serve/prompt-response-trace";
|
|
41
|
-
import type { KvQuantSpec, TurboQuantScheme } from "./config";
|
|
42
|
-
import { runtimeConfig, runtimeValue, withRuntimeConfig, type RuntimeConfig } from "./runtime-config";
|
|
43
|
-
import {
|
|
44
|
-
disposeStepExtras,
|
|
45
|
-
makeStepSampler,
|
|
46
|
-
readStepExtras,
|
|
47
|
-
stepExtrasArrays,
|
|
48
|
-
type LogitsProcessorOptions,
|
|
49
|
-
type SamplerOptions,
|
|
50
|
-
type StepExtras,
|
|
51
|
-
} from "./sampler";
|
|
52
|
-
import type { GrammarController } from "./grammar";
|
|
53
|
-
import {
|
|
54
|
-
fillTraceEnabled,
|
|
55
|
-
fillTracePath,
|
|
56
|
-
type FillTraceRecord,
|
|
57
|
-
resolveFillMode,
|
|
58
|
-
type FillSession,
|
|
59
|
-
type FillStats,
|
|
60
|
-
type Proposal,
|
|
61
|
-
} from "./fill/fill-session";
|
|
62
|
-
|
|
63
|
-
export interface GenerateOptions extends SamplerOptions, LogitsProcessorOptions {
|
|
64
|
-
/** Application session affinity; independent of numerical cache identity. */
|
|
65
|
-
cacheSessionId?: string;
|
|
66
|
-
/** Resolved host policy. Direct compatibility calls resolve from their
|
|
67
|
-
* captured binding when this is absent. */
|
|
68
|
-
decodePolicy?: Readonly<Pick<import("./contracts/execution").ResolvedExecution, "compiledDecode" | "grammarJump">>;
|
|
69
|
-
/** Cooperatively cancel at prefill/decode boundaries; pending native work
|
|
70
|
-
* finishes before owned resources are released. */
|
|
71
|
-
signal?: AbortSignal;
|
|
72
|
-
maxTokens?: number;
|
|
73
|
-
eosTokenIds?: number[];
|
|
74
|
-
prefillChunkSize?: number;
|
|
75
|
-
/** Whether the request supplied its seed explicitly. Durable generation
|
|
76
|
-
* checkpoints use this to distinguish a reproducible user policy from the
|
|
77
|
-
* server-generated seed that must be recovered from checkpoint metadata. */
|
|
78
|
-
seedWasExplicit?: boolean;
|
|
79
|
-
/** Fired once during prefill, at `snapshotAt` tokens (default: the full
|
|
80
|
-
* prompt) and BEFORE any further KV is written — the one moment the
|
|
81
|
-
* caches hold exactly that prefix (post-wrap rings can't be rewound
|
|
82
|
-
* later). The server's prompt-boundary cache snapshot hangs here. */
|
|
83
|
-
onPrefillDone?: () => void;
|
|
84
|
-
/** Token count at which onPrefillDone fires (the STABLE cache boundary —
|
|
85
|
-
* chat prompts end in a generation primer, e.g. 12B's thought-channel
|
|
86
|
-
* tokens, that the NEXT turn's re-render does not contain; snapshotting
|
|
87
|
-
* there would make the entry untrimmable-and-divergent). Prefill is
|
|
88
|
-
* split at this index when it falls mid-prompt. */
|
|
89
|
-
snapshotAt?: number;
|
|
90
|
-
/** Resume an interrupted autoregressive generation from a durable KV
|
|
91
|
-
* checkpoint. `cache` must cover every token in `promptTokens` exactly;
|
|
92
|
-
* `initialPendingToken` is the already-sampled next token, so no cache
|
|
93
|
-
* rewind is required (important for recurrent/untrimmable layers). */
|
|
94
|
-
initialPendingToken?: number;
|
|
95
|
-
/** Number of completion tokens already emitted before this resumed run. */
|
|
96
|
-
initialGeneratedTokens?: number;
|
|
97
|
-
/** Original request prompt length used for protocol usage accounting when
|
|
98
|
-
* `promptTokens` also contains replayed completion tokens. */
|
|
99
|
-
originalPromptTokens?: number;
|
|
100
|
-
/** Periodic, safe decode-boundary checkpoint hook. The cache covers
|
|
101
|
-
* `cacheTokens`; `pendingToken` has been sampled but not emitted. */
|
|
102
|
-
checkpointEveryTokens?: number;
|
|
103
|
-
onDecodeCheckpoint?: (state: {
|
|
104
|
-
cacheTokens: number[];
|
|
105
|
-
caches: Cache[];
|
|
106
|
-
generatedTokens: number;
|
|
107
|
-
pendingToken: number;
|
|
108
|
-
}) => void | Promise<void>;
|
|
109
|
-
/** Pre-warmed KV caches (e.g. from the prompt cache). cache[0].offset
|
|
110
|
-
* prompt tokens are treated as already prefilled; only the suffix is
|
|
111
|
-
* forwarded. Caller keeps ownership — generate() will not dispose. */
|
|
112
|
-
cache?: Cache[];
|
|
113
|
-
/** Vision path: pre-merged (unscaled) input embeddings [1, L, hidden]
|
|
114
|
-
* covering the whole prompt; prefilled in one shot (no chunking).
|
|
115
|
-
* Caller keeps ownership. */
|
|
116
|
-
promptEmbeddings?: MlxArray;
|
|
117
|
-
/** bool [L] marking image tokens (bidirectional attention among them).
|
|
118
|
-
* MUST be unset when the prompt contains any audio — audio prompts run
|
|
119
|
-
* fully causal (docs/design/generic-model-support.md §3.3 Q1). */
|
|
120
|
-
imageMask?: MlxArray;
|
|
121
|
-
/** bool [L] marking ALL multimodal soft tokens (image | audio) for
|
|
122
|
-
* per-layer-input id zeroing (e2b/e4b), decoupled from imageMask so
|
|
123
|
-
* audio-only prompts (no bidirectional mask) still zero their positions.
|
|
124
|
-
* When unset, zeroing falls back to imageMask (the legacy vision shape).
|
|
125
|
-
* Caller keeps ownership. */
|
|
126
|
-
multimodalMask?: MlxArray;
|
|
127
|
-
/** Quantize full-attention KV caches to this many bits (4 or 8).
|
|
128
|
-
* Full-attention and rotating caches both convert when populated. */
|
|
129
|
-
kvBits?: number;
|
|
130
|
-
kvGroupSize?: number;
|
|
131
|
-
/** Per-layer mixed-precision KV from kv_config.json (config.kvQuant).
|
|
132
|
-
* Overrides kvBits, like optiq serve's --kv-config. layerIdx indexes
|
|
133
|
-
* the cache list (== layer index for the donor prefix), including
|
|
134
|
-
* rotating/sliding caches. */
|
|
135
|
-
kvConfig?: KvQuantSpec[];
|
|
136
|
-
/** Convert once a cache's offset reaches this (uniform-kvBits default
|
|
137
|
-
* 5000 = mlx-lm; kvConfig default 0 = optiq serve). */
|
|
138
|
-
quantizedKvStart?: number;
|
|
139
|
-
/** TurboQuant scheme (docs/design/turboquant.md): rotation-based KV
|
|
140
|
-
* quantization, a CLI-only runtime lever in the same class as uniform
|
|
141
|
-
* kvBits (mutually exclusive with kvBits/kvConfig — maybeQuantizeKv
|
|
142
|
-
* checks turboQuant first, so set at most one). Full-attention
|
|
143
|
-
* KVCache layers convert via TurboQuantKVCache.fromKVCache;
|
|
144
|
-
* RotatingKVCache (sliding-window) layers stay bf16 in v1 — a one-time
|
|
145
|
-
* warning names the limitation, never a throw. */
|
|
146
|
-
turboQuant?: TurboQuantScheme;
|
|
147
|
-
/** OPTIONAL paged KV storage (docs/design/kv-cache.md): fresh
|
|
148
|
-
* full-attention KVCache layers are replaced with PagedKVCache (block
|
|
149
|
-
* pool + gather-to-contiguous) before prefill. Scope: B1/B>1
|
|
150
|
-
* Gemma4-family, bf16 — mutually exclusive with kvBits/kvConfig/
|
|
151
|
-
* turboQuant/draft/compiled decode (callers refuse the combos; the
|
|
152
|
-
* cache swap itself only ever touches plain empty KVCache entries).
|
|
153
|
-
* Values are gated bit-exact vs the plain path (tests/paged-kv*). */
|
|
154
|
-
pagedKv?: { blockSize?: number };
|
|
155
|
-
/** Mounted LoRA adapter ids to apply (resolved/validated by
|
|
156
|
-
* AdapterManager.resolveSpec). Residuals sum in order. Set on the
|
|
157
|
-
* model's LoraState for exactly the duration of this generation —
|
|
158
|
-
* a plain field, safe because the generation queue is serialized. */
|
|
159
|
-
adapters?: string[];
|
|
160
|
-
/** DiffusionGemma image-text-to-text: channel-first pixel_values [1,3,H,W].
|
|
161
|
-
* When set, `promptTokens` are the spliced (<|image|>-expanded) ids and the
|
|
162
|
-
* denoising engine prefills the merged vision features. Caller keeps ownership. */
|
|
163
|
-
visionPixels?: MlxArray;
|
|
164
|
-
/** Capture each emitted token's log-probability (mlx_lm.server `logprobs`).
|
|
165
|
-
* The distribution matches mlx-lm generate_step exactly: full-vocab
|
|
166
|
-
* log-softmax of the logits AFTER logits processors, BEFORE the sampler's
|
|
167
|
-
* temperature/top-p/top-k/min-p/XTC (generate.py L409-422). Off by default —
|
|
168
|
-
* the hot path pays nothing when unset. */
|
|
169
|
-
logprobs?: boolean;
|
|
170
|
-
/** Capture the top-k (token id, logprob) pairs per emitted token from the
|
|
171
|
-
* same distribution (mlx_lm.server `top_logprobs`). 0/unset = off. */
|
|
172
|
-
topLogprobs?: number;
|
|
173
|
-
/** Grammar-constrained decoding (src/grammar.ts): a compiled GrammarController
|
|
174
|
-
* that masks invalid tokens to -inf each step. L2-class (oMLX oracle). When
|
|
175
|
-
* set, the decode loop takes a slightly different shape: it eager-reads the
|
|
176
|
-
* token id (acceptToken needs a JS number, which the pipelined loop defers),
|
|
177
|
-
* advances the matcher, and awaits the async mask precompute (which overlaps
|
|
178
|
-
* the GPU forward). Non-grammar requests keep the fast pipelined loop. */
|
|
179
|
-
grammar?: GrammarController;
|
|
180
|
-
/** Token fast-forwarding (src/fill/, docs/design/speculative-decoding.md
|
|
181
|
-
* "Token fast-forwarding"): a per-request table of DETERMINED token spans
|
|
182
|
-
* (compiled from the request's `tools` + the chat template). When the
|
|
183
|
-
* stream enters one, the engine appends the whole span itself with ONE
|
|
184
|
-
* multi-token forward and resumes sampling after it.
|
|
185
|
-
*
|
|
186
|
-
* This is context extension, NOT speculation: nothing is drafted, verified,
|
|
187
|
-
* or rolled back, and no comparison is made against what the model would
|
|
188
|
-
* have produced. Injection bypasses the sampler, which is a documented
|
|
189
|
-
* behavior-policy deviation at temperature > 0 — hence opt-in
|
|
190
|
-
* (MLX_BUN_FILL=strict) and the composition refusals in shouldUseFill.
|
|
191
|
-
* Serial lane only: the batch scheduler and the spec loop never read it. */
|
|
192
|
-
fill?: FillSession;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function shouldUseGrammarJump(
|
|
196
|
-
options: Pick<GenerateOptions, "grammar" | "logprobs" | "topLogprobs">,
|
|
197
|
-
runtime: RuntimeConfig = runtimeConfig(),
|
|
198
|
-
): boolean {
|
|
199
|
-
return options.grammar !== undefined &&
|
|
200
|
-
runtime.flag("MLX_BUN_GRAMMAR_JUMP", false) &&
|
|
201
|
-
!options.logprobs &&
|
|
202
|
-
!(options.topLogprobs && options.topLogprobs > 0);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/** Composition gate for token fast-forwarding, enforced inside the engine
|
|
206
|
-
* The serving planner owns request-level eligibility, including explicit
|
|
207
|
-
* seeds in serial execution and mounted draft models. Shared strict fill
|
|
208
|
-
* has its own grouped method and uses the same append-format declaration.
|
|
209
|
-
* - grammar: forced-token production is the grammar's job; two producers in
|
|
210
|
-
* one iteration is forbidden (asserted in the loop).
|
|
211
|
-
* - logprobs/top_logprobs: injected tokens are never sampled, so they have
|
|
212
|
-
* no distribution row (same rule as shouldUseGrammarJump).
|
|
213
|
-
* - promptEmbeddings: vision/audio prompts (and mRoPE) — untested shape.
|
|
214
|
-
* - affine KV: the model's append binding declares its supported formats.
|
|
215
|
-
* Verify-policy quantized appends remain a separate gate. */
|
|
216
|
-
export function shouldUseFill(
|
|
217
|
-
options: Pick<
|
|
218
|
-
GenerateOptions,
|
|
219
|
-
"fill" | "grammar" | "logprobs" | "topLogprobs" | "promptEmbeddings"
|
|
220
|
-
| "kvBits" | "kvConfig" | "turboQuant"
|
|
221
|
-
>,
|
|
222
|
-
runtime: RuntimeConfig = runtimeConfig(),
|
|
223
|
-
append?: Pick<MlxTokenAppend, "affineKvBits" | "turboQuantFormats"> | null,
|
|
224
|
-
): boolean {
|
|
225
|
-
if (!options.fill) return false;
|
|
226
|
-
if (resolveFillMode(runtime.value("MLX_BUN_FILL") ?? "off") === "off") return false;
|
|
227
|
-
return options.grammar === undefined &&
|
|
228
|
-
!options.logprobs &&
|
|
229
|
-
!(options.topLogprobs && options.topLogprobs > 0) &&
|
|
230
|
-
options.promptEmbeddings === undefined &&
|
|
231
|
-
supportsCommittedAppendCache(append, options);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** Compatibility entry point for callers that perform a single conversion.
|
|
235
|
-
* Execution sessions compose createKvMaintenance once and reuse it. */
|
|
236
|
-
export function maybeQuantizeKv(cache: Cache[], options: GenerateOptions): void {
|
|
237
|
-
createKvMaintenance(options)(cache);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/** Emitted once per process: token fast-forwarding skips models with
|
|
241
|
-
* sliding-window layers in v1 (see the gate in generateInner). */
|
|
242
|
-
let warnedFillRotating = false;
|
|
243
|
-
|
|
244
|
-
/** The rewind surface a verify-policy fill needs. `Cache` declares the
|
|
245
|
-
* spec-round trio as optional members, but the cache list is a UNION with
|
|
246
|
-
* GLM's MLACache (which declares none of them), so the union has no such
|
|
247
|
-
* property to read. One narrowing helper keeps the call sites honest — every
|
|
248
|
-
* use is still optional-chained. */
|
|
249
|
-
type RewindableCache = {
|
|
250
|
-
isTrimmable(): boolean;
|
|
251
|
-
trim(n: number, bypass?: boolean): void;
|
|
252
|
-
specRoundBegin?(): void;
|
|
253
|
-
specRoundCommit?(): void;
|
|
254
|
-
specRoundRollback?(keep: number): void;
|
|
255
|
-
};
|
|
256
|
-
const rewindable = (c: unknown): RewindableCache => c as RewindableCache;
|
|
257
|
-
|
|
258
|
-
export interface GenerateStats {
|
|
259
|
-
promptTokens: number;
|
|
260
|
-
/** An observed terminal cause takes precedence over the token-count limit. */
|
|
261
|
-
finishReason?: "stop" | "length";
|
|
262
|
-
/** Prompt tokens skipped via a pre-warmed cache. */
|
|
263
|
-
cachedTokens: number;
|
|
264
|
-
generatedTokens: number;
|
|
265
|
-
prefillTps: number;
|
|
266
|
-
decodeTps: number;
|
|
267
|
-
prefillMs: number;
|
|
268
|
-
decodeMs: number;
|
|
269
|
-
/** Exact token sequence whose KV now lives in the cache (prompt + every
|
|
270
|
-
* decoded token that was forwarded, including a trailing EOS the
|
|
271
|
-
* pipeline forwarded before reading it). For PromptCache.put(). */
|
|
272
|
-
cacheTokens: number[];
|
|
273
|
-
/** Speculative-decoding telemetry (serve --draft-model path only).
|
|
274
|
-
* draftedByPos/acceptedByPos: per-draft-position round counts (index =
|
|
275
|
-
* position within a round's block) — the Phase-1c per-position
|
|
276
|
-
* acceptance signal. */
|
|
277
|
-
spec?: {
|
|
278
|
-
drafted: number; accepted: number; targetCalls: number;
|
|
279
|
-
draftedByPos?: number[]; acceptedByPos?: number[];
|
|
280
|
-
rejected?: number; rounds?: number; acceptanceLengths?: number[];
|
|
281
|
-
tokensPerForward?: number; forwardsSaved?: number;
|
|
282
|
-
};
|
|
283
|
-
/** Token fast-forwarding telemetry (serial lane, MLX_BUN_FILL=strict).
|
|
284
|
-
* Present only when a fill table was actually armed for this generation. */
|
|
285
|
-
fill?: FillStats;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
export interface GenerateDiagnostics {
|
|
289
|
-
trace?: PromptResponseTrace;
|
|
290
|
-
mechanism?: "serial" | "continuous";
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
export interface GeneratedToken {
|
|
294
|
-
token: number;
|
|
295
|
-
index: number;
|
|
296
|
-
/** Present only when GenerateOptions.logprobs / topLogprobs requested it. */
|
|
297
|
-
logprobs?: TokenLogprobs;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export class Generation implements AsyncIterable<GeneratedToken> {
|
|
301
|
-
stats: GenerateStats | null = null;
|
|
302
|
-
readonly #next: () => Promise<IteratorResult<GeneratedToken, GenerateStats>>;
|
|
303
|
-
readonly #return: () => Promise<IteratorResult<GeneratedToken, GenerateStats>>;
|
|
304
|
-
|
|
305
|
-
constructor(iter: AsyncGenerator<GeneratedToken, GenerateStats>, runtime = runtimeConfig()) {
|
|
306
|
-
// Async generators resume in the caller's context on every next/return.
|
|
307
|
-
// Bind both operations so consumer awaits and early close cannot change
|
|
308
|
-
// the runtime snapshot seen by kernels or their cleanup.
|
|
309
|
-
const next = iter.next.bind(iter);
|
|
310
|
-
const close = () => iter.return(undefined as unknown as GenerateStats);
|
|
311
|
-
this.#next = () => withRuntimeConfig(runtime, next);
|
|
312
|
-
this.#return = () => withRuntimeConfig(runtime, close);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
async *[Symbol.asyncIterator](): AsyncGenerator<GeneratedToken> {
|
|
316
|
-
try {
|
|
317
|
-
while (true) {
|
|
318
|
-
const r = await this.#next();
|
|
319
|
-
if (r.done) {
|
|
320
|
-
this.stats = r.value;
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
yield r.value;
|
|
324
|
-
}
|
|
325
|
-
} finally {
|
|
326
|
-
// Consumer broke early (e.g. a decoded-text stop sequence fired):
|
|
327
|
-
// drive the inner generator's shutdown so its finallys run (array
|
|
328
|
-
// disposal, wired/adapter scopes) and capture the stats its
|
|
329
|
-
// early-return path still reports.
|
|
330
|
-
if (this.stats === null) {
|
|
331
|
-
const r = await this.#return();
|
|
332
|
-
if (r.done && r.value) this.stats = r.value;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Scoped wired limit, raised only for near-ceiling models. mlx-lm's
|
|
339
|
-
// wired_limit context wires unconditionally per generation; we deviate
|
|
340
|
-
// with a measured justification (PLAN Phase 6 verification findings):
|
|
341
|
-
// - 26B-A4B (16.4 GB = 92% of the 17.8 GiB working set) NEEDS wiring —
|
|
342
|
-
// 8.6 tok/s without, 32.3 with (Metal evicts weight buffers per token).
|
|
343
|
-
// - 12B/e4b (≤47%) hit reference parity WITHOUT wiring, and wiring in a
|
|
344
|
-
// multi-model process (the test suite) pins memory the OTHER resident
|
|
345
|
-
// models need — async GPU exec OOM, which is uncatchable (the mlx
|
|
346
|
-
// completion-handler throw terminates the process).
|
|
347
|
-
// Scope semantics match the reference: set → generate → synchronize →
|
|
348
|
-
// restore; nothing stays pinned between generations. Re-entrant: only
|
|
349
|
-
// the outermost wiring scope touches the limit.
|
|
350
|
-
// macOS 26.6 reports a 26.8e9 B (24.96 GiB, 0.78 × RAM) recommended set on
|
|
351
|
-
// a 32 GB M1 Max (2026-09-08; the earlier "24 GB machine" attribution was
|
|
352
|
-
// wrong — 24.96 GiB exceeds a 24 GB box's RAM). At 0.75 × that the old
|
|
353
|
-
// fraction stopped wiring the 13-16 GiB Qwen/GLM models even though they
|
|
354
|
-
// page heavily without an explicit wired limit.
|
|
355
|
-
// Keep smaller 8-9 GiB models unwired while covering the large-model class.
|
|
356
|
-
const WIRE_THRESHOLD = 0.5;
|
|
357
|
-
let wiredScopeDepth = 0;
|
|
358
|
-
let wiredOldLimit = 0;
|
|
359
|
-
|
|
360
|
-
type WiredModelMemory = MlxModelMemory;
|
|
361
|
-
|
|
362
|
-
/** Bytes whose MLX graph must stay resident while a model executes. */
|
|
363
|
-
export function wiredWorkingSetBytes(model: WiredModelMemory): number {
|
|
364
|
-
const planned = model.expertRuntime?.plan.plannedBytes;
|
|
365
|
-
return typeof planned === "number" && Number.isFinite(planned) && planned > 0
|
|
366
|
-
? Math.max(model.weightsBytes, planned)
|
|
367
|
-
: model.weightsBytes;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export function modelNeedsWiredLimit(
|
|
371
|
-
model: WiredModelMemory,
|
|
372
|
-
recommendedBytes = maxRecommendedWorkingSetSize(),
|
|
373
|
-
force = runtimeValue("MLX_BUN_FORCE_WIRE") === "1",
|
|
374
|
-
): boolean {
|
|
375
|
-
return force ||
|
|
376
|
-
wiredWorkingSetBytes(model) > WIRE_THRESHOLD * recommendedBytes;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function enterWiredScope(): void {
|
|
380
|
-
if (wiredScopeDepth++ === 0)
|
|
381
|
-
wiredOldLimit = setWiredLimit(maxRecommendedWorkingSetSize());
|
|
382
|
-
}
|
|
383
|
-
function exitWiredScope(): void {
|
|
384
|
-
if (--wiredScopeDepth === 0) {
|
|
385
|
-
synchronize(gpuStream);
|
|
386
|
-
setWiredLimit(wiredOldLimit);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/** Hold the process wired limit while one model owns GPU execution. */
|
|
391
|
-
export function acquireModelWiredLimit(model: WiredModelMemory): () => void {
|
|
392
|
-
if (!modelNeedsWiredLimit(model)) return () => {};
|
|
393
|
-
enterWiredScope();
|
|
394
|
-
let released = false;
|
|
395
|
-
return () => {
|
|
396
|
-
if (released) return;
|
|
397
|
-
released = true;
|
|
398
|
-
exitWiredScope();
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
export function generate(
|
|
403
|
-
model: RuntimeModel,
|
|
404
|
-
promptTokens: number[],
|
|
405
|
-
options: GenerateOptions = {},
|
|
406
|
-
diagnostics: GenerateDiagnostics = {},
|
|
407
|
-
): Generation {
|
|
408
|
-
return bindGeneration(model)(promptTokens, options, diagnostics);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/** Resolve the compatibility model once at host construction. Every request
|
|
412
|
-
* borrows the same binding and immutable runtime snapshot. */
|
|
413
|
-
export function bindGeneration(model: RuntimeModel): (
|
|
414
|
-
promptTokens: number[], options?: GenerateOptions, diagnostics?: GenerateDiagnostics,
|
|
415
|
-
) => Generation {
|
|
416
|
-
// DiffusionGemma is non-autoregressive: route to the denoising engine instead
|
|
417
|
-
// of the AR decode loop. Same Generation/GenerateStats contract so the CLI and
|
|
418
|
-
// server stream it through the existing token machinery.
|
|
419
|
-
if (!(model instanceof DiffusionGemmaModel)) {
|
|
420
|
-
const binding = bindLegacyAutoregressiveModel(model);
|
|
421
|
-
return (prompt, options, diagnostics) => generateAutoregressive(binding, prompt, options, diagnostics);
|
|
422
|
-
}
|
|
423
|
-
const binding = bindLegacyDenoisingModel(model);
|
|
424
|
-
return (prompt, options) => generateDenoising(binding, prompt, options);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
/** Execute a supplied backend binding through the same AR loop and resource
|
|
428
|
-
* scopes as generate(). Callers serialize shared model/adapter access. Replacing
|
|
429
|
-
* this binding replaces graph, cache construction, media, and optional decode
|
|
430
|
-
* together; it never borrows another model's compiled execution implicitly. */
|
|
431
|
-
export function generateAutoregressive(
|
|
432
|
-
binding: MlxAutoregressiveBinding,
|
|
433
|
-
promptTokens: number[],
|
|
434
|
-
options: GenerateOptions = {},
|
|
435
|
-
diagnostics: GenerateDiagnostics = {},
|
|
436
|
-
): Generation {
|
|
437
|
-
const runtime = binding.runtime ?? runtimeConfig();
|
|
438
|
-
let inner = generateInner(binding, promptTokens, options, diagnostics, runtime);
|
|
439
|
-
if (options.adapters?.length && binding.adapters) {
|
|
440
|
-
inner = adapterScoped({ loraState: binding.adapters }, options.adapters, inner);
|
|
441
|
-
}
|
|
442
|
-
if (binding.memory.expertRuntime?.finishUsage || binding.memory.expertRuntime?.flushUsage)
|
|
443
|
-
inner = usageScoped(binding.memory, inner);
|
|
444
|
-
return new Generation(modelNeedsWiredLimit(binding.memory, undefined,
|
|
445
|
-
runtime.value("MLX_BUN_FORCE_WIRE") === "1") ? wiredScoped(inner) : inner, runtime);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/** Denoising bindings use their own state and feedback graph. Callers hold
|
|
449
|
-
* the native runtime's exclusive generation lease, including its global RNG. */
|
|
450
|
-
export function generateDenoising<State>(
|
|
451
|
-
binding: MlxDenoisingBinding<State>, promptTokens: number[], options: GenerateOptions = {},
|
|
452
|
-
): Generation {
|
|
453
|
-
let inner = generateDiffusionInner(binding, promptTokens, options);
|
|
454
|
-
if (options.adapters?.length && binding.adapters)
|
|
455
|
-
inner = adapterScoped({ loraState: binding.adapters }, options.adapters, inner);
|
|
456
|
-
if (binding.memory.expertRuntime?.finishUsage || binding.memory.expertRuntime?.flushUsage)
|
|
457
|
-
inner = usageScoped(binding.memory, inner);
|
|
458
|
-
const runtime = binding.runtime ?? runtimeConfig();
|
|
459
|
-
return new Generation(modelNeedsWiredLimit(binding.memory, undefined,
|
|
460
|
-
runtime.value("MLX_BUN_FORCE_WIRE") === "1") ? wiredScoped(inner) : inner, runtime);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/** Non-autoregressive diffusion generation, adapted to the AR Generation
|
|
464
|
-
* contract. Runs the denoising engine (its own prefill + canvas loop) and
|
|
465
|
-
* streams the emitted tokens. v1: greedy (temperature 0, confidence-threshold
|
|
466
|
-
* sampler — the OptiQ default); per-block intra-stream + temperature>0
|
|
467
|
-
* (categorical) are follow-ups. */
|
|
468
|
-
async function* generateDiffusionInner<State>(
|
|
469
|
-
binding: MlxDenoisingBinding<State>,
|
|
470
|
-
promptTokens: number[],
|
|
471
|
-
options: GenerateOptions,
|
|
472
|
-
): AsyncGenerator<GeneratedToken, GenerateStats> {
|
|
473
|
-
options.signal?.throwIfAborted();
|
|
474
|
-
const t0 = performance.now();
|
|
475
|
-
const maxTokens = options.maxTokens ?? 256;
|
|
476
|
-
// A fresh random canvas seed per request unless the caller pins one.
|
|
477
|
-
const seed =
|
|
478
|
-
options.seed !== undefined
|
|
479
|
-
? BigInt(options.seed)
|
|
480
|
-
: BigInt(Math.floor(Math.random() * 0x7fffffff));
|
|
481
|
-
// The shipped checkpoint's tokenizer stops on {1, 106}; union any caller eos.
|
|
482
|
-
const eos = [...new Set([1, 106, ...(options.eosTokenIds ?? [])])];
|
|
483
|
-
const result = await denoiseAsync(binding.graph, promptTokens, {
|
|
484
|
-
maxTokens,
|
|
485
|
-
sampler: "confidence-threshold",
|
|
486
|
-
temperature: 0,
|
|
487
|
-
eosTokenIds: eos,
|
|
488
|
-
seed,
|
|
489
|
-
visionPixels: options.visionPixels,
|
|
490
|
-
}, options.signal);
|
|
491
|
-
const decodeMs = performance.now() - t0;
|
|
492
|
-
let index = 0;
|
|
493
|
-
let failure: { error: unknown } | undefined;
|
|
494
|
-
try {
|
|
495
|
-
for (const token of result.tokens) {
|
|
496
|
-
options.signal?.throwIfAborted();
|
|
497
|
-
yield { token, index: index++ };
|
|
498
|
-
}
|
|
499
|
-
} catch (error) { failure = { error }; }
|
|
500
|
-
finally {
|
|
501
|
-
// A stop parser may close delivery before the finished canvas is exhausted.
|
|
502
|
-
// Settle the emitted count on return(), without suppressing cancellation/errors.
|
|
503
|
-
if (failure) throw failure.error;
|
|
504
|
-
return {
|
|
505
|
-
promptTokens: promptTokens.length,
|
|
506
|
-
cachedTokens: 0,
|
|
507
|
-
generatedTokens: index,
|
|
508
|
-
prefillTps: 0,
|
|
509
|
-
decodeTps: index / Math.max(decodeMs / 1000, 1e-9),
|
|
510
|
-
prefillMs: 0,
|
|
511
|
-
decodeMs,
|
|
512
|
-
cacheTokens: [],
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
/** Hold the model's active-adapter list for exactly this generation. */
|
|
518
|
-
async function* adapterScoped(
|
|
519
|
-
model: { loraState: { active: string[] } },
|
|
520
|
-
adapters: string[],
|
|
521
|
-
inner: AsyncGenerator<GeneratedToken, GenerateStats>,
|
|
522
|
-
): AsyncGenerator<GeneratedToken, GenerateStats> {
|
|
523
|
-
const previous = model.loraState.active;
|
|
524
|
-
model.loraState.active = adapters;
|
|
525
|
-
try {
|
|
526
|
-
return yield* inner;
|
|
527
|
-
} finally {
|
|
528
|
-
model.loraState.active = previous;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
/** Wrap the generator so the wired limit is held exactly while it runs
|
|
533
|
-
* (incl. early break/return/throw — finally fires on .return()). */
|
|
534
|
-
async function* wiredScoped(
|
|
535
|
-
inner: AsyncGenerator<GeneratedToken, GenerateStats>,
|
|
536
|
-
): AsyncGenerator<GeneratedToken, GenerateStats> {
|
|
537
|
-
enterWiredScope();
|
|
538
|
-
try {
|
|
539
|
-
return yield* inner;
|
|
540
|
-
} finally {
|
|
541
|
-
exitWiredScope();
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/** Publish the streamed model's route ledger on every generator exit path. */
|
|
546
|
-
async function* usageScoped(
|
|
547
|
-
model: WiredModelMemory,
|
|
548
|
-
inner: AsyncGenerator<GeneratedToken, GenerateStats>,
|
|
549
|
-
): AsyncGenerator<GeneratedToken, GenerateStats> {
|
|
550
|
-
try {
|
|
551
|
-
return yield* inner;
|
|
552
|
-
} finally {
|
|
553
|
-
if (model.expertRuntime?.finishUsage)
|
|
554
|
-
await model.expertRuntime.finishUsage();
|
|
555
|
-
else
|
|
556
|
-
model.expertRuntime?.flushUsage?.();
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
/** Apply generate()'s scoped wiring policy to non-generator execution paths. */
|
|
561
|
-
export async function withModelWiredLimit<T>(
|
|
562
|
-
model: WiredModelMemory,
|
|
563
|
-
run: () => Promise<T>,
|
|
564
|
-
): Promise<T> {
|
|
565
|
-
const release = acquireModelWiredLimit(model);
|
|
566
|
-
try {
|
|
567
|
-
return await run();
|
|
568
|
-
} finally {
|
|
569
|
-
release();
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
/** Apply generate()'s usage safe-point to direct/non-generator paths. */
|
|
574
|
-
export async function withModelUsageFlush<T>(
|
|
575
|
-
model: WiredModelMemory,
|
|
576
|
-
run: () => Promise<T>,
|
|
577
|
-
): Promise<T> {
|
|
578
|
-
try {
|
|
579
|
-
return await run();
|
|
580
|
-
} finally {
|
|
581
|
-
if (model.expertRuntime?.finishUsage)
|
|
582
|
-
await model.expertRuntime.finishUsage();
|
|
583
|
-
else
|
|
584
|
-
model.expertRuntime?.flushUsage?.();
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
async function* generateInner(
|
|
589
|
-
binding: MlxAutoregressiveBinding,
|
|
590
|
-
promptTokens: number[],
|
|
591
|
-
options: GenerateOptions,
|
|
592
|
-
diagnostics: GenerateDiagnostics,
|
|
593
|
-
runtime: RuntimeConfig,
|
|
594
|
-
): AsyncGenerator<GeneratedToken, GenerateStats> {
|
|
595
|
-
if (options.signal?.aborted) {
|
|
596
|
-
options.grammar?.dispose();
|
|
597
|
-
options.signal.throwIfAborted();
|
|
598
|
-
}
|
|
599
|
-
const {
|
|
600
|
-
maxTokens = 512,
|
|
601
|
-
eosTokenIds = binding.eosTokenIds,
|
|
602
|
-
prefillChunkSize = binding.prefillPolicy?.chunkSize(promptTokens.length) ?? 2048,
|
|
603
|
-
} = options;
|
|
604
|
-
|
|
605
|
-
try {
|
|
606
|
-
assertMlxAutoregressiveBinding(binding);
|
|
607
|
-
if (options.promptEmbeddings && !binding.forwardEmbeddings)
|
|
608
|
-
throw new Error("AR binding does not support prompt embeddings");
|
|
609
|
-
if (options.adapters?.length && !binding.adapters)
|
|
610
|
-
throw new Error("AR binding does not support adapters");
|
|
611
|
-
} catch (error) {
|
|
612
|
-
options.grammar?.dispose();
|
|
613
|
-
throw error;
|
|
614
|
-
}
|
|
615
|
-
const graph = binding.graph;
|
|
616
|
-
const maintainKv = createKvMaintenance(options);
|
|
617
|
-
|
|
618
|
-
// logprobs capture (mlx_lm.server semantics — see GenerateOptions.logprobs).
|
|
619
|
-
// Everything below is gated: when neither flag is set, no extra ops, evals,
|
|
620
|
-
// or readbacks happen on the hot path.
|
|
621
|
-
const captureSelLp = options.logprobs === true;
|
|
622
|
-
const captureTopK =
|
|
623
|
-
options.topLogprobs && options.topLogprobs > 0 ? options.topLogprobs : 0;
|
|
624
|
-
const stepSampler = makeStepSampler(options, {
|
|
625
|
-
tokenRepresentation: "device",
|
|
626
|
-
grammarWait: "external",
|
|
627
|
-
historyUpdate: "manual",
|
|
628
|
-
captureSelectedLogprob: captureSelLp,
|
|
629
|
-
captureTopLogprobs: captureTopK,
|
|
630
|
-
});
|
|
631
|
-
const needsTokenHistory = stepSampler.needsHistory;
|
|
632
|
-
|
|
633
|
-
const closePrefill = diagnostics.trace?.begin("prefill.total", {
|
|
634
|
-
mechanism: diagnostics.mechanism ?? "serial",
|
|
635
|
-
promptTokens: promptTokens.length,
|
|
636
|
-
cachedTokens: options.cache?.[0]?.offset ?? 0,
|
|
637
|
-
});
|
|
638
|
-
const closeBatchSetup = diagnostics.trace?.begin("prefill.batch_setup", {
|
|
639
|
-
mechanism: diagnostics.mechanism ?? "serial",
|
|
640
|
-
});
|
|
641
|
-
const ownsCache = !options.cache;
|
|
642
|
-
const resuming = options.initialPendingToken !== undefined;
|
|
643
|
-
let cache: Cache[] = options.cache ?? [];
|
|
644
|
-
let appender: MlxTokenAppend | null = null;
|
|
645
|
-
try {
|
|
646
|
-
if (ownsCache) cache = binding.makeCache();
|
|
647
|
-
if (!cache.length) throw new Error("AR binding returned an empty cache");
|
|
648
|
-
const cached = cache[0]!.offset;
|
|
649
|
-
if ((!resuming && cached >= promptTokens.length) ||
|
|
650
|
-
(resuming && cached !== promptTokens.length))
|
|
651
|
-
throw new Error(
|
|
652
|
-
resuming
|
|
653
|
-
? `resumed cache (${cached} tokens) must exactly cover the resume prefix (${promptTokens.length})`
|
|
654
|
-
: `pre-warmed cache (${cached} tokens) must be a strict prefix of the prompt (${promptTokens.length})`,
|
|
655
|
-
);
|
|
656
|
-
// Replace fresh full-attention caches before any forward. The deepest
|
|
657
|
-
// write is prompt + maxTokens - 1, so capacity covers every decode step.
|
|
658
|
-
maybePageKv(cache, options, promptTokens.length + maxTokens);
|
|
659
|
-
appender = options.fill ? binding.createAppend?.({
|
|
660
|
-
hasAdapters: !!options.adapters?.length, pagedKv: !!options.pagedKv,
|
|
661
|
-
}) ?? null : null;
|
|
662
|
-
} catch (error) {
|
|
663
|
-
if (ownsCache) for (const state of cache) state.dispose();
|
|
664
|
-
stepSampler.dispose();
|
|
665
|
-
options.grammar?.dispose();
|
|
666
|
-
closeBatchSetup?.();
|
|
667
|
-
closePrefill?.();
|
|
668
|
-
throw error;
|
|
669
|
-
}
|
|
670
|
-
const cachedTokens = cache[0]!.offset;
|
|
671
|
-
// Token fast-forwarding gate (see shouldUseFill). RotatingKVCache (sliding
|
|
672
|
-
// window) layers append multi-token writes through #updateConcat — O(window)
|
|
673
|
-
// per append rather than the O(L) a plain ring pays — so v1 warns and skips
|
|
674
|
-
// the whole feature for those models rather than paying it silently.
|
|
675
|
-
let fillOn = shouldUseFill(options, runtime, appender);
|
|
676
|
-
if (fillOn && cache.some((c) => c instanceof RotatingKVCache)) {
|
|
677
|
-
fillOn = false;
|
|
678
|
-
if (!warnedFillRotating) {
|
|
679
|
-
warnedFillRotating = true;
|
|
680
|
-
console.warn(
|
|
681
|
-
"[fill] sliding-window (RotatingKVCache) layers skip token " +
|
|
682
|
-
"fast-forwarding in v1 (multi-token append is O(window) there) — " +
|
|
683
|
-
"docs/design/speculative-decoding.md.",
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
const fillTrace = fillOn && fillTraceEnabled();
|
|
688
|
-
const fillTraceFile = fillOn ? fillTracePath() : null;
|
|
689
|
-
// Verify-policy proposals (echo tier) need the rejected tail rewound. That
|
|
690
|
-
// is the SAME contract the spec lane's rounds use: trimmable caches drop the
|
|
691
|
-
// tail; recurrent caches (SSMCache — gated-DeltaNet state, untrimmable)
|
|
692
|
-
// restore a pre-round snapshot and bit-exactly replay the accepted prefix.
|
|
693
|
-
// A model whose caches can do neither still gets assert-policy fills; verify
|
|
694
|
-
// proposals are dropped and counted (stats.verifyUnsupported).
|
|
695
|
-
const verifyCapable = fillOn && !options.kvBits && !options.kvConfig?.length && !options.turboQuant && cache.every(
|
|
696
|
-
(c) => c.isTrimmable() || typeof rewindable(c).specRoundRollback === "function",
|
|
697
|
-
);
|
|
698
|
-
closeBatchSetup?.();
|
|
699
|
-
|
|
700
|
-
// logits [1,1,V] → sampled token array [1] (+ optional logprob capture,
|
|
701
|
-
// all on-device)
|
|
702
|
-
const sampleStep = (
|
|
703
|
-
logits3d: MlxArray,
|
|
704
|
-
step: number,
|
|
705
|
-
): { tok: MlxArray; extras: StepExtras | null } => {
|
|
706
|
-
const result = stepSampler.sample(logits3d, step);
|
|
707
|
-
return { tok: result.token, extras: result.extras };
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
const pushHistory = (tok: MlxArray): void => stepSampler.commitDevice(tok);
|
|
711
|
-
|
|
712
|
-
// Decode-loop state lives at function scope so the finally can still
|
|
713
|
-
// report stats and dispose in-flight arrays when the consumer
|
|
714
|
-
// terminates the generator early (break on a stop sequence — the
|
|
715
|
-
// forced .return() resumes at the yield and runs the finally).
|
|
716
|
-
let prefillMs = 0;
|
|
717
|
-
let tDecode = 0;
|
|
718
|
-
let decodeMs = 0;
|
|
719
|
-
let generated = options.initialGeneratedTokens ?? 0;
|
|
720
|
-
let finishReason: GenerateStats["finishReason"];
|
|
721
|
-
const forwarded: number[] = [];
|
|
722
|
-
let pending: MlxArray | null = null;
|
|
723
|
-
let nextPending: MlxArray | null = null;
|
|
724
|
-
let pendingExtras: StepExtras | null = null;
|
|
725
|
-
let nextExtras: StepExtras | null = null;
|
|
726
|
-
let decoder: MlxDecodeStep | null = null;
|
|
727
|
-
let finished = false;
|
|
728
|
-
let threw = false;
|
|
729
|
-
let executionError: unknown;
|
|
730
|
-
let earlyUnforwardedToken: number | null = null;
|
|
731
|
-
let closeTokenZero: (() => void) | undefined;
|
|
732
|
-
const makeStats = (): GenerateStats => ({
|
|
733
|
-
promptTokens: options.originalPromptTokens ?? promptTokens.length,
|
|
734
|
-
cachedTokens: Math.min(cachedTokens, options.originalPromptTokens ?? promptTokens.length),
|
|
735
|
-
generatedTokens: generated,
|
|
736
|
-
...(finishReason ? { finishReason } : {}),
|
|
737
|
-
prefillMs,
|
|
738
|
-
decodeMs,
|
|
739
|
-
prefillTps: prefillMs > 0
|
|
740
|
-
? ((promptTokens.length - cachedTokens) / prefillMs) * 1000
|
|
741
|
-
: 0,
|
|
742
|
-
decodeTps: (generated / decodeMs) * 1000,
|
|
743
|
-
cacheTokens: [...promptTokens, ...forwarded],
|
|
744
|
-
...(fillOn ? { fill: options.fill!.stats } : {}),
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
/** The one invariant that makes fill safe: the caches hold exactly
|
|
748
|
-
* prompt + everything `forwarded` records, so a fill append must forward
|
|
749
|
-
* ONLY the injected ids — the normal step already consumed `cur` and wrote
|
|
750
|
-
* its KV. Forwarding [token, ...ids] would duplicate a position and
|
|
751
|
-
* silently corrupt both the KV and PromptCache.put's key. Checked under
|
|
752
|
-
* MLX_BUN_FILL_TRACE=1 on both sides of the append. */
|
|
753
|
-
const assertFillAlignment = (where: string): void => {
|
|
754
|
-
const offset = cache[0]!.offset;
|
|
755
|
-
const want = promptTokens.length + forwarded.length;
|
|
756
|
-
if (offset !== want)
|
|
757
|
-
throw new Error(
|
|
758
|
-
`[fill] cache misalignment ${where}: cache offset ${offset} != ` +
|
|
759
|
-
`prompt ${promptTokens.length} + forwarded ${forwarded.length}`,
|
|
760
|
-
);
|
|
761
|
-
};
|
|
762
|
-
|
|
763
|
-
/** THE apply primitive for token fast-forwarding — ONE chunked forward
|
|
764
|
-
* carries a proposal into the KV (and the recurrent state), and BOTH
|
|
765
|
-
* policies ride it (src/fill/proposal.ts):
|
|
766
|
-
*
|
|
767
|
-
* - assert: the span is determined (a template scaffold). Append and move
|
|
768
|
-
* on. No readback, no checkpoint, no rewind. The in-flight sample for
|
|
769
|
-
* the position after `token` is dropped UNEXAMINED — a discarded
|
|
770
|
-
* pipeline dispatch, not a rejected draft.
|
|
771
|
-
* - verify: the span is likely (a copy from earlier in the session).
|
|
772
|
-
* Position 0 is checked BEFORE the forward against the in-flight sample
|
|
773
|
-
* (free, and a mismatch costs nothing — nothing has been written).
|
|
774
|
-
* Positions 1..m-1 are checked against the argmax already sitting in
|
|
775
|
-
* THIS forward's logits, so verification adds no pass over the weights.
|
|
776
|
-
* The rejected tail is rewound through the same cache contract the spec
|
|
777
|
-
* lane's rounds use, and decode resumes at the first disagreement.
|
|
778
|
-
*
|
|
779
|
-
* Returns the ids the engine must now emit (empty = no fill happened).
|
|
780
|
-
* Mutates nextPending/nextExtras/forwarded, exactly like the normal step. */
|
|
781
|
-
/** One trace record: the proposal next to the model's own token at every
|
|
782
|
-
* span position (`actual[0]` = in-flight sample, `actual[j]` = argmax after
|
|
783
|
-
* ids[j-1]). Appended as JSONL to MLX_BUN_FILL_TRACE=<file>. */
|
|
784
|
-
const traceProposal = (
|
|
785
|
-
proposal: Proposal, generatedNow: number, accepted: number, actual: number[],
|
|
786
|
-
): void => {
|
|
787
|
-
const ids = proposal.ids;
|
|
788
|
-
let firstMismatch = -1;
|
|
789
|
-
for (let j = 0; j < ids.length && j < actual.length; j++)
|
|
790
|
-
if (actual[j] !== ids[j]) { firstMismatch = j; break; }
|
|
791
|
-
const dec = options.fill!.decode;
|
|
792
|
-
const rec: FillTraceRecord = {
|
|
793
|
-
ts: new Date().toISOString(), origin: proposal.origin, policy: proposal.policy,
|
|
794
|
-
generated: generatedNow, proposedLen: ids.length, accepted, firstMismatch,
|
|
795
|
-
proposed: ids, actual,
|
|
796
|
-
...(dec ? { proposedText: dec(ids), actualText: dec(actual) } : {}),
|
|
797
|
-
};
|
|
798
|
-
try { appendFileSync(fillTraceFile!, JSON.stringify(rec) + "\n"); } catch { /* trace is best-effort */ }
|
|
799
|
-
};
|
|
800
|
-
|
|
801
|
-
const applyProposal = async (
|
|
802
|
-
proposal: Proposal, generatedNow: number,
|
|
803
|
-
): Promise<number[]> => {
|
|
804
|
-
const fill = options.fill!;
|
|
805
|
-
const ids = proposal.ids;
|
|
806
|
-
const verify = proposal.policy === "verify";
|
|
807
|
-
if (verify && !verifyCapable) {
|
|
808
|
-
fill.noteVerifyUnsupported();
|
|
809
|
-
fill.commit(proposal, 0);
|
|
810
|
-
return [];
|
|
811
|
-
}
|
|
812
|
-
// The in-flight sample IS the model's own choice for position 0 (read for
|
|
813
|
-
// the trace under both policies; the served assert path never checks it).
|
|
814
|
-
const inFlight = verify || fillTraceFile ? ops.itemUint32(nextPending!) : -1;
|
|
815
|
-
if (verify) {
|
|
816
|
-
if (inFlight !== ids[0]) {
|
|
817
|
-
if (fillTraceFile) traceProposal(proposal, generatedNow, 0, [inFlight]);
|
|
818
|
-
fill.commit(proposal, 0);
|
|
819
|
-
return []; // nextPending survives; this becomes an ordinary step
|
|
820
|
-
}
|
|
821
|
-
} else {
|
|
822
|
-
fill.noteWastedSample();
|
|
823
|
-
}
|
|
824
|
-
if (fillTrace) assertFillAlignment("at fill entry");
|
|
825
|
-
nextPending!.dispose();
|
|
826
|
-
nextPending = null;
|
|
827
|
-
disposeStepExtras(nextExtras);
|
|
828
|
-
nextExtras = null;
|
|
829
|
-
// Conversion runs at the ordinary committed-token boundary as well.
|
|
830
|
-
maintainKv(cache);
|
|
831
|
-
|
|
832
|
-
let checkpointMs = 0;
|
|
833
|
-
let roundOpen = false;
|
|
834
|
-
if (verify) {
|
|
835
|
-
const t0 = performance.now();
|
|
836
|
-
for (const c of cache) rewindable(c).specRoundBegin?.();
|
|
837
|
-
roundOpen = true;
|
|
838
|
-
checkpointMs += performance.now() - t0;
|
|
839
|
-
}
|
|
840
|
-
let accepted = ids.length;
|
|
841
|
-
let hf: MlxArray | null = null;
|
|
842
|
-
let logitsAll: MlxArray | null = null;
|
|
843
|
-
try {
|
|
844
|
-
const chunkSize = (state: readonly Cache[]) => {
|
|
845
|
-
const maxChunk = Math.min(appender?.maxChunkSize(state) ?? 1,
|
|
846
|
-
maintainKv.maxAppendTokens?.(state) ?? Number.POSITIVE_INFINITY);
|
|
847
|
-
return fill.appendChunkSize > 0 ? Math.min(fill.appendChunkSize, maxChunk) : maxChunk;
|
|
848
|
-
};
|
|
849
|
-
const committedGraph = appender ? appender.forwardHidden.bind(appender) : graph.forwardHidden.bind(graph);
|
|
850
|
-
const forwardCommitted = maintainKv.maxAppendTokens ? async (ids: MlxArray, state: Cache[]) => {
|
|
851
|
-
const hidden = await committedGraph(ids, state);
|
|
852
|
-
try { maintainKv(state); return hidden; }
|
|
853
|
-
catch (error) { hidden.dispose(); throw error; }
|
|
854
|
-
} : committedGraph;
|
|
855
|
-
hf = await appendFillHidden(
|
|
856
|
-
verify ? graph.forwardHidden.bind(graph) : forwardCommitted,
|
|
857
|
-
cache, ids, verify ? () => ids.length : chunkSize,
|
|
858
|
-
);
|
|
859
|
-
const [, Lf, Hf] = hf.shape as [number, number, number];
|
|
860
|
-
let pred: number[] | null = null;
|
|
861
|
-
if (verify || fillTraceFile) {
|
|
862
|
-
// Free logits: this forward already computed every span position's
|
|
863
|
-
// hidden state. argmax at j is the model's continuation after ids[j],
|
|
864
|
-
// so it verifies ids[j+1]. (Under assert this readback is trace-only.)
|
|
865
|
-
logitsAll = graph.projectLogits(hf, { type: "all" });
|
|
866
|
-
const argmax = ops.argmaxAxis(logitsAll, -1);
|
|
867
|
-
pred = argmax.toIntTokens();
|
|
868
|
-
argmax.dispose();
|
|
869
|
-
}
|
|
870
|
-
if (verify && pred) {
|
|
871
|
-
for (let j = 0; j + 1 < ids.length; j++) {
|
|
872
|
-
if (pred[j] !== ids[j + 1]) { accepted = j + 1; break; }
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
if (fillTraceFile && pred)
|
|
876
|
-
traceProposal(proposal, generatedNow, accepted, [inFlight, ...pred.slice(0, ids.length - 1)]);
|
|
877
|
-
const t1 = performance.now();
|
|
878
|
-
if (accepted < ids.length) {
|
|
879
|
-
// Trimmable caches drop the rejected tail; recurrent caches restore
|
|
880
|
-
// their pre-round snapshot and bit-exactly replay the accepted prefix
|
|
881
|
-
// (Cache.specRoundRollback — the same primitive src/spec/serve-loop.ts
|
|
882
|
-
// uses after its accept walk).
|
|
883
|
-
for (const c of cache) {
|
|
884
|
-
const rc = rewindable(c);
|
|
885
|
-
if (rc.specRoundRollback) rc.specRoundRollback(accepted);
|
|
886
|
-
else rc.trim(ids.length - accepted);
|
|
887
|
-
}
|
|
888
|
-
roundOpen = false;
|
|
889
|
-
} else if (roundOpen) {
|
|
890
|
-
for (const c of cache) rewindable(c).specRoundCommit?.();
|
|
891
|
-
roundOpen = false;
|
|
892
|
-
}
|
|
893
|
-
if (verify) checkpointMs += performance.now() - t1;
|
|
894
|
-
forwarded.push(...ids.slice(0, accepted));
|
|
895
|
-
if (fillTrace) assertFillAlignment("after fill append");
|
|
896
|
-
// The sampler's token history gets the ACCEPTED ids only, before the
|
|
897
|
-
// resume sample — so logits processors (repetition/presence/frequency)
|
|
898
|
-
// see exactly the sequence that was emitted.
|
|
899
|
-
stepSampler.commitNumbers(ids.slice(0, accepted));
|
|
900
|
-
// Resume from the LAST ACCEPTED position's logits: the bonus token on a
|
|
901
|
-
// full accept, the correction on a partial one. Same sampler and step
|
|
902
|
-
// index as an ordinary step — the correction's KV is written by the next
|
|
903
|
-
// iteration's forward, like any sampled token.
|
|
904
|
-
const willGen = generatedNow + accepted;
|
|
905
|
-
if (willGen < maxTokens) {
|
|
906
|
-
let logits: MlxArray;
|
|
907
|
-
if (logitsAll) {
|
|
908
|
-
const V = logitsAll.shape[2]!;
|
|
909
|
-
logits = logitsAll.slice([0, accepted - 1, 0], [1, accepted, V]);
|
|
910
|
-
} else {
|
|
911
|
-
const hLast = hf.slice([0, Lf - 1, 0], [1, Lf, Hf]);
|
|
912
|
-
logits = graph.projectLogits(hLast, { type: "all" });
|
|
913
|
-
hLast.dispose();
|
|
914
|
-
}
|
|
915
|
-
const sn = sampleStep(logits, willGen);
|
|
916
|
-
nextPending = sn.tok;
|
|
917
|
-
nextExtras = sn.extras;
|
|
918
|
-
logits.dispose();
|
|
919
|
-
ops.asyncEvalAll([nextPending, ...stepExtrasArrays(nextExtras)]);
|
|
920
|
-
}
|
|
921
|
-
} finally {
|
|
922
|
-
// A throw mid-round would otherwise leave the recurrent caches armed
|
|
923
|
-
// (their next forward raises "spec round already recorded").
|
|
924
|
-
if (roundOpen) for (const c of cache) rewindable(c).specRoundCommit?.();
|
|
925
|
-
logitsAll?.dispose();
|
|
926
|
-
hf?.dispose();
|
|
927
|
-
}
|
|
928
|
-
if (verify) fill.noteVerifyEvent(checkpointMs);
|
|
929
|
-
fill.commit(proposal, accepted);
|
|
930
|
-
if (fillTrace)
|
|
931
|
-
console.error(
|
|
932
|
-
`[fill] ${proposal.origin}/${proposal.policy} ${accepted}/${ids.length} ` +
|
|
933
|
-
`after ${generatedNow} generated (events ${fill.stats.events}, ` +
|
|
934
|
-
`injected ${fill.stats.injected}, checkpoint ${checkpointMs.toFixed(2)}ms)`,
|
|
935
|
-
);
|
|
936
|
-
return ids.slice(0, accepted);
|
|
937
|
-
};
|
|
938
|
-
|
|
939
|
-
let nextCheckpoint = options.checkpointEveryTokens && options.checkpointEveryTokens > 0
|
|
940
|
-
? (Math.floor(generated / options.checkpointEveryTokens) + 1) * options.checkpointEveryTokens
|
|
941
|
-
: Number.POSITIVE_INFINITY;
|
|
942
|
-
let tPrefill = performance.now();
|
|
943
|
-
|
|
944
|
-
try {
|
|
945
|
-
if (resuming) {
|
|
946
|
-
// The checkpoint cache already covers the complete replay prefix. Its
|
|
947
|
-
// pending token was sampled from that exact state before the snapshot,
|
|
948
|
-
// so resume starts directly at the decode loop without another forward.
|
|
949
|
-
if (needsTokenHistory) stepSampler.seedHistory(promptTokens);
|
|
950
|
-
// Device samplers return uint32. Preserve that signature on restore so
|
|
951
|
-
// compiled decode does not trace a different signed-token graph first.
|
|
952
|
-
using restoredToken = ops.fromInt32([options.initialPendingToken!], [1]);
|
|
953
|
-
pending = restoredToken.astype(Dtype.uint32);
|
|
954
|
-
ops.asyncEvalAll([pending]);
|
|
955
|
-
prefillMs = 0;
|
|
956
|
-
closePrefill?.();
|
|
957
|
-
tDecode = performance.now();
|
|
958
|
-
} else {
|
|
959
|
-
// ---- prefill ----
|
|
960
|
-
const closeInitialKv = diagnostics.trace?.begin("prefill.kv_maintenance", {
|
|
961
|
-
mechanism: diagnostics.mechanism ?? "serial",
|
|
962
|
-
boundary: "initial",
|
|
963
|
-
});
|
|
964
|
-
maintainKv(cache);
|
|
965
|
-
closeInitialKv?.();
|
|
966
|
-
tPrefill = performance.now();
|
|
967
|
-
let h0: MlxArray;
|
|
968
|
-
if (options.promptEmbeddings) {
|
|
969
|
-
if (cachedTokens !== 0)
|
|
970
|
-
throw new Error("promptEmbeddings cannot be combined with a pre-warmed cache");
|
|
971
|
-
if (needsTokenHistory)
|
|
972
|
-
stepSampler.seedHistory(promptTokens);
|
|
973
|
-
// e2b/e4b need the spliced token ids to build per-layer inputs
|
|
974
|
-
// (multimodal soft-token positions zeroed inside forwardEmbeddings).
|
|
975
|
-
const embedIds = ops.fromInt32(promptTokens, [1, promptTokens.length]);
|
|
976
|
-
h0 = binding.forwardEmbeddings!(
|
|
977
|
-
options.promptEmbeddings, cache, options.imageMask ?? null, embedIds,
|
|
978
|
-
options.multimodalMask ?? null,
|
|
979
|
-
);
|
|
980
|
-
embedIds.dispose();
|
|
981
|
-
} else {
|
|
982
|
-
let pos = cachedTokens;
|
|
983
|
-
const snapshotAt = options.onPrefillDone && options.snapshotAt !== undefined
|
|
984
|
-
? Math.min(Math.max(options.snapshotAt, cachedTokens), promptTokens.length)
|
|
985
|
-
: undefined;
|
|
986
|
-
const tailSplit = runtime.flag("MLX_BUN_PREFILL_TAIL_SPLIT", true);
|
|
987
|
-
const forward = graph.forwardHidden.bind(graph);
|
|
988
|
-
const maintain = () => maintainKv(cache);
|
|
989
|
-
while (true) {
|
|
990
|
-
options.signal?.throwIfAborted();
|
|
991
|
-
const step = nextPrefillStep({ length: promptTokens.length, position: pos,
|
|
992
|
-
chunkSize: prefillChunkSize, tailSplit, snapshotAt });
|
|
993
|
-
if (step.kind === "final" && needsTokenHistory) stepSampler.seedHistory(promptTokens);
|
|
994
|
-
const closeChunk = diagnostics.trace?.begin("prefill.chunk", {
|
|
995
|
-
mechanism: diagnostics.mechanism ?? "serial", startToken: step.start,
|
|
996
|
-
tokens: step.end - step.start, ...(step.kind === "final" ? { final: true } : {}),
|
|
997
|
-
});
|
|
998
|
-
let hidden: MlxArray | null;
|
|
999
|
-
try { hidden = await executeMlxPrefillStep(forward, cache, promptTokens, step, maintain); }
|
|
1000
|
-
finally { closeChunk?.(); }
|
|
1001
|
-
pos = step.end;
|
|
1002
|
-
if (hidden) { h0 = hidden; break; }
|
|
1003
|
-
if (runtime.value("MLX_BUN_PREFILL_MEM_LOG") === "1")
|
|
1004
|
-
console.error(`[prefill-mem] ${pos} active ${(activeMemory() / 2 ** 30).toFixed(2)} peak ${(peakMemory() / 2 ** 30).toFixed(2)}`);
|
|
1005
|
-
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
1006
|
-
if (step.snapshot) options.onPrefillDone?.();
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
if (options.snapshotAt === undefined || options.snapshotAt >= promptTokens.length)
|
|
1010
|
-
options.onPrefillDone?.();
|
|
1011
|
-
// The trace's prefill span is the uncached model-forward portion only.
|
|
1012
|
-
// Token-0 lm-head, sampling, and readback are a separate additive stage.
|
|
1013
|
-
if (diagnostics.trace && runtime.value("MLX_BUN_P2R_SYNC") === "1")
|
|
1014
|
-
synchronize(gpuStream);
|
|
1015
|
-
closePrefill?.();
|
|
1016
|
-
closeTokenZero = diagnostics.trace?.begin("token_zero.total", {
|
|
1017
|
-
mechanism: diagnostics.mechanism ?? "serial",
|
|
1018
|
-
});
|
|
1019
|
-
const [, L0, H] = h0.shape as [number, number, number];
|
|
1020
|
-
const hLast = h0.slice([0, L0 - 1, 0], [1, L0, H]);
|
|
1021
|
-
h0.dispose();
|
|
1022
|
-
const logits0 = graph.projectLogits(hLast, { type: "all" });
|
|
1023
|
-
hLast.dispose();
|
|
1024
|
-
const s0 = sampleStep(logits0, 0); // token array [1] (+ optional extras)
|
|
1025
|
-
pending = s0.tok;
|
|
1026
|
-
pendingExtras = s0.extras;
|
|
1027
|
-
logits0.dispose();
|
|
1028
|
-
// mirror mlx-lm generate_step: async-dispatch the first token's
|
|
1029
|
-
// compute; the prefill clock keeps running until the token ARRIVES
|
|
1030
|
-
// (first itemUint32 below). mlx-lm stops its prompt clock at the
|
|
1031
|
-
// first yielded token, which bills the prefill→decode boundary
|
|
1032
|
-
// (allocator reclaim of prefill transients + first-step dispatch)
|
|
1033
|
-
// to prompt_time, not decode — replicated so cross-stack decode
|
|
1034
|
-
// tok/s measure the same quantity. The boundary cost is real and
|
|
1035
|
-
// scales with prompt length; it belongs to "having prefilled".
|
|
1036
|
-
ops.asyncEvalAll([pending, ...stepExtrasArrays(pendingExtras)]);
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
// ---- decode (pipelined) ----
|
|
1040
|
-
// Selection and transactional fallback belong to the graph's binding.
|
|
1041
|
-
decoder = options.decodePolicy?.compiledDecode === false ? null : binding.createDecode?.({
|
|
1042
|
-
hasAdapters: !!options.adapters?.length, pagedKv: !!options.pagedKv,
|
|
1043
|
-
}) ?? null;
|
|
1044
|
-
let stop = false;
|
|
1045
|
-
/** Token id read eagerly at the top of the loop for grammar (reused for
|
|
1046
|
-
* the yield, avoiding a second readback). -1 when grammar is off — the
|
|
1047
|
-
* pipelined path keeps its deferred itemUint32 below. */
|
|
1048
|
-
let grammarTok = -1;
|
|
1049
|
-
// Jump-forward decoding (opt-in, MLX_BUN_GRAMMAR_JUMP=1; serial lane
|
|
1050
|
-
// only — the batch lane's #stepGrammar doesn't jump yet): when the
|
|
1051
|
-
// grammar forces a unique continuation, emit its retokenized ids without
|
|
1052
|
-
// per-token forwards — ONE multi-token forward carries them into the KV
|
|
1053
|
-
// (see GrammarController.jumpForward for the contract + the fidelity
|
|
1054
|
-
// note on why this is opt-in). Excluded when logprobs are requested
|
|
1055
|
-
// (jumped tokens are never sampled, so they'd have no logprobs rows).
|
|
1056
|
-
const grammarJump = options.decodePolicy?.grammarJump ?? shouldUseGrammarJump(options, runtime);
|
|
1057
|
-
// Yield token zero before its own decode forward. The captured runtime
|
|
1058
|
-
// keeps this scheduling choice stable across consumer awaits.
|
|
1059
|
-
const earlyFirstToken = runtime.flag("MLX_BUN_EARLY_FIRST_TOKEN", true) &&
|
|
1060
|
-
maxTokens > 1 && !resuming && !fillOn && !options.grammar;
|
|
1061
|
-
while (!stop) {
|
|
1062
|
-
options.signal?.throwIfAborted();
|
|
1063
|
-
const cur = pending!;
|
|
1064
|
-
const stepIndex = generated;
|
|
1065
|
-
let curExtras = pendingExtras;
|
|
1066
|
-
let yieldedEarly = false;
|
|
1067
|
-
if (earlyFirstToken && stepIndex === 0) {
|
|
1068
|
-
const firstToken = ops.itemUint32(cur);
|
|
1069
|
-
options.signal?.throwIfAborted();
|
|
1070
|
-
if (!eosTokenIds.includes(firstToken)) {
|
|
1071
|
-
prefillMs = performance.now() - tPrefill;
|
|
1072
|
-
closeTokenZero?.();
|
|
1073
|
-
closeTokenZero = undefined;
|
|
1074
|
-
tDecode = performance.now();
|
|
1075
|
-
const logprobs = readStepExtras(curExtras);
|
|
1076
|
-
curExtras = null;
|
|
1077
|
-
pendingExtras = null;
|
|
1078
|
-
// Count before yielding so an early return reports this token. Its
|
|
1079
|
-
// KV is still absent; forwarded describes the prompt-only cache.
|
|
1080
|
-
generated++;
|
|
1081
|
-
yieldedEarly = true;
|
|
1082
|
-
earlyUnforwardedToken = firstToken;
|
|
1083
|
-
yield { token: firstToken, index: stepIndex, ...(logprobs ? { logprobs } : {}) };
|
|
1084
|
-
earlyUnforwardedToken = null;
|
|
1085
|
-
options.signal?.throwIfAborted();
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
// Keep current extras owned by the run until readback succeeds. A
|
|
1089
|
-
// forward/grammar failure must leave them reachable by finally.
|
|
1090
|
-
// Grammar advance (src/grammar.ts): acceptToken needs the token id as a
|
|
1091
|
-
// JS number, which the pipelined loop defers (it operates on device
|
|
1092
|
-
// arrays). So grammar requests eager-read cur here, advance the matcher,
|
|
1093
|
-
// and await the async mask precompute — which overlaps the GPU forward
|
|
1094
|
-
// dispatched just below. This trades the readback/forward overlap for
|
|
1095
|
-
// correctness (the mask for step n+1 must reflect token n). Non-grammar
|
|
1096
|
-
// requests keep the fast pipelined loop untouched.
|
|
1097
|
-
//
|
|
1098
|
-
// F1 fix (batched-lane plan): always eager-read `grammarTok` when
|
|
1099
|
-
// grammar is on, even at the max_tokens boundary — the emitted token
|
|
1100
|
-
// reuses it unconditionally. The OLD code gated the READ on
|
|
1101
|
-
// generated+1 < maxTokens but emitted grammarTok regardless, so the LAST
|
|
1102
|
-
// iteration skipped the refresh and emitted a stale/garbage token (the
|
|
1103
|
-
// previous step's token, or -1 when max_tokens=1) → truncated JSON ended
|
|
1104
|
-
// on a corrupted token + cacheTokens recorded the wrong id. Now only
|
|
1105
|
-
// accept()/ready() (which prepare the NEXT step's mask) are gated.
|
|
1106
|
-
/** Forced ids to emit after cur this iteration (jump-forward), else null. */
|
|
1107
|
-
let jumpEmit: number[] | null = null;
|
|
1108
|
-
if (options.grammar) {
|
|
1109
|
-
grammarTok = ops.itemUint32(cur);
|
|
1110
|
-
if (stepIndex + 1 < maxTokens) {
|
|
1111
|
-
options.grammar.accept(grammarTok);
|
|
1112
|
-
await options.grammar.ready();
|
|
1113
|
-
if (grammarJump && !options.grammar.isTerminated) {
|
|
1114
|
-
jumpEmit = options.grammar.jumpForward(maxTokens - (stepIndex + 1));
|
|
1115
|
-
// jumpForward advanced the matcher and fired the post-jump mask
|
|
1116
|
-
// fill; it must be ready before this iteration's sampleStep.
|
|
1117
|
-
if (jumpEmit) await options.grammar.ready();
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
// build step n+1's graph from the *unread* pending token
|
|
1122
|
-
nextPending = null;
|
|
1123
|
-
nextExtras = null;
|
|
1124
|
-
// When the grammar has terminated (a complete valid JSON/schema
|
|
1125
|
-
// accepted), there are no valid tokens left — skip building the next
|
|
1126
|
-
// step so the sampler never sees an all--inf distribution.
|
|
1127
|
-
if (jumpEmit) {
|
|
1128
|
-
// JUMP iteration: one [1, 1+m] forward carries cur AND the forced ids
|
|
1129
|
-
// into the KV (they are all committed content — jumpForward's
|
|
1130
|
-
// contract); the next sampled token, if the budget and grammar allow
|
|
1131
|
-
// one, comes from its last position. Compiled decode resumes on the
|
|
1132
|
-
// following iteration (supports() re-checks the grown caches).
|
|
1133
|
-
maintainKv(cache);
|
|
1134
|
-
pushHistory(cur);
|
|
1135
|
-
stepSampler.commitNumbers(jumpEmit);
|
|
1136
|
-
const chunk = [grammarTok, ...jumpEmit];
|
|
1137
|
-
const ids = ops.fromInt32(chunk, [1, chunk.length]);
|
|
1138
|
-
const h = await graph.forwardHidden(ids, cache);
|
|
1139
|
-
ids.dispose();
|
|
1140
|
-
// Every chunk token's KV is in the cache regardless of what follows.
|
|
1141
|
-
forwarded.push(...chunk);
|
|
1142
|
-
const willGen = stepIndex + 1 + jumpEmit.length;
|
|
1143
|
-
if (willGen < maxTokens && !options.grammar!.isTerminated) {
|
|
1144
|
-
const [, Lj, Hj] = h.shape as [number, number, number];
|
|
1145
|
-
const hLast = h.slice([0, Lj - 1, 0], [1, Lj, Hj]);
|
|
1146
|
-
h.dispose();
|
|
1147
|
-
const logits = graph.projectLogits(hLast, { type: "all" });
|
|
1148
|
-
hLast.dispose();
|
|
1149
|
-
const sn = sampleStep(logits, willGen);
|
|
1150
|
-
nextPending = sn.tok;
|
|
1151
|
-
nextExtras = sn.extras;
|
|
1152
|
-
logits.dispose();
|
|
1153
|
-
ops.asyncEvalAll([nextPending, ...stepExtrasArrays(nextExtras)]);
|
|
1154
|
-
} else {
|
|
1155
|
-
h.dispose(); // burst ends the generation (max_tokens or grammar done)
|
|
1156
|
-
}
|
|
1157
|
-
} else if (stepIndex + 1 < maxTokens && !options.grammar?.isTerminated) {
|
|
1158
|
-
maintainKv(cache);
|
|
1159
|
-
pushHistory(cur);
|
|
1160
|
-
let logits: MlxArray | null = null;
|
|
1161
|
-
let evalWith: MlxArray[] = [];
|
|
1162
|
-
const decoded = decoder?.tryStep(cur, cache);
|
|
1163
|
-
if (decoded) {
|
|
1164
|
-
logits = decoded.logits;
|
|
1165
|
-
evalWith = decoded.evalWith;
|
|
1166
|
-
}
|
|
1167
|
-
if (!logits) {
|
|
1168
|
-
const ids = ops.reshape(cur, [1, 1]);
|
|
1169
|
-
const h = await graph.forwardHidden(ids, cache);
|
|
1170
|
-
ids.dispose();
|
|
1171
|
-
logits = graph.projectLogits(h, { type: "all" });
|
|
1172
|
-
h.dispose();
|
|
1173
|
-
}
|
|
1174
|
-
const sn = sampleStep(logits, stepIndex + 1);
|
|
1175
|
-
nextPending = sn.tok;
|
|
1176
|
-
nextExtras = sn.extras;
|
|
1177
|
-
logits.dispose();
|
|
1178
|
-
ops.asyncEvalAll([nextPending, ...stepExtrasArrays(nextExtras), ...evalWith]);
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
// sync-read step n's token while n+1 computes
|
|
1182
|
-
// sync-read step n's token while n+1 computes (grammar already read it
|
|
1183
|
-
// eagerly above — reuse to avoid a second GPU sync)
|
|
1184
|
-
const token = options.grammar ? grammarTok : ops.itemUint32(cur);
|
|
1185
|
-
options.signal?.throwIfAborted();
|
|
1186
|
-
if (generated === 0) {
|
|
1187
|
-
// first token arrived: prompt clock stops, decode clock starts
|
|
1188
|
-
// (mlx-lm stream_generate's n==0 clock swap; the first token is
|
|
1189
|
-
// "free" on the decode clock there too)
|
|
1190
|
-
prefillMs = performance.now() - tPrefill;
|
|
1191
|
-
closeTokenZero?.();
|
|
1192
|
-
closeTokenZero = undefined;
|
|
1193
|
-
tDecode = performance.now();
|
|
1194
|
-
}
|
|
1195
|
-
cur.dispose();
|
|
1196
|
-
pending = null;
|
|
1197
|
-
if (!yieldedEarly) generated++;
|
|
1198
|
-
// if a next-step graph was built, this token's KV entered the cache
|
|
1199
|
-
// (jump iterations pushed the whole chunk already)
|
|
1200
|
-
if (nextPending !== null && !jumpEmit) forwarded.push(token);
|
|
1201
|
-
|
|
1202
|
-
if (eosTokenIds.includes(token)) {
|
|
1203
|
-
finishReason = "stop";
|
|
1204
|
-
disposeStepExtras(curExtras);
|
|
1205
|
-
pendingExtras = null;
|
|
1206
|
-
nextPending?.dispose();
|
|
1207
|
-
nextPending = null;
|
|
1208
|
-
disposeStepExtras(nextExtras);
|
|
1209
|
-
nextExtras = null;
|
|
1210
|
-
stop = true;
|
|
1211
|
-
} else {
|
|
1212
|
-
// ---- token fast-forwarding (K3): APPEND, then yield ----------------
|
|
1213
|
-
// The engine already knows the next m tokens, so it writes them into
|
|
1214
|
-
// the KV itself with an append and resumes sampling after them. No
|
|
1215
|
-
// draft, no verify, no rollback: an injected token is indistinguishable
|
|
1216
|
-
// to the model from one it sampled. The append happens BEFORE any of
|
|
1217
|
-
// the burst's yields, which is what makes a consumer break mid-burst
|
|
1218
|
-
// safe — `forwarded` already describes the cache exactly.
|
|
1219
|
-
let fillEmit: number[] | null = null;
|
|
1220
|
-
if (fillOn) {
|
|
1221
|
-
if (jumpEmit)
|
|
1222
|
-
throw new Error("fill and grammar jump-forward cannot share an iteration");
|
|
1223
|
-
// A fill needs an in-flight step to ride; with nextPending null this
|
|
1224
|
-
// token's KV was never written and the generation ends here. push()
|
|
1225
|
-
// still runs so the session's history (and echo index) stay exact.
|
|
1226
|
-
const proposal = options.fill!.push(
|
|
1227
|
-
token, nextPending !== null ? maxTokens - generated : 0,
|
|
1228
|
-
);
|
|
1229
|
-
if (proposal && nextPending !== null) {
|
|
1230
|
-
const emitted = await applyProposal(proposal, generated);
|
|
1231
|
-
if (emitted.length) fillEmit = emitted;
|
|
1232
|
-
} else if (proposal) {
|
|
1233
|
-
options.fill!.commit(proposal, 0); // unreachable at budget 0; belt
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
// readExtras before the yield: if the consumer breaks at this yield,
|
|
1237
|
-
// the extras are already read and disposed.
|
|
1238
|
-
const logprobs = readStepExtras(curExtras);
|
|
1239
|
-
pendingExtras = null;
|
|
1240
|
-
if (!yieldedEarly) yield { token, index: generated - 1, ...(logprobs ? { logprobs } : {}) };
|
|
1241
|
-
options.signal?.throwIfAborted();
|
|
1242
|
-
// mlx-lm generate_step: clear_cache after token 0 (drops the
|
|
1243
|
-
// remaining prefill transients) and every 256 tokens after
|
|
1244
|
-
if ((generated - 1) % 256 === 0) clearCache();
|
|
1245
|
-
// Jump-forward burst: the forced ids follow cur, one yield each (the
|
|
1246
|
-
// consumer's stop-sequence matcher and detokenizer see the same
|
|
1247
|
-
// one-at-a-time stream shape as always). Their KV is already in the
|
|
1248
|
-
// cache (the chunk forward above); a consumer break mid-burst is
|
|
1249
|
-
// safe — `forwarded` already reflects the cache exactly.
|
|
1250
|
-
if (jumpEmit) {
|
|
1251
|
-
for (const jt of jumpEmit) {
|
|
1252
|
-
options.signal?.throwIfAborted();
|
|
1253
|
-
generated++;
|
|
1254
|
-
yield { token: jt, index: generated - 1 };
|
|
1255
|
-
if ((generated - 1) % 256 === 0) clearCache();
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
// Fill burst: same one-yield-per-token shape as the jump burst, so
|
|
1259
|
-
// CompletionSink / StopMatcher / StreamDecoder see an ordinary stream
|
|
1260
|
-
// (a stop sequence inside an injected span fires exactly where it
|
|
1261
|
-
// would have in an unfilled run).
|
|
1262
|
-
if (fillEmit) {
|
|
1263
|
-
for (const ft of fillEmit) {
|
|
1264
|
-
options.signal?.throwIfAborted();
|
|
1265
|
-
generated++;
|
|
1266
|
-
yield { token: ft, index: generated - 1 };
|
|
1267
|
-
if ((generated - 1) % 256 === 0) clearCache();
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
if (nextPending === null) {
|
|
1271
|
-
stop = true;
|
|
1272
|
-
} else {
|
|
1273
|
-
pending = nextPending;
|
|
1274
|
-
nextPending = null;
|
|
1275
|
-
pendingExtras = nextExtras;
|
|
1276
|
-
nextExtras = null;
|
|
1277
|
-
}
|
|
1278
|
-
if (
|
|
1279
|
-
pending !== null &&
|
|
1280
|
-
options.onDecodeCheckpoint &&
|
|
1281
|
-
generated >= nextCheckpoint
|
|
1282
|
-
) {
|
|
1283
|
-
const pendingToken = ops.itemUint32(pending);
|
|
1284
|
-
await options.onDecodeCheckpoint({
|
|
1285
|
-
cacheTokens: [...promptTokens, ...forwarded],
|
|
1286
|
-
caches: cache,
|
|
1287
|
-
generatedTokens: generated,
|
|
1288
|
-
pendingToken,
|
|
1289
|
-
});
|
|
1290
|
-
const every = options.checkpointEveryTokens!;
|
|
1291
|
-
while (nextCheckpoint <= generated) nextCheckpoint += every;
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
decodeMs = performance.now() - tDecode;
|
|
1296
|
-
finished = true;
|
|
1297
|
-
return makeStats();
|
|
1298
|
-
} catch (e) {
|
|
1299
|
-
threw = true;
|
|
1300
|
-
executionError = e;
|
|
1301
|
-
throw e;
|
|
1302
|
-
} finally {
|
|
1303
|
-
try {
|
|
1304
|
-
// An early consumer return leaves token zero outside the retained KV.
|
|
1305
|
-
// Finish that one M=1 forward before handing caller-owned caches back,
|
|
1306
|
-
// matching the ordinary pipeline's boundary. Otherwise a later prefix
|
|
1307
|
-
// hit folds the token into an M>1 prefill and changes native reductions.
|
|
1308
|
-
// Aborted requests and caches owned by this run need no retained state.
|
|
1309
|
-
if (!ownsCache && !threw && !options.signal?.aborted && earlyUnforwardedToken !== null) {
|
|
1310
|
-
maintainKv(cache);
|
|
1311
|
-
const ids = ops.reshape(pending!, [1, 1]);
|
|
1312
|
-
try {
|
|
1313
|
-
const hidden = await graph.forwardHidden(ids, cache);
|
|
1314
|
-
try { evalCacheState(cache); }
|
|
1315
|
-
finally { hidden.dispose(); }
|
|
1316
|
-
} finally { ids.dispose(); }
|
|
1317
|
-
forwarded.push(earlyUnforwardedToken);
|
|
1318
|
-
}
|
|
1319
|
-
} catch (error) {
|
|
1320
|
-
threw = true;
|
|
1321
|
-
executionError = error;
|
|
1322
|
-
throw error;
|
|
1323
|
-
} finally {
|
|
1324
|
-
if (!finished) {
|
|
1325
|
-
pending?.dispose();
|
|
1326
|
-
nextPending?.dispose();
|
|
1327
|
-
disposeStepExtras(pendingExtras);
|
|
1328
|
-
disposeStepExtras(nextExtras);
|
|
1329
|
-
}
|
|
1330
|
-
try {
|
|
1331
|
-
const closing = decoder?.close();
|
|
1332
|
-
if (closing) await closing;
|
|
1333
|
-
} catch (error) {
|
|
1334
|
-
if (threw) throw new AggregateError([executionError, error],
|
|
1335
|
-
"AR execution and decoder cleanup failed", { cause: executionError });
|
|
1336
|
-
throw error;
|
|
1337
|
-
} finally {
|
|
1338
|
-
if (ownsCache) for (const c of cache) c.dispose();
|
|
1339
|
-
// TokenizerInfo is process-cached; this request owns the matcher.
|
|
1340
|
-
options.grammar?.dispose();
|
|
1341
|
-
stepSampler.dispose();
|
|
1342
|
-
}
|
|
1343
|
-
if (!finished && !threw) {
|
|
1344
|
-
// forced early return (consumer break at a yield): still report
|
|
1345
|
-
// stats — `forwarded` only lists tokens whose KV actually entered
|
|
1346
|
-
// the cache, so cacheTokens stays exact for PromptCache.put().
|
|
1347
|
-
decodeMs = performance.now() - tDecode;
|
|
1348
|
-
return makeStats();
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
}
|
|
1352
|
-
}
|