mlx-bun 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/README.md +4 -1
- package/docs/archive/investigations/openjarvis-fit.md +198 -0
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
- package/docs/design/batching.md +1321 -49
- package/docs/design/decode-speed-program.md +331 -1163
- package/docs/design/generic-model-support.md +79 -3
- package/docs/design/kv-cache.md +198 -54
- package/docs/design/speculative-decoding.md +387 -24
- package/docs/design/turboquant.md +123 -13
- package/docs/design/unified-engine-frontier-plan.md +144 -142
- package/docs/planning/PRODUCT_ROADMAP.md +16 -0
- package/docs/planning/release-notes-v0.4.0.md +45 -0
- package/docs/reference/benchmarks.md +2457 -6
- package/docs/reference/cli.md +41 -2
- package/docs/reference/distribution.md +16 -9
- package/docs/reference/environment.md +55 -5
- package/docs/reference/models.md +13 -4
- package/docs/reference/server-api.md +79 -25
- package/docs/reference/server-config.md +178 -84
- package/docs/reference/troubleshooting.md +13 -21
- package/package.json +1 -1
- package/src/backends/mlx/assistant-target.ts +46 -0
- package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
- package/src/backends/mlx/batch-group.ts +368 -224
- package/src/backends/mlx/cache-attachment.ts +67 -0
- package/src/backends/mlx/cache-layout.ts +31 -0
- package/src/backends/mlx/checkpoint-state.ts +53 -0
- package/src/backends/mlx/continuation-persistence.ts +106 -0
- package/src/backends/mlx/continuation-request.ts +43 -0
- package/src/backends/mlx/continuation.ts +77 -0
- package/src/backends/mlx/draft-target.ts +7 -22
- package/src/backends/mlx/full-kv-row-append.ts +32 -0
- package/src/backends/mlx/full-kv-row-donor.ts +54 -0
- package/src/backends/mlx/full-transitioning-kv-rows.ts +65 -0
- package/src/backends/mlx/gateway-binding.ts +77 -18
- package/src/backends/mlx/kv-codec.ts +9 -0
- package/src/backends/mlx/kv-maintenance.ts +133 -0
- package/src/backends/mlx/kv-tensor-rows.ts +212 -0
- package/src/backends/mlx/model-serving.ts +1 -1
- package/src/backends/mlx/prefill-cohort.ts +89 -0
- package/src/backends/mlx/prefill-rows.ts +200 -0
- package/src/backends/mlx/prefill.ts +1 -1
- package/src/backends/mlx/projected-context-rows.ts +72 -0
- package/src/backends/mlx/request-state-policy.ts +41 -0
- package/src/backends/mlx/rollback.ts +41 -3
- package/src/backends/mlx/row-sampling.ts +49 -0
- package/src/backends/mlx/serial-executor.ts +35 -66
- package/src/backends/mlx/speculative-group.ts +359 -0
- package/src/backends/mlx/speculative-round.ts +161 -0
- package/src/backends/mlx/state-rows.ts +104 -0
- package/src/backends/mlx/state-views.ts +21 -1
- package/src/backends/mlx/target-layout-capability.ts +35 -0
- package/src/backends/mlx/transitioning-kv-rows.ts +165 -0
- package/src/cli.ts +11 -36
- package/src/contracts/execution.ts +11 -0
- package/src/contracts/prefix-cache.ts +23 -0
- package/src/contracts/resources.ts +7 -0
- package/src/contracts/sampling.ts +23 -0
- package/src/contracts/scheduling.ts +18 -0
- package/src/engine/execution-plan.ts +18 -16
- package/src/engine/resources.ts +12 -1
- package/src/engine/scheduler.ts +10 -5
- package/src/fit.ts +29 -15
- package/src/generate.ts +77 -173
- package/src/inference/draft-acceptance.ts +41 -0
- package/src/inference/draft-output.ts +49 -0
- package/src/inference/rollback.ts +2 -2
- package/src/kv-scheme.ts +70 -12
- package/src/kv-store.ts +70 -16
- package/src/lab/paged-kv/paged-kv-rows.ts +83 -0
- package/src/lab/paged-kv/paged-kv.ts +25 -11
- package/src/mlx/array.ts +2 -1
- package/src/model/artifact-identity.ts +73 -0
- package/src/model/batched-kv.ts +235 -0
- package/src/model/batched-mask.ts +12 -4
- package/src/model/batched-quant.ts +8 -2
- package/src/model/batched-quantized-kv.ts +144 -0
- package/src/model/batched-rotating-quant.ts +76 -15
- package/src/model/batched-rotating-state.ts +76 -0
- package/src/model/batched-rotating.ts +64 -22
- package/src/model/batched-row-storage.ts +52 -0
- package/src/model/batched-ssm.ts +52 -0
- package/src/model/batched-turboquant-kv.ts +93 -0
- package/src/model/compiled-decode.ts +7 -1
- package/src/model/decoded-kv-donor.ts +12 -0
- package/src/model/delayed-quantized-kv.ts +72 -0
- package/src/model/delayed-rotating-quantized-kv.ts +93 -0
- package/src/model/delayed-turboquant-kv.ts +55 -0
- package/src/model/full-prefill-padding.ts +56 -0
- package/src/model/full-prefill-row.ts +105 -0
- package/src/model/gemma4-base.ts +162 -80
- package/src/model/gemma4.ts +34 -26
- package/src/model/generated/gemma4-12b.ts +4 -1
- package/src/model/generated/gemma4-26b.ts +4 -1
- package/src/model/generated/gemma4-e4b.ts +4 -1
- package/src/model/glm52-cache.ts +80 -4
- package/src/model/glm52-mla.ts +12 -6
- package/src/model/glm52.ts +19 -40
- package/src/model/kv-attention-view.ts +67 -0
- package/src/model/minicpm5.ts +11 -7
- package/src/model/qwen3-delta-state.ts +74 -0
- package/src/model/qwen3-delta.ts +59 -36
- package/src/model/qwen3_5.ts +82 -42
- package/src/model/rotating-kv-donor.ts +35 -0
- package/src/model/rotating-kv-layout.ts +262 -0
- package/src/model/rotating-row-transaction.ts +35 -0
- package/src/model/speculative-rotating-kv.ts +101 -0
- package/src/model/ssm-prefill-padding.ts +39 -0
- package/src/model/trellis-linear.ts +2 -2
- package/src/model/turboquant-codec.ts +83 -0
- package/src/model/universal/dense.ts +14 -13
- package/src/preflight.ts +31 -15
- package/src/prompt-cache.ts +42 -44
- package/src/sampler.ts +36 -25
- package/src/serve/chat-stage.ts +2 -2
- package/src/serve/generation-gateway.ts +39 -30
- package/src/serve/model-host.ts +3 -4
- package/src/serve/request-plan.ts +4 -3
- package/src/serve/request-prep.ts +2 -4
- package/src/serve/text-completion-stage.ts +2 -2
- package/src/server.ts +64 -108
- package/src/spec/assistant-rows.ts +92 -0
- package/src/spec/assistant-source.ts +28 -18
- package/src/spec/cache-identity.ts +22 -0
- package/src/spec/deepspec-rows.ts +29 -0
- package/src/spec/deepspec-source.ts +12 -10
- package/src/spec/dflash-source.ts +28 -4
- package/src/spec/drafter.ts +153 -142
- package/src/spec/dspark/deepspec-module.ts +95 -120
- package/src/spec/dspark/module-dflash.ts +126 -170
- package/src/spec/glm52-mtp-graph.ts +29 -0
- package/src/spec/glm52-mtp-rows.ts +132 -0
- package/src/spec/glm52-mtp-source.ts +53 -136
- package/src/spec/glm52-mtp-state.ts +33 -0
- package/src/spec/ngram-source.ts +92 -20
- package/src/spec/projected-draft-rows.ts +73 -0
- package/src/spec/qwen-mtp-module.ts +171 -0
- package/src/spec/qwen-mtp-rows.ts +219 -0
- package/src/spec/qwen-mtp-source.ts +81 -226
- package/src/spec/qwen-mtp-state.ts +30 -0
- package/src/spec/serve-loop.ts +70 -68
- package/src/spec/source.ts +80 -15
- package/src/spec/two-model-rows.ts +61 -0
- package/src/spec/two-model.ts +43 -101
- package/src/ssd-cache.ts +33 -21
- package/src/ssd-durability.ts +11 -7
- package/src/web/app.js +57 -57
- package/src/backends/mlx/memory-guard.ts +0 -40
- package/src/spec/prefix-state.ts +0 -45
- /package/docs/{planning → archive/planning}/release-notes-v0.3.0.md +0 -0
package/src/spec/serve-loop.ts
CHANGED
|
@@ -17,11 +17,9 @@
|
|
|
17
17
|
// trimmable mid-generation; a serve endpoint must not 500 mid-stream, so
|
|
18
18
|
// we STOP SPECULATING and finish the generation with plain single-token
|
|
19
19
|
// decode (bit-equivalent continuation — the target's own samples).
|
|
20
|
-
// -
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// SSD tier is a tracked follow-up in the integration plan, not silently
|
|
24
|
-
// absent.
|
|
20
|
+
// - Sources with checkpoint support use the injected prefix-cache service.
|
|
21
|
+
// Target and companion state share the existing RAM/SSD lifecycle. Other
|
|
22
|
+
// sources begin with fresh state until they supply a checkpoint codec.
|
|
25
23
|
//
|
|
26
24
|
// Grammar × spec (Phase C, the constrained verify walk — novel: NO runtime
|
|
27
25
|
// serves both; mlx-lm has no grammar, oMLX no spec): the drafter runs FREE
|
|
@@ -40,14 +38,18 @@
|
|
|
40
38
|
// false halts the generation mid-burst.
|
|
41
39
|
|
|
42
40
|
import { MlxArray } from "../mlx/array";
|
|
41
|
+
import { sampleSpeculativeRows } from "../backends/mlx/speculative-round";
|
|
43
42
|
import * as ops from "../mlx/ops";
|
|
44
43
|
import { clearCache } from "../mlx/ffi";
|
|
45
44
|
import { cleanupFailure, disposeResources } from "../engine/resources";
|
|
46
45
|
import type { DisposableResource } from "../contracts/resources";
|
|
47
46
|
import { assertMlxSpeculativeBinding, bindLegacySpeculativeModel, type MlxSpeculativeBinding } from "../backends/mlx/speculative";
|
|
48
47
|
import type { RuntimeModel } from "../model/factory";
|
|
48
|
+
import { cloneKvCaches } from "../kv-store";
|
|
49
|
+
import { disposeAttachments, type CheckpointAttachment } from "../backends/mlx/checkpoint-state";
|
|
49
50
|
import type { Cache } from "../model/gemma4";
|
|
50
|
-
import {
|
|
51
|
+
import { withModelUsageFlush, withModelWiredLimit, type GenerateOptions, type GenerateStats } from "../generate";
|
|
52
|
+
import { createKvMaintenance } from "../backends/mlx/kv-maintenance";
|
|
51
53
|
import { runtimeConfig, withRuntimeConfig } from "../runtime-config";
|
|
52
54
|
import { makeSampler, makeStepSampler } from "../sampler";
|
|
53
55
|
import type { OnToken } from "../serve/generation-gateway";
|
|
@@ -72,6 +74,13 @@ export interface SpecServeExtras {
|
|
|
72
74
|
acceptedByPos: number[];
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
export interface SpecRunServices {
|
|
78
|
+
readonly prefixCache?: import("../backends/mlx/checkpoint-state").MlxPrefixCache;
|
|
79
|
+
/** Mounted adapter revision, resolved while the execution lease is held. */
|
|
80
|
+
readonly cacheNamespace?: string;
|
|
81
|
+
readonly cloneState?: (caches: Cache[]) => Cache[];
|
|
82
|
+
}
|
|
83
|
+
|
|
75
84
|
export async function specServeRun(
|
|
76
85
|
model: RuntimeModel,
|
|
77
86
|
provider: DraftProvider,
|
|
@@ -79,8 +88,9 @@ export async function specServeRun(
|
|
|
79
88
|
promptIds: number[],
|
|
80
89
|
options: GenerateOptions & { stopSequences?: string[] },
|
|
81
90
|
onToken: OnToken,
|
|
91
|
+
services: SpecRunServices = {},
|
|
82
92
|
): Promise<GenerateStats> {
|
|
83
|
-
return specRun(bindLegacySpeculativeModel(model, provider), numDraftTokens, promptIds, options, onToken);
|
|
93
|
+
return specRun(bindLegacySpeculativeModel(model, provider), numDraftTokens, promptIds, options, onToken, services);
|
|
84
94
|
}
|
|
85
95
|
|
|
86
96
|
/** The verifier consumes a bound target and source factory, independent of
|
|
@@ -91,9 +101,10 @@ export async function specRun(
|
|
|
91
101
|
promptIds: number[],
|
|
92
102
|
options: GenerateOptions & { stopSequences?: string[] },
|
|
93
103
|
onToken: OnToken,
|
|
104
|
+
services: SpecRunServices = {},
|
|
94
105
|
): Promise<GenerateStats> {
|
|
95
106
|
return withRuntimeConfig(binding.runtime ?? runtimeConfig(), () => {
|
|
96
|
-
const run = () => specRunInner(binding, numDraftTokens, promptIds, options, onToken);
|
|
107
|
+
const run = () => specRunInner(binding, numDraftTokens, promptIds, options, onToken, services);
|
|
97
108
|
return binding.memory
|
|
98
109
|
? withModelWiredLimit(binding.memory, () => withModelUsageFlush(binding.memory!, run))
|
|
99
110
|
: run();
|
|
@@ -106,8 +117,10 @@ async function specRunInner(
|
|
|
106
117
|
promptIds: number[],
|
|
107
118
|
options: GenerateOptions & { stopSequences?: string[] },
|
|
108
119
|
onToken: OnToken,
|
|
120
|
+
services: SpecRunServices = {},
|
|
109
121
|
): Promise<GenerateStats> {
|
|
110
122
|
const maxTokens = options.maxTokens ?? 512;
|
|
123
|
+
const maintainKv = createKvMaintenance(options);
|
|
111
124
|
const eos = options.eosTokenIds ?? [...binding.eosTokenIds];
|
|
112
125
|
const sampler = makeSampler(options);
|
|
113
126
|
const stepSampler = makeStepSampler(options, {
|
|
@@ -129,6 +142,7 @@ async function specRunInner(
|
|
|
129
142
|
// and turned a config error into a per-request 500. loadContext now
|
|
130
143
|
// probe-opens the pairing at startup, so a throw here is belt+suspenders.
|
|
131
144
|
let caches: Cache[] = [];
|
|
145
|
+
let prefixRetain: (() => void) | undefined;
|
|
132
146
|
let source: import("./source").DraftSource | null = null;
|
|
133
147
|
let tapLayers: number[] | undefined;
|
|
134
148
|
// Target final hidden [1,1,H] at the anchor position — the assistant source
|
|
@@ -175,16 +189,6 @@ async function specRunInner(
|
|
|
175
189
|
const samplePos = async (logits1V: MlxArray, step: number): Promise<number> =>
|
|
176
190
|
(await stepSampler.sample(logits1V, step)).token;
|
|
177
191
|
|
|
178
|
-
/** The [1,V] logits row at position `pos` of a hidden window — batched
|
|
179
|
-
* lm-head is applied by the caller ONCE; this slices its output. */
|
|
180
|
-
const logitsRow = (logitsWindow: MlxArray, pos: number): MlxArray => {
|
|
181
|
-
const V = logitsWindow.shape[logitsWindow.shape.length - 1]!;
|
|
182
|
-
const sl = logitsWindow.slice([0, pos, 0], [1, pos + 1, V]);
|
|
183
|
-
const flat = ops.reshape(sl, [1, V]);
|
|
184
|
-
sl.dispose();
|
|
185
|
-
return flat;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
192
|
const forwardTokens = async (tokens: number[], taps?: number[]) => {
|
|
189
193
|
const ids = ops.fromInt32(tokens, [1, tokens.length]);
|
|
190
194
|
try { return await binding.forward(ids, caches, taps); }
|
|
@@ -197,25 +201,50 @@ async function specRunInner(
|
|
|
197
201
|
if (!Number.isSafeInteger(prefillChunk) || prefillChunk < 1)
|
|
198
202
|
throw new Error("speculative prefill chunk must be a positive integer");
|
|
199
203
|
assertMlxSpeculativeBinding(binding);
|
|
200
|
-
if (options.
|
|
201
|
-
(options.
|
|
202
|
-
|
|
204
|
+
if (options.kvConfig?.length ||
|
|
205
|
+
(options.turboQuant && (options.quantizedKvStart ?? 0) !== 0) ||
|
|
206
|
+
(options.kvBits && ((options.kvBits !== 4 && options.kvBits !== 8) || options.quantizedKvStart !== 0)))
|
|
207
|
+
throw new Error("speculative KV requires uniform KV4/KV8 or TurboQuant start=0");
|
|
203
208
|
caches = binding.makeCache();
|
|
204
209
|
const src = binding.openDraft(sampler, caches);
|
|
205
210
|
source = src;
|
|
206
211
|
tapLayers = src.tapLayers;
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
const prefixNamespace = JSON.stringify({
|
|
210
|
-
|
|
211
|
-
quantizedKvStart: options.quantizedKvStart ?? null
|
|
212
|
-
|
|
212
|
+
const checkpoint = src.prefillMode === "full" ? src.checkpoint : undefined;
|
|
213
|
+
const prefixCache = checkpoint ? services.prefixCache : undefined;
|
|
214
|
+
const prefixNamespace = JSON.stringify({ method: checkpoint?.namespace,
|
|
215
|
+
adapters: services.cacheNamespace ?? options.adapters ?? [], kvBits: options.kvBits ?? null,
|
|
216
|
+
kvGroupSize: options.kvGroupSize ?? 64, quantizedKvStart: options.quantizedKvStart ?? null,
|
|
217
|
+
turboQuant: options.turboQuant ?? null });
|
|
218
|
+
let cached = 0;
|
|
219
|
+
const hit = prefixCache?.take(promptIds, prefixNamespace);
|
|
220
|
+
if (hit) {
|
|
221
|
+
try {
|
|
222
|
+
disposeResources(caches);
|
|
223
|
+
caches.splice(0, caches.length, ...hit.caches.splice(0));
|
|
224
|
+
prefixRetain = hit.retain;
|
|
225
|
+
hit.retain = undefined;
|
|
226
|
+
checkpoint!.restore(hit.tokens.length, hit.attachments![0]!);
|
|
227
|
+
cached = hit.tokens.length;
|
|
228
|
+
} finally {
|
|
229
|
+
disposeResources([...hit.caches, { dispose: () => disposeAttachments(hit.attachments) },
|
|
230
|
+
{ dispose: () => hit.retain?.() }]);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
213
233
|
stats.cachedTokens = cached;
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
:
|
|
217
|
-
|
|
218
|
-
|
|
234
|
+
const capture = (tokens: number[]) => {
|
|
235
|
+
let state: Cache[] = [];
|
|
236
|
+
let attachments: CheckpointAttachment[] = [];
|
|
237
|
+
try {
|
|
238
|
+
state = (services.cloneState ?? cloneKvCaches)(caches);
|
|
239
|
+
attachments.push(checkpoint!.capture(tokens.length));
|
|
240
|
+
prefixCache!.put(tokens, state, prefixNamespace, undefined, attachments);
|
|
241
|
+
state = []; attachments = [];
|
|
242
|
+
} finally {
|
|
243
|
+
disposeResources([...state, { dispose: () => disposeAttachments(attachments) }]);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
const snapshotAt = prefixCache
|
|
247
|
+
? Math.min(options.snapshotAt ?? promptIds.length, promptIds.length - 1) : -1;
|
|
219
248
|
const seedSource = async (end: number) => {
|
|
220
249
|
if (ctxParts.length === 1) prefillCtx = ctxParts[0]!;
|
|
221
250
|
else if (ctxParts.length > 1) {
|
|
@@ -275,7 +304,7 @@ async function specRunInner(
|
|
|
275
304
|
anchorHidden = h.slice([0, L - 1, 0], [1, L, H]); // position len-2
|
|
276
305
|
}
|
|
277
306
|
} finally { h.dispose(); } // no logits during the drain
|
|
278
|
-
|
|
307
|
+
maintainKv(caches);
|
|
279
308
|
clearCache();
|
|
280
309
|
pos += n;
|
|
281
310
|
if (pos < end) await new Promise<void>((resolve) => setImmediate(resolve));
|
|
@@ -300,10 +329,10 @@ async function specRunInner(
|
|
|
300
329
|
lastLogits = ops.reshape(lg, [1, V]);
|
|
301
330
|
} finally { lg.dispose(); }
|
|
302
331
|
} } finally { h.dispose(); }
|
|
303
|
-
|
|
332
|
+
maintainKv(caches);
|
|
304
333
|
if (end === snapshotAt && snapshotAt > cached) {
|
|
305
334
|
await seedSource(end);
|
|
306
|
-
|
|
335
|
+
capture(promptIds.slice(0, end));
|
|
307
336
|
}
|
|
308
337
|
clearCache();
|
|
309
338
|
off = end;
|
|
@@ -460,39 +489,12 @@ async function specRunInner(
|
|
|
460
489
|
// accept while it reproduces the draft. Sampling is sequential because
|
|
461
490
|
// the processor history (and, in Phase C, the grammar mask) at position
|
|
462
491
|
// i depends on the tokens accepted at positions < i.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
492
|
+
const [result] = await sampleSpeculativeRows([{
|
|
493
|
+
pending, step: stats.generatedTokens, remaining: maxTokens - stats.generatedTokens,
|
|
494
|
+
eosTokenIds: eos, sampling: stepSampler, grammarDone: () => grammar?.isTerminated ?? false,
|
|
495
|
+
}], [drafts], vLogits!);
|
|
496
|
+
const { accepted: kAccept, correction, sawEos, emitted, grammarDone } = result!.acceptance;
|
|
466
497
|
let halted = false;
|
|
467
|
-
const emitted: number[] = [];
|
|
468
|
-
let grammarDone = false;
|
|
469
|
-
for (let i = 0; i <= d; i++) {
|
|
470
|
-
roundRow = logitsRow(vLogits!, i);
|
|
471
|
-
const tok = await samplePos(roundRow, stats.generatedTokens + emitted.length);
|
|
472
|
-
roundRow.dispose();
|
|
473
|
-
roundRow = null;
|
|
474
|
-
if (i < d && tok === drafts[i]) {
|
|
475
|
-
kAccept++;
|
|
476
|
-
// EOS is never content — even when it arrives as an ACCEPTED DRAFT
|
|
477
|
-
// (the correction/bonus branch below always excluded it; this
|
|
478
|
-
// branch pushed-then-broke, leaking the EOS through onToken —
|
|
479
|
-
// caught by the 2026-07-07 live oracle gate: streams were
|
|
480
|
-
// bit-identical, ours emitted one extra "content" token: <|eot_id|>).
|
|
481
|
-
if (eos.includes(tok)) { sawEos = true; break; }
|
|
482
|
-
emitted.push(tok);
|
|
483
|
-
// grammar termination mid-burst truncates the round — nothing may
|
|
484
|
-
// be sampled past a satisfied grammar (the all--inf guarantee).
|
|
485
|
-
if (grammar?.isTerminated) { grammarDone = true; break; }
|
|
486
|
-
if (stats.generatedTokens + emitted.length >= maxTokens) break;
|
|
487
|
-
continue;
|
|
488
|
-
}
|
|
489
|
-
// mismatch (target replaces the draft) or bonus (i === n)
|
|
490
|
-
correction = tok;
|
|
491
|
-
if (!eos.includes(tok)) emitted.push(tok);
|
|
492
|
-
else sawEos = true;
|
|
493
|
-
if (grammar?.isTerminated) grammarDone = true;
|
|
494
|
-
break;
|
|
495
|
-
}
|
|
496
498
|
vLogits!.dispose();
|
|
497
499
|
vLogits = null;
|
|
498
500
|
extras.accepted += kAccept;
|
|
@@ -575,7 +577,7 @@ async function specRunInner(
|
|
|
575
577
|
} finally {
|
|
576
578
|
const resources = [anchorHidden, lastLogits, prefillCtx, ...ctxParts,
|
|
577
579
|
vLogits, roundRow, vHidden, vCtxML, ...caches, source, stepSampler,
|
|
578
|
-
options.grammar, { dispose: clearCache }]
|
|
580
|
+
options.grammar, { dispose: () => prefixRetain?.() }, { dispose: clearCache }]
|
|
579
581
|
.filter((resource): resource is DisposableResource => resource != null);
|
|
580
582
|
if (failure) cleanupFailure(failure.error, () => disposeResources(resources));
|
|
581
583
|
else disposeResources(resources);
|
package/src/spec/source.ts
CHANGED
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
import type { MlxArray } from "../mlx/array";
|
|
24
24
|
import type { Dtype } from "../mlx/ffi";
|
|
25
25
|
import type { Cache } from "../model/gemma4";
|
|
26
|
+
import type { CheckpointAttachment } from "../backends/mlx/checkpoint-state";
|
|
27
|
+
import type { MlxDraftRows } from "../backends/mlx/speculative-round";
|
|
28
|
+
import type { PreparedStateChange } from "../contracts/resources";
|
|
26
29
|
|
|
27
30
|
/** Numerical ports are backend-specific; returned arrays are caller-owned. */
|
|
28
31
|
export interface DraftProjection {
|
|
@@ -33,11 +36,14 @@ export interface DraftProjection {
|
|
|
33
36
|
logitsFromHidden(hidden: MlxArray): MlxArray;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
readDonors():
|
|
39
|
+
/** Read-only target state and embedding, independent of draft scheduling. */
|
|
40
|
+
export interface AssistantRowsTarget {
|
|
41
|
+
readonly hiddenSize: number;
|
|
42
|
+
embed(ids: MlxArray): MlxArray;
|
|
43
|
+
readDonors(): import("./drafter").AssistantDonors & {
|
|
44
|
+
readonly positions: readonly number[];
|
|
45
|
+
dispose(): void;
|
|
46
|
+
};
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
export interface QwenMtpTarget {
|
|
@@ -53,7 +59,7 @@ export interface QwenMtpTarget {
|
|
|
53
59
|
export interface TargetView {
|
|
54
60
|
/** Opaque identity for providers borrowing weights from one exact target. */
|
|
55
61
|
readonly identity: object;
|
|
56
|
-
readonly
|
|
62
|
+
readonly assistantRows?: AssistantRowsTarget;
|
|
57
63
|
readonly gemmaTaps?: { readonly layerCount: number; readonly projection: DraftProjection };
|
|
58
64
|
readonly qwenMtp?: QwenMtpTarget;
|
|
59
65
|
}
|
|
@@ -61,12 +67,13 @@ export interface TargetView {
|
|
|
61
67
|
/** A per-request draft-token producer. Created per generation (owns its own
|
|
62
68
|
* draft-side state), disposed by the serve loop's finally. */
|
|
63
69
|
export interface DraftSource {
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
66
|
-
* the source
|
|
67
|
-
readonly
|
|
68
|
-
|
|
69
|
-
capture(
|
|
70
|
+
/** Method-owned companion state, independent of retention and storage.
|
|
71
|
+
* Capture returns owned immutable views. Restore borrows its attachment;
|
|
72
|
+
* the source retains the views it needs before returning. */
|
|
73
|
+
readonly checkpoint?: {
|
|
74
|
+
readonly namespace: string;
|
|
75
|
+
capture(processedTokens: number): import("../backends/mlx/checkpoint-state").CheckpointAttachment;
|
|
76
|
+
restore(processedTokens: number, attachment: import("../backends/mlx/checkpoint-state").CheckpointAttachment): void;
|
|
70
77
|
};
|
|
71
78
|
/** Target prefill shape required by this source's oracle. Most mlx-lm
|
|
72
79
|
* sources leave the final prompt token pending; native Colibri MTP starts
|
|
@@ -86,8 +93,9 @@ export interface DraftSource {
|
|
|
86
93
|
/** Process the prompt (two-model: prefill the draft model's cache;
|
|
87
94
|
* assistant/dflash: read the target's state — mostly a no-op, but DSpark
|
|
88
95
|
* seeds H_ctx from `ctxML`, the tapped prefill context [1,Lp,m*H], present
|
|
89
|
-
* iff tapLayers is set).
|
|
90
|
-
|
|
96
|
+
* iff tapLayers is set). A composed prefill may supply its exact processed
|
|
97
|
+
* token count, so a history source need not infer the target's boundary. */
|
|
98
|
+
prefill(promptIds: number[], ctxML?: MlxArray, processedTokens?: number): void | Promise<void>;
|
|
91
99
|
|
|
92
100
|
/** Propose 0..n tokens (RETURN LENGTH IS AUTHORITATIVE — a source may
|
|
93
101
|
* return fewer than n, e.g. DSpark's confidence-scheduled draft-length
|
|
@@ -132,12 +140,69 @@ export interface DraftSource {
|
|
|
132
140
|
dispose(): void;
|
|
133
141
|
}
|
|
134
142
|
|
|
143
|
+
export interface DraftRowSampling {
|
|
144
|
+
/** Borrow [B,V] log-probabilities; return owned [B] IDs on device. */
|
|
145
|
+
sample(logprobs: MlxArray, steps: readonly number[]): MlxArray;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface DraftRowCheckpoint {
|
|
149
|
+
readonly processedTokens: number;
|
|
150
|
+
readonly attachment: CheckpointAttachment;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Method-owned state membership, independent of scheduling and cache tiers.
|
|
154
|
+
* Open/append borrow checkpoints; capture returns owned immutable state.
|
|
155
|
+
* Membership changes and capture occur only between committed rounds.
|
|
156
|
+
* The caller retains the provider's residency lease for the group's lifetime. */
|
|
157
|
+
export interface DraftRowGroup extends MlxDraftRows {
|
|
158
|
+
readonly namespace: string;
|
|
159
|
+
readonly tapLayers: readonly number[];
|
|
160
|
+
readonly rowCount: number;
|
|
161
|
+
append(checkpoints: readonly DraftRowCheckpoint[]): void;
|
|
162
|
+
prepareAppend(checkpoints: readonly DraftRowCheckpoint[]): PreparedStateChange;
|
|
163
|
+
filterRows(keep: readonly number[]): void;
|
|
164
|
+
capture(row: number): DraftRowCheckpoint;
|
|
165
|
+
dispose(): void;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Draft preparation consumes each target chunk at the same batch geometry.
|
|
169
|
+
* Inputs are borrowed and contain only new tokens/context, with equal chunk
|
|
170
|
+
* lengths across rows. A null checkpoint appends a cold request. The provider
|
|
171
|
+
* owns alignment and retained hidden state; scheduling and persistence do not.
|
|
172
|
+
* Membership and capture occur between prefill calls. Capture returns owned
|
|
173
|
+
* state after at least one target token has been consumed for that row. */
|
|
174
|
+
export interface DraftPrefillGroup {
|
|
175
|
+
readonly namespace: string;
|
|
176
|
+
readonly prefillMode: NonNullable<DraftSource["prefillMode"]>;
|
|
177
|
+
readonly tapLayers: readonly number[];
|
|
178
|
+
readonly rowCount: number;
|
|
179
|
+
append(checkpoints: readonly (DraftRowCheckpoint | null)[]): void;
|
|
180
|
+
prepareAppend(checkpoints: readonly (DraftRowCheckpoint | null)[]): PreparedStateChange;
|
|
181
|
+
prefill(tokens: MlxArray, context?: MlxArray): void | Promise<void>;
|
|
182
|
+
/** Resolve borrowed backing before its external restore lease is released. */
|
|
183
|
+
materialize(): void;
|
|
184
|
+
filterRows(keep: readonly number[]): void;
|
|
185
|
+
capture(row: number): DraftRowCheckpoint;
|
|
186
|
+
dispose(): void;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface GroupedDraftProvider {
|
|
190
|
+
/** Draft state remains valid when target forwards run under a mounted
|
|
191
|
+
* adapter context. Unqualified learned draft graphs leave this absent. */
|
|
192
|
+
readonly supportsTargetAdapters?: boolean;
|
|
193
|
+
openPrefill(options: { target: TargetView;
|
|
194
|
+
checkpoints: readonly (DraftRowCheckpoint | null)[] }): DraftPrefillGroup;
|
|
195
|
+
open(options: { target: TargetView; sampling: DraftRowSampling;
|
|
196
|
+
checkpoints: readonly DraftRowCheckpoint[] }): DraftRowGroup;
|
|
197
|
+
}
|
|
198
|
+
|
|
135
199
|
/** Server-lifetime owner of the draft machinery (the loaded draft model);
|
|
136
|
-
*
|
|
200
|
+
* open() mints a per-request DraftSource; grouped opens shared row state. */
|
|
137
201
|
export interface DraftProvider {
|
|
138
202
|
/** Human-readable id (registry id / path tail) for logs + cache namespacing. */
|
|
139
203
|
readonly id: string;
|
|
140
204
|
readonly weightsBytes: number;
|
|
205
|
+
readonly grouped?: GroupedDraftProvider;
|
|
141
206
|
open(opts: {
|
|
142
207
|
/** The request's sampler over logprobs [1,V] → token array [1] (the SAME
|
|
143
208
|
* sampler as the target — mlx-lm parity; greedy drafting under a
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { MlxAutoregressiveBinding } from "../backends/mlx/autoregressive";
|
|
2
|
+
import { MlxAutoregressiveDraftRows, type MlxDraftLayout } from "../backends/mlx/autoregressive-draft-rows";
|
|
3
|
+
import { prefillCacheLayout } from "../backends/mlx/cache-layout";
|
|
4
|
+
import { MlxStateRows } from "../backends/mlx/state-rows";
|
|
5
|
+
import { captureCacheAttachment, restoreCacheAttachment } from "../backends/mlx/cache-attachment";
|
|
6
|
+
import { applyStateChanges, cleanupFailure, disposeResources } from "../engine/resources";
|
|
7
|
+
import type { Cache } from "../model/gemma4-base";
|
|
8
|
+
import type { DraftPrefillGroup, DraftRowCheckpoint, DraftRowGroup, DraftRowSampling, GroupedDraftProvider } from "./source";
|
|
9
|
+
import type { PreparedStateChange } from "../contracts/resources";
|
|
10
|
+
import type { MlxArray } from "../mlx/array";
|
|
11
|
+
|
|
12
|
+
export interface StandaloneDraftRows extends DraftPrefillGroup {
|
|
13
|
+
draft: DraftRowGroup["draft"];
|
|
14
|
+
commit(accepted: readonly number[], context?: MlxArray): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The standalone model supplies graph/state capabilities once. Target
|
|
18
|
+
* verification, scheduling and cache tiers use the existing provider ports. */
|
|
19
|
+
export function openStandaloneDraftRows(binding: MlxAutoregressiveBinding, namespace: string,
|
|
20
|
+
sampling: DraftRowSampling | null, checkpoints: readonly (DraftRowCheckpoint | null)[]): StandaloneDraftRows {
|
|
21
|
+
const empty = binding.makeCache(), layouts: MlxDraftLayout[] = [];
|
|
22
|
+
try { for (const cache of empty) layouts.push(prefillCacheLayout(cache) as MlxDraftLayout); }
|
|
23
|
+
catch (error) { disposeResources(layouts); throw error; }
|
|
24
|
+
finally { disposeResources(empty); }
|
|
25
|
+
const state = new MlxStateRows(layouts), rows = new MlxAutoregressiveDraftRows(binding.graph, state, sampling);
|
|
26
|
+
const prepareAppend = (checkpoints: readonly (DraftRowCheckpoint | null)[]): PreparedStateChange => {
|
|
27
|
+
const restored: Cache[][] = [], pending: Array<number | null> = [];
|
|
28
|
+
try {
|
|
29
|
+
for (const checkpoint of checkpoints) {
|
|
30
|
+
restored.push(checkpoint ? restoreCacheAttachment(checkpoint.attachment, binding.makeCache) : binding.makeCache());
|
|
31
|
+
pending.push(checkpoint && checkpoint.attachment.metadata.pendingLast !== false ? Number(checkpoint.attachment.metadata.pendingLast) : null);
|
|
32
|
+
}
|
|
33
|
+
return rows.prepareAppend(restored, pending);
|
|
34
|
+
} finally { disposeResources(restored.flat()); }
|
|
35
|
+
};
|
|
36
|
+
const append = (checkpoints: readonly (DraftRowCheckpoint | null)[]) => applyStateChanges([() => prepareAppend(checkpoints)]);
|
|
37
|
+
try { append(checkpoints); }
|
|
38
|
+
catch (error) { return cleanupFailure(error, () => rows.dispose()); }
|
|
39
|
+
return {
|
|
40
|
+
namespace, prefillMode: "tail-split", tapLayers: [],
|
|
41
|
+
get rowCount() { return state.rowCount; },
|
|
42
|
+
append, prepareAppend, prefill: tokens => rows.prefill(tokens), materialize: () => rows.materialize(),
|
|
43
|
+
filterRows: keep => rows.filterRows(keep), draft: rows.draft.bind(rows), commit: accepted => rows.commit(accepted),
|
|
44
|
+
capture(row): DraftRowCheckpoint {
|
|
45
|
+
const caches = state.extractRow(row), pendingLast = rows.pendingLast[row] ?? null;
|
|
46
|
+
try {
|
|
47
|
+
const processedTokens = caches[0]!.offset + Number(pendingLast !== null);
|
|
48
|
+
return { processedTokens, attachment: captureCacheAttachment("standalone-draft-v1", caches,
|
|
49
|
+
{ processedTokens, pendingLast: pendingLast ?? false }) };
|
|
50
|
+
} finally { disposeResources(caches); }
|
|
51
|
+
},
|
|
52
|
+
dispose: () => rows.dispose(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function standaloneDraftGroups(binding: MlxAutoregressiveBinding, namespace: string): GroupedDraftProvider {
|
|
57
|
+
return {
|
|
58
|
+
open: options => openStandaloneDraftRows(binding, namespace, options.sampling, options.checkpoints),
|
|
59
|
+
openPrefill: options => openStandaloneDraftRows(binding, namespace, null, options.checkpoints),
|
|
60
|
+
};
|
|
61
|
+
}
|
package/src/spec/two-model.ts
CHANGED
|
@@ -20,9 +20,11 @@ import { flagOn } from "../runtime-config";
|
|
|
20
20
|
import { loadModelConfig } from "../config";
|
|
21
21
|
import { Weights } from "../weights";
|
|
22
22
|
import { createModel, type RuntimeModel } from "../model/factory";
|
|
23
|
-
import type {
|
|
24
|
-
import {
|
|
25
|
-
import
|
|
23
|
+
import type { DraftProvider, DraftSource, GroupedDraftProvider } from "./source";
|
|
24
|
+
import { standaloneDraftGroups, openStandaloneDraftRows } from "./two-model-rows";
|
|
25
|
+
import { bindLegacyAutoregressiveModel } from "../backends/mlx/autoregressive";
|
|
26
|
+
import { artifactIdentity } from "../model/artifact-identity";
|
|
27
|
+
import { configFingerprint } from "../model/fingerprint";
|
|
26
28
|
|
|
27
29
|
/** mlx-lm's prefill_step_size — the draft drain chunks at the same stride
|
|
28
30
|
* as the target's (serve-loop.ts); chunking is numerically exact for
|
|
@@ -32,13 +34,16 @@ const PREFILL_CHUNK = 2048;
|
|
|
32
34
|
export class TwoModelProvider implements DraftProvider {
|
|
33
35
|
readonly id: string;
|
|
34
36
|
readonly weightsBytes: number;
|
|
37
|
+
readonly grouped: GroupedDraftProvider;
|
|
35
38
|
|
|
36
39
|
private constructor(
|
|
37
40
|
readonly model: RuntimeModel,
|
|
38
41
|
private readonly weights: Weights,
|
|
39
42
|
id: string,
|
|
43
|
+
private readonly namespace: string,
|
|
40
44
|
) {
|
|
41
45
|
this.id = id;
|
|
46
|
+
this.grouped = standaloneDraftGroups(bindLegacyAutoregressiveModel(model), namespace);
|
|
42
47
|
this.weightsBytes = [...weights.shards.files.values()].reduce(
|
|
43
48
|
(a, f) => a + f.mmap.size,
|
|
44
49
|
0,
|
|
@@ -51,20 +56,25 @@ export class TwoModelProvider implements DraftProvider {
|
|
|
51
56
|
static async load(modelDir: string, targetVocabSize?: number): Promise<TwoModelProvider> {
|
|
52
57
|
const config = await loadModelConfig(modelDir);
|
|
53
58
|
const weights = await Weights.open(modelDir);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
try {
|
|
60
|
+
const model = createModel(weights, config);
|
|
61
|
+
if (
|
|
62
|
+
targetVocabSize !== undefined &&
|
|
63
|
+
config.text.vocabSize !== targetVocabSize
|
|
64
|
+
)
|
|
65
|
+
console.warn(
|
|
66
|
+
`draft model vocab size ${config.text.vocabSize} != target ${targetVocabSize} — ` +
|
|
67
|
+
`speculation will accept nothing useful (mlx-lm warns identically)`,
|
|
68
|
+
);
|
|
69
|
+
const identity = await artifactIdentity(configFingerprint(config),
|
|
70
|
+
[...weights.shards.files].map(([name, shard]) => ({ name, path: shard.path })));
|
|
71
|
+
return new TwoModelProvider(model, weights, modelDir.split("/").filter(Boolean).at(-1)!,
|
|
72
|
+
`standalone-draft-v1:${identity}`);
|
|
73
|
+
} catch (error) { weights.dispose(); throw error; }
|
|
64
74
|
}
|
|
65
75
|
|
|
66
76
|
open(opts: Parameters<DraftProvider["open"]>[0]): DraftSource {
|
|
67
|
-
return new TwoModelSource(this.model, opts.sampler);
|
|
77
|
+
return new TwoModelSource(this.model, opts.sampler, this.namespace);
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
dispose(): void {
|
|
@@ -72,100 +82,32 @@ export class TwoModelProvider implements DraftProvider {
|
|
|
72
82
|
}
|
|
73
83
|
}
|
|
74
84
|
|
|
85
|
+
/** Compatibility adapter: the existing request loop also uses the shared
|
|
86
|
+
* draft graph at B=1. Proposal generation and state recovery have one owner. */
|
|
75
87
|
class TwoModelSource implements DraftSource {
|
|
76
|
-
readonly weightsBytes = 0;
|
|
77
|
-
private
|
|
78
|
-
|
|
79
|
-
constructor(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
) {
|
|
83
|
-
this.caches = this.model.makeCache();
|
|
88
|
+
readonly weightsBytes = 0;
|
|
89
|
+
private readonly rows: ReturnType<typeof openStandaloneDraftRows>;
|
|
90
|
+
private processed = 0;
|
|
91
|
+
constructor(model: RuntimeModel, sampler: (lp: MlxArray, step: number) => MlxArray, namespace: string) {
|
|
92
|
+
this.rows = openStandaloneDraftRows(bindLegacyAutoregressiveModel(model), namespace,
|
|
93
|
+
{ sample: (logprobs, steps) => sampler(logprobs, steps[0]!) }, [null]);
|
|
84
94
|
}
|
|
85
|
-
|
|
86
|
-
prefill(promptIds: number[]): void {
|
|
87
|
-
// Oracle convention (mlx-lm speculative_generate_step._prefill, re-anchored
|
|
88
|
-
// 2026-07-07): the DRAFT model also drains only to len-1 (`while y.size >
|
|
89
|
-
// 1`) — the last prompt token stays unprocessed and is consumed by the
|
|
90
|
-
// FIRST draft() round's feed ([pending] = that same token under the serve
|
|
91
|
-
// loop's oracle shape), mirroring _draft_generate's first _step. Draft-side
|
|
92
|
-
// full-prefill was the residual knife-edge flipper in the 2026-07-07 live
|
|
93
|
-
// oracle gate (γ=2 haiku cell). MLX_BUN_PREFILL_TAIL_SPLIT=0 reverts.
|
|
94
|
-
// (mlx-lm chunks its drain at prefill_step_size=2048; chunking is
|
|
95
|
-
// numerically exact for causal attention — same KV, same positions —
|
|
96
|
-
// so we chunk too. Pre-2026-07-07 this was a SINGLE forward of the
|
|
97
|
-
// whole head: nothing enforced the "<2048 serve regime" the old
|
|
98
|
-
// comment assumed, and a 32k prompt through the spec lane ran one
|
|
99
|
-
// 32k-position draft forward — a large activation/mask transient on a
|
|
100
|
-
// 24 GB box, and untested numerics vs the chunked oracle.)
|
|
95
|
+
async prefill(promptIds: number[]): Promise<void> {
|
|
101
96
|
const tailSplit = flagOn("MLX_BUN_PREFILL_TAIL_SPLIT", true);
|
|
102
97
|
const upTo = tailSplit && promptIds.length > 1 ? promptIds.length - 1 : promptIds.length;
|
|
103
|
-
for (let at =
|
|
98
|
+
for (let at = this.processed; at < upTo; at += PREFILL_CHUNK) {
|
|
104
99
|
const end = Math.min(at + PREFILL_CHUNK, upTo);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
ids.dispose();
|
|
108
|
-
// The head's last-position logits are not needed: the serve loop's
|
|
109
|
-
// first `feed` is consumed at the top of draft().
|
|
110
|
-
h.dispose();
|
|
111
|
-
clearCache();
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** One forward over `feed`, sample the first draft from its last position,
|
|
116
|
-
* then n-1 single-token steps — mirrors _draft_generate's autoregressive
|
|
117
|
-
* chain (with async_eval pipelining upstream; eager here, the draft is
|
|
118
|
-
* small and the serve loop overlaps nothing with it in v1). */
|
|
119
|
-
draft(feed: number[], n: number, stepBase: number): number[] {
|
|
120
|
-
const drafts: number[] = [];
|
|
121
|
-
let input = feed;
|
|
122
|
-
for (let k = 0; k < n; k++) {
|
|
123
|
-
const ids = ops.fromInt32(input, [1, input.length]);
|
|
124
|
-
const h = this.model.forwardHidden(ids, this.caches);
|
|
125
|
-
ids.dispose();
|
|
126
|
-
const L = h.shape[1]!;
|
|
127
|
-
const H = h.shape[2]!;
|
|
128
|
-
const hLast = h.slice([0, L - 1, 0], [1, L, H]);
|
|
129
|
-
h.dispose();
|
|
130
|
-
const logits = this.model.logitsFromHidden(hLast);
|
|
131
|
-
hLast.dispose();
|
|
132
|
-
const V = logits.shape[logits.shape.length - 1]!;
|
|
133
|
-
const flat = ops.reshape(logits, [1, V]);
|
|
134
|
-
logits.dispose();
|
|
135
|
-
const lp = toLogprobs(flat);
|
|
136
|
-
flat.dispose();
|
|
137
|
-
const tokArr = this.sampler(lp, stepBase + k);
|
|
138
|
-
lp.dispose();
|
|
139
|
-
const tok = ops.itemUint32(tokArr);
|
|
140
|
-
tokArr.dispose();
|
|
141
|
-
drafts.push(tok);
|
|
142
|
-
input = [tok];
|
|
100
|
+
using ids = ops.fromInt32(promptIds.slice(at, end), [1, end - at]);
|
|
101
|
+
await this.rows.prefill(ids); clearCache();
|
|
143
102
|
}
|
|
144
|
-
|
|
103
|
+
this.processed = upTo;
|
|
145
104
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const rewind = Math.max(n - kAccept - 1, 0);
|
|
149
|
-
if (rewind > 0) {
|
|
150
|
-
for (const c of this.caches) {
|
|
151
|
-
if (!c.isTrimmable()) {
|
|
152
|
-
// The serve loop stops speculating on the TARGET-side trim check
|
|
153
|
-
// before this can matter; a wrapped draft ring here just means the
|
|
154
|
-
// source is done being useful. Recreate is the safe recovery.
|
|
155
|
-
this.resetCaches();
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
c.trim(rewind);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private resetCaches(): void {
|
|
164
|
-
for (const c of this.caches) c.dispose();
|
|
165
|
-
this.caches = this.model.makeCache();
|
|
105
|
+
async draft(feed: number[], count: number, step: number): Promise<number[]> {
|
|
106
|
+
return (await this.rows.draft([feed.at(-1)!], count, [step]))[0]!;
|
|
166
107
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
108
|
+
async commit(_drafted: number, accepted: number): Promise<void> {
|
|
109
|
+
await this.rows.commit([accepted]);
|
|
110
|
+
this.processed += accepted + 1;
|
|
170
111
|
}
|
|
112
|
+
dispose(): void { this.rows.dispose(); }
|
|
171
113
|
}
|