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

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
@@ -16,12 +16,13 @@ import {
16
16
  hardenExistingLocalStore,
17
17
  loadLocalRunInput,
18
18
  loadLocalRunnerConfig,
19
+ localPathsOverlap,
19
20
  readHardwareSnapshot,
20
21
  runLocalPipeline,
21
22
  runLoggedProcess,
22
23
  warningsFromSnapshot,
23
24
  withFoundationPythonEnvironment
24
- } from "./chunk-AVMRHDFY.js";
25
+ } from "./chunk-W7MOBPCO.js";
25
26
 
26
27
  // src/cli.ts
27
28
  import { spawn as spawn2 } from "child_process";
@@ -1067,6 +1068,7 @@ var TRAINING_SOURCE_FILES = {
1067
1068
  foundation: {
1068
1069
  tokenizer: "training/foundation/src/train_tokenizer.py",
1069
1070
  pretrain: "training/foundation/src/pretrain.py",
1071
+ checkpoint: "training/foundation/src/checkpoint.py",
1070
1072
  model: "training/foundation/src/model.py",
1071
1073
  data: "training/foundation/src/data.py",
1072
1074
  finetune: "training/foundation/src/finetune.py",
@@ -1522,6 +1524,7 @@ function createTunedTensorTools(api, options = {}) {
1522
1524
  component: Type2.Union([
1523
1525
  Type2.Literal("tokenizer"),
1524
1526
  Type2.Literal("pretrain"),
1527
+ Type2.Literal("checkpoint"),
1525
1528
  Type2.Literal("model"),
1526
1529
  Type2.Literal("data"),
1527
1530
  Type2.Literal("finetune"),
@@ -5134,8 +5137,8 @@ import { resolve as resolve5 } from "path";
5134
5137
  // src/local-runtime/foundation-runner.ts
5135
5138
  import { createHash as createHash3, randomUUID as randomUUID5 } from "crypto";
5136
5139
  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";
5140
+ 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";
5141
+ import { basename as basename4, dirname as dirname4, join as join7, resolve as resolve4 } from "path";
5139
5142
  function isRef(value) {
5140
5143
  return Boolean(value) && typeof value === "object" && !Array.isArray(value) && typeof value.from === "string";
5141
5144
  }
@@ -5204,13 +5207,20 @@ async function readRequiredJsonObject(path, description) {
5204
5207
  }
5205
5208
  return parsed;
5206
5209
  }
5207
- async function writePrivateText(path, content) {
5208
- const file = await open2(path, "w", 384);
5210
+ async function writePrivateTextAtomic(path, content) {
5211
+ const temporary = join7(dirname4(path), `.${basename4(path)}.${randomUUID5()}.tmp`);
5212
+ const file = await open2(temporary, "wx", 384);
5213
+ let closed = false;
5209
5214
  try {
5210
5215
  await file.chmod(384);
5211
5216
  await file.writeFile(content, "utf8");
5212
- } finally {
5217
+ await file.sync();
5213
5218
  await file.close();
5219
+ closed = true;
5220
+ await rename2(temporary, path);
5221
+ } finally {
5222
+ if (!closed) await file.close().catch(() => void 0);
5223
+ await rm2(temporary, { force: true }).catch(() => void 0);
5214
5224
  }
5215
5225
  }
5216
5226
  async function artifactFor(path) {
@@ -5247,6 +5257,11 @@ function assertFoundationPlanSupported(spec, plan) {
5247
5257
  `Foundation runner does not implement ${unsupported.uses} (step ${unsupported.id}).`
5248
5258
  );
5249
5259
  }
5260
+ if (spec.foundation.corpus_path && spec.foundation.validation_path && localPathsOverlap(spec.foundation.corpus_path, spec.foundation.validation_path)) {
5261
+ throw new Error(
5262
+ "Foundation training and validation paths overlap; use disjoint corpora for held-out evaluation."
5263
+ );
5264
+ }
5250
5265
  if (plan.steps.some((step) => step.uses === "rl")) {
5251
5266
  const invalidReward = spec.examples.findIndex(
5252
5267
  (example) => !/-?\d+(?:\.\d+)?/.test(example.output.replaceAll(",", ""))
@@ -5274,7 +5289,9 @@ async function defaultFoundationSpawn(args2) {
5274
5289
  commandArgs: launched.commandArgs,
5275
5290
  env: withFoundationPythonEnvironment(process.env),
5276
5291
  logPath: args2.logPath,
5277
- stage: args2.stepId
5292
+ stage: args2.stepId,
5293
+ shutdownGraceMs: 12e4,
5294
+ appendLog: true
5278
5295
  });
5279
5296
  if (result2.exitCode !== 0) {
5280
5297
  throw new Error(
@@ -5288,8 +5305,26 @@ async function runFoundationPipeline(args2) {
5288
5305
  const outputDir = resolve4(
5289
5306
  args2.outputDir ?? join7(dirname4(resolve4(args2.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID5())
5290
5307
  );
5291
- await mkdir3(outputDir, { recursive: true, mode: 448 });
5292
- await chmod2(outputDir, 448);
5308
+ let outputExists = false;
5309
+ try {
5310
+ const outputInfo = await lstat3(outputDir);
5311
+ if (!args2.resume) {
5312
+ throw new Error(`Foundation output directory already exists; pass --resume ${outputDir} to recover it.`);
5313
+ }
5314
+ if (outputInfo.isSymbolicLink()) {
5315
+ throw new Error(`Foundation resume directory must not be a symbolic link: ${outputDir}`);
5316
+ }
5317
+ if (!outputInfo.isDirectory()) {
5318
+ throw new Error(`Foundation resume path must be a directory: ${outputDir}`);
5319
+ }
5320
+ outputExists = true;
5321
+ } catch (error) {
5322
+ if (error.code !== "ENOENT") throw error;
5323
+ }
5324
+ if (!outputExists) {
5325
+ await mkdir3(outputDir, { recursive: true, mode: 448 });
5326
+ }
5327
+ await makePrivateTree(outputDir);
5293
5328
  const spawnStep = args2.spawnStep ?? defaultFoundationSpawn;
5294
5329
  const produced = /* @__PURE__ */ new Map();
5295
5330
  const steps = [];
@@ -5297,6 +5332,7 @@ async function runFoundationPipeline(args2) {
5297
5332
  const hp = args2.spec.foundation;
5298
5333
  let tokenizerDir;
5299
5334
  let modelDir;
5335
+ let upstreamExecuted = false;
5300
5336
  const resolveKind = (stepId, kind) => {
5301
5337
  const path = produced.get(stepId)?.[kind];
5302
5338
  if (!path) throw new Error(`Step ${stepId} did not produce ${kind}.`);
@@ -5316,6 +5352,8 @@ async function runFoundationPipeline(args2) {
5316
5352
  await chmod2(output, 448);
5317
5353
  const configPath2 = join7(stepDir, "config.json");
5318
5354
  const logPath = join7(stepDir, "step.log");
5355
+ const metricsPath = join7(output, "metrics.json");
5356
+ const completionPath = join7(stepDir, "completion.json");
5319
5357
  const fields = stepConfig(step);
5320
5358
  let entrypoint;
5321
5359
  let config;
@@ -5324,7 +5362,8 @@ async function runFoundationPipeline(args2) {
5324
5362
  config = {
5325
5363
  output_dir: output,
5326
5364
  vocab_size: fields.vocabSize ?? hp.vocab_size,
5327
- max_chars: fields.maxChars ?? hp.max_chars,
5365
+ max_chars: hp.tokenizer_max_chars ?? fields.maxChars ?? hp.max_chars,
5366
+ corpus_path: hp.corpus_path,
5328
5367
  system_prompt: args2.spec.system_prompt,
5329
5368
  examples
5330
5369
  };
@@ -5334,12 +5373,28 @@ async function runFoundationPipeline(args2) {
5334
5373
  config = {
5335
5374
  tokenizer_dir: tokenizerDir,
5336
5375
  output_dir: output,
5376
+ work_dir: join7(stepDir, "recovery"),
5337
5377
  depth: fields.depth ?? hp.depth,
5338
5378
  steps: fields.steps ?? hp.pretrain_steps,
5339
5379
  batch_size: fields.batchSize ?? hp.batch_size,
5340
5380
  sequence_length: fields.sequenceLength ?? hp.sequence_length,
5341
5381
  nproc_per_node: fields.nprocPerNode ?? hp.nproc_per_node,
5342
5382
  max_chars: hp.max_chars,
5383
+ corpus_path: hp.corpus_path,
5384
+ seed: hp.seed,
5385
+ learning_rate: hp.learning_rate,
5386
+ weight_decay: hp.weight_decay,
5387
+ warmup_steps: hp.warmup_steps,
5388
+ min_lr_ratio: hp.min_lr_ratio,
5389
+ gradient_accumulation_steps: hp.gradient_accumulation_steps,
5390
+ gradient_clip: hp.gradient_clip,
5391
+ bf16: hp.bf16,
5392
+ checkpoint_interval_steps: hp.checkpoint_interval_steps,
5393
+ checkpoint_interval_seconds: hp.checkpoint_interval_seconds,
5394
+ keep_checkpoints: hp.keep_checkpoints,
5395
+ log_interval_steps: hp.log_interval_steps,
5396
+ checkpoint_backup_dir: hp.checkpoint_backup_dir,
5397
+ resume: true,
5343
5398
  system_prompt: args2.spec.system_prompt,
5344
5399
  examples
5345
5400
  };
@@ -5377,6 +5432,7 @@ async function runFoundationPipeline(args2) {
5377
5432
  output_dir: output,
5378
5433
  sequence_length: hp.sequence_length,
5379
5434
  max_chars: hp.max_chars,
5435
+ validation_path: hp.validation_path,
5380
5436
  system_prompt: args2.spec.system_prompt,
5381
5437
  examples
5382
5438
  };
@@ -5386,12 +5442,33 @@ async function runFoundationPipeline(args2) {
5386
5442
  if ((step.uses === "finetune" || step.uses === "rl" || step.uses === "evaluate") && !tokenizerDir) {
5387
5443
  throw new Error(`Step ${step.id} needs a tokenizer from an earlier tokenize step.`);
5388
5444
  }
5389
- await writePrivateText(configPath2, `${JSON.stringify(config, null, 2)}
5445
+ let completedStep = false;
5446
+ if (args2.resume) {
5447
+ try {
5448
+ const previousConfig = JSON.parse(await readFile4(configPath2, "utf8"));
5449
+ if (JSON.stringify(previousConfig) !== JSON.stringify(config)) {
5450
+ throw new Error(
5451
+ `Foundation step "${step.id}" configuration changed; resume requires the original spec and pipeline.`
5452
+ );
5453
+ }
5454
+ if (!upstreamExecuted) {
5455
+ await lstat3(metricsPath);
5456
+ await lstat3(completionPath);
5457
+ completedStep = true;
5458
+ }
5459
+ } catch (error) {
5460
+ if (error.code !== "ENOENT") throw error;
5461
+ }
5462
+ }
5463
+ if (!completedStep) {
5464
+ await writePrivateTextAtomic(configPath2, `${JSON.stringify(config, null, 2)}
5390
5465
  `);
5391
- await spawnStep({ entrypoint, configPath: configPath2, logPath, stepId: step.id });
5466
+ await spawnStep({ entrypoint, configPath: configPath2, logPath, stepId: step.id });
5467
+ upstreamExecuted = true;
5468
+ }
5392
5469
  await makePrivateTree(stepDir);
5393
5470
  const metrics = await readRequiredJsonObject(
5394
- join7(output, "metrics.json"),
5471
+ metricsPath,
5395
5472
  `Foundation step "${step.id}" metrics`
5396
5473
  );
5397
5474
  if (metrics.ok !== true) {
@@ -5434,6 +5511,26 @@ async function runFoundationPipeline(args2) {
5434
5511
  record4.report = reportPath2;
5435
5512
  artifacts.report = await artifactFor(reportPath2);
5436
5513
  }
5514
+ const completion = {
5515
+ version: 1,
5516
+ config_sha256: await hashPath(configPath2),
5517
+ metrics_sha256: await hashPath(metricsPath),
5518
+ artifacts
5519
+ };
5520
+ if (completedStep) {
5521
+ const previousCompletion = await readRequiredJsonObject(
5522
+ completionPath,
5523
+ `Foundation step "${step.id}" completion manifest`
5524
+ );
5525
+ if (JSON.stringify(previousCompletion) !== JSON.stringify(completion)) {
5526
+ throw new Error(
5527
+ `Foundation step "${step.id}" integrity changed after completion; use a new run directory.`
5528
+ );
5529
+ }
5530
+ } else {
5531
+ await writePrivateTextAtomic(completionPath, `${JSON.stringify(completion, null, 2)}
5532
+ `);
5533
+ }
5437
5534
  produced.set(step.id, record4);
5438
5535
  steps.push({ id: step.id, uses: step.uses, metrics, artifacts });
5439
5536
  }
@@ -5444,7 +5541,7 @@ async function runFoundationPipeline(args2) {
5444
5541
  steps
5445
5542
  };
5446
5543
  const reportPath = join7(outputDir, "report.json");
5447
- await writePrivateText(reportPath, `${JSON.stringify(report, null, 2)}
5544
+ await writePrivateTextAtomic(reportPath, `${JSON.stringify(report, null, 2)}
5448
5545
  `);
5449
5546
  return {
5450
5547
  status: "succeeded",
@@ -5592,7 +5689,10 @@ function registerPipelineCommands(parent) {
5592
5689
  const plan = createExecutionPlan(document, { only: parseList(options.only), skip: parseList(options.skip) });
5593
5690
  outputPlan(plan, await hostWarningsForPipeline(document, options.spec));
5594
5691
  });
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) => {
5692
+ 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) => {
5693
+ if (options.output && options.resume) {
5694
+ throw new Error("--output and --resume are mutually exclusive.");
5695
+ }
5596
5696
  const document = await resolvePipelineDocument(options);
5597
5697
  const plan = createExecutionPlan(document, { only: parseList(options.only), skip: parseList(options.skip) });
5598
5698
  const hostWarnings = await hostWarningsForPipeline(document, options.spec);
@@ -5621,12 +5721,17 @@ function registerPipelineCommands(parent) {
5621
5721
  const result3 = await runFoundationPipeline({
5622
5722
  spec: input.spec,
5623
5723
  plan,
5624
- specPath: resolve5(options.spec)
5724
+ specPath: resolve5(options.spec),
5725
+ ...options.output || options.resume ? { outputDir: resolve5(options.resume ?? options.output) } : {},
5726
+ resume: Boolean(options.resume)
5625
5727
  });
5626
5728
  if (isJsonMode()) return printJson(result3);
5627
5729
  printSuccess(`Foundation pipeline completed. Report: ${result3.report_path}`);
5628
5730
  return;
5629
5731
  }
5732
+ if (options.output || options.resume) {
5733
+ throw new Error("--output and --resume are only valid for foundation pipelines.");
5734
+ }
5630
5735
  assertLocalRunInputReady(input.request);
5631
5736
  const config = await loadLocalRunnerConfig(options.config ? resolve5(options.config) : void 0);
5632
5737
  const localPipeline = {
@@ -6036,7 +6141,7 @@ async function runCli(version, runtime = {}) {
6036
6141
  }
6037
6142
 
6038
6143
  // src/index.ts
6039
- runCli("0.13.1-beta.5").catch((err) => {
6144
+ runCli("0.13.1-beta.6").catch((err) => {
6040
6145
  reportError(err);
6041
6146
  process.exit(1);
6042
6147
  });