mlx-bun 0.3.0 → 0.5.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 (231) hide show
  1. package/README.md +57 -32
  2. package/docs/README.md +58 -42
  3. package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +3617 -0
  4. package/docs/archive/investigations/openjarvis-fit.md +198 -0
  5. package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
  6. package/docs/archive/planning/release-notes-v0.4.0.md +45 -0
  7. package/docs/archive/planning/release-notes-v0.5.0.md +37 -0
  8. package/docs/design/batching.md +1535 -50
  9. package/docs/design/decode-speed-program.md +394 -1171
  10. package/docs/design/generic-model-support.md +195 -3
  11. package/docs/design/kv-cache.md +538 -225
  12. package/docs/design/speculative-decoding.md +552 -50
  13. package/docs/design/turboquant.md +140 -17
  14. package/docs/design/unified-engine-frontier-plan.md +259 -150
  15. package/docs/planning/PRODUCT_ROADMAP.md +21 -6
  16. package/docs/planning/public-readiness-handoff.md +244 -0
  17. package/docs/planning/public-readiness.md +149 -0
  18. package/docs/reference/benchmarks.md +3537 -1112
  19. package/docs/reference/cli.md +102 -2
  20. package/docs/reference/distribution.md +45 -13
  21. package/docs/reference/environment.md +159 -13
  22. package/docs/reference/library-api.md +18 -4
  23. package/docs/reference/memory.md +1 -1
  24. package/docs/reference/models.md +28 -7
  25. package/docs/reference/server-api.md +257 -33
  26. package/docs/reference/server-config.md +372 -151
  27. package/docs/reference/troubleshooting.md +13 -21
  28. package/package.json +2 -2
  29. package/src/audio/audiotoolbox.ts +112 -0
  30. package/src/audio/conformer.ts +8 -0
  31. package/src/audio/mic-capture.ts +98 -0
  32. package/src/audio/silero-vad.ts +379 -0
  33. package/src/audio/whisper-decode.ts +774 -0
  34. package/src/audio/whisper-fast.ts +732 -0
  35. package/src/audio/whisper-format.ts +73 -0
  36. package/src/audio/whisper-mel.ts +185 -0
  37. package/src/audio/whisper-timing.ts +274 -0
  38. package/src/audio/whisper-tokenizer.ts +239 -0
  39. package/src/audio/whisper-transcribe.ts +410 -0
  40. package/src/backends/mlx/assistant-target.ts +46 -0
  41. package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
  42. package/src/backends/mlx/autoregressive.ts +22 -1
  43. package/src/backends/mlx/batch-group.ts +464 -236
  44. package/src/backends/mlx/cache-attachment.ts +67 -0
  45. package/src/backends/mlx/cache-layout.ts +31 -0
  46. package/src/backends/mlx/checkpoint-state.ts +53 -0
  47. package/src/backends/mlx/continuation-persistence.ts +107 -0
  48. package/src/backends/mlx/continuation-request.ts +43 -0
  49. package/src/backends/mlx/continuation.ts +77 -0
  50. package/src/backends/mlx/draft-target.ts +7 -22
  51. package/src/backends/mlx/encoder-cache.ts +25 -0
  52. package/src/backends/mlx/fill-append.ts +21 -8
  53. package/src/backends/mlx/fill-group.ts +497 -0
  54. package/src/backends/mlx/full-kv-row-append.ts +32 -0
  55. package/src/backends/mlx/full-kv-row-donor.ts +54 -0
  56. package/src/backends/mlx/full-transitioning-kv-rows.ts +65 -0
  57. package/src/backends/mlx/gateway-binding.ts +122 -19
  58. package/src/backends/mlx/kv-codec.ts +9 -0
  59. package/src/backends/mlx/kv-maintenance.ts +161 -0
  60. package/src/backends/mlx/kv-tensor-rows.ts +212 -0
  61. package/src/backends/mlx/mixed-iteration.ts +62 -0
  62. package/src/backends/mlx/model-prompt.ts +46 -32
  63. package/src/backends/mlx/model-serving.ts +5 -4
  64. package/src/backends/mlx/paged-cache-codec.ts +27 -0
  65. package/src/backends/mlx/prefill-cohort.ts +100 -0
  66. package/src/backends/mlx/prefill-policy.ts +26 -0
  67. package/src/backends/mlx/prefill-rows.ts +244 -0
  68. package/src/backends/mlx/prefill.ts +1 -1
  69. package/src/backends/mlx/projected-context-rows.ts +72 -0
  70. package/src/backends/mlx/prompt-input.ts +29 -0
  71. package/src/backends/mlx/qwen-prompt-input.ts +40 -0
  72. package/src/backends/mlx/request-state-policy.ts +54 -0
  73. package/src/backends/mlx/rollback.ts +45 -4
  74. package/src/backends/mlx/row-sampling.ts +49 -0
  75. package/src/backends/mlx/serial-executor.ts +51 -73
  76. package/src/backends/mlx/speculative-group.ts +438 -0
  77. package/src/backends/mlx/speculative-round.ts +161 -0
  78. package/src/backends/mlx/speculative.ts +24 -7
  79. package/src/backends/mlx/state-rows.ts +104 -0
  80. package/src/backends/mlx/state-views.ts +21 -1
  81. package/src/backends/mlx/target-layout-capability.ts +35 -0
  82. package/src/backends/mlx/transitioning-kv-rows.ts +173 -0
  83. package/src/cli.ts +333 -36
  84. package/src/contracts/execution.ts +21 -0
  85. package/src/contracts/object-cache.ts +8 -0
  86. package/src/contracts/pi-provider.ts +1 -1
  87. package/src/contracts/pi.ts +3 -1
  88. package/src/contracts/prefix-cache.ts +33 -0
  89. package/src/contracts/resources.ts +7 -0
  90. package/src/contracts/sampling.ts +23 -0
  91. package/src/contracts/scheduling.ts +27 -0
  92. package/src/engine/execution-plan.ts +38 -25
  93. package/src/engine/execution-tasks.ts +44 -0
  94. package/src/engine/namespaced-cache.ts +13 -0
  95. package/src/engine/resources.ts +12 -1
  96. package/src/engine/scheduler.ts +32 -7
  97. package/src/fill/fill-session.ts +14 -3
  98. package/src/fit.ts +29 -15
  99. package/src/generate.ts +105 -195
  100. package/src/grammar.ts +13 -0
  101. package/src/inference/draft-acceptance.ts +41 -0
  102. package/src/inference/draft-output.ts +49 -0
  103. package/src/inference/prefill.ts +6 -0
  104. package/src/inference/rollback.ts +2 -2
  105. package/src/kv-scheme.ts +92 -12
  106. package/src/kv-store.ts +183 -93
  107. package/src/lab/README.md +5 -1
  108. package/src/lab/paged-kv/paged-attention.ts +117 -0
  109. package/src/lab/paged-kv/paged-kv-rows.ts +111 -0
  110. package/src/lab/paged-kv/paged-kv.ts +126 -46
  111. package/src/md-modules.d.ts +5 -0
  112. package/src/mlx/array.ts +39 -1
  113. package/src/mlx/ffi.ts +19 -0
  114. package/src/mlx/normalized-argmax.ts +116 -0
  115. package/src/mlx/ops.ts +97 -10
  116. package/src/model/artifact-identity.ts +73 -0
  117. package/src/model/batched-kv.ts +235 -0
  118. package/src/model/batched-mask.ts +12 -4
  119. package/src/model/batched-quant.ts +8 -2
  120. package/src/model/batched-quantized-kv.ts +144 -0
  121. package/src/model/batched-rotating-quant.ts +76 -15
  122. package/src/model/batched-rotating-state.ts +76 -0
  123. package/src/model/batched-rotating.ts +64 -22
  124. package/src/model/batched-row-storage.ts +52 -0
  125. package/src/model/batched-ssm.ts +52 -0
  126. package/src/model/batched-turboquant-kv.ts +94 -0
  127. package/src/model/compiled-decode.ts +7 -1
  128. package/src/model/decoded-kv-donor.ts +12 -0
  129. package/src/model/delayed-quantized-kv.ts +72 -0
  130. package/src/model/delayed-rotating-quantized-kv.ts +93 -0
  131. package/src/model/delayed-turboquant-kv.ts +57 -0
  132. package/src/model/factory.ts +26 -0
  133. package/src/model/fingerprint.ts +12 -0
  134. package/src/model/full-prefill-padding.ts +56 -0
  135. package/src/model/full-prefill-row.ts +105 -0
  136. package/src/model/gemma4-base.ts +183 -82
  137. package/src/model/gemma4.ts +116 -28
  138. package/src/model/generated/gemma4-12b.ts +4 -1
  139. package/src/model/generated/gemma4-26b.ts +4 -1
  140. package/src/model/generated/gemma4-e4b.ts +4 -1
  141. package/src/model/glm52-cache.ts +80 -4
  142. package/src/model/glm52-mla.ts +12 -6
  143. package/src/model/glm52.ts +19 -40
  144. package/src/model/kv-attention-view.ts +70 -0
  145. package/src/model/minicpm5.ts +11 -7
  146. package/src/model/profile.ts +29 -4
  147. package/src/model/quantized-append-attention.ts +31 -0
  148. package/src/model/qwen3-delta-state.ts +74 -0
  149. package/src/model/qwen3-delta.ts +59 -36
  150. package/src/model/qwen3-mrope.ts +7 -9
  151. package/src/model/qwen3_5.ts +177 -75
  152. package/src/model/rotating-kv-donor.ts +35 -0
  153. package/src/model/rotating-kv-layout.ts +262 -0
  154. package/src/model/rotating-row-transaction.ts +35 -0
  155. package/src/model/speculative-rotating-kv.ts +101 -0
  156. package/src/model/ssm-prefill-padding.ts +39 -0
  157. package/src/model/support.ts +8 -0
  158. package/src/model/token-groups.ts +66 -0
  159. package/src/model/trellis-linear.ts +4 -4
  160. package/src/model/turboquant-codec.ts +89 -0
  161. package/src/model/universal/dense.ts +14 -13
  162. package/src/model/whisper.ts +435 -0
  163. package/src/native/mic_capture.swift +92 -0
  164. package/src/native-pack.ts +6 -5
  165. package/src/pi-web.ts +9 -1
  166. package/src/preflight.ts +31 -15
  167. package/src/prompt-cache.ts +306 -86
  168. package/src/runtime-config.ts +1 -5
  169. package/src/safetensors.ts +9 -2
  170. package/src/sampler.ts +37 -25
  171. package/src/serve/audio-routes.ts +323 -0
  172. package/src/serve/chat-request.ts +2 -0
  173. package/src/serve/chat-stage.ts +19 -14
  174. package/src/serve/completion-executor.ts +9 -1
  175. package/src/serve/discovery-routes.ts +17 -1
  176. package/src/serve/generated-token-history.ts +57 -0
  177. package/src/serve/generation-gateway.ts +88 -38
  178. package/src/serve/model-binding.ts +7 -1
  179. package/src/serve/model-host.ts +3 -4
  180. package/src/serve/preparation.ts +3 -3
  181. package/src/serve/prompt-response-trace.ts +14 -0
  182. package/src/serve/request-plan.ts +6 -3
  183. package/src/serve/request-prep.ts +40 -25
  184. package/src/serve/text-completion-stage.ts +2 -2
  185. package/src/serve/transcription-server.ts +64 -0
  186. package/src/serve/transcription-service.ts +502 -0
  187. package/src/server.ts +185 -261
  188. package/src/spec/assistant-rows.ts +93 -0
  189. package/src/spec/assistant-source.ts +28 -18
  190. package/src/spec/cache-identity.ts +22 -0
  191. package/src/spec/deepspec-rows.ts +29 -0
  192. package/src/spec/deepspec-source.ts +12 -10
  193. package/src/spec/dflash-source.ts +28 -4
  194. package/src/spec/drafter.ts +153 -142
  195. package/src/spec/dspark/deepspec-module.ts +95 -120
  196. package/src/spec/dspark/module-dflash.ts +126 -170
  197. package/src/spec/glm52-mtp-graph.ts +29 -0
  198. package/src/spec/glm52-mtp-rows.ts +132 -0
  199. package/src/spec/glm52-mtp-source.ts +54 -136
  200. package/src/spec/glm52-mtp-state.ts +33 -0
  201. package/src/spec/ngram-source.ts +115 -21
  202. package/src/spec/projected-draft-rows.ts +73 -0
  203. package/src/spec/qwen-mtp-module.ts +171 -0
  204. package/src/spec/qwen-mtp-rows.ts +230 -0
  205. package/src/spec/qwen-mtp-source.ts +84 -226
  206. package/src/spec/qwen-mtp-state.ts +30 -0
  207. package/src/spec/serve-loop.ts +73 -69
  208. package/src/spec/source.ts +93 -15
  209. package/src/spec/two-model-rows.ts +62 -0
  210. package/src/spec/two-model.ts +45 -103
  211. package/src/ssd-cache.ts +107 -36
  212. package/src/ssd-durability.ts +29 -27
  213. package/src/storage/host-buffer.ts +24 -0
  214. package/src/storage/kv-writer.ts +83 -0
  215. package/src/storage/kv-writer.worker.js +264 -0
  216. package/src/storage/retention-policy.ts +37 -0
  217. package/src/tiered-prompt-cache.ts +45 -0
  218. package/src/vision/embedder.ts +8 -0
  219. package/src/vision/prompt.ts +101 -65
  220. package/src/vision/qwen3vl-prompt.ts +101 -64
  221. package/src/vision/qwen3vl-tower.ts +11 -0
  222. package/src/vision/siglip.ts +8 -0
  223. package/src/web/app.html +12 -0
  224. package/src/web/app.js +217 -57
  225. package/src/web/src/chat.ts +4 -1
  226. package/src/web/src/composer.ts +3 -0
  227. package/src/web/src/voice.ts +141 -0
  228. package/src/web/sw.js +1 -1
  229. package/src/backends/mlx/memory-guard.ts +0 -40
  230. package/src/spec/prefix-state.ts +0 -45
  231. /package/docs/{planning → archive/planning}/release-notes-v0.3.0.md +0 -0
