@tuned-tensor/cli 0.15.0 → 0.16.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/README.md +110 -42
- package/dist/{chunk-YGN26POJ.js → chunk-6RLNBTM6.js} +377 -99
- package/dist/chunk-6RLNBTM6.js.map +1 -0
- package/dist/index.js +58 -142
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +143 -32
- package/dist/local-runtime.js.map +1 -1
- package/docs/local-runtime/architecture.md +17 -6
- package/docs/local-runtime/aws-gpu.md +131 -0
- package/docs/local-runtime/foundation-long-runs.md +21 -5
- package/docs/local-runtime/serving.md +11 -1
- package/examples/local-runtime/aws-runner.json +13 -0
- package/package.json +9 -6
- package/training/adapter/src/foundation_export.py +81 -0
- package/training/adapter/src/serve.py +12 -5
- package/training/adapter/src/train.py +4 -0
- package/training/foundation/pyproject.toml +4 -0
- package/training/foundation/src/model.py +2 -0
- package/training/foundation/uv.lock +225 -0
- package/dist/chunk-YGN26POJ.js.map +0 -1
|
@@ -1076,7 +1076,7 @@ var comparisonReportSchema = z.object({
|
|
|
1076
1076
|
}).strict())
|
|
1077
1077
|
}).strict();
|
|
1078
1078
|
var trainingReportSchema = z.object({
|
|
1079
|
-
provider: z.
|
|
1079
|
+
provider: z.enum(["local-uv", "aws-ssh"]),
|
|
1080
1080
|
training_job_name: z.string(),
|
|
1081
1081
|
model_artifact_uri: z.string().optional(),
|
|
1082
1082
|
base_model_artifact_uri: z.string().optional(),
|
|
@@ -1167,6 +1167,16 @@ var evaluationConfigSchema = z.object({
|
|
|
1167
1167
|
}).strict().optional()
|
|
1168
1168
|
}).strict();
|
|
1169
1169
|
var localRunnerConfigSchema = z.object({
|
|
1170
|
+
gpu: z.object({
|
|
1171
|
+
provider: z.literal("aws"),
|
|
1172
|
+
instanceId: z.string().regex(/^i-[a-f0-9]{8,17}$/),
|
|
1173
|
+
profile: z.string().min(1).optional(),
|
|
1174
|
+
region: z.string().min(1).optional(),
|
|
1175
|
+
user: z.string().regex(/^[a-z_][a-z0-9_-]*$/i),
|
|
1176
|
+
identityFile: z.string().min(1).optional(),
|
|
1177
|
+
privateIp: z.boolean().default(false),
|
|
1178
|
+
maxSeconds: z.number().int().min(60).max(604800).default(86400)
|
|
1179
|
+
}).strict().optional(),
|
|
1170
1180
|
storeRoot: z.string().optional(),
|
|
1171
1181
|
artifactRoot: z.string().default(DEFAULT_ARTIFACT_ROOT),
|
|
1172
1182
|
dryRun: z.boolean().default(false),
|
|
@@ -2907,17 +2917,240 @@ function evaluateGeneralRegressionGate(comparison, policy) {
|
|
|
2907
2917
|
}
|
|
2908
2918
|
|
|
2909
2919
|
// src/local-runtime/orchestrator.ts
|
|
2910
|
-
import { lstat as lstat5, mkdir as
|
|
2920
|
+
import { lstat as lstat5, mkdir as mkdir9, open as open3, readFile as readFile8, readdir as readdir5, rename as rename3, rm as rm4, stat as stat7 } from "fs/promises";
|
|
2911
2921
|
import { createReadStream as createReadStream3 } from "fs";
|
|
2912
|
-
import { createHash as
|
|
2922
|
+
import { createHash as createHash7, randomUUID as randomUUID5 } from "crypto";
|
|
2913
2923
|
import { performance } from "perf_hooks";
|
|
2914
2924
|
import { homedir as homedir4 } from "os";
|
|
2915
|
-
import { basename, dirname as
|
|
2925
|
+
import { basename as basename2, dirname as dirname9, join as join10, relative as relative4, resolve as resolve13 } from "path";
|
|
2926
|
+
|
|
2927
|
+
// src/local-runtime/gpu-executor.ts
|
|
2928
|
+
import { createHash as createHash5, randomUUID as randomUUID4 } from "crypto";
|
|
2929
|
+
import { mkdtemp, mkdir as mkdir6, realpath as realpath3, rm as rm2, stat as stat5, writeFile as writeFile4 } from "fs/promises";
|
|
2930
|
+
import { tmpdir } from "os";
|
|
2931
|
+
import { basename, dirname as dirname7, join as join7, resolve as resolve10 } from "path";
|
|
2932
|
+
function shellQuote(value) {
|
|
2933
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
2934
|
+
}
|
|
2935
|
+
async function resolveGpuHost(gpu) {
|
|
2936
|
+
let stdout = "";
|
|
2937
|
+
const result = await runLoggedProcess({
|
|
2938
|
+
command: "aws",
|
|
2939
|
+
commandArgs: [
|
|
2940
|
+
"ec2",
|
|
2941
|
+
"describe-instances",
|
|
2942
|
+
"--instance-ids",
|
|
2943
|
+
gpu.instanceId,
|
|
2944
|
+
...gpu.profile ? ["--profile", gpu.profile] : [],
|
|
2945
|
+
...gpu.region ? ["--region", gpu.region] : [],
|
|
2946
|
+
"--output",
|
|
2947
|
+
"json",
|
|
2948
|
+
"--no-cli-pager"
|
|
2949
|
+
],
|
|
2950
|
+
timeoutMs: 3e4,
|
|
2951
|
+
stage: "gpu_connect",
|
|
2952
|
+
onLine: (line, stream) => {
|
|
2953
|
+
if (stream === "stdout") stdout += line;
|
|
2954
|
+
}
|
|
2955
|
+
});
|
|
2956
|
+
if (result.exitCode !== 0) throw new Error(`AWS GPU lookup failed: ${result.stderr}`);
|
|
2957
|
+
const instances = JSON.parse(stdout).Reservations?.flatMap((r) => r.Instances ?? []);
|
|
2958
|
+
const instance = instances?.find((i) => i.InstanceId === gpu.instanceId);
|
|
2959
|
+
if (instance?.State?.Name !== "running") throw new Error(`AWS GPU instance ${gpu.instanceId} must already be running.`);
|
|
2960
|
+
const host = gpu.privateIp ? instance.PrivateIpAddress : instance.PublicIpAddress;
|
|
2961
|
+
if (typeof host !== "string" || !/^[0-9.]+$/.test(host)) {
|
|
2962
|
+
throw new Error("AWS GPU instance has no selected IP address. Use privateIp with a reachable VPC/VPN, or assign a public IP.");
|
|
2963
|
+
}
|
|
2964
|
+
return `${gpu.user}@${host}`;
|
|
2965
|
+
}
|
|
2966
|
+
function sshOptions(gpu) {
|
|
2967
|
+
return [
|
|
2968
|
+
"-o",
|
|
2969
|
+
"BatchMode=yes",
|
|
2970
|
+
"-o",
|
|
2971
|
+
"StrictHostKeyChecking=yes",
|
|
2972
|
+
"-o",
|
|
2973
|
+
"ConnectTimeout=15",
|
|
2974
|
+
"-o",
|
|
2975
|
+
"ServerAliveInterval=15",
|
|
2976
|
+
"-o",
|
|
2977
|
+
"ServerAliveCountMax=3",
|
|
2978
|
+
...gpu.identityFile ? ["-i", gpu.identityFile] : []
|
|
2979
|
+
];
|
|
2980
|
+
}
|
|
2981
|
+
async function checkAwsGpu(gpu) {
|
|
2982
|
+
const host = await resolveGpuHost(gpu);
|
|
2983
|
+
const result = await runLoggedProcess({
|
|
2984
|
+
command: "ssh",
|
|
2985
|
+
commandArgs: [
|
|
2986
|
+
...sshOptions(gpu),
|
|
2987
|
+
host,
|
|
2988
|
+
`bash -lc ${shellQuote("command -v uv && command -v rsync && command -v timeout && command -v setsid && nvidia-smi")}`
|
|
2989
|
+
],
|
|
2990
|
+
stage: "gpu_check",
|
|
2991
|
+
timeoutMs: 3e4
|
|
2992
|
+
});
|
|
2993
|
+
if (result.exitCode !== 0) throw new Error(`AWS GPU preflight failed: ${result.stderr}`);
|
|
2994
|
+
}
|
|
2995
|
+
function gpuBaseModelPath(config, model, revision) {
|
|
2996
|
+
if (config.paths.baseModel) return resolve10(config.paths.baseModel);
|
|
2997
|
+
if (!revision || !/^[a-f0-9]{40}$/i.test(revision)) {
|
|
2998
|
+
throw new Error("AWS GPU execution needs an immutable base-model revision. Run tt models prefetch first or configure paths.baseModel.");
|
|
2999
|
+
}
|
|
3000
|
+
const env = withHuggingFaceCacheEnvironment(process.env, config.paths.modelCache);
|
|
3001
|
+
return join7(env.HF_HUB_CACHE, `models--${model.replaceAll("/", "--")}`, "snapshots", revision);
|
|
3002
|
+
}
|
|
3003
|
+
async function physicalTransferPath(path) {
|
|
3004
|
+
try {
|
|
3005
|
+
return await realpath3(path);
|
|
3006
|
+
} catch (error) {
|
|
3007
|
+
if (error.code !== "ENOENT" || dirname7(path) === path) throw error;
|
|
3008
|
+
return join7(await physicalTransferPath(dirname7(path)), basename(path));
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
async function assertSeparateTransferPaths(files) {
|
|
3012
|
+
const paths = await Promise.all(files.map((file) => physicalTransferPath(resolve10(file.path))));
|
|
3013
|
+
for (let i = 0; i < files.length; i += 1) {
|
|
3014
|
+
for (let j = 0; j < i; j += 1) {
|
|
3015
|
+
if (files[i].direction === "input" && files[j].direction === "input") continue;
|
|
3016
|
+
const left = paths[i];
|
|
3017
|
+
const right = paths[j];
|
|
3018
|
+
if (left === right || files[i].directory && right.startsWith(`${left}/`) || files[j].directory && left.startsWith(`${right}/`)) {
|
|
3019
|
+
throw new Error(`GPU transfer paths overlap: ${files[i].path} and ${files[j].path}. Use separate input, output, recovery, and backup directories.`);
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
async function runGpuProcess(args) {
|
|
3025
|
+
if (await args.shouldCancel?.()) throw new ProcessCancelledError();
|
|
3026
|
+
await assertSeparateTransferPaths(args.files);
|
|
3027
|
+
const host = await resolveGpuHost(args.gpu);
|
|
3028
|
+
const options = sshOptions(args.gpu);
|
|
3029
|
+
const identity = args.logPath ? createHash5("sha256").update(resolve10(args.logPath)).digest("hex").slice(0, 32) : randomUUID4();
|
|
3030
|
+
const root = `/tmp/tt-gpu-${identity}`;
|
|
3031
|
+
const scratch = await mkdtemp(join7(tmpdir(), "tt-gpu-"));
|
|
3032
|
+
const runtime = join7(localRuntimePackageRoot(import.meta.url), "training", args.runtime);
|
|
3033
|
+
const mappings = args.files.map((file, index) => ({ ...file, path: resolve10(file.path), remote: `${root}/files/${index}/${basename(file.path)}` }));
|
|
3034
|
+
const mapPath = (path) => {
|
|
3035
|
+
const absolute = resolve10(path);
|
|
3036
|
+
const match = [...mappings].sort((a, b) => b.path.length - a.path.length).find((file) => absolute === file.path || file.directory && absolute.startsWith(`${file.path}/`));
|
|
3037
|
+
if (match) return match.remote + absolute.slice(match.path.length);
|
|
3038
|
+
if (absolute === runtime || absolute.startsWith(`${runtime}/`)) return `${root}/runtime${absolute.slice(runtime.length)}`;
|
|
3039
|
+
throw new Error(`GPU process path is not declared for transfer: ${path}`);
|
|
3040
|
+
};
|
|
3041
|
+
const transport = async (command, commandArgs, cancellable = true) => {
|
|
3042
|
+
const result = await runLoggedProcess({
|
|
3043
|
+
command,
|
|
3044
|
+
commandArgs,
|
|
3045
|
+
stage: args.stage,
|
|
3046
|
+
reporter: args.reporter,
|
|
3047
|
+
timeoutMs: 30 * 6e4,
|
|
3048
|
+
shouldCancel: cancellable ? args.shouldCancel : void 0
|
|
3049
|
+
});
|
|
3050
|
+
if (result.exitCode !== 0) throw new Error(`${command} failed for ${host}: ${result.stderr}`);
|
|
3051
|
+
};
|
|
3052
|
+
const ssh = async (script, cancellable = true) => transport("ssh", [...options, host, script], cancellable);
|
|
3053
|
+
const sync = async (source, destination, upload, cancellable = true, mirror = false) => {
|
|
3054
|
+
await transport("rsync", [
|
|
3055
|
+
"-rlt",
|
|
3056
|
+
"--copy-links",
|
|
3057
|
+
"--protect-args",
|
|
3058
|
+
"--modify-window=-1",
|
|
3059
|
+
"--chmod=Du=rwx,Dgo=,Fu=rw,Fgo=",
|
|
3060
|
+
...!upload ? ["--delay-updates", ...mirror ? ["--delete-delay"] : []] : [],
|
|
3061
|
+
"--exclude=.venv",
|
|
3062
|
+
"--exclude=__pycache__",
|
|
3063
|
+
"--exclude=.git",
|
|
3064
|
+
"-e",
|
|
3065
|
+
["ssh", ...options].map(shellQuote).join(" "),
|
|
3066
|
+
"--",
|
|
3067
|
+
upload ? source : `${host}:${source}`,
|
|
3068
|
+
upload ? `${host}:${destination}` : destination
|
|
3069
|
+
], cancellable);
|
|
3070
|
+
};
|
|
3071
|
+
const grace = Math.ceil((args.shutdownGraceMs ?? 5e3) / 1e3);
|
|
3072
|
+
const stop = `kill -TERM -- -"$pid" 2>/dev/null || true; remaining=${grace}; while kill -0 -- -"$pid" 2>/dev/null && [ "$remaining" -gt 0 ]; do sleep 1; remaining=$((remaining - 1)); done; kill -KILL -- -"$pid" 2>/dev/null || true`;
|
|
3073
|
+
const owner = randomUUID4();
|
|
3074
|
+
let reservationAttempted = false;
|
|
3075
|
+
let started = false;
|
|
3076
|
+
let completed = false;
|
|
3077
|
+
try {
|
|
3078
|
+
reservationAttempted = true;
|
|
3079
|
+
await ssh(`umask 077; mkdir ${root} && printf '%s' ${shellQuote(owner)} > ${root}/.owner && mkdir ${root}/runtime ${root}/files`).catch((error) => {
|
|
3080
|
+
if (error instanceof ProcessCancelledError) throw error;
|
|
3081
|
+
throw new Error(`Cannot reserve GPU staging directory ${host}:${root}. Another process or unrecovered artifacts may be present. ${error instanceof Error ? error.message : error}`);
|
|
3082
|
+
});
|
|
3083
|
+
for (const name of ["pyproject.toml", "uv.lock", "src"]) {
|
|
3084
|
+
await sync(join7(runtime, name), `${root}/runtime/`, true);
|
|
3085
|
+
}
|
|
3086
|
+
for (const file of mappings) {
|
|
3087
|
+
await ssh(`mkdir -p ${shellQuote(file.directory ? file.remote : dirname7(file.remote))}`);
|
|
3088
|
+
if (file.direction !== "output") {
|
|
3089
|
+
const exists3 = await stat5(file.path).catch((error) => {
|
|
3090
|
+
if (error.code === "ENOENT" && file.direction === "both") return void 0;
|
|
3091
|
+
throw error;
|
|
3092
|
+
});
|
|
3093
|
+
if (exists3) await sync(file.path + (file.directory ? "/" : ""), file.remote + (file.directory ? "/" : ""), true);
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
if (args.document) {
|
|
3097
|
+
const value = { ...args.document.value };
|
|
3098
|
+
for (const key of args.document.pathKeys) if (typeof value[key] === "string") value[key] = mapPath(value[key]);
|
|
3099
|
+
const path = join7(scratch, "document.json");
|
|
3100
|
+
await writeFile4(path, JSON.stringify(value), { mode: 384 });
|
|
3101
|
+
await sync(path, mapPath(args.document.path), true);
|
|
3102
|
+
}
|
|
3103
|
+
const env = {
|
|
3104
|
+
UV_PROJECT_ENVIRONMENT: `${root}/venv`,
|
|
3105
|
+
TORCH_DISABLE_NATIVE_JIT: "1",
|
|
3106
|
+
HF_HUB_OFFLINE: "1",
|
|
3107
|
+
TRANSFORMERS_OFFLINE: "1"
|
|
3108
|
+
};
|
|
3109
|
+
for (const [key, value] of Object.entries(args.env ?? {})) {
|
|
3110
|
+
if (value === void 0) continue;
|
|
3111
|
+
if (key === "BACKEND") env[key] = value;
|
|
3112
|
+
if (["SM_CHANNEL_TRAINING", "SM_CHANNEL_BASE_MODEL", "SM_MODEL_DIR", "SM_OUTPUT_DIR", "TT_HYPERPARAMETERS_PATH"].includes(key)) env[key] = mapPath(value);
|
|
3113
|
+
}
|
|
3114
|
+
const commandArgs = args.commandArgs.map((value) => value.startsWith("/") ? mapPath(value) : value);
|
|
3115
|
+
const command = ["env", ...Object.entries(env).map(([key, value]) => `${key}=${value}`), args.command, ...commandArgs].map(shellQuote).join(" ");
|
|
3116
|
+
const script = `umask 077; cd ${root}; setsid timeout -k 120 ${args.gpu.maxSeconds}s ${command} & pid=$!; echo "$pid" > pid; trap ${shellQuote(stop)} HUP INT TERM; wait "$pid"`;
|
|
3117
|
+
await args.reporter?.onEvent?.({ stage: args.stage, status: "running", message: `Running GPU process on ${args.gpu.instanceId}.`, details: { remote_directory: root } });
|
|
3118
|
+
started = true;
|
|
3119
|
+
const result = await runLoggedProcess({
|
|
3120
|
+
...args,
|
|
3121
|
+
env: process.env,
|
|
3122
|
+
command: "ssh",
|
|
3123
|
+
commandArgs: [...options, "-tt", host, `bash -lc ${shellQuote(script)}`]
|
|
3124
|
+
});
|
|
3125
|
+
completed = result.exitCode === 0;
|
|
3126
|
+
return result;
|
|
3127
|
+
} finally {
|
|
3128
|
+
try {
|
|
3129
|
+
if (started) {
|
|
3130
|
+
await ssh(`bash -c ${shellQuote(`if test -f ${root}/pid; then pid=$(cat ${root}/pid); ${stop}; fi`)}`, false);
|
|
3131
|
+
for (const file of mappings.filter((file2) => file2.direction !== "input")) {
|
|
3132
|
+
await mkdir6(file.directory ? file.path : dirname7(file.path), { recursive: true });
|
|
3133
|
+
if (!completed && !file.directory) {
|
|
3134
|
+
const exists3 = await runLoggedProcess({ command: "ssh", commandArgs: [...options, host, `test -e ${shellQuote(file.remote)}`], stage: args.stage, timeoutMs: 3e4 });
|
|
3135
|
+
if (exists3.exitCode === 1) continue;
|
|
3136
|
+
if (exists3.exitCode !== 0) throw new Error("Cannot inspect remote outputs.");
|
|
3137
|
+
}
|
|
3138
|
+
await sync(file.remote + (file.directory ? "/" : ""), file.path + (file.directory ? "/" : ""), false, false, file.directory === true && file.direction === "both");
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
if (reservationAttempted) await ssh(`if [ "$(cat ${root}/.owner 2>/dev/null)" = ${shellQuote(owner)} ]; then rm -rf -- ${root}; fi`, false);
|
|
3142
|
+
} catch (error) {
|
|
3143
|
+
throw new Error(`GPU cleanup or artifact retrieval failed. Remote files remain at ${host}:${root}. ${error instanceof Error ? error.message : error}`);
|
|
3144
|
+
} finally {
|
|
3145
|
+
await rm2(scratch, { recursive: true, force: true });
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
2916
3149
|
|
|
2917
3150
|
// src/local-runtime/evaluation.ts
|
|
2918
|
-
import { createHash as
|
|
2919
|
-
import { mkdir as
|
|
2920
|
-
import { dirname as
|
|
3151
|
+
import { createHash as createHash6 } from "crypto";
|
|
3152
|
+
import { mkdir as mkdir7, readFile as readFile6, rm as rm3, stat as stat6, writeFile as writeFile5 } from "fs/promises";
|
|
3153
|
+
import { dirname as dirname8, join as join8, resolve as resolve11 } from "path";
|
|
2921
3154
|
function normalize(value) {
|
|
2922
3155
|
return value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
2923
3156
|
}
|
|
@@ -3137,8 +3370,8 @@ async function runTransformersInference(args) {
|
|
|
3137
3370
|
const inputPath = `${args.outputPath}.inference-input.json`;
|
|
3138
3371
|
const outputPath = `${args.outputPath}.inference-output.json`;
|
|
3139
3372
|
const logPath = `${args.outputPath}.inference.log`;
|
|
3140
|
-
await
|
|
3141
|
-
await
|
|
3373
|
+
await mkdir7(dirname8(inputPath), { recursive: true });
|
|
3374
|
+
await writeFile5(inputPath, `${JSON.stringify({
|
|
3142
3375
|
protocol_version: INFERENCE_PROTOCOL_VERSION,
|
|
3143
3376
|
kind: args.kind,
|
|
3144
3377
|
model_id: args.modelId,
|
|
@@ -3152,7 +3385,7 @@ async function runTransformersInference(args) {
|
|
|
3152
3385
|
id: String(index),
|
|
3153
3386
|
input: example.input
|
|
3154
3387
|
})),
|
|
3155
|
-
model_cache: args.config.paths.modelCache ?
|
|
3388
|
+
model_cache: args.config.paths.modelCache ? resolve11(args.config.paths.modelCache) : void 0,
|
|
3156
3389
|
trust_remote_code: false,
|
|
3157
3390
|
device: args.config.evaluation.inference.device,
|
|
3158
3391
|
generation: {
|
|
@@ -3162,7 +3395,7 @@ async function runTransformersInference(args) {
|
|
|
3162
3395
|
}
|
|
3163
3396
|
}, null, 2)}
|
|
3164
3397
|
`, "utf8");
|
|
3165
|
-
await
|
|
3398
|
+
await rm3(outputPath, { force: true });
|
|
3166
3399
|
const entrypoint = buildBundledPythonCommand(
|
|
3167
3400
|
"evaluate.py",
|
|
3168
3401
|
["--input", inputPath, "--output", outputPath]
|
|
@@ -3178,7 +3411,30 @@ async function runTransformersInference(args) {
|
|
|
3178
3411
|
log_path: logPath
|
|
3179
3412
|
}
|
|
3180
3413
|
});
|
|
3181
|
-
const
|
|
3414
|
+
const gpu = args.config.evaluation.inference.device === "cuda" ? args.config.gpu : void 0;
|
|
3415
|
+
const execute = gpu ? async (processArgs) => {
|
|
3416
|
+
const document = JSON.parse(await readFile6(inputPath, "utf8"));
|
|
3417
|
+
document.model_source = gpuBaseModelPath(args.config, args.baseModelId, args.baseModelRevision);
|
|
3418
|
+
delete document.model_cache;
|
|
3419
|
+
const files = [
|
|
3420
|
+
{ path: inputPath, direction: "input" },
|
|
3421
|
+
{ path: outputPath, direction: "output" },
|
|
3422
|
+
{ path: document.model_source, direction: "input", directory: true },
|
|
3423
|
+
...document.adapter_path ? [{
|
|
3424
|
+
path: document.adapter_path,
|
|
3425
|
+
direction: "input",
|
|
3426
|
+
directory: (await stat6(document.adapter_path)).isDirectory()
|
|
3427
|
+
}] : []
|
|
3428
|
+
];
|
|
3429
|
+
return runGpuProcess({
|
|
3430
|
+
...processArgs,
|
|
3431
|
+
gpu,
|
|
3432
|
+
runtime: "adapter",
|
|
3433
|
+
files,
|
|
3434
|
+
document: { path: inputPath, value: document, pathKeys: ["model_source", "adapter_path"] }
|
|
3435
|
+
});
|
|
3436
|
+
} : runLoggedProcess;
|
|
3437
|
+
const result = await execute({
|
|
3182
3438
|
command: entrypoint.command,
|
|
3183
3439
|
commandArgs: entrypoint.commandArgs,
|
|
3184
3440
|
env: withBundledPythonEnvironment(
|
|
@@ -3218,7 +3474,7 @@ function assertEvaluationScoringReady(config) {
|
|
|
3218
3474
|
}
|
|
3219
3475
|
}
|
|
3220
3476
|
function baselineCacheKey(args) {
|
|
3221
|
-
return
|
|
3477
|
+
return createHash6("sha256").update(JSON.stringify({
|
|
3222
3478
|
v: 6,
|
|
3223
3479
|
inference_protocol_version: INFERENCE_PROTOCOL_VERSION,
|
|
3224
3480
|
package_version: args.packageVersion,
|
|
@@ -3238,10 +3494,10 @@ function baselineInputsAreStable(args) {
|
|
|
3238
3494
|
return args.config.paths.baseModel ? Boolean(args.sourceFingerprint) : Boolean(args.baseModelRevision);
|
|
3239
3495
|
}
|
|
3240
3496
|
function baselineStoreRoot(config) {
|
|
3241
|
-
return config.storeRoot ?
|
|
3497
|
+
return config.storeRoot ? resolve11(config.storeRoot) : defaultLocalHome();
|
|
3242
3498
|
}
|
|
3243
3499
|
function baselineCachePath(config, key) {
|
|
3244
|
-
return
|
|
3500
|
+
return join8(baselineStoreRoot(config), "cache", "baseline-evals", `${key}.json`);
|
|
3245
3501
|
}
|
|
3246
3502
|
async function readBaselineCache(config, path) {
|
|
3247
3503
|
await hardenExistingLocalStore(baselineStoreRoot(config));
|
|
@@ -3255,7 +3511,7 @@ var cachedPackageVersion = null;
|
|
|
3255
3511
|
async function packageVersion() {
|
|
3256
3512
|
if (cachedPackageVersion !== null) return cachedPackageVersion;
|
|
3257
3513
|
try {
|
|
3258
|
-
const raw = await readFile6(
|
|
3514
|
+
const raw = await readFile6(resolve11(localRuntimePackageRoot(import.meta.url), "package.json"), "utf8");
|
|
3259
3515
|
cachedPackageVersion = String(
|
|
3260
3516
|
JSON.parse(raw).version ?? "unknown"
|
|
3261
3517
|
);
|
|
@@ -3324,7 +3580,7 @@ async function evaluateExamples(args) {
|
|
|
3324
3580
|
baseModelId,
|
|
3325
3581
|
args.baseModelRevision
|
|
3326
3582
|
);
|
|
3327
|
-
const localModelSource = args.modelSource ?
|
|
3583
|
+
const localModelSource = args.modelSource ? resolve11(args.modelSource) !== resolve11(baseModelId) : false;
|
|
3328
3584
|
const baseModelRevision = localModelSource ? defaultBaseModelRevision(baseModelId) : requestedBaseModelRevision;
|
|
3329
3585
|
const maxExamples = args.config.evaluation.maxExamples ?? args.maxExamples ?? args.examples.length;
|
|
3330
3586
|
const truncated = args.examples.length > maxExamples;
|
|
@@ -3440,8 +3696,8 @@ function compareEvalReports(baseline, candidate) {
|
|
|
3440
3696
|
}
|
|
3441
3697
|
|
|
3442
3698
|
// src/local-runtime/process-training.ts
|
|
3443
|
-
import { access, mkdir as
|
|
3444
|
-
import { join as
|
|
3699
|
+
import { access, mkdir as mkdir8, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
|
|
3700
|
+
import { join as join9, resolve as resolve12 } from "path";
|
|
3445
3701
|
function buildTrainingHyperparameters(request, options = {}) {
|
|
3446
3702
|
const model = resolveTrainingModel(request.spec_snapshot.base_model);
|
|
3447
3703
|
const hyper = request.hyperparameters;
|
|
@@ -3532,15 +3788,15 @@ function createTrainingProgressForwarder(reporter) {
|
|
|
3532
3788
|
async function launchProcessTraining(args) {
|
|
3533
3789
|
const { request, artifacts, config } = args;
|
|
3534
3790
|
const jobName = `tt-local-${request.run_id}`;
|
|
3535
|
-
await
|
|
3791
|
+
await mkdir8(artifacts.trainingDir, { recursive: true });
|
|
3536
3792
|
await copyDatasetToTrainingChannel(artifacts);
|
|
3537
3793
|
const hyperparameters = buildTrainingHyperparameters(request, {
|
|
3538
3794
|
baseModelRevision: args.baseModelRevision
|
|
3539
3795
|
});
|
|
3540
|
-
await writeJson(
|
|
3796
|
+
await writeJson(join9(artifacts.trainingConfigDir, "hyperparameters.json"), hyperparameters);
|
|
3541
3797
|
const entrypoint = buildBundledPythonCommand("train.py");
|
|
3542
3798
|
if (config.dryRun) {
|
|
3543
|
-
await
|
|
3799
|
+
await writeFile6(
|
|
3544
3800
|
artifacts.trainingLog,
|
|
3545
3801
|
"Dry run enabled. Training process was not launched.\n",
|
|
3546
3802
|
"utf8"
|
|
@@ -3562,20 +3818,22 @@ async function launchProcessTraining(args) {
|
|
|
3562
3818
|
withOfflineHuggingFaceCacheEnvironment({
|
|
3563
3819
|
...inheritedEnv,
|
|
3564
3820
|
BACKEND: "local",
|
|
3565
|
-
SM_CHANNEL_TRAINING:
|
|
3566
|
-
SM_CHANNEL_BASE_MODEL: config.paths.baseModel ?
|
|
3567
|
-
SM_MODEL_DIR:
|
|
3568
|
-
SM_OUTPUT_DIR:
|
|
3569
|
-
TT_HYPERPARAMETERS_PATH:
|
|
3821
|
+
SM_CHANNEL_TRAINING: resolve12(artifacts.trainingInputDir),
|
|
3822
|
+
SM_CHANNEL_BASE_MODEL: config.paths.baseModel ? resolve12(config.paths.baseModel) : void 0,
|
|
3823
|
+
SM_MODEL_DIR: resolve12(artifacts.trainingModelDir),
|
|
3824
|
+
SM_OUTPUT_DIR: resolve12(artifacts.trainingOutputDir),
|
|
3825
|
+
TT_HYPERPARAMETERS_PATH: resolve12(artifacts.trainingConfigDir, "hyperparameters.json")
|
|
3570
3826
|
}, config.paths.modelCache)
|
|
3571
3827
|
);
|
|
3572
3828
|
for (const [key, value] of Object.entries(childEnv)) {
|
|
3573
3829
|
if (value === void 0) delete childEnv[key];
|
|
3574
3830
|
}
|
|
3575
|
-
|
|
3831
|
+
const remoteBaseModel = config.gpu ? gpuBaseModelPath(config, request.spec_snapshot.base_model, args.baseModelRevision) : void 0;
|
|
3832
|
+
if (remoteBaseModel) childEnv.SM_CHANNEL_BASE_MODEL = remoteBaseModel;
|
|
3833
|
+
if (config.paths.modelCache) await mkdir8(resolve12(config.paths.modelCache), { recursive: true });
|
|
3576
3834
|
if (config.paths.baseModel) {
|
|
3577
3835
|
await verifyLocalBaseModel(
|
|
3578
|
-
|
|
3836
|
+
resolve12(config.paths.baseModel),
|
|
3579
3837
|
request.spec_snapshot.base_model,
|
|
3580
3838
|
config.paths.modelCache
|
|
3581
3839
|
);
|
|
@@ -3592,7 +3850,19 @@ async function launchProcessTraining(args) {
|
|
|
3592
3850
|
}
|
|
3593
3851
|
});
|
|
3594
3852
|
const forwardTrainingProgress = createTrainingProgressForwarder(args.reporter);
|
|
3595
|
-
const
|
|
3853
|
+
const execute = config.gpu ? (processArgs) => runGpuProcess({
|
|
3854
|
+
...processArgs,
|
|
3855
|
+
gpu: config.gpu,
|
|
3856
|
+
runtime: "adapter",
|
|
3857
|
+
files: [
|
|
3858
|
+
{ path: artifacts.trainingInputDir, direction: "input", directory: true },
|
|
3859
|
+
{ path: artifacts.trainingConfigDir, direction: "input", directory: true },
|
|
3860
|
+
{ path: remoteBaseModel, direction: "input", directory: true },
|
|
3861
|
+
{ path: artifacts.trainingModelDir, direction: "output", directory: true },
|
|
3862
|
+
{ path: artifacts.trainingOutputDir, direction: "output", directory: true }
|
|
3863
|
+
]
|
|
3864
|
+
}) : runLoggedProcess;
|
|
3865
|
+
const { exitCode } = await execute({
|
|
3596
3866
|
command: entrypoint.command,
|
|
3597
3867
|
commandArgs: entrypoint.commandArgs,
|
|
3598
3868
|
env: childEnv,
|
|
@@ -3602,12 +3872,12 @@ async function launchProcessTraining(args) {
|
|
|
3602
3872
|
onLine: (line) => forwardTrainingProgress(line),
|
|
3603
3873
|
shouldCancel: args.shouldCancel
|
|
3604
3874
|
});
|
|
3605
|
-
const metricsPath =
|
|
3875
|
+
const metricsPath = join9(artifacts.trainingModelDir, "training-metrics.json");
|
|
3606
3876
|
const metrics = await pathExists(metricsPath) ? JSON.parse(await readFile7(metricsPath, "utf8")) : null;
|
|
3607
3877
|
if (exitCode !== 0) {
|
|
3608
3878
|
throw new Error(`Training process exited with code ${exitCode}. See ${artifacts.trainingLog}.`);
|
|
3609
3879
|
}
|
|
3610
|
-
const modelTarPath =
|
|
3880
|
+
const modelTarPath = join9(artifacts.trainingOutputDir, "model.tar.gz");
|
|
3611
3881
|
const modelUri = await pathExists(modelTarPath) ? fileUri(modelTarPath) : fileUri(artifacts.trainingModelDir);
|
|
3612
3882
|
await args.reporter?.onEvent?.({
|
|
3613
3883
|
stage: "training",
|
|
@@ -3621,7 +3891,7 @@ async function launchProcessTraining(args) {
|
|
|
3621
3891
|
}
|
|
3622
3892
|
});
|
|
3623
3893
|
return {
|
|
3624
|
-
provider: "local-uv",
|
|
3894
|
+
provider: config.gpu ? "aws-ssh" : "local-uv",
|
|
3625
3895
|
training_job_name: jobName,
|
|
3626
3896
|
model_artifact_uri: modelUri,
|
|
3627
3897
|
base_model_artifact_uri: config.paths.baseModel ? fileUri(config.paths.baseModel) : void 0,
|
|
@@ -3710,16 +3980,17 @@ async function loadRunRequest(path) {
|
|
|
3710
3980
|
function parseLocalRunnerConfig(document, path) {
|
|
3711
3981
|
const config = localRunnerConfigSchema.parse(document);
|
|
3712
3982
|
if (!path) return config;
|
|
3713
|
-
const configPath =
|
|
3714
|
-
const base =
|
|
3983
|
+
const configPath = resolve13(path);
|
|
3984
|
+
const base = dirname9(configPath);
|
|
3715
3985
|
const configPathValue = (value) => {
|
|
3716
3986
|
if (!value) return void 0;
|
|
3717
3987
|
if (value === "~") return homedir4();
|
|
3718
|
-
if (value.startsWith("~/")) return
|
|
3719
|
-
return
|
|
3988
|
+
if (value.startsWith("~/")) return resolve13(homedir4(), value.slice(2));
|
|
3989
|
+
return resolve13(base, value);
|
|
3720
3990
|
};
|
|
3721
3991
|
return {
|
|
3722
3992
|
...config,
|
|
3993
|
+
gpu: config.gpu ? { ...config.gpu, identityFile: configPathValue(config.gpu.identityFile) } : void 0,
|
|
3723
3994
|
artifactRoot: configPathValue(config.artifactRoot),
|
|
3724
3995
|
storeRoot: configPathValue(config.storeRoot),
|
|
3725
3996
|
paths: {
|
|
@@ -3737,7 +4008,7 @@ function parseLocalRunnerConfig(document, path) {
|
|
|
3737
4008
|
}
|
|
3738
4009
|
async function loadLocalRunnerConfig(path) {
|
|
3739
4010
|
if (!path) return parseLocalRunnerConfig({});
|
|
3740
|
-
const configPath =
|
|
4011
|
+
const configPath = resolve13(path);
|
|
3741
4012
|
return parseLocalRunnerConfig(
|
|
3742
4013
|
await loadJsonFile(configPath),
|
|
3743
4014
|
configPath
|
|
@@ -3752,7 +4023,7 @@ function stripFileUri(path) {
|
|
|
3752
4023
|
}
|
|
3753
4024
|
async function pathExists2(path) {
|
|
3754
4025
|
try {
|
|
3755
|
-
await
|
|
4026
|
+
await stat7(path);
|
|
3756
4027
|
return true;
|
|
3757
4028
|
} catch {
|
|
3758
4029
|
return false;
|
|
@@ -3910,7 +4181,7 @@ function canonicalJson2(value) {
|
|
|
3910
4181
|
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(object[key])}`).join(",")}}`;
|
|
3911
4182
|
}
|
|
3912
4183
|
function hashJson(value) {
|
|
3913
|
-
return
|
|
4184
|
+
return createHash7("sha256").update(canonicalJson2(value)).digest("hex");
|
|
3914
4185
|
}
|
|
3915
4186
|
var packageRoot2 = localRuntimePackageRoot(import.meta.url);
|
|
3916
4187
|
async function hashFileIfPresent(path) {
|
|
@@ -3922,21 +4193,21 @@ async function hashFileIfPresent(path) {
|
|
|
3922
4193
|
}
|
|
3923
4194
|
async function packageVersion2() {
|
|
3924
4195
|
try {
|
|
3925
|
-
const parsed = JSON.parse(await readFile8(
|
|
4196
|
+
const parsed = JSON.parse(await readFile8(resolve13(packageRoot2, "package.json"), "utf8"));
|
|
3926
4197
|
return typeof parsed.version === "string" ? parsed.version : "unknown";
|
|
3927
4198
|
} catch {
|
|
3928
4199
|
return "unknown";
|
|
3929
4200
|
}
|
|
3930
4201
|
}
|
|
3931
4202
|
async function runtimeFingerprint() {
|
|
3932
|
-
const bundledProject2 =
|
|
4203
|
+
const bundledProject2 = resolve13(packageRoot2, "training/adapter");
|
|
3933
4204
|
return hashJson({
|
|
3934
4205
|
tt_local_version: await packageVersion2(),
|
|
3935
4206
|
node_version: process.version,
|
|
3936
4207
|
platform: process.platform,
|
|
3937
4208
|
architecture: process.arch,
|
|
3938
|
-
runner_pyproject: await hashFileIfPresent(
|
|
3939
|
-
runner_uv_lock: await hashFileIfPresent(
|
|
4209
|
+
runner_pyproject: await hashFileIfPresent(resolve13(bundledProject2, "pyproject.toml")),
|
|
4210
|
+
runner_uv_lock: await hashFileIfPresent(resolve13(bundledProject2, "uv.lock"))
|
|
3940
4211
|
});
|
|
3941
4212
|
}
|
|
3942
4213
|
function preparedSourceFingerprint(args) {
|
|
@@ -3950,7 +4221,7 @@ function preparedSourceFingerprint(args) {
|
|
|
3950
4221
|
});
|
|
3951
4222
|
}
|
|
3952
4223
|
async function hashFile(path) {
|
|
3953
|
-
const hash =
|
|
4224
|
+
const hash = createHash7("sha256");
|
|
3954
4225
|
for await (const chunk of createReadStream3(path)) hash.update(chunk);
|
|
3955
4226
|
return hash.digest("hex");
|
|
3956
4227
|
}
|
|
@@ -3983,7 +4254,7 @@ async function resolveBaseModelRevision(request, config) {
|
|
|
3983
4254
|
}
|
|
3984
4255
|
const repository = `models--${request.spec_snapshot.base_model.replaceAll("/", "--")}`;
|
|
3985
4256
|
const cacheEnvironment = withHuggingFaceCacheEnvironment(process.env, config.paths.modelCache);
|
|
3986
|
-
const refPath =
|
|
4257
|
+
const refPath = resolve13(cacheEnvironment.HF_HUB_CACHE, repository, "refs", "main");
|
|
3987
4258
|
try {
|
|
3988
4259
|
const revision = (await readFile8(refPath, "utf8")).trim();
|
|
3989
4260
|
if (revision) return revision;
|
|
@@ -4004,12 +4275,12 @@ async function fingerprintLocalBaseModel(uri, expectedModelId, modelCache) {
|
|
|
4004
4275
|
const files = [];
|
|
4005
4276
|
const visit = async (directory) => {
|
|
4006
4277
|
for (const entry of await readdir5(directory, { withFileTypes: true })) {
|
|
4007
|
-
const child =
|
|
4278
|
+
const child = resolve13(directory, entry.name);
|
|
4008
4279
|
if (entry.isDirectory()) {
|
|
4009
4280
|
await visit(child);
|
|
4010
4281
|
continue;
|
|
4011
4282
|
}
|
|
4012
|
-
const childMetadata = await
|
|
4283
|
+
const childMetadata = await stat7(child);
|
|
4013
4284
|
if (entry.isSymbolicLink() && !childMetadata.isFile()) {
|
|
4014
4285
|
throw new Error(`Local base model contains a non-file symbolic link: ${child}`);
|
|
4015
4286
|
}
|
|
@@ -4043,27 +4314,27 @@ async function readStageMetadata(path) {
|
|
|
4043
4314
|
}
|
|
4044
4315
|
}
|
|
4045
4316
|
async function removePrefixedArtifacts(path) {
|
|
4046
|
-
const directory =
|
|
4047
|
-
const prefix = `${
|
|
4317
|
+
const directory = dirname9(path);
|
|
4318
|
+
const prefix = `${basename2(path)}.`;
|
|
4048
4319
|
const names = await readdir5(directory).catch(() => []);
|
|
4049
4320
|
await Promise.all([
|
|
4050
|
-
|
|
4051
|
-
...names.filter((name) => name.startsWith(prefix)).map((name) =>
|
|
4321
|
+
rm4(path, { force: true }),
|
|
4322
|
+
...names.filter((name) => name.startsWith(prefix)).map((name) => rm4(resolve13(directory, name), { recursive: true, force: true }))
|
|
4052
4323
|
]);
|
|
4053
4324
|
}
|
|
4054
4325
|
async function cleanupStageArtifacts(artifacts, stage) {
|
|
4055
|
-
const removeReport = async () =>
|
|
4326
|
+
const removeReport = async () => rm4(artifacts.runReportJson, { force: true });
|
|
4056
4327
|
if (stage === "prepare") {
|
|
4057
4328
|
await Promise.all([
|
|
4058
4329
|
removePrefixedArtifacts(artifacts.baselineEvalJson),
|
|
4059
4330
|
removePrefixedArtifacts(artifacts.candidateEvalJson),
|
|
4060
4331
|
removePrefixedArtifacts(artifacts.generalBaselineEvalJson),
|
|
4061
4332
|
removePrefixedArtifacts(artifacts.generalCandidateEvalJson),
|
|
4062
|
-
|
|
4333
|
+
rm4(artifacts.trainingDir, { recursive: true, force: true }),
|
|
4063
4334
|
removePrefixedArtifacts(artifacts.trainingReportJson),
|
|
4064
|
-
|
|
4335
|
+
rm4(resolve13(artifacts.runDir, "model.tar.gz"), { force: true }),
|
|
4065
4336
|
removeReport(),
|
|
4066
|
-
|
|
4337
|
+
rm4(artifacts.artifactManifestJson, { force: true })
|
|
4067
4338
|
]);
|
|
4068
4339
|
return;
|
|
4069
4340
|
}
|
|
@@ -4077,9 +4348,9 @@ async function cleanupStageArtifacts(artifacts, stage) {
|
|
|
4077
4348
|
}
|
|
4078
4349
|
if (stage === "train") {
|
|
4079
4350
|
await Promise.all([
|
|
4080
|
-
|
|
4351
|
+
rm4(artifacts.trainingDir, { recursive: true, force: true }),
|
|
4081
4352
|
removePrefixedArtifacts(artifacts.trainingReportJson),
|
|
4082
|
-
|
|
4353
|
+
rm4(resolve13(artifacts.runDir, "model.tar.gz"), { force: true }),
|
|
4083
4354
|
removePrefixedArtifacts(artifacts.candidateEvalJson),
|
|
4084
4355
|
removePrefixedArtifacts(artifacts.generalCandidateEvalJson),
|
|
4085
4356
|
removeReport()
|
|
@@ -4115,8 +4386,8 @@ async function throwIfCancelled(store, request) {
|
|
|
4115
4386
|
}
|
|
4116
4387
|
}
|
|
4117
4388
|
async function acquireWorkflowLock(lockPath, description) {
|
|
4118
|
-
await
|
|
4119
|
-
const token =
|
|
4389
|
+
await mkdir9(dirname9(lockPath), { recursive: true });
|
|
4390
|
+
const token = randomUUID5();
|
|
4120
4391
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
4121
4392
|
try {
|
|
4122
4393
|
const handle = await open3(lockPath, "wx", 384);
|
|
@@ -4129,13 +4400,13 @@ async function acquireWorkflowLock(lockPath, description) {
|
|
|
4129
4400
|
return async () => {
|
|
4130
4401
|
try {
|
|
4131
4402
|
const owner = JSON.parse(await readFile8(lockPath, "utf8"));
|
|
4132
|
-
if (owner.token === token) await
|
|
4403
|
+
if (owner.token === token) await rm4(lockPath, { force: true });
|
|
4133
4404
|
} catch {
|
|
4134
4405
|
}
|
|
4135
4406
|
};
|
|
4136
4407
|
} catch (error) {
|
|
4137
4408
|
if (error.code !== "EEXIST") throw error;
|
|
4138
|
-
const metadata = await
|
|
4409
|
+
const metadata = await stat7(lockPath).catch(() => null);
|
|
4139
4410
|
let owner = null;
|
|
4140
4411
|
try {
|
|
4141
4412
|
owner = JSON.parse(await readFile8(lockPath, "utf8"));
|
|
@@ -4157,10 +4428,10 @@ async function acquireWorkflowLock(lockPath, description) {
|
|
|
4157
4428
|
`${description} already has an active local workflow (lock: ${lockPath}).`
|
|
4158
4429
|
);
|
|
4159
4430
|
}
|
|
4160
|
-
const stalePath = `${lockPath}.stale.${
|
|
4431
|
+
const stalePath = `${lockPath}.stale.${randomUUID5()}`;
|
|
4161
4432
|
try {
|
|
4162
4433
|
await rename3(lockPath, stalePath);
|
|
4163
|
-
await
|
|
4434
|
+
await rm4(stalePath, { force: true });
|
|
4164
4435
|
} catch (renameError) {
|
|
4165
4436
|
if (renameError.code !== "ENOENT") throw renameError;
|
|
4166
4437
|
}
|
|
@@ -4170,11 +4441,11 @@ async function acquireWorkflowLock(lockPath, description) {
|
|
|
4170
4441
|
}
|
|
4171
4442
|
async function acquireRunLock(store, runId) {
|
|
4172
4443
|
await store.ensure();
|
|
4173
|
-
return acquireWorkflowLock(
|
|
4444
|
+
return acquireWorkflowLock(resolve13(store.paths.runsDir, runId, "workflow.lock"), `Run ${runId}`);
|
|
4174
4445
|
}
|
|
4175
4446
|
async function acquireArtifactLock(artifacts) {
|
|
4176
4447
|
return acquireWorkflowLock(
|
|
4177
|
-
|
|
4448
|
+
join10(artifacts.runDir, ARTIFACT_WORKFLOW_LOCK_FILE),
|
|
4178
4449
|
`Artifact directory ${artifacts.runDir}`
|
|
4179
4450
|
);
|
|
4180
4451
|
}
|
|
@@ -4221,13 +4492,13 @@ function stageFingerprintPath(prepared, stage) {
|
|
|
4221
4492
|
return `${stageOutputPath(prepared, stage)}.stage.json`;
|
|
4222
4493
|
}
|
|
4223
4494
|
async function runnerFileFingerprints() {
|
|
4224
|
-
const project =
|
|
4225
|
-
const bundledSource =
|
|
4226
|
-
const pythonSources = (await readdir5(bundledSource, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".py")).map((entry) =>
|
|
4495
|
+
const project = resolve13(packageRoot2, "training/adapter");
|
|
4496
|
+
const bundledSource = resolve13(packageRoot2, "training/adapter/src");
|
|
4497
|
+
const pythonSources = (await readdir5(bundledSource, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".py")).map((entry) => resolve13(bundledSource, entry.name));
|
|
4227
4498
|
const candidates = [.../* @__PURE__ */ new Set([
|
|
4228
4499
|
...pythonSources,
|
|
4229
|
-
|
|
4230
|
-
|
|
4500
|
+
resolve13(project, "pyproject.toml"),
|
|
4501
|
+
resolve13(project, "uv.lock")
|
|
4231
4502
|
])];
|
|
4232
4503
|
const fingerprints = {};
|
|
4233
4504
|
for (const path of candidates.sort()) fingerprints[path] = await hashFileIfPresent(path);
|
|
@@ -4341,7 +4612,7 @@ async function ensureRunRecord(args) {
|
|
|
4341
4612
|
`Run ${args.request.run_id} cannot be reused with different user, behavior spec, or run number identity.`
|
|
4342
4613
|
);
|
|
4343
4614
|
}
|
|
4344
|
-
if (
|
|
4615
|
+
if (resolve13(existing.artifact_dir) !== resolve13(args.artifacts.runDir)) {
|
|
4345
4616
|
throw new Error(
|
|
4346
4617
|
`Run ${args.request.run_id} is already bound to artifact directory ${existing.artifact_dir}; it cannot be resumed with ${args.artifacts.runDir}.`
|
|
4347
4618
|
);
|
|
@@ -4419,7 +4690,7 @@ async function computePreparedRun(args) {
|
|
|
4419
4690
|
base_model_for_evaluation: baseModelForEvaluation,
|
|
4420
4691
|
base_model_revision: baseModelRevision ?? null,
|
|
4421
4692
|
base_model_fingerprint: baseModelFingerprint ?? null,
|
|
4422
|
-
system_prompt_sha256:
|
|
4693
|
+
system_prompt_sha256: createHash7("sha256").update(system).digest("hex"),
|
|
4423
4694
|
prepared_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
4424
4695
|
};
|
|
4425
4696
|
if (args.writeArtifacts) await writeJsonAtomic(artifacts.stageMetadataJson, metadata);
|
|
@@ -4448,16 +4719,24 @@ async function prepareStage(args) {
|
|
|
4448
4719
|
const preparedExists = await pathExists2(args.artifacts.stageMetadataJson) && await pathExists2(args.artifacts.trainingJsonl);
|
|
4449
4720
|
const prepared = await computePreparedRun({ ...args, writeArtifacts: false });
|
|
4450
4721
|
await args.store.syncRunRequest(args.request, args.artifacts.runDir);
|
|
4451
|
-
await writeJsonAtomic(
|
|
4722
|
+
await writeJsonAtomic(resolve13(args.artifacts.runDir, "request.json"), args.request);
|
|
4452
4723
|
const existingMetadata = preparedExists ? await readStageMetadata(args.artifacts.stageMetadataJson) : null;
|
|
4453
4724
|
let canReuse = preparedExists && existingMetadata?.source_fingerprint === prepared.metadata.source_fingerprint;
|
|
4454
4725
|
if (canReuse) {
|
|
4455
4726
|
canReuse = await verifyReusableArtifacts(args.artifacts, [
|
|
4456
4727
|
args.artifacts.stageMetadataJson,
|
|
4457
4728
|
args.artifacts.trainingJsonl
|
|
4458
|
-
]);
|
|
4729
|
+
], { verifyModel: true });
|
|
4459
4730
|
}
|
|
4460
4731
|
if (canReuse) {
|
|
4732
|
+
for (const stage of ["baseline", "train", "candidate"]) {
|
|
4733
|
+
await canReuseStageArtifact({
|
|
4734
|
+
stage,
|
|
4735
|
+
prepared,
|
|
4736
|
+
config: args.config,
|
|
4737
|
+
additionalPaths: stage === "train" ? [] : generalRegressionRequiredPaths(prepared, stage)
|
|
4738
|
+
});
|
|
4739
|
+
}
|
|
4461
4740
|
await throwIfCancelled(args.store, args.request);
|
|
4462
4741
|
await updateRun({
|
|
4463
4742
|
...args,
|
|
@@ -4609,8 +4888,8 @@ async function runTrainStage(args) {
|
|
|
4609
4888
|
request: args.prepared.request,
|
|
4610
4889
|
status: "training",
|
|
4611
4890
|
stage: "training",
|
|
4612
|
-
message: args.config.dryRun ? "Recording dry-run training result." : "Launching local training process.",
|
|
4613
|
-
details: { training_backend: "local-uv", dry_run: args.config.dryRun }
|
|
4891
|
+
message: args.config.dryRun ? "Recording dry-run training result." : args.config.gpu ? "Launching training on your AWS GPU." : "Launching local training process.",
|
|
4892
|
+
details: { training_backend: args.config.gpu && !args.config.dryRun ? "aws-ssh" : "local-uv", dry_run: args.config.dryRun }
|
|
4614
4893
|
});
|
|
4615
4894
|
const training = await launchProcessTraining({
|
|
4616
4895
|
request: args.prepared.request,
|
|
@@ -4977,7 +5256,7 @@ async function runLocalPipeline(input) {
|
|
|
4977
5256
|
if (step.uses === "evaluate") {
|
|
4978
5257
|
const isBase = step.with.model === "base";
|
|
4979
5258
|
const report = isBase ? await runBaselineStage({ prepared, config: input.config, store, reporter: input.reporter, runReporter }) : await runCandidateStage({ prepared, config: input.config, store, reporter: input.reporter, runReporter });
|
|
4980
|
-
const reportPath =
|
|
5259
|
+
const reportPath = join10(artifacts.runDir, "evaluations", `${step.id}.json`);
|
|
4981
5260
|
await writeJsonAtomic(reportPath, { ...report, artifact_uri: fileUri(reportPath) });
|
|
4982
5261
|
outputs[step.id] = { report: { ...report, artifact_uri: fileUri(reportPath) }, reportPath };
|
|
4983
5262
|
await refreshArtifactManifest(prepared);
|
|
@@ -5298,10 +5577,7 @@ function evaluateCapabilities(inventory) {
|
|
|
5298
5577
|
train,
|
|
5299
5578
|
finetune: train,
|
|
5300
5579
|
inference: hasCuda ? inference : { ...inference, reason: `CPU eval: ${inference.reason}` },
|
|
5301
|
-
serve: {
|
|
5302
|
-
status: "not_possible",
|
|
5303
|
-
reason: "tt serve cannot host foundation checkpoints yet"
|
|
5304
|
-
}
|
|
5580
|
+
serve: hasCuda && inventory.os.platform === "linux" ? inference : { status: "not_possible", reason: "Foundation serving requires Linux and an NVIDIA CUDA GPU" }
|
|
5305
5581
|
};
|
|
5306
5582
|
return { cuda_available: hasCuda, gpu, adapters, foundation, notes };
|
|
5307
5583
|
}
|
|
@@ -5315,7 +5591,7 @@ function formatCapabilitySummary(report) {
|
|
|
5315
5591
|
return [
|
|
5316
5592
|
`GPU ${gpuLabel}, CUDA ${report.cuda_available ? "yes" : "no"}`,
|
|
5317
5593
|
`Adapter: ${adapterBits.join("; ")}`,
|
|
5318
|
-
`Foundation: train ${report.foundation.train.status}, suggested max depth ${report.foundation.suggested_max_depth}; serve
|
|
5594
|
+
`Foundation: train ${report.foundation.train.status}, suggested max depth ${report.foundation.suggested_max_depth}; serve ${report.foundation.serve.status}`
|
|
5319
5595
|
].join(". ");
|
|
5320
5596
|
}
|
|
5321
5597
|
function warningsFromSnapshot(report, target) {
|
|
@@ -5352,7 +5628,7 @@ function warningsFromSnapshot(report, target) {
|
|
|
5352
5628
|
import { constants as constants2 } from "fs";
|
|
5353
5629
|
import { access as access2, statfs } from "fs/promises";
|
|
5354
5630
|
import { cpus, freemem, homedir as homedir5, totalmem, type } from "os";
|
|
5355
|
-
import { dirname as
|
|
5631
|
+
import { dirname as dirname10, join as join11, resolve as resolve14 } from "path";
|
|
5356
5632
|
import { spawn as spawn2 } from "child_process";
|
|
5357
5633
|
async function runCommand(command, args, options = {}) {
|
|
5358
5634
|
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
@@ -5506,7 +5782,7 @@ async function queryNvidiaGpus(env) {
|
|
|
5506
5782
|
return { result: table.code === 0 || !last ? table : last, gpus };
|
|
5507
5783
|
}
|
|
5508
5784
|
async function diskCheck(name, path) {
|
|
5509
|
-
const resolvedPath =
|
|
5785
|
+
const resolvedPath = resolve14(path);
|
|
5510
5786
|
try {
|
|
5511
5787
|
await access2(resolvedPath, constants2.R_OK);
|
|
5512
5788
|
const fs = await statfs(resolvedPath);
|
|
@@ -5521,7 +5797,7 @@ async function diskCheck(name, path) {
|
|
|
5521
5797
|
} catch (error) {
|
|
5522
5798
|
let probe = resolvedPath;
|
|
5523
5799
|
for (let i = 0; i < 6; i += 1) {
|
|
5524
|
-
const parent =
|
|
5800
|
+
const parent = dirname10(probe);
|
|
5525
5801
|
if (parent === probe) break;
|
|
5526
5802
|
probe = parent;
|
|
5527
5803
|
try {
|
|
@@ -5607,10 +5883,10 @@ async function collectHostInventory(options = {}) {
|
|
|
5607
5883
|
const cwd = options.cwd ?? process.cwd();
|
|
5608
5884
|
const nodeVersion = process.versions.node;
|
|
5609
5885
|
const nodeMajor = Number(nodeVersion.split(".")[0]);
|
|
5610
|
-
const artifactRoot =
|
|
5611
|
-
const storeRoot =
|
|
5612
|
-
const modelCache =
|
|
5613
|
-
config?.paths.modelCache ?? env.HF_HOME ??
|
|
5886
|
+
const artifactRoot = resolve14(cwd, config?.artifactRoot ?? join11(cwd, DEFAULT_ARTIFACT_ROOT));
|
|
5887
|
+
const storeRoot = resolve14(config?.storeRoot ?? defaultStoreRoot(env));
|
|
5888
|
+
const modelCache = resolve14(
|
|
5889
|
+
config?.paths.modelCache ?? env.HF_HOME ?? join11(homedir5(), ".cache", "huggingface")
|
|
5614
5890
|
);
|
|
5615
5891
|
const [artifactDisk, storeDisk, cacheDisk, uvVersion, nvidiaQueried] = await Promise.all([
|
|
5616
5892
|
diskCheck("artifact-root", artifactRoot),
|
|
@@ -5656,12 +5932,12 @@ async function collectHostInventory(options = {}) {
|
|
|
5656
5932
|
}
|
|
5657
5933
|
|
|
5658
5934
|
// src/local-runtime/hardware-snapshot.ts
|
|
5659
|
-
import { chmod as chmod2, mkdir as
|
|
5660
|
-
import { dirname as
|
|
5935
|
+
import { chmod as chmod2, mkdir as mkdir10, readFile as readFile9, writeFile as writeFile7 } from "fs/promises";
|
|
5936
|
+
import { dirname as dirname11, join as join12 } from "path";
|
|
5661
5937
|
var HARDWARE_SNAPSHOT_VERSION = 1;
|
|
5662
5938
|
var HARDWARE_SNAPSHOT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5663
5939
|
function hardwareSnapshotPath(env = process.env) {
|
|
5664
|
-
return
|
|
5940
|
+
return join12(getTunedTensorHome(env), "hardware.json");
|
|
5665
5941
|
}
|
|
5666
5942
|
async function readHardwareSnapshot(env = process.env) {
|
|
5667
5943
|
const path = hardwareSnapshotPath(env);
|
|
@@ -5683,7 +5959,7 @@ async function readHardwareSnapshot(env = process.env) {
|
|
|
5683
5959
|
}
|
|
5684
5960
|
async function writeHardwareSnapshot(snapshot, env = process.env) {
|
|
5685
5961
|
const path = hardwareSnapshotPath(env);
|
|
5686
|
-
await
|
|
5962
|
+
await mkdir10(dirname11(path), { recursive: true, mode: 448 });
|
|
5687
5963
|
const gpu = snapshot.capabilities.gpu;
|
|
5688
5964
|
const record = {
|
|
5689
5965
|
version: HARDWARE_SNAPSHOT_VERSION,
|
|
@@ -5694,7 +5970,7 @@ async function writeHardwareSnapshot(snapshot, env = process.env) {
|
|
|
5694
5970
|
capabilities: snapshot.capabilities,
|
|
5695
5971
|
summary: snapshot.summary ?? formatCapabilitySummary(snapshot.capabilities)
|
|
5696
5972
|
};
|
|
5697
|
-
await
|
|
5973
|
+
await writeFile7(path, `${JSON.stringify(record, null, 2)}
|
|
5698
5974
|
`, { mode: 384 });
|
|
5699
5975
|
await chmod2(path, 384);
|
|
5700
5976
|
return record;
|
|
@@ -5817,15 +6093,17 @@ export {
|
|
|
5817
6093
|
buildFoundationPythonCommand,
|
|
5818
6094
|
withFoundationPythonEnvironment,
|
|
5819
6095
|
runLoggedProcess,
|
|
6096
|
+
minimalMachineLearningEnvironment,
|
|
6097
|
+
withHuggingFaceCacheEnvironment,
|
|
6098
|
+
withOfflineHuggingFaceCacheEnvironment,
|
|
6099
|
+
checkAwsGpu,
|
|
6100
|
+
runGpuProcess,
|
|
5820
6101
|
isTerminalRunState,
|
|
5821
6102
|
defaultLocalHome,
|
|
5822
6103
|
localStorePaths,
|
|
5823
6104
|
hardenExistingLocalStore,
|
|
5824
6105
|
writePrivateJsonAtomic,
|
|
5825
6106
|
createLocalStore,
|
|
5826
|
-
minimalMachineLearningEnvironment,
|
|
5827
|
-
withHuggingFaceCacheEnvironment,
|
|
5828
|
-
withOfflineHuggingFaceCacheEnvironment,
|
|
5829
6107
|
tokenF1,
|
|
5830
6108
|
verifyModelPrefetchCacheReport,
|
|
5831
6109
|
buildModelPrefetchPayload,
|
|
@@ -5848,4 +6126,4 @@ export {
|
|
|
5848
6126
|
formatAgentHostBlock,
|
|
5849
6127
|
assessHardware
|
|
5850
6128
|
};
|
|
5851
|
-
//# sourceMappingURL=chunk-
|
|
6129
|
+
//# sourceMappingURL=chunk-6RLNBTM6.js.map
|