@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/README.md +6 -0
- package/dist/{chunk-CAEBS5HH.js → chunk-VRDDO2FP.js} +2 -1
- package/dist/chunk-VRDDO2FP.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/local-runtime.js +115 -24
- package/dist/local-runtime.js.map +1 -1
- package/docs/local-runtime/serving.md +168 -0
- package/package.json +3 -1
- package/training/adapter/src/serve.py +133 -267
- package/training/serving/pyproject.toml +9 -0
- package/training/serving/uv.lock +2785 -0
- package/dist/chunk-CAEBS5HH.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
runLoggedProcess,
|
|
26
26
|
warningsFromSnapshot,
|
|
27
27
|
withFoundationPythonEnvironment
|
|
28
|
-
} from "./chunk-
|
|
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.
|
|
6624
|
+
runCli("0.14.0").catch((err) => {
|
|
6625
6625
|
reportError(err);
|
|
6626
6626
|
process.exit(1);
|
|
6627
6627
|
});
|
package/dist/local-runtime.js
CHANGED
|
@@ -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-
|
|
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
|
|
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 =
|
|
565
|
-
const env =
|
|
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:
|
|
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
|
|
717
|
+
import { join as join3 } from "path";
|
|
677
718
|
function pointerPath(store) {
|
|
678
|
-
return
|
|
719
|
+
return join3(store.root, "active-model.json");
|
|
679
720
|
}
|
|
680
721
|
function historyPath(store) {
|
|
681
|
-
return
|
|
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(
|
|
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
|
-
|
|
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
|
|
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: "
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1206
|
-
const generalCandidate =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
1360
|
+
await readFile2(join4(args.model.artifact_dir, "stage-metadata.json"), "utf8")
|
|
1317
1361
|
);
|
|
1318
|
-
const promptHash =
|
|
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,
|