from __future__ import annotations
import logging
import os
import threading
from pathlib import Path
from . import envs
logger = logging.getLogger("modelexpress.model_prefetch")
_REPO_DIR_PREFIX = "models--"
_lock = threading.RLock()
_snapshot_to_repo_id: dict[str, str] = {}
_revision_snapshots: dict[tuple[str, str | None, str], str] = {}
def is_enabled() -> bool:
if not envs.MODEL_EXPRESS_NO_SHARED_STORAGE:
return False
return bool(envs.MODEL_EXPRESS_URL or envs.MX_SERVER_ADDRESS)
def is_repo_id(model: str) -> bool:
if not model or os.path.isabs(model) or os.path.sep in os.path.dirname(model or ""):
return False
try:
from huggingface_hub.utils import validate_repo_id
except ImportError:
return False
try:
validate_repo_id(model)
except Exception:
return False
return not Path(model).exists()
def ensure_metadata(
repo_id: str,
revision: str | None = None,
*,
cache_directory: str | os.PathLike[str] | None = None,
) -> Path | None:
if not is_enabled() or not is_repo_id(repo_id):
return None
from .model_client import ModelCacheClient
from .model_snapshot import resolve_cache_root
requested = revision or None
root = resolve_cache_root(cache_directory).resolve()
with _lock:
installed = _known_snapshot(repo_id, requested, root)
if installed is not None:
return installed
with ModelCacheClient(
cache_directory=cache_directory, chunk_size=configured_chunk_size()
) as client:
snapshot_path = client.install_metadata_snapshot(
repo_id, requested_revision=requested
)
_snapshot_to_repo_id[_normalize(snapshot_path)] = repo_id
_revision_snapshots[(repo_id, requested, _normalize(root))] = _normalize(
snapshot_path
)
return snapshot_path
def repo_id_for(model: str | os.PathLike[str]) -> str | None:
model = str(model)
with _lock:
recorded = _snapshot_to_repo_id.get(_normalize(model))
if recorded is not None:
return recorded
if is_repo_id(model):
return model
return repo_id_from_cache_path(model)
def repo_id_from_cache_path(path: str | os.PathLike[str]) -> str | None:
try:
from huggingface_hub.utils import validate_repo_id
except ImportError:
return None
candidate = Path(str(path))
for part in (candidate, *candidate.parents):
name = part.name
if not name.startswith(_REPO_DIR_PREFIX):
continue
repo_id = name[len(_REPO_DIR_PREFIX):].replace("--", "/")
try:
validate_repo_id(repo_id)
except Exception:
return None
return repo_id
return None
def reset() -> None:
with _lock:
_snapshot_to_repo_id.clear()
_revision_snapshots.clear()
def _known_snapshot(repo_id: str, revision: str | None, root: Path) -> Path | None:
with _lock:
recorded = _revision_snapshots.get((repo_id, revision, _normalize(root)))
return Path(recorded) if recorded is not None else None
def _normalize(path: str | os.PathLike[str]) -> str:
return os.path.normpath(str(path))
def configured_chunk_size() -> int | None:
from .model_client import MAX_CHUNK_SIZE
raw = envs.MODEL_EXPRESS_TRANSFER_CHUNK_SIZE
if not raw:
return None
try:
value = int(raw)
except ValueError:
logger.warning("Invalid MODEL_EXPRESS_TRANSFER_CHUNK_SIZE=%r; using default", raw)
return None
if not 0 < value <= MAX_CHUNK_SIZE:
logger.warning(
"MODEL_EXPRESS_TRANSFER_CHUNK_SIZE=%r must be between 1 and %d; using default",
raw,
MAX_CHUNK_SIZE,
)
return None
return value