@@ -5,8 +5,8 @@
5
5
  // default (use_kernel = not training), and its float accumulation / simd_sum
6
6
  // reduction order differ from the pure-ops fallback — so BIT-EXACT parity with
7
7
  // mlx-lm requires the SAME kernel, dispatched with the SAME grid/threadgroup.
8
- // We port the non-vectorized, non-masked variant (g.ndim == 3, mask is None),
9
- // which is the B=1 single-stream path (ssm_mask is None at batch 1).
8
+ // We port the non-vectorized variant (g.ndim == 3). Padded prefill selects
9
+ // the oracle masked specialization; unpadded execution retains its kernel.
10
10
  //
11
11
  // Numerics (must match the reference dtypes exactly — mlx infers the kernel's
12
12
  // pointer element types from the input arrays):
@@ -20,10 +20,12 @@
20
20
  // GQA is handled inside the kernel (hk_idx = hv_idx / (Hv/Hk)); q/k stay at Hk.
21
21
 
22
22
  import { MlxArray } from "../mlx/array";
23
+ import { materializeCopy } from "../mlx/materialize";
23
24
  import { Dtype } from "../mlx/ffi";
24
25
  import { MetalKernel } from "../mlx/metal-kernel";
25
26
  import { CompiledFunction } from "../mlx/compile";
