@tuned-tensor/cli 0.13.1-beta.5 → 0.13.1-beta.7

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
@@ -7,6 +7,7 @@ import {
7
7
  assertLocalRunInputReady,
8
8
  assessHardware,
9
9
  buildFoundationPythonCommand,
10
+ buildSystemMessage,
10
11
  defaultStoreRoot,
11
12
  expandUserPath,
12
13
  formatAgentHostBlock,
@@ -16,12 +17,13 @@ import {
16
17
  hardenExistingLocalStore,
17
18
  loadLocalRunInput,
18
19
  loadLocalRunnerConfig,
20
+ localPathsOverlap,
19
21
  readHardwareSnapshot,
20
22
  runLocalPipeline,
21
23
  runLoggedProcess,
22
24
  warningsFromSnapshot,
23
25
  withFoundationPythonEnvironment
24
- } from "./chunk-AVMRHDFY.js";
26
+ } from "./chunk-AKJ5WZ2S.js";
25
27
 
26
28
  // src/cli.ts
27
29
  import { spawn as spawn2 } from "child_process";
@@ -1067,6 +1069,7 @@ var TRAINING_SOURCE_FILES = {
1067
1069
  foundation: {
1068
1070
  tokenizer: "training/foundation/src/train_tokenizer.py",
1069
1071
  pretrain: "training/foundation/src/pretrain.py",
1072
+ checkpoint: "training/foundation/src/checkpoint.py",
1070
1073
  model: "training/foundation/src/model.py",
1071
1074
  data: "training/foundation/src/data.py",
1072
1075
  finetune: "training/foundation/src/finetune.py",
@@ -1522,6 +1525,7 @@ function createTunedTensorTools(api, options = {}) {
1522
1525
  component: Type2.Union([
1523
1526
  Type2.Literal("tokenizer"),
1524
1527
  Type2.Literal("pretrain"),
1528
+ Type2.Literal("checkpoint"),
1525
1529
  Type2.Literal("model"),
1526
1530
  Type2.Literal("data"),
1527
1531
  Type2.Literal("finetune"),
@@ -5134,11 +5138,22 @@ import { resolve as resolve5 } from "path";
5134
5138
  // src/local-runtime/foundation-runner.ts
5135
5139
  import { createHash as createHash3, randomUUID as randomUUID5 } from "crypto";
5136
5140
  import { createReadStream } from "fs";
5137
- import { chmod as chmod2, lstat as lstat3, mkdir as mkdir3, open as open2, readdir as readdir3, readFile as readFile4 } from "fs/promises";
5138
- import { dirname as dirname4, join as join7, resolve as resolve4 } from "path";
5141
+ import { chmod as chmod2, lstat as lstat3, mkdir as mkdir3, open as open2, readdir as readdir3, readFile as readFile4, rename as rename2, rm as rm2 } from "fs/promises";
5142
+ import { basename as basename4, dirname as dirname4, join as join7, resolve as resolve4 } from "path";
5143
+ var INSTRUCTION_CORPUS_VERSION = 1;
5139
5144
  function isRef(value) {
5140
5145
  return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.from === "string";
5141
5146
  }
5147
+ function resumeConfigMatches(previous, current, legacySystemPrompt) {
5148
+ if (JSON.stringify(previous) === JSON.stringify(current)) return true;
5149
+ if (!previous || typeof previous !== "object" || Array.isArray(previous)) return false;
5150
+ const legacyInstruction = legacySystemPrompt.trim();
5151
+ if (!legacyInstruction || current.system_prompt !== legacyInstruction) return false;
5152
+ const migrated = { ...previous };
5153
+ if (migrated.system_prompt !== legacySystemPrompt) return false;
5154
+ migrated.system_prompt = legacyInstruction;
5155
+ return JSON.stringify(migrated) === JSON.stringify(current);
5156
+ }
5142
5157
  function parseRef(value) {
5143
5158
  const index = value.from.lastIndexOf(".");
5144
5159
  if (index <= 0) throw new Error(`Invalid artifact reference: ${value.from}`);
@@ -5204,13 +5219,20 @@ async function readRequiredJsonObject(path, description) {
5204
5219
  }
5205
5220
  return parsed;
5206
5221
  }
5207
- async function writePrivateText(path, content) {
5208
- const file = await open2(path, "w", 384);
5222
+ async function writePrivateTextAtomic(path, content) {
5223
+ const temporary = join7(dirname4(path), `.${basename4(path)}.${randomUUID5()}.tmp`);
5224
+ const file = await open2(temporary, "wx", 384);
5225
+ let closed = false;
5209
5226
  try {
5210
5227
  await file.chmod(384);
5211
5228
  await file.writeFile(content, "utf8");
5212
- } finally {
5229
+ await file.sync();
5213
5230
  await file.close();
5231
+ closed = true;
5232
+ await rename2(temporary, path);
5233
+ } finally {
5234
+ if (!closed) await file.close().catch(() => void 0);
5235
+ await rm2(temporary, { force: true }).catch(() => void 0);
5214
5236
  }
5215
5237
  }
5216
5238
  async function artifactFor(path) {
@@ -5247,6 +5269,11 @@ function assertFoundationPlanSupported(spec, plan) {
5247
5269
  `Foundation runner does not implement ${unsupported.uses} (step ${unsupported.id}).`
5248
5270
  );
5249
5271
  }
5272
+ if (spec.foundation.corpus_path && spec.foundation.validation_path && localPathsOverlap(spec.foundation.corpus_path, spec.foundation.validation_path)) {
5273
+ throw new Error(
5274
+ "Foundation training and validation paths overlap; use disjoint corpora for held-out evaluation."
5275
+ );
5276
+ }
5250
5277
  if (plan.steps.some((step) => step.uses === "rl")) {
5251
5278
  const invalidReward = spec.examples.findIndex(
5252
5279
  (example) => !/-?\d+(?:\.\d+)?/.test(example.output.replaceAll(",", ""))
@@ -5274,7 +5301,9 @@ async function defaultFoundationSpawn(args2) {
5274
5301
  commandArgs: launched.commandArgs,
5275
5302
  env: withFoundationPythonEnvironment(process.env),
5276
5303
  logPath: args2.logPath,
5277
- stage: args2.stepId
5304
+ stage: args2.stepId,
5305
+ shutdownGraceMs: 12e4,
5306
+ appendLog: true
5278
5307
  });
5279
5308
  if (result2.exitCode !== 0) {
5280
5309
  throw new Error(
@@ -5288,15 +5317,35 @@ async function runFoundationPipeline(args2) {
5288
5317
  const outputDir = resolve4(
5289
5318
  args2.outputDir ?? join7(dirname4(resolve4(args2.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID5())
5290
5319
  );
5291
- await mkdir3(outputDir, { recursive: true, mode: 448 });
5292
- await chmod2(outputDir, 448);
5320
+ let outputExists = false;
5321
+ try {
5322
+ const outputInfo = await lstat3(outputDir);
5323
+ if (!args2.resume) {
5324
+ throw new Error(`Foundation output directory already exists; pass --resume ${outputDir} to recover it.`);
5325
+ }
5326
+ if (outputInfo.isSymbolicLink()) {
5327
+ throw new Error(`Foundation resume directory must not be a symbolic link: ${outputDir}`);
5328
+ }
5329
+ if (!outputInfo.isDirectory()) {
5330
+ throw new Error(`Foundation resume path must be a directory: ${outputDir}`);
5331
+ }
5332
+ outputExists = true;
5333
+ } catch (error) {
5334
+ if (error.code !== "ENOENT") throw error;
5335
+ }
5336
+ if (!outputExists) {
5337
+ await mkdir3(outputDir, { recursive: true, mode: 448 });
5338
+ }
5339
+ await makePrivateTree(outputDir);
5293
5340
  const spawnStep = args2.spawnStep ?? defaultFoundationSpawn;
5294
5341
  const produced = /* @__PURE__ */ new Map();
5295
5342
  const steps = [];
5296
5343
  const examples = args2.spec.examples.map((example) => ({ input: example.input, output: example.output }));
5344
+ const systemInstruction = buildSystemMessage(args2.spec);
5297
5345
  const hp = args2.spec.foundation;
5298
5346
  let tokenizerDir;
5299
5347
  let modelDir;
5348
+ let upstreamExecuted = false;
5300
5349
  const resolveKind = (stepId, kind) => {
5301
5350
  const path = produced.get(stepId)?.[kind];
5302
5351
  if (!path) throw new Error(`Step ${stepId} did not produce ${kind}.`);
@@ -5316,6 +5365,8 @@ async function runFoundationPipeline(args2) {
5316
5365
  await chmod2(output, 448);
5317
5366
  const configPath2 = join7(stepDir, "config.json");
5318
5367
  const logPath = join7(stepDir, "step.log");
5368
+ const metricsPath = join7(output, "metrics.json");
5369
+ const completionPath = join7(stepDir, "completion.json");
5319
5370
  const fields = stepConfig(step);
5320
5371
  let entrypoint;
5321
5372
  let config;
@@ -5324,8 +5375,10 @@ async function runFoundationPipeline(args2) {
5324
5375
  config = {
5325
5376
  output_dir: output,
5326
5377
  vocab_size: fields.vocabSize ?? hp.vocab_size,
5327
- max_chars: fields.maxChars ?? hp.max_chars,
5328
- system_prompt: args2.spec.system_prompt,
5378
+ max_chars: hp.tokenizer_max_chars ?? fields.maxChars ?? hp.max_chars,
5379
+ corpus_path: hp.corpus_path,
5380
+ ...hp.corpus_path ? { instruction_corpus_version: INSTRUCTION_CORPUS_VERSION } : {},
5381
+ system_prompt: systemInstruction,
5329
5382
  examples
5330
5383
  };
5331
5384
  } else if (step.uses === "pretrain") {
@@ -5334,13 +5387,30 @@ async function runFoundationPipeline(args2) {
5334
5387
  config = {
5335
5388
  tokenizer_dir: tokenizerDir,
5336
5389
  output_dir: output,
5390
+ work_dir: join7(stepDir, "recovery"),
5337
5391
  depth: fields.depth ?? hp.depth,
5338
5392
  steps: fields.steps ?? hp.pretrain_steps,
5339
5393
  batch_size: fields.batchSize ?? hp.batch_size,
5340
5394
  sequence_length: fields.sequenceLength ?? hp.sequence_length,
5341
5395
  nproc_per_node: fields.nprocPerNode ?? hp.nproc_per_node,
5342
5396
  max_chars: hp.max_chars,
5343
- system_prompt: args2.spec.system_prompt,
5397
+ corpus_path: hp.corpus_path,
5398
+ ...hp.corpus_path ? { instruction_corpus_version: INSTRUCTION_CORPUS_VERSION } : {},
5399
+ seed: hp.seed,
5400
+ learning_rate: hp.learning_rate,
5401
+ weight_decay: hp.weight_decay,
5402
+ warmup_steps: hp.warmup_steps,
5403
+ min_lr_ratio: hp.min_lr_ratio,
5404
+ gradient_accumulation_steps: hp.gradient_accumulation_steps,
5405
+ gradient_clip: hp.gradient_clip,
5406
+ bf16: hp.bf16,
5407
+ checkpoint_interval_steps: hp.checkpoint_interval_steps,
5408
+ checkpoint_interval_seconds: hp.checkpoint_interval_seconds,
5409
+ keep_checkpoints: hp.keep_checkpoints,
5410
+ log_interval_steps: hp.log_interval_steps,
5411
+ checkpoint_backup_dir: hp.checkpoint_backup_dir,
5412
+ resume: true,
5413
+ system_prompt: systemInstruction,
5344
5414
  examples
5345
5415
  };
5346
5416
  } else if (step.uses === "finetune") {
@@ -5353,7 +5423,7 @@ async function runFoundationPipeline(args2) {
5353
5423
  steps: fields.steps ?? hp.finetune_steps,
5354
5424
  batch_size: fields.batchSize ?? hp.batch_size,
5355
5425
  sequence_length: hp.sequence_length,
5356
- system_prompt: args2.spec.system_prompt,
5426
+ system_prompt: systemInstruction,
5357
5427
  examples
5358
5428
  };
5359
5429
  } else if (step.uses === "rl") {
@@ -5364,7 +5434,7 @@ async function runFoundationPipeline(args2) {
5364
5434
  tokenizer_dir: tokenizerDir,
5365
5435
  output_dir: output,
5366
5436
  steps: fields.steps ?? hp.rl_steps,
5367
- system_prompt: args2.spec.system_prompt,
5437
+ system_prompt: systemInstruction,
5368
5438
  examples
5369
5439
  };
5370
5440
  } else if (step.uses === "evaluate") {
@@ -5377,7 +5447,8 @@ async function runFoundationPipeline(args2) {
5377
5447
  output_dir: output,
5378
5448
  sequence_length: hp.sequence_length,
5379
5449
  max_chars: hp.max_chars,
5380
- system_prompt: args2.spec.system_prompt,
5450
+ validation_path: hp.validation_path,
5451
+ system_prompt: systemInstruction,
5381
5452
  examples
5382
5453
  };
5383
5454
  } else {
@@ -5386,12 +5457,33 @@ async function runFoundationPipeline(args2) {
5386
5457
  if ((step.uses === "finetune" || step.uses === "rl" || step.uses === "evaluate") && !tokenizerDir) {
5387
5458
  throw new Error(`Step ${step.id} needs a tokenizer from an earlier tokenize step.`);
5388
5459
  }
5389
- await writePrivateText(configPath2, `${JSON.stringify(config, null, 2)}
5460
+ let completedStep = false;
5461
+ if (args2.resume) {
5462
+ try {
5463
+ const previousConfig = JSON.parse(await readFile4(configPath2, "utf8"));
5464
+ if (!resumeConfigMatches(previousConfig, config, args2.spec.system_prompt)) {
5465
+ throw new Error(
5466
+ `Foundation step "${step.id}" configuration changed; resume requires the original spec and pipeline.`
5467
+ );
5468
+ }
5469
+ if (!upstreamExecuted) {
5470
+ await lstat3(metricsPath);
5471
+ await lstat3(completionPath);
5472
+ completedStep = true;
5473
+ }
5474
+ } catch (error) {
5475
+ if (error.code !== "ENOENT") throw error;
5476
+ }
5477
+ }
5478
+ if (!completedStep) {
5479
+ await writePrivateTextAtomic(configPath2, `${JSON.stringify(config, null, 2)}
5390
5480
  `);
5391
- await spawnStep({ entrypoint, configPath: configPath2, logPath, stepId: step.id });
5481
+ await spawnStep({ entrypoint, configPath: configPath2, logPath, stepId: step.id });
5482
+ upstreamExecuted = true;
5483
+ }
5392
5484
  await makePrivateTree(stepDir);
5393
5485
  const metrics = await readRequiredJsonObject(
5394
- join7(output, "metrics.json"),
5486
+ metricsPath,
5395
5487
  `Foundation step "${step.id}" metrics`
5396
5488
  );
5397
5489
  if (metrics.ok !== true) {
@@ -5434,6 +5526,26 @@ async function runFoundationPipeline(args2) {
5434
5526
  record4.report = reportPath2;
5435
5527
  artifacts.report = await artifactFor(reportPath2);
5436
5528
  }
5529
+ const completion = {
5530
+ version: 1,
5531
+ config_sha256: await hashPath(configPath2),
5532
+ metrics_sha256: await hashPath(metricsPath),
5533
+ artifacts
5534
+ };
5535
+ if (completedStep) {
5536
+ const previousCompletion = await readRequiredJsonObject(
5537
+ completionPath,
5538
+ `Foundation step "${step.id}" completion manifest`
5539
+ );
5540
+ if (JSON.stringify(previousCompletion) !== JSON.stringify(completion)) {
5541
+ throw new Error(
5542
+ `Foundation step "${step.id}" integrity changed after completion; use a new run directory.`
5543
+ );
5544
+ }
5545
+ } else {
5546
+ await writePrivateTextAtomic(completionPath, `${JSON.stringify(completion, null, 2)}
5547
+ `);
5548
+ }
5437
5549
  produced.set(step.id, record4);
5438
5550
  steps.push({ id: step.id, uses: step.uses, metrics, artifacts });
5439
5551
  }
@@ -5444,7 +5556,7 @@ async function runFoundationPipeline(args2) {
5444
5556
  steps
5445
5557
  };
5446
5558
  const reportPath = join7(outputDir, "report.json");
5447
- await writePrivateText(reportPath, `${JSON.stringify(report, null, 2)}
5559
+ await writePrivateTextAtomic(reportPath, `${JSON.stringify(report, null, 2)}
5448
5560
  `);
5449
5561
  return {
5450
5562
  status: "succeeded",
@@ -5592,7 +5704,10 @@ function registerPipelineCommands(parent) {
5592
5704
  const plan = createExecutionPlan(document, { only: parseList(options.only), skip: parseList(options.skip) });
5593
5705
  outputPlan(plan, await hostWarningsForPipeline(document, options.spec));
5594
5706
  });
5595
- pipeline.command("run").description("Run an ordered local pipeline, or safely preview any pipeline").option("--dry-run", "Resolve and display only; never execute, transfer, or reserve credits").option("-f, --file <path>", "Pipeline file", DEFAULT_PIPELINE_FILE).option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).option("--config <path>", "Local runtime config").option("--only <ids>", "Comma-separated step IDs to include").option("--skip <ids>", "Comma-separated step IDs to omit").action(async (options) => {
5707
+ pipeline.command("run").description("Run an ordered local pipeline, or safely preview any pipeline").option("--dry-run", "Resolve and display only; never execute, transfer, or reserve credits").option("-f, --file <path>", "Pipeline file", DEFAULT_PIPELINE_FILE).option("--spec <path>", "Local behavior spec", DEFAULT_SPEC_FILE).option("--config <path>", "Local runtime config").option("--output <path>", "Foundation run directory (must not already exist)").option("--resume <path>", "Resume a foundation run directory").option("--only <ids>", "Comma-separated step IDs to include").option("--skip <ids>", "Comma-separated step IDs to omit").action(async (options) => {
5708
+ if (options.output && options.resume) {
5709
+ throw new Error("--output and --resume are mutually exclusive.");
5710
+ }
5596
5711
  const document = await resolvePipelineDocument(options);
5597
5712
  const plan = createExecutionPlan(document, { only: parseList(options.only), skip: parseList(options.skip) });
5598
5713
  const hostWarnings = await hostWarningsForPipeline(document, options.spec);
@@ -5621,12 +5736,17 @@ function registerPipelineCommands(parent) {
5621
5736
  const result3 = await runFoundationPipeline({
5622
5737
  spec: input.spec,
5623
5738
  plan,
5624
- specPath: resolve5(options.spec)
5739
+ specPath: resolve5(options.spec),
5740
+ ...options.output || options.resume ? { outputDir: resolve5(options.resume ?? options.output) } : {},
5741
+ resume: Boolean(options.resume)
5625
5742
  });
5626
5743
  if (isJsonMode()) return printJson(result3);
5627
5744
  printSuccess(`Foundation pipeline completed. Report: ${result3.report_path}`);
5628
5745
  return;
5629
5746
  }
5747
+ if (options.output || options.resume) {
5748
+ throw new Error("--output and --resume are only valid for foundation pipelines.");
5749
+ }
5630
5750
  assertLocalRunInputReady(input.request);
5631
5751
  const config = await loadLocalRunnerConfig(options.config ? resolve5(options.config) : void 0);
5632
5752
  const localPipeline = {
@@ -6036,7 +6156,7 @@ async function runCli(version, runtime = {}) {
6036
6156
  }
6037
6157
 
6038
6158
  // src/index.ts
6039
- runCli("0.13.1-beta.5").catch((err) => {
6159
+ runCli("0.13.1-beta.7").catch((err) => {
6040
6160
  reportError(err);
6041
6161
  process.exit(1);
6042
6162
  });