LiteLLM의 라우터는 여러 모델 배포를 아우르는 스마트 로드 밸런싱으로 소개된다. 나는 v1.92.0-dev.1, 커밋 88e03e5 기준으로 읽었고, 기본값은 동전 던지기다. 별도 설정 없이 simple-shuffle을 사용한다. 정상 배포 중 하나를 무작위로 고르되, 굳이 가중치를 설정했다면 rpm/tpm에 따라 가중치를 적용하고, 그렇지 않으면 균등하게 고른다. 그런 전략을 직접 선택하지 않는 한 지연 시간이나 부하를 인식하지 않는다. 대신 기본으로 주는 것은 재시도 → 쿨다운 → 폴백 장애 사다리다. 그리고 사용량/지연 시간 전략이 “스마트”라는 평가를 얻는 지점은 모든 키를 계측하기 때문이며, 이는 바로 구독 프록시가 할 수 없는 일이다. 아래의 모든 주장은 코드 라인을 가리킨다.
무엇이며, 어디에 자리하는가?
라우터는 모델 그룹 앞에 놓인다. 예를 들어 모두 gpt-4를 제공하는 여러 배포(API 키, 리전 또는 프로바이더) 중 요청마다 하나를 고른 뒤, 실패를 처리하는 구성 요소다. LLM 게이트웨이 비교에서 API 키 라우팅을 담당하는 절반이다. 나머지 절반은 별도로 해부한 구독 계정 프록시(CLIProxyAPI)다. 마지막에 나오는 둘의 대비가 이 글의 핵심이다.
기본값은 마케팅보다 단순하다
기본 라우팅 전략은 말 그대로 문자열 "simple-shuffle"이고(router.py:302), 스마트 전략이 사용하는 선택기 장치를 우회해 인라인으로 디스패치된다(router.py:10299). 선택 로직의 전부는 다음과 같다.
# weighted pick if a weight/rpm/tpm is set...
selected_index = random.choices(range(len(weights)), weights=weights)[0]
# ...otherwise a plain coin flip
item = random.choice(healthy_deployments)
각각 simple_shuffle.py:59와 :68이다. 지연 시간, 큐 깊이, 실시간 부하는 어느 것도 선택에 반영되지 않는다. “정상”은 단지 배포가 일련의 필터를 통과했다는 뜻이다. 쿨다운 중이 아니고, 관리자가 차단하지 않았으며, 사전 호출 검사를 활성화했다면 컨텍스트 윈도우에 맞고 RPM 한도 이하여야 한다. 그러니 솔직하게 한 문장으로 말하면 이렇다. 기본적으로 LiteLLM은 어떤 것도 측정하지 않고, 가중 무작위 선택으로 부하를 분산한다. 측정하게 만들고 싶다면 직접 선택해야 한다.
스마트 전략은 선택 사항이며, 계측한다
routing_strategy를 least-busy, latency-based-routing, usage-based-routing[-v2], cost-based-routing 중 하나로 설정하면 선택기가 만들어지고 로깅 콜백으로 등록된다(router.py:865). 선제적 라우팅을 가능하게 하는 비결은 각 전략이 모든 호출에서 배포별 메트릭을 기록하고 선택 시점에 읽는다는 것이다. 메트릭은 배포 ID를 키로 삼은 DualCache(인메모리와 선택적 Redis)에 들어간다.
- 최소 부하는 처리 중인 요청 카운터를 유지하며, 호출 전에
+1, 호출 후에-1을 적용한다(least_busy.py:44). - 최저 지연 시간은 각 응답의 지연 시간을 배포별 롤링 목록에 추가하고 가장 빠른 구간을 고른다.
- 사용량 기반 v2는 분당
tpm/rpm카운터를 증가시키며(lowest_tpm_rpm_v2.py:258), 디스패치 전에 사전 호출 증가 연산으로 RPM 슬롯을 예약한다. 예산을 넘으면RateLimitError를 일으킨다(lowest_tpm_rpm_v2.py:116,router.py:7120에서 구동).
그 예약이 가장 날카로운 부분이다. Redis를 사용하면 계측값이 여러 프록시 인스턴스에 걸쳐 단일 기준이 되므로, LiteLLM은 요청이 프로바이더에 도달하기도 전에 거부할 수 있다.1 이는 LiteLLM이 키를 소유하고 그 키를 통해 사용된 모든 토큰을 보기 때문에 가능한 일이다.

