mlx-bun 0.0.13 → 0.2.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 +31 -21
- package/bin/mlx-bun.mjs +1 -1
- package/docs/{design → archive}/hlg-sampling.md +3 -3
- package/docs/{investigations → archive/investigations}/hlg-sampling-investigation.md +1 -1
- package/docs/archive/investigations/pareto-specialized-runtime-findings.md +92 -0
- package/docs/{planning → archive/planning}/chat-ui-vision.md +1 -1
- package/docs/{planning → archive/planning}/release-notes-v0.0.9.md +1 -1
- package/docs/{planning → archive/planning}/website-readme-pass-plan.md +1 -1
- package/docs/design/colibri-glm52-port.md +1 -1
- package/docs/design/compat-cli-surface-design.md +1 -1
- package/docs/design/decode-speed-program.md +1 -1
- package/docs/design/docs-reorg-plan.md +1 -1
- package/docs/design/dspark-serving-program.md +1 -1
- package/docs/design/dspark-speculative-decoding.md +53 -3
- package/docs/design/grammar-spec-batching-integration.md +1 -1
- package/docs/design/mlx-lm-serving-execution-seam.md +81 -0
- package/docs/design/orpo-dynamic-lambda.md +1 -1
- package/docs/design/orpo-future-enhancements.md +1 -1
- package/docs/design/orpo-training.md +2 -2
- package/docs/design/parity-tier-dag.md +1 -1
- package/docs/design/repo-cleanup-plan.md +46 -4
- package/docs/design/segmented-backward-training.md +2 -2
- package/docs/design/ssd-kv-cold-tier.md +127 -0
- package/docs/design/turboquant-weights.md +467 -0
- package/docs/design/w4a16-compute-precision-spike.md +1 -1
- package/docs/design/web-chat-redesign.md +5 -5
- package/docs/planning/chunk-task-roadmap.md +1 -1
- package/docs/planning/curve-sampler-research-plan.md +4 -4
- package/docs/planning/memory-docs-and-dag-plan.md +1 -1
- package/docs/planning/release-notes-v0.2.0.md +49 -0
- package/docs/planning/video-series.md +421 -0
- package/docs/reference/cli.md +15 -4
- package/docs/reference/distribution.md +1 -0
- package/docs/reference/features-matrix.md +3 -2
- package/docs/reference/library-api.md +41 -1
- package/docs/reference/models.md +33 -0
- package/docs/reference/server-api.md +69 -14
- package/docs/reference/server-config.md +57 -33
- package/package.json +2 -2
- package/src/anthropic.ts +92 -3
- package/src/cli.ts +131 -36
- package/src/eval/kl.ts +27 -20
- package/src/eval/runner.ts +23 -4
- package/src/eval/tasks/bfcl.ts +2 -1
- package/src/eval/tasks/gsm8k.ts +2 -1
- package/src/eval/tasks/hashhop.ts +2 -1
- package/src/eval/tasks/humaneval.ts +5 -4
- package/src/eval/tasks/ifeval.ts +2 -1
- package/src/eval/tasks/mmlu.ts +2 -1
- package/src/expert-io.ts +3 -1
- package/src/expert-offload.ts +3 -1
- package/src/expert-trace.ts +3 -1
- package/src/fit.ts +24 -43
- package/src/flags.ts +7 -9
- package/src/generate.ts +107 -92
- package/src/grammar.ts +2 -1
- package/src/harness-pi.ts +1 -1
- package/src/index.ts +20 -0
- package/src/jobs/job-entry.ts +3 -2
- package/src/kv-scheme.ts +199 -0
- package/src/kv-store.ts +370 -205
- package/src/media-fetch.ts +19 -3
- package/src/memory/model.ts +2 -1
- package/src/memory/vault.ts +2 -1
- package/src/memory-plan.ts +18 -0
- package/src/mlx/ffi.ts +8 -11
- package/src/mlx/ops.ts +31 -6
- package/src/model/batched-mask.ts +1 -1
- package/src/model/batched-quant.ts +1 -1
- package/src/model/batched-rotating-quant.ts +43 -97
- package/src/model/batched-rotating-state.ts +73 -0
- package/src/model/batched-rotating.ts +56 -105
- package/src/model/batched-row-storage.ts +127 -0
- package/src/model/compiled-decode.ts +3 -2
- package/src/model/factory.ts +44 -49
- package/src/model/gemma4-base.ts +163 -10
- package/src/model/generated/gemma4-12b.ts +50 -146
- package/src/model/generated/gemma4-26b.ts +32 -92
- package/src/model/generated/gemma4-e4b.ts +26 -74
- package/src/model/glm52-cache.ts +19 -9
- package/src/model/glm52-memory.ts +28 -1
- package/src/model/minicpm5.ts +2 -1
- package/src/model/profile.ts +433 -0
- package/src/model/qwen3-delta.ts +98 -2
- package/src/model/qwen3-mrope.ts +252 -0
- package/src/model/qwen3_5.ts +217 -25
- package/src/native/frame_extract.swift +140 -0
- package/src/native-pack.ts +13 -6
- package/src/pi-web.ts +6 -5
- package/src/prompt-cache.ts +2 -2
- package/src/quantize/atomic-output.ts +38 -0
- package/src/quantize/calibration.ts +2 -1
- package/src/quantize/config-writer.ts +6 -0
- package/src/quantize/index.ts +28 -1
- package/src/quantize/job.ts +11 -0
- package/src/quantize/quantizer.ts +265 -100
- package/src/quantize/rotate.ts +603 -0
- package/src/quantize/safetensors-writer.ts +79 -1
- package/src/quantize/sensitivity.ts +41 -59
- package/src/quantize/weight-transform.ts +292 -0
- package/src/responses.ts +102 -2
- package/src/runtime-config.ts +67 -0
- package/src/sampler.ts +272 -0
- package/src/serve/admin-routes.ts +184 -0
- package/src/serve/aux-routes.ts +132 -0
- package/src/serve/batch-scheduler.ts +200 -87
- package/src/serve/completion-executor.ts +346 -0
- package/src/serve/completion-sink.ts +157 -0
- package/src/serve/discovery-routes.ts +205 -0
- package/src/serve/generation-gateway.ts +116 -109
- package/src/serve/kv-budget.ts +14 -0
- package/src/serve/lab-routes.ts +338 -0
- package/src/serve/model-admin-routes.ts +153 -0
- package/src/serve/prompt-response-trace.ts +147 -0
- package/src/serve/request-plan.ts +158 -0
- package/src/serve/static-routes.ts +101 -0
- package/src/server.ts +704 -1400
- package/src/spec/dflash-source.ts +2 -1
- package/src/spec/dspark/module-dflash.ts +1 -1
- package/src/spec/glm52-mtp-source.ts +8 -0
- package/src/spec/qwen-mtp-source.ts +431 -0
- package/src/spec/serve-loop.ts +36 -68
- package/src/ssd-cache.ts +17 -14
- package/src/ssd-durability.ts +195 -0
- package/src/train/flash-cce.ts +12 -11
- package/src/train/forward.ts +32 -1
- package/src/train/loss.ts +2 -1
- package/src/train/segmented.ts +4 -3
- package/src/train/trainer.ts +3 -2
- package/src/vision/preprocess.ts +1 -1
- package/src/vision/prompt.ts +47 -1
- package/src/vision/qwen3vl-preprocess.ts +409 -0
- package/src/vision/qwen3vl-prompt.ts +196 -0
- package/src/vision/qwen3vl-tower.ts +513 -0
- package/src/vision/video-frames.ts +148 -0
- package/src/web/app.js +10 -3
- package/src/web/src/chat.ts +12 -5
- package/src/weights.ts +81 -26
- /package/docs/{investigations → archive/investigations}/chunk-finetune-experiment.md +0 -0
- /package/docs/{investigations → archive/investigations}/colibri-oracle-pin.md +0 -0
- /package/docs/{investigations → archive/investigations}/curve-bisector-routing.md +0 -0
- /package/docs/{investigations → archive/investigations}/curve-runs/bisector-route.json +0 -0
- /package/docs/{investigations → archive/investigations}/curve-runs/bisector-trace.json +0 -0
- /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.json +0 -0
- /package/docs/{investigations → archive/investigations}/curve-runs/curve-terrain.svg +0 -0
- /package/docs/{investigations → archive/investigations}/curve-terrain.html +0 -0
- /package/docs/{investigations → archive/investigations}/decode-roofline-lookagain.md +0 -0
- /package/docs/{investigations → archive/investigations}/dspark-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/expert-offload-single-user-moe.md +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-ab.html +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-explorer.html +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/cliff.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/cross.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/edges.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/k20-grids.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/k20-peaks.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/lw-curve.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-figs/terrain.svg +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-report.html +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-ab.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind-prompts.md +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-blind.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cliff.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-cross.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-edge.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-eval1.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-frontier.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-grid2.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-lw-coarse.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/hlg-map.json +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/run0-original.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runA-recommended.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAA-cliff-cpm5.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAB-grid2-e4b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAC-peak-e4b-partial.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAD-ab-samples.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAE-edge-e4b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAF-logit-calibration.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runAG-eval-config2.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runB-gain-variants.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runC-pivots.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runD-oetf.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runE-eotf.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runF-shaper-working.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runG-shaper-sweep.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runH-wsweep.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runI-asweep.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runJ-knobmap.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runK-frontier.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runL-widemap.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runM-frontier-emb.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runN-verify.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runO-frontier-seed5000.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runP-grid-AxSm.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runQ-grid-tgW.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runR-blind-transcript.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runS-cross-coherence.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runT-cross-cpm5.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runU-cross-12b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runV-lw-e4b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runW-lw-12b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runX-lw-cpm5.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runY-cliff-e4b.log +0 -0
- /package/docs/{investigations → archive/investigations}/hlg-runs/runZ-cliff-12b.log +0 -0
- /package/docs/{investigations → archive/investigations}/indexshare-performance-spike.md +0 -0
- /package/docs/{investigations → archive/investigations}/kernel-perf-review-2026-07.md +0 -0
- /package/docs/{investigations → archive/investigations}/lab-build-journal.md +0 -0
- /package/docs/{investigations → archive/investigations}/openwiki-evaluation.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-base-uf-experiment-and-directions.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-flash-cce-pin-leak.md +0 -0
- /package/docs/{investigations → archive/investigations}/orpo-uf-testing-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/pi-builtin-investigation.md +0 -0
- /package/docs/{investigations → archive/investigations}/segmented-backward-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/starter-model-port-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/steel-flash-cce-handoff.md +0 -0
- /package/docs/{investigations → archive/investigations}/trainer-validation-experiment.md +0 -0
- /package/docs/{planning → archive/planning}/chat-ui-vision-memory-first.md +0 -0
- /package/docs/{planning → archive/planning}/journal.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.10.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.11.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.12.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.0.13.md +0 -0
- /package/docs/{planning → archive/planning}/web-ui-pass-plan.md +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// continuous
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
1
|
+
// GenerationGateway is the scheduling seam between CompletionExecutor and
|
|
2
|
+
// the inference runtime. It declares one mechanism per request: the preserved
|
|
3
|
+
// strict/dedicated serial executor, or the continuous scheduler (`--batch N`).
|
|
4
|
+
// The scheduler itself chooses its B=1 fast path or B=N step from its active
|
|
5
|
+
// row count; placement does not predict a batch size. The executor owns each
|
|
6
|
+
// request's semantic token sink and passes its callback plus one immutable
|
|
7
|
+
// placement into run(). Each request keeps its own callback, so the scheduler
|
|
8
|
+
// can fan tokens out to the matching response stream.
|
|
8
9
|
//
|
|
9
10
|
// The two lanes are MUTUALLY EXCLUSIVE on the GPU (and on shared model state
|
|
10
11
|
// like loraState, which generate() mutates per-generation assuming a serialized
|
|
@@ -34,16 +35,23 @@
|
|
|
34
35
|
// back on finish (merged rows' KV is not extracted — their entries age out).
|
|
35
36
|
|
|
36
37
|
import { MlxArray } from "../mlx/array";
|
|
37
|
-
import * as ops from "../mlx/ops";
|
|
38
38
|
import type { RuntimeModel } from "../model/factory";
|
|
39
39
|
import { DiffusionGemmaModel } from "../model/diffusion-gemma";
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
KVCache,
|
|
42
|
+
RotatingKVCache,
|
|
43
|
+
isBatchableCache,
|
|
44
|
+
isPlainKvCache,
|
|
45
|
+
isRotatingPlainCache,
|
|
46
|
+
} from "../model/gemma4-base";
|
|
41
47
|
import { SSMCache } from "../model/qwen3-delta";
|
|
42
48
|
import { UniversalDenseModel } from "../model/universal/dense";
|
|
43
49
|
import type { GenerateOptions, GenerateStats, TokenLogprobs } from "../generate";
|
|
44
|
-
import type {
|
|
45
|
-
import {
|
|
50
|
+
import type { KvScheme } from "../kv-scheme";
|
|
51
|
+
import { makeStepSampler } from "../sampler";
|
|
46
52
|
import { BatchScheduler, type RowPromptCache } from "./batch-scheduler";
|
|
53
|
+
import { runtimeValue } from "../runtime-config";
|
|
54
|
+
import type { PromptResponseTrace } from "./prompt-response-trace";
|
|
47
55
|
|
|
48
56
|
/** Async mutex: acquire() resolves to a release fn; releases run FIFO. */
|
|
49
57
|
class AsyncMutex {
|
|
@@ -83,6 +91,9 @@ export type Vision = {
|
|
|
83
91
|
* per-layer-input id zeroing. Absent on the legacy vision-only shape,
|
|
84
92
|
* where zeroing falls back to imageMask. */
|
|
85
93
|
multimodalMask?: MlxArray;
|
|
94
|
+
/** Qwen3.5/3.8 vision: the request's mRoPE positions + decode delta,
|
|
95
|
+
* installed on the model for exactly this serial run (server-side). */
|
|
96
|
+
mrope?: import("../model/qwen3-mrope").MropeRequestState;
|
|
86
97
|
};
|
|
87
98
|
|
|
88
99
|
/** Per-token sink: returning `false` halts this generation (stop sequence).
|
|
@@ -100,17 +111,19 @@ export type SerialRun = (
|
|
|
100
111
|
options: GenerateOptions & { stopSequences?: string[] },
|
|
101
112
|
onToken: OnToken,
|
|
102
113
|
vision?: Vision,
|
|
114
|
+
trace?: PromptResponseTrace,
|
|
103
115
|
) => Promise<GenerateStats>;
|
|
104
116
|
|
|
105
117
|
/** What the batchable decision needs from a request (cheap to compute). */
|
|
106
118
|
export interface RequestShape {
|
|
107
|
-
hasVision: boolean;
|
|
108
|
-
hasAdapters: boolean;
|
|
109
|
-
hasRepetitionPenalty: boolean;
|
|
119
|
+
readonly hasVision: boolean;
|
|
120
|
+
readonly hasAdapters: boolean;
|
|
121
|
+
readonly hasRepetitionPenalty: boolean;
|
|
110
122
|
/** The user explicitly set `seed` (reproducibility) — not the random default. */
|
|
111
|
-
userSeed: boolean;
|
|
112
|
-
/** KV quantization is active
|
|
113
|
-
|
|
123
|
+
readonly userSeed: boolean;
|
|
124
|
+
/** KV quantization is active. Per-layer configs may use the continuous
|
|
125
|
+
* scheduler; uniform schemes use the serial mechanism. */
|
|
126
|
+
readonly kvQuant: boolean;
|
|
114
127
|
/** TurboQuant is active (docs/design/turboquant-kv.md). Solo-only in v1:
|
|
115
128
|
* TurboQuantKVCache is a novel Cache implementation (not a KVCache/
|
|
116
129
|
* RotatingKVCache subclass), so #modelCachesBatchable() already excludes
|
|
@@ -118,32 +131,42 @@ export interface RequestShape {
|
|
|
118
131
|
* BELT on top of that automatic BRACES, an explicit refusal at the
|
|
119
132
|
* request-shape level so a turbo request never reaches the scheduler even
|
|
120
133
|
* before any cache conversion has happened. Both layers exist on purpose. */
|
|
121
|
-
turboQuant: boolean;
|
|
134
|
+
readonly turboQuant: boolean;
|
|
122
135
|
/** Any of the mlx-lm sampler/processor extensions is active: min_p, XTC,
|
|
123
136
|
* logit_bias, presence/frequency penalty. INFORMATIONAL ONLY: since the
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
137
|
+
* continuous scheduler grew per-row samplers/logits processors, these all
|
|
138
|
+
* compose. Placement deliberately does not gate on this field (see the
|
|
139
|
+
* repetition-penalty note in #supportsContinuous). Kept so /stats and
|
|
140
|
+
* scheduling traces can show what
|
|
127
141
|
* a request carries. */
|
|
128
|
-
hasLogitsExtras: boolean;
|
|
142
|
+
readonly hasLogitsExtras: boolean;
|
|
129
143
|
/** A grammar controller compiled for this request (response_format /
|
|
130
144
|
* guided_*). Degrade-path requests (compile failed → prompt injection)
|
|
131
145
|
* have NO controller and stay batchable — the injection already happened
|
|
132
146
|
* at the prompt level. B0 routes grammar to serial; B1 makes it batchable
|
|
133
147
|
* (per-row matchers), with MLX_BUN_GRAMMAR_BATCH=0 forcing the B0 serial
|
|
134
148
|
* fallback as the A/B + kill switch. */
|
|
135
|
-
hasGrammar: boolean;
|
|
149
|
+
readonly hasGrammar: boolean;
|
|
136
150
|
/** The request asked for logprobs/top_logprobs capture. Batch-lane logprobs
|
|
137
151
|
* are deferred (the scheduler's per-row sampler doesn't capture or read
|
|
138
152
|
* back logprob arrays yet), so these route to the serial lane like the
|
|
139
153
|
* other mlx-lm request extensions above. */
|
|
140
|
-
wantsLogprobs: boolean;
|
|
154
|
+
readonly wantsLogprobs: boolean;
|
|
141
155
|
/** A draft model is configured (serve --draft-model). Server-level and
|
|
142
156
|
* upstream-parity: mlx_lm.server sets is_batchable = (draft is None), so
|
|
143
157
|
* every request routes serial while a draft is mounted — speculation is a
|
|
144
158
|
* B=1 latency optimization, batching a throughput one; they are different
|
|
145
|
-
*
|
|
146
|
-
hasDraft: boolean;
|
|
159
|
+
* mechanisms by design (grammar-spec-batching-integration.md). */
|
|
160
|
+
readonly hasDraft: boolean;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export type GenerationMechanism = "serial" | "continuous";
|
|
164
|
+
|
|
165
|
+
/** One immutable scheduling declaration for one exact request shape.
|
|
166
|
+
* `continuous` means scheduler admission, not that another row is present. */
|
|
167
|
+
export interface GenerationPlacement {
|
|
168
|
+
readonly shape: RequestShape;
|
|
169
|
+
readonly mechanism: GenerationMechanism;
|
|
147
170
|
}
|
|
148
171
|
|
|
149
172
|
export class GenerationGateway {
|
|
@@ -156,12 +179,8 @@ export class GenerationGateway {
|
|
|
156
179
|
#serialWaiters = 0;
|
|
157
180
|
/** Lazy, memoized: can this model's caches do the dynamic-B ops? */
|
|
158
181
|
#cacheBatchable: boolean | null = null;
|
|
159
|
-
/** Lazy, memoized: can the
|
|
160
|
-
* (Phase 3.1: kvConfig whose every configured layerIdx is a plain
|
|
161
|
-
* full-attention KVCache — uniform kvBits and rotating-layer configs
|
|
162
|
-
* stay serial. The scheme is a server-lifetime constant, so one memo.) */
|
|
182
|
+
/** Lazy, memoized: can the configured KV scheme convert every named cache? */
|
|
163
183
|
#kvSchemeBatchable: boolean | null = null;
|
|
164
|
-
|
|
165
184
|
constructor(
|
|
166
185
|
private readonly model: RuntimeModel,
|
|
167
186
|
batch: number,
|
|
@@ -170,9 +189,9 @@ export class GenerationGateway {
|
|
|
170
189
|
kvBudgetBytes?: number;
|
|
171
190
|
/** The server-wide KV scheme (server.ts kvScheme) — threaded to the
|
|
172
191
|
* scheduler at construction when batchable (Phase 3.1). turboQuant is
|
|
173
|
-
* never threaded to the scheduler (always solo-only — see
|
|
192
|
+
* never threaded to the scheduler (always solo-only — see placement);
|
|
174
193
|
* it's here only so the gateway can see it's active. */
|
|
175
|
-
kvScheme?:
|
|
194
|
+
kvScheme?: KvScheme;
|
|
176
195
|
/** The server's prompt cache (Phase 3.2): batch-lane joiners take()
|
|
177
196
|
* the longest usable prefix at admission; never-merged rows put()
|
|
178
197
|
* back on finish. Safe to share with the serial lane — both use it
|
|
@@ -260,7 +279,7 @@ export class GenerationGateway {
|
|
|
260
279
|
!a.maskArray && !a.layerTypes?.includes("sliding_attention");
|
|
261
280
|
return this.#cacheBatchable;
|
|
262
281
|
}
|
|
263
|
-
const ssmOk =
|
|
282
|
+
const ssmOk = runtimeValue("MLX_BUN_BATCH_SSM") !== "0";
|
|
264
283
|
const proto = this.model.makeCache(); // fresh caches hold no buffers
|
|
265
284
|
this.#cacheBatchable = proto.every(
|
|
266
285
|
(c) =>
|
|
@@ -284,37 +303,36 @@ export class GenerationGateway {
|
|
|
284
303
|
#kvBatchable(): boolean {
|
|
285
304
|
if (this.#kvSchemeBatchable === null) {
|
|
286
305
|
const scheme = this.opts.kvScheme;
|
|
287
|
-
if (
|
|
288
|
-
// Only consulted when shape.kvQuant is SET: a kv-quant request with
|
|
289
|
-
// no scheme threaded to the gateway cannot be applied by the
|
|
290
|
-
// scheduler — batching it would SILENTLY DROP the quantization (the
|
|
291
|
-
// exact composition bug optiq serve ships). Route serial.
|
|
306
|
+
if (scheme?.kind !== "affine-config") {
|
|
292
307
|
this.#kvSchemeBatchable = false;
|
|
293
|
-
} else if (scheme.kvBits || !scheme.kvConfig?.length) {
|
|
294
|
-
this.#kvSchemeBatchable = false; // uniform bits: serial
|
|
295
308
|
} else {
|
|
296
309
|
const proto = this.model.makeCache();
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
310
|
+
try {
|
|
311
|
+
this.#kvSchemeBatchable = scheme.batchable(
|
|
312
|
+
this.model.config,
|
|
313
|
+
(layerIdx) =>
|
|
314
|
+
isPlainKvCache(proto[layerIdx]) || isRotatingPlainCache(proto[layerIdx]),
|
|
315
|
+
);
|
|
316
|
+
} finally {
|
|
317
|
+
for (const cache of proto) cache.dispose();
|
|
318
|
+
}
|
|
302
319
|
}
|
|
303
320
|
}
|
|
304
321
|
return this.#kvSchemeBatchable;
|
|
305
322
|
}
|
|
306
323
|
|
|
307
|
-
/**
|
|
308
|
-
|
|
324
|
+
/** Whether the declared execution composition is implemented by the
|
|
325
|
+
* continuous scheduler. This is a support check only: it never rewrites
|
|
326
|
+
* MTP/drafting, KV, TurboQuant, grammar, adapters, or sampling. */
|
|
327
|
+
#supportsContinuous(shape: RequestShape): boolean {
|
|
309
328
|
// DiffusionGemma is non-autoregressive — the batch scheduler assumes the AR
|
|
310
329
|
// KV-cache decode path, so it always runs serially through generate().
|
|
311
330
|
if (this.model instanceof DiffusionGemmaModel) return false;
|
|
312
331
|
if (!this.batchingEnabled) return false;
|
|
313
332
|
if (!this.#modelCachesBatchable()) return false;
|
|
314
333
|
// repetitionPenalty / logits extras (min_p, XTC, logit_bias,
|
|
315
|
-
// presence/frequency penalties) batch
|
|
316
|
-
//
|
|
317
|
-
// over a per-row device-side token history. (Load-bearing beyond opt-in
|
|
334
|
+
// presence/frequency penalties) batch through the same StepSampler and
|
|
335
|
+
// per-row device history as the serial lane. (Load-bearing beyond opt-in
|
|
318
336
|
// knobs: some models — Qwen3.5 — ship a default repetition penalty in
|
|
319
337
|
// generation_config.json, which used to route EVERY request serial.)
|
|
320
338
|
return (
|
|
@@ -336,20 +354,35 @@ export class GenerationGateway {
|
|
|
336
354
|
// Grammar: B1 makes it batchable (per-row matchers) unless the kill
|
|
337
355
|
// switch forces serial. MLX_BUN_GRAMMAR_BATCH=0 = B0 behavior (serial),
|
|
338
356
|
// the A/B + kill lever for the new code, house style.
|
|
339
|
-
!(shape.hasGrammar &&
|
|
357
|
+
!(shape.hasGrammar && runtimeValue("MLX_BUN_GRAMMAR_BATCH") === "0")
|
|
340
358
|
);
|
|
341
359
|
}
|
|
342
360
|
|
|
361
|
+
place(shape: RequestShape): GenerationPlacement {
|
|
362
|
+
const frozenShape = Object.freeze(shape);
|
|
363
|
+
return Object.freeze({
|
|
364
|
+
shape: frozenShape,
|
|
365
|
+
mechanism: this.#supportsContinuous(frozenShape) ? "continuous" : "serial",
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
343
369
|
/** Run `fn` with exclusive ownership of the GPU + shared model state (the
|
|
344
370
|
* serial lane's lock). THE single mutual-exclusion domain: the serial
|
|
345
371
|
* generation path, the curve /generate + /signal endpoints, and adapter
|
|
346
372
|
* mount/unmount all come through here, so nothing runs concurrently with
|
|
347
373
|
* batched decode steps (or with each other). Registers as a serial waiter
|
|
348
374
|
* so the scheduler drains (stops admitting) until `fn` has run. */
|
|
349
|
-
async runExclusive<T>(
|
|
375
|
+
async runExclusive<T>(
|
|
376
|
+
fn: () => Promise<T>,
|
|
377
|
+
trace?: PromptResponseTrace,
|
|
378
|
+
): Promise<T> {
|
|
350
379
|
this.#serialWaiters++;
|
|
380
|
+
const closeAdmission = trace?.begin("engine.admission_wait", {
|
|
381
|
+
mechanism: "serial",
|
|
382
|
+
});
|
|
351
383
|
try {
|
|
352
384
|
const release = await this.#mutex.acquire();
|
|
385
|
+
closeAdmission?.();
|
|
353
386
|
try {
|
|
354
387
|
return await fn();
|
|
355
388
|
} finally {
|
|
@@ -369,8 +402,14 @@ export class GenerationGateway {
|
|
|
369
402
|
onToken: OnToken,
|
|
370
403
|
vision: Vision | undefined,
|
|
371
404
|
shape: RequestShape,
|
|
405
|
+
placement: GenerationPlacement,
|
|
372
406
|
signal?: AbortSignal,
|
|
407
|
+
trace?: PromptResponseTrace,
|
|
373
408
|
): Promise<GenerateStats> {
|
|
409
|
+
if (placement.shape !== shape) {
|
|
410
|
+
options.grammar?.dispose();
|
|
411
|
+
throw new Error("generation placement does not belong to this request shape");
|
|
412
|
+
}
|
|
374
413
|
try {
|
|
375
414
|
signal?.throwIfAborted();
|
|
376
415
|
} catch (e) {
|
|
@@ -379,7 +418,7 @@ export class GenerationGateway {
|
|
|
379
418
|
options.grammar?.dispose();
|
|
380
419
|
throw e;
|
|
381
420
|
}
|
|
382
|
-
if (
|
|
421
|
+
if (placement.mechanism === "serial") {
|
|
383
422
|
// Serial decode is an unbroken microtask chain (FFI + generator
|
|
384
423
|
// resumes): without a periodic macrotask hop the event loop starves for
|
|
385
424
|
// the WHOLE generation — /stats, /health, and new-connection accepts
|
|
@@ -404,75 +443,43 @@ export class GenerationGateway {
|
|
|
404
443
|
};
|
|
405
444
|
return this.runExclusive(async () => {
|
|
406
445
|
signal?.throwIfAborted();
|
|
407
|
-
const stats = await this.serialRun(
|
|
446
|
+
const stats = await this.serialRun(
|
|
447
|
+
promptIds, options, hoppingOnToken, vision, trace,
|
|
448
|
+
);
|
|
408
449
|
signal?.throwIfAborted();
|
|
409
450
|
return stats;
|
|
410
|
-
})
|
|
451
|
+
}, trace)
|
|
411
452
|
// Covers a serial waiter aborted before serialRun takes ownership.
|
|
412
453
|
// generate() also disposes defensively; the operation is idempotent.
|
|
413
454
|
.finally(() => options.grammar?.dispose());
|
|
414
455
|
}
|
|
415
456
|
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
// inside the closure keeps history exact.
|
|
427
|
-
let history: MlxArray | null = null;
|
|
428
|
-
if (processors.length > 0) history = ops.fromInt32(promptIds, [promptIds.length]);
|
|
429
|
-
const sample = (logits1V: MlxArray, step: number): MlxArray => {
|
|
430
|
-
let cur = logits1V; // caller-owned; only intermediates are disposed here
|
|
431
|
-
for (const p of processors) {
|
|
432
|
-
const next = p(history, cur);
|
|
433
|
-
if (cur !== logits1V) cur.dispose();
|
|
434
|
-
cur = next;
|
|
435
|
-
}
|
|
436
|
-
// Grammar mask (B1): after the standard processors, before the sampler —
|
|
437
|
-
// same "grammar has the final say" ordering as serial sampleStep.
|
|
438
|
-
// Precondition: the scheduler has awaited ready() before invoking this
|
|
439
|
-
// closure (the mask for this step is materialized). applyMask is sync.
|
|
440
|
-
if (options.grammar) {
|
|
441
|
-
const masked = options.grammar.applyMask(cur);
|
|
442
|
-
if (cur !== logits1V) cur.dispose();
|
|
443
|
-
cur = masked;
|
|
444
|
-
}
|
|
445
|
-
const lp = toLogprobs(cur);
|
|
446
|
-
if (cur !== logits1V) cur.dispose();
|
|
447
|
-
const tok = sampler(lp, step);
|
|
448
|
-
lp.dispose();
|
|
449
|
-
if (history) {
|
|
450
|
-
const t1 = ops.reshape(tok, [1]);
|
|
451
|
-
const prev = history;
|
|
452
|
-
history = ops.concatAxis([prev, t1], 0);
|
|
453
|
-
prev.dispose();
|
|
454
|
-
t1.dispose();
|
|
455
|
-
}
|
|
456
|
-
return tok;
|
|
457
|
-
};
|
|
457
|
+
// The scheduler owns grammar ready/accept sequencing. StepSampler owns the
|
|
458
|
+
// shared processors -> mask -> logprobs -> sample -> history contract.
|
|
459
|
+
const stepSampler = makeStepSampler(options, {
|
|
460
|
+
tokenRepresentation: "device",
|
|
461
|
+
grammarWait: "external",
|
|
462
|
+
historyUpdate: "after-sample",
|
|
463
|
+
initialHistory: promptIds,
|
|
464
|
+
});
|
|
465
|
+
const sample = (logits1V: MlxArray, step: number): MlxArray =>
|
|
466
|
+
stepSampler.sample(logits1V, step).token;
|
|
458
467
|
|
|
459
468
|
let st;
|
|
460
469
|
this.#rowsSubmitted++;
|
|
470
|
+
const closeAdmission = trace?.begin("engine.admission_wait", {
|
|
471
|
+
mechanism: "continuous",
|
|
472
|
+
});
|
|
461
473
|
try {
|
|
462
474
|
st = await this.#ensureScheduler().submit({
|
|
463
475
|
promptIds,
|
|
464
476
|
maxTokens: options.maxTokens ?? 512,
|
|
465
477
|
eosTokenIds: options.eosTokenIds ?? this.model.config.eosTokenIds,
|
|
466
478
|
sample,
|
|
467
|
-
|
|
468
|
-
// (temperature 0, no curve override) with nothing per-row in the way
|
|
469
|
-
// (no processors, no grammar mask).
|
|
470
|
-
plainGreedy:
|
|
471
|
-
(options.temperature ?? 0) === 0 &&
|
|
472
|
-
!options.curve &&
|
|
473
|
-
processors.length === 0 &&
|
|
474
|
-
!options.grammar,
|
|
479
|
+
plainGreedy: stepSampler.isPlainGreedy,
|
|
475
480
|
onToken,
|
|
481
|
+
onAdmitted: closeAdmission,
|
|
482
|
+
trace,
|
|
476
483
|
...(signal ? { signal } : {}),
|
|
477
484
|
// B1: pass the per-row grammar controller through. The scheduler drives
|
|
478
485
|
// accept/ready/terminate; this gateway OWNS disposal (finally below)
|
|
@@ -483,8 +490,8 @@ export class GenerationGateway {
|
|
|
483
490
|
...(options.snapshotAt !== undefined ? { snapshotAt: options.snapshotAt } : {}),
|
|
484
491
|
});
|
|
485
492
|
} finally {
|
|
486
|
-
|
|
487
|
-
|
|
493
|
+
closeAdmission?.();
|
|
494
|
+
stepSampler.dispose();
|
|
488
495
|
// The scheduler never owns per-row grammar state.
|
|
489
496
|
options.grammar?.dispose();
|
|
490
497
|
}
|
|
@@ -507,7 +514,7 @@ export class GenerationGateway {
|
|
|
507
514
|
// scheduler (solo rows convert at serial chunk boundaries, then
|
|
508
515
|
// merge as quantized triples). Only threaded when the scheme
|
|
509
516
|
// passed #kvBatchable — otherwise those requests never reach here.
|
|
510
|
-
|
|
517
|
+
kvScheme: this.#kvBatchable() ? this.opts.kvScheme : undefined,
|
|
511
518
|
promptCache: this.opts.promptCache,
|
|
512
519
|
lock: { acquire: () => this.#mutex.acquire() },
|
|
513
520
|
// Drain: pause admission while any serial-lane request waits; the
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ModelConfig } from "../config";
|
|
2
|
+
import { kvBytesAt, KvScheme } from "../kv-scheme";
|
|
3
|
+
|
|
4
|
+
/** Project one batch row's worst-case KV bytes using the same per-layer
|
|
5
|
+
* scheme that the server-wide admission ceiling uses. */
|
|
6
|
+
export function batchRowKvBytes(
|
|
7
|
+
config: ModelConfig,
|
|
8
|
+
promptTokens: number,
|
|
9
|
+
maxTokens: number,
|
|
10
|
+
scheme?: KvScheme,
|
|
11
|
+
): number {
|
|
12
|
+
const tokens = promptTokens + maxTokens;
|
|
13
|
+
return scheme ? scheme.bytesAt(config, tokens) : kvBytesAt(config, tokens);
|
|
14
|
+
}
|