mlx-bun 0.0.13 → 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.
Files changed (427) hide show
  1. package/README.md +71 -578
  2. package/bin/mlx-bun.mjs +1 -1
  3. package/docs/README.md +91 -0
  4. package/docs/{design → archive}/hlg-sampling.md +3 -3
  5. package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
  6. package/docs/{design → archive/investigations}/colibri-glm52-port.md +1 -1
  7. package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
  8. package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
  9. package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
  10. package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
  11. package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
  12. package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
  13. package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
  14. package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
  15. package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
  16. package/docs/{planning → archive/planning}/chunk-task-roadmap.md +1 -1
  17. package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +4 -4
  18. package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +1 -1
  19. package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
  20. package/docs/archive/planning/release-notes-v0.2.0.md +49 -0
  21. package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
  22. package/docs/design/batching.md +569 -0
  23. package/docs/design/decode-speed-program.md +3178 -169
  24. package/docs/design/dreaming-nightly-pipeline.md +675 -282
  25. package/docs/design/generic-model-support.md +579 -334
  26. package/docs/design/kv-cache.md +680 -0
  27. package/docs/design/orpo-training.md +673 -1239
  28. package/docs/design/speculative-decoding.md +1627 -0
  29. package/docs/design/turboquant.md +2102 -0
  30. package/docs/design/unified-engine-frontier-plan.md +1831 -727
  31. package/docs/design/web-chat-redesign.md +666 -1083
  32. package/docs/planning/PRODUCT_ROADMAP.md +34 -1
  33. package/docs/planning/release-notes-v0.3.0.md +35 -0
  34. package/docs/planning/video-series.md +421 -0
  35. package/docs/reference/benchmarks.md +1162 -0
  36. package/docs/reference/cli.md +395 -149
  37. package/docs/reference/distribution.md +321 -181
  38. package/docs/reference/environment.md +348 -0
  39. package/docs/reference/glossary.md +39 -0
  40. package/docs/reference/library-api.md +580 -142
  41. package/docs/reference/memory.md +227 -64
  42. package/docs/reference/models.md +137 -6
  43. package/docs/reference/server-api.md +938 -422
  44. package/docs/reference/server-config.md +652 -320
  45. package/docs/reference/training.md +393 -254
  46. package/docs/reference/troubleshooting.md +166 -0
  47. package/package.json +10 -5
  48. package/src/admission-plan.ts +18 -0
  49. package/src/anthropic.ts +92 -3
  50. package/src/audio/decode.ts +2 -2
  51. package/src/audio/features.ts +2 -2
  52. package/src/backends/mlx/autoregressive.ts +108 -0
  53. package/src/backends/mlx/batch-group.ts +1646 -0
  54. package/src/backends/mlx/diffusion.ts +39 -0
  55. package/src/backends/mlx/draft-target.ts +47 -0
  56. package/src/backends/mlx/evaluation.ts +105 -0
  57. package/src/backends/mlx/fill-append.ts +45 -0
  58. package/src/backends/mlx/gateway-binding.ts +71 -0
  59. package/src/backends/mlx/graph.ts +38 -0
  60. package/src/backends/mlx/memory-client.ts +189 -0
  61. package/src/backends/mlx/memory-guard.ts +40 -0
  62. package/src/backends/mlx/methods.ts +90 -0
  63. package/src/backends/mlx/model-prompt.ts +164 -0
  64. package/src/backends/mlx/model-serving.ts +79 -0
  65. package/src/backends/mlx/prefill.ts +33 -0
  66. package/src/backends/mlx/request-policy.ts +20 -0
  67. package/src/backends/mlx/rollback.ts +51 -0
  68. package/src/backends/mlx/serial-executor.ts +273 -0
  69. package/src/backends/mlx/speculative.ts +114 -0
  70. package/src/backends/mlx/state-views.ts +26 -0
  71. package/src/backends/mlx/text-engine.ts +73 -0
  72. package/src/cli.ts +189 -61
  73. package/src/client.ts +66 -0
  74. package/src/config.ts +21 -4
  75. package/src/contracts/completion.ts +16 -0
  76. package/src/contracts/execution.ts +49 -0
  77. package/src/contracts/generation.ts +103 -0
  78. package/src/contracts/host.ts +7 -0
  79. package/src/contracts/jobs.ts +66 -0
  80. package/src/contracts/pi-provider.ts +30 -0
  81. package/src/contracts/pi.ts +259 -0
  82. package/src/contracts/resources.ts +18 -0
  83. package/src/contracts/scheduling.ts +27 -0
  84. package/src/contracts/tsconfig.json +14 -0
  85. package/src/diffusion/diffusion-generate.ts +218 -153
  86. package/src/embed.ts +1 -1
  87. package/src/engine/admission.ts +59 -0
  88. package/src/engine/cancellation.ts +38 -0
  89. package/src/engine/engine.ts +56 -0
  90. package/src/engine/execution-coordinator.ts +70 -0
  91. package/src/engine/execution-plan.ts +51 -0
  92. package/src/engine/resources.ts +42 -0
  93. package/src/engine/scheduler.ts +67 -0
  94. package/src/engine/session.ts +247 -0
  95. package/src/eval/README.md +44 -1
  96. package/src/eval/kl.ts +263 -23
  97. package/src/eval/reference-logits.ts +207 -0
  98. package/src/eval/runner.ts +10 -79
  99. package/src/eval/tasks/bfcl.ts +2 -1
  100. package/src/eval/tasks/gsm8k.ts +2 -1
  101. package/src/eval/tasks/hashhop.ts +2 -1
  102. package/src/eval/tasks/humaneval.ts +5 -4
  103. package/src/eval/tasks/ifeval.ts +2 -1
  104. package/src/eval/tasks/mmlu.ts +2 -1
  105. package/src/expert-io.ts +3 -1
  106. package/src/expert-offload.ts +3 -1
  107. package/src/fill/echo-index.ts +188 -0
  108. package/src/fill/fill-session.ts +452 -0
  109. package/src/fill/proposal.ts +68 -0
  110. package/src/fill/schema-rows.ts +506 -0
  111. package/src/fill/tool-boundary.ts +103 -0
  112. package/src/fit.ts +24 -43
  113. package/src/generate.ts +740 -337
  114. package/src/grammar.ts +14 -8
  115. package/src/harness-pi.ts +15 -34
  116. package/src/hub-rest.ts +4 -4
  117. package/src/index.ts +27 -0
  118. package/src/inference/denoising.ts +17 -0
  119. package/src/inference/graph.ts +33 -0
  120. package/src/inference/prefill.ts +39 -0
  121. package/src/inference/rollback.ts +7 -0
  122. package/src/jobs/db.ts +4 -0
  123. package/src/jobs/index.ts +2 -0
  124. package/src/jobs/job-entry.ts +15 -14
  125. package/src/jobs/runner.ts +98 -13
  126. package/src/jobs/task-client.ts +19 -0
  127. package/src/jobs/types.ts +2 -58
  128. package/src/kv-scheme.ts +199 -0
  129. package/src/kv-store.ts +459 -234
  130. package/src/lab/README.md +18 -0
  131. package/src/{assets → lab/curve}/curve-designer.html +2 -2
  132. package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
  133. package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +7 -5
  134. package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
  135. package/src/library.ts +40 -0
  136. package/src/lora.ts +18 -0
  137. package/src/media-fetch.ts +20 -4
  138. package/src/memory/article.ts +1 -1
  139. package/src/memory/chunk.ts +1 -1
  140. package/src/memory/model.ts +32 -170
  141. package/src/memory/prompts.ts +1 -1
  142. package/src/memory/rest.ts +1 -1
  143. package/src/memory/schedule.ts +1 -1
  144. package/src/memory/stages.ts +4 -1
  145. package/src/memory/vault.ts +3 -4
  146. package/src/mlx/array.ts +9 -10
  147. package/src/mlx/compile.ts +12 -2
  148. package/src/mlx/ffi.ts +66 -22
  149. package/src/mlx/materialize.ts +23 -0
  150. package/src/mlx/metal-kernel.ts +10 -11
  151. package/src/mlx/ops.ts +64 -19
  152. package/src/mlx/quantized-rows.ts +36 -0
  153. package/src/mlx/token-bitmask.ts +52 -0
  154. package/src/mlx/turboquant-kv-decode.ts +82 -0
  155. package/src/mlx/turboquant-kv-inverse.ts +122 -0
  156. package/src/mlx/turboquant-ops.ts +33 -3
  157. package/src/mlx/turboquant-tables.ts +2 -2
  158. package/src/model/batched-mask.ts +9 -3
  159. package/src/model/batched-quant.ts +2 -2
  160. package/src/model/batched-rotating-quant.ts +45 -99
  161. package/src/model/batched-rotating-state.ts +73 -0
  162. package/src/model/batched-rotating.ts +57 -106
  163. package/src/model/batched-row-storage.ts +127 -0
  164. package/src/model/compiled-decode.ts +4 -3
  165. package/src/model/diffusion-gemma.ts +8 -8
  166. package/src/model/factory.ts +87 -49
  167. package/src/model/fingerprint.ts +1 -1
  168. package/src/model/flash-attention.ts +3 -3
  169. package/src/model/gemma4-base.ts +187 -17
  170. package/src/model/gemma4.ts +6 -6
  171. package/src/model/generated/gemma4-12b.ts +52 -148
  172. package/src/model/generated/gemma4-26b.ts +34 -94
  173. package/src/model/generated/gemma4-e4b.ts +28 -76
  174. package/src/model/glm52-cache.ts +19 -9
  175. package/src/model/glm52-dsa.ts +13 -9
  176. package/src/model/glm52-memory.ts +28 -1
  177. package/src/model/implementation.ts +69 -0
  178. package/src/model/minicpm5.ts +7 -8
  179. package/src/model/profile.ts +443 -0
  180. package/src/model/qwen-append.ts +12 -0
  181. package/src/model/qwen-conv.ts +52 -0
  182. package/src/model/qwen3-delta.ts +98 -2
  183. package/src/model/qwen3-mrope.ts +252 -0
  184. package/src/model/qwen3_5-checkpoint.ts +164 -0
  185. package/src/model/qwen3_5.ts +336 -60
  186. package/src/model/support.ts +1 -1
  187. package/src/model/trellis-balanced-scatter.ts +51 -0
  188. package/src/model/trellis-linear.ts +672 -0
  189. package/src/model/trellis-shared-m.ts +151 -0
  190. package/src/model/trellis-shared-scatter.ts +144 -0
  191. package/src/model/trellis-splitk-prefill.ts +158 -0
  192. package/src/model/trellis-tiled-prefill.ts +136 -0
  193. package/src/model/trellis-vector-expand.ts +62 -0
  194. package/src/model/trellis-wide-prefill.ts +94 -0
  195. package/src/model/universal/dense.ts +1 -1
  196. package/src/model/universal/modules.ts +8 -1
  197. package/src/native/frame_extract.swift +140 -0
  198. package/src/native-pack.ts +15 -7
  199. package/src/pi-provider.ts +6 -64
  200. package/src/pi-web.ts +14 -320
  201. package/src/prompt-cache.ts +97 -54
  202. package/src/quantize/allocator.ts +1 -1
  203. package/src/quantize/atomic-output.ts +38 -0
  204. package/src/quantize/calibration.ts +2 -1
  205. package/src/quantize/config-writer.ts +16 -3
  206. package/src/quantize/index.ts +28 -1
  207. package/src/quantize/job.ts +11 -0
  208. package/src/quantize/quantizer.ts +265 -100
  209. package/src/quantize/rotate.ts +603 -0
  210. package/src/quantize/safetensors-writer.ts +79 -1
  211. package/src/quantize/sensitivity.ts +41 -59
  212. package/src/quantize/trellis.ts +586 -0
  213. package/src/quantize/weight-transform.ts +292 -0
  214. package/src/responses.ts +130 -3
  215. package/src/runtime-config.ts +81 -0
  216. package/src/sampler.ts +276 -4
  217. package/src/serve/admin-routes.ts +184 -0
  218. package/src/serve/aux-routes.ts +132 -0
  219. package/src/serve/batch-scheduler.ts +8 -1558
  220. package/src/serve/chat-request.ts +391 -0
  221. package/src/serve/chat-stage.ts +195 -0
  222. package/src/serve/checkpoint-identity.ts +64 -0
  223. package/src/serve/completion-executor.ts +354 -0
  224. package/src/serve/completion-sink.ts +157 -0
  225. package/src/serve/discovery-routes.ts +204 -0
  226. package/src/serve/generation-gateway.ts +211 -243
  227. package/src/serve/http.ts +159 -0
  228. package/src/serve/inference-request.ts +79 -0
  229. package/src/serve/isolate.ts +297 -105
  230. package/src/serve/kv-budget.ts +14 -0
  231. package/src/serve/lab-routes.ts +341 -0
  232. package/src/serve/lane-registry.ts +2 -1
  233. package/src/serve/model-admin-routes.ts +155 -0
  234. package/src/serve/model-binding.ts +43 -0
  235. package/src/serve/model-host.ts +512 -0
  236. package/src/serve/openai-wire.ts +103 -0
  237. package/src/serve/parent-application.ts +56 -0
  238. package/src/serve/pipeline.ts +33 -0
  239. package/src/serve/preparation.ts +33 -0
  240. package/src/serve/prompt-response-trace.ts +147 -0
  241. package/src/serve/request-plan.ts +162 -0
  242. package/src/serve/request-prep.ts +346 -0
  243. package/src/serve/response-lease.ts +24 -0
  244. package/src/serve/responses-client.ts +69 -0
  245. package/src/serve/session-completion-engine.ts +110 -0
  246. package/src/serve/session-search.ts +1 -1
  247. package/src/serve/static-routes.ts +101 -0
  248. package/src/serve/text-completion-stage.ts +104 -0
  249. package/src/serve/token-streams.ts +519 -0
  250. package/src/server.ts +523 -3551
  251. package/src/spec/assistant-source.ts +10 -44
  252. package/src/spec/deepspec-source.ts +6 -6
  253. package/src/spec/dflash-source.ts +9 -8
  254. package/src/spec/dspark/ab-stats.ts +3 -3
  255. package/src/spec/dspark/calibration.ts +2 -2
  256. package/src/spec/dspark/deepspec-module.ts +2 -2
  257. package/src/spec/dspark/generate-dflash.ts +2 -2
  258. package/src/spec/dspark/generate.ts +2 -2
  259. package/src/spec/dspark/loader.ts +1 -1
  260. package/src/spec/dspark/module-dflash.ts +5 -4
  261. package/src/spec/dspark/module.ts +3 -3
  262. package/src/spec/dspark/quantize-drafter.ts +1 -1
  263. package/src/spec/generate.ts +1 -1
  264. package/src/spec/glm52-mtp-source.ts +9 -1
  265. package/src/spec/ngram-source.ts +1 -1
  266. package/src/spec/prefix-state.ts +45 -0
  267. package/src/spec/qwen-mtp-source.ts +528 -0
  268. package/src/spec/serve-loop.ts +179 -213
  269. package/src/spec/source.ts +42 -10
  270. package/src/spec/two-model.ts +1 -1
  271. package/src/ssd-cache.ts +95 -24
  272. package/src/ssd-durability.ts +207 -0
  273. package/src/tool-call.ts +51 -14
  274. package/src/train/flash-cce.ts +14 -13
  275. package/src/train/forward.ts +38 -1
  276. package/src/train/loss.ts +3 -2
  277. package/src/train/prefix-shared.ts +5 -1
  278. package/src/train/segmented.ts +9 -6
  279. package/src/train/trainer.ts +9 -8
  280. package/src/vision/preprocess.ts +1 -1
  281. package/src/vision/prompt.ts +48 -2
  282. package/src/vision/qwen3vl-preprocess.ts +409 -0
  283. package/src/vision/qwen3vl-prompt.ts +196 -0
  284. package/src/vision/qwen3vl-tower.ts +555 -0
  285. package/src/vision/video-frames.ts +148 -0
  286. package/src/web/app.html +1 -1
  287. package/src/web/app.js +15 -6
  288. package/src/web/src/adapters-panel.ts +3 -3
  289. package/src/web/src/assistant.ts +2 -36
  290. package/src/web/src/chat.ts +16 -9
  291. package/src/web/src/composer.ts +4 -4
  292. package/src/web/src/hub.ts +1 -1
  293. package/src/web/src/markdown.ts +0 -0
  294. package/src/web/src/memory-panel.ts +1 -1
  295. package/src/web/src/model-picker.ts +1 -1
  296. package/src/web/src/palette.ts +1 -1
  297. package/src/web/src/protocol.ts +3 -39
  298. package/src/web/src/sessions.ts +1 -1
  299. package/src/web/src/ui-catalog.ts +4 -18
  300. package/src/web-assets.ts +47 -0
  301. package/src/weights.ts +134 -33
  302. package/docs/archive/mlx-bun-lab-report.html +0 -275
  303. package/docs/archive/wwdc-mlx-bun.html +0 -848
  304. package/docs/design/adapters-end-to-end.md +0 -205
  305. package/docs/design/batching-perf-path.md +0 -255
  306. package/docs/design/batching-v2-plan.md +0 -529
  307. package/docs/design/bucketing-stage.md +0 -197
  308. package/docs/design/compat-cli-surface-design.md +0 -230
  309. package/docs/design/docs-reorg-plan.md +0 -165
  310. package/docs/design/dspark-serving-program.md +0 -371
  311. package/docs/design/dspark-speculative-decoding.md +0 -268
  312. package/docs/design/grammar-spec-batching-integration.md +0 -301
  313. package/docs/design/memory-inference-path.md +0 -422
  314. package/docs/design/memory-synthesis.md +0 -287
  315. package/docs/design/memory-system.md +0 -237
  316. package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
  317. package/docs/design/omlx-adoption-map.md +0 -98
  318. package/docs/design/orpo-dynamic-lambda.md +0 -220
  319. package/docs/design/orpo-future-enhancements.md +0 -340
  320. package/docs/design/paged-kv-cache.md +0 -214
  321. package/docs/design/parallel-slots.md +0 -567
  322. package/docs/design/parity-tier-dag.md +0 -161
  323. package/docs/design/repo-cleanup-plan.md +0 -189
  324. package/docs/design/runtime-isolation.md +0 -263
  325. package/docs/design/segmented-backward-training.md +0 -543
  326. package/docs/design/spec-decode-larger-targets.md +0 -229
  327. package/docs/design/ssd-kv-cold-tier.md +0 -288
  328. package/docs/design/structured-output.md +0 -484
  329. package/docs/design/tauri-desktop-app.md +0 -127
  330. package/docs/design/the-dreaming-master-plan.md +0 -445
  331. package/docs/design/turboquant-kv.md +0 -235
  332. package/docs/design/w4a16-compute-precision-spike.md +0 -232
  333. package/docs/design/web-chat-beat-matrix.md +0 -315
  334. package/docs/design/write-pipeline-entity.md +0 -468
  335. package/docs/investigations/curve-runs/bisector-route.json +0 -98
  336. package/docs/investigations/curve-runs/bisector-trace.json +0 -1393
  337. package/docs/investigations/curve-runs/curve-terrain.json +0 -1179
  338. package/docs/investigations/curve-runs/curve-terrain.svg +0 -42
  339. package/docs/investigations/hlg-ab.html +0 -34
  340. package/docs/investigations/hlg-explorer.html +0 -238
  341. package/docs/investigations/hlg-figs/cliff.svg +0 -1
  342. package/docs/investigations/hlg-figs/cross.svg +0 -1
  343. package/docs/investigations/hlg-figs/edges.svg +0 -1
  344. package/docs/investigations/hlg-figs/k20-grids.svg +0 -1
  345. package/docs/investigations/hlg-figs/k20-peaks.svg +0 -1
  346. package/docs/investigations/hlg-figs/lw-curve.svg +0 -1
  347. package/docs/investigations/hlg-figs/terrain.svg +0 -1
  348. package/docs/investigations/hlg-report.html +0 -262
  349. package/docs/investigations/hlg-runs/hlg-ab.json +0 -149
  350. package/docs/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
  351. package/docs/investigations/hlg-runs/hlg-blind.json +0 -107
  352. package/docs/investigations/hlg-runs/hlg-cliff.json +0 -197
  353. package/docs/investigations/hlg-runs/hlg-cross.json +0 -505
  354. package/docs/investigations/hlg-runs/hlg-edge.json +0 -388
  355. package/docs/investigations/hlg-runs/hlg-eval1.json +0 -25
  356. package/docs/investigations/hlg-runs/hlg-frontier.json +0 -86
  357. package/docs/investigations/hlg-runs/hlg-grid.json +0 -242
  358. package/docs/investigations/hlg-runs/hlg-grid2.json +0 -343
  359. package/docs/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
  360. package/docs/investigations/hlg-runs/hlg-map.json +0 -383
  361. package/docs/investigations/hlg-runs/run0-original.log +0 -159
  362. package/docs/investigations/hlg-runs/runA-recommended.log +0 -131
  363. package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
  364. package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
  365. package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
  366. package/docs/investigations/hlg-runs/runAD-ab-samples.log +0 -8
  367. package/docs/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
  368. package/docs/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
  369. package/docs/investigations/hlg-runs/runAG-eval-config2.log +0 -16
  370. package/docs/investigations/hlg-runs/runB-gain-variants.log +0 -192
  371. package/docs/investigations/hlg-runs/runC-pivots.log +0 -162
  372. package/docs/investigations/hlg-runs/runD-oetf.log +0 -132
  373. package/docs/investigations/hlg-runs/runE-eotf.log +0 -162
  374. package/docs/investigations/hlg-runs/runF-shaper-working.log +0 -86
  375. package/docs/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
  376. package/docs/investigations/hlg-runs/runH-wsweep.log +0 -176
  377. package/docs/investigations/hlg-runs/runI-asweep.log +0 -176
  378. package/docs/investigations/hlg-runs/runJ-knobmap.log +0 -49
  379. package/docs/investigations/hlg-runs/runK-frontier.log +0 -19
  380. package/docs/investigations/hlg-runs/runL-widemap.log +0 -65
  381. package/docs/investigations/hlg-runs/runM-frontier-emb.log +0 -15
  382. package/docs/investigations/hlg-runs/runN-verify.log +0 -116
  383. package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
  384. package/docs/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
  385. package/docs/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
  386. package/docs/investigations/hlg-runs/runR-blind-transcript.log +0 -79
  387. package/docs/investigations/hlg-runs/runS-cross-coherence.log +0 -33
  388. package/docs/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
  389. package/docs/investigations/hlg-runs/runU-cross-12b.log +0 -33
  390. package/docs/investigations/hlg-runs/runV-lw-e4b.log +0 -22
  391. package/docs/investigations/hlg-runs/runW-lw-12b.log +0 -22
  392. package/docs/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
  393. package/docs/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
  394. package/docs/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
  395. package/docs/planning/IDEAS.md +0 -69
  396. package/docs/reference/embedding.md +0 -197
  397. package/docs/reference/features-matrix.md +0 -100
  398. package/docs/reference/orpo-quickstart.md +0 -135
  399. package/src/flags.ts +0 -13
  400. /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
  401. /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
  402. /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
  403. /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
  404. /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
  405. /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
  406. /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
  407. /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
  408. /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
  409. /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
  410. /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
  411. /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
  412. /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
  413. /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
  414. /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
  415. /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
  416. /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
  417. /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
  418. /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
  419. /package/docs/{planning → archive/planning}/journal.md +0 -0
  420. /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
  421. /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
  422. /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
  423. /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
  424. /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
  425. /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
  426. /package/{docs/investigations → src/serve/assets}/curve-terrain.html +0 -0
  427. /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