LiteLLM의 기본 라우터는 정상 배포를 대상으로 가중 무작위 선택을 한다. 스마트 전략은 LiteLLM이 읽을 수 있는 키별 계측기를 보유할 때만 가능해진다.
장애 사다리
호출이 실패하면 세 가지 메커니즘이 순서대로 작동한다. 기본값은 한 파일에 있다(constants.py).
| 단계 | 일어나는 일 | 기본값 |
|---|---|---|
| 재시도 | 정상 배포를 다시 고르고 재시도한다 | num_retries = 2 (:20) |
| 쿨다운 | 실패한 배포를 TTL 동안 벤치로 보낸다 | cooldown_time = 5s (:32) |
| 폴백 | 백업 모델 그룹으로 연쇄 이동한다 | ROUTER_MAX_FALLBACKS = 5 (:9) |
순서가 중요하다. 먼저 모델 그룹 안에서 1 + num_retries번의 시도를 모두 소진하고, 이들이 전부 실패했을 때만 폴백 그룹으로 이동한다(router.py:6193). 알아둘 만한 세부 사항은 두 가지다.
재시도는 대개 즉시 이루어진다. 그룹에 배포가 하나만 남은 상태가 아니고 정상 배포가 남아 있는 한, 백오프 함수는 0, 즉 즉시 재시도를 반환한다(router.py:6635). 재시도할 때마다 다시 고르고 방금 실패한 배포는 쿨다운 중이므로, “재시도”는 사실상 “지금 바로 다른 배포를 시도한다”는 뜻이다. 배포가 하나만 남았을 때에야 지수 백오프(0.5s부터 두 배씩 증가, 상한 8s, Retry-After 헤더 준수)로 물러난다.
쿨다운은 짧고 실패율에 기반하며, 유일한 모델은 건드리지 않는다. 배포가 429/401/408/404 또는 어떤 5xx든 반환하면 벤치로 보내며(cooldown_handlers.py:40), 윈도우 안에서 실패율이 50%를 넘고 요청이 최소 5번일 때도 마찬가지다(:210). 하지만 모델 그룹에 배포가 하나뿐이라면 429와 실패율 기반 쿨다운을 의도적으로 건너뛴다(:204) — 유일한 모델을 쿨다운하면 모든 요청이 실패할 뿐이기 때문이다. 쿨다운 자체는 5초 TTL이 설정된 캐시 키이며, 시간이 지나면 저절로 만료된다. 이후 폴백은 실패 유형에 따라 갈린다. 컨텍스트 윈도우 초과는 context_window_fallbacks로, 콘텐츠 정책 차단은 content_policy_fallbacks로, 나머지는 모두 일반 fallbacks 목록으로 라우팅한다.
이 시스템은 어디에 자리하는가?
CLIProxyAPI에 사용한 것과 같은 축이다. 무엇을 라우팅하며, 그 비용을 볼 수 있는가?
LiteLLM은 API 키를 라우팅하고 이를 계측한다. 모든 요청이 해당 배포의 tpm/rpm/지연 시간/처리 중 요청 수를 갱신한다. 이 가시성 덕분에 선택적으로 최소 부하, 사용량 또는 지연 시간에 따라 라우팅하고, 사용하기 전에 용량을 예약할 수 있다. CLIProxyAPI는 볼 수 없는 불투명한 할당량 윈도우에 묶인 구독 로그인을 라우팅하므로, 유일한 수단은 반응이다. 429를 기다렸다가 계정을 쿨다운한다. 나란히 읽어보면 둘은 똑같은 장애 조치-쿨다운 골격에 결정적인 차이 하나가 있다. LiteLLM은 계측기를 쥐고 있기 때문에 선제적으로 대응할 수 있고, 구독 프록시는 계측기가 보이지 않기 때문에 반응만 할 수 있다. (그리고 그 철학을 드러내는 작은 단서가 하나 있다. LiteLLM은 유일한 배포를 쿨다운하지 않지만, CLIProxyAPI는 개별 계정을 가리지 않고 벤치로 보낸다.)
언제 이 시스템을 선택할까?
같은 모델을 위한 키/배포가 여러 개 있고, 재시도, 쿨다운, 폴백이 대부분 알아서 작동하는 단일 엔드포인트를 원한다면 LiteLLM의 라우터를 선택할 만하다. 두 가지는 알고 들어가야 한다. 첫째, 기본값은 지능적으로 밸런싱하지 않는다. 부하나 지연 시간이 중요하다면 routing_strategy를 명시적으로 설정하고, 계측기를 공유할 수 있도록 가능하면 Redis를 연결하자. 그렇지 않으면 무작위 선택이 적어도 실제 용량을 따르도록 배포별 weight/rpm을 설정해야 한다. 둘째, 장애 기본값은 공격적이지만 짧다. 5초 쿨다운과 즉시 다시 고르기는 일시적인 흔들림에 훌륭하지만, 잠시 상태가 나빴던 배포를 금세 다시 두들긴다는 뜻이다. 프로바이더가 불안정하다면 cooldown_time과 allowed_fails를 조정하자.
방법론과 범위
나는 **LiteLLM v1.92.0-dev.1, 커밋 88e03e5**을 읽었고, litellm/router.py, litellm/router_strategy/*, litellm/router_utils/cooldown_*.py와 fallback_event_handlers.py, litellm/constants.py에 집중했다. 핵심 주장 세 가지, 즉 선택형 스마트 전략을 둔 가중 무작위 기본값, 재시도/쿨다운/폴백 사다리와 정확한 기본값, 선제적 라우팅을 가능하게 하는 배포별 계측은 각각 해당 커밋에서 반증을 시도하는 주장 검증을 거쳐 확인했다. 라인 번호는 해당 커밋에서 정확하다.
범위의 한계를 명확히 밝힌다.
- 라우터의 라우팅과 장애 경로는 읽었지만, 15+개 전략 파일을 모두 읽지는 않았다(내가 열어보지 않은 최신 적응형/복잡도/품질 라우터가 있다). “스마트 기능은 선택 사항”이라는 주장은 고전적인 다섯 가지에 관한 것이다.
- Redis 장애가 발생하면 RPM 예약은 최선 노력 방식으로 동작한다. 코드가 연결 오류를 삼키고 호출을 차단하는 대신 통과시키기 때문이다(
lowest_tpm_rpm_v2.py). 따라서 이를 하드 보장이 아닌 강한 예약으로 봐야 한다. - 문서와 코드가 어긋나는 지점은 덮지 않고 짚겠다.
cooldown_time독스트링은router.py:343에서 “Defaults to 1”이라고 하지만, 실제로 해석되는 상수는 5다.
Footnotes
-
원자성은 먼저 증가시킨 뒤 확인하는 방식이므로, 동시성이 높으면 증가와 확인 사이에 예약량이 한도를 일시적으로 초과할 수 있다. 실제 사전 호출 예약이지만 완벽하게 직렬화된 잠금은 아니다. 부하를 덜어내기에는 충분하나, 하드한 속도 제한을 보장하지는 않는다. ↩


