mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
fix(proxy): run prompt caching counts and custom tokenizer loads off the event loop
This commit is contained in:
parent
83d500bac9
commit
d202885f8b
7 changed files with 111 additions and 18 deletions
|
|
@ -12713,7 +12713,9 @@ async def token_counter(request: TokenCountRequest, call_endpoint: bool = False)
|
|||
CustomHuggingfaceTokenizer | None,
|
||||
model_info.get("custom_tokenizer", None),
|
||||
)
|
||||
_tokenizer_used: Final = litellm.utils._select_tokenizer(model=model_to_use, custom_tokenizer=custom_tokenizer)
|
||||
_tokenizer_used: Final = await asyncify(litellm.utils._select_tokenizer)(
|
||||
model=model_to_use, custom_tokenizer=custom_tokenizer
|
||||
)
|
||||
|
||||
tokenizer_used: Final = str(_tokenizer_used["type"])
|
||||
system_message: Final = _system_message(system)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from litellm.integrations.anthropic_cache_control_hook import (
|
|||
AnthropicCacheControlHook,
|
||||
)
|
||||
from litellm.integrations.custom_logger import CustomLogger, Span
|
||||
from litellm.litellm_core_utils.asyncify import asyncify
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
from litellm.types.utils import CallTypes, StandardLoggingPayload
|
||||
from litellm.utils import get_prompt_cache_min_tokens, is_prompt_caching_valid_prompt
|
||||
|
|
@ -61,7 +62,7 @@ class PromptCachingDeploymentCheck(CustomLogger):
|
|||
if request_kwargs is not None and request_kwargs.get("_target_order") is not None:
|
||||
return healthy_deployments
|
||||
|
||||
if messages is not None and is_prompt_caching_valid_prompt(
|
||||
if messages is not None and await asyncify(is_prompt_caching_valid_prompt)(
|
||||
messages=messages,
|
||||
model=model,
|
||||
min_token_count=_get_min_token_count_for_deployments(healthy_deployments),
|
||||
|
|
@ -139,7 +140,7 @@ class PromptCachingDeploymentCheck(CustomLogger):
|
|||
return
|
||||
|
||||
## PROMPT CACHING - cache model id, if prompt caching valid prompt + provider
|
||||
if is_prompt_caching_valid_prompt(
|
||||
if await asyncify(is_prompt_caching_valid_prompt)(
|
||||
model=model,
|
||||
messages=cast(list[AllMessageValues], messages),
|
||||
):
|
||||
|
|
|
|||
|
|
@ -2284,15 +2284,7 @@ def create_pretrained_tokenizer(identifier: str, revision="main", auth_token: st
|
|||
dict: A dictionary with the tokenizer and its type.
|
||||
"""
|
||||
|
||||
try:
|
||||
tokenizer = Tokenizer.from_pretrained(
|
||||
identifier,
|
||||
revision=revision,
|
||||
auth_token=auth_token,
|
||||
)
|
||||
except Exception as e:
|
||||
verbose_logger.error("Error creating pretrained tokenizer: %s. Defaulting to version without 'auth_token'.", e)
|
||||
tokenizer = Tokenizer.from_pretrained(identifier, revision=revision)
|
||||
tokenizer: Final = Tokenizer.from_pretrained(identifier, revision=revision, token=auth_token)
|
||||
return {"type": "huggingface_tokenizer", "tokenizer": tokenizer}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -23,9 +23,9 @@ from litellm.proxy.proxy_server import token_counter
|
|||
|
||||
def _fake_hf_tokenizer(num_tokens: int) -> MagicMock:
|
||||
encoding = MagicMock()
|
||||
encoding.ids = list(range(num_tokens))
|
||||
encoding.__len__.return_value = num_tokens
|
||||
tokenizer = MagicMock()
|
||||
tokenizer.encode.return_value = encoding
|
||||
tokenizer.encode_batch_fast.return_value = [encoding]
|
||||
return tokenizer
|
||||
|
||||
|
||||
|
|
@ -68,13 +68,11 @@ async def test_custom_tokenizer_from_model_info_is_used(monkeypatch):
|
|||
)
|
||||
)
|
||||
|
||||
mock_tokenizer_cls.from_pretrained.assert_called_once_with(
|
||||
"my-org/custom-tokenizer", revision="v2", auth_token=None
|
||||
)
|
||||
mock_tokenizer_cls.from_pretrained.assert_called_once_with("my-org/custom-tokenizer", revision="v2", token=None)
|
||||
assert response.tokenizer_type == "huggingface_tokenizer"
|
||||
assert response.request_model == "my-embedding-model"
|
||||
assert response.model_used == "self-hosted-embedder"
|
||||
assert response.total_tokens > 0
|
||||
assert response.total_tokens >= 7
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ async def timed_with_loop_lags(run: Callable[[], Awaitable[T]]) -> tuple[T, floa
|
|||
finished: Final = asyncio.Event()
|
||||
|
||||
async def timed() -> tuple[T, float]:
|
||||
await asyncio.sleep(0)
|
||||
started: Final = time.perf_counter()
|
||||
try:
|
||||
return await run(), time.perf_counter() - started
|
||||
|
|
|
|||
|
|
@ -12877,3 +12877,40 @@ async def test_token_counter_keeps_the_event_loop_free_during_a_huggingface_coun
|
|||
|
||||
assert response.total_tokens > 0
|
||||
assert_loop_stayed_free(took, lags)
|
||||
|
||||
|
||||
async def test_token_counter_loads_a_custom_tokenizer_off_the_event_loop(monkeypatch):
|
||||
from tokenizers import Tokenizer
|
||||
|
||||
from litellm import Router
|
||||
from tests.test_litellm.litellm_core_utils.event_loop_lag import assert_loop_stayed_free, timed_with_loop_lags
|
||||
|
||||
claude_tokenizer: Final = litellm.utils._select_tokenizer("claude-fable-5")["tokenizer"]
|
||||
|
||||
class SlowHubTokenizer:
|
||||
@staticmethod
|
||||
def from_pretrained(identifier: str, revision: str = "main", token: str | None = None) -> Tokenizer:
|
||||
time.sleep(0.3)
|
||||
return claude_tokenizer
|
||||
|
||||
monkeypatch.setattr(litellm.utils, "Tokenizer", SlowHubTokenizer)
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.proxy_server.llm_router",
|
||||
Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "self-hosted",
|
||||
"litellm_params": {"model": "openai/self-hosted-model", "api_base": "http://localhost:8080/v1"},
|
||||
"model_info": {"custom_tokenizer": {"identifier": "my-org/tokenizer", "revision": "main", "auth_token": None}},
|
||||
}
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
response, took, lags = await timed_with_loop_lags(
|
||||
lambda: proxy_server_module.token_counter(TokenCountRequest(model="self-hosted", prompt="count me off the loop"))
|
||||
)
|
||||
|
||||
assert response.tokenizer_type == "huggingface_tokenizer"
|
||||
assert response.total_tokens > 0
|
||||
assert_loop_stayed_free(took, lags)
|
||||
|
|
|
|||
|
|
@ -477,3 +477,65 @@ async def test_wildcard_route_resolves_underlying_model_minimum(local_model_cost
|
|||
|
||||
assert deployments[0]["litellm_params"]["model"] == "anthropic/claude-opus-4-6"
|
||||
assert _get_min_token_count_for_deployments(deployments) == 4096
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_filter_deployments_counts_the_prompt_off_the_event_loop():
|
||||
from tests.large_text import text
|
||||
from tests.test_litellm.litellm_core_utils.event_loop_lag import (
|
||||
assert_loop_stayed_free,
|
||||
timed_with_loop_lags,
|
||||
warm_tokenizer,
|
||||
)
|
||||
|
||||
warm_tokenizer("anthropic/claude-fable-5")
|
||||
check = PromptCachingDeploymentCheck(cache=DualCache())
|
||||
deployments = _deployments("anthropic/claude-fable-5")
|
||||
messages = cast(List[AllMessageValues], [{"role": "user", "content": text * 100}])
|
||||
|
||||
result, took, lags = await timed_with_loop_lags(
|
||||
lambda: check.async_filter_deployments(
|
||||
model=MODEL_GROUP_ALIAS, healthy_deployments=deployments, messages=messages
|
||||
)
|
||||
)
|
||||
|
||||
assert result == deployments
|
||||
assert_loop_stayed_free(took, lags)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_log_success_event_counts_the_prompt_off_the_event_loop():
|
||||
from tests.large_text import text
|
||||
from tests.test_litellm.litellm_core_utils.event_loop_lag import (
|
||||
assert_loop_stayed_free,
|
||||
timed_with_loop_lags,
|
||||
warm_tokenizer,
|
||||
)
|
||||
|
||||
warm_tokenizer("anthropic/claude-fable-5")
|
||||
cache = DualCache()
|
||||
check = PromptCachingDeploymentCheck(cache=cache)
|
||||
messages = cast(
|
||||
List[AllMessageValues],
|
||||
[{"role": "user", "content": [{"type": "text", "text": text * 100, "cache_control": {"type": "ephemeral"}}]}],
|
||||
)
|
||||
standard_logging_object = {
|
||||
"call_type": "acompletion",
|
||||
"model": "anthropic/claude-fable-5",
|
||||
"messages": messages,
|
||||
"model_id": "dep-1",
|
||||
}
|
||||
|
||||
_, took, lags = await timed_with_loop_lags(
|
||||
lambda: check.async_log_success_event(
|
||||
kwargs={"standard_logging_object": standard_logging_object},
|
||||
response_obj=None,
|
||||
start_time=None,
|
||||
end_time=None,
|
||||
)
|
||||
)
|
||||
|
||||
assert await PromptCachingCache(cache=cache).async_get_model_id(messages=messages, tools=None) == {
|
||||
"model_id": "dep-1"
|
||||
}
|
||||
assert_loop_stayed_free(took, lags)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue