본문 바로가기

AI 개발

vLLM vs SGLang: 프로덕션 LLM 서빙 프레임워크 완전 비교

반응형

 

DeepSeek V4가 MIT 라이선스로 공개되면서 자체 서버에 LLM을 올리려는 팀이 급증했습니다. 그 순간 반드시 마주치는 질문이 하나 있습니다. vLLM이냐 SGLang이냐. 둘 다 오픈소스에 OpenAI 호환 API를 제공하는데, 무엇을 골라야 하는지 기준이 없으면 잘못된 선택을 하고 나중에 마이그레이션하게 됩니다. 직접 숫자를 뜯어보고 정리했습니다.


핵심 요약

두 프레임워크를 한 줄로 정리하면, vLLM은 프로덕션 기본값이고 SGLang은 멀티턴·에이전트 워크로드에서 더 빠른 대안입니다.

vLLM은 PagedAttention으로 GPU 메모리를 가상 메모리처럼 관리합니다. 기존 방식이 KV 캐시에 연속 메모리 블록을 할당해서 60~80%를 낭비했다면, vLLM은 16토큰 단위 페이지로 쪼개서 단편화를 4% 미만으로 줄입니다. 덕분에 같은 하드웨어에서 더 많은 동시 요청을 처리합니다. 하드웨어 지원 폭이 가장 넓고 모델 호환성이 높아서 처음 LLM 서버를 올린다면 vLLM이 무조건 먼저입니다.

SGLang은 RadixAttention이 핵심 차별점입니다. vLLM이 요청이 끝나면 KV 캐시를 버리는 반면, SGLang은 LRU 캐시를 Radix 트리 구조로 유지합니다. 다음 요청이 오면 프리픽스 매칭을 해서 겹치는 계산을 재사용합니다. 시스템 프롬프트나 RAG 컨텍스트처럼 요청 간 공유되는 앞부분이 길수록 효과가 커집니다.

벤치마크는 명확합니다. H100에서 Llama 3.1 8B 기준 SGLang이 초당 16,200토큰, vLLM이 12,500토큰으로 SGLang이 29% 앞섭니다. 프리픽스를 많이 공유하는 워크로드에서는 격차가 최대 6.4배까지 벌어집니다. 하루 100만 요청 기준으로 SGLang 전환만으로 월 GPU 비용을 약 $15,000 절감할 수 있다는 계산이 나옵니다.

TGI(Text Generation Inference)는 2025년 12월부터 버그 픽스만 받는 상태라 신규 프로젝트에서는 선택지에서 제외됩니다. HuggingFace Inference Endpoints도 기본값을 vLLM으로 전환했습니다.


실전 1: vLLM 설치와 기본 서버 실행

vLLM은 pip 설치가 가장 간단하고, Docker 이미지로도 제공됩니다. CUDA 12.x와 Python 3.10 이상이 필요합니다. 설치 후 vllm serve 한 줄로 OpenAI 호환 API 서버가 뜹니다.

일반 모델 기준 설치와 서버 실행입니다. GPU 메모리 사용률은 0.90이 안전한 시작점이고, 더 올리면 처리량이 늘지만 OOM 위험이 커집니다.

# vLLM 설치 (CUDA 환경)
pip install vllm>=0.9.0

# 단일 GPU — 소형 모델 (8B 이하)
vllm serve Qwen/Qwen3.6-27B \
  --host 0.0.0.0 \
  --port 8000 \
  --dtype bfloat16 \
  --max-model-len 65536 \
  --gpu-memory-utilization 0.90

# 멀티 GPU — 텐서 병렬 (27B, A100 2장)
vllm serve Qwen/Qwen3.6-27B \
  --tensor-parallel-size 2 \
  --dtype bfloat16 \
  --max-model-len 131072 \
  --gpu-memory-utilization 0.90 \
  --port 8000

# DeepSeek V4-Flash (H200 2장, FP8 양자화)
vllm serve deepseek-ai/DeepSeek-V4-Flash \
  --trust-remote-code \
  --tensor-parallel-size 2 \
  --kv-cache-dtype fp8 \
  --max-model-len 131072 \
  --gpu-memory-utilization 0.90 \
  --tokenizer-mode deepseek_v4 \
  --enable-auto-tool-choice \
  --tool-call-parser deepseek_v4

