mlx-bun 0.0.8 → 0.0.10
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 +112 -20
- package/docs/design/batching-perf-path.md +232 -0
- package/docs/design/batching-v2-plan.md +515 -0
- package/docs/design/bucketing-stage.md +196 -0
- package/docs/design/compat-cli-surface-design.md +11 -11
- package/docs/design/docs-reorg-plan.md +1 -1
- package/docs/design/dreaming-nightly-pipeline.md +291 -0
- package/docs/design/dspark-speculative-decoding.md +106 -0
- package/docs/design/generic-model-support.md +381 -0
- package/docs/design/memory-inference-path.md +422 -0
- package/docs/design/memory-synthesis.md +287 -0
- package/docs/design/minicpm5-decode-megakernel.md +484 -0
- package/docs/design/mlx-lm-tool-parity-plan.md +535 -0
- package/docs/design/omlx-adoption-map.md +90 -0
- package/docs/design/orpo-dynamic-lambda.md +41 -0
- package/docs/design/orpo-training.md +35 -5
- package/docs/design/parallel-slots.md +12 -1
- package/docs/design/parity-tier-dag.md +13 -5
- package/docs/design/segmented-backward-training.md +15 -0
- package/docs/design/spec-decode-larger-targets.md +229 -0
- package/docs/design/ssd-kv-cold-tier.md +158 -0
- package/docs/design/tauri-desktop-app.md +2 -2
- package/docs/design/the-dreaming-handoff.md +141 -0
- package/docs/design/the-dreaming-master-plan.md +446 -0
- package/docs/design/write-pipeline-entity.md +468 -0
- package/docs/investigations/curve-bisector-routing.md +1 -1
- package/docs/investigations/decode-roofline-lookagain.md +348 -0
- package/docs/investigations/dspark-handoff.md +58 -0
- package/docs/investigations/kernel-perf-review-2026-07.md +171 -0
- package/docs/planning/PRODUCT_ROADMAP.md +2 -2
- package/docs/planning/curve-sampler-research-plan.md +331 -0
- package/docs/planning/memory-docs-and-dag-plan.md +185 -0
- package/docs/planning/pi-integration-review.md +178 -0
- package/docs/planning/release-notes-v0.0.10.md +57 -0
- package/docs/planning/release-notes-v0.0.9.md +122 -0
- package/docs/planning/web-ui-pass-plan.md +213 -0
- package/docs/planning/website-readme-pass-plan.md +224 -0
- package/docs/reference/cli.md +255 -0
- package/docs/reference/embedding.md +7 -2
- package/docs/reference/library-api.md +22 -3
- package/docs/reference/memory.md +5 -5
- package/docs/reference/models.md +137 -0
- package/docs/reference/orpo-quickstart.md +6 -0
- package/docs/reference/server-api.md +187 -9
- package/docs/reference/server-config.md +43 -9
- package/docs/reference/training.md +31 -12
- package/package.json +1 -1
- package/src/anthropic.ts +22 -0
- package/src/assets/curve-designer.html +1 -1
- package/src/chat-template.ts +25 -7
- package/src/cli.ts +1077 -90
- package/src/config.ts +4 -1
- package/src/download.ts +99 -8
- package/src/embed.ts +77 -0
- package/src/eval/perplexity.ts +164 -0
- package/src/eval/runner.ts +15 -6
- package/src/eval/tasks/bfcl.ts +1 -1
- package/src/generate.ts +149 -18
- package/src/harness-pi.ts +43 -17
- package/src/index.ts +2 -0
- package/src/kv-store.ts +240 -98
- package/src/lora.ts +2 -2
- package/src/memory/article.ts +558 -0
- package/src/memory/chunk-validation.ts +150 -0
- package/src/memory/chunk.ts +305 -12
- package/src/memory/cluster.ts +344 -17
- package/src/memory/coverage.ts +134 -0
- package/src/memory/crosslink.ts +0 -0
- package/src/memory/db.ts +184 -45
- package/src/memory/entity.ts +203 -0
- package/src/memory/gate.ts +187 -0
- package/src/memory/history.ts +152 -0
- package/src/memory/ingest.ts +324 -11
- package/src/memory/model.ts +321 -0
- package/src/memory/normalize.ts +671 -0
- package/src/memory/parse.ts +22 -0
- package/src/memory/pipeline.ts +242 -30
- package/src/memory/prompts.ts +42 -0
- package/src/memory/query.ts +334 -0
- package/src/memory/reconcile.ts +734 -0
- package/src/memory/redirect.ts +108 -0
- package/src/memory/reindex.ts +325 -0
- package/src/memory/resolve.ts +307 -0
- package/src/memory/route.ts +272 -0
- package/src/memory/schedule.ts +6 -4
- package/src/memory/stages.ts +774 -0
- package/src/memory/synthesize.ts +1629 -20
- package/src/memory/tools.ts +446 -96
- package/src/memory/vault.ts +462 -35
- package/src/memory/wikify.ts +1057 -16
- package/src/mlx/array.ts +21 -0
- package/src/mlx/ffi.ts +3 -0
- package/src/mlx/ops.ts +72 -0
- package/src/model/compiled-decode.ts +43 -8
- package/src/model/factory.ts +29 -8
- package/src/model/flash-attention.ts +4 -2
- package/src/model/fused-decode-kernel.ts +14 -7
- package/src/model/fused-mlp-kernel.ts +169 -0
- package/src/model/fused-swiglu-kernel.ts +240 -0
- package/src/model/gemma4-base.ts +96 -7
- package/src/model/gemma4.ts +33 -2
- package/src/model/minicpm5.ts +106 -8
- package/src/model/qwen3-delta.ts +48 -1
- package/src/model/qwen3.ts +276 -0
- package/src/model/qwen3_5.ts +11 -2
- package/src/model/steel-linear-kernel.ts +96 -0
- package/src/model/support.ts +38 -8
- package/src/model/universal/archs.ts +332 -0
- package/src/model/universal/dense.ts +576 -0
- package/src/model/universal/modules.ts +163 -0
- package/src/model/universal/rope.ts +366 -0
- package/src/pi-provider.ts +1 -1
- package/src/pi-terminal.ts +29 -8
- package/src/pi-web.ts +19 -2
- package/src/prompt-cache.ts +55 -9
- package/src/quantize/config-writer.ts +1 -1
- package/src/registry.ts +219 -8
- package/src/responses.ts +23 -0
- package/src/sampler.ts +220 -9
- package/src/serve/batch-scheduler.ts +307 -83
- package/src/serve/generation-gateway.ts +156 -29
- package/src/server.ts +746 -73
- package/src/spec/drafter.ts +81 -49
- package/src/spec/dspark/data-dflash.ts +156 -0
- package/src/spec/dspark/data.ts +201 -0
- package/src/spec/dspark/generate-dflash.ts +193 -0
- package/src/spec/dspark/generate.ts +290 -0
- package/src/spec/dspark/loss.ts +213 -0
- package/src/spec/dspark/module-dflash.ts +361 -0
- package/src/spec/dspark/module.ts +540 -0
- package/src/spec/dspark/sample.ts +112 -0
- package/src/spec/generate.ts +40 -19
- package/src/ssd-cache.ts +213 -0
- package/src/status-page.html +1 -1
- package/src/tokenizer.ts +7 -0
- package/src/train/flash-cce.ts +81 -23
- package/src/train/fuse.ts +243 -0
- package/src/train/index.ts +3 -0
- package/src/train/job.ts +8 -0
- package/src/train/loss.ts +341 -40
- package/src/train/prefix-shared.ts +62 -11
- package/src/train/segmented.ts +215 -55
- package/src/train/trainer.ts +112 -38
- package/src/web/skills/memory/SKILL.md +40 -17
package/src/train/segmented.ts
CHANGED
|
@@ -36,14 +36,31 @@
|
|
|
36
36
|
|
|
37
37
|
import { MlxArray } from "../mlx/array";
|
|
38
38
|
import * as ops from "../mlx/ops";
|
|
39
|
+
import { peakMemory } from "../mlx/ffi";
|
|
39
40
|
import { Vjp } from "../mlx/autograd";
|
|
41
|
+
|
|
42
|
+
// MLX_BUN_SEG_MEM_LOG=1: log the within-step peak after each phase of the
|
|
43
|
+
// segmented step (forward+boundaries / head vjp / each segment backward).
|
|
44
|
+
// The trainer resets the peak at step start (MLX_BUN_MEM_LOG=1), so the phase
|
|
45
|
+
// where the monotone peak JUMPS is where the step's memory actually lives.
|
|
46
|
+
// (This is how the backlog-#2 refutation below was measured. An inter-segment
|
|
47
|
+
// clearCache was also tried and had NO effect — the watermark is live memory,
|
|
48
|
+
// not allocator cache retention.)
|
|
49
|
+
const SEG_MEM = process.env.MLX_BUN_SEG_MEM_LOG === "1";
|
|
50
|
+
const segMem = (label: string): void => {
|
|
51
|
+
if (SEG_MEM) console.log(` [seg-mem] ${label}: peak=${(peakMemory() / 1e9).toFixed(2)} GB`);
|
|
52
|
+
};
|
|
40
53
|
import { MiniCPM5Model, setMiniCpmPrefixPlan } from "../model/minicpm5";
|
|
41
54
|
import type { Gemma4Model } from "../model/gemma4";
|
|
42
55
|
import { setGemmaPrefixPlan } from "../model/gemma4";
|
|
43
56
|
import type { Cache, Mask, SharedKv } from "../model/gemma4-base";
|
|
44
57
|
import { TrainingCache } from "./forward";
|
|
45
|
-
import {
|
|
46
|
-
|
|
58
|
+
import {
|
|
59
|
+
responseOnlyLogpMean, fusedLogpMeanFromHidden, chunkedLogpMeanFromHidden,
|
|
60
|
+
orpoLossFromLogps, spanLogpMeanFromHidden, respSpanFromMask, combineFullNll,
|
|
61
|
+
type ChunkCtx, type SftScope, type HeadSpan,
|
|
62
|
+
} from "./loss";
|
|
63
|
+
import { prefixSharedCaches, prefixGatherIdx, branchLogpMeanGathered, blockSparsePrefixMaskGemma, prefixFullNll } from "./prefix-shared";
|
|
47
64
|
|
|
48
65
|
// Experiment toggle: which bounded head to use inside the segmented mlx_vjp.
|
|
49
66
|
// "checkpoint" = per-chunk Checkpoint recompute (bounds memory under nesting);
|
|
@@ -54,14 +71,43 @@ import { PrefixSharedCache, prefixGatherIdx, branchLogpMeanGathered, blockSparse
|
|
|
54
71
|
const SEG_HEAD = process.env.MLX_BUN_SEG_HEAD ?? "checkpoint";
|
|
55
72
|
function boundedHeadFromHidden(
|
|
56
73
|
model: MiniCPM5Model | Gemma4Model, h: MlxArray, ids: number[], mask: number[],
|
|
57
|
-
chunkSize: number, sink: Array<{ dispose(): void }>,
|
|
74
|
+
chunkSize: number, sink: Array<{ dispose(): void }>, span?: HeadSpan,
|
|
58
75
|
): MlxArray {
|
|
59
76
|
return SEG_HEAD === "fused"
|
|
60
|
-
? fusedLogpMeanFromHidden(model, h, ids, mask, chunkSize, sink)
|
|
61
|
-
: chunkedLogpMeanFromHidden(model, h, ids, mask, chunkSize, sink);
|
|
77
|
+
? fusedLogpMeanFromHidden(model, h, ids, mask, chunkSize, sink, 0, false, span)
|
|
78
|
+
: chunkedLogpMeanFromHidden(model, h, ids, mask, chunkSize, sink, span);
|
|
62
79
|
}
|
|
63
80
|
import type { TrainableLora } from "./lora-params";
|
|
64
|
-
import { type SftBatch, type DpoBatch } from "./dataset";
|
|
81
|
+
import { rowLength, type SftBatch, type DpoBatch } from "./dataset";
|
|
82
|
+
|
|
83
|
+
// Token-chunk size for the bounded SFT head (the ORPO paths take theirs from
|
|
84
|
+
// cfg.orpoChunkSize; SFT has no per-run knob, 512 matches the ORPO default).
|
|
85
|
+
const SFT_HEAD_CHUNK = Number(process.env.MLX_BUN_SEG_HEAD_CHUNK ?? "512");
|
|
86
|
+
|
|
87
|
+
/** [M,V]-bounded response-only CE from post-finalNorm hidden — the SFT
|
|
88
|
+
* segmented head (kernel-review backlog #8). Same span arithmetic and
|
|
89
|
+
* token-mean reduction as loss.ts responseOnlyCe, but the logits only ever
|
|
90
|
+
* exist per token-chunk ([chunk,V] Checkpoint recompute, or the fused
|
|
91
|
+
* CustomVjp head via MLX_BUN_SEG_HEAD) instead of the full [1,M,V] + its
|
|
92
|
+
* cotangent inside the head vjp. CE = −(mean response logp); per-position
|
|
93
|
+
* grads are bit-exact vs the whole-vocab head (chunking is over TOKEN
|
|
94
|
+
* positions; each position's full-V lse is computed unchanged — only the
|
|
95
|
+
* scalar loss VALUE can differ in fp association across chunks). */
|
|
96
|
+
export function boundedSftCe(
|
|
97
|
+
model: MiniCPM5Model | Gemma4Model, hn: MlxArray, batch: SftBatch,
|
|
98
|
+
sink: Array<{ dispose(): void }>, chunkSize = SFT_HEAD_CHUNK,
|
|
99
|
+
): MlxArray {
|
|
100
|
+
const L = batch.ids[0]!.length;
|
|
101
|
+
const startT = Math.max(0, batch.promptLens[0]! - 1); // first supervised input position
|
|
102
|
+
const M = Math.min(rowLength(batch, 0), L) - 1 - startT;
|
|
103
|
+
if (M <= 0) throw new Error("sftLoss: no response tokens to supervise");
|
|
104
|
+
const mean = boundedHeadFromHidden(model, hn, batch.ids[0]!, [], chunkSize, sink, { startT, M });
|
|
105
|
+
const neg = ops.mulScalar(mean, -1); // CE = −(mean logp); exact sign flip
|
|
106
|
+
mean.dispose();
|
|
107
|
+
const loss = ops.sumAxis(neg, 0, false); // [1] -> scalar, matches the vjp cotangent `one`
|
|
108
|
+
neg.dispose();
|
|
109
|
+
return loss;
|
|
110
|
+
}
|
|
65
111
|
|
|
66
112
|
/** Split `nLayers` into contiguous `[lo, hi)` ranges of at most `segSize`
|
|
67
113
|
* layers each (the last range may be shorter). `segSize` is the memory knob:
|
|
@@ -73,24 +119,50 @@ export function planSegmentsBySize(nLayers: number, segSize: number): [number, n
|
|
|
73
119
|
return ranges;
|
|
74
120
|
}
|
|
75
121
|
|
|
122
|
+
// NOTE (2026-07-02, kernel-review backlog #2 REFUTED BY MEASUREMENT): a
|
|
123
|
+
// "full-attention isolation" planner (singleton segments for e4b's 7 full
|
|
124
|
+
// layers) was built and A/B'd @8K — ZERO peak win (18.09 vs 18.02 GB). The
|
|
125
|
+
// design §5 premise is false: mlx's sdpa BACKWARD materializes the O(L²)
|
|
126
|
+
// scores for EVERY layer (~3.5 GB/layer @8K on e4b — sliding's window is just
|
|
127
|
+
// an additive mask; measured: sliding pair +7.1 GB ≈ sliding+full +7.15). The
|
|
128
|
+
// worst segment is set by LAYER COUNT alone, so segSize IS the whole knob:
|
|
129
|
+
// segment_size 1 measures 14.59 GB @8K (+3% step time) vs 17.5 @seg2. Below
|
|
130
|
+
// that needs the [M,V]-bounded SFT head (backlog #8, ~3 GB head-vjp spike)
|
|
131
|
+
// and/or an O(L)-memory attention backward. Evidence:
|
|
132
|
+
// scripts/experiments/seg-isolation-smoke.ts + MLX_BUN_SEG_MEM_LOG=1 probes.
|
|
133
|
+
|
|
76
134
|
/** Detach `a` into a graph-free LEAF holding its exact (evaluated) bytes, then
|
|
77
135
|
* dispose `a`. mlx retains the upstream graph after `eval`, so a segment
|
|
78
136
|
* boundary (or a value_and_grad output) would otherwise keep its whole layer
|
|
79
137
|
* stack's activations alive. Copying the bytes into a fresh leaf frees that
|
|
80
138
|
* graph. The leaf is a valid differentiable input for the per-segment vag. */
|
|
81
139
|
function detachLeaf(a: MlxArray): MlxArray {
|
|
82
|
-
// Force a row-major copy first:
|
|
83
|
-
//
|
|
84
|
-
//
|
|
140
|
+
// Force a row-major copy first: the buffer is read linearly, so a
|
|
141
|
+
// NON-contiguous input (e.g. a transposed donor K/V view) would be
|
|
142
|
+
// byte-scrambled. `ops.contiguous` is a no-op for already-contiguous arrays
|
|
85
143
|
// (hidden boundaries) and materializes row-major for strided views.
|
|
144
|
+
// detachCopy = ONE mlx→mlx copy (backlog #9: the old
|
|
145
|
+
// fromBytesCopy(rawBytes()) route was two copies via the JS heap).
|
|
86
146
|
const c = ops.contiguous(a);
|
|
87
|
-
c.
|
|
88
|
-
const leaf = MlxArray.fromBytesCopy(c.rawBytes(), c.shape, c.dtype);
|
|
147
|
+
const leaf = c.detachCopy();
|
|
89
148
|
c.dispose();
|
|
90
149
|
a.dispose();
|
|
91
150
|
return leaf;
|
|
92
151
|
}
|
|
93
152
|
|
|
153
|
+
/** Batch detach: ONE evalAll barrier for the whole set (backlog #9 — each
|
|
154
|
+
* per-array eval was a separate blocking GPU drain; a segment's vjp returns
|
|
155
|
+
* dh + 2·consumed-donors + 2·targets arrays, so the per-segment sync count
|
|
156
|
+
* collapses from ~2+2n to 1), then one mlx→mlx copy each. Disposes inputs. */
|
|
157
|
+
function detachLeaves(arrs: MlxArray[]): MlxArray[] {
|
|
158
|
+
const cs = arrs.map((a) => ops.contiguous(a));
|
|
159
|
+
ops.evalAll(cs);
|
|
160
|
+
const leaves = cs.map((c) => c.detachCopy());
|
|
161
|
+
for (const c of cs) c.dispose();
|
|
162
|
+
for (const a of arrs) a.dispose();
|
|
163
|
+
return leaves;
|
|
164
|
+
}
|
|
165
|
+
|
|
94
166
|
/** Layer index a target's modulePath belongs to, or -1 (e.g. embedding/head). */
|
|
95
167
|
function targetLayer(modulePath: string): number {
|
|
96
168
|
const m = modulePath.match(/\.layers\.(\d+)\./);
|
|
@@ -116,6 +188,10 @@ export class SegmentedBackward {
|
|
|
116
188
|
private readonly one: MlxArray; // scalar cotangent 1.0 for the (scalar) head loss
|
|
117
189
|
// Per-step context the (reused) closures read.
|
|
118
190
|
private curBatch: SftBatch | null = null;
|
|
191
|
+
// Bounded-head per-step transients (chunk slices + Checkpoint/CustomVjp
|
|
192
|
+
// objects): the head backward recomputes through them, so they are disposed
|
|
193
|
+
// only AFTER the head vjp's outputs are detached (which forces the eval).
|
|
194
|
+
private headSink: Array<{ dispose(): void }> = [];
|
|
119
195
|
private disposed = false;
|
|
120
196
|
|
|
121
197
|
constructor(
|
|
@@ -136,11 +212,17 @@ export class SegmentedBackward {
|
|
|
136
212
|
this.caches = Array.from({ length: this.nLayers }, () => new TrainingCache());
|
|
137
213
|
this.one = MlxArray.fromFloat32(new Float32Array([1]), []); // d(loss)/d(loss) = 1
|
|
138
214
|
|
|
139
|
-
// Loss head: vjp of finalNorm +
|
|
215
|
+
// Loss head: vjp of finalNorm + the [M,V]-BOUNDED response CE over the last
|
|
216
|
+
// boundary leaf (kernel-review backlog #8: the old responseOnlyCe here
|
|
217
|
+
// materialized [1,M,V] logits + cotangents inside the head vjp — measured
|
|
218
|
+
// ~3 GB @8K on e4b, the exact transient the segmentation exists to kill).
|
|
219
|
+
// CE = −(mean response logp); span and reduction identical to
|
|
220
|
+
// responseOnlyCe, per-position grads bit-exact (chunking is over token
|
|
221
|
+
// positions — each position's full-V lse/softmax is computed unchanged).
|
|
140
222
|
// outputs[0] = loss scalar; vjps[0] = dLoss/d(last residual stream) = dh.
|
|
141
223
|
this.headVjp = new Vjp((p) => {
|
|
142
224
|
const hn = model.finalNorm.forward(p[0]!);
|
|
143
|
-
const loss =
|
|
225
|
+
const loss = boundedSftCe(model, hn, this.curBatch!, this.headSink);
|
|
144
226
|
hn.dispose();
|
|
145
227
|
return [loss];
|
|
146
228
|
}, 1);
|
|
@@ -200,9 +282,17 @@ export class SegmentedBackward {
|
|
|
200
282
|
}
|
|
201
283
|
|
|
202
284
|
// --- 2. Loss head over the last boundary (vjp with cotangent 1.0). ---
|
|
285
|
+
// The bounded head pushes per-step chunk slices + Checkpoints into
|
|
286
|
+
// headSink; the head BACKWARD recomputes through them, so materialize
|
|
287
|
+
// the full head-vjp graph BEFORE freeing the sink (lazy-eval UAF
|
|
288
|
+
// discipline, same as the ORPO classes).
|
|
289
|
+
this.headSink = [];
|
|
203
290
|
const head = this.headVjp.apply([boundaries[nSeg]!], [this.one]);
|
|
291
|
+
ops.evalAll([head.outputs[0]!, head.vjps[0]!]);
|
|
204
292
|
value = detachLeaf(head.outputs[0]!); // scalar loss, detached from the head graph (frees logits)
|
|
205
293
|
dhOut = detachLeaf(head.vjps[0]!); // dLoss / d(last residual stream), graph-free
|
|
294
|
+
for (const d of this.headSink) d.dispose();
|
|
295
|
+
this.headSink = [];
|
|
206
296
|
boundaries[nSeg]!.dispose();
|
|
207
297
|
boundaries[nSeg] = null;
|
|
208
298
|
|
|
@@ -216,13 +306,15 @@ export class SegmentedBackward {
|
|
|
216
306
|
res.outputs[0]!.dispose(); // segment output — unused (only its vjp matters)
|
|
217
307
|
// vjps[0] = dh_in (grad w.r.t. this segment's input boundary) -> dh_out
|
|
218
308
|
// for segment k-1; the rest scatter into the global flat vector. Detach
|
|
219
|
-
//
|
|
309
|
+
// to graph-free leaves (eval doesn't detach; keeps peak bounded) — ONE
|
|
310
|
+
// barrier for the segment's whole vjp set.
|
|
311
|
+
const leaves = detachLeaves(res.vjps);
|
|
220
312
|
idxs.forEach((j, s) => {
|
|
221
|
-
grads[j] =
|
|
222
|
-
grads[n + j] =
|
|
313
|
+
grads[j] = leaves[1 + 2 * s]!; // dA
|
|
314
|
+
grads[n + j] = leaves[1 + 2 * s + 1]!; // dB
|
|
223
315
|
});
|
|
224
316
|
dhOut!.dispose(); // consumed as this segment's cotangent
|
|
225
|
-
dhOut =
|
|
317
|
+
dhOut = leaves[0]!;
|
|
226
318
|
boundaries[k]!.dispose();
|
|
227
319
|
boundaries[k] = null;
|
|
228
320
|
}
|
|
@@ -234,6 +326,8 @@ export class SegmentedBackward {
|
|
|
234
326
|
} finally {
|
|
235
327
|
inputIds.dispose();
|
|
236
328
|
this.curBatch = null;
|
|
329
|
+
for (const d of this.headSink) d.dispose(); // no-op unless the head threw mid-step
|
|
330
|
+
this.headSink = [];
|
|
237
331
|
dhOut?.dispose();
|
|
238
332
|
for (const b of boundaries) b?.dispose();
|
|
239
333
|
if (!transferred) {
|
|
@@ -291,6 +385,8 @@ export class SegmentedBackwardGemma4 {
|
|
|
291
385
|
private curBatch: SftBatch | null = null;
|
|
292
386
|
private curMasks: Map<string, Mask> | null = null;
|
|
293
387
|
private curPerLayer: MlxArray | null = null;
|
|
388
|
+
// Bounded-head per-step transients — see SegmentedBackward.headSink.
|
|
389
|
+
private headSink: Array<{ dispose(): void }> = [];
|
|
294
390
|
private disposed = false;
|
|
295
391
|
|
|
296
392
|
constructor(
|
|
@@ -333,11 +429,11 @@ export class SegmentedBackwardGemma4 {
|
|
|
333
429
|
this.caches = Array.from({ length: model.numDonors }, () => new TrainingCache());
|
|
334
430
|
this.one = MlxArray.fromFloat32(new Float32Array([1]), []);
|
|
335
431
|
|
|
336
|
-
// Loss head (identical to MiniCPM5): finalNorm +
|
|
432
|
+
// Loss head (identical to MiniCPM5): finalNorm + bounded response CE over the last
|
|
337
433
|
// boundary, vjp with cotangent 1.0 -> [loss], [dh].
|
|
338
434
|
this.headVjp = new Vjp((p) => {
|
|
339
435
|
const hn = model.finalNorm.forward(p[0]!);
|
|
340
|
-
const loss =
|
|
436
|
+
const loss = boundedSftCe(model, hn, this.curBatch!, this.headSink);
|
|
341
437
|
hn.dispose();
|
|
342
438
|
return [loss];
|
|
343
439
|
}, 1);
|
|
@@ -433,14 +529,22 @@ export class SegmentedBackwardGemma4 {
|
|
|
433
529
|
for (const [d, kv] of donorKvOut)
|
|
434
530
|
if (!produces[k]!.includes(d) && kv.kind === "plain") { kv.keys.dispose(); kv.values.dispose(); }
|
|
435
531
|
boundaries.push(detachLeaf(h));
|
|
532
|
+
segMem(`fwd seg ${k} [${lo},${hi})`);
|
|
436
533
|
}
|
|
437
534
|
|
|
438
|
-
// --- 2. Loss head (vjp, cotangent 1.0).
|
|
535
|
+
// --- 2. Loss head (vjp, cotangent 1.0). Bounded-head sink discipline:
|
|
536
|
+
// materialize the head-vjp graph BEFORE freeing the sink (see the
|
|
537
|
+
// MiniCPM5 class / ORPO classes — lazy-eval UAF). ---
|
|
538
|
+
this.headSink = [];
|
|
439
539
|
const head = this.headVjp.apply([boundaries[nSeg]!], [this.one]);
|
|
540
|
+
ops.evalAll([head.outputs[0]!, head.vjps[0]!]);
|
|
440
541
|
value = detachLeaf(head.outputs[0]!);
|
|
441
542
|
dhOut = detachLeaf(head.vjps[0]!);
|
|
543
|
+
for (const d of this.headSink) d.dispose();
|
|
544
|
+
this.headSink = [];
|
|
442
545
|
boundaries[nSeg]!.dispose();
|
|
443
546
|
boundaries[nSeg] = null;
|
|
547
|
+
segMem("head vjp");
|
|
444
548
|
|
|
445
549
|
// --- 3. Backward, reverse over segments (vjp, cotangents dh + dKV). ---
|
|
446
550
|
for (let k = nSeg - 1; k >= 0; k--) {
|
|
@@ -462,17 +566,17 @@ export class SegmentedBackwardGemma4 {
|
|
|
462
566
|
const res = this.segVjps[k]!.apply(primals, cots);
|
|
463
567
|
for (const o of res.outputs) o.dispose();
|
|
464
568
|
|
|
465
|
-
// vjps[0] = dh_in; then (dk,dv) per consumed donor; then (da,db) per
|
|
466
|
-
|
|
569
|
+
// vjps[0] = dh_in; then (dk,dv) per consumed donor; then (da,db) per
|
|
570
|
+
// LoRA — detached in ONE barrier (backlog #9).
|
|
571
|
+
const leaves = detachLeaves(res.vjps);
|
|
572
|
+
const newDh = leaves[0]!;
|
|
467
573
|
cons.forEach((d, ci) => {
|
|
468
|
-
|
|
469
|
-
const dv = detachLeaf(res.vjps[1 + 2 * ci + 1]!);
|
|
470
|
-
this.accumulateDKV(dKV, d, dk, dv);
|
|
574
|
+
this.accumulateDKV(dKV, d, leaves[1 + 2 * ci]!, leaves[1 + 2 * ci + 1]!);
|
|
471
575
|
});
|
|
472
576
|
const base = 1 + 2 * cons.length;
|
|
473
577
|
idxs.forEach((j, s) => {
|
|
474
|
-
grads[j] =
|
|
475
|
-
grads[n + j] =
|
|
578
|
+
grads[j] = leaves[base + 2 * s]!;
|
|
579
|
+
grads[n + j] = leaves[base + 2 * s + 1]!;
|
|
476
580
|
});
|
|
477
581
|
|
|
478
582
|
dh.dispose();
|
|
@@ -484,6 +588,7 @@ export class SegmentedBackwardGemma4 {
|
|
|
484
588
|
}
|
|
485
589
|
boundaries[k]!.dispose();
|
|
486
590
|
boundaries[k] = null;
|
|
591
|
+
segMem(`bwd seg ${k} [${ranges[k]![0]},${ranges[k]![1]})`);
|
|
487
592
|
}
|
|
488
593
|
|
|
489
594
|
transferred = true;
|
|
@@ -494,6 +599,8 @@ export class SegmentedBackwardGemma4 {
|
|
|
494
599
|
this.curPerLayer?.dispose();
|
|
495
600
|
this.curPerLayer = null;
|
|
496
601
|
if (this.curMasks) { for (const m of this.curMasks.values()) m.arr?.dispose(); this.curMasks = null; }
|
|
602
|
+
for (const d of this.headSink) d.dispose(); // no-op unless the head threw mid-step
|
|
603
|
+
this.headSink = [];
|
|
497
604
|
dhOut?.dispose();
|
|
498
605
|
for (const b of boundaries) b?.dispose();
|
|
499
606
|
for (const kv of donorBnd.values()) { kv.keys.dispose(); kv.values.dispose(); }
|
|
@@ -582,6 +689,7 @@ export class SegmentedBackwardOrpo {
|
|
|
582
689
|
private readonly ranges: [number, number][],
|
|
583
690
|
private readonly lambda: number,
|
|
584
691
|
private readonly fusedChunkSize = 0,
|
|
692
|
+
private readonly sftScope: SftScope = "response",
|
|
585
693
|
) {
|
|
586
694
|
this.nLayers = model.layers.length;
|
|
587
695
|
const lastHi = ranges[ranges.length - 1]?.[1] ?? 0;
|
|
@@ -609,9 +717,26 @@ export class SegmentedBackwardOrpo {
|
|
|
609
717
|
const lr = cs > 0 // [1]
|
|
610
718
|
? boundedHeadFromHidden(model, hnR, this.curRejectedIds!, this.curRejectedMask!, cs, this.headSink)
|
|
611
719
|
: responseOnlyLogpMean(model, hnR, this.curRejectedIds!, this.curRejectedMask!);
|
|
720
|
+
// sft_scope:"full": the chosen head additionally runs over the PROMPT span
|
|
721
|
+
// [0, startT) of the SAME hnC (same tier: bounded or whole-vocab), and the
|
|
722
|
+
// full-scope chosen NLL replaces mean(-ℓw). The prompt-position gradient
|
|
723
|
+
// rides into this vjp's dh_chosen automatically (the loss graph includes
|
|
724
|
+
// the prompt-span head). ℓw/ℓr stay response-only for the odds ratio.
|
|
725
|
+
let nllFull: MlxArray | null = null;
|
|
726
|
+
if (this.sftScope === "full") {
|
|
727
|
+
const { startT, M } = respSpanFromMask(this.curChosenMask!, this.curChosenIds!.length - 1);
|
|
728
|
+
let pm: MlxArray | null = null;
|
|
729
|
+
if (M > 0 && startT > 0)
|
|
730
|
+
pm = cs > 0
|
|
731
|
+
? boundedHeadFromHidden(model, hnC, this.curChosenIds!, this.curChosenMask!, cs, this.headSink, { startT: 0, M: startT })
|
|
732
|
+
: spanLogpMeanFromHidden(model, hnC, this.curChosenIds!, 0, startT);
|
|
733
|
+
nllFull = combineFullNll(pm, lw, M > 0 ? startT : 0, M);
|
|
734
|
+
pm?.dispose();
|
|
735
|
+
}
|
|
612
736
|
hnC.dispose(); hnR.dispose();
|
|
613
|
-
const loss = orpoLossFromLogps(lw, lr, this.lambda);
|
|
737
|
+
const loss = orpoLossFromLogps(lw, lr, this.lambda, nllFull ?? undefined);
|
|
614
738
|
lw.dispose(); lr.dispose();
|
|
739
|
+
nllFull?.dispose();
|
|
615
740
|
return [loss];
|
|
616
741
|
}, 1);
|
|
617
742
|
|
|
@@ -703,12 +828,13 @@ export class SegmentedBackwardOrpo {
|
|
|
703
828
|
for (const j of idxs) params.push(lora.targets[j]!.lw.a, lora.targets[j]!.lw.b);
|
|
704
829
|
const res = this.segVjps[k]!.apply(params, [dhC!]);
|
|
705
830
|
res.outputs[0]!.dispose();
|
|
831
|
+
const leaves = detachLeaves(res.vjps); // one barrier (backlog #9)
|
|
706
832
|
idxs.forEach((j, s) => {
|
|
707
|
-
gradsC[j] =
|
|
708
|
-
gradsC[n + j] =
|
|
833
|
+
gradsC[j] = leaves[1 + 2 * s]!; // dA
|
|
834
|
+
gradsC[n + j] = leaves[1 + 2 * s + 1]!; // dB
|
|
709
835
|
});
|
|
710
836
|
dhC!.dispose();
|
|
711
|
-
dhC =
|
|
837
|
+
dhC = leaves[0]!;
|
|
712
838
|
bndC[k]!.dispose(); bndC[k] = null;
|
|
713
839
|
}
|
|
714
840
|
dhC!.dispose(); dhC = null; // embedding grad, discarded (no LoRA on embed)
|
|
@@ -720,12 +846,13 @@ export class SegmentedBackwardOrpo {
|
|
|
720
846
|
for (const j of idxs) params.push(lora.targets[j]!.lw.a, lora.targets[j]!.lw.b);
|
|
721
847
|
const res = this.segVjps[k]!.apply(params, [dhR!]);
|
|
722
848
|
res.outputs[0]!.dispose();
|
|
849
|
+
const leaves = detachLeaves(res.vjps); // one barrier (backlog #9)
|
|
723
850
|
idxs.forEach((j, s) => {
|
|
724
|
-
gradsR[j] =
|
|
725
|
-
gradsR[n + j] =
|
|
851
|
+
gradsR[j] = leaves[1 + 2 * s]!; // dA
|
|
852
|
+
gradsR[n + j] = leaves[1 + 2 * s + 1]!; // dB
|
|
726
853
|
});
|
|
727
854
|
dhR!.dispose();
|
|
728
|
-
dhR =
|
|
855
|
+
dhR = leaves[0]!;
|
|
729
856
|
bndR[k]!.dispose(); bndR[k] = null;
|
|
730
857
|
}
|
|
731
858
|
dhR!.dispose(); dhR = null; // embedding grad, discarded
|
|
@@ -801,6 +928,7 @@ export class SegmentedBackwardOrpoPrefix {
|
|
|
801
928
|
private curP = 0;
|
|
802
929
|
private curRc = 0;
|
|
803
930
|
private curRr = 0;
|
|
931
|
+
private curPromptIds: number[] | null = null;
|
|
804
932
|
private curChosenResp: number[] | null = null;
|
|
805
933
|
private curRejectedResp: number[] | null = null;
|
|
806
934
|
private curChunk: ChunkCtx | undefined = undefined;
|
|
@@ -816,6 +944,7 @@ export class SegmentedBackwardOrpoPrefix {
|
|
|
816
944
|
private readonly ranges: [number, number][],
|
|
817
945
|
private readonly lambda: number,
|
|
818
946
|
private readonly chunkCtx?: ChunkCtx,
|
|
947
|
+
private readonly sftScope: SftScope = "response",
|
|
819
948
|
) {
|
|
820
949
|
this.nLayers = model.layers.length;
|
|
821
950
|
const lastHi = ranges[ranges.length - 1]?.[1] ?? 0;
|
|
@@ -838,9 +967,16 @@ export class SegmentedBackwardOrpoPrefix {
|
|
|
838
967
|
const { chosenIdx, rejectedIdx } = prefixGatherIdx(this.curP, this.curRc, this.curRr);
|
|
839
968
|
const lw = branchLogpMeanGathered(model, hn, chosenIdx, this.curChosenResp!, this.curChunk);
|
|
840
969
|
const lr = branchLogpMeanGathered(model, hn, rejectedIdx, this.curRejectedResp!, this.curChunk);
|
|
970
|
+
// sft_scope:"full": prompt predictions (H[0..P-2] → prompt[1..P-1]) from the
|
|
971
|
+
// same concat hiddens; the gather's takeAxis vjp scatter-adds the prompt
|
|
972
|
+
// cotangent into dh alongside the responses'. ℓw/ℓr stay response-only.
|
|
973
|
+
const nllFull = this.sftScope === "full"
|
|
974
|
+
? prefixFullNll(model, hn, this.curPromptIds!, lw, this.curRc, this.curChunk)
|
|
975
|
+
: null;
|
|
841
976
|
hn.dispose();
|
|
842
|
-
const loss = orpoLossFromLogps(lw, lr, this.lambda);
|
|
977
|
+
const loss = orpoLossFromLogps(lw, lr, this.lambda, nllFull ?? undefined);
|
|
843
978
|
lw.dispose(); lr.dispose();
|
|
979
|
+
nllFull?.dispose();
|
|
844
980
|
return [loss];
|
|
845
981
|
}, 1);
|
|
846
982
|
|
|
@@ -873,6 +1009,7 @@ export class SegmentedBackwardOrpoPrefix {
|
|
|
873
1009
|
if (P < 1 || Rc < 1 || Rr < 1) throw new Error("SegmentedBackwardOrpoPrefix: need P,Rc,Rr >= 1");
|
|
874
1010
|
const T = P + Rc + Rr;
|
|
875
1011
|
this.curP = P; this.curRc = Rc; this.curRr = Rr;
|
|
1012
|
+
this.curPromptIds = promptIds;
|
|
876
1013
|
this.curChosenResp = chosenResp; this.curRejectedResp = rejectedResp;
|
|
877
1014
|
// Per-step chunk ctx pointing the head's CustomVjp/slice disposables at this
|
|
878
1015
|
// step's headSink (the flash/fused head recompute runs during headVjp.apply;
|
|
@@ -880,7 +1017,7 @@ export class SegmentedBackwardOrpoPrefix {
|
|
|
880
1017
|
this.headSink = [];
|
|
881
1018
|
this.curChunk = this.chunkCtx ? { ...this.chunkCtx, sink: this.headSink } : undefined;
|
|
882
1019
|
// One prefix cache per layer (captures P/Rc/Rr; makeMask -> block-sparse mask).
|
|
883
|
-
this.curCaches = model.layers.
|
|
1020
|
+
this.curCaches = prefixSharedCaches(model.layers.length, P, Rc, Rr);
|
|
884
1021
|
|
|
885
1022
|
const nSeg = ranges.length;
|
|
886
1023
|
const concat = new Int32Array(T);
|
|
@@ -925,12 +1062,13 @@ export class SegmentedBackwardOrpoPrefix {
|
|
|
925
1062
|
for (const j of idxs) params.push(lora.targets[j]!.lw.a, lora.targets[j]!.lw.b);
|
|
926
1063
|
const res = this.segVjps[k]!.apply(params, [dhOut!]);
|
|
927
1064
|
res.outputs[0]!.dispose();
|
|
1065
|
+
const leaves = detachLeaves(res.vjps); // one barrier (backlog #9)
|
|
928
1066
|
idxs.forEach((j, s) => {
|
|
929
|
-
grads[j] =
|
|
930
|
-
grads[n + j] =
|
|
1067
|
+
grads[j] = leaves[1 + 2 * s]!; // dA
|
|
1068
|
+
grads[n + j] = leaves[1 + 2 * s + 1]!; // dB
|
|
931
1069
|
});
|
|
932
1070
|
dhOut!.dispose();
|
|
933
|
-
dhOut =
|
|
1071
|
+
dhOut = leaves[0]!;
|
|
934
1072
|
boundaries[k]!.dispose(); boundaries[k] = null;
|
|
935
1073
|
}
|
|
936
1074
|
// Segment-0 dh_in = grad w.r.t. the embedding output (no LoRA on embed) — drop.
|
|
@@ -942,7 +1080,7 @@ export class SegmentedBackwardOrpoPrefix {
|
|
|
942
1080
|
inputIds.dispose();
|
|
943
1081
|
for (const c of this.curCaches) c.dispose();
|
|
944
1082
|
this.curCaches = [];
|
|
945
|
-
this.curChosenResp = this.curRejectedResp = null;
|
|
1083
|
+
this.curPromptIds = this.curChosenResp = this.curRejectedResp = null;
|
|
946
1084
|
for (const d of this.headSink) d.dispose();
|
|
947
1085
|
this.headSink = [];
|
|
948
1086
|
dhOut?.dispose();
|
|
@@ -1024,6 +1162,7 @@ export class SegmentedBackwardOrpoGemma4 {
|
|
|
1024
1162
|
private readonly ranges: [number, number][],
|
|
1025
1163
|
private readonly lambda: number,
|
|
1026
1164
|
private readonly fusedChunkSize = 0,
|
|
1165
|
+
private readonly sftScope: SftScope = "response",
|
|
1027
1166
|
) {
|
|
1028
1167
|
this.nLayers = model.layers.length;
|
|
1029
1168
|
const lastHi = ranges[ranges.length - 1]?.[1] ?? 0;
|
|
@@ -1072,9 +1211,23 @@ export class SegmentedBackwardOrpoGemma4 {
|
|
|
1072
1211
|
const lr = cs > 0
|
|
1073
1212
|
? boundedHeadFromHidden(this.model, hnR, this.curRejectedIds!, this.curRejectedMask!, cs, this.headSink)
|
|
1074
1213
|
: responseOnlyLogpMean(this.model, hnR, this.curRejectedIds!, this.curRejectedMask!);
|
|
1214
|
+
// sft_scope:"full": prompt-span head over the same hnC (see
|
|
1215
|
+
// SegmentedBackwardOrpo); ℓw/ℓr stay response-only for the odds ratio.
|
|
1216
|
+
let nllFull: MlxArray | null = null;
|
|
1217
|
+
if (this.sftScope === "full") {
|
|
1218
|
+
const { startT, M } = respSpanFromMask(this.curChosenMask!, this.curChosenIds!.length - 1);
|
|
1219
|
+
let pm: MlxArray | null = null;
|
|
1220
|
+
if (M > 0 && startT > 0)
|
|
1221
|
+
pm = cs > 0
|
|
1222
|
+
? boundedHeadFromHidden(this.model, hnC, this.curChosenIds!, this.curChosenMask!, cs, this.headSink, { startT: 0, M: startT })
|
|
1223
|
+
: spanLogpMeanFromHidden(this.model, hnC, this.curChosenIds!, 0, startT);
|
|
1224
|
+
nllFull = combineFullNll(pm, lw, M > 0 ? startT : 0, M);
|
|
1225
|
+
pm?.dispose();
|
|
1226
|
+
}
|
|
1075
1227
|
hnC.dispose(); hnR.dispose();
|
|
1076
|
-
const loss = orpoLossFromLogps(lw, lr, this.lambda);
|
|
1228
|
+
const loss = orpoLossFromLogps(lw, lr, this.lambda, nllFull ?? undefined);
|
|
1077
1229
|
lw.dispose(); lr.dispose();
|
|
1230
|
+
nllFull?.dispose();
|
|
1078
1231
|
return [loss];
|
|
1079
1232
|
}, 1);
|
|
1080
1233
|
|
|
@@ -1185,16 +1338,15 @@ export class SegmentedBackwardOrpoGemma4 {
|
|
|
1185
1338
|
const res = this.segVjps[k]!.apply(primals, cots);
|
|
1186
1339
|
for (const o of res.outputs) o.dispose();
|
|
1187
1340
|
|
|
1188
|
-
const
|
|
1341
|
+
const leaves = detachLeaves(res.vjps); // one barrier (backlog #9)
|
|
1342
|
+
const newDh = leaves[0]!;
|
|
1189
1343
|
cons.forEach((d, ci) => {
|
|
1190
|
-
|
|
1191
|
-
const dv = detachLeaf(res.vjps[1 + 2 * ci + 1]!);
|
|
1192
|
-
this.accumulateDKV(dKV, d, dk, dv);
|
|
1344
|
+
this.accumulateDKV(dKV, d, leaves[1 + 2 * ci]!, leaves[1 + 2 * ci + 1]!);
|
|
1193
1345
|
});
|
|
1194
1346
|
const base = 1 + 2 * cons.length;
|
|
1195
1347
|
idxs.forEach((j, s) => {
|
|
1196
|
-
grads[j] =
|
|
1197
|
-
grads[n + j] =
|
|
1348
|
+
grads[j] = leaves[base + 2 * s]!;
|
|
1349
|
+
grads[n + j] = leaves[base + 2 * s + 1]!;
|
|
1198
1350
|
});
|
|
1199
1351
|
|
|
1200
1352
|
dh.dispose();
|
|
@@ -1394,6 +1546,7 @@ export class SegmentedBackwardOrpoPrefixGemma4 {
|
|
|
1394
1546
|
private curP = 0;
|
|
1395
1547
|
private curRc = 0;
|
|
1396
1548
|
private curRr = 0;
|
|
1549
|
+
private curPromptIds: number[] | null = null;
|
|
1397
1550
|
private curChosenResp: number[] | null = null;
|
|
1398
1551
|
private curRejectedResp: number[] | null = null;
|
|
1399
1552
|
private curChunk: ChunkCtx | undefined = undefined;
|
|
@@ -1408,6 +1561,7 @@ export class SegmentedBackwardOrpoPrefixGemma4 {
|
|
|
1408
1561
|
private readonly ranges: [number, number][],
|
|
1409
1562
|
private readonly lambda: number,
|
|
1410
1563
|
private readonly chunkCtx?: ChunkCtx,
|
|
1564
|
+
private readonly sftScope: SftScope = "response",
|
|
1411
1565
|
) {
|
|
1412
1566
|
this.nLayers = model.layers.length;
|
|
1413
1567
|
const lastHi = ranges[ranges.length - 1]?.[1] ?? 0;
|
|
@@ -1455,9 +1609,15 @@ export class SegmentedBackwardOrpoPrefixGemma4 {
|
|
|
1455
1609
|
const { chosenIdx, rejectedIdx } = prefixGatherIdx(this.curP, this.curRc, this.curRr);
|
|
1456
1610
|
const lw = branchLogpMeanGathered(model, hn, chosenIdx, this.curChosenResp!, this.curChunk);
|
|
1457
1611
|
const lr = branchLogpMeanGathered(model, hn, rejectedIdx, this.curRejectedResp!, this.curChunk);
|
|
1612
|
+
// sft_scope:"full": prompt predictions from the same concat hiddens (see
|
|
1613
|
+
// SegmentedBackwardOrpoPrefix); ℓw/ℓr stay response-only.
|
|
1614
|
+
const nllFull = this.sftScope === "full"
|
|
1615
|
+
? prefixFullNll(model, hn, this.curPromptIds!, lw, this.curRc, this.curChunk)
|
|
1616
|
+
: null;
|
|
1458
1617
|
hn.dispose();
|
|
1459
|
-
const loss = orpoLossFromLogps(lw, lr, this.lambda);
|
|
1618
|
+
const loss = orpoLossFromLogps(lw, lr, this.lambda, nllFull ?? undefined);
|
|
1460
1619
|
lw.dispose(); lr.dispose();
|
|
1620
|
+
nllFull?.dispose();
|
|
1461
1621
|
return [loss];
|
|
1462
1622
|
}, 1);
|
|
1463
1623
|
|
|
@@ -1525,6 +1685,7 @@ export class SegmentedBackwardOrpoPrefixGemma4 {
|
|
|
1525
1685
|
if (P < 1 || Rc < 1 || Rr < 1) throw new Error("SegmentedBackwardOrpoPrefixGemma4: need P,Rc,Rr >= 1");
|
|
1526
1686
|
const T = P + Rc + Rr;
|
|
1527
1687
|
this.curP = P; this.curRc = Rc; this.curRr = Rr;
|
|
1688
|
+
this.curPromptIds = promptIds;
|
|
1528
1689
|
this.curChosenResp = chosenResp; this.curRejectedResp = rejectedResp;
|
|
1529
1690
|
this.headSink = [];
|
|
1530
1691
|
this.curChunk = this.chunkCtx ? { ...this.chunkCtx, sink: this.headSink } : undefined;
|
|
@@ -1607,16 +1768,15 @@ export class SegmentedBackwardOrpoPrefixGemma4 {
|
|
|
1607
1768
|
const res = this.segVjps[k]!.apply(primals, cots);
|
|
1608
1769
|
for (const o of res.outputs) o.dispose();
|
|
1609
1770
|
|
|
1610
|
-
const
|
|
1771
|
+
const leaves = detachLeaves(res.vjps); // one barrier (backlog #9)
|
|
1772
|
+
const newDh = leaves[0]!;
|
|
1611
1773
|
cons.forEach((d, ci) => {
|
|
1612
|
-
|
|
1613
|
-
const dv = detachLeaf(res.vjps[1 + 2 * ci + 1]!);
|
|
1614
|
-
this.accumulateDKV(dKV, d, dk, dv);
|
|
1774
|
+
this.accumulateDKV(dKV, d, leaves[1 + 2 * ci]!, leaves[1 + 2 * ci + 1]!);
|
|
1615
1775
|
});
|
|
1616
1776
|
const base = 1 + 2 * cons.length;
|
|
1617
1777
|
idxs.forEach((j, s) => {
|
|
1618
|
-
grads[j] =
|
|
1619
|
-
grads[n + j] =
|
|
1778
|
+
grads[j] = leaves[base + 2 * s]!;
|
|
1779
|
+
grads[n + j] = leaves[base + 2 * s + 1]!;
|
|
1620
1780
|
});
|
|
1621
1781
|
|
|
1622
1782
|
dh.dispose();
|
|
@@ -1640,7 +1800,7 @@ export class SegmentedBackwardOrpoPrefixGemma4 {
|
|
|
1640
1800
|
this.curPerLayer?.dispose();
|
|
1641
1801
|
this.curPerLayer = null;
|
|
1642
1802
|
if (this.curMasks) { for (const m of this.curMasks.values()) m.arr?.dispose(); this.curMasks = null; }
|
|
1643
|
-
this.curChosenResp = this.curRejectedResp = null;
|
|
1803
|
+
this.curPromptIds = this.curChosenResp = this.curRejectedResp = null;
|
|
1644
1804
|
for (const d of this.headSink) d.dispose();
|
|
1645
1805
|
this.headSink = [];
|
|
1646
1806
|
dhOut?.dispose();
|