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
package/src/spec/drafter.ts
CHANGED
|
@@ -28,8 +28,32 @@ import { Weights } from "../weights";
|
|
|
28
28
|
import { MlxArray } from "../mlx/array";
|
|
29
29
|
import { Dtype } from "../mlx/ffi";
|
|
30
30
|
import * as ops from "../mlx/ops";
|
|
31
|
+
import { disposeResources } from "../engine/resources";
|
|
31
32
|
import type { SharedKv } from "../model/gemma4";
|
|
32
33
|
|
|
34
|
+
/** The graph borrows donor attention; storage owns validity and encoding. */
|
|
35
|
+
export interface AssistantAttention {
|
|
36
|
+
attend(query: MlxArray, scale: number, window: number | null): MlxArray;
|
|
37
|
+
}
|
|
38
|
+
export interface AssistantDonors { sliding: AssistantAttention; full: AssistantAttention; }
|
|
39
|
+
|
|
40
|
+
/** Legacy plain donor adapter. The common graph never reads cache geometry. */
|
|
41
|
+
export function plainAssistantDonors(shared: { sliding: [MlxArray, MlxArray]; full: [MlxArray, MlxArray] },
|
|
42
|
+
position: number): AssistantDonors {
|
|
43
|
+
const bind = ([keys, values]: [MlxArray, MlxArray]): AssistantAttention => ({
|
|
44
|
+
attend(query, scale, window) {
|
|
45
|
+
const length = keys.shape[2]!;
|
|
46
|
+
const from = window === null ? 0 : Math.max(0, Math.max(0, position - window + 1) - (position - length + 1));
|
|
47
|
+
using floats = from ? MlxArray.fromFloat32(Float32Array.from({ length }, (_, i) => i < from ? -1e9 : 0), [1,1,1,length]) : null;
|
|
48
|
+
using mask = floats?.astype(keys.dtype);
|
|
49
|
+
return ops.sdpa(query, keys, values, scale, mask ? "array" : "", mask ?? null);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
return { sliding: bind(shared.sliding), full: bind(shared.full) };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface DrafterRowsStep { tokens: MlxArray; nextHidden: MlxArray; }
|
|
56
|
+
|
|
33
57
|
export interface DrafterStep {
|
|
34
58
|
/** Drafted token id. */
|
|
35
59
|
token: number;
|
|
@@ -64,6 +88,7 @@ export class GemmaAssistantDrafter {
|
|
|
64
88
|
readonly topK: number;
|
|
65
89
|
readonly vocabPerCentroid: number;
|
|
66
90
|
#w: Weights;
|
|
91
|
+
#owned: MlxArray[] = [];
|
|
67
92
|
#blocks: Block[] = [];
|
|
68
93
|
readonly useCentroids: boolean;
|
|
69
94
|
#prePT: MlxArray; // pre_projection transposed [in, out]
|
|
@@ -84,73 +109,80 @@ export class GemmaAssistantDrafter {
|
|
|
84
109
|
this.vocabPerCentroid = Math.floor(t.vocab_size / this.numCentroids);
|
|
85
110
|
this.#w = w;
|
|
86
111
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
a.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
const own = (array: MlxArray) => { this.#owned.push(array); return array; };
|
|
113
|
+
try {
|
|
114
|
+
const T = (name: string) => w.tensor(name);
|
|
115
|
+
// Keep the oracle's strided weight.T: a contiguous transpose changes
|
|
116
|
+
// M4 matmul rounding and allocates a second copy of every linear weight.
|
|
117
|
+
const transposed = (name: string): MlxArray => {
|
|
118
|
+
const c = own(ops.transposeAxes(T(name), [1, 0]));
|
|
119
|
+
c.eval();
|
|
120
|
+
return c;
|
|
121
|
+
};
|
|
122
|
+
this.#prePT = transposed("pre_projection.weight");
|
|
123
|
+
this.#postPT = transposed("post_projection.weight");
|
|
124
|
+
this.#embed = T("model.embed_tokens.weight");
|
|
125
|
+
this.#norm = T("model.norm.weight");
|
|
126
|
+
// Head variant: only the small drafters ship the centroid tensors.
|
|
127
|
+
// The 12B/26B artifacts ship a tied embed_tokens head instead (their
|
|
128
|
+
// config still declares num_centroids — ignore it, trust the tensors).
|
|
129
|
+
this.useCentroids = w.has("masked_embedding.centroids.weight");
|
|
130
|
+
if (this.useCentroids) {
|
|
131
|
+
this.#centroidsT = transposed("masked_embedding.centroids.weight");
|
|
132
|
+
const ord64 = T("masked_embedding.token_ordering");
|
|
133
|
+
const ord32 = ord64.astype(Dtype.int32);
|
|
134
|
+
this.#ordering = own(ops.reshape(ord32, [this.numCentroids, this.vocabPerCentroid]));
|
|
135
|
+
this.#ordering.eval();
|
|
136
|
+
ord32.dispose();
|
|
137
|
+
}
|
|
111
138
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
const ropeP = t.rope_parameters ?? {};
|
|
140
|
+
for (let i = 0; i < t.num_hidden_layers; i++) {
|
|
141
|
+
const layerType = t.layer_types[i];
|
|
142
|
+
const isFull = layerType === "full_attention";
|
|
143
|
+
const headDim = isFull ? t.global_head_dim : t.head_dim;
|
|
144
|
+
const rp = ropeP[layerType] ?? {};
|
|
145
|
+
let ropeBase: number | null = rp.rope_theta ?? (isFull ? 1e6 : 1e4);
|
|
146
|
+
let ropeFreqs: MlxArray | null = null;
|
|
147
|
+
const partial = rp.partial_rotary_factor ?? 1.0;
|
|
148
|
+
if (isFull && partial < 1.0) {
|
|
149
|
+
// ProportionalRoPE freqs, matching the target's donor-K rope
|
|
150
|
+
const rotated = Math.floor(headDim * partial);
|
|
151
|
+
const n = headDim / 2;
|
|
152
|
+
const freqs = new Float32Array(n).fill(Infinity);
|
|
153
|
+
for (let k = 0; k < rotated / 2; k++)
|
|
154
|
+
freqs[k] = Math.pow(ropeBase!, (2 * k) / headDim);
|
|
155
|
+
ropeFreqs = own(MlxArray.fromFloat32(freqs, [n]));
|
|
156
|
+
ropeBase = null;
|
|
157
|
+
}
|
|
158
|
+
const p = `model.layers.${i}`;
|
|
159
|
+
this.#blocks.push({
|
|
160
|
+
layerType, headDim, ropeBase, ropeFreqs,
|
|
161
|
+
qProj: transposed(`${p}.self_attn.q_proj.weight`),
|
|
162
|
+
oProj: transposed(`${p}.self_attn.o_proj.weight`),
|
|
163
|
+
qNorm: T(`${p}.self_attn.q_norm.weight`),
|
|
164
|
+
inputNorm: T(`${p}.input_layernorm.weight`),
|
|
165
|
+
postAttnNorm: T(`${p}.post_attention_layernorm.weight`),
|
|
166
|
+
preFfNorm: T(`${p}.pre_feedforward_layernorm.weight`),
|
|
167
|
+
postFfNorm: T(`${p}.post_feedforward_layernorm.weight`),
|
|
168
|
+
gateProj: transposed(`${p}.mlp.gate_proj.weight`),
|
|
169
|
+
upProj: transposed(`${p}.mlp.up_proj.weight`),
|
|
170
|
+
downProj: transposed(`${p}.mlp.down_proj.weight`),
|
|
171
|
+
layerScalar: T(`${p}.layer_scalar`),
|
|
172
|
+
});
|
|
130
173
|
}
|
|
131
|
-
|
|
132
|
-
this.#blocks.push({
|
|
133
|
-
layerType, headDim, ropeBase, ropeFreqs,
|
|
134
|
-
qProj: transposed(`${p}.self_attn.q_proj.weight`),
|
|
135
|
-
oProj: transposed(`${p}.self_attn.o_proj.weight`),
|
|
136
|
-
qNorm: T(`${p}.self_attn.q_norm.weight`),
|
|
137
|
-
inputNorm: T(`${p}.input_layernorm.weight`),
|
|
138
|
-
postAttnNorm: T(`${p}.post_attention_layernorm.weight`),
|
|
139
|
-
preFfNorm: T(`${p}.pre_feedforward_layernorm.weight`),
|
|
140
|
-
postFfNorm: T(`${p}.post_feedforward_layernorm.weight`),
|
|
141
|
-
gateProj: transposed(`${p}.mlp.gate_proj.weight`),
|
|
142
|
-
upProj: transposed(`${p}.mlp.up_proj.weight`),
|
|
143
|
-
downProj: transposed(`${p}.mlp.down_proj.weight`),
|
|
144
|
-
layerScalar: T(`${p}.layer_scalar`),
|
|
145
|
-
});
|
|
146
|
-
}
|
|
174
|
+
} catch (error) { disposeResources(this.#owned.splice(0)); throw error; }
|
|
147
175
|
}
|
|
148
176
|
|
|
149
177
|
static async load(modelDir: string): Promise<GemmaAssistantDrafter> {
|
|
150
178
|
const cfg = (await Bun.file(`${modelDir}/config.json`).json()) as Record<string, any>;
|
|
151
|
-
|
|
179
|
+
const weights = await Weights.open(modelDir);
|
|
180
|
+
try { return new GemmaAssistantDrafter(weights, cfg); }
|
|
181
|
+
catch (error) { weights.dispose(); throw error; }
|
|
152
182
|
}
|
|
153
183
|
|
|
184
|
+
dispose(): void { disposeResources([...this.#owned.splice(0), this.#w]); }
|
|
185
|
+
|
|
154
186
|
/** f32-internal RMSNorm (reference _RMSNorm casts through float32). */
|
|
155
187
|
#rms(x: MlxArray, weight: MlxArray): MlxArray {
|
|
156
188
|
const f = x.astype(Dtype.float32);
|
|
@@ -161,49 +193,77 @@ export class GemmaAssistantDrafter {
|
|
|
161
193
|
return back;
|
|
162
194
|
}
|
|
163
195
|
|
|
164
|
-
/**
|
|
165
|
-
*
|
|
166
|
-
forward(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
196
|
+
/** The request adapter consumes the same graph as a batch of one.
|
|
197
|
+
* Donors are chronological; position is the target's last position. */
|
|
198
|
+
forward(lastTokenEmb: MlxArray, targetHidden: MlxArray,
|
|
199
|
+
sharedKv: { sliding: [MlxArray, MlxArray]; full: [MlxArray, MlxArray] }, position: number): DrafterStep {
|
|
200
|
+
const result = this.forwardRows(lastTokenEmb, targetHidden, plainAssistantDonors(sharedKv, position), position);
|
|
201
|
+
try { return { token: ops.itemUint32(result.tokens), nextHidden: result.nextHidden }; }
|
|
202
|
+
catch (error) { result.nextHidden.dispose(); throw error; }
|
|
203
|
+
finally { result.tokens.dispose(); }
|
|
204
|
+
}
|
|
173
205
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
206
|
+
#sampleRows(h: MlxArray, B: number): MlxArray {
|
|
207
|
+
if (this.useCentroids) {
|
|
208
|
+
using scores = ops.matmul(h, this.#centroidsT!);
|
|
209
|
+
using flat = ops.reshape(scores, [B, this.numCentroids]);
|
|
210
|
+
using neg = ops.neg(flat);
|
|
211
|
+
using partition = ops.argpartitionAxis(neg, this.topK - 1, -1);
|
|
212
|
+
using top = partition.slice([0, 0], [B, this.topK]);
|
|
213
|
+
using topFlat = ops.reshape(top, [B * this.topK]);
|
|
214
|
+
using candidates = ops.takeAxis(this.#ordering!, topFlat, 0);
|
|
215
|
+
const count = this.topK * this.vocabPerCentroid;
|
|
216
|
+
using ids = ops.reshape(candidates, [B, count]);
|
|
217
|
+
using flatIds = ops.reshape(ids, [B * count]);
|
|
218
|
+
using embeddings = ops.takeAxis(this.#embed, flatIds, 0);
|
|
219
|
+
using matrix = ops.reshape(embeddings, [B, count, this.hidden]);
|
|
220
|
+
using vector = ops.reshape(h, [B, 1, this.hidden]);
|
|
221
|
+
// The oracle rounds each bf16 product before reduction. Matmul's
|
|
222
|
+
// accumulation changes candidate rankings on M1, even at identical h.
|
|
223
|
+
using products = ops.mul(matrix, vector);
|
|
224
|
+
using candidateScores = ops.sumAxis(products, -1, false);
|
|
225
|
+
using maximum = ops.maxAxis(candidateScores, -1, true);
|
|
226
|
+
using winners = ops.equal(candidateScores, maximum);
|
|
227
|
+
// Sparse full-vocabulary argmax chooses the lowest token ID on a tie.
|
|
228
|
+
// Apply that rule directly to the candidates without a vocab-size scatter.
|
|
229
|
+
using sentinel = ops.fromInt32([this.#embed.shape[0]!], []);
|
|
230
|
+
using eligible = ops.where(winners, ids, sentinel);
|
|
231
|
+
using selected = ops.minAxis(eligible, -1, false);
|
|
232
|
+
return selected.astype(Dtype.uint32);
|
|
233
|
+
}
|
|
234
|
+
using rows = ops.reshape(h, [B, this.hidden]);
|
|
235
|
+
using vector = B === 1 ? ops.reshape(h, [this.hidden, 1]) : ops.transposeAxes(rows, [1, 0]);
|
|
236
|
+
using scores = ops.matmul(this.#embed, vector);
|
|
237
|
+
using flat = B === 1 ? ops.reshape(scores, [1, this.#embed.shape[0]!]) : ops.transposeAxes(scores, [1, 0]);
|
|
238
|
+
return ops.argmaxAxis(flat, -1);
|
|
239
|
+
}
|
|
177
240
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const m = new Float32Array(kLen);
|
|
186
|
-
for (let i = 0; i < allowedFrom; i++) m[i] = -1e9;
|
|
187
|
-
const mf = MlxArray.fromFloat32(m, [1, 1, 1, kLen]);
|
|
188
|
-
mask = mf.astype(k.dtype);
|
|
189
|
-
mf.dispose();
|
|
190
|
-
}
|
|
191
|
-
}
|
|
241
|
+
/** Numerical graph only. Callers supply donor validity masks for different
|
|
242
|
+
* row positions; token selection remains on device across the draft chain. */
|
|
243
|
+
forwardRows(lastTokenEmb: MlxArray, targetHidden: MlxArray,
|
|
244
|
+
donors: AssistantDonors, position: number | MlxArray): DrafterRowsStep {
|
|
245
|
+
const B = lastTokenEmb.shape[0]!;
|
|
246
|
+
let h = ops.concatAxis([lastTokenEmb, targetHidden], 2); // (B,1,2*backbone)
|
|
247
|
+
h = disposing(h, ops.matmul(h, this.#prePT)); // (B,1,256)
|
|
192
248
|
|
|
249
|
+
for (const blk of this.#blocks) {
|
|
250
|
+
const donor = blk.layerType === "sliding_attention" ? donors.sliding : donors.full;
|
|
193
251
|
const residual = h;
|
|
194
252
|
let x = this.#rms(h, blk.inputNorm);
|
|
195
253
|
// Q-only attention
|
|
196
254
|
let q = ops.matmul(x, blk.qProj);
|
|
197
255
|
x.dispose();
|
|
198
|
-
q = disposing(q, ops.reshape(q, [
|
|
256
|
+
q = disposing(q, ops.reshape(q, [B, 1, this.nHeads, blk.headDim]));
|
|
199
257
|
q = disposing(q, this.#rms(q, blk.qNorm));
|
|
200
258
|
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
201
|
-
q = disposing(q,
|
|
202
|
-
|
|
259
|
+
q = disposing(q, typeof position === "number"
|
|
260
|
+
? ops.rope(q, blk.headDim, blk.ropeBase, position, blk.ropeFreqs)
|
|
261
|
+
: ops.ropeDynamic(q, blk.headDim, blk.ropeBase, position, blk.ropeFreqs));
|
|
262
|
+
let attn = donor.attend(q, Math.pow(blk.headDim, -0.5),
|
|
263
|
+
blk.layerType === "sliding_attention" ? this.slidingWindow : null);
|
|
203
264
|
q.dispose();
|
|
204
|
-
mask?.dispose();
|
|
205
265
|
attn = disposing(attn, ops.transposeAxes(attn, [0, 2, 1, 3]));
|
|
206
|
-
attn = disposing(attn, ops.reshape(attn, [
|
|
266
|
+
attn = disposing(attn, ops.reshape(attn, [B, 1, this.nHeads * blk.headDim]));
|
|
207
267
|
attn = disposing(attn, ops.matmul(attn, blk.oProj));
|
|
208
268
|
attn = disposing(attn, this.#rms(attn, blk.postAttnNorm));
|
|
209
269
|
h = ops.add(residual, attn);
|
|
@@ -231,59 +291,10 @@ export class GemmaAssistantDrafter {
|
|
|
231
291
|
|
|
232
292
|
h = disposing(h, this.#rms(h, this.#norm));
|
|
233
293
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
// centroid decode → drafted token (argmax-equivalent shortcut)
|
|
237
|
-
const cScores = ops.matmul(h, this.#centroidsT!); // (1,1,2048)
|
|
238
|
-
const flat = ops.reshape(cScores, [1, this.numCentroids]);
|
|
239
|
-
cScores.dispose();
|
|
240
|
-
const neg = ops.neg(flat);
|
|
241
|
-
flat.dispose();
|
|
242
|
-
const part = ops.argpartitionAxis(neg, this.topK - 1, -1);
|
|
243
|
-
neg.dispose();
|
|
244
|
-
const topIdx = part.slice([0, 0], [1, this.topK]); // (1, topK)
|
|
245
|
-
part.dispose();
|
|
246
|
-
const topFlat = ops.reshape(topIdx, [this.topK]);
|
|
247
|
-
topIdx.dispose();
|
|
248
|
-
const candIds = ops.takeAxis(this.#ordering!, topFlat, 0); // (topK, vpc) int32
|
|
249
|
-
topFlat.dispose();
|
|
250
|
-
const candFlat = ops.reshape(candIds, [this.topK * this.vocabPerCentroid]);
|
|
251
|
-
candIds.dispose();
|
|
252
|
-
const candEmb = ops.takeAxis(this.#embed, candFlat, 0); // (topK*vpc, hidden)
|
|
253
|
-
const hVec = ops.reshape(h, [this.hidden, 1]);
|
|
254
|
-
const scores = ops.matmul(candEmb, hVec); // (topK*vpc, 1)
|
|
255
|
-
candEmb.dispose();
|
|
256
|
-
hVec.dispose();
|
|
257
|
-
const scoresFlat = ops.reshape(scores, [1, this.topK * this.vocabPerCentroid]);
|
|
258
|
-
scores.dispose();
|
|
259
|
-
const am = ops.argmaxAxis(scoresFlat, -1);
|
|
260
|
-
scoresFlat.dispose();
|
|
261
|
-
const winner = ops.itemUint32(am);
|
|
262
|
-
am.dispose();
|
|
263
|
-
const tokArr = candFlat.slice([winner], [winner + 1]);
|
|
264
|
-
candFlat.dispose();
|
|
265
|
-
const tokU32 = tokArr.astype(Dtype.uint32);
|
|
266
|
-
tokArr.dispose();
|
|
267
|
-
token = ops.itemUint32(tokU32);
|
|
268
|
-
tokU32.dispose();
|
|
269
|
-
} else {
|
|
270
|
-
// tied-embedding head: token = argmax_v (embed[v] · h) over the
|
|
271
|
-
// full vocab — the dense form the centroid head approximates.
|
|
272
|
-
const V = this.#embed.shape[0]!;
|
|
273
|
-
const hVec = ops.reshape(h, [this.hidden, 1]);
|
|
274
|
-
const scores = ops.matmul(this.#embed, hVec); // (vocab, 1)
|
|
275
|
-
hVec.dispose();
|
|
276
|
-
const scoresFlat = ops.reshape(scores, [1, V]);
|
|
277
|
-
scores.dispose();
|
|
278
|
-
const am = ops.argmaxAxis(scoresFlat, -1);
|
|
279
|
-
scoresFlat.dispose();
|
|
280
|
-
token = ops.itemUint32(am);
|
|
281
|
-
am.dispose();
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const nextHidden = ops.matmul(h, this.#postPT); // (1,1,backbone)
|
|
294
|
+
const tokens = this.#sampleRows(h, B);
|
|
295
|
+
const nextHidden = ops.matmul(h, this.#postPT); // (B,1,backbone)
|
|
285
296
|
h.dispose();
|
|
286
|
-
return {
|
|
297
|
+
return { tokens, nextHidden };
|
|
287
298
|
}
|
|
288
299
|
}
|
|
289
300
|
|