@tuned-tensor/local 0.2.6 → 0.2.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +149 -0
  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 +4 -0
  22. package/dist/index.js +766 -73
  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 +43 -0
  37. package/dist/prefetch.js +192 -0
  38. package/dist/prefetch.js.map +1 -0
  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/docs/spark.md +10 -0
  55. package/package.json +3 -1
  56. package/training/local-runner/pyproject.toml +1 -0
  57. package/training/local-runner/src/evaluate.py +80 -20
  58. package/training/local-runner/src/prefetch.py +178 -0
  59. package/training/local-runner/src/serve.py +329 -0
  60. package/training/local-runner/src/train.py +47 -22
  61. package/training/local-runner/src/train_dpo.py +41 -25
  62. package/training/local-runner/uv.lock +2401 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,66 @@
2
2
 
3
3
  All notable changes to TT Local will be documented in this file.
4
4
 
5
+ ## 0.2.8 - 2026-07-14
6
+
7
+ ### Added
8
+
9
+ - Added strict side-effect-free CLI help, `--version`, exact-runtime `doctor`
10
+ checks, and a Spark initialization profile.
11
+ - Added atomic artifact manifests, `tt-local models verify`, and local
12
+ OpenAI-compatible serving of verified PEFT adapters.
13
+ - Added local-only `tt-local models verify-base` snapshot validation and made
14
+ deliberately staged runs terminate as `stage_completed`.
15
+ - Added process-group cancellation and immediate post-training model
16
+ registration so a valid model survives later evaluation/report failures.
17
+ - Included the bundled Python `uv.lock` in the npm package.
18
+ - Added durable run/artifact ownership claims, workflow locks, detached runs,
19
+ terminal stage/cancellation states, and `runs watch` lifecycle handling.
20
+
21
+ ### Changed
22
+
23
+ - Standardized `paths.modelCache` as Hugging Face `HF_HOME` across prefetch,
24
+ training, evaluation, doctor, and serving; prefetch now reports the resolved
25
+ snapshot revision, file count, and byte size.
26
+ - Made `exact_match` the runnable scoring default. Explicit LLM-judge mode now
27
+ requires valid configuration/credentials unless exact-match fallback was
28
+ deliberately selected, and fallback results are never cached as judge data.
29
+ - Resume invalidation now fingerprints the complete request and effective
30
+ runner configuration, local data/assets/base snapshot, immutable model
31
+ revision, parent artifact, and bundled runtime rather than reusing outputs
32
+ trained with stale inputs.
33
+ - Relative config paths resolve from the config file and relative spec data
34
+ paths resolve from the spec/request file.
35
+ - Stored parent models now verify their artifact and immutable base identity;
36
+ continuation runs inherit the recorded base revision and reject conflicts.
37
+ - Baseline caching now requires stable content identity and bypasses mutable
38
+ remote models/assets. Optional progress reporters can no longer crash or
39
+ orphan child processes.
40
+
41
+ ### Fixed
42
+
43
+ - Prevented help, unknown flags, missing values, and option typos from starting
44
+ work accidentally.
45
+ - Rejected empty or corrupt model outputs before registration and cleared stale
46
+ terminal errors after successful retries.
47
+ - Rejected incomplete PEFT/full-model contracts, optimizer-only payloads,
48
+ artifact-prefix collisions, and symlink escapes before publication.
49
+ - Disabled optional PyTorch native JIT kernels by default for bundled ML
50
+ processes so packaged CUDA inference does not require system Python headers;
51
+ explicit runner environments can opt back in.
52
+ - Made interactive model-server shutdown close cleanly without a Python
53
+ traceback.
54
+ - Stopped reporting model checkpoint shard loading as optimizer progress.
55
+
56
+ ## 0.2.7 - 2026-07-05
57
+
58
+ ### Added
59
+
60
+ - Added `tt-local models prefetch [tunedtensor.json|request.json]` to download
61
+ the configured Hugging Face base model into `paths.modelCache` before the
62
+ first real run, making first-time setup explicit instead of hiding the
63
+ download inside baseline evaluation or training.
64
+
5
65
  ## 0.2.6 - 2026-07-05
6
66
 
