[vLLM] OpenAI 호환 API 서버: FastAPI 기반 HTTP 서빙
들어가며
LLM을 프로덕션에 배포할 때 가장 중요한 요소 중 하나는 OpenAI API와의 호환성이다. 기존 클라이언트 코드 변경 없이 자체 호스팅 모델로 전환할 수 있기 때문이다. vLLM은 vllm/entrypoints/openai/api_server.py에서 FastAPI 기반의 OpenAI 호환 HTTP 서버를 제공한다.
공식 문서: https://docs.vllm.ai/en/latest/serving/openai_compatible_server.html
공식 문서
vLLM 공식 문서: OpenAI Compatible Server
핵심 구조/코드 분석
엔진 클라이언트 생성
서버의 시작점은 build_async_engine_client 함수이다. 이 함수는 비동기 엔진 클라이언트를 생성하는 컨텍스트 매니저로, 내부적으로 AsyncLLM을 초기화한다.
@asynccontextmanager
async def build_async_engine_client(
args: Namespace,
*,
usage_context: UsageContext = UsageContext.OPENAI_API_SERVER,
client_config: dict[str, Any] | None = None,
) -> AsyncIterator[EngineClient]:
engine_args = AsyncEngineArgs.from_cli_args(args)
async with build_async_engine_client_from_engine_args(
engine_args, usage_context=usage_context,
client_config=client_config,
) as engine:
yield engine
핵심은 AsyncLLM.from_vllm_config()을 통해 v1 엔진을 직접 프로세스 내에서 생성한다는 것이다.
FastAPI 앱 빌드
build_app 함수는 FastAPI 인스턴스를 생성하고, 모델이 지원하는 태스크에 따라 라우터를 동적으로 등록한다.
def build_app(args, supported_tasks, model_config) -> FastAPI:
app = FastAPI(lifespan=lifespan)
register_vllm_serve_api_routers(app)
register_models_api_router(app)
if "generate" in supported_tasks:
register_generate_api_routers(app)
attach_disagg_router(app)
attach_rlhf_router(app)
if any(task in POOLING_TASKS for task in supported_tasks):
register_pooling_api_routers(app, supported_tasks, model_config)
app.add_middleware(CORSMiddleware, ...)
return app
태스크 기반 라우터 등록이 핵심 설계이다. generate, transcription, realtime, pooling 등 모델이 지원하는 태스크에 맞춰 필요한 엔드포인트만 활성화한다.
앱 상태 초기화
init_app_state에서 서빙에 필요한 핵심 객체들이 FastAPI의 app.state에 바인딩된다.
async def init_app_state(engine_client, state, args, supported_tasks):
state.engine_client = engine_client
state.openai_serving_models = OpenAIServingModels(
engine_client=engine_client,
base_model_paths=base_model_paths,
lora_modules=lora_modules,
)
state.openai_serving_render = OpenAIServingRender(...)
state.openai_serving_tokenization = OpenAIServingTokenization(...)
서버 실행 흐름
전체 서버 실행은 run_server -> setup_server -> build_and_serve 순서로 진행된다.
async def run_server(args, **uvicorn_kwargs) -> None:
listen_address, sock = setup_server(args)
await run_server_worker(listen_address, sock, args, **uvicorn_kwargs)
setup_server에서 소켓을 미리 바인딩하는 이유는 Ray와의 레이스 컨디션을 방지하기 위해서이다. 엔진 초기화 전에 포트를 선점해야 다른 프로세스와의 충돌을 피할 수 있다.
미들웨어 체계
vLLM 서버는 다양한 미들웨어를 지원한다.
# API 키 인증
if tokens := [key for key in (args.api_key or [envs.VLLM_API_KEY]) if key]:
app.add_middleware(AuthenticationMiddleware, tokens=tokens)
# 스케일링 미들웨어
app.add_middleware(ScalingMiddleware)
# 사용자 커스텀 미들웨어
for middleware in args.middleware:
module_path, object_name = middleware.rsplit(".", 1)
imported = getattr(importlib.import_module(module_path), object_name)
if inspect.isclass(imported):
app.add_middleware(imported)
왜 이 설계인가
-
태스크 기반 라우터 분리: 모든 엔드포인트를 한 파일에 넣지 않고, 태스크별로 분리해서 모듈화했다. 임베딩 모델을 서빙할 때 generate 관련 엔드포인트가 불필요하게 노출되지 않는다.
-
소켓 선바인딩: Ray 분산 환경에서 엔진 초기화와 포트 바인딩 사이의 레이스 컨디션을 해결하기 위한 실용적 설계이다.
-
uvloop 사용:
uvloop.run(run_server(args))로 이벤트 루프 성능을 극대화한다. uvloop은 libuv 기반으로 표준 asyncio 대비 2~4배 빠른 성능을 제공한다. -
미들웨어 확장성:
--middleware인자로 외부 미들웨어를 동적으로 주입할 수 있어, 프로덕션 환경에서 로깅이나 모니터링 등을 유연하게 추가할 수 있다.
정리
vLLM의 OpenAI 호환 서버는 단순한 HTTP 래퍼가 아니다. 태스크 기반 동적 라우팅, 소켓 선바인딩, 유연한 미들웨어 체계 등 프로덕션 환경을 고려한 설계가 곳곳에 반영되어 있다. vllm serve 명령 한 줄 뒤에 이런 아키텍처가 동작하고 있다는 것을 알면, 서빙 환경을 커스터마이징할 때 큰 도움이 된다.
관련 포스트
vLLM 의 다른글
- 이전글 [vLLM] KV Transfer Connectors: KV 캐시 전송 프레임워크
- 현재글 : [vLLM] OpenAI 호환 API 서버: FastAPI 기반 HTTP 서빙
- 다음글 [vLLM] 오프라인 LLM API: 배치 추론 Python API
댓글