# 서버 동작 확인
curl http://localhost:8000/v1/models

서버가 뜨면 OpenAI 클라이언트로 바로 연결됩니다. base_url만 바꾸면 기존 코드가 그대로 동작합니다.

from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="not-needed"  # vLLM은 기본적으로 인증 없음
)

response = client.chat.completions.create(
    model="Qwen/Qwen3.6-27B",
    messages=[{"role": "user", "content": "Python으로 퀵소트 구현해줘"}],
    max_tokens=1024,
    temperature=0.7
)
print(response.choices[0].message.content)

실전 2: SGLang 설치와 서버 실행

SGLang은 Docker 이미지를 권장합니다. Python 패키지로도 설치되지만 커스텀 CUDA 커널 의존성 때문에 Docker가 훨씬 안정적입니다. 포트는 30000이 기본값이고, OpenAI 호환 엔드포인트가 /v1에 뜹니다.

# Docker로 SGLang 설치 (권장)
docker pull lmsysorg/sglang:latest

# 단일 GPU 실행
docker run --gpus all \
  --shm-size 32g \
  -p 30000:30000 \
  -v ~/.cache/huggingface:/root/.cache/huggingface \
  lmsysorg/sglang:latest \
  python -m sglang.launch_server \
    --model-path Qwen/Qwen3.6-27B \
    --host 0.0.0.0 \
    --port 30000 \
    --dtype bfloat16 \
    --context-length 131072

# pip 설치 (간단하지만 커널 이슈 가능성 있음)
pip install sglang[all]>=0.4.4

# 멀티 GPU (텐서 병렬)
python -m sglang.launch_server \
  --model-path Qwen/Qwen3.6-27B \
  --tp 2 \
  --dtype bfloat16 \
  --context-length 131072 \
  --port 30000

# DeepSeek V4-Flash (SGLang, RadixAttention 자동 활성화)
python -m sglang.launch_server \
  --model-path deepseek-ai/DeepSeek-V4-Flash \
  --trust-remote-code \
  --tp 2 \
  --quantization fp4_mixed \
  --context-length 131072 \
  --port 30000 \
  --enable-torch-compile  # Hopper에서 10~20% 추가 성능

SGLang도 OpenAI 호환이라 클라이언트 코드는 포트만 다를 뿐 vLLM과 동일합니다.

from openai import OpenAI

# SGLang 클라이언트 (포트 30000)
client = OpenAI(
    base_url="http://localhost:30000/v1",
    api_key="not-needed"
)

# RAG 파이프라인처럼 동일한 시스템 프롬프트를 반복 사용하는 경우
# SGLang의 RadixAttention이 프리픽스를 캐싱해서 두 번째 요청부터 빠름
system_prompt = "당신은 코딩 전문 AI 어시스턴트입니다. " * 100  # 긴 시스템 프롬프트

for question in ["Python 퀵소트", "이진 탐색 구현", "동적 프로그래밍 설명"]:
    response = client.chat.completions.create(
        model="Qwen/Qwen3.6-27B",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": question}
        ],
        max_tokens=512
    )
    # 두 번째, 세 번째 요청은 시스템 프롬프트 KV 캐시를 재사용
    print(f"Q: {question}")
    print(f"A: {response.choices[0].message.content[:100]}\n")

실전 3: 처리량 벤치마크 직접 돌리기

어느 프레임워크가 내 워크로드에서 더 빠른지는 직접 측정해야 합니다. 공개 벤치마크는 하드웨어와 모델이 달라서 참고만 하고, 아래 스크립트로 실제 환경에서 확인하는 게 낫습니다.

vLLM에는 내장 벤치마크 스크립트가 있고, SGLang도 비슷한 도구를 제공합니다. 두 서버를 동시에 띄우고 동일한 요청을 보내면 직접 비교할 수 있습니다.

import asyncio
import time
from openai import AsyncOpenAI

# 두 서버 클라이언트
vllm_client = AsyncOpenAI(base_url="http://localhost:8000/v1", api_key="x")
sglang_client = AsyncOpenAI(base_url="http://localhost:30000/v1", api_key="x")