@@ -0,0 +1,1627 @@
1
+ ---
2
+ status: landed
3
+ axis: ON
4
+ canonical-for: speculative-decoding
5
+ plan-anchor: "Phase 14 — Qwen 3.x family bring-up `[~]`"
6
+ last-verified: 2026-09-07
7
+ ---
8
+
9
+ # Speculative decoding — one verifier, every draft source
10
+
11
+ Speculative decoding in mlx-bun is ONE verify/accept executor
12
+ (`src/spec/serve-loop.ts`, `specServeRun`) behind ONE seam
13
+ (`src/spec/source.ts`, `DraftProvider` / `DraftSource` / `TargetView`), with
14
+ every drafter — a second model, a KV-borrowing head, a trained block
15
+ drafter, a model-free lookup, a target-native MTP row — differing only in
16
+ what fills the draft. Output is the target's own samples at every position;
17
+ a drafter can only change decode SPEED (acceptance, tokens per target
18
+ forward), never which tokens are emitted. This doc is canonical for the
19
+ seam contract, the verifier/rollback contract, the source roster and each
20
+ source's oracle, and the DSpark program (our trained drafter + DeepSeek's
21
+ released one). Status and changelog prose live in PLAN.md; this doc keeps the
22
+ design plus a short dated History.
23
+
24
+ Consolidated 2026-08-23 from `docs/design/speculative-decoding.md` (base),
25
+ `docs/design/speculative-decoding.md` (phases 0–6 + the wall-clock-negative problem
26
+ statement) and `docs/design/speculative-decoding.md` (12B assistant γ=1 result).
27
+ The DSpark handoff (first live run, recipe) is archived at
28
+ [docs/archive/investigations/dspark-handoff.md](../archive/investigations/dspark-handoff.md).
29
+
30
+ ## 1. The seam (`src/spec/source.ts`)
31
+
32
+ ```
33
+ DraftProvider server-lifetime: owns the loaded draft weights
34
+ .open({ sampler, target: { model, caches } }) → DraftSource (per request)
35
+ DraftSource per request: owns draft-side state
36
+ prefillMode? "tail-split" (default, mlx-lm shape) | "full" (native MTP)
37
+ pinTargetKernelFamily? GLM native MTP: one M=1 quantized family across the verify batch
38
+ tapLayers? layer indices the loop must capture on prefill + every verify forward
39
+ prefill(promptIds, ctxML?) seed draft state (ctxML iff tapLayers)
40
+ draft(feed, n, stepBase, anchorHidden?) → 0..n RETURN LENGTH IS AUTHORITATIVE
41
+ commit(d, kAccept, vCtxML?, verifiedHidden?, acceptedTokens?)
42
+ weightsBytes admission accounting (0 when the provider owns the weights)
43
+ ```
44
+
45
+ - `target` is a read-only view of the target model + its LIVE caches (the
46
+ same arrays the loop drives). Two-model ignores it; every KV-borrowing
47
+ source reads donor K/V, embeddings, lm-head or hidden taps through it.
48
+ - `sampler` is the REQUEST sampler (mlx-lm parity: drafts are sampled with
49
+ the same sampler as the target; greedy drafting under a temperature>0
50
+ request is not parity — two-model and both MTP sources honor this; the
51
+ assistant and DSpark sources draft greedily, which is lossless but not
52
+ parity at temp>0).
53
+ - `feed` is mlx-lm's re-feed rule (generate.py:645-648): `[correction]` after
54
+ a rejected round, `[lastDraft, bonus]` after an all-accept round.
55
+ - `draft()` may return fewer than `n` (DSpark confidence pruning) or ZERO
56
+ (DeepSpec ℓ=0 / ngram no-match): the loop degenerates to one plain target
57
+ step, still tapped and committed for context-growing sources.
58
+ - `commit()` ownership: `vCtxML` transfers to the source; `verifiedHidden`
59
+ stays caller-owned.
60
+ - Per-slot readiness: each source binds its own target state (donor views,
61
+ anchor hidden, tapped context), which is what a batched executor would
62
+ need to open one source per row. The executor is the hard part — variable
63
+ accept length per slot breaks a uniform-B step — so per-slot spec is a
64
+ batched-scheduler project tracked in
65
+ [unified-engine-frontier-plan.md](unified-engine-frontier-plan.md), not
66
+ free composition. Today a mounted draft forces the serial lane for every
67
+ request (upstream `is_batchable = draft is None`).
68
+
69
+ ## 2. The verifier / rollback contract (`src/spec/serve-loop.ts`)
70
+
71
+ Faithful to mlx-lm `speculative_generate_step` (generate.py:473-654, read
72
+ from the oracle venv). Per round:
73
+
74
+ 1. **Draft** `d ≤ n` tokens (`n = min(γ, tokens left)`), with the anchor
75
+ hidden offered to sources that borrow it.
76
+ 2. **One target forward** over `[pending, ...drafts]` (d+1 positions),
77
+ tapped when `tapLayers` is set. The lm-head runs ONCE over the whole
78
+ window (batched — matches mlx-lm and optiq; NOT bit-exact to stock
79
+ token-at-a-time decode at bf16 knife-edges, see §2.1).
80
+ 3. **Accept walk**: sample the target at each position in order through the
81
+ shared `StepSampler` (processors, grammar mask, history — identical
82
+ ordering to the serial and batched lanes); accept while the target's
83
+ token equals the draft's (exact token-match acceptance, NOT
84
+ distribution-level rejection sampling); the first mismatch emits the
85
+ target's correction, an all-accept round emits the bonus. EOS is never
86
+ emitted as content, even when it arrives as an accepted draft.
87
+ 4. **Emit** the round's tokens through `onToken` one at a time, in order
88
+ (bursts of ≤ d+1), so stop-sequence matching and detokenization see the
89
+ stream `generate()` would produce.
90
+ 5. **Roll back + commit**: the verify window kept positions `0..kAccept`.
91
+ Trimmable caches `trim(d − kAccept)`; recurrent caches
92
+ `specRoundRollback(kAccept + 1)`; on full accept `specRoundCommit()`.
93
+ Then `source.commit(d, kAccept, vCtxML, vHidden, drafts[0..kAccept))`;
94
+ the next anchor hidden is the verify hidden at the emitted position.
95
+ 6. **Chain** via the re-feed rule; `pending = emit`.
96
+
97
+ **Cache rollback contract** (`src/model/gemma4-base.ts` `Cache`):
98
+ `trim(n, bypass?)` for trimmable caches; the optional spec-round trio
99
+ `specRoundBegin()` / `specRoundCommit()` / `specRoundRollback(keep)` for
100
+ non-trimmable recurrent state. `SSMCache` (`src/model/qwen3-delta.ts`,
101
+ gated-DeltaNet) implements the trio: the snapshot is free (MLX arrays are
102
+ immutable — the layer hands its replaced conv/recurrent refs to the round),
103
+ and rollback restores the snapshot then REPLAYS the kept `keep` window tokens
104
+ through conv1d + the delta kernel from recorded position-local inputs
105
+ (`qkv`/`a`/`b`, retained not copied). Replay is bit-exact by construction:
106
+ the kernel's per-thread loop is serial, so the prefix arithmetic is identical
107
+ whether or not the rejected tail was processed (gated in
108
+ tests/qwen-ssm-specround.test.ts). The loop arms every cache before the
109
+ verify forward; a cache that is neither trimmable nor round-capable ends
110
+ speculation for the request.
111
+
112
+ **Deliberate deviations from upstream**
113
+
114
+ - Rotating-cache ring wrap: upstream RAISES when a `RotatingKVCache` stops
115
+ being trimmable mid-generation (generate.py:529-533); a serve endpoint
116
+ must not 500 mid-stream, so the loop checks `offset + n + 1 < maxSize`
117
+ before each round and, if the window is about to wrap, STOPS SPECULATING
118
+ and finishes with plain single-token decode (bit-equivalent — the
119
+ target's own samples). The standalone `src/spec/generate.ts` loop keeps
120
+ the reference behavior and throws.
121
+ - Prompt-cache reuse is BYPASSED on the spec path (fresh caches per spec
122
+ request, `cachedTokens = 0`). mlx-lm composes spec with its LRU prompt
123
+ cache; wiring ours through `PromptCache` + the SSD tier is the open item
124
+ in §8.
125
+ - Grammar × spec (novel — no runtime serves both): the drafter runs FREE;
126
+ the grammar mask rides the accept walk in `samplePos` (mask before
127
+ sample; the matcher advances on emitted tokens only, so rejected drafts
128
+ never touch grammar state and no matcher rollback exists). Grammar
129
+ termination mid-burst truncates the round. Gates: greedy grammar+spec ≡
130
+ greedy grammar-only long-prefix + 100% schema validity
131
+ (tests/spec-serve.test.ts).
132
+
133
+ Telemetry (`usage.speculation`): drafted / accepted / rejected / targetCalls /
134
+ rounds / acceptanceLengths / tokensPerForward / forwardsSaved, plus
135
+ per-position `draftedByPos` / `acceptedByPos` (the acceptance-A/B report).
136
+ `MLX_BUN_SPEC_TRACE=1` prints per-round feed + drafts.
137
+
138
+ ### 2.1 Prefill convention and what "lossless" means here
139
+
140
+ - **Oracle prefill shape** (`MLX_BUN_PREFILL_TAIL_SPLIT`, default on): mlx-lm's
141
+ speculative path drains BOTH models to len−1 and has NO separate step-0 —
142
+ the un-drained last prompt token HEADS the first verify window, and the
143
+ first emitted token is that window's position-0 sample. A separate L=1
144
+ step-0 is ulp-different from the (1+γ)-window GEMM and flips near-ties.
145
+ Gated live 4/4 token-for-token vs the oracle venv (γ∈{2,3} × 2 prompts,
146
+ including a knife-edge cell). Sources with `prefillMode: "full"` (Qwen
147
+ MTP, GLM MTP) take the full-prompt shape their references use.
148
+ - **Batched verify vs stock decode**: both oracles (mlx-lm, optiq) batch the
149
+ verify lm-head, so greedy spec is bit-exact to THE SPEC ORACLE and agrees
150
+ with stock decode only on tie-free prompts. An earlier port verified
151
+ per-position to match stock — a target no real implementation has, at a
152
+ γ× lm-head read tax — and was replaced by `picksBatched`
153
+ (`src/spec/generate.ts`, 2026-06-14). Losslessness gates therefore use
154
+ TIE-FREE prompts (tests/spec-decode.test.ts, spec-serve-assistant,
155
+ qwen38-mtp) or long-prefix agreement (spec-decode-12b): a flip on a
156
+ tie-free prompt is an accept/reject/rollback bug, never rounding.
157
+ - **"Lossless" is a claim about the verifier, not the artifact.** The
158
+ Qwen3.8-27B TQ artifact's serve gauntlet recorded MTP at 76% acceptance /
159
+ 2.53 tokens per forward WITH an output-divergence flag vs plain greedy
160
+ (consistent with verify-width reduction-order near-ties, the same class as
161
+ the 12B step-0 convention; margin analysis queued) — it is NOT claimed
162
+ lossless on the model card. The 27B bf16-target pairing gate, by
163
+ contrast, was token-identical on its tie-free prompt (PLAN 14g).
164
+
165
+ ## 3. Draft-source roster
166
+
167
+ `--draft-model <dir>` mounts a drafter; the artifact's KIND selects the
168
+ provider (`src/server.ts` `detectDraftKind`; `--draft-kind` overrides).
169
+ Detection order: `dspark.json` → `dspark`; `config.json` `architectures[0]
170
+ === "Gemma4DSparkModel"` → `deepspec`; `model_type` containing `assistant` →
171
+ `assistant`; `model_type` ending `_mtp` → `mtp`; else `two-model`. `ngram`
172
+ is never detected (no artifact) and refuses a `--draft-model`; any other
173
+ kind without one is refused. `--draft-kind mtp` with no `--draft-model`
174
+ resolves the companion bundled at `<model>/mtp/` (single-repo artifacts) and
175
+ refuses otherwise. GLM-5.2's checkpoint-native MTP row mounts by default
176
+ (`--mtp on|off`) and is mutually exclusive with an explicit drafter.
177
+
178
+ | kind | source | what fills the draft | target state used | draft sampler | default γ | oracle / gate |
179
+ |---|---|---|---|---|---|---|
180
+ | `two-model` | `src/spec/two-model.ts` | a full second model, autoregressive, own KV | none | request sampler | 3 (mlx_lm.server) | **L1**: mlx_lm.server `--draft-model`, same pair, greedy, token-for-token (spec-vs-spec). Draft-cache rewind `max(d − kAccept − 1, 0)`. Tokenizer-family probe at startup refuses mismatches (upstream silently accepts ~0%). |
181
+ | `assistant` | `src/spec/assistant-source.ts` (+ `drafter.ts`) | Gemma "-assistant" Q-only 4-layer head | donor K/V (last sliding + last full layer) + anchor hidden, borrowed each step | greedy (own head) | 3 | **L2**: optiq `spec_generate` — e4b γ=2 identical 48-token output AND identical accept/reject trace (drafted 60 / accepted 17 / target calls 31), `scripts/oracle/oracle-spec.py`. Head picked by TENSOR PRESENCE (centroid vs tied-embedding) — optiq's config-based detection loads the 12B/26B unified drafters with zero-init centroids → 0% acceptance; ours 29% on the same artifact. |
182
+ | `dspark` | `src/spec/dflash-source.ts` (+ `dspark/module-dflash.ts`) | our trained DSpark block drafter (KV injection + Markov + confidence) | multi-layer hidden tap (`tapLayers`, e4b {20,31,41,42}) grown into H_ctx | greedy | pinned to `cfg.gamma` | **Lab** (no external oracle for our checkpoints): CPU smoke, infer-loop bit-identity pins, real-weights serve gate. See §4. |
183
+ | `deepspec` | `src/spec/deepspec-source.ts` (+ `dspark/deepspec-module.ts`) | DeepSeek's released `Gemma4DSparkModel` drafters (e.g. `dspark_gemma4_12b_block7`, γ=7) | tap `[5,17,29,41,46]` on the 12B, projected into an incremental context-KV cache; accepted rows only | argmax (temp-0 reference) | pinned to `block_size` | **DeepSpec reference** at temperature 0: its leaky rejection sampling degenerates to exact argmax token-match, RNG-free — round-for-round trace fixtures (`scripts/oracle/oracle-dspark-deepspec.py`; the compare script was deleted 2026-08-23, git history). |
184
+ | `ngram` | `src/spec/ngram-source.ts` | model-free prompt lookup: longest trailing k-gram (max..min) matched at its FIRST earlier occurrence; the continuation is the draft | token history rebuilt from prompt + feed/commit | none | 10 (Saxena `num_pred_tokens`); `--ngram-max 3` / `--ngram-min 1` | Ported from Prompt Lookup Decoding (Saxena) + vLLM `ngram`. Lossless by verify — gated token-identical to non-spec greedy (tests/spec-ngram.test.ts on e4b; tests/qwen35-spec-ngram.test.ts on the 0.8B hybrid, proving real accepts AND real DeltaNet rollbacks). |
185
+ | `mtp` | `src/spec/qwen-mtp-source.ts` | Qwen-trained MTP head (`qwen3_5_mtp`, one full-attention layer + `fc` merge; `mlx-community/Qwen3.8-27B-MTP-*`) | target `embed_tokens` + `lm_head` (not standalone) + PRE-final-norm last-layer hidden via `tapLayers = [nLayers−1]`; `prefillMode: "full"` | request sampler | `block_size − 1` (=2) | Reference: mlx-vlm `qwen3_5_mtp` drafter. Draft-logit parity vs mlx-vlm 0.6.14: draft tokens exact through the chained block, worst \|Δlogprob\| 1.9e-1 = bf16-vs-f32 floor. Losslessness: token-identical on the tie-free 27B pairing gate (tests/qwen38-mtp.test.ts, opt-in `MLX_BUN_TEST_QWEN38_MTP=1`). Target rollback = the SSMCache spec-round contract (§2). |
186
+ | GLM native | `src/spec/glm52-mtp-source.ts` | GLM-5.2's checkpoint-native MTP row (`enorm`/`hnorm`/`eh_proj` + one decoder layer, int8 routed experts) | target embeddings + output head + anchor hidden; own `MLACache` role `mtp`; `prefillMode: "full"`, `pinTargetKernelFamily` | request sampler | `glmMemoryPlan.mtpDraftTokens` | Direct Colibri (SPEC_PIN contract; "niente prefill" — the first draft opens a decode-only window from the anchor). Commit rebuilds accepted rows from the target's VERIFIED hidden window (only the first speculative row was conditioned on a true hidden). Measured in docs/reference/benchmarks.md "Colibri G4 serial native MTP". |
187
+
188
+ The `source.ts` header roster lists five of these (two-model, assistant,
189
+ dflash, ngram, GLM MTP); `deepspec` and `mtp` are wired in `server.ts` and
190
+ the CLI and belong in the same list — this table is the complete roster.
191
+
192
+ **Row conventions worth knowing.** Qwen MTP is predict-2-ahead: drafter KV
193
+ row at position p is built from `(embed(token_{p+1}), hidden_p)`; `draft()`
194
+ opens by building the pending token's row from the target's TRUE hidden at
195
+ the emitted position (held over from the verify tap), then chains its own
196
+ output for the rest of the block; `commit()` trims rejected rows and, on
197
+ all-accept, appends the missing accepted row from the verify hidden. The
198
+ sampler contract is `[1, V]` — the module output `[1,1,H]` must be
199
+ reshaped before sampling or 2-D-slicing samplers (top-k) throw (the
200
+ 2026-08-20 serve-lane MTP 500; both MTP sources carry the fix).
201
+
202
+ ## 4. DSpark — the trained block drafter
203
+
204
+ DSpark (DeepSeek, arXiv:2607.05147, "Confidence-Scheduled Speculative
205
+ Decoding with Semi-Autoregressive Generation") = a **DFlash** parallel
206
+ backbone (KV-injected, all γ draft tokens in one pass) + a lightweight
207
+ **sequential head** (Markov, Eq 5; RNN, Eq 6) adding intra-block dependency
208
+ + a **confidence head → calibration → draft-length scheduler**. DFlash alone
209
+ suffers suffix decay; the sequential head is what makes it DSpark.
210
+
211
+ ### 4.1 Mechanism (our module, `src/spec/dspark/module-dflash.ts`, paper Eq 2–3)
212
+
213
+ - **Eq 2 (context):** tap the target's hidden states at layers `{l1…lm}`
214
+ (e4b default {20,31,41,42}, the last = post-finalNorm sentinel at index
215
+ nLayers), concat on the feature axis, project to draft width:
216
+ `H_ctx ∈ [L_ctx, d]`. Extraction is the parity-safe `model.hiddenTap`
217
+ (no-op when off) in `gemma4.ts` `forwardLayers`; `qwen3_5.ts` carries the
218
+ same contract.
219
+ - **Eq 3 (injection):** every draft layer forms
220
+ `K_i = [W_i^K H_ctx; W_i^K H_d]`, `V_i` likewise, `Q_i = W_i^Q H_d`; the
221
+ γ-block queries attend bidirectionally over `[context ++ block]`. The
222
+ draft projects the target's HIDDEN STATES with its OWN Wk/Wv (not the
223
+ target's K/V — that is the assistant drafter).
224
+ - **Markov head (Eq 5):** `B_k = W1[x_{k−1}]·W2`, r=256, W2 zero-init →
225
+ starts as pure DFlash, τ climbs as intra-block dependency is learned.
226
+ - **Confidence head (Eq 7):** `c_k = σ(wᵀ[h_k; W1[x_{k−1}]])` (we add a
227
+ zero-init bias — superset).
228
+ - **Loss (Eq 8–12):** `0.1·CE + 0.9·TV + 1.0·BCE`, position weights
229
+ `w_k = exp(−(k−1)/γ)`, analytic c*. Audit verdict: matches the paper to
230
+ the letter; our defaults (γ=5, 5 layers, Markov) = their production
231
+ config.
232
+ - **RNN head (`cfg.seqHead: "rnn"`)**: ungated Elman
233
+ `s_k = tanh(s_{k−1}·wH + E[x_{k−1}] + bH)`, `B_k = s_k·wO` (wO zero-init;
234
+ init-equivalent to Markov). The paper's Eq 6 is a gated cell over
235
+ `[s_{k−1}; W₁[x_{k−1}]; h_k]` — ours differs, kept as a variant (paper
236
+ calls the RNN gains marginal; DeepSpec ships the gated form if ever
237
+ wanted).
238
+ - **Confidence-scheduled pruning (Alg 1, single-user form)**: `forwardInfer`
239
+ drops position k (and truncates the block) when
240
+ `c_k < thresholds[k] ?? minConf`; position 0 always survives. Activation is
241
+ checkpoint-driven (`cfg.sts` thresholds) or `MLX_BUN_DSPARK_MINCONF`;
242
+ uncalibrated checkpoints draft fixed-γ. Invariant: pruning changes how
243
+ many positions the target verifies, never what is emitted (gated:
244
+ truncation-never-redraw, prefix-identical to the unpruned block).
245
+ - **Calibration (`src/spec/dspark/calibration.ts`)**: per position, the
246
+ smallest threshold whose Laplace-smoothed `P(accepted | conf ≥ τ)` meets
247
+ the target; pos 0 → 0; under-sampled → 0; unreachable → 1.0. This fits the
248
+ RELEASED reference's scheduler shape (per-position threshold truncation on
249
+ sigmoid confidence, default 0 = off, ℓ=0 allowed). The PAPER's §3.2.1 STS
250
+ (per-position temperatures by ECE grid search on survival products) and
251
+ Alg 1's throughput maximization over a profiled SPS(B) table live in
252
+ DeepSeek's UNRELEASED serving layer — paper-faithful STS/Alg-1 is a Lab
253
+ item only if the survival-product scheduler ever earns a default.
254
+ - **Verify**: the serve loop's token-match acceptance (§2). The standalone
255
+ `generate-dflash.ts` keeps the paper's distribution-level verify
256
+ (`min(1, p/q)` accept + residual `norm(relu(p−q))` resample) for the
257
+ measure path; greedy is identical, temp>0 is lossless either way but
258
+ token-match accepts less.
259
+ - **Rollback past the sliding window**: `trim(n, bypass=true)` physically
260
+ slices rejected tips on rotating caches (`gemma4-base.ts`).
261
+ - **Checkpoint format**: `dspark.json` stamps `variant: "dspark"` (legacy
262
+ `"dflash"` accepted); `src/spec/dspark/loader.ts` `loadDsparkDrafter()`
263
+ dispatches by variant and REFUSES v1 single-vector checkpoints
264
+ (`module.ts`/`data.ts`/`generate.ts` — the superseded baseline that
265
+ dropped KV injection) with a pointer at the trainer.
266
+
267
+ ### 4.2 The v1 → v2 finding (why "faithful" matters)
268
+
269
+ v1 fused a single final-hidden vector as one token — it removed the paper's
270
+ load-bearing mechanism and never tested it. v2 is faithful. Getting v2 to
271
+ paper-range required an off-by-one fix in the TV-loss target alignment
272
+ (block position k predicts `x_{t+1+k}` whose target is
273
+ `softmax(LM_head(h_{t+k}))`; the data gathered `t+k+1`) — present in both
274
+ v1 and v2, fixed in `data-dflash.ts` (`blockIdx = g+k`). Overfit on 3
275
+ articles: per-position acceptance ~0.75, τ 3.24 (paper range 0.6–0.9 / τ
276
+ 3–4) — the architecture is correct. 160 articles generalize to ~0.17 and
277
+ plateau; width is not the ceiling (`dDraft` 1024 and 2560 both plateau);
278
+ the paper trains on 1.3M samples × 10 epochs. (M1 Max 32 GB, e4b
279
+ OptiQ-4bit, 2026-06-30.)
280
+
281
+ ### 4.3 DeepSpec — the released reference, and why DSpark has an oracle
282
+
283
+ DeepSeek open-sourced DeepSpec (MIT) + trained drafters incl.
284
+ `deepseek-ai/dspark_gemma4_12b_block7` (6.86 GB bf16, γ=7, for
285
+ gemma-4-12B-it). Their drafter is a DIFFERENT module from ours: a full
286
+ Gemma4-shaped 5-layer transformer — hidden 3840, 16 Q heads × head_dim 512,
287
+ 1 KV head with K≡V (`attention_k_eq_v`, no v_proj), scale 1.0 (QK-norm),
288
+ partial RoPE 0.25 θ=1e6, per-layer `layer_scalar`, scaled embed (×√3840),
289
+ UNTIED lm_head, final softcap 30, incremental context-KV cache. 74 bf16
290
+ tensors, no key prefix, `architectures: ["Gemma4DSparkModel"]`. 12B tap
291
+ layers `[5,17,29,41,46]` (their layer_id+1 output convention == our
292
+ `tapLayers` index; no final-norm sentinel). Ported verbatim in
293
+ `deepspec-module.ts` (transcribe their forward, then op-for-op), with
294
+ context rows cached post-norm-post-rope (bit-equivalent to their in-round
295
+ concat; argued in `projectContextKV`). Argmax is over the bf16 sum — an f32
296
+ sum reorders near-ties vs torch.
297
+
298
+ Consequence: DSpark is no longer oracle-less. The DeepSpec port + their
299
+ checkpoint is the no-training path to a 12B speedup, gateable
300
+ round-for-round at temp 0. Our trainable module remains the research path
301
+ for custom targets. Their training recipe (for scale reference):
302
+ open-perfectblend regenerated by the target, 10 epochs, batch 512, lr 6e-4,
303
+ ~38 TB cached hiddens.
304
+
305
+ The drafter also loads QUANTIZED: `scripts/dspark.ts quantize` writes an
306
+ mlx-native affine sibling (`src/spec/dspark/quantize-drafter.ts`: every 2-D
307
+ matmul weight + `embed_tokens`/`markov_w1` as quantized-gather tables;
308
+ `confidence_head`, norms, `layer_scalar`, biases stay bf16 — the mlx
309
+ `false` convention), and `deepspec-module.ts` detects `.scales` siblings and
310
+ routes through `quantized_matmul` / QuantizedEmbedding gather-dequant
311
+ (mlx-lm numerics). 8-bit tracks bf16 token-for-token on the synthetic
312
+ checkpoint. Drafter numerics only move ACCEPTANCE, never correctness, so the
313
+ entire quality gate for any drafter quantization is one acceptance A/B.
314
+
315
+ ### 4.4 The wall-clock problem (why spec is opt-in)
316
+
317
+ Spec decode amortizes fixed draft overhead only when the TARGET forward is
318
+ expensive. The recurring finding across every drafter and target:
319
+
320
+ - **e4b is near the worst case** (fast target): assistant γ=2 ran 0.78× of
321
+ a 54 tok/s baseline at ~23% acceptance (Phase 6, 2026-06-10); even a τ≈3
322
+ DSpark drafter nets ~0.4–0.5×.
323
+ - **12B assistant γ=1 is a modest win** — see History 2026-06-14.
324
+ - **12B + DeepSpec block7 (first live run, 2026-07-07, loaded M4 Pro
325
+ 24 GB, directional only):** τ ≈ 2.8 committed tokens per target forward
326
+ at 26–33% per-token acceptance (the predicted OptiQ-4bit-target
327
+ degradation vs their bf16 training) — the algorithm WORKS — but
328
+ wall-clock LOST ~3.4× (spec 14.6 vs serial 49.8 aggregate tok/s, conc-4).
329
+ The tax is the drafter: 6.9 GB bf16 → ~3 GB of weight reads per round
330
+ (2 GB untied lm_head + 7 × 134 MB markov_w2 + layers), ~14 host syncs
331
+ per round in the transcription-faithful `draftBlock`, tapped verifies
332
+ falling back to the unoptimized monolith, and ~14 GB of paired weights
333
+ on a 24 GB box.
334
+ - **27B + Qwen native MTP (2026-08-18, quiet M1 Max 32 GB, interleaved
335
+ off/on ×3, spreads 2.2%/4.4%, arms token-identical):** median OFF 15.75
336
+ vs ON 12.93 tok/s — 0.821× at γ=2 despite 61% acceptance and 2.25
337
+ tokens/forward. The bf16 head's serial per-draft cost (full-vocab lm-head
338
+ matmul + sample per draft) exceeds the saved 27B forwards. Levers if
339
+ reopened: quantize the head; batch/defer its per-draft lm-head sampling.
340
+ - **GLM-5.2 native MTP (M1 Max 32 GB, Colibri G4):** 1.235× wall
341
+ throughput, 32/92 accepted, 2.065 tokens per verify forward — the one
342
+ measured wall-clock win, on a target whose forward is expensive enough
343
+ (docs/reference/benchmarks.md).
344
+
345
+ Doctrine: a lone request must never get slower because a flag exists; spec
346
+ stays opt-in per (target, drafter) pair until it wins a clean-machine
347
+ paired A/B; losing configs stay documented default-off. Denominator
348
+ discipline: speedups only ever vs the SAME model's non-spec baseline.
349
+
350
+ ### 4.5 The DSpark serving program (12B) — phases and gates
351
+
352
+ Goal: spec ≥ 1.3× serial decode on 12B at the best config, clean-machine
353
+ paired A/B — then decide when it is on by default. Objective stated right
354
+ (Josh, 2026-07-07): 26–33% acceptance with the bf16 drafter is the
355
+ WORKABLE baseline (τ≈2.8 already pays in forwards); the question
356
+ quantization must answer is PRESERVATION — do we keep that number at
357
+ 4-bit? Sensitivity is MEASURED, never guessed: optiq's method (simulate-quant
358
+ one group → KL vs reference on a calibration mix → greedy knapsack to target
359
+ bpw), cheaper on the drafter because cached tapped hiddens remove the 12B
360
+ from the sweep loop. TurboQuant's role is better-quant-at-EQUAL-bits (finer
361
+ levels around the Gaussian peak) — a preservation instrument, measured at
362
+ equal bpw before any lower rung.
363
+
364
+ | phase | what | state |
365
+ |---|---|---|
366
+ | 0a | oracle-gate the bf16 DeepSpec port ONCE (round-for-round vs their temp-0 trace on the bf16 HF 12B; OptiQ arm acceptance-only) | open (Josh: torch venv + GPU) |
367
+ | 0b | γ sweep {2,3,5,7} at conc 1 → per-γ tok/s / acceptance / TTFT table | open |
368
+ | 0c | time attribution (draft GPU / draft host syncs / verify / tap+commit; paging check) — orders phases 1–3 by measured value | open |
369
+ | 1a | `scripts/dspark.ts quantize` | done 2026-07-07 |
370
+ | 1b | quantized forward in `deepspec-module.ts` | done 2026-07-07 |
371
+ | 1c | `scripts/dspark.ts ab` — same target, same ≥32 prompts, temp 0, drafter A vs B; per-position acceptance, τ, tok/s, paired verdict (drop ≤ 3 pts AND wall-clock strictly improves); stats in `ab-stats.ts`, unit-tested | done 2026-07-07 |
372
+ | 1d | RUN 4-bit vs bf16 at best-γ | open (GPU) |
373
+ | 1e | measured sensitivity map (optiq's method on cached hiddens) → knapsack allocation → one confirming A/B | open |
374
+ | 1.5 | OPTIONAL acceptance upside: adaptation fine-tune on OUR target's tapped hiddens (their checkpoint as init; gradient in their torch trainer vs a local path — undecided); threshold sweep at 26% (objective = tok/s, not τ); target-fidelity axis measurement | open |
375
+ | 2 | tighten `Gemma4DSparkModel.draftBlock`: on-device token chaining (one host read per round), compute no confidence at threshold ≤ 0, skip unread `baseLogits`; gate = bit-identity on the synthetic checkpoint + real-checkpoint determinism | open |
376
+ | 3 | `captureLayer` in the generated Gemma forwards (`scripts/gen-model.ts`) so tapped prefill/verify keep the fast path; drop the `hiddenTap !== null` fallback guard; regen 12b/26b/e4b; gate = generated-parity + tap-parity + spec bench | open (guard still present 2026-08-23) |
377
+ | 4 | serving UX on 0–3 data: per-kind γ default (trained γ is the cap, not necessarily the default), quantized-drafter UX (`mlx-bun get` → quantize), spec × prompt-cache composition, surface audit (`/stats` drafter+kind+γ; server-config "speculative serving" section), the default decision per pair written down | open |
378
+ | 5 | TurboQuant weight scheme for the drafter: TQ-4 vs affine-4 AT EQUAL BPW through the 1c harness (headline); lower rungs only after that is won; method (TQ vs affine) and allocation (knapsack) are orthogonal axes, never mixed within an artifact; per-tensor mixed rungs from the measured table (lm_head ~2 GB read fully per round; embed ~2 GB gather-only; markov_w2 134 MB ×7) | open |
379
+ | 6 | clean-machine protocol → promote to benchmarks + default decision; close or re-defer loose ends (gated RNN head, paper Alg-1/STS, per-slot spec) | open |
380
+
381
+ Exit criteria: (1) ≥ 1.3× on 12B clean-machine paired, or a written verdict
382
+ that it cannot be reached and why; (2) acceptance PRESERVED and understood
383
+ (the quantized drafter holds the bf16 baseline, or the mixed rung that does
384
+ is adopted, or the loss is measured and accepted); (3) the
385
+ acceptance-per-byte curve exists (TQ vs 4-bit vs mixed vs bf16); (4) the
386
+ surface is documented, defaults decided per pair, and `mlx-bun get` →
387
+ serve → speedup works without reading a design doc.
388
+
389
+ ### 4.6 The 27B program (Qwen3.8, 2026-08-19)
390
+
391
+ The TurboQuant-weights campaign's 17 GB Qwen3.8-27B (~11.8 tok/s decode,
392
+ 2026-08-19) is the slow-target regime where τ≈3 nets 2–3×, and it retires
393
+ the "27B infeasible to train on 24 GB" premise: with the quantized trunk
394
+ FROZEN (inference-only taps + teacher logits) and only the ~5-layer drafter
395
+ holding grads/optimizer state, drafter training fits a 32 GB machine.
396
+ Verify/rollback across the 48 DeltaNet layers is proven engine behavior
397
+ (§2). Two tracks, cheap-first, queued behind the certification suite's GPU
398
+ ownership (PLAN.md TurboQuant-weights phase, "Queued follow-ups"):
399
+
400
+ - **Track A — confidence-scheduled verification on the stock MTP head (no
401
+ training).** Dynamic per-step block length: v0 confidence = the draft's
402
+ own token probability, mapped through the `scripts/dspark.ts calibrate`
403
+ pattern to expected acceptance; stop drafting when marginal
404
+ expected-accept drops below the verify amortization point. Calibration
405
+ data = thousands of (confidence, accepted?) pairs from served traffic.
406
+ Gate: paired A/B tok/s vs fixed-γ MTP; lossless by construction.
407
+ - **Track B — true DSpark drafter for the 27B.** The faithful v2 retargeted:
408
+ tap-layer selection for the 64-layer trunk (bias toward the 16
409
+ full-attention layers + post-finalNorm), data by TEACHER-FORCED passes
410
+ over corpus text with the frozen trunk (every position yields a sample).
411
+ Open idea: initialize/distill against the shipped MTP head. Gate:
412
+ generalizing per-position acceptance must beat the MTP head's 0.68 or
413
+ Track B is dropped for Track A + MTP.
414
+
415
+ Drafts trained on the TQ artifact are married to its rotation basis (card
416
+ note if published). `RadixArk/Qwen3.8-27B-DSpark` exists as a cross-check
417
+ candidate (PLAN 14h, optional).
418
+
419
+ ### 4.7 Packed 27B MTP recheck after kernel changes (2026-09-05)
420
+
421
+ The M4 Pro screen uses the actual packed k300 artifact and the existing
422
+ folded MTP companion from the affine flagship. Their recorded R1 seed,
423
+ hidden size and R2 setting agree; the head's documented final-gamma
424
+ approximation remains a draft-quality limitation. No head was downloaded,
425
+ trained or changed. The target verifies every MTP proposal; this is separate
426
+ from strict fill's no-verification assertions.
427
+
428
+ `tests/parity/qwen38-mtp.test.ts` now supports a warm native wall-time
429
+ diagnostic via `MLX_BUN_TEST_MTP_REPORT`. Target/draft path overrides select
430
+ the exact pair; `MLX_BUN_TEST_MTP_GAMMA` selects 1..4. The report records
431
+ both token sequences, actual completion counts, first-token and full-request
432
+ wall time, speculation counters, source and artifact identity, and machine
433
+ state. It does not compare differently defined decode-rate counters.
434
+
435
+ Six AB/BA blocks on the short enumeration fixture compare gamma 2 on variants
436
+ 6 and 8, then gamma 1/2/3/4 on variant 8. The optimized verification kernels
437
+ improve MTP's economics on that fixture; gamma 4 loses when the target append
438
+ crosses the packed/expanded boundary. Exact emitted-token identity holds in
439
+ the observed cells. These are loaded-machine diagnostics under
440
+ `reports/qwen38-rd/mtp-v*-gamma*.json`, not default-promotion evidence.
441
+
442
+ A predeclared code, explanation, arithmetic, extraction, JSON and prose
443
+ screen has one warm pair per prompt with alternating arm order. Four valid
444
+ cases improve, while prose loses with lower draft acceptance. The extraction
445
+ case ran past chat EOS and is excluded from serving comparisons. All six
446
+ preserve the observed token IDs, including that invalid overrun. Reports and the frozen prompts are under
447
+ `reports/qwen38-rd/mtp-corpus-screen/` and `mtp-screen-prompts.json`. The
448
+ fixed output budget truncates some answers; this is not a task-quality eval.
449
+ MTP remains opt-in. Repeat diverse longer sessions, include rejection/rollback
450
+ cost and memory, and test a bounded adaptive policy on held-out prompts before
451
+ changing that decision.
452
+
453
+ The next adaptive screen stays within the existing `DraftSource` contract:
454
+ start at two drafts, decrease after a partial acceptance and increase toward
455
+ three only after two fully accepted rounds. Per-request state stays with the
456
+ source. Keep one draft as the floor for this experiment. Qwen MTP's current
457
+ `draft(n<=0)` returns without appending its pending cache row, and the
458
+ zero-draft commit does not add that row. A skip/recovery policy therefore
459
+ needs an explicit cache-advancement design before it can preserve the source's
460
+ alignment. This is a source-audit constraint on a future policy, not a failure
461
+ in the current fixed-positive-length path. Compare complete native/HTTP
462
+ requests and memory against both fixed gamma and non-spec generation on new
463
+ frozen prompts before retaining an adaptive policy.
464
+
465
+ The subsequent three-arm serving gate freezes three new prompts and the
466
+ adaptive rule before measurement. It uses the lossless interleaved packed
467
+ artifact, variant 13 and shared affine dispatch, with one serial request at a
468
+ time, temperature zero, seed 42, thinking disabled, no prompt/SSD cache and a
469
+ 128-token budget. Six balanced-order blocks start 18 fresh servers. All exit
470
+ cleanly; measured sources remain fixed. Across 126 warm/measured responses,
471
+ all 84 native-versus-MTP text, prompt-count and finish-reason comparisons
472
+ match. The 12 JSON completion-count differences remain in the report. Native
473
+ `generateInner` counts the stopping EOS before excluding it from content;
474
+ `specRunInner` excludes it from both emitted content and `generatedTokens`.
475
+ Thus JSON reports 112 versus 111 despite identical response text. Compare
476
+ complete wall time for that cell, not the differently counted decode rates.
477
+
478
+ On the M4 Pro 24 GB, these are diagnostic medians in seconds:
479
+
480
+ | Frozen prompt | Native | Fixed two drafts | Adaptive one to three drafts |
481
+ |---|---:|---:|---:|
482
+ | TypeScript run-length encoder | 11.912 | 6.690 | 6.483 |
483
+ | Composite database index explanation | 11.914 | 7.217 | 7.062 |
484
+ | Normalize three records to JSON | 10.969 | 5.968 | 5.751 |
485
+
486
+ Every fixed/native, adaptive/native and adaptive/fixed complete-time pair
487
+ improves. Median paired adaptive/fixed time ratios are 0.9698, 0.9782 and
488
+ 0.9624 respectively. Fixed MTP also reduces observed TTFT by about 160 ms;
489
+ adaptive/fixed TTFT is essentially unchanged. Median per-server peak RSS is
490
+ 12,370,534,400 bytes native, 13,045,129,216 fixed and 13,029,392,384 adaptive.
491
+ The adaptive source exercises widths one, two and three; its policy state is
492
+ per request through the existing provider interface. The earlier broad corpus
493
+ and these held-out cells still do not establish a universal MTP gain. Code
494
+ and explanation stop at the token budget, so this is not a task-quality eval.
495
+ Machine preflight fails on existing swap use. Fixed MTP remains opt-in and
496
+ the adaptive wrapper is research-only. Raw evidence and source identities are
497
+ `reports/qwen38-rd/mtp-heldout-{plan,http,http-review}.json`. The native follow-up runs both arm orders and preserves every emitted token
498
+ ID in all 18 runs. Its 500 draft rounds include 78 partial-acceptance commits
499
+ and all three adaptive widths. Assertions check source offsets before and
500
+ after each draft and all 488 commits; all 12 sources dispose cleanly. Both
501
+ JSON runs emit exactly 111 IDs on every arm, confirming the separate native
502
+ EOS accounting. This is an alignment and trajectory gate, not a claim of
503
+ identical cache bytes between speculative and serial computation. The first
504
+ native run includes loading, so its times do not form a warm performance
505
+ comparison. Evidence: `reports/qwen38-rd/mtp-heldout-native{,-review}.json`.
506
+ Longer contexts, other quants and both quiet-machine gates remain open.
507
+
508
+ The EOS usage discrepancy is now fixed in the speculative serving loop.
509
+ It counts a stopping EOS from the initial sample, accepted draft, correction,
510
+ bonus or plain-decode fallback without emitting it. A callback or token-budget
511
+ stop before that position excludes the unused suffix. This follows the pinned
512
+ mlx-lm `stream_generate` final `generation_tokens=n+1` accounting. Nine
513
+ model-free cases reproduce seven old failures and cover both early-stop
514
+ controls. All typechecks, hygiene and 1,812 model-free tests pass. The fresh
515
+ three-arm HTTP gate completes 21 requests with fixed sources, clean exits and
516
+ no text, prompt-count, completion-count or finish-reason differences. JSON
517
+ now reports 112 tokens on every arm. This single block checks the fix; the
518
+ historical timing reports retain their original counts. Evidence:
519
+ `spec-token-counts-{before,after,typecheck,hygiene,model-free}.txt` and
520
+ `mtp-post-eos-http{,-review}.json` under the campaign reports.
521
+
522
+ An observed EOS now survives the method and serving adapters as
523
+ `finishReason: "stop"`. Native and speculative loops record it explicitly;
524
+ the adapters use the observed cause before their legacy count-based fallback.
525
+ Tool-call and explicit text-stop handling retain precedence. The first HTTP
526
+ replay exposed two additional serving overrides: at the known 112-token JSON
527
+ EOS budget, it still returned `"length"`. Three regression tests reproduced
528
+ those failures before the adapter fix. The final fresh replay completes all
529
+ 21 requests with fixed sources, clean exits and unchanged text/counts.
530
+ Native, fixed and adaptive MTP now report `"stop"` for that JSON response
531
+ and `"length"` for the two genuinely budget-limited responses. All typechecks,
532
+ hygiene and 1,820 model-free tests pass. The speculative tests also cover
533
+ bonus EOS at the limit and early callback stops. Evidence:
534
+ `spec-eos-serving-{before,after,typecheck,hygiene,model-free}.txt`, the failed
535
+ `mtp-eos-budget-http{,-review}.json` gate and the passing
536
+ `mtp-eos-budget-integrated-http{,-review}.json` replay.
537
+
538
+ The continuous scheduler already records the terminal cause in `BatchStats`;
539
+ the gateway now preserves it when returning `GenerateStats`. Two regression
540
+ tests reproduce the dropped stop/length field before this correction. The
541
+ expanded model-free tier passes 1,822 tests, with all typechecks and hygiene
542
+ passing. Two fresh default-scheduler servers then preserve all six HTTP
543
+ responses under synchronous/bounded projection evaluation. Both measured JSON
544
+ responses count 112 tokens and finish with `"stop"`; every request reports
545
+ the wire lane `"batched"` and increments the scheduler submission count.
546
+ Sources stay fixed and both servers exit cleanly. The test seeds MLX to 42
547
+ at startup and uses greedy sampling; an explicit request seed would force
548
+ serial placement. The initial harness used the wrong wire enum and stopped
549
+ at its routing assertion. Evidence:
550
+ `spec-eos-continuous-{before,after,typecheck,hygiene,model-free}.txt`,
551
+ `eos-budget-continuous-http.json` and
552
+ `eos-budget-continuous-v2-http{,-review}.json`.
553
+
554
+ The first draft-only quantization screen replaces the MTP head's eight dense
555
+ projections with affine g64 8-bit or 4-bit matmuls. Target computation and
556
+ fixed two-token verification are unchanged. Three fresh servers complete 21
557
+ requests with identical response text, counts and finish reasons, fixed
558
+ sources and clean exits. Relative to the dense draft, 8-bit complete time is
559
+ flat on code and falls about 2% on explanation/JSON; 4-bit falls 0.92%, 3.15%
560
+ and 3.06%. These are single-block diagnostics, requiring repeated pairs.
561
+ Acceptance changes slightly, including an extra code verification round.
562
+ The prototype retains dense buffers in every arm and adds 451/239 MB of
563
+ quantized arrays, so it establishes no memory saving. Its preload explicitly
564
+ disposes providers after request shutdown because normal CLI exit retains
565
+ the resident provider; the initial experiment stopped on that cleanup check.
566
+ Evidence: `mtp-quantized-draft-screen-http.json` and the completed
567
+ `mtp-quantized-draft-screen-v2-http{,-review}.json` under campaign reports.
568
+
569
+ The six-block repeat completes 126 requests across 18 fresh servers with
570
+ all 84 paired response comparisons identical, fixed sources, clean exits
571
+ and explicit provider disposal. Median paired request wall time with the
572
+ 4-bit draft falls 1.45% on code, 3.09% on explanation and 2.03% on JSON,
573
+ with six/five/four improving pairs. The 8-bit reductions are 0.34%, 2.16%
574
+ and 2.07%, with six/six/five improving pairs. TTFT is approximately flat.
575
+ The retained dense buffers mean sampled RSS remains unsuitable evidence
576
+ for the benefit of removing those weights. Quantized on-disk artifacts and
577
+ their actual loader/serving/memory gates are next; broader prompts, quants
578
+ and quiet acceptance remain. Evidence:
579
+ `mtp-quantized-draft-repeat-http{,-review}.json`.
580
+
581
+ The existing atomic CPU converter produces separate folded 4-bit and 8-bit
582
+ MTP artifacts with 238,930,944 and 451,267,584 tensor bytes, respectively.
583
+ Both retain all seven dense norm tensors and quantize exactly eight matrices.
584
+ The 4-bit artifact fails the initial comparison against GPU quantization,
585
+ but all 31 saved tensors match fresh CPU quantization or source passthrough.
586
+ CPU/GPU results differ in 16 quantized tensors, totaling 810,927 differing
587
+ bytes. The pinned Python oracle reproduces Bun's CPU and GPU hashes for all
588
+ three fc tensors, and the original companion files are unchanged. MLX 0.31.2
589
+ uses `std::rint` in the CPU affine quantizer and `round` in Metal; these
590
+ artifacts therefore require fresh serving/acceptance gates rather than
591
+ inheriting the GPU prototype's results. The 8-bit artifact passes all 31 CPU
592
+ comparisons, with fixed sources and zero active allocation after cleanup.
593
+ The provider now loads companion projections through the existing
594
+ `QuantizedLinear` when scales are present and retains dense projection
595
+ behavior otherwise. Its resource stack continues to own dense transpose
596
+ views; quantized companions retain no dense matrices. No draft policy or
597
+ target verification changes. The loader and initial serving/memory gates
598
+ pass below. Evidence: `mtp-draft-artifact-q4-{build,audit}.json`,
599
+ `mtp-draft-quant-oracle.json` and `mtp-draft-artifact-q8-build.json`.
600
+
601
+ The integrated loader passes all three typechecks, hygiene and the existing
602
+ 1,822-test model-free suite. Its ownership file also passes a new malformed
603
+ quantization-metadata case after earlier dense projections have loaded.
604
+ Nine materialized provider cycles cover dense/8-bit/4-bit heads three times
605
+ each. Loaded allocation is exactly 849,398,784/451,267,584/238,930,944 bytes,
606
+ and every cycle returns to its initial zero active allocation after repeated
607
+ disposal, without GC. The initial ownership helper read the counter before
608
+ lazy weight materialization and failed its coverage assertion at zero/zero;
609
+ it did not reproduce a leak. These are companion allocation measurements,
610
+ not complete-server RSS. The additional typecheck after the new ownership
611
+ case also passes. Evidence: `mtp-quantized-loader-{typecheck,hygiene,model-free}.txt`,
612
+ `mtp-quantized-loader-typecheck-v2.txt`,
613
+ `mtp-quantized-loader-ownership-tests-v2.txt` and
614
+ `mtp-artifact-ownership{,-v2}.json`.
615
+
616
+ The saved-artifact HTTP screen completes 21 responses across three fresh
617
+ servers. All 14 dense-versus-quantized comparisons preserve text, prompt and
618
+ completion counts, and finish reasons. Sources remain fixed; all servers exit
619
+ cleanly. Observation confirms dense projections only in the dense arm and
620
+ quantized projections only in the smaller arms. Explicit provider disposal
621
+ releases exactly each companion's tensor allocation and reaches the same
622
+ 11,936,569,478-byte resident-target baseline. This proves the smaller heads
623
+ load without retained dense matrices. Both quantized heads need one extra
624
+ target call on the code prompt, so cheaper projections do not guarantee a
625
+ faster request. Six balanced repeat blocks and instrumented native replay
626
+ are the next gates. Evidence: `mtp-artifact-screen-http{,-review}.json`.
627
+
628
+ Six balanced saved-artifact blocks then complete 126 responses from 18 fresh
629
+ servers, with all 84 paired text/count/finish comparisons exact, fixed sources
630
+ and clean exits. On the M4 Pro 24 GB, median paired complete-time changes
631
+ for code/explanation/JSON are -1.48/-3.67/-2.95% for the 4-bit companion and
632
+ -0.65/-2.29/-1.86% for 8-bit. The 4-bit arm wins 5/6, 6/6 and 5/6 pairs;
633
+ 8-bit wins all six pairs per fixture. TTFT is approximately flat. Median
634
+ per-server peak RSS is 13,036,756,992 bytes dense, 12,740,034,560 bytes 8-bit
635
+ and 12,531,777,536 bytes 4-bit. Unlike the earlier GPU projection prototype,
636
+ these arms actually replace the dense companion tensors. Every explicit
637
+ provider disposal releases exactly its companion's allocation to the same
638
+ resident-target baseline. Existing system swap prevents canonical benchmark
639
+ claims. Instrumented native token/logit/state checks, broader contexts/quants,
640
+ pressure and quiet-machine acceptance remain. Evidence:
641
+ `mtp-artifact-repeat-http{,-review}.json`.
642
+
643
+ The subsequent native audit runs all three saved companions against the same
644
+ full-prefill native generation on each short fixture. All nine emitted-ID,
645
+ usage and terminal-cause comparisons pass. For every verification window,
646
+ it clones the incoming target state and replays the same tokens individually
647
+ with the same hidden taps. All 1,184 complete vocabulary-logit rows across
648
+ 395 windows match byte-for-byte. At the first three and every sixteenth
649
+ window per request, every live attention and recurrent cache array also
650
+ matches, covering 48 complete state samples. All 386 commits, including 58
651
+ partial accepts, preserve the source position; all nine sources dispose.
652
+ Three fresh children exit cleanly with fixed sources and the same 16,390-byte
653
+ post-model-disposal active counter. This is instrumented correctness evidence,
654
+ not a timing result. Longer fixtures and other quants remain separate gates.
655
+ Evidence: `mtp-artifact-native{,-review}.json`.
656
+
657
+ Two new warehouse-ledger fixtures extend that audit to 688 and 1,968 prompt
658
+ tokens, using matching 2,048-token full-prefill chunks. Dense, 8-bit and 4-bit
659
+ companions preserve all six native/MTP ID, count and finish comparisons.
660
+ All 180 vocabulary-logit rows and 18 sampled complete cache states match;
661
+ 54 fully accepted commits preserve position. All twelve native/MTP outputs
662
+ are valid JSON and contain the independently computed requested records.
663
+ Each response counts 30 generation tokens. Sources stay fixed, all three
664
+ children exit cleanly and each leaves the same 16,390-byte active counter.
665
+ This extends correctness coverage; long serving pressure and other quants
666
+ remain open. Evidence: `mtp-artifact-native-context{,-review}.json`.
667
+
668
+ The matching saved-companion HTTP screen completes 15 warm/measured responses
669
+ from three fresh servers at those two context lengths. All ten paired
670
+ text/count/finish comparisons match, with fixed sources and clean exits.
671
+ Both measured fixtures stop at 30 generation tokens. Dense and quantized
672
+ projection counts and explicit provider release pass on every arm. This is
673
+ one serving screen, not a repeated performance or long-agent pressure claim.
674
+ Evidence: `mtp-artifact-context-screen-http.json`.
675
+
676
+ Matched-prefill timing screens now separate native tail-split, native full
677
+ prefill and dense fixed-two MTP in fresh HTTP servers. All 21 packed responses
678
+ preserve text/count/finish across the three arms. The RTN4 screen also completes
679
+ 21 responses; MTP matches native full-prefill throughout, while the two measured
680
+ code/explanation tail-split responses retain their known text differences.
681
+ Both screens have fixed sources and clean exits. On the M4 Pro 24 GB, complete
682
+ MTP request times improve 38.8–44.0% on packed and 27.6–38.1% on RTN4 relative
683
+ to native full-prefill. Each is one block, pending balanced repeats. MTP's
684
+ remaining TTFT advantage motivates checking native work before the first yield;
685
+ its decode loop constructs the next step before emitting the current token.
686
+ No default prefill policy changes. Evidence:
687
+ `mtp-matched-prefill-{r6,rtn4}-screen-http{,-review}.json`.
688
+
689
+ The packed matched-prefill repeat completes six balanced three-arm blocks,
690
+ 18 servers and 126 warm/measured responses. All text, token counts and finish
691
+ reasons match across native tail-split, native full-prefill and fixed-two
692
+ MTP. Sources stay fixed and all servers exit cleanly. Against native full
693
+ prefill on the M4 Pro 24 GB, MTP changes median paired complete request time
694
+ by -43.22% for code, -38.81% for explanation and -44.92% for JSON. Every pair
695
+ improves. Corresponding TTFT changes are -7.36/-8.42/-5.47%. Median paired
696
+ peak server RSS rises by 684,335,104 bytes, about 653 MiB; this samples startup
697
+ and the whole request sequence, not each fixture's physical-memory demand.
698
+ Native tail-split adds roughly 0.77–0.86% complete time and 7.37–10.46% TTFT
699
+ relative to native full-prefill. Existing swap and background CPU activity
700
+ in the final block keep these results diagnostic. All samples are retained.
701
+ Broader workloads and quiet acceptance remain.
702
+ Evidence: `mtp-matched-prefill-r6-repeat{,-review}.json`.
703
+
704
+ The RTN4 repeat also completes six balanced blocks, 18 servers and 126
705
+ responses with fixed sources and clean exits. MTP and native full-prefill
706
+ match all text/count/finish comparisons. Native tail-split retains the code
707
+ and explanation differences in every block. Relative to native full-prefill
708
+ on the M4 Pro 24 GB, median paired MTP complete-time changes are -34.95% for
709
+ code, -26.95% for explanation and -38.48% for JSON. Code and explanation
710
+ improve in five of six pairs; JSON improves in all six. Median paired sampled
711
+ peak server RSS rises by 850,173,952 bytes, about 811 MiB. The losing block
712
+ overlaps another application's TypeScript and Playwright work, followed by
713
+ system indexing and increased swap. Retain that block and the other samples;
714
+ these diagnostic medians do not substitute for quiet acceptance. Evidence:
715
+ `mtp-matched-prefill-rtn4-repeat{,-review}.json`.
716
+
717
+ The RTN4 cross-quant screen uses its unrotated target and the original
718
+ unrotated mlx-community MTP head, with the same three held-out prompts and
719
+ fixed/adaptive policies. All three servers exit cleanly with fixed sources.
720
+ Usage counts and finish reasons match, but both MTP arms differ from native
721
+ on the measured code and explanation responses. Those timings do not establish
722
+ identical-work speedups. The JSON response matches at 117 counted tokens;
723
+ its single-block complete times are 8.292/5.042/4.776 seconds for native,
724
+ fixed and adaptive MTP. Repeated timing and a first-divergence audit remain.
725
+ Keep the packed result specific to its tested artifact and prompts. Evidence:
726
+ `mtp-rtn4-screen-{plan,http,http-review}.json` under the campaign reports.
727
+
728
+ The RTN4 native follow-up reproduces code/explanation divergence at emitted
729
+ token indices 48/26 with seed 42 and greedy sampling. For each of 140 target
730
+ verification windows, it clones the pre-forward cache and replays the same
731
+ IDs through serial M1 forwards with the same hidden taps. All 420 projected
732
+ logit rows have identical values, and all cache offsets agree. Instrumented
733
+ MTP preserves the uninstrumented emitted IDs on all three fixtures. Thus a
734
+ local verification-window arithmetic difference does not explain these
735
+ responses. Native tail-split versus MTP full-prompt prefill and state
736
+ accumulation require separate controls.
737
+ Sources remain fixed. Evidence: `mtp-rtn4-divergence-v2-audit{,-review}.json`.
738
+
739
+ Two fresh processes isolate the prefill choice using the existing
740
+ `MLX_BUN_PREFILL_TAIL_SPLIT` setting. Native traces show 57+1, 53+1 and 91+1
741
+ prompt forwards with splitting, versus 58, 54 and 92 without it. MTP uses
742
+ full-prompt prefill in both processes. With splitting enabled, the earlier
743
+ code/explanation token differences recur. With splitting disabled, every
744
+ native emitted ID matches MTP on all three fixtures; MTP itself is unchanged
745
+ across processes. Counts are 128/128/117 including JSON EOS. Both children
746
+ exit cleanly and all source hashes stay fixed. This isolates the observed
747
+ trajectory differences to the native prefill policy for these fixtures.
748
+ Retain the original failed comparisons and add a native full-prefill arm
749
+ when measuring MTP, so prefill savings and drafting savings are distinct.
750
+ Changing that policy still needs its own oracle/quality gates. Evidence:
751
+ `mtp-rtn4-prefill-control{,-review}.json` under the campaign reports.
752
+
753
+ Variant 10 further shares small-M scatter work. Six warm AB/BA blocks at
754
+ gamma 2 and 3 on both enumeration and the earlier losing prose prompt now
755
+ retain an observed wall-time gain with identical token IDs. Enumeration
756
+ prefers gamma 3; prose prefers gamma 2. This changes the measured cost of
757
+ verification, not the recorded acceptance sequences. The reports are
758
+ `reports/qwen38-rd/mtp-v10-{planets,prose}-gamma{2,3}.json`. These repeated
759
+ fixtures are still diagnostic, with no general workload or quiet-machine
760
+ verdict. Native request rows are recorded in the eval DB under
761
+ `mlx-bun-native-diagnostic`, which does not replace the status page's
762
+ ordinary inference measurements.
763
+
764
+ The native MTP test now also uses the server's config-plus-tokenizer EOS set
765
+ and records it. The repeated variant-10 enumeration/prose cells contain no
766
+ additional EOS ID within their fixed budget, so that correction does not
767
+ truncate those sequences. The broader corpus audit found chat-EOS overrun
768
+ in extraction only. Its two DB rows and the old strict-fill rows now carry
769
+ explicit exclusion notes; corrected strict-fill reports are recorded separately.
770
+
771
+ The provider-lifetime audit found a separate cleanup bug. The Qwen provider's
772
+ old no-op `dispose()` assumed process-lifetime mapped weights, but `Weights`
773
+ now owns native MLX maps and each dense projection also retains a transpose
774
+ view. Three bounded load/use/source-dispose/provider-dispose cycles retained
775
+ 849,398,784 additional MLX bytes each, including after garbage collection.
776
+ The provider now owns a `DisposableStack`: weight-map cleanup is registered
777
+ first, then each derived transpose view, so disposal releases views before
778
+ maps. The same scope unwinds a partially constructed module. Disposal is
779
+ idempotent and opening a source after provider disposal is rejected. The
780
+ caller closes active request sources before unloading their provider, as for
781
+ the other providers. Inference operations and their ordering are unchanged.
782
+
783
+ The real 27B MTP-head probe returns active MLX memory to zero after each of
784
+ three cycles, before GC. Model-free tests also cover map/view release, repeat
785
+ disposal and failure at the final norm after earlier views have been created.
786
+ Evidence: `reports/qwen38-rd/mtp-provider-dispose-{before,after}.json` and
787
+ `mtp-provider-ownership-tests.txt`. All three typechecks, hygiene and the full
788
+ model-free tier pass: 1,803 tests, 10 skips and no failures. The post-change serving
789
+ gate completes all three native/fixed/adaptive servers with clean exits and
790
+ matching response text on the frozen requests. Only the previously documented
791
+ JSON EOS-count difference remains; sources stay fixed. Evidence:
792
+ `reports/qwen38-rd/mtp-post-dispose-http.json`. This addresses drafter unload/replacement;
793
+ it is separate from the already-fixed target-cache disposal and admission work.
794
+
795
+ ### 4.8 Paired Qwen MTP conversation state
796
+
797
+ An isolated candidate adds an optional prefix-state interface to `DraftSource`.
798
+ The Qwen provider retains one evaluated prefill boundary containing target KV
799
+ and recurrent state, draft KV, the preceding target hidden row, and exact token
800
+ IDs. Restore transfers ownership only when the incoming prompt extends those
801
+ IDs under the same target and adapter namespace. A mismatch releases the old
802
+ entry. Reuse never trims recurrent state to an arbitrary common prefix.
803
+
804
+ The MTP cache is one position behind the target at a prefill boundary. Extending
805
+ it first pairs the next incoming token with the saved preceding hidden row,
806
+ then processes the remaining suffix. Future generation cannot alter the retained
807
+ prefix. The candidate caps this one snapshot by the configured prompt-cache
808
+ budget and disposes it with the provider. It does not yet retain completed
809
+ generation state or participate in the ordinary cache's pressure eviction,
810
+ SSD persistence, or byte telemetry. It remains experimental and separate from
811
+ the default serving path.
812
+
813
+ On the M4 Pro, the unit gate passes 18 tests. Native prompts at 128, 513 and
814
+ 2,051 tokens pass six restore checks with unchanged state hashes, token IDs
815
+ and acceptance traces; repeated allocation is stable at each length. The HTTP
816
+ gate completes eight responses with identical text, token counts and finish
817
+ reasons across uncached and cached MTP policies, including repeated cache hits.
818
+ The native restore check also passes with Luke's coding sampling recipe,
819
+ temperature 0.6, top-p 0.95, top-k 20 and seed 42: six unchanged state restores
820
+ and exact repeated continuations. Evidence: `mtp-prefix-luke-sampling.json`.
821
+ These gates establish the initial composition, not complete long-task speed or
822
+ pressure acceptance. The fresh thinking-off Pi task reused saved prefixes and
823
+ finished generation without compaction, but its generated app failed acceptance.
824
+ The task disposition and next frozen configuration are in decode-speed-program
825
+ section 7.7. Raw evidence: `reports/qwen38-rd/mtp-cache-candidate-native.json` and
826
+ `reports/qwen38-rd/mtp-cache-http-gate.json`.
827
+
828
+ ## 5. Files
829
+
830
+ - `src/spec/source.ts` — the seam. `src/spec/serve-loop.ts` — the executor.
831
+ - Sources: `two-model.ts`, `assistant-source.ts` (+ `drafter.ts`),
832
+ `dflash-source.ts`, `deepspec-source.ts`, `ngram-source.ts`,
833
+ `qwen-mtp-source.ts`, `glm52-mtp-source.ts`.
834
+ - `src/spec/generate.ts` — the standalone optiq-oracled assistant loop
835
+ (`specGenerate`, full-prompt prefill, throws on ring wrap).
836
+ - `src/spec/dspark/`: `module-dflash.ts` (our module), `deepspec-module.ts`
837
+ (DeepSeek's), `loader.ts`, `data-dflash.ts`, `generate-dflash.ts`,
838
+ `loss.ts`, `sample.ts`, `calibration.ts`, `quantize-drafter.ts`,
839
+ `ab-stats.ts`; `module.ts`/`data.ts`/`generate.ts` = v1 baseline (refused
840
+ for serving).
841
+ - `src/model/gemma4.ts` `hiddenTap`/`captureLayer`; `src/model/qwen3_5.ts`
842
+ `hiddenTap`; `src/model/gemma4-base.ts` `Cache` (`trim(n, bypass)`, the
843
+ spec-round trio); `src/model/qwen3-delta.ts` `SSMCache.specRound*`.
844
+ - `src/server.ts` `detectDraftKind` / `loadContext` (provider selection, γ
845
+ pinning, tokenizer probe, fail-fast pairing probe-open, admission).
846
+ - `scripts/dspark.ts <train|regen|calibrate|quantize|ab>` — the DSpark
847
+ pipeline (one dispatcher, jobs in `scripts/dspark/`):
848
+ ```
849
+ bun scripts/dspark.ts regen --topics <topics.txt> --out <data> --max-resp 320
850
+ bun scripts/dspark.ts train --data <data> --out <ckpt> --iters 8000 --batch 8 [--resume] [--ddraft 2560] [--seq-head rnn]
851
+ bun scripts/dspark.ts calibrate --drafter <ckpt> --data <prompts.jsonl>
852
+ bun scripts/dspark.ts quantize <drafter-dir> [--out <dir>] [--bits 4|8] [--group-size 32|64]
853
+ bun scripts/dspark.ts ab --target <id|dir> --drafter-a <dir> --drafter-b <dir> [--num-draft-tokens N] [--n-prompts 32] [--max-drop 3]
854
+ ```
855
+ (The standalone measure/compare/bench one-offs — `dspark-measure-dflash`,
856
+ `dspark-deepspec-compare`, `spec-bench`, `spec-dump`, `qwen38-mtp-ab` —
857
+ were deleted 2026-08-23; git history.)
858
+ - Oracles: `scripts/oracle/oracle-spec.py` (optiq spec_generate),
859
+ `scripts/oracle/oracle-dspark-deepspec.py` (DeepSpec temp-0 trace),
860
+ `scripts/oracle/oracle-qwen38-mtp-logits.py` (mlx-vlm drafter logits).
861
+ - Tests: `spec-decode.test.ts` (e4b assistant, exact + knife-edge),
862
+ `spec-decode-12b.test.ts` (long-prefix rollback gate), `spec-serve.test.ts`
863
+ (L1 knife-edge, grammar × spec), `spec-serve-assistant.test.ts`,
864
+ `spec-ngram.test.ts`, `qwen35-spec-ngram.test.ts`,
865
+ `qwen-ssm-specround.test.ts`, `qwen38-mtp.test.ts` (opt-in),
866
+ `dspark-{smoke,infer-loop,rnn,calibration,deepspec,ab-stats}.test.ts`.
867
+ - User-facing mirror: docs/reference/server-config.md (`--draft-model`,
868
+ `--draft-kind`, `--num-draft-tokens`, `--ngram-*`, `--mtp`,
869
+ `MLX_BUN_PREFILL_TAIL_SPLIT`, `MLX_BUN_DSPARK_MINCONF`), cli.md,
870
+ docs/reference/server-config.md.
871
+
872
+ ## 6. Composition rules (as served)
873
+
874
+ - A mounted draft routes EVERY request to the serial lane (B=1 latency
875
+ mode).
876
+ - Quantized KV (any axis) wins over spec: drafted requests decode serially
877
+ WITHOUT speculation, with a startup warning (spec lane is bf16-KV-only).
878
+ - Structured output composes (the constrained verify walk).
879
+ - Prompt-cache reuse is bypassed on the spec path.
880
+ - Native GLM MTP and an explicit `--draft-model`/`--draft-kind` are mutually
881
+ exclusive; one provider per request so two draft histories never advance
882
+ target or grammar state twice.
883
+
884
+ ## 7. Token fast-forwarding — lookup, not speculation
885
+
886
+ **No draft/verify contract is involved.** Nothing in this section routes
887
+ through `src/spec/`; there is no drafter, no verify forward, no rollback, and
888
+ no comparison against what the model "would have" produced. It is filed here
889
+ only because the spec lane is the neighbouring mechanism and the two are
890
+ routinely confused. The one fact borrowed from it is that a multi-token
891
+ mid-decode append works — which the grammar jump-forward burst already proved.
892
+
893
+ **The doctrine.** A transformer is a next-token function over a token
894
+ sequence. It has no memory of who wrote which token, so an APPENDED token is
895
+ indistinguishable from a sampled one: injection is pure context extension. When
896
+ the engine already knows the next *m* tokens, consulting the model for them
897
+ buys nothing — it writes them into the KV itself with ONE chunked forward and
898
+ resumes sampling after them. The model is consulted only for tokens it does
899
+ not already know.
900
+
901
+ **What "already knows" means in the strict tier.** A request's
902
+ `tools` array plus the model's own chat template determine large parts of the
903
+ assistant turn: the tool-call opening scaffold, the remainder of a tool name
904
+ after its first disambiguating token, the punctuation from the name to the
905
+ arguments object, and — when the schema has exactly one required property —
906
+ the argument key itself. Those spans are compiled per request by **scaffold
907
+ probing**: render the same conversation several ways, tokenize each, and diff
908
+ the token id sequences. What does not change is the template's fixed scaffold,
909
+ by construction. The compiler knows nothing about tool-call syntax: Qwen3.5's
910
+ `<function=NAME>`/`<parameter=KEY>` XML, a JSON `<tool_call>` template, and
911
+ GLM's `<arg_key>`/`<arg_value>` all compile through the same diff, and a
912
+ template that does not render `tool_calls` (or renders them without the name)
913
+ produces identical probes, an empty diff, and no rows — degrade to no-fill,
914
+ never wrong output. Same technique as `request-prep.ts::stableLenFor` uses on
915
+ the generation primer, applied to the assistant turn.
916
+
917
+ **Serialization and state invariants.**
918
+ - **Every span is sliced from a rendering the model could actually produce** —
919
+ real tool names, real schema keys. The diff only decides WHERE to cut; the
920
+ ids always come from the real-name rendering, so every cut is a token
921
+ boundary of a producible stream. This is not a stylistic preference. Qwen3.5
922
+ renders `<function=get_weather>`, which the tokenizer encodes as
923
+ `< function =get _weather >` — `=get` is ONE token. A probe named
924
+ `zzalphatoolqq` splits the same position as `… = zzalpha…`, inventing a
925
+ boundary after `=`. A scaffold row cut there injects a bare `=`, the model
926
+ then emits `get` where it would have emitted `=get`, and the result is
927
+ byte-identical TEXT over divergent token IDS — with KV that no longer
928
+ matches what a plain decode would have written. Caught on Qwen3.5-0.8B
929
+ (2026-08-31); the regression gate is id containment, not text containment
930
+ (`tests/unit/fill-schema-rows.test.ts`, `tests/parity/fill-strict.test.ts`).
931
+ With one tool the whole header through the sole key is one span; with
932
+ several, the scaffold ends exactly where the real merged name tokens diverge
933
+ (`=get` vs `=search`) and per-tool name rows resume from there.
934
+ - Never `encode(fragment)` in isolation — same reason.
935
+ - Every row is anchored at a token carrying LETTERS (e.g. `<tool_call>`,
936
+ `</parameter`), with leading template-join whitespace dropped from the
937
+ trigger. Markup alone is not enough: `</` opens every closing tag a model
938
+ might write in prose or a code block, so arming on it would inject tool-call
939
+ markup into an HTML snippet.
940
+ - A close row exists only when EVERY tool in the request takes exactly one
941
+ required argument — otherwise the model may still be about to write a second
942
+ `<parameter=…>`, and injecting the close would silently drop it. The
943
+ request's own schema decides whether the row exists.
944
+ - Ending the turn stays the model's decision: every span is cut at the first
945
+ EOS id (the grammar jump burst's missing EOS check is deliberately not
946
+ inherited).
947
+ - A span shorter than 2 ids is rejected — it would save no forward.
948
+ - The cache-alignment invariant: the fill forward carries ONLY the injected
949
+ ids. The normal step already consumed the trigger token and wrote its KV;
950
+ forwarding `[trigger, ...ids]` would duplicate a position and silently
951
+ corrupt both the KV and `PromptCache.put`'s key. `MLX_BUN_FILL_TRACE=1`
952
+ asserts `cache[0].offset === promptTokens.length + forwarded.length` on both
953
+ sides of every append.
954
+ - Injected tokens flow through `CompletionSink.push` one at a time, in the
955
+ same shape as the jump burst, so `StopMatcher` fires mid-burst exactly where
956
+ it would have. The append happens BEFORE any of the burst's yields, which is
957
+ what makes a consumer break mid-burst safe — `forwarded` already describes
958
+ the cache exactly.
959
+
960
+ **The deviation, stated plainly.** Injection bypasses the sampler. At
961
+ `temperature > 0` a filled reply is not the same draw an unfilled one would
962
+ have been (the sampler is never asked about those positions). That is a
963
+ behavior-policy deviation, not a numerics one, and it is why the feature is
964
+ opt-in (`MLX_BUN_FILL=strict`, default off). At `temperature 0` the strict
965
+ tier must demonstrate token identity against ordinary generation. A fixed
966
+ template serialization does not prove that the unconstrained model chooses
967
+ it. The weights gate is `tests/parity/fill-strict.test.ts`; section 7.4 states
968
+ the stronger contract needed for a claim of guaranteed deterministic replay.
969
+
970
+ **Mechanism.** `src/generate.ts::generateInner` — the same burst shape as the
971
+ grammar `jumpEmit` branch, with the DEFERRED trigger (fill reads the token the
972
+ pipelined loop already read back; it does not add grammar's eager readback).
973
+ On an ASSERT fill the in-flight sample for the next position is dropped
974
+ unexamined — a discarded pipeline dispatch, counted as `wastedSamples`, not a
975
+ rejected draft. Grammar and fill are forbidden in the same iteration
976
+ (asserted).
977
+
978
+ ### 7.1 One interface, two policies (`src/fill/proposal.ts`)
979
+
980
+ Every source that can propose the next tokens implements one interface, and
981
+ one apply primitive in the decode loop hosts both policies — because the
982
+ EXPENSIVE half is identical: one chunked forward advancing the KV (and the
983
+ recurrent SSM state) over the whole span.
984
+
985
+ ```ts
986
+ interface ProposalSource { propose(tail: TokenView): Proposal | null }
987
+ interface Proposal { ids: number[]; policy: "assert" | "verify"; origin: … }
988
+ ```
989
+
990
+ - **assert** — the tokens are DETERMINED (a template scaffold). Append and move
991
+ on: no readback, no checkpoint, no rewind. Strict rows are always assert.
992
+ - **verify** — the tokens are LIKELY (a session self-copy). Position 0 is
993
+ checked BEFORE the forward against the in-flight sample — free, and a
994
+ mismatch costs nothing because nothing has been written yet. Positions
995
+ 1..m−1 are checked against the argmax already sitting in THAT SAME forward's
996
+ logits, so verification adds no pass over the weights. The rejected tail is
997
+ rewound and decode resumes at the first disagreement, which reproduces
998
+ exactly the stream an unfilled run would have produced. A wrong guess costs
999
+ a rewound forward, never a wrong token.
1000
+
1001
+ **Rewind reuses the spec lane's cache contract, not its executor.** Trimmable
1002
+ caches drop the tail with `trim(n)`; NON-trimmable recurrent caches (SSMCache —
1003
+ gated-DeltaNet conv + recurrent state) go through `specRoundBegin()` before the
1004
+ forward and `specRoundRollback(keep)` after the accept walk, which restores the
1005
+ pre-round snapshot and bit-exactly REPLAYS the accepted prefix (§2). Those are
1006
+ plain `Cache`-interface methods driven by the owning layer, so nothing in
1007
+ `src/spec/serve-loop.ts` is touched. A model whose caches can do neither still
1008
+ gets assert fills; verify proposals are dropped and counted
1009
+ (`usage.fill.verifyUnsupported`). Checkpointing is real work (~48 DeltaNet
1010
+ states on Qwen3.8) and is measured: `usage.fill.checkpointMs`.
1011
+
1012
+ **Not migrated (deliberate).** The shipped `DraftSource` roster (ngram, MTP,
1013
+ two-model, DSpark) keeps its own seam and executor. The adapter — a DraftSource
1014
+ wrapped as a verify-policy ProposalSource, so one apply primitive serves both
1015
+ lanes — is future work; rewiring it here would have put the spec lane's oracles
1016
+ at risk for no new capability.
1017
+
1018
+ ### 7.2 The echo tier (K3c, Lab, `MLX_BUN_FILL=echo`)
1019
+
1020
+ A growing per-request k-gram index over promptIds plus everything emitted
1021
+ (injected tokens included — the model cannot tell them apart, so neither does
1022
+ the index). The structure is the TS port of `GrowingMatcher` from the corpus
1023
+ study: sequence + k-gram → positions, appended incrementally, bucket scan
1024
+ capped at the NEAREST occurrences (`src/fill/echo-index.ts`).
1025
+
1026
+ The lookup is the boring half. The **stopping rule** is the measurement that
1027
+ made the tier worth building:
1028
+
1029
+ - **Branch-point stopping.** A match says where this context occurred before;
1030
+ it says nothing about how far the future agrees with the past. So a span
1031
+ extends only while EVERY nearby occurrence continues the same way, and stops
1032
+ the moment they disagree. That fork is exactly where old-query-vs-new-query
1033
+ divergence lives, and delimiters fall out for free — a closing quote is where
1034
+ histories fork.
1035
+ - **Corroboration decides the policy.** `assert` requires (a) no branch stop,
1036
+ (b) the span ends at a delimiter-class token, AND (c) at least TWO
1037
+ occurrences agreed across the whole span. A single occurrence is a copy, not
1038
+ a pattern: it will happily replay whatever followed it in the transcript —
1039
+ including another role's turn — so it is the model's call. Verified on the
1040
+ 0.8B tokenizer against a synthetic agent transcript: an uncorroborated copy
1041
+ ran 30 tokens past `</tool_call>` into a mocked tool RESULT. Under verify
1042
+ that costs one rewound forward; under assert it would have been wrong
1043
+ output. Everything else is `verify`.
1044
+ - **Delimiters** are read off the template by the strict-row compiler (the
1045
+ first non-whitespace token that follows an argument value — `"` for JSON,
1046
+ `</` for Qwen3.5's XML). They clamp ECHO spans only: a strict scaffold
1047
+ legitimately contains the same tokens as structure (`{"name": "` is three
1048
+ quotes deep) and is determined by construction.
1049
+
1050
+ Deterministic value transforms (url-encode, JSON-escape) as
1051
+ `(source-span, transform)` table entries are NOT implemented — the seam is the
1052
+ same `ProposalSource` interface, and the corpus rates that motivate them are in
1053
+ PLAN K3.
1054
+
1055
+ The bar for this tier is NOT token identity (sampling never guaranteed that):
1056
+ it is a paired A/B on task success and wall clock over mocked-replay agent
1057
+ sessions. Default off until that lands.
1058
+
1059
+ **Composition.** Serial lane only, and it never FORCES a request serial — a
1060
+ batch-placed request simply does not fill, because `generate()` is the only
1061
+ site that reads `options.fill`. Refused for: a compiled grammar (forced tokens
1062
+ are its job), `logprobs`/`top_logprobs` (injected tokens have no distribution
1063
+ row — the same rule as `shouldUseGrammarJump`), a user-fixed `seed`
1064
+ (reproducibility: the step index would skip injected positions), media
1065
+ prompts, a mounted draft model, quantized/TurboQuant KV (post-conversion
1066
+ multi-token append is L-generic but unvalidated), and sliding-window models
1067
+ (RotatingKVCache multi-token append is O(window) via `#updateConcat` — one
1068
+ warning, then no fill).
1069
+
1070
+ **Mismatch policy.** If `parseGeneratedToolCalls` rejects the emitted markup
1071
+ on a request whose rows fired, `usage.fill.parseFallback` increments and
1072
+ strict rows disarm. Today the served parse runs at sink flush, i.e. after the
1073
+ generation ends, so within one request this is telemetry; the seam is wired so
1074
+ an incremental parse (or a plan cached across requests) disarms for real.
1075
+
1076
+ ### 7.3 Measuring it (K3d, `scripts/fill.ts`)
1077
+
1078
+ Neither tier ships on a vibe. The harness replays RECORDED agent sessions with
1079
+ their tool results mocked verbatim from the JSONL — the transcript IS the
1080
+ environment, a model cannot tell an executed result from a recorded one — so a
1081
+ whole corpus replays deterministically and side-effect-free. The session reader
1082
+ is a straight port of the corpus study's (`reports/k3-replication/analyze.py`
1083
+ `load_session` / `serialize_tool_call`), including its `excludeFromContext` rule
1084
+ and corrupt-line tolerance, so the measured rates in PLAN K3 keep describing
1085
+ what the harness actually sends. The recordings carry no `tools` array, so the
1086
+ harness SYNTHESIZES one per session (union of observed argument keys; keys
1087
+ present in every call marked required) — a reconstruction that is itself part
1088
+ of what the A/B measures.
1089
+
1090
+ ```
1091
+ bun scripts/fill.ts replay --sessions <file|dir> --server-url <url>
1092
+ bun scripts/fill.ts ab --sessions <file|dir> --url-a <off> --url-b <echo>
1093
+ bun scripts/fill.ts ab --showcase fixtures/showcase-silicon-exchange.txt --url-a … --url-b …
1094
+ bun scripts/fill.ts report <runs.jsonl> [--by-tool]
1095
+ ```
1096
+
1097
+ Fill is a process-wide lever, so the two arms are two SERVERS (A without
1098
+ `MLX_BUN_FILL`, B with it). Turns interleave A, B, A, B … so load drift and
1099
+ thermal state land on both arms, and the verdict pairs on (session, turn, rep).
1100
+
1101
+ **The echo-tier gate**: task-output agreement must not drop within CI AND
1102
+ median wall clock must strictly improve. Agreement is the CALL the model made
1103
+ (tool name + arguments, key-order independent) or a prose turn's normalized
1104
+ text — never token identity, which sampling never promised and which an agent
1105
+ loop does not care about. "Does not drop" is McNemar's paired counts with a
1106
+ one-sided 95% bound, so a tie or a swing inside the noise passes and a real
1107
+ regression does not. The strict tier keeps its own, stricter bar (token
1108
+ identity at temperature 0, `tests/parity/fill-strict.test.ts`).
1109
+
1110
+ **The showcase** runs ONE large tool-dense prompt ×3 interleaved and reports
1111
+ emitted vs decoded tok/s, the fill fraction, time-to-first-tool-call, and the
1112
+ comparison with a one-token-per-forward bandwidth estimate. An emitted rate
1113
+ above that estimate can show amortized weight reads; the injected positions
1114
+ still pass through the state-update graph. `apparent = decoded / (1 − fillFrac)`
1115
+ omits append cost and is not a speedup measurement.
1116
+
1117
+ The verdict math is model-free and unit-tested against a stub server
1118
+ (`tests/research/fill-echo-replay.test.ts`), including the ways it FAILS — a
1119
+ harness that can only produce PASS is not a gate.
1120
+
1121
+ **Live result, 2026-09-02 (M1 Max 32 GB, mjriii/Qwen3.8-27B 4.80 bpw, six
1122
+ recorded pi sessions, 32 paired turns, `--batch 1`).** Two servers do not fit
1123
+ the wired ceiling at 27B, so the arms ran SERIALLY (one server restarted
1124
+ between them; `fill report` now prints the paired verdict for two `replay`
1125
+ files). Two false starts are findings in their own right: (1) the default
1126
+ server lane is the batch scheduler, which never fills — an A/B on it is two
1127
+ identical arms (fill 0.0%, same 4925 tokens); the serial lane needs
1128
+ `--batch 1`. (2) The strict tier compiled ZERO rows on the 27B: Qwen3.x
1129
+ thinking templates end the primer in `<think>\n` and the reply opens with
1130
+ `\n</think>`, the tokenizer merges the two newlines, the primer stops being a
1131
+ token prefix and the compiler bailed silently. Fixed (`schema-rows.ts
1132
+ after()`: text-level boundary, suffix tokenized on its own; 4 rows compile on
1133
+ the 27B). So the run measured the ECHO tier alone:
1134
+
1135
+ | arm | tok/s emitted · decoded | fill | median wall | agreement |
1136
+ |---|---|---|---|---|
1137
+ | fill-off | 8.0 · 8.0 | 0 | 7663 ms | 3.1% |
1138
+ | fill-echo | 8.1 · 7.5 | 7.4% (bash 10%, read 32%) | 7630 ms | 6.3% |
1139
+
1140
+ 369 tokens injected in 66 echo events; verify accepted 369 / rejected 1195
1141
+ (76% of proposed span positions), 31 branch stops, 147 ms of checkpoints.
1142
+ Agreement held (B-only agreements 1, A-only 0; the McNemar bound cannot
1143
+ resolve +3 pts at n=32). **Wall clock ×1.00 — the echo gate FAILS**: each
1144
+ event pays a multi-token forward for its whole span and a rewind, and with
1145
+ three of four proposed positions rejected the forward compute per accepted
1146
+ token exceeds a decode step. Echo stays Lab, default off. Levers before a
1147
+ rerun: a longer anchor (`MLX_BUN_FILL_K`), fewer candidates, a span cap near
1148
+ the observed accepted length (~5–6), and asserting corroborated spans instead
1149
+ of verifying. Absolute rates are not quotable: the box ran at 8 tok/s (2.3 GB
1150
+ swapped, the 16 GB server plus the day's leftovers) where 19 is predicted;
1151
+ both arms saw the same conditions.
1152
+
1153
+ **Strict tier, same day, corrected rows** (serial arms back to back, 32 paired
1154
+ turns): fill 5.3% of emitted tokens, all proposed strict spans injected
1155
+ (assert policy, no agreement readback), identical tool calls on every paired turn,
1156
+ median wall ×0.99 (the win is bounded by the fill fraction). The proposal
1157
+ trace (`MLX_BUN_FILL_TRACE=<file.jsonl>`, `bun scripts/fill.ts trace`) is
1158
+ what made the rows right: the first strict run's list showed the scaffold row
1159
+ firing on `</think>` and asserting `<tool_call>…` where the model would have
1160
+ written prose ("Hey", "You", "I") in 10 of 47 template proposals, while the
1161
+ schema-derived name/close rows agreed 9/9. The scaffold trigger now runs
1162
+ through the first token where the call rendering diverges from a plain-
1163
+ content rendering — the model's own `<tool_call>` — and the failure class is
1164
+ gone by construction. Echo-tier list, same tool: 66 events, verify accepted
1165
+ 369 / rejected 1195, survival by position falls to ~50% by position 6 — the
1166
+ policy levers named above are what the list points at. **Showcase measured**
1167
+ (same day, `fixtures/showcase-silicon-exchange.txt`, 1024 tokens × 2 reps,
1168
+ serial arms): echo 8.9 tok/s emitted = 8.9 decoded, off 9.0 — fill 0.0%.
1169
+ A fresh build prompt has no earlier session text to copy, so the echo index
1170
+ has nothing to propose; the bandwidth-ceiling demonstration (25.2 tok/s for
1171
+ 14.76 GiB at 400 GB/s) needs a turn that REPEATS earlier context. The fixture
1172
+ is the wrong shape for the tier it was written for — a multi-turn edit loop
1173
+ is the honest showcase. Repro to file: turn 8 of session
1174
+ `2026-08-18T04-34-36-341Z_01a01326…` kills the server on either lane (bare
1175
+ MLX C++ exception, three of three runs, `runs/k3/serve-*.log`).
1176
+
1177
+ **Files.** `src/fill/proposal.ts` (the interface + the two policies),
1178
+ `src/fill/fill-session.ts` (sources, clamping, flags, telemetry),
1179
+ `src/fill/schema-rows.ts` (scaffold probing + value delimiters),
1180
+ `src/fill/echo-index.ts` (k-gram index + branch-point rule), the apply
1181
+ primitive in `src/generate.ts`, `fillPlanFor` in `src/serve/request-prep.ts`,
1182
+ the attach + serve-level refusals in `src/serve/chat-stage.ts`, `usage.fill` in
1183
+ `src/serve/{completion-executor,openai-wire}.ts`. Tests:
1184
+ `tests/unit/fill-{session,schema-rows,echo-index,generate-loop}.test.ts`,
1185
+ `tests/serve/fill-{composition,stream}.test.ts`,
1186
+ `tests/parity/fill-strict.test.ts` (weights-gated). Harness: `scripts/fill.ts`
1187
+ + `scripts/fill/{session-replay,runner,client,metrics,args,replay,ab,report}.ts`,
1188
+ `tests/unit/fill-session-reader.test.ts`,
1189
+ `tests/research/fill-echo-replay.test.ts`,
1190
+ `fixtures/showcase-silicon-exchange.txt`.
1191
+ User-facing mirror: `docs/reference/server-config.md` (`MLX_BUN_FILL`,
1192
+ `MLX_BUN_FILL_MAX_SPAN`, `MLX_BUN_FILL_TRACE`, `MLX_BUN_FILL_K`,
1193
+ `MLX_BUN_FILL_CANDIDATES`, `MLX_BUN_FILL_INDEX_MAX`), `server-api.md`
1194
+ (`usage.fill`).
1195
+
1196
+ ### 7.4 No-verification replay contract and next experiments
1197
+
1198
+ The 2026-09-04 Qwen performance program makes this a dedicated track.
1199
+ Production `assert` must remain free of verification. Separate correctness
1200
+ runs may inspect every skipped prediction; those instrumented runs cannot
1201
+ supply the performance number. `applyProposal` in `generate.ts` already
1202
+ implements this separation: `verify` or a trace filename computes logits at
1203
+ all appended positions, while normal `assert` projects only the final hidden
1204
+ state to resume sampling. It still forwards the span through model layers
1205
+ and updates attention KV and GDN state.
1206
+
1207
+ This track appends serialization fixed by the current tool-call state. It
1208
+ does not predict unknown tokens. Once the protocol fixes a field following
1209
+ a selected tool name, that field can become context immediately; sampling
1210
+ resumes where the tool or argument value has a choice. Map fixed protocol
1211
+ fields through the model's own chat template, including the preceding token
1212
+ boundary. Pi's client-side tool representation and a model's wire rendering
1213
+ can differ. Parser state must distinguish structural fields from the same
1214
+ text inside argument strings, quoted examples or code. Offline parity audits
1215
+ validate the implementation; they do not add verification to served asserts.
1216
+
1217
+ There are different possible guarantees:
1218
+
1219
+ | source | what would justify an assertion |
1220
+ |---|---|
1221
+ | Enforced output grammar/protocol | All legal continuations share the emitted token span at the current parser state. Account for tokenizer boundaries and stop conditions. Grammar uniqueness over bytes does not automatically imply unique token IDs. |
1222
+ | Deterministic application transform | An explicit contract fixes the source span and transformation, such as escaping a selected literal. Choosing which source/value to emit remains a model/application decision. |
1223
+ | Cached continuation | Exact model/quant, prefix, recurrent state, positional metadata and sampler/constraint state establish an identical continuation. A matching short suffix is insufficient. |
1224
+ | Template/schema row | Establishes a valid serialization. Without enforced generation constraints, token identity with the model is an empirical gate and can fail even at temperature zero. |
1225
+ | Repeated text | Agreement among observed occurrences is evidence, not certainty; retain the existing Lab distinction for echo assertions. |
1226
+
1227
+ Audit suffix triggers against real parser/role state, tool-name prefixes,
1228
+ optional/extra parameters, `additionalProperties`, multiple calls, quoted
1229
+ markup, code blocks, escaping, EOS and partial-burst cancellation. The original
1230
+ strict row source matched a token suffix without an independent parser-state
1231
+ guard. Do not interpret an all-assert `accepted` count as verified agreement.
1232
+
1233
+ A CPU-only audit with the actual R6 tokenizer/template finds a concrete close
1234
+ boundary error in both thinking modes. The compiled close trigger ends at
1235
+ `</parameter`, before `>`. A valid bash argument containing
1236
+ `printf "%s" "</parameterization>"` therefore triggers the structural close
1237
+ inside argument data. Injecting the current row changes the parsed command to
1238
+ `printf "%s" "`. Both complete calls parse; the suffix alone cannot establish
1239
+ that the value ended. The audit constructs valid token streams from the exact
1240
+ compiled trigger IDs; it does not claim these are observed model trajectories
1241
+ or quantify their frequency. Scaffold/close rows also match reasoning, quoted
1242
+ prose and fenced examples without a role/parser guard. Evidence:
1243
+ fill-parser-boundary-r6.json. Keep fill off by default. Complete the delimiter
1244
+ and establish protocol state before asserting its continuation; runtime model
1245
+ verification is not the remedy for a missing structural proof.
1246
+
1247
+ An isolated parser prototype now waits for the complete `</parameter>` or
1248
+ `</arg_value>` delimiter and requires a request-local tool-only assistant
1249
+ context, optionally following closed reasoning. It reuses the tool parser
1250
+ with full input consumption and no repair; quoted or fenced examples,
1251
+ nested reasoning, unfinished literals, duplicate XML/GLM keys and requests
1252
+ above the probe budget decline strict fills. Cached row plans create context
1253
+ from the current prompt and do not retain the first request's messages.
1254
+ The CPU-only real-R6-tokenizer audit rejects all sixteen negative contexts
1255
+ and preserves four positive rendered token streams across both thinking
1256
+ modes, including the original `</parameterization>` command. Each positive
1257
+ case injects nineteen tokens in two spans with zero verification events.
1258
+ The broader parser/stream suite passes 138 tests. Evidence:
1259
+ `fill-boundary-tokenizer-audit-v4.json` and `fill-boundary-cpu-tests-v8.log`.
1260
+ The parser and model-owned append method are integrated in the main tree.
1261
+ The gates below distinguish the original short-context integration from
1262
+ the subsequent attention-boundary correction.
1263
+ On the consolidated MLX 0.32.2 core with the interleaved R6 artifact and
1264
+ variant 13, the first weather fixture emits identical tokens but four-token
1265
+ appends change recurrent state and continuation logits. One-token appends
1266
+ restore exact state, all four continuation probes and disposal accounting,
1267
+ with the same twelve injected tokens and zero verification. The earlier
1268
+ four-token acceptance used a different runtime/artifact configuration;
1269
+ it does not establish this combination. Isolate the changed arithmetic
1270
+ before selecting an append shape. Evidence: `fill-boundary-native-r6.json`
1271
+ and `fill-boundary-native-m1-r6.json`. A first-layer operation audit localizes
1272
+ the drift to native affine projections: embedding and normalization match,
1273
+ but MLX's new multirow matvec differs from repeated M1. Broadcast weight
1274
+ views with input shape `[M,1,K]` select native batched M1 instead. All six
1275
+ boundary/chunk cases then match every captured intermediate and both state
1276
+ arrays exactly, without copying stored weights. The arithmetic prototype
1277
+ then passes seven full native fixtures per R6/RTN4 quant, with exact emitted
1278
+ IDs, complete state, four continuation probes, usage and disposal accounting.
1279
+ Runtime verification remains zero. RTN4's negative-intent code-block prompt
1280
+ produces a valid weather call in both arms; its original no-fill expectation
1281
+ is retained as a failed fixture assumption, and the rerun classifies that
1282
+ cell as a generated-call identity control. Forced-token negative contexts
1283
+ remain covered by the tokenizer/parser gates. Evidence:
1284
+ `fill-first-layer-shape-probe.json` and `fill-first-layer-batched-m1.json`.
1285
+ The full-model reports are `fill-boundary-native-batch1-r6.json` and
1286
+ `fill-boundary-native-batch1-v2-rtn4.json`.
1287
+ The original guarded-parser/arithmetic prototype also passes six balanced
1288
+ three-arm HTTP blocks per quant on the loaded M4 Pro 24 GB. All 144 responses
1289
+ match across 36 clean server exits, with fixed effective seed 42, empty prompt
1290
+ caches, identical tool calls and zero verification. Four-token append reduces
1291
+ paired-median complete time for weather/bash fixtures by 9.95%/15.24% on R6
1292
+ and 1.92%/3.34% on RTN4; every corresponding pair improves. M1 append is
1293
+ mostly flat, and RSS supplies no memory-reduction claim. Tool output is
1294
+ buffered until completion here, so these are complete-request and first
1295
+ meaningful-tool-output results, not decode-rate measurements. Source hashes
1296
+ remain fixed across the initial screen and five remaining order blocks.
1297
+ Evidence: `fill-boundary-http-{screen,repeat,review}.json`. The model-owned
1298
+ integration passes the same fourteen native cases and six balanced four-arm
1299
+ HTTP blocks per quant. All 192 responses match across 48 clean server exits.
1300
+ Paired-median weather/bash complete time improves by 9.87%/15.37% on R6
1301
+ and 1.97%/2.77% on RTN4, with every pair improving. Ordinary generation's
1302
+ before/after medians differ by at most 0.23%. These remain loaded M4 Pro
1303
+ diagnostics. The independent-row operator matches sequential M1 across
1304
+ 720 shape/bit/group/dtype/layout cases, with unchanged stored weight bytes.
1305
+ The main-source state regression passes on both quants, and eight actual
1306
+ HTTP cancellations recover exact responses and steady active allocations.
1307
+ Evidence: `fill-append-integration.json`,
1308
+ `fill-append-http-integration-review.json`, `fill-append-main-native.json`
1309
+ and `fill-append-lifecycle.json`.
1310
+
1311
+ A longer-context audit finds another native dispatch dependency. MLX 0.32.2
1312
+ changes its attention kernel and two-pass reduction block count according
1313
+ to sequence length and GPU architecture. The rules are in
1314
+ [MLX's attention dispatch](https://github.com/ml-explore/mlx/blob/v0.32.2/mlx/backend/metal/scaled_dot_product_attention.cpp).
1315
+ The qualified GQA-6/D256 path on
1316
+ `applegpu_g16s` must end chunks at inclusive KV lengths 1023, 1024, 8192,
1317
+ 32768 and 65536. An unsplit append that crosses a transition can change
1318
+ arithmetic for its earlier positions. The first operation screen reproduces
1319
+ 18 failures among 108 cases. The corrected model-owned limit is rechecked
1320
+ after every chunk; MLX still selects the kernels. Other architectures retain
1321
+ single-token appends pending their own qualification.
1322
+
1323
+ All 225 corrected attention cases through 65,537 cached tokens are exact
1324
+ across bf16, fp16 and fp32, and active allocation returns to zero. Full R6
1325
+ and RTN4 models also match eight committed positions from identical prefixes
1326
+ of 1021 and 8189 tokens. Hidden rows, per-position M1 vocabulary projections,
1327
+ complete live state and four ordinary continuation probes match exactly;
1328
+ the borrowed prefix state and post-arm active allocation are unchanged.
1329
+ These teacher-forced checks establish arithmetic identity, not that the
1330
+ model would independently choose those committed IDs. Production regression
1331
+ coverage checks the attention boundaries and dynamic model limit. The final
1332
+ model-free suite passes 1,947 tests with ten skips and no failures; all three
1333
+ typechecks pass. The corrected path also passes eight actual HTTP
1334
+ cancellations, fourteen completed responses and exact steady allocation
1335
+ recovery across both quants. Final serving timing acceptance passes the
1336
+ internal-SSD diagnostic below; held-out, combined, pressure and quiet gates
1337
+ remain open.
1338
+ Evidence: `fill-sdpa-context-probe.json`,
1339
+ `fill-sdpa-context-boundaries.json`, `fill-append-context.json`,
1340
+ `fill-append-final-model-free.log` and `fill-append-lifecycle-boundaries.json`.
1341
+ The final-source timing attempts complete 116 responses with exact content,
1342
+ tool calls and usage comparisons. Two RTN4 first-request warmups hit Metal
1343
+ GPU timeouts, one with fill disabled and one with fill enabled. Neither
1344
+ executes an append forward. Keep both failed requests and the incomplete
1345
+ block in the evidence. Complete three-arm blocks cover six R6 pairs and
1346
+ three RTN4 pairs; every append-versus-native pair improves complete time on
1347
+ both fixtures. The dynamic boundary checks retain the earlier append benefit.
1348
+ Separate recovery, storage and loading controls do not fill missing timing
1349
+ cells. The failure investigation and its limits live in
1350
+ [environment.md](../reference/environment.md#external-storage-loading).
1351
+ Evidence: `fill-append-http-final.json`, `fill-append-http-resume.json` and
1352
+ `fill-append-http-final-partial-review.json`.
1353
+
1354
+ After Josh specifies internal storage for active models, a separate final
1355
+ comparison uses byte-verified R6 and RTN4 copies with ordinary lazy loading.
1356
+ All 96 responses across 24 fresh servers match, with twelve balanced pairs,
1357
+ zero failures, zero verification calls and identical final active allocation.
1358
+ On the M4 Pro 24 GB, the median paired complete-time reductions are 9.83% and
1359
+ 15.40% for R6's weather/bash fixtures, and 1.63% and 2.94% for RTN4. Every pair
1360
+ improves on each fixture. Tool output is buffered, so first visible output
1361
+ arrives near completion; these figures do not establish faster ordinary
1362
+ decode. The machine still fails the quiet preflight, so these are diagnostic
1363
+ results. Both external-drive failures remain recorded separately. Evidence:
1364
+ `fill-append-http-internal-repeat.json` and `fill-append-http-internal-review.json`.
1365
+
1366
+ Quantized-KV composition remains disabled. A direct R6 append screen with
1367
+ TurboQuant k8v3 preserves hidden rows, logits, live cache state and four
1368
+ continuations at prefixes 128 and 1021, but exposes retained temporary state
1369
+ views between chunks. `appendFillHidden` now uses the existing
1370
+ `leaseCacheStates` ownership interface, as prefill does. Explicit release
1371
+ removes the extra 21,528,576 and 131,153,920 active bytes in those two checks;
1372
+ both ordinary and fused TurboQuant decoding then have identical post-arm
1373
+ allocation and numerical results. This fixes the research composition's
1374
+ view lifetime; the ordinary served path already excludes this combination.
1375
+ Seven targeted ownership tests and all three typechecks pass. The affine
1376
+ KV4 comparison fails both contexts, first changing recurrent state at layer
1377
+ 4 after the first attention layer. Keep its failure and the composition
1378
+ guard. Other quant/cache modes, longer contexts and actual serving
1379
+ performance remain unqualified. Evidence:
1380
+ `fill-append-quant-cache-review.json` and
1381
+ `fill-append-state-ownership-native-test.log`.
1382
+
1383
+ The literal/reasoning checks deliberately decline ambiguous text, including some
1384
+ otherwise valid prose with unmatched quote characters. This is bounded
1385
+ coverage, not proof for every unconstrained model trajectory.
1386
+
1387
+ The schema-choice audit reproduced assertions that selected a first key or
1388
+ closed the call while other valid arguments remained. The compiler now uses
1389
+ a sole required key only when `additionalProperties: false` and no nonempty
1390
+ `patternProperties` map establish that no other key is legal. Optional keys
1391
+ may come before a required key. When no property is required, the common
1392
+ span also includes the empty-argument rendering. This follows the
1393
+ [JSON Schema object applicators](https://json-schema.org/draft/2020-12/json-schema-core#section-10.3.2),
1394
+ where omitting `additionalProperties` leaves those properties unconstrained.
1395
+ Open objects also compare an undeclared-key rendering, even when declared
1396
+ keys share a tokenizer prefix. Twenty-seven regressions across XML, JSON and
1397
+ GLM templates preserve these
1398
+ choices, alongside the existing positive fill cases. This is a compiler
1399
+ correctness fix, with no runtime verification or new inference operation.
1400
+ The real packed-Qwen and RTN4 tokenizers each pass 20 cases across thinking
1401
+ on/off with nonzero row matches. The packed weighted regression also retains
1402
+ token-identical output and nonzero injection with four-token appends. All
1403
+ 1,867 model-free tests and three typechecks pass. Parser/role-state proof
1404
+ and the broader session corpus remain. Evidence is
1405
+ `fill-schema-open-objects-before.json`,
1406
+ `fill-schema-choices-original-control-corrected.txt`,
1407
+ `fill-schema-shared-prefix-merged-before.txt`,
1408
+ `fill-schema-choices-model-free-final.txt`,
1409
+ `fill-schema-tokenizer-{r6,rtn4}-final.json` and
1410
+ `fill-schema-choices-weighted-r6.json` in the campaign report directory.
1411
+
1412
+ Run the actual artifact regression with
1413
+ `MLX_BUN_TEST_FILL_MODEL=<local-model-dir> MLX_BUN_FILL=strict bun test tests/parity/fill-strict.test.ts`.
1414
+ It selects one artifact per process and defaults to the small Qwen fixture
1415
+ when no override is set. This is an initial positive-fill identity check;
1416
+ extend it to the held-out 27B session/adversarial corpus before a default.
1417
+ Track token IDs and subsequent logits/state as well as parsed tool arguments.
1418
+
1419
+ On 2026-09-05 the actual packed Qwen3.8-27B passes this regression on both
1420
+ trellis variants 6 and 7. Both runs perform nonzero assert injection with
1421
+ zero verification events and emit the same token IDs as their unfilled
1422
+ control. Logs: `reports/qwen38-rd/strict-fill-variant{6,7}.txt`. This is one
1423
+ weather/tool-call fixture; it does not establish universal determinism or a
1424
+ wall-time win. Test duration includes loading and warmup and is not the
1425
+ performance comparison. The older logs' `baselineForwardSteps` field contains
1426
+ the baseline generated-token count; the test now labels it accordingly.
1427
+
1428
+ The native tests now union config EOS with tokenizer EOS, matching
1429
+ `loadRuntimeModel`. The packed config alone omits the chat terminator. Earlier
1430
+ `strict-fill-v10-{wall,max4-wall}.json` runs continued beyond the first chat
1431
+ turn and are excluded from serving-performance conclusions. This was a test
1432
+ policy error; the server already applies the union. The initial variant-6/7
1433
+ logs also used the config-only stop policy.
1434
+
1435
+ Corrected variant-10 diagnostics warm complete answers and retain six
1436
+ alternating on/off pairs per cap. Both the default cap and the existing
1437
+ `MLX_BUN_FILL_MAX_SPAN=4` cap preserve the first-turn token IDs with nonzero
1438
+ assertions and no verification. Both improve observed request time; the
1439
+ shorter cap injects fewer tokens and avoids an expanded append. These are
1440
+ separate diagnostic sessions with prefill noise, not a default-policy verdict
1441
+ or a direct comparison of truncation against chunking. Corrected reports:
1442
+ `reports/qwen38-rd/strict-fill-v10-cap{32,4}-served-eos.json`.
1443
+ `MLX_BUN_TEST_FILL_REPORT` enables the retained test's paired diagnostic;
1444
+ `MLX_BUN_TEST_FILL_BLOCKS`, `MLX_BUN_TEST_FILL_PROMPT` and
1445
+ `MLX_BUN_TEST_FILL_REVERSE` select its workload and schedule. Reports record
1446
+ the cap, rows, prompt IDs, exact emitted IDs, counters and whole-request time.
1447
+ `MLX_BUN_TEST_FILL_THINKING=0|1` and `MLX_BUN_TEST_FILL_TOKENS` select the
1448
+ thinking policy and completion budget for broader fixtures. The test rejects
1449
+ emitted EOS IDs; its report records the effective stop set.
1450
+ The corrected variant-11 default-cap repeat also preserves first-turn IDs
1451
+ with nonzero injection, zero verification and lower complete-request time.
1452
+ Evidence: `reports/qwen38-rd/strict-fill-v11-cap32-served-eos.json`.
1453
+ The EOS-correct five-case audit (weather control plus four frozen adversarial
1454
+ prompts) preserves all emitted IDs, cache-token records and offsets. Quoted
1455
+ malformed markup and code blocks produce no injections and retain exact
1456
+ state/probe logits. The three injected cases preserve the response but differ
1457
+ in live state after longer appends; subsequent teacher-forced logits differ
1458
+ too. All verification counts remain zero. Explicit seed 42 reproduces those
1459
+ results; temperature-zero sampling uses argmax. Reducing the span cap to four
1460
+ restores exact state and four subsequent probes in the weather/multiple-call
1461
+ fixtures, while injecting fewer tokens. Evidence:
1462
+ `reports/qwen38-rd/fill-adversarial-state-v12-eos-review.json`,
1463
+ `fill-state-v12-cap4-eos.json` and
1464
+ `fill-state-v12-cap{32,4}-seed42.json` in the same directory.
1465
+
1466
+ An isolated follow-up removes generation and sampling entirely. It copies a
1467
+ nine-token span from the unfilled model's recorded output and appends it to
1468
+ byte-identical cloned starting caches. Serial and repeated serial appends,
1469
+ full M3/4 appends, and chunks of at most four tokens agree exactly in state,
1470
+ final logits and the next teacher-forced probe. Full M5/9 appends reproduce
1471
+ the numerical difference. Restoring the saved serial cache preserves its
1472
+ bytes and next logits exactly. Thus copying a cached state works as expected;
1473
+ recomputing known tokens through a different graph shape changes numerics.
1474
+ The packed projection dispatch crosses from small-M matvec to prefill at
1475
+ M=5; that prefill path also rounds reconstructed weights to the activation
1476
+ dtype. This is a compute-path distinction, not a changed token span or seed.
1477
+ Evidence: `reports/qwen38-rd/fill-identical-input-append-review.json`.
1478
+
1479
+ Four-token execution chunks now preserve every asserted token and pass all
1480
+ five complete-generation fixtures at seed 42. Emitted ids, cache-covered ids,
1481
+ live cache bytes and four subsequent teacher-forced logits are exact against
1482
+ ordinary generation. Positive cases still inject 12, 8 and 20 tokens with no
1483
+ verification; both negative cases inject zero. The production implementation
1484
+ passes the same audit, without the prototype model adapter. It is a shared
1485
+ MLX append operation, selected by a captured FillSession setting rather than
1486
+ model inspection in the generation loop. Verify proposals retain a single
1487
+ forward because recurrent rollback records one forward per round. Tests cover
1488
+ budget clamping, one final vocabulary projection, a consumer break inside the
1489
+ burst, cancellation, failed-chunk cleanup and recurrent verification rollback.
1490
+ The setting and default live in server-config.md.
1491
+
1492
+ Six balanced three-arm blocks per positive timing fixture compare ordinary
1493
+ generation, existing full-span fill and four-token execution chunks. The full
1494
+ span is retained in both fill arms; every pair preserves emitted ids and
1495
+ cache-covered ids. The chunked prototype improves complete-request diagnostic
1496
+ timing in every pair, with higher peak allocation than ordinary generation.
1497
+ These are local M4 Pro observations with residual swap, not quiet benchmark
1498
+ rows or a universal determinism guarantee. Six production HTTP server pairs
1499
+ then preserve all 24 paired warm/measured responses, tool calls and injection
1500
+ counts. Every measured fixture pair improves complete-request time. Dispatch
1501
+ counters confirm chunking is used only by the candidate, all twelve servers
1502
+ exit cleanly, and source hashes remain fixed. HTTP uses greedy temperature zero
1503
+ without an explicit seed because explicit HTTP seeds currently exclude fill;
1504
+ the separate native acceptance uses seed 42. Broader held-out/parser, model
1505
+ and quiet/second-Mac gates remain. Evidence:
1506
+ `reports/qwen38-rd/fill-chunked-complete-requests-review.json`,
1507
+ `fill-chunked-state-v12-seed42-review.json`,
1508
+ `fill-integrated-state-v12-seed42-review.json` and
1509
+ `fill-integrated-http-packed-review.json`, plus
1510
+ `fill-chunked-integrated-{unit,model-free,typecheck,hygiene}.txt`.
1511
+
1512
+ Performance experiments: eliminate redundant in-flight sampling/head work,
1513
+ merge adjacent determined spans, reuse compiled schema plans with exact
1514
+ identity keys, and choose append batch size from the packed-trellis M curve.
1515
+ For the discarded head, test scheduling the next model body first, reading
1516
+ the current token while that body runs, then projecting only when the fill
1517
+ decision requires a sampled continuation. The existing graph interface already
1518
+ separates body and head. Cache advancement must be tracked independently of
1519
+ a pending sample. Verify and trace policies still require their sampled
1520
+ position; assert must not acquire runtime verification. Check EOS, cancellation,
1521
+ budget limits and consumer breaks, and measure unfilled tokens for any loss
1522
+ of pipeline overlap before retaining this change.
1523
+ The first staged-body prototype passes 26 lifecycle/verification checks and
1524
+ all five frozen 27B fixtures. Both arms retain strict fill and four-token
1525
+ appends, with compiled decode disabled. Emitted IDs, cache-covered tokens,
1526
+ live cache bytes and all continuation probes match. The positive fixtures
1527
+ retain their injected spans with zero wasted samples and zero verification;
1528
+ the two negative fixtures still inject nothing. This establishes the staged
1529
+ path's tested correctness, not its speed or a universal forced-token proof.
1530
+ Six complete-request pairs per fixture include an enabled-fill negative
1531
+ fixture and an explicit fill-disabled control. All emitted IDs, cache-covered
1532
+ tokens and injection counts match. Timing is approximately flat in every
1533
+ fixture; the disabled control moves by as much as the best positive fixture.
1534
+ Peak allocation is unchanged or slightly higher. Close this staging prototype
1535
+ without integration: removing discarded samples does not establish a useful
1536
+ complete-request gain. Evidence: `reports/qwen38-rd/fill-staged-unit.txt`,
1537
+ `fill-staged-state-v13-seed42-review.json` and
1538
+ `fill-staged-complete-requests-review.json`. Closed staging helpers are removed.
1539
+ Superseded chunking/state/HTTP research helpers are removed; their frozen
1540
+ fixtures, raw reports and the retained production append helper remain.
1541
+ On default variant 6, M>4 triggers dense expansion. Experimental variants
1542
+ 11/12 add direct tiles and change that cost curve; span policy needs the
1543
+ selected variant's measured append costs. A longer known span is not
1544
+ necessarily cheaper per token. Inspect cache writes and resume logits in traces, then
1545
+ time with `MLX_BUN_FILL_TRACE` unset. Measure emitted/predicted counts, total
1546
+ wall time, first-tool-call latency and task success. Extend the current
1547
+ grammar/KV/batch/logprobs/seed exclusions individually after their own gates.
1548
+ The full experimental matrix and completion rule are in
1549
+ [decode-speed-program.md](decode-speed-program.md#7-qwen38-27b-research-program).
1550
+
1551
+ ## 8. Open items
1552
+
1553
+ - K3 (fill): the packed 27B positive-fill token-identity fixture passes;
1554
+ held-out/adversarial token and state identity plus a paired wall-time win
1555
+ remain before default-on (§7.4). Echo-tier policy levers before any
1556
+ rerun (§7.3); a multi-turn showcase fixture (the single-prompt one fills
1557
+ 0%); the turn-8 server-crash repro (`lab/repro/serve-crash-turn8`).
1558
+
1559
+ - DSpark serving program phases 0, 1d, 1e, 1.5, 2, 3, 4, 5, 6 (§4.5) —
1560
+ every GPU measurement is Josh's shell; Phase 3 (generated-forward tap)
1561
+ and Phase 2 (draftBlock tightening) are agent-runnable.
1562
+ - 27B Track A / Track B (§4.6; PLAN.md boxes) + 14h DSpark cross-check.
1563
+ - Spec × prompt-cache composition (serve-loop v1 bypass).
1564
+ - Per-slot spec under batching (unified-engine frontier row, not this
1565
+ program).
1566
+ - Qwen MTP wall-clock: head quantization / deferred per-draft lm-head
1567
+ sampling (the 0.821× verdict's levers); the TQ-artifact divergence-flag
1568
+ margin analysis (§2.1).
1569
+ - Assistant drafter γ≥2 on 12B: the 262k tied-embedding argmax per draft
1570
+ step is the dominant cost; capping/approximating it could extend the win
1571
+ past γ=1. 26B-MoE unmeasured (expected loss — decode reads top-8/128
1572
+ experts, so per-token target cost is lower than its size).
1573
+ - Optional `strictVerify` (per-position verify, "spec output identical to
1574
+ spec-off") — a product nicety no oracle provides; not implemented; ask.
1575
+ - Verify the Eq 6 / §3.2.1 shapes against the paper PDF (both flagged in
1576
+ code); paper-faithful gated RNN head and survival-product scheduler stay
1577
+ Lab items.
1578
+
1579
+ ## History
1580
+
1581
+ - **2026-06-10** — e4b assistant drafter measured a net loss (γ=2 0.78× of
1582
+ 54 tok/s, ~23% acceptance); spec ships default-off.
1583
+ - **2026-06-14** — `docs/design/speculative-decoding.md` result: the verify path
1584
+ had targeted the WRONG oracle (per-position lm-head to match stock decode;
1585
+ no real implementation does this) — fixed with `picksBatched`, now
1586
+ bit-exact to optiq `spec_generate` on e4b γ=2 (identical output and
1587
+ accept/reject trace). 12B + `gemma-4-12B-it-assistant-bf16`, batched
1588
+ verify, loaded machine (paired ratios only): γ=1 **1.09×** (42%
1589
+ acceptance), γ=2 0.91× (29%), γ=3 0.72× (23%), γ=4 0.56× (17%) — the
1590
+ earlier "net loss at every γ" (0.96/0.77/0.67/0.52×) was partly the
1591
+ wrong-oracle tax. γ≥2 loses because the 12B drafter is heavy (hidden
1592
+ 1024, 16 heads, 262k tied head per draft step). Clean-machine rerun
1593
+ never promoted; 26B not run.
1594
+ - **2026-06-29/30** — DSpark v1 (single-vector, superseded) then faithful
1595
+ v2 (KV injection + Markov + confidence) built; TV-loss off-by-one found;
1596
+ overfit τ 3.24 proves the architecture (M1 Max 32 GB, e4b).
1597
+ - **2026-07-06** — Serve integration: the seam extended to carry target
1598
+ state (TargetView, tapLayers, ctx flow); provider selection by artifact
1599
+ kind; γ pinned to the trained block. Paper components code-complete
1600
+ (pruning, calibration, RNN variant, loop tightening, `dspark` rename +
1601
+ loader). DeepSpec audit: our loss/heads match the paper; the released
1602
+ scheduler is threshold-truncation, not the paper's STS/Alg-1; DeepSpec's
1603
+ temp-0 trace becomes the oracle. Tests 21/21 smoke, 17/17 dspark files,
1604
+ 3/3 real-weights serve gate.
1605
+ - **2026-07-07** — DeepSpec port first live run on 12B (τ≈2.8, 26–33%
1606
+ acceptance, wall-clock −3.4× on a loaded M4 Pro 24 GB); the serving
1607
+ program written; 1a/1b/1c landed the same day; oracle prefill convention
1608
+ re-anchored to mlx-lm's spec shape (4/4 token-for-token incl. knife-edge);
1609
+ accepted-draft EOS leak fixed.
1610
+ - **2026-08-17/18** — Qwen native MTP: DeltaNet rollback solved via the
1611
+ spec-round snapshot/replay contract (bit-exact by construction, gated);
1612
+ 27B pairing gate token-identical (88% = 30/34, 2.82 tok/forward); quiet
1613
+ M1 Max A/B 0.821× at γ=2 (61% acceptance) — durable negative perf
1614
+ verdict, opt-in only; drafter-logit parity vs mlx-vlm 0.6.14 exact
1615
+ through the chained block. TQ artifact gauntlet: MTP 76% accept / 2.53
1616
+ tok/forward with a divergence flag, not claimed lossless.
1617
+ - **2026-08-19** — 27B program (Track A / Track B) recorded, queued behind
1618
+ the certification suite.
1619
+ - **2026-08-20** — MTP `[1,V]` sampler-shape fix (serve-lane 500 under
1620
+ top-k); GLM native MTP source carries the same note.
1621
+ - **2026-08-23** — DSpark scripts consolidated under `scripts/dspark.ts
1622
+ <job>`; one-off measure/compare/bench scripts deleted (git history); this
1623
+ doc consolidates the three design docs.
1624
+ - **2026-09-02** — K3 live A/B on the 27B (§7.3): echo tier injects 7.4%
1625
+ but wall clock ×1.00 (76% verify rejection) — gate FAILS, stays Lab; batch
1626
+ lane never fills; strict rows were silently empty on thinking templates
1627
+ (primer newline merge) — fixed.