@tuned-tensor/cli 0.17.0 → 0.18.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 +33 -28
- package/dist/{chunk-KRKISPHG.js → chunk-U5HNS367.js} +319 -40
- package/dist/chunk-U5HNS367.js.map +1 -0
- package/dist/index.js +290 -341
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +26 -39
- package/dist/local-runtime.js.map +1 -1
- package/docs/local-runtime/aws-gpu.md +28 -27
- package/docs/local-runtime/serving.md +2 -2
- package/docs/local-runtime/spark.md +12 -17
- package/docs/spec-architecture.html +18 -18
- package/docs/spec-workflow.md +27 -6
- package/examples/single-spec/adapter/tunedtensor.json +49 -0
- package/examples/single-spec/foundation/tunedtensor.json +31 -0
- package/package.json +4 -3
- package/training/adapter/src/serve.py +6 -0
- package/dist/chunk-KRKISPHG.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Use your AWS GPU with the local pipeline
|
|
2
2
|
|
|
3
|
-
TT runs one pipeline on your laptop. Add `gpu` to `
|
|
3
|
+
TT runs one pipeline on your laptop. Add `runtime.gpu` to `tunedtensor.json` to send
|
|
4
4
|
adapter training and CUDA evaluation, or foundation pretraining, fine-tuning,
|
|
5
5
|
RL and evaluation, to an existing EC2 instance in your account. Foundation
|
|
6
6
|
tokenization, dataset preparation, adapter scoring/comparisons, run history,
|
|
@@ -52,21 +52,23 @@ only needs `ec2:DescribeInstances` for lookup.
|
|
|
52
52
|
|
|
53
53
|
## 3. Configure the connection
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
Add this `runtime` section to your existing `tunedtensor.json`, replacing the
|
|
56
|
+
example values with your own. All paths refer to the laptop; TT translates
|
|
57
|
+
paths needed remotely. Existing sidecars can be consolidated with
|
|
58
|
+
`tt pipeline migrate`, which retains `.bak` copies.
|
|
59
59
|
|
|
60
60
|
```json
|
|
61
61
|
{
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
"runtime": {
|
|
63
|
+
"gpu": {
|
|
64
|
+
"provider": "aws",
|
|
65
|
+
"profile": "research",
|
|
66
|
+
"region": "eu-west-1",
|
|
67
|
+
"instanceId": "i-0123456789abcdef0",
|
|
68
|
+
"user": "ubuntu",
|
|
69
|
+
"identityFile": "~/.ssh/research-gpu.pem",
|
|
70
|
+
"maxSeconds": 86400
|
|
71
|
+
}
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
```
|
|
@@ -94,7 +96,7 @@ Verify the instance's SSH host key for the selected IP address and connect
|
|
|
94
96
|
once yourself before using TT. TT requires a known host key and noninteractive
|
|
95
97
|
SSH authentication. Omit
|
|
96
98
|
`identityFile` to use your SSH agent or SSH configuration. Relative identity
|
|
97
|
-
file paths resolve beside the
|
|
99
|
+
file paths resolve beside the core spec; `~` expands locally. Set
|
|
98
100
|
`privateIp: true` when connected to the instance's VPC through a VPN or other
|
|
99
101
|
private network. The default uses its public IPv4 address. Security groups and
|
|
100
102
|
network routes must allow the laptop to reach that address over SSH.
|
|
@@ -137,28 +139,27 @@ Run these commands on your laptop in the project directory. Adapter workflow:
|
|
|
137
139
|
```bash
|
|
138
140
|
# Fetch the base model locally first.
|
|
139
141
|
tt validate tunedtensor.json
|
|
140
|
-
tt models prefetch tunedtensor.json
|
|
141
|
-
tt doctor tunedtensor.json
|
|
142
|
-
tt pipeline run --spec tunedtensor.json --
|
|
143
|
-
tt pipeline run --spec tunedtensor.json
|
|
142
|
+
tt models prefetch tunedtensor.json
|
|
143
|
+
tt doctor tunedtensor.json
|
|
144
|
+
tt pipeline run --spec tunedtensor.json --dry-run
|
|
145
|
+
tt pipeline run --spec tunedtensor.json
|
|
144
146
|
```
|
|
145
147
|
|
|
146
148
|
For a foundation spec, no base-model prefetch is needed. Start a new run with:
|
|
147
149
|
|
|
148
150
|
```bash
|
|
149
151
|
tt validate tunedtensor.json
|
|
150
|
-
tt doctor tunedtensor.json
|
|
151
|
-
tt pipeline run --spec tunedtensor.json
|
|
152
|
+
tt doctor tunedtensor.json
|
|
153
|
+
tt pipeline run --spec tunedtensor.json \
|
|
152
154
|
--dry-run
|
|
153
|
-
tt pipeline run --spec tunedtensor.json
|
|
155
|
+
tt pipeline run --spec tunedtensor.json
|
|
154
156
|
```
|
|
155
157
|
|
|
156
158
|
Choose the command block for your spec's engine. `tt doctor` connects to the
|
|
157
159
|
instance and prepares/probes its locked Python/CUDA runtime, which may take
|
|
158
160
|
time on first use. It does not start training or reserve GPU capacity.
|
|
159
161
|
`tt hardware` reports the laptop's hardware; it does not inspect the AWS
|
|
160
|
-
instance. `tt pipeline run`
|
|
161
|
-
spec when `--config` is omitted.
|
|
162
|
+
instance. `tt pipeline run` reads `runtime.gpu` from the selected spec.
|
|
162
163
|
|
|
163
164
|
Keep pipeline step targets set to `local`: the target describes the
|
|
164
165
|
orchestrator, and `gpu` selects where its GPU processes execute. CPU adapter
|
|
@@ -172,10 +173,10 @@ Wait for a successful completion message. Adapter runs return a run ID and a
|
|
|
172
173
|
local model ID; use the actual IDs from the output:
|
|
173
174
|
|
|
174
175
|
```bash
|
|
175
|
-
tt runs list
|
|
176
|
-
tt runs report RUN_ID
|
|
177
|
-
tt models list
|
|
178
|
-
tt models verify MODEL_ID
|
|
176
|
+
tt runs list
|
|
177
|
+
tt runs report RUN_ID
|
|
178
|
+
tt models list
|
|
179
|
+
tt models verify MODEL_ID
|
|
179
180
|
```
|
|
180
181
|
|
|
181
182
|
For foundation runs, inspect `report.json` in the run directory printed by the
|
|
@@ -38,7 +38,7 @@ not mutate the training runtime.
|
|
|
38
38
|
```bash
|
|
39
39
|
tt models prefetch tunedtensor.json
|
|
40
40
|
|
|
41
|
-
tt serve local-<run-id>
|
|
41
|
+
tt serve local-<run-id> \
|
|
42
42
|
--context-length 8192 --max-tokens 512
|
|
43
43
|
```
|
|
44
44
|
|
|
@@ -81,7 +81,7 @@ Export a new isolated configuration rather than replacing global settings:
|
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
83
|
export PI_CODING_AGENT_DIR="$(mktemp -d)"
|
|
84
|
-
tt serve local-<run-id>
|
|
84
|
+
tt serve local-<run-id> \
|
|
85
85
|
--context-length 8192 --max-tokens 512 \
|
|
86
86
|
--print-client-config pi > "$PI_CODING_AGENT_DIR/models.json"
|
|
87
87
|
```
|
|
@@ -78,28 +78,23 @@ Edit both generated examples in `tunedtensor.json`. For a meaningful run,
|
|
|
78
78
|
replace them with a larger, representative dataset and a separate validation
|
|
79
79
|
split.
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
durable Spark
|
|
81
|
+
Initialization creates only `tunedtensor.json`; CUDA and project-local storage
|
|
82
|
+
are defaults. For durable Spark paths, add this optional section to the spec:
|
|
83
83
|
|
|
84
84
|
```json
|
|
85
85
|
{
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"modelCache": "/home/eve/.cache/huggingface"
|
|
90
|
-
},
|
|
91
|
-
"evaluation": {
|
|
92
|
-
"inference": {
|
|
93
|
-
"device": "cuda"
|
|
94
|
-
},
|
|
95
|
-
"scoring": {
|
|
96
|
-
"mode": "exact_match"
|
|
97
|
-
},
|
|
98
|
-
"timeoutMs": 1800000
|
|
86
|
+
"runtime": {
|
|
87
|
+
"artifactRoot": "/home/eve/tuned-tensor-runs/artifacts",
|
|
88
|
+
"storeRoot": "/home/eve/tuned-tensor-runs/store",
|
|
89
|
+
"paths": { "modelCache": "/home/eve/.cache/huggingface" }
|
|
99
90
|
}
|
|
100
91
|
}
|
|
101
92
|
```
|
|
102
93
|
|
|
94
|
+
Existing projects can use `tt pipeline migrate` to fold sidecar settings into
|
|
95
|
+
the spec while keeping backups.
|
|
96
|
+
|
|
97
|
+
|
|
103
98
|
Every Python stage uses the locked runtime included in the npm package; a
|
|
104
99
|
source checkout and a custom runner path are neither required nor supported.
|
|
105
100
|
|
|
@@ -110,7 +105,7 @@ tt doctor tunedtensor.json
|
|
|
110
105
|
tt validate tunedtensor.json
|
|
111
106
|
tt models prefetch tunedtensor.json
|
|
112
107
|
tt models verify-base tunedtensor.json
|
|
113
|
-
tt pipeline run --spec tunedtensor.json
|
|
108
|
+
tt pipeline run --spec tunedtensor.json
|
|
114
109
|
```
|
|
115
110
|
|
|
116
111
|
`doctor` resolves the same bundled project and paths the run will use, imports
|
|
@@ -136,7 +131,7 @@ tt serve local-<run-id> --spec tunedtensor.json --port 8000
|
|
|
136
131
|
```
|
|
137
132
|
|
|
138
133
|
`tt serve active` fails unless an adapter is activated. Activation requires a
|
|
139
|
-
`generalRegression` suite in `
|
|
134
|
+
`generalRegression` suite in `tunedtensor.json`. Serve a specific adapter
|
|
140
135
|
with `tt serve local-<run-id>` until then.
|
|
141
136
|
|
|
142
137
|
In another shell:
|
|
@@ -85,9 +85,9 @@
|
|
|
85
85
|
<div class="tt-level">
|
|
86
86
|
<div class="tt-unit"><button class="btn" type="button" data-component="validation" aria-pressed="false">Shared validation</button><small class="text-small">schema → readiness checks</small></div>
|
|
87
87
|
<div class="tt-unit"><button class="btn" type="button" data-component="plan" aria-pressed="false">Derive the pipeline</button><small class="text-small">pipelineForRunInput → plan</small></div>
|
|
88
|
-
<div class="tt-unit"><button class="btn" type="button" data-component="config" aria-pressed="false">
|
|
88
|
+
<div class="tt-unit"><button class="btn" type="button" data-component="config" aria-pressed="false">Spec runtime and evaluation</button><small class="text-small">GPU placement · paths · scoring</small></div>
|
|
89
89
|
</div>
|
|
90
|
-
<div class="tt-arrow text-small">↓ same spec and plan · preview or explicit run ·
|
|
90
|
+
<div class="tt-arrow text-small">↓ same spec and plan · preview or explicit run · core spec supplies execution policy</div>
|
|
91
91
|
<div class="tt-boundary text-small">03 / EXECUTE & EVALUATE</div>
|
|
92
92
|
<div class="tt-level tt-pair">
|
|
93
93
|
<div class="tt-unit"><button class="btn" type="button" data-component="adapter" aria-pressed="false">Adapter engine</button><small class="text-small">baseline → LoRA → candidate → compare</small></div>
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
"summary": "One saved recipe for instructions, examples, engine and training parameters.",
|
|
168
168
|
"input": "Reviewed user intent: prompt, guidelines, constraints, examples and settings.",
|
|
169
169
|
"output": "One parsed snapshot, identified by a SHA-256, for planning and execution.",
|
|
170
|
-
"boundary": "The spec defines behavior and training.
|
|
170
|
+
"boundary": "The spec defines behavior and training. Its runtime and evaluation sections define placement, paths and evaluation policy.",
|
|
171
171
|
"flow": [
|
|
172
172
|
"read bytes",
|
|
173
173
|
"parse once",
|
|
@@ -185,26 +185,26 @@
|
|
|
185
185
|
"example": "/spec\n/spec diff\n/spec validate\n/spec history\n\n# Outside the TT shell:\ntt spec show --json"
|
|
186
186
|
},
|
|
187
187
|
"config": {
|
|
188
|
-
"title": "
|
|
188
|
+
"title": "Spec runtime and evaluation",
|
|
189
189
|
"location": "Project · JSON",
|
|
190
190
|
"summary": "Execution placement, filesystem paths and evaluation policy remain explicit runtime choices.",
|
|
191
|
-
"input": "
|
|
191
|
+
"input": "runtime and evaluation sections in tunedtensor.json; legacy --config remains compatible.",
|
|
192
192
|
"output": "GPU dispatch, artifact locations, inference/scoring configuration and regression policy.",
|
|
193
|
-
"boundary": "
|
|
193
|
+
"boundary": "These optional sections belong to the core spec. Runtime defaults apply when omitted; legacy sidecars can be migrated with backups.",
|
|
194
194
|
"flow": [
|
|
195
195
|
"config",
|
|
196
196
|
"resolve paths",
|
|
197
197
|
"choose process host"
|
|
198
198
|
],
|
|
199
|
-
"path": "examples/
|
|
199
|
+
"path": "examples/single-spec/adapter/tunedtensor.json",
|
|
200
200
|
"line": 1,
|
|
201
|
-
"code": "{\n \"
|
|
201
|
+
"code": "{\n \"id\": \"22222222-2222-4222-8222-222222222222\",\n \"name\": \"Local Smoke Assistant\",\n \"description\": \"Tiny local runner smoke test.\",\n \"system_prompt\": \"Return exactly one sentiment label.\",\n \"guidelines\": [\n \"Reply with positive or negative.\"\n ],\n \"constraints\": [\n \"Do not include extra commentary.\"\n ],\n \"base_model\": \"Qwen/Qwen3.5-2B\",\n \"examples\": [\n {\n \"input\": \"Classify sentiment: I love this product.\",\n \"output\": \"positive\"\n },\n {\n \"input\": \"Classify sentiment: This was disappointing.\",\n \"output\": \"negative\"\n },\n {\n \"input\": \"Classify sentiment: The update works perfectly.\",\n \"output\": \"positive\"\n },\n {\n \"input\": \"Classify sentiment: Support never replied.\",\n \"output\": \"negative\"\n }\n ],\n \"hyperparameters\": {\n \"n_epochs\": 1,\n \"learning_rate\": 0.0002,\n \"batch_size\": 1,\n \"gradient_accumulation_steps\": 1,\n \"lora_rank\": 4,\n \"lora_alpha\": 8,\n \"max_seq_length\": 256,\n \"max_eval_examples\": 2\n },\n \"evaluation\": {\n \"inference\": {\n \"maxNewTokens\": 32\n },\n \"scoring\": {\n \"mode\": \"exact_match\"\n }\n }\n}\n",
|
|
202
202
|
"files": [
|
|
203
|
-
"examples/
|
|
203
|
+
"examples/single-spec/adapter/tunedtensor.json",
|
|
204
204
|
"src/local-runtime/contracts.ts",
|
|
205
205
|
"src/commands/pipeline.ts"
|
|
206
206
|
],
|
|
207
|
-
"example": "tt doctor tunedtensor.json
|
|
207
|
+
"example": "tt doctor tunedtensor.json\ntt pipeline run --spec tunedtensor.json --dry-run"
|
|
208
208
|
},
|
|
209
209
|
"agent": {
|
|
210
210
|
"title": "Agent & model provider",
|
|
@@ -242,8 +242,8 @@
|
|
|
242
242
|
"applySpecUpdate"
|
|
243
243
|
],
|
|
244
244
|
"path": "src/spec-workspace.ts",
|
|
245
|
-
"line":
|
|
246
|
-
"code": "export async function prepareSpecUpdate(workspaceRoot: string, specPath: string, expectedSha256: string, changes: unknown) {\n const current = await inspectLocalSpec(workspaceRoot, specPath);\n if (current.sha256 !== expectedSha256) throw new Error(\"The behavior spec changed; inspect it and prepare the edit again.\");\n if (!isObject(current.document)) throw new Error(\"Repair the spec JSON before preparing an edit.\");\n if (!isObject(changes) || !Object.keys(changes).length || Object.keys(changes).some(key => !EDIT_KEYS.has(key))) {\n throw new Error(\"Provide supported spec field changes; identity and engine cannot be changed by an edit.\");\n }\n const next = { ...current.document, ...changes };\n for (const key of [\"hyperparameters\", \"foundation\"] as const) {\n if (isObject(changes[key])) {\n const previous = isObject(current.document[key]) ? current.document[key] : {};\n next[key] = { ...previous, ...changes[key] };\n }\n }\n const validation = validateDocument(next, current.path);\n if (!validation.valid) throw new Error(`Invalid behavior spec:\\n${validation.errors.join(\"\\n\")}`)
|
|
245
|
+
"line": 138,
|
|
246
|
+
"code": "export async function prepareSpecUpdate(workspaceRoot: string, specPath: string, expectedSha256: string, changes: unknown) {\n const current = await inspectLocalSpec(workspaceRoot, specPath);\n if (current.sha256 !== expectedSha256) throw new Error(\"The behavior spec changed; inspect it and prepare the edit again.\");\n if (!isObject(current.document)) throw new Error(\"Repair the spec JSON before preparing an edit.\");\n if (!isObject(changes) || !Object.keys(changes).length || Object.keys(changes).some(key => !EDIT_KEYS.has(key))) {\n throw new Error(\"Provide supported spec field changes; identity and engine cannot be changed by an edit.\");\n }\n const next = { ...current.document, ...changes };\n function mergeSettings(before: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown> {\n const result = { ...before };\n for (const [key, value] of Object.entries(patch)) {\n if (value === null) delete result[key];\n else result[key] = isObject(value) && isObject(result[key]) ? mergeSettings(result[key], value) : value;\n }\n return result;\n }\n for (const key of [\"runtime\", \"evaluation\", \"pipeline\"]) {\n if (changes[key] === null) delete next[key];\n else if (key !== \"pipeline\" && isObject(changes[key])) next[key] = mergeSettings(isObject(current.document[key]) ? current.document[key] : {}, changes[key]);\n }\n for (const key of [\"hyperparameters\", \"foundation\"] as const) {\n if (isObject(changes[key])) {\n const previous = isObject(current.document[key]) ? current.document[key] : {};\n next[key] = { ...previous, ...changes[key] };\n }\n }\n const validation = validateDocument(next, current.path);\n if (!validation.valid) throw new Error(`Invalid behavior spec:\\n${validation.errors.join(\"\\n\")}`);",
|
|
247
247
|
"files": [
|
|
248
248
|
"src/spec-workspace.ts",
|
|
249
249
|
"src/agent-approval.ts",
|
|
@@ -264,8 +264,8 @@
|
|
|
264
264
|
"preview / run"
|
|
265
265
|
],
|
|
266
266
|
"path": "src/pipeline.ts",
|
|
267
|
-
"line":
|
|
268
|
-
"code": "export function pipelineForRunInput(input: LocalRunInput, requested?: unknown): NormalizedPipeline {\n if (input.kind === \"request\") {\n // Preserve the direct CLI's full run-request compatibility.\n assertLocalRunInputReady(input.request);\n } else {\n const validation = validateBehaviorSpec(input);\n if (!validation.valid) {\n throw new Error(`Invalid behavior spec:\\n${validation.errors.join(\"\\n\")}`);\n }\n }\n\n const foundation = input.kind === \"foundation-spec\";\n const recipe = requested !== undefined ? requested : (foundation\n ? pipelineFromFoundationHyperparameters(input.spec.name, input.spec.foundation)\n : canonicalPipeline(\"local\"));\n const pipeline = parsePipeline(recipe);\n if (isFoundationPipeline(pipeline) !== foundation) {\n const recipeEngine = isFoundationPipeline(pipeline) ? \"a foundation\" : \"an adapter\";\n const specEngine = foundation ? \"a foundation\" : \"an adapter\";\n throw new Error(`Pipeline is ${recipeEngine} recipe, but the behavior spec is ${specEngine} spec. Use a matching recipe or omit --file.`);\n }\n if (foundation) assertFoundationSettingsMatch(input.spec.foundation, pipeline);\n return pipeline;\n}",
|
|
267
|
+
"line": 197,
|
|
268
|
+
"code": "export function pipelineForRunInput(input: LocalRunInput, requested?: unknown): NormalizedPipeline {\n if (input.kind === \"request\") {\n // Preserve the direct CLI's full run-request compatibility.\n assertLocalRunInputReady(input.request);\n } else {\n const validation = validateBehaviorSpec(input);\n if (!validation.valid) {\n throw new Error(`Invalid behavior spec:\\n${validation.errors.join(\"\\n\")}`);\n }\n }\n\n const foundation = input.kind === \"foundation-spec\";\n const embedded = input.kind === \"request\" ? undefined : input.spec.pipeline;\n if (embedded && requested !== undefined && canonicalJson(parsePipeline(embedded)) !== canonicalJson(parsePipeline(requested))) {\n throw new Error(\"Conflicting pipeline settings: update tunedtensor.json.pipeline before running a different recipe.\");\n }\n const recipe = requested !== undefined ? requested : embedded ?? (foundation\n ? pipelineFromFoundationHyperparameters(input.spec.name, input.spec.foundation)\n : canonicalPipeline(\"local\"));\n const pipeline = parsePipeline(recipe);\n if (isFoundationPipeline(pipeline) !== foundation) {\n const recipeEngine = isFoundationPipeline(pipeline) ? \"a foundation\" : \"an adapter\";\n const specEngine = foundation ? \"a foundation\" : \"an adapter\";\n throw new Error(`Pipeline is ${recipeEngine} recipe, but the behavior spec is ${specEngine} spec. Use a matching recipe or omit --file.`);\n }\n if (foundation) assertFoundationSettingsMatch(input.spec.foundation, pipeline);\n return pipeline;\n}",
|
|
269
269
|
"files": [
|
|
270
270
|
"src/pipeline.ts",
|
|
271
271
|
"src/commands/pipeline.ts",
|
|
@@ -314,7 +314,7 @@
|
|
|
314
314
|
],
|
|
315
315
|
"path": "src/local-runtime/foundation-runner.ts",
|
|
316
316
|
"line": 271,
|
|
317
|
-
"code": "export async function runFoundationPipeline(args: {\n spec: LocalFoundationSpecFile;\n plan: ExecutionPlan;\n specPath: string;\n outputDir?: string;\n resume?: boolean;\n spawnStep?: FoundationStepSpawn;\n gpu?: AwsGpuConfig;\n}): Promise<FoundationPipelineResult> {\n assertFoundationPlanSupported(args.spec, args.plan);\n assertFoundationSettingsMatch(args.spec.foundation, args.plan);\n const outputDir = resolve(\n args.outputDir ?? join(dirname(resolve(args.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID()),\n );\n if (args.gpu && args.spec.foundation.checkpoint_backup_dir\n && localPathsOverlap(outputDir, args.spec.foundation.checkpoint_backup_dir)) {\n throw new Error(\"AWS checkpoint_backup_dir must be separate from the foundation run directory; overlapping backups can overwrite returned checkpoints.\");\n }",
|
|
317
|
+
"code": "export async function runFoundationPipeline(args: {\n spec: LocalFoundationSpecFile;\n plan: ExecutionPlan;\n specPath: string;\n outputDir?: string;\n resume?: boolean;\n spawnStep?: FoundationStepSpawn;\n gpu?: AwsGpuConfig;\n}): Promise<FoundationPipelineResult> {\n assertFoundationPlanSupported(args.spec, args.plan);\n assertFoundationSettingsMatch(args.spec.foundation, args.plan);\n const outputDir = resolve(\n args.outputDir ?? (args.spec.runtime?.artifactRoot\n ? join(resolve(dirname(args.specPath), args.spec.runtime.artifactRoot), \"foundation-runs\", randomUUID())\n : join(dirname(resolve(args.specPath)), DEFAULT_FOUNDATION_RUNS_DIR, randomUUID())),\n );\n if (args.gpu && args.spec.foundation.checkpoint_backup_dir\n && localPathsOverlap(outputDir, args.spec.foundation.checkpoint_backup_dir)) {\n throw new Error(\"AWS checkpoint_backup_dir must be separate from the foundation run directory; overlapping backups can overwrite returned checkpoints.\");\n }\n let outputExists = false;\n try {\n const outputInfo = await lstat(outputDir);\n if (!args.resume) {\n throw new Error(`Foundation output directory already exists; pass --resume ${outputDir} to recover it.`);\n }\n if (outputInfo.isSymbolicLink()) {\n throw new Error(`Foundation resume directory must not be a symbolic link: ${outputDir}`);\n }\n if (!outputInfo.isDirectory()) {\n throw new Error(`Foundation resume path must be a directory: ${outputDir}`);\n }\n outputExists = true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n }\n if (!outputExists) {\n await mkdir(outputDir, { recursive: true, mode: 0o700 });\n }\n await makePrivateTree(outputDir);\n const workflowPath = join(outputDir, \"resolved-workflow.json\");\n const workflow = { spec: args.spec, plan: args.plan };\n if (outputExists) {\n const previous = await readFile(workflowPath, \"utf8\").catch((error) => {\n if (error.code === \"ENOENT\") return undefined;\n throw error;\n });\n if (previous && JSON.stringify(JSON.parse(previous)) !== JSON.stringify(workflow)) {\n throw new Error(\"Foundation workflow configuration changed; resume requires the original resolved workflow.\");\n }\n }\n await writePrivateTextAtomic(workflowPath, `${JSON.stringify(workflow, null, 2)}\\n`);",
|
|
318
318
|
"files": [
|
|
319
319
|
"src/local-runtime/foundation-runner.ts",
|
|
320
320
|
"src/pipeline.ts",
|
|
@@ -345,7 +345,7 @@
|
|
|
345
345
|
"src/local-runtime/process-runner.ts",
|
|
346
346
|
"docs/local-runtime/aws-gpu.md"
|
|
347
347
|
],
|
|
348
|
-
"example": "tt doctor tunedtensor.json
|
|
348
|
+
"example": "tt doctor tunedtensor.json\ntt pipeline run --spec tunedtensor.json --dry-run\n# After checking the preview and GPU readiness:\ntt pipeline run --spec tunedtensor.json"
|
|
349
349
|
},
|
|
350
350
|
"lora": {
|
|
351
351
|
"title": "LoRA training implementation",
|
|
@@ -447,7 +447,7 @@
|
|
|
447
447
|
"src/paths.ts",
|
|
448
448
|
"src/agent-store.ts"
|
|
449
449
|
],
|
|
450
|
-
"example": "~/.tuned-tensor/config.json\n~/.tuned-tensor/agent/\n~/.tuned-tensor/cache/\n<project>/.tuned-tensor/artifacts/\n<project>/.tuned-tensor/store/ #
|
|
450
|
+
"example": "~/.tuned-tensor/config.json\n~/.tuned-tensor/agent/\n~/.tuned-tensor/cache/\n<project>/.tuned-tensor/artifacts/\n<project>/.tuned-tensor/store/ # default for spec projects\n<spec-directory>/.tuned-tensor/foundation-runs/\n# Store fallback: ~/.tuned-tensor/store/"
|
|
451
451
|
},
|
|
452
452
|
"serve": {
|
|
453
453
|
"title": "Serving & API clients",
|
|
@@ -472,7 +472,7 @@
|
|
|
472
472
|
"training/serving/pyproject.toml",
|
|
473
473
|
"docs/local-runtime/serving.md"
|
|
474
474
|
],
|
|
475
|
-
"example": "tt serve local-<run-id
|
|
475
|
+
"example": "tt serve local-<run-id>\n# Or a foundation checkpoint:\ntt serve foundation --checkpoint <model-dir> --tokenizer <tokenizer.json>"
|
|
476
476
|
},
|
|
477
477
|
"publish": {
|
|
478
478
|
"title": "Publish & account operations",
|
package/docs/spec-workflow.md
CHANGED
|
@@ -47,7 +47,8 @@ for an invalid spec.
|
|
|
47
47
|
`get_local_spec` returns the document, validation diagnostics and content hash.
|
|
48
48
|
`prepare_update_local_spec` requires that hash and a patch of supported fields.
|
|
49
49
|
Arrays replace their whole field; `hyperparameters` and `foundation` merge by
|
|
50
|
-
key.
|
|
50
|
+
key. `runtime` and `evaluation` merge recursively; null removes optional settings.
|
|
51
|
+
`pipeline` replaces the entire recipe (null restores the derived default). Unmentioned fields remain unchanged. Identity and engine cannot change
|
|
51
52
|
through this edit tool. The full resulting document must pass the runtime's
|
|
52
53
|
strict schema, readiness checks, nonblank content checks and distinct-example
|
|
53
54
|
checks. Small example sets produce a warning; validation is not a claim of model
|
|
@@ -82,7 +83,7 @@ tt pipeline run --spec tunedtensor.json --dry-run
|
|
|
82
83
|
tt pipeline run --spec tunedtensor.json
|
|
83
84
|
```
|
|
84
85
|
|
|
85
|
-
The
|
|
86
|
+
The pipeline uses the optional `pipeline` section or derives from the current spec. An adjacent
|
|
86
87
|
`tunedtensor.pipeline.json` is no longer loaded implicitly. To select an advanced
|
|
87
88
|
recipe explicitly, pass `--file`; a missing explicit recipe or `--spec` file fails. Foundation
|
|
88
89
|
training parameters in an explicit recipe must agree with the spec, including
|
|
@@ -100,10 +101,30 @@ The command reads one spec snapshot for planning, hardware warnings and executio
|
|
|
100
101
|
JSON and final result JSON identify its path and SHA-256. The spec's compiled
|
|
101
102
|
instructions and examples feed training and task evaluation; the independently
|
|
102
103
|
configured general-regression suite keeps its own prompt. Provider choice and
|
|
103
|
-
GPU placement
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
GPU placement is configured in `runtime.gpu` in the spec. Model-provider choice
|
|
105
|
+
remains in agent configuration. Evaluation policy belongs in `evaluation`;
|
|
106
|
+
artifact/store locations and model paths belong in `runtime`. All relative paths
|
|
107
|
+
resolve beside the spec. New projects default to `.tuned-tensor/artifacts` and
|
|
108
|
+
`.tuned-tensor/store` beside it (unless `TT_LOCAL_HOME` overrides the store).
|
|
109
|
+
Foundation supports `runtime.artifactRoot` and `runtime.gpu`; its evaluation
|
|
110
|
+
and training settings remain under `foundation`.
|
|
111
|
+
|
|
112
|
+
Only `tunedtensor.json` is required. `tt pipeline init --spec tunedtensor.json`
|
|
113
|
+
embeds an advanced recipe when needed; omit this step to keep automatic derivation.
|
|
114
|
+
An explicit `--file` cannot override a conflicting embedded recipe.
|
|
115
|
+
|
|
116
|
+
For existing projects, run `tt pipeline migrate --spec tunedtensor.json` to
|
|
117
|
+
consolidate adjacent pipeline and runner files. It validates the result first,
|
|
118
|
+
keeps exclusive `.bak` copies, and preserves resolved paths and the old store
|
|
119
|
+
location. Conflicting sources, enabled legacy `dryRun`, or unsupported foundation
|
|
120
|
+
runner settings require reconciliation first. Existing `.bak` files are never
|
|
121
|
+
overwritten. Avoid concurrent edits during migration. Legacy runner files remain
|
|
122
|
+
readable with a warning, but cannot coexist with inline runtime/evaluation settings.
|
|
123
|
+
A legacy adjacent pipeline is used only with explicit `--file` or after migration.
|
|
124
|
+
|
|
125
|
+
Runs write generated `resolved-workflow.json` snapshots for diagnosis and
|
|
126
|
+
reproducibility; these are outputs, not additional user-authored configuration.
|
|
127
|
+
|
|
107
128
|
|
|
108
129
|
Agent `/approve` for a pipeline still runs a preview only. Real training uses
|
|
109
130
|
the direct command above. The spec is the behavior recipe; approval of a spec
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "22222222-2222-4222-8222-222222222222",
|
|
3
|
+
"name": "Local Smoke Assistant",
|
|
4
|
+
"description": "Tiny local runner smoke test.",
|
|
5
|
+
"system_prompt": "Return exactly one sentiment label.",
|
|
6
|
+
"guidelines": [
|
|
7
|
+
"Reply with positive or negative."
|
|
8
|
+
],
|
|
9
|
+
"constraints": [
|
|
10
|
+
"Do not include extra commentary."
|
|
11
|
+
],
|
|
12
|
+
"base_model": "Qwen/Qwen3.5-2B",
|
|
13
|
+
"examples": [
|
|
14
|
+
{
|
|
15
|
+
"input": "Classify sentiment: I love this product.",
|
|
16
|
+
"output": "positive"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"input": "Classify sentiment: This was disappointing.",
|
|
20
|
+
"output": "negative"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"input": "Classify sentiment: The update works perfectly.",
|
|
24
|
+
"output": "positive"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"input": "Classify sentiment: Support never replied.",
|
|
28
|
+
"output": "negative"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"hyperparameters": {
|
|
32
|
+
"n_epochs": 1,
|
|
33
|
+
"learning_rate": 0.0002,
|
|
34
|
+
"batch_size": 1,
|
|
35
|
+
"gradient_accumulation_steps": 1,
|
|
36
|
+
"lora_rank": 4,
|
|
37
|
+
"lora_alpha": 8,
|
|
38
|
+
"max_seq_length": 256,
|
|
39
|
+
"max_eval_examples": 2
|
|
40
|
+
},
|
|
41
|
+
"evaluation": {
|
|
42
|
+
"inference": {
|
|
43
|
+
"maxNewTokens": 32
|
|
44
|
+
},
|
|
45
|
+
"scoring": {
|
|
46
|
+
"mode": "exact_match"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"engine": "foundation",
|
|
3
|
+
"name": "Foundation smoke chat",
|
|
4
|
+
"description": "Tiny from-scratch tokenizer, pretrain, chat SFT, and eval.",
|
|
5
|
+
"system_prompt": "You are a helpful assistant.",
|
|
6
|
+
"guidelines": [
|
|
7
|
+
"Answer the user request directly."
|
|
8
|
+
],
|
|
9
|
+
"constraints": [],
|
|
10
|
+
"examples": [
|
|
11
|
+
{
|
|
12
|
+
"input": "Say hello in one sentence.",
|
|
13
|
+
"output": "Hello \u2014 how can I help you today?"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"input": "What is 2 + 2?",
|
|
17
|
+
"output": "2 + 2 = 4."
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"foundation": {
|
|
21
|
+
"vocab_size": 256,
|
|
22
|
+
"max_chars": 20000,
|
|
23
|
+
"depth": 2,
|
|
24
|
+
"pretrain_steps": 2,
|
|
25
|
+
"finetune_steps": 2,
|
|
26
|
+
"rl_steps": 0,
|
|
27
|
+
"batch_size": 2,
|
|
28
|
+
"sequence_length": 64,
|
|
29
|
+
"nproc_per_node": 1
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuned-tensor/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Local TT agent and CUDA fine-tuning CLI for Tuned Tensor workflows",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"docs/spec-workflow.md",
|
|
38
38
|
"docs/spec-architecture.html",
|
|
39
39
|
"examples/local-runtime/*.json",
|
|
40
|
-
"examples/local-runtime/*.jsonl"
|
|
40
|
+
"examples/local-runtime/*.jsonl",
|
|
41
|
+
"examples/single-spec/**/*.json"
|
|
41
42
|
],
|
|
42
43
|
"engines": {
|
|
43
44
|
"node": ">=22.19.0"
|
|
@@ -68,6 +69,6 @@
|
|
|
68
69
|
"tsup": "^8.5.1",
|
|
69
70
|
"tsx": "^4.23.12",
|
|
70
71
|
"typescript": "^5.9.3",
|
|
71
|
-
"vitest": "^4.1.
|
|
72
|
+
"vitest": "^4.1.11"
|
|
72
73
|
}
|
|
73
74
|
}
|
|
@@ -93,7 +93,13 @@ def build_vllm_args(model_source: str, adapter_path: str | None, temp_dir: Path)
|
|
|
93
93
|
# tool calls/results verbatim while merging leading system context.
|
|
94
94
|
prefix = "{%- set tt = namespace(system=" + json.dumps(prompt) + ", history=[]) -%}"
|
|
95
95
|
prefix += "{%- for message in messages -%}{%- if message.role == 'system' -%}"
|
|
96
|
+
# vLLM may normalize text into OpenAI content parts for the model's
|
|
97
|
+
# native template. Merge those parts without stringifying the list.
|
|
98
|
+
prefix += "{%- if message.content is string -%}"
|
|
96
99
|
prefix += "{%- set tt.system = tt.system + '\\n\\n' + message.content -%}"
|
|
100
|
+
prefix += "{%- else -%}{%- for part in message.content -%}{%- if part.type == 'text' -%}"
|
|
101
|
+
prefix += "{%- set tt.system = tt.system + '\\n\\n' + part.text -%}"
|
|
102
|
+
prefix += "{%- endif -%}{%- endfor -%}{%- endif -%}"
|
|
97
103
|
prefix += "{%- else -%}{%- set tt.history = tt.history + [message] -%}{%- endif -%}{%- endfor -%}"
|
|
98
104
|
prefix += "{%- set messages = [{'role': 'system', 'content': tt.system}] + tt.history -%}"
|
|
99
105
|
path = temp_dir / "chat-template.jinja"
|