@tuned-tensor/local 0.2.7 → 0.2.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +147 -2
  3. package/dist/artifacts.d.ts +92 -0
  4. package/dist/artifacts.js +591 -3
  5. package/dist/artifacts.js.map +1 -1
  6. package/dist/contracts.d.ts +5 -2
  7. package/dist/contracts.js +15 -10
  8. package/dist/contracts.js.map +1 -1
  9. package/dist/dataset.d.ts +3 -0
  10. package/dist/dataset.js +87 -5
  11. package/dist/dataset.js.map +1 -1
  12. package/dist/doctor.d.ts +13 -2
  13. package/dist/doctor.js +372 -49
  14. package/dist/doctor.js.map +1 -1
  15. package/dist/evaluation.d.ts +15 -0
  16. package/dist/evaluation.js +120 -16
  17. package/dist/evaluation.js.map +1 -1
  18. package/dist/huggingface-cache.d.ts +26 -0
  19. package/dist/huggingface-cache.js +68 -0
  20. package/dist/huggingface-cache.js.map +1 -0
  21. package/dist/index.d.ts +3 -0
  22. package/dist/index.js +742 -76
  23. package/dist/index.js.map +1 -1
  24. package/dist/local-project.d.ts +11 -0
  25. package/dist/local-project.js +96 -3
  26. package/dist/local-project.js.map +1 -1
  27. package/dist/model-registry.d.ts +21 -0
  28. package/dist/model-registry.js +198 -0
  29. package/dist/model-registry.js.map +1 -1
  30. package/dist/model-server.d.ts +32 -0
  31. package/dist/model-server.js +158 -0
  32. package/dist/model-server.js.map +1 -0
  33. package/dist/orchestrator.d.ts +3 -0
  34. package/dist/orchestrator.js +1001 -142
  35. package/dist/orchestrator.js.map +1 -1
  36. package/dist/prefetch.d.ts +13 -0
  37. package/dist/prefetch.js +107 -19
  38. package/dist/prefetch.js.map +1 -1
  39. package/dist/process-runner.d.ts +7 -0
  40. package/dist/process-runner.js +171 -16
  41. package/dist/process-runner.js.map +1 -1
  42. package/dist/process-training.d.ts +5 -1
  43. package/dist/process-training.js +32 -16
  44. package/dist/process-training.js.map +1 -1
  45. package/dist/run-reporter.d.ts +2 -0
  46. package/dist/run-reporter.js +10 -0
  47. package/dist/run-reporter.js.map +1 -1
  48. package/dist/store.d.ts +16 -2
  49. package/dist/store.js +189 -24
  50. package/dist/store.js.map +1 -1
  51. package/docs/architecture.md +32 -0
  52. package/docs/local-workflow-remediation-2026-07-13.md +130 -0
  53. package/docs/local-workflow-ux-review-2026-07-13.md +458 -0
  54. package/package.json +4 -2
  55. package/training/local-runner/src/evaluate.py +80 -20
  56. package/training/local-runner/src/prefetch.py +107 -8
  57. package/training/local-runner/src/serve.py +329 -0
  58. package/training/local-runner/src/train.py +47 -22
  59. package/training/local-runner/src/train_dpo.py +41 -25
  60. package/training/local-runner/uv.lock +2401 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,65 @@
2
2
 
3
3
  All notable changes to TT Local will be documented in this file.
4
4
 
5
+ ## 0.2.9 - 2026-07-14
6
+
7
+ ### Changed
8
+
9
+ - Reframed TT Local as local-first fine-tuning with paired baseline-vs-tuned
10
+ evaluation for small open-weight models, and aligned the README, package
11
+ metadata, and CLI help with that positioning.
12
+
13
+ ## 0.2.8 - 2026-07-14
14
+
15
+ ### Added
16
+
17
+ - Added strict side-effect-free CLI help, `--version`, exact-runtime `doctor`
18
+ checks, and a Spark initialization profile.
19
+ - Added atomic artifact manifests, `tt-local models verify`, and local
20
+ OpenAI-compatible serving of verified PEFT adapters.
21
+ - Added local-only `tt-local models verify-base` snapshot validation and made
22
+ deliberately staged runs terminate as `stage_completed`.
23
+ - Added process-group cancellation and immediate post-training model
24
+ registration so a valid model survives later evaluation/report failures.
25
+ - Included the bundled Python `uv.lock` in the npm package.
26
+ - Added durable run/artifact ownership claims, workflow locks, detached runs,
27
+ terminal stage/cancellation states, and `runs watch` lifecycle handling.
28
+
29
+ ### Changed
30
+
31
+ - Standardized `paths.modelCache` as Hugging Face `HF_HOME` across prefetch,
32
+ training, evaluation, doctor, and serving; prefetch now reports the resolved
33
+ snapshot revision, file count, and byte size.
34
+ - Made `exact_match` the runnable scoring default. Explicit LLM-judge mode now
35
+ requires valid configuration/credentials unless exact-match fallback was
36
+ deliberately selected, and fallback results are never cached as judge data.
37
+ - Resume invalidation now fingerprints the complete request and effective
38
+ runner configuration, local data/assets/base snapshot, immutable model
39
+ revision, parent artifact, and bundled runtime rather than reusing outputs
40
+ trained with stale inputs.
41
+ - Relative config paths resolve from the config file and relative spec data
42
+ paths resolve from the spec/request file.
43
+ - Stored parent models now verify their artifact and immutable base identity;
44
+ continuation runs inherit the recorded base revision and reject conflicts.
45
+ - Baseline caching now requires stable content identity and bypasses mutable
46
+ remote models/assets. Optional progress reporters can no longer crash or
47
+ orphan child processes.
48
+
49
+ ### Fixed
50
+
51
+ - Prevented help, unknown flags, missing values, and option typos from starting
52
+ work accidentally.
53
+ - Rejected empty or corrupt model outputs before registration and cleared stale
54
+ terminal errors after successful retries.
55
+ - Rejected incomplete PEFT/full-model contracts, optimizer-only payloads,
56
+ artifact-prefix collisions, and symlink escapes before publication.
57
+ - Disabled optional PyTorch native JIT kernels by default for bundled ML
58
+ processes so packaged CUDA inference does not require system Python headers;
59
+ explicit runner environments can opt back in.
60
+ - Made interactive model-server shutdown close cleanly without a Python
61
+ traceback.
62
+ - Stopped reporting model checkpoint shard loading as optimizer progress.
63
+
5
64
  ## 0.2.7 - 2026-07-05
