@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.
@@ -1076,7 +1076,7 @@ var comparisonReportSchema = z.object({
1076
1076
  }).strict())
1077
1077
  }).strict();
1078
1078
  var trainingReportSchema = z.object({
1079
- provider: z.literal("local-uv"),
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 mkdir8, open as open3, readFile as readFile8, readdir as readdir5, rename as rename3, rm as rm3, stat as stat5 } from "fs/promises";
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 createHash6, randomUUID as randomUUID4 } from "crypto";
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 dirname8, join as join9, relative as relative4, resolve as resolve12 } from "path";
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 createHash5 } from "crypto";
2919
- import { mkdir as mkdir6, readFile as readFile6, rm as rm2, writeFile as writeFile4 } from "fs/promises";
2920
- import { dirname as dirname7, join as join7, resolve as resolve10 } from "path";
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 mkdir6(dirname7(inputPath), { recursive: true });
3141
- await writeFile4(inputPath, `${JSON.stringify({
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 ? resolve10(args.config.paths.modelCache) : void 0,
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 rm2(outputPath, { force: true });
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 result = await runLoggedProcess({
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 createHash5("sha256").update(JSON.stringify({
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 ? resolve10(config.storeRoot) : defaultLocalHome();
3497
+ return config.storeRoot ? resolve11(config.storeRoot) : defaultLocalHome();
3242
3498
  }
3243
3499
  function baselineCachePath(config, key) {
3244
- return join7(baselineStoreRoot(config), "cache", "baseline-evals", `${key}.json`);
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(resolve10(localRuntimePackageRoot(import.meta.url), "package.json"), "utf8");
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 ? resolve10(args.modelSource) !== resolve10(baseModelId) : false;
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 mkdir7, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
3444
- import { join as join8, resolve as resolve11 } from "path";
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 mkdir7(artifacts.trainingDir, { recursive: true });
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(join8(artifacts.trainingConfigDir, "hyperparameters.json"), hyperparameters);
3796
+ await writeJson(join9(artifacts.trainingConfigDir, "hyperparameters.json"), hyperparameters);
3541
3797
  const entrypoint = buildBundledPythonCommand("train.py");
3542
3798
  if (config.dryRun) {
3543
- await writeFile5(
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: resolve11(artifacts.trainingInputDir),
3566
- SM_CHANNEL_BASE_MODEL: config.paths.baseModel ? resolve11(config.paths.baseModel) : void 0,
3567
- SM_MODEL_DIR: resolve11(artifacts.trainingModelDir),
3568
- SM_OUTPUT_DIR: resolve11(artifacts.trainingOutputDir),
3569
- TT_HYPERPARAMETERS_PATH: resolve11(artifacts.trainingConfigDir, "hyperparameters.json")
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
- if (config.paths.modelCache) await mkdir7(resolve11(config.paths.modelCache), { recursive: true });
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
- resolve11(config.paths.baseModel),
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 { exitCode } = await runLoggedProcess({
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 = join8(artifacts.trainingModelDir, "training-metrics.json");
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 = join8(artifacts.trainingOutputDir, "model.tar.gz");
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 = resolve12(path);
3714
- const base = dirname8(configPath);
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 resolve12(homedir4(), value.slice(2));
3719
- return resolve12(base, value);
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 = resolve12(path);
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 stat5(path);
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 createHash6("sha256").update(canonicalJson2(value)).digest("hex");
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(resolve12(packageRoot2, "package.json"), "utf8"));
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 = resolve12(packageRoot2, "training/adapter");
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(resolve12(bundledProject2, "pyproject.toml")),
3939
- runner_uv_lock: await hashFileIfPresent(resolve12(bundledProject2, "uv.lock"))
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 = createHash6("sha256");
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 = resolve12(cacheEnvironment.HF_HUB_CACHE, repository, "refs", "main");
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 = resolve12(directory, entry.name);
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 stat5(child);
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 = dirname8(path);
4047
- const prefix = `${basename(path)}.`;
4317
+ const directory = dirname9(path);
4318
+ const prefix = `${basename2(path)}.`;
4048
4319
  const names = await readdir5(directory).catch(() => []);
4049
4320
  await Promise.all([
4050
- rm3(path, { force: true }),
4051
- ...names.filter((name) => name.startsWith(prefix)).map((name) => rm3(resolve12(directory, name), { recursive: true, force: true }))
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 () => rm3(artifacts.runReportJson, { force: true });
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
- rm3(artifacts.trainingDir, { recursive: true, force: true }),
4333
+ rm4(artifacts.trainingDir, { recursive: true, force: true }),
4063
4334
  removePrefixedArtifacts(artifacts.trainingReportJson),
4064
- rm3(resolve12(artifacts.runDir, "model.tar.gz"), { force: true }),
4335
+ rm4(resolve13(artifacts.runDir, "model.tar.gz"), { force: true }),
4065
4336
  removeReport(),
4066
- rm3(artifacts.artifactManifestJson, { force: true })
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
- rm3(artifacts.trainingDir, { recursive: true, force: true }),
4351
+ rm4(artifacts.trainingDir, { recursive: true, force: true }),
4081
4352
  removePrefixedArtifacts(artifacts.trainingReportJson),
4082
- rm3(resolve12(artifacts.runDir, "model.tar.gz"), { force: true }),
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 mkdir8(dirname8(lockPath), { recursive: true });
4119
- const token = randomUUID4();
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 rm3(lockPath, { force: true });
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 stat5(lockPath).catch(() => null);
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.${randomUUID4()}`;
4431
+ const stalePath = `${lockPath}.stale.${randomUUID5()}`;
4161
4432
  try {
4162
4433
  await rename3(lockPath, stalePath);
4163
- await rm3(stalePath, { force: true });
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(resolve12(store.paths.runsDir, runId, "workflow.lock"), `Run ${runId}`);
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
- join9(artifacts.runDir, ARTIFACT_WORKFLOW_LOCK_FILE),
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 = resolve12(packageRoot2, "training/adapter");
4225
- const bundledSource = resolve12(packageRoot2, "training/adapter/src");
4226
- const pythonSources = (await readdir5(bundledSource, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".py")).map((entry) => resolve12(bundledSource, entry.name));
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
- resolve12(project, "pyproject.toml"),
4230
- resolve12(project, "uv.lock")
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 (resolve12(existing.artifact_dir) !== resolve12(args.artifacts.runDir)) {
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: createHash6("sha256").update(system).digest("hex"),
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(resolve12(args.artifacts.runDir, "request.json"), args.request);
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 = join9(artifacts.runDir, "evaluations", `${step.id}.json`);
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 not supported`
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 dirname9, join as join10, resolve as resolve13 } from "path";
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 = resolve13(path);
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 = dirname9(probe);
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 = resolve13(cwd, config?.artifactRoot ?? join10(cwd, DEFAULT_ARTIFACT_ROOT));
5611
- const storeRoot = resolve13(config?.storeRoot ?? defaultStoreRoot(env));
5612
- const modelCache = resolve13(
5613
- config?.paths.modelCache ?? env.HF_HOME ?? join10(homedir5(), ".cache", "huggingface")
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 mkdir9, readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
5660
- import { dirname as dirname10, join as join11 } from "path";
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 join11(getTunedTensorHome(env), "hardware.json");
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 mkdir9(dirname10(path), { recursive: true, mode: 448 });
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 writeFile6(path, `${JSON.stringify(record, null, 2)}
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-YGN26POJ.js.map
6129
+ //# sourceMappingURL=chunk-6RLNBTM6.js.map