7
67
  ### Fixed
package/README.md CHANGED
@@ -31,6 +31,146 @@ The default uv project is included in the npm package at
31
31
  `training/local-runner`; using bundled training does not require cloning this
32
32
  repository.
33
33
 
34
+ ## First Local Run
35
+
36
+ On an NVIDIA Spark or another CUDA host, initialize both the behavior spec and
37
+ a durable project-local runner config:
38
+
39
+ ```bash
40
+ tt-local --version
41
+ tt-local init --name "Support Bot" --model Qwen/Qwen3.5-2B --profile spark
42
+ ```
43
+
44
+ Edit `tunedtensor.json` to replace the generated system prompt and example.
45
+ TT Local refuses to validate or train the unchanged placeholder so an
46
+ accidental run cannot spend GPU time on it.
47
+
48
+ Then preflight the exact Python environments and paths that the run will use:
49
+
50
+ ```bash
51
+ tt-local doctor tunedtensor.json --config local-runner.json
52
+ tt-local validate tunedtensor.json --config local-runner.json
53
+ ```
54
+
55
+ `doctor` checks the bundled uv environment, Torch/Transformers/PEFT imports,
56
+ the requested CUDA/MPS device, judge credentials when configured, writable
57
+ artifact/cache/store roots, and available disk space. GPU checks are skipped
58
+ for dry runs and are conditional for CPU/MPS configurations.
59
+
60
+ Download the base model explicitly, then start the run:
61
+
62
+ ```bash
63
+ tt-local models prefetch tunedtensor.json --config local-runner.json
64
+ tt-local models verify-base tunedtensor.json --config local-runner.json
65
+ tt-local run tunedtensor.json --config local-runner.json
66
+ ```
67
+
68
+ Prefetch progress is visible by default. Pass `--quiet` only when another
69
+ program is consuming the final JSON. `verify-base` is local-only: it checks
70
+ the cached snapshot structure, indexed weight shards, tokenizer files, and
71
+ non-empty weights without downloading a replacement. Cached Hugging Face blobs
72
+ whose ETags encode Git or LFS checksums are re-hashed as part of verification.
73
+
74
+ For a long run, start it in the background and watch it from the current or a
75
+ second terminal:
76
+
77
+ ```bash
78
+ tt-local run tunedtensor.json --config local-runner.json --detach
79
+ tt-local runs watch <run-id> --config local-runner.json
80
+ ```
81
+
82
+ The detached command returns the persisted run ID, process ID, log path, and
83
+ copy-paste watch/cancel commands before model work begins.
84
+
85
+ After training:
86
+
87
+ ```bash
88
+ tt-local runs list --config local-runner.json
89
+ tt-local runs report <run-id> --config local-runner.json
90
+ tt-local models verify local-<run-id> --config local-runner.json
91
+ ```
92
+
93
+ The model is registered immediately after its artifact passes verification,
94
+ so it remains discoverable if candidate evaluation or report generation later
95
+ fails.
96
+
97
+ ## Serve a Trained Adapter
98
+
99
+ `tt-local serve` is the run dashboard. To serve a trained TT Local adapter as
100
+ an OpenAI-compatible model endpoint, use the nested model command:
101
+
102
+ ```bash
103
+ tt-local models serve local-<run-id> \
104
+ --config local-runner.json \
105
+ --spec tunedtensor.json \
106
+ --host 127.0.0.1 \
107
+ --port 8000
108
+ ```
109
+
110
+ The server verifies the stored artifact, loads the recorded base model and
111
+ PEFT adapter through the bundled uv environment, and binds to localhost by
112
+ default. Test it from another terminal:
113
+
114
+ ```bash
115
+ curl http://127.0.0.1:8000/health
116
+ curl http://127.0.0.1:8000/v1/models
117
+ curl http://127.0.0.1:8000/v1/chat/completions \
118
+ -H 'Content-Type: application/json' \
119
+ -d '{"messages":[{"role":"user","content":"Hello"}]}'
120
+ ```
121
+
122
+ Use `--device cuda|mps|cpu|auto` to override the configured inference device.
123
+ Non-loopback binds require both `--allow-remote` and
124
+ `--api-key-env <environment-variable>`; requests must then send that value as
125
+ a Bearer token. The server accepts multimodal images only as bounded `data:`
126
+ URIs and does not support streaming yet.
127
+
128
+ ## Cache and Evaluation Semantics
129
+
130
+ `paths.modelCache` means Hugging Face `HF_HOME` everywhere. Hub snapshots live
131
+ under `<modelCache>/hub`; prefetch, training, evaluation, doctor, and serving
132
+ all use that same layout. Set `HF_TOKEN` in the environment or project `.env`
133
+ before prefetching a gated model. Do not commit `.env`.
134
+
135
+ For reproducible remote loading, set
136
+ `hyperparameters.base_model_revision` to an immutable Hugging Face commit.
137
+ Alternatively, `paths.baseModel` may point to a complete local Hugging Face
138
+ snapshot directory. It cannot point to a model archive or standalone weights
139
+ file; TT Local requires non-empty model weights, `config.json`, tokenizer
140
+ metadata and vocabulary, and every indexed shard. Local snapshot contents are
141
+ fingerprinted so a byte change invalidates dependent stages.
142
+
143
+ Shared baseline-cache entries are used only when the model and every local
144
+ input have a stable content identity. An unpinned remote model or remotely
145
+ hosted image bypasses the cache rather than risking stale evaluation results.
146
+
147
+ The safe default scoring mode is deterministic `exact_match`. To use an
148
+ OpenRouter judge, configure `evaluation.scoring.mode: "llm_judge"`, an `llm`
149
+ block, and its API-key environment variable. Judge mode fails preflight when
150
+ those are missing. Exact-match fallback occurs only when explicitly requested
151
+ with `evaluation.scoring.fallback: "exact_match"`; fallback-scored results are
152
+ marked and never written to the judge baseline cache.
153
+
154
+ Before the first real run, you can download the configured Hugging Face base
155
+ model into `paths.modelCache` explicitly:
156
+
157
+ ```bash
158
+ tt-local models prefetch tunedtensor.json --config local-runner.json
159
+ tt-local models verify-base tunedtensor.json --config local-runner.json
160
+ ```
161
+
162
+ Without this step, the first non-dry `tt-local run` downloads the base model
163
+ when baseline evaluation or training first loads it.
164
+
165
+ Individual `run --stage ...` commands finish in the terminal
166
+ `stage_completed` state, so `runs watch` exits instead of waiting for stages
167
+ that were intentionally not requested.
168
+
169
+ Relative paths in a runner config resolve from the config file's directory.
170
+ Relative dataset and image paths resolve from the behavior spec or request
171
+ file's directory. The published package includes `uv.lock` so bundled Python
172
+ dependencies are reproducible.
173
+
34
174
  ## DPO
