mlx-bun 0.0.7 → 0.0.9
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-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/diffusion-gemma-port.md +602 -0
- 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/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/spec-decode-larger-targets.md +229 -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 +262 -0
- package/docs/investigations/dspark-handoff.md +58 -0
- package/docs/investigations/kernel-perf-review-2026-07.md +92 -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.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 +40 -9
- package/docs/reference/training.md +25 -11
- 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 +1029 -86
- package/src/config.ts +54 -3
- package/src/diffusion/diffusion-generate.ts +436 -0
- package/src/download.ts +99 -8
- package/src/embed.ts +77 -0
- package/src/eval/perplexity.ts +164 -0
- package/src/eval/runner.ts +19 -7
- package/src/eval/tasks/bfcl.ts +1 -1
- package/src/generate.ts +196 -19
- package/src/harness-pi.ts +43 -17
- package/src/index.ts +2 -0
- 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/ffi.ts +11 -0
- package/src/mlx/ops.ts +113 -0
- package/src/model/compiled-decode.ts +43 -8
- package/src/model/diffusion-gemma.ts +1026 -0
- package/src/model/factory.ts +35 -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 +62 -6
- package/src/model/gemma4.ts +33 -2
- package/src/model/minicpm5.ts +106 -8
- package/src/model/qwen3.ts +276 -0
- package/src/model/steel-linear-kernel.ts +96 -0
- package/src/model/support.ts +45 -7
- 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/quantize/config-writer.ts +1 -1
- package/src/registry.ts +215 -7
- package/src/responses.ts +23 -0
- package/src/sampler.ts +220 -9
- package/src/serve/batch-scheduler.ts +272 -80
- package/src/serve/generation-gateway.ts +90 -18
- package/src/server.ts +643 -50
- 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/status-page.html +1 -1
- package/src/tokenizer.ts +7 -0
- package/src/train/diffusion-lora.ts +216 -0
- package/src/train/flash-cce.ts +70 -15
- 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 +289 -30
- package/src/train/prefix-shared.ts +33 -5
- package/src/train/segmented.ts +67 -11
- package/src/train/trainer.ts +92 -28
- package/src/vision/diffusion-vision.ts +620 -0
- package/src/web/skills/memory/SKILL.md +40 -17
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// Fuse a LoRA adapter into base model weights → a standalone model snapshot
|
|
2
|
+
// (mlx_lm.fuse counterpart, minus GGUF/de-quantize export).
|
|
3
|
+
//
|
|
4
|
+
// Math (matches mlx-lm's LoRALinear.fuse and our QuantizedLinear.forward):
|
|
5
|
+
// forward: y = x @ W_dequantᵀ + scale · ((x @ A) @ B)
|
|
6
|
+
// fused: W'[out,in] = W + (scale · B)ᵀ.astype(bf16) @ Aᵀ.astype(bf16)
|
|
7
|
+
// mlx-lm casts the (scaled) LoRA factors to the base dtype BEFORE the matmul,
|
|
8
|
+
// then re-quantizes with the module's OWN source spec (bits/group unchanged) —
|
|
9
|
+
// we do the same, so the output config.json is the source config verbatim.
|
|
10
|
+
//
|
|
11
|
+
// Everything runs on the CPU stream (same discipline as src/quantize/quantizer:
|
|
12
|
+
// the safetensors loader lives there, and one module is materialized, fused,
|
|
13
|
+
// and disposed at a time). Modules the adapter does not touch pass through
|
|
14
|
+
// bit-identical (no dequant→requant round trip).
|
|
15
|
+
|
|
16
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { clearCache, Dtype } from "../mlx/ffi";
|
|
19
|
+
import { cpuStream, type MlxArray } from "../mlx/array";
|
|
20
|
+
import * as ops from "../mlx/ops";
|
|
21
|
+
import { Weights } from "../weights";
|
|
22
|
+
import { loadModelConfig, quantFor } from "../config";
|
|
23
|
+
import { loadAdapterTensors, readAdapterScale, adapterWeightsFile } from "../lora";
|
|
24
|
+
import { writeShardedSafetensors, type NamedTensor } from "../quantize/safetensors-writer";
|
|
25
|
+
import { copyAuxFiles } from "../quantize/config-writer";
|
|
26
|
+
|
|
27
|
+
/** Quantize the allocator cache this often (in modules processed). */
|
|
28
|
+
const CACHE_CLEAR_EVERY = 16;
|
|
29
|
+
|
|
30
|
+
export interface FuseStats {
|
|
31
|
+
outDir: string;
|
|
32
|
+
/** Modules whose weights had an adapter delta folded in. */
|
|
33
|
+
fusedModules: number;
|
|
34
|
+
/** Adapter tensors that matched no base weight (skipped). */
|
|
35
|
+
skippedAdapterTensors: number;
|
|
36
|
+
totalTensors: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface FuseProgress {
|
|
40
|
+
stage: string;
|
|
41
|
+
message: string;
|
|
42
|
+
/** 0..1 fraction of modules processed. */
|
|
43
|
+
progress: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Resolve the adapter safetensors file: `<dir>/adapters.safetensors`,
|
|
47
|
+
* `<dir>/best/adapters.safetensors`, `adapter_model.safetensors`, or an
|
|
48
|
+
* explicit file path. */
|
|
49
|
+
function resolveAdapterFile(dir: string): string {
|
|
50
|
+
const best = join(dir, "best");
|
|
51
|
+
if (existsSync(join(best, "adapters.safetensors"))) return join(best, "adapters.safetensors");
|
|
52
|
+
if (dir.endsWith(".safetensors") && existsSync(dir)) return dir;
|
|
53
|
+
return adapterWeightsFile(dir);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Group adapter tensors into (modulePath → {a, b}); disposes non-LoRA
|
|
57
|
+
* tensors and returns how many were skipped. */
|
|
58
|
+
function groupPairs(tensors: Map<string, MlxArray>): {
|
|
59
|
+
pairs: Map<string, { a?: MlxArray; b?: MlxArray }>;
|
|
60
|
+
skipped: number;
|
|
61
|
+
} {
|
|
62
|
+
const pairs = new Map<string, { a?: MlxArray; b?: MlxArray }>();
|
|
63
|
+
let skipped = 0;
|
|
64
|
+
for (const [name, arr] of tensors) {
|
|
65
|
+
const m = name.match(/^(.*)\.(lora_a|lora_A|lora_b|lora_B)(\.weight)?$/);
|
|
66
|
+
if (!m) { skipped++; arr.dispose(); continue; }
|
|
67
|
+
const slot = pairs.get(m[1]!) ?? {};
|
|
68
|
+
if (m[2]!.toLowerCase() === "lora_a") slot.a = arr; else slot.b = arr;
|
|
69
|
+
pairs.set(m[1]!, slot);
|
|
70
|
+
}
|
|
71
|
+
return { pairs, skipped };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Fuse the adapter at `adapterDir` into the base model at `modelDir` and
|
|
76
|
+
* write a complete, loadable snapshot to `outDir` (weights + config.json +
|
|
77
|
+
* tokenizer aux files). Quantization layout is preserved verbatim: fused
|
|
78
|
+
* modules are dequantized, updated, and re-quantized with their own source
|
|
79
|
+
* spec; untouched modules pass through bit-identical.
|
|
80
|
+
*/
|
|
81
|
+
export async function fuseAdapter(
|
|
82
|
+
modelDir: string,
|
|
83
|
+
adapterDir: string,
|
|
84
|
+
outDir: string,
|
|
85
|
+
onProgress?: (e: FuseProgress) => void,
|
|
86
|
+
): Promise<FuseStats> {
|
|
87
|
+
const progress = (stage: string, message: string, frac: number) =>
|
|
88
|
+
onProgress?.({ stage, message, progress: frac });
|
|
89
|
+
|
|
90
|
+
progress("loading", `Reading ${modelDir}`, 0);
|
|
91
|
+
const config = await loadModelConfig(modelDir);
|
|
92
|
+
const weights = await Weights.open(modelDir);
|
|
93
|
+
|
|
94
|
+
const adapterFile = resolveAdapterFile(adapterDir);
|
|
95
|
+
const { scale, rsLora } = await readAdapterScale(adapterDir);
|
|
96
|
+
const { pairs, skipped } = groupPairs(loadAdapterTensors(adapterFile));
|
|
97
|
+
|
|
98
|
+
// Map each adapter module path to the base weight-file module path,
|
|
99
|
+
// probing the VLM-wrapped/unwrapped `language_model.` prefixes both ways
|
|
100
|
+
// (same normalization as AdapterManager.mount).
|
|
101
|
+
const weightNames = new Set(weights.tensorNames);
|
|
102
|
+
const byModule = new Map<string, { a: MlxArray; b: MlxArray }>();
|
|
103
|
+
let unmatched = 0;
|
|
104
|
+
const disposeAll = () => {
|
|
105
|
+
for (const { a, b } of pairs.values()) { a?.dispose(); b?.dispose(); }
|
|
106
|
+
};
|
|
107
|
+
try {
|
|
108
|
+
for (const [path, { a, b }] of pairs) {
|
|
109
|
+
if (!a || !b) throw new Error(`${path}: adapter has only one of lora_a/lora_b`);
|
|
110
|
+
const candidates = [
|
|
111
|
+
path,
|
|
112
|
+
`language_model.${path}`,
|
|
113
|
+
path.startsWith("language_model.") ? path.slice("language_model.".length) : null,
|
|
114
|
+
].filter((c): c is string => c !== null);
|
|
115
|
+
const hit = candidates.find((c) => weightNames.has(`${c}.weight`));
|
|
116
|
+
if (!hit) { unmatched++; continue; }
|
|
117
|
+
byModule.set(hit, { a, b });
|
|
118
|
+
}
|
|
119
|
+
if (byModule.size === 0)
|
|
120
|
+
throw new Error(
|
|
121
|
+
`no adapter tensors match the base model's weights — was ` +
|
|
122
|
+
`${adapterFile} trained for a different base?`,
|
|
123
|
+
);
|
|
124
|
+
} catch (e) {
|
|
125
|
+
disposeAll();
|
|
126
|
+
weights.dispose();
|
|
127
|
+
throw e;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
mkdirSync(outDir, { recursive: true });
|
|
131
|
+
|
|
132
|
+
// Walk tensors in source order (diff-friendly like the quantizer): group
|
|
133
|
+
// names into module bases so a fused module's weight/scales/biases are
|
|
134
|
+
// regenerated together.
|
|
135
|
+
const names = weights.tensorNames;
|
|
136
|
+
const moduleSuffixes = new Map<string, Set<string>>();
|
|
137
|
+
const passthroughNames: string[] = [];
|
|
138
|
+
for (const name of names) {
|
|
139
|
+
const m = /^(.*)\.(weight|scales|biases)$/.exec(name);
|
|
140
|
+
if (!m) { passthroughNames.push(name); continue; }
|
|
141
|
+
let set = moduleSuffixes.get(m[1]!);
|
|
142
|
+
if (!set) { set = new Set(); moduleSuffixes.set(m[1]!, set); }
|
|
143
|
+
set.add(m[2]!);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const out: NamedTensor[] = [];
|
|
147
|
+
let fusedModules = 0;
|
|
148
|
+
let processed = 0;
|
|
149
|
+
const totalModules = moduleSuffixes.size;
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
for (const [base, suffixes] of moduleSuffixes) {
|
|
153
|
+
processed++;
|
|
154
|
+
if (processed % CACHE_CLEAR_EVERY === 0) clearCache();
|
|
155
|
+
const pair = suffixes.has("weight") ? byModule.get(base) : undefined;
|
|
156
|
+
if (!pair) {
|
|
157
|
+
for (const suf of suffixes) {
|
|
158
|
+
const n = `${base}.${suf}`;
|
|
159
|
+
out.push({ name: n, array: weights.tensor(n) });
|
|
160
|
+
}
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
progress("fusing", `Module ${processed}/${totalModules}: ${base}`, processed / totalModules);
|
|
164
|
+
|
|
165
|
+
// Materialize the full-precision weight [out, in].
|
|
166
|
+
const srcSpec = suffixes.has("scales") ? quantFor(config.quantization, base) : null;
|
|
167
|
+
let w: MlxArray;
|
|
168
|
+
if (srcSpec) {
|
|
169
|
+
const packed = weights.tensor(`${base}.weight`);
|
|
170
|
+
const scales = weights.tensor(`${base}.scales`);
|
|
171
|
+
const biases = suffixes.has("biases") ? weights.tensor(`${base}.biases`) : null;
|
|
172
|
+
w = ops.dequantize(packed, scales, biases, srcSpec, cpuStream);
|
|
173
|
+
} else {
|
|
174
|
+
w = weights.tensor(`${base}.weight`);
|
|
175
|
+
}
|
|
176
|
+
const [outF, inF] = w.shape as [number, number];
|
|
177
|
+
const { a, b } = pair;
|
|
178
|
+
const [aIn, rank] = a.shape as [number, number];
|
|
179
|
+
const [bRank, bOut] = b.shape as [number, number];
|
|
180
|
+
if (aIn !== inF || bRank !== rank || bOut !== outF)
|
|
181
|
+
throw new Error(
|
|
182
|
+
`${base}: shape mismatch — lora_a [${a.shape}] / lora_b [${b.shape}] ` +
|
|
183
|
+
`vs base [out ${outF}, in ${inF}]`,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// delta[out,in] = (scale·B)ᵀ.astype(bf16) @ Aᵀ.astype(bf16)
|
|
187
|
+
// (rsLoRA: effective per-layer scale is α/√rank, matching mount/training.)
|
|
188
|
+
const effScale = rsLora ? scale / Math.sqrt(rank) : scale;
|
|
189
|
+
const bScaled = ops.mulScalar(b, effScale, cpuStream);
|
|
190
|
+
const bT = ops.transposeAxes(bScaled, [1, 0], cpuStream);
|
|
191
|
+
bScaled.dispose();
|
|
192
|
+
const bT16 = bT.astype(Dtype.bfloat16, cpuStream);
|
|
193
|
+
bT.dispose();
|
|
194
|
+
const aT = ops.transposeAxes(a, [1, 0], cpuStream);
|
|
195
|
+
const aT16 = aT.astype(Dtype.bfloat16, cpuStream);
|
|
196
|
+
aT.dispose();
|
|
197
|
+
const delta = ops.matmul(bT16, aT16, cpuStream);
|
|
198
|
+
bT16.dispose();
|
|
199
|
+
aT16.dispose();
|
|
200
|
+
|
|
201
|
+
const w16 = w.dtype === Dtype.bfloat16 ? w : w.astype(Dtype.bfloat16, cpuStream);
|
|
202
|
+
if (w16 !== w) w.dispose();
|
|
203
|
+
const fusedW = ops.add(w16, delta, cpuStream);
|
|
204
|
+
w16.dispose();
|
|
205
|
+
delta.dispose();
|
|
206
|
+
|
|
207
|
+
if (srcSpec) {
|
|
208
|
+
const q = ops.quantize(fusedW, srcSpec.groupSize, srcSpec.bits, srcSpec.mode, cpuStream);
|
|
209
|
+
fusedW.dispose();
|
|
210
|
+
out.push({ name: `${base}.weight`, array: q.packed });
|
|
211
|
+
out.push({ name: `${base}.scales`, array: q.scales });
|
|
212
|
+
out.push({ name: `${base}.biases`, array: q.biases });
|
|
213
|
+
} else {
|
|
214
|
+
out.push({ name: `${base}.weight`, array: fusedW });
|
|
215
|
+
}
|
|
216
|
+
fusedModules++;
|
|
217
|
+
}
|
|
218
|
+
for (const name of passthroughNames) out.push({ name, array: weights.tensor(name) });
|
|
219
|
+
|
|
220
|
+
progress("writing", `Writing ${out.length} tensors to ${outDir}`, 1);
|
|
221
|
+
writeShardedSafetensors(outDir, out);
|
|
222
|
+
|
|
223
|
+
// Config is unchanged (same quantization layout) — copy it and the aux
|
|
224
|
+
// files (tokenizer, chat template, …) verbatim for a loadable snapshot.
|
|
225
|
+
await Bun.write(join(outDir, "config.json"), Bun.file(join(modelDir, "config.json")));
|
|
226
|
+
await copyAuxFiles(modelDir, outDir);
|
|
227
|
+
|
|
228
|
+
progress("done", `Fused ${fusedModules} module(s) → ${outDir}`, 1);
|
|
229
|
+
return {
|
|
230
|
+
outDir,
|
|
231
|
+
fusedModules,
|
|
232
|
+
skippedAdapterTensors: skipped + unmatched * 2,
|
|
233
|
+
totalTensors: out.length,
|
|
234
|
+
};
|
|
235
|
+
} finally {
|
|
236
|
+
for (const t of out) {
|
|
237
|
+
try { t.array.dispose(); } catch {}
|
|
238
|
+
}
|
|
239
|
+
disposeAll();
|
|
240
|
+
weights.dispose();
|
|
241
|
+
clearCache();
|
|
242
|
+
}
|
|
243
|
+
}
|
package/src/train/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { trainForward, buildBatchedPadMask } from "./forward";
|
|
|
14
14
|
|
|
15
15
|
export {
|
|
16
16
|
sftLoss, dpoLoss, dpoRefLogps, dpoMetrics,
|
|
17
|
+
orpoLoss, orpoMetrics, type SftScope,
|
|
17
18
|
} from "./loss";
|
|
18
19
|
|
|
19
20
|
export {
|
|
@@ -38,6 +39,8 @@ export {
|
|
|
38
39
|
|
|
39
40
|
export { mergeAdapters, type MergeStats } from "./merge";
|
|
40
41
|
|
|
42
|
+
export { fuseAdapter, type FuseStats, type FuseProgress } from "./fuse";
|
|
43
|
+
|
|
41
44
|
export { exportAdapter, type ExportManifest } from "./export";
|
|
42
45
|
|
|
43
46
|
export { finetuneRunner, inspectDataset } from "./job";
|
package/src/train/job.ts
CHANGED
|
@@ -51,6 +51,7 @@ interface FinetuneSubmit {
|
|
|
51
51
|
orpo_fused_ce?: boolean;
|
|
52
52
|
orpo_flash_ce?: boolean;
|
|
53
53
|
orpo_prefix_shared?: boolean;
|
|
54
|
+
sft_scope?: "full" | "response";
|
|
54
55
|
warm_start_adapter?: string;
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -68,6 +69,12 @@ function parseConfig(raw: Record<string, unknown>): { modelDir: string; dataDir:
|
|
|
68
69
|
// quantized-head pieces if the base turns out to be unquantized.
|
|
69
70
|
const isOrpo = (c.method ?? "sft") === "orpo";
|
|
70
71
|
|
|
72
|
+
// ORPO L_SFT scope: "full" (default; paper/TRL-faithful) | "response"
|
|
73
|
+
// (pre-2026-07 behavior). Validate here so a typo fails at submit, not mid-run.
|
|
74
|
+
const sftScope = c.sft_scope ?? DEFAULT_TRAIN_CONFIG.sftScope;
|
|
75
|
+
if (sftScope !== "full" && sftScope !== "response")
|
|
76
|
+
throw new Error(`finetune job: sft_scope must be "full" or "response" (got ${JSON.stringify(c.sft_scope)})`);
|
|
77
|
+
|
|
71
78
|
const cfg: TrainConfig = {
|
|
72
79
|
...DEFAULT_TRAIN_CONFIG,
|
|
73
80
|
method: c.method ?? "sft",
|
|
@@ -105,6 +112,7 @@ function parseConfig(raw: Record<string, unknown>): { modelDir: string; dataDir:
|
|
|
105
112
|
orpoFusedCe: c.orpo_fused_ce ?? DEFAULT_TRAIN_CONFIG.orpoFusedCe,
|
|
106
113
|
orpoFlashCe: c.orpo_flash_ce ?? (isOrpo ? true : DEFAULT_TRAIN_CONFIG.orpoFlashCe),
|
|
107
114
|
orpoPrefixShared: c.orpo_prefix_shared ?? (isOrpo ? true : DEFAULT_TRAIN_CONFIG.orpoPrefixShared),
|
|
115
|
+
sftScope,
|
|
108
116
|
warmStartAdapter: c.warm_start_adapter ?? DEFAULT_TRAIN_CONFIG.warmStartAdapter,
|
|
109
117
|
adapterPath: c.adapter_path,
|
|
110
118
|
baseModel: c.model_dir,
|