mlx-bun 0.3.0 → 0.4.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/docs/README.md +4 -1
- package/docs/archive/investigations/openjarvis-fit.md +198 -0
- package/docs/archive/investigations/qwen38-27b-campaign-log.md +1092 -0
- package/docs/design/batching.md +1321 -49
- package/docs/design/decode-speed-program.md +331 -1163
- package/docs/design/generic-model-support.md +79 -3
- package/docs/design/kv-cache.md +198 -54
- package/docs/design/speculative-decoding.md +387 -24
- package/docs/design/turboquant.md +123 -13
- package/docs/design/unified-engine-frontier-plan.md +144 -142
- package/docs/planning/PRODUCT_ROADMAP.md +16 -0
- package/docs/planning/release-notes-v0.4.0.md +45 -0
- package/docs/reference/benchmarks.md +2457 -6
- package/docs/reference/cli.md +41 -2
- package/docs/reference/distribution.md +16 -9
- package/docs/reference/environment.md +55 -5
- package/docs/reference/models.md +13 -4
- package/docs/reference/server-api.md +79 -25
- package/docs/reference/server-config.md +178 -84
- package/docs/reference/troubleshooting.md +13 -21
- package/package.json +1 -1
- package/src/backends/mlx/assistant-target.ts +46 -0
- package/src/backends/mlx/autoregressive-draft-rows.ts +120 -0
- package/src/backends/mlx/batch-group.ts +368 -224
- 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 +106 -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/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 +77 -18
- package/src/backends/mlx/kv-codec.ts +9 -0
- package/src/backends/mlx/kv-maintenance.ts +133 -0
- package/src/backends/mlx/kv-tensor-rows.ts +212 -0
- package/src/backends/mlx/model-serving.ts +1 -1
- package/src/backends/mlx/prefill-cohort.ts +89 -0
- package/src/backends/mlx/prefill-rows.ts +200 -0
- package/src/backends/mlx/prefill.ts +1 -1
- package/src/backends/mlx/projected-context-rows.ts +72 -0
- package/src/backends/mlx/request-state-policy.ts +41 -0
- package/src/backends/mlx/rollback.ts +41 -3
- package/src/backends/mlx/row-sampling.ts +49 -0
- package/src/backends/mlx/serial-executor.ts +35 -66
- package/src/backends/mlx/speculative-group.ts +359 -0
- package/src/backends/mlx/speculative-round.ts +161 -0
- 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 +165 -0
- package/src/cli.ts +11 -36
- package/src/contracts/execution.ts +11 -0
- package/src/contracts/prefix-cache.ts +23 -0
- package/src/contracts/resources.ts +7 -0
- package/src/contracts/sampling.ts +23 -0
- package/src/contracts/scheduling.ts +18 -0
- package/src/engine/execution-plan.ts +18 -16
- package/src/engine/resources.ts +12 -1
- package/src/engine/scheduler.ts +10 -5
- package/src/fit.ts +29 -15
- package/src/generate.ts +77 -173
- package/src/inference/draft-acceptance.ts +41 -0
- package/src/inference/draft-output.ts +49 -0
- package/src/inference/rollback.ts +2 -2
- package/src/kv-scheme.ts +70 -12
- package/src/kv-store.ts +70 -16
- package/src/lab/paged-kv/paged-kv-rows.ts +83 -0
- package/src/lab/paged-kv/paged-kv.ts +25 -11
- package/src/mlx/array.ts +2 -1
- 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 +93 -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 +55 -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 +162 -80
- package/src/model/gemma4.ts +34 -26
- 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 +67 -0
- package/src/model/minicpm5.ts +11 -7
- package/src/model/qwen3-delta-state.ts +74 -0
- package/src/model/qwen3-delta.ts +59 -36
- package/src/model/qwen3_5.ts +82 -42
- 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/trellis-linear.ts +2 -2
- package/src/model/turboquant-codec.ts +83 -0
- package/src/model/universal/dense.ts +14 -13
- package/src/preflight.ts +31 -15
- package/src/prompt-cache.ts +42 -44
- package/src/sampler.ts +36 -25
- package/src/serve/chat-stage.ts +2 -2
- package/src/serve/generation-gateway.ts +39 -30
- package/src/serve/model-host.ts +3 -4
- package/src/serve/request-plan.ts +4 -3
- package/src/serve/request-prep.ts +2 -4
- package/src/serve/text-completion-stage.ts +2 -2
- package/src/server.ts +64 -108
- package/src/spec/assistant-rows.ts +92 -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 +53 -136
- package/src/spec/glm52-mtp-state.ts +33 -0
- package/src/spec/ngram-source.ts +92 -20
- 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 +219 -0
- package/src/spec/qwen-mtp-source.ts +81 -226
- package/src/spec/qwen-mtp-state.ts +30 -0
- package/src/spec/serve-loop.ts +70 -68
- package/src/spec/source.ts +80 -15
- package/src/spec/two-model-rows.ts +61 -0
- package/src/spec/two-model.ts +43 -101
- package/src/ssd-cache.ts +33 -21
- package/src/ssd-durability.ts +11 -7
- package/src/web/app.js +57 -57
- package/src/backends/mlx/memory-guard.ts +0 -40
- package/src/spec/prefix-state.ts +0 -45
- /package/docs/{planning → archive/planning}/release-notes-v0.3.0.md +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { MlxArray } from "../mlx/array";
|
|
2
|
+
import * as ops from "../mlx/ops";
|
|
3
|
+
import { RotatingKVCache, type Cache, type KvAttentionState, type KvAttentionView, type PaddedPrefillCache, type PrefillPadding } from "./gemma4-base";
|
|
4
|
+
import { SpeculativeTransitioningKvRows } from "../backends/mlx/transitioning-kv-rows";
|
|
5
|
+
import { AlignedRotatingCache, alignRotatingRows, RotatingAffineLayout, SpeculativeRotatingAffineLayout, RotatingKvPositions } from "./rotating-kv-layout";
|
|
6
|
+
import { BatchedRotatingQuantCache } from "./batched-rotating-quant";
|
|
7
|
+
import { captureKvAttention, combineKvDonorAttention } from "./kv-attention-view";
|
|
8
|
+
|
|
9
|
+
/** Precision changes preserve each row's physical columns. The model owns
|
|
10
|
+
* queries and scale; the shared lifecycle owns membership and conversion. */
|
|
11
|
+
export class DelayedRotatingQuantizedKVCache extends SpeculativeTransitioningKvRows<RotatingAffineLayout> implements KvAttentionState, PaddedPrefillCache {
|
|
12
|
+
constructor(readonly maxSize: number, readonly groupSize: number, readonly bits: number, readonly start: number,
|
|
13
|
+
readonly maintain: (rows: Cache[]) => void, row?: Cache, readonly speculative = false) {
|
|
14
|
+
super({ signature: `kv:delayed-rotating-quant:${maxSize}:${bits}:${groupSize}:${start}`, maintain,
|
|
15
|
+
converted: row => row instanceof AlignedRotatingCache && row.inner instanceof BatchedRotatingQuantCache,
|
|
16
|
+
makeLayout: () => speculative ? new SpeculativeRotatingAffineLayout(maxSize, groupSize, bits) : new RotatingAffineLayout(maxSize, groupSize, bits), prepareRows: alignRotatingRows,
|
|
17
|
+
packRows: (layout, rows) => layout.adoptAlignedRows(rows as readonly AlignedRotatingCache[]),
|
|
18
|
+
extractRow: row => (row as AlignedRotatingCache).extract(speculative ? maxSize : undefined),
|
|
19
|
+
rollbackRow: (row, before, keep, preserve) => (row as AlignedRotatingCache).rollback(before, keep, preserve) }, row, new RotatingKvPositions(maxSize));
|
|
20
|
+
}
|
|
21
|
+
get attentionState(): KvAttentionState { return this; }
|
|
22
|
+
captureDonorAttention(): import("./gemma4-base").KvDonorAttention {
|
|
23
|
+
if (this.packed) return this.packed.captureDonorAttention();
|
|
24
|
+
const views: import("./gemma4-base").KvDonorAttention[] = [];
|
|
25
|
+
try {
|
|
26
|
+
for (const row of this.rows) views.push((row as AlignedRotatingCache).captureDonorAttention());
|
|
27
|
+
return combineKvDonorAttention(views);
|
|
28
|
+
} catch (error) { for (const view of views) view.dispose(); throw error; }
|
|
29
|
+
}
|
|
30
|
+
override makeMask(tokens: number, window: number | null) {
|
|
31
|
+
if (this.speculative && this.leftPad.every(pad => pad === 0) &&
|
|
32
|
+
this.offset + tokens <= Math.min(this.maxSize, window ?? this.maxSize))
|
|
33
|
+
return { mode: tokens === 1 ? "" as const : "causal" as const, arr: null };
|
|
34
|
+
return super.makeMask(tokens, window);
|
|
35
|
+
}
|
|
36
|
+
preparePrefill(padding: PrefillPadding): void {
|
|
37
|
+
if (!this.batchSize) {
|
|
38
|
+
const rows = padding.lengths.map(() => new RotatingKVCache(this.maxSize));
|
|
39
|
+
try { this.mergeRows(rows); } finally { for (const row of rows) row.dispose(); }
|
|
40
|
+
}
|
|
41
|
+
if (this.packed) { this.packed.preparePrefill(padding); return; }
|
|
42
|
+
// Every physical row follows the same block geometry, even when only a
|
|
43
|
+
// sibling has right padding. The token lengths still belong to each row.
|
|
44
|
+
const rightPaddedBatch = padding.rightPadding?.some(pad => pad > 0);
|
|
45
|
+
for (const [row, cache] of this.rows.entries()) (cache as AlignedRotatingCache).inner.preparePrefill({
|
|
46
|
+
lengths: [padding.lengths[row]!],
|
|
47
|
+
...(padding.leftPadding ? { leftPadding: [padding.leftPadding[row]!] } : {}),
|
|
48
|
+
...(padding.rightPadding ? { rightPadding: [padding.rightPadding[row]!] } : {}),
|
|
49
|
+
}, rightPaddedBatch);
|
|
50
|
+
this.syncPositions(true);
|
|
51
|
+
}
|
|
52
|
+
finalizePrefill(): void {
|
|
53
|
+
if (this.packed) { this.packed.finalizePrefill(); return; }
|
|
54
|
+
for (const cache of this.rows) (cache as AlignedRotatingCache).inner.finalizePrefill();
|
|
55
|
+
this.syncPositions(true);
|
|
56
|
+
}
|
|
57
|
+
get maxTokens(): number { return this.maxSize; }
|
|
58
|
+
projectedBytes(tokens: number): number { return this.bytesPerToken() * Math.min(tokens, this.maxSize); }
|
|
59
|
+
makeEmptyBatch(): DelayedRotatingQuantizedKVCache {
|
|
60
|
+
return new DelayedRotatingQuantizedKVCache(this.maxSize, this.groupSize, this.bits, this.start, this.maintain, undefined, this.speculative);
|
|
61
|
+
}
|
|
62
|
+
updateAndFetch(): [MlxArray, MlxArray] { throw new Error("mixed precision rows use their attention state"); }
|
|
63
|
+
releaseRopeArr(): void { this.packed?.releaseRopeArr(); }
|
|
64
|
+
appendAndFetch(k: MlxArray, v: MlxArray): KvAttentionView {
|
|
65
|
+
this.advance();
|
|
66
|
+
if (this.packed) return captureKvAttention(this.packed, k, v);
|
|
67
|
+
const views: KvAttentionView[] = [];
|
|
68
|
+
try {
|
|
69
|
+
for (const [row, cache] of this.rows.entries()) {
|
|
70
|
+
const slice = (a: MlxArray) => a.slice([row, 0, 0, 0], [row + 1, a.shape[1]!, a.shape[2]!, a.shape[3]!]);
|
|
71
|
+
using kr = slice(k), vr = slice(v);
|
|
72
|
+
views.push(captureKvAttention(cache, kr, vr));
|
|
73
|
+
}
|
|
74
|
+
this.syncPositions(true);
|
|
75
|
+
} catch (error) { for (const view of views) view.dispose(); throw error; }
|
|
76
|
+
return {
|
|
77
|
+
attend(q, scale, mask) {
|
|
78
|
+
const outputs: MlxArray[] = [];
|
|
79
|
+
try {
|
|
80
|
+
for (const [row, view] of views.entries()) {
|
|
81
|
+
using qr = q.slice([row, 0, 0, 0], [row + 1, q.shape[1]!, q.shape[2]!, q.shape[3]!]);
|
|
82
|
+
const start = mask.arr?.shape.map(() => 0), end = mask.arr ? [...mask.arr.shape] : undefined;
|
|
83
|
+
if (start && end && end.length === 4 && end[0]! > 1) { start[0] = row; end[0] = row + 1; }
|
|
84
|
+
using arr = mask.arr ? mask.arr.slice(start!, end!) : null;
|
|
85
|
+
outputs.push(view.attend(qr, scale, { mode: mask.mode, arr }));
|
|
86
|
+
}
|
|
87
|
+
return outputs.length === 1 ? ops.contiguous(outputs[0]!) : ops.concatAxis(outputs, 0);
|
|
88
|
+
} finally { for (const output of outputs) output.dispose(); }
|
|
89
|
+
},
|
|
90
|
+
dispose() { for (const view of views) view.dispose(); },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { captureFullKvDonorRows } from "../backends/mlx/full-kv-row-donor";
|
|
2
|
+
import { decodedKvDonorAttention } from "./decoded-kv-donor";
|
|
3
|
+
import { appendFullKvRows } from "../backends/mlx/full-kv-row-append";
|
|
4
|
+
import type { MlxArray } from "../mlx/array";
|
|
5
|
+
import { Dtype } from "../mlx/ffi";
|
|
6
|
+
import * as ops from "../mlx/ops";
|
|
7
|
+
import { unrotateValues } from "../mlx/turboquant-ops";
|
|
8
|
+
import { type Cache, type RotatedValueAttentionState, TurboQuantKVCache } from "./gemma4-base";
|
|
9
|
+
import { BatchedTurboQuantKVCache } from "./batched-turboquant-kv";
|
|
10
|
+
import { FullTransitioningKvRows } from "../backends/mlx/full-transitioning-kv-rows";
|
|
11
|
+
|
|
12
|
+
/** TurboQuant tensor access and value-domain handling over the shared
|
|
13
|
+
* row-transition lifecycle. Model projections and attention remain batched. */
|
|
14
|
+
export class DelayedTurboQuantKVCache extends FullTransitioningKvRows<BatchedTurboQuantKVCache> implements RotatedValueAttentionState {
|
|
15
|
+
#rotated: boolean[] = [];
|
|
16
|
+
constructor(readonly kBits: number, readonly vBits: number, readonly start: number,
|
|
17
|
+
readonly maintain: (rows: Cache[]) => void, row?: Cache) {
|
|
18
|
+
super({ signature: `kv:delayed-turboquant:${kBits}:${vBits}:${start}`, maintain,
|
|
19
|
+
converted: row => row instanceof TurboQuantKVCache,
|
|
20
|
+
makeLayout: () => new BatchedTurboQuantKVCache(kBits, vBits) }, row);
|
|
21
|
+
}
|
|
22
|
+
captureDonorRows(): import("./gemma4-base").KvDonorRows {
|
|
23
|
+
return this.packed?.captureDonorRows() ?? captureFullKvDonorRows(this.rows, this.leftPad, this.offset);
|
|
24
|
+
}
|
|
25
|
+
captureDonorAttention(): import("./gemma4-base").KvDonorAttention {
|
|
26
|
+
return decodedKvDonorAttention(this.captureDonorRows());
|
|
27
|
+
}
|
|
28
|
+
get rotatedValueAttention(): RotatedValueAttentionState { return this; }
|
|
29
|
+
#append(k: MlxArray, v: MlxArray, deferred: boolean): [MlxArray, MlxArray] {
|
|
30
|
+
this.advance();
|
|
31
|
+
if (this.packed) {
|
|
32
|
+
this.#rotated = Array.from({ length: this.batchSize! }, () => true);
|
|
33
|
+
return deferred ? this.packed.updateAndFetchDeferredV(k, v) : this.packed.updateAndFetch(k, v);
|
|
34
|
+
}
|
|
35
|
+
this.#rotated = this.rows.map(row => !!row.rotatedValueAttention);
|
|
36
|
+
const result = appendFullKvRows(this.rows, this.leftPad, this.offset + k.shape[2]!, k, v, deferred);
|
|
37
|
+
try { this.syncPositions(true); } catch (error) { result[0].dispose(); result[1].dispose(); throw error; }
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.#append(k, v, false); }
|
|
42
|
+
updateAndFetchDeferredV(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.#append(k, v, true); }
|
|
43
|
+
captureValueTransform(): (output: MlxArray) => MlxArray {
|
|
44
|
+
const rotated = [...this.#rotated];
|
|
45
|
+
if (rotated.every(value => value)) return unrotateValues;
|
|
46
|
+
if (rotated.every(value => !value)) return output => ops.contiguous(output);
|
|
47
|
+
return output => {
|
|
48
|
+
using restored = unrotateValues(output);
|
|
49
|
+
using flags = ops.fromInt32(rotated.map(value => Number(value)), [rotated.length, 1, 1, 1]);
|
|
50
|
+
using mask = flags.astype(Dtype.bool);
|
|
51
|
+
return ops.where(mask, restored, output);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
makeEmptyBatch(): DelayedTurboQuantKVCache { return new DelayedTurboQuantKVCache(this.kBits, this.vBits, this.start, this.maintain); }
|
|
55
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { MlxArray } from "../mlx/array";
|
|
2
|
+
import * as ops from "../mlx/ops";
|
|
3
|
+
import type { PrefillPadding } from "./gemma4-base";
|
|
4
|
+
import { rowRollIndices } from "./batched-row-storage";
|
|
5
|
+
import { disposeResources } from "../engine/resources";
|
|
6
|
+
|
|
7
|
+
interface Positions { rowOffsets: number[]; leftPad: number[] }
|
|
8
|
+
|
|
9
|
+
/** mlx-lm BatchKVCache.prepare/finalize (MIT), over arbitrary stored planes.
|
|
10
|
+
* Padding changes token positions; the representation's codec is uninvolved. */
|
|
11
|
+
export class FullPrefillPadding {
|
|
12
|
+
constructor(readonly tokenAxis = 2) {}
|
|
13
|
+
#ends?: number[];
|
|
14
|
+
get endOffsets(): readonly number[] | undefined { return this.#ends; }
|
|
15
|
+
restoreEnds(ends: readonly number[] | undefined): void { this.#ends = ends ? [...ends] : undefined; }
|
|
16
|
+
validOffset(offset: number, row: number): number { return Math.min(offset, this.#ends?.[row] ?? offset); }
|
|
17
|
+
|
|
18
|
+
prepare(positions: Positions, padding: PrefillPadding): void {
|
|
19
|
+
if (!positions.rowOffsets.length) {
|
|
20
|
+
positions.rowOffsets = padding.lengths.map(() => 0);
|
|
21
|
+
positions.leftPad = [...positions.rowOffsets];
|
|
22
|
+
}
|
|
23
|
+
if (padding.leftPadding) {
|
|
24
|
+
positions.leftPad = positions.leftPad.map((pad, row) => pad + padding.leftPadding![row]!);
|
|
25
|
+
positions.rowOffsets = positions.rowOffsets.map((offset, row) => offset - padding.leftPadding![row]!);
|
|
26
|
+
}
|
|
27
|
+
// Like rotating storage, retain absolute valid ends. A cohort can finish
|
|
28
|
+
// before its planned width when a longer request leaves during prefill.
|
|
29
|
+
this.#ends = padding.rightPadding?.some(pad => pad > 0)
|
|
30
|
+
? padding.lengths.map((length, row) => positions.rowOffsets[row]! + length) : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Borrow planes; return owned replacements only when padding needs moving.
|
|
34
|
+
* Stage every plane before committing positions, including on FFI failure. */
|
|
35
|
+
finalize(planes: readonly MlxArray[], positions: Positions): MlxArray[] | undefined {
|
|
36
|
+
if (!this.#ends) return;
|
|
37
|
+
const padding = this.#ends.map((end, row) => Math.max(0, positions.rowOffsets[row]! - end));
|
|
38
|
+
if (!padding.some(pad => pad > 0)) { this.#ends = undefined; return; }
|
|
39
|
+
const next: MlxArray[] = [];
|
|
40
|
+
try {
|
|
41
|
+
if (planes.length) {
|
|
42
|
+
const width = planes[0]!.shape[this.tokenAxis]!;
|
|
43
|
+
using indices = rowRollIndices(width, padding);
|
|
44
|
+
using compressed = this.tokenAxis === 1 ? ops.reshape(indices, [padding.length, width, 1]) : null;
|
|
45
|
+
for (const plane of planes) next.push(ops.takeAlongAxis(plane, compressed ?? indices, this.tokenAxis));
|
|
46
|
+
}
|
|
47
|
+
} catch (error) { disposeResources(next); throw error; }
|
|
48
|
+
positions.rowOffsets = positions.rowOffsets.map((offset, row) => offset - padding[row]!);
|
|
49
|
+
positions.leftPad = positions.leftPad.map((pad, row) => pad + padding[row]!);
|
|
50
|
+
this.#ends = undefined;
|
|
51
|
+
return next;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
filter(keep: readonly number[]): void { if (this.#ends) this.#ends = keep.map(row => this.#ends![row]!); }
|
|
55
|
+
clear(): void { this.#ends = undefined; }
|
|
56
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { MlxArray } from "../mlx/array";
|
|
2
|
+
import { materializeCopy } from "../mlx/materialize";
|
|
3
|
+
import { withResource, disposeResources } from "../engine/resources";
|
|
4
|
+
import { leaseCacheState } from "../backends/mlx/state-views";
|
|
5
|
+
import { FullPrefillPadding } from "./full-prefill-padding";
|
|
6
|
+
import { KVCache, QuantizedKVCache, TurboQuantKVCache, type Cache, type Mask, type PrefillPadding } from "./gemma4-base";
|
|
7
|
+
|
|
8
|
+
type FullCache = KVCache | QuantizedKVCache | TurboQuantKVCache;
|
|
9
|
+
|
|
10
|
+
/** A physical full-attention row with separate valid-token coverage. Codecs
|
|
11
|
+
* consume the same physical tensors; padding never advances their threshold. */
|
|
12
|
+
export class FullPrefillRow implements Cache {
|
|
13
|
+
readonly padding = new FullPrefillPadding();
|
|
14
|
+
leftPadding = 0;
|
|
15
|
+
constructor(public inner: FullCache) {}
|
|
16
|
+
get minimumReusableOffset(): number { return (this.inner as Cache).minimumReusableOffset ?? 0; }
|
|
17
|
+
set minimumReusableOffset(value: number) { (this.inner as Cache).minimumReusableOffset = value; }
|
|
18
|
+
get positionOffset(): number { return this.inner.offset - this.leftPadding; }
|
|
19
|
+
get physicalLength(): number { return this.inner.offset; }
|
|
20
|
+
get offset(): number { return Math.max(0, this.padding.validOffset(this.positionOffset, 0)); }
|
|
21
|
+
get stateNeedsDispose(): boolean { return (this.inner as Cache).stateNeedsDispose ?? false; }
|
|
22
|
+
get affineConversion(): FullPrefillRow | undefined { return this.inner instanceof KVCache ? this : undefined; }
|
|
23
|
+
get turboConversion(): FullPrefillRow | undefined { return this.inner instanceof KVCache ? this : undefined; }
|
|
24
|
+
get quantizedAttention() { return (this.inner as Cache).quantizedAttention; }
|
|
25
|
+
get rotatedValueAttention() { return (this.inner as Cache).rotatedValueAttention; }
|
|
26
|
+
signature(): string { return this.inner.signature(); }
|
|
27
|
+
state(): MlxArray[] { return this.inner.state(); }
|
|
28
|
+
makeMask(tokens: number, window: number | null): Mask { return this.inner.makeMask(tokens, window); }
|
|
29
|
+
isTrimmable(): boolean { return true; }
|
|
30
|
+
trim(count: number): void { this.inner.trim(count); }
|
|
31
|
+
updateAndFetch(k: MlxArray, v: MlxArray): [MlxArray, MlxArray] { return this.inner.updateAndFetch(k, v); }
|
|
32
|
+
bytesPerToken(): number { return (this.inner as Cache).bytesPerToken?.() ?? 0; }
|
|
33
|
+
preparePrefill(padding: PrefillPadding): void {
|
|
34
|
+
const positions = { rowOffsets: [this.positionOffset], leftPad: [this.leftPadding] };
|
|
35
|
+
this.padding.prepare(positions, padding);
|
|
36
|
+
this.leftPadding = positions.leftPad[0]!;
|
|
37
|
+
}
|
|
38
|
+
finalizePrefill(): void {
|
|
39
|
+
const positions = { rowOffsets: [this.positionOffset], leftPad: [this.leftPadding] };
|
|
40
|
+
const next = withResource(leaseCacheState(this.inner), state => this.padding.finalize(state, positions));
|
|
41
|
+
if (next) this.replace(next, this.inner.offset);
|
|
42
|
+
this.leftPadding = positions.leftPad[0]!;
|
|
43
|
+
}
|
|
44
|
+
removeLeftPadding(count: number): void {
|
|
45
|
+
if (!count) return;
|
|
46
|
+
const next: MlxArray[] = [];
|
|
47
|
+
try {
|
|
48
|
+
withResource(leaseCacheState(this.inner), state => {
|
|
49
|
+
for (const plane of state) next.push(plane.slice([0, 0, count, 0], [...plane.shape]));
|
|
50
|
+
});
|
|
51
|
+
this.replace(next, this.inner.offset - count); next.length = 0;
|
|
52
|
+
this.leftPadding -= count;
|
|
53
|
+
} finally { disposeResources(next); }
|
|
54
|
+
}
|
|
55
|
+
private restored(planes: MlxArray[], offset: number): FullCache {
|
|
56
|
+
const source = this.inner;
|
|
57
|
+
if (source instanceof KVCache) {
|
|
58
|
+
const result = new KVCache();
|
|
59
|
+
if (planes.length) result.restoreState(planes[0]!, planes[1]!, offset);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
if (source instanceof QuantizedKVCache) {
|
|
63
|
+
const result = new QuantizedKVCache(source.groupSize, source.bits);
|
|
64
|
+
if (planes.length) result.restoreState({ packed: planes[0]!, scales: planes[1]!, biases: planes[2]! },
|
|
65
|
+
{ packed: planes[3]!, scales: planes[4]!, biases: planes[5]! }, offset);
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
const result = new TurboQuantKVCache(source.kBits, source.vBits);
|
|
69
|
+
if (planes.length) result.restoreState({ kIdx: planes[0]!, kScales: planes[1]!, kZeros: planes[2]!,
|
|
70
|
+
vPacked: planes[3]!, vScales: planes[4]! }, offset, source.headDim!);
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
private replace(planes: MlxArray[], offset: number): void {
|
|
74
|
+
const next = this.restored(planes, offset), previous = this.inner;
|
|
75
|
+
(next as Cache).minimumReusableOffset = this.minimumReusableOffset;
|
|
76
|
+
this.inner = next; previous.dispose();
|
|
77
|
+
}
|
|
78
|
+
extract(): Cache {
|
|
79
|
+
const count = this.offset, planes: MlxArray[] = [];
|
|
80
|
+
try {
|
|
81
|
+
if (count) withResource(leaseCacheState(this.inner), state => {
|
|
82
|
+
for (const plane of state) {
|
|
83
|
+
using view = plane.slice([0, 0, this.leftPadding, 0], [1, plane.shape[1]!, this.leftPadding + count, plane.shape[3]!]);
|
|
84
|
+
planes.push(materializeCopy(view));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const result = this.restored(planes, count); planes.length = 0;
|
|
88
|
+
(result as Cache).minimumReusableOffset = this.minimumReusableOffset;
|
|
89
|
+
return result;
|
|
90
|
+
} finally { disposeResources(planes); }
|
|
91
|
+
}
|
|
92
|
+
toQuantized(groupSize: number, bits: number): FullPrefillRow {
|
|
93
|
+
this.inner = (this.inner as KVCache).toQuantized(groupSize, bits);
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
toTurboQuantized(kBits: number, vBits: number): FullPrefillRow {
|
|
97
|
+
this.inner = TurboQuantKVCache.fromKVCache(this.inner as KVCache, kBits, vBits);
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
dispose(): void { this.inner.dispose(); this.padding.clear(); }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function fullRowInner(row: Cache): Cache { return row instanceof FullPrefillRow ? row.inner : row; }
|
|
104
|
+
export function fullRowPadding(row: Cache): number { return row instanceof FullPrefillRow ? row.leftPadding : 0; }
|
|
105
|
+
export function fullRowPhysicalLength(row: Cache): number { return row instanceof FullPrefillRow ? row.physicalLength : row.offset; }
|