@tuned-tensor/cli 0.20.0 → 0.22.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.
@@ -44,7 +44,7 @@ The local adapter runtime excludes:
44
44
  The next model or method should arrive with its own locked dependencies,
45
45
  resource defaults, data contract, and real CUDA acceptance test.
46
46
 
47
- TT bundles two local training methods:
47
+ TT bundles three local training methods:
48
48
 
49
49
  - **Adapter** (`training/adapter`): certified Transformers checkpoints and
50
50
  PEFT LoRA SFT. Driven by `tt pipeline run`.
@@ -55,6 +55,11 @@ TT bundles two local training methods:
55
55
  rejects `nproc_per_node` values above 1 and unsupported steps such as
56
56
  `compare` before creating run artifacts.
57
57
 
58
+ - **Decision** (`training/decision`): fine-tunes a pinned typed decision
59
+ model (`convaiinnovations/laya`) on labelled examples. Driven by
60
+ `tt pipeline run` from a decision `tunedtensor.json`. Runs on CPU, Apple MPS
61
+ or CUDA; see [decision.md](decision.md).
62
+
58
63
  The foundation engine is a readable single-GPU trainer inspired by nanochat,
59
64
  not a port of nanochat's distributed or compute-optimal training stack. It can
60
65
  stream configured text/JSONL shards into a deterministic token cache, use a
@@ -92,6 +97,8 @@ Node owns input validation, state, process lifecycle, manifests, and reports.
92
97
  The bundled Python project owns four small operations:
93
98
 
94
99
  - `prefetch.py`: download and verify the pinned Hugging Face snapshot;
100
+ - `prefetch_dataset.py`: download and verify the spec's Hugging Face dataset
101
+ files (`dataset_prebuilt.huggingface`);
95
102
  - `evaluate.py`: generate predictions without receiving reference answers;
96
103
  - `train.py`: perform CUDA-only, model-specific LoRA SFT;
97
104
  - `serve.py`: prepare the verified base model and adapter for the vLLM serving runtime.
@@ -120,6 +127,19 @@ Canonical JSON and JSONL files are the state store; there is no database or
120
127
  mirrored index. Writes are atomic and listings scan the small local metadata
121
128
  tree deterministically.
122
129
 
