@tuned-tensor/local 0.2.9 → 0.4.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +110 -201
  3. package/dist/active-model.d.ts +16 -0
  4. package/dist/active-model.js +88 -0
  5. package/dist/active-model.js.map +1 -0
  6. package/dist/artifacts.d.ts +5 -4
  7. package/dist/artifacts.js +57 -33
  8. package/dist/artifacts.js.map +1 -1
  9. package/dist/compare.d.ts +1 -8
  10. package/dist/compare.js +1 -23
  11. package/dist/compare.js.map +1 -1
  12. package/dist/contracts.d.ts +234 -365
  13. package/dist/contracts.js +95 -224
  14. package/dist/contracts.js.map +1 -1
  15. package/dist/dataset.d.ts +11 -4
  16. package/dist/dataset.js +67 -125
  17. package/dist/dataset.js.map +1 -1
  18. package/dist/doctor.d.ts +2 -3
  19. package/dist/doctor.js +23 -161
  20. package/dist/doctor.js.map +1 -1
  21. package/dist/evaluation.d.ts +11 -71
  22. package/dist/evaluation.js +212 -572
  23. package/dist/evaluation.js.map +1 -1
  24. package/dist/general-regression.d.ts +10 -0
  25. package/dist/general-regression.js +16 -0
  26. package/dist/general-regression.js.map +1 -0
  27. package/dist/huggingface-cache.d.ts +8 -7
  28. package/dist/huggingface-cache.js +16 -8
  29. package/dist/huggingface-cache.js.map +1 -1
  30. package/dist/index.d.ts +2 -10
  31. package/dist/index.js +358 -627
  32. package/dist/index.js.map +1 -1
  33. package/dist/local-project.d.ts +1 -11
  34. package/dist/local-project.js +33 -61
  35. package/dist/local-project.js.map +1 -1
  36. package/dist/model-registry.d.ts +3 -16
  37. package/dist/model-registry.js +76 -292
  38. package/dist/model-registry.js.map +1 -1
  39. package/dist/model-server.d.ts +7 -3
  40. package/dist/model-server.js +34 -25
  41. package/dist/model-server.js.map +1 -1
  42. package/dist/orchestrator.d.ts +11 -25
  43. package/dist/orchestrator.js +393 -563
  44. package/dist/orchestrator.js.map +1 -1
  45. package/dist/prefetch.d.ts +1 -5
  46. package/dist/prefetch.js +14 -19
  47. package/dist/prefetch.js.map +1 -1
  48. package/dist/process-runner.d.ts +10 -29
  49. package/dist/process-runner.js +63 -169
  50. package/dist/process-runner.js.map +1 -1
  51. package/dist/process-training.d.ts +0 -1
  52. package/dist/process-training.js +10 -87
  53. package/dist/process-training.js.map +1 -1
  54. package/dist/store.d.ts +1 -3
  55. package/dist/store.js +92 -290
  56. package/dist/store.js.map +1 -1
  57. package/docs/architecture.md +87 -152
  58. package/docs/spark.md +66 -97
  59. package/examples/dry-runner.json +14 -0
  60. package/examples/general-regression.jsonl +4 -0
  61. package/examples/local-runner.json +13 -6
  62. package/examples/smoke-spec.json +41 -0
  63. package/package.json +6 -6
  64. package/training/local-runner/pyproject.toml +0 -5
  65. package/training/local-runner/src/evaluate.py +89 -234
  66. package/training/local-runner/src/model_contract.py +47 -0
  67. package/training/local-runner/src/prefetch.py +18 -4
  68. package/training/local-runner/src/serve.py +60 -117
  69. package/training/local-runner/src/sft_data.py +97 -0
  70. package/training/local-runner/src/train.py +146 -346
  71. package/training/local-runner/uv.lock +0 -1197
  72. package/dist/labeling-sanitize.d.ts +0 -31
  73. package/dist/labeling-sanitize.js +0 -158
  74. package/dist/labeling-sanitize.js.map +0 -1
  75. package/dist/labeling.d.ts +0 -155
  76. package/dist/labeling.js +0 -496
  77. package/dist/labeling.js.map +0 -1
  78. package/dist/openrouter.d.ts +0 -29
  79. package/dist/openrouter.js +0 -66
  80. package/dist/openrouter.js.map +0 -1
  81. package/dist/server.d.ts +0 -9
  82. package/dist/server.js +0 -211
  83. package/dist/server.js.map +0 -1
  84. package/docs/local-workflow-remediation-2026-07-13.md +0 -130
  85. package/docs/local-workflow-ux-review-2026-07-13.md +0 -458
  86. package/examples/dpo-preferences.jsonl +0 -2
  87. package/examples/dpo-run-request.json +0 -34
  88. package/examples/smoke-run-request.json +0 -34
  89. package/training/local-runner/src/train_dpo.py +0 -286