6
65
 
7
66
  ### Added
package/README.md CHANGED
@@ -3,8 +3,11 @@
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 runs Tuned Tensor-style fine-tuning jobs on your own machine. It keeps
7
- specs, datasets, model artifacts, events, reports, and dashboard state on local
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
8
11
  disk.
9
12
 
10
13
  Usage docs:
@@ -31,16 +34,149 @@ The default uv project is included in the npm package at
31
34
  `training/local-runner`; using bundled training does not require cloning this
32
35
  repository.
33
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:
41
+
42
+ ```bash
43
+ tt-local --version
44
+ tt-local init --name "Support Bot" --model Qwen/Qwen3.5-2B --profile spark
45
+ ```
46
+
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.
50
+
51
+ Then preflight the exact Python environments and paths that the run will use:
52
+
53
+ ```bash
54
+ tt-local doctor tunedtensor.json --config local-runner.json
55
+ tt-local validate tunedtensor.json --config local-runner.json
56
+ ```
57
+
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.
62
+
63
+ Download the base model explicitly, then start the run:
64
+
65
+ ```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
69
+ ```
70
+
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.
76
+
77
+ For a long run, start it in the background and watch it from the current or a
78
+ second terminal:
79
+
80
+ ```bash
81
+ tt-local run tunedtensor.json --config local-runner.json --detach
82
+ tt-local runs watch <run-id> --config local-runner.json
83
+ ```
84
+
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:
89
+
90
+ ```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
94
+ ```
95
+
96
+ Treat the report as evidence on this run's evaluation cases, not as a
97
+ guarantee of live application improvement.
98
+
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
114
+ ```
115
+
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:
119
+
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
+
34
160
  Before the first real run, you can download the configured Hugging Face base
35
161
  model into `paths.modelCache` explicitly:
36
162
 
37
163
  ```bash
38
164
  tt-local models prefetch tunedtensor.json --config local-runner.json
165
+ tt-local models verify-base tunedtensor.json --config local-runner.json
39
166
  ```
40
167
 
41
168
  Without this step, the first non-dry `tt-local run` downloads the base model
42
169
  when baseline evaluation or training first loads it.
43
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.
174
+
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.
179
+
44
180
  ## DPO
45
181
 
46
182
  TT Local supports first-class offline DPO for text causal-LM models. Set
@@ -75,6 +211,8 @@ example `external:karpathy/nanochat`. The bundled `uv` trainer still requires a
75
211
  supported Hugging Face base model, but command trainers receive adapter-focused
76
212
  hyperparameters without injected LoRA/model-loader defaults. Custom
77
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.
78
216
 
79
217
  ## Continuing From a Fine-Tuned Model
80
218
 
@@ -96,6 +234,13 @@ For config-only workflows, put the same value in
96
234
  baseline for the new run, and the bundled SFT/DPO trainers load it before
97
235
  continuing LoRA training.
98
236
 
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.
243
+
99
244
  Non-Hugging Face artifacts can describe their layout in `training.artifact`:
100
245
 
101
246
  ```json
