@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/README.md CHANGED
@@ -141,7 +141,7 @@ guarantee that a model or dataset is compatible with a TT workflow.
141
141
 
142
142
  For educational questions about how or why training works, the agent can read
143
143
  the exact Python implementation shipped with the running TT build. The source
144
- tool is limited to named foundation components (tokenizer, pretraining, model,
144
+ tool is limited to named foundation components (tokenizer, pretraining, checkpointing, model,
145
145
  data, SFT, RL, evaluation, and common helpers) and adapter components (training,
146
146
  data, model contract, and evaluation); it cannot read arbitrary paths. Answers
147
147
  separate behavior directly visible in the code from inferred rationale and
@@ -206,16 +206,25 @@ Two local engines share that document version:
206
206
  `inference` evaluators. Requires a foundation `tunedtensor.json`
207
207
  (`engine: "foundation"`, at least two examples, no `base_model`).
208
208
 
209
- The foundation engine is a small, readable local baseline inspired by Andrej
209
+ Both engines are spec-driven. `system_prompt`, `guidelines`, and `constraints`
210
+ are compiled into one canonical system instruction, and `examples` supply the
211
+ demonstrated behavior. Adapter training, foundation tokenizer/pretraining,
212
+ chat SFT, optional RL, and evaluation all receive that same compiled
213
+ instruction. Engine-specific fields choose how TT learns the behavior; they do
214
+ not redefine the behavior itself.
215
+
216
+ The foundation engine is a readable, single-GPU local trainer inspired by Andrej
210
217
  Karpathy's [nanochat](https://github.com/karpathy/nanochat), not a port of its
211
- compute-optimal training stack. The first version deliberately builds a bounded
212
- pretraining corpus from the spec prompt and examples, reports chat accuracy on
213
- those same contract examples, and supports one training process. Foundation
218
+ distributed training stack. With `foundation.corpus_path`, it streams `.txt`
219
+ or JSONL (`{"text":"..."}`) shards into a deterministic on-disk token cache;
220
+ without that field it retains the bounded prompt/example corpus for smoke runs.
221
+ `foundation.validation_path` supplies a distinct held-out BPB corpus. Foundation
214
222
  plans reject `compare` before creating run artifacts; comparison remains an
215
223
  adapter-engine capability. Optional RL samples one seeded on-policy completion
216
224
  per step with a sparse last-number exact reward and therefore requires a numeric
217
225
  expected output for every example. Treat those metrics as an end-to-end
218
- execution and overfit check—not held-out capability or multi-GPU evidence.
226
+ execution and overfit check—not held-out capability or multi-GPU evidence when
227
+ no validation corpus is configured.
219
228
 
220
229
  ```bash
221
230
  tt pipeline init --file pipeline.json
@@ -227,6 +236,7 @@ tt --json pipeline run --file pipeline.json \
227
236
  --spec tunedtensor.json --config local-runner.json
228
237
  tt init --engine foundation --name "Tiny GPT"
229
238
  tt --json pipeline run --spec tunedtensor.json --dry-run
239
+ tt pipeline run --spec tunedtensor.json --resume /absolute/path/to/foundation-run
230
240
  ```
231
241
 
232
242
  `--only` and `--skip` preserve dependency safety: a selected step cannot refer
@@ -234,6 +244,14 @@ to an omitted predecessor. Cloud-targeted steps fail closed unless you pass
234
244
  `--dry-run`. Foundation documents are local-only. The TT agent may describe,
235
245
  validate, and prepare a plan, but has no direct pipeline-execute tool.
236
246
 
247
+ Long pretraining uses BF16, warmup plus cosine decay, gradient clipping,
248
+ periodic metrics, and atomic rolling checkpoints. `--resume <run-directory>`
249
+ starts or resumes one stable run identity; completed stages are verified and
250
+ skipped, while an interrupted pretrain step restores model, optimizer,
251
+ scheduler, RNG, counters, and token cursor. See
252
+ [Long foundation runs](docs/local-runtime/foundation-long-runs.md) before an
253
+ unattended job.
254
+
237
255
  ## Quick start
238
256
 
239
257
  Create a local project on an NVIDIA host:
@@ -1176,7 +1176,7 @@ function warningsFromSnapshot(report, target) {
1176
1176
  }
1177
1177
 
1178
1178
  // src/local-runtime/host-inventory.ts
1179
- import { constants } from "fs";
1179
+ import { constants as constants2 } from "fs";
1180
1180
  import { access, statfs } from "fs/promises";
1181
1181
  import { cpus, freemem, homedir as homedir3, totalmem, type } from "os";
1182
1182
  import { dirname as dirname5, join as join5, resolve as resolve7 } from "path";
@@ -1334,8 +1334,8 @@ function withOfflineHuggingFaceCacheEnvironment(env, modelCache) {
1334
1334
  // src/local-runtime/process-runner.ts
1335
1335
  import { createHash as createHash2 } from "crypto";
1336
1336
  import { spawn } from "child_process";
1337
- import { createWriteStream, readFileSync } from "fs";
1338
- import { mkdir as mkdir2 } from "fs/promises";
1337
+ import { constants, readFileSync } from "fs";
1338
+ import { mkdir as mkdir2, open as open2 } from "fs/promises";
1339
1339
  import { dirname as dirname4, join as join4, resolve as resolve6 } from "path";
1340
1340
  var packageRoot = localRuntimePackageRoot(import.meta.url);
1341
1341
  var bundledProject = join4(packageRoot, "training/adapter");
@@ -1381,6 +1381,7 @@ function withBundledPythonEnvironment(env) {
1381
1381
  };
1382
1382
  }
1383
1383
  function buildFoundationPythonCommand(entrypoint, args = []) {
1384
+ const target = entrypoint === "-c" ? entrypoint : join4(foundationProject, "src", entrypoint);
1384
1385
  const commandArgs = [
1385
1386
  "run",
1386
1387
  "--frozen",
@@ -1388,7 +1389,7 @@ function buildFoundationPythonCommand(entrypoint, args = []) {
1388
1389
  "--project",
1389
1390
  foundationProject,
1390
1391
  "python",
1391
- join4(foundationProject, "src", entrypoint),
1392
+ target,
1392
1393
  ...args
1393
1394
  ];
1394
1395
  return {
@@ -1409,28 +1410,19 @@ var ProcessCancelledError = class extends Error {
1409
1410
  this.name = "ProcessCancelledError";
1410
1411
  }
1411
1412
  };
1412
- async function openProcessLog(path, exclusive) {
1413
- const stream = createWriteStream(path, {
1414
- flags: exclusive ? "wx" : "w",
1415
- mode: 384
1416
- });
1417
- await new Promise((resolveOpen, reject) => {
1418
- const onOpen = () => {
1419
- stream.off("error", onError);
1420
- resolveOpen();
1421
- };
1422
- const onError = (error) => {
1423
- stream.off("open", onOpen);
1424
- reject(error);
1425
- };
1426
- stream.once("open", onOpen);
1427
- stream.once("error", onError);
1428
- });
1429
- return stream;
1413
+ async function openProcessLog(path, exclusive, append) {
1414
+ const flags = constants.O_WRONLY | constants.O_CREAT | (exclusive ? constants.O_EXCL : append ? constants.O_APPEND : constants.O_TRUNC) | (process.platform === "win32" ? 0 : constants.O_NOFOLLOW);
1415
+ const handle = await open2(path, flags, 384);
1416
+ try {
1417
+ return handle.createWriteStream({ autoClose: true });
1418
+ } catch (error) {
1419
+ await handle.close();
1420
+ throw error;
1421
+ }
1430
1422
  }
1431
1423
  async function runLoggedProcess(args) {
1432
1424
  if (args.logPath) await mkdir2(dirname4(args.logPath), { recursive: true });
1433
- const logStream = args.logPath ? await openProcessLog(args.logPath, args.exclusiveLog ?? false) : null;
1425
+ const logStream = args.logPath ? await openProcessLog(args.logPath, args.exclusiveLog ?? false, args.appendLog ?? false) : null;
1434
1426
  let stderr = "";
1435
1427
  try {
1436
1428
  const exitCode = await new Promise((resolvePromise, reject) => {
@@ -1458,7 +1450,7 @@ async function runLoggedProcess(args) {
1458
1450
  const requestStop = (signal = "SIGTERM") => {
1459
1451
  killProcessGroup(signal);
1460
1452
  if (!forceKillTimer) {
1461
- forceKillTimer = setTimeout(() => killProcessGroup("SIGKILL"), 5e3);
1453
+ forceKillTimer = setTimeout(() => killProcessGroup("SIGKILL"), args.shutdownGraceMs ?? 5e3);
1462
1454
  forceKillTimer.unref();
1463
1455
  }
1464
1456
  };
@@ -1705,7 +1697,7 @@ async function queryNvidiaGpus(env) {
1705
1697
  async function diskCheck(name, path) {
1706
1698
  const resolvedPath = resolve7(path);
1707
1699
  try {
1708
- await access(resolvedPath, constants.R_OK);
1700
+ await access(resolvedPath, constants2.R_OK);
1709
1701
  const fs = await statfs(resolvedPath);
1710
1702
  const freeBytes = Number(fs.bavail) * Number(fs.bsize);
1711
1703
  return {
@@ -2527,37 +2519,50 @@ var behaviorSpecExampleSchema = z.object({
2527
2519
  input: z.string().min(1),
2528
2520
  output: z.string().min(1)
2529
2521
  }).strict();
2530
- var specSnapshotSchema = z.object({
2522
+ var behaviorSpecSchema = z.object({
2531
2523
  name: z.string().min(1),
2532
2524
  description: z.string().default(""),
2533
2525
  system_prompt: z.string().default(""),
2534
2526
  guidelines: z.array(z.string()).default([]),
2535
2527
  examples: z.array(behaviorSpecExampleSchema).default([]),
2536
- constraints: z.array(z.string()).default([]),
2537
- base_model: z.string().transform((value) => canonicalizeTrainingModel(value))
2528
+ constraints: z.array(z.string()).default([])
2538
2529
  }).strict();
2530
+ var specSnapshotSchema = behaviorSpecSchema.extend({
2531
+ base_model: z.string().transform((value) => canonicalizeTrainingModel(value))
2532
+ });
2539
2533
  var foundationHyperparametersSchema = z.object({
2540
2534
  vocab_size: z.number().int().min(64).max(65536).default(256),
2541
- max_chars: z.number().int().min(1e3).max(2e9).default(2e4),
2535
+ max_chars: z.number().int().min(1e3).max(1e12).default(2e4),
2542
2536
  depth: z.number().int().min(1).max(64).default(2),
2543
2537
  pretrain_steps: z.number().int().min(1).max(1e7).default(2),
2544
2538
  finetune_steps: z.number().int().min(1).max(1e7).default(2),
2545
2539
  rl_steps: z.number().int().min(0).max(1e7).default(0),
2546
2540
  batch_size: z.number().int().min(1).max(1024).default(2),
2547
2541
  sequence_length: z.number().int().min(16).max(8192).default(64),
2548
- nproc_per_node: z.number().int().min(1).max(16).default(1)
2542
+ nproc_per_node: z.number().int().min(1).max(16).default(1),
2543
+ corpus_path: z.string().min(1).optional(),
2544
+ tokenizer_max_chars: z.number().int().min(1e3).max(1e12).optional(),
2545
+ validation_path: z.string().min(1).optional(),
2546
+ checkpoint_backup_dir: z.string().min(1).optional(),
2547
+ seed: z.number().int().min(0).max(2147483647).optional(),
2548
+ learning_rate: z.number().positive().max(1).optional(),
2549
+ weight_decay: z.number().min(0).max(10).optional(),
2550
+ warmup_steps: z.number().int().min(0).max(1e7).optional(),
2551
+ min_lr_ratio: z.number().min(0).max(1).optional(),
2552
+ gradient_accumulation_steps: z.number().int().min(1).max(1024).optional(),
2553
+ gradient_clip: z.number().positive().max(1e3).optional(),
2554
+ bf16: z.boolean().optional(),
2555
+ checkpoint_interval_steps: z.number().int().min(1).max(1e7).optional(),
2556
+ checkpoint_interval_seconds: z.number().int().min(1).max(86400).optional(),
2557
+ keep_checkpoints: z.number().int().min(1).max(100).optional(),
2558
+ log_interval_steps: z.number().int().min(1).max(1e6).optional()
2549
2559
  }).strict();
2550
- var localFoundationSpecFileSchema = z.object({
2560
+ var localFoundationSpecFileSchema = behaviorSpecSchema.extend({
2551
2561
  engine: z.literal("foundation"),
2552
2562
  id: z.string().uuid().optional(),
2553
- name: z.string().min(1),
2554
- description: z.string().default(""),
2555
- system_prompt: z.string().default(""),
2556
- guidelines: z.array(z.string()).default([]),
2557
2563
  examples: z.array(behaviorSpecExampleSchema).min(2),
2558
- constraints: z.array(z.string()).default([]),
2559
2564
  foundation: foundationHyperparametersSchema
2560
- }).strict();
2565
+ });
2561
2566
  var datasetPrebuiltSchema = z.object({
2562
2567
  training: z.string().min(1),
2563
2568
  validation: z.string().min(1).optional(),
@@ -2799,7 +2804,7 @@ function isFoundationSpecFile(spec) {
2799
2804
  // src/local-runtime/local-project.ts
2800
2805
  import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
2801
2806
  import { readFile as readFile4, stat as stat3, writeFile as writeFile4 } from "fs/promises";
2802
- import { dirname as dirname8, isAbsolute as isAbsolute3, resolve as resolve9 } from "path";
2807
+ import { dirname as dirname8, isAbsolute as isAbsolute3, relative as relative2, resolve as resolve9, sep as sep3 } from "path";
2803
2808
  import { homedir as homedir4 } from "os";
2804
2809
  import { mkdir as mkdir5 } from "fs/promises";
2805
2810
  var DEFAULT_LOCAL_SPEC_PATH = "tunedtensor.json";
@@ -2811,6 +2816,13 @@ function resolveLocalReference(value, baseDirectory) {
2811
2816
  if (value.startsWith("~/")) return resolve9(homedir4(), value.slice(2));
2812
2817
  return resolve9(baseDirectory, value);
2813
2818
  }
2819
+ function localPathsOverlap(left, right) {
2820
+ const contains = (parent, candidate) => {
2821
+ const relation = relative2(resolve9(parent), resolve9(candidate));
2822
+ return relation === "" || !isAbsolute3(relation) && relation !== ".." && !relation.startsWith(`..${sep3}`);
2823
+ };
2824
+ return contains(left, right) || contains(right, left);
2825
+ }
2814
2826
  function resolveLocalRunInputPaths(raw, inputPath) {
2815
2827
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return raw;
2816
2828
  const baseDirectory = dirname8(resolve9(inputPath));
@@ -2822,6 +2834,13 @@ function resolveLocalRunInputPaths(raw, inputPath) {
2822
2834
  if (fields[key] !== void 0) fields[key] = resolveLocalReference(fields[key], baseDirectory);
2823
2835
  }
2824
2836
  }
2837
+ const foundation = value.foundation;
2838
+ if (foundation && typeof foundation === "object" && !Array.isArray(foundation)) {
2839
+ const fields = foundation;
2840
+ for (const key of ["corpus_path", "validation_path", "checkpoint_backup_dir"]) {
2841
+ if (fields[key] !== void 0) fields[key] = resolveLocalReference(fields[key], baseDirectory);
2842
+ }
2843
+ }
2825
2844
  return value;
2826
2845
  }
2827
2846
  async function exists2(path) {
@@ -3060,14 +3079,16 @@ async function normalizeChatJsonlForRelocation(path) {
3060
3079
  }
3061
3080
  function buildSystemMessage(spec) {
3062
3081
  const parts = [];
3082
+ const guidelines = spec.guidelines.map((value) => value.trim()).filter(Boolean);
3083
+ const constraints = spec.constraints.map((value) => value.trim()).filter(Boolean);
3063
3084
  if (spec.system_prompt.trim()) parts.push(spec.system_prompt.trim());
3064
- if (spec.guidelines.length > 0) {
3085
+ if (guidelines.length > 0) {
3065
3086
  parts.push(`Guidelines:
3066
- ${spec.guidelines.map((guideline) => `- ${guideline}`).join("\n")}`);
3087
+ ${guidelines.map((guideline) => `- ${guideline}`).join("\n")}`);
3067
3088
  }
3068
- if (spec.constraints.length > 0) {
3089
+ if (constraints.length > 0) {
3069
3090
  parts.push(`Constraints:
3070
- ${spec.constraints.map((constraint) => `- ${constraint}`).join("\n")}`);
3091
+ ${constraints.map((constraint) => `- ${constraint}`).join("\n")}`);
3071
3092
  }
3072
3093
  return parts.join("\n\n") || "Follow the demonstrated behavior.";
3073
3094
  }
@@ -3138,7 +3159,7 @@ async function evaluationSuiteFromChatJsonl(path, configuredSystem) {
3138
3159
  import { createHash as createHash4 } from "crypto";
3139
3160
  import { createReadStream as createReadStream2 } from "fs";
3140
3161
  import { lstat as lstat4, mkdir as mkdir6, readFile as readFile6, readdir as readdir4, realpath as realpath2, stat as stat4 } from "fs/promises";
3141
- import { isAbsolute as isAbsolute4, join as join8, relative as relative2, resolve as resolve10, sep as sep3 } from "path";
3162
+ import { isAbsolute as isAbsolute4, join as join8, relative as relative3, resolve as resolve10, sep as sep4 } from "path";
3142
3163
  var NEMOTRON_BF16_FILE_DIGESTS = {
3143
3164
  ".gitattributes": { algorithm: "sha1", digest: "fe3f694ce14b577c9833fca6e83c5e26a7119127" },
3144
3165
  "LICENSE": { algorithm: "sha1", digest: "4d76cc87c9edb280753e3bee3b499ae461113835" },
@@ -3204,8 +3225,8 @@ async function certifiedFileDigest(path, expected) {
3204
3225
  return digest.digest("hex");
3205
3226
  }
3206
3227
  function isStrictPhysicalDescendant(parent, child) {
3207
- const candidate = relative2(parent, child);
3208
- return candidate !== "" && candidate !== ".." && !candidate.startsWith(`..${sep3}`) && !isAbsolute4(candidate);
3228
+ const candidate = relative3(parent, child);
3229
+ return candidate !== "" && candidate !== ".." && !candidate.startsWith(`..${sep4}`) && !isAbsolute4(candidate);
3209
3230
  }
3210
3231
  async function verifySnapshotCacheLocation(path, modelId, revision, modelCache) {
3211
3232
  const repository = `models--${modelId.replaceAll("/", "--")}`;
@@ -3297,7 +3318,7 @@ async function verifyCertifiedLocalSnapshot(path, modelId, snapshotFiles, modelC
3297
3318
  }
3298
3319
  physicalFiles.set(file, target);
3299
3320
  }
3300
- const actualFiles = new Set(snapshotFiles.map((file) => relative2(path, file).split("\\").join("/")));
3321
+ const actualFiles = new Set(snapshotFiles.map((file) => relative3(path, file).split("\\").join("/")));
3301
3322
  const expectedFiles = new Set(Object.keys(digests));
3302
3323
  const unexpected = [...actualFiles].filter((name) => !expectedFiles.has(name)).sort();
3303
3324
  if (unexpected.length > 0) {
@@ -3578,12 +3599,12 @@ function evaluateGeneralRegressionGate(comparison, policy) {
3578
3599
  }
3579
3600
 
3580
3601
  // src/local-runtime/orchestrator.ts
3581
- import { lstat as lstat5, mkdir as mkdir9, open as open2, readFile as readFile9, readdir as readdir5, rename as rename3, rm as rm3, stat as stat5 } from "fs/promises";
3602
+ import { lstat as lstat5, mkdir as mkdir9, open as open3, readFile as readFile9, readdir as readdir5, rename as rename3, rm as rm3, stat as stat5 } from "fs/promises";
3582
3603
  import { createReadStream as createReadStream3 } from "fs";
3583
3604
  import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
3584
3605
  import { performance } from "perf_hooks";
3585
3606
  import { homedir as homedir5 } from "os";
3586
- import { basename, dirname as dirname10, join as join11, relative as relative3, resolve as resolve13 } from "path";
3607
+ import { basename, dirname as dirname10, join as join11, relative as relative4, resolve as resolve13 } from "path";
3587
3608
 
3588
3609
  // src/local-runtime/evaluation.ts
3589
3610
  import { createHash as createHash5 } from "crypto";
@@ -4678,7 +4699,7 @@ async function fingerprintLocalBaseModel(uri, expectedModelId, modelCache) {
4678
4699
  }
4679
4700
  if (!childMetadata.isFile()) continue;
4680
4701
  files.push({
4681
- path: relative3(root, child).split("\\").join("/"),
4702
+ path: relative4(root, child).split("\\").join("/"),
4682
4703
  size_bytes: childMetadata.size,
4683
4704
  sha256: await hashFile(child)
4684
4705
  });
@@ -4761,7 +4782,7 @@ async function cleanupStageArtifacts(artifacts, stage) {
4761
4782
  await removeReport();
4762
4783
  }
4763
4784
  function manifestRelativePath(artifacts, path) {
4764
- return relative3(artifacts.runDir, path).split("\\").join("/");
4785
+ return relative4(artifacts.runDir, path).split("\\").join("/");
4765
4786
  }
4766
4787
  async function verifyReusableArtifacts(artifacts, paths, options = {}) {
4767
4788
  if (!await pathExists2(artifacts.artifactManifestJson)) return false;
@@ -4782,7 +4803,7 @@ async function acquireWorkflowLock(lockPath, description) {
4782
4803
  const token = randomUUID4();
4783
4804
  for (let attempt = 0; attempt < 3; attempt += 1) {
4784
4805
  try {
4785
- const handle = await open2(lockPath, "wx", 384);
4806
+ const handle = await open3(lockPath, "wx", 384);
4786
4807
  try {
4787
4808
  await handle.writeFile(`${JSON.stringify({ token, pid: process.pid, created_at: (/* @__PURE__ */ new Date()).toISOString() })}
4788
4809
  `);
@@ -5755,6 +5776,7 @@ export {
5755
5776
  datasetFormatSchema,
5756
5777
  baseModelRevisionSchema,
5757
5778
  behaviorSpecExampleSchema,
5779
+ behaviorSpecSchema,
5758
5780
  specSnapshotSchema,
5759
5781
  foundationHyperparametersSchema,
5760
5782
  localFoundationSpecFileSchema,
@@ -5773,6 +5795,7 @@ export {
5773
5795
  localRunnerConfigSchema,
5774
5796
  isFoundationSpecFile,
5775
5797
  DEFAULT_LOCAL_SPEC_PATH,
5798
+ localPathsOverlap,
5776
5799
  resolveLocalRunInputPaths,
5777
5800
  initLocalSpecFile,
5778
5801
  initLocalRunnerConfigFile,
@@ -5805,4 +5828,4 @@ export {
5805
5828
  runLocalPipeline,
5806
5829
  runLocalFineTune
5807
5830
  };
5808
- //# sourceMappingURL=chunk-AVMRHDFY.js.map
5831
+ //# sourceMappingURL=chunk-AKJ5WZ2S.js.map