@tuned-tensor/cli 0.13.1-beta.7 → 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.
@@ -55,7 +55,10 @@ import {
55
55
  localStorePaths,
56
56
  minimalMachineLearningEnvironment,
57
57
  normalizeChatJsonlForRelocation,
58
+ parseLocalRunInput,
59
+ parseLocalRunnerConfig,
58
60
  prefetchBaseModel,
61
+ pythonEnvironmentPath,
59
62
  reportInBackground,
60
63
  resolveLocalRunInputPaths,
61
64
  resolveModelLoader,
@@ -78,13 +81,13 @@ import {
78
81
  withHuggingFaceCacheEnvironment,
79
82
  withOfflineHuggingFaceCacheEnvironment,
80
83
  writePrivateJsonAtomic
81
- } from "./chunk-AKJ5WZ2S.js";
84
+ } from "./chunk-VRDDO2FP.js";
82
85
 
83
86
  // src/local-runtime/index.ts
84
- import { readFileSync, realpathSync } from "fs";
85
- import { createHash } from "crypto";
87
+ import { readFileSync as readFileSync2, realpathSync } from "fs";
88
+ import { createHash as createHash2 } from "crypto";
86
89
  import { readFile as readFile2, stat } from "fs/promises";
87
- import { basename, dirname, join as join3, resolve as resolve3 } from "path";
90
+ import { basename, dirname, join as join4, resolve as resolve3 } from "path";
88
91
  import { cwd } from "process";
89
92
  import { fileURLToPath as fileURLToPath2 } from "url";
90
93
 
