@tuned-tensor/cli 0.20.0 → 0.22.0

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.
@@ -10,12 +10,12 @@ from tempfile import TemporaryDirectory
10
10
  from typing import Any
11
11
 
12
12
  from model_contract import (
13
- CERTIFIED_BASE_MODELS,
14
13
  CERTIFIED_BASE_MODEL,
15
14
  assert_certified_base_model,
16
15
  assert_certified_base_model_revision,
17
16
  assert_certified_model_config,
18
17
  chat_template_kwargs,
18
+ uncertified_load_kwargs,
19
19
  )
20
20
 
21
21
  MAX_ARCHIVE_MEMBERS = 20_000
@@ -145,10 +145,12 @@ def resolve_device(device: str) -> str:
145
145
  return device
146
146
 
147
147
 
148
- def _assert_certified_model_source(base_model: str) -> None:
149
- if not Path(base_model).exists() and base_model not in CERTIFIED_BASE_MODELS:
148
+ def _assert_certified_model_source(base_model: str, model_source: str) -> None:
149
+ # A hub source must be the requested model; certified or not, it is
150
+ # checked by assert_certified_base_model.
151
+ if not Path(model_source).exists() and model_source != base_model:
150
152
  raise ValueError(
151
- f"The bundled evaluator does not certify {base_model!r}"
153
+ f"The bundled evaluator does not certify {model_source!r}"
152
154
  )
153
155
 
154
156
 
