Skip to content

Commit

Permalink
Always proxy hf-inference calls + update tests (#2798)
Browse files Browse the repository at this point in the history
* Always proxy hf-inference calls + update tests

* fix some tests

* fix test

* use https://router.huggingface.co/ as proxy
  • Loading branch information
Wauplin authored Feb 5, 2025
1 parent b60804b commit a70b69d
Show file tree
Hide file tree
Showing 58 changed files with 229 additions and 179 deletions.
2 changes: 1 addition & 1 deletion src/huggingface_hub/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def _as_int(value: Optional[str]) -> Optional[int]:
INFERENCE_ENDPOINTS_ENDPOINT = "https://api.endpoints.huggingface.cloud/v2"

# Proxy for third-party providers
INFERENCE_PROXY_TEMPLATE = ENDPOINT + "/api/inference-proxy/{provider}"
INFERENCE_PROXY_TEMPLATE = "https://router.huggingface.co/{provider}"

REPO_ID_SEPARATOR = "--"
# ^ this substring is not allowed in repo_ids on hf.co
Expand Down
19 changes: 10 additions & 9 deletions src/huggingface_hub/inference/_providers/hf_inference.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, Union

from huggingface_hub.constants import ENDPOINT
from huggingface_hub import constants
from huggingface_hub.inference._common import RequestParameters, TaskProviderHelper, _b64_encode, _open_as_binary
from huggingface_hub.utils import build_hf_headers, get_session, hf_raise_for_status


## RECOMMENDED MODELS

# Will be globally fetched only once (see '_fetch_recommended_models')
_RECOMMENDED_MODELS: Optional[Dict[str, Optional[str]]] = None

BASE_URL = "https://api-inference.huggingface.co"


def _first_or_none(items: List[Any]) -> Optional[Any]:
try:
Expand All @@ -24,7 +20,7 @@ def _first_or_none(items: List[Any]) -> Optional[Any]:
def _fetch_recommended_models() -> Dict[str, Optional[str]]:
global _RECOMMENDED_MODELS
if _RECOMMENDED_MODELS is None:
response = get_session().get(f"{ENDPOINT}/api/tasks", headers=build_hf_headers())
response = get_session().get(f"{constants.ENDPOINT}/api/tasks", headers=build_hf_headers())
hf_raise_for_status(response)
_RECOMMENDED_MODELS = {
task: _first_or_none(details["widgetModels"]) for task, details in response.json().items()
Expand Down Expand Up @@ -96,12 +92,13 @@ def build_url(self, model: str) -> str:
if model.startswith(("http://", "https://")):
return model

base_url = constants.INFERENCE_PROXY_TEMPLATE.format(provider="hf-inference")
return (
# Feature-extraction and sentence-similarity are the only cases where we handle models with several tasks.
f"{BASE_URL}/pipeline/{self.task}/{model}"
f"{base_url}/pipeline/{self.task}/{model}"
if self.task in ("feature-extraction", "sentence-similarity")
# Otherwise, we use the default endpoint
else f"{BASE_URL}/models/{model}"
else f"{base_url}/models/{model}"
)

def prepare_headers(self, headers: Dict, *, api_key: Optional[Union[bool, str]] = None) -> Dict:
Expand Down Expand Up @@ -183,7 +180,11 @@ def prepare_request(
)

def build_url(self, model: str) -> str:
base_url = model if model.startswith(("http://", "https://")) else f"{BASE_URL}/models/{model}"
base_url = (
model
if model.startswith(("http://", "https://"))
else f"{constants.INFERENCE_PROXY_TEMPLATE.format(provider='hf-inference')}/models/{model}"
)
return _build_chat_completion_url(base_url)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interactions:
X-Amzn-Trace-Id:
- 38fba685-c633-42e7-8815-fa9ca92c69bf
method: POST
uri: https://api-inference.huggingface.co/models/alefiury/wav2vec2-large-xlsr-53-gender-recognition-librispeech
uri: https://router.huggingface.co/hf-inference/models/alefiury/wav2vec2-large-xlsr-53-gender-recognition-librispeech
response:
body:
string: '[{"score":0.9987353682518005,"label":"male"},{"score":0.0012646110262721777,"label":"female"}]'
Expand Down Expand Up @@ -61,7 +61,7 @@ interactions:
X-Amzn-Trace-Id:
- 52b71104-6dba-4cc3-94c2-958d19d63c94
method: POST
uri: https://api-inference.huggingface.co/models/alefiury/wav2vec2-large-xlsr-53-gender-recognition-librispeech
uri: https://router.huggingface.co/hf-inference/models/alefiury/wav2vec2-large-xlsr-53-gender-recognition-librispeech
response:
body:
string: '[{"score":0.9987353682518005,"label":"male"},{"score":0.0012646110262721777,"label":"female"}]'
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1668,7 +1668,7 @@ interactions:
X-Amzn-Trace-Id:
- bd900989-e43c-405a-8bac-ad7c4c26fcd2
method: POST
uri: https://api-inference.huggingface.co/models/jonatasgrosman/wav2vec2-large-xlsr-53-english
uri: https://router.huggingface.co/hf-inference/models/jonatasgrosman/wav2vec2-large-xlsr-53-english
response:
body:
string: '{"text":"a man said to the universe sir i exist"}'
Expand Down Expand Up @@ -3365,7 +3365,7 @@ interactions:
X-Amzn-Trace-Id:
- 0c1c2537-940b-4dec-b991-817f36c89848
method: POST
uri: https://api-inference.huggingface.co/models/jonatasgrosman/wav2vec2-large-xlsr-53-english
uri: https://router.huggingface.co/hf-inference/models/jonatasgrosman/wav2vec2-large-xlsr-53-english
response:
body:
string: '{"text":"a man said to the universe sir i exist"}'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interactions:
X-Amzn-Trace-Id:
- e8ee0418-6fed-4215-b10a-fe79f5df03fb
method: POST
uri: https://api-inference.huggingface.co/models/meta-llama/Llama-3.1-8B-Instruct/v1/chat/completions
uri: https://router.huggingface.co/hf-inference/models/meta-llama/Llama-3.1-8B-Instruct/v1/chat/completions
response:
body:
string: '{"object":"chat.completion","id":"","created":1737555075,"model":"meta-llama/Llama-3.1-8B-Instruct","system_fingerprint":"3.0.1-sha-bb9095a","choices":[{"index":0,"message":{"role":"assistant","content":"Deep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interactions:
X-Amzn-Trace-Id:
- ad258f7a-bcc6-4b6b-a218-e7f2957d9e36
method: POST
uri: https://api-inference.huggingface.co/models/meta-llama/Meta-Llama-3-70B-Instruct/v1/chat/completions
uri: https://router.huggingface.co/hf-inference/models/meta-llama/Meta-Llama-3-70B-Instruct/v1/chat/completions
response:
body:
string: 'Failed to deserialize the JSON body into the target type: messages:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,69 @@
interactions:
- request:
body: '{"model": "microsoft/DialoGPT-small", "messages": [{"role": "system", "content":
"You are a helpful assistant."}, {"role": "user", "content": "What is deep learning?"}],
"max_tokens": 20, "stream": false}'
body: '{"model": "microsoft/DialoGPT-small", "max_tokens": 20, "stream": false,
"messages": [{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is deep learning?"}]}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
- gzip, deflate, br
Connection:
- keep-alive
Content-Length:
- '204'
Content-Type:
- application/json
X-Amzn-Trace-Id:
- 63bdefbc-ab2f-4db6-bc2b-31a34a32b60f
- 81d3ad41-5c57-4790-8e87-53c24ab9ef47
method: POST
uri: https://api-inference.huggingface.co/models/microsoft/DialoGPT-small/v1/chat/completions
uri: https://router.huggingface.co/hf-inference/models/microsoft/DialoGPT-small/v1/chat/completions
response:
body:
string: '{"object":"chat.completion","id":"","created":1737553735,"model":"microsoft/DialoGPT-small","system_fingerprint":"3.0.1-sha-bb9095a","choices":[{"index":0,"message":{"role":"assistant","content":"Deep
learning. Years and years of learns or old fall out of the sky. All that that
time"},"logprobs":null,"finish_reason":"length"}],"usage":{"prompt_tokens":13,"completion_tokens":20,"total_tokens":33}}'
string: '{"object":"chat.completion","id":"","created":1738149493,"model":"microsoft/DialoGPT-small","system_fingerprint":"3.0.1-sha-bb9095a","choices":[{"index":0,"message":{"role":"assistant","content":"What
is this deep learning?"},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":13,"completion_tokens":7,"total_tokens":20}}'
headers:
Access-Control-Allow-Origin:
- https://huggingface.co
Access-Control-Expose-Headers:
- X-Repo-Commit,X-Request-Id,X-Error-Code,X-Error-Message,X-Total-Count,ETag,Link,Accept-Ranges,Content-Range,X-Xet-Access-Token,X-Xet-Token-Expiration,X-Xet-Refresh-Route,X-Xet-Cas-Url,X-Xet-Hash
Connection:
- keep-alive
Content-Length:
- '399'
- '336'
Content-Type:
- application/json
Date:
- Wed, 22 Jan 2025 14:11:17 GMT
- Wed, 29 Jan 2025 11:21:41 GMT
Referrer-Policy:
- strict-origin-when-cross-origin
Via:
- 1.1 5e061de75f6666d04b790352c63b08c0.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- 4i8MGmtyqTmQaRvAad63AzdyBTzjKxUelXnRpcefIC7uUL46LDqr_Q==
X-Amz-Cf-Pop:
- MRS52-P4
X-Cache:
- Miss from cloudfront
X-Powered-By:
- huggingface-moon
access-control-allow-credentials:
- 'true'
cross-origin-opener-policy:
- same-origin
vary:
- Origin, Access-Control-Request-Method, Access-Control-Request-Headers
- Origin, Access-Control-Request-Method, Access-Control-Request-Headers, origin,
access-control-request-method, access-control-request-headers
x-compute-time:
- '1.172018712'
- '0.202893681'
x-compute-type:
- cache
x-proxied-host:
- internal.api-inference.huggingface.co
x-proxied-path:
- /v1/chat/completions
x-request-id:
- TPKAHfV8X8pScrvDU1EOD
- p6PYU3
x-sha:
- 49c537161a457d5256512f9d2d38a87d81ae0f0e
status:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interactions:
X-Amzn-Trace-Id:
- d0f30279-f376-4077-b6e0-c410661701fb
method: POST
uri: https://api-inference.huggingface.co/models/meta-llama/Llama-3.1-8B-Instruct/v1/chat/completions
uri: https://router.huggingface.co/hf-inference/models/meta-llama/Llama-3.1-8B-Instruct/v1/chat/completions
response:
body:
string: 'data: {"object":"chat.completion.chunk","id":"","created":1737555076,"model":"meta-llama/Llama-3.1-8B-Instruct","system_fingerprint":"3.0.1-sha-bb9095a","choices":[{"index":0,"delta":{"role":"assistant","content":"Deep"},"logprobs":null,"finish_reason":null}],"usage":null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interactions:
X-Amzn-Trace-Id:
- 95d72510-afc2-4df3-ba03-11e660f87bb6
method: POST
uri: https://api-inference.huggingface.co/models/naver-clova-ix/donut-base-finetuned-docvqa
uri: https://router.huggingface.co/hf-inference/models/naver-clova-ix/donut-base-finetuned-docvqa
response:
body:
string: '{"error":"Model naver-clova-ix/donut-base-finetuned-docvqa is currently
Expand Down Expand Up @@ -58,7 +58,7 @@ interactions:
X-wait-for-model:
- '1'
method: POST
uri: https://api-inference.huggingface.co/models/naver-clova-ix/donut-base-finetuned-docvqa
uri: https://router.huggingface.co/hf-inference/models/naver-clova-ix/donut-base-finetuned-docvqa
response:
body:
string: '[{"answer":"$1,0000,000,00"}]'
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -3489,7 +3489,7 @@ interactions:
X-Amzn-Trace-Id:
- e243d335-d22c-4764-acc3-b87ac1361be8
method: POST
uri: https://api-inference.huggingface.co/models/google/vit-base-patch16-224
uri: https://router.huggingface.co/hf-inference/models/google/vit-base-patch16-224
response:
body:
string: '[{"label":"brassiere, bra, bandeau","score":0.11767438799142838},{"label":"sombrero","score":0.09572819620370865},{"label":"cowboy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interactions:
X-Amzn-Trace-Id:
- 2e3fe32c-e63c-43e4-87aa-19fb09c8fa0c
method: POST
uri: https://api-inference.huggingface.co/models/facebook/detr-resnet-50-panoptic
uri: https://router.huggingface.co/hf-inference/models/facebook/detr-resnet-50-panoptic
response:
body:
string: '{"error":"Model facebook/detr-resnet-50-panoptic is currently loading","estimated_time":20.0}'
Expand Down Expand Up @@ -57,7 +57,7 @@ interactions:
X-wait-for-model:
- '1'
method: POST
uri: https://api-inference.huggingface.co/models/facebook/detr-resnet-50-panoptic
uri: https://router.huggingface.co/hf-inference/models/facebook/detr-resnet-50-panoptic
response:
body:
string: '[{"score":0.915131,"label":"LABEL_199","mask":"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"},{"score":0.993846,"label":"person","mask":"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"}]'
Expand Down
Loading

0 comments on commit a70b69d

Please sign in to comment.