35
175
 
36
176
  TT Local supports first-class offline DPO for text causal-LM models. Set
@@ -65,6 +205,8 @@ example `external:karpathy/nanochat`. The bundled `uv` trainer still requires a
65
205
  supported Hugging Face base model, but command trainers receive adapter-focused
66
206
  hyperparameters without injected LoRA/model-loader defaults. Custom
67
207
  hyperparameter keys are passed through to `TT_HYPERPARAMETERS_PATH`.
208
+ Set `hyperparameters.base_model_revision` to an immutable Hugging Face commit
209
+ when a run must load and record an exact base-model revision.
68
210
 
69
211
  ## Continuing From a Fine-Tuned Model
70
212
 
@@ -86,6 +228,13 @@ For config-only workflows, put the same value in
86
228
  baseline for the new run, and the bundled SFT/DPO trainers load it before
87
229
  continuing LoRA training.
88
230
 
231
+ Stored `--parent-model` references are verified before launch. TT Local pins
232
+ the child's base revision from the parent (or requires the same configured
233
+ local snapshot content), rejects conflicting base identity, and fingerprints
234
+ the parent artifact so changed bytes invalidate reuse. The explicit
235
+ `--parent-model-artifact` form is for caller-managed local artifacts and should
236
+ be used only when that provenance is understood.
237
+
89
238
  Non-Hugging Face artifacts can describe their layout in `training.artifact`:
90
239
 
91
240
  ```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>;