@tuned-tensor/cli 0.13.1-beta.6 → 0.13.1
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/README.md +47 -19
- package/dist/{chunk-W7MOBPCO.js → chunk-CAEBS5HH.js} +2812 -2793
- package/dist/chunk-CAEBS5HH.js.map +1 -0
- package/dist/index.js +787 -307
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +8 -2
- package/dist/local-runtime.js.map +1 -1
- package/docs/local-runtime/architecture.md +16 -2
- package/docs/local-runtime/spark.md +1 -1
- package/package.json +1 -1
- package/training/foundation/src/data.py +65 -3
- package/training/foundation/src/evaluate.py +34 -8
- package/training/foundation/src/pretrain.py +15 -2
- package/training/foundation/src/rl.py +11 -3
- package/training/foundation/src/train_tokenizer.py +11 -2
- package/dist/chunk-W7MOBPCO.js.map +0 -1
|
@@ -43,9 +43,9 @@ resource defaults, data contract, and real CUDA acceptance test.
|
|
|
43
43
|
TT Local currently ships two such methods:
|
|
44
44
|
|
|
45
45
|
- **Adapter** (`training/adapter`): certified Transformers checkpoints and
|
|
46
|
-
PEFT LoRA SFT. Driven by `tt run
|
|
46
|
+
PEFT LoRA SFT. Driven by `tt pipeline run`.
|
|
47
47
|
- **Foundation** (`training/foundation`): a from-scratch BPE tokenizer and GPT
|
|
48
|
-
with no Transformers/PEFT. Driven
|
|
48
|
+
with no Transformers/PEFT. Driven by `tt pipeline run` from a foundation
|
|
49
49
|
`tunedtensor.json`. Pretrain, SFT, and RL require CUDA. Tokenizer training
|
|
50
50
|
and the CPU unit tests do not. The current runtime is single-process and
|
|
51
51
|
rejects `nproc_per_node` values above 1 and unsupported steps such as
|
|
@@ -68,6 +68,20 @@ held-out model capability.
|
|
|
68
68
|
Do not mix those lockfiles. Each engine hashes its own `pyproject.toml` and
|
|
69
69
|
`uv.lock` into a distinct cache environment.
|
|
70
70
|
|
|
71
|
+
## Spec-driven instruction contract
|
|
72
|
+
|
|
73
|
+
Adapter and foundation specs extend one engine-neutral behavior schema:
|
|
74
|
+
`name`, `description`, `system_prompt`, `guidelines`, `constraints`, and
|
|
75
|
+
`examples`. Node compiles the three instruction fields into one canonical
|
|
76
|
+
system message. The adapter dataset compiler and every foundation stage use
|
|
77
|
+
that same compiler, so tokenizer input, pretraining smoke data, chat SFT, RL,
|
|
78
|
+
and evaluation all receive guidelines and constraints consistently.
|
|
79
|
+
|
|
80
|
+
Engine-specific fields control execution only. Adapter specs add `base_model`,
|
|
81
|
+
LoRA hyperparameters, and optional prebuilt datasets. Foundation specs add
|
|
82
|
+
`engine: "foundation"` and the `foundation` training configuration. The
|
|
83
|
+
behavioral instruction remains the shared source of truth.
|
|
84
|
+
|
|
71
85
|
## Python boundary
|
|
72
86
|
|
|
73
87
|
Node owns input validation, state, process lifecycle, manifests, and reports.
|
|
@@ -108,7 +108,7 @@ tt doctor tunedtensor.json
|
|
|
108
108
|
tt validate tunedtensor.json
|
|
109
109
|
tt models prefetch tunedtensor.json
|
|
110
110
|
tt models verify-base tunedtensor.json
|
|
111
|
-
tt run tunedtensor.json
|
|
111
|
+
tt pipeline run --spec tunedtensor.json --config local-runner.json
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
`doctor` resolves the same bundled project and paths the run will use, imports
|
package/package.json
CHANGED
|
@@ -105,6 +105,13 @@ def iter_corpus_documents(path: str | Path, max_chars: int | None = None) -> Ite
|
|
|
105
105
|
yield text
|
|
106
106
|
|
|
107
107
|
|
|
108
|
+
def instruction_corpus_documents(system_prompt: str, documents: Iterable[str]) -> Iterator[str]:
|
|
109
|
+
instruction = system_prompt.strip()
|
|
110
|
+
if instruction:
|
|
111
|
+
yield instruction
|
|
112
|
+
yield from documents
|
|
113
|
+
|
|
114
|
+
|
|
108
115
|
def corpus_documents_hash(documents: Iterable[str]) -> tuple[str, int]:
|
|
109
116
|
digest = hashlib.sha256()
|
|
110
117
|
chars = 0
|
|
@@ -141,6 +148,52 @@ def encode_ids(tokenizer: Tokenizer, text: str) -> list[int]:
|
|
|
141
148
|
return list(tokenizer.encode(text).ids)
|
|
142
149
|
|
|
143
150
|
|
|
151
|
+
def encode_prompt_ids(
|
|
152
|
+
tokenizer: Tokenizer,
|
|
153
|
+
system_prompt: str,
|
|
154
|
+
user: str,
|
|
155
|
+
max_tokens: int,
|
|
156
|
+
) -> list[int]:
|
|
157
|
+
"""Keep the complete spec instruction and trim only user content."""
|
|
158
|
+
full_ids = encode_ids(tokenizer, format_prompt(system_prompt, user))
|
|
159
|
+
if len(full_ids) <= max_tokens:
|
|
160
|
+
return full_ids
|
|
161
|
+
system = system_prompt.strip() or "You are a helpful assistant."
|
|
162
|
+
prefix_ids = encode_ids(tokenizer, f"{SYSTEM}{system}{END}{USER}")
|
|
163
|
+
suffix_ids = encode_ids(tokenizer, f"{END}{ASSISTANT}")
|
|
164
|
+
if len(prefix_ids) + len(suffix_ids) > max_tokens:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
"Compiled spec instruction exceeds the foundation model context; "
|
|
167
|
+
"increase foundation.sequence_length or shorten the spec instruction."
|
|
168
|
+
)
|
|
169
|
+
user_ids = encode_ids(tokenizer, user)
|
|
170
|
+
user_budget = max_tokens - len(prefix_ids) - len(suffix_ids)
|
|
171
|
+
retained_user_ids = user_ids[-user_budget:] if user_budget > 0 else []
|
|
172
|
+
return prefix_ids + retained_user_ids + suffix_ids
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def encode_generation_prompt(
|
|
176
|
+
tokenizer: Tokenizer,
|
|
177
|
+
system_prompt: str,
|
|
178
|
+
user: str,
|
|
179
|
+
context_length: int,
|
|
180
|
+
requested_completion_tokens: int,
|
|
181
|
+
) -> tuple[list[int], int]:
|
|
182
|
+
"""Reserve completion space without rejecting an instruction that can fit."""
|
|
183
|
+
completion_tokens = min(requested_completion_tokens, max(1, context_length // 2))
|
|
184
|
+
try:
|
|
185
|
+
prompt_ids = encode_prompt_ids(
|
|
186
|
+
tokenizer,
|
|
187
|
+
system_prompt,
|
|
188
|
+
user,
|
|
189
|
+
context_length - completion_tokens,
|
|
190
|
+
)
|
|
191
|
+
except ValueError:
|
|
192
|
+
prompt_ids = encode_prompt_ids(tokenizer, system_prompt, user, context_length - 1)
|
|
193
|
+
completion_tokens = context_length - len(prompt_ids)
|
|
194
|
+
return prompt_ids, completion_tokens
|
|
195
|
+
|
|
196
|
+
|
|
144
197
|
def decode_ids(tokenizer: Tokenizer, ids: list[int]) -> str:
|
|
145
198
|
return tokenizer.decode(ids, skip_special_tokens=False)
|
|
146
199
|
|
|
@@ -168,9 +221,18 @@ def encode_sft_example(
|
|
|
168
221
|
full_ids = encode_ids(tokenizer, format_chat(system_prompt, user, assistant))
|
|
169
222
|
model_tokens = sequence_length + 1
|
|
170
223
|
if len(full_ids) > model_tokens:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
224
|
+
assistant_ids = encode_ids(tokenizer, f"{assistant}{END}")
|
|
225
|
+
reserved_assistant_tokens = min(2, len(assistant_ids))
|
|
226
|
+
prefix_ids = encode_prompt_ids(
|
|
227
|
+
tokenizer,
|
|
228
|
+
system_prompt,
|
|
229
|
+
user,
|
|
230
|
+
model_tokens - reserved_assistant_tokens,
|
|
231
|
+
)
|
|
232
|
+
assistant_budget = model_tokens - len(prefix_ids)
|
|
233
|
+
if len(assistant_ids) > assistant_budget:
|
|
234
|
+
assistant_ids = assistant_ids[:assistant_budget - 1] + assistant_ids[-1:]
|
|
235
|
+
full_ids = prefix_ids + assistant_ids
|
|
174
236
|
input_ids = pad_or_trim(full_ids[:-1], sequence_length, pad_id)
|
|
175
237
|
target_ids = full_ids[1:]
|
|
176
238
|
first_assistant_target = max(0, min(len(prefix_ids) - 1, len(target_ids)))
|
|
@@ -8,7 +8,7 @@ from tokenizers import Tokenizer
|
|
|
8
8
|
from torch.nn import functional as F
|
|
9
9
|
|
|
10
10
|
from common import load_config, write_json
|
|
11
|
-
from data import END, decoded_byte_count,
|
|
11
|
+
from data import END, decoded_byte_count, encode_generation_prompt, example_pairs, iter_corpus_documents, smoke_corpus, window_tokens
|
|
12
12
|
from model import bits_per_byte, generate, load_model
|
|
13
13
|
|
|
14
14
|
|
|
@@ -45,12 +45,19 @@ def evaluate_chat(
|
|
|
45
45
|
correct = 0
|
|
46
46
|
model.eval()
|
|
47
47
|
for example in examples:
|
|
48
|
-
|
|
48
|
+
context_length = int(model.config["sequence_length"])
|
|
49
|
+
prompt_ids, completion_tokens = encode_generation_prompt(
|
|
50
|
+
tokenizer,
|
|
51
|
+
system_prompt,
|
|
52
|
+
example["input"],
|
|
53
|
+
context_length,
|
|
54
|
+
32,
|
|
55
|
+
)
|
|
49
56
|
tokens = torch.tensor([prompt_ids], dtype=torch.long, device=device)
|
|
50
57
|
generated = generate(
|
|
51
58
|
model,
|
|
52
59
|
tokens,
|
|
53
|
-
max_new_tokens=
|
|
60
|
+
max_new_tokens=completion_tokens,
|
|
54
61
|
stop_token_id=tokenizer.token_to_id(END),
|
|
55
62
|
)[0].tolist()[len(prompt_ids):]
|
|
56
63
|
text = tokenizer.decode(generated).split(END)[0].strip()
|
|
@@ -65,14 +72,27 @@ def evaluate_chat(
|
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
|
|
68
|
-
def evaluate_inference(
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
def evaluate_inference(
|
|
76
|
+
model,
|
|
77
|
+
tokenizer: Tokenizer,
|
|
78
|
+
system_prompt: str,
|
|
79
|
+
user_prompt: str,
|
|
80
|
+
device: torch.device,
|
|
81
|
+
) -> dict[str, float | int | str]:
|
|
82
|
+
context_length = int(model.config["sequence_length"])
|
|
83
|
+
prompt_ids, completion_tokens = encode_generation_prompt(
|
|
84
|
+
tokenizer,
|
|
85
|
+
system_prompt,
|
|
86
|
+
user_prompt,
|
|
87
|
+
context_length,
|
|
88
|
+
32,
|
|
89
|
+
)
|
|
90
|
+
tokens = torch.tensor([prompt_ids], dtype=torch.long, device=device)
|
|
71
91
|
if device.type == "cuda":
|
|
72
92
|
torch.cuda.reset_peak_memory_stats(device)
|
|
73
93
|
torch.cuda.synchronize()
|
|
74
94
|
started = time.perf_counter()
|
|
75
|
-
generated = generate(model, tokens, max_new_tokens=
|
|
95
|
+
generated = generate(model, tokens, max_new_tokens=completion_tokens)
|
|
76
96
|
if device.type == "cuda":
|
|
77
97
|
torch.cuda.synchronize()
|
|
78
98
|
elapsed = max(time.perf_counter() - started, 1e-6)
|
|
@@ -112,7 +132,13 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
112
132
|
metrics.update(evaluate_chat(model, tokenizer, str(config.get("system_prompt") or ""), examples, device))
|
|
113
133
|
elif evaluator == "inference":
|
|
114
134
|
prompt = examples[0]["input"] if examples else "hello"
|
|
115
|
-
metrics.update(evaluate_inference(
|
|
135
|
+
metrics.update(evaluate_inference(
|
|
136
|
+
model,
|
|
137
|
+
tokenizer,
|
|
138
|
+
str(config.get("system_prompt") or ""),
|
|
139
|
+
prompt,
|
|
140
|
+
device,
|
|
141
|
+
))
|
|
116
142
|
else:
|
|
117
143
|
raise SystemExit(f"Unknown foundation evaluator: {evaluator}")
|
|
118
144
|
write_json(output_dir / "metrics.json", metrics)
|
|
@@ -16,7 +16,15 @@ from torch.nn import functional as F
|
|
|
16
16
|
|
|
17
17
|
from checkpoint import CheckpointManager
|
|
18
18
|
from common import append_jsonl, ensure_private_directory, load_config, make_private_file, require_cuda, write_json
|
|
19
|
-
from data import
|
|
19
|
+
from data import (
|
|
20
|
+
END,
|
|
21
|
+
corpus_hash,
|
|
22
|
+
corpus_manifest,
|
|
23
|
+
example_pairs,
|
|
24
|
+
instruction_corpus_documents,
|
|
25
|
+
iter_corpus_documents,
|
|
26
|
+
smoke_corpus,
|
|
27
|
+
)
|
|
20
28
|
from model import FoundationGPT, model_config_from_depth, param_count, save_model
|
|
21
29
|
|
|
22
30
|
|
|
@@ -37,10 +45,12 @@ def file_sha256(path: str | Path) -> str:
|
|
|
37
45
|
def source_signature(config: dict[str, Any]) -> dict[str, Any]:
|
|
38
46
|
corpus_path = config.get("corpus_path")
|
|
39
47
|
if corpus_path:
|
|
48
|
+
instruction = str(config.get("system_prompt") or "").strip()
|
|
40
49
|
return {
|
|
41
50
|
"kind": "files",
|
|
42
51
|
"manifest": corpus_manifest(corpus_path),
|
|
43
52
|
"max_chars": int(config.get("max_chars") or 0),
|
|
53
|
+
"instruction_sha256": corpus_hash(instruction),
|
|
44
54
|
}
|
|
45
55
|
examples = example_pairs(config.get("examples") or [])
|
|
46
56
|
corpus = smoke_corpus(
|
|
@@ -142,7 +152,10 @@ def prepare_token_cache(
|
|
|
142
152
|
recorded_bytes = 0
|
|
143
153
|
corpus_path = config.get("corpus_path")
|
|
144
154
|
if corpus_path:
|
|
145
|
-
documents =
|
|
155
|
+
documents = instruction_corpus_documents(
|
|
156
|
+
str(config.get("system_prompt") or ""),
|
|
157
|
+
iter_corpus_documents(corpus_path, int(config.get("max_chars") or 0)),
|
|
158
|
+
)
|
|
146
159
|
else:
|
|
147
160
|
examples = example_pairs(config.get("examples") or [])
|
|
148
161
|
documents = iter([smoke_corpus(
|
|
@@ -7,7 +7,7 @@ from tokenizers import Tokenizer
|
|
|
7
7
|
from torch.nn import functional as F
|
|
8
8
|
|
|
9
9
|
from common import load_config, require_cuda, write_json
|
|
10
|
-
from data import END,
|
|
10
|
+
from data import END, encode_generation_prompt, example_pairs, numeric_reward
|
|
11
11
|
from model import FoundationGPT, load_model, param_count, save_model
|
|
12
12
|
|
|
13
13
|
|
|
@@ -73,10 +73,18 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
73
73
|
last_loss = 0.0
|
|
74
74
|
for step in range(steps):
|
|
75
75
|
example = examples[step % len(examples)]
|
|
76
|
-
|
|
76
|
+
context_length = int(model.config["sequence_length"])
|
|
77
|
+
prompt_ids, completion_tokens = encode_generation_prompt(
|
|
78
|
+
tokenizer,
|
|
79
|
+
str(config.get("system_prompt") or ""),
|
|
80
|
+
example["input"],
|
|
81
|
+
context_length,
|
|
82
|
+
24,
|
|
83
|
+
)
|
|
77
84
|
prompt_ids, completion_tokens = bounded_rollout(
|
|
78
85
|
prompt_ids,
|
|
79
|
-
|
|
86
|
+
context_length,
|
|
87
|
+
completion_tokens,
|
|
80
88
|
)
|
|
81
89
|
prompt = torch.tensor([prompt_ids], dtype=torch.long, device=device)
|
|
82
90
|
sampled = sample_rollout(
|
|
@@ -6,6 +6,7 @@ from data import (
|
|
|
6
6
|
corpus_hash,
|
|
7
7
|
corpus_manifest,
|
|
8
8
|
example_pairs,
|
|
9
|
+
instruction_corpus_documents,
|
|
9
10
|
iter_corpus_documents,
|
|
10
11
|
smoke_corpus,
|
|
11
12
|
train_tokenizer,
|
|
@@ -20,9 +21,17 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
20
21
|
corpus_path = config.get("corpus_path")
|
|
21
22
|
if corpus_path:
|
|
22
23
|
manifest_before = corpus_manifest(corpus_path)
|
|
23
|
-
|
|
24
|
+
system_prompt = str(config.get("system_prompt") or "")
|
|
25
|
+
|
|
26
|
+
def documents():
|
|
27
|
+
return instruction_corpus_documents(
|
|
28
|
+
system_prompt,
|
|
29
|
+
iter_corpus_documents(corpus_path, max_chars),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
corpus_sha256, corpus_chars = corpus_documents_hash(documents())
|
|
24
33
|
tokenizer = train_tokenizer(
|
|
25
|
-
|
|
34
|
+
documents(),
|
|
26
35
|
int(config.get("vocab_size") or 256),
|
|
27
36
|
)
|
|
28
37
|
if corpus_manifest(corpus_path) != manifest_before:
|