@tuned-tensor/cli 0.13.1 → 0.14.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
@@ -25,7 +25,7 @@ import {
25
25
  runLoggedProcess,
26
26
  warningsFromSnapshot,
27
27
  withFoundationPythonEnvironment
28
- } from "./chunk-CAEBS5HH.js";
28
+ } from "./chunk-VRDDO2FP.js";
29
29
 
30
30
  // src/cli.ts
31
31
  import { spawn as spawn2 } from "child_process";
@@ -6621,7 +6621,7 @@ async function runCli(version, runtime = {}) {
6621
6621
  }
6622
6622
 
6623
6623
  // src/index.ts
6624
- runCli("0.13.1").catch((err) => {
6624
+ runCli("0.14.0").catch((err) => {
6625
6625
  reportError(err);
6626
6626
  process.exit(1);
6627
6627
  });
@@ -58,6 +58,7 @@ import {
58
58
  parseLocalRunInput,
59
59
  parseLocalRunnerConfig,
60
60
  prefetchBaseModel,
61
+ pythonEnvironmentPath,
61
62
  reportInBackground,
62
63
  resolveLocalRunInputPaths,
63
64
  resolveModelLoader,
@@ -80,13 +81,13 @@ import {
80
81
  withHuggingFaceCacheEnvironment,
81
82
  withOfflineHuggingFaceCacheEnvironment,
82
83
  writePrivateJsonAtomic
83
- } from "./chunk-CAEBS5HH.js";
84
+ } from "./chunk-VRDDO2FP.js";
84
85
 
85
86
  // src/local-runtime/index.ts
86
- import { readFileSync, realpathSync } from "fs";
87
- import { createHash } from "crypto";
87
+ import { readFileSync as readFileSync2, realpathSync } from "fs";
88
+ import { createHash as createHash2 } from "crypto";
88
89
  import { readFile as readFile2, stat } from "fs/promises";
89
- import { basename, dirname, join as join3, resolve as resolve3 } from "path";
90
+ import { basename, dirname, join as join4, resolve as resolve3 } from "path";
90
91
  import { cwd } from "process";
91
92
  import { fileURLToPath as fileURLToPath2 } from "url";
92
93
 
@@ -505,6 +506,39 @@ async function runDoctor(config, request, foundationSpec) {
505
506
  import { spawn as spawn2 } from "child_process";
506
507
  import { resolve as resolve2 } from "path";
507
508
  import { fileURLToPath } from "url";
509
+
510
+ // src/local-runtime/serving-runtime.ts
511
+ import { createHash } from "crypto";
512
+ import { readFileSync } from "fs";
513
+ import { join as join2 } from "path";
514
+ var root = localRuntimePackageRoot(import.meta.url);
515
+ var project = join2(root, "training/serving");
516
+ var hash = createHash("sha256");
517
+ for (const name of ["pyproject.toml", "uv.lock"]) hash.update(readFileSync(join2(project, name)));
518
+ var SERVING_PYTHON_ENVIRONMENT = pythonEnvironmentPath("uv-serving", hash.digest("hex").slice(0, 20));
519
+ function buildServingPythonCommand() {
520
+ const commandArgs = [
521
+ "run",
522
+ "--frozen",
523
+ "--quiet",
524
+ "--project",
525
+ project,
526
+ "python",
527
+ join2(root, "training/adapter/src/serve.py")
528
+ ];
529
+ return { command: "uv", commandArgs, displayCommand: ["uv", ...commandArgs] };
530
+ }
531
+ function withServingPythonEnvironment(env) {
532
+ return {
533
+ ...env,
534
+ UV_PROJECT_ENVIRONMENT: SERVING_PYTHON_ENVIRONMENT,
535
+ VLLM_NO_USAGE_STATS: "1",
536
+ DO_NOT_TRACK: "1",
537
+ VLLM_ALLOW_RUNTIME_LORA_UPDATING: "0"
538
+ };
539
+ }
540
+
541
+ // src/local-runtime/model-server.ts
508
542
  function localArtifactPath(uri) {
509
543
  if (uri.startsWith("file://")) return fileURLToPath(uri);
510
544
  if (/^[a-z][a-z0-9+.-]*:/i.test(uri)) {
@@ -526,6 +560,10 @@ function httpHost(host) {
526
560
  }
527
561
  function buildModelServerLaunch(args) {
528
562
  const options = args.options ?? {};
563
+ const device = options.device ?? args.config.evaluation.inference.device;
564
+ if (device !== "cuda") throw new Error("The pinned vLLM serving runtime requires CUDA; CPU serving is not supported.");
565
+ const contextLength = boundedNumber("contextLength", options.contextLength ?? 16384, 512, 131072, true);
566
+ const gpuMemoryUtilization = boundedNumber("gpuMemoryUtilization", options.gpuMemoryUtilization ?? 0.8, 0.05, 0.95);
529
567
  const host = options.host ?? "127.0.0.1";
530
568
  const remote = !isLoopbackHost(host);
531
569
  if (remote && !options.allowRemote) {
@@ -561,8 +599,8 @@ function buildModelServerLaunch(args) {
561
599
  options.baseModelRevision
562
600
  );
563
601
  const baseModelRevision = localBaseModelPath ? defaultBaseModelRevision(args.baseModel) : requestedBaseModelRevision;
564
- const entrypoint = buildBundledPythonCommand("serve.py");
565
- const env = withBundledPythonEnvironment(
602
+ const entrypoint = buildServingPythonCommand();
603
+ const env = withServingPythonEnvironment(
566
604
  withOfflineHuggingFaceCacheEnvironment({
567
605
  ...minimalMachineLearningEnvironment(process.env),
568
606
  ...artifactPath ? { TT_MODEL_ARTIFACT: artifactPath } : {},
@@ -570,10 +608,12 @@ function buildModelServerLaunch(args) {
570
608
  ...localBaseModelPath ? { TT_MODEL_SOURCE: localBaseModelPath } : {},
571
609
  ...baseModelRevision ? { TT_BASE_MODEL_REVISION: baseModelRevision } : {},
572
610
  TT_MODEL_NAME: args.modelName,
611
+ TT_CONTEXT_LENGTH: String(contextLength),
612
+ TT_GPU_MEMORY_UTILIZATION: String(gpuMemoryUtilization),
573
613
  TT_MODEL_LOADER: resolveModelLoader(args.baseModel),
574
614
  TT_HOST: host,
575
615
  TT_PORT: String(port),
576
- TT_DEVICE: options.device ?? args.config.evaluation.inference.device,
616
+ TT_DEVICE: device,
577
617
  TT_MAX_TOKENS: String(maxTokens),
578
618
  TT_TEMPERATURE: String(temperature),
579
619
  TT_TOP_P: String(topP),
@@ -618,6 +658,7 @@ async function verifyLocalModelServerLaunch(launch) {
618
658
  }
619
659
  }
620
660
  async function serveLocalModel(launch) {
661
+ if (process.platform !== "linux") throw new Error("Local vLLM serving requires Linux and an NVIDIA CUDA GPU.");
621
662
  await verifyLocalModelServerLaunch(launch);
622
663
  await new Promise((resolveServer, reject) => {
623
664
  const child = spawn2(launch.command, launch.commandArgs, {
@@ -673,12 +714,12 @@ async function serveLocalModel(launch) {
673
714
  // src/local-runtime/active-model.ts
674
715
  import { randomUUID } from "crypto";
675
716
  import { appendFile, chmod, readFile, rename, writeFile as writeFile2 } from "fs/promises";
676
- import { join as join2 } from "path";
717
+ import { join as join3 } from "path";
677
718
  function pointerPath(store) {
678
- return join2(store.root, "active-model.json");
719
+ return join3(store.root, "active-model.json");
679
720
  }
680
721
  function historyPath(store) {
681
- return join2(store.root, "activation-history.jsonl");
722
+ return join3(store.root, "activation-history.jsonl");
682
723
  }
683
724
  async function readPointer(store) {
684
725
  if (!await hardenExistingLocalStore(store.root)) return null;
@@ -718,7 +759,7 @@ async function assertModelCanActivate(store, modelId) {
718
759
  throw new Error(`Model ${model.id} belongs to run ${run.id}, which is not completed.`);
719
760
  }
720
761
  const report = runReportSchema.parse(
721
- JSON.parse(await readFile(join2(model.artifact_dir, "run-report.json"), "utf8"))
762
+ JSON.parse(await readFile(join3(model.artifact_dir, "run-report.json"), "utf8"))
722
763
  );
723
764
  if (!report.general_regression) {
724
765
  throw new Error(`Model ${model.id} has no general regression evaluation.`);
@@ -775,7 +816,7 @@ async function rollbackActiveModel(store) {
775
816
  function packageVersion() {
776
817
  try {
777
818
  const packageJson = JSON.parse(
778
- readFileSync(resolve3(localRuntimePackageRoot(import.meta.url), "package.json"), "utf8")
819
+ readFileSync2(resolve3(localRuntimePackageRoot(import.meta.url), "package.json"), "utf8")
779
820
  );
780
821
  return typeof packageJson.version === "string" ? packageJson.version : "unknown";
781
822
  } catch {
@@ -830,16 +871,19 @@ var MODEL_SERVE_OPTIONS = [
830
871
  CONFIG_OPTION,
831
872
  { name: "--host", value: "host", description: "Bind host (localhost by default)" },
832
873
  { name: "--port", value: "port", description: "Bind port" },
833
- { name: "--device", value: "device", description: "cuda or cpu" },
874
+ { name: "--device", value: "device", description: "cuda (pinned vLLM runtime)" },
834
875
  { name: "--max-tokens", value: "count", description: "Default response token limit" },
835
876
  { name: "--temperature", value: "number", description: "Default sampling temperature" },
836
877
  { name: "--top-p", value: "number", description: "Default nucleus sampling threshold" },
837
- { name: "--max-concurrent-requests", value: "count", description: "Concurrent generation limit" },
878
+ { name: "--max-concurrent-requests", value: "count", description: "Maximum sequences in an upstream generation batch" },
879
+ { name: "--context-length", value: "count", description: "Total context token budget (default 16384)" },
880
+ { name: "--gpu-memory-utilization", value: "fraction", description: "vLLM GPU memory budget fraction (default 0.8)" },
838
881
  { name: "--spec", value: "path", description: "Behavior spec whose instructions are enforced" },
839
882
  { name: "--no-spec-prompt", description: "Do not enforce the stored behavior-spec prompt" },
840
883
  { name: "--allow-remote", description: "Allow a non-loopback bind" },
841
884
  { name: "--api-key-env", value: "name", description: "Environment variable containing a bearer token" },
842
- { name: "--print-command", description: "Validate and print the launch plan without starting" }
885
+ { name: "--print-command", description: "Validate and print the launch plan without starting" },
886
+ { name: "--print-client-config", value: "client", description: "Print Pi models.json; does not start serving" }
843
887
  ];
844
888
  var COMMAND_DEFINITIONS = {
845
889
  info: {
@@ -1102,7 +1146,7 @@ function readNumberOption(argv, name) {
1102
1146
  async function selectedConfigPath(argv, adjacentTo) {
1103
1147
  const explicitPath = readOption(argv, "--config");
1104
1148
  if (explicitPath) return resolve3(explicitPath);
1105
- const candidate = join3(adjacentTo ? dirname(resolve3(adjacentTo)) : cwd(), "local-runner.json");
1149
+ const candidate = join4(adjacentTo ? dirname(resolve3(adjacentTo)) : cwd(), "local-runner.json");
1106
1150
  const metadata = await stat(candidate).catch(() => null);
1107
1151
  return metadata?.isFile() ? candidate : void 0;
1108
1152
  }
@@ -1167,7 +1211,7 @@ function createConsoleReporter(options) {
1167
1211
  };
1168
1212
  }
1169
1213
  async function verifyStoredModel(model, config) {
1170
- const manifestPath = join3(model.artifact_dir, "artifact-manifest.json");
1214
+ const manifestPath = join4(model.artifact_dir, "artifact-manifest.json");
1171
1215
  const integrity = await assertArtifactManifest(manifestPath, {
1172
1216
  requiredPaths: ["stage-metadata.json", "training-report.json"],
1173
1217
  scopeToRequired: true,
@@ -1202,8 +1246,8 @@ async function verifyStoredModel(model, config) {
1202
1246
  };
1203
1247
  }
1204
1248
  async function verifyActivationEvidence(model) {
1205
- const generalBaseline = join3(model.artifact_dir, "general-baseline-eval.json");
1206
- const generalCandidate = join3(model.artifact_dir, "general-candidate-eval.json");
1249
+ const generalBaseline = join4(model.artifact_dir, "general-baseline-eval.json");
1250
+ const generalCandidate = join4(model.artifact_dir, "general-candidate-eval.json");
1207
1251
  const [hasBaseline, hasCandidate] = await Promise.all([
1208
1252
  stat(generalBaseline).then((metadata) => metadata.isFile(), () => false),
1209
1253
  stat(generalCandidate).then((metadata) => metadata.isFile(), () => false)
@@ -1213,7 +1257,7 @@ async function verifyActivationEvidence(model) {
1213
1257
  "This model cannot be activated because the run did not include a general-regression suite. Add evaluation.generalRegression to local-runner.json with a held-out dataset, re-run training, then activate. Until then, serve the adapter with `tt serve local-<run-id>`."
1214
1258
  );
1215
1259
  }
1216
- await assertArtifactManifest(join3(model.artifact_dir, "artifact-manifest.json"), {
1260
+ await assertArtifactManifest(join4(model.artifact_dir, "artifact-manifest.json"), {
1217
1261
  requiredPaths: [
1218
1262
  "run-report.json",
1219
1263
  "general-baseline-eval.json",
@@ -1239,7 +1283,7 @@ async function verifyModelArtifactPath(input, config) {
1239
1283
  if (!metadata) throw new Error(`Model not found and artifact path does not exist: ${input}`);
1240
1284
  let current = metadata.isDirectory() ? inputPath : dirname(inputPath);
1241
1285
  for (let depth = 0; depth < 8; depth += 1) {
1242
- const candidate = join3(current, "artifact-manifest.json");
1286
+ const candidate = join4(current, "artifact-manifest.json");
1243
1287
  const raw = await readFile2(candidate, "utf8").catch(() => null);
1244
1288
  if (raw) {
1245
1289
  const parsed = JSON.parse(raw);
@@ -1297,7 +1341,7 @@ async function modelSystemPrompt(args) {
1297
1341
  }
1298
1342
  spec = local.data;
1299
1343
  } else {
1300
- const runRequestPath = join3(args.store.paths.runsDir, args.model.run_id, "request.json");
1344
+ const runRequestPath = join4(args.store.paths.runsDir, args.model.run_id, "request.json");
1301
1345
  let persistedRequest = null;
1302
1346
  try {
1303
1347
  persistedRequest = fineTuneRunRequestSchema.parse(JSON.parse(await readFile2(runRequestPath, "utf8")));
@@ -1313,9 +1357,9 @@ async function modelSystemPrompt(args) {
1313
1357
  }
1314
1358
  const prompt = buildSystemMessage(spec);
1315
1359
  const metadata = JSON.parse(
1316
- await readFile2(join3(args.model.artifact_dir, "stage-metadata.json"), "utf8")
1360
+ await readFile2(join4(args.model.artifact_dir, "stage-metadata.json"), "utf8")
1317
1361
  );
1318
- const promptHash = createHash("sha256").update(prompt).digest("hex");
1362
+ const promptHash = createHash2("sha256").update(prompt).digest("hex");
1319
1363
  if (metadata.system_prompt_sha256 !== promptHash) {
1320
1364
  throw new Error(
1321
1365
  "Behavior spec instructions do not match the prompt fingerprint used for this trained model. Pass the original --spec, or use --no-spec-prompt only when this change is intentional."
@@ -1330,6 +1374,47 @@ function modelServeDevice(argv) {
1330
1374
  }
1331
1375
  throw new Error(`--device must be cuda or cpu; got: ${value}`);
1332
1376
  }
1377
+ function printServeClientConfig(argv, launch) {
1378
+ const client = readOption(argv, "--print-client-config");
1379
+ if (client === void 0) return false;
1380
+ if (client !== "pi") throw new Error("--print-client-config must be pi.");
1381
+ if (hasFlag(argv, "--print-command")) throw new Error("Use only one of --print-command or --print-client-config.");
1382
+ const apiKeyEnv = readOption(argv, "--api-key-env");
1383
+ if (apiKeyEnv && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(apiKeyEnv)) {
1384
+ throw new Error("--api-key-env must be a plain environment variable name for Pi config.");
1385
+ }
1386
+ const url = new URL(launch.url);
1387
+ if (url.hostname === "0.0.0.0") url.hostname = "127.0.0.1";
1388
+ if (url.hostname === "[::]") url.hostname = "[::1]";
1389
+ url.pathname = "/v1";
1390
+ printJson({
1391
+ providers: {
1392
+ "tt-local": {
1393
+ baseUrl: url.toString(),
1394
+ api: "openai-completions",
1395
+ apiKey: apiKeyEnv ? "${" + apiKeyEnv + "}" : "tt-local",
1396
+ compat: {
1397
+ supportsStore: false,
1398
+ supportsDeveloperRole: false,
1399
+ supportsReasoningEffort: false,
1400
+ supportsUsageInStreaming: true,
1401
+ maxTokensField: "max_tokens"
1402
+ },
1403
+ models: [.../* @__PURE__ */ new Set([launch.modelName, `base:${launch.env.TT_BASE_MODEL}`])].map((id) => ({
1404
+ id,
1405
+ name: id,
1406
+ // This endpoint exports ordinary text/tool calls, not reasoning-effort controls.
1407
+ reasoning: false,
1408
+ input: ["text"],
1409
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1410
+ contextWindow: Number(launch.env.TT_CONTEXT_LENGTH),
1411
+ maxTokens: Number(launch.env.TT_MAX_TOKENS)
1412
+ }))
1413
+ }
1414
+ }
1415
+ });
1416
+ return true;
1417
+ }
1333
1418
  async function serveStoredModelFromCli(args) {
1334
1419
  const store = createLocalStore(args.config.storeRoot);
1335
1420
  const model = await store.getModel(args.modelId);
@@ -1345,6 +1430,8 @@ async function serveStoredModelFromCli(args) {
1345
1430
  temperature: readNumberOption(args.argv, "--temperature"),
1346
1431
  topP: readNumberOption(args.argv, "--top-p"),
1347
1432
  maxConcurrentRequests: readNumberOption(args.argv, "--max-concurrent-requests"),
1433
+ contextLength: readNumberOption(args.argv, "--context-length"),
1434
+ gpuMemoryUtilization: readNumberOption(args.argv, "--gpu-memory-utilization"),
1348
1435
  systemPrompt: await modelSystemPrompt({ argv: args.argv, model, store }),
1349
1436
  allowRemote: hasFlag(args.argv, "--allow-remote"),
1350
1437
  apiKeyEnv: readOption(args.argv, "--api-key-env"),
@@ -1358,6 +1445,7 @@ async function serveStoredModelFromCli(args) {
1358
1445
  })()
1359
1446
  }
1360
1447
  });
1448
+ if (printServeClientConfig(args.argv, launch)) return;
1361
1449
  if (hasFlag(args.argv, "--print-command")) {
1362
1450
  printJson({
1363
1451
  ok: true,
@@ -1383,6 +1471,8 @@ function serveOptionsFromArgv(argv) {
1383
1471
  temperature: readNumberOption(argv, "--temperature"),
1384
1472
  topP: readNumberOption(argv, "--top-p"),
1385
1473
  maxConcurrentRequests: readNumberOption(argv, "--max-concurrent-requests"),
1474
+ contextLength: readNumberOption(argv, "--context-length"),
1475
+ gpuMemoryUtilization: readNumberOption(argv, "--gpu-memory-utilization"),
1386
1476
  allowRemote: hasFlag(argv, "--allow-remote"),
1387
1477
  apiKeyEnv: readOption(argv, "--api-key-env")
1388
1478
  };
@@ -1421,6 +1511,7 @@ async function serveBaseModelFromCli(args) {
1421
1511
  baseModelRevision: spec?.hyperparameters?.base_model_revision ?? defaultBaseModelRevision(baseModel)
1422
1512
  }
1423
1513
  });
1514
+ if (printServeClientConfig(args.argv, launch)) return;
1424
1515
  if (hasFlag(args.argv, "--print-command")) {
1425
1516
  printJson({
1426
1517
  ok: true,