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
|
@@ -1,48 +1,61 @@
|
|
|
1
1
|
import type { ExecutionCapabilities, ExecutionFeatures, ExecutionRequirements, ResolvedExecution } from "../contracts/execution";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* placement even when this request must use the ordinary AR method. */
|
|
3
|
+
/** Select the request method, then place it using executor capabilities. */
|
|
5
4
|
export function resolveExecution(
|
|
6
5
|
request: ExecutionRequirements, capabilities: ExecutionCapabilities,
|
|
7
6
|
features: ExecutionFeatures = { pagedKv: false, fill: false },
|
|
8
7
|
): ResolvedExecution {
|
|
9
8
|
const reasons: string[] = [];
|
|
10
|
-
const
|
|
9
|
+
const sharedRequestExclusions = [
|
|
11
10
|
[!capabilities.continuous, "continuous-unavailable"],
|
|
12
|
-
[
|
|
13
|
-
[request.
|
|
14
|
-
[request.hasAdapters, "adapters-require-serial"],
|
|
15
|
-
[request.wantsLogprobs, "logprobs-require-serial"],
|
|
16
|
-
[request.userSeed, "explicit-seed-requires-serial"],
|
|
11
|
+
[request.hasVision && !capabilities.mediaBatch, "media-requires-serial"],
|
|
12
|
+
[request.hasAdapters && !capabilities.adapterBatch, "adapters-require-serial"],
|
|
17
13
|
[request.kvQuant && !capabilities.quantizedBatch, "kv-scheme-requires-serial"],
|
|
18
|
-
[request.turboQuant, "turbo-kv-requires-serial"],
|
|
19
|
-
[request.hasDraft, "configured-draft-requires-serial"],
|
|
14
|
+
[request.turboQuant && !capabilities.turboQuantBatch, "turbo-kv-requires-serial"],
|
|
20
15
|
[request.hasGrammar && !capabilities.grammarBatch, "grammar-batching-disabled"],
|
|
21
|
-
[features.pagedKv, "paged-kv-requires-serial"],
|
|
16
|
+
[features.pagedKv && !capabilities.pagedBatch, "paged-kv-requires-serial"],
|
|
17
|
+
] as const;
|
|
18
|
+
const sharedAdapterMethod = capabilities.sharedSpeculativeAdapters === true &&
|
|
19
|
+
!sharedRequestExclusions.some(([excluded]) => excluded) &&
|
|
20
|
+
capabilities.groupedMethods?.includes("speculative") === true;
|
|
21
|
+
const grammarProposals = features.grammarJump === true && request.hasGrammar && !request.hasDraft && !request.hasVision &&
|
|
22
|
+
(!request.wantsLogprobs || capabilities.speculativeLogprobs === true) && !features.pagedKv && capabilities.method === "autoregressive" &&
|
|
23
|
+
capabilities.sharedGrammarProposals === true && capabilities.groupedMethods?.includes("speculative") === true &&
|
|
24
|
+
(!request.hasAdapters || sharedAdapterMethod) && (!request.kvQuant || capabilities.speculativeKvQuant === true) &&
|
|
25
|
+
(!request.turboQuant || capabilities.speculativeTurboQuant === true) &&
|
|
26
|
+
!sharedRequestExclusions.some(([excluded]) => excluded);
|
|
27
|
+
const speculative = grammarProposals || (capabilities.method === "autoregressive" && request.hasDraft &&
|
|
28
|
+
!request.hasVision && (!request.hasAdapters || sharedAdapterMethod) && (!request.wantsLogprobs || capabilities.speculativeLogprobs === true) &&
|
|
29
|
+
(!request.kvQuant || capabilities.speculativeKvQuant === true) &&
|
|
30
|
+
(!request.turboQuant || capabilities.speculativeTurboQuant === true) && !features.pagedKv);
|
|
31
|
+
const method = speculative ? "speculative" : capabilities.method;
|
|
32
|
+
const continuousExclusions = [sharedRequestExclusions[0],
|
|
33
|
+
[!(capabilities.groupedMethods ?? ["autoregressive"]).includes(method), "method-requires-serial"],
|
|
34
|
+
...sharedRequestExclusions.slice(1),
|
|
22
35
|
] as const;
|
|
23
36
|
for (const [excluded, reason] of continuousExclusions) if (excluded) reasons.push(reason);
|
|
24
37
|
const mechanism = reasons.length ? "serial" : "continuous";
|
|
25
|
-
const speculative = capabilities.method === "autoregressive" && request.hasDraft &&
|
|
26
|
-
!request.hasVision && !request.hasAdapters && !request.wantsLogprobs &&
|
|
27
|
-
(!request.kvQuant || capabilities.speculativeKvQuant === true) &&
|
|
28
|
-
!request.turboQuant && !features.pagedKv;
|
|
29
38
|
if (request.hasDraft && !speculative) reasons.push("draft-incompatible-with-request");
|
|
30
|
-
const method = speculative ? "speculative" : capabilities.method;
|
|
31
39
|
const pagedKv = features.pagedKv && !request.hasVision && !request.hasAdapters;
|
|
32
40
|
if (features.pagedKv && !pagedKv) reasons.push("paged-kv-bypassed-for-media-or-adapters");
|
|
33
|
-
const promptCache = method !== "speculative" && !request.hasVision
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
const promptCache = method !== "speculative" && (!request.hasVision ||
|
|
42
|
+
(mechanism === "continuous" && request.hasPreparedPrefixIdentity === true && capabilities.mediaPrefixCache === true));
|
|
43
|
+
const sharedFill = mechanism === "continuous" && capabilities.sharedFill === true && !pagedKv;
|
|
44
|
+
const speculativeEcho = method === "speculative" && mechanism === "continuous" &&
|
|
45
|
+
capabilities.sharedSpeculativeEcho === true;
|
|
46
|
+
const fill = features.fill && !request.hasVision && !request.hasGrammar && !request.wantsLogprobs &&
|
|
47
|
+
(speculativeEcho || (method === "autoregressive" && (mechanism === "serial" || sharedFill) &&
|
|
48
|
+
!request.hasDraft && (!request.userSeed || sharedFill)));
|
|
49
|
+
// Cache-format eligibility belongs to the method's append binding.
|
|
37
50
|
if (features.fill && !fill) reasons.push("fill-incompatible-with-request");
|
|
38
|
-
const checkpoint = capabilities.checkpoints && method === "autoregressive" &&
|
|
39
|
-
mechanism === "serial" && promptCache && !request.hasGrammar &&
|
|
51
|
+
const checkpoint = capabilities.checkpoints && method === "autoregressive" && !request.hasVision &&
|
|
52
|
+
(mechanism === "serial" || capabilities.sharedCheckpoints === true) && promptCache && !pagedKv && !request.hasGrammar &&
|
|
40
53
|
!features.fill && !request.wantsLogprobs;
|
|
41
54
|
const compiledDecode = features.compiledDecode === true && capabilities.compiledDecode === true &&
|
|
42
|
-
method === "autoregressive" && !request.hasAdapters && !pagedKv;
|
|
55
|
+
method === "autoregressive" && !request.hasAdapters && !pagedKv && !(sharedFill && fill);
|
|
43
56
|
if (features.compiledDecode && !compiledDecode) reasons.push("compiled-decode-unavailable-for-request");
|
|
44
|
-
const grammarJump = features.grammarJump === true && request.hasGrammar &&
|
|
45
|
-
method === "autoregressive" && mechanism === "serial" && !request.wantsLogprobs;
|
|
57
|
+
const grammarJump = grammarProposals || (features.grammarJump === true && request.hasGrammar &&
|
|
58
|
+
method === "autoregressive" && mechanism === "serial" && !request.wantsLogprobs);
|
|
46
59
|
if (features.grammarJump && request.hasGrammar && !grammarJump)
|
|
47
60
|
reasons.push("grammar-jump-incompatible-with-request");
|
|
48
61
|
return Object.freeze({ method, mechanism, pagedKv, promptCache, checkpoint, fill,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
interface Task {
|
|
2
|
+
run(): Promise<void>;
|
|
3
|
+
reject(error: unknown): void;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** Work whose owner requires an execution boundary, without owning a decode
|
|
7
|
+
* row. The driver supplies the lease; this queue owns waiting and cancellation.
|
|
8
|
+
* Once started, work settles before its caller can release native resources. */
|
|
9
|
+
export class ExecutionTasks {
|
|
10
|
+
#pending: Task[] = [];
|
|
11
|
+
get pending(): number { return this.#pending.length; }
|
|
12
|
+
|
|
13
|
+
enqueue<T>(work: () => Promise<T>, cancellation?: Cancellation): Promise<T> {
|
|
14
|
+
if (cancellation?.reason !== undefined) return Promise.reject(new GenerationCancelled(cancellation.reason));
|
|
15
|
+
return new Promise<T>((resolve, reject) => {
|
|
16
|
+
let unsubscribe = () => {};
|
|
17
|
+
const cleanup = () => unsubscribe();
|
|
18
|
+
const task: Task = {
|
|
19
|
+
async run() {
|
|
20
|
+
cleanup();
|
|
21
|
+
try { resolve(await work()); } catch (error) { reject(error); }
|
|
22
|
+
},
|
|
23
|
+
reject(error) { cleanup(); reject(error); },
|
|
24
|
+
};
|
|
25
|
+
this.#pending.push(task);
|
|
26
|
+
unsubscribe = cancellation?.subscribe(reason => {
|
|
27
|
+
const index = this.#pending.indexOf(task);
|
|
28
|
+
if (index < 0) return;
|
|
29
|
+
this.#pending.splice(index, 1);
|
|
30
|
+
task.reject(new GenerationCancelled(reason));
|
|
31
|
+
}) ?? (() => {});
|
|
32
|
+
if (!this.#pending.includes(task)) unsubscribe();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async advance(): Promise<void> { await this.#pending.shift()?.run(); }
|
|
37
|
+
|
|
38
|
+
rejectAll(error: unknown): void {
|
|
39
|
+
const tasks = this.#pending; this.#pending = [];
|
|
40
|
+
for (const task of tasks) task.reject(error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
import type { Cancellation } from "../contracts/generation";
|
|
44
|
+
import { GenerationCancelled } from "./cancellation";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PrefixCache } from "../contracts/prefix-cache";
|
|
2
|
+
|
|
3
|
+
/** Compose numerical identity with a cache without exposing its storage tiers. */
|
|
4
|
+
export function namespacedCache<State, Attachment>(cache: PrefixCache<State, Attachment>,
|
|
5
|
+
namespace: (base: string) => string): PrefixCache<State, Attachment> {
|
|
6
|
+
return {
|
|
7
|
+
closeSession: id => cache.closeSession?.(id),
|
|
8
|
+
reclaim: () => cache.reclaim?.(),
|
|
9
|
+
...(cache.prefetch ? { prefetch: (tokens: number[], ns = "", sessionId?: string) => cache.prefetch!(tokens, namespace(ns), sessionId) } : {}),
|
|
10
|
+
take: (tokens, ns = "", sessionId) => cache.take(tokens, namespace(ns), sessionId),
|
|
11
|
+
put: (tokens, state, ns = "", retain, attachments, sessionId) => cache.put(tokens, state, namespace(ns), retain, attachments, sessionId),
|
|
12
|
+
};
|
|
13
|
+
}
|
package/src/engine/resources.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DisposableResource, ResourceOwner } from "../contracts/resources";
|
|
1
|
+
import type { DisposableResource, PreparedStateChange, ResourceOwner } from "../contracts/resources";
|
|
2
2
|
|
|
3
3
|
export function ownResource<T>(value: T, release: (value: T) => void): ResourceOwner<T> {
|
|
4
4
|
let held: { value: T } | undefined = { value };
|
|
@@ -35,6 +35,17 @@ export function cleanupFailure(error: unknown, cleanup: () => void): never {
|
|
|
35
35
|
throw error;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** Complete every preparation before publishing any owner's replacement.
|
|
39
|
+
* Old state is released only after all owners have committed. */
|
|
40
|
+
export function applyStateChanges(prepare: readonly (() => PreparedStateChange)[]): void {
|
|
41
|
+
const changes: PreparedStateChange[] = [];
|
|
42
|
+
try {
|
|
43
|
+
for (const build of prepare) changes.push(build());
|
|
44
|
+
for (const change of changes) change.commit();
|
|
45
|
+
} catch (error) { return cleanupFailure(error, () => disposeResources(changes.splice(0))); }
|
|
46
|
+
disposeResources(changes);
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
export function withResource<T, R>(owner: Pick<ResourceOwner<T>, "borrow" | "close">, use: (value: T) => R): R {
|
|
39
50
|
try { return use(owner.borrow()); }
|
|
40
51
|
catch (error) { cleanupFailure(error, () => owner.close()); }
|
package/src/engine/scheduler.ts
CHANGED
|
@@ -18,8 +18,8 @@ export async function driveExecutionGroup(
|
|
|
18
18
|
try {
|
|
19
19
|
while (!group.closed) {
|
|
20
20
|
group.pruneCancelled();
|
|
21
|
-
|
|
22
|
-
if (!group.active && !group.preparing && (held || !group.queued)) {
|
|
21
|
+
let held = group.admissionHeld;
|
|
22
|
+
if (!group.active && !group.preparing && (held || (!group.queued && !group.pendingTasks))) {
|
|
23
23
|
release();
|
|
24
24
|
await group.waitForWork();
|
|
25
25
|
continue;
|
|
@@ -28,17 +28,42 @@ export async function driveExecutionGroup(
|
|
|
28
28
|
if (!execution && group.acquireExecution) execution = await group.acquireExecution();
|
|
29
29
|
if (group.closed) break;
|
|
30
30
|
|
|
31
|
+
// One preparation task per iteration. An exclusive model mutation
|
|
32
|
+
// keeps its existing drain priority; preparation cannot prolong it.
|
|
33
|
+
if (!held && group.pendingTasks && group.advanceTask) {
|
|
34
|
+
await group.advanceTask();
|
|
35
|
+
if (group.closed) break;
|
|
36
|
+
group.pruneCancelled();
|
|
37
|
+
held = group.admissionHeld;
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
if (!group.preparing && !held && group.queued && group.active < group.maxActive)
|
|
32
41
|
group.admitNext();
|
|
42
|
+
while (group.preparing && group.canPrepareMore && !held && group.queued &&
|
|
43
|
+
group.active + (group.preparingRows ?? 1) < group.maxActive && group.canBurst() &&
|
|
44
|
+
(group.preparingTokens ?? 0) + (group.nextPreparationTokens ?? 0) <= (group.maxPreparationTokens ?? Infinity)) {
|
|
45
|
+
if (!group.admitNext()) break;
|
|
46
|
+
}
|
|
47
|
+
const mixed = group.preparing && group.active ? group.mixedPreparation : undefined;
|
|
48
|
+
if (mixed && group.advanceMixed) {
|
|
49
|
+
// Reserve the method's running-token demand first; prompt work fills
|
|
50
|
+
// the rest. A budget smaller than one token per row still progresses.
|
|
51
|
+
const budget = Math.max(group.maxIterationTokens ?? Infinity,
|
|
52
|
+
mixed.runningTokens + mixed.minimumPreparationTokens);
|
|
53
|
+
await group.advanceMixed(budget);
|
|
54
|
+
lastYield = clock.now();
|
|
55
|
+
await clock.yield();
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
33
58
|
if (group.preparing) {
|
|
34
59
|
const activeBefore = group.active;
|
|
35
60
|
await group.advancePreparation();
|
|
36
|
-
// A
|
|
37
|
-
// Flush
|
|
38
|
-
// group
|
|
61
|
+
// A bounded preparation may publish output before its state work ends.
|
|
62
|
+
// Flush it before the next work unit; queued short admissions still
|
|
63
|
+
// form a group across yields. Re-enter policy to observe new work,
|
|
39
64
|
// cancellation, admission holds and shutdown.
|
|
40
|
-
if (
|
|
41
|
-
group.active === 1 && !group.queued) {
|
|
65
|
+
if (group.preparationPublishedOutput ||
|
|
66
|
+
(yieldAfterPreparation && !activeBefore && !group.preparing && group.active === 1 && !group.queued)) {
|
|
42
67
|
lastYield = clock.now();
|
|
43
68
|
await clock.yield();
|
|
44
69
|
continue;
|
package/src/fill/fill-session.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
// sources in priority order, clamps what they return, and counts. It never
|
|
21
21
|
// touches MLX, the tokenizer, or the template. Row COMPILATION is
|
|
22
22
|
// ./schema-rows.ts.
|
|
23
|
-
import {
|
|
23
|
+
import { runtimeFlag, runtimeNumber, runtimeValue } from "../runtime-config";
|
|
24
24
|
import { EchoSource, type EchoConfig } from "./echo-index";
|
|
25
25
|
import type {
|
|
26
26
|
Proposal, ProposalOrigin, ProposalPolicy, ProposalSource, TokenView,
|
|
@@ -153,7 +153,7 @@ export function fillEchoConfig(): FillEchoConfig {
|
|
|
153
153
|
* the engine's fill path. Off by default (the invariant it checks is the
|
|
154
154
|
* easiest bug in the feature, and the check costs a JS compare). */
|
|
155
155
|
export function fillTraceEnabled(): boolean {
|
|
156
|
-
return
|
|
156
|
+
return runtimeFlag("MLX_BUN_FILL_TRACE", false) || fillTracePath() !== null;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
/** MLX_BUN_FILL_TRACE=<file.jsonl> — additionally append one record per
|
|
@@ -319,12 +319,23 @@ export class FillSession {
|
|
|
319
319
|
/** Feed one SAMPLED token, then ask the sources what follows. `budget` is
|
|
320
320
|
* the caller's remaining allowance (max_tokens − generated). */
|
|
321
321
|
push(tokenId: number, budget: number = Number.POSITIVE_INFINITY): Proposal | null {
|
|
322
|
+
this.observe(tokenId);
|
|
323
|
+
return this.propose(budget);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Record sampled output independently of when the method requests proposals. */
|
|
327
|
+
observe(tokenId: number): void {
|
|
322
328
|
this.stats.decodeSteps++;
|
|
323
329
|
this.#append([tokenId]);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Select a continuation after a committed output boundary. Methods may
|
|
333
|
+
* request only verified proposals while retaining ordinary sampled policy. */
|
|
334
|
+
propose(budget: number = Number.POSITIVE_INFINITY, policy?: ProposalPolicy): Proposal | null {
|
|
324
335
|
this.#budget = budget;
|
|
325
336
|
for (const source of this.#sources) {
|
|
326
337
|
const proposal = source.propose(this);
|
|
327
|
-
if (!proposal) continue;
|
|
338
|
+
if (!proposal || (policy && proposal.policy !== policy)) continue;
|
|
328
339
|
if (proposal.branchStop) this.stats.branchStops++;
|
|
329
340
|
const clamped = this.#clamp(proposal.ids, budget, proposal.origin === "echo");
|
|
330
341
|
if (!clamped) continue;
|
package/src/fit.ts
CHANGED
|
@@ -13,12 +13,12 @@ import type { ModelConfig } from "./config";
|
|
|
13
13
|
import type { MemoryPlan } from "./admission-plan";
|
|
14
14
|
import {
|
|
15
15
|
kvBytesAt,
|
|
16
|
-
kvGeometry,
|
|
17
16
|
kvQuantBytesPerElement,
|
|
17
|
+
sdpaFallbackBytes,
|
|
18
18
|
type KvSchemeOptions,
|
|
19
19
|
} from "./kv-scheme";
|
|
20
20
|
|
|
21
|
-
export { kvBytesAt, kvQuantBytesPerElement } from "./kv-scheme";
|
|
21
|
+
export { kvBytesAt, kvQuantBytesPerElement, sdpaFallbackBytes } from "./kv-scheme";
|
|
22
22
|
export type FitKvScheme = KvSchemeOptions;
|
|
23
23
|
|
|
24
24
|
/** Decode-efficiency vs theoretical bandwidth ceiling, measured on the
|
|
@@ -161,23 +161,37 @@ export function fit(
|
|
|
161
161
|
kvScheme?: FitKvScheme,
|
|
162
162
|
): FitReport {
|
|
163
163
|
const usable = usableBytes ?? machine.ramBytes * WIRED_FRACTION;
|
|
164
|
-
|
|
164
|
+
// Prefill transient = the calibrated per-chunk-token constant + the
|
|
165
|
+
// SDPA-fallback scores tensor (heads × min(chunk, ctx) × ctx × 2 B) for
|
|
166
|
+
// attention layers whose head dim MLX cannot fuse (kv-scheme.ts). The
|
|
167
|
+
// second term is what grows with context on Qwen3.x (head_dim 256) and
|
|
168
|
+
// Gemma 4 (256/512) and was unbilled before 2026-09-08.
|
|
169
|
+
const transientAt = (n: number) =>
|
|
170
|
+
Math.min(chunk, n) * TRANSIENT_PER_TOKEN + sdpaFallbackBytes(config, chunk, n);
|
|
171
|
+
const transient = transientAt(ctx);
|
|
165
172
|
const kv = kvBytesAt(config, ctx, kvScheme);
|
|
166
173
|
const total = weightsBytes + kv + transient;
|
|
167
174
|
|
|
168
|
-
// solve max context: weights + kv(
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
const
|
|
175
|
+
// solve max context: weights + kv(n) + transient(n) ≤ usable. The same
|
|
176
|
+
// formula `fits` uses, so the solved ceiling always fits. It is monotone
|
|
177
|
+
// in n (KV linear, sliding saturating at the window, scores quadratic
|
|
178
|
+
// below the chunk then linear), so bisect over [0, maxPositionEmbeddings].
|
|
179
|
+
const totalAt = (n: number) => weightsBytes + kvBytesAt(config, n, kvScheme) + transientAt(n);
|
|
173
180
|
let maxCtx = 0;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
:
|
|
180
|
-
|
|
181
|
+
{
|
|
182
|
+
let lo = 0;
|
|
183
|
+
let hi = Math.max(0, Math.floor(config.text.maxPositionEmbeddings));
|
|
184
|
+
if (totalAt(hi) <= usable) lo = hi;
|
|
185
|
+
else {
|
|
186
|
+
// invariant: totalAt(lo) ≤ usable (lo = 0 may itself not fit → 0)
|
|
187
|
+
if (totalAt(0) > usable) hi = 0;
|
|
188
|
+
while (hi - lo > 1) {
|
|
189
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
190
|
+
if (totalAt(mid) <= usable) lo = mid;
|
|
191
|
+
else hi = mid;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
maxCtx = lo;
|
|
181
195
|
}
|
|
182
196
|
|
|
183
197
|
// decode reads all weights + the KV cache once per token — except MoE
|