unsloth/studio/backend/hub/utils/local_snapshot.py
Unsloth 224b90eb02 Make forced offline hub-specific and close the parent preflight gap
A forced guard now only no-ops when HF_HUB_OFFLINE itself is truthy:
huggingface_hub ignores TRANSFORMERS_OFFLINE, so with only that flag set the
GGUF cache-size check could still call get_paths_info. Snapshot selection
prefers revisions holding the inventoried safetensors weights, so a newest
metadata-only revision no longer shadows a complete older one and fails a
valid candidate. The orchestrator's parent-side preflight (transformers tier
probe and GPU sizing, which calls hf model_info) runs under the local-only
guard, closed before the worker spawn so the child does not inherit the
offline env for its whole lifetime; ordinary loads keep their current
unguarded behavior.
2026-07-26 23:41:35 -07:00

87 lines
3.4 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""Local-only snapshot resolution for background model loads."""
import os
from typing import Optional
def _snapshot_has_weights(rev: str) -> bool:
"""Whether a snapshot dir holds at least one safetensors weight file.
Background loads only target safetensors-format rows (adapters and
pickle checkpoints are excluded upstream), so this is the runnable-weight
signal that made the inventory row eligible.
"""
try:
return any(name.endswith(".safetensors") for name in os.listdir(rev))
except OSError:
return False
def _snapshot_dir_fallback(repo_id: str, cache_dir: Optional[str]) -> Optional[str]:
"""Newest snapshots/* dir (by mtime) holding a config.json, preferring
revisions that also hold weights.
This mirrors the inventory scanner's selection (newest revision that
actually carries the inventoried weights), so the load targets the
snapshot that made the row eligible: a newest metadata-only revision must
not shadow an older complete one. It also covers revision-only layouts
(refs/ missing or pruned) that snapshot_download(local_files_only = True)
cannot map through refs/main.
"""
if cache_dir is None:
try:
from huggingface_hub.constants import HF_HUB_CACHE
cache_dir = HF_HUB_CACHE
except Exception:
return None
folder = os.path.join(cache_dir, "models--" + repo_id.replace("/", "--"), "snapshots")
try:
revisions = [
os.path.join(folder, name)
for name in os.listdir(folder)
if os.path.isdir(os.path.join(folder, name))
]
except OSError:
return None
candidates = [rev for rev in revisions if os.path.isfile(os.path.join(rev, "config.json"))]
if not candidates:
return None
weightful = [rev for rev in candidates if _snapshot_has_weights(rev)]
return max(weightful or candidates, key = os.path.getmtime)
def resolve_local_snapshot_path(
repo_id: str,
hf_token: Optional[str] = None,
cache_dir: Optional[str] = None,
) -> Optional[str]:
"""Resolve a Hub repo id to its snapshot directory in the local HF cache
without any network access; None when the repo is not cached.
The newest snapshot dir (by mtime) holding a config.json is preferred:
that is the same selection the inventory scanner surfaces, so the load
targets the revision that made the row eligible. refs/main can lag it
when a non-main commit was downloaded later, so consulting refs first
could load an older revision or fail on its missing files.
``snapshot_download(local_files_only = True)`` stays as the fallback for
layouts the directory scan cannot interpret. Either way resolution never
touches the network, and a missing-file snapshot still resolves so the
subsequent weight load fails instead of downloading the gaps, which is
the fail-closed behavior background loads need.
"""
resolved = _snapshot_dir_fallback(repo_id, cache_dir)
if resolved is not None:
return resolved
try:
from huggingface_hub import snapshot_download
return snapshot_download(
repo_id,
local_files_only = True,
token = hf_token or None,
cache_dir = cache_dir or None,
)
except Exception:
return None