워커 시작 훅
워커 시작 훅 (Worker Startup Hooks)
LITELLM_WORKER_STARTUP_HOOKS를 사용해 프록시 시작 시 각 워커 프로세스에서 커스텀 초기화 함수를 실행하는 방법이에요. gflags 같은 프로세스별 초기화가 필요한 라이브러리를 쓰는 멀티 워커 배포(--num_workers > 1)에서 필수적이에요.
출처: 문서
본문
LITELLM_WORKER_STARTUP_HOOKS를 사용해 프록시 시작 시 각 워커 프로세스에서 커스텀 초기화 함수를 실행해요. gflags처럼 프로세스별 초기화가 필요한 라이브러리를 쓰는 멀티 워커 배포(--num_workers > 1)에서 필수적이에요.
문제
멀티 워커로 LiteLLM 프록시를 실행할 때:
litellm --config config.yaml --num_workers 4
각 워커는 uvicorn, gunicorn, 또는 Granian(--run_granian)이 생성한 별도 프로세스예요. 마스터 프로세스에서(run_server() 전에) 초기화된 모든 인-프로세스 상태는 워커 프로세스에서 사용할 수 없어요. 여기에는 다음이 포함돼요:
- python-gflags (
gflags.FLAGS) - absl-py flags (
absl.flags.FLAGS) - 커스텀 싱글톤 레지스트리나 연결 풀
- 명시적 초기화가 필요한 모든 모듈 수준 상태
사용법
LITELLM_WORKER_STARTUP_HOOKS 환경 변수를 module.path:function_name 콜러블의 쉼표로 구분된 목록으로 설정해요:
export LITELLM_WORKER_STARTUP_HOOKS="my_module:my_init_function"
각 훅은 config 로딩, 데이터베이스 설정, 요청 처리가 시작되기 전인 워커 시작 생애주기의 이른 시점에 호출돼요. 동기 및 비동기 함수 모두 지원돼요.
예시: gflags 초기화
1. 래퍼 모듈 정의하기
my_litellm_wrapper.py
import gflags
import json
import os
import sys
from typing import Optional, List, Any
def init_gflags(
usage: Optional[Any] = None,
raw_args: Optional[List[str]] = None,
known_only: bool = False,
) -> List[str]:
"""Initialize gflags from command-line arguments."""
try:
gflags.FLAGS.set_gnu_getopt(True)
if raw_args is None:
raw_args = sys.argv
argv = gflags.FLAGS(raw_args, known_only=known_only)
except gflags.Error as e:
if usage is None:
print("%s\nUsage: %s ARGS\n%s" % (e, sys.argv[0], gflags.FLAGS))
else:
print(usage % dict(cmd=sys.argv[0], flags=gflags.FLAGS))
sys.exit(1)
return argv
def init_gflags_for_worker():
"""Re-initialize gflags in each worker process.
Reads the original sys.argv from the GFLAGS_ARGV env var
(set by the master process before starting the proxy).
"""
raw_args = json.loads(os.environ.get("GFLAGS_ARGV", "[]")) or sys.argv
init_gflags(raw_args=raw_args, known_only=True)
2. 프록시 시작하기
start_proxy.py
import json
import os
import sys
from my_litellm_wrapper import init_gflags
# Store sys.argv so workers can re-parse the same flags
os.environ["GFLAGS_ARGV"] = json.dumps(sys.argv)
# Tell LiteLLM to call our hook in each worker
os.environ["LITELLM_WORKER_STARTUP_HOOKS"] = "my_litellm_wrapper:init_gflags_for_worker"
# Initialize gflags in the master process
init_gflags()
# Start the proxy (programmatic invocation)
from litellm.proxy.proxy_cli import run_server
run_server(
["--config", "config.yaml", "--num_workers", "4"],
standalone_mode=False,
)
또는 셸로:
export GFLAGS_ARGV='["my_app", "--my_flag=value", "--batch_size=32"]'
export LITELLM_WORKER_STARTUP_HOOKS="my_litellm_wrapper:init_gflags_for_worker"
litellm --config config.yaml --num_workers 4
동작 방식
Master Process Worker Process (×N)
───────────────── ──────────────────────
1. init_gflags() 3. proxy_startup_event():
2. run_server() → Read LITELLM_WORKER_STARTUP_HOOKS
→ sets env vars → Import & call each hook
→ uvicorn.run(workers=N) (gflags.FLAGS re-initialized ✓)
→ spawns workers ──────────────────► → Continue with config/DB setup
→ Ready to serve requests
- 훅은
proxy_startup_event(FastAPI lifespan)의 맨 앞에서, config 로딩, DB 연결, 다른 초기화 이전에 실행돼요. - 마스터 프로세스에서 설정한 환경 변수는 표준 Unix fork/spawn 동작으로 워커 프로세스에 상속돼요.
- 훅이 예외를 던지면 워커가 시작에 실패해요. 초기화 누락(예: 초기화되지 않은 gflags)이 다운스트림 오류를 일으키기 때문에 의도적인 동작이에요.
여러 훅
여러 훅은 쉼표로 구분해요:
export LITELLM_WORKER_STARTUP_HOOKS="my_module:init_gflags,my_module:init_metrics,my_module:init_connections"
훅은 왼쪽에서 오른쪽 순서로 실행돼요.
비동기 훅
비동기 함수도 지원되며 자동으로 await돼요:
async def init_async_connections():
"""Example async hook for initializing async resources."""
await setup_async_connection_pool()
export LITELLM_WORKER_STARTUP_HOOKS="my_module:init_async_connections"
레퍼런스
| 환경 변수 | 설명 |
|---|---|
LITELLM_WORKER_STARTUP_HOOKS |
시작 시 각 워커에서 실행할 쉼표로 구분된 module.path:function_name 콜러블 |
훅 형식은 표준 Python 진입점 문법 module.path:function_name을 따르며, 여기서 module.path는 점으로 된 Python 임포트 경로이고 function_name은 그 모듈 안의 콜러블 이름이에요.