Skip to content

Commit 276c42e

Browse files
committed
Merge branch 'main' into v0.30-release
2 parents fe1534b + cb315d9 commit 276c42e

File tree

7 files changed

+106
-8
lines changed

7 files changed

+106
-8
lines changed

.github/workflows/python-prerelease.yml

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ name: Python prerelease
33
on:
44
push:
55
tags:
6-
- "v*.rc*"
7-
6+
- v*rc*
7+
workflow_dispatch:
8+
inputs:
9+
tag:
10+
description: "Tag to test (e.g., 0.30.0rc2)"
11+
required: true
812
jobs:
913
trigger_rc_testing:
1014
runs-on: ubuntu-latest
@@ -15,9 +19,14 @@ jobs:
1519
target-repo: ["transformers", "datasets", "diffusers"]
1620

1721
steps:
18-
- name: Extract version from tag
22+
- name: Determine version from tag
1923
id: get-version
20-
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
24+
run: |
25+
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
26+
echo "VERSION=${{ inputs.tag }}" >> $GITHUB_OUTPUT
27+
else
28+
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
29+
fi
2130
2231
- name: Checkout target repo
2332
uses: actions/checkout@v4
@@ -80,4 +89,4 @@ jobs:
8089
run: |
8190
VERSION=${{ steps.get-version.outputs.VERSION }}
8291
echo "https://github.com/huggingface/${{ matrix.target-repo }}/actions"
83-
echo "https://github.com/huggingface/${{ matrix.target-repo }}/compare/main...ci-test-huggingface-hub-${VERSION}"
92+
echo "https://github.com/huggingface/${{ matrix.target-repo }}/compare/main...ci-test-huggingface-hub-${VERSION}"

