mlx-bun 0.0.4
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/LICENSE +21 -0
- package/README.md +362 -0
- package/THIRD_PARTY_LICENSES.md +41 -0
- package/bin/mlx-bun.mjs +32 -0
- package/docs/curve-designer.html +303 -0
- package/docs/design/adapters-end-to-end.md +201 -0
- package/docs/design/compat-cli-surface-design.md +227 -0
- package/docs/design/docs-reorg-plan.md +152 -0
- package/docs/design/hlg-sampling.md +340 -0
- package/docs/design/memory-system.md +339 -0
- package/docs/design/optimization_plan.md +344 -0
- package/docs/design/parallel-slots.md +515 -0
- package/docs/design/runtime-isolation.md +247 -0
- package/docs/design/segmented-backward-training.md +508 -0
- package/docs/investigations/chunk-finetune-experiment.md +84 -0
- package/docs/investigations/curve-bisector-routing.md +123 -0
- package/docs/investigations/curve-runs/bisector-route.json +98 -0
- package/docs/investigations/curve-runs/bisector-trace.json +1393 -0
- package/docs/investigations/curve-runs/curve-terrain.json +1179 -0
- package/docs/investigations/curve-runs/curve-terrain.svg +42 -0
- package/docs/investigations/curve-terrain.html +105 -0
- package/docs/investigations/expert-offload-single-user-moe.md +292 -0
- package/docs/investigations/hlg-ab.html +34 -0
- package/docs/investigations/hlg-explorer.html +238 -0
- package/docs/investigations/hlg-figs/cliff.svg +1 -0
- package/docs/investigations/hlg-figs/cross.svg +1 -0
- package/docs/investigations/hlg-figs/edges.svg +1 -0
- package/docs/investigations/hlg-figs/k20-grids.svg +1 -0
- package/docs/investigations/hlg-figs/k20-peaks.svg +1 -0
- package/docs/investigations/hlg-figs/lw-curve.svg +1 -0
- package/docs/investigations/hlg-figs/terrain.svg +1 -0
- package/docs/investigations/hlg-report.html +262 -0
- package/docs/investigations/hlg-runs/hlg-ab.json +149 -0
- package/docs/investigations/hlg-runs/hlg-blind-prompts.md +87 -0
- package/docs/investigations/hlg-runs/hlg-blind.json +107 -0
- package/docs/investigations/hlg-runs/hlg-cliff.json +197 -0
- package/docs/investigations/hlg-runs/hlg-cross.json +505 -0
- package/docs/investigations/hlg-runs/hlg-edge.json +388 -0
- package/docs/investigations/hlg-runs/hlg-eval1.json +25 -0
- package/docs/investigations/hlg-runs/hlg-frontier.json +86 -0
- package/docs/investigations/hlg-runs/hlg-grid.json +242 -0
- package/docs/investigations/hlg-runs/hlg-grid2.json +343 -0
- package/docs/investigations/hlg-runs/hlg-lw-coarse.json +110 -0
- package/docs/investigations/hlg-runs/hlg-map.json +383 -0
- package/docs/investigations/hlg-runs/run0-original.log +159 -0
- package/docs/investigations/hlg-runs/runA-recommended.log +131 -0
- package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +26 -0
- package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +18 -0
- package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +25 -0
- package/docs/investigations/hlg-runs/runAD-ab-samples.log +8 -0
- package/docs/investigations/hlg-runs/runAE-edge-e4b.log +51 -0
- package/docs/investigations/hlg-runs/runAF-logit-calibration.log +10 -0
- package/docs/investigations/hlg-runs/runAG-eval-config2.log +16 -0
- package/docs/investigations/hlg-runs/runB-gain-variants.log +192 -0
- package/docs/investigations/hlg-runs/runC-pivots.log +162 -0
- package/docs/investigations/hlg-runs/runD-oetf.log +132 -0
- package/docs/investigations/hlg-runs/runE-eotf.log +162 -0
- package/docs/investigations/hlg-runs/runF-shaper-working.log +86 -0
- package/docs/investigations/hlg-runs/runG-shaper-sweep.log +101 -0
- package/docs/investigations/hlg-runs/runH-wsweep.log +176 -0
- package/docs/investigations/hlg-runs/runI-asweep.log +176 -0
- package/docs/investigations/hlg-runs/runJ-knobmap.log +49 -0
- package/docs/investigations/hlg-runs/runK-frontier.log +19 -0
- package/docs/investigations/hlg-runs/runL-widemap.log +65 -0
- package/docs/investigations/hlg-runs/runM-frontier-emb.log +15 -0
- package/docs/investigations/hlg-runs/runN-verify.log +116 -0
- package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +15 -0
- package/docs/investigations/hlg-runs/runP-grid-AxSm.log +15 -0
- package/docs/investigations/hlg-runs/runQ-grid-tgW.log +15 -0
- package/docs/investigations/hlg-runs/runR-blind-transcript.log +79 -0
- package/docs/investigations/hlg-runs/runS-cross-coherence.log +33 -0
- package/docs/investigations/hlg-runs/runT-cross-cpm5.log +33 -0
- package/docs/investigations/hlg-runs/runU-cross-12b.log +33 -0
- package/docs/investigations/hlg-runs/runV-lw-e4b.log +22 -0
- package/docs/investigations/hlg-runs/runW-lw-12b.log +22 -0
- package/docs/investigations/hlg-runs/runX-lw-cpm5.log +22 -0
- package/docs/investigations/hlg-runs/runY-cliff-e4b.log +26 -0
- package/docs/investigations/hlg-runs/runZ-cliff-12b.log +26 -0
- package/docs/investigations/hlg-sampling-investigation.md +970 -0
- package/docs/investigations/lab-build-journal.md +157 -0
- package/docs/investigations/pi-builtin-investigation.md +308 -0
- package/docs/investigations/starter-model-port-handoff.md +323 -0
- package/docs/planning/IDEAS.md +26 -0
- package/docs/planning/PRODUCT_ROADMAP.md +426 -0
- package/docs/planning/ResearchTopics.md +119 -0
- package/docs/planning/chunk-task-roadmap.md +77 -0
- package/docs/planning/journal.md +155 -0
- package/docs/reference/distribution.md +121 -0
- package/docs/reference/embedding.md +168 -0
- package/docs/reference/library-api.md +153 -0
- package/docs/reference/server-api.md +224 -0
- package/docs/reference/server-config.md +243 -0
- package/docs/segmented-backward-handoff.md +148 -0
- package/package.json +68 -0
- package/src/anthropic.ts +491 -0
- package/src/chat-template.ts +186 -0
- package/src/cli.ts +1075 -0
- package/src/config.ts +229 -0
- package/src/curve-sampler.ts +122 -0
- package/src/dataset/generators.ts +1087 -0
- package/src/dataset/index.ts +12 -0
- package/src/dataset/job.ts +81 -0
- package/src/dataset/llm.ts +71 -0
- package/src/dataset/registry.ts +528 -0
- package/src/download.ts +283 -0
- package/src/eval/README.md +86 -0
- package/src/eval/kl-prompts.ts +29 -0
- package/src/eval/kl.ts +294 -0
- package/src/eval/quality-db.ts +92 -0
- package/src/eval/runner.ts +102 -0
- package/src/eval/score.ts +49 -0
- package/src/eval/tasks/bfcl.ts +500 -0
- package/src/eval/tasks/gsm8k.ts +85 -0
- package/src/eval/tasks/hashhop.ts +109 -0
- package/src/eval/tasks/humaneval.ts +252 -0
- package/src/eval/tasks/ifeval.ts +354 -0
- package/src/eval/tasks/mmlu.ts +145 -0
- package/src/evaldb.ts +199 -0
- package/src/expert-offload-build.ts +77 -0
- package/src/expert-offload.ts +61 -0
- package/src/expert-trace.ts +67 -0
- package/src/fit.ts +254 -0
- package/src/generate.ts +451 -0
- package/src/harness-pi.ts +177 -0
- package/src/hf-push.ts +628 -0
- package/src/index.ts +22 -0
- package/src/jobs/db.ts +139 -0
- package/src/jobs/index.ts +29 -0
- package/src/jobs/job-entry.ts +106 -0
- package/src/jobs/runner.ts +304 -0
- package/src/jobs/sse.ts +114 -0
- package/src/jobs/types.ts +58 -0
- package/src/kv-store.ts +185 -0
- package/src/lora.ts +314 -0
- package/src/md-modules.d.ts +7 -0
- package/src/mlx/array.ts +237 -0
- package/src/mlx/autograd.ts +267 -0
- package/src/mlx/checkpoint.ts +116 -0
- package/src/mlx/compile.ts +158 -0
- package/src/mlx/custom-vjp.ts +140 -0
- package/src/mlx/ffi.ts +433 -0
- package/src/mlx/metal-kernel.ts +130 -0
- package/src/mlx/ops.ts +707 -0
- package/src/mmap.ts +90 -0
- package/src/model/batched-mask.ts +204 -0
- package/src/model/batched-rotating.ts +300 -0
- package/src/model/compiled-decode.ts +681 -0
- package/src/model/factory.ts +27 -0
- package/src/model/fingerprint.ts +33 -0
- package/src/model/flash-attention.ts +674 -0
- package/src/model/fused-decode-kernel.ts +305 -0
- package/src/model/fused-geglu-kernel.ts +87 -0
- package/src/model/gemma4-base.ts +1616 -0
- package/src/model/gemma4.ts +1012 -0
- package/src/model/generated/gemma4-12b.ts +526 -0
- package/src/model/generated/gemma4-26b.ts +500 -0
- package/src/model/generated/gemma4-e4b.ts +757 -0
- package/src/model/generated/index.ts +18 -0
- package/src/model/minicpm5.ts +278 -0
- package/src/model/qwen3-delta.ts +224 -0
- package/src/model/qwen3_5.ts +429 -0
- package/src/model/support.ts +46 -0
- package/src/native-pack.ts +152 -0
- package/src/pi-provider.ts +95 -0
- package/src/pi-terminal.ts +264 -0
- package/src/pi-web.ts +924 -0
- package/src/preflight.ts +123 -0
- package/src/prompt-cache.ts +113 -0
- package/src/quantize/allocator.ts +627 -0
- package/src/quantize/calibration.ts +180 -0
- package/src/quantize/config-writer.ts +132 -0
- package/src/quantize/index.ts +57 -0
- package/src/quantize/job.ts +136 -0
- package/src/quantize/quantizer.ts +462 -0
- package/src/quantize/safetensors-writer.ts +171 -0
- package/src/quantize/sensitivity.ts +234 -0
- package/src/registry.ts +336 -0
- package/src/responses.ts +774 -0
- package/src/safetensors.ts +158 -0
- package/src/sampler.ts +517 -0
- package/src/serve/batch-scheduler.ts +362 -0
- package/src/serve/generation-gateway.ts +160 -0
- package/src/server.ts +1891 -0
- package/src/spec/drafter.ts +261 -0
- package/src/spec/generate.ts +221 -0
- package/src/status-page.html +497 -0
- package/src/tokenizer.ts +38 -0
- package/src/tool-call.ts +242 -0
- package/src/train/dataset.ts +461 -0
- package/src/train/export.ts +32 -0
- package/src/train/forward.ts +229 -0
- package/src/train/index.ts +43 -0
- package/src/train/job.ts +148 -0
- package/src/train/lora-params.ts +194 -0
- package/src/train/loss.ts +342 -0
- package/src/train/merge.ts +195 -0
- package/src/train/optimizer.ts +176 -0
- package/src/train/rank.ts +101 -0
- package/src/train/segmented.ts +520 -0
- package/src/train/trainer.ts +600 -0
- package/src/tui.ts +161 -0
- package/src/vision/embedder.ts +168 -0
- package/src/vision/preprocess.ts +247 -0
- package/src/vision/prompt.ts +158 -0
- package/src/vision/siglip.ts +599 -0
- package/src/web/app.html +2471 -0
- package/src/web/skills/web-research/SKILL.md +44 -0
- package/src/web/skills.ts +59 -0
- package/src/web-tools.ts +426 -0
- package/src/weights.ts +91 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
// Segmented backward: long-context LoRA SFT that streams the backward pass
|
|
2
|
+
// segment-by-segment so only ONE segment's activations are ever live, instead
|
|
3
|
+
// of holding every layer's recompute activations at once (the spike that makes
|
|
4
|
+
// naive gradient checkpointing crash at long context). Design + proof:
|
|
5
|
+
// docs/design/segmented-backward-training.md (mechanism validated on a toy in
|
|
6
|
+
// scripts/ckpt-mem-test.ts, SEG mode: 0.000% grad error, peak below per-layer
|
|
7
|
+
// checkpointing; bit-exact on MiniCPM5 under flash attention).
|
|
8
|
+
//
|
|
9
|
+
// Mechanism (B=1 SFT only — the responseOnlyCe path):
|
|
10
|
+
// 1. Forward, saving boundaries. Run the layer stack; at each segment edge
|
|
11
|
+
// materialize (eval) and DETACH (copy to a graph-free leaf) the residual
|
|
12
|
+
// stream hidden into a boundary. Intra-segment interiors are discarded; the
|
|
13
|
+
// boundaries are small ([1, T, hidden] per edge).
|
|
14
|
+
// 2. Loss head. finalNorm + LM head + response-only CE over the LAST boundary,
|
|
15
|
+
// differentiated w.r.t. that boundary -> the loss value and dh_out, the
|
|
16
|
+
// gradient of the loss w.r.t. the final residual stream.
|
|
17
|
+
// 3. Backward, reverse over segments. For each segment k (last -> first) an
|
|
18
|
+
// mlx_vjp of `segment_forward(boundary_k, ...LoRA)` with the cotangent dh_out
|
|
19
|
+
// returns [dh_in, ...dLoRA_segment]: dh_in seeds segment k-1, the LoRA grads
|
|
20
|
+
// scatter into the global flat grad vector. Only segment k's activations are
|
|
21
|
+
// live. (A vjp is exactly reverse-mode AD: vjps = (df/dx)^T (.) dh.)
|
|
22
|
+
//
|
|
23
|
+
// Why mlx_vjp and not a surrogate-loss value_and_grad: the equivalent
|
|
24
|
+
// `value_and_grad(sum(stop_grad(dh) (.) segment_forward(...)))` is numerically
|
|
25
|
+
// identical but LEAKS ~one activation buffer per segment per step at the mlx
|
|
26
|
+
// level (measured; not GC/cache/synchronize/reuse reclaimable — see
|
|
27
|
+
// docs/design/segmented-backward-training.md §9.4). mlx_vjp takes the cotangent
|
|
28
|
+
// directly, needs no surrogate, and does not leak.
|
|
29
|
+
//
|
|
30
|
+
// IMPORTANT lifetime fact: mlx `eval` does NOT detach — an eval'd array retains
|
|
31
|
+
// its upstream graph, so each forward boundary must be copied into a fresh leaf
|
|
32
|
+
// (detachLeaf) or it keeps a whole layer-stack's activations alive (~0.1 GB per
|
|
33
|
+
// 4-layer segment of within-step retention otherwise). The vjp objects are built
|
|
34
|
+
// ONCE and reused across steps; their closures read the per-step batch from a
|
|
35
|
+
// mutable holder.
|
|
36
|
+
|
|
37
|
+
import { MlxArray } from "../mlx/array";
|
|
38
|
+
import * as ops from "../mlx/ops";
|
|
39
|
+
import { Vjp } from "../mlx/autograd";
|
|
40
|
+
import type { MiniCPM5Model } from "../model/minicpm5";
|
|
41
|
+
import type { Gemma4Model } from "../model/gemma4";
|
|
42
|
+
import type { Cache, Mask, SharedKv } from "../model/gemma4-base";
|
|
43
|
+
import { TrainingCache } from "./forward";
|
|
44
|
+
import { responseOnlyCe } from "./loss";
|
|
45
|
+
import type { TrainableLora } from "./lora-params";
|
|
46
|
+
import { type SftBatch } from "./dataset";
|
|
47
|
+
|
|
48
|
+
/** Split `nLayers` into contiguous `[lo, hi)` ranges of at most `segSize`
|
|
49
|
+
* layers each (the last range may be shorter). `segSize` is the memory knob:
|
|
50
|
+
* fewer layers per segment -> lower peak, ~fixed total recompute time. */
|
|
51
|
+
export function planSegmentsBySize(nLayers: number, segSize: number): [number, number][] {
|
|
52
|
+
if (segSize < 1) throw new Error(`planSegmentsBySize: segSize must be >= 1 (got ${segSize})`);
|
|
53
|
+
const ranges: [number, number][] = [];
|
|
54
|
+
for (let lo = 0; lo < nLayers; lo += segSize) ranges.push([lo, Math.min(lo + segSize, nLayers)]);
|
|
55
|
+
return ranges;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Detach `a` into a graph-free LEAF holding its exact (evaluated) bytes, then
|
|
59
|
+
* dispose `a`. mlx retains the upstream graph after `eval`, so a segment
|
|
60
|
+
* boundary (or a value_and_grad output) would otherwise keep its whole layer
|
|
61
|
+
* stack's activations alive. Copying the bytes into a fresh leaf frees that
|
|
62
|
+
* graph. The leaf is a valid differentiable input for the per-segment vag. */
|
|
63
|
+
function detachLeaf(a: MlxArray): MlxArray {
|
|
64
|
+
// Force a row-major copy first: `rawBytes` reads the underlying buffer
|
|
65
|
+
// linearly, so a NON-contiguous input (e.g. a transposed donor K/V view) would
|
|
66
|
+
// be byte-scrambled. `ops.contiguous` is a no-op for already-contiguous arrays
|
|
67
|
+
// (hidden boundaries) and materializes row-major for strided views.
|
|
68
|
+
const c = ops.contiguous(a);
|
|
69
|
+
c.eval();
|
|
70
|
+
const leaf = MlxArray.fromBytesCopy(c.rawBytes(), c.shape, c.dtype);
|
|
71
|
+
c.dispose();
|
|
72
|
+
a.dispose();
|
|
73
|
+
return leaf;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Layer index a target's modulePath belongs to, or -1 (e.g. embedding/head). */
|
|
77
|
+
function targetLayer(modulePath: string): number {
|
|
78
|
+
const m = modulePath.match(/\.layers\.(\d+)\./);
|
|
79
|
+
return m ? Number(m[1]) : -1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Reusable segmented-backward driver for one (model, lora, segmentation). Build
|
|
83
|
+
* ONCE per training run and call `.step(batch)` each iteration — the per-segment
|
|
84
|
+
* value_and_grads are created up front and reused (rebuilding them every step
|
|
85
|
+
* leaks at the mlx level). Returns the loss scalar and the LoRA gradients in
|
|
86
|
+
* `flatParams(lora)` order ([...A, ...B] over targets), drop-in for
|
|
87
|
+
* `ValueAndGrad.apply(flatParams(lora))`. Caller owns the returned arrays.
|
|
88
|
+
*
|
|
89
|
+
* The active adapter must be attached (attachForTraining) so the layer forward
|
|
90
|
+
* picks up the swapped LoRA leaves via loraState. B=1 SFT only. */
|
|
91
|
+
export class SegmentedBackward {
|
|
92
|
+
private readonly nLayers: number;
|
|
93
|
+
private readonly n: number; // number of LoRA targets
|
|
94
|
+
private readonly segIdxs: number[][]; // target indices per segment
|
|
95
|
+
private readonly caches: Cache[];
|
|
96
|
+
private readonly headVjp: Vjp;
|
|
97
|
+
private readonly segVjps: Vjp[];
|
|
98
|
+
private readonly one: MlxArray; // scalar cotangent 1.0 for the (scalar) head loss
|
|
99
|
+
// Per-step context the (reused) closures read.
|
|
100
|
+
private curBatch: SftBatch | null = null;
|
|
101
|
+
private disposed = false;
|
|
102
|
+
|
|
103
|
+
constructor(
|
|
104
|
+
private readonly model: MiniCPM5Model,
|
|
105
|
+
private readonly lora: TrainableLora,
|
|
106
|
+
private readonly ranges: [number, number][],
|
|
107
|
+
) {
|
|
108
|
+
this.nLayers = model.layers.length;
|
|
109
|
+
const lastHi = ranges[ranges.length - 1]?.[1] ?? 0;
|
|
110
|
+
if (ranges[0]?.[0] !== 0 || lastHi !== this.nLayers)
|
|
111
|
+
throw new Error(`SegmentedBackward: ranges must cover [0, ${this.nLayers}) (got ${JSON.stringify(ranges)})`);
|
|
112
|
+
|
|
113
|
+
this.n = lora.targets.length;
|
|
114
|
+
const layerOf = lora.targets.map((t) => targetLayer(t.modulePath));
|
|
115
|
+
this.segIdxs = ranges.map(([lo, hi]) =>
|
|
116
|
+
layerOf.map((li, j) => ({ li, j })).filter((x) => x.li >= lo && x.li < hi).map((x) => x.j),
|
|
117
|
+
);
|
|
118
|
+
this.caches = Array.from({ length: this.nLayers }, () => new TrainingCache());
|
|
119
|
+
this.one = MlxArray.fromFloat32(new Float32Array([1]), []); // d(loss)/d(loss) = 1
|
|
120
|
+
|
|
121
|
+
// Loss head: vjp of finalNorm + responseOnlyCe over the last boundary leaf.
|
|
122
|
+
// outputs[0] = loss scalar; vjps[0] = dLoss/d(last residual stream) = dh.
|
|
123
|
+
this.headVjp = new Vjp((p) => {
|
|
124
|
+
const hn = model.finalNorm.forward(p[0]!);
|
|
125
|
+
const loss = responseOnlyCe(model, hn, this.curBatch!);
|
|
126
|
+
hn.dispose();
|
|
127
|
+
return [loss];
|
|
128
|
+
}, 1);
|
|
129
|
+
|
|
130
|
+
// One reused vjp per segment over [boundary, ...segment LoRA] -> [output].
|
|
131
|
+
// apply(..., [dh]) returns vjps [dh_in, dA_0, dB_0, ...].
|
|
132
|
+
this.segVjps = ranges.map(([lo, hi], k) => {
|
|
133
|
+
const idxs = this.segIdxs[k]!;
|
|
134
|
+
return new Vjp((p) => {
|
|
135
|
+
// Swap this segment's LoRA primals into the live LoraWeights so the
|
|
136
|
+
// forward graph differentiates them; restore the originals after (the
|
|
137
|
+
// primal wrappers are disposed by the vjp closure).
|
|
138
|
+
const saved: [MlxArray, MlxArray][] = idxs.map((j) => [lora.targets[j]!.lw.a, lora.targets[j]!.lw.b]);
|
|
139
|
+
idxs.forEach((j, s) => {
|
|
140
|
+
lora.targets[j]!.lw.a = p[1 + 2 * s]!;
|
|
141
|
+
lora.targets[j]!.lw.b = p[1 + 2 * s + 1]!;
|
|
142
|
+
});
|
|
143
|
+
try {
|
|
144
|
+
return [model.runLayerRange(p[0]!, lo, hi, this.caches)];
|
|
145
|
+
} finally {
|
|
146
|
+
idxs.forEach((j, s) => {
|
|
147
|
+
lora.targets[j]!.lw.a = saved[s]![0];
|
|
148
|
+
lora.targets[j]!.lw.b = saved[s]![1];
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}, 1);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
step(batch: SftBatch): { value: MlxArray; grads: MlxArray[] } {
|
|
156
|
+
if (this.disposed) throw new Error("SegmentedBackward used after dispose");
|
|
157
|
+
const { model, lora, ranges, caches, segIdxs, n } = this;
|
|
158
|
+
const B = batch.ids.length;
|
|
159
|
+
if (B !== 1) throw new Error("SegmentedBackward: only B=1 is supported (responseOnlyCe path)");
|
|
160
|
+
const L = batch.ids[0]!.length;
|
|
161
|
+
if (L < 2) throw new Error("SegmentedBackward: sequence too short (need >= 2 tokens)");
|
|
162
|
+
this.curBatch = batch;
|
|
163
|
+
|
|
164
|
+
const nSeg = ranges.length;
|
|
165
|
+
const T = L - 1;
|
|
166
|
+
const inputHost = new Int32Array(T);
|
|
167
|
+
for (let t = 0; t < T; t++) inputHost[t] = batch.ids[0]![t]!;
|
|
168
|
+
const inputIds = MlxArray.fromInt32(inputHost, [1, T]);
|
|
169
|
+
|
|
170
|
+
const boundaries: (MlxArray | null)[] = [];
|
|
171
|
+
let value: MlxArray | null = null;
|
|
172
|
+
let dhOut: MlxArray | null = null;
|
|
173
|
+
let transferred = false;
|
|
174
|
+
const grads: (MlxArray | null)[] = new Array(2 * n).fill(null);
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
// --- 1. Forward, saving detached boundaries at segment edges. --------
|
|
178
|
+
boundaries.push(detachLeaf(model.embed.encode(inputIds))); // [1, T, hidden]
|
|
179
|
+
for (let k = 0; k < nSeg; k++) {
|
|
180
|
+
const [lo, hi] = ranges[k]!;
|
|
181
|
+
boundaries.push(detachLeaf(model.runLayerRange(boundaries[k]!, lo, hi, caches)));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// --- 2. Loss head over the last boundary (vjp with cotangent 1.0). ---
|
|
185
|
+
const head = this.headVjp.apply([boundaries[nSeg]!], [this.one]);
|
|
186
|
+
value = detachLeaf(head.outputs[0]!); // scalar loss, detached from the head graph (frees logits)
|
|
187
|
+
dhOut = detachLeaf(head.vjps[0]!); // dLoss / d(last residual stream), graph-free
|
|
188
|
+
boundaries[nSeg]!.dispose();
|
|
189
|
+
boundaries[nSeg] = null;
|
|
190
|
+
|
|
191
|
+
// --- 3. Backward, reverse over segments (vjp, cotangent = dh). --------
|
|
192
|
+
for (let k = nSeg - 1; k >= 0; k--) {
|
|
193
|
+
const idxs = segIdxs[k]!;
|
|
194
|
+
const params: MlxArray[] = [boundaries[k]!];
|
|
195
|
+
for (const j of idxs) params.push(lora.targets[j]!.lw.a, lora.targets[j]!.lw.b);
|
|
196
|
+
|
|
197
|
+
const res = this.segVjps[k]!.apply(params, [dhOut!]);
|
|
198
|
+
res.outputs[0]!.dispose(); // segment output — unused (only its vjp matters)
|
|
199
|
+
// vjps[0] = dh_in (grad w.r.t. this segment's input boundary) -> dh_out
|
|
200
|
+
// for segment k-1; the rest scatter into the global flat vector. Detach
|
|
201
|
+
// each to a graph-free leaf (eval doesn't detach; keeps peak bounded).
|
|
202
|
+
idxs.forEach((j, s) => {
|
|
203
|
+
grads[j] = detachLeaf(res.vjps[1 + 2 * s]!); // dA
|
|
204
|
+
grads[n + j] = detachLeaf(res.vjps[1 + 2 * s + 1]!); // dB
|
|
205
|
+
});
|
|
206
|
+
dhOut!.dispose(); // consumed as this segment's cotangent
|
|
207
|
+
dhOut = detachLeaf(res.vjps[0]!);
|
|
208
|
+
boundaries[k]!.dispose();
|
|
209
|
+
boundaries[k] = null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Segment-0 dh_in is the gradient w.r.t. the embedding output — MiniCPM5's
|
|
213
|
+
// embedding carries no LoRA, so it is discarded (disposed in finally).
|
|
214
|
+
transferred = true;
|
|
215
|
+
return { value: value!, grads: grads as MlxArray[] };
|
|
216
|
+
} finally {
|
|
217
|
+
inputIds.dispose();
|
|
218
|
+
this.curBatch = null;
|
|
219
|
+
dhOut?.dispose();
|
|
220
|
+
for (const b of boundaries) b?.dispose();
|
|
221
|
+
if (!transferred) {
|
|
222
|
+
value?.dispose();
|
|
223
|
+
for (const g of grads) g?.dispose();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
dispose(): void {
|
|
229
|
+
if (this.disposed) return;
|
|
230
|
+
this.disposed = true;
|
|
231
|
+
this.headVjp.dispose();
|
|
232
|
+
for (const v of this.segVjps) v.dispose();
|
|
233
|
+
for (const c of this.caches) c.dispose();
|
|
234
|
+
this.one.dispose();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ===========================================================================
|
|
239
|
+
// e4b (Gemma4Model): segmented backward with KV-shared donor threading.
|
|
240
|
+
// ===========================================================================
|
|
241
|
+
//
|
|
242
|
+
// Same skeleton as SegmentedBackward (forward boundaries -> head vjp -> reverse
|
|
243
|
+
// per-segment vjp), with the two e4b-specific pieces (docs §10):
|
|
244
|
+
//
|
|
245
|
+
// - per-layer-input is a PURE CONSTANT boundary: the per_layer_input_gate /
|
|
246
|
+
// per_layer_projection LoRA live inside the layer (differentiated normally
|
|
247
|
+
// within each segment); the [B,L,nLayers,width] tensor feeds in as a
|
|
248
|
+
// constant (its grad would flow to the embed / per_layer_model_projection,
|
|
249
|
+
// neither a LoRA target) — detached once, sliced per segment, cotangent
|
|
250
|
+
// discarded.
|
|
251
|
+
//
|
|
252
|
+
// - the KV-shared donor K/V is a SECOND boundary stream WITH its own cotangent.
|
|
253
|
+
// A reused donor d (e4b: 22 sliding, 23 full) is PRODUCED by the segment
|
|
254
|
+
// containing d (saved as a detached boundary) and CONSUMED by later segments
|
|
255
|
+
// whose sharers reuse it. In the backward, a consumer segment's vjp yields a
|
|
256
|
+
// cotangent on the donor K/V (accumulated into dKV[d]); the producer segment
|
|
257
|
+
// has a MULTI-OUTPUT forward [h, donorK, donorV] whose vjp takes cotangents
|
|
258
|
+
// [dh, dKV[d].k, dKV[d].v] — folding the sharers' gradient into the donor.
|
|
259
|
+
|
|
260
|
+
type KvPair = { keys: MlxArray; values: MlxArray };
|
|
261
|
+
|
|
262
|
+
export class SegmentedBackwardGemma4 {
|
|
263
|
+
private readonly nLayers: number;
|
|
264
|
+
private readonly n: number;
|
|
265
|
+
private readonly segIdxs: number[][];
|
|
266
|
+
private readonly consumes: number[][]; // reused donors consumed as input, per segment
|
|
267
|
+
private readonly produces: number[][]; // reused donors output for later segments, per segment
|
|
268
|
+
private readonly caches: Cache[];
|
|
269
|
+
private readonly headVjp: Vjp;
|
|
270
|
+
private readonly segVjps: Vjp[];
|
|
271
|
+
private readonly one: MlxArray;
|
|
272
|
+
// Per-step constants the (reused) closures read.
|
|
273
|
+
private curBatch: SftBatch | null = null;
|
|
274
|
+
private curMasks: Map<string, Mask> | null = null;
|
|
275
|
+
private curPerLayer: MlxArray | null = null;
|
|
276
|
+
private disposed = false;
|
|
277
|
+
|
|
278
|
+
constructor(
|
|
279
|
+
private readonly model: Gemma4Model,
|
|
280
|
+
private readonly lora: TrainableLora,
|
|
281
|
+
private readonly ranges: [number, number][],
|
|
282
|
+
) {
|
|
283
|
+
this.nLayers = model.layers.length;
|
|
284
|
+
const lastHi = ranges[ranges.length - 1]?.[1] ?? 0;
|
|
285
|
+
if (ranges[0]?.[0] !== 0 || lastHi !== this.nLayers)
|
|
286
|
+
throw new Error(`SegmentedBackwardGemma4: ranges must cover [0, ${this.nLayers})`);
|
|
287
|
+
this.n = lora.targets.length;
|
|
288
|
+
|
|
289
|
+
const layerOf = lora.targets.map((t) => targetLayer(t.modulePath));
|
|
290
|
+
this.segIdxs = ranges.map(([lo, hi]) =>
|
|
291
|
+
layerOf.map((li, j) => ({ li, j })).filter((x) => x.li >= lo && x.li < hi).map((x) => x.j),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// Donor/sharer KV threading: for each reused donor d, the segment that holds
|
|
295
|
+
// d PRODUCES its K/V; a later segment with a sharer of d CONSUMES it.
|
|
296
|
+
const segOfLayer = new Map<number, number>();
|
|
297
|
+
ranges.forEach(([lo, hi], k) => { for (let i = lo; i < hi; i++) segOfLayer.set(i, k); });
|
|
298
|
+
const reused = model.reusedDonors;
|
|
299
|
+
const producerSeg = new Map<number, number>();
|
|
300
|
+
for (const d of reused) producerSeg.set(d, segOfLayer.get(d)!);
|
|
301
|
+
this.consumes = ranges.map(() => []);
|
|
302
|
+
this.produces = ranges.map(() => []);
|
|
303
|
+
for (let i = 0; i < this.nLayers; i++) {
|
|
304
|
+
if (model.cacheIndex[i] !== -1) continue; // donors don't consume
|
|
305
|
+
const d = model.previousKvs[i]!; // this sharer's donor (a reused donor)
|
|
306
|
+
const sSeg = segOfLayer.get(i)!;
|
|
307
|
+
const pSeg = producerSeg.get(d)!;
|
|
308
|
+
if (sSeg > pSeg) { // cross-segment: thread d's K/V from pSeg to sSeg
|
|
309
|
+
if (!this.consumes[sSeg]!.includes(d)) this.consumes[sSeg]!.push(d);
|
|
310
|
+
if (!this.produces[pSeg]!.includes(d)) this.produces[pSeg]!.push(d);
|
|
311
|
+
}
|
|
312
|
+
// sSeg === pSeg: runLayerRange threads d internally; no boundary needed.
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
this.caches = Array.from({ length: model.numDonors }, () => new TrainingCache());
|
|
316
|
+
this.one = MlxArray.fromFloat32(new Float32Array([1]), []);
|
|
317
|
+
|
|
318
|
+
// Loss head (identical to MiniCPM5): finalNorm + responseOnlyCe over the last
|
|
319
|
+
// boundary, vjp with cotangent 1.0 -> [loss], [dh].
|
|
320
|
+
this.headVjp = new Vjp((p) => {
|
|
321
|
+
const hn = model.finalNorm.forward(p[0]!);
|
|
322
|
+
const loss = responseOnlyCe(model, hn, this.curBatch!);
|
|
323
|
+
hn.dispose();
|
|
324
|
+
return [loss];
|
|
325
|
+
}, 1);
|
|
326
|
+
|
|
327
|
+
// One reused vjp per segment. Inputs: [boundary, (k,v)*consumes, (a,b)*lora].
|
|
328
|
+
// Outputs: [h, (k,v)*produces]. The closures read curMasks/curPerLayer.
|
|
329
|
+
this.segVjps = ranges.map(([lo, hi], k) => {
|
|
330
|
+
const idxs = this.segIdxs[k]!;
|
|
331
|
+
const cons = this.consumes[k]!;
|
|
332
|
+
const prod = this.produces[k]!;
|
|
333
|
+
const base = 1 + 2 * cons.length; // first LoRA primal index
|
|
334
|
+
const nOut = 1 + 2 * prod.length;
|
|
335
|
+
const vjp = new Vjp((p) => {
|
|
336
|
+
const boundary = p[0]!;
|
|
337
|
+
const kvIn = new Map<number, SharedKv>();
|
|
338
|
+
cons.forEach((d, ci) => kvIn.set(d, { kind: "plain", keys: p[1 + 2 * ci]!, values: p[1 + 2 * ci + 1]!, offset: 0 }));
|
|
339
|
+
const saved: [MlxArray, MlxArray][] = idxs.map((j) => [lora.targets[j]!.lw.a, lora.targets[j]!.lw.b]);
|
|
340
|
+
idxs.forEach((j, s) => {
|
|
341
|
+
lora.targets[j]!.lw.a = p[base + 2 * s]!;
|
|
342
|
+
lora.targets[j]!.lw.b = p[base + 2 * s + 1]!;
|
|
343
|
+
});
|
|
344
|
+
try {
|
|
345
|
+
const { h, donorKvOut } = model.runLayerRange(
|
|
346
|
+
boundary, lo, hi, this.caches, this.curMasks!, this.curPerLayer, kvIn,
|
|
347
|
+
);
|
|
348
|
+
const outs: MlxArray[] = [h];
|
|
349
|
+
for (const d of prod) {
|
|
350
|
+
const kv = donorKvOut.get(d)!;
|
|
351
|
+
if (kv.kind !== "plain") throw new Error("segmented e4b: quantized donor KV unsupported in training");
|
|
352
|
+
// Output the CONTIGUOUS donor K/V: the consumer segments received a
|
|
353
|
+
// contiguous (detachLeaf'd) copy, so their accumulated cotangent dKV
|
|
354
|
+
// is row-major. The producer's raw K/V are transposed views; matching
|
|
355
|
+
// layouts keeps the vjp's cotangent indexing consistent.
|
|
356
|
+
const ck = ops.contiguous(kv.keys), cv = ops.contiguous(kv.values);
|
|
357
|
+
kv.keys.dispose(); kv.values.dispose();
|
|
358
|
+
outs.push(ck, cv);
|
|
359
|
+
}
|
|
360
|
+
// in-segment-only reused donors (produced but not threaded forward) — free.
|
|
361
|
+
for (const [d, kv] of donorKvOut)
|
|
362
|
+
if (!prod.includes(d) && kv.kind === "plain") { kv.keys.dispose(); kv.values.dispose(); }
|
|
363
|
+
return outs;
|
|
364
|
+
} finally {
|
|
365
|
+
idxs.forEach((j, s) => { lora.targets[j]!.lw.a = saved[s]![0]; lora.targets[j]!.lw.b = saved[s]![1]; });
|
|
366
|
+
}
|
|
367
|
+
}, nOut);
|
|
368
|
+
return vjp;
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
step(batch: SftBatch): { value: MlxArray; grads: MlxArray[] } {
|
|
373
|
+
if (this.disposed) throw new Error("SegmentedBackwardGemma4 used after dispose");
|
|
374
|
+
const { model, lora, ranges, caches, segIdxs, consumes, produces, n } = this;
|
|
375
|
+
const B = batch.ids.length;
|
|
376
|
+
if (B !== 1) throw new Error("SegmentedBackwardGemma4: only B=1 is supported");
|
|
377
|
+
const L = batch.ids[0]!.length;
|
|
378
|
+
if (L < 2) throw new Error("SegmentedBackwardGemma4: sequence too short");
|
|
379
|
+
this.curBatch = batch;
|
|
380
|
+
|
|
381
|
+
const nSeg = ranges.length;
|
|
382
|
+
const T = L - 1;
|
|
383
|
+
const inputHost = new Int32Array(T);
|
|
384
|
+
for (let t = 0; t < T; t++) inputHost[t] = batch.ids[0]![t]!;
|
|
385
|
+
const inputIds = MlxArray.fromInt32(inputHost, [1, T]);
|
|
386
|
+
|
|
387
|
+
const boundaries: (MlxArray | null)[] = [];
|
|
388
|
+
const donorBnd = new Map<number, KvPair>(); // detached reused-donor K/V boundaries
|
|
389
|
+
const dKV = new Map<number, KvPair>(); // accumulated reused-donor cotangents
|
|
390
|
+
let value: MlxArray | null = null;
|
|
391
|
+
let dhOut: MlxArray | null = null;
|
|
392
|
+
let transferred = false;
|
|
393
|
+
const grads: (MlxArray | null)[] = new Array(2 * n).fill(null);
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
// --- 1. Forward, saving detached boundaries (hidden + reused donor K/V). ---
|
|
397
|
+
const { hScaled, perLayer } = model.embedForSegmented(inputIds);
|
|
398
|
+
boundaries.push(detachLeaf(hScaled));
|
|
399
|
+
this.curPerLayer = perLayer ? detachLeaf(perLayer) : null;
|
|
400
|
+
this.curMasks = model.makeTrainingMasks(caches, T);
|
|
401
|
+
|
|
402
|
+
for (let k = 0; k < nSeg; k++) {
|
|
403
|
+
const [lo, hi] = ranges[k]!;
|
|
404
|
+
const kvIn = new Map<number, SharedKv>();
|
|
405
|
+
for (const d of consumes[k]!) {
|
|
406
|
+
const b = donorBnd.get(d)!;
|
|
407
|
+
kvIn.set(d, { kind: "plain", keys: b.keys, values: b.values, offset: 0 });
|
|
408
|
+
}
|
|
409
|
+
const { h, donorKvOut } = model.runLayerRange(boundaries[k]!, lo, hi, caches, this.curMasks, this.curPerLayer, kvIn);
|
|
410
|
+
for (const d of produces[k]!) {
|
|
411
|
+
const kv = donorKvOut.get(d)!;
|
|
412
|
+
if (kv.kind !== "plain") throw new Error("segmented e4b: quantized donor KV unsupported in training");
|
|
413
|
+
donorBnd.set(d, { keys: detachLeaf(kv.keys), values: detachLeaf(kv.values) });
|
|
414
|
+
}
|
|
415
|
+
for (const [d, kv] of donorKvOut)
|
|
416
|
+
if (!produces[k]!.includes(d) && kv.kind === "plain") { kv.keys.dispose(); kv.values.dispose(); }
|
|
417
|
+
boundaries.push(detachLeaf(h));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// --- 2. Loss head (vjp, cotangent 1.0). ---
|
|
421
|
+
const head = this.headVjp.apply([boundaries[nSeg]!], [this.one]);
|
|
422
|
+
value = detachLeaf(head.outputs[0]!);
|
|
423
|
+
dhOut = detachLeaf(head.vjps[0]!);
|
|
424
|
+
boundaries[nSeg]!.dispose();
|
|
425
|
+
boundaries[nSeg] = null;
|
|
426
|
+
|
|
427
|
+
// --- 3. Backward, reverse over segments (vjp, cotangents dh + dKV). ---
|
|
428
|
+
for (let k = nSeg - 1; k >= 0; k--) {
|
|
429
|
+
const idxs = segIdxs[k]!;
|
|
430
|
+
const cons = consumes[k]!;
|
|
431
|
+
const prod = produces[k]!;
|
|
432
|
+
const dh = dhOut!;
|
|
433
|
+
|
|
434
|
+
const primals: MlxArray[] = [boundaries[k]!];
|
|
435
|
+
for (const d of cons) { const b = donorBnd.get(d)!; primals.push(b.keys, b.values); }
|
|
436
|
+
for (const j of idxs) primals.push(lora.targets[j]!.lw.a, lora.targets[j]!.lw.b);
|
|
437
|
+
|
|
438
|
+
const cots: MlxArray[] = [dh];
|
|
439
|
+
for (const d of prod) {
|
|
440
|
+
const acc = dKV.get(d)!; // set by the consumer segments already processed (reverse order)
|
|
441
|
+
cots.push(acc.keys, acc.values);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const res = this.segVjps[k]!.apply(primals, cots);
|
|
445
|
+
for (const o of res.outputs) o.dispose();
|
|
446
|
+
|
|
447
|
+
// vjps[0] = dh_in; then (dk,dv) per consumed donor; then (da,db) per LoRA.
|
|
448
|
+
const newDh = detachLeaf(res.vjps[0]!);
|
|
449
|
+
cons.forEach((d, ci) => {
|
|
450
|
+
const dk = detachLeaf(res.vjps[1 + 2 * ci]!);
|
|
451
|
+
const dv = detachLeaf(res.vjps[1 + 2 * ci + 1]!);
|
|
452
|
+
this.accumulateDKV(dKV, d, dk, dv);
|
|
453
|
+
});
|
|
454
|
+
const base = 1 + 2 * cons.length;
|
|
455
|
+
idxs.forEach((j, s) => {
|
|
456
|
+
grads[j] = detachLeaf(res.vjps[base + 2 * s]!);
|
|
457
|
+
grads[n + j] = detachLeaf(res.vjps[base + 2 * s + 1]!);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
dh.dispose();
|
|
461
|
+
dhOut = newDh;
|
|
462
|
+
// produced donors' cotangents are now consumed -> free them + their boundary.
|
|
463
|
+
for (const d of prod) {
|
|
464
|
+
const acc = dKV.get(d)!; acc.keys.dispose(); acc.values.dispose(); dKV.delete(d);
|
|
465
|
+
const b = donorBnd.get(d)!; b.keys.dispose(); b.values.dispose(); donorBnd.delete(d);
|
|
466
|
+
}
|
|
467
|
+
boundaries[k]!.dispose();
|
|
468
|
+
boundaries[k] = null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
transferred = true;
|
|
472
|
+
return { value: value!, grads: grads as MlxArray[] };
|
|
473
|
+
} finally {
|
|
474
|
+
inputIds.dispose();
|
|
475
|
+
this.curBatch = null;
|
|
476
|
+
this.curPerLayer?.dispose();
|
|
477
|
+
this.curPerLayer = null;
|
|
478
|
+
if (this.curMasks) { for (const m of this.curMasks.values()) m.arr?.dispose(); this.curMasks = null; }
|
|
479
|
+
dhOut?.dispose();
|
|
480
|
+
for (const b of boundaries) b?.dispose();
|
|
481
|
+
for (const kv of donorBnd.values()) { kv.keys.dispose(); kv.values.dispose(); }
|
|
482
|
+
for (const kv of dKV.values()) { kv.keys.dispose(); kv.values.dispose(); }
|
|
483
|
+
if (!transferred) {
|
|
484
|
+
value?.dispose();
|
|
485
|
+
for (const g of grads) g?.dispose();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/** Accumulate a reused-donor K/V cotangent (sum across consuming segments) in
|
|
491
|
+
* bf16. The donor K/V grad ends up ~0.5-1% (relNorm) off the full backward —
|
|
492
|
+
* bf16 NON-ASSOCIATIVITY, established (not a logic bug), via:
|
|
493
|
+
* - single-consumer reuse is BIT-EXACT (no sum to reorder);
|
|
494
|
+
* - the error is flat in consumer-SEGMENT count and tracks SUMMAND count
|
|
495
|
+
* (donor 22's 15 sharers ≈0.46%, donor 23's 3 sharers ≈0.000%);
|
|
496
|
+
* - it is grouping-DEPENDENT (donor-22 grad moves ~0.5% from a 2- vs
|
|
497
|
+
* 9-consumer split) for BOTH bf16 AND fp32 accumulation.
|
|
498
|
+
* fp32 cross-segment accumulation does NOT fix it (it's identical at the
|
|
499
|
+
* natural cut and marginally worse at fine cuts): the dominant term is mlx's
|
|
500
|
+
* WITHIN-vjp bf16 cotangent sum per consumer, which regrouping the sharers
|
|
501
|
+
* changes — unfixable without an fp32 forward. bf16-class, fine for training.
|
|
502
|
+
* `dk`/`dv` are detached bf16 leaves; the sum is re-detached. */
|
|
503
|
+
private accumulateDKV(dKV: Map<number, KvPair>, d: number, dk: MlxArray, dv: MlxArray): void {
|
|
504
|
+
const prev = dKV.get(d);
|
|
505
|
+
if (!prev) { dKV.set(d, { keys: dk, values: dv }); return; }
|
|
506
|
+
const sk = detachLeaf(ops.add(prev.keys, dk));
|
|
507
|
+
const sv = detachLeaf(ops.add(prev.values, dv));
|
|
508
|
+
prev.keys.dispose(); prev.values.dispose(); dk.dispose(); dv.dispose();
|
|
509
|
+
dKV.set(d, { keys: sk, values: sv });
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
dispose(): void {
|
|
513
|
+
if (this.disposed) return;
|
|
514
|
+
this.disposed = true;
|
|
515
|
+
this.headVjp.dispose();
|
|
516
|
+
for (const v of this.segVjps) v.dispose();
|
|
517
|
+
for (const c of this.caches) c.dispose();
|
|
518
|
+
this.one.dispose();
|
|
519
|
+
}
|
|
520
|
+
}
|