Spaces:
Running
Running
Update hub_models.py
Browse files- hub_models.py +52 -1
hub_models.py
CHANGED
|
@@ -7,6 +7,8 @@ import io
|
|
| 7 |
import json
|
| 8 |
import os
|
| 9 |
import re
|
|
|
|
|
|
|
| 10 |
import time
|
| 11 |
|
| 12 |
CACHE_ROOT = Path(__file__).resolve().parent / '.model-cache'
|
|
@@ -17,6 +19,11 @@ from huggingface_hub.utils import validate_repo_id
|
|
| 17 |
from tqdm.auto import tqdm
|
| 18 |
|
| 19 |
from embeddings import embedding_name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
|
| 22 |
@dataclass(frozen=True)
|
|
@@ -47,6 +54,7 @@ class DownloadPlan:
|
|
| 47 |
directory: Path
|
| 48 |
size: int | None
|
| 49 |
embeddings_only: bool = False
|
|
|
|
| 50 |
|
| 51 |
|
| 52 |
def safe_filename(name):
|
|
@@ -151,11 +159,54 @@ def build_plan(catalog, selection, token=None, cache_root=CACHE_ROOT):
|
|
| 151 |
size = sum(sizes) if all(value is not None for value in sizes) else None
|
| 152 |
model_path = directory if selection.endswith('.index.json') else directory / selection
|
| 153 |
return DownloadPlan(catalog.repo_id, catalog.commit, selection, files, tuple(weights), model_path,
|
| 154 |
-
directory / tokenizer if tokenizer else None, directory, size, embeddings_only)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
|
| 156 |
|
| 157 |
def download_model(plan, token=None, progress=None):
|
| 158 |
"""Progress callbacks may run on SDK threads; callers should use a queue."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
total_files = len(plan.files)
|
| 160 |
for index, filename in enumerate(plan.files):
|
| 161 |
safe_filename(filename)
|
|
|
|
| 7 |
import json
|
| 8 |
import os
|
| 9 |
import re
|
| 10 |
+
import shutil
|
| 11 |
+
from threading import Lock
|
| 12 |
import time
|
| 13 |
|
| 14 |
CACHE_ROOT = Path(__file__).resolve().parent / '.model-cache'
|
|
|
|
| 19 |
from tqdm.auto import tqdm
|
| 20 |
|
| 21 |
from embeddings import embedding_name
|
| 22 |
+
from app_environment import hosted_mode, hosted_download_limit
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
HOSTED_DOWNLOAD_LOCK = Lock()
|
| 26 |
+
DISK_RESERVE_BYTES = 2_000_000_000
|
| 27 |
|
| 28 |
|
| 29 |
@dataclass(frozen=True)
|
|
|
|
| 54 |
directory: Path
|
| 55 |
size: int | None
|
| 56 |
embeddings_only: bool = False
|
| 57 |
+
file_sizes: tuple[int | None, ...] = ()
|
| 58 |
|
| 59 |
|
| 60 |
def safe_filename(name):
|
|
|
|
| 159 |
size = sum(sizes) if all(value is not None for value in sizes) else None
|
| 160 |
model_path = directory if selection.endswith('.index.json') else directory / selection
|
| 161 |
return DownloadPlan(catalog.repo_id, catalog.commit, selection, files, tuple(weights), model_path,
|
| 162 |
+
directory / tokenizer if tokenizer else None, directory, size, embeddings_only, tuple(sizes))
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def check_hosted_download(plan):
|
| 166 |
+
"""Enforce the per-model limit and leave room for the running Space.
|
| 167 |
+
|
| 168 |
+
Complete cached files need no additional disk space. Incomplete downloads
|
| 169 |
+
are counted at their full size, making this estimate conservative.
|
| 170 |
+
"""
|
| 171 |
+
limit = hosted_download_limit()
|
| 172 |
+
if limit is None:
|
| 173 |
+
return
|
| 174 |
+
if plan.size is None or plan.size > limit:
|
| 175 |
+
raise ValueError(f'This Space supports models up to {format_size(limit)} with a known download size. '
|
| 176 |
+
'Choose smaller weights or embedding shards, or run the app locally.')
|
| 177 |
+
remaining = plan.size
|
| 178 |
+
if len(plan.file_sizes) == len(plan.files):
|
| 179 |
+
for name, size in zip(plan.files, plan.file_sizes):
|
| 180 |
+
safe_filename(name)
|
| 181 |
+
path = plan.directory / name
|
| 182 |
+
if size is not None and path.is_file() and path.stat().st_size == size:
|
| 183 |
+
remaining -= size
|
| 184 |
+
ancestor = plan.directory.resolve()
|
| 185 |
+
while not ancestor.exists():
|
| 186 |
+
ancestor = ancestor.parent
|
| 187 |
+
free = shutil.disk_usage(ancestor).free
|
| 188 |
+
if remaining and remaining + DISK_RESERVE_BYTES > free:
|
| 189 |
+
raise ValueError(f'Not enough free storage on this Space: {format_size(remaining)} still needs downloading, '
|
| 190 |
+
f'but only {format_size(free)} is free. We reserve 2 GB for the app. '
|
| 191 |
+
'Choose smaller weights or try again after the Space cache is cleared.')
|
| 192 |
|
| 193 |
|
| 194 |
def download_model(plan, token=None, progress=None):
|
| 195 |
"""Progress callbacks may run on SDK threads; callers should use a queue."""
|
| 196 |
+
locked = hosted_mode()
|
| 197 |
+
if locked and not HOSTED_DOWNLOAD_LOCK.acquire(blocking=False):
|
| 198 |
+
raise ValueError('Another model is downloading on this Space. Please try again when it finishes.')
|
| 199 |
+
try:
|
| 200 |
+
# Recheck inside the lock: another visitor may have consumed disk space
|
| 201 |
+
# since this session displayed the download plan.
|
| 202 |
+
check_hosted_download(plan)
|
| 203 |
+
return _download_model(plan, token, progress)
|
| 204 |
+
finally:
|
| 205 |
+
if locked:
|
| 206 |
+
HOSTED_DOWNLOAD_LOCK.release()
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def _download_model(plan, token=None, progress=None):
|
| 210 |
total_files = len(plan.files)
|
| 211 |
for index, filename in enumerate(plan.files):
|
| 212 |
safe_filename(filename)
|