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
|
@@ -30,206 +30,42 @@ import { materializeCopy } from "../mlx/materialize";
|
|
|
30
30
|
import { toLogprobs } from "../sampler";
|
|
31
31
|
import { loadModelConfig, type ModelConfig } from "../config";
|
|
32
32
|
import { Weights } from "../weights";
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import type { DraftProvider, DraftSource, QwenMtpTarget } from "./source";
|
|
33
|
+
import { KVCache } from "../model/gemma4-base";
|
|
34
|
+
import { MtpModule } from "./qwen-mtp-module";
|
|
35
|
+
import type { DraftProvider, DraftSource, DraftRowCheckpoint, DraftRowGroup, DraftPrefillGroup, DraftRowSampling, GroupedDraftProvider, QwenMtpTarget, TargetView } from "./source";
|
|
36
|
+
import { QwenMtpRows, type MtpRowState } from "./qwen-mtp-rows";
|
|
37
|
+
import { captureQwenMtpState, restoreQwenMtpState } from "./qwen-mtp-state";
|
|
36
38
|
import type { Cache } from "../model/gemma4";
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
39
|
+
import { applyStateChanges, cleanupFailure, disposeResources } from "../engine/resources";
|
|
40
|
+
import { artifactIdentity } from "../model/artifact-identity";
|
|
41
|
+
import { configFingerprint } from "../model/fingerprint";
|
|
42
|
+
import type { PreparedStateChange } from "../contracts/resources";
|
|
41
43
|
|
|
42
44
|
type Sampler = (logprobs: MlxArray, step: number) => MlxArray;
|
|
43
45
|
|
|
44
46
|
const DRAFT_PREFILL_CHUNK = 2048;
|
|
45
47
|
|
|
46
|
-
type MtpLinear = DenseLinear | QuantizedLinear;
|
|
47
|
-
|
|
48
|
-
/** Projection tensors belong to Weights; dense transpose views belong to
|
|
49
|
-
* the provider's resource stack. Quantized heads keep no dense copy. */
|
|
50
|
-
function loadMtpLinear(
|
|
51
|
-
weights: Weights, path: string, config: ModelConfig, resources: DisposableStack,
|
|
52
|
-
): MtpLinear {
|
|
53
|
-
if (weights.has(`${path}.scales`)) return QuantizedLinear.load(weights, path, config);
|
|
54
|
-
const layer = new DenseLinear(weights.tensor(`${path}.weight`), null);
|
|
55
|
-
resources.use(layer.wT);
|
|
56
|
-
return layer;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Qwen3Attention.forward with the companion's dense or quantized projections.
|
|
60
|
-
* Attention operations follow src/model/qwen3_5.ts verbatim. */
|
|
61
|
-
class MtpAttention {
|
|
62
|
-
readonly qProj: MtpLinear;
|
|
63
|
-
readonly kProj: MtpLinear;
|
|
64
|
-
readonly vProj: MtpLinear;
|
|
65
|
-
readonly oProj: MtpLinear;
|
|
66
|
-
readonly qNorm: RMSNorm;
|
|
67
|
-
readonly kNorm: RMSNorm;
|
|
68
|
-
readonly nHeads: number;
|
|
69
|
-
readonly nKvHeads: number;
|
|
70
|
-
readonly headDim: number;
|
|
71
|
-
readonly scale: number;
|
|
72
|
-
readonly ropeDims: number;
|
|
73
|
-
readonly ropeBase: number;
|
|
74
|
-
|
|
75
|
-
constructor(weights: Weights, config: ModelConfig, prefix: string, resources: DisposableStack) {
|
|
76
|
-
const t = config.text;
|
|
77
|
-
this.nHeads = t.numAttentionHeads;
|
|
78
|
-
this.nKvHeads = t.numKeyValueHeads;
|
|
79
|
-
this.headDim = t.headDim;
|
|
80
|
-
this.scale = Math.pow(this.headDim, -0.5);
|
|
81
|
-
this.ropeDims = Math.trunc(this.headDim * t.partialRotaryFactor);
|
|
82
|
-
this.ropeBase = t.ropeParameters.full_attention?.ropeTheta ?? 10000;
|
|
83
|
-
this.qProj = loadMtpLinear(weights, `${prefix}.q_proj`, config, resources);
|
|
84
|
-
this.kProj = loadMtpLinear(weights, `${prefix}.k_proj`, config, resources);
|
|
85
|
-
this.vProj = loadMtpLinear(weights, `${prefix}.v_proj`, config, resources);
|
|
86
|
-
this.oProj = loadMtpLinear(weights, `${prefix}.o_proj`, config, resources);
|
|
87
|
-
this.qNorm = new RMSNorm(weights.tensor(`${prefix}.q_norm.weight`), t.rmsNormEps);
|
|
88
|
-
this.kNorm = new RMSNorm(weights.tensor(`${prefix}.k_norm.weight`), t.rmsNormEps);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
forward(x: MlxArray, mask: Mask, cache: KVCache): MlxArray {
|
|
92
|
-
const [B, L] = x.shape as [number, number, number];
|
|
93
|
-
const qp = this.qProj.forward(x);
|
|
94
|
-
const qpr = disposing(qp, ops.reshape(qp, [B, L, this.nHeads, this.headDim * 2]));
|
|
95
|
-
const [qHeads, gateHeads] = ops.split(qpr, [this.headDim], -1) as [MlxArray, MlxArray];
|
|
96
|
-
qpr.dispose();
|
|
97
|
-
const gate = disposing(gateHeads, ops.reshape(gateHeads, [B, L, this.nHeads * this.headDim]));
|
|
98
|
-
|
|
99
|
-
let k = this.kProj.forward(x);
|
|
100
|
-
let v = this.vProj.forward(x);
|
|
101
|
-
|
|
102
|
-
let q = this.qNorm.forward(qHeads);
|
|
103
|
-
qHeads.dispose();
|
|
104
|
-
q = disposing(q, ops.transposeAxes(q, [0, 2, 1, 3]));
|
|
105
|
-
k = disposing(k, ops.reshape(k, [B, L, this.nKvHeads, this.headDim]));
|
|
106
|
-
k = disposing(k, this.kNorm.forward(k));
|
|
107
|
-
k = disposing(k, ops.transposeAxes(k, [0, 2, 1, 3]));
|
|
108
|
-
v = disposing(v, ops.reshape(v, [B, L, this.nKvHeads, this.headDim]));
|
|
109
|
-
v = disposing(v, ops.transposeAxes(v, [0, 2, 1, 3]));
|
|
110
|
-
|
|
111
|
-
q = disposing(q, ops.rope(q, this.ropeDims, this.ropeBase, cache.offset, null));
|
|
112
|
-
k = disposing(k, ops.rope(k, this.ropeDims, this.ropeBase, cache.offset, null));
|
|
113
|
-
|
|
114
|
-
const [keys, values] = cache.updateAndFetch(k, v);
|
|
115
|
-
k.dispose();
|
|
116
|
-
v.dispose();
|
|
117
|
-
const attn = ops.sdpa(q, keys, values, this.scale, mask.mode, mask.arr);
|
|
118
|
-
keys.dispose();
|
|
119
|
-
values.dispose();
|
|
120
|
-
q.dispose();
|
|
121
|
-
|
|
122
|
-
const attnT = ops.transposeAxes(attn, [0, 2, 1, 3]);
|
|
123
|
-
attn.dispose();
|
|
124
|
-
const merged = ops.reshape(attnT, [B, L, -1]);
|
|
125
|
-
attnT.dispose();
|
|
126
|
-
const sig = ops.sigmoid(gate);
|
|
127
|
-
gate.dispose();
|
|
128
|
-
const gated = ops.mul(merged, sig);
|
|
129
|
-
merged.dispose();
|
|
130
|
-
sig.dispose();
|
|
131
|
-
const out = this.oProj.forward(gated);
|
|
132
|
-
gated.dispose();
|
|
133
|
-
return out;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** The one MTP decoder block: fc-merge → attention → swiglu MLP → norm. */
|
|
138
|
-
class MtpModule {
|
|
139
|
-
readonly fc: MtpLinear;
|
|
140
|
-
readonly preFcNormEmbedding: RMSNorm;
|
|
141
|
-
readonly preFcNormHidden: RMSNorm;
|
|
142
|
-
readonly attn: MtpAttention;
|
|
143
|
-
readonly mlpGate: MtpLinear;
|
|
144
|
-
readonly mlpUp: MtpLinear;
|
|
145
|
-
readonly mlpDown: MtpLinear;
|
|
146
|
-
readonly inputNorm: RMSNorm;
|
|
147
|
-
readonly postAttnNorm: RMSNorm;
|
|
148
|
-
readonly finalNorm: RMSNorm;
|
|
149
|
-
|
|
150
|
-
constructor(weights: Weights, config: ModelConfig, resources: DisposableStack) {
|
|
151
|
-
const eps = config.text.rmsNormEps;
|
|
152
|
-
this.fc = loadMtpLinear(weights, "fc", config, resources);
|
|
153
|
-
this.preFcNormEmbedding = new RMSNorm(weights.tensor("pre_fc_norm_embedding.weight"), eps);
|
|
154
|
-
this.preFcNormHidden = new RMSNorm(weights.tensor("pre_fc_norm_hidden.weight"), eps);
|
|
155
|
-
this.attn = new MtpAttention(weights, config, "layers.0.self_attn", resources);
|
|
156
|
-
this.mlpGate = loadMtpLinear(weights, "layers.0.mlp.gate_proj", config, resources);
|
|
157
|
-
this.mlpUp = loadMtpLinear(weights, "layers.0.mlp.up_proj", config, resources);
|
|
158
|
-
this.mlpDown = loadMtpLinear(weights, "layers.0.mlp.down_proj", config, resources);
|
|
159
|
-
this.inputNorm = new RMSNorm(weights.tensor("layers.0.input_layernorm.weight"), eps);
|
|
160
|
-
this.postAttnNorm = new RMSNorm(weights.tensor("layers.0.post_attention_layernorm.weight"), eps);
|
|
161
|
-
this.finalNorm = new RMSNorm(weights.tensor("norm.weight"), eps);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/** One block forward over [1,S,·]: token embeddings ([1,S,H], target
|
|
165
|
-
* embed_tokens output) paired with hiddens ([1,S,H], target pre-final-norm
|
|
166
|
-
* or the module's own chained output). Appends S rows to `cache`; returns
|
|
167
|
-
* the module output [1,S,H] (post final norm — what the target lm_head
|
|
168
|
-
* consumes AND what chains into the next step's `hidden`). */
|
|
169
|
-
forward(tokenEmbeds: MlxArray, hiddens: MlxArray, cache: KVCache): MlxArray {
|
|
170
|
-
const embNorm = this.preFcNormEmbedding.forward(tokenEmbeds);
|
|
171
|
-
const hidNorm = this.preFcNormHidden.forward(hiddens);
|
|
172
|
-
const joined = ops.concatAxis([embNorm, hidNorm], 2);
|
|
173
|
-
embNorm.dispose();
|
|
174
|
-
hidNorm.dispose();
|
|
175
|
-
const x = this.fc.forward(joined);
|
|
176
|
-
joined.dispose();
|
|
177
|
-
|
|
178
|
-
// Decoder layer (Qwen3Layer.forward shape).
|
|
179
|
-
const L = x.shape[1]!;
|
|
180
|
-
const mask = cache.makeMask(L, null);
|
|
181
|
-
const xn = this.inputNorm.forward(x);
|
|
182
|
-
const r = this.attn.forward(xn, mask, cache);
|
|
183
|
-
xn.dispose();
|
|
184
|
-
mask.arr?.dispose();
|
|
185
|
-
const h = ops.add(x, r);
|
|
186
|
-
x.dispose();
|
|
187
|
-
r.dispose();
|
|
188
|
-
const hn = this.postAttnNorm.forward(h);
|
|
189
|
-
const g = this.mlpGate.forward(hn);
|
|
190
|
-
const u = this.mlpUp.forward(hn);
|
|
191
|
-
hn.dispose();
|
|
192
|
-
const silu = ops.silu(g);
|
|
193
|
-
g.dispose();
|
|
194
|
-
const act = ops.mul(silu, u);
|
|
195
|
-
silu.dispose();
|
|
196
|
-
u.dispose();
|
|
197
|
-
const m = this.mlpDown.forward(act);
|
|
198
|
-
act.dispose();
|
|
199
|
-
const out = ops.add(h, m);
|
|
200
|
-
h.dispose();
|
|
201
|
-
m.dispose();
|
|
202
|
-
return disposing(out, this.finalNorm.forward(out));
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
class QwenMtpPrefixState implements SpeculativePrefixState {
|
|
207
|
-
constructor(readonly tokens: number[], readonly targetIdentity: object,
|
|
208
|
-
readonly namespace: string, readonly bytes: number, readonly target: Cache[],
|
|
209
|
-
public draft: KVCache | null, public hidden: MlxArray | null) {}
|
|
210
|
-
|
|
211
|
-
dispose(): void {
|
|
212
|
-
const resources = [...this.target.splice(0), this.draft, this.hidden];
|
|
213
|
-
this.draft = null;
|
|
214
|
-
this.hidden = null;
|
|
215
|
-
disposeResources(resources.filter((r) => r != null));
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
48
|
export class QwenMtpProvider implements DraftProvider {
|
|
49
|
+
readonly grouped: GroupedDraftProvider = {
|
|
50
|
+
supportsExternalTokens: true,
|
|
51
|
+
checkpointNamespace: () => this.#checkpointNamespace,
|
|
52
|
+
open: options => this.#openRows(options.target, options.sampling, options.checkpoints),
|
|
53
|
+
openPrefill: options => this.#openRows(options.target, null, options.checkpoints),
|
|
54
|
+
};
|
|
220
55
|
readonly id: string;
|
|
221
56
|
readonly weightsBytes: number;
|
|
222
57
|
readonly #module: MtpModule;
|
|
223
58
|
readonly #config: ModelConfig;
|
|
224
59
|
readonly #resources: DisposableStack;
|
|
225
|
-
readonly #
|
|
60
|
+
readonly #checkpointNamespace: string;
|
|
226
61
|
|
|
227
|
-
private constructor(id: string, config: ModelConfig, weightsBytes: number, module: MtpModule, resources: DisposableStack) {
|
|
62
|
+
private constructor(id: string, config: ModelConfig, weightsBytes: number, module: MtpModule, resources: DisposableStack, checkpointNamespace: string) {
|
|
228
63
|
this.id = id;
|
|
229
64
|
this.#config = config;
|
|
230
65
|
this.#module = module;
|
|
231
66
|
this.weightsBytes = weightsBytes;
|
|
232
67
|
this.#resources = resources;
|
|
68
|
+
this.#checkpointNamespace = checkpointNamespace;
|
|
233
69
|
}
|
|
234
70
|
|
|
235
71
|
static async load(dir: string): Promise<QwenMtpProvider> {
|
|
@@ -242,16 +78,24 @@ export class QwenMtpProvider implements DraftProvider {
|
|
|
242
78
|
const weightsBytes = [...weights.shards.files.values()]
|
|
243
79
|
.reduce((a, f) => a + f.mmap.size, 0);
|
|
244
80
|
const module = new MtpModule(weights, config, resources);
|
|
81
|
+
// Stable across restarts and distinct for differently folded/quantized
|
|
82
|
+
// companions. Hash once at provider load, outside inference execution.
|
|
83
|
+
const identity = await artifactIdentity(configFingerprint(config),
|
|
84
|
+
[...weights.shards.files].map(([name, shard]) => ({ name, path: shard.path })));
|
|
245
85
|
return new QwenMtpProvider(
|
|
246
86
|
dir.split("/").filter(Boolean).at(-1) ?? "qwen-mtp",
|
|
247
|
-
config, weightsBytes, module, resources.move(),
|
|
87
|
+
config, weightsBytes, module, resources.move(), `qwen-mtp-v1:${identity}`,
|
|
248
88
|
);
|
|
249
89
|
}
|
|
250
90
|
|
|
251
91
|
open(opts: Parameters<DraftProvider["open"]>[0]): DraftSource {
|
|
92
|
+
return new QwenMtpSource(this.#target(opts.target), this.#module, opts.sampler, this.#checkpointNamespace);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#target(view: TargetView): QwenMtpTarget {
|
|
252
96
|
if (this.#resources.disposed)
|
|
253
97
|
throw new Error("qwen MTP provider is disposed");
|
|
254
|
-
const target =
|
|
98
|
+
const target = view.qwenMtp;
|
|
255
99
|
if (!target)
|
|
256
100
|
throw new Error("qwen MTP drafting requires a qwen3_5-family target");
|
|
257
101
|
if (target.hiddenSize !== this.#config.text.hiddenSize) {
|
|
@@ -260,14 +104,51 @@ export class QwenMtpProvider implements DraftProvider {
|
|
|
260
104
|
`${target.hiddenSize} — split from a different checkpoint?`,
|
|
261
105
|
);
|
|
262
106
|
}
|
|
263
|
-
return
|
|
264
|
-
|
|
107
|
+
return target;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#openRows(view: TargetView, sampling: DraftRowSampling | null,
|
|
111
|
+
checkpoints: readonly (DraftRowCheckpoint | null)[]): DraftRowGroup & DraftPrefillGroup {
|
|
112
|
+
const target = this.#target(view);
|
|
113
|
+
const rows = new QwenMtpRows(target, this.#module, sampling, []);
|
|
114
|
+
const prepareAppend = (checkpoints: readonly (DraftRowCheckpoint | null)[]) => {
|
|
115
|
+
const states: Array<MtpRowState | null> = [];
|
|
116
|
+
let change: PreparedStateChange | undefined;
|
|
117
|
+
try {
|
|
118
|
+
for (const checkpoint of checkpoints) states.push(checkpoint ? restoreQwenMtpState(checkpoint) : null);
|
|
119
|
+
change = rows.prepareAppend(states);
|
|
120
|
+
disposeResources(states.splice(0).flatMap(state => state ? [state.cache, state.hidden] : []));
|
|
121
|
+
return change;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return cleanupFailure(error, () => disposeResources([
|
|
124
|
+
...states.flatMap(state => state ? [state.cache, state.hidden] : []), ...(change ? [change] : []),
|
|
125
|
+
]));
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const append = (checkpoints: readonly (DraftRowCheckpoint | null)[]) => applyStateChanges([() => prepareAppend(checkpoints)]);
|
|
129
|
+
try { append(checkpoints); }
|
|
130
|
+
catch (error) { return cleanupFailure(error, () => rows.dispose()); }
|
|
131
|
+
return {
|
|
132
|
+
namespace: this.#checkpointNamespace, prefillMode: "full", tapLayers: [target.layerCount - 1],
|
|
133
|
+
get rowCount() { return rows.rowCount; },
|
|
134
|
+
append, prepareAppend, prefill: (tokens, context) => rows.prefill(tokens, context!), materialize: rows.materialize.bind(rows),
|
|
135
|
+
filterRows: rows.filterRows.bind(rows),
|
|
136
|
+
draft: rows.draft.bind(rows), commit: rows.commit.bind(rows),
|
|
137
|
+
consume: rows.consume.bind(rows),
|
|
138
|
+
capture(row) {
|
|
139
|
+
const state = rows.extractRow(row);
|
|
140
|
+
try { return captureQwenMtpState(state); }
|
|
141
|
+
finally { disposeResources([state.cache, state.hidden]); }
|
|
142
|
+
},
|
|
143
|
+
dispose: rows.dispose.bind(rows),
|
|
144
|
+
};
|
|
265
145
|
}
|
|
266
146
|
|
|
147
|
+
|
|
267
148
|
dispose(): void {
|
|
268
149
|
// Release cached transpose views before their native weight maps. MLX
|
|
269
150
|
// retains buffers needed by outstanding GPU commands until completion.
|
|
270
|
-
|
|
151
|
+
this.#resources.dispose();
|
|
271
152
|
}
|
|
272
153
|
}
|
|
273
154
|
|
|
@@ -284,7 +165,7 @@ export class QwenMtpSource implements DraftSource {
|
|
|
284
165
|
readonly #module: MtpModule;
|
|
285
166
|
readonly #sampler: Sampler;
|
|
286
167
|
#cache = new KVCache();
|
|
287
|
-
readonly
|
|
168
|
+
readonly checkpoint: NonNullable<DraftSource["checkpoint"]>;
|
|
288
169
|
#prefilledTokens = 0;
|
|
289
170
|
#hasDrafted = false;
|
|
290
171
|
/** Target pre-norm hidden at the position preceding the next pending
|
|
@@ -295,52 +176,29 @@ export class QwenMtpSource implements DraftSource {
|
|
|
295
176
|
#closed = false;
|
|
296
177
|
|
|
297
178
|
constructor(target: QwenMtpTarget, module: MtpModule, sampler: Sampler,
|
|
298
|
-
|
|
179
|
+
namespace = "qwen-mtp-v1") {
|
|
299
180
|
this.#target = target;
|
|
300
181
|
this.#module = module;
|
|
301
182
|
this.#sampler = sampler;
|
|
302
183
|
this.tapLayers = [target.layerCount - 1];
|
|
303
|
-
|
|
304
|
-
|
|
184
|
+
this.checkpoint = {
|
|
185
|
+
namespace,
|
|
186
|
+
restore: (tokens, attachment) => {
|
|
305
187
|
this.#checkOpen();
|
|
306
188
|
if (this.#prefilledTokens !== 0 || this.#hasDrafted)
|
|
307
|
-
throw new Error("Qwen MTP
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
disposeResources(caches);
|
|
315
|
-
caches.splice(0, caches.length, ...entry.target.splice(0));
|
|
316
|
-
this.#cache.dispose();
|
|
317
|
-
this.#cache = entry.draft;
|
|
318
|
-
entry.draft = null;
|
|
319
|
-
this.#pendingTrueHidden?.dispose();
|
|
320
|
-
this.#pendingTrueHidden = entry.hidden;
|
|
321
|
-
entry.hidden = null;
|
|
322
|
-
return this.#prefilledTokens = entry.tokens.length;
|
|
323
|
-
} finally { entry.dispose(); }
|
|
189
|
+
throw new Error("Qwen MTP checkpoint restore requires a fresh source");
|
|
190
|
+
const state = restoreQwenMtpState({ processedTokens: tokens, attachment });
|
|
191
|
+
try { disposeResources([this.#cache, ...(this.#pendingTrueHidden ? [this.#pendingTrueHidden] : [])]); }
|
|
192
|
+
catch (error) { return cleanupFailure(error, () => disposeResources([state.cache, state.hidden])); }
|
|
193
|
+
this.#cache = state.cache;
|
|
194
|
+
this.#pendingTrueHidden = state.hidden;
|
|
195
|
+
this.#prefilledTokens = tokens;
|
|
324
196
|
},
|
|
325
|
-
capture: (tokens
|
|
197
|
+
capture: (tokens) => {
|
|
326
198
|
this.#checkOpen();
|
|
327
|
-
if (
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
const bytes = cacheBytes([...caches, this.#cache]) + this.#pendingTrueHidden.nbytes;
|
|
331
|
-
if (bytes > maxBytes) { prefix.store.dispose(); return; }
|
|
332
|
-
const retained: Cache[] = [];
|
|
333
|
-
let draft: KVCache | null = null, hidden: MlxArray | null = null;
|
|
334
|
-
try {
|
|
335
|
-
retained.push(...cloneKvCaches(caches));
|
|
336
|
-
draft = cloneKvCaches([this.#cache])[0] as KVCache;
|
|
337
|
-
hidden = this.#pendingTrueHidden.slice([0, 0, 0], this.#pendingTrueHidden.shape);
|
|
338
|
-
const entry = new QwenMtpPrefixState([...tokens], prefix.identity, namespace,
|
|
339
|
-
bytes, retained.splice(0), draft, hidden);
|
|
340
|
-
draft = null;
|
|
341
|
-
hidden = null;
|
|
342
|
-
prefix.store.put(entry, maxBytes);
|
|
343
|
-
} finally { disposeResources([...retained, draft, hidden].filter((r) => r != null)); }
|
|
199
|
+
if (!this.#pendingTrueHidden || this.#cache.offset !== tokens - 1 || this.#roundAppended !== 0)
|
|
200
|
+
throw new Error("Qwen MTP snapshot requires an aligned committed boundary");
|
|
201
|
+
return captureQwenMtpState({ cache: this.#cache, hidden: this.#pendingTrueHidden }).attachment;
|
|
344
202
|
},
|
|
345
203
|
};
|
|
346
204
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MlxArray } from "../mlx/array";
|
|
2
|
+
import { KVCache } from "../model/gemma4-base";
|
|
3
|
+
import { cloneAttachments } from "../backends/mlx/checkpoint-state";
|
|
4
|
+
import { disposeResources } from "../engine/resources";
|
|
5
|
+
import type { MtpRowState } from "./qwen-mtp-rows";
|
|
6
|
+
import type { DraftRowCheckpoint } from "./source";
|
|
7
|
+
|
|
8
|
+
/** The existing Qwen companion attachment is also the state interchange
|
|
9
|
+
* between prepared requests and a group. Storage-tier policy stays outside. */
|
|
10
|
+
export function restoreQwenMtpState(checkpoint: DraftRowCheckpoint): MtpRowState {
|
|
11
|
+
const { processedTokens: tokens, attachment } = checkpoint;
|
|
12
|
+
if (attachment.schema !== "qwen-mtp-v1" || attachment.metadata.draftOffset !== tokens - 1)
|
|
13
|
+
throw new Error("invalid paired Qwen MTP checkpoint alignment");
|
|
14
|
+
const held = cloneAttachments([attachment])[0]!.tensors;
|
|
15
|
+
let cache: KVCache | null = new KVCache();
|
|
16
|
+
try {
|
|
17
|
+
if (tokens > 1) { cache.restoreState(held[0]!, held[1]!, tokens - 1); held.splice(0, 2); }
|
|
18
|
+
const state = { cache, hidden: held.pop()! }; cache = null; return state;
|
|
19
|
+
} finally { disposeResources([...held, ...(cache ? [cache] : [])]); }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function captureQwenMtpState(state: MtpRowState): DraftRowCheckpoint {
|
|
23
|
+
const held: MlxArray[] = [];
|
|
24
|
+
try {
|
|
25
|
+
if (state.cache.offset > 0) held.push(...state.cache.temporalView());
|
|
26
|
+
held.push(state.hidden.slice([0, 0, 0], [...state.hidden.shape]));
|
|
27
|
+
return { processedTokens: state.cache.offset + 1,
|
|
28
|
+
attachment: { schema: "qwen-mtp-v1", metadata: { draftOffset: state.cache.offset }, tensors: held.splice(0) } };
|
|
29
|
+
} finally { disposeResources(held); }
|
|
30
|
+
}
|