130
+ Run events (`progress.jsonl` in the store's run directory) form a hash chain.
131
+ Each event carries `seq`, `prev_hash` and `hash`, a SHA-256 over its canonical
132
+ JSON. Appends take a short cross-process lock so concurrent writers, such as a
133
+ cancel from another terminal, extend one chain. The lock records its owner's
134
+ pid and host, so a crashed owner's lock is reclaimed at once and a live owner
135
+ is never broken. Appends read only the log's tail. A line torn by a crash is
136
+ closed off and skipped, and the audit counts it as unreadable. The completion event records
137
+ the report's SHA-256 and the artifact manifest's SHA-256. The report embeds a
138
+ `provenance` block (software, execution target, spec, base model, data
139
+ digests, hyperparameters). `tt runs audit` verifies the chain and report
140
+ digests and reports the first broken link. Events written by older versions
141
+ have no hashes and are reported as legacy.
142
+
123
143
  Each real adapter has an atomic `artifact-manifest.json` with the expected PEFT
124
144
  files, byte sizes, and SHA-256 hashes. A model is registered immediately after
125
145
  training and manifest verification, so a later evaluation failure does not
@@ -134,6 +154,29 @@ The runner config controls:
134
154
  - CUDA or CPU evaluation, with local serving through the CUDA vLLM runtime;
135
155
  - deterministic generation and scoring limits.
136
156
 
157
+ ## Hugging Face downloads
158
+
159
+ Model and dataset prefetch share `hub_download.py`. It resolves the requested
160
+ revision to one commit before downloading, so retries never mix files from two
161
+ commits. Network and server errors are retried with exponential backoff (2s up
162
+ to 60s, `TT_HF_DOWNLOAD_MAX_ATTEMPTS` attempts, default 10). Repo, auth,
163
+ revision and disk-full errors fail at once. Completed files stay in the cache,
164
+ so a rerun after Ctrl-C or a crash downloads only what is missing. A file
165
+ interrupted mid-transfer restarts from its beginning.
166
+
167
+ Progress is measured from the cache blob directory against the repo's file
168
+ sizes and printed as `@@tt-progress {json}` lines. The process runner sends
169
+ them to the reporter instead of the log stream. A terminal shows an in-place
170
+ bar with rate and ETA. Captured output gets a line every 10% or 30 seconds.
171
+ The parent `tt` process sets `TT_PROGRESS_TTY` for the local runtime child,
172
+ whose stderr is a pipe.
173
+
174
+ Dataset runs resolve the cached snapshot in Node without network access.
175
+ Each split file must link into the repo's blob store, and its digest must match
176
+ the blob name. With `columns`, records are converted to chat rows under
177
+ `<artifactRoot>/datasets/huggingface/`. The conversion path is keyed by the
178
+ source digests, the mapping and the system message.
179
+
137
180
  The training project, Python entrypoints, working directory, and forwarded
138
181
  runtime settings are internal and fixed. Every stage uses the locked `uv`
139
182
  project included in the npm package. Locally, the mutable virtual environment
@@ -140,6 +140,8 @@ Run these commands on your laptop in the project directory. Adapter workflow:
140
140
  # Fetch the base model locally first.
141
141
  tt validate tunedtensor.json
142
142
  tt models prefetch tunedtensor.json
143
+ # Only when dataset_prebuilt.huggingface is set:
144
+ tt datasets prefetch tunedtensor.json
143
145
  tt doctor tunedtensor.json
144
146
  tt pipeline run --spec tunedtensor.json --dry-run
145
147
  tt pipeline run --spec tunedtensor.json
@@ -0,0 +1,142 @@
1
+ # Decision models
2
+
3
+ A decision model answers one typed question about an input — which queue,
4
+ yes or no, how urgent — in a single encoder forward pass. It returns a
5
+ probability for every allowed answer instead of generating text, so the output
6
+ always parses and its confidence can be thresholded. Use the decision engine
7
+ for classification, routing, triage, guardrails and score gates where a
8
+ generative LLM is more than the job needs.
9
+
10
+ TT fine-tunes [Laya](https://huggingface.co/convaiinnovations/laya)
11
+ (`convaiinnovations/laya`, Apache-2.0): a 421M-parameter ModernBERT-large
12
+ encoder with a typed decision head. Every stage uses Laya revision
13
+ `55cf4c4ebb4ebe31b2550e8bdf3bd21b99753851`, the reviewed commit that Laya 0.3.22
14
+ pins. Training and evaluation run on CPU, Apple MPS or CUDA; no GPU is required.
15
+
16
+ ## Spec
17
+
18
+ ```json
19
+ {
20
+ "engine": "decision",
21
+ "name": "Card support triage",
22
+ "system_prompt": "Which card-support queue should handle this customer message?",
23
+ "guidelines": ["Choose the queue for the customer's main problem."],
24
+ "constraints": [],
25
+ "base_model": "convaiinnovations/laya",
26
+ "decision": {
27
+ "type": "choice",
28
+ "criteria": {
29
+ "lost_or_stolen": "customer lost their card or it was stolen",
30
+ "payment_declined": "a card payment was declined or rejected"
31
+ }
32
+ },
33
+ "examples": [
34
+ { "input": "I think I left my card at the bar last night.", "output": "lost_or_stolen" },
35
+ { "input": "Why was my payment at the grocery store refused?", "output": "payment_declined" }
36
+ ],
37
+ "hyperparameters": { "n_epochs": 3 }
38
+ }
39
+ ```
40
+
41
+ `tt init --engine decision` writes a starter spec. A complete example lives in
42
+ [`examples/single-spec/decision`](../../examples/single-spec/decision/tunedtensor.json).
43
+
44
+ `system_prompt`, `guidelines` and `constraints` compile into the question's
45
+ instructions, as they compile into the system message for the other engines.
46
+ The model reads roughly 190 tokens of instructions and option descriptions, so
47
+ keep them short; validation warns above 600 characters.
48
+
49
+ | `decision.type` | `criteria` | Example `output` |
50
+ | --- | --- | --- |
51
+ | `choice` | Object of label → description (2–32 labels, in option order) | A label, e.g. `payment_declined` |
52
+ | `noul` | Optional `{ "true": "...", "false": "..." }` | `true` or `false` |
53
+ | `score` | Array of level descriptions, level 0 first (2–10) | A level index, e.g. `2` |
54
+
55
+ Outputs match labels ignoring case and surrounding spaces. Validation rejects
56
+ an output that is not a label and warns when a label has no examples.
57
+
58
+ Optional `hyperparameters`:
59
+
60
+ | Field | Default | Meaning |
61
+ | --- | --- | --- |
62
+ | `n_epochs` | 3 | Passes over the training split |
63
+ | `learning_rate` | 2e-5 | AdamW learning rate, linear warmup and decay |
64
+ | `batch_size` | 8 | Examples per optimizer step |
65
+ | `freeze_encoder` | `false` | Train only the decision head (faster, usually less accurate) |
66
+ | `shuffle_options` | `true` | Shuffle option order per row so the head learns meaning, not position |
67
+ | `seed` | 0 | Training seed |
68
+ | `device` | `auto` | `auto`, `cpu`, `mps` or `cuda` |
69
+
70
+ `runtime.artifactRoot` is supported. `runtime.gpu`, `evaluation` and adapter
71
+ runtime paths are rejected: the model is small enough to run where the CLI runs.
72
+
73
+ ## Run
74
+
75
+ ```bash
76
+ tt validate
77
+ tt pipeline run --spec tunedtensor.json --dry-run
78
+ tt pipeline run --spec tunedtensor.json
79
+ ```
80
+
81
+ The pipeline is the adapter recipe: `baseline` evaluates the pinned base model,
82
+ `train` fine-tunes it, `candidate` evaluates the tuned model and `compare`
83
+ pairs the two. TT splits the spec examples once with a seed derived from the
84
+ spec `id` (20% held out, at least one). Training sees only the training split.
85
+ The evaluator receives held-out IDs and inputs only; Node joins its predictions
86
+ to the expected labels and scores them.
87
+
88
+ Each evaluation reports accuracy, log-loss and Brier score of the expected
89
+ label, mean confidence and per-label recall. The comparison adds improved and
90
+ regressed examples and the log-loss and Brier deltas. Because Laya answers
91
+ zero-shot from the label descriptions, the baseline is a real model, not an
92
+ empty one: the report shows what fine-tuning added on top of the descriptions.
93
+
94
+ Runs are written to `.tuned-tensor/decision-runs/<run-id>` beside the spec (or
95
+ `--output <dir>`), with owner-only permissions:
96
+
97
+ ```text
98
+ report.json summary: model, revision, split, every step's metrics
99
+ resolved-workflow.json spec, plan, question and split used
100
+ data/ train.jsonl and unlabeled eval-inputs.jsonl
101
+ baseline/ candidate/ config, log, predictions.jsonl, report.json
102
+ train/model/ the tuned checkpoint
103
+ compare/comparison.json
104
+ ```
105
+
106
+ On a 4-core CPU a step of 8 examples takes about 15 seconds, so 120 examples
107
+ for 3 epochs trains in about 12 minutes. The tuned checkpoint is about 1.7 GB
108
+ because weights are saved in float32; small fine-tuning updates would be lost
109
+ in bfloat16.
110
+
111
+ ## Use the tuned model
112
+
113
+ `train/model` is a complete Laya checkpoint. The fitted temperatures of the
114
+ base checkpoint are reset to 1.0, because they described the base model's
115
+ logits; cross-entropy training is itself the calibration objective.
116
+
117
+ ```python
118
+ import laya
119
+
120
+ agent = laya.load("path/to/decision-runs/<run-id>/train/model")
121
+ question = {
122
+ "type": "choice",
123
+ "instructions": "Which card-support queue should handle this customer message?",
124
+ "criteria": {"lost_or_stolen": "...", "payment_declined": "..."},
125
+ }
126
+ answer = agent.predict("My card was refused at the shop.", {"queue": question})["answers"]["queue"]
127
+ print(answer["choice"], answer["probabilities"])
128
+ ```
129
+
130
+ Pass the same instructions and criteria the model was trained with: they are
131
+ part of its input. `resolved-workflow.json` records them under `question`.
132
+ The tuned model is not registered with `tt models` and is not served through
133
+ vLLM.
134
+
135
+ ## Limits
136
+
137
+ - One question per spec. Laya can answer several questions per input; TT
138
+ trains and evaluates one.
139
+ - `convaiinnovations/laya` is the English checkpoint. Its multilingual and
140
+ typed-decisions variants are not certified yet.
141
+ - The held-out split comes from the spec examples. With few examples the
142
+ comparison is noisy; add examples before trusting a small delta.
@@ -73,7 +73,7 @@
73
73
  <div class="tt-level">
74
74
  <div class="tt-unit"><button class="btn" type="button" data-component="agent" aria-pressed="false">User intent → agent</button><small class="text-small">get_local_spec → field patch</small></div>
75
75
  <div class="tt-unit"><button class="btn" type="button" data-component="approval" aria-pressed="false">Diff → /approve</button><small class="text-small">validate → atomic save → history</small></div>
76
- <div class="tt-unit"><button class="btn" type="button" data-component="review" aria-pressed="false">/spec review</button><small class="text-small">show · diff · validate · history</small></div>
76
+ <div class="tt-unit"><button class="btn" type="button" data-component="review" aria-pressed="false">/spec review</button><small class="text-small">overview · sections · show · diff · validate · history</small></div>
77
77
  </div>
78
78
  <div class="tt-arrow text-small">↕ inspect the recipe · save only reviewed edits</div>
79
79
  <div class="tt-recipe tt-unit">
@@ -182,7 +182,7 @@
182
182
  "src/local-runtime/local-project.ts",
183
183
  "src/pipeline.ts"
184
184
  ],
185
- "example": "/spec\n/spec diff\n/spec validate\n/spec history\n\n# Outside the TT shell:\ntt spec show --json"
185
+ "example": "/spec\n/spec examples\n/spec diff\n/spec validate\n/spec history\n\n# Outside the TT shell:\ntt spec view\ntt spec show --json"
186
186
  },
