[SGLang] gRPC 서버: 분산 추론을 위한 고성능 통신 계층
들어가며
SGLang은 기본적으로 HTTP/REST 기반의 OpenAI 호환 API를 제공한다. 하지만 대규모 분산 추론 환경에서는 HTTP의 오버헤드가 병목이 될 수 있다. SGLang은 이를 해결하기 위해 --grpc-mode 플래그 하나로 gRPC 서버를 활성화할 수 있는 구조를 제공한다.
gRPC 서버는 두 가지 주요 역할을 담당한다. 첫째, SGLang Model Gateway(Rust 기반 라우터)와 Python Scheduler 사이의 내부 통신 프로토콜로 동작한다. 둘째, 멀티모달 입력을 처리하는 Encoder 서버의 분산 통신 계층으로 활용된다. 이 글에서는 python/sglang/srt/entrypoints/grpc_server.py와 관련 코드를 분석하며 SGLang이 gRPC를 어떻게 활용하는지 살펴본다.
gRPC vs HTTP 비교
LLM 추론 서버에서 gRPC와 HTTP/REST의 차이를 이해하는 것이 중요하다.
HTTP/REST 요청 흐름:
┌────────┐ JSON(text) ┌────────────┐ JSON parse ┌───────────┐
│ Client ├───────────────►│ HTTP Server├──────────────►│ Scheduler │
│ │◄───────────────┤ (uvicorn) │◄──────────────┤ │
└────────┘ JSON(text) └────────────┘ JSON serialize └───────────┘
+ SSE for streaming
gRPC 요청 흐름:
┌────────┐ Protobuf(bin) ┌────────────┐ zero-copy ┌───────────┐
│ Client ├───────────────►│ gRPC Server├──────────────►│ Scheduler │
│ │◄═══════════════┤ (HTTP/2) │◄══════════════┤ │
└────────┘ Stream(bin) └────────────┘ structured └───────────┘
bidirectional streaming
| 항목 | HTTP/REST | gRPC |
|---|---|---|
| 프로토콜 | HTTP/1.1 | HTTP/2 |
| 직렬화 | JSON (텍스트) | Protobuf (바이너리) |
| 스키마 | 비공식 (OpenAPI) | 엄격한 .proto 정의 |
| Streaming | SSE (서버→클라이언트 단방향) | 양방향 Streaming |
| 연결 관리 | 요청당 또는 Keep-Alive | Multiplexed 연결 |
| 타입 안전성 | 런타임 검증 | 컴파일 타임 검증 |
| 메시지 크기 | 상대적으로 큰 JSON | 3-10배 작은 바이너리 |
| Latency | 직렬화/역직렬화 오버헤드 | 최소 오버헤드 |
LLM 추론에서 특히 중요한 차이는 Streaming이다. HTTP/REST에서는 SSE(Server-Sent Events)로 토큰을 하나씩 전달하지만, 텍스트 기반이라 파싱 오버헤드가 있다. gRPC의 Server Streaming RPC는 바이너리 Protobuf 메시지를 HTTP/2 프레임으로 직접 전달하므로, 토큰 단위 전달에서 더 효율적이다.
핵심 코드 분석
gRPC 서버 진입점
python/sglang/srt/entrypoints/grpc_server.py는 gRPC 서버의 진입점이다. 핵심 구현은 smg-grpc-servicer 패키지에 위임하고, 이 파일은 Metrics 서버 관리와 서버 생명주기를 담당한다.
async def serve_grpc(server_args, model_info=None):
"""Start the standalone gRPC server with integrated scheduler."""
try:
from smg_grpc_servicer.sglang.server import serve_grpc as _serve_grpc
except ImportError as e:
raise ImportError(
"gRPC mode requires the smg-grpc-servicer package. "
"If not installed, run: pip install smg-grpc-servicer[sglang]. "
"If already installed, there may be a broken import due to a "
"version mismatch — see the chained exception above for details."
) from e
이 구조가 흥미로운 점은, gRPC 구현체를 별도 패키지(smg-grpc-servicer)로 분리했다는 것이다. SGLang 코어는 HTTP 서버만 포함하고, gRPC는 선택적 의존성으로 설치한다. 이를 통해 gRPC가 필요 없는 단독 배포 환경에서 불필요한 의존성을 피할 수 있다.
Prometheus Metrics 통합
gRPC 모드에서는 HTTP 서버가 없으므로, Prometheus metrics를 별도의 경량 HTTP 서버로 노출한다.
async def _start_metrics_server(host: str, port: int):
from aiohttp import web
from prometheus_client import CollectorRegistry, multiprocess
from prometheus_client.openmetrics.exposition import (
CONTENT_TYPE_LATEST, generate_latest,
)
async def metrics_handler(request):
registry = CollectorRegistry()
multiprocess.MultiProcessCollector(registry)
data = generate_latest(registry)
return web.Response(
body=data,
headers={"Content-Type": CONTENT_TYPE_LATEST},
)
app = web.Application()
app.router.add_get("/metrics", metrics_handler)
매 요청마다 CollectorRegistry를 새로 생성하고 MultiProcessCollector를 붙이는 패턴이 사용된다. 이는 prometheus_client 공식 문서가 권장하는 multiprocess 환경 패턴으로, PROMETHEUS_MULTIPROC_DIR에서 최신 데이터를 읽어 올 수 있다. Metrics 서버는 기본적으로 gRPC 포트 + 1에서 동작한다.
metrics_port = (
server_args.metrics_http_port
if server_args.metrics_http_port is not None
else server_args.port + 1
)
metrics_runner = await _start_metrics_server(server_args.host, metrics_port)
gRPC 서비스 정의: Scheduler Protocol
SGLang의 gRPC 서비스는 sglang_scheduler.proto에 정의되어 있다. 생성된 Go 코드(sglang_scheduler_grpc.pb.go)를 통해 서비스 인터페이스를 확인할 수 있다.
// Service definition for SGLang scheduler communication
// This protocol bridges the Rust router and Python scheduler
type SglangSchedulerClient interface {
// Submit a generation request (supports streaming)
Generate(ctx context.Context, in *GenerateRequest, opts ...grpc.CallOption) (
grpc.ServerStreamingClient[GenerateResponse], error)
// Submit an embedding request
Embed(ctx context.Context, in *EmbedRequest, opts ...grpc.CallOption) (
*EmbedResponse, error)
// Health check and metrics
HealthCheck(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (
*HealthCheckResponse, error)
// Abort a running request
Abort(ctx context.Context, in *AbortRequest, opts ...grpc.CallOption) (
*AbortResponse, error)
// Get model information
GetModelInfo(ctx context.Context, in *GetModelInfoRequest, opts ...grpc.CallOption) (
*GetModelInfoResponse, error)
// Get server information
GetServerInfo(ctx context.Context, in *GetServerInfoRequest, opts ...grpc.CallOption) (
*GetServerInfoResponse, error)
}
6개의 RPC 메서드 중 Generate만 Server Streaming RPC이고, 나머지는 Unary RPC다. 주석이 명시하듯 이 프로토콜은 Rust 라우터와 Python Scheduler를 연결하는 브릿지 역할을 한다.
Streaming RPC: 실시간 토큰 전달
Generate RPC의 응답 메시지는 oneof 패턴으로 세 가지 타입을 구분한다.
type GenerateResponse struct {
RequestId string
// oneof response
Response isGenerateResponse_Response // Chunk | Complete | Error
}
type GenerateStreamChunk struct {
TokenIds []uint32 // 생성된 토큰 ID (증분)
PromptTokens int32 // 누적 프롬프트 토큰 수
CompletionTokens int32 // 누적 완성 토큰 수
CachedTokens int32 // 캐시 히트 토큰 수
OutputLogprobs *OutputLogProbs // 출력 로그 확률 (요청 시)
HiddenStates []float32 // 히든 스테이트 (요청 시)
InputLogprobs *InputLogProbs // 입력 로그 확률 (첫 청크만)
Index uint32 // n>1일 때 순서 식별
}
JSON 기반 SSE에서는 토큰을 텍스트로 직렬화해야 하지만, Protobuf에서는 TokenIds를 uint32 배열로 직접 전달한다. 이 차이가 고처리량 환경에서 유의미한 성능 차이를 만든다. CachedTokens 필드는 SGLang의 RadixAttention KV 캐시 히트 정보를 실시간으로 전달하는 데 활용된다.
분산 환경 활용: Encoder gRPC Server
python/sglang/srt/disaggregation/encode_grpc_server.py는 EPD(Encode-Prefill-Decode) 분리 모드에서 멀티모달 인코딩을 담당하는 gRPC 서버다.
class SGLangEncoderServer(SGLangEncoderServicer):
def __init__(self, encoder: MMEncoder, send_sockets: List[zmq.Socket],
server_args: ServerArgs):
self.encoder = encoder
self.send_sockets = send_sockets
self.server_args = server_args
async def Encode(self, request, context):
for socket in self.send_sockets:
await socket.send_pyobj(request_dict)
(nbytes, embedding_len, embedding_dim,
error_msg, error_code) = await self.encoder.encode(
mm_items=list(request.mm_items),
modality=Modality.IMAGE,
req_id=request.req_id,
num_parts=request.num_parts,
part_idx=request.part_idx,
)
이 구현에서 주목할 점은 gRPC와 ZeroMQ를 함께 사용하는 하이브리드 아키텍처다. gRPC는 외부 인터페이스(인코딩 요청 수신)를, ZeroMQ는 내부 프로세스 간 통신(TP 워커 간 조율)을 담당한다.
서버 초기화 코드를 보면 이 구조가 명확해진다.
async def serve_grpc_encoder(server_args: ServerArgs):
server = grpc.aio.server(
futures.ThreadPoolExecutor(max_workers=10),
options=[
("grpc.max_send_message_length", 1024 * 1024 * 256), # 256MB
("grpc.max_receive_message_length", 1024 * 1024 * 256),
],
)
# Health check + Reflection 등록
health_servicer = EncoderHealthServicer()
health_pb2_grpc.add_HealthServicer_to_server(health_servicer, server)
reflection.enable_server_reflection(SERVICE_NAMES, server)
메시지 크기 제한을 256MB로 설정한 것은, 멀티모달 임베딩 데이터가 대용량일 수 있기 때문이다. 또한 grpc.health.v1.Health 서비스와 Server Reflection을 함께 등록하여 Kubernetes 헬스 프로브와 동적 서비스 탐색을 지원한다.
SGLang Model Gateway: Rust gRPC 라우터
SGLang Model Gateway는 Rust로 구현된 고성능 라우터로, gRPC를 통해 Python Scheduler와 통신한다. sgl-model-gateway/src/routers/grpc/ 아래의 코드 구조를 보면 아키텍처가 드러난다.
// client.rs — Polymorphic gRPC client
pub enum GrpcClient {
Sglang(SglangSchedulerClient),
Vllm(VllmEngineClient),
}
SGLang뿐 아니라 vLLM 백엔드도 같은 gRPC 인터페이스로 추상화한다. 라우터는 RequestExecutionStage에서 Single(단일 워커) 또는 DualDispatch(Prefill + Decode 분리) 모드를 지원하며, gRPC Streaming을 통해 응답을 실시간으로 클라이언트에게 전달한다.
pub(crate) enum ExecutionMode {
Single, // 단일 워커 실행
DualDispatch, // PD 분리: prefill + decode 워커
}
왜 gRPC인가 -- 성능 비교
SGLang이 gRPC를 도입한 핵심 이유는 세 가지다.
1. 직렬화 효율성: GenerateRequest 메시지에는 SamplingParams(temperature, top_p, top_k 등 20개 이상 필드), TokenizedInput, MultimodalInputs 등이 포함된다. JSON으로 직렬화하면 필드명이 반복되지만, Protobuf는 필드 번호 기반 바이너리 인코딩을 사용해 메시지 크기가 3-10배 작다.
2. Streaming 성능: LLM의 토큰 생성 속도가 초당 수십-수백 토큰에 달하는 환경에서, SSE의 텍스트 파싱 오버헤드는 무시할 수 없다. gRPC Server Streaming은 HTTP/2 프레임 위에서 바이너리 메시지를 직접 전달하므로 토큰당 오버헤드가 최소화된다.
3. 다중 언어 연동: SGLang의 아키텍처는 Python(Scheduler), Rust(Router/Gateway), Go(Bindings)로 구성된다. Protobuf의 코드 생성으로 모든 언어에서 동일한 타입 정의를 공유하며, 인터페이스 불일치로 인한 버그를 컴파일 타임에 차단한다.
관련 포스트
참고
관련 포스트
SGLang 의 다른글
- 이전글 [SGLang] Anthropic/Ollama 호환 API: 멀티 프로토콜 LLM 서빙
- 현재글 : [SGLang] gRPC 서버: 분산 추론을 위한 고성능 통신 계층
- 다음글 [SGLang] Function Calling & Tool Use: 20+ 모델별 포맷 파서 구현
댓글