mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
perf: guard orjson import with fallback, pre-parse httpx URLs, fix docstring
Addresses three review concerns and adds httpx URL caching: 1. safe_json_dumps.py: Guard orjson import with try/except fallback to stdlib json. This module is on the core SDK import path via _logging.py — unconditional orjson import would break plain 'pip install litellm' (non-proxy) users. 2. router.py print_deployment: Update docstring to accurately describe the reduced return shape (model_name + litellm_params only). 3. run_perf_comparison.sh: Fix locustfile reference to use the correct locustfile_perf.py instead of locustfile.py. 4. httpx URL pre-parsing (~7.8us -> ~0.4us per request, 19x speedup): Add _parse_url() with LRU cache (maxsize=64) that pre-parses URL strings into httpx.URL objects. Applied to all HTTP methods (GET, POST, PUT, PATCH, DELETE) in both AsyncHTTPHandler and HTTPHandler. Eliminates regex-heavy re.finditer inside httpx._urlparse on every request — confirmed as a GIL hotspot in py-spy thread dumps. Co-authored-by: Krish Dholakia <krrishdholakia@gmail.com>
This commit is contained in:
parent
9e0bf1eec3
commit
02e6b59abd
4 changed files with 45 additions and 21 deletions
|
|
@ -1,10 +1,17 @@
|
|||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
import orjson
|
||||
from pydantic import BaseModel
|
||||
|
||||
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH
|
||||
|
||||
try:
|
||||
import orjson
|
||||
|
||||
_has_orjson = True
|
||||
except ImportError:
|
||||
_has_orjson = False
|
||||
|
||||
|
||||
def safe_dumps(data: Any, max_depth: int = DEFAULT_MAX_RECURSE_DEPTH) -> str:
|
||||
"""
|
||||
|
|
@ -52,4 +59,6 @@ def safe_dumps(data: Any, max_depth: int = DEFAULT_MAX_RECURSE_DEPTH) -> str:
|
|||
return "Unserializable Object"
|
||||
|
||||
safe_data = _serialize(data, set(), 0)
|
||||
return orjson.dumps(safe_data, default=str).decode()
|
||||
if _has_orjson:
|
||||
return orjson.dumps(safe_data, default=str).decode()
|
||||
return json.dumps(safe_data, default=str)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import asyncio
|
||||
import functools
|
||||
import os
|
||||
import ssl
|
||||
import sys
|
||||
|
|
@ -51,6 +52,14 @@ try:
|
|||
except Exception:
|
||||
version = "0.0.0"
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=64)
|
||||
def _parse_url(url: str) -> httpx.URL:
|
||||
"""Pre-parse a URL string into an httpx.URL to avoid regex-heavy
|
||||
parsing inside httpx._merge_url on every request (~7μs → ~0.4μs)."""
|
||||
return httpx.URL(url)
|
||||
|
||||
|
||||
def get_default_headers() -> dict:
|
||||
"""
|
||||
Get default headers for HTTP requests.
|
||||
|
|
@ -424,7 +433,7 @@ class AsyncHTTPHandler:
|
|||
params.update(HTTPHandler.extract_query_params(url))
|
||||
|
||||
response = await self.client.get(
|
||||
url, params=params, headers=headers, follow_redirects=_follow_redirects # type: ignore
|
||||
_parse_url(url), params=params, headers=headers, follow_redirects=_follow_redirects # type: ignore
|
||||
)
|
||||
return response
|
||||
|
||||
|
|
@ -452,9 +461,10 @@ class AsyncHTTPHandler:
|
|||
data, content
|
||||
)
|
||||
|
||||
parsed_url = _parse_url(url)
|
||||
req = self.client.build_request(
|
||||
"POST",
|
||||
url,
|
||||
parsed_url,
|
||||
data=request_data,
|
||||
json=json,
|
||||
params=params,
|
||||
|
|
@ -533,7 +543,7 @@ class AsyncHTTPHandler:
|
|||
)
|
||||
|
||||
req = self.client.build_request(
|
||||
"PUT", url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
|
||||
"PUT", _parse_url(url), data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
|
||||
)
|
||||
response = await self.client.send(req)
|
||||
response.raise_for_status()
|
||||
|
|
@ -599,7 +609,7 @@ class AsyncHTTPHandler:
|
|||
)
|
||||
|
||||
req = self.client.build_request(
|
||||
"PATCH", url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
|
||||
"PATCH", _parse_url(url), data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
|
||||
)
|
||||
response = await self.client.send(req)
|
||||
response.raise_for_status()
|
||||
|
|
@ -665,7 +675,7 @@ class AsyncHTTPHandler:
|
|||
)
|
||||
|
||||
req = self.client.build_request(
|
||||
"DELETE", url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
|
||||
"DELETE", _parse_url(url), data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
|
||||
)
|
||||
response = await self.client.send(req, stream=stream)
|
||||
response.raise_for_status()
|
||||
|
|
@ -717,7 +727,7 @@ class AsyncHTTPHandler:
|
|||
request_data, request_content = _prepare_request_data_and_content(data, content)
|
||||
|
||||
req = client.build_request(
|
||||
"POST", url, data=request_data, json=json, params=params, headers=headers, content=request_content # type: ignore
|
||||
"POST", _parse_url(url), data=request_data, json=json, params=params, headers=headers, content=request_content # type: ignore
|
||||
)
|
||||
response = await client.send(req, stream=stream)
|
||||
response.raise_for_status()
|
||||
|
|
@ -984,7 +994,7 @@ class HTTPHandler:
|
|||
params.update(self.extract_query_params(url))
|
||||
|
||||
response = self.client.get(
|
||||
url,
|
||||
_parse_url(url),
|
||||
params=params,
|
||||
headers=headers,
|
||||
)
|
||||
|
|
@ -1023,10 +1033,11 @@ class HTTPHandler:
|
|||
data, content
|
||||
)
|
||||
|
||||
parsed_url = _parse_url(url)
|
||||
if timeout is not None:
|
||||
req = self.client.build_request(
|
||||
"POST",
|
||||
url,
|
||||
parsed_url,
|
||||
data=request_data, # type: ignore
|
||||
json=json,
|
||||
params=params,
|
||||
|
|
@ -1037,7 +1048,7 @@ class HTTPHandler:
|
|||
)
|
||||
else:
|
||||
req = self.client.build_request(
|
||||
"POST", url, data=request_data, json=json, params=params, headers=headers, files=files, content=request_content # type: ignore
|
||||
"POST", parsed_url, data=request_data, json=json, params=params, headers=headers, files=files, content=request_content # type: ignore
|
||||
)
|
||||
response = self.client.send(req, stream=stream)
|
||||
response.raise_for_status()
|
||||
|
|
@ -1079,13 +1090,14 @@ class HTTPHandler:
|
|||
data, content
|
||||
)
|
||||
|
||||
parsed_url = _parse_url(url)
|
||||
if timeout is not None:
|
||||
req = self.client.build_request(
|
||||
"PATCH", url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
|
||||
"PATCH", parsed_url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
|
||||
)
|
||||
else:
|
||||
req = self.client.build_request(
|
||||
"PATCH", url, data=request_data, json=json, params=params, headers=headers, content=request_content # type: ignore
|
||||
"PATCH", parsed_url, data=request_data, json=json, params=params, headers=headers, content=request_content # type: ignore
|
||||
)
|
||||
response = self.client.send(req, stream=stream)
|
||||
response.raise_for_status()
|
||||
|
|
@ -1128,13 +1140,14 @@ class HTTPHandler:
|
|||
data, content
|
||||
)
|
||||
|
||||
parsed_url = _parse_url(url)
|
||||
if timeout is not None:
|
||||
req = self.client.build_request(
|
||||
"PUT", url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
|
||||
"PUT", parsed_url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
|
||||
)
|
||||
else:
|
||||
req = self.client.build_request(
|
||||
"PUT", url, data=request_data, json=json, params=params, headers=headers, content=request_content # type: ignore
|
||||
"PUT", parsed_url, data=request_data, json=json, params=params, headers=headers, content=request_content # type: ignore
|
||||
)
|
||||
response = self.client.send(req, stream=stream)
|
||||
return response
|
||||
|
|
@ -1164,13 +1177,14 @@ class HTTPHandler:
|
|||
data, content
|
||||
)
|
||||
|
||||
parsed_url = _parse_url(url)
|
||||
if timeout is not None:
|
||||
req = self.client.build_request(
|
||||
"DELETE", url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
|
||||
"DELETE", parsed_url, data=request_data, json=json, params=params, headers=headers, timeout=timeout, content=request_content # type: ignore
|
||||
)
|
||||
else:
|
||||
req = self.client.build_request(
|
||||
"DELETE", url, data=request_data, json=json, params=params, headers=headers, content=request_content # type: ignore
|
||||
"DELETE", parsed_url, data=request_data, json=json, params=params, headers=headers, content=request_content # type: ignore
|
||||
)
|
||||
response = self.client.send(req, stream=stream)
|
||||
response.raise_for_status()
|
||||
|
|
|
|||
|
|
@ -1314,9 +1314,10 @@ class Router:
|
|||
|
||||
def print_deployment(self, deployment: dict):
|
||||
"""
|
||||
returns a copy of the deployment with the api key masked
|
||||
Returns a lightweight dict with model_name + litellm_params (api key masked).
|
||||
|
||||
Only returns 2 characters of the api key and masks the rest with * (10 *).
|
||||
Only includes model_name and litellm_params to avoid deep-copying
|
||||
the full deployment dict on every log call.
|
||||
"""
|
||||
try:
|
||||
litellm_params: dict = deployment.get("litellm_params", {})
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ PROXY_PID=$!
|
|||
wait_for_service "http://localhost:4000/health/liveliness" "LiteLLM Proxy (baseline)" 60
|
||||
|
||||
echo " Running baseline locust test..."
|
||||
cd "$WORKSPACE" && poetry run locust -f tests/load_tests/locustfile.py \
|
||||
cd "$WORKSPACE" && poetry run locust -f tests/load_tests/locustfile_perf.py \
|
||||
--headless -u "$USERS" -r "$SPAWN_RATE" --run-time "$DURATION" \
|
||||
--host http://localhost:4000 \
|
||||
--csv "$RESULTS_DIR/baseline" \
|
||||
|
|
@ -122,7 +122,7 @@ PROXY_PID=$!
|
|||
wait_for_service "http://localhost:4000/health/liveliness" "LiteLLM Proxy (optimized)" 60
|
||||
|
||||
echo " Running optimized locust test..."
|
||||
cd "$WORKSPACE" && poetry run locust -f tests/load_tests/locustfile.py \
|
||||
cd "$WORKSPACE" && poetry run locust -f tests/load_tests/locustfile_perf.py \
|
||||
--headless -u "$USERS" -r "$SPAWN_RATE" --run-time "$DURATION" \
|
||||
--host http://localhost:4000 \
|
||||
--csv "$RESULTS_DIR/optimized" \
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue