trainfabric 0.1.12 → 0.1.14

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
@@ -28,7 +28,7 @@ Secrets are stored in macOS Keychain when available. Other platforms use an encr
28
28
  trainfabric projects:list
29
29
  trainfabric datasets:upload ./train.jsonl --project <projectId>
30
30
  trainfabric runs:quote --summary --project <projectId> --dataset <datasetId> --model llama-3-8b
31
- trainfabric runs:create --yes --project <projectId> --dataset <datasetId> --model llama-3-8b
31
+ trainfabric runs:create --yes --quote <quoteId> --project <projectId> --dataset <datasetId> --model llama-3-8b
32
32
  trainfabric runs:watch <runId> --timeout 30m
33
33
  trainfabric runs:cost-breakdown <runId> --summary
34
34
  ```
package/dist/index.cjs CHANGED
@@ -7975,12 +7975,6 @@ function validateChatJsonl(text) {
7975
7975
  }
7976
7976
  tokenEstimate += estimateTokens(result.data.messages.map((message) => `${message.role}:${message.content}`).join("\n"));
7977
7977
  }
7978
- if (tokenEstimate < 100) {
7979
- warnings.push({
7980
- code: "small_dataset",
7981
- message: "Dataset is valid but very small for a meaningful training run."
7982
- });
7983
- }
7984
7978
  const invalidWarnings = /* @__PURE__ */ new Set([
7985
7979
  "empty_file",
7986
7980
  "invalid_jsonl",
@@ -7988,8 +7982,15 @@ function validateChatJsonl(text) {
7988
7982
  "missing_assistant",
7989
7983
  "empty_assistant"
7990
7984
  ]);
7985
+ const isValid2 = !warnings.some((warning) => invalidWarnings.has(warning.code));
7986
+ if (tokenEstimate < 100) {
7987
+ warnings.push({
7988
+ code: "small_dataset",
7989
+ message: isValid2 ? "Dataset is valid but very small for a meaningful training run." : "Dataset is very small; fix schema errors before using it for a training run."
7990
+ });
7991
+ }
7991
7992
  return {
7992
- valid: !warnings.some((warning) => invalidWarnings.has(warning.code)),
7993
+ valid: isValid2,
7993
7994
  format: "chat_jsonl",
7994
7995
  rowCount,
7995
7996
  duplicateCount,
@@ -8921,7 +8922,7 @@ function buildComputeSpec(options) {
8921
8922
 
8922
8923
  // src/index.ts
8923
8924
  var DEFAULT_TRAINFABRIC_API_URL2 = "https://api.trainfabric.com";
8924
- var CLI_VERSION = "0.1.12";
8925
+ var CLI_VERSION = "0.1.14";
8925
8926
  var CONFIG_DIR = import_node_path3.default.join(import_node_os2.default.homedir(), ".trainfabric");
8926
8927
  var CONFIG_PATH = import_node_path3.default.join(CONFIG_DIR, "config.json");
8927
8928
  var FALLBACK_SECRET_PATH = import_node_path3.default.join(CONFIG_DIR, "session.enc");
@@ -9215,6 +9216,7 @@ function printQuoteSummary(bundle) {
9215
9216
  console.log(` data path: ${quote.dataPath ?? "auto"}`);
9216
9217
  console.log(` GPUs: ${gpuCount}`);
9217
9218
  console.log(` pool: ${quote.selectedPoolId}`);
9219
+ console.log(` launch with: --quote ${quote.id}`);
9218
9220
  }
9219
9221
  console.log("");
9220
9222
  console.log("Launch only after the customer accepts the quote. Streaming/data-plane cost is included in the estimate when the selected data path is stream, and realized usage may differ.");
@@ -9258,15 +9260,23 @@ function visibleConfig(config) {
9258
9260
  refreshToken: config.refreshToken ? "<stored>" : void 0
9259
9261
  };
9260
9262
  }
9261
- function buildRunInput(options) {
9263
+ function requireProjectId(options, config) {
9264
+ const projectId = options.project ?? config.projectId;
9265
+ if (!projectId) {
9266
+ throw new Error("Project is required. Pass --project <projectId> or run `trainfabric config:set-project <projectId>`.");
9267
+ }
9268
+ return projectId;
9269
+ }
9270
+ function buildRunInput(options, config = loadConfig()) {
9262
9271
  const sourceOptions = buildSourceOptions(options);
9263
9272
  return {
9264
- projectId: options.project,
9273
+ projectId: requireProjectId(options, config),
9265
9274
  task: "sft",
9266
9275
  method: "lora",
9267
9276
  baseModel: normalizeBaseModel(options.model),
9268
9277
  datasetId: options.dataset,
9269
9278
  evalDatasetId: options.eval,
9279
+ pricingQuoteId: options.quote,
9270
9280
  ...sourceOptions,
9271
9281
  mode: options.mode,
9272
9282
  compute: buildComputeSpec(options),
@@ -9297,7 +9307,16 @@ function assertQuoteOptions(bundle, options) {
9297
9307
  )}). Omit --accelerator or increase --min-memory.`
