mlx-bun 0.3.0 → 0.4.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/docs/README.md +4 -1
- package/docs/archive/investigations/openjarvis-fit.md +198 -0
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
- package/docs/design/batching.md +1321 -49
- package/docs/design/decode-speed-program.md +331 -1163
- package/docs/design/generic-model-support.md +79 -3
- package/docs/design/kv-cache.md +198 -54
- package/docs/design/speculative-decoding.md +387 -24
- package/docs/design/turboquant.md +123 -13
- package/docs/design/unified-engine-frontier-plan.md +144 -142
- package/docs/planning/PRODUCT_ROADMAP.md +16 -0
- package/docs/planning/release-notes-v0.4.0.md +45 -0
- package/docs/reference/benchmarks.md +2457 -6
- package/docs/reference/cli.md +41 -2
- package/docs/reference/distribution.md +16 -9
- package/docs/reference/environment.md +55 -5
- package/docs/reference/models.md +13 -4
- package/docs/reference/server-api.md +79 -25
- package/docs/reference/server-config.md +178 -84
- package/docs/reference/troubleshooting.md +13 -21
- package/package.json +1 -1
- package/src/backends/mlx/assistant-target.ts +46 -0
- package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
- package/src/backends/mlx/batch-group.ts +368 -224
- package/src/backends/mlx/cache-attachment.ts +67 -0
- package/src/backends/mlx/cache-layout.ts +31 -0
- package/src/backends/mlx/checkpoint-state.ts +53 -0
- package/src/backends/mlx/continuation-persistence.ts +106 -0
- package/src/backends/mlx/continuation-request.ts +43 -0
- package/src/backends/mlx/continuation.ts +77 -0
- package/src/backends/mlx/draft-target.ts +7 -22
- package/src/backends/mlx/full-kv-row-append.ts +32 -0
- package/src/backends/mlx/full-kv-row-donor.ts +54 -0
- package/src/backends/mlx/full-transitioning-kv-rows.ts +65 -0
- package/src/backends/mlx/gateway-binding.ts +77 -18
- package/src/backends/mlx/kv-codec.ts +9 -0
- package/src/backends/mlx/kv-maintenance.ts +133 -0
- package/src/backends/mlx/kv-tensor-rows.ts +212 -0
- package/src/backends/mlx/model-serving.ts +1 -1
- package/src/backends/mlx/prefill-cohort.ts +89 -0
- package/src/backends/mlx/prefill-rows.ts +200 -0
- package/src/backends/mlx/prefill.ts +1 -1
- package/src/backends/mlx/projected-context-rows.ts +72 -0
- package/src/backends/mlx/request-state-policy.ts +41 -0
- package/src/backends/mlx/rollback.ts +41 -3
- package/src/backends/mlx/row-sampling.ts +49 -0
- package/src/backends/mlx/serial-executor.ts +35 -66
- package/src/backends/mlx/speculative-group.ts +359 -0
- package/src/backends/mlx/speculative-round.ts +161 -0
- package/src/backends/mlx/state-rows.ts +104 -0
- package/src/backends/mlx/state-views.ts +21 -1
- package/src/backends/mlx/target-layout-capability.ts +35 -0
- package/src/backends/mlx/transitioning-kv-rows.ts +165 -0
- package/src/cli.ts +11 -36
- package/src/contracts/execution.ts +11 -0
- package/src/contracts/prefix-cache.ts +23 -0
- package/src/contracts/resources.ts +7 -0
- package/src/contracts/sampling.ts +23 -0
- package/src/contracts/scheduling.ts +18 -0
- package/src/engine/execution-plan.ts +18 -16
- package/src/engine/resources.ts +12 -1
- package/src/engine/scheduler.ts +10 -5
- package/src/fit.ts +29 -15
- package/src/generate.ts +77 -173
- package/src/inference/draft-acceptance.ts +41 -0
- package/src/inference/draft-output.ts +49 -0
- package/src/inference/rollback.ts +2 -2
- package/src/kv-scheme.ts +70 -12
- package/src/kv-store.ts +70 -16
- package/src/lab/paged-kv/paged-kv-rows.ts +83 -0
- package/src/lab/paged-kv/paged-kv.ts +25 -11
- package/src/mlx/array.ts +2 -1
- package/src/model/artifact-identity.ts +73 -0
- package/src/model/batched-kv.ts +235 -0
- package/src/model/batched-mask.ts +12 -4
- package/src/model/batched-quant.ts +8 -2
- package/src/model/batched-quantized-kv.ts +144 -0
- package/src/model/batched-rotating-quant.ts +76 -15
- package/src/model/batched-rotating-state.ts +76 -0
- package/src/model/batched-rotating.ts +64 -22
- package/src/model/batched-row-storage.ts +52 -0
- package/src/model/batched-ssm.ts +52 -0
- package/src/model/batched-turboquant-kv.ts +93 -0
- package/src/model/compiled-decode.ts +7 -1
- package/src/model/decoded-kv-donor.ts +12 -0
- package/src/model/delayed-quantized-kv.ts +72 -0
- package/src/model/delayed-rotating-quantized-kv.ts +93 -0
- package/src/model/delayed-turboquant-kv.ts +55 -0
- package/src/model/full-prefill-padding.ts +56 -0
- package/src/model/full-prefill-row.ts +105 -0
- package/src/model/gemma4-base.ts +162 -80
- package/src/model/gemma4.ts +34 -26
- package/src/model/generated/gemma4-12b.ts +4 -1
- package/src/model/generated/gemma4-26b.ts +4 -1
- package/src/model/generated/gemma4-e4b.ts +4 -1
- package/src/model/glm52-cache.ts +80 -4
- package/src/model/glm52-mla.ts +12 -6
- package/src/model/glm52.ts +19 -40
- package/src/model/kv-attention-view.ts +67 -0
- package/src/model/minicpm5.ts +11 -7
- package/src/model/qwen3-delta-state.ts +74 -0
- package/src/model/qwen3-delta.ts +59 -36
- package/src/model/qwen3_5.ts +82 -42
- package/src/model/rotating-kv-donor.ts +35 -0
- package/src/model/rotating-kv-layout.ts +262 -0
- package/src/model/rotating-row-transaction.ts +35 -0
- package/src/model/speculative-rotating-kv.ts +101 -0
- package/src/model/ssm-prefill-padding.ts +39 -0
- package/src/model/trellis-linear.ts +2 -2
- package/src/model/turboquant-codec.ts +83 -0
- package/src/model/universal/dense.ts +14 -13
- package/src/preflight.ts +31 -15
- package/src/prompt-cache.ts +42 -44
- package/src/sampler.ts +36 -25
- package/src/serve/chat-stage.ts +2 -2
- package/src/serve/generation-gateway.ts +39 -30
- package/src/serve/model-host.ts +3 -4
- package/src/serve/request-plan.ts +4 -3
- package/src/serve/request-prep.ts +2 -4
- package/src/serve/text-completion-stage.ts +2 -2
- package/src/server.ts +64 -108
- package/src/spec/assistant-rows.ts +92 -0
- package/src/spec/assistant-source.ts +28 -18
- package/src/spec/cache-identity.ts +22 -0
- package/src/spec/deepspec-rows.ts +29 -0
- package/src/spec/deepspec-source.ts +12 -10
- package/src/spec/dflash-source.ts +28 -4
- package/src/spec/drafter.ts +153 -142
- package/src/spec/dspark/deepspec-module.ts +95 -120
- package/src/spec/dspark/module-dflash.ts +126 -170
- package/src/spec/glm52-mtp-graph.ts +29 -0
- package/src/spec/glm52-mtp-rows.ts +132 -0
- package/src/spec/glm52-mtp-source.ts +53 -136
- package/src/spec/glm52-mtp-state.ts +33 -0
- package/src/spec/ngram-source.ts +92 -20
- package/src/spec/projected-draft-rows.ts +73 -0
- package/src/spec/qwen-mtp-module.ts +171 -0
- package/src/spec/qwen-mtp-rows.ts +219 -0
- package/src/spec/qwen-mtp-source.ts +81 -226
- package/src/spec/qwen-mtp-state.ts +30 -0
- package/src/spec/serve-loop.ts +70 -68
- package/src/spec/source.ts +80 -15
- package/src/spec/two-model-rows.ts +61 -0
- package/src/spec/two-model.ts +43 -101
- package/src/ssd-cache.ts +33 -21
- package/src/ssd-durability.ts +11 -7
- package/src/web/app.js +57 -57
- package/src/backends/mlx/memory-guard.ts +0 -40
- package/src/spec/prefix-state.ts +0 -45
- /package/docs/{planning → archive/planning}/release-notes-v0.3.0.md +0 -0
|
@@ -23,8 +23,12 @@ Two tiers, one contract:
|
|
|
23
23
|
|
|
24
24
|
- **Tier-0 GENERIC** — one config-driven "universal" module per architecture
|
|
25
25
|
*family*. Runs any covered checkpoint at **L1 parity** (bit-exact vs mlx-lm
|
|
26
|
-
on this machine's GPU). Monolith path only
|
|
27
|
-
|
|
26
|
+
on this machine's GPU). Monolith path only, without compiled decode.
|
|
27
|
+
Standard attention now uses the shared plain/affine KV port; the softcap
|
|
28
|
+
branch retains plain KV. Qualified full-attention targets compose delayed
|
|
29
|
+
affine conversion with standalone and lookup drafting through the same
|
|
30
|
+
cache transaction interface; rotating layouts use that interface for
|
|
31
|
+
per-layer conversion and assistant donors. Built: `src/model/universal/` (dense
|
|
28
32
|
llama-family). Design only: UniversalMoE.
|
|
29
33
|
- **Tier-1 TARGETED** — a model *graduates*: dedicated or generated forward,
|
|
30
34
|
L2 (mlx-optiq) composition where an optiq artifact exists, Lab experiments
|
|
@@ -200,7 +204,9 @@ Key choices:
|
|
|
200
204
|
or missing tensors are a LOAD ERROR naming them, never a silently-wrong
|
|
201
205
|
model.
|
|
202
206
|
- **KV cache** — `KVCache`/`RotatingKVCache` unchanged; `layer_types` /
|
|
203
|
-
`sliding_window` already normalized.
|
|
207
|
+
`sliding_window` already normalized. Standard attention consumes the shared
|
|
208
|
+
cache-attention interface; qualified Llama uniform KV4/KV8 serving and
|
|
209
|
+
generated-cache coverage are described in batching.md.
|
|
204
210
|
- **RuntimeModel surface** — the minimal contract `Qwen3Model` implements
|
|
205
211
|
(config, weightsBytes, loraState, makeCache, forwardHidden,
|
|
206
212
|
logitsFromHidden, forward, generate, loraTargets) so serving/eval/LoRA
|
|
@@ -336,6 +342,72 @@ design-relevant facts; phase logs live in PLAN.md at the named anchor.
|
|
|
336
342
|
- Training reference model for segmented backward and ORPO
|
|
337
343
|
(docs/design/orpo-training.md, orpo-training.md).
|
|
338
344
|
|
|
345
|
+
#### 6.2.1 MiniCPM5-2B investigation
|
|
346
|
+
|
|
347
|
+
Metadata and template inspection completed 2026-09-10. No weights were
|
|
348
|
+
downloaded and no GPU inference was run. This checkpoint is a candidate,
|
|
349
|
+
not an artifact with established parity or local performance results.
|
|
350
|
+
|
|
351
|
+
Start with the official [OpenBMB MLX artifact](https://huggingface.co/openbmb/MiniCPM5-2B-MLX),
|
|
352
|
+
revision `3d00c3da500debfe345e60e25a87ed2669f5b1ee`. Its config uses standard
|
|
353
|
+
Llama blocks, 42 layers, hidden size 2048, intermediate size 6144, 16 query
|
|
354
|
+
and 2 KV heads of dimension 128, untied embeddings, vocab 130560 and
|
|
355
|
+
RoPE theta 5000000. Quantization is affine 4-bit with group size 64.
|
|
356
|
+
Preserve both EOS IDs, `1` and `130073`, and the separate
|
|
357
|
+
`chat_template.jinja`. The release is text-only and Apache-2.0.
|
|
358
|
+
|
|
359
|
+
Current integration findings:
|
|
360
|
+
|
|
361
|
+
- `isMiniCPM5Config` in `src/model/support.ts` matches the older 24-layer,
|
|
362
|
+
hidden-1536 artifact. The 2B config fails that predicate and qualifies for
|
|
363
|
+
generic Llama through `genericArgsFor`; `familyProfile` therefore selects
|
|
364
|
+
the universal graph. Do not widen the predicate before auditing dedicated
|
|
365
|
+
execution, training and cache assumptions against this shape.
|
|
366
|
+
- A CPU-only smoke using the pinned config and template passed
|
|
367
|
+
`loadModelConfig`, `genericArgsFor`, `ChatTemplate.load` and rendering a
|
|
368
|
+
user → assistant tool call → tool result → user conversation. Explicit
|
|
369
|
+
thinking true/false both render correctly; the XML function call survives.
|
|
370
|
+
This checks rendering, not generated tool-call parsing or tokenizer parity.
|
|
371
|
+
- `resolveEnableThinking` applies the MiniCPM no-thinking default through
|
|
372
|
+
the same exact-shape predicate. It does not apply to 2B. With no explicit
|
|
373
|
+
option, this artifact's prompt ends at the assistant header; true appends
|
|
374
|
+
an open think tag, false appends a closed empty think block. Set this
|
|
375
|
+
explicitly in comparisons. Template support for disabling thinking does
|
|
376
|
+
not establish no-thinking quality.
|
|
377
|
+
|
|
378
|
+
Evaluation sequence:
|
|
379
|
+
|
|
380
|
+
1. Pin an 8-bit or BF16 control alongside the official 4-bit candidate.
|
|
381
|
+
Establish tokenizer/prompt identity and same-machine logits against the
|
|
382
|
+
pinned mlx-lm oracle, then test multi-turn tool calls, reasoning splits,
|
|
383
|
+
EOS, cache reuse and batched continuation. Keep quantization quality
|
|
384
|
+
comparisons separate from same-artifact runtime parity.
|
|
385
|
+
2. Compare with MiniCPM5-1B and Gemma e4b on held-out Dreaming segmentation,
|
|
386
|
+
extraction and source-grounded synthesis, plus bounded repository/tool
|
|
387
|
+
tasks. Score boundary/label accuracy and completed tasks, not just valid
|
|
388
|
+
JSON or XML. Use explicit thinking modes and equal token/time budgets.
|
|
389
|
+
3. Record first-answer latency, complete task time, output/reasoning tokens,
|
|
390
|
+
repetition and truncation rates, memory and context scaling. Local
|
|
391
|
+
measurements belong only in `docs/reference/benchmarks.md`, with a named
|
|
392
|
+
quiet machine and the required serving benchmark procedure.
|
|
393
|
+
4. Investigate the [released DSpark draft](https://huggingface.co/openbmb/MiniCPM5-2B-DSpark)
|
|
394
|
+
after ordinary inference passes. Its config declares `Qwen3DSparkModel`,
|
|
395
|
+
five draft layers, block size seven, and target taps `[1,10,20,30,39]`.
|
|
396
|
+
Existing Gemma DSpark support does not establish compatibility. Audit
|
|
397
|
+
the draft's architecture, target taps, weights and verifier before a
|
|
398
|
+
paired task-time comparison.
|
|
399
|
+
|
|
400
|
+
Evidence to use when designing the quality checks:
|
|
401
|
+
[OpenBMB's model card](https://huggingface.co/openbmb/MiniCPM5-2B) distinguishes
|
|
402
|
+
internally reproduced scores from Artificial Analysis results. The
|
|
403
|
+
[independent September 7 evaluation](https://artificialanalysis.ai/articles/openbmb-releases-minicpm5-2b)
|
|
404
|
+
supports strong capability for its size but reports substantial reasoning
|
|
405
|
+
token use and weak absolute terminal-task success. These evaluations do not
|
|
406
|
+
validate MLX 4-bit quality. [Upstream issue 374](https://github.com/OpenBMB/MiniCPM/issues/374)
|
|
407
|
+
reports repetition sensitivity in GGUF coding runs. That is an unreproduced
|
|
408
|
+
cross-runtime hypothesis for our sweep, not evidence of the same MLX failure
|
|
409
|
+
or a reason to change repetition defaults.
|
|
410
|
+
|
|
339
411
|
### 6.3 Qwen3 / Qwen3-MoE / Qwen3.5-family (3.6, 3.8) — `qwen3.ts`, `qwen3-moe.ts`, `qwen3_5.ts`
|
|
340
412
|
|
|
341
413
|
- **Qwen3** (`model_type: qwen3`): dense GQA with per-head q/k RMSNorm
|
|
@@ -627,3 +699,7 @@ non-goals.
|
|
|
627
699
|
- 2026-08-23 — This doc consolidated: audio-input-plan,
|
|
628
700
|
diffusion-gemma-port, minicpm5-decode-megakernel, and the Colibri archive
|
|
629
701
|
summarized here; ports become sections.
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
Non-Qwen grouped TurboQuant drafting, including assistant donor attention, uses
|
|
705
|
+
the shared cache interfaces. See [composition coverage](batching.md#turboquant-donor-attention-and-grouped-draft-composition).
|
package/docs/design/kv-cache.md
CHANGED
|
@@ -3,7 +3,7 @@ status: landed
|
|
|
3
3
|
axis: ON
|
|
4
4
|
canonical-for: kv-cache
|
|
5
5
|
plan-anchor: "Phase 18 — Concurrent / batched serving (slots) + parallel load benchmark `[~]` (2026-06-13)"
|
|
6
|
-
last-verified: 2026-
|
|
6
|
+
last-verified: 2026-09-09
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# KV cache — residency and layout
|
|
@@ -42,11 +42,10 @@ its KV encodes:
|
|
|
42
42
|
The prompt cache tiers over the SSD store *inside* `take()` (structural
|
|
43
43
|
`ColdTier` interface), so every consumer (serial lane, batch scheduler
|
|
44
44
|
joiners, `/admin/drain`) gets both tiers through the same `take()`/`put()`
|
|
45
|
-
calls.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
generation (section 5.7). Paged KV
|
|
45
|
+
calls. Requests and evictions enqueue persistence work. The writer performs
|
|
46
|
+
it when the engine is idle; RAM reuse does not enqueue an extra write or wait
|
|
47
|
+
for the queue. Explicit flushes await the queued work. The opt-in `--generation-checkpoint N` path transfers owned snapshots at coarse decode boundaries and queues atomic
|
|
48
|
+
persistence without awaiting disk in decode (section 5.7). Paged KV
|
|
50
49
|
(section 6) is a *layout* choice for the live tier and, in v1, bypasses the
|
|
51
50
|
other two.
|
|
52
51
|
|
|
@@ -114,10 +113,10 @@ Two independent checks decide whether a model/scheme can batch
|
|
|
114
113
|
`KVCache`, `RotatingKVCache`, `BatchableCache`, or (unless
|
|
115
114
|
`MLX_BUN_BATCH_SSM=0`) `SSMCache`. Novel classes (TurboQuant, paged) fail
|
|
116
115
|
this by construction.
|
|
117
|
-
- `#kvBatchable()` — `KvScheme.batchable(config, canConvert)`:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
- `#kvBatchable()` — `KvScheme.batchable(config, canConvert)`: per-layer
|
|
117
|
+
`affine-config` and uniform affine KV4/KV8 batch when their cache-owning
|
|
118
|
+
layers can convert. TurboQuant still requires merged-row layout support;
|
|
119
|
+
a scheme-less gateway must never silently
|
|
121
120
|
drop quantization (`kv-scheme.ts` makes the capability probe mandatory
|
|
122
121
|
for quantized schemes).
|
|
123
122
|
|
|
@@ -210,16 +209,23 @@ upstream is byte-capped now.
|
|
|
210
209
|
**`take(prompt, ns)` — prefix sharing.** Finds the entry with the longest
|
|
211
210
|
usable common prefix (usable = common prefix capped at `prompt.length - 1`,
|
|
212
211
|
so at least one token is forwarded; an entry longer than the match must be
|
|
213
|
-
trimmable).
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
212
|
+
trimmable). Every RAM hit lends zero-copy views (`cloneKvCaches` and
|
|
213
|
+
`cloneAttachments`) with a ref-counted backing share; the donor remains in
|
|
214
|
+
place. This includes exact-boundary recurrent and method state. Only
|
|
215
|
+
trimmable state may be shortened. Decode extends its own views without
|
|
216
|
+
changing the retained checkpoint.
|
|
217
|
+
|
|
218
|
+
SSD is an eventually consistent persistence layer. RAM reuse never writes
|
|
219
|
+
to SSD or waits for durability. Background writes retain immutable snapshot
|
|
220
|
+
views and yield between tensor writes; explicit flush and graceful shutdown
|
|
221
|
+
wait for persistence. A pending or failed write does not prevent RAM reuse.
|
|
222
|
+
The existing byte cap controls retained donors and LRU eviction.
|
|
223
|
+
|
|
224
|
+
The native sharing gate checks logical checkpoint bytes after repeated decode
|
|
225
|
+
and after SSD restore. It passes on both development Macs; the M1 long-prefix
|
|
226
|
+
case also preserves three independent 512-token continuations. The queued
|
|
227
|
+
writer test retains snapshot state after both donor and borrower disposal,
|
|
228
|
+
and explicit flush remains pending until the write completes.
|
|
223
229
|
|
|
224
230
|
**Tier order inside `take()`.** The cold tier is consulted with
|
|
225
231
|
`find(prompt, ns)` (index-only, no I/O) and wins only with a *strictly*
|
|
@@ -293,8 +299,9 @@ full prefill (GEMV-vs-GEMM reduction order). Both tiers share it.
|
|
|
293
299
|
`PromptCache` (section 4); restart persistence comes from a debounced
|
|
294
300
|
write-behind snapshot after each put, not only from spill-on-evict.
|
|
295
301
|
- **D3 — Own page-aligned format, not safetensors.** `src/kv-store.ts`
|
|
296
|
-
writes `MLXBUNKV2\n`-magic files
|
|
297
|
-
|
|
302
|
+
writes `MLXBUNKV2\n`-magic files with a streaming writer: ordinary KV
|
|
303
|
+
retains format v3; checkpoints with method companion state use v4.
|
|
304
|
+
The header is sized up front with fixed-width tensor
|
|
298
305
|
hashes, tensors materialized/hashed/written one at a time at 16 KiB
|
|
299
306
|
aligned offsets, header patched last (peak host transient = one
|
|
300
307
|
tensor). Header carries `formatVersion`, `modelId`,
|
|
@@ -344,10 +351,13 @@ full prefill (GEMV-vs-GEMM reduction order). Both tiers share it.
|
|
|
344
351
|
`--memory-budget` is unchanged (admission already assumes worst-case
|
|
345
352
|
bf16). `--paged-kv` warns that the tier sees nothing (section 6).
|
|
346
353
|
- **D6 — In-flight resume is explicit.** `--generation-checkpoint <tokens>`
|
|
347
|
-
requires `--ssd-cache` and
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
prompt or sampling policy starts normally.
|
|
354
|
+
requires `--ssd-cache` and supports eligible shared ordinary execution. It
|
|
355
|
+
is off by default. Captured states queue for SSD persistence; an explicit
|
|
356
|
+
flush establishes durability of queued work (section 5.7). An identical request resumes; a changed
|
|
357
|
+
prompt or sampling policy starts normally. Ordinary adapter requests use the
|
|
358
|
+
same continuation hooks under their existing execution context. The checkpoint
|
|
359
|
+
identity includes the ordered adapter IDs and content-revision namespace; a
|
|
360
|
+
base-model request or replacement adapter cannot restore that checkpoint.
|
|
351
361
|
|
|
352
362
|
### 5.2 Restore is a streamed copy (supersedes the original zero-copy plan)
|
|
353
363
|
|
|
@@ -392,22 +402,21 @@ Every per-tensor flush step is real GPU-stream and JS-thread work
|
|
|
392
402
|
between decode tokens taxed decode at long context, so:
|
|
393
403
|
|
|
394
404
|
- `saveKvCacheAsync` / `SsdCacheStore.storeAsync` accept a per-step runner.
|
|
395
|
-
The server passes `gateway.
|
|
396
|
-
blocking MLX readback are one atomic engine turn.
|
|
405
|
+
The server passes `gateway.runWhenIdle`, so the idle decision and the
|
|
406
|
+
blocking MLX readback are one atomic engine turn. Background work waits
|
|
407
|
+
without registering a serial waiter or draining active batches. The earlier
|
|
397
408
|
`gateway.onIdle()` check had a check/use race: a request could begin after
|
|
398
409
|
the check and overlap the next tensor sync. A request arriving mid-flush
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
snapshot clones release their buffers before the next prefill.
|
|
410
|
+
can run between tensor writes. Neither execution lane drains pending
|
|
411
|
+
durability work before generation.
|
|
402
412
|
- `GenerationGateway.busy` covers both lanes (serial mutex held/awaited or
|
|
403
413
|
batch rows active/pending).
|
|
404
414
|
- `MLX_BUN_SSD_WRITEBEHIND=0` disables write-behind snapshots entirely
|
|
405
415
|
(kill switch + paired-A/B lever; restart survival then degrades to
|
|
406
416
|
spill-on-evict).
|
|
407
417
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
same lock. Continuous batching retains per-tensor scheduling.
|
|
418
|
+
Explicit flush and shutdown drain outstanding writes outside the generation
|
|
419
|
+
lock because the writer needs that same lock. Ordinary requests do not.
|
|
411
420
|
|
|
412
421
|
The RAM cache's configured byte cap is only an upper bound. Serial serving
|
|
413
422
|
checks MLX's live allocation count against the smaller of admission's usable
|
|
@@ -446,7 +455,7 @@ the write — the server implied durability it did not have.
|
|
|
446
455
|
- `schedule(tokens, ns)` records a *dirty* key that includes the exact
|
|
447
456
|
token sequence (two same-ns, same-length conversations cannot cancel
|
|
448
457
|
each other) and arms the debounce.
|
|
449
|
-
- An attempt checks SSD coverage under `gateway.
|
|
458
|
+
- An attempt checks SSD coverage under `gateway.runWhenIdle` before cloning.
|
|
450
459
|
Otherwise it snapshots the RAM entry (`findExact` + `cloneKvCaches`) and
|
|
451
460
|
enqueues it; a busy gateway re-arms;
|
|
452
461
|
a dropped or failed store leaves the key dirty so it is retryable.
|
|
@@ -495,13 +504,128 @@ IDs and every sampling/KV policy field. Grammar, token-fill, media, paged KV,
|
|
|
495
504
|
and logprob requests are not checkpointed in v1 because they carry additional
|
|
496
505
|
state or response data that cannot yet be reconstructed by token replay.
|
|
497
506
|
|
|
507
|
+
Ordinary continuation adds an optional request port to
|
|
508
|
+
that same ordinary driver. Checkpoint lookup, keys and persistence are shared
|
|
509
|
+
with the compatibility executor; the numerical driver supplies owned row
|
|
510
|
+
snapshots at an emitted-prefix/pending-token boundary. Restored rows complete
|
|
511
|
+
preparation without a target forward, before merging cold prefill rows, and
|
|
512
|
+
recover sampler seed, history and sample index. The server binding qualifies
|
|
513
|
+
ordinary requests through supported row-cache layouts, including recurrent Qwen
|
|
514
|
+
and full-attention Llama/MiniCPM as well as Gemma4. It transfers owned row
|
|
515
|
+
snapshots to a bounded persistence coordinator without awaiting disk in decode.
|
|
516
|
+
Tensor serialization and file maintenance use the existing idle gate. Completion
|
|
517
|
+
invalidates reuse immediately and queues cleanup after any in-flight write;
|
|
518
|
+
new attempts cannot be erased by older cleanup. Cancellation retains eventual
|
|
519
|
+
persistence, and explicit flush reports failed or pending durability. A hard
|
|
520
|
+
process exit may lose queued intervals. Plain greedy retains the ordinary
|
|
521
|
+
vectorized sampler when no history-dependent processors are active. Supported KV layouts retain their existing affine, per-layer and full-attention
|
|
522
|
+
TurboQuant conversion policy. Requests remain without
|
|
523
|
+
media, grammar, fill, paging, logprobs or speculative methods. Native tests
|
|
524
|
+
compare interrupted/restored runs at the same batch geometry; arbitrary
|
|
525
|
+
cohort changes are not an exact numerical identity guarantee. The HTTP fixture
|
|
526
|
+
checks serial B1, shared B1 and shared B4 with identical seeded requests and
|
|
527
|
+
held arrivals, and inspects the generated checkpoint before restart.
|
|
528
|
+
When checkpointing is enabled, the existing total spill queue budget is split
|
|
529
|
+
between prompt spills and continuation snapshots. Flush duration and pending/failed
|
|
530
|
+
statistics cover both queues.
|
|
531
|
+
|
|
498
532
|
Only the newest successful checkpoint for a request is retained. The old file
|
|
499
|
-
remains valid until the new file's fsync+rename completes
|
|
500
|
-
|
|
533
|
+
remains valid until the new file's fsync+rename completes. Queue backpressure
|
|
534
|
+
can leave more than one interval undurable; a crash loses work since the latest
|
|
535
|
+
successful write. Normal completion immediately invalidates the checkpoint and
|
|
536
|
+
queues its disk removal.
|
|
501
537
|
Client cancellation throws through the serial token callback and preserves the
|
|
502
538
|
last checkpoint, so terminating a long-lived client does not erase its restart
|
|
503
539
|
point.
|
|
504
540
|
|
|
541
|
+
Fresh-model compiled continuation preserves the device sampler's uint32 pending
|
|
542
|
+
register. The Gemma model materializes its computed FP32 RoPE frequencies once
|
|
543
|
+
before compiled graph capture: a restored request may skip the prefill that
|
|
544
|
+
normally evaluates those constants. Capturing their unevaluated construction
|
|
545
|
+
graph can change arithmetic on M1. The forced-token continuation regression
|
|
546
|
+
compares a warmed model and a fresh model with cloned state, including every
|
|
547
|
+
active cache byte and full logits across delayed KV conversion. Live delayed
|
|
548
|
+
row layouts publish compact state through their extraction interface before
|
|
549
|
+
persistence; they do not need storage codecs for the live wrapper itself.
|
|
550
|
+
|
|
551
|
+
### Method companion state
|
|
552
|
+
|
|
553
|
+
`PrefixCache<State, Attachment>` is the shared consumer contract for ordinary
|
|
554
|
+
and batched execution. It has no scheduling or storage-tier operations. The
|
|
555
|
+
MLX binding supplies target cache layers plus optional `CheckpointAttachment`
|
|
556
|
+
records containing a method schema, scalar metadata and tensor snapshots.
|
|
557
|
+
The method owns their meaning and alignment; the shared cache owns retention.
|
|
558
|
+
Method and drafter compatibility must be part of the namespace before a
|
|
559
|
+
producer publishes these records. Storage does not interpret draft state as
|
|
560
|
+
extra target-model layers.
|
|
561
|
+
|
|
562
|
+
RAM accounting, eviction, idle demotion and the bounded SSD spill queue include
|
|
563
|
+
companion tensors. Checkpoints containing them require an exact prefix
|
|
564
|
+
boundary and preserve shorter fallback entries. Write-behind captures companion
|
|
565
|
+
views together with target-cache views under the existing exclusive boundary.
|
|
566
|
+
The writer persists them in the same atomic file and the same per-tensor write
|
|
567
|
+
loop. Restore copies and verifies them through the existing tensor reader.
|
|
568
|
+
Ordinary files retain v3 compatibility; older readers reject v4 files rather
|
|
569
|
+
than restore an incomplete checkpoint.
|
|
570
|
+
|
|
571
|
+
The storage implementation is in the working tree. Synthetic gates cover RAM
|
|
572
|
+
ownership, divergent-prefix fallback, SSD eviction, fresh-process restore,
|
|
573
|
+
corruption, queue drops/failures and durability on both development Macs.
|
|
574
|
+
Qwen MTP now uses this interface for prefill snapshots; its private store and
|
|
575
|
+
separate budget option are removed. `DraftSource.checkpoint` supplies the draft
|
|
576
|
+
KV and pending true hidden row. The namespace includes a SHA-256 of draft
|
|
577
|
+
configuration and shard bytes, resolved at provider load, plus the mounted
|
|
578
|
+
adapter revision and KV policy. Model loading resolves that digest through
|
|
579
|
+
`artifactIdentity`, independently of inference and scheduling. A memo key includes
|
|
580
|
+
the configuration seed, sorted shard names, resolved paths, device/inode, size,
|
|
581
|
+
and nanosecond modification/change times. Unchanged revisions reuse the exact
|
|
582
|
+
existing SHA-256; a miss reads every shard byte. Same-size writes with restored
|
|
583
|
+
mtime and changed symlink targets invalidate the memo. Rechecking revisions after
|
|
584
|
+
hashing prevents persisting a digest under a revision that changed during reading.
|
|
585
|
+
|
|
586
|
+
`ArtifactIdentityStore` owns memo retention and persistence. Its file adapter uses
|
|
587
|
+
`~/.cache/mlx-bun/artifact-identities/`, keeps RAM hits and queues atomic file writes
|
|
588
|
+
without awaiting them in model loading. Missing, malformed or unwritable memo
|
|
589
|
+
storage does not prevent computing the identity. It stores only digests, not
|
|
590
|
+
weights, prompts or KV. Qwen MTP, standalone, assistant, DeepSpec and DSpark use
|
|
591
|
+
the same identity service; existing namespaces and SSD entries remain compatible.
|
|
592
|
+
Tests cover byte identity, no weight reads after a fresh-store memo hit, file
|
|
593
|
+
mutation, configuration changes, symlink replacement and write failure.
|
|
594
|
+
|
|
595
|
+
Native bf16/KV4 gates on M1 Max and M4 Pro
|
|
596
|
+
preserve live target/companion state hashes, emitted IDs and acceptance decisions
|
|
597
|
+
through repeated RAM reuse and SSD cache/provider restart. SSD allocation
|
|
598
|
+
padding is excluded from the live-state comparison. Generated-output acceptance
|
|
599
|
+
is described below; long-conversation timing remains R17 work.
|
|
600
|
+
|
|
601
|
+
The unreleased shared method now publishes completed row state before
|
|
602
|
+
retirement through the same `PrefixCache.put` port. It records only inputs
|
|
603
|
+
retained by committed target/draft rounds; an unprocessed correction or bonus
|
|
604
|
+
token is absent from the key. The target row layout and draft provider supply
|
|
605
|
+
their snapshots independently. Failed and cancelled consumers publish no
|
|
606
|
+
generated checkpoint. Persistence continues through the existing queue.
|
|
607
|
+
|
|
608
|
+
M1 and M4 native bf16, affine KV4 and fused TurboQuant K8/V3 tests cover four active
|
|
609
|
+
rows, early retirement, exact sampled-ID coverage, unchanged retained bytes
|
|
610
|
+
after sibling and follow-up work, shorter-prefix selection after an edit, and
|
|
611
|
+
matching RAM/SSD continuation and acceptance. Existing paired-prefill tests
|
|
612
|
+
also pass. A second native gate records actual sampled IDs for an echo tool
|
|
613
|
+
turn with thinking off/on, passes them through the production output parsers,
|
|
614
|
+
and renders the subsequent tool-result request with the same chat template.
|
|
615
|
+
Both published checkpoints are exact token prefixes of that next request.
|
|
616
|
+
A production HTTP gate now passes on both Macs for bf16, KV4 and fused K8/V3, with
|
|
617
|
+
thinking off/on. The next tool-result request reuses generated content in RAM.
|
|
618
|
+
After a durable flush, a separate server process restores the first turn's
|
|
619
|
+
SSD state and reproduces the same response, logprobs and acceptance. The test
|
|
620
|
+
copies only the first turn's flushed files before running the RAM continuation,
|
|
621
|
+
so no later prefill checkpoint can explain the restart hit. All six cases
|
|
622
|
+
pass, including the actual wire `reasoning` alias and template options.
|
|
623
|
+
The five-file change is adopted alongside the captured prefill-policy fix.
|
|
624
|
+
Native acceptance of that final composition, long saved Kanban replay,
|
|
625
|
+
pressure and complete next-turn timing remain open.
|
|
626
|
+
It does not resolve the older serial prototype's bf16 comparison with fresh
|
|
627
|
+
full prefill, whose operation shapes differ from a generated history.
|
|
628
|
+
|
|
505
629
|
### 5.8 Invalidation
|
|
506
630
|
|
|
507
631
|
Compatibility key = `configFingerprint(config)` + `KvScheme.cacheKey` +
|
|
@@ -555,10 +679,10 @@ already shipped, so what transfers is: (a) padded-batch waste removal —
|
|
|
555
679
|
today's batched buffer width tracks the longest live row
|
|
556
680
|
(`mergeKVRows`/`extendKVRows`), so a short row cohabiting with a long one
|
|
557
681
|
pays the long one's KV; and (b) block-level copy-on-write prefix sharing.
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
682
|
+
Persistent pools now stay separate per request under the shared executor.
|
|
683
|
+
Attention still gathers and pads a temporary batch tensor; block-level prefix
|
|
684
|
+
sharing and direct paged attention remain open. At batch one, the gather adds
|
|
685
|
+
a copy. Paging remains optional and does not imply a speed improvement.
|
|
562
686
|
|
|
563
687
|
This is neither of the two paging rejections already on record:
|
|
564
688
|
paged KV as a *prompt-cache substitute* (rejected 2026-07-07 — `take()`'s
|
|
@@ -584,8 +708,8 @@ greedy trajectory paged-on vs paged-off).
|
|
|
584
708
|
`[block, 0, within, 0]` — the incoming `[1,H,l,D]` piece matches the
|
|
585
709
|
destination slice directly.
|
|
586
710
|
- **`PagedKVCache implements Cache`** — standalone (not a `KVCache`
|
|
587
|
-
subclass
|
|
588
|
-
|
|
711
|
+
subclass). The cache-layout binding supplies `PagedKvRows` for dynamic
|
|
712
|
+
membership, masks and per-row positions. `updateAndFetch` splits the
|
|
589
713
|
incoming `L` along block boundaries, allocates tail blocks as reached,
|
|
590
714
|
then gathers occupied blocks with `ops.takeAxis(pool, blockTable, 0)` →
|
|
591
715
|
transpose `[1,0,2,3]` → reshape `[1,H,nb·bs,D]` → slice to `offset`. Only
|
|
@@ -599,7 +723,8 @@ greedy trajectory paged-on vs paged-off).
|
|
|
599
723
|
prompt + `max_tokens`, so exhaustion is unreachable absent an
|
|
600
724
|
accounting bug — it exists as a tripwire, never silent truncation.
|
|
601
725
|
- **Wiring** — `maybePageKv(cache, options, capacityTokens)`
|
|
602
|
-
(`
|
|
726
|
+
(`backends/mlx/request-state-policy.ts`, re-exported by `generate.ts`)
|
|
727
|
+
mirrors `maybeQuantizeKv`'s post-construction in-place
|
|
603
728
|
swap but runs *once before prefill*: paging changes layout, not
|
|
604
729
|
arithmetic, so there is no "convert when populated" trigger. Only fresh
|
|
605
730
|
(`offset === 0`) plain `KVCache` entries are replaced; rotating layers
|
|
@@ -610,32 +735,34 @@ greedy trajectory paged-on vs paged-off).
|
|
|
610
735
|
|
|
611
736
|
### 6.3 Gates (explicit refusals, never silent downgrades)
|
|
612
737
|
|
|
613
|
-
- Startup (`server.ts`): `--paged-kv` with
|
|
738
|
+
- Startup (`server.ts`): `--paged-kv` with any `--kv-quant`
|
|
614
739
|
(affine or TurboQuant), `--draft-model`, or a non-`gemma4*` model exits
|
|
615
740
|
with a clear message; `--paged-kv-block-size` must be a positive
|
|
616
741
|
integer; `--ssd-cache` alongside it warns that the tier sees nothing.
|
|
617
|
-
|
|
618
|
-
|
|
742
|
+
Paging preserves the selected batch size. Env `MLX_BUN_PAGED_KV=1`
|
|
743
|
+
is equivalent to the flag.
|
|
619
744
|
- Request scope: media (vision/audio) and LoRA-adapter requests strip the
|
|
620
745
|
flag and run the plain cache path (v1 non-goal cells, never a 400). One
|
|
621
746
|
effective `pagedKv` value per request keeps the strip and the
|
|
622
747
|
prompt-cache bypass coherent.
|
|
623
|
-
-
|
|
748
|
+
- Both execution modes: paged requests skip `take`/`put` and the boundary
|
|
624
749
|
snapshot; caches are disposed on completion. Spec eligibility also
|
|
625
750
|
excludes `options.pagedKv` (belt on top of the startup refusal).
|
|
626
751
|
- Compiled decode: `CompiledDecode.supports()` excludes `PagedKVCache`
|
|
627
752
|
automatically (not one of the four supported classes) — a
|
|
628
753
|
data-dependent block-list length is the shape shapeless replay already
|
|
629
754
|
broke on.
|
|
630
|
-
-
|
|
631
|
-
|
|
755
|
+
- Shared execution: `PagedKvRows` owns independent block pools and immutable
|
|
756
|
+
row snapshots. A request-state policy creates the storage and supplies no
|
|
757
|
+
reusable-prefix store. Its opaque compatibility key keeps incompatible
|
|
758
|
+
layouts in separate cohorts without making scheduling inspect paging flags.
|
|
632
759
|
|
|
633
760
|
### 6.4 Follow-ups (dependency order) and open items
|
|
634
761
|
|
|
635
|
-
1.
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
762
|
+
1. Direct paged attention and block sharing — row integration now uses the
|
|
763
|
+
shared layout interface with independent per-request pools. Attention still
|
|
764
|
+
gathers padded contiguous inputs. Measure block-level shared arenas and
|
|
765
|
+
direct reads before replacing this compatibility implementation. Tracked under the plan anchor (Phase 18
|
|
639
766
|
S3+).
|
|
640
767
|
2. Block-level CoW prefix sharing — refcounted block table, fork on
|
|
641
768
|
divergent write; extends `PromptCache`'s entry-level ref-counting down
|
|
@@ -678,3 +805,20 @@ greedy trajectory paged-on vs paged-off).
|
|
|
678
805
|
- Paged KV v1 (serial, Gemma4, bf16, gather-then-SDPA) landed with
|
|
679
806
|
bit-exact gates; flag rows in cli.md / server-config.md (landing date
|
|
680
807
|
not recorded in the source doc, which still reads "in progress").
|
|
808
|
+
|
|
809
|
+
## Minimum reusable offsets
|
|
810
|
+
|
|
811
|
+
Physical trimmability does not prove that an older prefix has a valid
|
|
812
|
+
numerical representation. Caches may expose `minimumReusableOffset` after
|
|
813
|
+
an irreversible transition, such as delayed TurboQuant conversion. Prefix
|
|
814
|
+
selection requires the matched offset to meet every layer's bound. A
|
|
815
|
+
trimmable descendant supersedes an ancestor only when it can serve that
|
|
816
|
+
ancestor's offset; otherwise the earlier donor remains.
|
|
817
|
+
|
|
818
|
+
Cloning and per-row extraction preserve this state metadata. New SSD
|
|
819
|
+
headers record it, and indexing, durable-prefix checks and restore use the
|
|
820
|
+
same rule as RAM. Legacy TurboQuant headers without a recorded boundary
|
|
821
|
+
are conservatively reusable from their full stored offset only. Other
|
|
822
|
+
legacy cache kinds retain their existing interpretation. This changes
|
|
823
|
+
reuse eligibility, not scheduling, quantization arithmetic or memory
|
|
824
|
+
admission policy.
|