Files
wehub-resource-sync 6b7e6b44f1
gh-pages / build (push) Waiting to run
Python Publish (pypi) / Upload release to PyPI (push) Waiting to run
Spellcheck / spellcheck (push) Waiting to run
Python Build and Type Check / python-ci (ubuntu-latest, 3.11) (push) Has been cancelled
Python Build and Type Check / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Build and Type Check / python-ci (windows-latest, 3.11) (push) Has been cancelled
Python Build and Type Check / python-ci (windows-latest, 3.13) (push) Has been cancelled
Python Integration Tests / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Integration Tests / python-ci (windows-latest, 3.13) (push) Has been cancelled
Python Notebook Tests / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Notebook Tests / python-ci (windows-latest, 3.13) (push) Has been cancelled
Python Smoke Tests / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Smoke Tests / python-ci (windows-latest, 3.13) (push) Has been cancelled
Python Unit Tests / python-ci (ubuntu-latest, 3.13) (push) Has been cancelled
Python Unit Tests / python-ci (windows-latest, 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:37:31 +08:00

350 lines
11 KiB
Python

# Copyright (c) 2024 Microsoft Corporation.
# Licensed under the MIT License
"""Test LiteLLM Rate Limiter."""
import threading
import time
from math import ceil
from queue import Queue
from graphrag_llm.config import RateLimitConfig, RateLimitType
from graphrag_llm.rate_limit import RateLimiter, create_rate_limiter
from tests.integration.language_model.utils import (
assert_max_num_values_per_period,
assert_stagger,
bin_time_intervals,
)
_period_in_seconds = 1
_rpm = 4
_tpm = 75
_tokens_per_request = 25
_stagger = _period_in_seconds / _rpm
_num_requests = 10
def test_binning():
"""Test binning timings into 1-second intervals."""
values = [0.1, 0.2, 0.3, 0.4, 1.1, 1.2, 1.3, 1.4, 5.1]
binned_values = bin_time_intervals(values, 1)
assert binned_values == [
[0.1, 0.2, 0.3, 0.4],
[1.1, 1.2, 1.3, 1.4],
[],
[],
[],
[5.1],
]
def test_rpm():
"""Test that the rate limiter enforces RPM limits."""
rate_limiter = create_rate_limiter(
RateLimitConfig(
type=RateLimitType.SlidingWindow,
period_in_seconds=_period_in_seconds,
requests_per_period=_rpm,
)
)
time_values: list[float] = []
start_time = time.time()
for _ in range(_num_requests):
with rate_limiter.acquire(token_count=_tokens_per_request):
time_values.append(time.time() - start_time)
assert len(time_values) == _num_requests
binned_time_values = bin_time_intervals(time_values, _period_in_seconds)
"""
With _num_requests = 10 and _rpm = 4, we expect the requests to be
distributed across ceil(10/4) = 3 bins:
with a stagger of 1/4 = 0.25 seconds between requests.
"""
expected_num_bins = ceil(_num_requests / _rpm)
assert len(binned_time_values) == expected_num_bins
assert_max_num_values_per_period(binned_time_values, _rpm)
assert_stagger(time_values, _stagger)
def test_tpm():
"""Test that the rate limiter enforces TPM limits."""
rate_limiter = create_rate_limiter(
RateLimitConfig(
type=RateLimitType.SlidingWindow,
period_in_seconds=_period_in_seconds,
tokens_per_period=_tpm,
)
)
time_values: list[float] = []
start_time = time.time()
for _ in range(_num_requests):
with rate_limiter.acquire(token_count=_tokens_per_request):
time_values.append(time.time() - start_time)
assert len(time_values) == _num_requests
binned_time_values = bin_time_intervals(time_values, _period_in_seconds)
"""
With _num_requests = 10, _tpm = 75 and _tokens_per_request = 25, we expect the requests to be
distributed across ceil( (10 * 25) / 75) ) = 4 bins
and max requests per bin = (75 / 25) = 3 requests per bin.
"""
expected_num_bins = ceil((_num_requests * _tokens_per_request) / _tpm)
assert len(binned_time_values) == expected_num_bins
max_num_of_requests_per_bin = _tpm // _tokens_per_request
assert_max_num_values_per_period(binned_time_values, max_num_of_requests_per_bin)
def test_token_in_request_exceeds_tpm():
"""Test that the rate limiter allows for requests that use more tokens than the TPM.
A rate limiter could be configured with a tpm of 1000 but a request may use 2000 tokens,
greater than the tpm limit but still below the context window limit of the underlying model.
In this case, the request should still be allowed to proceed but may take up its own rate limit bin.
"""
rate_limiter = create_rate_limiter(
RateLimitConfig(
type=RateLimitType.SlidingWindow,
period_in_seconds=_period_in_seconds,
tokens_per_period=_tpm,
)
)
time_values: list[float] = []
start_time = time.time()
for _ in range(2):
with rate_limiter.acquire(token_count=_tpm * 2):
time_values.append(time.time() - start_time)
assert len(time_values) == 2
binned_time_values = bin_time_intervals(time_values, _period_in_seconds)
"""
Since each request exceeds the tpm, we expect each request to still be fired off but to be in its own bin
"""
assert len(binned_time_values) == 2
assert_max_num_values_per_period(binned_time_values, 1)
def test_rpm_and_tpm_with_rpm_as_limiting_factor():
"""Test that the rate limiter enforces RPM and TPM limits."""
rate_limiter = create_rate_limiter(
RateLimitConfig(
type=RateLimitType.SlidingWindow,
period_in_seconds=_period_in_seconds,
requests_per_period=_rpm,
tokens_per_period=_tpm,
)
)
time_values: list[float] = []
start_time = time.time()
for _ in range(_num_requests):
# Use 0 tokens per request to simulate RPM as the limiting factor
with rate_limiter.acquire(token_count=0):
time_values.append(time.time() - start_time)
assert len(time_values) == _num_requests
binned_time_values = bin_time_intervals(time_values, _period_in_seconds)
"""
With _num_requests = 10 and _rpm = 4, we expect the requests to be
distributed across ceil(10/4) = 3 bins:
with a stagger of 1/4 = 0.25 seconds between requests.
"""
expected_num_bins = ceil(_num_requests / _rpm)
assert len(binned_time_values) == expected_num_bins
assert_max_num_values_per_period(binned_time_values, _rpm)
assert_stagger(time_values, _stagger)
def test_rpm_and_tpm_with_tpm_as_limiting_factor():
"""Test that the rate limiter enforces TPM limits."""
rate_limiter = create_rate_limiter(
RateLimitConfig(
type=RateLimitType.SlidingWindow,
period_in_seconds=_period_in_seconds,
requests_per_period=_rpm,
tokens_per_period=_tpm,
)
)
time_values: list[float] = []
start_time = time.time()
for _ in range(_num_requests):
with rate_limiter.acquire(token_count=_tokens_per_request):
time_values.append(time.time() - start_time)
assert len(time_values) == _num_requests
binned_time_values = bin_time_intervals(time_values, _period_in_seconds)
"""
With _num_requests = 10, _tpm = 75 and _tokens_per_request = 25, we expect the requests to be
distributed across ceil( (10 * 25) / 75) ) = 4 bins
and max requests per bin = (75 / 25) = 3 requests per bin.
"""
expected_num_bins = ceil((_num_requests * _tokens_per_request) / _tpm)
assert len(binned_time_values) == expected_num_bins
max_num_of_requests_per_bin = _tpm // _tokens_per_request
assert_max_num_values_per_period(binned_time_values, max_num_of_requests_per_bin)
assert_stagger(time_values, _stagger)
def _run_rate_limiter(
rate_limiter: RateLimiter,
# Acquire cost
input_queue: Queue[int | None],
# time value
output_queue: Queue[float | None],
):
while True:
token_count = input_queue.get()
if token_count is None:
break
with rate_limiter.acquire(token_count=token_count):
output_queue.put(time.time())
def test_rpm_threaded():
"""Test that the rate limiter enforces RPM limits in a threaded environment."""
rate_limiter = create_rate_limiter(
RateLimitConfig(
type=RateLimitType.SlidingWindow,
period_in_seconds=_period_in_seconds,
requests_per_period=_rpm,
tokens_per_period=_tpm,
)
)
input_queue: Queue[int | None] = Queue()
output_queue: Queue[float | None] = Queue()
# Spin up threads for half the number of requests
threads = [
threading.Thread(
target=_run_rate_limiter,
args=(rate_limiter, input_queue, output_queue),
)
for _ in range(_num_requests // 2) # Create 5 threads
]
for thread in threads:
thread.start()
start_time = time.time()
for _ in range(_num_requests):
# Use 0 tokens per request to simulate RPM as the limiting factor
input_queue.put(0)
# Signal threads to stop
for _ in range(len(threads)):
input_queue.put(None)
for thread in threads:
thread.join()
output_queue.put(None) # Signal end of output
time_values = []
while True:
time_value = output_queue.get()
if time_value is None:
break
time_values.append(time_value - start_time)
time_values.sort()
assert len(time_values) == _num_requests
binned_time_values = bin_time_intervals(time_values, _period_in_seconds)
"""
With _num_requests = 10 and _rpm = 4, we expect the requests to be
distributed across ceil(10/4) = 3 bins:
with a stagger of 1/4 = 0.25 seconds between requests.
"""
expected_num_bins = ceil(_num_requests / _rpm)
assert len(binned_time_values) == expected_num_bins
assert_max_num_values_per_period(binned_time_values, _rpm)
assert_stagger(time_values, _stagger)
def test_tpm_threaded():
"""Test that the rate limiter enforces TPM limits in a threaded environment."""
rate_limiter = create_rate_limiter(
RateLimitConfig(
type=RateLimitType.SlidingWindow,
period_in_seconds=_period_in_seconds,
requests_per_period=_rpm,
tokens_per_period=_tpm,
)
)
input_queue: Queue[int | None] = Queue()
output_queue: Queue[float | None] = Queue()
# Spin up threads for half the number of requests
threads = [
threading.Thread(
target=_run_rate_limiter,
args=(rate_limiter, input_queue, output_queue),
)
for _ in range(_num_requests // 2) # Create 5 threads
]
for thread in threads:
thread.start()
start_time = time.time()
for _ in range(_num_requests):
input_queue.put(_tokens_per_request)
# Signal threads to stop
for _ in range(len(threads)):
input_queue.put(None)
for thread in threads:
thread.join()
output_queue.put(None) # Signal end of output
time_values = []
while True:
time_value = output_queue.get()
if time_value is None:
break
time_values.append(time_value - start_time)
time_values.sort()
assert len(time_values) == _num_requests
binned_time_values = bin_time_intervals(time_values, _period_in_seconds)
"""
With _num_requests = 10, _tpm = 75 and _tokens_per_request = 25, we expect the requests to be
distributed across ceil( (10 * 25) / 75) ) = 4 bins
and max requests per bin = (75 / 25) = 3 requests per bin.
"""
expected_num_bins = ceil((_num_requests * _tokens_per_request) / _tpm)
assert len(binned_time_values) == expected_num_bins
max_num_of_requests_per_bin = _tpm // _tokens_per_request
assert_max_num_values_per_period(binned_time_values, max_num_of_requests_per_bin)
assert_stagger(time_values, _stagger)