@tuned-tensor/cli 0.15.1 → 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/dist/index.js CHANGED
@@ -23,12 +23,13 @@ import {
23
23
  parseLocalRunInput,
24
24
  parseLocalRunnerConfig,
25
25
  readHardwareSnapshot,
26
+ runGpuProcess,
26
27
  runLocalPipeline,
27
28
  runLoggedProcess,
28
29
  runReportSchema,
29
30
  warningsFromSnapshot,
30
31
  withFoundationPythonEnvironment
31
- } from "./chunk-VVKC563R.js";
32
+ } from "./chunk-6RLNBTM6.js";
32
33
 
33
34
  // src/cli.ts
34
35
  import { spawn as spawn4 } from "child_process";
@@ -1971,7 +1972,7 @@ function createTunedTensorTools(api, options = {}) {
1971
1972
  return {
1972
1973
  version: 1,
1973
1974
  engine,
1974
- scope: { execution: "local-only", cloud_supported: false },
1975
+ scope: { execution: "local", gpu_provider: "aws" },
1975
1976
  canonical: canonicalFoundationPipeline2(),
1976
1977
  optional_rl: {
1977
1978
  enabled_when: "foundation.rl_steps > 0",
@@ -1991,17 +1992,13 @@ function createTunedTensorTools(api, options = {}) {
1991
1992
  return {
1992
1993
  version: 1,
1993
1994
  engine,
1994
- scope: { execution: "cloud", cli: "tt cloud", access_token_required: true },
1995
- canonical: canonicalPipeline("cloud"),
1995
+ scope: { execution: "local", gpu_provider: "aws", access_token_required: false },
1996
+ canonical: canonicalPipeline("local"),
1996
1997
  commands: {
1997
- login: "tt auth login",
1998
- push: "tt cloud push --file tunedtensor.json",
1999
- estimate: "tt cloud runs estimate <spec-id>",
2000
- run: "tt cloud runs start <spec-id>",
2001
- watch: "tt cloud runs watch <run-id>",
2002
- report: "tt cloud runs report <run-id>"
1998
+ run: "tt pipeline run --file tunedtensor.pipeline.json --spec tunedtensor.json --config local-runner.json",
1999
+ doctor: "tt doctor --config local-runner.json"
2003
2000
  },
2004
- note: "Use the spec ID returned by push. Cloud recipes cannot be dispatched by tt pipeline run; cloud runs use the hosted run API."
2001
+ note: "Configure gpu in local-runner.json with provider aws, instanceId, SSH user and optional AWS profile/region/identityFile. The instance must already be running and reachable. GPU processes use it automatically; orchestration, scoring and artifacts stay local. Hosted training is retired."
2005
2002
  };
2006
2003
  }
2007
2004
  return {
@@ -2184,7 +2181,7 @@ Mutation tools only prepare proposals. Never claim a proposed mutation happened.
2184
2181
  Do not request or reveal Tuned Tensor or model-provider credentials. You have no shell, upload, delete, top-up, API-key, watch, or serving tools.`;
2185
2182
  async function systemPrompt(cloudEnabled) {
2186
2183
  const host = formatAgentHostBlock(await readHardwareSnapshot());
2187
- const account = cloudEnabled ? "Account tools inspect cloud specs, runs, models, balance, transactions, and managed agent usage. Cloud spec mutations only prepare proposals for /approve; real cloud runs require the user to invoke `tt cloud runs start` directly. Never confuse cloud resources with local resources." : "No TT access token is configured, so account and cloud tools are unavailable. The user can run `tt auth login` or /login tunedtensor to enable them.";
2184
+ const account = cloudEnabled ? "Account tools inspect cloud specs, runs, models, balance, transactions, and managed agent usage. Cloud spec mutations only prepare proposals for /approve; training uses `tt pipeline run` with optional user-owned AWS GPU configuration. Hosted training submission is retired. Never confuse cloud resources with local resources." : "No TT access token is configured, so account and cloud tools are unavailable. The user can run `tt auth login` or /login tunedtensor to enable them.";
2188
2185
  return `${SYSTEM_PROMPT}
2189
2186
  ${account}
2190
2187
  You have no general filesystem tools. Workspace-scoped capabilities can prepare one new validated spec folder or a sealed local pipeline dry-run; both still require /approve.
@@ -2762,8 +2759,8 @@ var COMMAND_CATALOG = [
2762
2759
  { path: "cloud label cancel", description: "Cancel a labeling job and release unused credits", group: "Cloud", modes: LOCAL },
2763
2760
  { path: "cloud runs list", description: "List runs", group: "Cloud", modes: LOCAL },
2764
2761
  { path: "cloud runs get", description: "Show run details and eval results", group: "Cloud", modes: LOCAL },
2765
- { path: "cloud runs estimate", description: "Estimate run cost and duration before starting", group: "Cloud", modes: LOCAL },
2766
- { path: "cloud runs start", description: "Start a new run for a behaviour spec", group: "Cloud", modes: LOCAL },
2762
+ { path: "cloud runs estimate", description: "Retired: use the local pipeline with your own AWS GPU", group: "Cloud", modes: LOCAL },
2763
+ { path: "cloud runs start", description: "Retired: use the local pipeline with your own AWS GPU", group: "Cloud", modes: LOCAL },
2767
2764
  { path: "cloud runs cancel", description: "Cancel a running run", group: "Cloud", modes: LOCAL },
2768
2765
  { path: "cloud runs watch", description: "Watch a run until it completes", group: "Cloud", modes: LOCAL },
2769
2766
  { path: "cloud runs diagnose", description: "Show live run diagnostics", group: "Cloud", modes: LOCAL },
@@ -5170,7 +5167,7 @@ function renderHardware(value) {
5170
5167
  );
5171
5168
  if (foundation) {
5172
5169
  printDetail([
5173
- ["Foundation serve", text(record3(foundation.serve)?.reason) ?? "cannot host yet"]
5170
+ ["Foundation serve", text(record3(foundation.serve)?.reason) ?? "unknown"]
5174
5171
  ]);
5175
5172
  }
5176
5173
  const notes = Array.isArray(capabilities?.notes) ? capabilities.notes.filter((note) => typeof note === "string") : [];
@@ -6021,7 +6018,27 @@ function assertFoundationPlanSupported(spec, plan) {
6021
6018
  }
6022
6019
  async function defaultFoundationSpawn(args2) {
6023
6020
  const launched = buildFoundationPythonCommand(args2.entrypoint, ["--config", args2.configPath]);
6024
- const result2 = await runLoggedProcess({
6021
+ const execute = args2.gpu && args2.entrypoint !== "train_tokenizer.py" ? async (processArgs) => {
6022
+ const document = await readRequiredJsonObject(args2.configPath, "Foundation GPU config");
6023
+ const files = [{ path: args2.configPath, direction: "input" }];
6024
+ const pathKeys = ["tokenizer_dir", "model_dir", "corpus_path", "validation_path", "output_dir", "work_dir", "checkpoint_backup_dir"];
6025
+ for (const key of pathKeys) {
6026
+ if (typeof document[key] !== "string") continue;
6027
+ files.push({
6028
+ path: document[key],
6029
+ direction: ["output_dir", "work_dir", "checkpoint_backup_dir"].includes(key) ? "both" : "input",
6030
+ directory: key.endsWith("_dir") || (await lstat4(document[key])).isDirectory()
6031
+ });
6032
+ }
6033
+ return runGpuProcess({
6034
+ ...processArgs,
6035
+ gpu: args2.gpu,
6036
+ runtime: "foundation",
6037
+ files,
6038
+ document: { path: args2.configPath, value: document, pathKeys }
6039
+ });
6040
+ } : runLoggedProcess;
6041
+ const result2 = await execute({
6025
6042
  command: launched.command,
6026
6043
  commandArgs: launched.commandArgs,
6027
6044
  env: withFoundationPythonEnvironment(process.env),
@@ -6042,6 +6059,9 @@ async function runFoundationPipeline(args2) {
6042
6059
  const outputDir = resolve5(
6043
6060
  args2.outputDir ?? join8(dirname5(resolve5(args2.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID6())
6044
6061
  );
6062
+ if (args2.gpu && args2.spec.foundation.checkpoint_backup_dir && localPathsOverlap(outputDir, args2.spec.foundation.checkpoint_backup_dir)) {
6063
+ throw new Error("AWS checkpoint_backup_dir must be separate from the foundation run directory; overlapping backups can overwrite returned checkpoints.");
6064
+ }
6045
6065
  let outputExists = false;
6046
6066
  try {
6047
6067
  const outputInfo = await lstat4(outputDir);
@@ -6203,7 +6223,7 @@ async function runFoundationPipeline(args2) {
6203
6223
  if (!completedStep) {
6204
6224
  await writePrivateTextAtomic(configPath2, `${JSON.stringify(config, null, 2)}
6205
6225
  `);
6206
- await spawnStep({ entrypoint, configPath: configPath2, logPath, stepId: step.id });
6226
+ await spawnStep({ entrypoint, configPath: configPath2, logPath, stepId: step.id, ...args2.gpu ? { gpu: args2.gpu } : {} });
6207
6227
  upstreamExecuted = true;
6208
6228
  }
6209
6229
  await makePrivateTree(stepDir);
@@ -6439,24 +6459,27 @@ function registerPipelineCommands(parent) {
6439
6459
  if (options.output && options.resume) {
6440
6460
  throw new Error("--output and --resume are mutually exclusive.");
6441
6461
  }
6462
+ const config = await loadLocalRunnerConfig(localConfigPath(options.config, options.spec));
6442
6463
  const document = await resolvePipelineDocument(options);
6443
6464
  const plan = createExecutionPlan(document, { only: parseList(options.only), skip: parseList(options.skip) });
6444
- const hostWarnings = await hostWarningsForPipeline(document, options.spec);
6445
- if (options.dryRun) {
6465
+ const hostWarnings = config.gpu ? [] : await hostWarningsForPipeline(document, options.spec);
6466
+ if (options.dryRun || config.dryRun) {
6446
6467
  if (isJsonMode()) {
6447
6468
  return printJson({
6448
6469
  dry_run: true,
6449
6470
  ...plan,
6471
+ ...config.gpu ? { gpu: { provider: config.gpu.provider, instanceId: config.gpu.instanceId, region: config.gpu.region, profile: config.gpu.profile } } : {},
6450
6472
  ...hostWarnings.length ? { host_warnings: hostWarnings } : {}
6451
6473
  });
6452
6474
  }
6453
6475
  console.log("Dry run only \u2014 no execution, artifact transfer, or credit reservation will occur.");
6476
+ if (config.gpu) console.log(`GPU processes: AWS instance ${config.gpu.instanceId}. Orchestration stays local.`);
6454
6477
  return outputPlan(plan, hostWarnings);
6455
6478
  }
6456
6479
  for (const warning of hostWarnings) printWarning(warning);
6457
6480
  const remote = plan.steps.find((step) => step.target !== "local");
6458
6481
  if (remote) {
6459
- throw new Error(`Step "${remote.id}" targets cloud execution. Pipeline execution requires local targets; use tt cloud runs for hosted execution, or --dry-run to inspect this plan.`);
6482
+ throw new Error(`Step "${remote.id}" targets cloud execution. Pipeline execution requires local targets; set targets to local and configure gpu in local-runner.json to use your AWS instance.`);
6460
6483
  }
6461
6484
  const input = await loadLocalRunInput(resolve6(options.spec));
6462
6485
  if (isParsedFoundationPipeline(document) || input.kind === "foundation-spec") {
@@ -6469,7 +6492,8 @@ function registerPipelineCommands(parent) {
6469
6492
  plan,
6470
6493
  specPath: resolve6(options.spec),
6471
6494
  ...options.output || options.resume ? { outputDir: resolve6(options.resume ?? options.output) } : {},
6472
- resume: Boolean(options.resume)
6495
+ resume: Boolean(options.resume),
6496
+ gpu: config.gpu
6473
6497
  });
6474
6498
  if (isJsonMode()) return printJson(result3);
6475
6499
  printSuccess(`Foundation pipeline completed. Report: ${result3.report_path}`);
@@ -6479,7 +6503,6 @@ function registerPipelineCommands(parent) {
6479
6503
  throw new Error("--output and --resume are only valid for foundation pipelines.");
6480
6504
  }
6481
6505
  assertLocalRunInputReady(input.request);
6482
- const config = await loadLocalRunnerConfig(localConfigPath(options.config, options.spec));
6483
6506
  const localPipeline = {
6484
6507
  version: 1,
6485
6508
  ...plan.name ? { name: plan.name } : {},
@@ -7856,7 +7879,7 @@ function registerLabelCommands(parent) {
7856
7879
  `Dataset created: ${data3.name} (${shortId(data3.id)}) \u2014 ${data3.row_count} rows.`
7857
7880
  );
7858
7881
  console.log(
7859
- `Start a run with \`tt cloud runs start <spec-id> --dataset ${shortId(data3.id)}\`.`
7882
+ "Export the dataset for use with tt pipeline run and optional AWS GPU configuration."
7860
7883
  );
7861
7884
  });
7862
7885
  label.command("cancel").description("Cancel a labeling job and release unused credits").argument("<id>", "Labeling job ID (full UUID or 4+ char prefix)").action(async (id) => {
@@ -7874,15 +7897,6 @@ function registerLabelCommands(parent) {
7874
7897
 
7875
7898
  // src/commands/runs.ts
7876
7899
  import ora from "ora";
7877
- var LONG_EXAMPLE_POLICIES = ["error", "truncate", "skip"];
7878
- function parseLongExamplesPolicy(value) {
7879
- if (LONG_EXAMPLE_POLICIES.includes(value)) {
7880
- return value;
7881
- }
7882
- throw new Error(
7883
- `--long-examples must be one of: ${LONG_EXAMPLE_POLICIES.join(", ")}`
7884
- );
7885
- }
7886
7900
  function formatProgress(run) {
7887
7901
  if (run.progress_pct == null && !run.stage_label) return void 0;
7888
7902
  const label = run.stage_label ?? run.current_stage ?? "Progress";
@@ -7915,43 +7929,6 @@ function formatMinutes(minutes) {
7915
7929
  if (minutes < 60) return `${Math.max(1, Math.round(minutes))}m`;
7916
7930
  return `${(minutes / 60).toFixed(1)}h`;
7917
7931
  }
7918
- function formatCents3(cents) {
7919
- const dollars = cents / 100;
7920
- if (Math.abs(dollars) >= 100) return `$${dollars.toFixed(0)}`;
7921
- return `$${dollars.toFixed(2)}`;
7922
- }
7923
- function formatEstimateRange(estimate) {
7924
- const duration = estimate.duration;
7925
- return `${formatMinutes(duration.estimated_minutes)} (${formatMinutes(duration.range_minutes.low)}-${formatMinutes(duration.range_minutes.high)})`;
7926
- }
7927
- function printRunEstimate(estimate) {
7928
- const billing = estimate.billing;
7929
- printDetail([
7930
- ["Estimated Time", formatEstimateRange(estimate)],
7931
- ["Confidence", estimate.duration.confidence],
7932
- ["History Samples", String(estimate.duration.sample_count)],
7933
- ["Basis", estimate.duration.basis.replaceAll("_", " ")],
7934
- ["Estimated Cost", formatCents3(estimate.estimated_cost_cents)],
7935
- ["Training Tokens", `${(estimate.estimated_training_tokens / 1e3).toFixed(1)}k`],
7936
- ["Epochs", String(estimate.estimated_epochs)],
7937
- ["Plan", billing?.plan],
7938
- [
7939
- "Billing Source",
7940
- billing?.billing_source === "free_quota" ? "Free monthly quota" : billing?.billing_source === "credits" ? "Credits" : void 0
7941
- ],
7942
- [
7943
- "Free Runs",
7944
- billing ? `${billing.free_runs_remaining}/${billing.free_runs_monthly_limit} remaining` : void 0
7945
- ],
7946
- [
7947
- "Free Eligible",
7948
- billing ? billing.free_run_eligible ? "yes" : `no (${billing.free_run_ineligibility.join(", ") || "not eligible"})` : void 0
7949
- ]
7950
- ]);
7951
- console.log(
7952
- "\nDuration is a rough historical range. Final cost uses provider-reported training tokens."
7953
- );
7954
- }
7955
7932
  function formatEpochProgress(diagnostics) {
7956
7933
  const curve = diagnostics.training.curve;
7957
7934
  if (curve.latest_epoch == null) return void 0;
@@ -8182,46 +8159,6 @@ function printRunReport(report, options) {
8182
8159
  printReportExamples(label, split, options.mode, options.limit);
8183
8160
  }
8184
8161
  }
8185
- function addRunConfigurationOptions(command) {
8186
- return command.option("--no-augment", "Disable data augmentation").option("--no-llm-judge", "Disable LLM judging").option("--epochs <n>", "Number of training epochs").option("--lr <rate>", "Learning rate").option("--batch-size <n>", "Batch size").option("--dataset <id>", "Dataset ID to use instead of inline spec examples (full UUID or 4+ char prefix)").option("--parent-model <id>", "Fine-tuned model ID to continue training from (full UUID or 4+ char prefix)").option("--train-ratio <ratio>", "Dataset training split ratio (default: 0.8 when any split ratio is set)").option("--validation-ratio <ratio>", "Dataset validation split ratio (default: 0.1 when any split ratio is set)").option("--test-ratio <ratio>", "Dataset test split ratio (default: 0.1 when any split ratio is set)").option("--lora-rank <n>", "LoRA rank").option("--lora-alpha <n>", "LoRA alpha").option("--max-eval-examples <n>", "Max examples for the primary eval pass").option("--max-test-eval-examples <n>", "Max examples for the secondary test eval pass").option("--long-examples <policy>", "Long training row policy: error, truncate, or skip").option("--max-seq-length <tokens>", "Maximum training sequence length in tokens").option("--max-output-tokens <tokens>", "Desired evaluation output budget in tokens").option("--eval-reserved-output-tokens <tokens>", "Minimum evaluation output tokens reserved per row");
8187
- }
8188
- async function buildRunRequestBody(cmdOpts, opts) {
8189
- const body = {};
8190
- if (cmdOpts.augment === false) body.augment = false;
8191
- if (cmdOpts.dataset) body.dataset_id = await resolveDatasetId(String(cmdOpts.dataset), opts);
8192
- if (cmdOpts.parentModel) {
8193
- body.parent_model_id = await resolveModelId(String(cmdOpts.parentModel), opts);
8194
- }
8195
- const splitRatioOptions = [
8196
- cmdOpts.trainRatio,
8197
- cmdOpts.validationRatio,
8198
- cmdOpts.testRatio
8199
- ];
8200
- if (splitRatioOptions.some((value) => value != null)) {
8201
- body.split_ratios = {
8202
- train: cmdOpts.trainRatio != null ? Number(cmdOpts.trainRatio) : 0.8,
8203
- validation: cmdOpts.validationRatio != null ? Number(cmdOpts.validationRatio) : 0.1,
8204
- test: cmdOpts.testRatio != null ? Number(cmdOpts.testRatio) : 0.1
8205
- };
8206
- }
8207
- const hp = {};
8208
- if (cmdOpts.epochs) hp.n_epochs = Number(cmdOpts.epochs);
8209
- if (cmdOpts.lr) hp.learning_rate = Number(cmdOpts.lr);
8210
- if (cmdOpts.batchSize) hp.batch_size = Number(cmdOpts.batchSize);
8211
- if (cmdOpts.loraRank) hp.lora_rank = Number(cmdOpts.loraRank);
8212
- if (cmdOpts.loraAlpha) hp.lora_alpha = Number(cmdOpts.loraAlpha);
8213
- if (cmdOpts.maxEvalExamples) hp.max_eval_examples = Number(cmdOpts.maxEvalExamples);
8214
- if (cmdOpts.maxTestEvalExamples) hp.max_test_eval_examples = Number(cmdOpts.maxTestEvalExamples);
8215
- if (cmdOpts.longExamples) hp.long_examples = parseLongExamplesPolicy(String(cmdOpts.longExamples));
8216
- if (cmdOpts.maxSeqLength) hp.max_seq_length = Number(cmdOpts.maxSeqLength);
8217
- if (cmdOpts.maxOutputTokens) hp.max_output_tokens = Number(cmdOpts.maxOutputTokens);
8218
- if (cmdOpts.evalReservedOutputTokens) {
8219
- hp.eval_reserved_output_tokens = Number(cmdOpts.evalReservedOutputTokens);
8220
- }
8221
- if (cmdOpts.llmJudge === false) body.use_llm_judge = false;
8222
- if (Object.keys(hp).length) body.hyperparameters = hp;
8223
- return body;
8224
- }
8225
8162
  function registerRunsCommands(parent) {
8226
8163
  const runs = parent.command("runs").description("Manage runs");
8227
8164
  runs.command("archive").description("Archive an inactive local run report published to the dashboard").argument("<id>", "Published run ID or prefix").action(async (id) => {
@@ -8312,36 +8249,11 @@ Eval Results (${data3._evals.length}):`);
8312
8249
  );
8313
8250
  }
8314
8251
  });
8315
- addRunConfigurationOptions(
8316
- runs.command("estimate").description("Estimate run cost and duration before starting").argument("<spec-id>", "Behaviour spec ID (full UUID or 8+ char prefix)")
8317
- ).action(async (specId, cmdOpts) => {
8318
- const opts = parent.optsWithGlobals();
8319
- const body = await buildRunRequestBody(cmdOpts, opts);
8320
- const fullSpecId = await resolveSpecId(specId, opts);
8321
- const { data: data3 } = await post(
8322
- `/behavior-specs/${fullSpecId}/runs/estimate`,
8323
- body,
8324
- opts
8325
- );
8326
- if (isJsonMode()) return printJson(data3);
8327
- printRunEstimate(data3);
8328
- });
8329
- addRunConfigurationOptions(
8330
- runs.command("start").description("Start a new run for a behaviour spec").argument("<spec-id>", "Behaviour spec ID (full UUID or 8+ char prefix)")
8331
- ).action(async (specId, cmdOpts) => {
8332
- const opts = parent.optsWithGlobals();
8333
- const body = await buildRunRequestBody(cmdOpts, opts);
8334
- const fullSpecId = await resolveSpecId(specId, opts);
8335
- const { data: data3 } = await post(
8336
- `/behavior-specs/${fullSpecId}/runs`,
8337
- body,
8338
- opts
8339
- );
8340
- if (isJsonMode()) return printJson(data3);
8341
- printSuccess(
8342
- `Run #${data3.run_number} started (${shortId(data3.id)}). ${formatProgress(data3) ?? `Status: ${formatStatus(data3.status)}`}`
8343
- );
8344
- });
8252
+ for (const name of ["start", "estimate"]) {
8253
+ runs.command(name).description("Retired: use the local pipeline with your own AWS GPU").argument("[spec-id]").action(() => {
8254
+ throw new Error("Hosted training is retired. Configure gpu in local-runner.json and use tt run or tt pipeline run. See docs/local-runtime/aws-gpu.md.");
8255
+ });
8256
+ }
8345
8257
  runs.command("cancel").description("Cancel a running run").argument("<id>", "Run ID (full UUID or 8+ char prefix)").action(async (id) => {
8346
8258
  const opts = parent.optsWithGlobals();
8347
8259
  const fullId = await resolveRunId(id, opts);
@@ -10829,7 +10741,7 @@ async function runCli(version, runtime = {}) {
10829
10741
  }
10830
10742
 
10831
10743
  // src/index.ts
10832
- runCli("0.15.1").catch((err) => {
10744
+ runCli("0.16.0").catch((err) => {
10833
10745
  reportError(err);
10834
10746
  process.exit(1);
10835
10747
  });