Repository navigation
fix(token_counter): release the GIL for HuggingFace counts and cap exact counting per string #40186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
mateo-berri
merged 12 commits into
litellm_internal_staging
from
litellm_lit_5546_count_tokens_offload
Sep 10, 2026
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
170fece
fix(token_counter): release the GIL for HuggingFace counts and cap ex…
mateo-berri 486af36
Merge remote-tracking branch 'origin/litellm_internal_staging' into l…
mateo-berri 3829ebd
fix(token_counter): estimate over-cap strings from evenly spaced samp…
mateo-berri 0d89873
test(token_counter): type the parametrized cap test arguments
mateo-berri dcd38ab
fix(proxy): count failure and rate-limit input tokens off the event loop
mateo-berri 83d500b
fix(router): count deployment itpm reservations off the event loop
mateo-berri d202885
fix(proxy): run prompt caching counts and custom tokenizer loads off …
mateo-berri c6a0381
fix(token_counter): bound concurrent HuggingFace encodes so a burst o…
mateo-berri b5d40be
fix(token_counter): bound offloaded token counts with a dedicated cap…
mateo-berri babf18f
test(token_counter): type the project ITPM lag test's request body
mateo-berri 47eb7f4
fix(token_counter): give each event loop its own count limiter
mateo-berri c1ca963
Merge remote-tracking branch 'origin/litellm_internal_staging' into l…
mateo-berri File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import asyncio | ||
| import time | ||
| from collections.abc import Awaitable, Callable | ||
| from typing import Final, TypeVar | ||
|
|
||
| import litellm | ||
|
|
||
| T = TypeVar("T") | ||
|
|
||
|
|
||
| def warm_tokenizer(model: str) -> None: | ||
| litellm.token_counter(model=model, text="load the tokenizer before anything is timed") | ||
|
|
||
|
|
||
| async def loop_wake_lags(until: asyncio.Event) -> tuple[float, ...]: | ||
| async def wake_lag() -> float: | ||
| started: Final = time.perf_counter() | ||
| await asyncio.sleep(0.001) | ||
| return time.perf_counter() - started - 0.001 | ||
|
|
||
| return tuple([await wake_lag() for _ in iter(until.is_set, True)]) | ||
|
|
||
|
|
||
| async def timed_with_loop_lags(run: Callable[[], Awaitable[T]]) -> tuple[T, float, tuple[float, ...]]: | ||
| 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 | ||
| finally: | ||
| finished.set() | ||
|
|
||
| (result, took), lags = await asyncio.gather(timed(), loop_wake_lags(finished)) | ||
| return result, took, lags | ||
|
|
||
|
|
||
| def assert_loop_stayed_free(took: float, lags: tuple[float, ...]) -> None: | ||
| assert max(lags) < took / 4, f"the event loop stalled {max(lags):.3f}s during a {took:.3f}s count" |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Synchronous HuggingFace token-counter callers now bypass concurrency control, so concurrent SDK counts can again multiply tokenizer memory usage
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tiktoken, the default path, already releases the GIL and runs uncapped from SDK threads; proxy counts are bounded by the limiter and per-string sampling
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You’re right—I conflated the synchronous SDK API with the proxy paths this PR is changing. The proxy call sites use
offload_token_count, so HuggingFace counts are bounded by the per-worker limiter; the default synchronous tiktoken path is already GIL-free and intentionally remains uncapped for SDK callers. I’m withdrawing this as a blocking finding.