@tuned-tensor/local 0.1.7 → 0.1.9
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/CHANGELOG.md +49 -1
- package/README.md +231 -207
- package/dist/contracts.d.ts +22 -5
- package/dist/contracts.js +22 -3
- package/dist/contracts.js.map +1 -1
- package/dist/evaluation.d.ts +1 -1
- package/dist/evaluation.js +54 -121
- package/dist/evaluation.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +54 -1
- package/dist/index.js.map +1 -1
- package/dist/labeling-sanitize.d.ts +31 -0
- package/dist/labeling-sanitize.js +158 -0
- package/dist/labeling-sanitize.js.map +1 -0
- package/dist/labeling.d.ts +155 -0
- package/dist/labeling.js +496 -0
- package/dist/labeling.js.map +1 -0
- package/dist/openrouter.d.ts +13 -0
- package/dist/openrouter.js +31 -4
- package/dist/openrouter.js.map +1 -1
- package/dist/process-runner.d.ts +36 -0
- package/dist/process-runner.js +132 -0
- package/dist/process-runner.js.map +1 -0
- package/dist/process-training.d.ts +1 -1
- package/dist/process-training.js +13 -53
- package/dist/process-training.js.map +1 -1
- package/dist/server.js +2 -2
- package/docs/architecture.md +1 -1
- package/docs/spark.md +1 -1
- package/examples/local-runner.json +6 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
All notable changes to
|
|
3
|
+
All notable changes to TT Local will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## 0.1.9 - 2026-07-03
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added a `tt-local label` command, porting the tuned-tensor-runs teacher
|
|
10
|
+
labeling job to the local runner. It reads unlabeled JSONL
|
|
11
|
+
(`{"input": "..."}` rows, optionally pre-labeled with `"output"`) or CSV
|
|
12
|
+
(`--input-column`) sources, labels pending rows with an OpenRouter teacher
|
|
13
|
+
model under the spec's system message, and writes a labeled
|
|
14
|
+
`{"input", "output"}` JSONL, a per-row review file, and a job report under
|
|
15
|
+
`<artifactRoot>/labeling/<job-id>/`. Supports `--dry-run`, `--output`,
|
|
16
|
+
`--model`, `--spec`, and `--system-prompt`.
|
|
17
|
+
- Rows are sanitized before leaving the machine: secret-like content (API
|
|
18
|
+
keys, private keys, connection strings, passwords) blocks the row; PII
|
|
19
|
+
(emails, phones, SSNs, card numbers) is redacted.
|
|
20
|
+
- Added a `labeling` runner-config block (`model`, `maxTokens`, `temperature`,
|
|
21
|
+
`concurrency`, `minIntervalMs`, `maxAttempts`, `maxRows`, `timeoutMs`); the
|
|
22
|
+
teacher model falls back to `llm.model`.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- `openRouterChat` now supports free-text responses (`responseFormat:
|
|
27
|
+
"text"`), `temperature`/`maxTokens` overrides, and returns prompt/completion
|
|
28
|
+
token usage; it throws a typed `OpenRouterHttpError` so callers can retry
|
|
29
|
+
retryable statuses. Judge behavior (JSON object output, temperature 0) is
|
|
30
|
+
unchanged by default.
|
|
31
|
+
|
|
32
|
+
## 0.1.8 - 2026-07-02
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Renamed the user-facing project language to TT Local with the tagline
|
|
37
|
+
"Tuning tensors locally", while keeping the `tt-local` CLI, npm package, repo,
|
|
38
|
+
and filesystem identifiers unchanged.
|
|
39
|
+
- Reworked the README around a first-time user flow: install, safe dry run,
|
|
40
|
+
real training, config basics, evaluation behavior, artifacts, Spark notes,
|
|
41
|
+
and common commands.
|
|
42
|
+
- Simplified evaluation configuration by removing `evaluation.mode`. Inference
|
|
43
|
+
is now selected with `evaluation.inference.provider`, and scoring is selected
|
|
44
|
+
with `evaluation.scoring.mode`.
|
|
45
|
+
- Shared the uv/subprocess execution helper between training and evaluation so
|
|
46
|
+
logging, timeout, and stderr handling are consistent.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- Added direct support for `evaluation.inference.provider: "command"` with
|
|
51
|
+
`baselineCommand` and `candidateCommand` for custom inference adapters.
|
|
4
52
|
|
|
5
53
|
## 0.1.7 - 2026-07-02
|
|
6
54
|
|
package/README.md
CHANGED
|
@@ -1,62 +1,55 @@
|
|
|
1
|
-
#
|
|
1
|
+
# TT Local
|
|
2
|
+
|
|
3
|
+
Tuning tensors locally.
|
|
2
4
|
|
|
3
5
|
[](https://github.com/tunedtensor/tuned-tensor-local/actions/workflows/ci.yml)
|
|
4
6
|
[](https://www.npmjs.com/package/@tuned-tensor/local)
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
jobs on a machine you control. It stores specs, datasets, model
|
|
8
|
-
progress, reports, and dashboard state on local disk.
|
|
8
|
+
TT Local is a standalone CLI for running Tuned Tensor-style
|
|
9
|
+
fine-tuning jobs on a machine you control. It stores specs, datasets, model
|
|
10
|
+
artifacts, progress events, reports, and dashboard state on local disk.
|
|
9
11
|
|
|
10
|
-
It does not require the hosted Tuned Tensor CLI, cloud orchestration, Docker,
|
|
11
|
-
a managed database.
|
|
12
|
+
It does not require the hosted Tuned Tensor CLI, cloud orchestration, Docker,
|
|
13
|
+
or a managed database.
|
|
12
14
|
|
|
13
|
-
## What
|
|
15
|
+
## What Happens In A Run
|
|
14
16
|
|
|
15
|
-
`tt-local` runs this
|
|
17
|
+
`tt-local` runs this loop:
|
|
16
18
|
|
|
17
19
|
1. Read a behavior spec from `tunedtensor.json`.
|
|
18
|
-
2. Compile examples into a training dataset.
|
|
20
|
+
2. Compile examples into a local training dataset.
|
|
19
21
|
3. Evaluate the original Hugging Face base model.
|
|
20
22
|
4. Fine-tune locally with uv, Transformers, and PEFT.
|
|
21
23
|
5. Evaluate the fine-tuned Hugging Face/PEFT artifact.
|
|
22
|
-
6. Compare baseline vs tuned outputs and write a
|
|
24
|
+
6. Compare baseline vs tuned outputs and write a report.
|
|
23
25
|
|
|
24
|
-
OpenRouter can
|
|
25
|
-
|
|
26
|
+
OpenRouter can optionally score generated outputs as an LLM judge. It does not
|
|
27
|
+
generate the baseline or tuned responses.
|
|
26
28
|
|
|
27
|
-
## Install
|
|
29
|
+
## Install
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
git clone https://github.com/tunedtensor/tuned-tensor-local.git
|
|
33
|
-
cd tuned-tensor-local
|
|
34
|
-
npm install
|
|
35
|
-
npm run build
|
|
36
|
-
npm link
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
You can install the published [@tuned-tensor/local npm package](https://www.npmjs.com/package/@tuned-tensor/local)
|
|
40
|
-
globally:
|
|
31
|
+
Install the published CLI:
|
|
41
32
|
|
|
42
33
|
```bash
|
|
43
34
|
npm install -g @tuned-tensor/local
|
|
35
|
+
tt-local info
|
|
44
36
|
```
|
|
45
37
|
|
|
46
|
-
|
|
38
|
+
For local development or Spark:
|
|
47
39
|
|
|
48
40
|
```bash
|
|
41
|
+
git clone https://github.com/tunedtensor/tuned-tensor-local.git
|
|
42
|
+
cd tuned-tensor-local
|
|
43
|
+
npm install
|
|
44
|
+
npm run build
|
|
45
|
+
npm link
|
|
49
46
|
tt-local info
|
|
50
47
|
```
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
On the Spark host, confirm the GPU and uv are available:
|
|
49
|
+
Real training also needs `uv`:
|
|
55
50
|
|
|
56
51
|
```bash
|
|
57
|
-
nvidia-smi
|
|
58
52
|
uv --version
|
|
59
|
-
uv run python --version
|
|
60
53
|
```
|
|
61
54
|
|
|
62
55
|
If uv is missing:
|
|
@@ -65,29 +58,11 @@ If uv is missing:
|
|
|
65
58
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
66
59
|
```
|
|
67
60
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
uv --version
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Clone this repo on the Spark, install the Node dependencies, and build:
|
|
61
|
+
## First Run: Safe Dry Run
|
|
75
62
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
npm install
|
|
80
|
-
npm run build
|
|
81
|
-
npm link
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Run the doctor:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
tt-local doctor --config examples/local-runner.json
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Run Your First Job
|
|
63
|
+
The included example config is a dry run. It validates the end-to-end local
|
|
64
|
+
workflow and writes artifacts without downloading models or starting GPU
|
|
65
|
+
training.
|
|
91
66
|
|
|
92
67
|
Create a local behavior spec:
|
|
93
68
|
|
|
@@ -110,16 +85,13 @@ Validate the spec and config:
|
|
|
110
85
|
tt-local validate --config examples/local-runner.json
|
|
111
86
|
```
|
|
112
87
|
|
|
113
|
-
Run the
|
|
88
|
+
Run the dry workflow:
|
|
114
89
|
|
|
115
90
|
```bash
|
|
116
91
|
tt-local run --config examples/local-runner.json
|
|
117
92
|
```
|
|
118
93
|
|
|
119
|
-
|
|
120
|
-
writing without loading models or starting GPU training.
|
|
121
|
-
|
|
122
|
-
Inspect the run:
|
|
94
|
+
Inspect the result:
|
|
123
95
|
|
|
124
96
|
```bash
|
|
125
97
|
tt-local runs list --config examples/local-runner.json
|
|
@@ -132,11 +104,11 @@ Start the local dashboard:
|
|
|
132
104
|
tt-local serve --config examples/local-runner.json
|
|
133
105
|
```
|
|
134
106
|
|
|
135
|
-
|
|
107
|
+
Open the printed local URL in your browser.
|
|
136
108
|
|
|
137
|
-
## Run Real Training
|
|
109
|
+
## Run Real Training
|
|
138
110
|
|
|
139
|
-
Create a
|
|
111
|
+
Create a config such as `local-runner.json` or `spark-runner.json`:
|
|
140
112
|
|
|
141
113
|
```json
|
|
142
114
|
{
|
|
@@ -169,8 +141,109 @@ Create a Spark config, for example `spark-runner.json`:
|
|
|
169
141
|
}
|
|
170
142
|
```
|
|
171
143
|
|
|
172
|
-
|
|
173
|
-
|
|
144
|
+
If you use OpenRouter judging, set an API key:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
export OPENROUTER_API_KEY="your_openrouter_key"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`tt-local` also loads a `.env` file from the working directory. Existing
|
|
151
|
+
environment variables win.
|
|
152
|
+
|
|
153
|
+
Run the job:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
tt-local doctor --config spark-runner.json
|
|
157
|
+
tt-local validate --config spark-runner.json
|
|
158
|
+
tt-local run --config spark-runner.json
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`tt-local run` prints live stage updates to stderr and writes the final JSON
|
|
162
|
+
summary to stdout. Use `--verbose` to stream Python subprocess output, or
|
|
163
|
+
`--quiet` for JSON-only automation.
|
|
164
|
+
|
|
165
|
+
Watch progress:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
tt-local runs watch <run-id> --config spark-runner.json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
View the final report:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
tt-local runs report <run-id> --config spark-runner.json
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Compare two completed runs:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
tt-local runs compare <run-id-a> <run-id-b> --config spark-runner.json
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The comparison aligns shared eval prompts, separates newly added eval examples,
|
|
184
|
+
and measures judge score noise for identical baseline outputs.
|
|
185
|
+
|
|
186
|
+
## Configuration Basics
|
|
187
|
+
|
|
188
|
+
The main config fields are:
|
|
189
|
+
|
|
190
|
+
- `artifactRoot`: datasets, logs, model outputs, eval JSON, and run reports.
|
|
191
|
+
- `storeRoot`: run/spec/model metadata used by CLI listing and the dashboard.
|
|
192
|
+
- `dryRun`: when `true`, skips model loading and training.
|
|
193
|
+
- `training`: uv/Python training entrypoint and environment.
|
|
194
|
+
- `evaluation.inference`: how baseline and candidate outputs are generated.
|
|
195
|
+
- `evaluation.scoring`: how generated outputs are scored.
|
|
196
|
+
- `paths.modelCache`: Hugging Face cache location.
|
|
197
|
+
|
|
198
|
+
Supported inference providers:
|
|
199
|
+
|
|
200
|
+
- `"transformers"`: run the included local evaluator with Hugging Face/PEFT.
|
|
201
|
+
- `"command"`: call `baselineCommand` or `candidateCommand` for custom inference.
|
|
202
|
+
- `"none"`: skip inference and record empty local responses.
|
|
203
|
+
|
|
204
|
+
Supported scoring modes:
|
|
205
|
+
|
|
206
|
+
- `"exact_match"`: normalized exact match.
|
|
207
|
+
- `"llm_judge"`: OpenRouter judge, with exact-match fallback by default.
|
|
208
|
+
- `"json_fields"`: field-level comparison for structured JSON outputs.
|
|
209
|
+
|
|
210
|
+
When `evaluation.scoring.mode` is `"llm_judge"`, `fallback` controls judge
|
|
211
|
+
failures. `"exact_match"` keeps the run going and records fallback reasoning.
|
|
212
|
+
`"fail"` fails the run.
|
|
213
|
+
|
|
214
|
+
## Evaluation Behavior
|
|
215
|
+
|
|
216
|
+
`evaluation.maxExamples` caps how many examples are scored. If it is unset,
|
|
217
|
+
the request hyperparameter `max_eval_examples` is used. An explicit
|
|
218
|
+
`maxExamples` in the config wins. When capped, the runner uses a deterministic
|
|
219
|
+
seeded sample rather than a prefix, so sorted or grouped eval files do not
|
|
220
|
+
bias results. The seed defaults to a hash of the run id and can be pinned with
|
|
221
|
+
`evaluation.sampleSeed`.
|
|
222
|
+
|
|
223
|
+
For spec-based runs without `dataset_prebuilt`, the runner holds out about 20%
|
|
224
|
+
of the spec examples for evaluation, with at least one training example and one
|
|
225
|
+
holdout when possible. With only one spec example, evaluation runs on that
|
|
226
|
+
example and reports `eval_split: "spec_examples"`.
|
|
227
|
+
|
|
228
|
+
For prebuilt datasets, `dataset_prebuilt.training` is always copied into the
|
|
229
|
+
run artifact as the training set. Evaluation uses `dataset_prebuilt.test` when
|
|
230
|
+
present, then `dataset_prebuilt.validation`. If neither is provided, a real
|
|
231
|
+
run fails because evaluation would otherwise measure the training split. Set
|
|
232
|
+
`evaluation.allowPrebuiltTrainingEval: true` to allow that explicitly.
|
|
233
|
+
|
|
234
|
+
Baseline evaluations are cached under `<storeRoot>/cache/baseline-evals/`.
|
|
235
|
+
Re-running an unchanged spec with the same model, eval examples, generation
|
|
236
|
+
settings, and scoring config reuses the previous baseline report. Set
|
|
237
|
+
`evaluation.baselineCache: false` to disable.
|
|
238
|
+
|
|
239
|
+
Training progress from Python metric lines and tqdm progress bars is recorded
|
|
240
|
+
as run events, so `tt-local runs watch` and per-run `progress.jsonl` show
|
|
241
|
+
epoch, loss, step, percentage, and ETA when the trainer emits them.
|
|
242
|
+
|
|
243
|
+
## Structured JSON Scoring
|
|
244
|
+
|
|
245
|
+
For structured tasks, use field-level scoring instead of whole-output exact
|
|
246
|
+
matching:
|
|
174
247
|
|
|
175
248
|
```json
|
|
176
249
|
{
|
|
@@ -183,143 +256,85 @@ exact matching:
|
|
|
183
256
|
}
|
|
184
257
|
```
|
|
185
258
|
|
|
186
|
-
The evaluation report still includes `exact_match_rate`, but `avg_score`,
|
|
187
|
-
`pass_rate`, and `json_field_metrics` are based on the selected JSON fields.
|
|
188
259
|
If `fields` is omitted, the evaluator scores every key in the expected JSON
|
|
189
|
-
object
|
|
190
|
-
|
|
191
|
-
field lists cannot inflate scores.
|
|
192
|
-
|
|
193
|
-
The judge receives the spec's compiled system message (system prompt,
|
|
194
|
-
guidelines, and constraints) as task instructions and scores how well each
|
|
195
|
-
output fulfills the task; the expected output is treated as a reference
|
|
196
|
-
answer, not a fact checklist. Every eval report also includes `avg_token_f1`
|
|
197
|
-
(and the comparison includes `token_f1_delta`), a deterministic token-overlap
|
|
198
|
-
similarity against the reference outputs that is useful for free-text tasks
|
|
199
|
-
such as summarization, where exact match is always 0 and judge scores can be
|
|
200
|
-
noisy.
|
|
201
|
-
|
|
202
|
-
Each eval example records how it was scored (`results[].scored_by`), and the
|
|
203
|
-
report aggregates `judge_scored_count`, `fallback_scored_count`, and
|
|
204
|
-
`judge_only_avg_score`. When a judge request fails transiently and the
|
|
205
|
-
example falls back to exact match, `avg_score` mixes two metrics; use
|
|
206
|
-
`judge_only_avg_score` (and the comparison's `judge_only_avg_score_delta`)
|
|
207
|
-
when comparing judge quality. The comparison also classifies each regressed
|
|
208
|
-
example's judge reasoning as `factual`, `omission`, `style`, `fallback`, or
|
|
209
|
-
`other` and reports the counts in `regression_taxonomy`.
|
|
210
|
-
|
|
211
|
-
Baseline evaluations are cached under `<storeRoot>/cache/baseline-evals/`:
|
|
212
|
-
re-running a spec with an unchanged base model, eval examples, generation
|
|
213
|
-
settings, and scoring config reuses the previous baseline report instead of
|
|
214
|
-
re-running inference and judge calls. Cached reports carry `cached: true`.
|
|
215
|
-
Set `evaluation.baselineCache: false` to disable. Reports containing
|
|
216
|
-
fallback-scored examples are never cached, so a transient judge failure is
|
|
217
|
-
not replayed into future runs.
|
|
218
|
-
|
|
219
|
-
For thinking-mode base models (for example `Qwen/Qwen3.5-4B`, whose chat
|
|
220
|
-
template opens a `<think>` block by default and can consume the entire
|
|
221
|
-
`maxNewTokens` budget before any visible answer), forward template kwargs to
|
|
222
|
-
both evaluation and training:
|
|
260
|
+
object. A configured field missing from the expected JSON is always scored as
|
|
261
|
+
incorrect, so a misconfigured field list cannot inflate scores.
|
|
223
262
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
"evaluation": {
|
|
227
|
-
"inference": { "chatTemplateKwargs": { "enable_thinking": false } }
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
```
|
|
263
|
+
Reports still include `exact_match_rate`, but `avg_score`, `pass_rate`, and
|
|
264
|
+
`json_field_metrics` are based on the selected JSON fields.
|
|
231
265
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
```json
|
|
235
|
-
{ "chat_template_kwargs": { "enable_thinking": false } }
|
|
236
|
-
```
|
|
266
|
+
## Labeling Data With A Teacher Model
|
|
237
267
|
|
|
238
|
-
|
|
239
|
-
|
|
268
|
+
`tt-local label` turns unlabeled inputs into training examples by asking a
|
|
269
|
+
stronger teacher model on OpenRouter to answer each row under your spec's
|
|
270
|
+
system message — the same instructions the fine-tuned model will see at
|
|
271
|
+
inference:
|
|
240
272
|
|
|
241
273
|
```bash
|
|
242
|
-
|
|
274
|
+
export OPENROUTER_API_KEY=... # or put it in .env
|
|
275
|
+
tt-local label rows.jsonl --output labeled.jsonl
|
|
276
|
+
tt-local label rows.csv --input-column text --model anthropic/claude-fable-5
|
|
243
277
|
```
|
|
244
278
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
With `"mode": "llm_judge"`, `scoring.fallback` controls what happens when the
|
|
251
|
-
judge is unavailable, a judge request fails, or the judge returns malformed
|
|
252
|
-
JSON. With `"fallback": "exact_match"` (the default), the affected example is
|
|
253
|
-
scored by normalized exact match and its reasoning records the judge failure;
|
|
254
|
-
the run does not fail. With `"fallback": "fail"`, judge errors fail the run.
|
|
255
|
-
|
|
256
|
-
`evaluation.maxExamples` caps how many evaluation examples are scored. If it
|
|
257
|
-
is unset, the request hyperparameter `max_eval_examples` is used instead;
|
|
258
|
-
an explicit `maxExamples` in the config always wins. When the cap truncates
|
|
259
|
-
the eval set, the runner draws a deterministic seeded random sample (not a
|
|
260
|
-
prefix), so sorted or grouped eval files do not bias the subset. The seed
|
|
261
|
-
defaults to a hash of the run id, can be pinned with `evaluation.sampleSeed`,
|
|
262
|
-
and is recorded in each eval report as `eval_sample_seed`. Baseline and
|
|
263
|
-
candidate evaluations always score the same sampled examples in the same
|
|
264
|
-
order.
|
|
265
|
-
|
|
266
|
-
If you want OpenRouter judging, set your key:
|
|
279
|
+
Sources are JSONL (`{"input": "..."}` per line; rows that already carry an
|
|
280
|
+
`"output"` skip the teacher) or CSV with a header row and `--input-column`.
|
|
281
|
+
The system message comes from `tunedtensor.json` (or `--spec <path>`);
|
|
282
|
+
pass `--system-prompt "..."` to override it. Use `--dry-run` to parse,
|
|
283
|
+
sanitize, and estimate without calling the teacher.
|
|
267
284
|
|
|
268
|
-
|
|
269
|
-
|
|
285
|
+
Because rows are sent to OpenRouter, each row is sanitized first:
|
|
286
|
+
secret-like content (API keys, private keys, connection strings) blocks the
|
|
287
|
+
row, and PII (emails, phone numbers, SSNs, card numbers) is redacted before
|
|
288
|
+
sending. The labeled `{"input", "output"}` JSONL, a full per-row
|
|
289
|
+
`rows.jsonl` (including failures for review), and a job report are written
|
|
290
|
+
under `<artifactRoot>/labeling/<job-id>/`.
|
|
291
|
+
|
|
292
|
+
Teacher behavior is configured in the runner config; the model falls back to
|
|
293
|
+
`llm.model`:
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
{
|
|
297
|
+
"labeling": {
|
|
298
|
+
"model": "anthropic/claude-fable-5",
|
|
299
|
+
"maxTokens": 2048,
|
|
300
|
+
"temperature": 0.2,
|
|
301
|
+
"concurrency": 4,
|
|
302
|
+
"maxAttempts": 3
|
|
303
|
+
}
|
|
304
|
+
}
|
|
270
305
|
```
|
|
271
306
|
|
|
272
|
-
|
|
273
|
-
environment variables win), so the key can live in the project's `.env`
|
|
274
|
-
instead.
|
|
307
|
+
## Thinking-Mode Models
|
|
275
308
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
309
|
+
Some models, such as `Qwen/Qwen3.5-4B`, may open a hidden thinking block by
|
|
310
|
+
default and consume the token budget before producing a visible answer. Forward
|
|
311
|
+
template kwargs to both evaluation and training:
|
|
279
312
|
|
|
280
|
-
|
|
313
|
+
```json
|
|
314
|
+
{
|
|
315
|
+
"evaluation": {
|
|
316
|
+
"inference": {
|
|
317
|
+
"chatTemplateKwargs": { "enable_thinking": false }
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
281
322
|
|
|
282
|
-
|
|
283
|
-
|
|
323
|
+
In `tunedtensor.json`, also set:
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"hyperparameters": {
|
|
328
|
+
"chat_template_kwargs": { "enable_thinking": false }
|
|
329
|
+
}
|
|
330
|
+
}
|
|
284
331
|
```
|
|
285
332
|
|
|
286
|
-
|
|
287
|
-
summary to stdout. Use `--verbose` to stream Python subprocess output, or
|
|
288
|
-
`--quiet` for script-only JSON output.
|
|
289
|
-
|
|
290
|
-
Training progress from Python metric lines and tqdm progress bars is also
|
|
291
|
-
recorded as run events, so `tt-local runs watch` and per-run `progress.jsonl`
|
|
292
|
-
show epoch, loss, step, percentage, and ETA updates when the trainer emits
|
|
293
|
-
them.
|
|
294
|
-
|
|
295
|
-
For spec-based runs (no `dataset_prebuilt`), the runner automatically holds
|
|
296
|
-
out about 20% of the spec examples (at least 1 holdout and at least 1 training
|
|
297
|
-
example) for evaluation, so baseline and candidate scores are not measured on
|
|
298
|
-
the training data. The split is deterministic: it uses the per-run sample seed
|
|
299
|
-
(a hash of the run id, or `evaluation.sampleSeed` when set), so re-running the
|
|
300
|
-
same run id reproduces the same split and baseline/candidate always score the
|
|
301
|
-
identical holdout. The training JSONL artifact contains only the training
|
|
302
|
-
split, `run_metadata.training_example_count` records the training split size,
|
|
303
|
-
and `eval_examples_total` records the holdout size. These runs report
|
|
304
|
-
`eval_split: "spec_holdout"`. With only 1 spec example there is nothing to
|
|
305
|
-
hold out, so evaluation runs on the training set and is reported as
|
|
306
|
-
`eval_split: "spec_examples"` (training-set evaluation due to insufficient
|
|
307
|
-
examples).
|
|
333
|
+
## Multimodal Examples
|
|
308
334
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
(non-dry) run fails with an error, because evaluating on the training split
|
|
313
|
-
overstates improvement; set `evaluation.allowPrebuiltTrainingEval: true` to
|
|
314
|
-
evaluate on the training file anyway. Dry runs are allowed to fall back to the
|
|
315
|
-
training file without the override. Each eval report and the run's
|
|
316
|
-
`run_metadata` record the split that was evaluated in `eval_split`
|
|
317
|
-
(`spec_holdout`, `spec_examples`, `prebuilt_test`, `prebuilt_validation`, or
|
|
318
|
-
`prebuilt_training`), so training-set evaluation is always visible.
|
|
319
|
-
|
|
320
|
-
Multimodal runs use the same chat JSONL shape with structured user content.
|
|
321
|
-
For `image_text_to_text` models such as `Qwen/Qwen3-VL-2B-Instruct`, image
|
|
322
|
-
parts are loaded by the local SFT trainer and evaluator:
|
|
335
|
+
Multimodal runs use chat JSONL with structured user content. For
|
|
336
|
+
`image_text_to_text` models such as `Qwen/Qwen3-VL-2B-Instruct`, image parts
|
|
337
|
+
are loaded by the local trainer and evaluator:
|
|
323
338
|
|
|
324
339
|
```json
|
|
325
340
|
{
|
|
@@ -340,34 +355,17 @@ parts are loaded by the local SFT trainer and evaluator:
|
|
|
340
355
|
Image values in prebuilt JSONL may be absolute paths, paths relative to the
|
|
341
356
|
JSONL file, `file://` URIs, HTTP(S) URLs, or `data:` URIs. Behavior spec
|
|
342
357
|
examples can also use `input_assets` with `image`, `path`, `uri`, or
|
|
343
|
-
`data_uri
|
|
344
|
-
the copied run artifact can still resolve the image.
|
|
345
|
-
|
|
346
|
-
Watch progress:
|
|
347
|
-
|
|
348
|
-
```bash
|
|
349
|
-
tt-local runs watch <run-id> --config spark-runner.json
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
View the final report:
|
|
353
|
-
|
|
354
|
-
```bash
|
|
355
|
-
tt-local runs report <run-id> --config spark-runner.json
|
|
356
|
-
```
|
|
358
|
+
`data_uri`.
|
|
357
359
|
|
|
358
360
|
## Where Files Go
|
|
359
361
|
|
|
360
|
-
The runner writes two local roots:
|
|
361
|
-
|
|
362
|
-
- `artifactRoot`: datasets, logs, model outputs, eval JSON, and `run-report.json`.
|
|
363
|
-
- `storeRoot`: run/spec/model metadata used by CLI listing and the dashboard.
|
|
364
|
-
|
|
365
362
|
If `storeRoot` is omitted, `tt-local` uses `TT_LOCAL_HOME` or
|
|
366
363
|
`~/.tuned-tensor-local`.
|
|
367
364
|
|
|
368
365
|
Per-run artifacts include:
|
|
369
366
|
|
|
370
|
-
- `progress.jsonl`: stage changes and parsed training progress
|
|
367
|
+
- `progress.jsonl`: stage changes and parsed training progress.
|
|
368
|
+
- `baseline-eval.json` and `candidate-eval.json`: generated outputs and scores.
|
|
371
369
|
- `baseline-eval.json.inference.log` and `candidate-eval.json.inference.log`:
|
|
372
370
|
local inference subprocess output.
|
|
373
371
|
- `training/training.log`: uv fine-tuning output.
|
|
@@ -377,18 +375,44 @@ Per-run artifacts include:
|
|
|
377
375
|
|
|
378
376
|
```bash
|
|
379
377
|
tt-local init --name "Support Bot" --model Qwen/Qwen3.5-2B
|
|
378
|
+
tt-local doctor --config spark-runner.json
|
|
380
379
|
tt-local validate --config spark-runner.json
|
|
381
380
|
tt-local run --config spark-runner.json
|
|
382
381
|
tt-local run --config spark-runner.json --verbose
|
|
382
|
+
tt-local label rows.jsonl --output labeled.jsonl
|
|
383
383
|
tt-local runs list --config spark-runner.json
|
|
384
384
|
tt-local runs get <run-id> --config spark-runner.json
|
|
385
385
|
tt-local runs events <run-id> --config spark-runner.json
|
|
386
|
+
tt-local runs watch <run-id> --config spark-runner.json
|
|
386
387
|
tt-local runs report <run-id> --config spark-runner.json
|
|
387
388
|
tt-local runs compare <run-id-a> <run-id-b> --config spark-runner.json
|
|
388
389
|
tt-local models list --config spark-runner.json
|
|
389
390
|
tt-local serve --config spark-runner.json
|
|
390
391
|
```
|
|
391
392
|
|
|
393
|
+
## DGX Spark Notes
|
|
394
|
+
|
|
395
|
+
On the Spark host, confirm the GPU and uv are available:
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
nvidia-smi
|
|
399
|
+
uv --version
|
|
400
|
+
uv run python --version
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Then clone, install, build, and link the CLI on the Spark:
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
git clone https://github.com/tunedtensor/tuned-tensor-local.git
|
|
407
|
+
cd tuned-tensor-local
|
|
408
|
+
npm install
|
|
409
|
+
npm run build
|
|
410
|
+
npm link
|
|
411
|
+
tt-local doctor --config examples/local-runner.json
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Spark-specific details live in [docs/spark.md](docs/spark.md).
|
|
415
|
+
|
|
392
416
|
## Development
|
|
393
417
|
|
|
394
418
|
```bash
|
|
@@ -404,12 +428,12 @@ secret.
|
|
|
404
428
|
|
|
405
429
|
## Notes
|
|
406
430
|
|
|
407
|
-
- Real training uses
|
|
431
|
+
- Real training uses `training/sft-local/src/train.py`.
|
|
408
432
|
- Real evaluation uses `training/sft-local/src/evaluate.py`.
|
|
409
|
-
- The default evaluation path uses Hugging Face/PEFT artifacts directly; it
|
|
410
|
-
not convert models to GGUF.
|
|
433
|
+
- The default evaluation path uses Hugging Face/PEFT artifacts directly; it
|
|
434
|
+
does not convert models to GGUF.
|
|
435
|
+
- `validate` and `run` warn about unknown hyperparameter keys and ignore them.
|
|
411
436
|
- Release notes are in [CHANGELOG.md](CHANGELOG.md).
|
|
412
|
-
- Spark-specific details live in [docs/spark.md](docs/spark.md).
|
|
413
437
|
|
|
414
438
|
## License
|
|
415
439
|
|
package/dist/contracts.d.ts
CHANGED
|
@@ -580,6 +580,16 @@ export declare const runReportSchema: z.ZodObject<{
|
|
|
580
580
|
created_at: z.ZodString;
|
|
581
581
|
}, z.core.$strip>;
|
|
582
582
|
export declare const commandSchema: z.ZodArray<z.ZodString>;
|
|
583
|
+
export declare const labelingConfigSchema: z.ZodObject<{
|
|
584
|
+
model: z.ZodOptional<z.ZodString>;
|
|
585
|
+
maxTokens: z.ZodDefault<z.ZodNumber>;
|
|
586
|
+
temperature: z.ZodDefault<z.ZodNumber>;
|
|
587
|
+
concurrency: z.ZodDefault<z.ZodNumber>;
|
|
588
|
+
minIntervalMs: z.ZodDefault<z.ZodNumber>;
|
|
589
|
+
maxAttempts: z.ZodDefault<z.ZodNumber>;
|
|
590
|
+
maxRows: z.ZodDefault<z.ZodNumber>;
|
|
591
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
592
|
+
}, z.core.$strip>;
|
|
583
593
|
export declare const localRunnerConfigSchema: z.ZodObject<{
|
|
584
594
|
storeRoot: z.ZodOptional<z.ZodString>;
|
|
585
595
|
artifactRoot: z.ZodDefault<z.ZodString>;
|
|
@@ -599,15 +609,11 @@ export declare const localRunnerConfigSchema: z.ZodObject<{
|
|
|
599
609
|
modelCache: z.ZodOptional<z.ZodString>;
|
|
600
610
|
}, z.core.$strip>>;
|
|
601
611
|
evaluation: z.ZodDefault<z.ZodObject<{
|
|
602
|
-
mode: z.ZodDefault<z.ZodEnum<{
|
|
603
|
-
command: "command";
|
|
604
|
-
llm_judge: "llm_judge";
|
|
605
|
-
heuristic: "heuristic";
|
|
606
|
-
}>>;
|
|
607
612
|
baselineCommand: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
608
613
|
candidateCommand: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
609
614
|
inference: z.ZodDefault<z.ZodObject<{
|
|
610
615
|
provider: z.ZodDefault<z.ZodEnum<{
|
|
616
|
+
command: "command";
|
|
611
617
|
none: "none";
|
|
612
618
|
transformers: "transformers";
|
|
613
619
|
}>>;
|
|
@@ -654,6 +660,16 @@ export declare const localRunnerConfigSchema: z.ZodObject<{
|
|
|
654
660
|
appName: z.ZodOptional<z.ZodString>;
|
|
655
661
|
siteUrl: z.ZodOptional<z.ZodString>;
|
|
656
662
|
}, z.core.$strip>>;
|
|
663
|
+
labeling: z.ZodDefault<z.ZodObject<{
|
|
664
|
+
model: z.ZodOptional<z.ZodString>;
|
|
665
|
+
maxTokens: z.ZodDefault<z.ZodNumber>;
|
|
666
|
+
temperature: z.ZodDefault<z.ZodNumber>;
|
|
667
|
+
concurrency: z.ZodDefault<z.ZodNumber>;
|
|
668
|
+
minIntervalMs: z.ZodDefault<z.ZodNumber>;
|
|
669
|
+
maxAttempts: z.ZodDefault<z.ZodNumber>;
|
|
670
|
+
maxRows: z.ZodDefault<z.ZodNumber>;
|
|
671
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
672
|
+
}, z.core.$strip>>;
|
|
657
673
|
}, z.core.$strip>;
|
|
658
674
|
export type DocumentInputAsset = z.infer<typeof documentInputAssetSchema>;
|
|
659
675
|
export type BehaviorSpecExample = z.infer<typeof behaviorSpecExampleSchema>;
|
|
@@ -667,4 +683,5 @@ export type EvalReport = z.infer<typeof evalReportSchema>;
|
|
|
667
683
|
export type ComparisonReport = z.infer<typeof comparisonReportSchema>;
|
|
668
684
|
export type TrainingReport = z.infer<typeof trainingReportSchema>;
|
|
669
685
|
export type RunReport = z.infer<typeof runReportSchema>;
|
|
686
|
+
export type LocalLabelingConfig = z.infer<typeof labelingConfigSchema>;
|
|
670
687
|
export type LocalRunnerConfig = z.infer<typeof localRunnerConfigSchema>;
|