@tuned-tensor/cli 0.4.21 → 0.4.23
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 +7 -3
- package/dist/index.js +176 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# tt - Tuned Tensor CLI
|
|
2
2
|
|
|
3
|
-
`tt` is the command-line
|
|
4
|
-
Use it to define behaviour
|
|
5
|
-
|
|
3
|
+
`tt` is the command-line client for the optional managed
|
|
4
|
+
[Tuned Tensor](https://www.tunedtensor.com) service. Use it to define behaviour
|
|
5
|
+
specs, launch managed fine-tuning runs, inspect paired baseline-vs-tuned
|
|
6
|
+
reports, and download or serve trained models. For local CUDA or DGX Spark
|
|
7
|
+
training without a Tuned Tensor account, use `tt-local` from
|
|
8
|
+
`@tuned-tensor/local`.
|
|
6
9
|
|
|
7
10
|
The main CLI documentation lives at
|
|
8
11
|
[tunedtensor.com/docs/cli](https://tunedtensor.com/docs/cli). This README is a
|
|
@@ -37,6 +40,7 @@ tt push
|
|
|
37
40
|
tt runs estimate <spec-id>
|
|
38
41
|
tt runs start <spec-id>
|
|
39
42
|
tt runs watch <run-id>
|
|
43
|
+
tt runs report <run-id>
|
|
40
44
|
```
|
|
41
45
|
|
|
42
46
|
To continue training from a completed fine-tuned model artifact:
|
package/dist/index.js
CHANGED
|
@@ -625,6 +625,14 @@ function formatEvalScore(run) {
|
|
|
625
625
|
function formatPercent(rate) {
|
|
626
626
|
return rate == null ? void 0 : (rate * 100).toFixed(1) + "%";
|
|
627
627
|
}
|
|
628
|
+
function formatPointDelta(delta) {
|
|
629
|
+
if (delta == null) return void 0;
|
|
630
|
+
const points = delta * 100;
|
|
631
|
+
return `${points >= 0 ? "+" : ""}${points.toFixed(1)} pp`;
|
|
632
|
+
}
|
|
633
|
+
function formatScore(score) {
|
|
634
|
+
return score == null ? "\u2014" : score.toFixed(2);
|
|
635
|
+
}
|
|
628
636
|
function formatCountRate(count, total, rate) {
|
|
629
637
|
if (count == null || total == null || rate == null) return void 0;
|
|
630
638
|
return `${formatPercent(rate)} (${count}/${total})`;
|
|
@@ -644,6 +652,7 @@ function formatEstimateRange(estimate) {
|
|
|
644
652
|
return `${formatMinutes(duration.estimated_minutes)} (${formatMinutes(duration.range_minutes.low)}-${formatMinutes(duration.range_minutes.high)})`;
|
|
645
653
|
}
|
|
646
654
|
function printRunEstimate(estimate) {
|
|
655
|
+
const billing = estimate.billing;
|
|
647
656
|
printDetail([
|
|
648
657
|
["Estimated Time", formatEstimateRange(estimate)],
|
|
649
658
|
["Confidence", estimate.duration.confidence],
|
|
@@ -651,7 +660,20 @@ function printRunEstimate(estimate) {
|
|
|
651
660
|
["Basis", estimate.duration.basis.replaceAll("_", " ")],
|
|
652
661
|
["Estimated Cost", formatCents(estimate.estimated_cost_cents)],
|
|
653
662
|
["Training Tokens", `${(estimate.estimated_training_tokens / 1e3).toFixed(1)}k`],
|
|
654
|
-
["Epochs", String(estimate.estimated_epochs)]
|
|
663
|
+
["Epochs", String(estimate.estimated_epochs)],
|
|
664
|
+
["Plan", billing?.plan],
|
|
665
|
+
[
|
|
666
|
+
"Billing Source",
|
|
667
|
+
billing?.billing_source === "free_quota" ? "Free monthly quota" : billing?.billing_source === "credits" ? "Credits" : void 0
|
|
668
|
+
],
|
|
669
|
+
[
|
|
670
|
+
"Free Runs",
|
|
671
|
+
billing ? `${billing.free_runs_remaining}/${billing.free_runs_monthly_limit} remaining` : void 0
|
|
672
|
+
],
|
|
673
|
+
[
|
|
674
|
+
"Free Eligible",
|
|
675
|
+
billing ? billing.free_run_eligible ? "yes" : `no (${billing.free_run_ineligibility.join(", ") || "not eligible"})` : void 0
|
|
676
|
+
]
|
|
655
677
|
]);
|
|
656
678
|
console.log(
|
|
657
679
|
"\nDuration is a rough historical range. Final cost uses provider-reported training tokens."
|
|
@@ -762,6 +784,131 @@ function printEvalOutputDiagnostics(diagnostics) {
|
|
|
762
784
|
}
|
|
763
785
|
}
|
|
764
786
|
}
|
|
787
|
+
function compactWhitespace(value) {
|
|
788
|
+
return value.replace(/\s+/g, " ").trim();
|
|
789
|
+
}
|
|
790
|
+
function parseJsonish(value) {
|
|
791
|
+
if (!value) return value;
|
|
792
|
+
try {
|
|
793
|
+
return JSON.parse(value);
|
|
794
|
+
} catch {
|
|
795
|
+
return value;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
function formatJsonish(value, max = 360) {
|
|
799
|
+
const parsed = parseJsonish(value);
|
|
800
|
+
const rendered = typeof parsed === "string" ? compactWhitespace(parsed) : JSON.stringify(parsed);
|
|
801
|
+
return truncate(rendered ?? "\u2014", max);
|
|
802
|
+
}
|
|
803
|
+
function formatPromptSummary(prompt, max = 260) {
|
|
804
|
+
const subject = prompt.match(/^Subject:\s*(.+)$/m)?.[1]?.trim();
|
|
805
|
+
const body = prompt.match(/^Body:\s*([\s\S]+)$/m)?.[1];
|
|
806
|
+
if (subject) {
|
|
807
|
+
const bodySummary = body ? ` | Body: ${compactWhitespace(body)}` : "";
|
|
808
|
+
return truncate(`Subject: ${subject}${bodySummary}`, max);
|
|
809
|
+
}
|
|
810
|
+
return truncate(compactWhitespace(prompt), max);
|
|
811
|
+
}
|
|
812
|
+
function indexResults(results) {
|
|
813
|
+
return new Map((results ?? []).map((result) => [result.prompt, result]));
|
|
814
|
+
}
|
|
815
|
+
function printReportMetrics(label, split) {
|
|
816
|
+
if (!split) return;
|
|
817
|
+
const comparison = split.comparison;
|
|
818
|
+
const baseline = split.baseline;
|
|
819
|
+
const candidate = split.candidate;
|
|
820
|
+
console.log(`
|
|
821
|
+
${label} Metrics:`);
|
|
822
|
+
printDetail([
|
|
823
|
+
["Base Avg", formatPercent(baseline?.avg_score)],
|
|
824
|
+
["Tuned Avg", formatPercent(candidate?.avg_score)],
|
|
825
|
+
["Avg Delta", formatPointDelta(comparison?.avg_score_delta)],
|
|
826
|
+
["Base Pass", formatPercent(baseline?.pass_rate)],
|
|
827
|
+
["Tuned Pass", formatPercent(candidate?.pass_rate)],
|
|
828
|
+
["Pass Delta", formatPointDelta(comparison?.pass_rate_delta)],
|
|
829
|
+
["Regressions", comparison?.regressions == null ? void 0 : String(comparison.regressions)],
|
|
830
|
+
["Improvements", comparison?.improvements == null ? void 0 : String(comparison.improvements)],
|
|
831
|
+
["Eval Rows", candidate?.eval_examples_used == null ? candidate?.total == null ? void 0 : String(candidate.total) : String(candidate.eval_examples_used)]
|
|
832
|
+
]);
|
|
833
|
+
}
|
|
834
|
+
function printComparedExample(input) {
|
|
835
|
+
const { index, prompt, baseline, candidate, oldScore, newScore } = input;
|
|
836
|
+
const expected = candidate?.expected ?? baseline?.expected;
|
|
837
|
+
const scoreLine = oldScore != null || newScore != null ? oldScore == null ? `tuned score ${formatScore(newScore)}` : `score ${formatScore(oldScore)} -> ${formatScore(newScore)}` : `tuned score ${formatScore(candidate?.score)}`;
|
|
838
|
+
console.log(`
|
|
839
|
+
${index}. ${scoreLine}`);
|
|
840
|
+
console.log(` Prompt: ${formatPromptSummary(prompt)}`);
|
|
841
|
+
console.log(` Expected: ${formatJsonish(expected)}`);
|
|
842
|
+
if (baseline?.actual != null) {
|
|
843
|
+
console.log(` Base: ${formatJsonish(baseline.actual)}`);
|
|
844
|
+
}
|
|
845
|
+
if (candidate?.actual != null) {
|
|
846
|
+
console.log(` Tuned: ${formatJsonish(candidate.actual)}`);
|
|
847
|
+
}
|
|
848
|
+
const reasoning = candidate?.reasoning ?? baseline?.reasoning;
|
|
849
|
+
if (reasoning) {
|
|
850
|
+
console.log(` Judge: ${truncate(compactWhitespace(reasoning), 420)}`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
function printReportExamples(label, split, mode, limit) {
|
|
854
|
+
if (!split || limit <= 0) return;
|
|
855
|
+
const baselineByPrompt = indexResults(split.baseline?.results);
|
|
856
|
+
const candidateByPrompt = indexResults(split.candidate?.results);
|
|
857
|
+
if (mode === "regressions") {
|
|
858
|
+
const regressions = split.comparison?.regressed_examples ?? [];
|
|
859
|
+
console.log(`
|
|
860
|
+
${label} Regressions:`);
|
|
861
|
+
if (!regressions.length) {
|
|
862
|
+
console.log(" No regressed examples in the report.");
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
regressions.slice(0, limit).forEach((example, i) => {
|
|
866
|
+
printComparedExample({
|
|
867
|
+
index: i + 1,
|
|
868
|
+
prompt: example.prompt,
|
|
869
|
+
baseline: baselineByPrompt.get(example.prompt),
|
|
870
|
+
candidate: candidateByPrompt.get(example.prompt),
|
|
871
|
+
oldScore: example.old_score,
|
|
872
|
+
newScore: example.new_score
|
|
873
|
+
});
|
|
874
|
+
});
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
const failures = (split.candidate?.results ?? []).filter((result) => result.passed === false || (result.score ?? 1) < 1).sort((a, b) => (a.score ?? 0) - (b.score ?? 0));
|
|
878
|
+
console.log(`
|
|
879
|
+
${label} Tuned Failures:`);
|
|
880
|
+
if (!failures.length) {
|
|
881
|
+
console.log(" No tuned failures in the report.");
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
failures.slice(0, limit).forEach((candidate, i) => {
|
|
885
|
+
printComparedExample({
|
|
886
|
+
index: i + 1,
|
|
887
|
+
prompt: candidate.prompt,
|
|
888
|
+
baseline: baselineByPrompt.get(candidate.prompt),
|
|
889
|
+
candidate,
|
|
890
|
+
newScore: candidate.score
|
|
891
|
+
});
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
function printRunReport(report, options) {
|
|
895
|
+
printDetail([
|
|
896
|
+
["Run", report.run_id ? shortId(report.run_id) : void 0],
|
|
897
|
+
["Status", report.status ? formatStatus(report.status) : void 0],
|
|
898
|
+
["Model", report.fine_tuned_model_id ? shortId(report.fine_tuned_model_id) : void 0]
|
|
899
|
+
]);
|
|
900
|
+
const splits = [];
|
|
901
|
+
if (options.split === "primary" || options.split === "all") {
|
|
902
|
+
splits.push(["Primary", report]);
|
|
903
|
+
}
|
|
904
|
+
if (options.split === "test" || options.split === "all") {
|
|
905
|
+
splits.push(["Test", report.test]);
|
|
906
|
+
}
|
|
907
|
+
for (const [label, split] of splits) {
|
|
908
|
+
printReportMetrics(label, split);
|
|
909
|
+
printReportExamples(label, split, options.mode, options.limit);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
765
912
|
function addRunConfigurationOptions(command) {
|
|
766
913
|
return command.option("--no-augment", "Disable data augmentation").option("--no-llm-judge", "Disable LLM judging").option("--epochs <n>", "Number of training epochs").option("--lr <rate>", "Learning rate").option("--batch-size <n>", "Batch size").option("--dataset <id>", "Dataset ID to use instead of inline spec examples (full UUID or 4+ char prefix)").option("--parent-model <id>", "Fine-tuned model ID to continue training from (full UUID or 4+ char prefix)").option("--train-ratio <ratio>", "Dataset training split ratio (default: 0.8 when any split ratio is set)").option("--validation-ratio <ratio>", "Dataset validation split ratio (default: 0.1 when any split ratio is set)").option("--test-ratio <ratio>", "Dataset test split ratio (default: 0.1 when any split ratio is set)").option("--lora-rank <n>", "LoRA rank").option("--lora-alpha <n>", "LoRA alpha").option("--max-eval-examples <n>", "Max examples for the primary eval pass").option("--max-test-eval-examples <n>", "Max examples for the secondary test eval pass").option("--long-examples <policy>", "Long training row policy: error, truncate, or skip").option("--max-seq-length <tokens>", "Maximum training sequence length in tokens").option("--max-output-tokens <tokens>", "Desired evaluation output budget in tokens").option("--eval-reserved-output-tokens <tokens>", "Minimum evaluation output tokens reserved per row");
|
|
767
914
|
}
|
|
@@ -959,6 +1106,33 @@ Eval Results (${data._evals.length}):`);
|
|
|
959
1106
|
if (isJsonMode()) return printJson(data);
|
|
960
1107
|
printDiagnostics(data);
|
|
961
1108
|
});
|
|
1109
|
+
runs.command("report").description("Show run metrics and side-by-side eval output insights").argument("<id>", "Run ID (full UUID or 8+ char prefix)").option("--split <split>", "Split to show: primary, test, or all", "primary").option("--limit <n>", "Number of examples to show", "5").option("--mode <mode>", "Example mode: regressions or failures", "regressions").action(async (id, cmdOpts) => {
|
|
1110
|
+
const opts = parent.opts();
|
|
1111
|
+
const fullId = await resolveRunId(id, opts);
|
|
1112
|
+
const { data } = await get(
|
|
1113
|
+
`/runs/${fullId}/report`,
|
|
1114
|
+
void 0,
|
|
1115
|
+
opts
|
|
1116
|
+
);
|
|
1117
|
+
if (isJsonMode()) return printJson(data);
|
|
1118
|
+
const split = String(cmdOpts.split);
|
|
1119
|
+
if (!["primary", "test", "all"].includes(split)) {
|
|
1120
|
+
throw new Error("--split must be one of: primary, test, all");
|
|
1121
|
+
}
|
|
1122
|
+
const mode = String(cmdOpts.mode);
|
|
1123
|
+
if (!["regressions", "failures"].includes(mode)) {
|
|
1124
|
+
throw new Error("--mode must be one of: regressions, failures");
|
|
1125
|
+
}
|
|
1126
|
+
const limit = Number(cmdOpts.limit);
|
|
1127
|
+
if (!Number.isFinite(limit) || limit < 0) {
|
|
1128
|
+
throw new Error("--limit must be a non-negative number");
|
|
1129
|
+
}
|
|
1130
|
+
printRunReport(data, {
|
|
1131
|
+
split,
|
|
1132
|
+
mode,
|
|
1133
|
+
limit: Math.floor(limit)
|
|
1134
|
+
});
|
|
1135
|
+
});
|
|
962
1136
|
}
|
|
963
1137
|
|
|
964
1138
|
// src/commands/datasets.ts
|
|
@@ -3963,7 +4137,7 @@ function registerPushCommand(parent) {
|
|
|
3963
4137
|
|
|
3964
4138
|
// src/index.ts
|
|
3965
4139
|
var program = new Command();
|
|
3966
|
-
program.name("tt").description("Tuned Tensor CLI \u2014 fine-tune and evaluate LLMs").version("0.4.
|
|
4140
|
+
program.name("tt").description("Tuned Tensor CLI \u2014 fine-tune and evaluate LLMs").version("0.4.23").option("-k, --api-key <key>", "API key (overrides stored key)").option(
|
|
3967
4141
|
"-u, --base-url <url>",
|
|
3968
4142
|
"API base URL (default: https://tunedtensor.com)"
|
|
3969
4143
|
).option("--json", "Output raw JSON").option("--no-color", "Disable colors").hook("preAction", (_thisCommand, actionCommand) => {
|