@tuned-tensor/local 0.1.6 → 0.1.8
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 +32 -1
- package/README.md +196 -195
- package/dist/contracts.d.ts +5 -5
- package/dist/contracts.js +3 -3
- package/dist/contracts.js.map +1 -1
- package/dist/evaluation.d.ts +2 -2
- package/dist/evaluation.js +64 -122
- package/dist/evaluation.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.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 +16 -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/training/sft-local/src/evaluate.py +21 -3
- package/training/sft-local/src/train.py +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
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.8 - 2026-07-02
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Renamed the user-facing project language to TT Local with the tagline
|
|
10
|
+
"Tuning tensors locally", while keeping the `tt-local` CLI, npm package, repo,
|
|
11
|
+
and filesystem identifiers unchanged.
|
|
12
|
+
- Reworked the README around a first-time user flow: install, safe dry run,
|
|
13
|
+
real training, config basics, evaluation behavior, artifacts, Spark notes,
|
|
14
|
+
and common commands.
|
|
15
|
+
- Simplified evaluation configuration by removing `evaluation.mode`. Inference
|
|
16
|
+
is now selected with `evaluation.inference.provider`, and scoring is selected
|
|
17
|
+
with `evaluation.scoring.mode`.
|
|
18
|
+
- Shared the uv/subprocess execution helper between training and evaluation so
|
|
19
|
+
logging, timeout, and stderr handling are consistent.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Added direct support for `evaluation.inference.provider: "command"` with
|
|
24
|
+
`baselineCommand` and `candidateCommand` for custom inference adapters.
|
|
25
|
+
|
|
26
|
+
## 0.1.7 - 2026-07-02
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Added chat-template kwargs plumbing for thinking-mode models: `evaluation.inference.chatTemplateKwargs` (runner config) and the `chat_template_kwargs` hyperparameter (spec) are forwarded to `apply_chat_template` in the local evaluator and SFT trainer. Setting `{"enable_thinking": false}` stops models like `Qwen/Qwen3.5-4B` from opening a `<think>` block that consumes the whole `maxNewTokens` budget and truncates every output.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Fixed a run-fatal report validation error when a comparison had regressions in only some taxonomy categories: zod v4 enum-keyed records are exhaustive, so the partial `regression_taxonomy` failed `runReportSchema` at the end of an otherwise-successful run. The taxonomy now always contains all categories with zero defaults.
|
|
4
35
|
|
|
5
36
|
## 0.1.6 - 2026-07-02
|
|
6
37
|
|
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
|
|
23
|
-
|
|
24
|
-
OpenRouter can be used as an LLM judge for scoring generated outputs. It does
|
|
25
|
-
not generate the baseline or tuned responses.
|
|
26
|
-
|
|
27
|
-
## Install Locally
|
|
24
|
+
6. Compare baseline vs tuned outputs and write a report.
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
OpenRouter can optionally score generated outputs as an LLM judge. It does not
|
|
27
|
+
generate the baseline or tuned responses.
|
|
30
28
|
|
|
31
|
-
|
|
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
|
-
```
|
|
29
|
+
## Install
|
|
38
30
|
|
|
39
|
-
|
|
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
|
-
|
|
61
|
+
## First Run: Safe Dry Run
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
Clone this repo on the Spark, install the Node dependencies, and build:
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
git clone https://github.com/tunedtensor/tuned-tensor-local.git
|
|
78
|
-
cd tuned-tensor-local
|
|
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,124 +256,44 @@ 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
|
-
To compare two runs whose eval sets may differ (for example after raising
|
|
220
|
-
`evaluation.maxExamples`), use:
|
|
260
|
+
object. A configured field missing from the expected JSON is always scored as
|
|
261
|
+
incorrect, so a misconfigured field list cannot inflate scores.
|
|
221
262
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
```
|
|
263
|
+
Reports still include `exact_match_rate`, but `avg_score`, `pass_rate`, and
|
|
264
|
+
`json_field_metrics` are based on the selected JSON fields.
|
|
225
265
|
|
|
226
|
-
|
|
227
|
-
on that shared subset plus the new-example subset separately, and measures
|
|
228
|
-
judge score noise on baseline outputs that are byte-identical across the two
|
|
229
|
-
runs.
|
|
230
|
-
|
|
231
|
-
With `"mode": "llm_judge"`, `scoring.fallback` controls what happens when the
|
|
232
|
-
judge is unavailable, a judge request fails, or the judge returns malformed
|
|
233
|
-
JSON. With `"fallback": "exact_match"` (the default), the affected example is
|
|
234
|
-
scored by normalized exact match and its reasoning records the judge failure;
|
|
235
|
-
the run does not fail. With `"fallback": "fail"`, judge errors fail the run.
|
|
236
|
-
|
|
237
|
-
`evaluation.maxExamples` caps how many evaluation examples are scored. If it
|
|
238
|
-
is unset, the request hyperparameter `max_eval_examples` is used instead;
|
|
239
|
-
an explicit `maxExamples` in the config always wins. When the cap truncates
|
|
240
|
-
the eval set, the runner draws a deterministic seeded random sample (not a
|
|
241
|
-
prefix), so sorted or grouped eval files do not bias the subset. The seed
|
|
242
|
-
defaults to a hash of the run id, can be pinned with `evaluation.sampleSeed`,
|
|
243
|
-
and is recorded in each eval report as `eval_sample_seed`. Baseline and
|
|
244
|
-
candidate evaluations always score the same sampled examples in the same
|
|
245
|
-
order.
|
|
246
|
-
|
|
247
|
-
If you want OpenRouter judging, set your key:
|
|
266
|
+
## Thinking-Mode Models
|
|
248
267
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
268
|
+
Some models, such as `Qwen/Qwen3.5-4B`, may open a hidden thinking block by
|
|
269
|
+
default and consume the token budget before producing a visible answer. Forward
|
|
270
|
+
template kwargs to both evaluation and training:
|
|
252
271
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"evaluation": {
|
|
275
|
+
"inference": {
|
|
276
|
+
"chatTemplateKwargs": { "enable_thinking": false }
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
260
281
|
|
|
261
|
-
|
|
282
|
+
In `tunedtensor.json`, also set:
|
|
262
283
|
|
|
263
|
-
```
|
|
264
|
-
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"hyperparameters": {
|
|
287
|
+
"chat_template_kwargs": { "enable_thinking": false }
|
|
288
|
+
}
|
|
289
|
+
}
|
|
265
290
|
```
|
|
266
291
|
|
|
267
|
-
|
|
268
|
-
summary to stdout. Use `--verbose` to stream Python subprocess output, or
|
|
269
|
-
`--quiet` for script-only JSON output.
|
|
270
|
-
|
|
271
|
-
Training progress from Python metric lines and tqdm progress bars is also
|
|
272
|
-
recorded as run events, so `tt-local runs watch` and per-run `progress.jsonl`
|
|
273
|
-
show epoch, loss, step, percentage, and ETA updates when the trainer emits
|
|
274
|
-
them.
|
|
275
|
-
|
|
276
|
-
For spec-based runs (no `dataset_prebuilt`), the runner automatically holds
|
|
277
|
-
out about 20% of the spec examples (at least 1 holdout and at least 1 training
|
|
278
|
-
example) for evaluation, so baseline and candidate scores are not measured on
|
|
279
|
-
the training data. The split is deterministic: it uses the per-run sample seed
|
|
280
|
-
(a hash of the run id, or `evaluation.sampleSeed` when set), so re-running the
|
|
281
|
-
same run id reproduces the same split and baseline/candidate always score the
|
|
282
|
-
identical holdout. The training JSONL artifact contains only the training
|
|
283
|
-
split, `run_metadata.training_example_count` records the training split size,
|
|
284
|
-
and `eval_examples_total` records the holdout size. These runs report
|
|
285
|
-
`eval_split: "spec_holdout"`. With only 1 spec example there is nothing to
|
|
286
|
-
hold out, so evaluation runs on the training set and is reported as
|
|
287
|
-
`eval_split: "spec_examples"` (training-set evaluation due to insufficient
|
|
288
|
-
examples).
|
|
292
|
+
## Multimodal Examples
|
|
289
293
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
(non-dry) run fails with an error, because evaluating on the training split
|
|
294
|
-
overstates improvement; set `evaluation.allowPrebuiltTrainingEval: true` to
|
|
295
|
-
evaluate on the training file anyway. Dry runs are allowed to fall back to the
|
|
296
|
-
training file without the override. Each eval report and the run's
|
|
297
|
-
`run_metadata` record the split that was evaluated in `eval_split`
|
|
298
|
-
(`spec_holdout`, `spec_examples`, `prebuilt_test`, `prebuilt_validation`, or
|
|
299
|
-
`prebuilt_training`), so training-set evaluation is always visible.
|
|
300
|
-
|
|
301
|
-
Multimodal runs use the same chat JSONL shape with structured user content.
|
|
302
|
-
For `image_text_to_text` models such as `Qwen/Qwen3-VL-2B-Instruct`, image
|
|
303
|
-
parts are loaded by the local SFT trainer and evaluator:
|
|
294
|
+
Multimodal runs use chat JSONL with structured user content. For
|
|
295
|
+
`image_text_to_text` models such as `Qwen/Qwen3-VL-2B-Instruct`, image parts
|
|
296
|
+
are loaded by the local trainer and evaluator:
|
|
304
297
|
|
|
305
298
|
```json
|
|
306
299
|
{
|
|
@@ -321,34 +314,17 @@ parts are loaded by the local SFT trainer and evaluator:
|
|
|
321
314
|
Image values in prebuilt JSONL may be absolute paths, paths relative to the
|
|
322
315
|
JSONL file, `file://` URIs, HTTP(S) URLs, or `data:` URIs. Behavior spec
|
|
323
316
|
examples can also use `input_assets` with `image`, `path`, `uri`, or
|
|
324
|
-
`data_uri
|
|
325
|
-
the copied run artifact can still resolve the image.
|
|
326
|
-
|
|
327
|
-
Watch progress:
|
|
328
|
-
|
|
329
|
-
```bash
|
|
330
|
-
tt-local runs watch <run-id> --config spark-runner.json
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
View the final report:
|
|
334
|
-
|
|
335
|
-
```bash
|
|
336
|
-
tt-local runs report <run-id> --config spark-runner.json
|
|
337
|
-
```
|
|
317
|
+
`data_uri`.
|
|
338
318
|
|
|
339
319
|
## Where Files Go
|
|
340
320
|
|
|
341
|
-
The runner writes two local roots:
|
|
342
|
-
|
|
343
|
-
- `artifactRoot`: datasets, logs, model outputs, eval JSON, and `run-report.json`.
|
|
344
|
-
- `storeRoot`: run/spec/model metadata used by CLI listing and the dashboard.
|
|
345
|
-
|
|
346
321
|
If `storeRoot` is omitted, `tt-local` uses `TT_LOCAL_HOME` or
|
|
347
322
|
`~/.tuned-tensor-local`.
|
|
348
323
|
|
|
349
324
|
Per-run artifacts include:
|
|
350
325
|
|
|
351
|
-
- `progress.jsonl`: stage changes and parsed training progress
|
|
326
|
+
- `progress.jsonl`: stage changes and parsed training progress.
|
|
327
|
+
- `baseline-eval.json` and `candidate-eval.json`: generated outputs and scores.
|
|
352
328
|
- `baseline-eval.json.inference.log` and `candidate-eval.json.inference.log`:
|
|
353
329
|
local inference subprocess output.
|
|
354
330
|
- `training/training.log`: uv fine-tuning output.
|
|
@@ -358,18 +334,43 @@ Per-run artifacts include:
|
|
|
358
334
|
|
|
359
335
|
```bash
|
|
360
336
|
tt-local init --name "Support Bot" --model Qwen/Qwen3.5-2B
|
|
337
|
+
tt-local doctor --config spark-runner.json
|
|
361
338
|
tt-local validate --config spark-runner.json
|
|
362
339
|
tt-local run --config spark-runner.json
|
|
363
340
|
tt-local run --config spark-runner.json --verbose
|
|
364
341
|
tt-local runs list --config spark-runner.json
|
|
365
342
|
tt-local runs get <run-id> --config spark-runner.json
|
|
366
343
|
tt-local runs events <run-id> --config spark-runner.json
|
|
344
|
+
tt-local runs watch <run-id> --config spark-runner.json
|
|
367
345
|
tt-local runs report <run-id> --config spark-runner.json
|
|
368
346
|
tt-local runs compare <run-id-a> <run-id-b> --config spark-runner.json
|
|
369
347
|
tt-local models list --config spark-runner.json
|
|
370
348
|
tt-local serve --config spark-runner.json
|
|
371
349
|
```
|
|
372
350
|
|
|
351
|
+
## DGX Spark Notes
|
|
352
|
+
|
|
353
|
+
On the Spark host, confirm the GPU and uv are available:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
nvidia-smi
|
|
357
|
+
uv --version
|
|
358
|
+
uv run python --version
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Then clone, install, build, and link the CLI on the Spark:
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
git clone https://github.com/tunedtensor/tuned-tensor-local.git
|
|
365
|
+
cd tuned-tensor-local
|
|
366
|
+
npm install
|
|
367
|
+
npm run build
|
|
368
|
+
npm link
|
|
369
|
+
tt-local doctor --config examples/local-runner.json
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Spark-specific details live in [docs/spark.md](docs/spark.md).
|
|
373
|
+
|
|
373
374
|
## Development
|
|
374
375
|
|
|
375
376
|
```bash
|
|
@@ -385,12 +386,12 @@ secret.
|
|
|
385
386
|
|
|
386
387
|
## Notes
|
|
387
388
|
|
|
388
|
-
- Real training uses
|
|
389
|
+
- Real training uses `training/sft-local/src/train.py`.
|
|
389
390
|
- Real evaluation uses `training/sft-local/src/evaluate.py`.
|
|
390
|
-
- The default evaluation path uses Hugging Face/PEFT artifacts directly; it
|
|
391
|
-
not convert models to GGUF.
|
|
391
|
+
- The default evaluation path uses Hugging Face/PEFT artifacts directly; it
|
|
392
|
+
does not convert models to GGUF.
|
|
393
|
+
- `validate` and `run` warn about unknown hyperparameter keys and ignore them.
|
|
392
394
|
- Release notes are in [CHANGELOG.md](CHANGELOG.md).
|
|
393
|
-
- Spark-specific details live in [docs/spark.md](docs/spark.md).
|
|
394
395
|
|
|
395
396
|
## License
|
|
396
397
|
|
package/dist/contracts.d.ts
CHANGED
|
@@ -66,6 +66,7 @@ export declare const fineTuneHyperparametersSchema: z.ZodObject<{
|
|
|
66
66
|
augment: z.ZodDefault<z.ZodBoolean>;
|
|
67
67
|
use_llm_judge: z.ZodDefault<z.ZodBoolean>;
|
|
68
68
|
max_eval_examples: z.ZodOptional<z.ZodNumber>;
|
|
69
|
+
chat_template_kwargs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
69
70
|
}, z.core.$strip>;
|
|
70
71
|
export declare const localArtifactsSchema: z.ZodObject<{
|
|
71
72
|
prefix: z.ZodOptional<z.ZodString>;
|
|
@@ -110,6 +111,7 @@ export declare const fineTuneRunRequestSchema: z.ZodObject<{
|
|
|
110
111
|
augment: z.ZodDefault<z.ZodBoolean>;
|
|
111
112
|
use_llm_judge: z.ZodDefault<z.ZodBoolean>;
|
|
112
113
|
max_eval_examples: z.ZodOptional<z.ZodNumber>;
|
|
114
|
+
chat_template_kwargs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
113
115
|
}, z.core.$strip>>;
|
|
114
116
|
artifacts: z.ZodOptional<z.ZodObject<{
|
|
115
117
|
prefix: z.ZodOptional<z.ZodString>;
|
|
@@ -161,6 +163,7 @@ export declare const localBehaviorSpecFileSchema: z.ZodObject<{
|
|
|
161
163
|
augment: z.ZodDefault<z.ZodBoolean>;
|
|
162
164
|
use_llm_judge: z.ZodDefault<z.ZodBoolean>;
|
|
163
165
|
max_eval_examples: z.ZodOptional<z.ZodNumber>;
|
|
166
|
+
chat_template_kwargs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
164
167
|
}, z.core.$strip>>;
|
|
165
168
|
artifacts: z.ZodOptional<z.ZodObject<{
|
|
166
169
|
prefix: z.ZodOptional<z.ZodString>;
|
|
@@ -596,15 +599,11 @@ export declare const localRunnerConfigSchema: z.ZodObject<{
|
|
|
596
599
|
modelCache: z.ZodOptional<z.ZodString>;
|
|
597
600
|
}, z.core.$strip>>;
|
|
598
601
|
evaluation: z.ZodDefault<z.ZodObject<{
|
|
599
|
-
mode: z.ZodDefault<z.ZodEnum<{
|
|
600
|
-
command: "command";
|
|
601
|
-
llm_judge: "llm_judge";
|
|
602
|
-
heuristic: "heuristic";
|
|
603
|
-
}>>;
|
|
604
602
|
baselineCommand: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
605
603
|
candidateCommand: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
606
604
|
inference: z.ZodDefault<z.ZodObject<{
|
|
607
605
|
provider: z.ZodDefault<z.ZodEnum<{
|
|
606
|
+
command: "command";
|
|
608
607
|
none: "none";
|
|
609
608
|
transformers: "transformers";
|
|
610
609
|
}>>;
|
|
@@ -624,6 +623,7 @@ export declare const localRunnerConfigSchema: z.ZodObject<{
|
|
|
624
623
|
cuda: "cuda";
|
|
625
624
|
mps: "mps";
|
|
626
625
|
}>>;
|
|
626
|
+
chatTemplateKwargs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
627
627
|
}, z.core.$strip>>;
|
|
628
628
|
scoring: z.ZodDefault<z.ZodObject<{
|
|
629
629
|
mode: z.ZodDefault<z.ZodEnum<{
|
package/dist/contracts.js
CHANGED
|
@@ -43,6 +43,7 @@ export const fineTuneHyperparametersSchema = z.object({
|
|
|
43
43
|
augment: z.boolean().default(false),
|
|
44
44
|
use_llm_judge: z.boolean().default(false),
|
|
45
45
|
max_eval_examples: z.number().int().min(1).optional(),
|
|
46
|
+
chat_template_kwargs: z.record(z.string(), z.unknown()).optional(),
|
|
46
47
|
});
|
|
47
48
|
export const localArtifactsSchema = z.object({
|
|
48
49
|
prefix: z.string().min(1).optional(),
|
|
@@ -226,11 +227,10 @@ export const localRunnerConfigSchema = z.object({
|
|
|
226
227
|
modelCache: z.string().optional(),
|
|
227
228
|
}).default({}),
|
|
228
229
|
evaluation: z.object({
|
|
229
|
-
mode: z.enum(["heuristic", "command", "llm_judge"]).default("heuristic"),
|
|
230
230
|
baselineCommand: commandSchema.optional(),
|
|
231
231
|
candidateCommand: commandSchema.optional(),
|
|
232
232
|
inference: z.object({
|
|
233
|
-
provider: z.enum(["transformers", "none"]).default("transformers"),
|
|
233
|
+
provider: z.enum(["transformers", "command", "none"]).default("transformers"),
|
|
234
234
|
project: z.string().optional(),
|
|
235
235
|
cwd: z.string().optional(),
|
|
236
236
|
module: z.string().optional(),
|
|
@@ -242,6 +242,7 @@ export const localRunnerConfigSchema = z.object({
|
|
|
242
242
|
topP: z.number().min(0).max(1).default(1),
|
|
243
243
|
trustRemoteCode: z.boolean().default(true),
|
|
244
244
|
device: z.enum(["auto", "cpu", "cuda", "mps"]).default("auto"),
|
|
245
|
+
chatTemplateKwargs: z.record(z.string(), z.unknown()).optional(),
|
|
245
246
|
}).default({
|
|
246
247
|
provider: "transformers",
|
|
247
248
|
script: "training/sft-local/src/evaluate.py",
|
|
@@ -267,7 +268,6 @@ export const localRunnerConfigSchema = z.object({
|
|
|
267
268
|
allowPrebuiltTrainingEval: z.boolean().default(false),
|
|
268
269
|
baselineCache: z.boolean().default(true),
|
|
269
270
|
}).default({
|
|
270
|
-
mode: "heuristic",
|
|
271
271
|
inference: {
|
|
272
272
|
provider: "transformers",
|
|
273
273
|
script: "training/sft-local/src/evaluate.py",
|