mlx-bun 0.0.12 → 0.2.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 +40 -24
- package/bin/mlx-bun.mjs +1 -1
- package/docs/{design → archive}/hlg-sampling.md +3 -3
- package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
- package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
- package/docs/archive/planning/release-notes-v0.0.13.md +77 -0
- package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
- package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
- package/docs/design/colibri-glm52-port.md +8 -1
- package/docs/design/compat-cli-surface-design.md +1 -1
- package/docs/design/decode-speed-program.md +1 -1
- package/docs/design/docs-reorg-plan.md +1 -1
- package/docs/design/dspark-serving-program.md +1 -1
- package/docs/design/dspark-speculative-decoding.md +53 -3
- package/docs/design/grammar-spec-batching-integration.md +1 -1
- package/docs/design/mlx-lm-serving-execution-seam.md +81 -0
- package/docs/design/orpo-dynamic-lambda.md +1 -1
- package/docs/design/orpo-future-enhancements.md +1 -1
- package/docs/design/orpo-training.md +2 -2
- package/docs/design/parity-tier-dag.md +1 -1
- package/docs/design/repo-cleanup-plan.md +46 -4
- package/docs/design/segmented-backward-training.md +2 -2
- package/docs/design/ssd-kv-cold-tier.md +127 -0
- package/docs/design/turboquant-weights.md +467 -0
- package/docs/design/w4a16-compute-precision-spike.md +1 -1
- package/docs/design/web-chat-redesign.md +5 -5
- package/docs/planning/chunk-task-roadmap.md +1 -1
- package/docs/planning/curve-sampler-research-plan.md +4 -4
- package/docs/planning/memory-docs-and-dag-plan.md +1 -1
- package/docs/planning/release-notes-v0.2.0.md +49 -0
- package/docs/planning/video-series.md +421 -0
- package/docs/reference/cli.md +15 -4
- package/docs/reference/distribution.md +1 -0
- package/docs/reference/features-matrix.md +3 -1
- package/docs/reference/library-api.md +41 -1
- package/docs/reference/models.md +33 -0
- package/docs/reference/server-api.md +86 -19
- package/docs/reference/server-config.md +57 -33
- package/package.json +2 -2
- package/src/anthropic.ts +92 -3
- package/src/chat-template.ts +34 -1
- package/src/cli.ts +131 -36
- package/src/config.ts +10 -0
- package/src/eval/kl.ts +27 -20
- package/src/eval/runner.ts +23 -4
- package/src/eval/tasks/bfcl.ts +2 -1
- package/src/eval/tasks/gsm8k.ts +2 -1
- package/src/eval/tasks/hashhop.ts +2 -1
- package/src/eval/tasks/humaneval.ts +5 -4
- package/src/eval/tasks/ifeval.ts +2 -1
- package/src/eval/tasks/mmlu.ts +2 -1
- package/src/expert-io.ts +3 -1
- package/src/expert-offload.ts +3 -1
- package/src/expert-trace.ts +3 -1
- package/src/fit.ts +24 -43
- package/src/flags.ts +7 -9
- package/src/generate.ts +107 -92
- package/src/grammar.ts +2 -1
- package/src/harness-pi.ts +1 -1
- package/src/index.ts +20 -0
- package/src/jobs/job-entry.ts +3 -2
- package/src/kv-scheme.ts +199 -0
- package/src/kv-store.ts +370 -205
- package/src/media-fetch.ts +19 -3
- package/src/memory/model.ts +2 -1
- package/src/memory/vault.ts +2 -1
- package/src/memory-plan.ts +18 -0
- package/src/mlx/ffi.ts +8 -11
- package/src/mlx/ops.ts +31 -6
- package/src/model/batched-mask.ts +1 -1
- package/src/model/batched-quant.ts +1 -1
- package/src/model/batched-rotating-quant.ts +43 -97
- package/src/model/batched-rotating-state.ts +73 -0
- package/src/model/batched-rotating.ts +56 -105
- package/src/model/batched-row-storage.ts +127 -0
- package/src/model/compiled-decode.ts +3 -2
- package/src/model/factory.ts +44 -49
- package/src/model/gemma4-base.ts +163 -10
- package/src/model/generated/gemma4-12b.ts +50 -146
- package/src/model/generated/gemma4-26b.ts +32 -92
- package/src/model/generated/gemma4-e4b.ts +26 -74
- package/src/model/glm52-cache.ts +19 -9
- package/src/model/glm52-memory.ts +28 -1
- package/src/model/minicpm5.ts +2 -1
- package/src/model/profile.ts +433 -0
- package/src/model/qwen3-delta.ts +98 -2
- package/src/model/qwen3-mrope.ts +252 -0
- package/src/model/qwen3_5.ts +217 -25
- package/src/native/frame_extract.swift +140 -0
- package/src/native-pack.ts +13 -6
- package/src/pi-web.ts +6 -5
- package/src/prompt-cache.ts +2 -2
- package/src/quantize/atomic-output.ts +38 -0
- package/src/quantize/calibration.ts +2 -1
- package/src/quantize/config-writer.ts +6 -0
- package/src/quantize/index.ts +28 -1
- package/src/quantize/job.ts +11 -0
- package/src/quantize/quantizer.ts +265 -100
- package/src/quantize/rotate.ts +603 -0
- package/src/quantize/safetensors-writer.ts +79 -1
- package/src/quantize/sensitivity.ts +41 -59
- package/src/quantize/weight-transform.ts +292 -0
- package/src/responses.ts +102 -2
- package/src/runtime-config.ts +67 -0
- package/src/sampler.ts +272 -0
- package/src/serve/admin-routes.ts +184 -0
- package/src/serve/aux-routes.ts +132 -0
- package/src/serve/batch-scheduler.ts +200 -87
- package/src/serve/completion-executor.ts +346 -0
- package/src/serve/completion-sink.ts +157 -0
- package/src/serve/discovery-routes.ts +205 -0
- package/src/serve/generation-gateway.ts +116 -109
- package/src/serve/kv-budget.ts +14 -0
- package/src/serve/lab-routes.ts +338 -0
- package/src/serve/model-admin-routes.ts +153 -0
- package/src/serve/prompt-response-trace.ts +147 -0
- package/src/serve/request-plan.ts +158 -0
- package/src/serve/static-routes.ts +101 -0
- package/src/server.ts +768 -1367
- package/src/spec/dflash-source.ts +2 -1
- package/src/spec/dspark/module-dflash.ts +1 -1
- package/src/spec/glm52-mtp-source.ts +8 -0
- package/src/spec/qwen-mtp-source.ts +431 -0
- package/src/spec/serve-loop.ts +36 -68
- package/src/ssd-cache.ts +17 -14
- package/src/ssd-durability.ts +195 -0
- package/src/train/flash-cce.ts +12 -11
- package/src/train/forward.ts +32 -1
- package/src/train/loss.ts +2 -1
- package/src/train/segmented.ts +4 -3
- package/src/train/trainer.ts +3 -2
- package/src/vision/preprocess.ts +1 -1
- package/src/vision/prompt.ts +47 -1
- package/src/vision/qwen3vl-preprocess.ts +409 -0
- package/src/vision/qwen3vl-prompt.ts +196 -0
- package/src/vision/qwen3vl-tower.ts +513 -0
- package/src/vision/video-frames.ts +148 -0
- package/src/web/app.js +10 -3
- package/src/web/src/chat.ts +12 -5
- package/src/weights.ts +81 -26
- /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
- /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
- /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
- /package/docs/{investigations → archive/investigations}/curve-runs/bisector-route.json +0 -0
- /package/docs/{investigations → archive/investigations}/curve-runs/bisector-trace.json +0 -0
- /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.json +0 -0
- /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.svg +0 -0
- /package/docs/{investigations → archive/investigations}/curve-terrain.html +0 -0
- /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
- /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-ab.html +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-explorer.html +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/cliff.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/cross.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/edges.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/k20-grids.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/k20-peaks.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/lw-curve.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/terrain.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-report.html +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-ab.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind-prompts.md +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cliff.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cross.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-edge.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-eval1.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-frontier.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid2.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-lw-coarse.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-map.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/run0-original.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runA-recommended.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAA-cliff-cpm5.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAB-grid2-e4b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAC-peak-e4b-partial.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAD-ab-samples.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAE-edge-e4b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAF-logit-calibration.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAG-eval-config2.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runB-gain-variants.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runC-pivots.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runD-oetf.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runE-eotf.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runF-shaper-working.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runG-shaper-sweep.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runH-wsweep.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runI-asweep.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runJ-knobmap.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runK-frontier.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runL-widemap.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runM-frontier-emb.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runN-verify.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runO-frontier-seed5000.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runP-grid-AxSm.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runQ-grid-tgW.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runR-blind-transcript.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runS-cross-coherence.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runT-cross-cpm5.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runU-cross-12b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runV-lw-e4b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runW-lw-12b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runX-lw-cpm5.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runY-cliff-e4b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runZ-cliff-12b.log +0 -0
- /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
- /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
- /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
- /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
- /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
- /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
- /package/docs/{planning → archive/planning}/journal.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
- /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# mlx-lm serving execution seam
|
|
2
|
+
|
|
3
|
+
## Question
|
|
4
|
+
|
|
5
|
+
What should mlx-bun borrow from the pinned mlx-lm oracle when deepening Completion execution around this flow?
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
prepared prompt -> semantic events -> terminal summary
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This note reads the local mlx-lm 0.31.3 source installed in the reference venv. The comparison uses the serving term defined in `CONTEXT.md`: a **Completion execution** includes output, resource lifetime, usage, and scheduling placement.
|
|
12
|
+
|
|
13
|
+
mlx-lm is the correctness oracle only for behavior and numeric schemes it implements. It is not mlx-bun's architecture oracle or capability ceiling. mlx-bun also implements explicitly selected features such as mixed-precision KV, TurboQuant, and native draft methods. The execution path must preserve those resolved settings exactly.
|
|
14
|
+
|
|
15
|
+
## Implementation status
|
|
16
|
+
|
|
17
|
+
The first consolidation slice landed on 2026-08-21. `prepareCompletion()` performs admission before a streaming response starts and returns an opaque, single-use `PreparedCompletion`. `CompletionExecutor.execute()` then owns placement reporting, `CompletionSink`, logprobs, semantic event delivery, finish reason, usage, and pre-generation cleanup. [`completion-executor.ts:75`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-executor.ts:75) [`completion-executor.ts:219`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-executor.ts:219)
|
|
18
|
+
|
|
19
|
+
Chat and raw-text adapters still declare their semantic components and format their own wire responses. Their streaming and non-streaming branches now call the same executor. `GenerationGateway` still owns placement and model execution. The change does not add or remove flags, alter defaults, or substitute one inference method for another.
|
|
20
|
+
|
|
21
|
+
S3 completed the scheduling declaration at the same seam. A
|
|
22
|
+
`GenerationPlacement` now names `serial` or `continuous`; it no longer calls a
|
|
23
|
+
lone scheduler request "batched." The continuous scheduler chooses its B=1
|
|
24
|
+
fast path or B=N step from active rows. The strict serial executor remains for
|
|
25
|
+
`--batch 1` and for declared compositions the continuous scheduler does not yet
|
|
26
|
+
implement. Existing parity evidence covers both the default scheduler's B=1
|
|
27
|
+
path and strict serial. The recorded scheduler/serial decode ratios are
|
|
28
|
+
0.992–0.996, and a fresh main-vs-branch serial gate after the sampler
|
|
29
|
+
consolidation measured a 1.000 median-best ratio (about 265.0 tok/s on both
|
|
30
|
+
sides, MiniCPM5, two interleaved 256-token repeats).
|
|
31
|
+
|
|
32
|
+
Final acceptance after the GPQA pause exercised the seam through the complete
|
|
33
|
+
two-shard suite and a real two-turn browser conversation. The suite passed
|
|
34
|
+
2,064 tests with zero failures. The browser run verified streaming, tool
|
|
35
|
+
events, context carryover, metrics, and turn recovery. A mainline-only queued
|
|
36
|
+
DOM render race found by that run was fixed at the UI lifecycle boundary; no
|
|
37
|
+
decode-loop work was added. The architecture changes likewise keep accounting
|
|
38
|
+
and capability decisions outside numerical kernels. The fresh serial A/B plus
|
|
39
|
+
the established scheduler B=1 parity and latency evidence are the performance
|
|
40
|
+
gate.
|
|
41
|
+
|
|
42
|
+
## What the oracle actually does
|
|
43
|
+
|
|
44
|
+
The HTTP adapter creates a small `CompletionRequest` that still contains protocol-shaped chat messages or a raw text prompt. It separately builds `GenerationArguments`. Prompt preparation has not happened yet. [`server.py:182`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:182) [`server.py:198`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:198) [`server.py:1368`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1368)
|
|
45
|
+
|
|
46
|
+
`ResponseGenerator.generate()` submits that pair to one worker thread through a queue. The worker applies the chat template, tokenizes, finds cache segments, chooses the initial reasoning state, constructs stop/tool/reasoning transitions, selects a lane, owns cache work, and emits per-token `Response` records. [`server.py:516`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:516) [`server.py:626`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:626) [`server.py:688`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:688) [`server.py:1026`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1026)
|
|
47
|
+
|
|
48
|
+
That organization can be summarized as:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
HTTP request
|
|
52
|
+
-> CompletionRequest + GenerationArguments
|
|
53
|
+
-> ResponseGenerator queue
|
|
54
|
+
-> tokenize + state machine + lane + cache
|
|
55
|
+
-> token Response { text, token, state, match, logprob, finish_reason }
|
|
56
|
+
-> HTTP handler accumulates semantics and formats the protocol response
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The model loader is separate. `utils.load()` returns the model and a tokenizer wrapper, while tokenizer loading chooses the streaming detokenizer and tool parser. This keeps model construction out of per-request execution. [`utils.py:453`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/utils.py:453) [`tokenizer_utils.py:578`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/tokenizer_utils.py:578)
|
|
60
|
+
|
|
61
|
+
## Comparison by concern
|
|
62
|
+
|
|
63
|
+
| Concern | Oracle organization | Consequence for the mlx-bun seam |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| Prepared prompt | The worker owns chat templating, tokenization, initial reasoning state, and cache-oriented prompt segmentation. The HTTP adapter does not know those details. [`server.py:530`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:530) [`server.py:565`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:565) | A prepared-prompt value is a sound seam, but it should be richer than token ids. It must carry the generation inputs and owned media or grammar state needed by either lane. Protocol adapters should not assemble `RequestShape` or transfer resources piecemeal. |
|
|
66
|
+
| Semantic events | The state machine classifies tokens as `normal`, `reasoning`, or `tool`. `_process_control_tokens` withholds matched control sequences. The HTTP handler then accumulates reasoning, tool text, visible text, and parses completed tool calls. [`server.py:236`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:236) [`server.py:654`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:654) [`server.py:1442`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1442) | Do not copy this split. mlx-bun's `CompletionSink` already emits content, reasoning, and tool-call events through one interface. [`completion-sink.ts:7`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-sink.ts:7) [`completion-sink.ts:90`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-sink.ts:90) The deepened module should own that semantic work so all protocol adapters consume the same events. |
|
|
67
|
+
| Terminal summary | `generate.stream_generate()` puts timing, token counts, memory, and finish reason on every low-level response, but the server discards most of it. The HTTP handler counts emitted tokens, reads prompt and cache counts from `GenerationContext`, derives tool-call finish, and builds usage. [`generate.py:269`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/generate.py:269) [`generate.py:728`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/generate.py:728) [`server.py:1495`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1495) [`server.py:1517`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1517) | The oracle has no single terminal summary at the server seam. mlx-bun should add one. It should be the sole source of finish reason, prompt/generated/cached counts, final lane, speculation facts, and collected result. |
|
|
68
|
+
| Streaming and non-streaming | Both modes consume the same response iterator and semantic accumulation loop. Streaming flushes deltas and optionally emits a final usage packet; non-streaming sends one accumulated response. [`server.py:1454`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1454) [`server.py:1507`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1507) [`server.py:1529`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1529) | Keep one Completion execution interface. Streaming and non-streaming are protocol adapters over the same semantic events and terminal summary, not separate execution paths. |
|
|
69
|
+
| Scheduling placement | Model loading marks a model batchable only when no draft model exists and every cache supports merge. Per-request placement also excludes a fixed seed. The worker joins the active batch, creates a batch, runs serially, or drains an incompatible batch. [`server.py:370`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:370) [`server.py:685`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:685) [`server.py:731`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:731) [`server.py:801`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:801) [`server.py:831`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:831) | `RequestShape` describes settings already resolved from flags, request fields, model metadata, and defaults. `place()` asks only whether the continuous scheduler implements that exact composition; it never replaces MTP, a KV scheme, or another requested method. The frozen declaration names the `serial` or `continuous` mechanism, and both reporting and `run()` consume it. Active-row count selects B=1 or B=N inside the continuous mechanism. |
|
|
70
|
+
| Cancellation and flow | Cancellation is cooperative. The handler's `finally` sets a flag; serial execution checks it after tokens, while batched execution checks it during prompt and generation work and removes the row. [`server.py:1008`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1008) [`server.py:861`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:861) [`server.py:911`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:911) [`server.py:1551`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1551) The response queue is unbounded, and socket writes block only the HTTP thread, so there is no end-to-end demand signal from the client to generation. [`server.py:444`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:444) [`server.py:1032`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1032) [`server.py:1489`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1489) | Do not regress to the oracle here. mlx-bun's token callback accepts a promise or `false`, and `GenerationGateway.run()` accepts an `AbortSignal`. [`generation-gateway.ts:94`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/generation-gateway.ts:94) [`generation-gateway.ts:354`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/generation-gateway.ts:354) Cancellation and async flow control are part of the interface, not HTTP details. |
|
|
71
|
+
| Cache and resource lifetime | The worker fetches or creates the serial cache, generates, then inserts it. Batched execution fetches before admission, snapshots segment caches, inserts the final cache, and removes cancelled rows. [`server.py:963`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:963) [`server.py:1018`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1018) [`server.py:752`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:752) [`server.py:864`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:864) [`server.py:901`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:901) `LRUPromptCache` hides prefix matching, copying, trimming, and count/byte eviction behind a small interface. [`cache.py:1623`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/models/cache.py:1623) [`cache.py:1674`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/models/cache.py:1674) [`cache.py:1696`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/models/cache.py:1696) | `prepareCompletion()` now disposes owned media and grammar state on admission rejection. `execute()` disposes the same plan on placement or pre-generation failure, then transfers ownership exactly once before `GenerationGateway.run()`. Cache and in-generation disposal remain in the gateway and generation path. [`completion-executor.ts:75`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-executor.ts:75) [`completion-executor.ts:219`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-executor.ts:219) |
|
|
72
|
+
| Errors | Errors before the response iterator exists become a JSON error, although the oracle uses status 404 for that whole class. Errors delivered later through the worker queue are raised by the iterator. The handler guarantees `ctx.stop()` but has no matching mid-stream error packet in this loop. [`server.py:1417`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1417) [`server.py:1424`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1424) [`server.py:1035`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1035) [`server.py:1454`](/Users/joshrossi/Code/mlx-lm/.venv/lib/python3.14/site-packages/mlx_lm/server.py:1454) | Preserve a typed distinction between rejection before execution and failure after events begin. Both must run the same ownership cleanup. Protocol adapters decide how to encode the failure after headers have been sent. |
|
|
73
|
+
|
|
74
|
+
## Strongest conclusions
|
|
75
|
+
|
|
76
|
+
1. **Keep resolved inference settings authoritative.** Serial and continuous execution are scheduling mechanisms for the already-selected composition. Consolidating placement may shrink the HTTP adapter, but it must not turn explicit features or opinionated defaults into inferred choices.
|
|
77
|
+
2. **Do not borrow the oracle's semantic split.** Its worker emits state-tagged token records, then the HTTP handler performs semantic accumulation and tool parsing. mlx-bun already has a better starting point in `CompletionSink`. Move that module behind the Completion execution seam so protocol adapters see semantic events only.
|
|
78
|
+
3. **Keep the terminal summary as the only completion result.** It now settles finish reason, usage, cached-token count, final lane, speculation, logprobs, and collected output once. The four HTTP branches only format that summary. [`completion-executor.ts:103`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/serve/completion-executor.ts:103) [`server.ts:3104`](/Users/joshrossi/.codex/worktrees/e408/mlx-bun/src/server.ts:3104)
|
|
79
|
+
4. **Treat cancellation, async flow control, and ownership as interface requirements.** The oracle's queue makes cancellation and socket pressure indirect. mlx-bun has stronger mechanisms already. The deepening should hide their implementation without weakening their behavior.
|
|
80
|
+
|
|
81
|
+
The deletion test is positive. If this Completion execution module disappeared, scheduling policy, cache participation, semantic parsing, finish rules, usage, and cleanup would spread back across four protocol branches. Concentrating those rules gives callers leverage and gives maintainers locality. Feature selection and fidelity policy remain outside this seam as resolved inputs.
|
|
@@ -158,7 +158,7 @@ than the absolute max-bearable (the edge is fragile).
|
|
|
158
158
|
**Synergy with the diffusion frontier:** all of this is noise-robustness machinery for
|
|
159
159
|
controlling off a noisy process variable — so it transfers *especially* well to a
|
|
160
160
|
diffusion LM, where the likelihood is a stochastic ELBO (even noisier). See
|
|
161
|
-
[orpo-base-uf-experiment-and-directions.md](../investigations/orpo-base-uf-experiment-and-directions.md) §6.7.
|
|
161
|
+
[orpo-base-uf-experiment-and-directions.md](../archive/investigations/orpo-base-uf-experiment-and-directions.md) §6.7.
|
|
162
162
|
|
|
163
163
|
## Wiring
|
|
164
164
|
|
|
@@ -11,7 +11,7 @@ comfortable.
|
|
|
11
11
|
> trainer)** shipped as `orpo_prefix_shared`, composed with the flash head AND the
|
|
12
12
|
> segmented backward for MiniCPM5 **and** e4b. See [training.md](../reference/training.md),
|
|
13
13
|
> [orpo-quickstart.md](../reference/orpo-quickstart.md), and the live record
|
|
14
|
-
> [steel-flash-cce-handoff.md](../investigations/steel-flash-cce-handoff.md). The
|
|
14
|
+
> [steel-flash-cce-handoff.md](../archive/investigations/steel-flash-cce-handoff.md). The
|
|
15
15
|
> remaining sections (§2, §4–§9, the objective variants) are still genuinely future.
|
|
16
16
|
|
|
17
17
|
## Deferred levers — queued, do NOT start mid-run (2026-06-19)
|
|
@@ -19,7 +19,7 @@ runs half the forwards per step.
|
|
|
19
19
|
> The ✅/🔬 entries + optimization log below are the **design/build history**; for the
|
|
20
20
|
> current usage, flags, and numbers see [training.md](../reference/training.md) +
|
|
21
21
|
> [orpo-quickstart.md](../reference/orpo-quickstart.md), and the live engineering record
|
|
22
|
-
> [steel-flash-cce-handoff.md](../investigations/steel-flash-cce-handoff.md).
|
|
22
|
+
> [steel-flash-cce-handoff.md](../archive/investigations/steel-flash-cce-handoff.md).
|
|
23
23
|
|
|
24
24
|
> Oracle note: neither `mlx_lm` nor `optiq` ships ORPO, so — unlike DPO
|
|
25
25
|
> ([`loss.ts`](../../src/train/loss.ts) ported `optiq/lora/dpo.py`) — ORPO is
|
|
@@ -38,7 +38,7 @@ runs half the forwards per step.
|
|
|
38
38
|
> hiddens before judging** (it's plausibly a free speedup to enable). Standing Lab
|
|
39
39
|
> gates (filter-on-real-data, teacher-forced grad fidelity, end-to-end quality)
|
|
40
40
|
> + the host-buffer pin-leak post-mortem:
|
|
41
|
-
> [orpo-flash-cce-pin-leak.md](../investigations/orpo-flash-cce-pin-leak.md).
|
|
41
|
+
> [orpo-flash-cce-pin-leak.md](../archive/investigations/orpo-flash-cce-pin-leak.md).
|
|
42
42
|
|
|
43
43
|
## Implementation status
|
|
44
44
|
|
|
@@ -26,7 +26,7 @@ exists AND it matches bit-exactly; otherwise it bubbles up. Note the subtlety th
|
|
|
26
26
|
map already shows: an *optimization* that still matches the oracle stays low —
|
|
27
27
|
`compiled-decode` is ours but is L1 because it replays the same ops bit-for-bit.
|
|
28
28
|
Only nodes with **no** oracle (flash-CCE) are *forced* into the Lab. (Background +
|
|
29
|
-
post-mortem: [orpo-flash-cce-pin-leak.md](../investigations/orpo-flash-cce-pin-leak.md).)
|
|
29
|
+
post-mortem: [orpo-flash-cce-pin-leak.md](../archive/investigations/orpo-flash-cce-pin-leak.md).)
|
|
30
30
|
|
|
31
31
|
## Two axes: parity gates performance
|
|
32
32
|
|
|
@@ -45,7 +45,7 @@ check). Untracked ≠ deleted ≠ unrunnable.
|
|
|
45
45
|
+ the two docs that reference `repro/` (grep first: `grep -rn "repro/"
|
|
46
46
|
docs/ *.md`).
|
|
47
47
|
- Also updated every path reference (CLAUDE.md, PLAN.md, PLAN-archive.md,
|
|
48
|
-
docs/investigations/lab-build-journal.md, docs/design/docs-reorg-plan.md,
|
|
48
|
+
docs/archive/investigations/lab-build-journal.md, docs/design/docs-reorg-plan.md,
|
|
49
49
|
and the `repro/`-pointing comments in src/mlx/{ffi,array,autograd,ops}.ts
|
|
50
50
|
+ tests/{ffi-jit,train-autograd}.test.ts + the repro's own ISSUE.md).
|
|
51
51
|
- `[x]` **A2. root strays** (all untracked; no git surgery): delete
|
|
@@ -168,12 +168,12 @@ suspenders until both machines are re-cloned.
|
|
|
168
168
|
threads). The closed `oMLX` phase (2026-07-02) and Phase 19 are
|
|
169
169
|
*newer* than 17, so they stay in PLAN.md as recent history per the
|
|
170
170
|
criterion. Revisit D1 when a <17 phase is confirmed closed.
|
|
171
|
-
- `[x]` **D2.** `docs/investigations/curve-runs/` + two untracked HTML
|
|
171
|
+
- `[x]` **D2.** `docs/archive/investigations/curve-runs/` + two untracked HTML
|
|
172
172
|
strays in docs/investigations: gitignore or move under `reports/`.
|
|
173
|
-
- Already satisfied: `docs/investigations/curve-runs/` is gitignored
|
|
173
|
+
- Already satisfied: `docs/archive/investigations/curve-runs/` is gitignored
|
|
174
174
|
(`.gitignore`), and the four HTML strays (`curve-terrain.html`,
|
|
175
175
|
`hlg-ab.html`, `hlg-report.html`, `hlg-explorer.html`) are gitignored
|
|
176
|
-
via `docs/investigations/*.html`. No action needed.
|
|
176
|
+
via `docs/archive/investigations/*.html`. No action needed.
|
|
177
177
|
- `[x]` **D3.** The CLAUDE.md doc-map lists are hand-maintained; add a
|
|
178
178
|
`scripts/check-hygiene.ts` assertion that every `docs/**/*.md` appears in
|
|
179
179
|
the map (same guardrail spirit as B1).
|
|
@@ -187,3 +187,45 @@ A and D anytime (safe, agent-executable solo) — DONE 2026-07-02. B gate
|
|
|
187
187
|
DONE 2026-07-02 (B2 adapter-untrack deferred to Josh — see B2 finding).
|
|
188
188
|
C executed 2026-07-02 on Josh's explicit go — only remaining tail is the
|
|
189
189
|
M4 Pro reset + deleting the backup tarball once both boxes are green.
|
|
190
|
+
|
|
191
|
+
## Phase D — work-artifact hygiene + rules (2026-08-18) — DONE
|
|
192
|
+
|
|
193
|
+
Trigger: Josh — "this is a software project; we should not be adding all
|
|
194
|
+
these random work things into it." Compared against ml-explore/mlx-lm's
|
|
195
|
+
root (9 files, 4 dirs, zero work artifacts).
|
|
196
|
+
|
|
197
|
+
- **D1 root sweep**: six tracked `benchmarks-serve-2026-07-0*.md` dumps
|
|
198
|
+
git-rm'd (they leaked past a gitignore pattern that only covered
|
|
199
|
+
`benchmarks-h2h-*`); seven untracked root bench artifacts moved to
|
|
200
|
+
`reports/bench-archive/`; `tmp/` deleted. `.gitignore` now blocks
|
|
201
|
+
`/benchmarks-*`, `/bench-*`, `/tmp/`, `/*.log` at root.
|
|
202
|
+
- **D2 guardrail**: `scripts/check-hygiene.ts` gained a third gate —
|
|
203
|
+
tracked ROOT files must be on an explicit allowlist (16 meta files).
|
|
204
|
+
- **D3 CONTRIBUTING.md**: the rules (what-goes-where table; no dated
|
|
205
|
+
artifacts; models → `~/models`; docs lifecycle; PLAN/STATUS discipline;
|
|
206
|
+
external oracle venv is not a repo).
|
|
207
|
+
- **D4 docs archive**: all 20 `docs/investigations/` →
|
|
208
|
+
`docs/archive/investigations/` (investigations document finished work);
|
|
209
|
+
5 release-notes + journal + 2 chat-ui visions + 2 pass-plans →
|
|
210
|
+
`docs/archive/planning/`; `hlg-sampling.md` (self-declared superseded) →
|
|
211
|
+
`docs/archive/`. All inbound links rewritten; docs-map gate green.
|
|
212
|
+
- **D5 journal trim (rule 4)**: STATUS.md 1648 → 106 lines (Colibri
|
|
213
|
+
gate-history + all "where we were" + the stale July ranked backlog →
|
|
214
|
+
PLAN-archive under "STATUS archive 2026-08-18"); PLAN.md 3815 → 2679
|
|
215
|
+
(8 closed `[x]` phase blocks → PLAN-archive with one-line pointers).
|
|
216
|
+
- Model artifacts moved out of the tree entirely (`~/models/`) earlier
|
|
217
|
+
the same day.
|
|
218
|
+
|
|
219
|
+
Remaining (curated, not mechanical — needs judgment per file):
|
|
220
|
+
- `docs/design/` still holds 46 docs; many describe LANDED work and
|
|
221
|
+
should archive under rule 3 (candidates: optimization_plan,
|
|
222
|
+
faithful-base superseded pair, diffusion/audio/omlx/dspark docs whose
|
|
223
|
+
phases closed). Do this per-doc with link fixes, not as a bulk move.
|
|
224
|
+
- `docs/planning/` leftovers (chunk-task-roadmap, curve-sampler-research-
|
|
225
|
+
plan, memory-docs-and-dag-plan, pi-integration-review) — keep-or-archive
|
|
226
|
+
per Josh.
|
|
227
|
+
- `scripts/experiments/` = 199 files; prune ones whose findings are fully
|
|
228
|
+
recorded in archived docs (keep regen/oracle scripts).
|
|
229
|
+
- `runs/` is 32 GB machine-local (Colibri evidence + quant lab arms) —
|
|
230
|
+
delete lab arms once the TQ/GPTQ program closes; Colibri evidence dirs
|
|
231
|
+
are cited from the archive and stay.
|
|
@@ -35,7 +35,7 @@ All committed-worthy, validated this session:
|
|
|
35
35
|
Baseline 11.89/100 → fine-tuned **91.70/100** on lucien's 25-case chunk holdout
|
|
36
36
|
(valid-JSON rate 12% → 100%). Full mlx-bun loop: train → save adapter (hot-swap
|
|
37
37
|
format) → `AdapterManager.mount` → generate. Writeup:
|
|
38
|
-
`docs/investigations/chunk-finetune-experiment.md`. Scripts: `scripts/chunk-eval.ts`,
|
|
38
|
+
`docs/archive/investigations/chunk-finetune-experiment.md`. Scripts: `scripts/chunk-eval.ts`,
|
|
39
39
|
`scripts/chunk-finetune.ts`, `scripts/chunk-filter.ts`. Adapter:
|
|
40
40
|
`~/.cache/mlx-bun-finetunes/minicpm5-chunk-final`.
|
|
41
41
|
|
|
@@ -69,7 +69,7 @@ active-after 8.66 + cache-after 6.61 = 15.27 > peak 14.90, impossible if peak we
|
|
|
69
69
|
total). MLX holds freed buffers in a **cache** (not returned to OS) so RSS overstates
|
|
70
70
|
live memory; use `activeMemory()` / `cacheMemory()` / `clearCache()` to separate
|
|
71
71
|
them (`src/mlx/ffi.ts`). Caveat from the MoE expert-offload work
|
|
72
|
-
(`docs/investigations/expert-offload-single-user-moe.md`): `clearCache()` does NOT
|
|
72
|
+
(`docs/archive/investigations/expert-offload-single-user-moe.md`): `clearCache()` does NOT
|
|
73
73
|
reliably return device-buffer RAM to the OS — only the mmap clean-page path does.
|
|
74
74
|
|
|
75
75
|
Facts established:
|
|
@@ -286,3 +286,130 @@ SIGINT/SIGTERM during (or within the debounce after) traffic loses queued
|
|
|
286
286
|
write-behind entries; restart survival degrades to whatever already
|
|
287
287
|
flushed. This is a best-effort cache tier; the cost of a lost entry is one
|
|
288
288
|
re-prefill. `SpillQueue.drain()` exists if a shutdown hook is ever wanted.
|
|
289
|
+
|
|
290
|
+
This acceptance was superseded by the 2026-08-22 durability fix below.
|
|
291
|
+
|
|
292
|
+
## Bug brief 2026-08-22: restart can outrun SSD write-behind
|
|
293
|
+
|
|
294
|
+
### User-visible failure
|
|
295
|
+
|
|
296
|
+
A completed long-context request can report a full RAM-cache hit on repeat,
|
|
297
|
+
then lose that prefix when the server restarts a few seconds later. The next
|
|
298
|
+
request pays a full prefill even though `--ssd-cache` was enabled.
|
|
299
|
+
|
|
300
|
+
The 2026-08-22 M1 Max serve matrix reproduced this with the standard restart
|
|
301
|
+
leg, which waits 2.5 seconds after the long request before sending SIGTERM:
|
|
302
|
+
|
|
303
|
+
| model and mode | cached tokens after restart | expected |
|
|
304
|
+
|---|---:|---:|
|
|
305
|
+
| MiniCPM5, unified bf16 | 15,817 | about 15.8k |
|
|
306
|
+
| e4b, unified bf16 | 4 | about 15.9k |
|
|
307
|
+
| e4b, serial bf16 | 15,940 | about 15.9k |
|
|
308
|
+
| 12B, unified bf16 | 4 | about 15.9k |
|
|
309
|
+
| 12B, serial bf16 | 15,866 | about 15.9k |
|
|
310
|
+
| Qwen3.8, unified bf16 | 0 | about 15.1k |
|
|
311
|
+
| Qwen3.8, serial bf16 | 0 | about 15.1k |
|
|
312
|
+
| Qwen3.8, unified mixed KV | 15,112 | about 15.1k |
|
|
313
|
+
|
|
314
|
+
The Bun 1.4 rerun reproduced the lane split before this brief was written:
|
|
315
|
+
e4b unified restored 2 of 15,942 tokens, while e4b serial restored 15,939.
|
|
316
|
+
The completed run also restored only 4 tokens for unified 12B versus 15,794
|
|
317
|
+
for serial 12B, and 5 for mixed-KV 12B. Qwen bf16 restored zero in both
|
|
318
|
+
lanes, while mixed-KV Qwen restored all 15,114 tokens. This is not a Bun
|
|
319
|
+
1.3-only result.
|
|
320
|
+
|
|
321
|
+
### Expected contract
|
|
322
|
+
|
|
323
|
+
After a request completes and the server has reported the cache durable, a
|
|
324
|
+
normal SIGTERM and restart must restore the longest compatible prefix. For
|
|
325
|
+
the benchmark's exact-repeat request, `cached_tokens` should be at least
|
|
326
|
+
`prompt_tokens - 1`. The server must never imply durability while a matching
|
|
327
|
+
snapshot is still waiting on its debounce timer or the spill queue.
|
|
328
|
+
|
|
329
|
+
### Evidence and current diagnosis
|
|
330
|
+
|
|
331
|
+
The read and reconstruction path works when the file reaches disk. MiniCPM,
|
|
332
|
+
serial e4b and 12B, and mixed-KV Qwen all restore a full prefix through the
|
|
333
|
+
same `PromptCache.take()` and `SsdCacheStore.restore()` path. That makes a
|
|
334
|
+
model-format or prefix-matching defect unlikely as the primary cause.
|
|
335
|
+
|
|
336
|
+
The likely failure is the durability boundary:
|
|
337
|
+
|
|
338
|
+
1. `PromptCache.onPut` schedules a snapshot with a one-second debounce.
|
|
339
|
+
2. A busy gateway re-arms that timer for five seconds.
|
|
340
|
+
3. The snapshot then joins a serial `SpillQueue`; large bf16 entries can still
|
|
341
|
+
be queued or in flight when the benchmark's 2.5-second wait expires.
|
|
342
|
+
4. SIGTERM stops the process without forcing pending timers to fire or awaiting
|
|
343
|
+
`SpillQueue.drain()`. The queue's own comment calls drain a future shutdown
|
|
344
|
+
hook.
|
|
345
|
+
|
|
346
|
+
Entry size explains the broad pattern. Small MiniCPM and smaller mixed-KV Qwen
|
|
347
|
+
files finish in time; large Qwen bf16 files do not. The remaining unified versus
|
|
348
|
+
serial split needs telemetry before assigning a second root cause. The unified
|
|
349
|
+
lane may enqueue more snapshots, take longer to extract row caches, or hit the
|
|
350
|
+
queue cap. The current report does not capture that state before termination.
|
|
351
|
+
|
|
352
|
+
### Fix plan
|
|
353
|
+
|
|
354
|
+
1. Expose `pending_snapshots`, `pending_spills`, `pending_spill_bytes`,
|
|
355
|
+
`dropped_spills`, and the longest durable prefix in `/stats.ssd_cache`.
|
|
356
|
+
`pending_snapshots` must include debounce and busy-rearm timers, not only
|
|
357
|
+
entries already placed in `SpillQueue`.
|
|
358
|
+
2. Add one async durability operation that cancels debounce timers, snapshots
|
|
359
|
+
matching RAM entries under the gateway lock, and awaits
|
|
360
|
+
`SpillQueue.drain()`. Return success only after the atomic rename completes.
|
|
361
|
+
3. Route graceful SIGINT/SIGTERM through that operation with a bounded timeout.
|
|
362
|
+
Stop new admission first. A timeout may drop the cache, but must log the
|
|
363
|
+
number of pending and dropped snapshots.
|
|
364
|
+
4. Change the benchmark restart leg to request or poll durability instead of
|
|
365
|
+
sleeping for 2.5 seconds. A fixed sleep measures SSD timing luck, not the
|
|
366
|
+
restart contract.
|
|
367
|
+
5. Capture pre-kill stats and the recovered entry count in every restart row so
|
|
368
|
+
a write miss cannot masquerade as a restore miss again.
|
|
369
|
+
|
|
370
|
+
### Implemented 2026-08-22
|
|
371
|
+
|
|
372
|
+
- `SsdDurabilityCoordinator` owns debounce, busy retry, queue completion, and
|
|
373
|
+
explicit flush as one state machine. A snapshot stays dirty until the atomic
|
|
374
|
+
store succeeds. Queue drops and write failures remain retryable.
|
|
375
|
+
- Dirty keys include the exact token sequence, so two conversations with the
|
|
376
|
+
same namespace and token count cannot cancel each other.
|
|
377
|
+
- `POST /admin/cache/flush` returns `200` only at the durability boundary and
|
|
378
|
+
exposes pending, dropped, failed, entry-count, and longest-prefix evidence.
|
|
379
|
+
- `mlx-bun serve` handles `SIGINT` and `SIGTERM` by stopping admission,
|
|
380
|
+
draining active requests, and awaiting the same flush, bounded by
|
|
381
|
+
`MLX_BUN_SHUTDOWN_TIMEOUT_MS`.
|
|
382
|
+
- The standard restart benchmark calls the endpoint instead of sleeping and
|
|
383
|
+
records its evidence in the Markdown report and eval database notes.
|
|
384
|
+
- Model-free regression coverage holds an atomic store open and proves flush
|
|
385
|
+
waits, forces the busy-rearm path, retries a failed write, preserves
|
|
386
|
+
equal-length conversations, and reports a missing RAM boundary honestly.
|
|
387
|
+
|
|
388
|
+
Focused real-server smoke on unified e4b passed the repaired path: a
|
|
389
|
+
4,026-token prompt flushed in 408 ms with `durable=true`, zero pending,
|
|
390
|
+
dropped, or failed work, then restored 4,025 cached tokens after SIGTERM and
|
|
391
|
+
respawn. The machine failed the clean-performance preflight, so this is cache
|
|
392
|
+
correctness evidence only, not a performance result. The full clean 16k
|
|
393
|
+
e4b/12B/Qwen matrix remains the acceptance run.
|
|
394
|
+
|
|
395
|
+
### Acceptance tests
|
|
396
|
+
|
|
397
|
+
- A deterministic coordinator test holds `storeAsync`, requests a flush,
|
|
398
|
+
releases the store, and proves the durability boundary waits for the atomic
|
|
399
|
+
file. The focused real-server smoke exercises the SIGTERM path through that
|
|
400
|
+
same operation before restart.
|
|
401
|
+
- A busy-gateway test forces the five-second re-arm path, then requests a flush
|
|
402
|
+
and proves the timer becomes an immediate queued snapshot without blocking an
|
|
403
|
+
active generation.
|
|
404
|
+
- Batch 8 and `--batch 1` both restore at least `prompt_tokens - 1` for e4b,
|
|
405
|
+
12B, and Qwen3.8 at roughly 16k context.
|
|
406
|
+
- The mixed-KV and SSM cache kinds retain their existing continuation parity.
|
|
407
|
+
- Steady-state decode remains unchanged because normal writes still pause while
|
|
408
|
+
the engine is busy. Queued clone memory stays within the spill cap.
|
|
409
|
+
|
|
410
|
+
Focused reproduction:
|
|
411
|
+
|
|
412
|
+
```sh
|
|
413
|
+
./benchmark.sh --models e4b,12B,qwen27b \
|
|
414
|
+
--arms mlx-bun,mlx-bun-serial,mlx-bun-mixed
|
|
415
|
+
```
|