9298
9308
  );
9299
9309
  }
9300
- async function watchRun(runId, json = false, timeoutMs) {
9310
+ function filterQuoteBundleForRequestedMode(bundle, options) {
9311
+ if (!options.mode) {
9312
+ return bundle;
9313
+ }
9314
+ return {
9315
+ ...bundle,
9316
+ quotes: bundle.quotes.filter((item) => item.mode === options.mode || item.quote.mode === options.mode)
9317
+ };
9318
+ }
9319
+ async function watchRun(runId, json = false, timeoutMs, pollMs) {
9301
9320
  const handle = await createClient(loadConfig()).runs.watch(runId);
9302
9321
  if (json) {
9303
9322
  const emit = (type, payload) => printJson({ type, payload });
@@ -9334,7 +9353,7 @@ async function watchRun(runId, json = false, timeoutMs) {
9334
9353
  console.log(`[failed] ${payload.reason ?? "Run failed."}`);
9335
9354
  });
9336
9355
  }
9337
- const detail = await handle.wait({ timeoutMs });
9356
+ const detail = await handle.wait({ timeoutMs, pollMs });
9338
9357
  printJson(detail);
9339
9358
  }
9340
9359
  function parseDurationMs(value) {
@@ -9453,12 +9472,14 @@ program2.command("projects:create").requiredOption("--name <name>").option("--or
9453
9472
  printJson(await createClient(loadConfig()).projects.create({ name: options.name, organizationId: options.org }));
9454
9473
  });
9455
9474
  program2.command("datasets:validate").argument("<file>").description("Validate a local dataset file").action(async (file) => {
9456
- printJson(
9457
- await createClient(loadConfig()).datasets.validate({
9458
- path: file,
9459
- format: "chat_jsonl"
9460
- })
9461
- );
9475
+ const validation = await createClient(loadConfig()).datasets.validate({
9476
+ path: file,
9477
+ format: "chat_jsonl"
9478
+ });
9479
+ printJson(validation);
9480
+ if (!validation.valid) {
9481
+ process.exitCode = 1;
9482
+ }
9462
9483
  });
9463
9484
  program2.command("datasets:list").option("--project <projectId>").description("List datasets").action(async (options) => {
9464
9485
  printJson(await createClient(loadConfig()).datasets.list(options.project));
@@ -9476,36 +9497,43 @@ program2.command("datasets:upload").argument("<file>").option("--project <projec
9476
9497
  });
9477
9498
  printJson(dataset);
9478
9499
  });