@@ -160,9 +162,14 @@ def load_text_model(
160
162
  base_model = str(payload["base_model"])
161
163
  model_source = str(payload.get("model_source", base_model))
162
164
  assert_certified_base_model(base_model, "Evaluation base model")
163
- _assert_certified_model_source(model_source)
165
+ _assert_certified_model_source(base_model, model_source)
164
166
  revision = payload.get("base_model_revision")
165
- assert_certified_base_model_revision(base_model, revision, "Evaluation base model revision")
167
+ assert_certified_base_model_revision(
168
+ base_model,
169
+ revision,
170
+ "Evaluation base model revision",
171
+ local_source=Path(model_source).is_dir(),
172
+ )
166
173
  device = resolve_device(str(payload.get("device", "cuda")))
167
174
  token = os.getenv("HF_TOKEN")
168
175
  source_kwargs: dict[str, Any] = {
@@ -184,7 +191,7 @@ def load_text_model(
184
191
  if device == "cuda":
185
192
  dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
186
193
 
187
- model_kwargs: dict[str, Any] = dict(source_kwargs)
194
+ model_kwargs: dict[str, Any] = {**source_kwargs, **uncertified_load_kwargs(base_model)}
188
195
  if dtype is not None:
189
196
  model_kwargs["dtype"] = dtype
190
197
  if device == "cuda":
@@ -0,0 +1,384 @@
1
+ """Resumable, observable Hugging Face Hub snapshot downloads.
2
+
3
+ Model and dataset prefetch share this module so both get the same behavior:
4
+
5
+ * the requested revision is resolved once to an immutable commit, so retries
6
+ can never mix files from two commits when a branch moves mid-download;
7
+ * transient network failures are retried with exponential backoff. Completed
8
+ files stay in the Hugging Face cache, so each retry only fetches what is
9
+ still missing;
10
+ * progress is emitted as ``@@tt-progress {json}`` lines on stdout, which the
11
+ Node runner renders as a progress bar. huggingface_hub's own tqdm bars are
12
+ disabled because redrawn carriage-return output is unreadable in logs.
13
+
14
+ huggingface_hub is imported lazily so the pure helpers stay testable with the
15
+ system Python.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import errno
20
+ import hashlib
21
+ import json
22
+ import os
23
+ import re
24
+ import sys
25
+ import threading
26
+ import time
27
+ from dataclasses import dataclass
28
+ from pathlib import Path
29
+ from typing import Any, Callable, Iterable
30
+
31
+ PROGRESS_PREFIX = "@@tt-progress "
32
+ COMMIT = re.compile(r"[0-9a-fA-F]{40}")
33
+ DEFAULT_MAX_ATTEMPTS = 10
34
+ MAX_BACKOFF_SECONDS = 60.0
35
+ # A per-request read timeout that tolerates slow or briefly stalled Wi-Fi.
36
+ DEFAULT_DOWNLOAD_TIMEOUT_SECONDS = "60"
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class PlannedFile:
41
+ """One repository file expected in the snapshot, keyed by its cache blob name."""
42
+
43
+ path: str
44
+ size: int
45
+ etag: str
46
+
47
+
48
+ def configure_download_environment() -> None:
49
+ """Must run before huggingface_hub is imported; it reads these at import."""
50
+ os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
51
+ os.environ.setdefault("HF_HUB_DOWNLOAD_TIMEOUT", DEFAULT_DOWNLOAD_TIMEOUT_SECONDS)
52
+
53
+
54
+ def repo_folder_name(repo_id: str, repo_type: str) -> str:
55
+ return f"{repo_type}s--{repo_id.replace('/', '--')}"
56
+
57
+
58
+ def max_attempts_from_env(environ: dict[str, str] | None = None) -> int:
59
+ raw = (environ if environ is not None else os.environ).get("TT_HF_DOWNLOAD_MAX_ATTEMPTS")
60
+ if raw is None or not raw.strip():
61
+ return DEFAULT_MAX_ATTEMPTS
62
+ try:
63
+ value = int(raw)
64
+ except ValueError as exc:
65
+ raise ValueError("TT_HF_DOWNLOAD_MAX_ATTEMPTS must be an integer from 1 to 100") from exc
66
+ if not 1 <= value <= 100:
67
+ raise ValueError("TT_HF_DOWNLOAD_MAX_ATTEMPTS must be an integer from 1 to 100")
68
+ return value
69
+
70
+
71
+ def backoff_seconds(attempt: int) -> float:
72
+ """Delay before retry ``attempt`` (1-based): 2, 4, 8, ... capped at 60s."""
73
+ return min(MAX_BACKOFF_SECONDS, float(2 ** attempt))
74
+
75
+
76
+ class IncompleteDownloadError(RuntimeError):
77
+ """snapshot_download returned a cached folder that still lacks planned files."""
78
+
79
+
80
+ def is_retryable(error: BaseException) -> bool:
81
+ """Retry network and server failures; fail fast on caller or auth errors.
82
+
83
+ huggingface_hub reports a connection failure on a file that is not cached
84
+ yet as LocalEntryNotFoundError (a FileNotFoundError), so that and
85
+ IncompleteSnapshotError are retried during downloads.
86
+ """
87
+ if isinstance(error, (KeyboardInterrupt, SystemExit)):
88
+ return False
89
+ if isinstance(error, OSError) and error.errno in (errno.ENOSPC, errno.EACCES, errno.EROFS, errno.EDQUOT):
90
+ return False
91
+ name = type(error).__name__
92
+ if name in {
93
+ "RepositoryNotFoundError",
94
+ "GatedRepoError",
95
+ "RevisionNotFoundError",
96
+ "RemoteEntryNotFoundError",
97
+ "DisabledRepoError",
98
+ "OfflineModeIsEnabled",
99
+ }:
100
+ return False
101
+ if isinstance(error, (ValueError, TypeError)):
102
+ return False
103
+ response = getattr(error, "response", None)
104
+ status = getattr(response, "status_code", None)
105
+ if isinstance(status, int) and 400 <= status < 500 and status not in (408, 429):
106
+ return False
107
+ return True
108
+
109
+
110
+ def plan_files(siblings: Iterable[Any]) -> list[PlannedFile]:
111
+ """Build the expected cache inventory from repo_info(files_metadata=True)."""
112
+ planned: list[PlannedFile] = []
113
+ for sibling in siblings:
114
+ lfs = getattr(sibling, "lfs", None)
115
+ etag = getattr(lfs, "sha256", None) if lfs is not None else None
116
+ etag = etag or getattr(sibling, "blob_id", None)
117
+ size = getattr(sibling, "size", None)
118
+ if not etag or not isinstance(size, int):
119
+ # Without a blob name and size the file cannot be tracked; the
120
+ # download still fetches and verifies it, only progress is coarser.
121
+ continue
122
+ planned.append(PlannedFile(path=sibling.rfilename, size=size, etag=str(etag)))
123
+ return planned
124
+
125
+
126
+ def downloaded_bytes(blobs: Path, planned: PlannedFile) -> tuple[int, bool]:
127
+ """Bytes present for one planned file and whether its blob is complete."""
128
+ if (blobs / planned.etag).is_file():
129
+ return planned.size, True
130
+ partial = 0
131
+ try:
132
+ for candidate in blobs.glob(f"{planned.etag}*.incomplete"):
133
+ try:
134
+ partial = max(partial, candidate.stat().st_size)
135
+ except OSError:
136
+ continue
137
+ except OSError:
138
+ return 0, False
139
+ return min(partial, planned.size), False
140
+
141
+
142
+ def measure(blobs: Path, planned: list[PlannedFile]) -> dict[str, int]:
143
+ completed_bytes = 0
144
+ completed_files = 0
145
+ for item in planned:
146
+ size, done = downloaded_bytes(blobs, item)
147
+ completed_bytes += size
148
+ completed_files += int(done)
149
+ return {
150
+ "completed_bytes": completed_bytes,
151
+ "total_bytes": sum(item.size for item in planned),
152
+ "files_completed": completed_files,
153
+ "files_total": len(planned),
154
+ }
155
+
156
+
157
+ _STDOUT_LOCK = threading.Lock()
158
+
159
+
160
+ def write_line(text: str, stream: Any = None) -> None:
161
+ """Write one whole line; the progress thread and log messages share stdout."""
162
+ target = stream if stream is not None else sys.stdout
163
+ with _STDOUT_LOCK:
164
+ target.write(text + "\n")
165
+ target.flush()
166
+
167
+
168
+ def emit_progress(event: dict[str, Any], stream: Any = None) -> None:
169
+ write_line(PROGRESS_PREFIX + json.dumps(event, separators=(",", ":")), stream)
170
+
171
+
172
+ class ProgressMonitor:
173
+ """Polls the cache blob directory and emits progress while a download runs."""
174
+
175
+ def __init__(
176
+ self,
177
+ blobs: Path,
178
+ planned: list[PlannedFile],
179
+ label: str,
180
+ emit: Callable[[dict[str, Any]], None] = emit_progress,
181
+ interval: float = 0.5,
182
+ heartbeat: float = 5.0,
183
+ ) -> None:
184
+ self.blobs = blobs
185
+ self.planned = planned
186
+ self.label = label
187
+ self.emit = emit
188
+ self.interval = interval
189
+ self.heartbeat = heartbeat
190
+ self._stop = threading.Event()
191
+ self._thread: threading.Thread | None = None
192
+ self._last: dict[str, int] | None = None
193
+ self._last_emit = 0.0
194
+
195
+ def snapshot(self, force: bool = False, phase: str = "download") -> None:
196
+ current = measure(self.blobs, self.planned)
197
+ now = time.monotonic()
198
+ if not force and current == self._last and now - self._last_emit < self.heartbeat:
199
+ return
200
+ self._last = current
201
+ self._last_emit = now
202
+ self.emit({"label": self.label, "phase": phase, **current})
203
+
204
+ def _run(self) -> None:
205
+ while not self._stop.wait(self.interval):
206
+ try:
207
+ self.snapshot()
208
+ except Exception: # Progress is best effort; never break the download.
209
+ pass
210
+
211
+ def __enter__(self) -> "ProgressMonitor":
212
+ self.snapshot(force=True)
213
+ self._thread = threading.Thread(target=self._run, name="tt-progress", daemon=True)
214
+ self._thread.start()
215
+ return self
216
+
217
+ def __exit__(self, *_: object) -> None:
218
+ self._stop.set()
219
+ if self._thread:
220
+ self._thread.join(timeout=2)
221
+
222
+
223
+ def with_retries(
224
+ action: Callable[[], Any],
225
+ *,
226
+ description: str,
227
+ max_attempts: int,
228
+ sleep: Callable[[float], None] | None = None,
229
+ log: Callable[[str], None] = write_line,
230
+ ) -> Any:
231
+ for attempt in range(1, max_attempts + 1):
232
+ try:
233
+ return action()
234
+ except Exception as error: # noqa: BLE001 - classified below
235
+ if attempt >= max_attempts or not is_retryable(error):
236
+ raise
237
+ delay = backoff_seconds(attempt)
238
+ log(
239
+ f"{description} interrupted ({type(error).__name__}: {error}). "
240
+ f"Retrying in {delay:.0f}s (attempt {attempt + 1}/{max_attempts}); "
241
+ "completed files are kept in the cache."
242
+ )
243
+ (sleep or time.sleep)(delay)
244
+ raise AssertionError("unreachable")
245
+
246
+
247
+ def verify_blob(path: Path) -> bool:
248
+ """Check a cached file against its content-addressed blob name.
249
+
250
+ Returns True when the blob name is a recognized digest and it matches;
251
+ False when the name carries no digest (for example a cache without
252
+ symlinks). Raises ValueError on a mismatch.
253
+ """
254
+ return verify_blob_digest(path)[0]
255
+
256
+
257
+ def verify_blob_digest(path: Path) -> tuple[bool, str]:
258
+ """Like verify_blob, also returning the file's SHA-256 from the same read."""
259
+ blob = path.resolve()
260
+ expected = blob.name.lower()
261
+ sha256 = hashlib.sha256()
262
+ git_sha1 = hashlib.sha1(f"blob {blob.stat().st_size}\0".encode())
263
+ with blob.open("rb") as source:
264
+ for chunk in iter(lambda: source.read(8 * 1024 * 1024), b""):
265
+ sha256.update(chunk)
266
+ git_sha1.update(chunk)
267
+ digests = {64: sha256.hexdigest(), 40: git_sha1.hexdigest()}
268
+ if len(expected) in digests and all(character in "0123456789abcdef" for character in expected):
269
+ if digests[len(expected)] != expected:
270
+ raise ValueError(f"Cached Hugging Face blob checksum mismatch: {path}")
271
+ return True, digests[64]
272
+ return False, digests[64]
273
+
274
+
275
+ def download_snapshot(
276
+ *,
277
+ repo_id: str,
278
+ repo_type: str,
279
+ revision: str | None,
280
+ allow_patterns: list[str] | None = None,
281
+ ignore_patterns: list[str] | None = None,
282
+ local_files_only: bool = False,
283
+ label: str,
284
+ ) -> tuple[str, str]:
285
+ """Download (or verify locally) one snapshot. Returns (path, commit sha)."""
286
+ from huggingface_hub import constants, snapshot_download
287
+
288
+ token = os.getenv("HF_TOKEN")
289
+ if local_files_only:
290
+ path = snapshot_download(
291
+ repo_id=repo_id,
292
+ repo_type=repo_type,
293
+ revision=revision,
294
+ token=token,
295
+ allow_patterns=allow_patterns,
296
+ ignore_patterns=ignore_patterns,
297
+ local_files_only=True,
298
+ )
299
+ return path, Path(path).name
300
+
301
+ if revision and COMMIT.fullmatch(revision):
302
+ # A pinned commit that is already fully cached needs no network, so
303
+ # prefetch keeps working offline. huggingface_hub checks completeness
304
+ # against its cached tree listing and raises if files are missing.
305
+ try:
306
+ path = snapshot_download(
307
+ repo_id=repo_id,
308
+ repo_type=repo_type,
309
+ revision=revision,
310
+ token=token,
311
+ allow_patterns=allow_patterns,
312
+ ignore_patterns=ignore_patterns,
313
+ local_files_only=True,
314
+ )
315
+ write_line(f"{label}: {repo_id}@{revision} is already cached; no download needed.")
316
+ return path, revision
317
+ except Exception: # noqa: BLE001 - not cached or incomplete: download below
318
+ pass
319
+
320
+ from huggingface_hub import HfApi
321
+ from huggingface_hub.utils import filter_repo_objects
322
+
323
+ max_attempts = max_attempts_from_env()
324
+ api = HfApi(token=token)
325
+ info = with_retries(
326
+ lambda: api.repo_info(repo_id, repo_type=repo_type, revision=revision, files_metadata=True),
327
+ description=f"Resolving {repo_id}",
328
+ max_attempts=max_attempts,
329
+ )
330
+ commit = str(info.sha)
331
+ siblings = list(filter_repo_objects(
332
+ info.siblings or [],
333
+ allow_patterns=allow_patterns,
334
+ ignore_patterns=ignore_patterns,
335
+ key=lambda sibling: sibling.rfilename,
336
+ ))
337
+ planned = plan_files(siblings)
338
+ blobs = Path(constants.HF_HUB_CACHE) / repo_folder_name(repo_id, repo_type) / "blobs"
339
+ blobs.mkdir(parents=True, exist_ok=True)
340
+ total = sum(item.size for item in planned)
341
+ write_line(
342
+ f"{label}: {repo_id}@{commit} — {len(planned)} file(s), {total} bytes. "
343
+ "Interrupted downloads can be restarted; completed files are reused."
344
+ )
345
+ monitor = ProgressMonitor(blobs, planned, label)
346
+ with monitor:
347
+ def attempt() -> str:
348
+ path = snapshot_download(
349
+ repo_id=repo_id,
350
+ repo_type=repo_type,
351
+ revision=commit,
352
+ token=token,
353
+ allow_patterns=allow_patterns,
354
+ ignore_patterns=ignore_patterns,
355
+ )
356
+ # If the Hub becomes unreachable mid-way, huggingface_hub may hand
357
+ # back the partial cached folder; treat that as a retryable failure.
358
+ missing = [sibling.rfilename for sibling in siblings if not (Path(path) / sibling.rfilename).exists()]
359
+ if missing:
360
+ raise IncompleteDownloadError(f"{len(missing)} file(s) still missing, e.g. {missing[0]}")
361
+ return path
362
+
363
+ path = with_retries(attempt, description=f"Downloading {repo_id}", max_attempts=max_attempts)
364
+ monitor.snapshot(force=True, phase="downloaded")
365
+ if revision and revision != commit:
366
+ # Downloading by commit skips huggingface_hub's ref bookkeeping. Record
367
+ # the branch or tag -> commit mapping it would have written, so later
368
+ # offline resolution of an unpinned revision finds this snapshot.
369
+ write_ref(Path(constants.HF_HUB_CACHE) / repo_folder_name(repo_id, repo_type), revision, commit)
370
+ elif not revision:
371
+ write_ref(Path(constants.HF_HUB_CACHE) / repo_folder_name(repo_id, repo_type), "main", commit)
372
+ return path, commit
373
+
374
+
375
+ def write_ref(repository: Path, revision: str, commit: str) -> None:
376
+ if revision.lower() == commit.lower():
377
+ return
378
+ ref = repository / "refs" / revision
379
+ if repository.resolve() not in ref.resolve().parents:
380
+ raise ValueError(f"Invalid Hugging Face revision name: {revision}")
381
+ ref.parent.mkdir(parents=True, exist_ok=True)
382
+ temporary = ref.with_name(f".{ref.name}.{os.getpid()}.tmp")
383
+ temporary.write_text(commit, encoding="utf-8")
384
+ os.replace(temporary, ref)
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import re
3
4
  from typing import Any
4
5
 
5
6
 
@@ -15,6 +16,10 @@ NEMOTRON_BASE_MODEL_REVISION = "ce38b6ab8b252b4b8ee7165b4605e93191cafd73"
15
16
  # Immutable Hugging Face revision reviewed and certified for Muse Glimmer.
16
17
  MUSE_GLIMMER_BASE_MODEL_REVISION = "a4e59da52a7bc87ae7251dd5545c0dd437c44b68"
17
18
  CERTIFIED_BASE_MODELS = (CERTIFIED_BASE_MODEL, NEMOTRON_BASE_MODEL, MUSE_GLIMMER_BASE_MODEL)
19
+ # Any other Hugging Face repo may be trained as an uncertified causal LM when
20
+ # the run pins an immutable commit. Node enforces the same contract.
21
+ 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}")
22
+ COMMIT_SHA = re.compile(r"[0-9a-fA-F]{40}")
18
23
  CERTIFIED_QWEN_TEXT_CONFIG = {
19
24
  "model_type": "qwen3_5_text",
20
25
  "hidden_size": 2048,
@@ -58,6 +63,9 @@ def assert_certified_model_config(
58
63
  label: str = "base-model config",
59
64
  expected_model_id: str | None = None,
60
65
  ) -> None:
66
+ if expected_model_id and not is_certified_base_model(expected_model_id):
67
+ # No reviewed architecture exists for an uncertified model.
68
+ return
61
69
  model_type = _field(value, "model_type")
62
70
  architectures = _field(value, "architectures")
63
71
 
@@ -126,17 +134,30 @@ def assert_certified_model_config(
126
134
  )
127
135
 
128
136
 
137
+ def is_certified_base_model(model_id: str) -> bool:
138
+ return model_id in CERTIFIED_BASE_MODELS
139
+
140
+
129
141
  def assert_certified_base_model(model_id: str, label: str = "base model") -> None:
130
- if model_id not in CERTIFIED_BASE_MODELS:
131
- supported = ", ".join(CERTIFIED_BASE_MODELS)
132
- raise ValueError(f"{label} must be one of {supported}; got {model_id!r}")
142
+ """Accept a certified model or an uncertified Hugging Face repo id."""
143
+ if is_certified_base_model(model_id) or HUGGING_FACE_REPO_ID.fullmatch(model_id):
144
+ return
145
+ supported = ", ".join(CERTIFIED_BASE_MODELS)
146
+ raise ValueError(f"{label} must be one of {supported} or a Hugging Face repo id; got {model_id!r}")
133
147
 
134
148
 
135
- def assert_certified_base_model_revision(base_model: str, revision: str | None, label: str = "base model revision") -> None:
149
+ def assert_certified_base_model_revision(
150
+ base_model: str,
151
+ revision: str | None,
152
+ label: str = "base model revision",
153
+ *,
154
+ local_source: bool = False,
155
+ ) -> None:
136
156
  """Require the certified immutable revision for pinned training models.
137
157
 
138
158
  Qwen, Nemotron, and Muse Glimmer loads are bound here; local snapshot
139
- contents are additionally verified by the Node runtime.
159
+ contents are additionally verified by the Node runtime. An uncertified
160
+ model must pin a commit SHA unless it loads from a local directory.
140
161
  """
141
162
  expected = {
142
163
  CERTIFIED_BASE_MODEL: QWEN_BASE_MODEL_REVISION,
@@ -144,6 +165,13 @@ def assert_certified_base_model_revision(base_model: str, revision: str | None,
144
165
  MUSE_GLIMMER_BASE_MODEL: MUSE_GLIMMER_BASE_MODEL_REVISION,
145
166
  }.get(base_model)
146
167
  if expected is None:
168
+ if revision is None and local_source:
169
+ return
170
+ if revision is None or not COMMIT_SHA.fullmatch(str(revision)):
171
+ raise ValueError(
172
+ f"{label} must be a 40-character commit SHA for uncertified base model {base_model}; "
173
+ f"got {revision!r}"
174
+ )
147
175
  return
148
176
  if revision is None:
149
177
  raise ValueError(f"{label} is required for {base_model}")
@@ -167,3 +195,8 @@ def chat_template_kwargs(model_id: str) -> dict[str, Any]:
167
195
  if model_id == NEMOTRON_BASE_MODEL:
168
196
  return {"enable_thinking": False}
169
197
  return {}
198
+
199
+
200
+ def uncertified_load_kwargs(model_id: str) -> dict[str, Any]:
201
+ """Refuse pickle weights for models TT has not reviewed."""
202
+ return {} if is_certified_base_model(model_id) else {"use_safetensors": True}
@@ -1,15 +1,16 @@
1
1
  import argparse
2
- import hashlib
3
2
  import json
4
3
  import os
5
4
  import re
6
5
  from pathlib import Path
7
6
  from typing import Any
8
7
 
8
+ from hub_download import configure_download_environment, download_snapshot, verify_blob
9
9
  from model_contract import (
10
10
  assert_certified_base_model,
11
11
  assert_certified_base_model_revision,
12
12
  assert_certified_model_config,
13
+ is_certified_base_model,
13
14
  )
14
15
 
15
16
  ALLOW_PATTERNS = [
@@ -76,15 +77,6 @@ def configure_hugging_face_cache(cache_home: str | None) -> None:
76
77
  os.environ.pop(deprecated, None)
77
78
 
78
79
 
79
- def hash_file(path: Path, algorithm: str, prefix: bytes = b"") -> str:
80
- digest = hashlib.new(algorithm)
81
- digest.update(prefix)
82
- with path.open("rb") as source:
83
- for chunk in iter(lambda: source.read(8 * 1024 * 1024), b""):
84
- digest.update(chunk)
85
- return digest.hexdigest()
86
-
87
-
88
80
  def verify_snapshot(snapshot: Path, expected_model_id: str | None = None) -> tuple[list[Path], int]:
89
81
  config = snapshot / "config.json"
90
82
  if not config.is_file() or config.stat().st_size == 0:
@@ -99,8 +91,15 @@ def verify_snapshot(snapshot: Path, expected_model_id: str | None = None) -> tup
99
91
  except (OSError, json.JSONDecodeError) as exc:
100
92
  raise ValueError(f"Cached snapshot has an invalid config.json: {snapshot}") from exc
101
93
 
94
+ if expected_model_id and not is_certified_base_model(expected_model_id):
95
+ if not any(path.stat().st_size > 0 for path in snapshot.glob("*.safetensors")):
96
+ raise ValueError("Uncertified base models require non-empty safetensors weights; pickle-only checkpoints are unsupported")
97
+
102
98
  weight_files = sorted(snapshot.glob("*.safetensors")) + sorted(snapshot.glob("pytorch_model*.bin"))
103
99
  index_files = sorted(snapshot.glob("*.safetensors.index.json")) + sorted(snapshot.glob("pytorch_model*.bin.index.json"))
100
+ # Keep lexical snapshot names: normal HF file symlinks target sibling blobs.
101
+ files = [path for path in snapshot.rglob("*") if path.is_file()]
102
+ inventory = set(files)
104
103
  for index in index_files:
105
104
  try:
106
105
  weight_map = json.loads(index.read_text(encoding="utf-8")).get("weight_map", {})
@@ -108,8 +107,13 @@ def verify_snapshot(snapshot: Path, expected_model_id: str | None = None) -> tup
108
107
  raise ValueError(f"Cached snapshot has an invalid weight index: {index}") from exc
109
108
  if not isinstance(weight_map, dict) or not weight_map:
110
109
  raise ValueError(f"Cached snapshot has an empty weight index: {index}")
111
- for name in set(weight_map.values()):
112
- if not isinstance(name, str) or not (snapshot / name).is_file():
110
+ for name in weight_map.values():
111
+ if (not isinstance(name, str) or not name
112
+ or re.match(r"^(?:[A-Za-z]:|[/\\])", name)
113
+ or ".." in re.split(r"[/\\]", name)):
114
+ raise ValueError("Cached snapshot has an invalid indexed weight shard path")
115
+ shard = snapshot / name
116
+ if shard not in inventory or shard.stat().st_size == 0:
113
117
  raise ValueError(f"Cached snapshot is missing an indexed weight shard: {name}")
114
118
  if not weight_files:
115
119
  raise ValueError(f"Cached snapshot contains no Transformers model weights: {snapshot}")
@@ -129,20 +133,7 @@ def verify_snapshot(snapshot: Path, expected_model_id: str | None = None) -> tup
129
133
  if not tokenizer_config.is_file() or not any(path.is_file() and path.stat().st_size > 0 for path in vocabulary):
130
134
  raise ValueError(f"Cached snapshot is missing tokenizer metadata or vocabulary files: {snapshot}")
131
135
 
132
- files = [path for path in snapshot.rglob("*") if path.is_file()]
133
- verified_blobs = 0
134
- for path in files:
135
- blob = path.resolve()
136
- expected = blob.name.lower()
137
- if len(expected) == 64 and all(character in "0123456789abcdef" for character in expected):
138
- actual = hash_file(blob, "sha256")
139
- elif len(expected) == 40 and all(character in "0123456789abcdef" for character in expected):
140
- actual = hash_file(blob, "sha1", f"blob {blob.stat().st_size}\0".encode())
141
- else:
142
- continue
143
- if actual != expected:
144
- raise ValueError(f"Cached Hugging Face blob checksum mismatch: {path}")
145
- verified_blobs += 1
136
+ verified_blobs = sum(1 for path in files if verify_blob(path))
146
137
  return files, verified_blobs
147
138
 
148
139
 
@@ -166,17 +157,20 @@ def main() -> None:
166
157
 
167
158
  # Import only after the cache environment is final. huggingface_hub reads
168
159
  # these variables while importing its constants module.
169
- from huggingface_hub import constants, snapshot_download
160
+ configure_download_environment()
161
+ from huggingface_hub import constants
170
162
 
171
- action = "Verifying cached" if payload.get("local_files_only") else "Prefetching"
163
+ local_only = bool(payload.get("local_files_only", False))
164
+ action = "Verifying cached" if local_only else "Prefetching"
172
165
  print(f"{action} {base_model} in {constants.HF_HUB_CACHE}...", flush=True)
173
- snapshot_path = snapshot_download(
166
+ snapshot_path, _ = download_snapshot(
174
167
  repo_id=base_model,
168
+ repo_type="model",
175
169
  revision=payload.get("revision"),
176
- token=os.getenv("HF_TOKEN"),
177
170
  allow_patterns=ALLOW_PATTERNS,
178
- ignore_patterns=IGNORE_PATTERNS,
179
- local_files_only=bool(payload.get("local_files_only", False)),
171
+ ignore_patterns=IGNORE_PATTERNS + ([] if is_certified_base_model(base_model) else ["pytorch_model*.bin"]),
172
+ local_files_only=local_only,
173
+ label="model_prefetch",
180
174
  )
181
175
  snapshot = Path(snapshot_path)
182
176
  snapshot_revision = snapshot.name