huggingface-hub 1.0.0rc5__py3-none-any.whl → 1.0.0rc7__py3-none-any.whl
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.
Potentially problematic release.
This version of huggingface-hub might be problematic. Click here for more details.
- huggingface_hub/__init__.py +12 -1
- huggingface_hub/_commit_api.py +1 -5
- huggingface_hub/_jobs_api.py +1 -1
- huggingface_hub/_login.py +3 -3
- huggingface_hub/_snapshot_download.py +4 -3
- huggingface_hub/_upload_large_folder.py +2 -15
- huggingface_hub/_webhooks_server.py +1 -1
- huggingface_hub/cli/_cli_utils.py +1 -1
- huggingface_hub/cli/auth.py +0 -20
- huggingface_hub/cli/cache.py +561 -304
- huggingface_hub/cli/download.py +2 -2
- huggingface_hub/cli/repo.py +0 -7
- huggingface_hub/cli/upload.py +0 -8
- huggingface_hub/community.py +16 -8
- huggingface_hub/constants.py +10 -11
- huggingface_hub/file_download.py +9 -61
- huggingface_hub/hf_api.py +170 -126
- huggingface_hub/hf_file_system.py +31 -6
- huggingface_hub/inference/_client.py +1 -1
- huggingface_hub/inference/_generated/_async_client.py +1 -1
- huggingface_hub/inference/_providers/__init__.py +15 -2
- huggingface_hub/inference/_providers/_common.py +39 -0
- huggingface_hub/inference/_providers/clarifai.py +13 -0
- huggingface_hub/lfs.py +3 -65
- huggingface_hub/serialization/_torch.py +1 -1
- huggingface_hub/utils/__init__.py +0 -2
- huggingface_hub/utils/_cache_manager.py +17 -42
- huggingface_hub/utils/_http.py +25 -3
- huggingface_hub/utils/_parsing.py +98 -0
- huggingface_hub/utils/_runtime.py +1 -14
- {huggingface_hub-1.0.0rc5.dist-info → huggingface_hub-1.0.0rc7.dist-info}/METADATA +4 -14
- {huggingface_hub-1.0.0rc5.dist-info → huggingface_hub-1.0.0rc7.dist-info}/RECORD +36 -34
- {huggingface_hub-1.0.0rc5.dist-info → huggingface_hub-1.0.0rc7.dist-info}/LICENSE +0 -0
- {huggingface_hub-1.0.0rc5.dist-info → huggingface_hub-1.0.0rc7.dist-info}/WHEEL +0 -0
- {huggingface_hub-1.0.0rc5.dist-info → huggingface_hub-1.0.0rc7.dist-info}/entry_points.txt +0 -0
- {huggingface_hub-1.0.0rc5.dist-info → huggingface_hub-1.0.0rc7.dist-info}/top_level.txt +0 -0
|
@@ -102,18 +102,22 @@ class HfFileSystem(fsspec.AbstractFileSystem):
|
|
|
102
102
|
*args,
|
|
103
103
|
endpoint: Optional[str] = None,
|
|
104
104
|
token: Union[bool, str, None] = None,
|
|
105
|
+
block_size: Optional[int] = None,
|
|
105
106
|
**storage_options,
|
|
106
107
|
):
|
|
107
108
|
super().__init__(*args, **storage_options)
|
|
108
109
|
self.endpoint = endpoint or constants.ENDPOINT
|
|
109
110
|
self.token = token
|
|
110
111
|
self._api = HfApi(endpoint=endpoint, token=token)
|
|
112
|
+
self.block_size = block_size
|
|
111
113
|
# Maps (repo_type, repo_id, revision) to a 2-tuple with:
|
|
112
114
|
# * the 1st element indicating whether the repositoy and the revision exist
|
|
113
115
|
# * the 2nd element being the exception raised if the repository or revision doesn't exist
|
|
114
116
|
self._repo_and_revision_exists_cache: dict[
|
|
115
117
|
tuple[str, str, Optional[str]], tuple[bool, Optional[Exception]]
|
|
116
118
|
] = {}
|
|
119
|
+
# Maps parent directory path to path infos
|
|
120
|
+
self.dircache: dict[str, list[dict[str, Any]]] = {}
|
|
117
121
|
|
|
118
122
|
def _repo_and_revision_exist(
|
|
119
123
|
self, repo_type: str, repo_id: str, revision: Optional[str]
|
|
@@ -265,12 +269,15 @@ class HfFileSystem(fsspec.AbstractFileSystem):
|
|
|
265
269
|
block_size: Optional[int] = None,
|
|
266
270
|
**kwargs,
|
|
267
271
|
) -> "HfFileSystemFile":
|
|
272
|
+
block_size = block_size if block_size is not None else self.block_size
|
|
273
|
+
if block_size is not None:
|
|
274
|
+
kwargs["block_size"] = block_size
|
|
268
275
|
if "a" in mode:
|
|
269
276
|
raise NotImplementedError("Appending to remote files is not yet supported.")
|
|
270
277
|
if block_size == 0:
|
|
271
|
-
return HfFileSystemStreamFile(self, path, mode=mode, revision=revision,
|
|
278
|
+
return HfFileSystemStreamFile(self, path, mode=mode, revision=revision, **kwargs)
|
|
272
279
|
else:
|
|
273
|
-
return HfFileSystemFile(self, path, mode=mode, revision=revision,
|
|
280
|
+
return HfFileSystemFile(self, path, mode=mode, revision=revision, **kwargs)
|
|
274
281
|
|
|
275
282
|
def _rm(self, path: str, revision: Optional[str] = None, **kwargs) -> None:
|
|
276
283
|
resolved_path = self.resolve_path(path, revision=revision)
|
|
@@ -439,7 +446,7 @@ class HfFileSystem(fsspec.AbstractFileSystem):
|
|
|
439
446
|
common_path_depth = common_path[len(path) :].count("/")
|
|
440
447
|
maxdepth -= common_path_depth
|
|
441
448
|
out = [o for o in out if not o["name"].startswith(common_path + "/")]
|
|
442
|
-
for cached_path in self.dircache:
|
|
449
|
+
for cached_path in list(self.dircache):
|
|
443
450
|
if cached_path.startswith(common_path + "/"):
|
|
444
451
|
self.dircache.pop(cached_path, None)
|
|
445
452
|
self.dircache.pop(common_path, None)
|
|
@@ -923,6 +930,18 @@ class HfFileSystem(fsspec.AbstractFileSystem):
|
|
|
923
930
|
# See https://github.com/huggingface/huggingface_hub/issues/1733
|
|
924
931
|
raise NotImplementedError("Transactional commits are not supported.")
|
|
925
932
|
|
|
933
|
+
def __reduce__(self):
|
|
934
|
+
# re-populate the instance cache at HfFileSystem._cache and re-populate the cache attributes of every instance
|
|
935
|
+
return make_instance, (
|
|
936
|
+
type(self),
|
|
937
|
+
self.storage_args,
|
|
938
|
+
self.storage_options,
|
|
939
|
+
{
|
|
940
|
+
"dircache": self.dircache,
|
|
941
|
+
"_repo_and_revision_exists_cache": self._repo_and_revision_exists_cache,
|
|
942
|
+
},
|
|
943
|
+
)
|
|
944
|
+
|
|
926
945
|
|
|
927
946
|
class HfFileSystemFile(fsspec.spec.AbstractBufferedFile):
|
|
928
947
|
def __init__(self, fs: HfFileSystem, path: str, revision: Optional[str] = None, **kwargs):
|
|
@@ -986,9 +1005,8 @@ class HfFileSystemFile(fsspec.spec.AbstractBufferedFile):
|
|
|
986
1005
|
def read(self, length=-1):
|
|
987
1006
|
"""Read remote file.
|
|
988
1007
|
|
|
989
|
-
If `length` is not provided or is -1, the entire file is downloaded and read. On POSIX systems
|
|
990
|
-
|
|
991
|
-
temporary file and read from there.
|
|
1008
|
+
If `length` is not provided or is -1, the entire file is downloaded and read. On POSIX systems the file is
|
|
1009
|
+
loaded in memory directly. Otherwise, the file is downloaded to a temporary file and read from there.
|
|
992
1010
|
"""
|
|
993
1011
|
if self.mode == "rb" and (length is None or length == -1) and self.loc == 0:
|
|
994
1012
|
with self.fs.open(self.path, "rb", block_size=0) as f: # block_size=0 enables fast streaming
|
|
@@ -1158,3 +1176,10 @@ def _partial_read(response: httpx.Response, length: int = -1) -> bytes:
|
|
|
1158
1176
|
return bytes(buf[:length])
|
|
1159
1177
|
|
|
1160
1178
|
return bytes(buf) # may be < length if response ended
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def make_instance(cls, args, kwargs, instance_cache_attributes_dict):
|
|
1182
|
+
fs = cls(*args, **kwargs)
|
|
1183
|
+
for attr, cached_value in instance_cache_attributes_dict.items():
|
|
1184
|
+
setattr(fs, attr, cached_value)
|
|
1185
|
+
return fs
|
|
@@ -135,7 +135,7 @@ class InferenceClient:
|
|
|
135
135
|
Note: for better compatibility with OpenAI's client, `model` has been aliased as `base_url`. Those 2
|
|
136
136
|
arguments are mutually exclusive. If a URL is passed as `model` or `base_url` for chat completion, the `(/v1)/chat/completions` suffix path will be appended to the URL.
|
|
137
137
|
provider (`str`, *optional*):
|
|
138
|
-
Name of the provider to use for inference. Can be `"black-forest-labs"`, `"cerebras"`, `"cohere"`, `"fal-ai"`, `"featherless-ai"`, `"fireworks-ai"`, `"groq"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"nscale"`, `"openai"`, `publicai`, `"replicate"`, `"sambanova"`, `"scaleway"`, `"together"` or `"zai-org"`.
|
|
138
|
+
Name of the provider to use for inference. Can be `"black-forest-labs"`, `"cerebras"`, `"clarifai"`, `"cohere"`, `"fal-ai"`, `"featherless-ai"`, `"fireworks-ai"`, `"groq"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"nscale"`, `"openai"`, `publicai`, `"replicate"`, `"sambanova"`, `"scaleway"`, `"together"` or `"zai-org"`.
|
|
139
139
|
Defaults to "auto" i.e. the first of the providers available for the model, sorted by the user's order in https://hf.co/settings/inference-providers.
|
|
140
140
|
If model is a URL or `base_url` is passed, then `provider` is not used.
|
|
141
141
|
token (`str`, *optional*):
|
|
@@ -126,7 +126,7 @@ class AsyncInferenceClient:
|
|
|
126
126
|
Note: for better compatibility with OpenAI's client, `model` has been aliased as `base_url`. Those 2
|
|
127
127
|
arguments are mutually exclusive. If a URL is passed as `model` or `base_url` for chat completion, the `(/v1)/chat/completions` suffix path will be appended to the URL.
|
|
128
128
|
provider (`str`, *optional*):
|
|
129
|
-
Name of the provider to use for inference. Can be `"black-forest-labs"`, `"cerebras"`, `"cohere"`, `"fal-ai"`, `"featherless-ai"`, `"fireworks-ai"`, `"groq"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"nscale"`, `"openai"`, `publicai`, `"replicate"`, `"sambanova"`, `"scaleway"`, `"together"` or `"zai-org"`.
|
|
129
|
+
Name of the provider to use for inference. Can be `"black-forest-labs"`, `"cerebras"`, `"clarifai"`, `"cohere"`, `"fal-ai"`, `"featherless-ai"`, `"fireworks-ai"`, `"groq"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"nscale"`, `"openai"`, `publicai`, `"replicate"`, `"sambanova"`, `"scaleway"`, `"together"` or `"zai-org"`.
|
|
130
130
|
Defaults to "auto" i.e. the first of the providers available for the model, sorted by the user's order in https://hf.co/settings/inference-providers.
|
|
131
131
|
If model is a URL or `base_url` is passed, then `provider` is not used.
|
|
132
132
|
token (`str`, *optional*):
|
|
@@ -6,9 +6,10 @@ from huggingface_hub.inference._providers.featherless_ai import (
|
|
|
6
6
|
)
|
|
7
7
|
from huggingface_hub.utils import logging
|
|
8
8
|
|
|
9
|
-
from ._common import TaskProviderHelper, _fetch_inference_provider_mapping
|
|
9
|
+
from ._common import AutoRouterConversationalTask, TaskProviderHelper, _fetch_inference_provider_mapping
|
|
10
10
|
from .black_forest_labs import BlackForestLabsTextToImageTask
|
|
11
11
|
from .cerebras import CerebrasConversationalTask
|
|
12
|
+
from .clarifai import ClarifaiConversationalTask
|
|
12
13
|
from .cohere import CohereConversationalTask
|
|
13
14
|
from .fal_ai import (
|
|
14
15
|
FalAIAutomaticSpeechRecognitionTask,
|
|
@@ -50,6 +51,7 @@ logger = logging.get_logger(__name__)
|
|
|
50
51
|
PROVIDER_T = Literal[
|
|
51
52
|
"black-forest-labs",
|
|
52
53
|
"cerebras",
|
|
54
|
+
"clarifai",
|
|
53
55
|
"cohere",
|
|
54
56
|
"fal-ai",
|
|
55
57
|
"featherless-ai",
|
|
@@ -71,6 +73,8 @@ PROVIDER_T = Literal[
|
|
|
71
73
|
|
|
72
74
|
PROVIDER_OR_POLICY_T = Union[PROVIDER_T, Literal["auto"]]
|
|
73
75
|
|
|
76
|
+
CONVERSATIONAL_AUTO_ROUTER = AutoRouterConversationalTask()
|
|
77
|
+
|
|
74
78
|
PROVIDERS: dict[PROVIDER_T, dict[str, TaskProviderHelper]] = {
|
|
75
79
|
"black-forest-labs": {
|
|
76
80
|
"text-to-image": BlackForestLabsTextToImageTask(),
|
|
@@ -78,6 +82,9 @@ PROVIDERS: dict[PROVIDER_T, dict[str, TaskProviderHelper]] = {
|
|
|
78
82
|
"cerebras": {
|
|
79
83
|
"conversational": CerebrasConversationalTask(),
|
|
80
84
|
},
|
|
85
|
+
"clarifai": {
|
|
86
|
+
"conversational": ClarifaiConversationalTask(),
|
|
87
|
+
},
|
|
81
88
|
"cohere": {
|
|
82
89
|
"conversational": CohereConversationalTask(),
|
|
83
90
|
},
|
|
@@ -201,13 +208,19 @@ def get_provider_helper(
|
|
|
201
208
|
|
|
202
209
|
if provider is None:
|
|
203
210
|
logger.info(
|
|
204
|
-
"
|
|
211
|
+
"No provider specified for task `conversational`. Defaulting to server-side auto routing."
|
|
212
|
+
if task == "conversational"
|
|
213
|
+
else "Defaulting to 'auto' which will select the first provider available for the model, sorted by the user's order in https://hf.co/settings/inference-providers."
|
|
205
214
|
)
|
|
206
215
|
provider = "auto"
|
|
207
216
|
|
|
208
217
|
if provider == "auto":
|
|
209
218
|
if model is None:
|
|
210
219
|
raise ValueError("Specifying a model is required when provider is 'auto'")
|
|
220
|
+
if task == "conversational":
|
|
221
|
+
# Special case: we have a dedicated auto-router for conversational models. No need to fetch provider mapping.
|
|
222
|
+
return CONVERSATIONAL_AUTO_ROUTER
|
|
223
|
+
|
|
211
224
|
provider_mapping = _fetch_inference_provider_mapping(model)
|
|
212
225
|
provider = next(iter(provider_mapping)).provider
|
|
213
226
|
|
|
@@ -24,6 +24,7 @@ HARDCODED_MODEL_INFERENCE_MAPPING: dict[str, dict[str, InferenceProviderMapping]
|
|
|
24
24
|
# status="live")
|
|
25
25
|
"cerebras": {},
|
|
26
26
|
"cohere": {},
|
|
27
|
+
"clarifai": {},
|
|
27
28
|
"fal-ai": {},
|
|
28
29
|
"fireworks-ai": {},
|
|
29
30
|
"groq": {},
|
|
@@ -278,6 +279,44 @@ class BaseConversationalTask(TaskProviderHelper):
|
|
|
278
279
|
return filter_none({"messages": inputs, **parameters, "model": provider_mapping_info.provider_id})
|
|
279
280
|
|
|
280
281
|
|
|
282
|
+
class AutoRouterConversationalTask(BaseConversationalTask):
|
|
283
|
+
"""
|
|
284
|
+
Auto-router for conversational tasks.
|
|
285
|
+
|
|
286
|
+
We let the Hugging Face router select the best provider for the model, based on availability and user preferences.
|
|
287
|
+
This is a special case since the selection is done server-side (avoid 1 API call to fetch provider mapping).
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
def __init__(self):
|
|
291
|
+
super().__init__(provider="auto", base_url="https://router.huggingface.co")
|
|
292
|
+
|
|
293
|
+
def _prepare_base_url(self, api_key: str) -> str:
|
|
294
|
+
"""Return the base URL to use for the request.
|
|
295
|
+
|
|
296
|
+
Usually not overwritten in subclasses."""
|
|
297
|
+
# Route to the proxy if the api_key is a HF TOKEN
|
|
298
|
+
if not api_key.startswith("hf_"):
|
|
299
|
+
raise ValueError("Cannot select auto-router when using non-Hugging Face API key.")
|
|
300
|
+
else:
|
|
301
|
+
return self.base_url # No `/auto` suffix in the URL
|
|
302
|
+
|
|
303
|
+
def _prepare_mapping_info(self, model: Optional[str]) -> InferenceProviderMapping:
|
|
304
|
+
"""
|
|
305
|
+
In auto-router, we don't need to fetch provider mapping info.
|
|
306
|
+
We just return a dummy mapping info with provider_id set to the HF model ID.
|
|
307
|
+
"""
|
|
308
|
+
if model is None:
|
|
309
|
+
raise ValueError("Please provide an HF model ID.")
|
|
310
|
+
|
|
311
|
+
return InferenceProviderMapping(
|
|
312
|
+
provider="auto",
|
|
313
|
+
hf_model_id=model,
|
|
314
|
+
providerId=model,
|
|
315
|
+
status="live",
|
|
316
|
+
task="conversational",
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
281
320
|
class BaseTextGenerationTask(TaskProviderHelper):
|
|
282
321
|
"""
|
|
283
322
|
Base class for text-generation (completion) tasks.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ._common import BaseConversationalTask
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
_PROVIDER = "clarifai"
|
|
5
|
+
_BASE_URL = "https://api.clarifai.com"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ClarifaiConversationalTask(BaseConversationalTask):
|
|
9
|
+
def __init__(self):
|
|
10
|
+
super().__init__(provider=_PROVIDER, base_url=_BASE_URL)
|
|
11
|
+
|
|
12
|
+
def _prepare_route(self, mapped_model: str, api_key: str) -> str:
|
|
13
|
+
return "/v2/ext/openai/v1/chat/completions"
|
huggingface_hub/lfs.py
CHANGED
|
@@ -16,11 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
import io
|
|
18
18
|
import re
|
|
19
|
-
import warnings
|
|
20
19
|
from dataclasses import dataclass
|
|
21
20
|
from math import ceil
|
|
22
21
|
from os.path import getsize
|
|
23
|
-
from pathlib import Path
|
|
24
22
|
from typing import TYPE_CHECKING, BinaryIO, Iterable, Optional, TypedDict
|
|
25
23
|
from urllib.parse import unquote
|
|
26
24
|
|
|
@@ -33,12 +31,10 @@ from .utils import (
|
|
|
33
31
|
hf_raise_for_status,
|
|
34
32
|
http_backoff,
|
|
35
33
|
logging,
|
|
36
|
-
tqdm,
|
|
37
34
|
validate_hf_hub_args,
|
|
38
35
|
)
|
|
39
36
|
from .utils._lfs import SliceFileObj
|
|
40
37
|
from .utils.sha import sha256, sha_fileobj
|
|
41
|
-
from .utils.tqdm import is_tqdm_disabled
|
|
42
38
|
|
|
43
39
|
|
|
44
40
|
if TYPE_CHECKING:
|
|
@@ -332,23 +328,9 @@ def _upload_multi_part(operation: "CommitOperationAdd", header: dict, chunk_size
|
|
|
332
328
|
# 1. Get upload URLs for each part
|
|
333
329
|
sorted_parts_urls = _get_sorted_parts_urls(header=header, upload_info=operation.upload_info, chunk_size=chunk_size)
|
|
334
330
|
|
|
335
|
-
# 2. Upload parts (
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
constants.HF_HUB_ENABLE_HF_TRANSFER
|
|
339
|
-
and not isinstance(operation.path_or_fileobj, str)
|
|
340
|
-
and not isinstance(operation.path_or_fileobj, Path)
|
|
341
|
-
):
|
|
342
|
-
warnings.warn(
|
|
343
|
-
"hf_transfer is enabled but does not support uploading from bytes or BinaryIO, falling back to regular"
|
|
344
|
-
" upload"
|
|
345
|
-
)
|
|
346
|
-
use_hf_transfer = False
|
|
347
|
-
|
|
348
|
-
response_headers = (
|
|
349
|
-
_upload_parts_hf_transfer(operation=operation, sorted_parts_urls=sorted_parts_urls, chunk_size=chunk_size)
|
|
350
|
-
if use_hf_transfer
|
|
351
|
-
else _upload_parts_iteratively(operation=operation, sorted_parts_urls=sorted_parts_urls, chunk_size=chunk_size)
|
|
331
|
+
# 2. Upload parts (pure Python)
|
|
332
|
+
response_headers = _upload_parts_iteratively(
|
|
333
|
+
operation=operation, sorted_parts_urls=sorted_parts_urls, chunk_size=chunk_size
|
|
352
334
|
)
|
|
353
335
|
|
|
354
336
|
# 3. Send completion request
|
|
@@ -409,47 +391,3 @@ def _upload_parts_iteratively(
|
|
|
409
391
|
hf_raise_for_status(part_upload_res)
|
|
410
392
|
headers.append(part_upload_res.headers)
|
|
411
393
|
return headers # type: ignore
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
def _upload_parts_hf_transfer(
|
|
415
|
-
operation: "CommitOperationAdd", sorted_parts_urls: list[str], chunk_size: int
|
|
416
|
-
) -> list[dict]:
|
|
417
|
-
# Upload file using an external Rust-based package. Upload is faster but support less features (no progress bars).
|
|
418
|
-
try:
|
|
419
|
-
from hf_transfer import multipart_upload
|
|
420
|
-
except ImportError:
|
|
421
|
-
raise ValueError(
|
|
422
|
-
"Fast uploading using 'hf_transfer' is enabled (HF_HUB_ENABLE_HF_TRANSFER=1) but 'hf_transfer' package is"
|
|
423
|
-
" not available in your environment. Try `pip install hf_transfer`."
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
total = operation.upload_info.size
|
|
427
|
-
desc = operation.path_in_repo
|
|
428
|
-
if len(desc) > 40:
|
|
429
|
-
desc = f"(…){desc[-40:]}"
|
|
430
|
-
|
|
431
|
-
with tqdm(
|
|
432
|
-
unit="B",
|
|
433
|
-
unit_scale=True,
|
|
434
|
-
total=total,
|
|
435
|
-
initial=0,
|
|
436
|
-
desc=desc,
|
|
437
|
-
disable=is_tqdm_disabled(logger.getEffectiveLevel()),
|
|
438
|
-
name="huggingface_hub.lfs_upload",
|
|
439
|
-
) as progress:
|
|
440
|
-
try:
|
|
441
|
-
output = multipart_upload(
|
|
442
|
-
file_path=operation.path_or_fileobj,
|
|
443
|
-
parts_urls=sorted_parts_urls,
|
|
444
|
-
chunk_size=chunk_size,
|
|
445
|
-
max_files=128,
|
|
446
|
-
parallel_failures=127, # could be removed
|
|
447
|
-
max_retries=5,
|
|
448
|
-
callback=progress.update,
|
|
449
|
-
)
|
|
450
|
-
except Exception as e:
|
|
451
|
-
raise RuntimeError(
|
|
452
|
-
"An error occurred while uploading using `hf_transfer`. Consider disabling HF_HUB_ENABLE_HF_TRANSFER for"
|
|
453
|
-
" better error handling."
|
|
454
|
-
) from e
|
|
455
|
-
return output
|
|
@@ -266,7 +266,7 @@ def save_torch_state_dict(
|
|
|
266
266
|
safe_file_kwargs = {"metadata": per_file_metadata} if safe_serialization else {}
|
|
267
267
|
for filename, tensors in state_dict_split.filename_to_tensors.items():
|
|
268
268
|
shard = {tensor: state_dict[tensor] for tensor in tensors}
|
|
269
|
-
save_file_fn(shard, os.path.join(save_directory, filename), **safe_file_kwargs)
|
|
269
|
+
save_file_fn(shard, os.path.join(save_directory, filename), **safe_file_kwargs) # ty: ignore[invalid-argument-type]
|
|
270
270
|
logger.debug(f"Shard saved to {filename}")
|
|
271
271
|
|
|
272
272
|
# Save the index (if any)
|
|
@@ -75,7 +75,6 @@ from ._runtime import (
|
|
|
75
75
|
get_gradio_version,
|
|
76
76
|
get_graphviz_version,
|
|
77
77
|
get_hf_hub_version,
|
|
78
|
-
get_hf_transfer_version,
|
|
79
78
|
get_jinja_version,
|
|
80
79
|
get_numpy_version,
|
|
81
80
|
get_pillow_version,
|
|
@@ -94,7 +93,6 @@ from ._runtime import (
|
|
|
94
93
|
is_google_colab,
|
|
95
94
|
is_gradio_available,
|
|
96
95
|
is_graphviz_available,
|
|
97
|
-
is_hf_transfer_available,
|
|
98
96
|
is_jinja_available,
|
|
99
97
|
is_notebook,
|
|
100
98
|
is_numpy_available,
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
import os
|
|
18
18
|
import shutil
|
|
19
|
-
import time
|
|
20
19
|
from collections import defaultdict
|
|
21
20
|
from dataclasses import dataclass
|
|
22
21
|
from pathlib import Path
|
|
@@ -26,6 +25,7 @@ from huggingface_hub.errors import CacheNotFound, CorruptedCacheException
|
|
|
26
25
|
|
|
27
26
|
from ..constants import HF_HUB_CACHE
|
|
28
27
|
from . import logging
|
|
28
|
+
from ._parsing import format_timesince
|
|
29
29
|
from ._terminal import tabulate
|
|
30
30
|
|
|
31
31
|
|
|
@@ -79,7 +79,7 @@ class CachedFileInfo:
|
|
|
79
79
|
|
|
80
80
|
Example: "2 weeks ago".
|
|
81
81
|
"""
|
|
82
|
-
return
|
|
82
|
+
return format_timesince(self.blob_last_accessed)
|
|
83
83
|
|
|
84
84
|
@property
|
|
85
85
|
def blob_last_modified_str(self) -> str:
|
|
@@ -89,7 +89,7 @@ class CachedFileInfo:
|
|
|
89
89
|
|
|
90
90
|
Example: "2 weeks ago".
|
|
91
91
|
"""
|
|
92
|
-
return
|
|
92
|
+
return format_timesince(self.blob_last_modified)
|
|
93
93
|
|
|
94
94
|
@property
|
|
95
95
|
def size_on_disk_str(self) -> str:
|
|
@@ -153,7 +153,7 @@ class CachedRevisionInfo:
|
|
|
153
153
|
|
|
154
154
|
Example: "2 weeks ago".
|
|
155
155
|
"""
|
|
156
|
-
return
|
|
156
|
+
return format_timesince(self.last_modified)
|
|
157
157
|
|
|
158
158
|
@property
|
|
159
159
|
def size_on_disk_str(self) -> str:
|
|
@@ -223,7 +223,7 @@ class CachedRepoInfo:
|
|
|
223
223
|
|
|
224
224
|
Example: "2 weeks ago".
|
|
225
225
|
"""
|
|
226
|
-
return
|
|
226
|
+
return format_timesince(self.last_accessed)
|
|
227
227
|
|
|
228
228
|
@property
|
|
229
229
|
def last_modified_str(self) -> str:
|
|
@@ -233,7 +233,7 @@ class CachedRepoInfo:
|
|
|
233
233
|
|
|
234
234
|
Example: "2 weeks ago".
|
|
235
235
|
"""
|
|
236
|
-
return
|
|
236
|
+
return format_timesince(self.last_modified)
|
|
237
237
|
|
|
238
238
|
@property
|
|
239
239
|
def size_on_disk_str(self) -> str:
|
|
@@ -244,6 +244,11 @@ class CachedRepoInfo:
|
|
|
244
244
|
"""
|
|
245
245
|
return _format_size(self.size_on_disk)
|
|
246
246
|
|
|
247
|
+
@property
|
|
248
|
+
def cache_id(self) -> str:
|
|
249
|
+
"""Canonical `type/id` identifier used across cache tooling."""
|
|
250
|
+
return f"{self.repo_type}/{self.repo_id}"
|
|
251
|
+
|
|
247
252
|
@property
|
|
248
253
|
def refs(self) -> dict[str, CachedRevisionInfo]:
|
|
249
254
|
"""
|
|
@@ -607,15 +612,12 @@ def scan_cache_dir(cache_dir: Optional[Union[str, Path]] = None) -> HFCacheInfo:
|
|
|
607
612
|
|
|
608
613
|
You can also print a detailed report directly from the `hf` command line using:
|
|
609
614
|
```text
|
|
610
|
-
> hf cache
|
|
611
|
-
|
|
612
|
-
---------------------------
|
|
613
|
-
glue
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
bert-base-cased model 1.9G 13 main /Users/lucain/.cache/huggingface/hub/models--bert-base-cased
|
|
617
|
-
t5-base model 10.1K 3 main /Users/lucain/.cache/huggingface/hub/models--t5-base
|
|
618
|
-
t5-small model 970.7M 11 refs/pr/1, main /Users/lucain/.cache/huggingface/hub/models--t5-small
|
|
615
|
+
> hf cache ls
|
|
616
|
+
ID SIZE LAST_ACCESSED LAST_MODIFIED REFS
|
|
617
|
+
--------------------------- -------- ------------- ------------- -----------
|
|
618
|
+
dataset/nyu-mll/glue 157.4M 2 days ago 2 days ago main script
|
|
619
|
+
model/LiquidAI/LFM2-VL-1.6B 3.2G 4 days ago 4 days ago main
|
|
620
|
+
model/microsoft/UserLM-8b 32.1G 4 days ago 4 days ago main
|
|
619
621
|
|
|
620
622
|
Done in 0.0s. Scanned 6 repo(s) for a total of 3.4G.
|
|
621
623
|
Got 1 warning(s) while scanning. Use -vvv to print details.
|
|
@@ -816,33 +818,6 @@ def _format_size(num: int) -> str:
|
|
|
816
818
|
return f"{num_f:.1f}Y"
|
|
817
819
|
|
|
818
820
|
|
|
819
|
-
_TIMESINCE_CHUNKS = (
|
|
820
|
-
# Label, divider, max value
|
|
821
|
-
("second", 1, 60),
|
|
822
|
-
("minute", 60, 60),
|
|
823
|
-
("hour", 60 * 60, 24),
|
|
824
|
-
("day", 60 * 60 * 24, 6),
|
|
825
|
-
("week", 60 * 60 * 24 * 7, 6),
|
|
826
|
-
("month", 60 * 60 * 24 * 30, 11),
|
|
827
|
-
("year", 60 * 60 * 24 * 365, None),
|
|
828
|
-
)
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
def _format_timesince(ts: float) -> str:
|
|
832
|
-
"""Format timestamp in seconds into a human-readable string, relative to now.
|
|
833
|
-
|
|
834
|
-
Vaguely inspired by Django's `timesince` formatter.
|
|
835
|
-
"""
|
|
836
|
-
delta = time.time() - ts
|
|
837
|
-
if delta < 20:
|
|
838
|
-
return "a few seconds ago"
|
|
839
|
-
for label, divider, max_value in _TIMESINCE_CHUNKS: # noqa: B007
|
|
840
|
-
value = round(delta / divider)
|
|
841
|
-
if max_value is not None and value <= max_value:
|
|
842
|
-
break
|
|
843
|
-
return f"{value} {label}{'s' if value > 1 else ''} ago"
|
|
844
|
-
|
|
845
|
-
|
|
846
821
|
def _try_delete_path(path: Path, path_type: str) -> None:
|
|
847
822
|
"""Try to delete a local file or folder.
|
|
848
823
|
|
huggingface_hub/utils/_http.py
CHANGED
|
@@ -109,6 +109,20 @@ async def async_hf_request_event_hook(request: httpx.Request) -> None:
|
|
|
109
109
|
return hf_request_event_hook(request)
|
|
110
110
|
|
|
111
111
|
|
|
112
|
+
async def async_hf_response_event_hook(response: httpx.Response) -> None:
|
|
113
|
+
if response.status_code >= 400:
|
|
114
|
+
# If response will raise, read content from stream to have it available when raising the exception
|
|
115
|
+
# If content-length is not set or is too large, skip reading the content to avoid OOM
|
|
116
|
+
if "Content-length" in response.headers:
|
|
117
|
+
try:
|
|
118
|
+
length = int(response.headers["Content-length"])
|
|
119
|
+
except ValueError:
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
if length < 1_000_000:
|
|
123
|
+
await response.aread()
|
|
124
|
+
|
|
125
|
+
|
|
112
126
|
def default_client_factory() -> httpx.Client:
|
|
113
127
|
"""
|
|
114
128
|
Factory function to create a `httpx.Client` with the default transport.
|
|
@@ -125,7 +139,7 @@ def default_async_client_factory() -> httpx.AsyncClient:
|
|
|
125
139
|
Factory function to create a `httpx.AsyncClient` with the default transport.
|
|
126
140
|
"""
|
|
127
141
|
return httpx.AsyncClient(
|
|
128
|
-
event_hooks={"request": [async_hf_request_event_hook]},
|
|
142
|
+
event_hooks={"request": [async_hf_request_event_hook], "response": [async_hf_response_event_hook]},
|
|
129
143
|
follow_redirects=True,
|
|
130
144
|
timeout=httpx.Timeout(constants.DEFAULT_REQUEST_TIMEOUT, write=60.0),
|
|
131
145
|
)
|
|
@@ -626,8 +640,16 @@ def _format(error_type: type[HfHubHTTPError], custom_message: str, response: htt
|
|
|
626
640
|
try:
|
|
627
641
|
data = response.json()
|
|
628
642
|
except httpx.ResponseNotRead:
|
|
629
|
-
|
|
630
|
-
|
|
643
|
+
try:
|
|
644
|
+
response.read() # In case of streaming response, we need to read the response first
|
|
645
|
+
data = response.json()
|
|
646
|
+
except RuntimeError:
|
|
647
|
+
# In case of async streaming response, we can't read the stream here.
|
|
648
|
+
# In practice if user is using the default async client from `get_async_client`, the stream will have
|
|
649
|
+
# already been read in the async event hook `async_hf_response_event_hook`.
|
|
650
|
+
#
|
|
651
|
+
# Here, we are skipping reading the response to avoid RuntimeError but it happens only if async + stream + used httpx.AsyncClient directly.
|
|
652
|
+
data = {}
|
|
631
653
|
|
|
632
654
|
error = data.get("error")
|
|
633
655
|
if error is not None:
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
# Copyright 2025-present, the HuggingFace Inc. team.
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
"""Parsing helpers shared across modules."""
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
import time
|
|
19
|
+
from typing import Dict
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
RE_NUMBER_WITH_UNIT = re.compile(r"(\d+)([a-z]+)", re.IGNORECASE)
|
|
23
|
+
|
|
24
|
+
BYTE_UNITS: Dict[str, int] = {
|
|
25
|
+
"k": 1_000,
|
|
26
|
+
"m": 1_000_000,
|
|
27
|
+
"g": 1_000_000_000,
|
|
28
|
+
"t": 1_000_000_000_000,
|
|
29
|
+
"p": 1_000_000_000_000_000,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
TIME_UNITS: Dict[str, int] = {
|
|
33
|
+
"s": 1,
|
|
34
|
+
"m": 60,
|
|
35
|
+
"h": 60 * 60,
|
|
36
|
+
"d": 24 * 60 * 60,
|
|
37
|
+
"w": 7 * 24 * 60 * 60,
|
|
38
|
+
"mo": 30 * 24 * 60 * 60,
|
|
39
|
+
"y": 365 * 24 * 60 * 60,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def parse_size(value: str) -> int:
|
|
44
|
+
"""Parse a size expressed as a string with digits and unit (like `"10MB"`) to an integer (in bytes)."""
|
|
45
|
+
return _parse_with_unit(value, BYTE_UNITS)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def parse_duration(value: str) -> int:
|
|
49
|
+
"""Parse a duration expressed as a string with digits and unit (like `"10s"`) to an integer (in seconds)."""
|
|
50
|
+
return _parse_with_unit(value, TIME_UNITS)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _parse_with_unit(value: str, units: Dict[str, int]) -> int:
|
|
54
|
+
"""Parse a numeric value with optional unit."""
|
|
55
|
+
stripped = value.strip()
|
|
56
|
+
if not stripped:
|
|
57
|
+
raise ValueError("Value cannot be empty.")
|
|
58
|
+
try:
|
|
59
|
+
return int(value)
|
|
60
|
+
except ValueError:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
match = RE_NUMBER_WITH_UNIT.fullmatch(stripped)
|
|
64
|
+
if not match:
|
|
65
|
+
raise ValueError(f"Invalid value '{value}'. Must match pattern '\\d+[a-z]+' or be a plain number.")
|
|
66
|
+
|
|
67
|
+
number = int(match.group(1))
|
|
68
|
+
unit = match.group(2).lower()
|
|
69
|
+
|
|
70
|
+
if unit not in units:
|
|
71
|
+
raise ValueError(f"Unknown unit '{unit}'. Must be one of {list(units.keys())}.")
|
|
72
|
+
|
|
73
|
+
return number * units[unit]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def format_timesince(ts: float) -> str:
|
|
77
|
+
"""Format timestamp in seconds into a human-readable string, relative to now.
|
|
78
|
+
|
|
79
|
+
Vaguely inspired by Django's `timesince` formatter.
|
|
80
|
+
"""
|
|
81
|
+
_TIMESINCE_CHUNKS = (
|
|
82
|
+
# Label, divider, max value
|
|
83
|
+
("second", 1, 60),
|
|
84
|
+
("minute", 60, 60),
|
|
85
|
+
("hour", 60 * 60, 24),
|
|
86
|
+
("day", 60 * 60 * 24, 6),
|
|
87
|
+
("week", 60 * 60 * 24 * 7, 6),
|
|
88
|
+
("month", 60 * 60 * 24 * 30, 11),
|
|
89
|
+
("year", 60 * 60 * 24 * 365, None),
|
|
90
|
+
)
|
|
91
|
+
delta = time.time() - ts
|
|
92
|
+
if delta < 20:
|
|
93
|
+
return "a few seconds ago"
|
|
94
|
+
for label, divider, max_value in _TIMESINCE_CHUNKS: # noqa: B007
|
|
95
|
+
value = round(delta / divider)
|
|
96
|
+
if max_value is not None and value <= max_value:
|
|
97
|
+
break
|
|
98
|
+
return f"{value} {label}{'s' if value > 1 else ''} ago"
|