@@ -8,6 +8,7 @@ export interface RunArtifacts {
8
8
  baselineEvalJson: string;
9
9
  candidateEvalJson: string;
10
10
  runReportJson: string;
11
+ artifactManifestJson: string;
11
12
  progressJsonl: string;
12
13
  trainingDir: string;
13
14
  trainingInputDir: string;
@@ -16,6 +17,14 @@ export interface RunArtifacts {
16
17
  trainingModelDir: string;
17
18
  trainingLog: string;
18
19
  }
20
+ export declare const ARTIFACT_WORKFLOW_LOCK_FILE = ".tt-local-workflow.lock";
21
+ export interface ArtifactRunOwner {
22
+ schema_version: 1;
23
+ run_id: string;
24
+ user_id: string;
25
+ behavior_spec_id: string;
26
+ created_at: string;
27
+ }
19
28
  export declare function fileUri(path: string): string;
20
29
  export declare function defaultArtifactPrefix(input: {
21
30
  userId: string;
@@ -26,8 +35,28 @@ export declare function resolveRunArtifacts(args: {
26
35
  artifactRoot: string;
27
36
  prefix: string;
28
37
  }): RunArtifacts;
38
+ /**
39
+ * Creates the run directory one component at a time and rejects symlinked
40
+ * descendants. Lexical `resolve()` containment alone is insufficient because
41
+ * an existing child symlink could redirect later writes or recursive cleanup
42
+ * outside `artifactRoot`.
43
+ */
44
+ export declare function ensureSafeRunDirectory(artifacts: RunArtifacts): Promise<void>;
45
+ /**
46
+ * Durably binds an artifact directory to one run identity. The owner survives
47
+ * workflow-lock release, preventing a later run with a colliding custom
48
+ * prefix from deleting or silently reusing another run's artifacts.
49
+ */
50
+ export declare function claimRunArtifactDirectory(args: {
51
+ artifacts: RunArtifacts;
52
+ runId: string;
53
+ userId: string;
54
+ behaviorSpecId: string;
55
+ }): Promise<ArtifactRunOwner>;
29
56
  export declare function prepareRunDirectories(artifacts: RunArtifacts): Promise<void>;
30
57
  export declare function writeJson(path: string, value: unknown): Promise<void>;
58
+ export declare function writeJsonAtomic(path: string, value: unknown): Promise<void>;
59
+ export declare function writeFileAtomic(path: string, value: string | Uint8Array): Promise<void>;
31
60
  export declare function readJson<T>(path: string): Promise<T>;
32
61
  export declare function appendProgress(artifacts: RunArtifacts, event: {
33
62
  stage: string;
@@ -35,3 +64,66 @@ export declare function appendProgress(artifacts: RunArtifacts, event: {
35
64
  details?: Record<string, unknown>;
36
65
  }): Promise<void>;
37
66
  export declare function copyDatasetToTrainingChannel(artifacts: RunArtifacts): Promise<string>;
67
+ export interface ArtifactManifestFile {
68
+ path: string;
69
+ size_bytes: number;
70
+ sha256: string;
71
+ }
72
+ export interface ArtifactManifest {
73
+ schema_version: 1;
74
+ generated_at: string;
75
+ files: ArtifactManifestFile[];
76
+ model?: ArtifactManifestModel;
77
+ }
78
+ export interface ArtifactManifestModel {
79
+ artifact_kind: "file" | "directory";
80
+ format: string;
81
+ framework: string;
82
+ base_model: string;
83
+ base_model_revision?: string;
84
+ base_model_artifact_uri?: string;
85
+ base_model_fingerprint?: string;
86
+ parent_model_artifact?: string;
87
+ artifact_uri: string;
88
+ artifact_root: string;
89
+ servable: boolean;
90
+ files: ArtifactManifestFile[];
91
+ }
92
+ export interface ArtifactManifestVerification {
93
+ valid: boolean;
94
+ checked: number;
95
+ missing: string[];
96
+ changed: string[];
97
+ unexpected: string[];
98
+ }
99
+ export interface TarGzipVerification {
100
+ entries: number;
101
+ payload_entries: number;
102
+ recognized_payload_entries: number;
103
+ recognized_payload_bytes: number;
104
+ adapter_weight_entries: number;
105
+ adapter_weight_bytes: number;
106
+ full_model_weight_entries: number;
107
+ full_model_weight_bytes: number;
108
+ adapter_config_entries: number;
109
+ expanded_bytes: number;
110
+ }
111
+ /** Atomically snapshots every immutable file currently present in a run directory. */
112
+ export declare function writeArtifactManifest(artifacts: RunArtifacts, options?: {
113
+ model?: Omit<ArtifactManifestModel, "files">;
114
+ }): Promise<ArtifactManifest>;
115
+ /** Re-hashes a persisted run and reports missing, changed, and untracked files. */
116
+ export declare function verifyArtifactManifest(manifestPath: string, options?: {
117
+ requiredPaths?: string[];
118
+ allowUnexpected?: boolean;
119
+ scopeToRequired?: boolean;
120
+ verifyModel?: boolean;
121
+ }): Promise<ArtifactManifestVerification>;
122
+ /** Validates gzip CRC/truncation, tar headers, paths, and bounded PAX metadata. */
123
+ export declare function verifyTarGzipArchive(path: string): Promise<TarGzipVerification>;
124
+ export declare function assertArtifactManifest(manifestPath: string, options?: {
125
+ requiredPaths?: string[];
126
+ allowUnexpected?: boolean;
127
+ scopeToRequired?: boolean;
128
+ verifyModel?: boolean;
129
+ }): Promise<ArtifactManifestVerification>;