package/CHANGELOG.md CHANGED
@@ -2,6 +2,92 @@
2
2
 
3
3
  All notable changes to TT Local will be documented in this file.
4
4
 
5
+ ## Unreleased
6
+
7
+ ## 0.4.0 - 2026-07-26
8
+
9
+ ### Added
10
+
11
+ - Added an independent chat JSONL general-regression suite that evaluates the
12
+ protected base and tuned adapter with configurable score and pass-rate
13
+ budgets, immutable artifacts, and explicit pass/fail evidence in run reports.
14
+ - Added lightweight active-model promotion and rollback through
15
+ `models activate`, `models active`, `models rollback`, `serve active`, and
16
+ protected-base serving. Activation requires a completed run, a passing
17
+ general-regression gate, and verified run and model artifacts.
18
+
19
+ ### Changed
20
+
21
+ - General-regression dataset or prompt changes now invalidate only the
22
+ affected evaluations, while policy-only changes recalculate the report
23
+ without retraining or repeating inference.
24
+ - The bundled OpenAI-compatible server can now launch the certified protected
25
+ base without attaching a PEFT adapter.
26
+
27
+ ### Fixed
28
+
29
+ - Repeated activation of the already-active model is idempotent and preserves
30
+ its rollback target.
31
+ - Regression-budget comparisons tolerate insignificant floating-point noise at
32
+ the configured boundary.
33
+
34
+ ## 0.3.0 - 2026-07-26
35
+
36
+ ### Added
37
+
38
+ - Added one certified end-to-end local workflow for CUDA LoRA SFT of
39
+ `Qwen/Qwen3.5-2B`, including immutable snapshot prefetch, held-out
40
+ base-versus-tuned evaluation, verified PEFT artifacts, and
41
+ OpenAI-compatible local serving.
42
+ - Added exact Qwen model-contract checks in both TypeScript and Python, plus
43
+ strict text-only chat JSONL validation and assistant-only SFT data tests.
44
+
45
+ ### Removed
46
+
47
+ - **Breaking:** reduced TT Local to its end-to-end verified purpose: text LoRA
48
+ SFT of `Qwen/Qwen3.5-2B` on CUDA. Removed DPO, multimodal and continuation
49
+ inputs, arbitrary training/evaluation commands, external model contracts,
50
+ cloud teacher labeling and LLM judging, the classic-ML Study subsystem, and
51
+ the local dashboard.
52
+ - Removed the SQLite metadata mirror. Canonical JSON/JSONL files are now the
53
+ only state store and remain directly inspectable and recoverable.
54
+ - Removed implicit working-directory `.env` loading. Credentials and serving
55
+ keys must be provided explicitly through the process environment.
56
+ - Removed the incomplete detached-run, watch, and remote-cancel surfaces.
57
+ Workflows now run in the foreground so process and state lifecycles cannot
58
+ diverge after a host restart or worker crash.
59
+
60
+ ### Changed
61
+
62
+ - Training now computes loss only on assistant answer tokens and truncates the
63
+ prompt before the answer. CUDA training and tokenizer chat-template failures
64
+ fail fast instead of silently changing behavior.
65
+ - Runner, spec, and hyperparameter schemas are strict; obsolete and misspelled
66
+ fields are rejected rather than ignored.
67
+ - Training, evaluation, and serving now always use the bundled locked Python
68
+ project. Removed public runner project, working-directory, provider, and
69
+ environment overrides.
70
+ - Bundled `uv` commands now use `--frozen` and a content-keyed virtual
71
+ environment in the writable user cache instead of modifying the npm package.
72
+ - After prefetch verifies an immutable snapshot, training, evaluation, and
73
+ serving enforce offline Hugging Face cache use and the current Transformers
74
+ `dtype` loading API.
75
+ - Explicit base-model revisions must be immutable 40-character Hugging Face
76
+ commit SHAs; moving branches and tags are rejected.
77
+ - Default holdout sampling is stable across runs of the same behavior spec,
78
+ and baseline cache identity no longer includes the per-run UUID.
79
+ - The supported workflow is now documented and tested as one coherent path:
80
+ initialize, preflight, prepare held-out data, train, compare, verify, and
81
+ serve a local PEFT adapter.
82
+ - Prebuilt datasets with both validation and test files now use validation for
83
+ normal iterative evaluation, preserving test as a separate holdout.
84
+
85
+ ### Fixed
86
+
87
+ - Completed process-group teardown when a logged command's direct child exits,
88
+ including an opt-in cleanup mode for successful commands that must not leave
89
+ background descendants running.
90
+
5
91
  ## 0.2.9 - 2026-07-14
