mlx-bun 0.4.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1453 -88
- package/THIRD_PARTY_NOTICES.md +882 -0
- package/bin/mlx-bun.convert.mjs +4 -0
- package/bin/mlx-bun.fuse.mjs +4 -0
- package/bin/mlx-bun.generate.mjs +4 -0
- package/bin/mlx-bun.lora.mjs +4 -0
- package/bin/mlx-bun.mjs +13 -21
- package/bin/mlx-bun.server.mjs +4 -0
- package/bin/mlx-bun.upload.mjs +4 -0
- package/dist/web/app.js +9584 -0
- package/package.json +50 -53
- package/src/cli/ane-install.ts +76 -0
- package/src/cli/args.ts +179 -0
- package/src/cli/engine-entry.ts +68 -0
- package/src/cli/inference.ts +214 -0
- package/src/cli/job-entry.ts +97 -0
- package/src/cli/library-host.ts +117 -0
- package/src/cli/main.ts +57 -0
- package/src/cli/memory-engine.ts +163 -0
- package/src/cli/memory.ts +36 -0
- package/src/cli/mlx-lm-aliases.ts +309 -0
- package/src/cli/model-choice.ts +33 -0
- package/src/cli/model-selection.ts +84 -0
- package/src/cli/module-host.ts +11 -0
- package/src/cli/module-verbs.ts +50 -0
- package/src/cli/numerical-policy.ts +12 -0
- package/src/cli/perplexity.ts +89 -0
- package/src/cli/serve-host.ts +423 -0
- package/src/cli/serve-isolated.ts +217 -0
- package/src/cli/serve-options.ts +106 -0
- package/src/cli/serve-state.ts +205 -0
- package/src/cli/serve.ts +500 -0
- package/src/cli/served-model-host.ts +102 -0
- package/src/cli/server-entry.ts +148 -0
- package/src/cli/serving-unit.ts +177 -0
- package/src/{tui.ts → cli/terminal.ts} +13 -8
- package/src/cli/worker-adapters.ts +34 -0
- package/src/cli/worker-entry.ts +272 -0
- package/src/cli/worker-unit.ts +179 -0
- package/src/engine/adapter-operation.ts +26 -0
- package/src/engine/cache-services.ts +114 -0
- package/src/engine/completion.ts +39 -0
- package/src/engine/generation-gateway.ts +358 -0
- package/src/engine/index.ts +59 -0
- package/src/engine/kv-budget.ts +120 -0
- package/src/engine/media-preparation.ts +232 -0
- package/src/engine/model-binding.ts +24 -0
- package/src/engine/model-host.ts +513 -0
- package/src/engine/model-serving.ts +89 -0
- package/src/{serve → engine}/preparation.ts +10 -8
- package/src/{serve → engine}/session-completion-engine.ts +6 -6
- package/src/engine/telemetry.ts +109 -0
- package/src/hub/downloads.ts +116 -0
- package/src/jobs/db.ts +16 -7
- package/src/jobs/events.ts +29 -0
- package/src/jobs/executable.ts +5 -0
- package/src/jobs/host.ts +51 -0
- package/src/jobs/in-process.ts +55 -0
- package/src/{contracts/jobs.ts → jobs/protocol.ts} +7 -9
- package/src/jobs/runner.ts +150 -231
- package/src/jobs/service.ts +75 -0
- package/src/jobs/sse.ts +59 -23
- package/src/jobs/worker-memory.ts +46 -0
- package/src/jobs/worker-process.ts +163 -0
- package/src/jobs/worker-supervisor.ts +258 -0
- package/src/modules.ts +58 -0
- package/src/publishing/catalog-hub.ts +18 -0
- package/src/publishing/credentials.ts +31 -0
- package/src/publishing/upload.ts +37 -0
- package/src/residency/leased-adapters.ts +21 -0
- package/src/residency/local-records.ts +16 -0
- package/src/residency/model-residency.ts +405 -0
- package/src/residency/record-index.ts +28 -0
- package/src/residency/resident-estimate.ts +50 -0
- package/src/{anthropic.ts → server/anthropic.ts} +3 -81
- package/src/server/cache-routes.ts +36 -0
- package/src/{serve → server}/chat-request.ts +12 -10
- package/src/{serve → server}/chat-stage.ts +27 -22
- package/src/{client.ts → server/client.ts} +28 -2
- package/src/{serve → server}/completion-executor.ts +42 -42
- package/src/server/decision-body.ts +55 -0
- package/src/server/decision-routes.ts +134 -0
- package/src/server/decision-wire.ts +228 -0
- package/src/{serve → server}/discovery-routes.ts +42 -83
- package/src/server/generated-token-history.ts +57 -0
- package/src/{serve → server}/http.ts +25 -13
- package/src/{serve → server}/inference-request.ts +5 -5
- package/src/server/job-routes.ts +20 -0
- package/src/server/media-prompt.ts +63 -0
- package/src/server/memory-completion-client.ts +46 -0
- package/src/server/model-routes.ts +192 -0
- package/src/{serve → server}/openai-wire.ts +3 -2
- package/src/{serve → server}/pipeline.ts +1 -1
- package/src/server/prompt-builder.ts +13 -0
- package/src/server/prompt-contracts.ts +17 -0
- package/src/server/proxy-routes.ts +187 -0
- package/src/server/publishing-routes.ts +41 -0
- package/src/{serve → server}/request-plan.ts +8 -8
- package/src/{serve → server}/request-prep.ts +82 -68
- package/src/{serve → server}/responses-client.ts +30 -13
- package/src/{responses.ts → server/responses.ts} +12 -93
- package/src/server/routes.ts +205 -0
- package/src/server/start.ts +72 -0
- package/src/server/status-routes.ts +108 -0
- package/src/{serve → server}/text-completion-stage.ts +25 -4
- package/src/server/text-prompt.ts +12 -0
- package/src/{serve → server}/token-streams.ts +36 -38
- package/src/server/worker-routes.ts +311 -0
- package/src/storage/paths.ts +61 -0
- package/src/web/assets.ts +37 -0
- package/src/web/{src → browser}/api.ts +5 -23
- package/src/web/browser/chat-host.ts +17 -0
- package/src/web/browser/installed-panels.ts +6 -0
- package/src/web/browser/main.ts +55 -0
- package/src/web/browser/markdown.ts +279 -0
- package/src/web/browser/memory-panel.ts +9 -0
- package/src/web/browser/palette.ts +64 -0
- package/src/web/browser/protocol.ts +13 -0
- package/src/web/browser/shell.ts +467 -0
- package/src/web/{src → browser}/status.ts +32 -36
- package/src/web/build.ts +51 -0
- package/src/web/public/app.html +1054 -0
- package/src/web/{sw.js → public/sw.js} +4 -3
- package/src/web/{vendor → public/vendor}/hljs-theme.css +2 -2
- package/src/web/{vendor → public/vendor}/hljs.js +2 -2
- package/THIRD_PARTY_LICENSES.md +0 -51
- package/docs/README.md +0 -94
- package/docs/archive/hlg-sampling.md +0 -350
- package/docs/archive/investigations/audio-input-plan.md +0 -356
- package/docs/archive/investigations/chunk-finetune-experiment.md +0 -84
- package/docs/archive/investigations/colibri-glm52-port.md +0 -1486
- package/docs/archive/investigations/colibri-oracle-pin.md +0 -1284
- package/docs/archive/investigations/curve-bisector-routing.md +0 -123
- package/docs/archive/investigations/decode-roofline-lookagain.md +0 -348
- package/docs/archive/investigations/diffusion-gemma-port.md +0 -602
- package/docs/archive/investigations/dspark-handoff.md +0 -224
- package/docs/archive/investigations/expert-offload-single-user-moe.md +0 -292
- package/docs/archive/investigations/faithful-l1-consolidation.md +0 -310
- package/docs/archive/investigations/hlg-sampling-investigation.md +0 -970
- package/docs/archive/investigations/indexshare-performance-spike.md +0 -357
- package/docs/archive/investigations/kernel-perf-review-2026-07.md +0 -171
- package/docs/archive/investigations/lab-build-journal.md +0 -157
- package/docs/archive/investigations/minicpm5-decode-megakernel.md +0 -489
- package/docs/archive/investigations/openjarvis-fit.md +0 -198
- package/docs/archive/investigations/openwiki-evaluation.md +0 -249
- package/docs/archive/investigations/optimization_plan.md +0 -346
- package/docs/archive/investigations/orpo-base-uf-experiment-and-directions.md +0 -120
- package/docs/archive/investigations/orpo-flash-cce-pin-leak.md +0 -167
- package/docs/archive/investigations/orpo-uf-testing-handoff.md +0 -126
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +0 -92
- package/docs/archive/investigations/pi-builtin-investigation.md +0 -308
- package/docs/archive/investigations/pre-colibri-stabilization.md +0 -329
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +0 -1092
- package/docs/archive/investigations/segmented-backward-handoff.md +0 -148
- package/docs/archive/investigations/starter-model-port-handoff.md +0 -342
- package/docs/archive/investigations/steel-flash-cce-handoff.md +0 -489
- package/docs/archive/investigations/the-dreaming-handoff.md +0 -141
- package/docs/archive/investigations/trainer-validation-experiment.md +0 -173
- package/docs/archive/planning/chat-ui-vision-memory-first.md +0 -199
- package/docs/archive/planning/chat-ui-vision.md +0 -50
- package/docs/archive/planning/chunk-task-roadmap.md +0 -77
- package/docs/archive/planning/curve-sampler-research-plan.md +0 -331
- package/docs/archive/planning/journal.md +0 -316
- package/docs/archive/planning/memory-docs-and-dag-plan.md +0 -194
- package/docs/archive/planning/pi-integration-review.md +0 -178
- package/docs/archive/planning/release-notes-v0.0.10.md +0 -57
- package/docs/archive/planning/release-notes-v0.0.11.md +0 -239
- package/docs/archive/planning/release-notes-v0.0.12.md +0 -80
- package/docs/archive/planning/release-notes-v0.0.13.md +0 -77
- package/docs/archive/planning/release-notes-v0.0.9.md +0 -122
- package/docs/archive/planning/release-notes-v0.2.0.md +0 -49
- package/docs/archive/planning/release-notes-v0.3.0.md +0 -35
- package/docs/archive/planning/web-ui-pass-plan.md +0 -213
- package/docs/archive/planning/website-readme-pass-plan.md +0 -224
- package/docs/design/batching.md +0 -1841
- package/docs/design/decode-speed-program.md +0 -2349
- package/docs/design/dreaming-nightly-pipeline.md +0 -684
- package/docs/design/generic-model-support.md +0 -705
- package/docs/design/kv-cache.md +0 -824
- package/docs/design/orpo-training.md +0 -680
- package/docs/design/speculative-decoding.md +0 -1990
- package/docs/design/turboquant.md +0 -2212
- package/docs/design/unified-engine-frontier-plan.md +0 -1880
- package/docs/design/web-chat-redesign.md +0 -695
- package/docs/planning/PRODUCT_ROADMAP.md +0 -594
- package/docs/planning/ResearchTopics.md +0 -119
- package/docs/planning/release-notes-v0.4.0.md +0 -45
- package/docs/planning/video-series.md +0 -421
- package/docs/reference/benchmarks.md +0 -3613
- package/docs/reference/cli.md +0 -585
- package/docs/reference/distribution.md +0 -355
- package/docs/reference/environment.md +0 -398
- package/docs/reference/glossary.md +0 -39
- package/docs/reference/library-api.md +0 -674
- package/docs/reference/memory.md +0 -260
- package/docs/reference/models.md +0 -334
- package/docs/reference/server-api.md +0 -1775
- package/docs/reference/server-config.md +0 -846
- package/docs/reference/training.md +0 -462
- package/docs/reference/troubleshooting.md +0 -158
- package/src/admission-plan.ts +0 -18
- package/src/audio/conformer.ts +0 -647
- package/src/audio/decode.ts +0 -133
- package/src/audio/features.ts +0 -202
- package/src/audio/transcode.ts +0 -67
- package/src/backends/mlx/assistant-target.ts +0 -46
- package/src/backends/mlx/autoregressive-draft-rows.ts +0 -120
- package/src/backends/mlx/autoregressive.ts +0 -108
- package/src/backends/mlx/batch-group.ts +0 -1790
- package/src/backends/mlx/cache-attachment.ts +0 -67
- package/src/backends/mlx/cache-layout.ts +0 -31
- package/src/backends/mlx/checkpoint-state.ts +0 -53
- package/src/backends/mlx/continuation-persistence.ts +0 -106
- package/src/backends/mlx/continuation-request.ts +0 -43
- package/src/backends/mlx/continuation.ts +0 -77
- package/src/backends/mlx/diffusion.ts +0 -39
- package/src/backends/mlx/draft-target.ts +0 -32
- package/src/backends/mlx/evaluation.ts +0 -105
- package/src/backends/mlx/fill-append.ts +0 -45
- package/src/backends/mlx/full-kv-row-append.ts +0 -32
- package/src/backends/mlx/full-kv-row-donor.ts +0 -54
- package/src/backends/mlx/full-transitioning-kv-rows.ts +0 -65
- package/src/backends/mlx/gateway-binding.ts +0 -130
- package/src/backends/mlx/graph.ts +0 -38
- package/src/backends/mlx/kv-codec.ts +0 -9
- package/src/backends/mlx/kv-maintenance.ts +0 -133
- package/src/backends/mlx/kv-tensor-rows.ts +0 -212
- package/src/backends/mlx/memory-client.ts +0 -189
- package/src/backends/mlx/methods.ts +0 -90
- package/src/backends/mlx/model-prompt.ts +0 -164
- package/src/backends/mlx/model-serving.ts +0 -79
- package/src/backends/mlx/prefill-cohort.ts +0 -89
- package/src/backends/mlx/prefill-rows.ts +0 -200
- package/src/backends/mlx/prefill.ts +0 -33
- package/src/backends/mlx/projected-context-rows.ts +0 -72
- package/src/backends/mlx/request-policy.ts +0 -20
- package/src/backends/mlx/request-state-policy.ts +0 -41
- package/src/backends/mlx/rollback.ts +0 -89
- package/src/backends/mlx/row-sampling.ts +0 -49
- package/src/backends/mlx/serial-executor.ts +0 -242
- package/src/backends/mlx/speculative-group.ts +0 -359
- package/src/backends/mlx/speculative-round.ts +0 -161
- package/src/backends/mlx/speculative.ts +0 -114
- package/src/backends/mlx/state-rows.ts +0 -104
- package/src/backends/mlx/state-views.ts +0 -46
- package/src/backends/mlx/target-layout-capability.ts +0 -35
- package/src/backends/mlx/text-engine.ts +0 -73
- package/src/backends/mlx/transitioning-kv-rows.ts +0 -165
- package/src/chat-template.ts +0 -382
- package/src/cli.ts +0 -3168
- package/src/config.ts +0 -399
- package/src/contracts/completion.ts +0 -16
- package/src/contracts/execution.ts +0 -60
- package/src/contracts/generation.ts +0 -103
- package/src/contracts/host.ts +0 -7
- package/src/contracts/pi-provider.ts +0 -30
- package/src/contracts/pi.ts +0 -259
- package/src/contracts/prefix-cache.ts +0 -23
- package/src/contracts/resources.ts +0 -25
- package/src/contracts/sampling.ts +0 -23
- package/src/contracts/scheduling.ts +0 -45
- package/src/contracts/tsconfig.json +0 -14
- package/src/dataset/generators.ts +0 -1087
- package/src/dataset/index.ts +0 -12
- package/src/dataset/job.ts +0 -81
- package/src/dataset/llm.ts +0 -71
- package/src/dataset/registry.ts +0 -528
- package/src/diffusion/diffusion-generate.ts +0 -507
- package/src/download.ts +0 -505
- package/src/embed.ts +0 -77
- package/src/engine/admission.ts +0 -59
- package/src/engine/cancellation.ts +0 -38
- package/src/engine/engine.ts +0 -56
- package/src/engine/execution-coordinator.ts +0 -70
- package/src/engine/execution-plan.ts +0 -53
- package/src/engine/resources.ts +0 -53
- package/src/engine/scheduler.ts +0 -72
- package/src/engine/session.ts +0 -247
- package/src/eval/README.md +0 -130
- package/src/eval/ifeval.ts +0 -81
- package/src/eval/kl-prompts.ts +0 -29
- package/src/eval/kl.ts +0 -590
- package/src/eval/perplexity.ts +0 -164
- package/src/eval/quality-db.ts +0 -92
- package/src/eval/reference-logits.ts +0 -207
- package/src/eval/runner.ts +0 -124
- package/src/eval/score.ts +0 -49
- package/src/eval/tasks/bfcl.ts +0 -535
- package/src/eval/tasks/gsm8k.ts +0 -98
- package/src/eval/tasks/hashhop.ts +0 -139
- package/src/eval/tasks/humaneval.ts +0 -275
- package/src/eval/tasks/ifeval.ts +0 -525
- package/src/eval/tasks/mmlu.ts +0 -157
- package/src/eval/ultrafeedback.ts +0 -60
- package/src/evaldb.ts +0 -199
- package/src/expert-io.ts +0 -393
- package/src/expert-offload-build.ts +0 -77
- package/src/expert-offload.ts +0 -76
- package/src/expert-residency.ts +0 -1134
- package/src/expert-usage.ts +0 -415
- package/src/fill/echo-index.ts +0 -188
- package/src/fill/fill-session.ts +0 -452
- package/src/fill/proposal.ts +0 -68
- package/src/fill/schema-rows.ts +0 -506
- package/src/fill/tool-boundary.ts +0 -103
- package/src/fit.ts +0 -249
- package/src/generate.ts +0 -1346
- package/src/grammar.ts +0 -591
- package/src/harness-pi.ts +0 -184
- package/src/hf-push.ts +0 -628
- package/src/hub-rest.ts +0 -242
- package/src/index.ts +0 -63
- package/src/inference/denoising.ts +0 -17
- package/src/inference/draft-acceptance.ts +0 -41
- package/src/inference/draft-output.ts +0 -49
- package/src/inference/graph.ts +0 -33
- package/src/inference/prefill.ts +0 -39
- package/src/inference/rollback.ts +0 -7
- package/src/jobs/index.ts +0 -31
- package/src/jobs/job-entry.ts +0 -107
- package/src/jobs/task-client.ts +0 -19
- package/src/jobs/types.ts +0 -2
- package/src/kv-scheme.ts +0 -257
- package/src/kv-store.ts +0 -1099
- package/src/lab/README.md +0 -18
- package/src/lab/curve/curve-designer.html +0 -303
- package/src/lab/curve/curve-sampler.ts +0 -122
- package/src/lab/expert-trace/expert-trace.ts +0 -69
- package/src/lab/paged-kv/paged-kv-rows.ts +0 -83
- package/src/lab/paged-kv/paged-kv.ts +0 -270
- package/src/library.ts +0 -40
- package/src/lora.ts +0 -408
- package/src/md-modules.d.ts +0 -49
- package/src/media-fetch.ts +0 -442
- package/src/memory/article.ts +0 -558
- package/src/memory/chunk-validation.ts +0 -150
- package/src/memory/chunk.ts +0 -318
- package/src/memory/cluster.ts +0 -351
- package/src/memory/coverage.ts +0 -134
- package/src/memory/crosslink.ts +0 -0
- package/src/memory/db.ts +0 -253
- package/src/memory/entity.ts +0 -203
- package/src/memory/gate.ts +0 -187
- package/src/memory/history.ts +0 -152
- package/src/memory/ingest.ts +0 -335
- package/src/memory/model.ts +0 -183
- package/src/memory/normalize.ts +0 -671
- package/src/memory/parse.ts +0 -22
- package/src/memory/pipeline.ts +0 -275
- package/src/memory/prompts.ts +0 -65
- package/src/memory/query.ts +0 -334
- package/src/memory/reconcile.ts +0 -734
- package/src/memory/redirect.ts +0 -108
- package/src/memory/reindex.ts +0 -325
- package/src/memory/resolve.ts +0 -307
- package/src/memory/rest.ts +0 -245
- package/src/memory/route.ts +0 -272
- package/src/memory/schedule.ts +0 -165
- package/src/memory/stages.ts +0 -777
- package/src/memory/synthesize.ts +0 -1638
- package/src/memory/tools.ts +0 -749
- package/src/memory/vault.ts +0 -1194
- package/src/memory/wikify.ts +0 -1064
- package/src/mlx/array.ts +0 -388
- package/src/mlx/autograd.ts +0 -304
- package/src/mlx/checkpoint.ts +0 -149
- package/src/mlx/compile.ts +0 -168
- package/src/mlx/custom-vjp.ts +0 -188
- package/src/mlx/ffi.ts +0 -512
- package/src/mlx/materialize.ts +0 -23
- package/src/mlx/metal-kernel.ts +0 -159
- package/src/mlx/ops.ts +0 -1021
- package/src/mlx/quantized-rows.ts +0 -36
- package/src/mlx/token-bitmask.ts +0 -52
- package/src/mlx/turboquant-kv-decode.ts +0 -82
- package/src/mlx/turboquant-kv-inverse.ts +0 -122
- package/src/mlx/turboquant-ops.ts +0 -861
- package/src/mlx/turboquant-tables.ts +0 -237
- package/src/mmap.ts +0 -90
- package/src/model/artifact-identity.ts +0 -73
- package/src/model/batched-kv.ts +0 -235
- package/src/model/batched-mask.ts +0 -279
- package/src/model/batched-quant.ts +0 -212
- package/src/model/batched-quantized-kv.ts +0 -144
- package/src/model/batched-rotating-quant.ts +0 -353
- package/src/model/batched-rotating-state.ts +0 -149
- package/src/model/batched-rotating.ts +0 -338
- package/src/model/batched-row-storage.ts +0 -179
- package/src/model/batched-ssm.ts +0 -52
- package/src/model/batched-turboquant-kv.ts +0 -93
- package/src/model/compiled-decode.ts +0 -712
- package/src/model/decoded-kv-donor.ts +0 -12
- package/src/model/delayed-quantized-kv.ts +0 -72
- package/src/model/delayed-rotating-quantized-kv.ts +0 -93
- package/src/model/delayed-turboquant-kv.ts +0 -55
- package/src/model/diffusion-gemma.ts +0 -1026
- package/src/model/factory.ts +0 -189
- package/src/model/fingerprint.ts +0 -33
- package/src/model/flash-attention.ts +0 -694
- package/src/model/full-prefill-padding.ts +0 -56
- package/src/model/full-prefill-row.ts +0 -105
- package/src/model/gemma4-base.ts +0 -2351
- package/src/model/gemma4.ts +0 -1315
- package/src/model/generated/gemma4-12b.ts +0 -429
- package/src/model/generated/gemma4-26b.ts +0 -448
- package/src/model/generated/gemma4-e4b.ts +0 -717
- package/src/model/generated/index.ts +0 -18
- package/src/model/glm52-atlas.ts +0 -511
- package/src/model/glm52-cache.ts +0 -792
- package/src/model/glm52-config.ts +0 -262
- package/src/model/glm52-container.ts +0 -305
- package/src/model/glm52-coupling.ts +0 -388
- package/src/model/glm52-dsa.ts +0 -606
- package/src/model/glm52-expert-layout.ts +0 -253
- package/src/model/glm52-layout.ts +0 -152
- package/src/model/glm52-memory.ts +0 -500
- package/src/model/glm52-mla.ts +0 -792
- package/src/model/glm52-moe.ts +0 -223
- package/src/model/glm52-pilot.ts +0 -537
- package/src/model/glm52-reference.ts +0 -445
- package/src/model/glm52-residency.ts +0 -557
- package/src/model/glm52-resident-weights.ts +0 -135
- package/src/model/glm52-streamed-experts.ts +0 -453
- package/src/model/glm52-streamed-metal.ts +0 -610
- package/src/model/glm52-weights.ts +0 -503
- package/src/model/glm52.ts +0 -1132
- package/src/model/implementation.ts +0 -69
- package/src/model/kv-attention-view.ts +0 -67
- package/src/model/minicpm5.ts +0 -376
- package/src/model/profile.ts +0 -443
- package/src/model/qwen-append.ts +0 -12
- package/src/model/qwen-conv.ts +0 -52
- package/src/model/qwen3-delta-state.ts +0 -74
- package/src/model/qwen3-delta.ts +0 -451
- package/src/model/qwen3-moe.ts +0 -404
- package/src/model/qwen3-mrope.ts +0 -252
- package/src/model/qwen3.ts +0 -277
- package/src/model/qwen3_5-checkpoint.ts +0 -164
- package/src/model/qwen3_5.ts +0 -819
- package/src/model/rotating-kv-donor.ts +0 -35
- package/src/model/rotating-kv-layout.ts +0 -262
- package/src/model/rotating-row-transaction.ts +0 -35
- package/src/model/speculative-rotating-kv.ts +0 -101
- package/src/model/ssm-prefill-padding.ts +0 -39
- package/src/model/support.ts +0 -102
- package/src/model/trellis-balanced-scatter.ts +0 -51
- package/src/model/trellis-linear.ts +0 -672
- package/src/model/trellis-shared-m.ts +0 -151
- package/src/model/trellis-shared-scatter.ts +0 -144
- package/src/model/trellis-splitk-prefill.ts +0 -158
- package/src/model/trellis-tiled-prefill.ts +0 -136
- package/src/model/trellis-vector-expand.ts +0 -62
- package/src/model/trellis-wide-prefill.ts +0 -94
- package/src/model/turboquant-codec.ts +0 -83
- package/src/model/universal/archs.ts +0 -332
- package/src/model/universal/dense.ts +0 -612
- package/src/model/universal/modules.ts +0 -170
- package/src/model/universal/rope.ts +0 -381
- package/src/native/expert_io.c +0 -523
- package/src/native/frame_extract.swift +0 -140
- package/src/native-pack.ts +0 -162
- package/src/pi-provider.ts +0 -53
- package/src/pi-session.ts +0 -81
- package/src/pi-terminal.ts +0 -274
- package/src/pi-web.ts +0 -1932
- package/src/preflight.ts +0 -158
- package/src/prompt-cache.ts +0 -460
- package/src/quantize/allocator.ts +0 -627
- package/src/quantize/atomic-output.ts +0 -38
- package/src/quantize/calibration.ts +0 -181
- package/src/quantize/config-writer.ts +0 -145
- package/src/quantize/index.ts +0 -84
- package/src/quantize/job.ts +0 -162
- package/src/quantize/quantizer.ts +0 -666
- package/src/quantize/rotate.ts +0 -603
- package/src/quantize/safetensors-writer.ts +0 -249
- package/src/quantize/sensitivity.ts +0 -216
- package/src/quantize/trellis.ts +0 -586
- package/src/quantize/weight-transform.ts +0 -292
- package/src/registry.ts +0 -602
- package/src/runtime-config.ts +0 -81
- package/src/safetensors.ts +0 -158
- package/src/sampler.ts +0 -1011
- package/src/serve/admin-routes.ts +0 -184
- package/src/serve/assets/curve-terrain.html +0 -105
- package/src/serve/assets/training-inference-map.html +0 -575
- package/src/serve/aux-routes.ts +0 -132
- package/src/serve/batch-scheduler.ts +0 -8
- package/src/serve/checkpoint-identity.ts +0 -64
- package/src/serve/generation-gateway.ts +0 -496
- package/src/serve/isolate.ts +0 -609
- package/src/serve/kv-budget.ts +0 -14
- package/src/serve/lab-routes.ts +0 -341
- package/src/serve/lane-registry.ts +0 -45
- package/src/serve/model-admin-routes.ts +0 -155
- package/src/serve/model-binding.ts +0 -43
- package/src/serve/model-host.ts +0 -511
- package/src/serve/parent-application.ts +0 -56
- package/src/serve/prompt-response-trace.ts +0 -147
- package/src/serve/response-lease.ts +0 -24
- package/src/serve/session-search.ts +0 -326
- package/src/serve/static-routes.ts +0 -101
- package/src/server.ts +0 -1314
- package/src/spec/assistant-rows.ts +0 -92
- package/src/spec/assistant-source.ts +0 -126
- package/src/spec/cache-identity.ts +0 -22
- package/src/spec/deepspec-rows.ts +0 -29
- package/src/spec/deepspec-source.ts +0 -145
- package/src/spec/dflash-source.ts +0 -149
- package/src/spec/drafter.ts +0 -304
- package/src/spec/dspark/ab-stats.ts +0 -136
- package/src/spec/dspark/calibration.ts +0 -112
- package/src/spec/dspark/data-dflash.ts +0 -156
- package/src/spec/dspark/data.ts +0 -201
- package/src/spec/dspark/deepspec-module.ts +0 -776
- package/src/spec/dspark/generate-dflash.ts +0 -245
- package/src/spec/dspark/generate.ts +0 -290
- package/src/spec/dspark/loader.ts +0 -23
- package/src/spec/dspark/loss.ts +0 -213
- package/src/spec/dspark/module-dflash.ts +0 -560
- package/src/spec/dspark/module.ts +0 -540
- package/src/spec/dspark/quantize-drafter.ts +0 -91
- package/src/spec/dspark/sample.ts +0 -112
- package/src/spec/generate.ts +0 -276
- package/src/spec/glm52-mtp-graph.ts +0 -29
- package/src/spec/glm52-mtp-rows.ts +0 -132
- package/src/spec/glm52-mtp-source.ts +0 -317
- package/src/spec/glm52-mtp-state.ts +0 -33
- package/src/spec/ngram-source.ts +0 -204
- package/src/spec/projected-draft-rows.ts +0 -73
- package/src/spec/qwen-mtp-module.ts +0 -171
- package/src/spec/qwen-mtp-rows.ts +0 -219
- package/src/spec/qwen-mtp-source.ts +0 -383
- package/src/spec/qwen-mtp-state.ts +0 -30
- package/src/spec/serve-loop.ts +0 -585
- package/src/spec/source.ts +0 -215
- package/src/spec/two-model-rows.ts +0 -61
- package/src/spec/two-model.ts +0 -113
- package/src/ssd-cache.ts +0 -372
- package/src/ssd-durability.ts +0 -211
- package/src/tokenizer.ts +0 -335
- package/src/tool-approvals.ts +0 -131
- package/src/tool-call.ts +0 -695
- package/src/train/dataset.ts +0 -484
- package/src/train/diffusion-lora.ts +0 -216
- package/src/train/export.ts +0 -32
- package/src/train/flash-cce.ts +0 -1234
- package/src/train/forward.ts +0 -266
- package/src/train/fuse.ts +0 -243
- package/src/train/index.ts +0 -46
- package/src/train/job.ts +0 -205
- package/src/train/lora-params.ts +0 -254
- package/src/train/loss.ts +0 -1591
- package/src/train/merge.ts +0 -195
- package/src/train/optimizer.ts +0 -183
- package/src/train/prefix-shared.ts +0 -490
- package/src/train/rank.ts +0 -137
- package/src/train/segmented.ts +0 -1828
- package/src/train/steel-qmm-header.ts +0 -1461
- package/src/train/trainer.ts +0 -1221
- package/src/train/watch.ts +0 -542
- package/src/vision/diffusion-vision.ts +0 -620
- package/src/vision/embedder.ts +0 -168
- package/src/vision/preprocess.ts +0 -247
- package/src/vision/prompt.ts +0 -373
- package/src/vision/qwen3vl-preprocess.ts +0 -409
- package/src/vision/qwen3vl-prompt.ts +0 -196
- package/src/vision/qwen3vl-tower.ts +0 -555
- package/src/vision/siglip.ts +0 -599
- package/src/vision/video-frames.ts +0 -148
- package/src/web/app.html +0 -2143
- package/src/web/app.js +0 -5703
- package/src/web/skills/memory/SKILL.md +0 -120
- package/src/web/skills/web-research/SKILL.md +0 -46
- package/src/web/skills.ts +0 -97
- package/src/web/src/adapters-panel.ts +0 -288
- package/src/web/src/assistant.ts +0 -451
- package/src/web/src/chat.ts +0 -1057
- package/src/web/src/composer.ts +0 -959
- package/src/web/src/dataset.ts +0 -132
- package/src/web/src/finetune.ts +0 -267
- package/src/web/src/hub.ts +0 -428
- package/src/web/src/main.ts +0 -98
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +0 -572
- package/src/web/src/model-picker.ts +0 -159
- package/src/web/src/palette.ts +0 -346
- package/src/web/src/protocol.ts +0 -26
- package/src/web/src/quantize.ts +0 -167
- package/src/web/src/rag.ts +0 -274
- package/src/web/src/sessions.ts +0 -469
- package/src/web/src/shell.ts +0 -804
- package/src/web/src/ui-catalog.ts +0 -78
- package/src/web/vendor/README.md +0 -61
- package/src/web-assets.ts +0 -47
- package/src/web-tools.ts +0 -478
- package/src/weights.ts +0 -255
- /package/src/{serve → server}/completion-sink.ts +0 -0
- /package/src/web/{icon.svg → public/icon.svg} +0 -0
- /package/src/web/{manifest.webmanifest → public/manifest.webmanifest} +0 -0
- /package/src/web/{vendor → public/vendor}/hljs-LICENSE +0 -0
package/src/preflight.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
// Machine-state preflight for fair benchmarks (Phase 15 method rules).
|
|
2
|
-
// Every failure mode here was MEASURED on this machine (PLAN findings):
|
|
3
|
-
// - 6.4 GB swap residue: 26B decode 32 tok/s → 0.02 tok/s
|
|
4
|
-
// - session drift: identical bf16 runs 15.5 vs 14.3 tok/s an hour apart
|
|
5
|
-
// - cold page cache: prefill timer dominated by SSD page-in
|
|
6
|
-
// The harness refuses to record headline numbers when checks fail;
|
|
7
|
-
// the state snapshot is stored in every eval-DB row either way.
|
|
8
|
-
//
|
|
9
|
-
// LIMITS of this instrument (2026-07-05 finding): the snapshot is taken
|
|
10
|
-
// BETWEEN legs, when the bench process has exited and memory is quiet —
|
|
11
|
-
// free_pct reads a near-constant ~92% on an otherwise-idle box and load1m
|
|
12
|
-
// sat under every threshold while a 40-minute mid-pass slow window cut
|
|
13
|
-
// decode rates ~40% across every stack. Preflight ok ≠ clean measurements
|
|
14
|
-
// and preflight-failed ≠ dirty ones (the 07-04 pass was ‡-tagged yet
|
|
15
|
-
// stable). The DIRECT quality signal is per-cell run spread — see the
|
|
16
|
-
// run-stability policy in scripts/bench-h2h.ts (SPREAD_TOL retries +
|
|
17
|
-
// `unstable`/`stabilized` tags). Preflight remains the coarse pre-gate.
|
|
18
|
-
|
|
19
|
-
export interface MachineState {
|
|
20
|
-
swapUsedMB: number;
|
|
21
|
-
freePercent: number;
|
|
22
|
-
/** pmset -g therm CPU_Speed_Limit (100 = no thermal throttle; -1 if
|
|
23
|
-
* unavailable on this platform). */
|
|
24
|
-
cpuSpeedLimit: number;
|
|
25
|
-
/** iogpu.wired_limit_mb sysctl (0 = macOS default ≈75% of unified
|
|
26
|
-
* memory). A raised ceiling helps every stack equally but MUST be
|
|
27
|
-
* recorded — results depend on it. Resets on reboot. */
|
|
28
|
-
gpuWiredLimitMB: number;
|
|
29
|
-
/** 1-minute load average — post-boot indexing (Spotlight etc.) shows
|
|
30
|
-
* up here; let it settle before benchmarking. */
|
|
31
|
-
loadAvg1m: number;
|
|
32
|
-
/** Foreign processes holding > bigRssMB resident (excludes kernel,
|
|
33
|
-
* WindowServer, and our own bun/python benchmark processes). */
|
|
34
|
-
bigProcesses: { rssMB: number; command: string }[];
|
|
35
|
-
/** Explicitly permitted CPU-only background commands; RSS limits still apply. */
|
|
36
|
-
allowedCpuProcesses?: string[];
|
|
37
|
-
backgroundCpuProcesses?: { rssMB: number; command: string }[];
|
|
38
|
-
ok: boolean;
|
|
39
|
-
problems: string[];
|
|
40
|
-
at: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface PreflightLimits {
|
|
44
|
-
maxSwapUsedMB?: number; // default 512
|
|
45
|
-
minFreePercent?: number; // default 35
|
|
46
|
-
bigRssMB?: number; // default 2048
|
|
47
|
-
allowCpuProcesses?: string[];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function foreignProcessFindings(output: string, limits: PreflightLimits = {}) {
|
|
51
|
-
const bigProcesses: { rssMB: number; command: string }[] = [];
|
|
52
|
-
const backgroundCpuProcesses: { rssMB: number; command: string }[] = [];
|
|
53
|
-
for (const line of output.split("\n")) {
|
|
54
|
-
const m = line.match(/^\s*([\d.]+)\s+(\d+)\s+(.*)$/);
|
|
55
|
-
if (!m) continue;
|
|
56
|
-
const cpuPct = Number(m[1]);
|
|
57
|
-
const rssMB = Number(m[2]) / 1024;
|
|
58
|
-
const command = m[3]!;
|
|
59
|
-
if (rssMB < (limits.bigRssMB ?? 2048) && cpuPct < 50) continue;
|
|
60
|
-
if (/(bun|\.venv\/bin\/python|mlx_lm|optiq)/.test(command) ||
|
|
61
|
-
/(kernel_task|WindowServer|launchd|mds|spotlight)/i.test(command)) continue;
|
|
62
|
-
const finding = { rssMB: Math.round(rssMB),
|
|
63
|
-
command: command.slice(0, 80) + (cpuPct >= 50 ? ` (${cpuPct.toFixed(0)}% cpu)` : "") };
|
|
64
|
-
// Exact command match; permission to tolerate CPU activity never permits
|
|
65
|
-
// that process to consume the benchmark's memory headroom.
|
|
66
|
-
const allowed = rssMB < (limits.bigRssMB ?? 2048) && limits.allowCpuProcesses?.includes(command);
|
|
67
|
-
(allowed ? backgroundCpuProcesses : bigProcesses).push(finding);
|
|
68
|
-
}
|
|
69
|
-
return { bigProcesses, backgroundCpuProcesses };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function sh(cmd: string[]): string {
|
|
73
|
-
const r = Bun.spawnSync(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
74
|
-
return r.exitCode === 0 ? r.stdout.toString() : "";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function checkMachine(limits: PreflightLimits = {}): MachineState {
|
|
78
|
-
const maxSwap = limits.maxSwapUsedMB ?? 512;
|
|
79
|
-
const minFree = limits.minFreePercent ?? 35;
|
|
80
|
-
const problems: string[] = [];
|
|
81
|
-
|
|
82
|
-
// swap: "vm.swapusage: total = 7168.00M used = 6413.06M free = ..."
|
|
83
|
-
const swapOut = sh(["sysctl", "vm.swapusage"]);
|
|
84
|
-
const swapUsedMB = Number(swapOut.match(/used = ([\d.]+)M/)?.[1] ?? -1);
|
|
85
|
-
if (swapUsedMB < 0) problems.push("could not read vm.swapusage");
|
|
86
|
-
else if (swapUsedMB > maxSwap)
|
|
87
|
-
problems.push(`swap in use: ${swapUsedMB.toFixed(0)} MB (> ${maxSwap} MB) — reboot before benchmarking`);
|
|
88
|
-
|
|
89
|
-
// free memory: "System-wide memory free percentage: 34%"
|
|
90
|
-
const memOut = sh(["memory_pressure", "-Q"]);
|
|
91
|
-
const freePercent = Number(memOut.match(/free percentage: (\d+)/)?.[1] ?? -1);
|
|
92
|
-
if (freePercent < 0) problems.push("could not read memory_pressure");
|
|
93
|
-
else if (freePercent < minFree)
|
|
94
|
-
problems.push(`free memory ${freePercent}% (< ${minFree}%) — close apps / reboot`);
|
|
95
|
-
|
|
96
|
-
// thermal: "CPU_Speed_Limit = 100"
|
|
97
|
-
const thermOut = sh(["pmset", "-g", "therm"]);
|
|
98
|
-
const speedMatch = thermOut.match(/CPU_Speed_Limit\s*=\s*(\d+)/);
|
|
99
|
-
const cpuSpeedLimit = speedMatch ? Number(speedMatch[1]) : -1;
|
|
100
|
-
if (cpuSpeedLimit !== -1 && cpuSpeedLimit < 100)
|
|
101
|
-
problems.push(`thermal throttle active: CPU speed limit ${cpuSpeedLimit}% — let the machine cool`);
|
|
102
|
-
|
|
103
|
-
// GPU wired ceiling: 0 = stock (~75% of unified). Recorded, not judged
|
|
104
|
-
// — raising it (sudo sysctl iogpu.wired_limit_mb=N) helps all stacks
|
|
105
|
-
// equally, but rows measured under different ceilings don't compare.
|
|
106
|
-
const wiredOut = sh(["sysctl", "-n", "iogpu.wired_limit_mb"]);
|
|
107
|
-
const gpuWiredLimitMB = Number(wiredOut.trim() || 0) || 0;
|
|
108
|
-
|
|
109
|
-
// load average: "{ 4.96 3.21 1.90 }" — post-boot indexing settles fast
|
|
110
|
-
const loadOut = sh(["sysctl", "-n", "vm.loadavg"]);
|
|
111
|
-
const loadAvg1m = Number(loadOut.match(/([\d.]+)/)?.[1] ?? -1);
|
|
112
|
-
if (loadAvg1m > 8)
|
|
113
|
-
problems.push(`1-min load ${loadAvg1m.toFixed(1)} — post-boot indexing still running; wait a minute`);
|
|
114
|
-
|
|
115
|
-
// big foreign processes: high RSS *or* high CPU (pcpu + rss in KB from
|
|
116
|
-
// ps). The CPU arm is the 2026-07-05 lesson: Apple's
|
|
117
|
-
// knowledgeconstructiond (Apple Intelligence indexing) burned 87% CPU
|
|
118
|
-
// with a tiny RSS during the pass's 40-min slow window — a background
|
|
119
|
-
// ML workload the RSS-only check could never flag. CPU is bursty, so
|
|
120
|
-
// one clean sample doesn't prove absence (the run-spread stability
|
|
121
|
-
// policy in bench-h2h.ts is the direct signal); a hot sample here is
|
|
122
|
-
// still a cheap early warning.
|
|
123
|
-
const { bigProcesses, backgroundCpuProcesses } = foreignProcessFindings(
|
|
124
|
-
sh(["ps", "axo", "pcpu=,rss=,command="]), limits);
|
|
125
|
-
for (const p of bigProcesses)
|
|
126
|
-
problems.push(`big process: ${p.rssMB} MB — ${p.command}`);
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
swapUsedMB, freePercent, cpuSpeedLimit, gpuWiredLimitMB, loadAvg1m,
|
|
130
|
-
bigProcesses,
|
|
131
|
-
allowedCpuProcesses: limits.allowCpuProcesses ?? [], backgroundCpuProcesses,
|
|
132
|
-
ok: problems.length === 0,
|
|
133
|
-
problems,
|
|
134
|
-
at: new Date().toISOString(),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Compact JSON for the eval-DB machine_state column. Includes machine
|
|
139
|
-
* identity — rows from different Macs must never be confused (the
|
|
140
|
-
* eval DB is per-machine, but rendered tables travel in git). */
|
|
141
|
-
export function machineStateJson(s: MachineState): string {
|
|
142
|
-
const host = (sh(["hostname", "-s"]).trim() || "unknown");
|
|
143
|
-
const chip = (sh(["sysctl", "-n", "machdep.cpu.brand_string"]).trim() || "unknown");
|
|
144
|
-
const ramGB = Math.round(Number(sh(["sysctl", "-n", "hw.memsize"]).trim() || 0) / 2 ** 30);
|
|
145
|
-
return JSON.stringify({
|
|
146
|
-
host, chip, ram_gb: ramGB,
|
|
147
|
-
swap_mb: Math.round(s.swapUsedMB),
|
|
148
|
-
free_pct: s.freePercent,
|
|
149
|
-
cpu_limit: s.cpuSpeedLimit,
|
|
150
|
-
gpu_wired_mb: s.gpuWiredLimitMB,
|
|
151
|
-
load1m: s.loadAvg1m,
|
|
152
|
-
big_procs: s.bigProcesses.length,
|
|
153
|
-
allowed_cpu_processes: s.allowedCpuProcesses ?? [],
|
|
154
|
-
background_cpu_processes: s.backgroundCpuProcesses ?? [],
|
|
155
|
-
ok: s.ok,
|
|
156
|
-
at: s.at,
|
|
157
|
-
});
|
|
158
|
-
}
|
package/src/prompt-cache.ts
DELETED
|
@@ -1,460 +0,0 @@
|
|
|
1
|
-
import { type CheckpointAttachment, materializeCheckpoint, attachmentBytes, cloneAttachments, disposeAttachments } from "./backends/mlx/checkpoint-state";
|
|
2
|
-
import { leaseCacheState, minimumReusableOffset } from "./backends/mlx/state-views";
|
|
3
|
-
import { withResource, cleanupFailure, disposeResources, ownResource } from "./engine/resources";
|
|
4
|
-
// Byte-capped LRU prompt cache — the RAM tier of the Layer-0 KV store.
|
|
5
|
-
//
|
|
6
|
-
// The mlx-lm lesson (PLAN.md): a count-capped cache of multi-GB KV
|
|
7
|
-
// entries is an OOM footgun. Ours accounts bytes (sum of KV array bytes
|
|
8
|
-
// per entry) and evicts least-recently-used until under the cap.
|
|
9
|
-
//
|
|
10
|
-
// Usage pattern (single generation queue, so take/put is race-free):
|
|
11
|
-
// const hit = cache.take(promptIds); // longest-prefix match
|
|
12
|
-
// generate(model, promptIds, { cache: hit?.caches ?? fresh })
|
|
13
|
-
// cache.put([...promptIds, ...generated], caches, ns, hit?.retain);
|
|
14
|
-
// take() lends zero-copy views and leaves every RAM donor in place, including
|
|
15
|
-
// recurrent and method state. SSD persistence runs independently of RAM reuse.
|
|
16
|
-
// put() supersedes same-ns prefix-ancestors when the new entry is trimmable.
|
|
17
|
-
//
|
|
18
|
-
// TIERING (Layer 0, unified-engine plan): when a ColdTier is attached,
|
|
19
|
-
// take() itself runs the two-tier dance — RAM peek vs cold find, restore
|
|
20
|
-
// (zero-copy mmap) + trim when the cold tier holds a strictly longer
|
|
21
|
-
// prefix — so EVERY consumer (the serial lane, the batch scheduler, future
|
|
22
|
-
// prefix sharing) gets SSD restores through the same take()/put() it
|
|
23
|
-
// already calls. Eviction spills to the tier (the #spill hook); idle
|
|
24
|
-
// entries DEMOTE to it (demoteIdle — free the GPU memory, keep the prefix
|
|
25
|
-
// reachable); onPut lets the server schedule its debounced write-behind
|
|
26
|
-
// snapshot for both lanes.
|
|
27
|
-
|
|
28
|
-
import type { Cache } from "./model/gemma4";
|
|
29
|
-
import type { PrefixCache, PrefixCacheHit } from "./contracts/prefix-cache";
|
|
30
|
-
import { cloneKvCaches } from "./kv-store";
|
|
31
|
-
|
|
32
|
-
/** Reference-counted release: wraps an entry's `retain` (e.g. an mmap
|
|
33
|
-
* unmap) so DONOR + CLONES can each hold a share — the underlying hook
|
|
34
|
-
* runs once, after the LAST holder releases. Prefix sharing hands out
|
|
35
|
-
* zero-copy VIEWS of a donor's buffers; if the donor's unmap ran while a
|
|
36
|
-
* clone still referenced the mapped pages, the clone would read freed
|
|
37
|
-
* memory. */
|
|
38
|
-
function makeSharedRetain(retain: (() => void) | undefined): { acquire(): () => void } {
|
|
39
|
-
let count = 0;
|
|
40
|
-
let released = false;
|
|
41
|
-
return {
|
|
42
|
-
acquire() {
|
|
43
|
-
count++;
|
|
44
|
-
let mine = false;
|
|
45
|
-
return () => {
|
|
46
|
-
if (mine) return; // idempotent per share
|
|
47
|
-
mine = true;
|
|
48
|
-
count--;
|
|
49
|
-
if (count === 0 && !released) {
|
|
50
|
-
released = true;
|
|
51
|
-
retain?.();
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** The cold (SSD) tier the RAM cache tiers over. Structural — this module
|
|
59
|
-
* never imports ssd-cache (which imports us); the server binds
|
|
60
|
-
* SsdCacheStore + the model into this shape (src/server.ts). Lookup and
|
|
61
|
-
* restore failures return null; store returns false on failure. Operations
|
|
62
|
-
* that require persistence retain their donor if storing fails. */
|
|
63
|
-
export interface ColdTier {
|
|
64
|
-
/** Longest stored usable prefix for prompt/ns — index-only, no I/O.
|
|
65
|
-
* `handle` is the tier's opaque entry token, passed back to restore. */
|
|
66
|
-
find(prompt: number[], ns: string): { prefixLen: number; handle: unknown } | null;
|
|
67
|
-
/** Materialize a found entry as GPU-visible caches (zero-copy COW mmap;
|
|
68
|
-
* pages fault in lazily). `retain` must run after the caches are
|
|
69
|
-
* disposed (it unmaps the backing file). Null on any failure. */
|
|
70
|
-
restore(handle: unknown): { tokens: number[]; caches: Cache[]; attachments?: CheckpointAttachment[]; retain: () => void } | null;
|
|
71
|
-
/** Borrow live caches and finish persistence before returning. A false
|
|
72
|
-
* result prevents a required demotion. */
|
|
73
|
-
store(tokens: number[], caches: Cache[], ns: string, attachments?: CheckpointAttachment[]): boolean | void;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Spill sink for eviction/demotion (write-behind, 2026-07-06).
|
|
77
|
-
*
|
|
78
|
-
* `spillOwned` (preferred, NON-BLOCKING): the cache hands the sink an
|
|
79
|
-
* entry it OWNS — zero-copy CLONES of the caches (made via the same
|
|
80
|
-
* injectable cloner take() uses, BEFORE the donor is disposed; entries
|
|
81
|
-
* are immutable so the clones stay consistent forever) plus a copy of
|
|
82
|
-
* the tokens. The sink must dispose entry.caches when done, on BOTH
|
|
83
|
-
* fulfill and reject paths (the server chains storeAsync -> dispose on
|
|
84
|
-
* ssdWriteChain). A sink that throws SYNCHRONOUSLY has not taken
|
|
85
|
-
* ownership — the cache disposes the clones and the spill degrades to
|
|
86
|
-
* a no-op; eviction/demotion never unwinds.
|
|
87
|
-
*
|
|
88
|
-
* `spillSync` (fallback): called with the LIVE entry about to be
|
|
89
|
-
* disposed; must finish reading before returning. This is the old
|
|
90
|
-
* contract — a full synchronous serialize+write under the generation
|
|
91
|
-
* lock — kept for tests and simple embedders. */
|
|
92
|
-
export interface SpillSink {
|
|
93
|
-
spillOwned?: (entry: PromptCacheEntry) => void;
|
|
94
|
-
spillSync?: (entry: PromptCacheEntry) => void;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export interface PromptCacheEntry extends PrefixCacheHit<Cache[], CheckpointAttachment[]> {
|
|
98
|
-
/** Namespace key — adapter spec for LoRA requests ("" = base model).
|
|
99
|
-
* KV computed under one adapter must never seed another's prefill. */
|
|
100
|
-
ns: string;
|
|
101
|
-
/** Release hook for entries restored from the SSD cold tier: unmaps the
|
|
102
|
-
* backing COW file mapping. MUST run only after `caches` are disposed
|
|
103
|
-
* (they may alias file pages); every dispose path here honors that, and
|
|
104
|
-
* callers who take() an entry carry the thunk with it. */
|
|
105
|
-
retain?: () => void;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function cacheBytes(caches: Cache[]): number {
|
|
109
|
-
let total = 0;
|
|
110
|
-
for (const c of caches) {
|
|
111
|
-
total += withResource(leaseCacheState(c), (state) => state.reduce((bytes, a) => bytes + a.nbytes, 0));
|
|
112
|
-
}
|
|
113
|
-
return total;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Length of the longest common prefix of two token sequences. */
|
|
117
|
-
export function commonPrefixLength(a: number[], b: number[]): number {
|
|
118
|
-
const n = Math.min(a.length, b.length);
|
|
119
|
-
let i = 0;
|
|
120
|
-
while (i < n && a[i] === b[i]) i++;
|
|
121
|
-
return i;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
interface EntryRecord {
|
|
125
|
-
entry: PromptCacheEntry;
|
|
126
|
-
bytes: number;
|
|
127
|
-
lastUsed: number;
|
|
128
|
-
lastUsedMs: number;
|
|
129
|
-
/** Ref-counted retain: the donor holds one share (entry.retain); every
|
|
130
|
-
* clone handed out by take() holds another. */
|
|
131
|
-
share: { acquire(): () => void };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export class PromptCache implements PrefixCache<Cache[], CheckpointAttachment[]> {
|
|
135
|
-
readonly maxBytes: number;
|
|
136
|
-
#entries: EntryRecord[] = [];
|
|
137
|
-
#clock = 0;
|
|
138
|
-
hits = 0;
|
|
139
|
-
misses = 0;
|
|
140
|
-
demotions = 0;
|
|
141
|
-
/** Cold-tier spill sinks (see SpillSink): spillOwned gets clones and
|
|
142
|
-
* runs the write off-lock; spillSync gets the live entry inline.
|
|
143
|
-
* Failures are the sink's problem; eviction proceeds regardless. */
|
|
144
|
-
readonly #spillOwned: ((entry: PromptCacheEntry) => void) | null;
|
|
145
|
-
readonly #spillSync: ((entry: PromptCacheEntry) => void) | null;
|
|
146
|
-
/** Optional cold tier: take() tiers over it, demoteIdle() spills into it. */
|
|
147
|
-
readonly #cold: ColdTier | null;
|
|
148
|
-
/** Fired after every successful put() — the server hangs its debounced
|
|
149
|
-
* write-behind SSD snapshot here so BOTH lanes' entries persist. */
|
|
150
|
-
onPut: ((tokens: number[], ns: string) => void) | null = null;
|
|
151
|
-
|
|
152
|
-
/** Zero-copy view cloner — injectable so model-free tests can stub it. */
|
|
153
|
-
readonly #clone: (caches: Cache[]) => Cache[];
|
|
154
|
-
|
|
155
|
-
constructor(
|
|
156
|
-
maxBytes: number,
|
|
157
|
-
// Bare function = legacy sync spill (tests, simple embedders).
|
|
158
|
-
spill: SpillSink | ((entry: PromptCacheEntry) => void) | null = null,
|
|
159
|
-
cold: ColdTier | null = null,
|
|
160
|
-
clone: (caches: Cache[]) => Cache[] = cloneKvCaches,
|
|
161
|
-
private readonly materialize: (caches: Cache[], attachments?: CheckpointAttachment[]) => void = materializeCheckpoint,
|
|
162
|
-
) {
|
|
163
|
-
this.maxBytes = maxBytes;
|
|
164
|
-
this.#spillOwned = typeof spill === "function" ? null : spill?.spillOwned ?? null;
|
|
165
|
-
this.#spillSync = typeof spill === "function" ? spill : spill?.spillSync ?? null;
|
|
166
|
-
this.#cold = cold;
|
|
167
|
-
this.#clone = clone;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
#disposeEntry(entry: PromptCacheEntry, spillFirst: boolean): void {
|
|
171
|
-
if (spillFirst) {
|
|
172
|
-
if (this.#spillOwned) {
|
|
173
|
-
// NON-BLOCKING spill: clone BEFORE disposing (clones are zero-copy
|
|
174
|
-
// views of the live arrays) and hand ownership to the sink, which
|
|
175
|
-
// flushes off the generation lock. The underlying GPU buffers stay
|
|
176
|
-
// alive until the sink disposes the clones — for demoteIdle that
|
|
177
|
-
// means the memory is freed one bounded write-chain hop later, not
|
|
178
|
-
// instantly; acceptable because the chain is serial and each write
|
|
179
|
-
// disposes its clones on settle (fulfill AND reject).
|
|
180
|
-
// Retain contract: entry.retain still runs below, possibly while
|
|
181
|
-
// the clones are in flight — safe because cold-restored entries
|
|
182
|
-
// OWN their bytes (streamed copy-restore, 2026-07-07): there is
|
|
183
|
-
// no backing mmap for retain to unmap, so nothing the clones
|
|
184
|
-
// could alias goes away.
|
|
185
|
-
let clones: Cache[] | null = null;
|
|
186
|
-
let attachments: CheckpointAttachment[] | undefined;
|
|
187
|
-
try {
|
|
188
|
-
clones = this.#clone(entry.caches);
|
|
189
|
-
attachments = cloneAttachments(entry.attachments);
|
|
190
|
-
this.#spillOwned({ tokens: [...entry.tokens], caches: clones, ns: entry.ns, attachments });
|
|
191
|
-
} catch {
|
|
192
|
-
// Failure degrades to no-spill; eviction/demotion never unwinds.
|
|
193
|
-
if (clones) for (const c of clones) try { c.dispose(); } catch {}
|
|
194
|
-
try { disposeAttachments(attachments); } catch {}
|
|
195
|
-
}
|
|
196
|
-
} else {
|
|
197
|
-
const spill =
|
|
198
|
-
this.#spillSync ??
|
|
199
|
-
(this.#cold
|
|
200
|
-
? (e: PromptCacheEntry) => this.#cold!.store(e.tokens, e.caches, e.ns, e.attachments)
|
|
201
|
-
: null);
|
|
202
|
-
if (spill) try { spill(entry); } catch {}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
disposeResources([...entry.caches, { dispose: () => disposeAttachments(entry.attachments) }]);
|
|
206
|
-
entry.retain?.(); // backing release requires successful cache disposal
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
get totalBytes(): number {
|
|
210
|
-
return this.#entries.reduce((a, e) => a + e.bytes, 0);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
get size(): number {
|
|
214
|
-
return this.#entries.length;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/** Release LRU entries until the caller's live-memory check is satisfied.
|
|
218
|
-
* Unlike background eviction, pressure relief must finish the SSD write
|
|
219
|
-
* and release the buffers now, not enqueue clones that keep them alive.
|
|
220
|
-
* Call under the engine lock, after taking the request's reusable prefix. */
|
|
221
|
-
relievePressure(overBudget: () => boolean): number {
|
|
222
|
-
let count = 0;
|
|
223
|
-
while (this.#entries.length && overBudget()) {
|
|
224
|
-
let oldest = 0;
|
|
225
|
-
for (let i = 1; i < this.#entries.length; i++)
|
|
226
|
-
if (this.#entries[i]!.lastUsed < this.#entries[oldest]!.lastUsed) oldest = i;
|
|
227
|
-
const { entry } = this.#entries[oldest]!;
|
|
228
|
-
// Keep the entry resident if persistence throws. A failed SSD write
|
|
229
|
-
// must not be presented as a successful demotion.
|
|
230
|
-
if (this.#cold) {
|
|
231
|
-
if (this.#cold.store(entry.tokens, entry.caches, entry.ns, entry.attachments) === false) break;
|
|
232
|
-
}
|
|
233
|
-
else this.#spillSync?.(entry);
|
|
234
|
-
this.#entries.splice(oldest, 1);
|
|
235
|
-
this.#disposeEntry(entry, false);
|
|
236
|
-
this.demotions++;
|
|
237
|
-
count++;
|
|
238
|
-
}
|
|
239
|
-
return count;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/** Find the entry with the longest usable common prefix of `prompt`
|
|
243
|
-
* across BOTH tiers. Every RAM entry lends zero-copy views and retains its
|
|
244
|
-
* donor, including exact-boundary recurrent state and method attachments.
|
|
245
|
-
* The caller owns the returned views —
|
|
246
|
-
* dispose them or put() an extended entry, honoring `retain` either way.
|
|
247
|
-
*
|
|
248
|
-
* Usable prefix = common prefix capped at prompt.length - 1 (at least
|
|
249
|
-
* one token must be forwarded to produce logits). Entries longer than
|
|
250
|
-
* the prefix need cache.trim(); ring caches lose trimability once
|
|
251
|
-
* wrapped — those entries only match in full.
|
|
252
|
-
*
|
|
253
|
-
* Tier order: the cold tier wins only with a STRICTLY longer usable
|
|
254
|
-
* prefix (its restore costs a mmap + lazy fault-in; RAM is free). A
|
|
255
|
-
* cold entry with an untrimmable divergent tail (ring post-wrap, SSM)
|
|
256
|
-
* is dropped and the RAM candidate (or a fresh prefill) serves.
|
|
257
|
-
* hits/misses count RAM candidacy only (cold restores are counted by
|
|
258
|
-
* the tier itself), preserving the /stats meaning. */
|
|
259
|
-
take(prompt: number[], ns = ""): PromptCacheEntry | null {
|
|
260
|
-
let bestIdx = -1;
|
|
261
|
-
let bestLen = 0;
|
|
262
|
-
for (let i = 0; i < this.#entries.length; i++) {
|
|
263
|
-
const e = this.#entries[i]!.entry;
|
|
264
|
-
if (e.ns !== ns) continue;
|
|
265
|
-
const p = Math.min(commonPrefixLength(e.tokens, prompt), prompt.length - 1);
|
|
266
|
-
if (p <= bestLen || p < minimumReusableOffset(e.caches)) continue;
|
|
267
|
-
const trimNeeded = e.tokens.length - p;
|
|
268
|
-
if (trimNeeded > 0 && (!!e.attachments?.length || !e.caches.every((c) => c.isTrimmable()))) continue;
|
|
269
|
-
bestLen = p;
|
|
270
|
-
bestIdx = i;
|
|
271
|
-
}
|
|
272
|
-
if (bestIdx === -1) this.misses++;
|
|
273
|
-
else this.hits++;
|
|
274
|
-
|
|
275
|
-
// Cold tier: strictly longer usable prefix ⇒ restore + trim; the RAM
|
|
276
|
-
// candidate stays put UNTOUCHED (the old serial-lane flow trimmed it
|
|
277
|
-
// before comparing — a needless entry degradation, gone now).
|
|
278
|
-
if (this.#cold) {
|
|
279
|
-
const hit = this.#cold.find(prompt, ns);
|
|
280
|
-
if (hit && hit.prefixLen > bestLen) {
|
|
281
|
-
const loaded = this.#cold.restore(hit.handle);
|
|
282
|
-
if (loaded) {
|
|
283
|
-
const owner = ownResource(loaded, (entry) =>
|
|
284
|
-
this.#disposeEntry({ ...entry, ns }, false));
|
|
285
|
-
const result = withResource(owner, (entry) => {
|
|
286
|
-
const trimNeeded = entry.tokens.length - hit.prefixLen;
|
|
287
|
-
if (hit.prefixLen < minimumReusableOffset(entry.caches)) return null;
|
|
288
|
-
if (trimNeeded > 0 && (!!entry.attachments?.length || !entry.caches.every((cache) => cache.isTrimmable()))) {
|
|
289
|
-
console.warn(
|
|
290
|
-
`[prompt-cache] cold entry unusable: ${entry.tokens.length} tokens, ` +
|
|
291
|
-
`needs trim ${trimNeeded} but untrimmable (wrapped ring/SSM) — re-prefilling`,
|
|
292
|
-
);
|
|
293
|
-
return null;
|
|
294
|
-
}
|
|
295
|
-
if (trimNeeded > 0) for (const cache of entry.caches) cache.trim(trimNeeded);
|
|
296
|
-
const tokens = entry.tokens.slice(0, hit.prefixLen);
|
|
297
|
-
return { ...owner.transfer(), tokens, ns };
|
|
298
|
-
});
|
|
299
|
-
if (result) return result;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (bestIdx === -1) return null;
|
|
305
|
-
// PREFIX SHARING (non-consuming serve): the caller gets zero-copy
|
|
306
|
-
// CLONES of the donor's caches, trimmed to the matched prefix; the
|
|
307
|
-
// donor stays in the cache untouched, ready to serve the next agent /
|
|
308
|
-
// session with the same prefix. Safe because mlx cache updates are
|
|
309
|
-
// FUNCTIONAL (updateAndFetch reassigns fresh arrays; buffer donation
|
|
310
|
-
// only fires at refcount 1, and the donor always holds a ref), so a
|
|
311
|
-
// clone extending itself can never mutate the donor's bytes. The old
|
|
312
|
-
// consume-and-trim semantics CANNIBALIZED donors: agent B borrowing
|
|
313
|
-
// agent A's 2k system prompt destroyed A's 10k entry to do it.
|
|
314
|
-
const rec = this.#entries[bestIdx]!;
|
|
315
|
-
rec.lastUsed = ++this.#clock;
|
|
316
|
-
rec.lastUsedMs = Date.now();
|
|
317
|
-
// Lend immutable views for every backend state. RAM retention and SSD
|
|
318
|
-
// durability remain cache responsibilities while the caller extends its
|
|
319
|
-
// own views. This keeps synchronous persistence out of a RAM hit.
|
|
320
|
-
const clones = this.#clone(rec.entry.caches);
|
|
321
|
-
let attachments: CheckpointAttachment[] | undefined;
|
|
322
|
-
const trimNeeded = rec.entry.tokens.length - bestLen;
|
|
323
|
-
try {
|
|
324
|
-
attachments = cloneAttachments(rec.entry.attachments);
|
|
325
|
-
if (trimNeeded > 0) for (const c of clones) c.trim(trimNeeded);
|
|
326
|
-
} catch (error) {
|
|
327
|
-
cleanupFailure(error, () => disposeResources([...clones, { dispose: () => disposeAttachments(attachments) }]));
|
|
328
|
-
}
|
|
329
|
-
return {
|
|
330
|
-
tokens: rec.entry.tokens.slice(0, bestLen),
|
|
331
|
-
caches: clones,
|
|
332
|
-
attachments,
|
|
333
|
-
ns,
|
|
334
|
-
retain: rec.share.acquire(),
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/** Longest usable prefix length available for `prompt` WITHOUT taking
|
|
339
|
-
* (the SSD tier compares this against its own best before restoring). */
|
|
340
|
-
peekPrefixLen(prompt: number[], ns = ""): number {
|
|
341
|
-
let best = 0;
|
|
342
|
-
for (const { entry: e } of this.#entries) {
|
|
343
|
-
if (e.ns !== ns) continue;
|
|
344
|
-
const p = Math.min(commonPrefixLength(e.tokens, prompt), prompt.length - 1);
|
|
345
|
-
if (p <= best || p < minimumReusableOffset(e.caches)) continue;
|
|
346
|
-
const trimNeeded = e.tokens.length - p; // same usability rule as take()
|
|
347
|
-
if (trimNeeded > 0 && (!!e.attachments?.length || !e.caches.every((c) => c.isTrimmable()))) continue;
|
|
348
|
-
best = p;
|
|
349
|
-
}
|
|
350
|
-
return best;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/** Read-only exact-token lookup (the write-behind snapshot path: the
|
|
354
|
-
* entry stays owned by the cache; the caller only reads array state,
|
|
355
|
-
* under the gateway lock so no generation is mutating it). */
|
|
356
|
-
findExact(tokens: number[], ns = ""): PromptCacheEntry | null {
|
|
357
|
-
for (const { entry: e } of this.#entries) {
|
|
358
|
-
if (e.ns !== ns || e.tokens.length !== tokens.length) continue;
|
|
359
|
-
if (commonPrefixLength(e.tokens, tokens) === tokens.length) return e;
|
|
360
|
-
}
|
|
361
|
-
return null;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/** Insert (or reinsert) an entry; evicts LRU entries over the byte cap
|
|
365
|
-
* (spilling each to the cold tier first, when one is attached). If the
|
|
366
|
-
* entry itself exceeds the cap it is spilled + disposed, not stored. */
|
|
367
|
-
put(tokens: number[], caches: Cache[], ns = "", retain?: () => void, attachments?: CheckpointAttachment[]): void {
|
|
368
|
-
// All cache-provided validation runs before ownership is adopted.
|
|
369
|
-
const bytes = cacheBytes(caches) + attachmentBytes(attachments);
|
|
370
|
-
const trimmable = !attachments?.length && caches.every((cache) => cache.isTrimmable());
|
|
371
|
-
// Storage takes resolved snapshots, independently of their producer.
|
|
372
|
-
this.materialize(caches, attachments);
|
|
373
|
-
const discard = (entry: PromptCacheEntry, spill: boolean) => {
|
|
374
|
-
try { this.#disposeEntry(entry, spill); }
|
|
375
|
-
catch (error) {
|
|
376
|
-
// The new entry is already owned here. Throwing back to its previous
|
|
377
|
-
// owner would invite disposal of state still held by this store.
|
|
378
|
-
console.warn(`[prompt-cache] entry cleanup failed: ${error}`);
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
const entry: PromptCacheEntry = { tokens, caches, ns, retain, attachments };
|
|
382
|
-
if (bytes > this.maxBytes) {
|
|
383
|
-
discard(entry, true);
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
// The donor's retain becomes ref-counted so take()'s clones can hold
|
|
387
|
-
// shares; the entry's own retain is the donor's share.
|
|
388
|
-
const share = makeSharedRetain(retain);
|
|
389
|
-
entry.retain = share.acquire();
|
|
390
|
-
const rec: EntryRecord = { entry, bytes, lastUsed: ++this.#clock, lastUsedMs: Date.now(), share };
|
|
391
|
-
this.#entries.push(rec);
|
|
392
|
-
// Exact duplicates (equal tokens) are redundant REGARDLESS of
|
|
393
|
-
// trimmability — the new entry serves exactly the matches the old one
|
|
394
|
-
// did (mlx-lm's trie replaces in place). Without this, every gemma ctx
|
|
395
|
-
// repeat left another full-size wrapped-ring entry behind (measured
|
|
396
|
-
// ~0.4 GB retained per repeat on the 12B bench).
|
|
397
|
-
for (const old of [...this.#entries]) {
|
|
398
|
-
if (old === rec || old.entry.ns !== ns) continue;
|
|
399
|
-
if (old.entry.tokens.length !== tokens.length) continue;
|
|
400
|
-
if (commonPrefixLength(old.entry.tokens, tokens) !== tokens.length) continue;
|
|
401
|
-
this.#entries = this.#entries.filter((r) => r !== old);
|
|
402
|
-
discard(old.entry, false);
|
|
403
|
-
}
|
|
404
|
-
// SUPERSEDE strict prefix-ancestors (the SSD store's rule, brought to
|
|
405
|
-
// RAM — prefix sharing would otherwise grow an entry per turn): same-ns
|
|
406
|
-
// entries whose tokens are a prefix of the new entry are redundant
|
|
407
|
-
// and disposed WITHOUT spill (the new entry serves their prefixes and
|
|
408
|
-
// its own write-behind persists them) — but ONLY when the new entry's
|
|
409
|
-
// caches are all trimmable. An untrimmable new entry (wrapped ring)
|
|
410
|
-
// can only serve exact-length matches, so shorter ancestors — e.g.
|
|
411
|
-
// the prompt-boundary snapshot that makes drifted multi-turn agents
|
|
412
|
-
// hit — must survive.
|
|
413
|
-
if (trimmable) {
|
|
414
|
-
for (const old of [...this.#entries]) {
|
|
415
|
-
if (old === rec || old.entry.ns !== ns) continue;
|
|
416
|
-
if (old.entry.tokens.length >= tokens.length || old.entry.tokens.length < minimumReusableOffset(caches)) continue;
|
|
417
|
-
if (commonPrefixLength(old.entry.tokens, tokens) !== old.entry.tokens.length) continue;
|
|
418
|
-
this.#entries = this.#entries.filter((r) => r !== old);
|
|
419
|
-
discard(old.entry, false);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
while (this.totalBytes > this.maxBytes && this.#entries.length > 1) {
|
|
423
|
-
let lruIdx = 0;
|
|
424
|
-
for (let i = 1; i < this.#entries.length; i++)
|
|
425
|
-
if (this.#entries[i]!.lastUsed < this.#entries[lruIdx]!.lastUsed) lruIdx = i;
|
|
426
|
-
const [evicted] = this.#entries.splice(lruIdx, 1);
|
|
427
|
-
discard(evicted!.entry, true);
|
|
428
|
-
}
|
|
429
|
-
try { this.onPut?.(tokens, ns); } catch {}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/** Layer-0 idle demotion: spill every entry unused for `idleMs` to the
|
|
433
|
-
* cold tier and FREE its GPU memory. The prefix stays reachable — the
|
|
434
|
-
* next take() restores it via zero-copy mmap (~0.25 s for a 13.7k-token
|
|
435
|
-
* entry, vs a 12 s re-prefill) — but between bursts the RAM tier drains
|
|
436
|
-
* toward empty, returning unified memory to the system. Caller must
|
|
437
|
-
* hold the generation lock (entries' arrays are disposed here) — with
|
|
438
|
-
* a spillOwned sink the lock covers only the zero-copy clone; the
|
|
439
|
-
* write runs off-lock and the buffers free when it settles. No-op
|
|
440
|
-
* without a cold tier — demotion without a place to demote TO would
|
|
441
|
-
* just be data loss. Returns entries demoted. */
|
|
442
|
-
demoteIdle(idleMs: number, now = Date.now()): number {
|
|
443
|
-
if (!this.#cold) return 0;
|
|
444
|
-
let n = 0;
|
|
445
|
-
for (const rec of [...this.#entries]) {
|
|
446
|
-
if (now - rec.lastUsedMs < idleMs) continue;
|
|
447
|
-
this.#entries = this.#entries.filter((r) => r !== rec);
|
|
448
|
-
this.#disposeEntry(rec.entry, true); // spill-first, then dispose
|
|
449
|
-
this.demotions++;
|
|
450
|
-
n++;
|
|
451
|
-
}
|
|
452
|
-
return n;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
clear(): void {
|
|
456
|
-
const entries = this.#entries;
|
|
457
|
-
this.#entries = [];
|
|
458
|
-
disposeResources(entries.map(({ entry }) => ({ dispose: () => this.#disposeEntry(entry, false) })));
|
|
459
|
-
}
|
|
460
|
-
}
|