@tuned-tensor/local 0.2.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +110 -201
- package/dist/active-model.d.ts +16 -0
- package/dist/active-model.js +88 -0
- package/dist/active-model.js.map +1 -0
- package/dist/artifacts.d.ts +5 -4
- package/dist/artifacts.js +57 -33
- package/dist/artifacts.js.map +1 -1
- package/dist/compare.d.ts +1 -8
- package/dist/compare.js +1 -23
- package/dist/compare.js.map +1 -1
- package/dist/contracts.d.ts +234 -365
- package/dist/contracts.js +95 -224
- package/dist/contracts.js.map +1 -1
- package/dist/dataset.d.ts +11 -4
- package/dist/dataset.js +67 -125
- package/dist/dataset.js.map +1 -1
- package/dist/doctor.d.ts +2 -3
- package/dist/doctor.js +23 -161
- package/dist/doctor.js.map +1 -1
- package/dist/evaluation.d.ts +11 -71
- package/dist/evaluation.js +212 -572
- package/dist/evaluation.js.map +1 -1
- package/dist/general-regression.d.ts +10 -0
- package/dist/general-regression.js +16 -0
- package/dist/general-regression.js.map +1 -0
- package/dist/huggingface-cache.d.ts +8 -7
- package/dist/huggingface-cache.js +16 -8
- package/dist/huggingface-cache.js.map +1 -1
- package/dist/index.d.ts +2 -10
- package/dist/index.js +358 -627
- package/dist/index.js.map +1 -1
- package/dist/local-project.d.ts +1 -11
- package/dist/local-project.js +33 -61
- package/dist/local-project.js.map +1 -1
- package/dist/model-registry.d.ts +3 -16
- package/dist/model-registry.js +76 -292
- package/dist/model-registry.js.map +1 -1
- package/dist/model-server.d.ts +7 -3
- package/dist/model-server.js +34 -25
- package/dist/model-server.js.map +1 -1
- package/dist/orchestrator.d.ts +11 -25
- package/dist/orchestrator.js +393 -563
- package/dist/orchestrator.js.map +1 -1
- package/dist/prefetch.d.ts +1 -5
- package/dist/prefetch.js +14 -19
- package/dist/prefetch.js.map +1 -1
- package/dist/process-runner.d.ts +10 -29
- package/dist/process-runner.js +63 -169
- package/dist/process-runner.js.map +1 -1
- package/dist/process-training.d.ts +0 -1
- package/dist/process-training.js +10 -87
- package/dist/process-training.js.map +1 -1
- package/dist/store.d.ts +1 -3
- package/dist/store.js +92 -290
- package/dist/store.js.map +1 -1
- package/docs/architecture.md +87 -152
- package/docs/spark.md +66 -97
- package/examples/dry-runner.json +14 -0
- package/examples/general-regression.jsonl +4 -0
- package/examples/local-runner.json +13 -6
- package/examples/smoke-spec.json +41 -0
- package/package.json +6 -6
- package/training/local-runner/pyproject.toml +0 -5
- package/training/local-runner/src/evaluate.py +89 -234
- package/training/local-runner/src/model_contract.py +47 -0
- package/training/local-runner/src/prefetch.py +18 -4
- package/training/local-runner/src/serve.py +60 -117
- package/training/local-runner/src/sft_data.py +97 -0
- package/training/local-runner/src/train.py +146 -346
- package/training/local-runner/uv.lock +0 -1197
- package/dist/labeling-sanitize.d.ts +0 -31
- package/dist/labeling-sanitize.js +0 -158
- package/dist/labeling-sanitize.js.map +0 -1
- package/dist/labeling.d.ts +0 -155
- package/dist/labeling.js +0 -496
- package/dist/labeling.js.map +0 -1
- package/dist/openrouter.d.ts +0 -29
- package/dist/openrouter.js +0 -66
- package/dist/openrouter.js.map +0 -1
- package/dist/server.d.ts +0 -9
- package/dist/server.js +0 -211
- package/dist/server.js.map +0 -1
- package/docs/local-workflow-remediation-2026-07-13.md +0 -130
- package/docs/local-workflow-ux-review-2026-07-13.md +0 -458
- package/examples/dpo-preferences.jsonl +0 -2
- package/examples/dpo-run-request.json +0 -34
- package/examples/smoke-run-request.json +0 -34
- package/training/local-runner/src/train_dpo.py +0 -286
package/dist/dataset.js
CHANGED
|
@@ -1,89 +1,54 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
function normalizeAssetReference(value, baseDirectory) {
|
|
6
|
-
if (value.startsWith("~/"))
|
|
7
|
-
return join(homedir(), value.slice(2));
|
|
8
|
-
if (!value || isAbsolute(value) || /^[a-z][a-z0-9+.-]*:/i.test(value))
|
|
9
|
-
return value;
|
|
10
|
-
return resolve(baseDirectory, value);
|
|
11
|
-
}
|
|
12
|
-
function localAssetPath(value) {
|
|
13
|
-
if (value.startsWith("file://")) {
|
|
14
|
-
try {
|
|
15
|
-
return fileURLToPath(new URL(value));
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return /^[a-z][a-z0-9+.-]*:/i.test(value) ? null : value;
|
|
22
|
-
}
|
|
23
2
|
async function loadNormalizedChatJsonl(path) {
|
|
24
3
|
const text = await readFile(path, "utf8");
|
|
25
|
-
const baseDirectory = dirname(resolve(path));
|
|
26
4
|
const rows = [];
|
|
27
5
|
const jsonLines = [];
|
|
28
|
-
const localAssets = new Set();
|
|
29
|
-
const normalize = (value) => {
|
|
30
|
-
if (typeof value !== "string" || !value)
|
|
31
|
-
return value;
|
|
32
|
-
const normalized = normalizeAssetReference(value, baseDirectory);
|
|
33
|
-
const local = localAssetPath(normalized);
|
|
34
|
-
if (local)
|
|
35
|
-
localAssets.add(resolve(local));
|
|
36
|
-
return normalized;
|
|
37
|
-
};
|
|
38
6
|
for (const [index, line] of text.split(/\r?\n/).entries()) {
|
|
39
7
|
if (!line.trim())
|
|
40
8
|
continue;
|
|
41
|
-
let
|
|
9
|
+
let value;
|
|
42
10
|
try {
|
|
43
|
-
|
|
11
|
+
value = JSON.parse(line);
|
|
44
12
|
}
|
|
45
13
|
catch (error) {
|
|
46
14
|
throw new Error(`Invalid chat JSONL row ${index + 1}: malformed JSON`, { cause: error });
|
|
47
15
|
}
|
|
48
|
-
if (Array.isArray(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
16
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
17
|
+
throw new Error(`Invalid chat JSONL row ${index + 1}: expected an object`);
|
|
18
|
+
}
|
|
19
|
+
const messages = value.messages;
|
|
20
|
+
if (!Array.isArray(messages)
|
|
21
|
+
|| ![2, 3].includes(messages.length)
|
|
22
|
+
|| messages.some((message) => !message
|
|
23
|
+
|| typeof message !== "object"
|
|
24
|
+
|| Array.isArray(message)
|
|
25
|
+
|| !["system", "user", "assistant"].includes(String(message.role))
|
|
26
|
+
|| typeof message.content !== "string")) {
|
|
27
|
+
throw new Error(`Invalid chat JSONL row ${index + 1}: expected an optional system message, one user message, and one assistant answer`);
|
|
28
|
+
}
|
|
29
|
+
const row = { messages };
|
|
30
|
+
const offset = row.messages.length === 3 ? 1 : 0;
|
|
31
|
+
if ((offset === 1 && row.messages[0]?.role !== "system")
|
|
32
|
+
|| row.messages[offset]?.role !== "user"
|
|
33
|
+
|| !row.messages[offset]?.content.trim()
|
|
34
|
+
|| row.messages[offset + 1]?.role !== "assistant"
|
|
35
|
+
|| !row.messages[offset + 1]?.content.trim()) {
|
|
36
|
+
throw new Error(`Invalid chat JSONL row ${index + 1}: expected an optional system message, one non-empty user message, and one non-empty assistant answer`);
|
|
70
37
|
}
|
|
71
38
|
rows.push(row);
|
|
72
39
|
jsonLines.push(JSON.stringify(row));
|
|
73
40
|
}
|
|
41
|
+
if (rows.length === 0)
|
|
42
|
+
throw new Error(`Chat JSONL contains no examples: ${path}`);
|
|
74
43
|
return {
|
|
75
44
|
rows,
|
|
76
45
|
jsonl: jsonLines.join("\n"),
|
|
77
|
-
localAssetPaths: [...localAssets].sort(),
|
|
78
46
|
};
|
|
79
47
|
}
|
|
80
|
-
/**
|
|
48
|
+
/** Parses and canonically rewrites text chat JSONL before copying it. */
|
|
81
49
|
export async function normalizeChatJsonlForRelocation(path) {
|
|
82
50
|
return (await loadNormalizedChatJsonl(path)).jsonl;
|
|
83
51
|
}
|
|
84
|
-
export async function localAssetPathsFromChatJsonl(path) {
|
|
85
|
-
return (await loadNormalizedChatJsonl(path)).localAssetPaths;
|
|
86
|
-
}
|
|
87
52
|
export function buildSystemMessage(spec) {
|
|
88
53
|
const parts = [];
|
|
89
54
|
if (spec.system_prompt.trim())
|
|
@@ -94,25 +59,14 @@ export function buildSystemMessage(spec) {
|
|
|
94
59
|
if (spec.constraints.length > 0) {
|
|
95
60
|
parts.push(`Constraints:\n${spec.constraints.map((constraint) => `- ${constraint}`).join("\n")}`);
|
|
96
61
|
}
|
|
97
|
-
return parts.join("\n\n");
|
|
62
|
+
return parts.join("\n\n") || "Follow the demonstrated behavior.";
|
|
98
63
|
}
|
|
99
64
|
export function exampleToChatRow(spec, example) {
|
|
100
65
|
const system = buildSystemMessage(spec);
|
|
101
|
-
const userContent = example.input_assets?.length
|
|
102
|
-
? [
|
|
103
|
-
...example.input_assets.map((asset) => ({
|
|
104
|
-
type: "image",
|
|
105
|
-
image: asset.image ?? asset.data_uri ?? asset.uri ?? asset.path,
|
|
106
|
-
...(asset.mime_type ? { mime_type: asset.mime_type } : {}),
|
|
107
|
-
...(asset.page ? { page: asset.page } : {}),
|
|
108
|
-
})),
|
|
109
|
-
{ type: "text", text: example.input },
|
|
110
|
-
]
|
|
111
|
-
: example.input;
|
|
112
66
|
return {
|
|
113
67
|
messages: [
|
|
114
68
|
{ role: "system", content: system },
|
|
115
|
-
{ role: "user", content:
|
|
69
|
+
{ role: "user", content: example.input },
|
|
116
70
|
{ role: "assistant", content: example.output },
|
|
117
71
|
],
|
|
118
72
|
};
|
|
@@ -127,61 +81,49 @@ export async function examplesFromChatJsonl(path) {
|
|
|
127
81
|
const normalized = await loadNormalizedChatJsonl(path);
|
|
128
82
|
const examples = [];
|
|
129
83
|
for (const [index, row] of normalized.rows.entries()) {
|
|
130
|
-
const user = row.messages.
|
|
131
|
-
const assistant =
|
|
132
|
-
if (!user || !assistant || typeof assistant.content !== "string") {
|
|
133
|
-
throw new Error(`Invalid chat JSONL row ${index + 1}: expected user and assistant messages`);
|
|
134
|
-
}
|
|
135
|
-
const inputAssets = [];
|
|
136
|
-
let textInput = "";
|
|
137
|
-
if (typeof user.content === "string") {
|
|
138
|
-
textInput = user.content;
|
|
139
|
-
if (Array.isArray(row.images)) {
|
|
140
|
-
for (const image of row.images) {
|
|
141
|
-
if (typeof image === "string" && image) {
|
|
142
|
-
inputAssets.push({ type: "image", image });
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
const topLevelImages = Array.isArray(row.images) ? row.images : [];
|
|
149
|
-
let imageIndex = 0;
|
|
150
|
-
const textParts = [];
|
|
151
|
-
for (const part of user.content) {
|
|
152
|
-
if (part.type === "text" && typeof part.text === "string") {
|
|
153
|
-
textParts.push(part.text);
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
if (part.type !== "image")
|
|
157
|
-
continue;
|
|
158
|
-
const image = typeof part.image === "string"
|
|
159
|
-
? part.image
|
|
160
|
-
: typeof part.path === "string"
|
|
161
|
-
? part.path
|
|
162
|
-
: typeof part.uri === "string"
|
|
163
|
-
? part.uri
|
|
164
|
-
: typeof part.data_uri === "string"
|
|
165
|
-
? part.data_uri
|
|
166
|
-
: topLevelImages[imageIndex];
|
|
167
|
-
imageIndex += 1;
|
|
168
|
-
if (image) {
|
|
169
|
-
inputAssets.push({
|
|
170
|
-
type: "image",
|
|
171
|
-
image,
|
|
172
|
-
...(typeof part.mime_type === "string" ? { mime_type: part.mime_type } : {}),
|
|
173
|
-
...(typeof part.page === "number" ? { page: part.page } : {}),
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
textInput = textParts.join("\n").trim() || JSON.stringify(user.content);
|
|
178
|
-
}
|
|
84
|
+
const user = row.messages.at(-2);
|
|
85
|
+
const assistant = row.messages.at(-1);
|
|
179
86
|
examples.push({
|
|
180
|
-
input:
|
|
87
|
+
input: user.content,
|
|
181
88
|
output: assistant.content,
|
|
182
|
-
...(inputAssets.length ? { input_assets: inputAssets } : {}),
|
|
183
89
|
});
|
|
184
90
|
}
|
|
185
91
|
return examples;
|
|
186
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Loads an evaluation-only chat JSONL suite. Rows may omit the system message,
|
|
95
|
+
* but when they include one it must be identical across the suite.
|
|
96
|
+
*/
|
|
97
|
+
export async function evaluationSuiteFromChatJsonl(path, configuredSystem) {
|
|
98
|
+
const normalized = await loadNormalizedChatJsonl(path);
|
|
99
|
+
const inputIdentities = new Set();
|
|
100
|
+
for (const row of normalized.rows) {
|
|
101
|
+
const input = row.messages.at(-2).content;
|
|
102
|
+
const identity = input.trim().replace(/\s+/g, " ").toLowerCase();
|
|
103
|
+
if (inputIdentities.has(identity)) {
|
|
104
|
+
throw new Error("General regression data contains duplicate inputs; "
|
|
105
|
+
+ "evaluation prompts must be unique.");
|
|
106
|
+
}
|
|
107
|
+
inputIdentities.add(identity);
|
|
108
|
+
}
|
|
109
|
+
const systems = new Set(normalized.rows
|
|
110
|
+
.map((row) => row.messages.find((message) => message.role === "system")?.content)
|
|
111
|
+
.filter((value) => value !== undefined));
|
|
112
|
+
if (systems.size > 1) {
|
|
113
|
+
throw new Error("General regression rows must use one consistent system message.");
|
|
114
|
+
}
|
|
115
|
+
const embeddedSystem = [...systems][0];
|
|
116
|
+
if (configuredSystem !== undefined
|
|
117
|
+
&& embeddedSystem !== undefined
|
|
118
|
+
&& configuredSystem !== embeddedSystem) {
|
|
119
|
+
throw new Error("evaluation.generalRegression.systemPrompt must match the system message embedded in the dataset.");
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
examples: normalized.rows.map((row) => ({
|
|
123
|
+
input: row.messages.at(-2).content,
|
|
124
|
+
output: row.messages.at(-1).content,
|
|
125
|
+
})),
|
|
126
|
+
system: configuredSystem ?? embeddedSystem ?? "You are a helpful assistant.",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
187
129
|
//# sourceMappingURL=dataset.js.map
|
package/dist/dataset.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset.js","sourceRoot":"","sources":["../src/dataset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"dataset.js","sourceRoot":"","sources":["../src/dataset.ts"],"names":[],"mappings":"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"}
|
package/dist/doctor.d.ts
CHANGED
|
@@ -6,13 +6,12 @@ export interface DoctorCheck {
|
|
|
6
6
|
details?: Record<string, unknown>;
|
|
7
7
|
}
|
|
8
8
|
interface PythonProbePlan {
|
|
9
|
-
name: "
|
|
9
|
+
name: "python-runtime";
|
|
10
10
|
command: string;
|
|
11
11
|
args: string[];
|
|
12
|
-
cwd?: string;
|
|
13
12
|
env: NodeJS.ProcessEnv;
|
|
14
13
|
}
|
|
15
|
-
/** Build the exact uv
|
|
14
|
+
/** Build the exact bundled uv environment that every real stage uses. */
|
|
16
15
|
export declare function buildDoctorPythonPlans(config: LocalRunnerConfig): PythonProbePlan[];
|
|
17
16
|
export declare function runDoctor(config: LocalRunnerConfig, request?: FineTuneRunRequest): Promise<DoctorCheck[]>;
|
|
18
17
|
export {};
|
package/dist/doctor.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { constants } from "node:fs";
|
|
2
2
|
import { access, mkdir, statfs, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import {
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { withHuggingFaceCacheEnvironment } from "./huggingface-cache.js";
|
|
7
|
-
import {
|
|
6
|
+
import { minimalMachineLearningEnvironment, withHuggingFaceCacheEnvironment, } from "./huggingface-cache.js";
|
|
7
|
+
import { buildBundledPythonCommand, withBundledPythonEnvironment, } from "./process-runner.js";
|
|
8
8
|
import { resolveTrainingModel } from "./model-registry.js";
|
|
9
9
|
import { defaultLocalHome } from "./store.js";
|
|
10
10
|
import { verifyLocalBaseModel } from "./prefetch.js";
|
|
@@ -91,27 +91,6 @@ function firstLine(value) {
|
|
|
91
91
|
function commandText(command, args) {
|
|
92
92
|
return [command, ...args].map((part) => /\s/.test(part) ? JSON.stringify(part) : part).join(" ");
|
|
93
93
|
}
|
|
94
|
-
async function executableCheck(command, options = {}) {
|
|
95
|
-
const env = { ...process.env, ...options.env };
|
|
96
|
-
const cwd = options.cwd ? resolve(options.cwd) : process.cwd();
|
|
97
|
-
const candidates = isAbsolute(command) || command.includes("/")
|
|
98
|
-
? [isAbsolute(command) ? command : resolve(cwd, command)]
|
|
99
|
-
: (env.PATH ?? "").split(delimiter).filter(Boolean).map((directory) => join(isAbsolute(directory) ? directory : resolve(cwd, directory), command));
|
|
100
|
-
for (const candidate of candidates) {
|
|
101
|
-
try {
|
|
102
|
-
await access(candidate, constants.X_OK);
|
|
103
|
-
return { name: `executable:${command}`, ok: true, message: candidate };
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
// Continue searching PATH.
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return {
|
|
110
|
-
name: `executable:${command}`,
|
|
111
|
-
ok: false,
|
|
112
|
-
message: `${command} is not executable or was not found on PATH`,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
94
|
async function writableDirectoryCheck(name, path) {
|
|
116
95
|
const resolvedPath = resolve(path);
|
|
117
96
|
const probePath = join(resolvedPath, `.tt-local-write-probe-${process.pid}-${Date.now()}`);
|
|
@@ -144,80 +123,31 @@ function pythonProbeSource(device) {
|
|
|
144
123
|
"import json",
|
|
145
124
|
"import torch, transformers, peft, huggingface_hub",
|
|
146
125
|
`requested = ${JSON.stringify(device)}`,
|
|
147
|
-
"assert
|
|
148
|
-
"
|
|
149
|
-
"print(json.dumps({'python_ok': True, 'torch': torch.__version__, 'transformers': transformers.__version__, 'cuda_available':
|
|
126
|
+
"assert torch.cuda.is_available(), 'TT Local training requires CUDA but torch.cuda.is_available() is false'",
|
|
127
|
+
"props = torch.cuda.get_device_properties(0)",
|
|
128
|
+
"print(json.dumps({'python_ok': True, 'torch': torch.__version__, 'transformers': transformers.__version__, 'cuda_available': True, 'cuda_device': torch.cuda.get_device_name(0), 'compute_capability': list(torch.cuda.get_device_capability(0)), 'total_memory_bytes': props.total_memory, 'evaluation_device': requested}))",
|
|
150
129
|
].join("; ");
|
|
151
130
|
}
|
|
152
131
|
function pythonProbePlan(args) {
|
|
153
|
-
const entrypoint =
|
|
154
|
-
|
|
155
|
-
backend: "uv",
|
|
156
|
-
module: undefined,
|
|
157
|
-
script: "-c",
|
|
158
|
-
args: [],
|
|
159
|
-
}, {
|
|
160
|
-
extraArgs: [pythonProbeSource(args.device)],
|
|
161
|
-
});
|
|
162
|
-
const env = withHuggingFaceCacheEnvironment({
|
|
163
|
-
...process.env,
|
|
164
|
-
...args.env,
|
|
165
|
-
}, args.modelCache);
|
|
132
|
+
const entrypoint = buildBundledPythonCommand("-c", [pythonProbeSource(args.device)]);
|
|
133
|
+
const env = withBundledPythonEnvironment(withHuggingFaceCacheEnvironment(minimalMachineLearningEnvironment(process.env), args.modelCache));
|
|
166
134
|
return {
|
|
167
|
-
name:
|
|
135
|
+
name: "python-runtime",
|
|
168
136
|
command: entrypoint.command,
|
|
169
137
|
args: entrypoint.commandArgs,
|
|
170
|
-
cwd: args.cwd,
|
|
171
138
|
env,
|
|
172
139
|
};
|
|
173
140
|
}
|
|
174
|
-
/** Build the exact uv
|
|
141
|
+
/** Build the exact bundled uv environment that every real stage uses. */
|
|
175
142
|
export function buildDoctorPythonPlans(config) {
|
|
176
143
|
if (config.dryRun)
|
|
177
144
|
return [];
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
plans.push(pythonProbePlan({
|
|
181
|
-
name: "training-python",
|
|
182
|
-
entrypoint: config.training,
|
|
183
|
-
cwd: config.training.cwd,
|
|
184
|
-
env: config.training.env,
|
|
185
|
-
device: config.evaluation.inference.device,
|
|
186
|
-
modelCache: config.paths.modelCache,
|
|
187
|
-
}));
|
|
188
|
-
}
|
|
189
|
-
if (config.evaluation.inference.provider === "transformers") {
|
|
190
|
-
plans.push(pythonProbePlan({
|
|
191
|
-
name: "evaluation-python",
|
|
192
|
-
entrypoint: config.evaluation.inference,
|
|
193
|
-
cwd: config.evaluation.inference.cwd,
|
|
194
|
-
env: config.evaluation.inference.env,
|
|
145
|
+
return [
|
|
146
|
+
pythonProbePlan({
|
|
195
147
|
device: config.evaluation.inference.device,
|
|
196
148
|
modelCache: config.paths.modelCache,
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
return plans;
|
|
200
|
-
}
|
|
201
|
-
function configuredCommandChecks(config) {
|
|
202
|
-
const checks = [];
|
|
203
|
-
if (config.training.backend === "command" && !config.dryRun) {
|
|
204
|
-
checks.push({
|
|
205
|
-
name: "training-command",
|
|
206
|
-
command: config.training.command?.[0],
|
|
207
|
-
cwd: config.training.cwd,
|
|
208
|
-
env: config.training.env,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
const inference = config.evaluation.inference;
|
|
212
|
-
if ((inference.provider === "command" || inference.provider === "batch_command") && !config.dryRun) {
|
|
213
|
-
checks.push({
|
|
214
|
-
name: "evaluation-command",
|
|
215
|
-
command: inference.command?.[0],
|
|
216
|
-
cwd: inference.cwd,
|
|
217
|
-
env: inference.env,
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
return checks;
|
|
149
|
+
}),
|
|
150
|
+
];
|
|
221
151
|
}
|
|
222
152
|
function placeholderSpecCheck(request) {
|
|
223
153
|
const placeholder = request.spec_snapshot.examples.some((example) => /replace this with/i.test(example.input) || /replace this with/i.test(example.output)) || /describe the behavior this local model should learn/i.test(request.spec_snapshot.system_prompt);
|
|
@@ -229,51 +159,6 @@ function placeholderSpecCheck(request) {
|
|
|
229
159
|
: `${request.spec_snapshot.examples.length} spec example(s); base model ${request.spec_snapshot.base_model}`,
|
|
230
160
|
};
|
|
231
161
|
}
|
|
232
|
-
function gatedModelTokenCheck(request) {
|
|
233
|
-
let model;
|
|
234
|
-
try {
|
|
235
|
-
model = resolveTrainingModel(request.spec_snapshot.base_model);
|
|
236
|
-
}
|
|
237
|
-
catch {
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
if (!model.requiresHfToken)
|
|
241
|
-
return null;
|
|
242
|
-
const available = Boolean(process.env.HF_TOKEN?.trim());
|
|
243
|
-
return {
|
|
244
|
-
name: "hugging-face-token",
|
|
245
|
-
ok: available,
|
|
246
|
-
message: available
|
|
247
|
-
? `HF_TOKEN is configured for gated model ${model.id}.`
|
|
248
|
-
: `${model.id} is gated and requires a non-empty HF_TOKEN.`,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
function judgeCheck(config) {
|
|
252
|
-
if (config.evaluation.scoring.mode !== "llm_judge")
|
|
253
|
-
return null;
|
|
254
|
-
if (config.evaluation.scoring.fallback === "exact_match" && !config.llm) {
|
|
255
|
-
return {
|
|
256
|
-
name: "llm-judge",
|
|
257
|
-
ok: true,
|
|
258
|
-
message: "LLM judge is not configured; explicit exact_match fallback will be used.",
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
if (!config.llm) {
|
|
262
|
-
return {
|
|
263
|
-
name: "llm-judge",
|
|
264
|
-
ok: false,
|
|
265
|
-
message: "evaluation.scoring.mode=llm_judge requires an llm configuration, or explicitly set fallback=exact_match.",
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
const available = Boolean(process.env[config.llm.apiKeyEnv]);
|
|
269
|
-
return {
|
|
270
|
-
name: "llm-judge",
|
|
271
|
-
ok: available || config.evaluation.scoring.fallback === "exact_match",
|
|
272
|
-
message: available
|
|
273
|
-
? `${config.llm.model} credentials found in ${config.llm.apiKeyEnv}`
|
|
274
|
-
: `${config.llm.apiKeyEnv} is not set${config.evaluation.scoring.fallback === "exact_match" ? "; explicit exact_match fallback will be used" : ""}`,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
162
|
async function localBaseModelCheck(path) {
|
|
278
163
|
const resolvedPath = resolve(path);
|
|
279
164
|
try {
|
|
@@ -306,13 +191,8 @@ export async function runDoctor(config, request) {
|
|
|
306
191
|
checks.push(await localBaseModelCheck(config.paths.baseModel));
|
|
307
192
|
if (request) {
|
|
308
193
|
checks.push(placeholderSpecCheck(request));
|
|
309
|
-
|
|
310
|
-
if (gatedToken)
|
|
311
|
-
checks.push(gatedToken);
|
|
194
|
+
resolveTrainingModel(request.spec_snapshot.base_model);
|
|
312
195
|
}
|
|
313
|
-
const judge = judgeCheck(config);
|
|
314
|
-
if (judge)
|
|
315
|
-
checks.push(judge);
|
|
316
196
|
const pythonPlans = buildDoctorPythonPlans(config);
|
|
317
197
|
if (pythonPlans.length > 0) {
|
|
318
198
|
const uvVersion = await runCommand("uv", ["--version"]);
|
|
@@ -326,13 +206,12 @@ export async function runDoctor(config, request) {
|
|
|
326
206
|
if (uvVersion.code === 0) {
|
|
327
207
|
const uniquePlans = new Map();
|
|
328
208
|
for (const plan of pythonPlans) {
|
|
329
|
-
const key = JSON.stringify([plan.command, plan.args, plan.
|
|
209
|
+
const key = JSON.stringify([plan.command, plan.args, plan.env]);
|
|
330
210
|
if (!uniquePlans.has(key))
|
|
331
211
|
uniquePlans.set(key, plan);
|
|
332
212
|
}
|
|
333
213
|
for (const plan of uniquePlans.values()) {
|
|
334
214
|
const result = await runCommand(plan.command, plan.args, {
|
|
335
|
-
cwd: plan.cwd,
|
|
336
215
|
env: plan.env,
|
|
337
216
|
timeoutMs: 1_800_000,
|
|
338
217
|
});
|
|
@@ -342,7 +221,7 @@ export async function runDoctor(config, request) {
|
|
|
342
221
|
message: result.code === 0
|
|
343
222
|
? firstLine(result.stdout)
|
|
344
223
|
: result.error ?? (firstLine(result.stderr) || `${plan.command} exited ${result.code}`),
|
|
345
|
-
details: { command: commandText(plan.command, plan.args)
|
|
224
|
+
details: { command: commandText(plan.command, plan.args) },
|
|
346
225
|
});
|
|
347
226
|
}
|
|
348
227
|
}
|
|
@@ -353,29 +232,18 @@ export async function runDoctor(config, request) {
|
|
|
353
232
|
ok: true,
|
|
354
233
|
message: config.dryRun
|
|
355
234
|
? "Python dependency checks skipped because dryRun is enabled."
|
|
356
|
-
: "
|
|
235
|
+
: "Python dependency checks were skipped.",
|
|
357
236
|
});
|
|
358
237
|
}
|
|
359
|
-
for (const entry of configuredCommandChecks(config)) {
|
|
360
|
-
if (!entry.command) {
|
|
361
|
-
checks.push({ name: entry.name, ok: false, message: `${entry.name} has no command configured` });
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
const check = await executableCheck(entry.command, { cwd: entry.cwd, env: entry.env });
|
|
365
|
-
checks.push({ ...check, name: entry.name });
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
238
|
const device = config.evaluation.inference.device;
|
|
369
|
-
if (!config.dryRun
|
|
239
|
+
if (!config.dryRun) {
|
|
370
240
|
const nvidiaSmi = await runCommand("nvidia-smi", []);
|
|
371
241
|
checks.push({
|
|
372
242
|
name: "nvidia-smi",
|
|
373
|
-
ok:
|
|
243
|
+
ok: nvidiaSmi.code === 0,
|
|
374
244
|
message: nvidiaSmi.code === 0
|
|
375
245
|
? firstLine(nvidiaSmi.stdout)
|
|
376
|
-
:
|
|
377
|
-
? "nvidia-smi is unavailable; device=auto may use MPS or CPU."
|
|
378
|
-
: nvidiaSmi.error ?? (firstLine(nvidiaSmi.stderr) || "nvidia-smi not available"),
|
|
246
|
+
: nvidiaSmi.error ?? (firstLine(nvidiaSmi.stderr) || "nvidia-smi not available"),
|
|
379
247
|
});
|
|
380
248
|
}
|
|
381
249
|
else {
|
|
@@ -387,20 +255,14 @@ export async function runDoctor(config, request) {
|
|
|
387
255
|
: `nvidia-smi is not required for device=${device}.`,
|
|
388
256
|
});
|
|
389
257
|
}
|
|
390
|
-
const trainingCommand =
|
|
391
|
-
? config.training.command
|
|
392
|
-
: buildEntrypointCommand(config.training, {
|
|
393
|
-
defaultScript: request?.training_method === "dpo"
|
|
394
|
-
? "training/local-runner/src/train_dpo.py"
|
|
395
|
-
: "training/local-runner/src/train.py",
|
|
396
|
-
}).displayCommand;
|
|
258
|
+
const trainingCommand = buildBundledPythonCommand("train.py").displayCommand;
|
|
397
259
|
checks.push({
|
|
398
260
|
name: "effective-plan",
|
|
399
261
|
ok: true,
|
|
400
262
|
message: "Resolved the configured training and evaluation plan.",
|
|
401
263
|
details: {
|
|
402
264
|
training_command: trainingCommand ?? null,
|
|
403
|
-
evaluation_provider:
|
|
265
|
+
evaluation_provider: "transformers",
|
|
404
266
|
evaluation_device: device,
|
|
405
267
|
artifact_root: resolve(config.artifactRoot),
|
|
406
268
|
store_root: resolve(config.storeRoot ?? defaultLocalHome()),
|