6
92
 
7
93
  ### Changed
package/README.md CHANGED
@@ -3,272 +3,181 @@
3
3
  [![CI](https://github.com/tunedtensor/tuned-tensor-local/actions/workflows/ci.yml/badge.svg)](https://github.com/tunedtensor/tuned-tensor-local/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/@tuned-tensor/local)](https://www.npmjs.com/package/@tuned-tensor/local)
5
5
 
6
- TT Local is local-first fine-tuning with built-in evaluation for small
7
- open-weight models. On a compatible NVIDIA GPU on Linux, it turns a behavior
8
- spec into a fine-tuned model and a paired, inspectable report comparing the run
9
- baseline with the tuned candidate on representative evaluation cases. Specs,
10
- datasets, model artifacts, events, reports, and dashboard state stay on local
11
- disk.
6
+ TT Local fine-tunes an open-weight language model on your own NVIDIA GPU. It
7
+ turns a small behavior spec or chat JSONL dataset into a verified LoRA adapter,
8
+ compares the base and tuned model on held-out examples, and can serve the
9
+ adapter through an OpenAI-compatible endpoint.
12
10
 
13
- Usage docs:
11
+ The current release deliberately has one supported path:
14
12
 
15
- https://tunedtensor.com/docs/local-training
13
+ - text supervised fine-tuning (SFT);
14
+ - `Qwen/Qwen3.5-2B`;
15
+ - LoRA/PEFT on CUDA;
16
+ - deterministic exact-match or JSON-field evaluation;
17
+ - local files for datasets, state, reports, and model artifacts.
16
18
 
17
- ## Install
19
+ New model families and training methods should be added only after they have a
20
+ repeatable end-to-end GPU acceptance test.
18
21
 
19
- ```bash
20
- npm install -g @tuned-tensor/local
21
- tt-local info
22
- ```
22
+ ## Requirements
23
23
 
24
- The bundled SFT, DPO, and Transformers evaluator path also needs `uv`:
25
-
26
- ```bash
27
- uv --version
28
- ```
24
+ - Node.js 22 or later
25
+ - [`uv`](https://docs.astral.sh/uv/)
26
+ - an NVIDIA CUDA host with enough free disk space for the model cache and run
27
+ artifacts
29
28
 
30
- Custom training or evaluation workflows can use command entrypoints instead of
31
- `uv`.
32
-
33
- The default uv project is included in the npm package at
34
- `training/local-runner`; using bundled training does not require cloning this
35
- repository.
36
-
37
- ## First Local Run
38
-
39
- On an NVIDIA Spark or another CUDA host, initialize both the behavior spec and
40
- a durable project-local runner config:
29
+ The npm package includes the locked Python training environment.
41
30
 
42
31
  ```bash
43
- tt-local --version
44
- tt-local init --name "Support Bot" --model Qwen/Qwen3.5-2B --profile spark
32
+ npm install -g @tuned-tensor/local
33
+ tt-local info
45
34
  ```
46
35
 
47
- Edit `tunedtensor.json` to replace the generated system prompt and example.
48
- TT Local refuses to validate or train the unchanged placeholder so an
49
- accidental run cannot spend GPU time on it.
36
+ ## Fine-tune a model
50
37
 
51
- Then preflight the exact Python environments and paths that the run will use:
38
+ Create a project on the GPU host:
52
39
 
53
40
  ```bash
54
- tt-local doctor tunedtensor.json --config local-runner.json
55
- tt-local validate tunedtensor.json --config local-runner.json
41
+ mkdir support-adapter && cd support-adapter
42
+ tt-local init --name "Support Adapter" --model Qwen/Qwen3.5-2B --profile spark
56
43
  ```
57
44
 
58
- `doctor` checks the bundled uv environment, Torch/Transformers/PEFT imports,
59
- the requested CUDA/MPS device, judge credentials when configured, writable
60
- artifact/cache/store roots, and available disk space. GPU checks are skipped
61
- for dry runs and are conditional for CPU/MPS configurations.
45
+ This creates `tunedtensor.json` and `local-runner.json`. Edit the generated
46
+ system prompt and replace both placeholder examples with representative,
47
+ different examples. Two examples are the minimum for a real run because
48
+ training and evaluation must not use the same row.
62
49
 
63
- Download the base model explicitly, then start the run:
50
+ Preflight the machine, input, and cached base model:
64
51
 
65
52
  ```bash
66
- tt-local models prefetch tunedtensor.json --config local-runner.json
67
- tt-local models verify-base tunedtensor.json --config local-runner.json
68
- tt-local run tunedtensor.json --config local-runner.json
53
+ tt-local doctor tunedtensor.json
54
+ tt-local validate tunedtensor.json
55
+ tt-local models prefetch tunedtensor.json
56
+ tt-local models verify-base tunedtensor.json
69
57
  ```
70
58
 
71
- Prefetch progress is visible by default. Pass `--quiet` only when another
72
- program is consuming the final JSON. `verify-base` is local-only: it checks
73
- the cached snapshot structure, indexed weight shards, tokenizer files, and
74
- non-empty weights without downloading a replacement. Cached Hugging Face blobs
75
- whose ETags encode Git or LFS checksums are re-hashed as part of verification.
59
+ Commands discover `local-runner.json` beside the spec. Use
60
+ `--config /path/to/local-runner.json` to select another config explicitly.
76
61
 
77
- For a long run, start it in the background and watch it from the current or a
78
- second terminal:
62
+ Run the complete base-evaluate, train, tuned-evaluate, and report workflow:
79
63
 
80
64
  ```bash
81
- tt-local run tunedtensor.json --config local-runner.json --detach
82
- tt-local runs watch <run-id> --config local-runner.json
65
+ tt-local run tunedtensor.json
83
66
  ```
84
67
 
85
- The detached command returns the persisted run ID, process ID, log path, and
86
- copy-paste watch/cancel commands before model work begins.
87
-
88
- Inspect the evidence after the run:
68
+ Inspect the evidence and verify the adapter:
89
69
 
90
70
  ```bash
91
- tt-local runs list --config local-runner.json
92
- tt-local runs report <run-id> --config local-runner.json
93
- tt-local models verify local-<run-id> --config local-runner.json
71
+ tt-local runs report <run-id>
72
+ tt-local models verify local-<run-id>
94
73
  ```
95
74
 
96
- Treat the report as evidence on this run's evaluation cases, not as a
97
- guarantee of live application improvement.
75
+ To protect broad capability, configure a separate chat JSONL suite in
76
+ `local-runner.json`:
98
77
 
99
- The model is registered immediately after its artifact passes verification,
100
- so it remains discoverable if candidate evaluation or report generation later
101
- fails.
102
-
103
- ## Serve a Trained Adapter
104
-
105
- `tt-local serve` is the run dashboard. To serve a trained TT Local adapter as
106
- an OpenAI-compatible model endpoint, use the nested model command:
107
-
108
- ```bash
109
- tt-local models serve local-<run-id> \
110
- --config local-runner.json \
111
- --spec tunedtensor.json \
112
- --host 127.0.0.1 \
113
- --port 8000
78
+ ```json
79
+ {
80
+ "evaluation": {
81
+ "generalRegression": {
82
+ "dataset": "evals/general.jsonl",
83
+ "maxScoreDrop": 0.03,
84
+ "maxPassRateDrop": 0.05
85
+ }
86
+ }
87
+ }
114
88
  ```
115
89
 
116
- The server verifies the stored artifact, loads the recorded base model and
117
- PEFT adapter through the bundled uv environment, and binds to localhost by
118
- default. Test it from another terminal:
90
+ TT Local evaluates the protected base and candidate adapter on this suite,
91
+ records both reports, and marks whether the configured score and pass-rate
92
+ budgets were respected. The base result uses the existing immutable baseline
93
+ cache.
119
94
 
120
- ```bash
121
- curl http://127.0.0.1:8000/health
122
- curl http://127.0.0.1:8000/v1/models
123
- curl http://127.0.0.1:8000/v1/chat/completions \
124
- -H 'Content-Type: application/json' \
125
- -d '{"messages":[{"role":"user","content":"Hello"}]}'
126
- ```
127
-
128
- Use `--device cuda|mps|cpu|auto` to override the configured inference device.
129
- Non-loopback binds require both `--allow-remote` and
130
- `--api-key-env <environment-variable>`; requests must then send that value as
131
- a Bearer token. The server accepts multimodal images only as bounded `data:`
132
- URIs and does not support streaming yet.
133
-
134
- ## Cache and Evaluation Semantics
135
-
136
- `paths.modelCache` means Hugging Face `HF_HOME` everywhere. Hub snapshots live
137
- under `<modelCache>/hub`; prefetch, training, evaluation, doctor, and serving
138
- all use that same layout. Set `HF_TOKEN` in the environment or project `.env`
139
- before prefetching a gated model. Do not commit `.env`.
140
-
141
- For reproducible remote loading, set
142
- `hyperparameters.base_model_revision` to an immutable Hugging Face commit.
143
- Alternatively, `paths.baseModel` may point to a complete local Hugging Face
144
- snapshot directory. It cannot point to a model archive or standalone weights
145
- file; TT Local requires non-empty model weights, `config.json`, tokenizer
146
- metadata and vocabulary, and every indexed shard. Local snapshot contents are
147
- fingerprinted so a byte change invalidates dependent stages.
148
-
149
- Shared baseline-cache entries are used only when the model and every local
150
- input have a stable content identity. An unpinned remote model or remotely
151
- hosted image bypasses the cache rather than risking stale evaluation results.
152
-
153
- The safe default scoring mode is deterministic `exact_match`. To use an
154
- OpenRouter judge, configure `evaluation.scoring.mode: "llm_judge"`, an `llm`
155
- block, and its API-key environment variable. Judge mode fails preflight when
156
- those are missing. Exact-match fallback occurs only when explicitly requested
157
- with `evaluation.scoring.fallback: "exact_match"`; fallback-scored results are
158
- marked and never written to the judge baseline cache.
159
-
160
- Before the first real run, you can download the configured Hugging Face base
161
- model into `paths.modelCache` explicitly:
95
+ After a passing run, activation is a lightweight pointer to the existing model
96
+ record:
162
97
 
163
98
  ```bash
164
- tt-local models prefetch tunedtensor.json --config local-runner.json
165
- tt-local models verify-base tunedtensor.json --config local-runner.json
99
+ tt-local models activate local-<run-id>
100
+ tt-local models active
101
+ tt-local serve active --config local-runner.json
102
+ tt-local models rollback
103
+ tt-local serve base --config local-runner.json
166
104
  ```
167
105
 
168
- Without this step, the first non-dry `tt-local run` downloads the base model
169
- when baseline evaluation or training first loads it.
170
-
171
- Individual `run --stage ...` commands finish in the terminal
172
- `stage_completed` state, so `runs watch` exits instead of waiting for stages
173
- that were intentionally not requested.
106
+ Activation re-verifies the model artifact and requires a completed run with a
107
+ passing general-regression result. Rollback restores the previous adapter or
108
+ the protected base; it does not copy or delete model files.
174
109
 
175
- Relative paths in a runner config resolve from the config file's directory.
176
- Relative dataset and image paths resolve from the behavior spec or request
177
- file's directory. The published package includes `uv.lock` so bundled Python
178
- dependencies are reproducible.
110
+ The report is evidence on the selected evaluation rows, not a guarantee of
111
+ general model improvement.
179
112
 
180
- ## DPO
113
+ ## Use a chat JSONL dataset
181
114
 
182
- TT Local supports first-class offline DPO for text causal-LM models. Set
183
- `training_method` to `dpo` and provide a prebuilt preference JSONL training
184
- dataset:
115
+ For more than a few examples, set `dataset_prebuilt` in `tunedtensor.json`:
185
116
 
186
117
  ```json
187
118
  {
188
- "training_method": "dpo",
189
119
  "dataset_prebuilt": {
190
- "training": "file://examples/dpo-preferences.jsonl",
191
- "format": "preference_jsonl"
120
+ "training": "data/train.jsonl",
121
+ "validation": "data/validation.jsonl",
122
+ "format": "chat_jsonl"
192
123
  }
193
124
  }
194
125
  ```
195
126
 
196
- Each preference JSONL row must use explicit `prompt`, `chosen`, and `rejected`
197
- string fields:
127
+ Each line is a JSON object with a `messages` array. The final message must be
128
+ the assistant answer; preceding messages form the prompt:
198
129
 
199
130
  ```json
200
- {"prompt":"Summarize status: build passed.","chosen":"Build passed.","rejected":"The build failed."}
131
+ {"messages":[{"role":"system","content":"Return one sentiment label."},{"role":"user","content":"I loved it."},{"role":"assistant","content":"positive"}]}
201
132
  ```
202
133
 
203
- DPO v1 is text-only for the bundled `uv` trainer. Validation and reporting still
204
- use the existing baseline-vs-candidate evaluation loop, so provide
205
- `dataset_prebuilt.test`, `dataset_prebuilt.validation`, or normal
206
- `spec_snapshot.examples` with reference outputs for evaluation.
134
+ TT Local masks every prompt token from the loss and preserves the complete
135
+ assistant answer when truncating a long example. Use a distinct validation or
136
+ test file for trustworthy evaluation.
207
137
 
208
- Command-backed workflows may use external model ids by setting
209
- `spec_snapshot.base_model` to an `external:<id>` or `command:<id>` value, for
210
- example `external:karpathy/nanochat`. The bundled `uv` trainer still requires a
211
- supported Hugging Face base model, but command trainers receive adapter-focused
212
- hyperparameters without injected LoRA/model-loader defaults. Custom
213
- hyperparameter keys are passed through to `TT_HYPERPARAMETERS_PATH`.
214
- Set `hyperparameters.base_model_revision` to an immutable Hugging Face commit
215
- when a run must load and record an exact base-model revision.
216
-
217
- ## Continuing From a Fine-Tuned Model
218
-
219
- Start another loop from an existing TT Local model by passing the stored local
220
- model id:
138
+ ## Serve the adapter
221
139
 
222
140
  ```bash
223
- tt-local run tunedtensor.json --parent-model local-<previous-run-id>
141
+ tt-local serve local-<run-id> \
142
+ --spec tunedtensor.json \
143
+ --host 127.0.0.1 \
144
+ --port 8000
224
145
  ```
225
146
 
226
- You can also set the parent adapter artifact explicitly:
147
+ `tt-local models serve` is an equivalent nested command. Before launch, TT
148
+ Local re-hashes the stored artifact manifest and then loads the recorded base
149
+ model plus PEFT adapter with the bundled Python environment.
227
150
 
228
151
  ```bash
229
- tt-local run tunedtensor.json --parent-model-artifact file:///path/to/model.tar.gz
152
+ curl http://127.0.0.1:8000/health
153
+ curl http://127.0.0.1:8000/v1/models
154
+ curl http://127.0.0.1:8000/v1/chat/completions \
155
+ -H 'Content-Type: application/json' \
156
+ -d '{"messages":[{"role":"user","content":"Classify: I loved it."}]}'
230
157
  ```
231
158
 
232
- For config-only workflows, put the same value in
233
- `hyperparameters.parent_model_artifact`. The parent adapter becomes the
234
- baseline for the new run, and the bundled SFT/DPO trainers load it before
235
- continuing LoRA training.
159
+ The default bind is localhost. A non-loopback bind requires `--allow-remote`
160
+ and `--api-key-env <environment-variable>`.
236
161
 
237
- Stored `--parent-model` references are verified before launch. TT Local pins
238
- the child's base revision from the parent (or requires the same configured
239
- local snapshot content), rejects conflicting base identity, and fingerprints
240
- the parent artifact so changed bytes invalidate reuse. The explicit
241
- `--parent-model-artifact` form is for caller-managed local artifacts and should
242
- be used only when that provenance is understood.
162
+ ## Reproducibility and local data
243
163
 
244
- Non-Hugging Face artifacts can describe their layout in `training.artifact`:
245
-
246
- ```json
247
- {
248
- "training": {
249
- "backend": "command",
250
- "command": ["python", "train_adapter.py"],
251
- "artifact": {
252
- "framework": "nanochat",
253
- "format": "custom-directory",
254
- "entrypoint": "batch_command",
255
- "servable": false
256
- }
257
- }
258
- }
259
- ```
164
+ Set `paths.modelCache` in `local-runner.json` to a durable Hugging Face cache.
165
+ Prefetch records the resolved model commit; set
166
+ `hyperparameters.base_model_revision` to a 40-character Hugging Face commit
167
+ SHA when an immutable revision is required before download.
260
168
 
261
- ## Local Development
169
+ Run state is intentionally plain and recoverable:
262
170
 
263
- ```bash
264
- npm install
265
- npm run typecheck
266
- npm test
267
- npm run build
171
+ ```text
172
+ .tt-local/
173
+ artifacts/users/<user-id>/specs/<spec-id>/runs/<run-id>/...
174
+ store/specs/<spec-id>/spec.json
175
+ store/runs/<run-id>/{request.json,state.json,progress.jsonl,run-report.json}
176
+ store/models/<model-id>/model.json
268
177
  ```
269
178
 
270
- Release notes are in [CHANGELOG.md](CHANGELOG.md).
271
-
272
- ## License
179
+ Real training writes an `artifact-manifest.json` containing file sizes and
180
+ SHA-256 hashes. Dry runs exercise orchestration but do not register a model.
273
181
 
274
- Apache-2.0
182
+ See [docs/spark.md](docs/spark.md) for the DGX Spark workflow and
183
+ [docs/architecture.md](docs/architecture.md) for the design boundary.
@@ -0,0 +1,16 @@
1
+ import type { LocalModelRecord, LocalStore } from "./store.js";
2
+ export interface ActiveModelPointer {
3
+ schema_version: 1;
4
+ model_id: string | null;
5
+ run_id: string | null;
6
+ previous_model_id: string | null;
7
+ activated_at: string;
8
+ action: "activate" | "rollback";
9
+ }
10
+ export interface ActiveModelState {
11
+ pointer: ActiveModelPointer | null;
12
+ model: LocalModelRecord | null;
13
+ }
14
+ export declare function getActiveModel(store: LocalStore): Promise<ActiveModelState>;
15
+ export declare function activateModel(store: LocalStore, modelId: string): Promise<ActiveModelPointer>;
16
+ export declare function rollbackActiveModel(store: LocalStore): Promise<ActiveModelPointer>;
@@ -0,0 +1,88 @@
1
+ import { appendFile, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { writeJsonAtomic } from "./artifacts.js";
4
+ import { runReportSchema } from "./contracts.js";
5
+ function pointerPath(store) {
6
+ return join(store.root, "active-model.json");
7
+ }
8
+ function historyPath(store) {
9
+ return join(store.root, "activation-history.jsonl");
10
+ }
11
+ async function readPointer(store) {
12
+ try {
13
+ const value = JSON.parse(await readFile(pointerPath(store), "utf8"));
14
+ if (value.schema_version !== 1
15
+ || !["activate", "rollback"].includes(value.action)
16
+ || (value.model_id !== null && typeof value.model_id !== "string")
17
+ || (value.run_id !== null && typeof value.run_id !== "string")
18
+ || (value.previous_model_id !== null && typeof value.previous_model_id !== "string")) {
19
+ throw new Error("Active model pointer has an invalid shape.");
20
+ }
21
+ return value;
22
+ }
23
+ catch (error) {
24
+ if (error.code === "ENOENT")
25
+ return null;
26
+ throw error;
27
+ }
28
+ }
29
+ async function assertModelCanActivate(store, modelId) {
30
+ const model = await store.getModel(modelId);
31
+ const run = await store.getRun(model.run_id);
32
+ if (run.status !== "completed") {
33
+ throw new Error(`Model ${model.id} belongs to run ${run.id}, which is not completed.`);
34
+ }
35
+ const report = runReportSchema.parse(JSON.parse(await readFile(join(model.artifact_dir, "run-report.json"), "utf8")));
36
+ if (!report.general_regression) {
37
+ throw new Error(`Model ${model.id} has no general regression evaluation.`);
38
+ }
39
+ if (!report.general_regression.passed) {
40
+ throw new Error(`Model ${model.id} failed general regression: `
41
+ + (report.general_regression.failures.join(" ") || "regression budget exceeded"));
42
+ }
43
+ return model;
44
+ }
45
+ async function publishPointer(store, pointer) {
46
+ await store.ensure();
47
+ await writeJsonAtomic(pointerPath(store), pointer);
48
+ await appendFile(historyPath(store), `${JSON.stringify(pointer)}\n`, "utf8");
49
+ return pointer;
50
+ }
51
+ export async function getActiveModel(store) {
52
+ const pointer = await readPointer(store);
53
+ if (!pointer?.model_id)
54
+ return { pointer, model: null };
55
+ return { pointer, model: await store.getModel(pointer.model_id) };
56
+ }
57
+ export async function activateModel(store, modelId) {
58
+ const [model, current] = await Promise.all([
59
+ assertModelCanActivate(store, modelId),
60
+ readPointer(store),
61
+ ]);
62
+ if (current?.model_id === model.id)
63
+ return current;
64
+ return publishPointer(store, {
65
+ schema_version: 1,
66
+ model_id: model.id,
67
+ run_id: model.run_id,
68
+ previous_model_id: current?.model_id ?? null,
69
+ activated_at: new Date().toISOString(),
70
+ action: "activate",
71
+ });
72
+ }
73
+ export async function rollbackActiveModel(store) {
74
+ const current = await readPointer(store);
75
+ if (!current)
76
+ throw new Error("No active-model history exists to roll back.");
77
+ const targetId = current.previous_model_id;
78
+ const target = targetId ? await assertModelCanActivate(store, targetId) : null;
79
+ return publishPointer(store, {
80
+ schema_version: 1,
81
+ model_id: target?.id ?? null,
82
+ run_id: target?.run_id ?? null,
83
+ previous_model_id: current.model_id,
84
+ activated_at: new Date().toISOString(),
85
+ action: "rollback",
86
+ });
87
+ }
88
+ //# sourceMappingURL=active-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-model.js","sourceRoot":"","sources":["../src/active-model.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,8 @@ export interface RunArtifacts {
7
7
  trainingReportJson: string;
8
8
  baselineEvalJson: string;
9
9
  candidateEvalJson: string;
10
+ generalBaselineEvalJson: string;
11
+ generalCandidateEvalJson: string;
10
12
  runReportJson: string;
11
13
  artifactManifestJson: string;
12
14
  progressJsonl: string;
@@ -77,16 +79,15 @@ export interface ArtifactManifest {
77
79
  }
78
80
  export interface ArtifactManifestModel {
79
81
  artifact_kind: "file" | "directory";
80
- format: string;
81
- framework: string;
82
+ format: "tar.gz" | "huggingface-directory";
83
+ framework: "transformers-peft";
82
84
  base_model: string;
83
85
  base_model_revision?: string;
84
86
  base_model_artifact_uri?: string;
85
87
  base_model_fingerprint?: string;
86
- parent_model_artifact?: string;
87
88
  artifact_uri: string;
88
89
  artifact_root: string;
89
- servable: boolean;
90
+ servable: true;
90
91
  files: ArtifactManifestFile[];
91
92
  }
92
93
  export interface ArtifactManifestVerification {