@@ -503,6 +506,39 @@ async function runDoctor(config, request, foundationSpec) {
503
506
  import { spawn as spawn2 } from "child_process";
504
507
  import { resolve as resolve2 } from "path";
505
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
506
542
  function localArtifactPath(uri) {
507
543
  if (uri.startsWith("file://")) return fileURLToPath(uri);
508
544
  if (/^[a-z][a-z0-9+.-]*:/i.test(uri)) {
@@ -524,6 +560,10 @@ function httpHost(host) {
524
560
  }
525
561
  function buildModelServerLaunch(args) {
526
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);
527
567
  const host = options.host ?? "127.0.0.1";
528
568
  const remote = !isLoopbackHost(host);
529
569
  if (remote && !options.allowRemote) {
@@ -559,8 +599,8 @@ function buildModelServerLaunch(args) {
559
599
  options.baseModelRevision
560
600
  );
561
601
  const baseModelRevision = localBaseModelPath ? defaultBaseModelRevision(args.baseModel) : requestedBaseModelRevision;
562
- const entrypoint = buildBundledPythonCommand("serve.py");
563
- const env = withBundledPythonEnvironment(
602
+ const entrypoint = buildServingPythonCommand();
603
+ const env = withServingPythonEnvironment(
564
604
  withOfflineHuggingFaceCacheEnvironment({
565
605
  ...minimalMachineLearningEnvironment(process.env),
566
606
  ...artifactPath ? { TT_MODEL_ARTIFACT: artifactPath } : {},
@@ -568,10 +608,12 @@ function buildModelServerLaunch(args) {
568
608
  ...localBaseModelPath ? { TT_MODEL_SOURCE: localBaseModelPath } : {},
569
609
  ...baseModelRevision ? { TT_BASE_MODEL_REVISION: baseModelRevision } : {},
570
610
  TT_MODEL_NAME: args.modelName,
611
+ TT_CONTEXT_LENGTH: String(contextLength),
612
+ TT_GPU_MEMORY_UTILIZATION: String(gpuMemoryUtilization),
571
613
  TT_MODEL_LOADER: resolveModelLoader(args.baseModel),
572
614
  TT_HOST: host,
573
615
  TT_PORT: String(port),
574
- TT_DEVICE: options.device ?? args.config.evaluation.inference.device,
616
+ TT_DEVICE: device,
575
617
  TT_MAX_TOKENS: String(maxTokens),
576
618
  TT_TEMPERATURE: String(temperature),
577
619
  TT_TOP_P: String(topP),
@@ -616,6 +658,7 @@ async function verifyLocalModelServerLaunch(launch) {
616
658
  }
617
659
  }
618
660
  async function serveLocalModel(launch) {
661
+ if (process.platform !== "linux") throw new Error("Local vLLM serving requires Linux and an NVIDIA CUDA GPU.");
619
662
  await verifyLocalModelServerLaunch(launch);
620
663
  await new Promise((resolveServer, reject) => {
621
664
  const child = spawn2(launch.command, launch.commandArgs, {
@@ -671,12 +714,12 @@ async function serveLocalModel(launch) {
671
714
  // src/local-runtime/active-model.ts
672
715
  import { randomUUID } from "crypto";
673
716
  import { appendFile, chmod, readFile, rename, writeFile as writeFile2 } from "fs/promises";
674
- import { join as join2 } from "path";
717
+ import { join as join3 } from "path";
675
718
  function pointerPath(store) {
676
- return join2(store.root, "active-model.json");
719
+ return join3(store.root, "active-model.json");
677
720
  }
678
721
  function historyPath(store) {
679
- return join2(store.root, "activation-history.jsonl");
722
+ return join3(store.root, "activation-history.jsonl");
680
723
  }
681
724
  async function readPointer(store) {
682
725
  if (!await hardenExistingLocalStore(store.root)) return null;
@@ -716,7 +759,7 @@ async function assertModelCanActivate(store, modelId) {
716
759
  throw new Error(`Model ${model.id} belongs to run ${run.id}, which is not completed.`);
717
760
  }
718
761
  const report = runReportSchema.parse(
719
- 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"))
720
763
  );
721
764
  if (!report.general_regression) {
722
765
  throw new Error(`Model ${model.id} has no general regression evaluation.`);
@@ -773,7 +816,7 @@ async function rollbackActiveModel(store) {
773
816
  function packageVersion() {
774
817
  try {
775
818
  const packageJson = JSON.parse(
776
- readFileSync(resolve3(localRuntimePackageRoot(import.meta.url), "package.json"), "utf8")
819
+ readFileSync2(resolve3(localRuntimePackageRoot(import.meta.url), "package.json"), "utf8")
777
820
  );
778
821
  return typeof packageJson.version === "string" ? packageJson.version : "unknown";
779
822
  } catch {
@@ -828,16 +871,19 @@ var MODEL_SERVE_OPTIONS = [
828
871
  CONFIG_OPTION,
829
872
  { name: "--host", value: "host", description: "Bind host (localhost by default)" },
830
873
  { name: "--port", value: "port", description: "Bind port" },
831
- { name: "--device", value: "device", description: "cuda or cpu" },
874
+ { name: "--device", value: "device", description: "cuda (pinned vLLM runtime)" },
832
875
  { name: "--max-tokens", value: "count", description: "Default response token limit" },
833
876
  { name: "--temperature", value: "number", description: "Default sampling temperature" },
834
877
  { name: "--top-p", value: "number", description: "Default nucleus sampling threshold" },
835
- { 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)" },
836
881
  { name: "--spec", value: "path", description: "Behavior spec whose instructions are enforced" },
837
882
  { name: "--no-spec-prompt", description: "Do not enforce the stored behavior-spec prompt" },
838
883
  { name: "--allow-remote", description: "Allow a non-loopback bind" },
839
884
  { name: "--api-key-env", value: "name", description: "Environment variable containing a bearer token" },
840
- { 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" }
841
887
  ];
842
888
  var COMMAND_DEFINITIONS = {
843
889
  info: {
@@ -1100,7 +1146,7 @@ function readNumberOption(argv, name) {
1100
1146
  async function selectedConfigPath(argv, adjacentTo) {
1101
1147
  const explicitPath = readOption(argv, "--config");
1102
1148
  if (explicitPath) return resolve3(explicitPath);
1103
- const candidate = join3(adjacentTo ? dirname(resolve3(adjacentTo)) : cwd(), "local-runner.json");
1149
+ const candidate = join4(adjacentTo ? dirname(resolve3(adjacentTo)) : cwd(), "local-runner.json");
1104
1150
  const metadata = await stat(candidate).catch(() => null);
1105
1151
  return metadata?.isFile() ? candidate : void 0;
1106
1152
  }
@@ -1120,7 +1166,7 @@ async function loadCliBehaviorSpec(inputPath, runId) {
1120
1166
  });
1121
1167
  if (input.kind === "foundation-spec") {
1122
1168
  throw new Error(
1123
- `This spec uses engine "foundation". Use \`tt pipeline run --spec ${input.path}\` instead of adapter commands like \`tt run\`.`
1169
+ `This spec uses engine "foundation". Use \`tt pipeline run --spec ${input.path}\`; the legacy adapter runner cannot execute foundation specs.`
1124
1170
  );
1125
1171
  }
1126
1172
  if (input.kind !== "spec") {
@@ -1165,7 +1211,7 @@ function createConsoleReporter(options) {
1165
1211
  };
1166
1212
  }
1167
1213
  async function verifyStoredModel(model, config) {
1168
- const manifestPath = join3(model.artifact_dir, "artifact-manifest.json");
1214
+ const manifestPath = join4(model.artifact_dir, "artifact-manifest.json");
1169
1215
  const integrity = await assertArtifactManifest(manifestPath, {
1170
1216
  requiredPaths: ["stage-metadata.json", "training-report.json"],
1171
1217
  scopeToRequired: true,
@@ -1200,8 +1246,8 @@ async function verifyStoredModel(model, config) {
1200
1246
  };
1201
1247
  }
1202
1248
  async function verifyActivationEvidence(model) {
1203
- const generalBaseline = join3(model.artifact_dir, "general-baseline-eval.json");
1204
- 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");
1205
1251
  const [hasBaseline, hasCandidate] = await Promise.all([
1206
1252
  stat(generalBaseline).then((metadata) => metadata.isFile(), () => false),
1207
1253
  stat(generalCandidate).then((metadata) => metadata.isFile(), () => false)
@@ -1211,7 +1257,7 @@ async function verifyActivationEvidence(model) {
1211
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>`."
1212
1258
  );
1213
1259
  }
1214
- await assertArtifactManifest(join3(model.artifact_dir, "artifact-manifest.json"), {
1260
+ await assertArtifactManifest(join4(model.artifact_dir, "artifact-manifest.json"), {
1215
1261
  requiredPaths: [
1216
1262
  "run-report.json",
1217
1263
  "general-baseline-eval.json",
@@ -1237,7 +1283,7 @@ async function verifyModelArtifactPath(input, config) {
1237
1283
  if (!metadata) throw new Error(`Model not found and artifact path does not exist: ${input}`);
1238
1284
  let current = metadata.isDirectory() ? inputPath : dirname(inputPath);
1239
1285
  for (let depth = 0; depth < 8; depth += 1) {
1240
- const candidate = join3(current, "artifact-manifest.json");
1286
+ const candidate = join4(current, "artifact-manifest.json");
1241
1287
  const raw = await readFile2(candidate, "utf8").catch(() => null);
1242
1288
  if (raw) {
1243
1289
  const parsed = JSON.parse(raw);
@@ -1295,7 +1341,7 @@ async function modelSystemPrompt(args) {
1295
1341
  }
1296
1342
  spec = local.data;
1297
1343
  } else {
1298
- 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");
1299
1345
  let persistedRequest = null;
1300
1346
  try {
1301
1347
  persistedRequest = fineTuneRunRequestSchema.parse(JSON.parse(await readFile2(runRequestPath, "utf8")));
@@ -1311,9 +1357,9 @@ async function modelSystemPrompt(args) {
1311
1357
  }
1312
1358
  const prompt = buildSystemMessage(spec);
1313
1359
  const metadata = JSON.parse(
1314
- await readFile2(join3(args.model.artifact_dir, "stage-metadata.json"), "utf8")
1360
+ await readFile2(join4(args.model.artifact_dir, "stage-metadata.json"), "utf8")
1315
1361
  );
1316
- const promptHash = createHash("sha256").update(prompt).digest("hex");
1362
+ const promptHash = createHash2("sha256").update(prompt).digest("hex");
1317
1363
  if (metadata.system_prompt_sha256 !== promptHash) {
1318
1364
  throw new Error(
1319
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."
@@ -1328,6 +1374,47 @@ function modelServeDevice(argv) {
1328
1374
  }
1329
1375
  throw new Error(`--device must be cuda or cpu; got: ${value}`);
1330
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
+ }
1331
1418
  async function serveStoredModelFromCli(args) {
1332
1419
  const store = createLocalStore(args.config.storeRoot);
1333
1420
  const model = await store.getModel(args.modelId);
@@ -1343,6 +1430,8 @@ async function serveStoredModelFromCli(args) {
1343
1430
  temperature: readNumberOption(args.argv, "--temperature"),
1344
1431
  topP: readNumberOption(args.argv, "--top-p"),
1345
1432
  maxConcurrentRequests: readNumberOption(args.argv, "--max-concurrent-requests"),
1433
+ contextLength: readNumberOption(args.argv, "--context-length"),
1434
+ gpuMemoryUtilization: readNumberOption(args.argv, "--gpu-memory-utilization"),
1346
1435
  systemPrompt: await modelSystemPrompt({ argv: args.argv, model, store }),
1347
1436
  allowRemote: hasFlag(args.argv, "--allow-remote"),
1348
1437
  apiKeyEnv: readOption(args.argv, "--api-key-env"),
@@ -1356,6 +1445,7 @@ async function serveStoredModelFromCli(args) {
1356
1445
  })()
1357
1446
  }
1358
1447
  });
1448
+ if (printServeClientConfig(args.argv, launch)) return;
1359
1449
  if (hasFlag(args.argv, "--print-command")) {
1360
1450
  printJson({
1361
1451
  ok: true,
@@ -1381,6 +1471,8 @@ function serveOptionsFromArgv(argv) {
1381
1471
  temperature: readNumberOption(argv, "--temperature"),
1382
1472
  topP: readNumberOption(argv, "--top-p"),
1383
1473
  maxConcurrentRequests: readNumberOption(argv, "--max-concurrent-requests"),
1474
+ contextLength: readNumberOption(argv, "--context-length"),
1475
+ gpuMemoryUtilization: readNumberOption(argv, "--gpu-memory-utilization"),
1384
1476
  allowRemote: hasFlag(argv, "--allow-remote"),
1385
1477
  apiKeyEnv: readOption(argv, "--api-key-env")
1386
1478
  };
@@ -1419,6 +1511,7 @@ async function serveBaseModelFromCli(args) {
1419
1511
  baseModelRevision: spec?.hyperparameters?.base_model_revision ?? defaultBaseModelRevision(baseModel)
1420
1512
  }
1421
1513
  });
1514
+ if (printServeClientConfig(args.argv, launch)) return;
1422
1515
  if (hasFlag(args.argv, "--print-command")) {
1423
1516
  printJson({
1424
1517
  ok: true,
@@ -1856,6 +1949,8 @@ export {
1856
1949
  localRunnerConfigSchema,
1857
1950
  localStorePaths,
1858
1951
  normalizeChatJsonlForRelocation,
1952
+ parseLocalRunInput,
1953
+ parseLocalRunnerConfig,
1859
1954
  prefetchBaseModel,
1860
1955
  reportInBackground,
1861
1956
  resolveLocalRunInputPaths,