mlx-bun 0.2.0 → 0.3.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 +71 -588
- package/docs/README.md +91 -0
- package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
- package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
- package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
- package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- package/docs/design/batching.md +569 -0
- package/docs/design/decode-speed-program.md +3178 -169
- package/docs/design/dreaming-nightly-pipeline.md +675 -282
- package/docs/design/generic-model-support.md +579 -334
- package/docs/design/kv-cache.md +680 -0
- package/docs/design/orpo-training.md +673 -1239
- package/docs/design/speculative-decoding.md +1627 -0
- package/docs/design/turboquant.md +2102 -0
- package/docs/design/unified-engine-frontier-plan.md +1831 -727
- package/docs/design/web-chat-redesign.md +666 -1083
- package/docs/planning/PRODUCT_ROADMAP.md +34 -1
- package/docs/planning/release-notes-v0.3.0.md +35 -0
- package/docs/planning/video-series.md +2 -2
- package/docs/reference/benchmarks.md +1162 -0
- package/docs/reference/cli.md +394 -159
- package/docs/reference/distribution.md +321 -182
- package/docs/reference/environment.md +348 -0
- package/docs/reference/glossary.md +39 -0
- package/docs/reference/library-api.md +566 -168
- package/docs/reference/memory.md +227 -64
- package/docs/reference/models.md +104 -6
- package/docs/reference/server-api.md +886 -425
- package/docs/reference/server-config.md +638 -330
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +166 -0
- package/package.json +9 -4
- package/src/audio/decode.ts +2 -2
- package/src/audio/features.ts +2 -2
- package/src/backends/mlx/autoregressive.ts +108 -0
- package/src/backends/mlx/batch-group.ts +1646 -0
- package/src/backends/mlx/diffusion.ts +39 -0
- package/src/backends/mlx/draft-target.ts +47 -0
- package/src/backends/mlx/evaluation.ts +105 -0
- package/src/backends/mlx/fill-append.ts +45 -0
- package/src/backends/mlx/gateway-binding.ts +71 -0
- package/src/backends/mlx/graph.ts +38 -0
- package/src/backends/mlx/memory-client.ts +189 -0
- package/src/backends/mlx/memory-guard.ts +40 -0
- package/src/backends/mlx/methods.ts +90 -0
- package/src/backends/mlx/model-prompt.ts +164 -0
- package/src/backends/mlx/model-serving.ts +79 -0
- package/src/backends/mlx/prefill.ts +33 -0
- package/src/backends/mlx/request-policy.ts +20 -0
- package/src/backends/mlx/rollback.ts +51 -0
- package/src/backends/mlx/serial-executor.ts +273 -0
- package/src/backends/mlx/speculative.ts +114 -0
- package/src/backends/mlx/state-views.ts +26 -0
- package/src/backends/mlx/text-engine.ts +73 -0
- package/src/cli.ts +58 -25
- package/src/client.ts +66 -0
- package/src/config.ts +21 -4
- package/src/contracts/completion.ts +16 -0
- package/src/contracts/execution.ts +49 -0
- package/src/contracts/generation.ts +103 -0
- package/src/contracts/host.ts +7 -0
- package/src/contracts/jobs.ts +66 -0
- package/src/contracts/pi-provider.ts +30 -0
- package/src/contracts/pi.ts +259 -0
- package/src/contracts/resources.ts +18 -0
- package/src/contracts/scheduling.ts +27 -0
- package/src/contracts/tsconfig.json +14 -0
- package/src/diffusion/diffusion-generate.ts +218 -153
- package/src/embed.ts +1 -1
- package/src/engine/admission.ts +59 -0
- package/src/engine/cancellation.ts +38 -0
- package/src/engine/engine.ts +56 -0
- package/src/engine/execution-coordinator.ts +70 -0
- package/src/engine/execution-plan.ts +51 -0
- package/src/engine/resources.ts +42 -0
- package/src/engine/scheduler.ts +67 -0
- package/src/engine/session.ts +247 -0
- package/src/eval/README.md +44 -1
- package/src/eval/kl.ts +236 -3
- package/src/eval/reference-logits.ts +207 -0
- package/src/eval/runner.ts +8 -96
- package/src/fill/echo-index.ts +188 -0
- package/src/fill/fill-session.ts +452 -0
- package/src/fill/proposal.ts +68 -0
- package/src/fill/schema-rows.ts +506 -0
- package/src/fill/tool-boundary.ts +103 -0
- package/src/fit.ts +1 -1
- package/src/generate.ts +656 -268
- package/src/grammar.ts +12 -7
- package/src/harness-pi.ts +14 -33
- package/src/hub-rest.ts +4 -4
- package/src/index.ts +7 -0
- package/src/inference/denoising.ts +17 -0
- package/src/inference/graph.ts +33 -0
- package/src/inference/prefill.ts +39 -0
- package/src/inference/rollback.ts +7 -0
- package/src/jobs/db.ts +4 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-entry.ts +12 -12
- package/src/jobs/runner.ts +98 -13
- package/src/jobs/task-client.ts +19 -0
- package/src/jobs/types.ts +2 -58
- package/src/kv-store.ts +109 -49
- package/src/lab/README.md +18 -0
- package/src/{assets → lab/curve}/curve-designer.html +2 -2
- package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
- package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
- package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
- package/src/library.ts +40 -0
- package/src/lora.ts +18 -0
- package/src/media-fetch.ts +1 -1
- package/src/memory/article.ts +1 -1
- package/src/memory/chunk.ts +1 -1
- package/src/memory/model.ts +30 -169
- package/src/memory/prompts.ts +1 -1
- package/src/memory/rest.ts +1 -1
- package/src/memory/schedule.ts +1 -1
- package/src/memory/stages.ts +4 -1
- package/src/memory/vault.ts +1 -3
- package/src/mlx/array.ts +9 -10
- package/src/mlx/compile.ts +12 -2
- package/src/mlx/ffi.ts +58 -11
- package/src/mlx/materialize.ts +23 -0
- package/src/mlx/metal-kernel.ts +10 -11
- package/src/mlx/ops.ts +33 -13
- package/src/mlx/quantized-rows.ts +36 -0
- package/src/mlx/token-bitmask.ts +52 -0
- package/src/mlx/turboquant-kv-decode.ts +82 -0
- package/src/mlx/turboquant-kv-inverse.ts +122 -0
- package/src/mlx/turboquant-ops.ts +33 -3
- package/src/mlx/turboquant-tables.ts +2 -2
- package/src/model/batched-mask.ts +8 -2
- package/src/model/batched-quant.ts +1 -1
- package/src/model/batched-rotating-quant.ts +2 -2
- package/src/model/batched-rotating.ts +1 -1
- package/src/model/compiled-decode.ts +1 -1
- package/src/model/diffusion-gemma.ts +8 -8
- package/src/model/factory.ts +76 -33
- package/src/model/fingerprint.ts +1 -1
- package/src/model/flash-attention.ts +3 -3
- package/src/model/gemma4-base.ts +26 -9
- package/src/model/gemma4.ts +6 -6
- package/src/model/generated/gemma4-12b.ts +2 -2
- package/src/model/generated/gemma4-26b.ts +2 -2
- package/src/model/generated/gemma4-e4b.ts +2 -2
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +1 -1
- package/src/model/implementation.ts +69 -0
- package/src/model/minicpm5.ts +6 -8
- package/src/model/profile.ts +12 -2
- package/src/model/qwen-append.ts +12 -0
- package/src/model/qwen-conv.ts +52 -0
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +140 -56
- package/src/model/support.ts +1 -1
- package/src/model/trellis-balanced-scatter.ts +51 -0
- package/src/model/trellis-linear.ts +672 -0
- package/src/model/trellis-shared-m.ts +151 -0
- package/src/model/trellis-shared-scatter.ts +144 -0
- package/src/model/trellis-splitk-prefill.ts +158 -0
- package/src/model/trellis-tiled-prefill.ts +136 -0
- package/src/model/trellis-vector-expand.ts +62 -0
- package/src/model/trellis-wide-prefill.ts +94 -0
- package/src/model/universal/dense.ts +1 -1
- package/src/model/universal/modules.ts +8 -1
- package/src/native-pack.ts +6 -5
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +8 -315
- package/src/prompt-cache.ts +96 -53
- package/src/quantize/allocator.ts +1 -1
- package/src/quantize/config-writer.ts +10 -3
- package/src/quantize/rotate.ts +4 -4
- package/src/quantize/trellis.ts +586 -0
- package/src/responses.ts +28 -1
- package/src/runtime-config.ts +18 -4
- package/src/sampler.ts +4 -4
- package/src/serve/batch-scheduler.ts +8 -1671
- package/src/serve/chat-request.ts +391 -0
- package/src/serve/chat-stage.ts +195 -0
- package/src/serve/checkpoint-identity.ts +64 -0
- package/src/serve/completion-executor.ts +15 -7
- package/src/serve/discovery-routes.ts +12 -13
- package/src/serve/generation-gateway.ts +154 -193
- package/src/serve/http.ts +159 -0
- package/src/serve/inference-request.ts +79 -0
- package/src/serve/isolate.ts +297 -105
- package/src/serve/lab-routes.ts +3 -0
- package/src/serve/lane-registry.ts +2 -1
- package/src/serve/model-admin-routes.ts +7 -5
- package/src/serve/model-binding.ts +43 -0
- package/src/serve/model-host.ts +512 -0
- package/src/serve/openai-wire.ts +103 -0
- package/src/serve/parent-application.ts +56 -0
- package/src/serve/pipeline.ts +33 -0
- package/src/serve/preparation.ts +33 -0
- package/src/serve/request-plan.ts +19 -15
- package/src/serve/request-prep.ts +346 -0
- package/src/serve/response-lease.ts +24 -0
- package/src/serve/responses-client.ts +69 -0
- package/src/serve/session-completion-engine.ts +110 -0
- package/src/serve/session-search.ts +1 -1
- package/src/serve/static-routes.ts +5 -5
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +317 -2649
- package/src/spec/assistant-source.ts +10 -44
- package/src/spec/deepspec-source.ts +6 -6
- package/src/spec/dflash-source.ts +8 -8
- package/src/spec/dspark/ab-stats.ts +3 -3
- package/src/spec/dspark/calibration.ts +2 -2
- package/src/spec/dspark/deepspec-module.ts +2 -2
- package/src/spec/dspark/generate-dflash.ts +2 -2
- package/src/spec/dspark/generate.ts +2 -2
- package/src/spec/dspark/loader.ts +1 -1
- package/src/spec/dspark/module-dflash.ts +4 -3
- package/src/spec/dspark/module.ts +3 -3
- package/src/spec/dspark/quantize-drafter.ts +1 -1
- package/src/spec/generate.ts +1 -1
- package/src/spec/glm52-mtp-source.ts +1 -1
- package/src/spec/ngram-source.ts +1 -1
- package/src/spec/prefix-state.ts +45 -0
- package/src/spec/qwen-mtp-source.ts +166 -69
- package/src/spec/serve-loop.ts +154 -156
- package/src/spec/source.ts +42 -10
- package/src/spec/two-model.ts +1 -1
- package/src/ssd-cache.ts +80 -12
- package/src/ssd-durability.ts +25 -13
- package/src/tool-call.ts +51 -14
- package/src/train/flash-cce.ts +2 -2
- package/src/train/forward.ts +6 -0
- package/src/train/loss.ts +1 -1
- package/src/train/prefix-shared.ts +5 -1
- package/src/train/segmented.ts +5 -3
- package/src/train/trainer.ts +6 -6
- package/src/vision/prompt.ts +1 -1
- package/src/vision/qwen3vl-tower.ts +52 -10
- package/src/web/app.html +1 -1
- package/src/web/app.js +5 -3
- package/src/web/src/adapters-panel.ts +3 -3
- package/src/web/src/assistant.ts +2 -36
- package/src/web/src/chat.ts +4 -4
- package/src/web/src/composer.ts +4 -4
- package/src/web/src/hub.ts +1 -1
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +1 -1
- package/src/web/src/model-picker.ts +1 -1
- package/src/web/src/palette.ts +1 -1
- package/src/web/src/protocol.ts +3 -39
- package/src/web/src/sessions.ts +1 -1
- package/src/web/src/ui-catalog.ts +4 -18
- package/src/web-assets.ts +47 -0
- package/src/weights.ts +55 -9
- package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
- package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
- package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
- package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
- package/docs/archive/investigations/hlg-ab.html +0 -34
- package/docs/archive/investigations/hlg-explorer.html +0 -238
- package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
- package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
- package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
- package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
- package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
- package/docs/archive/investigations/hlg-report.html +0 -262
- package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
- package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
- package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
- package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
- package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
- package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
- package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
- package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
- package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
- package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
- package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
- package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
- package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
- package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
- package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
- package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
- package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
- package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
- package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
- package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
- package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
- package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
- package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
- package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
- package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
- package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
- package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
- package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
- package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
- package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
- package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
- package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
- package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
- package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
- package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
- package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
- package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
- package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
- package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
- package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
- package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
- package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
- package/docs/archive/mlx-bun-lab-report.html +0 -275
- package/docs/archive/wwdc-mlx-bun.html +0 -848
- package/docs/design/adapters-end-to-end.md +0 -205
- package/docs/design/batching-perf-path.md +0 -255
- package/docs/design/batching-v2-plan.md +0 -529
- package/docs/design/bucketing-stage.md +0 -197
- package/docs/design/compat-cli-surface-design.md +0 -230
- package/docs/design/docs-reorg-plan.md +0 -165
- package/docs/design/dspark-serving-program.md +0 -371
- package/docs/design/dspark-speculative-decoding.md +0 -318
- package/docs/design/grammar-spec-batching-integration.md +0 -301
- package/docs/design/memory-inference-path.md +0 -422
- package/docs/design/memory-synthesis.md +0 -287
- package/docs/design/memory-system.md +0 -237
- package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
- package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
- package/docs/design/omlx-adoption-map.md +0 -98
- package/docs/design/orpo-dynamic-lambda.md +0 -220
- package/docs/design/orpo-future-enhancements.md +0 -340
- package/docs/design/paged-kv-cache.md +0 -214
- package/docs/design/parallel-slots.md +0 -567
- package/docs/design/parity-tier-dag.md +0 -161
- package/docs/design/repo-cleanup-plan.md +0 -231
- package/docs/design/runtime-isolation.md +0 -263
- package/docs/design/segmented-backward-training.md +0 -543
- package/docs/design/spec-decode-larger-targets.md +0 -229
- package/docs/design/ssd-kv-cold-tier.md +0 -415
- package/docs/design/structured-output.md +0 -484
- package/docs/design/tauri-desktop-app.md +0 -127
- package/docs/design/the-dreaming-master-plan.md +0 -445
- package/docs/design/turboquant-kv.md +0 -235
- package/docs/design/turboquant-weights.md +0 -467
- package/docs/design/w4a16-compute-precision-spike.md +0 -232
- package/docs/design/web-chat-beat-matrix.md +0 -315
- package/docs/design/write-pipeline-entity.md +0 -468
- package/docs/planning/IDEAS.md +0 -69
- package/docs/reference/embedding.md +0 -197
- package/docs/reference/features-matrix.md +0 -101
- package/docs/reference/orpo-quickstart.md +0 -135
- package/src/flags.ts +0 -11
- /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
- /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
- /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
- /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
- /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
- /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
- /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
- /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
|
@@ -1,529 +0,0 @@
|
|
|
1
|
-
# Batching v2 — state map, debt, and the gap plan
|
|
2
|
-
|
|
3
|
-
Status: **PLAN, largely EXECUTED** (written 2026-07-01 as a read-only
|
|
4
|
-
audit; §1's state map is that day's snapshot). Landed since: steps 1–3
|
|
5
|
-
(hotfix bundle, failure/fairness, engine hygiene — 2026-07-01); step 8
|
|
6
|
-
SSM batched path (2026-07-02, superseded by batching-perf-path.md P5 —
|
|
7
|
-
token-exact vs mlx-lm B=2); step 5 KV-budget admission (2026-07-03,
|
|
8
|
-
executed as grammar-spec-batching-integration.md Phase D:
|
|
9
|
-
`--kv-budget`, queue-don't-OOM, `/stats.batch` fields,
|
|
10
|
-
tests/batch-kv-budget.test.ts — the `--prompt-concurrency` half still
|
|
11
|
-
open); step 7's e1+e3 (min_p/XTC + penalties/logit_bias batch via
|
|
12
|
-
per-row logits processors, 2026-07-02, batching-perf-path.md header
|
|
13
|
-
item 2; e2 logprobs still routes serial). Still open: step 4 (extend
|
|
14
|
-
join), step 6 (prompt-cache reuse), 7's e2, 9 (rolling), 10 (deferred).
|
|
15
|
-
Scope: the `--batch N` continuous-batching engine (`src/serve/batch-scheduler.ts`,
|
|
16
|
-
`src/serve/generation-gateway.ts`, `src/model/batched-mask.ts`,
|
|
17
|
-
`src/model/batched-rotating.ts`, the `server.ts` wiring).
|
|
18
|
-
Predecessor docs: [parallel-slots.md](parallel-slots.md) (the design, still
|
|
19
|
-
accurate on the primitives), PLAN.md Phase 18 (history),
|
|
20
|
-
[server-config.md](../reference/server-config.md) (user-facing modes doc).
|
|
21
|
-
|
|
22
|
-
Mandate (Josh): batching is "kinda implemented, but is not great… make sure we
|
|
23
|
-
have all the code really well done, and documented and exposed via the flags."
|
|
24
|
-
|
|
25
|
-
**Standing decisions honored throughout (do not relitigate):**
|
|
26
|
-
- `--batch N` is a **mode switch**, not a load-dependent fallback. Josh decided
|
|
27
|
-
this explicitly (determinism: results must not depend on concurrency, and the
|
|
28
|
-
drop-in-for-`mlx_lm.server` promise needs a predictable path). Auto-batching
|
|
29
|
-
"when >1 request arrives" was considered and **rejected** — an idle-vs-loaded
|
|
30
|
-
server would produce different numerics for the same request. This plan keeps
|
|
31
|
-
`--batch` as the UX and documents the rejection.
|
|
32
|
-
- The batched lane is **compat mode** (bit-parity with mlx-lm B=N, bf16, no
|
|
33
|
-
perf flags) — see server-config.md "compat mode" section. All work below
|
|
34
|
-
preserves that guarantee; anything that can't be oracle-gated is L3 and
|
|
35
|
-
deferred (item f).
|
|
36
|
-
|
|
37
|
-
**In-flight work this plan routes around (2026-07-01):**
|
|
38
|
-
- **Same-ms seed collision fix** — `server.ts:1120` defaults
|
|
39
|
-
`seed: req.seed ?? (Date.now() & 0xffffffff)`; two batched rows admitted in
|
|
40
|
-
the same millisecond sample identical RNG streams. A fix is IN FLIGHT by
|
|
41
|
-
another agent. Noted as context; **not planned here**.
|
|
42
|
-
- **`src/model/gemma4-base.ts` is modified in another agent's working tree**
|
|
43
|
-
(and `src/model/universal/` is a new in-flight subsystem). Items below that
|
|
44
|
-
would naturally touch cache classes are designed to live in NEW files
|
|
45
|
-
(`BatchedKVCache` beside `BatchedRotatingCache`) precisely so they don't
|
|
46
|
-
collide; coordinate before touching gemma4-base.ts.
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## 1. State map (verified against code, not docs)
|
|
51
|
-
|
|
52
|
-
### 1.1 Architecture as built
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
handler (server.ts) GenerationGateway (generation-gateway.ts)
|
|
56
|
-
promptIdsFor → options → RequestShape ──▶ willBatch(shape)?
|
|
57
|
-
onToken closure (StopMatcher + ├─ no → AsyncMutex → serialRun
|
|
58
|
-
tool router + thinking splitter + │ (= runGeneration: PromptCache
|
|
59
|
-
SSE send / collector) │ take/put + generate())
|
|
60
|
-
└─ yes → BatchScheduler.submit
|
|
61
|
-
(batch-scheduler.ts)
|
|
62
|
-
BatchScheduler #drive loop (detached, Bun-async, no threads):
|
|
63
|
-
acquire mutex (held first-admit → batch-empty)
|
|
64
|
-
├─ #admit each pending row: SOLO prefill (one forward, whole prompt)
|
|
65
|
-
│ → sample token 0 → re-MERGE whole batch per layer
|
|
66
|
-
│ (full layers: temporalView + per-row slice + mergeKVRows → new KVCache;
|
|
67
|
-
│ rot layers: BatchedRotatingCache.merge)
|
|
68
|
-
├─ #step: forward [B,1] (full layers wrapped per-step in
|
|
69
|
-
│ BatchedDecodeMaskCache; rot caches persistent), per-row sample on
|
|
70
|
-
│ [1,V] slices, SYNC readback [B], clearCache()
|
|
71
|
-
├─ #emit per row (EOS → "stop" w/o onToken; onToken false → "stop";
|
|
72
|
-
│ generated ≥ maxTokens → "length")
|
|
73
|
-
└─ #applyFilter(keep): full layers filterKVRows → new KVCache;
|
|
74
|
-
rot layers filter() in place
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
- **Scheduler loop shape**: iteration-level (continuous) scheduling — admit
|
|
78
|
-
between steps, evict after each step. Correct design; the loop-body
|
|
79
|
-
*mechanics* carry the debt (§2).
|
|
80
|
-
- **Admission today**: count-only — `#running.length < #maxBatch`
|
|
81
|
-
(batch-scheduler.ts:138). No byte projection, no KV budget. The server's
|
|
82
|
-
per-request `context_over_budget` 400 (server.ts:~1780, from `fit()`) checks
|
|
83
|
-
each request against **single-sequence** max-safe-context; N rows can jointly
|
|
84
|
-
exceed the budget (documented as limitation #2 in server-config.md).
|
|
85
|
-
- **Eviction**: per-step `#applyFilter` (batch-scheduler.ts:327-355) —
|
|
86
|
-
full-attention layers rebuild a fresh `KVCache` from `filterKVRows`
|
|
87
|
-
(takeAxis on B); rotating caches filter in place including padding
|
|
88
|
-
reduction. Matches mlx-lm `filter` semantics (cache.py:1016-1033) except
|
|
89
|
-
ours doesn't do the `min_left_pad` shift-left for full layers (mlx-lm
|
|
90
|
-
reduces padding after filter; ours keeps stale left pad until the next
|
|
91
|
-
merge — correct but wasteful width).
|
|
92
|
-
- **Join**: `#admit` re-merges the ENTIRE batch (batch-scheduler.ts:193-254):
|
|
93
|
-
per layer, temporalView of the running batch, B per-row slices, re-concat
|
|
94
|
-
with the new row. O(B·S) traffic per join **plus** ~2B slice kernels per
|
|
95
|
-
layer. mlx-lm instead `extend`s (cache.py:1035-1078): pad self+other once,
|
|
96
|
-
one concat — and prefills joiners as a right-padded *batch* (see item a/d).
|
|
97
|
-
|
|
98
|
-
### 1.2 The `willBatch` gate — exact predicate today
|
|
99
|
-
|
|
100
|
-
`generation-gateway.ts:113-127`: batches iff **all** of
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
batch > 1
|
|
104
|
-
AND model is not DiffusionGemmaModel (non-AR)
|
|
105
|
-
AND !hasVision (offset-0 prefill + bidir mask)
|
|
106
|
-
AND !hasAdapters (loraState is one global field)
|
|
107
|
-
AND !hasRepetitionPenalty (no per-row logits processors)
|
|
108
|
-
AND !hasLogitsExtras (min_p ∥ xtc_probability ∥ logit_bias
|
|
109
|
-
∥ presence ∥ frequency — one family gate)
|
|
110
|
-
AND !wantsLogprobs (non-stream logprobs/top_logprobs capture)
|
|
111
|
-
AND !userSeed (body.seed !== undefined)
|
|
112
|
-
AND !kvQuant (request carries kvConfig/kvBits —
|
|
113
|
-
i.e. any explicit --kv-quant)
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
`RequestShape` is built **twice**, near-identically: chat (server.ts:1813-1826)
|
|
117
|
-
and completions (server.ts:2085-2096, `hasVision: false`). mlx-lm's gate for
|
|
118
|
-
comparison (server.py:371-374, 685-686): `draft_model is None AND all caches
|
|
119
|
-
hasattr 'merge' AND args.seed is None`. **We have no cache-capability check**
|
|
120
|
-
— see debt D1.
|
|
121
|
-
|
|
122
|
-
### 1.3 Which caches support which dynamic-B ops
|
|
123
|
-
|
|
124
|
-
| Cache | merge | filter | extend | extract | batched mask | verified vs |
|
|
125
|
-
| --- | --- | --- | --- | --- | --- | --- |
|
|
126
|
-
| `KVCache` (full attn) | ✅ `mergeKVRows` (batched-mask.ts:171) | ✅ `filterKVRows` (:196) | ❌ (join = re-merge) | ✅ implicit (per-row slice in `#admit`) | ✅ `BatchedDecodeMaskCache` wrapper | mlx-lm `BatchKVCache` dynamic golden (gated) |
|
|
127
|
-
| `RotatingKVCache` (sliding) | ✅ `BatchedRotatingCache.merge` (batched-rotating.ts:273) | ✅ `.filter` (:233) | ❌ | ✅ `.temporalView` (:207) | ✅ built-in `makeMask` incl. ring-roll | mlx-lm `BatchRotatingKVCache` model-free golden (FAST, incl. ring-wrap) |
|
|
128
|
-
| `SSMCache` (Qwen3.5 gated-DeltaNet, qwen3-delta.ts:184) | ❌ | ❌ | ❌ | ❌ (no `temporalView`) | ❌ (`makeMask` returns empty; comment says "B=1 single-stream") | — |
|
|
129
|
-
| `QuantizedKVCache` / rotating-quant | n/a — excluded by the `kvQuant` gate (bf16-only v1) | | | | | no oracle exists (L2/L3, item f) |
|
|
130
|
-
|
|
131
|
-
**⇒ D1 (bug): a Qwen3.5 server with `--batch N` errors every batched
|
|
132
|
-
request.** The scheduler's constructor classifies `SSMCache` as `"full"`
|
|
133
|
-
(batch-scheduler.ts:105, `instanceof RotatingKVCache ? "rot" : "full"`), then
|
|
134
|
-
`#admit` casts it to `KVCache` and calls `temporalView()`
|
|
135
|
-
(batch-scheduler.ts:200-201) → TypeError; `#drive` catches and rejects the row,
|
|
136
|
-
so **every** batched request 500s. `willBatch` has no family/capability gate
|
|
137
|
-
(only DiffusionGemma is excluded). And **server-config.md:151-153 claims the
|
|
138
|
-
opposite** ("full-attention (CPM), sliding-window (Gemma), and hybrid
|
|
139
|
-
gated-DeltaNet (Qwen3.5) — all batch"). The doc claim is false today. Note an
|
|
140
|
-
oracle for the real fix exists: mlx-lm's `ArraysCache` implements
|
|
141
|
-
`filter/extend/extract/finalize/merge` (cache.py:632-702), which is how mlx-lm
|
|
142
|
-
batches Mamba-family state.
|
|
143
|
-
|
|
144
|
-
### 1.4 Test coverage matrix
|
|
145
|
-
|
|
146
|
-
| Behavior | Test | Tier | Gate quality |
|
|
147
|
-
| --- | --- | --- | --- |
|
|
148
|
-
| decode mask math (`buildBatchedDecodeMask`), `mergeKVRows`/`filterKVRows` pure | `tests/batched-decode-mask.test.ts` | FAST (no model) | unit, exact |
|
|
149
|
-
| rotating merge/decode/mask/filter incl. **ring-wrap** | `tests/batched-rotating.test.ts` | FAST (fixture) | **oracle-gated** — replays `scripts/gen-rotating-golden.py` vs mlx-lm `BatchRotatingKVCache`, per-step mask + keys |
|
|
150
|
-
| batched prefill+decode forward, 4 models | `tests/batched-decode-parity.test.ts` | GATED `MLX_BUN_TEST_BATCH_DECODE=1` + weights | **oracle-gated** — bit-exact vs mlx-lm B=2 goldens (`scripts/gen-batched-golden.py`) |
|
|
151
|
-
| dynamic-B join/leave ({A,B}→+C→−A) | same file, CPM | GATED | **oracle-gated** — token-for-token vs `scripts/gen-batched-dynamic-golden.py` (mlx-lm merge/extract/filter) |
|
|
152
|
-
| scheduler orchestration (admit/step/evict/join) | `tests/batch-scheduler.test.ts` | GATED | **KL-gated** teacher-forced (tol 1e-2) CPM; Gemma 12B greedy vs mlx-lm B=2 golden |
|
|
153
|
-
| server wiring: Option-B engage, `/stats`, concurrent completion, SSE fan-out, serial-lane coexist (user-seed drain, no deadlock) | `tests/batch-serving.test.ts` | GATED (live in-process server) | **smoke** — coherence assertions, no numeric oracle |
|
|
154
|
-
| prefill batched mask (training side) | `tests/train-batch{,-e2e}.test.ts` | FAST / gated | oracle (per-row loss == solo) |
|
|
155
|
-
|
|
156
|
-
**Not tested anywhere:** row-failure containment (one row's error vs the
|
|
157
|
-
batch), serial-lane starvation/fairness under sustained batchable load,
|
|
158
|
-
KV admission (doesn't exist), batched Qwen3.5 (would fail, §1.3),
|
|
159
|
-
ring-wrap **end-to-end** on a real Gemma forward (model-free math is gated;
|
|
160
|
-
the long-context forward golden is the known open L1 follow-up),
|
|
161
|
-
batched-lane usage-timing stats (currently hardwired zeros).
|
|
162
|
-
|
|
163
|
-
### 1.5 What the flags/docs currently say (accurate except one claim)
|
|
164
|
-
|
|
165
|
-
`--batch <n>` (cli.ts:750-757; `--decode-concurrency` accepted as alias),
|
|
166
|
-
`--kv-quant` interplay (Option B: unset+batch ⇒ bf16; explicit ⇒ those requests
|
|
167
|
-
serial, warned at server.ts:1065-1070), `/stats.batch`
|
|
168
|
-
{configured, batched, active_rows} (server.ts:1437-1441). server-config.md's
|
|
169
|
-
"Execution modes", lane-picker table, compatibility matrix, compat-mode
|
|
170
|
-
rationale, and known-limitations list are **verified accurate against code**
|
|
171
|
-
— except the Qwen3.5 batching claim (D1) and it doesn't mention the curve/
|
|
172
|
-
signal endpoints' lock bypass (D3, which is a bug to fix, not to document).
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## 2. Code-quality assessment
|
|
177
|
-
|
|
178
|
-
**Verdict:** the *primitives* are excellent — oracle-gated, well-commented,
|
|
179
|
-
disposal-disciplined (`batched-mask.ts`, `batched-rotating.ts` are among the
|
|
180
|
-
cleanest files in the repo). The *orchestration* (`batch-scheduler.ts`,
|
|
181
|
-
~360 lines, readable) is correct-by-gate but **naive as an engine**: it has
|
|
182
|
-
none of the serial loop's hard-won loop hygiene, its failure domain is the
|
|
183
|
-
whole batch, and the server grew a second mutual-exclusion domain that two
|
|
184
|
-
endpoints silently don't participate in. Specific debt, ranked:
|
|
185
|
-
|
|
186
|
-
- **D1 — Qwen3.5 batched = guaranteed request failure + false doc claim.**
|
|
187
|
-
batch-scheduler.ts:105 + :200-201 vs qwen3-delta.ts:184 (`SSMCache`, no
|
|
188
|
-
`temporalView`); server-config.md:151-153. Fix now: a cache-capability gate
|
|
189
|
-
(mirror mlx-lm server.py:371-374) in `willBatch` — batchable iff every
|
|
190
|
-
`makeCache()` element is `KVCache | RotatingKVCache` — plus the doc
|
|
191
|
-
correction. The real SSM batch path is item (h), later. *(confidence 95)*
|
|
192
|
-
**RESOLVED**: gate landed 2026-07-01; the full SSM batch path landed
|
|
193
|
-
2026-07-02 (batching-perf-path.md P5) — server-config's Qwen3.5 claim is
|
|
194
|
-
true again.
|
|
195
|
-
|
|
196
|
-
- **D2 — `#admit` materializes full-prompt logits.**
|
|
197
|
-
batch-scheduler.ts:173-174 runs `logitsFromHidden(h)` on the **whole**
|
|
198
|
-
`[1,Lp,H]` hidden, then slices the last position. The lm_head matmul
|
|
199
|
-
computes `[1,Lp,V]` at eval — at Gemma's V=262k and an 8k prompt that is a
|
|
200
|
-
~4.3 GB bf16 transient *per admit* (plus the wasted GEMM). The serial path
|
|
201
|
-
slices `hLast` FIRST (generate.ts:466-468) precisely to avoid this. One-line
|
|
202
|
-
reorder + the gated suites re-run. *(confidence 95)*
|
|
203
|
-
|
|
204
|
-
- **D3 — two mutual-exclusion domains; curve `/generate` and `/signal` bypass
|
|
205
|
-
the gateway.** The old serialized queue (`enqueue`, server.ts:946-951) used
|
|
206
|
-
to be THE GPU lock; generation moved to the gateway's `AsyncMutex`, but
|
|
207
|
-
`/generate` (curve designer, server.ts:1663 — `enqueue(() =>
|
|
208
|
-
runGeneration(...))`) and `/signal` (server.ts:1608 — `enqueue` + raw
|
|
209
|
-
`model.forward`) still run GPU work (and `PromptCache.take/put`) under
|
|
210
|
-
`enqueue` only ⇒ they can run **concurrently with batched decode steps**
|
|
211
|
-
(GPU contention + the prompt-cache "single owner" invariant from
|
|
212
|
-
parallel-slots.md broken). Adapter mount/unmount (server.ts:1578, 1589) sit
|
|
213
|
-
on `enqueue` too — mount is registry-level, but unmount disposing adapter
|
|
214
|
-
arrays concurrently with a serial-lane generation using them is the same
|
|
215
|
-
class of race. The `runGeneration` doc comment "Must be called inside the
|
|
216
|
-
queue" (server.ts:960) is stale. Fix: one lock — expose
|
|
217
|
-
`gateway.runExclusive(fn)` and route curve/signal/adapter-unmount through
|
|
218
|
-
it; fix the comment. *(confidence 90 on curve//signal; 80 on unmount)*
|
|
219
|
-
|
|
220
|
-
- **D4 — the batched loop lacks the serial loop's pipelining + clearCache
|
|
221
|
-
cadence** (= reports/kernel-perf-review-2026-07-01.json backlog #6, area 7
|
|
222
|
-
verdict). Three sub-items, all in `#step`/`#admit`:
|
|
223
|
-
1. **No `asyncEval` pipelining**: `#step` builds one step's graph then
|
|
224
|
-
synchronously reads all B tokens (`toFloat32`, batch-scheduler.ts:286)
|
|
225
|
-
— every step eats a full dispatch+readback bubble. Serial overlaps step
|
|
226
|
-
n's readback with step n+1's compute (generate.ts:482, 539); mlx-lm's
|
|
227
|
-
`GenerationBatch._step` does exactly this batched (`mx.async_eval`
|
|
228
|
-
next, `mx.eval` current — generate.py:1367-1373).
|
|
229
|
-
2. **`clearCache()` EVERY step** (batch-scheduler.ts:293) — trashes the
|
|
230
|
-
buffer pool each token, so every step re-allocates its transients.
|
|
231
|
-
Serial clears every 256 tokens (generate.ts:571); mlx-lm batched every
|
|
232
|
-
512 steps (generate.py:1778-1779).
|
|
233
|
-
3. **No prefill chunking in `#admit`** — the whole prompt goes through one
|
|
234
|
-
forward (batch-scheduler.ts:172), so a long prompt is an unbounded
|
|
235
|
-
transient (serial chunks at `prefillChunkSize` with per-chunk eval +
|
|
236
|
-
clearCache, generate.ts:440-456; mlx-lm at `prefill_step_size=2048`).
|
|
237
|
-
Worse, admits run to completion before the next `#step`, so **every
|
|
238
|
-
running row stalls for the entire prefill of every joiner**
|
|
239
|
-
(head-of-line blocking; mlx-lm interleaves one decode step with one
|
|
240
|
-
prefill chunk per loop iteration — generate.py:1760-1844).
|
|
241
|
-
*(confidence 90; perf claims need bench numbers per house rules)*
|
|
242
|
-
|
|
243
|
-
- **D5 — whole-batch failure domain.** Any throw in `#step` — including a
|
|
244
|
-
row's own `onToken` (awaited inline, batch-scheduler.ts:299; an SSE
|
|
245
|
-
controller error qualifies) — is caught in `#drive` (:153-158) and
|
|
246
|
-
**rejects every row and drops the entire batch KV**. mlx-lm removes the
|
|
247
|
-
offending uid and keeps the batch (server.py:913-919). Also: `#admit` has
|
|
248
|
-
no try/finally around the prefill, so `solo` caches + `ids` leak if the
|
|
249
|
-
forward throws (:170-173); onToken should be wrapped per-row so one
|
|
250
|
-
consumer's exception evicts one row. *(confidence 90)*
|
|
251
|
-
|
|
252
|
-
- **D6 — serial-lane starvation under sustained batchable load.** The
|
|
253
|
-
scheduler holds the mutex while `running ∪ pending ≠ ∅` and **keeps
|
|
254
|
-
admitting** new batchable arrivals (batch-scheduler.ts:135-145); a queued
|
|
255
|
-
serial-lane request (vision, adapter, seed…) waits for the batch to empty,
|
|
256
|
-
which under continuous traffic is *never*. mlx-lm's drain sets
|
|
257
|
-
`drain_batch=True` and **stops admitting** until the incompatible request
|
|
258
|
-
has run (server.py:826-830). Fix: gateway signals "serial waiter present" →
|
|
259
|
-
scheduler pauses admission (finish running rows, release, FIFO mutex hands
|
|
260
|
-
over, then resume). Single-user appliance softens this, but agent fan-out
|
|
261
|
-
is exactly the batching use case, so it's real. *(confidence 85)*
|
|
262
|
-
|
|
263
|
-
- **D7 — zeroed stats + duplicated request-shape/sampling logic.**
|
|
264
|
-
- Batched stats hardwire `prefillMs/decodeMs/…Tps = 0`
|
|
265
|
-
(generation-gateway.ts:170) → SSE `usage` timing and any logging report
|
|
266
|
-
zeros for batched requests; `cachedTokens` 0 is documented, the timings
|
|
267
|
-
are just missing accounting.
|
|
268
|
-
- `RequestShape` construction duplicated chat vs completions
|
|
269
|
-
(server.ts:1813 vs 2085) — extract one `shapeFor()` helper.
|
|
270
|
-
- `maxTokens` fallback drift: gateway `?? 512` (generation-gateway.ts:160),
|
|
271
|
-
chat finish `?? 1024` (server.ts:1904), completions finish `?? 512`
|
|
272
|
-
(server.ts:2098) — all dead in practice (server defaults 65_536 at
|
|
273
|
-
:1116) but a trap for library callers; unify.
|
|
274
|
-
- The `#emit` EOS/length/onToken contract mirrors `generate()` by comment
|
|
275
|
-
only (batch-scheduler.ts:306-316) — drift risk; a shared contract test
|
|
276
|
-
(same fixture through both lanes) would pin it.
|
|
277
|
-
- Full-layer `filter` keeps stale left-padding (no `min_left_pad`
|
|
278
|
-
shift-left, cf. cache.py:1027-1033) — width waste, not correctness.
|
|
279
|
-
|
|
280
|
-
None of this contradicts the gates that exist: the numerics ARE oracle-proven.
|
|
281
|
-
The debt is engine mechanics, failure domains, and the second lock.
|
|
282
|
-
|
|
283
|
-
---
|
|
284
|
-
|
|
285
|
-
## 3. Gap plan (implementation-ready, per item)
|
|
286
|
-
|
|
287
|
-
### (a) `extend` join op — stop re-merging the running batch
|
|
288
|
-
|
|
289
|
-
**Port target:** mlx-lm `BatchKVCache.extend` (cache.py:1035-1078) and
|
|
290
|
-
`BatchRotatingKVCache.extend` (cache.py:1369-1416). Semantics: right-justify
|
|
291
|
-
both caches to a common `_idx`/width (one `mx.pad` each), concat on B, sum
|
|
292
|
-
left-padding adjustments. One pad+concat per layer per join, vs today's
|
|
293
|
-
per-row slice storm + full rebuild.
|
|
294
|
-
|
|
295
|
-
**Design:** introduce **`BatchedKVCache`** (new file or in batched-mask.ts) —
|
|
296
|
-
the full-attention twin of `BatchedRotatingCache`: owns
|
|
297
|
-
`keys/values/leftPad/offset`, methods `merge` (subsumes `mergeKVRows`),
|
|
298
|
-
`filter` (subsumes `filterKVRows` + adds the min-left-pad shift),
|
|
299
|
-
**`extend`**, `extract(row)` (the per-row slice `#admit` currently inlines),
|
|
300
|
-
`makeMask` + `ropeOffsetArr` (subsumes the per-step `BatchedDecodeMaskCache`
|
|
301
|
-
wrapper rebuild and the scheduler's shared `#fullLeftPad` field —
|
|
302
|
-
batch-scheduler.ts:93, a state smell). `BatchedRotatingCache.extend` follows
|
|
303
|
-
the same pad-to-common-idx recipe in temporal order (both sides are kept
|
|
304
|
-
temporal by our merge, so no dynamic-roll needed for the N=1-update scope —
|
|
305
|
-
verify against the golden, don't assume). Scheduler `#admit` becomes:
|
|
306
|
-
solo-prefill → `inners[layer].extend(soloRow)` — no re-merge, no wrapper churn.
|
|
307
|
-
**Does not touch gemma4-base.ts** (in-flight ownership).
|
|
308
|
-
|
|
309
|
-
**Oracle:** extend `scripts/gen-batched-dynamic-golden.py` to drive the join
|
|
310
|
-
through mlx-lm's real `extend` (it already drives merge/extract/filter);
|
|
311
|
-
assert the same {A,B}→+C→−A per-row trajectories token-for-token, CPM L1 +
|
|
312
|
-
a Gemma variant for the rotating extend. Existing gated suites must stay
|
|
313
|
-
green unchanged (numerically `extend ≡ re-merge`; the golden proves it).
|
|
314
|
-
|
|
315
|
-
**Effort: M.**
|
|
316
|
-
|
|
317
|
-
### (b) Prompt-cache reuse under batching
|
|
318
|
-
|
|
319
|
-
**mlx-lm behavior (the model to follow):** on insert,
|
|
320
|
-
`prompt_cache.fetch_nearest_cache` → pass the pre-warmed per-sequence cache
|
|
321
|
-
into the batch (server.py:751-767); on row finish, extract the row's cache and
|
|
322
|
-
`insert_cache` it back keyed by all tokens (server.py:900-907).
|
|
323
|
-
|
|
324
|
-
**Design (parallel-slots.md's sole-owner rule, kept):** all `PromptCache`
|
|
325
|
-
take/put happen **inside the scheduler loop** (plus the serial lane, which
|
|
326
|
-
already does — the two are mutex-exclusive, and D3's fix closes the remaining
|
|
327
|
-
holes).
|
|
328
|
-
- `#admit`: `promptCache.take(promptIds, "")` (batched lane is always
|
|
329
|
-
adapter-less, so namespace ""); on hit, the entry's caches ARE the solo
|
|
330
|
-
caches — forward only the uncached tail (the trim/`cachedTokens` logic
|
|
331
|
-
exists in generate.ts; lift the prefix-trim helper rather than reimplement —
|
|
332
|
-
memory: eval-reuse-serving-prompt-path). `BatchStats.cachedTokens` becomes
|
|
333
|
-
real.
|
|
334
|
-
- Row finish (`#finish`) or eviction: `extract(row)` (from item a) →
|
|
335
|
-
`restoreState` into fresh solo `KVCache`/`RotatingKVCache`s →
|
|
336
|
-
`promptCache.put(rowTokens, caches, "")`. Cost: O(S_row) copy per finished
|
|
337
|
-
row, off the step hot path (do it after `#applyFilter`).
|
|
338
|
-
- Byte interplay with (c): mirror mlx-lm's
|
|
339
|
-
`prompt_cache.trim_to(total − activeBatchBytes)` (server.py:795-799) so the
|
|
340
|
-
LRU + live batch share one ceiling.
|
|
341
|
-
|
|
342
|
-
**Gate:** batch-serving test asserting `cached_tokens > 0` on an identical
|
|
343
|
-
repeat under `--batch 2`; parity assertion that a warm-start row's trajectory
|
|
344
|
-
equals the cold row's (same-B, bit-exact — prefix KV is identical by
|
|
345
|
-
construction); serial suite untouched.
|
|
346
|
-
|
|
347
|
-
**Effort: M–L** (the prefix-trim + rotating-cache restore corners are where
|
|
348
|
-
the time goes; RotatingKVCache entries past the window are non-trimmable —
|
|
349
|
-
mirror the serial path's existing handling).
|
|
350
|
-
|
|
351
|
-
### (c) KV-budget admission (B×S_max) + `--prompt-concurrency`
|
|
352
|
-
|
|
353
|
-
**Fact-check first:** mlx-lm does NOT byte-budget admission — it caps counts
|
|
354
|
-
(`--decode-concurrency` 32, `--prompt-concurrency` 8), optionally swaps KVCache
|
|
355
|
-
→ RotatingKVCache via `max_kv_size`, and byte-trims only the *prompt cache*
|
|
356
|
-
against `--prompt-cache-bytes` minus live batch bytes. So byte-budget admission
|
|
357
|
-
is OUR addition (rung 2 of parallel-slots.md), consistent with the byte-capped
|
|
358
|
-
`PromptCache` precedent and the existing `fit()` admission (server.ts:1012).
|
|
359
|
-
|
|
360
|
-
**Design:**
|
|
361
|
-
- Projected row bytes = Σ_layers 2·bytes_bf16(H_kv·D·S_cap) where
|
|
362
|
-
S_cap = min(promptLen+maxTokens, window) for rotating layers and
|
|
363
|
-
promptLen+maxTokens for full layers (a `projectKvBytes(model, promptLen,
|
|
364
|
-
maxTokens)` helper next to `fit()`, which already knows per-layer shapes).
|
|
365
|
-
- Budget = explicit `--kv-budget <GB>` else derived: `fit()`'s usable bytes −
|
|
366
|
-
weights − prompt-cache cap − headroom. Admission: admit while
|
|
367
|
-
Σ projected(running+candidate) ≤ budget; otherwise leave in `#pending`
|
|
368
|
-
(FIFO — no reordering, keeps fairness and determinism of arrival order).
|
|
369
|
-
`maxTokens` default 65_536 makes naive projection admit ~1 row — so
|
|
370
|
-
projection uses a decode-allowance term (e.g. min(maxTokens, 4096) beyond
|
|
371
|
-
prompt) with the true backstop being re-check-on-growth: on each STEP-chunk
|
|
372
|
-
cache growth, re-evaluate; if over budget, stop admitting (never evict a
|
|
373
|
-
running row). Document the estimate honestly.
|
|
374
|
-
- **`--prompt-concurrency`: accept for drop-in** (we already accept
|
|
375
|
-
`--decode-concurrency`), mapped to "max joiners admitted per loop
|
|
376
|
-
iteration" — which becomes meaningful exactly when (d)'s interleaved
|
|
377
|
-
chunked admit lands. Until then accept + warn like `--decode-concurrency`'s
|
|
378
|
-
semantic note in cli.ts:744-748. `/stats.batch` grows
|
|
379
|
-
`{pending_rows, kv_bytes, kv_budget_bytes}`.
|
|
380
|
-
|
|
381
|
-
**Gate:** unit tests on the projection math (pure); a gated live test that
|
|
382
|
-
M oversized requests are admitted ≤ budget-implied width and all complete
|
|
383
|
-
(queueing, not OOM). Perf numbers only via bench-serving-load on a clean
|
|
384
|
-
machine (Josh-gated).
|
|
385
|
-
|
|
386
|
-
**Effort: M.**
|
|
387
|
-
|
|
388
|
-
### (d) Pipelining + clearCache cadence + chunked/interleaved admit
|
|
389
|
-
(the backlog-#6 transplant — pairs with fixing D2, D4)
|
|
390
|
-
|
|
391
|
-
1. **Slice-before-head in `#admit`** (D2): `hLast = h.slice(last)` →
|
|
392
|
-
`logitsFromHidden(hLast)`. One-line; do first.
|
|
393
|
-
2. **Chunked prefill in `#admit`**: loop `prefillChunkSize` (reuse the serial
|
|
394
|
-
constant) with `evalAll(cache.state)` + `clearCache()` per chunk
|
|
395
|
-
(generate.ts:440-456 pattern).
|
|
396
|
-
3. **Interleave admit with steps**: `#drive` admits at most one prefill
|
|
397
|
-
*chunk* (per joiner, up to `--prompt-concurrency` joiners) between decode
|
|
398
|
-
steps instead of running each admit to completion — bounds running-row
|
|
399
|
-
stall to one chunk. (This restructures `#admit` into a resumable
|
|
400
|
-
per-joiner state; mlx-lm's `_currently_processing` is the reference,
|
|
401
|
-
generate.py:1760-1844.)
|
|
402
|
-
4. **Pipelined decode**: mirror mlx-lm's `GenerationBatch._step`
|
|
403
|
-
(generate.py:1320-1373): keep step n's sampled `[B]` token array on
|
|
404
|
-
device, `asyncEvalAll` it, build step n+1's graph from the **unread**
|
|
405
|
-
array, then read step n. Eviction moves after the read: rows evicted at
|
|
406
|
-
step n have already contributed a step-n+1 KV write — harmless, `filter`
|
|
407
|
-
drops the row (mlx-lm does exactly this). EOS rows' extra write is also
|
|
408
|
-
how mlx-lm behaves, so parity holds by construction.
|
|
409
|
-
5. **clearCache cadence**: every 256 steps (serial's constant; mlx-lm uses
|
|
410
|
-
512 — either is defensible, pick 256 for house consistency and comment
|
|
411
|
-
the divergence).
|
|
412
|
-
|
|
413
|
-
**Gate:** numerics unchanged ⇒ the teacher-forced KL suite and the mlx-lm
|
|
414
|
-
goldens must pass **unchanged** (pipelining is scheduling, not math);
|
|
415
|
-
batch-serving smoke; A/B throughput via `scripts/bench-serving-load.ts`
|
|
416
|
-
(quotable numbers clean-machine only).
|
|
417
|
-
|
|
418
|
-
**Effort: M** (item 3 is the bulk; 1/2/5 are S alone).
|
|
419
|
-
|
|
420
|
-
### (e) Gate widening — which serial-only features can batch safely
|
|
421
|
-
|
|
422
|
-
Ranked by value/risk. Each needs per-row state listed; each lands with its own
|
|
423
|
-
oracle before the `willBatch`/`hasLogitsExtras` split changes.
|
|
424
|
-
|
|
425
|
-
1. **min_p + XTC — S, low risk, do first.** Pure per-row *samplers*; the
|
|
426
|
-
batched lane already builds a per-request sampler closure
|
|
427
|
-
(generation-gateway.ts:150-156 → `makeSampler(options)`), which already
|
|
428
|
-
implements min_p/XTC for serial. Per-row state: none beyond the closure
|
|
429
|
-
(XTC needs `xtcSpecialTokens`, already in options). Change: move
|
|
430
|
-
`minP`/`xtc*` out of `hasLogitsExtras` into the batchable set. Gate:
|
|
431
|
-
teacher-forced determinism (same seed ⇒ same tokens, batched == serial,
|
|
432
|
-
per row — sampling happens on the `[1,V]` row slice so it's the SAME code
|
|
433
|
-
path); note the seed-collision fix must land first for two same-ms hot
|
|
434
|
-
rows to be meaningfully independent (in flight).
|
|
435
|
-
2. **logprobs / top_logprobs — M, low-medium risk.** Non-stream only
|
|
436
|
-
(server.ts:1797-1805 keeps streams capture-free). The scheduler's sampler
|
|
437
|
-
already computes `toLogprobs` per row; extend `RowSampler` to optionally
|
|
438
|
-
return extras (the serial `sampleStep` extras shape, generate.ts) and have
|
|
439
|
-
`#emit` pass them to `onToken` — the handler's `LogprobsCollector` then
|
|
440
|
-
works unchanged. Per-row state: none (readback per emitted token, rows
|
|
441
|
-
that asked for it only). mlx-lm computes logprobs for every batched row
|
|
442
|
-
natively, so behavior-parity is clean.
|
|
443
|
-
3. **penalties (repetition/presence/frequency) + logit_bias — M, medium
|
|
444
|
-
risk.** Needs per-row token history + per-row logits processors applied to
|
|
445
|
-
the `[1,V]` slice before sampling — exactly mlx-lm's per-row
|
|
446
|
-
`logits_processors` loop (generate.py:1338-1349, TokenBuffer). We have the
|
|
447
|
-
processors (sampler.ts, serial); per-row state: a bounded history array
|
|
448
|
-
(context sizes 20 by default) per row, filtered on eviction. logit_bias is
|
|
449
|
-
stateless (easiest of the family) — could ship with tier 1 if the family
|
|
450
|
-
gate is split per-feature. Gate: teacher-forced vs serial with identical
|
|
451
|
-
forced trajectories (processors are deterministic given history).
|
|
452
|
-
4. **LoRA same-adapter-set batching — L, defer.** Design already decided
|
|
453
|
-
(group-by-adapter + drain, parallel-slots.md Fallbacks); needs adapter-set
|
|
454
|
-
as a batch-compatibility KEY in the scheduler, not just a boolean gate.
|
|
455
|
-
Not before (a)–(d).
|
|
456
|
-
|
|
457
|
-
Never batch: vision (offset-0 bidirectional prefill), user seed
|
|
458
|
-
(reproducibility ⇒ solo — matches mlx-lm), explicit kv-quant (item f),
|
|
459
|
-
DiffusionGemma (non-AR).
|
|
460
|
-
|
|
461
|
-
### (f) Quantized-KV batched — L3 novel extension (design sketch ONLY, deferred)
|
|
462
|
-
|
|
463
|
-
No ancestor does it (mlx-lm's `to_quantized` raises on batch caches; optiq
|
|
464
|
-
doesn't batch) ⇒ no bit-exact oracle, per the three-level fidelity tree this
|
|
465
|
-
is KL + 6-task + benchmark gated. Sketch: `BatchedQuantizedKVCache` mirroring
|
|
466
|
-
(a)'s `BatchedKVCache` over (packed, scales, biases) triples — merge/filter/
|
|
467
|
-
extend are the same B-axis surgery per component; attention falls to
|
|
468
|
-
`quantizedSdpaUnfused` with the `[B,1,N,S]` bool mask (the fused decode kernel
|
|
469
|
-
requires `mask.mode===""` and the tiled path 2-D causal — both disqualified,
|
|
470
|
-
known perf debt from the modes analysis). Value: memory-density (4-bit KV ×
|
|
471
|
-
batching compound, ~4× rows per GB) on the 24 GB machine. **Do not start**
|
|
472
|
-
until (a)–(d) are landed and a concrete density need exists; then it begins
|
|
473
|
-
with a KL harness (batched-quant vs batched-bf16 at same B), never a
|
|
474
|
-
trajectory diff.
|
|
475
|
-
|
|
476
|
-
### (g) Docs + flags
|
|
477
|
-
|
|
478
|
-
- **server-config.md**: fix the Qwen3.5 batching claim (D1) — with the
|
|
479
|
-
capability gate landed the honest row is "hybrid-cache models (Qwen3.5)
|
|
480
|
-
route to the serial lane; batched SSM is a later port (mlx-lm ArraysCache
|
|
481
|
-
is the oracle)". Update the known-limitations list as (a)–(e) land (each
|
|
482
|
-
item literally deletes a numbered limitation: #1 prompt cache, #2
|
|
483
|
-
admission, #5 extend). Add `--kv-budget`, `--prompt-concurrency`,
|
|
484
|
-
`/stats.batch.{pending_rows,kv_bytes}` when they exist. Add one line
|
|
485
|
-
documenting the auto-batch rejection ("mode, not load-fallback —
|
|
486
|
-
determinism", the standing decision).
|
|
487
|
-
- **parallel-slots.md**: refresh the STATUS block (S2 admission half, extend,
|
|
488
|
-
prompt-cache reuse) to point HERE for the v2 execution plan; it remains the
|
|
489
|
-
primitive-design record.
|
|
490
|
-
- **`--batch` stays the UX.** No smarter default. `--decode-concurrency`
|
|
491
|
-
alias kept; `--prompt-concurrency` accepted per (c). No new mode flags.
|
|
492
|
-
- **STATUS.md / PLAN.md Phase 18**: check off items as they land (house
|
|
493
|
-
rule: write conclusions down).
|
|
494
|
-
|
|
495
|
-
### (h) (surfaced by this audit, not in the original list) SSM batched path
|
|
496
|
-
|
|
497
|
-
Port mlx-lm's ArraysCache batch ops for `SSMCache` (conv/recurrent state are
|
|
498
|
-
`[B,…]` tensors; merge/filter/extend are trivial B-axis concat/take — no
|
|
499
|
-
padding subtleties since there's no sequence axis; the mask is the harder bit:
|
|
500
|
-
Qwen3.5's linear layers need an `ssm_mask` under left-padding). Oracle exists
|
|
501
|
-
(mlx-lm batches these models). Schedule after (a); until then the capability
|
|
502
|
-
gate (D1 fix) keeps Qwen servers correct-but-serial.
|
|
503
|
-
|
|
504
|
-
---
|
|
505
|
-
|
|
506
|
-
## 4. Sequencing
|
|
507
|
-
|
|
508
|
-
Order minimizes risk (correctness first, then the engine, then features),
|
|
509
|
-
respects in-flight work (seed fix in flight — untouched; gemma4-base.ts owned
|
|
510
|
-
elsewhere — all new cache code goes in new/batched-* files), and gives every
|
|
511
|
-
step a gate.
|
|
512
|
-
|
|
513
|
-
| # | Item | Effort | Test gate | Notes |
|
|
514
|
-
| --- | --- | --- | --- | --- |
|
|
515
|
-
| 1 | ✅ **DONE 2026-07-01** — **Hotfix bundle**: D2 slice-before-head; D1 capability gate + server-config.md correction; D3 route curve/`/signal`(/adapter-unmount) through `gateway.runExclusive`; stale comment server.ts:960 | **S** | gated batch suites unchanged; fast suite; new unit: willBatch(Qwen)=false | no numerics change anywhere |
|
|
516
|
-
| 2 | ✅ **DONE 2026-07-01** — **D5/D6 failure + fairness**: per-row onToken containment (evict row, keep batch); `#admit` try/finally; drain-on-serial-waiter | **S–M** | new gated test: throwing onToken evicts one row, siblings complete; drain test (serial completes under sustained batchable load) | mlx-lm `remove` semantics |
|
|
517
|
-
| 3 | ✅ **DONE 2026-07-01** — **(d) engine hygiene**: chunked+interleaved admit, pipelined decode, clearCache cadence | **M** | KL suite + mlx-lm goldens UNCHANGED; bench-serving-load A/B (clean machine, Josh-gated for quotables) | closes perf-review backlog #6 |
|
|
518
|
-
| 4 | **(a) `BatchedKVCache` + `extend`** (full + rotating) — **still open** | **M** | extended dynamic golden (mlx-lm `extend` driven); existing suites green | new files only — no gemma4-base.ts conflict |
|
|
519
|
-
| 5 | ✅ **DONE 2026-07-03** (admission half; `--prompt-concurrency` still open) — **(c) KV-budget admission** — executed as grammar-spec-batching-integration.md Phase D | **M** | projection unit tests; gated queueing test (tests/batch-kv-budget.test.ts); `/stats` fields | depends on 3 (interleaved admit) for prompt-concurrency semantics |
|
|
520
|
-
| 6 | **(b) prompt-cache reuse under batching** — **still open** | **M–L** | `cached_tokens>0` gated test; warm==cold row parity; serial suite | depends on 4 (`extract`) + 5 (byte interplay) |
|
|
521
|
-
| 7 | **(e) gate widening**: ✅ e1 min_p/XTC + e3 penalties/logit_bias **DONE 2026-07-02** (per-row logits processors, batching-perf-path.md header item 2); e2 logprobs **still open** (routes serial) | **S+M+M** | per-feature: batched==serial teacher-forced/deterministic-seed tests | e1 waits for the seed fix to merge |
|
|
522
|
-
| 8 | ✅ **DONE 2026-07-02** — **(h) SSM batched path** (Qwen3.5) — landed as batching-perf-path.md P5, token-exact vs mlx-lm B=2 | **M–L** | mlx-lm B=N golden for a Qwen3.5 model (ArraysCache oracle) | un-does the item-1 gate for Qwen; doc row flips back |
|
|
523
|
-
| 9 | **(g) docs pass** | **S** | doc-vs-code re-audit (this file's §1 as the checklist) | rolling — update per landing, final sweep here |
|
|
524
|
-
| 10 | **(f) quantized-KV batched** | **L** | KL + 6-task + density benchmark — design doc first | DEFERRED — needs a concrete density case |
|
|
525
|
-
|
|
526
|
-
D7's small items (shared `shapeFor()`, `maxTokens` fallback unification, real
|
|
527
|
-
batched timing stats, filter min-left-pad shift, shared emit-contract test)
|
|
528
|
-
slot into whichever of steps 2–4 touches the neighboring code; none justify a
|
|
529
|
-
standalone step.
|