fix: remove router inefficiencies (from O(M*N) to O(1)) - 62.5% faster P99 latency (#15046)
* fix: remove redundant deep copy set_model_list already does the deep copy at the beginning of the call. * fix: remove unused model_list arguments The `model_list` parameter was being passed to classes that did not use it. * fix: reduce per-request memory and time from O(N×M) to O(N) No need to create a whole array for a simple look up. * add: missing test * fix: remove unused parameter
This commit is contained in:
parent
e0172b86e2
commit
d4830e34e5
@ -120,7 +120,7 @@ async def anthropic_response( # noqa: PLR0915
|
||||
): # model in router deployments, calling a specific deployment on the router
|
||||
llm_coro = llm_router.aanthropic_messages(**data, specific_deployment=True)
|
||||
elif (
|
||||
llm_router is not None and data["model"] in llm_router.get_model_ids()
|
||||
llm_router is not None and llm_router.has_model_id(data["model"])
|
||||
): # model in router model list
|
||||
llm_coro = llm_router.aanthropic_messages(**data)
|
||||
elif (
|
||||
|
||||
@ -215,7 +215,7 @@ async def chat_completion_pass_through_endpoint( # noqa: PLR0915
|
||||
llm_router.aadapter_completion(**data, specific_deployment=True)
|
||||
)
|
||||
elif (
|
||||
llm_router is not None and data["model"] in llm_router.get_model_ids()
|
||||
llm_router is not None and llm_router.has_model_id(data["model"])
|
||||
): # model in router model list
|
||||
llm_response = asyncio.create_task(llm_router.aadapter_completion(**data))
|
||||
elif (
|
||||
|
||||
@ -130,7 +130,7 @@ async def route_request(
|
||||
|
||||
elif (
|
||||
data["model"] in router_model_names
|
||||
or data["model"] in llm_router.get_model_ids()
|
||||
or llm_router.has_model_id(data["model"])
|
||||
):
|
||||
return getattr(llm_router, f"{route_type}")(**data)
|
||||
|
||||
|
||||
@ -415,7 +415,6 @@ class Router:
|
||||
if model_list is not None:
|
||||
# Build model index immediately to enable O(1) lookups from the start
|
||||
self._build_model_id_to_deployment_index_map(model_list)
|
||||
model_list = copy.deepcopy(model_list)
|
||||
self.set_model_list(model_list)
|
||||
self.healthy_deployments: List = self.model_list # type: ignore
|
||||
for m in model_list:
|
||||
@ -700,7 +699,7 @@ class Router:
|
||||
or routing_strategy == RoutingStrategy.LEAST_BUSY
|
||||
):
|
||||
self.leastbusy_logger = LeastBusyLoggingHandler(
|
||||
router_cache=self.cache, model_list=self.model_list
|
||||
router_cache=self.cache
|
||||
)
|
||||
## add callback
|
||||
if isinstance(litellm.input_callback, list):
|
||||
@ -715,7 +714,6 @@ class Router:
|
||||
):
|
||||
self.lowesttpm_logger = LowestTPMLoggingHandler(
|
||||
router_cache=self.cache,
|
||||
model_list=self.model_list,
|
||||
routing_args=routing_strategy_args,
|
||||
)
|
||||
if isinstance(litellm.callbacks, list):
|
||||
@ -726,7 +724,6 @@ class Router:
|
||||
):
|
||||
self.lowesttpm_logger_v2 = LowestTPMLoggingHandler_v2(
|
||||
router_cache=self.cache,
|
||||
model_list=self.model_list,
|
||||
routing_args=routing_strategy_args,
|
||||
)
|
||||
if isinstance(litellm.callbacks, list):
|
||||
@ -737,7 +734,6 @@ class Router:
|
||||
):
|
||||
self.lowestlatency_logger = LowestLatencyLoggingHandler(
|
||||
router_cache=self.cache,
|
||||
model_list=self.model_list,
|
||||
routing_args=routing_strategy_args,
|
||||
)
|
||||
if isinstance(litellm.callbacks, list):
|
||||
@ -748,7 +744,6 @@ class Router:
|
||||
):
|
||||
self.lowestcost_logger = LowestCostLoggingHandler(
|
||||
router_cache=self.cache,
|
||||
model_list=self.model_list,
|
||||
routing_args={},
|
||||
)
|
||||
if isinstance(litellm.callbacks, list):
|
||||
@ -972,7 +967,7 @@ class Router:
|
||||
|
||||
### DEPLOYMENT-SPECIFIC PRE-CALL CHECKS ### (e.g. update rpm pre-call. Raise error, if deployment over limit)
|
||||
## only run if model group given, not model id
|
||||
if model not in self.get_model_ids():
|
||||
if not self.has_model_id(model):
|
||||
self.routing_strategy_pre_call_checks(deployment=deployment)
|
||||
|
||||
response = litellm.completion(
|
||||
@ -5331,7 +5326,8 @@ class Router:
|
||||
"""
|
||||
# check if deployment already exists
|
||||
|
||||
if deployment.model_info.id in self.get_model_ids():
|
||||
_deployment_model_id = deployment.model_info.id
|
||||
if _deployment_model_id and self.has_model_id(_deployment_model_id):
|
||||
return None
|
||||
|
||||
# add to model list
|
||||
@ -6113,7 +6109,7 @@ class Router:
|
||||
if 'model_name' is none, returns all.
|
||||
|
||||
Returns list of model id's.
|
||||
"""
|
||||
"""
|
||||
ids = []
|
||||
for model in self.model_list:
|
||||
if "model_info" in model and "id" in model["model_info"]:
|
||||
@ -6126,6 +6122,19 @@ class Router:
|
||||
ids.append(id)
|
||||
return ids
|
||||
|
||||
def has_model_id(self, candidate_id: str) -> bool:
|
||||
"""
|
||||
O(1) membership check for a deployment ID without allocating large lists.
|
||||
|
||||
Note: Call sites may pass a variable named `model` when it actually
|
||||
contains a deployment ID. This helper expects the deployment ID string.
|
||||
|
||||
Uses the existing `model_id_to_deployment_index_map` which is kept
|
||||
in sync by `_build_model_id_to_deployment_index_map` and model-list
|
||||
mutation helpers.
|
||||
"""
|
||||
return candidate_id in self.model_id_to_deployment_index_map
|
||||
|
||||
def map_team_model(self, team_model_name: str, team_id: str) -> Optional[str]:
|
||||
"""
|
||||
Map a team model name to a team-specific model name.
|
||||
@ -6762,14 +6771,13 @@ class Router:
|
||||
# check if aliases set on litellm model alias map
|
||||
if specific_deployment is True:
|
||||
return model, self._get_deployment_by_litellm_model(model=model)
|
||||
elif model in self.get_model_ids():
|
||||
elif self.has_model_id(model):
|
||||
deployment = self.get_deployment(model_id=model)
|
||||
if deployment is not None:
|
||||
deployment_model = deployment.litellm_params.model
|
||||
return deployment_model, deployment.model_dump(exclude_none=True)
|
||||
raise ValueError(
|
||||
f"LiteLLM Router: Trying to call specific deployment, but Model ID :{model} does not exist in \
|
||||
Model ID List: {self.get_model_ids}"
|
||||
f"LiteLLM Router: Trying to call specific deployment, but Model ID :{model} does not exist in Model ID map"
|
||||
)
|
||||
|
||||
_model_from_alias = self._get_model_from_alias(model=model)
|
||||
|
||||
@ -18,10 +18,9 @@ class LeastBusyLoggingHandler(CustomLogger):
|
||||
logged_success: int = 0
|
||||
logged_failure: int = 0
|
||||
|
||||
def __init__(self, router_cache: DualCache, model_list: list):
|
||||
def __init__(self, router_cache: DualCache):
|
||||
self.router_cache = router_cache
|
||||
self.mapping_deployment_to_id: dict = {}
|
||||
self.model_list = model_list
|
||||
|
||||
|
||||
def log_pre_api_call(self, model, messages, kwargs):
|
||||
"""
|
||||
|
||||
@ -16,10 +16,9 @@ class LowestCostLoggingHandler(CustomLogger):
|
||||
logged_failure: int = 0
|
||||
|
||||
def __init__(
|
||||
self, router_cache: DualCache, model_list: list, routing_args: dict = {}
|
||||
self, router_cache: DualCache, routing_args: dict = {}
|
||||
):
|
||||
self.router_cache = router_cache
|
||||
self.model_list = model_list
|
||||
|
||||
def log_success_event(self, kwargs, response_obj, start_time, end_time):
|
||||
try:
|
||||
|
||||
@ -32,10 +32,9 @@ class LowestLatencyLoggingHandler(CustomLogger):
|
||||
logged_failure: int = 0
|
||||
|
||||
def __init__(
|
||||
self, router_cache: DualCache, model_list: list, routing_args: dict = {}
|
||||
self, router_cache: DualCache, routing_args: dict = {}
|
||||
):
|
||||
self.router_cache = router_cache
|
||||
self.model_list = model_list
|
||||
self.routing_args = RoutingArgs(**routing_args)
|
||||
|
||||
def log_success_event( # noqa: PLR0915
|
||||
|
||||
@ -23,10 +23,9 @@ class LowestTPMLoggingHandler(CustomLogger):
|
||||
default_cache_time_seconds: int = 1 * 60 * 60 # 1 hour
|
||||
|
||||
def __init__(
|
||||
self, router_cache: DualCache, model_list: list, routing_args: dict = {}
|
||||
self, router_cache: DualCache, routing_args: dict = {}
|
||||
):
|
||||
self.router_cache = router_cache
|
||||
self.model_list = model_list
|
||||
self.routing_args = RoutingArgs(**routing_args)
|
||||
|
||||
def log_success_event(self, kwargs, response_obj, start_time, end_time):
|
||||
|
||||
@ -48,10 +48,9 @@ class LowestTPMLoggingHandler_v2(BaseRoutingStrategy, CustomLogger):
|
||||
default_cache_time_seconds: int = 1 * 60 * 60 # 1 hour
|
||||
|
||||
def __init__(
|
||||
self, router_cache: DualCache, model_list: list, routing_args: dict = {}
|
||||
self, router_cache: DualCache, routing_args: dict = {}
|
||||
):
|
||||
self.router_cache = router_cache
|
||||
self.model_list = model_list
|
||||
self.routing_args = RoutingArgs(**routing_args)
|
||||
BaseRoutingStrategy.__init__(
|
||||
self,
|
||||
|
||||
@ -28,7 +28,7 @@ from litellm.router_strategy.least_busy import LeastBusyLoggingHandler
|
||||
|
||||
def test_model_added():
|
||||
test_cache = DualCache()
|
||||
least_busy_logger = LeastBusyLoggingHandler(router_cache=test_cache, model_list=[])
|
||||
least_busy_logger = LeastBusyLoggingHandler(router_cache=test_cache)
|
||||
kwargs = {
|
||||
"litellm_params": {
|
||||
"metadata": {
|
||||
@ -45,7 +45,7 @@ def test_model_added():
|
||||
|
||||
def test_get_available_deployments():
|
||||
test_cache = DualCache()
|
||||
least_busy_logger = LeastBusyLoggingHandler(router_cache=test_cache, model_list=[])
|
||||
least_busy_logger = LeastBusyLoggingHandler(router_cache=test_cache)
|
||||
model_group = "gpt-3.5-turbo"
|
||||
deployment = "azure/gpt-4.1-nano"
|
||||
kwargs = {
|
||||
|
||||
@ -36,7 +36,7 @@ async def test_get_available_deployments():
|
||||
},
|
||||
]
|
||||
lowest_cost_logger = LowestCostLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache,
|
||||
)
|
||||
model_group = "gpt-3.5-turbo"
|
||||
|
||||
@ -86,7 +86,7 @@ async def test_get_available_deployments_custom_price():
|
||||
},
|
||||
]
|
||||
lowest_cost_logger = LowestCostLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache,
|
||||
)
|
||||
model_group = "gpt-3.5-turbo"
|
||||
|
||||
@ -187,7 +187,7 @@ async def test_get_available_endpoints_tpm_rpm_check_async(ans_rpm):
|
||||
},
|
||||
]
|
||||
lowest_cost_logger = LowestCostLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache
|
||||
)
|
||||
model_group = "gpt-3.5-turbo"
|
||||
d1 = [(lowest_cost_logger, "1234", 50, 0.01)] * non_ans_rpm
|
||||
|
||||
@ -38,9 +38,8 @@ async def test_latency_memory_leak(sync_mode):
|
||||
- make 11th call -> no change in memory
|
||||
"""
|
||||
test_cache = DualCache()
|
||||
model_list = []
|
||||
lowest_latency_logger = LowestLatencyLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache
|
||||
)
|
||||
model_group = "gpt-3.5-turbo"
|
||||
deployment_id = "1234"
|
||||
@ -120,9 +119,8 @@ def get_size(obj, seen=None):
|
||||
|
||||
def test_latency_updated():
|
||||
test_cache = DualCache()
|
||||
model_list = []
|
||||
lowest_latency_logger = LowestLatencyLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache
|
||||
)
|
||||
model_group = "gpt-3.5-turbo"
|
||||
deployment_id = "1234"
|
||||
@ -165,7 +163,7 @@ def test_latency_updated_custom_ttl():
|
||||
model_list = []
|
||||
cache_time = 3
|
||||
lowest_latency_logger = LowestLatencyLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list, routing_args={"ttl": cache_time}
|
||||
router_cache=test_cache, routing_args={"ttl": cache_time}
|
||||
)
|
||||
model_group = "gpt-3.5-turbo"
|
||||
deployment_id = "1234"
|
||||
@ -210,7 +208,7 @@ def test_get_available_deployments():
|
||||
},
|
||||
]
|
||||
lowest_latency_logger = LowestLatencyLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache
|
||||
)
|
||||
model_group = "gpt-3.5-turbo"
|
||||
## DEPLOYMENT 1 ##
|
||||
@ -327,7 +325,7 @@ def test_get_available_endpoints_tpm_rpm_check_async(ans_rpm):
|
||||
},
|
||||
]
|
||||
lowest_latency_logger = LowestLatencyLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache
|
||||
)
|
||||
model_group = "gpt-3.5-turbo"
|
||||
d1 = [(lowest_latency_logger, "1234", 50, 0.01)] * non_ans_rpm
|
||||
@ -376,7 +374,7 @@ def test_get_available_endpoints_tpm_rpm_check(ans_rpm):
|
||||
},
|
||||
]
|
||||
lowest_latency_logger = LowestLatencyLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache
|
||||
)
|
||||
model_group = "gpt-3.5-turbo"
|
||||
## DEPLOYMENT 1 ##
|
||||
|
||||
@ -39,9 +39,8 @@ from create_mock_standard_logging_payload import create_standard_logging_payload
|
||||
|
||||
def test_tpm_rpm_updated():
|
||||
test_cache = DualCache()
|
||||
model_list = []
|
||||
lowest_tpm_logger = LowestTPMLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache
|
||||
)
|
||||
model_group = "gpt-3.5-turbo"
|
||||
deployment_id = "1234"
|
||||
@ -110,7 +109,7 @@ def test_get_available_deployments():
|
||||
},
|
||||
]
|
||||
lowest_tpm_logger = LowestTPMLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache
|
||||
)
|
||||
model_group = "gpt-3.5-turbo"
|
||||
## DEPLOYMENT 1 ##
|
||||
@ -668,12 +667,10 @@ def test_return_potential_deployments():
|
||||
"""
|
||||
Assert deployment at limit is filtered out
|
||||
"""
|
||||
from litellm.router_strategy.lowest_tpm_rpm_v2 import LowestTPMLoggingHandler_v2
|
||||
|
||||
test_cache = DualCache()
|
||||
model_list = []
|
||||
lowest_tpm_logger = LowestTPMLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache
|
||||
)
|
||||
|
||||
args: Dict = {
|
||||
|
||||
@ -103,3 +103,27 @@ class TestRouterIndexManagement:
|
||||
assert router.model_id_to_deployment_index_map["id-1"] == 0
|
||||
assert router.model_id_to_deployment_index_map["id-2"] == 1
|
||||
assert router.model_id_to_deployment_index_map["id-3"] == 2
|
||||
|
||||
def test_has_model_id(self, router):
|
||||
"""Test has_model_id function for O(1) membership check"""
|
||||
# Setup: Add models to router
|
||||
router.model_list = [
|
||||
{"model": "test1", "model_info": {"id": "model-1"}},
|
||||
{"model": "test2", "model_info": {"id": "model-2"}},
|
||||
{"model": "test3", "model_info": {"id": "model-3"}}
|
||||
]
|
||||
router.model_id_to_deployment_index_map = {"model-1": 0, "model-2": 1, "model-3": 2}
|
||||
|
||||
# Test: Check existing model IDs
|
||||
assert router.has_model_id("model-1") == True
|
||||
assert router.has_model_id("model-2") == True
|
||||
assert router.has_model_id("model-3") == True
|
||||
|
||||
# Test: Check non-existing model IDs
|
||||
assert router.has_model_id("non-existent") == False
|
||||
assert router.has_model_id("") == False
|
||||
assert router.has_model_id("model-4") == False
|
||||
|
||||
# Test: Empty router
|
||||
empty_router = Router(model_list=[])
|
||||
assert empty_router.has_model_id("any-id") == False
|
||||
|
||||
@ -23,16 +23,9 @@ def test_zero_completion_tokens_no_division_error():
|
||||
(e.g., from Gemini with long contexts) caused ZeroDivisionError
|
||||
"""
|
||||
test_cache = DualCache()
|
||||
model_list = [
|
||||
{
|
||||
"model_name": "gemini-2.5-flash",
|
||||
"litellm_params": {"model": "gemini/gemini-2.5-flash"},
|
||||
"model_info": {"id": "1234"},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
lowest_latency_logger = LowestLatencyLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache
|
||||
)
|
||||
|
||||
deployment_id = "1234"
|
||||
@ -98,16 +91,9 @@ def test_zero_completion_tokens_with_time_to_first_token():
|
||||
Test that time_to_first_token calculation also handles zero completion tokens
|
||||
"""
|
||||
test_cache = DualCache()
|
||||
model_list = [
|
||||
{
|
||||
"model_name": "gemini-2.5-flash",
|
||||
"litellm_params": {"model": "gemini/gemini-2.5-flash"},
|
||||
"model_info": {"id": "1234"},
|
||||
}
|
||||
]
|
||||
|
||||
lowest_latency_logger = LowestLatencyLoggingHandler(
|
||||
router_cache=test_cache, model_list=model_list
|
||||
router_cache=test_cache
|
||||
)
|
||||
|
||||
deployment_id = "1234"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user