187
187
  "config": {
188
188
  "title": "Spec runtime and evaluation",
@@ -15,19 +15,27 @@ Inside the TT shell, in your project directory:
15
15
 
16
16
  ```text
17
17
  /spec
18
+ /spec examples
19
+ /spec show
18
20
  /spec validate
19
21
  /spec diff
20
22
  /spec history
21
23
  ```
22
24
 
23
- `/spec` shows the complete current file, validation results and SHA-256.
25
+ `/spec` shows a readable overview: name, engine, base model and example count,
26
+ then highlighted sections for behavior (system prompt, guidelines, constraints),
27
+ examples, training settings, evaluation, runtime and pipeline, followed by
28
+ validation results. `/spec <section>` shows one section in full; sections are
29
+ `identity`, `behavior`, `examples`, `training`, `evaluation`, `runtime` and
30
+ `pipeline`. `/spec show` prints the complete file with JSON highlighting,
31
+ validation results and SHA-256.
24
32
  `/spec diff` shows pending spec edits in the current conversation; otherwise it
25
33
  shows the latest approved edit, or external changes since that revision.
26
34
  `/spec history` lists up to 50 recent revision records. These commands require
27
35
  no credentials and work before the agent is configured.
28
36
 
29
- Outside the shell, use `tt spec show`, `tt spec validate`, `tt spec diff`, and
30
- `tt spec history`. Add a workspace-relative path, for example
37
+ Outside the shell, use `tt spec view` (or `tt spec <section>`), `tt spec show`,
38
+ `tt spec validate`, `tt spec diff`, and `tt spec history`. Add a workspace-relative path, for example
31
39
  `tt spec show feedback/tunedtensor.json`. For continued work on a nested
32
40
  project, `/cd feedback` makes its `tunedtensor.json` the current recipe.
33
41
  `--json` returns structured review results. `tt spec validate` exits nonzero
@@ -114,6 +122,13 @@ resolve beside the spec. New projects default to `.tuned-tensor/artifacts` and
114
122
  Foundation supports `runtime.artifactRoot` and `runtime.gpu`; its evaluation
115
123
  and training settings remain under `foundation`.
116
124
 
125
+ `dataset_prebuilt.huggingface` names a Hugging Face dataset repo, an optional
126
+ pinned `revision` and optional `columns` (`input`/`output` field names). Its
127
+ split paths are then files inside that repo. `tt datasets prefetch` downloads
128
+ them, and `tt pipeline run` downloads them automatically if they are missing.
129
+ Runs record the resolved commit and file digests in the report. See the README
130
+ section "Train on a Hugging Face dataset".
131
+
117
132
  Only `tunedtensor.json` is required. `tt pipeline init --spec tunedtensor.json`
118
133
  embeds an advanced recipe when needed; omit this step to keep automatic derivation.
119
134
  An explicit `--file` cannot override a conflicting embedded recipe.