26
27
  import * as ops from "../mlx/ops";
28
+ import type { SsmPrefillPadding } from "./ssm-prefill-padding";
27
29
  import type { Cache, Mask } from "./gemma4-base";
28
30
 
29
31
  // Verbatim body of mlx-lm's gated_delta_step (has_mask=False, vectorized=False),
@@ -104,7 +106,15 @@ const SOURCE = String.raw`
104
106
  `;
105
107
 
106
108
  let kernel: MetalKernel | null = null;
107
- function getKernel(): MetalKernel {
109
+ let maskedKernel: MetalKernel | null = null;
110
+ function getKernel(masked = false): MetalKernel {
111
+ if (masked) return maskedKernel ??= new MetalKernel({
112
+ name: "gated_delta_step_mask",
113
+ inputNames: ["q", "k", "v", "g", "beta", "state_in", "Tin", "mask"],
114
+ outputNames: ["y", "state_out"],
115
+ source: SOURCE.replace("if (true)", "if (mask[b_idx * T + t])"),
116
+ ensureRowContiguous: true,
117
+ });
108
118
  if (!kernel)
109
119
  kernel = new MetalKernel({
110
120
  name: "gated_delta_step",
@@ -154,7 +164,7 @@ export function computeG(aLog: MlxArray, a: MlxArray, dtBias: MlxArray): MlxArra
154
164
  * state: [B, Hv, Dv, Dk] f32 (or null → zeros) */
155
165
  export function gatedDeltaUpdate(
156
166
  q: MlxArray, k: MlxArray, v: MlxArray, a: MlxArray, b: MlxArray,
157
- aLog: MlxArray, dtBias: MlxArray, state: MlxArray | null,
167
+ aLog: MlxArray, dtBias: MlxArray, state: MlxArray | null, mask: MlxArray | null = null,
158
168
  ): [MlxArray, MlxArray] {
159
169
  const [B, , Hk, Dk] = q.shape as [number, number, number, number];
160
170
  const [, , Hv, Dv] = v.shape as [number, number, number, number];
@@ -171,7 +181,9 @@ export function gatedDeltaUpdate(
171
181
 
172
182
  const T = q.shape[1]!;
173
183
  const tArr = MlxArray.fromInt32(new Int32Array([T]), [1]);
174
- const [y, stateOut] = getKernel().apply([q, k, v, g, beta, stateIn, tArr], {
184
+ const inputs = [q, k, v, g, beta, stateIn, tArr];
185
+ if (mask) inputs.push(mask);
186
+ const [y, stateOut] = getKernel(mask !== null).apply(inputs, {
175
187
  outputs: [
176
188
  { shape: [B, T, Hv, Dv], dtype: q.dtype },
177
189
  { shape: [B, Hv, Dv, Dk], dtype: Dtype.float32 },
@@ -207,6 +219,7 @@ export interface SsmSpecRound {
207
219
  prevConv: MlxArray | null;
208
220
  prevRecurrent: MlxArray | null;
209
221
  prevOffset: number;
222
+ prevOffsets: number[] | null;
210
223
  /** Pre-conv in_proj output [B,S,convDim] — position-local. */
211
224
  qkv: MlxArray | null;
212
225
  /** Gate/beta projections [B,S,Hv] — position-local. */
@@ -216,14 +229,15 @@ export interface SsmSpecRound {
216
229
  S: number;
217
230
  /** Layer-bound prefix replay: called AFTER the snapshot is restored onto
218
231
  * the cache; advances conv/recurrent/offset by `keep` tokens. */
219
- replay: ((cache: SSMCache, keep: number) => void) | null;
232
+ replay: ((cache: SSMCache, keep: number | readonly number[]) => void) | null;
220
233
  }
221
234
 
222
235
  export class SSMCache implements Cache {
223
236
  conv: MlxArray | null = null;
224
237
  recurrent: MlxArray | null = null;
225
238
  offset = 0;
226
- /** Live speculative verify round (serial spec lane only; null otherwise). */
239
+ prefillPadding: SsmPrefillPadding | null = null;
240
+ /** Live speculative verify round; null outside a verify transaction. */
227
241
  specRound: SsmSpecRound | null = null;
228
242
  /** Per-row token coverage (batch lane only; null on serial B=1 caches,
229
243
  * where `offset` IS the row's count). mlx-lm's ArraysCache tracks no
@@ -248,15 +262,13 @@ export class SSMCache implements Cache {
248
262
  throw new Error("SSMCache has no KV updateAndFetch (gated-DeltaNet layer)");
249
263
  }
250
264
 
251
- makeMask(_N: number, _windowSize: number | null): Mask {
252
- // ssm_mask is None both single-stream and in the batch lane: rows
253
- // solo-prefill UNPADDED at B=1 (state never sees pad tokens), and
254
- // batched decode feeds one real token per row — so unlike mlx-lm's
255
- // left-padded batch-prefill there is never a pad position to mask.
256
- return { mode: "", arr: null };
265
+ makeMask(N: number, _windowSize: number | null): Mask {
266
+ const arr = this.prefillPadding?.makeMask(N) ?? null;
267
+ return { mode: arr ? "array" : "", arr };
257
268
  }
258
269
 
259
270
  advance(n: number): void {
271
+ if (this.prefillPadding) { this.advanceRows(this.prefillPadding.advance(n)); return; }
260
272
  this.offset += n;
261
273
  // Batched rows step together (one forward advances every row), so the
262
274
  // per-row counts move in lockstep; they DIFFER only in their merge-time
@@ -264,6 +276,13 @@ export class SSMCache implements Cache {
264
276
  if (this.offsets) for (let i = 0; i < this.offsets.length; i++) this.offsets[i]! += n;
265
277
  }
266
278
 
279
+ /** Methods may retain different verified prefixes for each active request. */
280
+ advanceRows(counts: readonly number[]): void {
281
+ if (!this.offsets) { this.offset += counts[0]!; return; }
282
+ for (let row = 0; row < this.offsets.length; row++) this.offsets[row]! += counts[row]!;
283
+ this.offset = Math.max(...this.offsets);
284
+ }
285
+
267
286
  state(): MlxArray[] {
268
287
  const out: MlxArray[] = [];
269
288
  if (this.conv) out.push(this.conv);
@@ -279,7 +298,7 @@ export class SSMCache implements Cache {
279
298
  throw new Error("SSMCache is not trimmable");
280
299
  }
281
300
 
282
- // --- speculative verify-round support (serial spec lane, B=1) ------------
301
+ // --- speculative verify-round support -----------------------------------
283
302
  // The serve loop arms a round before the verify forward and resolves it
284
303
  // after the accept walk. The layer's forward, seeing an armed round, hands
285
304
  // its replaced state slots to the round instead of disposing them, records
@@ -294,6 +313,7 @@ export class SSMCache implements Cache {
294
313
  prevConv: null,
295
314
  prevRecurrent: null,
296
315
  prevOffset: this.offset,
316
+ prevOffsets: this.offsets ? [...this.offsets] : null,
297
317
  qkv: null,
298
318
  a: null,
299
319
  b: null,
@@ -306,12 +326,12 @@ export class SSMCache implements Cache {
306
326
  this.#dropSpecRound();
307
327
  }
308
328
 
309
- specRoundRollback(keep: number): void {
329
+ specRoundRollback(keep: number | readonly number[]): void {
310
330
  const r = this.specRound;
311
331
  if (!r) throw new Error("SSMCache.specRoundRollback without an armed round");
312
332
  if (r.armed)
313
333
  throw new Error("SSMCache.specRoundRollback before the verify forward recorded");
314
- if (keep < 0 || keep > r.S)
334
+ if (typeof keep === "number" ? keep < 0 || keep > r.S : keep.some(count => count < 0 || count > r.S))
315
335
  throw new Error(`SSMCache.specRoundRollback keep=${keep} outside window S=${r.S}`);
316
336
  // Restore the pre-round snapshot (ownership moves back to the cache).
317
337
  this.conv?.dispose();
@@ -321,9 +341,8 @@ export class SSMCache implements Cache {
321
341
  r.prevConv = null;
322
342
  r.prevRecurrent = null;
323
343
  this.offset = r.prevOffset;
324
- if (this.offsets)
325
- throw new Error("SSMCache.specRoundRollback on a batched cache (serial lane only)");
326
- if (keep > 0) {
344
+ this.offsets = r.prevOffsets;
345
+ if (typeof keep === "number" ? keep > 0 : keep.some(count => count > 0)) {
327
346
  if (!r.replay) throw new Error("SSMCache.specRoundRollback with no recorded replay");
328
347
  r.replay(this, keep); // advances conv/recurrent/offset by `keep`
329
348
  }
@@ -347,6 +366,7 @@ export class SSMCache implements Cache {
347
366
  this.recurrent?.dispose();
348
367
  this.conv = null;
349
368
  this.recurrent = null;
369
+ this.prefillPadding = null;
350
370
  }
351
371
 
352
372
  // --- batch-lane dynamic-B ops (BatchScheduler only) ----------------------
@@ -386,25 +406,30 @@ export class SSMCache implements Cache {
386
406
 
387
407
  /** Evict rows not in `keep` (ascending row indices), in place. */
388
408
  filter(keep: number[]): void {
389
- if (!this.conv || !this.recurrent) return;
390
- const idx = ops.fromInt32(keep, [keep.length]);
391
- const conv = ops.takeAxis(this.conv, idx, 0);
392
- const recurrent = ops.takeAxis(this.recurrent, idx, 0);
393
- idx.dispose();
394
- this.conv.dispose();
395
- this.recurrent.dispose();
396
- this.conv = conv;
397
- this.recurrent = recurrent;
398
- if (this.offsets) this.offsets = keep.map((i) => this.offsets![i]!);
409
+ if (this.conv && this.recurrent) {
410
+ const idx = ops.fromInt32(keep, [keep.length]);
411
+ const conv = ops.takeAxis(this.conv, idx, 0);
412
+ const recurrent = ops.takeAxis(this.recurrent, idx, 0);
413
+ idx.dispose();
414
+ this.conv.dispose();
415
+ this.recurrent.dispose();
416
+ this.conv = conv;
417
+ this.recurrent = recurrent;
418
+ }
419
+ this.prefillPadding?.filter(keep);
420
+ if (this.offsets) {
421
+ this.offsets = keep.map((i) => this.offsets![i]!);
422
+ this.offset = Math.max(0, ...this.offsets);
423
+ }
399
424
  }
400
425
 
401
426
  filterRows(keep: readonly number[]): void { this.filter([...keep]); }
402
427
 
403
428
  /** Row `i` as a fresh SERIAL cache — port of mlx-lm ArraysCache.extract
404
429
  * (cache.py:673-676: `cache.cache = [c[idx : idx + 1] for c in self.cache]`,
405
- * a B-axis slice of every state slot). Ours are OWNED contiguous copies
406
- * (the entry outlives the batch; the batched buffers must free when the
407
- * batch moves on — same rule as extractKVRow, batched-mask.ts). Offset =
430
+ * a B-axis slice of every state slot). A proper subset gets compact storage
431
+ * so retaining one row does not pin the other rows. A singleton retains an
432
+ * independent handle over its complete allocation. Offset =
408
433
  * the row's OWN coverage, not the shared max. */
409
434
  extractRow(i: number): SSMCache {
410
435
  if (!this.conv || !this.recurrent)
@@ -414,10 +439,8 @@ export class SSMCache implements Cache {
414
439
  lo[0] = i;
415
440
  const hi = [...a.shape];
416
441
  hi[0] = i + 1;
417
- const view = a.slice(lo, hi);
418
- const own = ops.copyOf(view); // TRUE copy: contiguous(view) is a no-op VIEW when already contiguous — pins the source buffer (2026-08-20 DeltaNet conv leak class)
419
- view.dispose();
420
- return own;
442
+ using view = a.slice(lo, hi);
443
+ return a.shape[0] === 1 ? ops.contiguous(view) : materializeCopy(view);
421
444
  };
422
445
  const out = new SSMCache();
423
446
  out.conv = cut(this.conv);
@@ -30,20 +30,13 @@ export interface MropeRequestState {
30
30
  }
31
31
 
32
32
  export interface MropeForwardState {
33
- /** Effective positions [3, 1, L] int32 for the CURRENT forward window. */
33
+ /** Effective positions [3, B, L] int32 for the current forward window. */
34
34
  posIds: MlxArray;
35
35
  /** Borrowed model-owned f32 inv_freq (mropeInvFreq) — not disposed here. */
36
36
  invFreq: MlxArray;
37
37
  rotaryDims: number;
38
38
  }
39
39
 
40
- /** Active per-forward mRoPE cos/sin, read by Qwen3Attention.forward. Null on
41
- * the text-only path (serial lane only — vision requests never batch). */
42
- export let activeMrope: MropeForwardState | null = null;
43
- export function setActiveMrope(s: MropeForwardState | null): void {
44
- activeMrope = s;
45
- }
46
-
47
40
  const MROPE_SECTION = [11, 11, 10] as const;
48
41
 
49
42
  /** Interleaved frequency→stream selector (rope_utils
@@ -85,6 +78,11 @@ export function buildMropePositions(
85
78
  state: MropeRequestState, offset: number, L: number,
86
79
  invFreq: MlxArray, rotaryDims: number,
87
80
  ): MropeForwardState {
81
+ return { posIds: mropePositionIds(state, offset, L), invFreq, rotaryDims };
82
+ }
83
+
84
+ /** Request-owned prompt positions; the returned native array belongs to the caller. */
85
+ export function mropePositionIds(state: MropeRequestState, offset: number, L: number): MlxArray {
88
86
  const P = state.positions;
89
87
  const promptLen = P[0].length;
90
88
  const joint = new Int32Array(3 * L);
@@ -94,7 +92,7 @@ export function buildMropePositions(
94
92
  joint[axis * L + l] = idx < promptLen ? P[axis]![idx]! : idx + state.delta;
95
93
  }
96
94
  }
97
- return { posIds: MlxArray.fromInt32(joint, [3, 1, L]), invFreq, rotaryDims };
95
+ return MlxArray.fromInt32(joint, [3, 1, L]);
98
96
  }
99
97
 
100
98
  // The reference language-side apply is a CUSTOM METAL KERNEL (rope_utils