@tuned-tensor/cli 0.13.1-beta.6 → 0.13.1-beta.7
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 +7 -0
- package/dist/{chunk-W7MOBPCO.js → chunk-AKJ5WZ2S.js} +15 -15
- package/dist/chunk-AKJ5WZ2S.js.map +1 -0
- package/dist/index.js +23 -8
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +3 -1
- package/dist/local-runtime.js.map +1 -1
- package/docs/local-runtime/architecture.md +14 -0
- 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
|
@@ -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.
|
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:
|