# 공유 프리픽스가 있는 테스트 케이스 (RAG 시뮬레이션)
SHARED_CONTEXT = "다음은 회사 기술 문서입니다: " + "내용 " * 500
QUESTIONS = [f"질문 {i}: 이 문서의 핵심은 무엇인가요?" for i in range(20)]

async def benchmark(client, model: str, name: str, n_concurrent: int = 10):
    async def single_request(question: str):
        start = time.time()
        resp = await client.chat.completions.create(
            model=model,
            messages=[
                {"role": "system", "content": SHARED_CONTEXT},
                {"role": "user", "content": question}
            ],
            max_tokens=256
        )
        elapsed = time.time() - start
        tokens = resp.usage.completion_tokens
        return tokens / elapsed  # 토큰/초

    start = time.time()
    # 동시 요청
    tasks = [single_request(q) for q in QUESTIONS[:n_concurrent]]
    tps_list = await asyncio.gather(*tasks)
    total_elapsed = time.time() - start

    avg_tps = sum(tps_list) / len(tps_list)
    print(f"[{name}] 평균 {avg_tps:.0f} tok/s | 총 {total_elapsed:.1f}s | {n_concurrent}개 동시 요청")

async def main():
    print("=== 처리량 비교 (공유 프리픽스 있음) ===")
    await benchmark(vllm_client, "Qwen/Qwen3.6-27B", "vLLM")
    await benchmark(sglang_client, "Qwen/Qwen3.6-27B", "SGLang")

asyncio.run(main())

공유 프리픽스가 긴 워크로드(RAG, 에이전트 루프, 멀티턴 대화)에서는 SGLang이 확실히 앞서고, 각 요청이 완전히 다른 고유 프롬프트라면 둘의 차이가 노이즈 수준으로 줄어듭니다.


실전 4: 언제 무엇을 쓸지

두 프레임워크의 선택 기준을 정리하면 이렇습니다.

vLLM이 맞는 경우는 처음 LLM 서버를 올릴 때, 다양한 모델을 바꿔가며 테스트할 때, TPU나 AMD ROCm 같은 비NVIDIA 하드웨어를 써야 할 때, 배치 처리처럼 요청마다 프롬프트가 완전히 다른 워크로드일 때입니다. 커뮤니티가 3배 크고 공식 레시피가 많아서 막히면 도움받기 쉽습니다.

SGLang이 맞는 경우는 챗봇이나 멀티턴 대화처럼 대화 히스토리가 쌓이는 서비스, RAG 파이프라인처럼 문서 컨텍스트가 반복되는 환경, 에이전트 루프처럼 시스템 프롬프트가 길고 고정된 워크로드, JSON 스키마나 정규식 제약 조건을 걸어야 하는 구조화된 출력이 필요할 때입니다. DeepSeek V4처럼 대형 MoE를 올릴 때도 SGLang의 RadixAttention과 하이브리드 어텐션 구조가 잘 맞습니다.

# 두 프레임워크를 같이 쓰는 실전 라우팅 패턴
# LiteLLM으로 워크로드에 따라 자동 라우팅

# litellm_config.yaml
# model_list:
#   - model_name: chat-model         # 멀티턴/에이전트 → SGLang
#     litellm_params:
#       model: openai/Qwen3.6-27B
#       api_base: http://sglang-server:30000/v1
#   - model_name: batch-model        # 배치/고유 프롬프트 → vLLM
#     litellm_params:
#       model: openai/Qwen3.6-27B
#       api_base: http://vllm-server:8000/v1

pip install litellm
litellm --config litellm_config.yaml --port 4000

마무리

vLLM과 SGLang은 2026년 기준 프로덕션 LLM 서빙의 양강입니다. 처음 시작하거나 워크로드가 명확하지 않다면 vLLM부터 올리는 게 맞습니다. 챗봇이나 에이전트 파이프라인을 올리고 있고 GPU 비용이 의미 있게 나온다면, SGLang으로 전환 실험을 해볼 시점입니다. 동일 하드웨어에서 30% 처리량 향상은 GPU 대수를 줄이거나 같은 비용으로 더 많은 트래픽을 처리할 수 있다는 뜻입니다. DeepSeek V4처럼 신규 대형 모델은 두 프레임워크 모두 Day-0 지원이 표준이 됐으니, 모델 선택보다 워크로드 패턴이 프레임워크 선택의 기준입니다.

 

반응형