docs/source/en/guides/download.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ There are two options to speed up downloads. Both involve installing a Python pa
176176
Take advantage of faster downloads through `hf_xet`, the Python binding to the [`xet-core`](https://github.com/huggingface/xet-core) library that enables
177177
chunk-based deduplication for faster downloads and uploads. `hf_xet` integrates seamlessly with `huggingface_hub`, but uses the Rust `xet-core` library and Xet storage instead of LFS.
178178

179-
`hf_xet` uses the Xet storage system, which breaks files down into immutable chunks, storing collections of these chunks (called blocks or xorbs) remotely and retrieving them to reassemble the file when requested. When downloading, after confirming the user is authorized to access the files, `hf_xet` will query the Xet content-addressable service (CAS) with the LFS SHA256 hash for this file to receive the reconstruction metadata (ranges within xorbs) to assemble these files, along with presigned URLs to download the xorbs directly. Then `hf_xet` will efficiently download the xorb ranges necessary and will write out the files on disk. `hf_xet` uses a local disk cache to only download chunks once, learn more in the [Chunk-based caching(Xet)](./manage-cache.md#chunk-based-caching-xet) section.
179+
`hf_xet` uses the Xet storage system, which breaks files down into immutable chunks, storing collections of these chunks (called blocks or xorbs) remotely and retrieving them to reassemble the file when requested. When downloading, after confirming the user is authorized to access the files, `hf_xet` will query the Xet content-addressable service (CAS) with the LFS SHA256 hash for this file to receive the reconstruction metadata (ranges within xorbs) to assemble these files, along with presigned URLs to download the xorbs directly. Then `hf_xet` will efficiently download the xorb ranges necessary and will write out the files on disk. `hf_xet` uses a local disk cache to only download chunks once, learn more in the [Chunk-based caching(Xet)](./manage-cache#chunk-based-caching-xet) section.
180180

181181
To enable it, specify the `hf_xet` package when installing `huggingface_hub`:
182182

docs/source/en/guides/inference.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,23 @@ For more information about the `asyncio` module, please refer to the [official d
320320

321321
In the above section, we saw the main aspects of [`InferenceClient`]. Let's dive into some more advanced tips.
322322

323+
### Billing
324+
325+
As an HF user, you get monthly credits to run inference through various providers on the Hub. The amount of credits you get depends on your type of account (Free or PRO or Enterprise Hub). You get charged for every inference request, depending on the provider's pricing table. By default, the requests are billed to your personal account. However, it is possible to set the billing so that requests are charged to an organization you are part of by simply passing `bill_to="<your_org_name>"` to `InferenceClient`. For this to work, your organization must be subscribed to Enterprise Hub. For more details about billing, check out [this guide](https://huggingface.co/docs/api-inference/pricing#features-using-inference-providers).
326+
327+
```py
328+
>>> from huggingface_hub import InferenceClient
329+
>>> client = InferenceClient(provider="fal-ai", bill_to="openai")
330+
>>> image = client.text_to_image(
331+
... "A majestic lion in a fantasy forest",
332+
... model="black-forest-labs/FLUX.1-schnell",
333+
... )
334+
>>> image.save("lion.png")
335+
```
336+
337+
Note that it is NOT possible to charge another user or an organization you are not part of. If you want to grant someone else some credits, you must create a joint organization with them.
338+
339+
323340
### Timeout
324341

325342
Inference calls can take a significant amount of time. By default, [`InferenceClient`] will wait "indefinitely" until the inference complete. If you want more control in your workflow, you can set the `timeout` parameter to a specific value in seconds. If the timeout delay expires, an [`InferenceTimeoutError`] is raised, which you can catch in your code:

src/huggingface_hub/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def _as_int(value: Optional[str]) -> Optional[int]:
7373
HUGGINGFACE_HEADER_X_REPO_COMMIT = "X-Repo-Commit"
7474
HUGGINGFACE_HEADER_X_LINKED_ETAG = "X-Linked-Etag"
7575
HUGGINGFACE_HEADER_X_LINKED_SIZE = "X-Linked-Size"
76+
HUGGINGFACE_HEADER_X_BILL_TO = "X-HF-Bill-To"
7677

7778
INFERENCE_ENDPOINT = os.environ.get("HF_INFERENCE_ENDPOINT", "https://api-inference.huggingface.co")
7879

src/huggingface_hub/inference/_client.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ class InferenceClient:
146146
headers (`Dict[str, str]`, `optional`):
147147
Additional headers to send to the server. By default only the authorization and user-agent headers are sent.
148148
Values in this dictionary will override the default values.
149+
bill_to (`str`, `optional`):
150+
The billing account to use for the requests. By default the requests are billed on the user's account.
151+
Requests can only be billed to an organization the user is a member of, and which has subscribed to Enterprise Hub.
149152
cookies (`Dict[str, str]`, `optional`):
150153
Additional cookies to send to the server.
151154
proxies (`Any`, `optional`):
@@ -168,6 +171,7 @@ def __init__(
168171
headers: Optional[Dict[str, str]] = None,
169172
cookies: Optional[Dict[str, str]] = None,
170173
proxies: Optional[Any] = None,
174+
bill_to: Optional[str] = None,
171175
# OpenAI compatibility
172176
base_url: Optional[str] = None,
173177
api_key: Optional[str] = None,
@@ -203,7 +207,25 @@ def __init__(
203207

204208
self.model: Optional[str] = base_url or model
205209
self.token: Optional[str] = token
206-
self.headers = headers if headers is not None else {}
210+
211+
self.headers = {**headers} if headers is not None else {}
212+
if bill_to is not None:
213+
if (
214+
constants.HUGGINGFACE_HEADER_X_BILL_TO in self.headers
215+
and self.headers[constants.HUGGINGFACE_HEADER_X_BILL_TO] != bill_to
216+
):
217+
warnings.warn(
218+
f"Overriding existing '{self.headers[constants.HUGGINGFACE_HEADER_X_BILL_TO]}' value in headers with '{bill_to}'.",
219+
UserWarning,
220+
)
221+
self.headers[constants.HUGGINGFACE_HEADER_X_BILL_TO] = bill_to
222+
223+
if token is not None and not token.startswith("hf_"):
224+
warnings.warn(
225+
"You've provided an external provider's API key, so requests will be billed directly by the provider. "
226+
"The `bill_to` parameter is only applicable for Hugging Face billing and will be ignored.",
227+
UserWarning,
228+
)
207229

208230
# Configure provider
209231
self.provider = provider if provider is not None else "hf-inference"

src/huggingface_hub/inference/_generated/_async_client.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ class AsyncInferenceClient:
134134
headers (`Dict[str, str]`, `optional`):
135135
Additional headers to send to the server. By default only the authorization and user-agent headers are sent.
136136
Values in this dictionary will override the default values.
137+
bill_to (`str`, `optional`):
138+
The billing account to use for the requests. By default the requests are billed on the user's account.
139+
Requests can only be billed to an organization the user is a member of, and which has subscribed to Enterprise Hub.
137140
cookies (`Dict[str, str]`, `optional`):
138141
Additional cookies to send to the server.
139142
trust_env ('bool', 'optional'):
@@ -159,6 +162,7 @@ def __init__(
159162
cookies: Optional[Dict[str, str]] = None,
160163
trust_env: bool = False,
161164
proxies: Optional[Any] = None,
165+
bill_to: Optional[str] = None,
162166
# OpenAI compatibility
163167
base_url: Optional[str] = None,
164168
api_key: Optional[str] = None,
@@ -194,7 +198,25 @@ def __init__(
194198

195199
self.model: Optional[str] = base_url or model
196200
self.token: Optional[str] = token
197-
self.headers = headers if headers is not None else {}
201+
202+
self.headers = {**headers} if headers is not None else {}
203+
if bill_to is not None:
204+
if (
205+
constants.HUGGINGFACE_HEADER_X_BILL_TO in self.headers
206+
and self.headers[constants.HUGGINGFACE_HEADER_X_BILL_TO] != bill_to
207+
):
208+
warnings.warn(
209+
f"Overriding existing '{self.headers[constants.HUGGINGFACE_HEADER_X_BILL_TO]}' value in headers with '{bill_to}'.",
210+
UserWarning,
211+
)
212+
self.headers[constants.HUGGINGFACE_HEADER_X_BILL_TO] = bill_to
213+
214+
if token is not None and not token.startswith("hf_"):
215+
warnings.warn(
216+
"You've provided an external provider's API key, so requests will be billed directly by the provider. "
217+
"The `bill_to` parameter is only applicable for Hugging Face billing and will be ignored.",
218+
UserWarning,
219+
)
198220

199221
# Configure provider
200222
self.provider = provider if provider is not None else "hf-inference"

tests/test_inference_client.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,3 +1079,30 @@ def test_cannot_pass_token_false():
10791079
"""
10801080
with pytest.raises(ValueError):
10811081
InferenceClient(token=False)
1082+
1083+
1084+
class TestBillToOrganization:
1085+
def test_bill_to_added_to_new_headers(self):
1086+
client = InferenceClient(bill_to="huggingface_hub")
1087+
assert client.headers["X-HF-Bill-To"] == "huggingface_hub"
1088+
1089+
def test_bill_to_added_to_existing_headers(self):
1090+
headers = {"foo": "bar"}
1091+
client = InferenceClient(bill_to="huggingface_hub", headers=headers)
1092+
assert client.headers["X-HF-Bill-To"] == "huggingface_hub"
1093+
assert client.headers["foo"] == "bar"
1094+
assert headers == {"foo": "bar"} # do not mutate the original headers
1095+
1096+
def test_warning_if_bill_to_already_set(self):
1097+
headers = {"X-HF-Bill-To": "huggingface"}
1098+
with pytest.warns(UserWarning, match="Overriding existing 'huggingface' value in headers with 'openai'."):
1099+
client = InferenceClient(bill_to="openai", headers=headers)
1100+
assert client.headers["X-HF-Bill-To"] == "openai"
1101+
assert headers == {"X-HF-Bill-To": "huggingface"} # do not mutate the original headers
1102+
1103+
def test_warning_if_bill_to_with_direct_calls(self):
1104+
with pytest.warns(
1105+
UserWarning,
1106+
match="You've provided an external provider's API key, so requests will be billed directly by the provider.",
1107+
):
1108+
InferenceClient(bill_to="openai", token="replicate_key", provider="replicate")

0 commit comments

Comments
 (0)