[SGLang] FastAPI 기반 HTTP 서버: 비동기 추론 서빙의 진입점
들어가며
LLM 추론 서버의 진입점은 결국 HTTP 서버다. 모델 로딩, 스케줄링, 디토크나이징이 아무리 최적화되어 있어도 클라이언트 요청을 받아 응답으로 돌려주는 HTTP 레이어가 없으면 서빙이 성립하지 않는다. SGLang은 python/sglang/srt/entrypoints/http_server.py 단일 파일에서 FastAPI 기반의 HTTP 서버 전체를 구현한다.
이 파일은 약 2,300줄로, Native API, OpenAI 호환 API, Ollama 호환 API, Anthropic 호환 API, SageMaker, Vertex AI까지 6종의 API 표준을 하나의 FastAPI 앱에서 서빙한다. 이 글에서는 서버의 구조, 초기화 흐름, 요청 처리 패턴을 코드 레벨에서 분석한다.
서버 구조도
┌─────────────────────────────────────────┐
│ FastAPI Application │
│ │
Client Request ────────►│ CORSMiddleware │
│ Prometheus Middleware (optional) │
│ API Key Middleware (optional) │
│ │
│ ┌──────────────────────────────────┐ │
│ │ Route Groups │ │
│ │ │ │
│ │ /generate, /encode (Native) │ │
│ │ /v1/completions (OpenAI) │ │
│ │ /v1/chat/completions (OpenAI) │ │
│ │ /v1/embeddings (OpenAI) │ │
│ │ /v1/responses (OpenAI) │ │
│ │ /api/chat, /api/generate (Ollama)│ │
│ │ /v1/messages (Anthropic)│ │
│ │ /invocations (SageMaker)│ │
│ │ /health, /model_info (Admin) │ │
│ └──────────────┬─────────────────────┘ │
└─────────────────┼───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ _GlobalState │
│ ┌─────────────────────────────────┐ │
│ │ TokenizerManager │ │
│ │ (ZMQ IPC ↔ Scheduler/Detok) │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ OpenAI Serving Handlers │ │
│ │ (Chat, Completion, Embedding, │ │
│ │ Rerank, Score, Tokenize, ...) │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Ollama / Anthropic Serving │ │
│ └─────────────────────────────────┘ │
└────────────────────────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ uvicorn (uvloop) / Granian (HTTP/2) │
└────────────────────────────────────────┘
핵심 코드 분석
전역 상태와 FastAPI 앱 생성
SGLang HTTP 서버의 핵심은 _GlobalState dataclass와 모듈 레벨 app 객체다. Flask나 Django처럼 요청마다 새 컨텍스트를 만드는 대신, 전역 상태에 TokenizerManager와 TemplateManager를 저장해두고 모든 핸들러에서 공유한다.
@dataclasses.dataclass
class _GlobalState:
tokenizer_manager: Union[TokenizerManager, MultiTokenizerRouter, TokenizerWorker]
template_manager: TemplateManager
scheduler_info: Dict
_global_state: Optional[_GlobalState] = None
FastAPI 앱은 모듈 레벨에서 생성되며, CORS 미들웨어가 기본으로 적용된다.
app = FastAPI(
lifespan=lifespan,
openapi_url=None if get_bool_env_var("DISABLE_OPENAPI_DOC") else "/openapi.json",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Django의 settings 기반 미들웨어 체인이나 Flask의 WSGI 래퍼와 달리, FastAPI는 미들웨어를 add_middleware()로 직접 추가한다. SGLang은 여기에 Prometheus, API key 인증 미들웨어를 조건부로 추가하는 방식을 취한다.
Lifespan을 통한 서빙 핸들러 초기화
FastAPI의 lifespan 컨텍스트 매니저에서 OpenAI, Ollama, Anthropic 호환 핸들러를 모두 초기화한다. 이는 서버 시작 시 한 번만 실행되며, app.state에 바인딩되어 요청 핸들러에서 접근할 수 있다.
@asynccontextmanager
async def lifespan(fast_api_app: FastAPI):
# ...
# Initialize OpenAI serving handlers
fast_api_app.state.openai_serving_completion = OpenAIServingCompletion(
_global_state.tokenizer_manager, _global_state.template_manager
)
fast_api_app.state.openai_serving_chat = OpenAIServingChat(
_global_state.tokenizer_manager, _global_state.template_manager
)
fast_api_app.state.openai_serving_embedding = OpenAIServingEmbedding(
_global_state.tokenizer_manager, _global_state.template_manager
)
# ... (9개 OpenAI 핸들러 + Ollama + Anthropic)
# Initialize Ollama-compatible serving handler
fast_api_app.state.ollama_serving = OllamaServing(_global_state.tokenizer_manager)
# Initialize Anthropic-compatible serving handler
fast_api_app.state.anthropic_serving = AnthropicServing(
fast_api_app.state.openai_serving_chat
)
주목할 점은 AnthropicServing이 독립적으로 TokenizerManager를 받는 것이 아니라 OpenAIServingChat을 래핑한다는 것이다. Anthropic Messages API 요청을 내부적으로 OpenAI Chat Completion 형식으로 변환해서 처리하는 어댑터 패턴이다.
비동기 스트리밍 요청 처리
Native /generate 엔드포인트의 구현은 SGLang의 비동기 처리 패턴을 잘 보여준다. streaming과 non-streaming 경로가 obj.stream 플래그로 분기된다.
@app.api_route("/generate", methods=["POST", "PUT"], response_class=SGLangORJSONResponse)
async def generate_request(obj: GenerateReqInput, request: Request):
if obj.stream:
async def stream_results() -> AsyncIterator[bytes]:
try:
async for out in _global_state.tokenizer_manager.generate_request(
obj, request
):
yield b"data: " + dumps_json(out) + b"\n\n"
except ValueError as e:
out = {"error": {"message": str(e)}}
yield b"data: " + dumps_json(out) + b"\n\n"
yield b"data: [DONE]\n\n"
return StreamingResponse(
stream_results(),
media_type="text/event-stream",
background=_global_state.tokenizer_manager.create_abort_task(obj),
)
else:
ret = await _global_state.tokenizer_manager.generate_request(
obj, request
).__anext__()
return orjson_response(ret)
Flask에서 동일한 구현을 하려면 flask.Response와 generator를 조합해야 하고, ASGI가 아닌 WSGI 기반이라 진정한 비동기 처리가 불가능하다. FastAPI + uvloop 조합은 이 스트리밍 패턴을 자연스럽게 지원한다.
StreamingResponse의 background 파라미터에 create_abort_task를 전달하는 것도 중요하다. 클라이언트가 연결을 끊으면 백그라운드 태스크가 실행되어 진행 중인 추론을 중단한다.
OpenAI 호환 엔드포인트 위임 패턴
OpenAI 호환 엔드포인트들은 일관된 위임 패턴을 따른다. 각 엔드포인트는 요청 파싱만 담당하고, 실제 로직은 app.state의 서빙 핸들러에 위임한다.
@app.post("/v1/chat/completions", dependencies=[Depends(validate_json_request)])
async def openai_v1_chat_completions(
request: ChatCompletionRequest, raw_request: Request
):
return await raw_request.app.state.openai_serving_chat.handle_request(
request, raw_request
)
Depends(validate_json_request)로 Content-Type 검증을 공통 dependency로 처리하는 것이 FastAPI 답다. Django의 @require_http_methods나 Flask의 데코레이터보다 선언적이다.
서버 기동: launch_server
전체 서버 기동 흐름은 launch_server 함수에 집약되어 있다. 이 함수의 docstring이 SGLang 서빙 아키텍처의 핵심을 설명한다.
def launch_server(
server_args: ServerArgs,
init_tokenizer_manager_func: Callable = init_tokenizer_manager,
run_scheduler_process_func: Callable = run_scheduler_process,
run_detokenizer_process_func: Callable = run_detokenizer_process,
execute_warmup_func: Callable = _execute_server_warmup,
launch_callback: Optional[Callable[[], None]] = None,
):
"""
The SRT server consists of an HTTP server and an SRT engine.
- HTTP server: A FastAPI server that routes requests to the engine.
- The engine consists of three components:
1. TokenizerManager: Tokenizes the requests and sends them to the scheduler.
2. Scheduler (subprocess): Receives requests, schedules batches, forwards them,
and sends the output tokens to the Detokenizer Manager.
3. DetokenizerManager (subprocess): Detokenizes the output tokens and sends
the result back to the Tokenizer Manager.
"""
서브프로세스(Scheduler, Detokenizer)를 먼저 기동한 후, 메인 프로세스에서 HTTP 서버를 실행한다. 프로세스 간 통신은 ZMQ IPC를 사용한다. HTTP 서버 자체는 uvicorn(기본) 또는 Granian(HTTP/2 지원 시)으로 구동된다.
Health Check의 깊이
SGLang의 health check는 단순한 200 OK 반환이 아니다. 실제로 토큰 1개를 생성하는 요청을 보내 전체 파이프라인이 살아있는지 확인한다.
@app.get("/health")
@app.get("/health_generate")
async def health_generate(request: Request) -> Response:
sampling_params = {"max_new_tokens": 1, "temperature": 0.0}
rid = f"{HEALTH_CHECK_RID_PREFIX}_{time.time()}"
if _global_state.tokenizer_manager.is_generation:
gri = GenerateReqInput(
rid=rid, input_ids=[0], sampling_params=sampling_params, log_metrics=False,
)
# ... 타임아웃 내에 detokenizer로부터 응답을 받으면 200, 아니면 503
Tokenizer -> Scheduler -> Detokenizer 전체 경로를 검증하기 때문에, 모델이 로드되었지만 스케줄러가 행(hang)된 경우도 감지할 수 있다. Kubernetes의 readiness probe로 이 엔드포인트를 사용하면 실질적인 서빙 가용성을 판단할 수 있다.
왜 이 설계인가
-
단일 파일, 다중 프로토콜: OpenAI, Ollama, Anthropic, SageMaker, Vertex AI 호환 엔드포인트를 모두 하나의 FastAPI 앱에 등록한다. 별도 서버를 띄울 필요 없이 하나의 포트로 모든 클라이언트를 수용한다. vLLM도 유사한 접근이지만, SGLang은 Anthropic Messages API까지 네이티브로 지원하는 점이 차별화된다.
-
전역 상태 + app.state 이중 구조:
_GlobalState는 모듈 레벨에서TokenizerManager를 보관하고,app.state는 lifespan에서 서빙 핸들러를 보관한다. 전역 상태는 Native API 핸들러에서,app.state는 OpenAI/Ollama/Anthropic 핸들러에서 사용한다. 이 분리 덕분에 핸들러 교체나 테스트가 용이하다. -
uvloop 강제 적용:
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())를 모듈 임포트 시점에 실행한다. 표준 asyncio 이벤트 루프 대비 2~4배 빠른 I/O 처리를 보장한다. LLM 서빙에서 토큰 스트리밍은 수천 개의 SSE 이벤트를 빠르게 전송해야 하므로 이 최적화가 중요하다. -
Granian HTTP/2 옵션:
--enable-http2플래그로 uvicorn 대신 Granian 서버를 사용할 수 있다. HTTP/2의 멀티플렉싱은 동시 다발 스트리밍 요청에서 head-of-line blocking을 제거한다. -
Multi-tokenizer 모드:
tokenizer_worker_num > 1이면 공유 메모리를 통해 여러 tokenizer 프로세스를 운영한다. CPU 바운드인 토크나이징 작업을 병렬화하여 GPU 활용률을 높이는 전략이다.
관련 포스트
참고
관련 포스트
SGLang 의 다른글
- 이전글 [SGLang] 프로젝트 전체 아키텍처 분석 - 개요 및 목차
- 현재글 : [SGLang] FastAPI 기반 HTTP 서버: 비동기 추론 서빙의 진입점
- 다음글 [SGLang] Engine: 멀티프로세스 오케스트레이터의 설계와 구현
댓글