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.
- package/README.md +57 -32
- package/docs/README.md +58 -42
- package/docs/archive/investigations/benchmark-ledger-through-v0.4.0.md +3617 -0
- package/docs/archive/investigations/openjarvis-fit.md +198 -0
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
- package/docs/archive/planning/release-notes-v0.4.0.md +45 -0
- package/docs/archive/planning/release-notes-v0.5.0.md +37 -0
- package/docs/design/batching.md +1535 -50
- package/docs/design/decode-speed-program.md +394 -1171
- package/docs/design/generic-model-support.md +195 -3
- package/docs/design/kv-cache.md +538 -225
- package/docs/design/speculative-decoding.md +552 -50
- package/docs/design/turboquant.md +140 -17
- package/docs/design/unified-engine-frontier-plan.md +259 -150
- package/docs/planning/PRODUCT_ROADMAP.md +21 -6
- package/docs/planning/public-readiness-handoff.md +244 -0
- package/docs/planning/public-readiness.md +149 -0
- package/docs/reference/benchmarks.md +3537 -1112
- package/docs/reference/cli.md +102 -2
- package/docs/reference/distribution.md +45 -13
- package/docs/reference/environment.md +159 -13
- package/docs/reference/library-api.md +18 -4
- package/docs/reference/memory.md +1 -1
- package/docs/reference/models.md +28 -7
- package/docs/reference/server-api.md +257 -33
- package/docs/reference/server-config.md +372 -151
- package/docs/reference/troubleshooting.md +13 -21
- package/package.json +2 -2
- package/src/audio/audiotoolbox.ts +112 -0
- package/src/audio/conformer.ts +8 -0
- package/src/audio/mic-capture.ts +98 -0
- package/src/audio/silero-vad.ts +379 -0
- package/src/audio/whisper-decode.ts +774 -0
- package/src/audio/whisper-fast.ts +732 -0
- package/src/audio/whisper-format.ts +73 -0
- package/src/audio/whisper-mel.ts +185 -0
- package/src/audio/whisper-timing.ts +274 -0
- package/src/audio/whisper-tokenizer.ts +239 -0
- package/src/audio/whisper-transcribe.ts +410 -0
- package/src/backends/mlx/assistant-target.ts +46 -0
- package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
- package/src/backends/mlx/autoregressive.ts +22 -1
- package/src/backends/mlx/batch-group.ts +464 -236
- 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 +107 -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/encoder-cache.ts +25 -0
- package/src/backends/mlx/fill-append.ts +21 -8
- package/src/backends/mlx/fill-group.ts +497 -0
- 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 +122 -19
- package/src/backends/mlx/kv-codec.ts +9 -0
- package/src/backends/mlx/kv-maintenance.ts +161 -0
- package/src/backends/mlx/kv-tensor-rows.ts +212 -0
- package/src/backends/mlx/mixed-iteration.ts +62 -0
- package/src/backends/mlx/model-prompt.ts +46 -32
- package/src/backends/mlx/model-serving.ts +5 -4
- package/src/backends/mlx/paged-cache-codec.ts +27 -0
- package/src/backends/mlx/prefill-cohort.ts +100 -0
- package/src/backends/mlx/prefill-policy.ts +26 -0
- package/src/backends/mlx/prefill-rows.ts +244 -0
- package/src/backends/mlx/prefill.ts +1 -1
- package/src/backends/mlx/projected-context-rows.ts +72 -0
- package/src/backends/mlx/prompt-input.ts +29 -0
- package/src/backends/mlx/qwen-prompt-input.ts +40 -0
- package/src/backends/mlx/request-state-policy.ts +54 -0
- package/src/backends/mlx/rollback.ts +45 -4
- package/src/backends/mlx/row-sampling.ts +49 -0
- package/src/backends/mlx/serial-executor.ts +51 -73
- package/src/backends/mlx/speculative-group.ts +438 -0
- package/src/backends/mlx/speculative-round.ts +161 -0
- package/src/backends/mlx/speculative.ts +24 -7
- 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 +173 -0
- package/src/cli.ts +333 -36
- package/src/contracts/execution.ts +21 -0
- package/src/contracts/object-cache.ts +8 -0
- package/src/contracts/pi-provider.ts +1 -1
- package/src/contracts/pi.ts +3 -1
- package/src/contracts/prefix-cache.ts +33 -0
- package/src/contracts/resources.ts +7 -0
- package/src/contracts/sampling.ts +23 -0
- package/src/contracts/scheduling.ts +27 -0
- package/src/engine/execution-plan.ts +38 -25
- package/src/engine/execution-tasks.ts +44 -0
- package/src/engine/namespaced-cache.ts +13 -0
- package/src/engine/resources.ts +12 -1
- package/src/engine/scheduler.ts +32 -7
- package/src/fill/fill-session.ts +14 -3
- package/src/fit.ts +29 -15
- package/src/generate.ts +105 -195
- package/src/grammar.ts +13 -0
- package/src/inference/draft-acceptance.ts +41 -0
- package/src/inference/draft-output.ts +49 -0
- package/src/inference/prefill.ts +6 -0
- package/src/inference/rollback.ts +2 -2
- package/src/kv-scheme.ts +92 -12
- package/src/kv-store.ts +183 -93
- package/src/lab/README.md +5 -1
- package/src/lab/paged-kv/paged-attention.ts +117 -0
- package/src/lab/paged-kv/paged-kv-rows.ts +111 -0
- package/src/lab/paged-kv/paged-kv.ts +126 -46
- package/src/md-modules.d.ts +5 -0
- package/src/mlx/array.ts +39 -1
- package/src/mlx/ffi.ts +19 -0
- package/src/mlx/normalized-argmax.ts +116 -0
- package/src/mlx/ops.ts +97 -10
- 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 +94 -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 +57 -0
- package/src/model/factory.ts +26 -0
- package/src/model/fingerprint.ts +12 -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 +183 -82
- package/src/model/gemma4.ts +116 -28
- 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 +70 -0
- package/src/model/minicpm5.ts +11 -7
- package/src/model/profile.ts +29 -4
- package/src/model/quantized-append-attention.ts +31 -0
- package/src/model/qwen3-delta-state.ts +74 -0
- package/src/model/qwen3-delta.ts +59 -36
- package/src/model/qwen3-mrope.ts +7 -9
- package/src/model/qwen3_5.ts +177 -75
- 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/support.ts +8 -0
- package/src/model/token-groups.ts +66 -0
- package/src/model/trellis-linear.ts +4 -4
- package/src/model/turboquant-codec.ts +89 -0
- package/src/model/universal/dense.ts +14 -13
- package/src/model/whisper.ts +435 -0
- package/src/native/mic_capture.swift +92 -0
- package/src/native-pack.ts +6 -5
- package/src/pi-web.ts +9 -1
- package/src/preflight.ts +31 -15
- package/src/prompt-cache.ts +306 -86
- package/src/runtime-config.ts +1 -5
- package/src/safetensors.ts +9 -2
- package/src/sampler.ts +37 -25
- package/src/serve/audio-routes.ts +323 -0
- package/src/serve/chat-request.ts +2 -0
- package/src/serve/chat-stage.ts +19 -14
- package/src/serve/completion-executor.ts +9 -1
- package/src/serve/discovery-routes.ts +17 -1
- package/src/serve/generated-token-history.ts +57 -0
- package/src/serve/generation-gateway.ts +88 -38
- package/src/serve/model-binding.ts +7 -1
- package/src/serve/model-host.ts +3 -4
- package/src/serve/preparation.ts +3 -3
- package/src/serve/prompt-response-trace.ts +14 -0
- package/src/serve/request-plan.ts +6 -3
- package/src/serve/request-prep.ts +40 -25
- package/src/serve/text-completion-stage.ts +2 -2
- package/src/serve/transcription-server.ts +64 -0
- package/src/serve/transcription-service.ts +502 -0
- package/src/server.ts +185 -261
- package/src/spec/assistant-rows.ts +93 -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 +54 -136
- package/src/spec/glm52-mtp-state.ts +33 -0
- package/src/spec/ngram-source.ts +115 -21
- 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 +230 -0
- package/src/spec/qwen-mtp-source.ts +84 -226
- package/src/spec/qwen-mtp-state.ts +30 -0
- package/src/spec/serve-loop.ts +73 -69
- package/src/spec/source.ts +93 -15
- package/src/spec/two-model-rows.ts +62 -0
- package/src/spec/two-model.ts +45 -103
- package/src/ssd-cache.ts +107 -36
- package/src/ssd-durability.ts +29 -27
- package/src/storage/host-buffer.ts +24 -0
- package/src/storage/kv-writer.ts +83 -0
- package/src/storage/kv-writer.worker.js +264 -0
- package/src/storage/retention-policy.ts +37 -0
- package/src/tiered-prompt-cache.ts +45 -0
- package/src/vision/embedder.ts +8 -0
- package/src/vision/prompt.ts +101 -65
- package/src/vision/qwen3vl-prompt.ts +101 -64
- package/src/vision/qwen3vl-tower.ts +11 -0
- package/src/vision/siglip.ts +8 -0
- package/src/web/app.html +12 -0
- package/src/web/app.js +217 -57
- package/src/web/src/chat.ts +4 -1
- package/src/web/src/composer.ts +3 -0
- package/src/web/src/voice.ts +141 -0
- package/src/web/sw.js +1 -1
- 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
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { MlxArray } from "../mlx/array";
|
|
2
|
+
import * as ops from "../mlx/ops";
|
|
3
|
+
import { materializeCopy } from "../mlx/materialize";
|
|
4
|
+
import { disposeResources, applyStateChanges } from "../engine/resources";
|
|
5
|
+
import type { PreparedStateChange } from "../contracts/resources";
|
|
6
|
+
import type { AssistantRowsTarget, DraftPrefillGroup, DraftRowGroup, DraftRowCheckpoint, GroupedDraftProvider } from "./source";
|
|
7
|
+
import type { GemmaAssistantDrafter } from "./drafter";
|
|
8
|
+
|
|
9
|
+
/** The assistant owns only its last true target hidden and coverage. Donor
|
|
10
|
+
* attention stays with target storage; checkpoints contain no duplicate KV. */
|
|
11
|
+
class AssistantRows implements DraftPrefillGroup, DraftRowGroup {
|
|
12
|
+
readonly prefillMode = "tail-split" as const;
|
|
13
|
+
readonly tapLayers: readonly number[] = [];
|
|
14
|
+
#hidden: MlxArray | null = null;
|
|
15
|
+
#processed: number[] = [];
|
|
16
|
+
constructor(readonly target: AssistantRowsTarget, readonly drafter: GemmaAssistantDrafter,
|
|
17
|
+
readonly namespace: string, checkpoints: readonly (DraftRowCheckpoint | null)[]) { this.append(checkpoints); }
|
|
18
|
+
get rowCount(): number { return this.#processed.length; }
|
|
19
|
+
append(checkpoints: readonly (DraftRowCheckpoint | null)[]): void {
|
|
20
|
+
if (checkpoints.length) applyStateChanges([() => this.prepareAppend(checkpoints)]);
|
|
21
|
+
}
|
|
22
|
+
prepareAppend(checkpoints: readonly (DraftRowCheckpoint | null)[]): PreparedStateChange {
|
|
23
|
+
const processed = [...this.#processed, ...checkpoints.map(checkpoint => checkpoint?.processedTokens ?? 0)];
|
|
24
|
+
const example = this.#hidden ?? checkpoints.find(checkpoint => checkpoint !== null)?.attachment.tensors[0];
|
|
25
|
+
const held: MlxArray[] = []; let hidden: MlxArray | null = null;
|
|
26
|
+
try {
|
|
27
|
+
for (const checkpoint of checkpoints) if (checkpoint && (checkpoint.attachment.schema !== "gemma-assistant-v1" ||
|
|
28
|
+
checkpoint.attachment.metadata.processedTokens !== checkpoint.processedTokens))
|
|
29
|
+
throw new Error("invalid assistant checkpoint alignment");
|
|
30
|
+
if (example) {
|
|
31
|
+
const zero = (rows: number) => { const value = ops.zeros([rows,1,this.target.hiddenSize],example.dtype); held.push(value); return value; };
|
|
32
|
+
hidden = ops.concatAxis([...(this.rowCount ? [this.#hidden ?? zero(this.rowCount)] : []),
|
|
33
|
+
...checkpoints.map(checkpoint => checkpoint?.attachment.tensors[0] ?? zero(1))],0);
|
|
34
|
+
}
|
|
35
|
+
} finally { disposeResources(held); }
|
|
36
|
+
return { commit: () => { const old = this.#hidden; this.#hidden=hidden; hidden=old; this.#processed=processed; },
|
|
37
|
+
dispose: () => { hidden?.dispose(); hidden=null; } };
|
|
38
|
+
}
|
|
39
|
+
filterRows(keep: readonly number[]): void {
|
|
40
|
+
if (!keep.length) { this.dispose(); return; }
|
|
41
|
+
using indices = ops.fromInt32([...keep],[keep.length]);
|
|
42
|
+
const next = this.#hidden && keep.length ? ops.takeAxis(this.#hidden,indices,0) : null;
|
|
43
|
+
this.#hidden?.dispose(); this.#hidden=next; this.#processed=keep.map(row=>this.#processed[row]!);
|
|
44
|
+
}
|
|
45
|
+
prefill(tokens: MlxArray, context?: MlxArray): void {
|
|
46
|
+
const [B,N] = tokens.shape as [number,number];
|
|
47
|
+
using tail = context!.slice([0,N-1,0],[B,N,context!.shape[2]!]);
|
|
48
|
+
const hidden = materializeCopy(tail);
|
|
49
|
+
this.#hidden?.dispose(); this.#hidden=hidden; this.#processed=this.#processed.map(count=>count+N);
|
|
50
|
+
}
|
|
51
|
+
materialize(): void { this.#hidden?.eval(); }
|
|
52
|
+
capture(row: number): DraftRowCheckpoint {
|
|
53
|
+
const processedTokens=this.#processed[row]!;
|
|
54
|
+
const hidden=this.#hidden!.slice([row,0,0],[row+1,1,this.target.hiddenSize]);
|
|
55
|
+
return { processedTokens, attachment: { schema:"gemma-assistant-v1",metadata:{processedTokens},tensors:[hidden] } };
|
|
56
|
+
}
|
|
57
|
+
draft(pending: readonly number[], depth: number, _steps: readonly number[]): number[][] {
|
|
58
|
+
if (!depth) return pending.map(()=>[]);
|
|
59
|
+
const B=pending.length, owned: MlxArray[]=[];
|
|
60
|
+
const donors=this.target.readDonors();
|
|
61
|
+
let ids=ops.fromInt32([...pending],[B,1]), hidden=this.#hidden!;
|
|
62
|
+
const tokens: MlxArray[]=[];
|
|
63
|
+
try {
|
|
64
|
+
for(let step=0;step<depth;step++) {
|
|
65
|
+
using emb=this.target.embed(ids);
|
|
66
|
+
const positions=donors.positions.map(position=>position+step);
|
|
67
|
+
const common=positions.every(position=>position===positions[0]);
|
|
68
|
+
using offsets=common ? null : ops.fromInt32(positions,[B]);
|
|
69
|
+
const next=this.drafter.forwardRows(emb,hidden,donors,offsets ?? positions[0]!);
|
|
70
|
+
owned.push(next.nextHidden); tokens.push(next.tokens); hidden=next.nextHidden;
|
|
71
|
+
ids.dispose(); ids=ops.reshape(next.tokens,[B,1]);
|
|
72
|
+
}
|
|
73
|
+
using packed=ops.concatAxis(tokens,0);
|
|
74
|
+
const values=packed.toIntTokens();
|
|
75
|
+
return pending.map((_,row)=>Array.from({length:depth},(_,step)=>values[step*B+row]!));
|
|
76
|
+
} finally { ids.dispose(); donors.dispose(); disposeResources([...owned,...tokens]); }
|
|
77
|
+
}
|
|
78
|
+
commit(accepted: readonly number[], context: MlxArray): void {
|
|
79
|
+
using indices=ops.fromInt32([...accepted],[accepted.length,1,1]);
|
|
80
|
+
const hidden=ops.takeAlongAxis(context,indices,1);
|
|
81
|
+
this.#hidden?.dispose(); this.#hidden=hidden;
|
|
82
|
+
this.#processed=this.#processed.map((count,row)=>count+accepted[row]!+1);
|
|
83
|
+
}
|
|
84
|
+
dispose(): void { this.#hidden?.dispose(); this.#hidden=null; this.#processed=[]; }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function assistantGroups(drafter: GemmaAssistantDrafter, namespace: string): GroupedDraftProvider {
|
|
88
|
+
return {
|
|
89
|
+
checkpointNamespace: () => namespace,
|
|
90
|
+
openPrefill: ({target,checkpoints}) => new AssistantRows(target.assistantRows!,drafter,namespace,checkpoints),
|
|
91
|
+
open: ({target,checkpoints}) => new AssistantRows(target.assistantRows!,drafter,namespace,checkpoints),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import * as ops from "../mlx/ops";
|
|
2
|
+
import { artifactIdentity } from "../model/artifact-identity";
|
|
3
|
+
import { assistantGroups } from "./assistant-rows";
|
|
1
4
|
// AssistantSource — the optiq KV-borrowing Gemma "-assistant" drafter
|
|
2
5
|
// (src/spec/drafter.ts) behind the serve-time DraftSource seam. L2 oracle:
|
|
3
6
|
// optiq spec_generate. This is the SAME drafter the standalone specGenerate
|
|
@@ -22,7 +25,7 @@ import { readdirSync, statSync } from "node:fs";
|
|
|
22
25
|
import { join } from "node:path";
|
|
23
26
|
import type { MlxArray } from "../mlx/array";
|
|
24
27
|
import { GemmaAssistantDrafter } from "./drafter";
|
|
25
|
-
import type { DraftProvider, DraftSource, TargetView,
|
|
28
|
+
import type { DraftProvider, DraftSource, TargetView, AssistantRowsTarget } from "./source";
|
|
26
29
|
|
|
27
30
|
/** Sum of the drafter's on-disk safetensors — admission accounting only. */
|
|
28
31
|
function safetensorsBytes(dir: string): number {
|
|
@@ -35,20 +38,27 @@ function safetensorsBytes(dir: string): number {
|
|
|
35
38
|
export class AssistantProvider implements DraftProvider {
|
|
36
39
|
readonly id: string;
|
|
37
40
|
readonly weightsBytes: number;
|
|
41
|
+
readonly grouped: import("./source").GroupedDraftProvider;
|
|
38
42
|
|
|
39
43
|
private constructor(
|
|
40
44
|
private readonly drafter: GemmaAssistantDrafter,
|
|
41
45
|
id: string,
|
|
42
|
-
weightsBytes: number,
|
|
46
|
+
weightsBytes: number, namespace: string,
|
|
43
47
|
) {
|
|
44
48
|
this.id = id;
|
|
45
49
|
this.weightsBytes = weightsBytes;
|
|
50
|
+
this.grouped = assistantGroups(drafter,namespace);
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
static async load(modelDir: string): Promise<AssistantProvider> {
|
|
49
54
|
const drafter = await GemmaAssistantDrafter.load(modelDir);
|
|
50
55
|
const id = modelDir.split("/").filter(Boolean).at(-1)!;
|
|
51
|
-
|
|
56
|
+
try {
|
|
57
|
+
const identity = await artifactIdentity(await Bun.file(`${modelDir}/config.json`).text(),
|
|
58
|
+
readdirSync(modelDir).filter(file => file.endsWith(".safetensors"))
|
|
59
|
+
.map(name => ({ name, path: join(modelDir, name) })));
|
|
60
|
+
return new AssistantProvider(drafter, id, safetensorsBytes(modelDir), `gemma-assistant-v1:${identity}`);
|
|
61
|
+
} catch (error) { drafter.dispose(); throw error; }
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
open(opts: Parameters<DraftProvider["open"]>[0]): DraftSource {
|
|
@@ -56,18 +66,18 @@ export class AssistantProvider implements DraftProvider {
|
|
|
56
66
|
}
|
|
57
67
|
|
|
58
68
|
dispose(): void {
|
|
59
|
-
|
|
69
|
+
this.drafter.dispose();
|
|
60
70
|
}
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
export class AssistantSource implements DraftSource {
|
|
64
74
|
readonly weightsBytes = 0; // provider-owned weights; per-request adds nothing
|
|
65
|
-
private readonly target:
|
|
75
|
+
private readonly target: AssistantRowsTarget;
|
|
66
76
|
|
|
67
|
-
constructor(private readonly drafter: Pick<GemmaAssistantDrafter, "
|
|
68
|
-
if (!target.
|
|
77
|
+
constructor(private readonly drafter: Pick<GemmaAssistantDrafter, "forwardRows">, target: TargetView) {
|
|
78
|
+
if (!target.assistantRows)
|
|
69
79
|
throw new Error("assistant drafter requires a Gemma4 target with donor views");
|
|
70
|
-
this.target = target.
|
|
80
|
+
this.target = target.assistantRows;
|
|
71
81
|
}
|
|
72
82
|
|
|
73
83
|
/** No own cache to prime — the drafter reads the target's live state. */
|
|
@@ -79,8 +89,8 @@ export class AssistantSource implements DraftSource {
|
|
|
79
89
|
* specGenerate's 2a block. anchorHidden is borrowed; never disposed here. */
|
|
80
90
|
draft(feed: number[], n: number, _stepBase: number, anchorHidden?: MlxArray): number[] {
|
|
81
91
|
if (!anchorHidden) throw new Error("assistant drafter needs the target anchor hidden");
|
|
82
|
-
const position = this.target.position();
|
|
83
92
|
const shared = this.target.readDonors();
|
|
93
|
+
const position = shared.positions[0]!;
|
|
84
94
|
const drafts: number[] = [];
|
|
85
95
|
const ownedHiddens: MlxArray[] = [];
|
|
86
96
|
let dTok = feed[feed.length - 1]!; // the pending token (anchorHidden's token)
|
|
@@ -88,22 +98,22 @@ export class AssistantSource implements DraftSource {
|
|
|
88
98
|
let emb: MlxArray | null = null; // hoisted so a mid-loop throw still frees it
|
|
89
99
|
try {
|
|
90
100
|
for (let k = 0; k < n; k++) {
|
|
91
|
-
|
|
92
|
-
|
|
101
|
+
using ids = ops.fromInt32([dTok], [1, 1]);
|
|
102
|
+
emb = this.target.embed(ids);
|
|
103
|
+
const step = this.drafter.forwardRows(emb, dHid, shared, position + k);
|
|
104
|
+
ownedHiddens.push(step.nextHidden);
|
|
105
|
+
let token: number;
|
|
106
|
+
try { token = ops.itemUint32(step.tokens); } finally { step.tokens.dispose(); }
|
|
93
107
|
emb.dispose();
|
|
94
108
|
emb = null;
|
|
95
|
-
drafts.push(
|
|
96
|
-
|
|
97
|
-
dTok = step.token;
|
|
109
|
+
drafts.push(token);
|
|
110
|
+
dTok = token;
|
|
98
111
|
dHid = step.nextHidden;
|
|
99
112
|
}
|
|
100
113
|
} finally {
|
|
101
114
|
emb?.dispose();
|
|
102
115
|
for (const a of ownedHiddens) a.dispose();
|
|
103
|
-
|
|
104
|
-
k.dispose();
|
|
105
|
-
v.dispose();
|
|
106
|
-
}
|
|
116
|
+
shared.dispose();
|
|
107
117
|
}
|
|
108
118
|
return drafts;
|
|
109
119
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GenerateOptions } from "../generate";
|
|
2
|
+
|
|
3
|
+
/** Snapshot numerical configuration when the request is bound. Caller-owned
|
|
4
|
+
* arrays must not change the graph or persistence identity after grouping. */
|
|
5
|
+
export function captureSpeculativeOptions(options: GenerateOptions): GenerateOptions {
|
|
6
|
+
return { ...options,
|
|
7
|
+
...(options.kvConfig ? { kvConfig: options.kvConfig.map(({ layerIdx, bits, groupSize }) =>
|
|
8
|
+
({ layerIdx, bits, groupSize })).sort((a, b) => a.layerIdx - b.layerIdx) } : {}),
|
|
9
|
+
...(options.turboQuant ? { turboQuant: { ...options.turboQuant } } : {}),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Existing namespaces remain byte-identical when no per-layer scheme is
|
|
14
|
+
* supplied. Execution and persistence consume the same captured settings. */
|
|
15
|
+
export function speculativePrefixNamespace(method: string | undefined, adapters: string | string[],
|
|
16
|
+
options: GenerateOptions): string {
|
|
17
|
+
return JSON.stringify({ method, adapters, kvBits: options.kvBits ?? null,
|
|
18
|
+
kvGroupSize: options.kvGroupSize ?? 64, quantizedKvStart: options.quantizedKvStart ?? null,
|
|
19
|
+
turboQuant: options.turboQuant ?? null,
|
|
20
|
+
...(options.kvConfig?.length ? { kvConfig: options.kvConfig } : {}),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { projectedDraftGroups } from "./projected-draft-rows";
|
|
2
|
+
import type { GroupedDraftProvider, TargetView } from "./source";
|
|
3
|
+
import type { DeepspecDrafter } from "./dspark/deepspec-module";
|
|
4
|
+
|
|
5
|
+
/** Existing pairing requirements are checked once when the provider opens. */
|
|
6
|
+
export function bindDeepspecTarget(target: TargetView, drafter: Pick<DeepspecDrafter, "cfg">): void {
|
|
7
|
+
if (!target.gemmaTaps) throw new Error("DeepSpec drafter requires a Gemma4 target");
|
|
8
|
+
const layers = drafter.cfg.num_target_layers;
|
|
9
|
+
if (target.gemmaTaps.layerCount !== layers) throw new Error(
|
|
10
|
+
`DeepSpec drafter was trained for a ${layers}-layer target; ` +
|
|
11
|
+
`this model has ${target.gemmaTaps.layerCount} layers — wrong (target, drafter) pairing`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function deepspecGroups(drafter: DeepspecDrafter, namespace: string): GroupedDraftProvider {
|
|
15
|
+
return projectedDraftGroups(namespace, target => {
|
|
16
|
+
bindDeepspecTarget(target, drafter);
|
|
17
|
+
return {
|
|
18
|
+
namespace, schema: "deepspec-context-v1",
|
|
19
|
+
layers: drafter.cfg.num_hidden_layers, tapLayers: drafter.tapLayers,
|
|
20
|
+
project(hidden, positions) {
|
|
21
|
+
using projected = drafter.projectContext(hidden);
|
|
22
|
+
return drafter.projectContextKVRows(projected, positions);
|
|
23
|
+
},
|
|
24
|
+
draft(context, pending, positions, depth) {
|
|
25
|
+
return drafter.draftRows(context, pending, positions).tokens.map(row => row.slice(0, depth));
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
// Context rows are cached post-norm-post-rope — bit-equivalent to their
|
|
23
23
|
// in-round concat (per-row RMSNorm/RoPE; argued in projectContextKV's doc).
|
|
24
24
|
|
|
25
|
+
import { artifactIdentity } from "../model/artifact-identity";
|
|
26
|
+
import { deepspecGroups, bindDeepspecTarget } from "./deepspec-rows";
|
|
25
27
|
import { readdirSync, statSync } from "node:fs";
|
|
26
28
|
import { join } from "node:path";
|
|
27
29
|
import type { MlxArray } from "../mlx/array";
|
|
@@ -39,20 +41,27 @@ function safetensorsBytes(dir: string): number {
|
|
|
39
41
|
export class DeepspecProvider implements DraftProvider {
|
|
40
42
|
readonly id: string;
|
|
41
43
|
readonly weightsBytes: number;
|
|
44
|
+
readonly grouped: import("./source").GroupedDraftProvider;
|
|
42
45
|
/** Trained block width (config block_size, e.g. 7) — the server pins
|
|
43
46
|
* numDraftTokens to this. */
|
|
44
47
|
readonly gamma: number;
|
|
45
48
|
|
|
46
|
-
private constructor(private readonly drafter: DeepspecDrafter, id: string, weightsBytes: number) {
|
|
49
|
+
private constructor(private readonly drafter: DeepspecDrafter, id: string, weightsBytes: number, namespace: string) {
|
|
47
50
|
this.id = id;
|
|
48
51
|
this.weightsBytes = weightsBytes;
|
|
49
52
|
this.gamma = drafter.gamma;
|
|
53
|
+
this.grouped = deepspecGroups(drafter, namespace);
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
static async load(modelDir: string): Promise<DeepspecProvider> {
|
|
53
57
|
const drafter = await DeepspecDrafter.load(modelDir);
|
|
54
58
|
const id = modelDir.split("/").filter(Boolean).at(-1)!;
|
|
55
|
-
|
|
59
|
+
try {
|
|
60
|
+
const identity = await artifactIdentity(await Bun.file(`${modelDir}/config.json`).text(),
|
|
61
|
+
readdirSync(modelDir).filter(file => file.endsWith(".safetensors"))
|
|
62
|
+
.map(name => ({ name, path: join(modelDir, name) })));
|
|
63
|
+
return new DeepspecProvider(drafter, id, safetensorsBytes(modelDir), `deepspec-context-v1:${identity}`);
|
|
64
|
+
} catch (error) { drafter.dispose(); throw error; }
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
open(opts: Parameters<DraftProvider["open"]>[0]): DraftSource {
|
|
@@ -72,14 +81,7 @@ export class DeepspecSource implements DraftSource {
|
|
|
72
81
|
|
|
73
82
|
constructor(private readonly drafter: Pick<DeepspecDrafter,
|
|
74
83
|
"cfg" | "tapLayers" | "projectContext" | "projectContextKV" | "draftBlock">, target: TargetView) {
|
|
75
|
-
|
|
76
|
-
throw new Error("DeepSpec drafter requires a Gemma4 target");
|
|
77
|
-
const nLayers = target.gemmaTaps.layerCount;
|
|
78
|
-
if (nLayers !== drafter.cfg.num_target_layers)
|
|
79
|
-
throw new Error(
|
|
80
|
-
`DeepSpec drafter was trained for a ${drafter.cfg.num_target_layers}-layer target; ` +
|
|
81
|
-
`this model has ${nLayers} layers — wrong (target, drafter) pairing`,
|
|
82
|
-
);
|
|
84
|
+
bindDeepspecTarget(target, drafter);
|
|
83
85
|
this.tapLayers = drafter.tapLayers;
|
|
84
86
|
}
|
|
85
87
|
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
// target verify decides). The richer rejection-sampling verify stays in the
|
|
19
19
|
// standalone dflashGenerate for the measure script.
|
|
20
20
|
|
|
21
|
+
import { artifactIdentity } from "../model/artifact-identity";
|
|
22
|
+
import { projectedDraftGroups } from "./projected-draft-rows";
|
|
21
23
|
import { readdirSync, statSync } from "node:fs";
|
|
22
24
|
import { join } from "node:path";
|
|
23
25
|
import type { MlxArray } from "../mlx/array";
|
|
@@ -37,20 +39,43 @@ function safetensorsBytes(dir: string): number {
|
|
|
37
39
|
export class DflashProvider implements DraftProvider {
|
|
38
40
|
readonly id: string;
|
|
39
41
|
readonly weightsBytes: number;
|
|
42
|
+
readonly grouped: import("./source").GroupedDraftProvider;
|
|
40
43
|
/** The trained block width — the server pins numDraftTokens to this so the
|
|
41
44
|
* serve loop never asks for more positions than the block was trained for. */
|
|
42
45
|
readonly gamma: number;
|
|
43
46
|
|
|
44
|
-
private constructor(private readonly drafter: DflashDrafter, id: string, weightsBytes: number) {
|
|
47
|
+
private constructor(private readonly drafter: DflashDrafter, id: string, weightsBytes: number, namespace: string) {
|
|
45
48
|
this.id = id;
|
|
46
49
|
this.weightsBytes = weightsBytes;
|
|
47
50
|
this.gamma = drafter.cfg.gamma;
|
|
51
|
+
this.grouped = projectedDraftGroups(namespace, target => {
|
|
52
|
+
if (!target.gemmaTaps) throw new Error("DSpark drafter requires a Gemma4 target");
|
|
53
|
+
const projection = target.gemmaTaps.projection;
|
|
54
|
+
const minConf = runtimeValue("MLX_BUN_DSPARK_MINCONF");
|
|
55
|
+
return {
|
|
56
|
+
namespace, schema: "dflash-context-v1",
|
|
57
|
+
layers: drafter.cfg.nLayers, tapLayers: drafter.cfg.tapLayers,
|
|
58
|
+
project: hidden => drafter.projectContextRows(hidden),
|
|
59
|
+
draft(context, pending, _positions, depth) {
|
|
60
|
+
return drafter.forwardRows(projection, null, pending, depth, {
|
|
61
|
+
collectLogits: false, collectConfidence: false,
|
|
62
|
+
thresholds: drafter.cfg.sts?.thresholds,
|
|
63
|
+
...(minConf ? { minConf: Number(minConf) } : {}),
|
|
64
|
+
}, context).tokens;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
});
|
|
48
68
|
}
|
|
49
69
|
|
|
50
70
|
static async load(modelDir: string): Promise<DflashProvider> {
|
|
51
71
|
const drafter = loadDsparkDrafter(modelDir); // variant dispatch (dspark|legacy dflash)
|
|
52
72
|
const id = modelDir.split("/").filter(Boolean).at(-1)!;
|
|
53
|
-
|
|
73
|
+
try {
|
|
74
|
+
const identity = await artifactIdentity(await Bun.file(`${modelDir}/dspark.json`).text(),
|
|
75
|
+
readdirSync(modelDir).filter(file => file.endsWith(".safetensors"))
|
|
76
|
+
.map(name => ({ name, path: join(modelDir, name) })));
|
|
77
|
+
return new DflashProvider(drafter, id, safetensorsBytes(modelDir), `dflash-context-v1:${identity}`);
|
|
78
|
+
} catch (error) { drafter.dispose(); throw error; }
|
|
54
79
|
}
|
|
55
80
|
|
|
56
81
|
open(opts: Parameters<DraftProvider["open"]>[0]): DraftSource {
|
|
@@ -58,8 +83,7 @@ export class DflashProvider implements DraftProvider {
|
|
|
58
83
|
}
|
|
59
84
|
|
|
60
85
|
dispose(): void {
|
|
61
|
-
//
|
|
62
|
-
// materializes owned MlxArrays — free them on teardown.
|
|
86
|
+
// Provider-owned weights are released when the mounted draft is closed.
|
|
63
87
|
this.drafter.dispose();
|
|
64
88
|
}
|
|
65
89
|
}
|