9479
- program2.command("runs:create").requiredOption("--project <projectId>").requiredOption("--dataset <datasetId>").requiredOption("--model <baseModel>").option("--eval <evalDatasetId>").option("--epochs <epochs>", "number of epochs", "3").option("--lr <lr>", "learning rate", "0.0002").option("--gpus <gpuCount>", "gpu count", "1").option("--nodes <nodeCount>", "node count", "1").option("--accelerator <acceleratorClass>", "optional hard accelerator constraint (for example: a10g, a100, h100)").option("--min-memory <gigabytes>", "minimum GPU memory in GB").option("--precision <precision>", "fp16_bf16, fp8, or fp32").option("--interconnect <interconnect>", "pcie or nvlink").option("--mode <mode>", "efficient, balanced, or power", "balanced").option("--repo <path>", "local repo path for runtime autodetect").option("--git <url>", "git repo URL for runtime metadata").option("--branch <branch>", "git branch for runtime metadata").option("--yes", "confirm that you reviewed pricing with runs:quote --summary and accept fluctuating realized usage").description("Create a training run").action(async (options) => {
9500
+ program2.command("runs:create").option("--project <projectId>").requiredOption("--dataset <datasetId>").requiredOption("--model <baseModel>").option("--eval <evalDatasetId>").option("--epochs <epochs>", "number of epochs", "3").option("--lr <lr>", "learning rate", "0.0002").option("--gpus <gpuCount>", "gpu count", "1").option("--nodes <nodeCount>", "node count", "1").option("--accelerator <acceleratorClass>", "optional hard accelerator constraint (for example: a10g, a100, h100)").option("--min-memory <gigabytes>", "minimum GPU memory in GB").option("--precision <precision>", "fp16_bf16, fp8, or fp32").option("--interconnect <interconnect>", "pcie or nvlink").option("--mode <mode>", "efficient, balanced, or power", "balanced").option("--quote <pricingQuoteId>", "launch the exact accepted quote id from runs:quote").option("--repo <path>", "local repo path for runtime autodetect").option("--git <url>", "git repo URL for runtime metadata").option("--branch <branch>", "git branch for runtime metadata").option("--yes", "confirm that you reviewed pricing with runs:quote --summary and accept fluctuating realized usage").description("Create a training run").action(async (options) => {
9480
9501
  if (!options.yes) {
9481
9502
  throw new Error("Refusing to launch without explicit cost acceptance. Run `trainfabric runs:quote --summary ...` first, then rerun `runs:create` with --yes.");
9482
9503
  }
9483
- const client = createClient(loadConfig());
9484
- const run = await client.runs.create(buildRunInput(options));
9504
+ const config = loadConfig();
9505
+ const client = createClient(config);
9506
+ const run = await client.runs.create(buildRunInput(options, config));
9485
9507
  printJson(run.snapshot);
9486
9508
  });
9487
- program2.command("runtime:detect").requiredOption("--project <projectId>").option("--repo <path>").option("--git <url>").option("--branch <branch>").description("Detect a supported runtime from a local repo snapshot or git metadata").action(async (options) => {
9509
+ program2.command("runtime:detect").option("--project <projectId>").option("--repo <path>").option("--git <url>").option("--branch <branch>").description("Detect a supported runtime from a local repo snapshot or git metadata").action(async (options) => {
9488
9510
  const sourceOptions = buildSourceOptions(options);
9489
9511
  if (!sourceOptions.source) {
9490
9512
  throw new Error("Provide --repo or --git.");
9491
9513
  }
9514
+ const config = loadConfig();
9492
9515
  printJson(
9493
- await createClient(loadConfig()).runtime.detect({
9494
- projectId: options.project,
9516
+ await createClient(config).runtime.detect({
9517
+ projectId: requireProjectId(options, config),
9495
9518
  source: sourceOptions.source
9496
9519
  })
9497
9520
  );
9498
9521
  });
9499
- program2.command("runtime:build").requiredOption("--project <projectId>").requiredOption("--detection <detectionId>").description("Build or reuse a cached runtime image from a runtime detection").action(async (options) => {
9522
+ program2.command("runtime:build").option("--project <projectId>").requiredOption("--detection <detectionId>").description("Build or reuse a cached runtime image from a runtime detection").action(async (options) => {
9523
+ const config = loadConfig();
9500
9524
  printJson(
9501
- await createClient(loadConfig()).runtime.build({
9502
- projectId: options.project,
9525
+ await createClient(config).runtime.build({
9526
+ projectId: requireProjectId(options, config),
9503
9527
  detectionId: options.detection
9504
9528
  })
9505
9529
  );
9506
9530
  });
9507
- program2.command("runs:quote").requiredOption("--project <projectId>").requiredOption("--dataset <datasetId>").requiredOption("--model <baseModel>").option("--eval <evalDatasetId>").option("--gpus <gpuCount>", "gpu count", "1").option("--nodes <nodeCount>", "node count", "1").option("--accelerator <acceleratorClass>", "optional hard accelerator constraint (for example: a10g, a100, h100)").option("--min-memory <gigabytes>", "minimum GPU memory in GB").option("--precision <precision>", "fp16_bf16, fp8, or fp32").option("--interconnect <interconnect>", "pcie or nvlink").option("--mode <mode>", "efficient, balanced, or power").option("--repo <path>").option("--git <url>").option("--branch <branch>").option("--summary", "print a human-readable price summary instead of raw JSON").description("Preview Efficient, Balanced, and Power launch options").action(async (options) => {
9508
- const bundle = await createClient(loadConfig()).runs.quote(buildRunInput(options));
9531
+ program2.command("runs:quote").option("--project <projectId>").requiredOption("--dataset <datasetId>").requiredOption("--model <baseModel>").option("--eval <evalDatasetId>").option("--gpus <gpuCount>", "gpu count", "1").option("--nodes <nodeCount>", "node count", "1").option("--accelerator <acceleratorClass>", "optional hard accelerator constraint (for example: a10g, a100, h100)").option("--min-memory <gigabytes>", "minimum GPU memory in GB").option("--precision <precision>", "fp16_bf16, fp8, or fp32").option("--interconnect <interconnect>", "pcie or nvlink").option("--mode <mode>", "efficient, balanced, or power").option("--repo <path>").option("--git <url>").option("--branch <branch>").option("--summary", "print a human-readable price summary instead of raw JSON").description("Preview Efficient, Balanced, and Power launch options").action(async (options) => {
9532
+ const config = loadConfig();
9533
+ const bundle = filterQuoteBundleForRequestedMode(
9534
+ await createClient(config).runs.quote(buildRunInput(options, config)),
9535
+ options
9536
+ );
9509
9537
  assertQuoteOptions(bundle, options);
9510
9538
  if (options.summary) {
9511
9539
  printQuoteSummary(bundle);
@@ -9530,8 +9558,8 @@ program2.command("runs:wait").argument("<runId>").option("--timeout <duration>",
9530
9558
  })
9531
9559
  );
9532
9560
  });
9533
- program2.command("runs:watch").argument("<runId>").option("--json", "emit event output as JSON").option("--timeout <duration>", "maximum watch duration, for example 30s, 10m, or 1h").description("Stream a run until it completes").action(async (runId, options) => {
9534
- await watchRun(String(runId), Boolean(options.json), parseDurationMs(options.timeout));
9561
+ program2.command("runs:watch").argument("<runId>").option("--json", "emit event output as JSON").option("--timeout <duration>", "maximum watch duration, for example 30s, 10m, or 1h").option("--poll <duration>", "poll interval, for example 1s or 500ms").description("Stream a run until it completes").action(async (runId, options) => {
9562
+ await watchRun(String(runId), Boolean(options.json), parseDurationMs(options.timeout), parsePositiveMs(options.poll, 1e3));
9535
9563
  });
9536
9564
  program2.command("runs:usage").argument("<runId>").option("--summary", "print a human-readable cost summary instead of raw JSON").description("Fetch run usage summary").action(async (runId, options) => {
9537
9565
  const usage = await createClient(loadConfig()).runs.usage(String(runId));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trainfabric",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Trainfabric CLI for launching GPU training jobs on the hosted Trainfabric backend.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",