@tuned-tensor/cli 0.21.0 → 0.22.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 +97 -5
- package/dist/{chunk-PVLX7LJI.js → chunk-RPWOCVIL.js} +1320 -229
- package/dist/chunk-RPWOCVIL.js.map +1 -0
- package/dist/index.js +527 -55
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.js +107 -44
- package/dist/local-runtime.js.map +1 -1
- package/docs/local-runtime/architecture.md +38 -0
- package/docs/local-runtime/aws-gpu.md +2 -0
- package/docs/spec-workflow.md +7 -0
- package/package.json +1 -1
- package/training/adapter/src/hub_download.py +384 -0
- package/training/adapter/src/prefetch.py +20 -31
- package/training/adapter/src/prefetch_dataset.py +116 -0
- package/training/adapter/src/serve.py +5 -1
- package/dist/chunk-PVLX7LJI.js.map +0 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Prefetch the dataset files a behavior spec trains on from the Hugging Face Hub.
|
|
2
|
+
|
|
3
|
+
Mirrors prefetch.py for models: the files land in the shared Hugging Face cache
|
|
4
|
+
(``<HF_HOME>/hub/datasets--org--name``), the revision is pinned to an immutable
|
|
5
|
+
commit, and every requested file is checked against its content-addressed blob.
|
|
6
|
+
Only the split files named in the spec are downloaded, never the whole repo.
|
|
7
|
+
"""
|
|
8
|
+
import argparse
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from hub_download import configure_download_environment, download_snapshot, verify_blob_digest
|
|
15
|
+
from prefetch import configure_hugging_face_cache, write_json
|
|
16
|
+
|
|
17
|
+
HUGGING_FACE_REPO_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,95}/[A-Za-z0-9][A-Za-z0-9._-]{0,95}")
|
|
18
|
+
COMMIT_SHA = re.compile(r"[0-9a-fA-F]{40}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def validate_repo_path(value: Any) -> str:
|
|
22
|
+
if not isinstance(value, str) or not value or value.startswith(("/", "\\")):
|
|
23
|
+
raise ValueError(f"Dataset file must be a repository-relative path: {value!r}")
|
|
24
|
+
parts = re.split(r"[/\\]", value)
|
|
25
|
+
if any(part in ("", ".", "..") for part in parts) or re.match(r"^[A-Za-z]:", value):
|
|
26
|
+
raise ValueError(f"Dataset file must be a repository-relative path: {value!r}")
|
|
27
|
+
return "/".join(parts)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def verify_dataset_files(snapshot: Path, files: dict[str, str]) -> dict[str, dict[str, Any]]:
|
|
31
|
+
"""Check each split file is a complete cache entry inside the dataset repo.
|
|
32
|
+
|
|
33
|
+
Normal caches link snapshot files to content-addressed blobs, which are
|
|
34
|
+
checked against their digest. Caches without symlink support (Windows
|
|
35
|
+
without Developer Mode) hold plain copies; those are contained and hashed.
|
|
36
|
+
"""
|
|
37
|
+
repository = snapshot.parent.parent.resolve()
|
|
38
|
+
verified: dict[str, dict[str, Any]] = {}
|
|
39
|
+
for split, relative in files.items():
|
|
40
|
+
path = snapshot / relative
|
|
41
|
+
if not path.is_file():
|
|
42
|
+
raise ValueError(f"Dataset file {relative} ({split}) is missing from snapshot {snapshot}")
|
|
43
|
+
target = path.resolve()
|
|
44
|
+
if repository not in target.parents:
|
|
45
|
+
raise ValueError(f"Dataset file {relative} escapes the Hugging Face dataset cache: {target}")
|
|
46
|
+
size = target.stat().st_size
|
|
47
|
+
if size == 0:
|
|
48
|
+
raise ValueError(f"Dataset file {relative} ({split}) is empty")
|
|
49
|
+
_, sha256 = verify_blob_digest(path)
|
|
50
|
+
verified[split] = {
|
|
51
|
+
"path": relative,
|
|
52
|
+
"local_path": str(path),
|
|
53
|
+
"size_bytes": size,
|
|
54
|
+
"sha256": sha256,
|
|
55
|
+
}
|
|
56
|
+
return verified
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def main() -> None:
|
|
60
|
+
parser = argparse.ArgumentParser(description="Prefetch Hugging Face dataset files for a TT behavior spec.")
|
|
61
|
+
parser.add_argument("--input", required=True)
|
|
62
|
+
parser.add_argument("--output", required=True)
|
|
63
|
+
args = parser.parse_args()
|
|
64
|
+
|
|
65
|
+
payload = json.loads(Path(args.input).read_text(encoding="utf-8"))
|
|
66
|
+
repo = str(payload["repo"])
|
|
67
|
+
if not HUGGING_FACE_REPO_ID.fullmatch(repo):
|
|
68
|
+
raise ValueError(f"Dataset repo must be a Hugging Face id like org/name: {repo!r}")
|
|
69
|
+
revision = payload.get("revision")
|
|
70
|
+
if revision is not None and not COMMIT_SHA.fullmatch(str(revision)):
|
|
71
|
+
raise ValueError("Dataset revision must be a 40-character Hugging Face commit SHA")
|
|
72
|
+
raw_files = payload.get("files")
|
|
73
|
+
if not isinstance(raw_files, dict) or not raw_files:
|
|
74
|
+
raise ValueError("Dataset prefetch requires at least one split file")
|
|
75
|
+
files = {str(split): validate_repo_path(path) for split, path in raw_files.items()}
|
|
76
|
+
|
|
77
|
+
configure_hugging_face_cache(payload.get("model_cache"))
|
|
78
|
+
configure_download_environment()
|
|
79
|
+
from huggingface_hub import constants
|
|
80
|
+
|
|
81
|
+
local_only = bool(payload.get("local_files_only", False))
|
|
82
|
+
action = "Verifying cached" if local_only else "Prefetching"
|
|
83
|
+
print(f"{action} dataset {repo} in {constants.HF_HUB_CACHE}...", flush=True)
|
|
84
|
+
snapshot_path, commit = download_snapshot(
|
|
85
|
+
repo_id=repo,
|
|
86
|
+
repo_type="dataset",
|
|
87
|
+
revision=revision,
|
|
88
|
+
allow_patterns=sorted(set(files.values())),
|
|
89
|
+
local_files_only=local_only,
|
|
90
|
+
label="dataset_prefetch",
|
|
91
|
+
)
|
|
92
|
+
snapshot = Path(snapshot_path)
|
|
93
|
+
if not COMMIT_SHA.fullmatch(snapshot.name):
|
|
94
|
+
raise ValueError("Hugging Face dataset snapshot did not resolve to a 40-character immutable commit SHA")
|
|
95
|
+
if revision and snapshot.name.lower() != str(revision).lower():
|
|
96
|
+
raise ValueError(f"Hugging Face returned dataset revision {snapshot.name}, not requested revision {revision}")
|
|
97
|
+
print(f"Verifying dataset files under {snapshot}...", flush=True)
|
|
98
|
+
verified = verify_dataset_files(snapshot, files)
|
|
99
|
+
|
|
100
|
+
write_json(args.output, {
|
|
101
|
+
"ok": True,
|
|
102
|
+
"repo": repo,
|
|
103
|
+
"repo_type": "dataset",
|
|
104
|
+
"requested_revision": revision,
|
|
105
|
+
"snapshot_revision": snapshot.name.lower(),
|
|
106
|
+
"snapshot_path": str(snapshot),
|
|
107
|
+
"hf_home": str(constants.HF_HOME),
|
|
108
|
+
"hub_cache": str(constants.HF_HUB_CACHE),
|
|
109
|
+
"files": verified,
|
|
110
|
+
"size_bytes": sum(item["size_bytes"] for item in verified.values()),
|
|
111
|
+
"commit": commit,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
main()
|
|
@@ -109,6 +109,8 @@ def build_vllm_args(model_source: str, adapter_path: str | None, temp_dir: Path)
|
|
|
109
109
|
prompt = os.environ.get("TT_SYSTEM_PROMPT", "").strip()
|
|
110
110
|
if prompt:
|
|
111
111
|
template = native_chat_template(model_source)
|
|
112
|
+
from vllm.renderers.hf import _detect_content_format
|
|
113
|
+
content_format = _detect_content_format(template, default="string")
|
|
112
114
|
# Literal data, never prompt text interpolated as Jinja source. Preserve
|
|
113
115
|
# tool calls/results verbatim while merging leading system context.
|
|
114
116
|
prefix = "{%- set tt = namespace(system=" + json.dumps(prompt) + ", history=[]) -%}"
|
|
@@ -121,7 +123,9 @@ def build_vllm_args(model_source: str, adapter_path: str | None, temp_dir: Path)
|
|
|
121
123
|
prefix += "{%- set tt.system = tt.system + '\\n\\n' + part.text -%}"
|
|
122
124
|
prefix += "{%- endif -%}{%- endfor -%}{%- endif -%}"
|
|
123
125
|
prefix += "{%- else -%}{%- set tt.history = tt.history + [message] -%}{%- endif -%}{%- endfor -%}"
|
|
124
|
-
|
|
126
|
+
# Match the ORIGINAL native template's expected content representation.
|
|
127
|
+
system_content = "[{'type': 'text', 'text': tt.system}]" if content_format == "openai" else "tt.system"
|
|
128
|
+
prefix += "{%- set messages = [{'role': 'system', 'content': " + system_content + "}] + tt.history -%}"
|
|
125
129
|
path = temp_dir / "chat-template.jinja"
|
|
126
130
|
path.write_text(prefix + template, encoding="utf-8")
|
|
127
131
|
args += ["--chat-template", str(path)]
|