vLLM SGLang 성능 비교 — 2026년 KV 캐시 구조로 이해하는 추론 엔진 선택
LLM을 직접 서빙해본 적 있으신가요? 처음엔 모델 선택이 전부인 줄 알았는데, 막상 프로덕션에 올리고 나면 추론 엔진 선택이 성능과 비용을 완전히 갈라놓는다는 걸 뼈저리게 느끼게 됩니다. 저도 초반에 "그냥 vLLM 쓰면 되는 거 아냐?" 하고 넘어갔다가, 멀티턴 RAG 파이프라인에서 한 달 청구서가 예상의 2.3배로 나오고서야 진지하게 두 엔진을 비교해봤거든요. 알고 보니 문제는 모델이 아니라, 반복되는 시스템 프롬프트와 문서 컨텍스트를 매 요청마다 처음부터 다시 계산하고 있었던 겁니다.
vLLM과 SGLang은 모두 UC Berkeley에서 출발한 오픈소스 LLM 추론 엔진입니다. 얼핏 보면 역할이 비슷해 보이지만, 내부 KV 캐시 관리 방식이 근본적으로 달라서 워크로드 특성에 따라 성능 차이가 최대 6배까지 벌어집니다. 이 글에서는 두 엔진의 핵심 차이를 이해하고, 내 상황에 맞는 엔진을 자신 있게 선택할 수 있는 판단 기준을 얻어가실 수 있습니다.
왜 두 엔진이 서로 다른 워크로드에서 이렇게 다른 결과를 내는지, 그 이유를 하나씩 풀어볼게요.
핵심 개념
PagedAttention vs RadixAttention — KV 캐시를 보는 두 가지 시각
두 엔진의 가장 핵심적인 차이는 KV(Key-Value) 캐시를 어떻게 관리하느냐입니다. 솔직히 이 부분만 이해하면 나머지 선택 기준은 자연스럽게 따라옵니다.
KV 캐시(Key-Value Cache): Transformer 모델이 이전 토큰들을 처리할 때 계산한 Key, Value 행렬을 저장해두는 메모리 공간입니다. 이걸 재사용하면 이미 처리한 부분을 다시 계산하지 않아도 되므로 처리 속도가 크게 향상됩니다.
vLLM의 PagedAttention은 운영체제의 가상 메모리 페이징에서 영감을 받았습니다. GPU 메모리를 고정 크기의 페이지 단위로 분할해서 비연속적으로 할당하는 방식이에요. 덕분에 메모리 단편화가 크게 줄고, 서로 다른 길이의 요청들이 메모리를 효율적으로 나눠 쓸 수 있습니다. 단, 각 요청은 독립적인 페이지를 받기 때문에 요청 간에 공통 내용이 있어도 다시 계산합니다.
[vLLM - PagedAttention 개념]
요청 A: [페이지1][페이지2][페이지3]
요청 B: [페이지4][페이지5]
요청 C: [페이지6][페이지7][페이지8][페이지9]
→ 각 요청은 독립적인 메모리 페이지를 할당받음
→ 공통 프리픽스 공유 없음, 각 요청은 처음부터 다시 계산반면 SGLang의 RadixAttention은 래딕스 트리(Radix Tree) 구조로 KV 캐시를 관리합니다.
래딕스 트리(Radix Tree): 공통 접두사(prefix)를 공유하는 트리 구조입니다. 사전을 알파벳 순으로 정렬해서 "car", "card", "care"를 저장할 때 "car"를 한 번만 두고 d와 e만 분기하는 것과 유사합니다. 토큰 시퀀스에 적용하면, 공통으로 시작하는 토큰 묶음을 한 번만 계산해두고 여러 요청이 공유할 수 있습니다.
핵심은 여러 요청에서 공통된 프리픽스(앞부분 토큰 시퀀스)를 자동으로 감지해서 재사용한다는 거예요. 시스템 프롬프트나 문서 컨텍스트처럼 반복되는 부분이 있다면, 단 한 번만 계산하고 나머지 요청은 그 캐시를 그대로 가져다 씁니다.
[SGLang - RadixAttention 트리 구조]
시스템 프롬프트: "You are a helpful assistant. [문서 A]"
│ (한 번만 계산, 캐시에 저장)
┌─────────────┼─────────────┐
요청 A 요청 B 요청 C
"핵심 주장은?" "반론은?" "결론 요약해줘"
│ │ │
(추가 토큰만) (추가 토큰만) (추가 토큰만)
새로 계산 새로 계산 새로 계산
→ 공통 부분(시스템 프롬프트+문서)은 한 번만 계산
→ 각 요청의 고유 부분만 새로 처리숫자로 보는 차이
2026년 기준 H100 GPU, Llama-3.1-8B 모델 기준 벤치마크입니다. 70B+ 대형 모델에서는 격차가 3~5%로 줄어드니 모델 크기에 따라 해석이 달라질 수 있습니다.
| 지표 | vLLM | SGLang | 비고 |
|---|---|---|---|
| 처리량 (tok/s, H100 / 8B) | ~12,600 | ~16,200 | SGLang 약 29% 우위 |
| 프리픽스 공유 워크로드 | 기준 | 최대 6.4배 | RadixAttention 효과 |
| TTFT p95 | 기준 | 5~8% 낮음 | 첫 토큰 생성 시간 |
| 고동시성 (100+ 요청) | ~4,741 tok/s | 측정 환경 차이로 직접 비교 어려움 | vLLM C++ 라우팅 우위 |
| 모델 지원 범위 | 매우 넓음 | 상대적으로 좁음 | vLLM 우위 |
| 하드웨어 지원 | GPU, TPU, Trainium, Gaudi | GPU 중심 | vLLM 우위 |
H100에서 16,200 tok/s라는 숫자가 와닿지 않을 수 있는데, 실무로 환산하면 100명이 동시에 요청해도 1인당 162 tok/s 처리가 가능하다는 의미입니다. 한 문장이 평균 20토큰이라고 하면 초당 8문장 이상 생성하는 속도예요.
TTFT(Time To First Token): 사용자가 요청을 보내고 첫 번째 토큰이 반환되기까지 걸리는 시간. 스트리밍 응답에서 체감 반응성을 결정하는 핵심 지표입니다.
고동시성 상황에서 vLLM이 안정적인 이유는 내부 라우팅을 C++로 구현했기 때문입니다. Python은 GIL(Global Interpreter Lock) 때문에 여러 스레드가 동시에 실행되지 못하는 구조적 한계가 있는데, C++은 이 제약이 없어서 100개 이상의 동시 요청이 들어와도 처리 대기열 관리 자체에서 병목이 생기지 않습니다.
실전 적용
예시 1: 멀티턴 RAG 파이프라인에서 SGLang 활용
이게 바로 제가 엔진을 바꾼 계기였던 시나리오입니다. RAG 파이프라인에서는 검색된 문서 컨텍스트가 여러 사용자 요청에 걸쳐 반복되는 경우가 많아요. "이 문서를 기반으로 질문에 답해줘" 같은 패턴인데, vLLM을 쓰면 같은 문서를 100번 질문하면 100번 전부 다시 계산합니다.
# SGLang 서버 실행 (멀티턴 RAG에 최적화된 설정)
# python -m sglang.launch_server \
# --model-path meta-llama/Llama-3.1-8B-Instruct \
# --host 0.0.0.0 \
# --port 30000 \
# --mem-fraction-static 0.9 \
# --max-prefill-tokens 16384
import openai
client = openai.OpenAI(
base_url="http://localhost:30000/v1",
api_key="EMPTY",
)
SHARED_CONTEXT = (
"당신은 문서 분석 전문가입니다.\n\n"
"[참고 문서]\n"
"{document_content}"
)
def ask_question(document: str, question: str, history: list) -> str:
messages = [
{
"role": "system",
"content": SHARED_CONTEXT.format(document_content=document),
},
*history,
{"role": "user", "content": question},
]
response = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=messages,
max_tokens=512,
)
return response.choices[0].message.content
# 실제 문서 내용 (긴 보고서, 논문 등을 여기에 넣으면 됩니다)
sample_doc = (
"2024년 글로벌 AI 시장 규모는 약 2,000억 달러로 추산된다. "
"주요 성장 동력은 생성형 AI 서비스의 대중화와 클라우드 인프라 확장이다. "
"북미가 시장의 40%를 점유하며, 아시아태평양 지역의 성장세가 두드러진다."
)
history = []
questions = ["이 문서의 핵심 주장은?", "반론은 있나요?", "결론을 요약하면?"]
for question in questions:
answer = ask_question(sample_doc, question, history)
history.extend([
{"role": "user", "content": question},
{"role": "assistant", "content": answer},
])
print(f"Q: {question}\nA: {answer}\n")| 코드 포인트 | 설명 |
|---|---|
SHARED_CONTEXT |
모든 요청에 공통으로 들어가는 부분 → RadixAttention이 자동 캐싱 |
history 누적 |
멀티턴 컨텍스트가 길어질수록 캐시 히트율 상승 |
base_url 변경 |
OpenAI SDK 그대로 사용, URL만 바꾸면 됨 |
mem-fraction-static 0.9 |
캐시 풀 크기 확대 — 더 키울수록 캐시 재사용 효과 증가 |
같은 문서를 100명이 반복 질문하면 시스템 프롬프트+문서 컨텍스트는 단 한 번만 계산되고 나머지는 캐시에서 가져옵니다. 실제로 이 패턴에서 GPU 사용량을 약 30% 줄인 사례가 보고되고 있어요.
예시 2: 대규모 배치 문서 분류에서 vLLM 활용
프롬프트 구조는 고정이지만 각 문서 내용이 완전히 다른 경우, 즉 캐시 재사용 여지가 없는 워크로드에서는 vLLM이 빛납니다.
# vLLM 서버 실행 (배치 처리 최적화)
# python -m vllm.entrypoints.openai.api_server \
# --model meta-llama/Llama-3.1-70B-Instruct \
# --tensor-parallel-size 4 \
# --max-model-len 8192 \
# --gpu-memory-utilization 0.95
import asyncio
import openai
client = openai.AsyncOpenAI(
base_url="http://localhost:8000/v1",
api_key="EMPTY",
)
CLASSIFICATION_PROMPT = (
"다음 텍스트를 아래 카테고리 중 하나로 분류해주세요.\n"
"카테고리: [기술, 비즈니스, 스포츠, 엔터테인먼트, 정치]\n\n"
"텍스트:\n{text}\n\n"
"카테고리만 한 단어로 답변해주세요."
)
async def classify_document(text: str) -> str:
response = await client.chat.completions.create(
model="meta-llama/Llama-3.1-70B-Instruct",
messages=[{
"role": "user",
"content": CLASSIFICATION_PROMPT.format(text=text),
}],
max_tokens=10,
temperature=0,
)
return response.choices[0].message.content.strip()
async def batch_classify(documents: list[str]) -> list[str]:
tasks = [classify_document(doc) for doc in documents]
return await asyncio.gather(*tasks)
# 각각 고유한 내용을 가진 문서 목록 (실제 사용 시 파일에서 로드)
documents = [
"삼성전자가 3나노 공정 칩 양산에 성공했다고 발표했다.",
"손흥민이 해트트릭을 기록하며 팀을 승리로 이끌었다.",
"연방준비제도가 기준금리를 0.25%p 인하했다.",
]
results = asyncio.run(batch_classify(documents))
for doc, label in zip(documents, results):
print(f"[{label}] {doc[:30]}...")| 코드 포인트 | 설명 |
|---|---|
tensor-parallel-size 4 |
70B 모델을 4 GPU에 분산 — vLLM의 광범위한 병렬화 지원 |
asyncio.gather |
요청을 동시 발송, vLLM의 continuous batching이 내부에서 묶어 처리 |
max_tokens=10 |
짧은 출력 길이 → throughput 극대화 |
| 각 요청의 고유 컨텍스트 | 캐시 재사용 여지 없음 → PagedAttention의 메모리 효율이 핵심 |
예시 3: DeepSeek 모델 서빙 — SGLang이 사실상 표준
DeepSeek-R1이나 DeepSeek-V3를 서빙한다면 SGLang을 강력하게 권장합니다. SGLang은 DeepSeek 계열에 대한 전용 최적화를 내장하고 있어서 vLLM 대비 유의미한 차이를 보여줍니다.
세 가지 핵심 최적화를 간단히 짚으면:
- MLA(Multi-head Latent Attention): DeepSeek이 개발한 KV 캐시 압축 기법. Key와 Value를 저차원 잠재 공간으로 투영해서 캐시 메모리를 대폭 줄입니다.
- MoE(Mixture of Experts): 모델의 전체 파라미터 중 일부 "전문가" 네트워크만 토큰별로 선택적으로 활성화하는 구조. 큰 모델 용량을 유지하면서 실제 연산량은 적게 유지합니다.
- Elastic Expert Parallelism: MoE 전문가 레이어를 GPU 간에 동적으로 재분배하는 전략. 특정 GPU에 장애가 나도 워크로드를 자동으로 재조정해서 서빙이 중단되지 않습니다.
# DeepSeek-R1 서버 실행 (SGLang 권장 설정)
python -m sglang.launch_server \
--model-path deepseek-ai/DeepSeek-R1 \
--trust-remote-code \
--tp 8 \
--dp 2 \
--host 0.0.0.0 \
--port 30000 \
--reasoning-parser deepseek-r1
# MLA 어텐션 백엔드 자동 활성화
# Elastic Expert Parallelism으로 MoE 장애 허용서버가 뜨면 Python에서 추론 결과를 이렇게 받을 수 있습니다. DeepSeek-R1은 <think> 태그 안에 추론 과정을 담아 반환하는 게 특징이에요.
import openai
client = openai.OpenAI(
base_url="http://localhost:30000/v1",
api_key="EMPTY",
)
response = client.chat.completions.create(
model="deepseek-ai/DeepSeek-R1",
messages=[{
"role": "user",
"content": (
"다음 코드에서 버그를 찾고 수정해줘:\n\n"
"def factorial(n):\n"
" if n == 0:\n"
" return 0\n"
" return n * factorial(n-1)"
),
}],
max_tokens=2048,
temperature=0.6,
)
full_response = response.choices[0].message.content
# <think> 태그로 감싸진 추론 과정과 최종 답변 분리
if "<think>" in full_response:
think_end = full_response.find("</think>")
reasoning = full_response[7:think_end] # <think> 이후
answer = full_response[think_end + 8:].strip() # </think> 이후
print(f"[추론 과정]\n{reasoning}\n")
print(f"[최종 답변]\n{answer}")
else:
print(full_response)| 코드 포인트 | 설명 |
|---|---|
--tp 8 --dp 2 |
텐서 병렬 8 + 데이터 병렬 2, DeepSeek-R1 규모에서 권장 조합 |
--reasoning-parser deepseek-r1 |
<think> 태그 파싱 활성화, 추론 과정과 답변 분리 가능 |
think_end = find("</think>") |
R1의 추론 체인을 별도로 로깅하거나 디버깅할 때 유용 |
장단점 분석
장점
vLLM
| 항목 | 내용 |
|---|---|
| 모델 지원 범위 | 업계에서 가장 넓음. Meta, Mistral, Cohere, Google 등 거의 모든 오픈소스 모델 |
| 하드웨어 다양성 | NVIDIA GPU, TPU, AWS Trainium, Intel Gaudi 모두 지원 |
| 커뮤니티 성숙도 | 컨트리뷰터 수 SGLang의 약 3배, 문서화·생태계 안정적 |
| OpenAI 호환성 | URL 하나만 바꾸면 기존 OpenAI SDK 앱이 그대로 동작 |
| 고동시성 안정성 | C++ 라우팅으로 100+ 동시 요청에서도 안정적 처리량 유지 |
| Speculative Decoding | EAGLE, MTP 등 다양한 spec decode 지원으로 1.3~2배 속도 향상 |
SGLang
| 항목 | 내용 |
|---|---|
| 프리픽스 재사용 워크로드 | 동일 컨텍스트 반복 시 최대 6.4배 성능, GPU 메모리 최대 30% 절감 |
| 원시 처리량 | H100 / 8B 기준 16,200 tok/s로 vLLM 대비 약 29% 우위 |
| 구조화 출력 | FSM 기반 constrained decoding으로 JSON·정규식 출력 보장 |
| DeepSeek 최적화 | DeepSeek 계열 모델의 사실상 표준 서빙 엔진 |
| TTFT | p95 기준 첫 토큰 생성 시간 5~8% 단축 |
| 클라우드 지원 | AWS, GCP, Azure, Oracle Cloud 공식 지원 |
Constrained Decoding(제약 디코딩): 모델 출력을 특정 포맷(JSON 스키마, 정규식 등)에 맞도록 강제하는 기술. 유한 상태 머신(FSM)으로 각 토큰 생성 단계에서 유효한 토큰만 허용합니다. 함수 호출·에이전트 워크플로우에서 파싱 오류를 원천 차단할 수 있습니다.
Speculative Decoding: 소형 "드래프트 모델"이 여러 토큰을 미리 예측하고, 대형 모델이 이를 한 번에 검증하는 방식. 출력 품질을 유지하면서 처리 속도를 1.3~2배 향상시킵니다.
단점 및 주의사항
vLLM
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 프리픽스 공유 한계 | 고정 컨텍스트 반복 워크로드에서 SGLang 대비 최대 29% 낮은 처리량 | 해당 워크로드라면 SGLang 전환 검토 |
| 멀티턴 캐시 효율 | 긴 대화 히스토리에서 매 요청마다 재계산 발생 | SGLang으로 마이그레이션 고려 |
SGLang
솔직히 이 단점 목록만 보면 SGLang이 밀리는 것 같지만, 실제로 DeepSeek 서빙이나 RAG 워크로드에서 체감 차이는 꽤 큽니다.
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 모델 지원 범위 | vLLM보다 지원 모델 수 적음 | 사용 모델이 지원 목록에 있는지 먼저 확인 |
| 하드웨어 제한 | TPU, Trainium, Gaudi 지원 초기 단계 | NVIDIA GPU 환경이면 문제없음 |
| 커뮤니티 규모 | vLLM 대비 상대적으로 작음 | 2025년부터 주요 클라우드 공식 지원, 빠르게 성장 중 |
| 운영 성숙도 | 빠른 업데이트 주기로 API 변경 잦음 | 버전 고정 후 점진적 업그레이드 권장 |
어떤 엔진을 선택할까 — 의사결정 흐름
본문을 다 읽고 "그래서 나는 뭘 써야 하지?"라는 생각이 든다면, 아래 흐름을 참고해보시면 좋습니다.
내 워크로드에 공통 시스템 프롬프트/문서 컨텍스트가 반복되나요?
│
├─ Yes → 고정 컨텍스트가 전체 프롬프트의 30% 이상인가요?
│ ├─ Yes → SGLang ✓ (RadixAttention 효과 극대화)
│ └─ No → DeepSeek 계열 모델을 사용하나요?
│ ├─ Yes → SGLang ✓ (전용 최적화 내장)
│ └─ No → 둘 다 비슷, vLLM이 운영하기 편함
│
└─ No → 동시 요청이 100개 이상인가요?
├─ Yes → vLLM ✓ (C++ 라우팅 안정성)
└─ No → GPU/TPU 혼합 환경이거나 모델을 자주 바꾸나요?
├─ Yes → vLLM ✓ (하드웨어·모델 다양성)
└─ No → 처리량 우선이면 SGLang,
생태계 안정성 우선이면 vLLM실무에서 가장 흔한 실수
저도 비슷한 실수를 겪었기 때문에 공유드립니다.
-
워크로드 분석 없이 엔진 선택하기 — 저도 처음에 "vLLM이 유명하니까"로 결정했다가 나중에 멀티턴 RAG로 전환하면서 배포 파이프라인을 처음부터 다시 구성해야 했습니다. 어떤 패턴의 요청이 들어올지를 먼저 파악한 다음 엔진을 고르는 순서가 맞습니다.
-
벤치마크를 내 워크로드가 아닌 범용 수치로 판단하기 — 7B
8B 모델에서는 SGLang 우위가 뚜렷하지만, 70B+ 대형 모델에서는 격차가 35%로 줄어듭니다. 범용 벤치마크보다 내 모델 크기와 요청 패턴에 맞는 수치를 직접 측정해보시는 걸 권장합니다.locust나wrk로 실제 트래픽 패턴을 재현해서 TTFT, 처리량, GPU 사용률을 직접 보면 훨씬 명확해집니다. -
고동시성 상황을 간과하기 — 저도 이 부분을 놓쳤는데, 단순 처리량 수치만 보고 결정하면 프로덕션에서 당황할 수 있습니다. 100개 이상의 동시 요청 환경에서는 vLLM의 C++ 라우팅이 유리한 경우가 있어서, 예상 동시 사용자 수를 함께 고려해야 합니다.
마치며
워크로드가 엔진을 결정합니다. 단발성 배치라면 vLLM, 반복 컨텍스트·에이전트라면 SGLang으로 시작하세요.
지금 바로 시작해볼 수 있는 3단계:
-
내 워크로드 패턴 파악하기: 요청들 사이에 공통 프리픽스(시스템 프롬프트, 문서 컨텍스트)가 얼마나 반복되는지 확인해보시면 좋습니다. 고정된 컨텍스트가 전체 프롬프트의 30% 이상이라면 SGLang이 유리할 가능성이 높습니다.
-
로컬에서 양쪽 엔진 띄워보기:
pip install vllm과pip install sglang[all]으로 각각 설치한 뒤, OpenAI 호환 API 모드로 실행해볼 수 있습니다. 두 엔진 모두base_url만 바꾸면 기존 코드가 그대로 동작합니다. -
실제 트래픽 패턴으로 벤치마크 돌리기:
locust나wrk로 실제 요청 패턴을 재현해서 TTFT, 처리량, GPU 사용률을 직접 측정해보시는 것을 권장합니다. 범용 벤치마크보다 내 상황에 맞는 수치가 훨씬 가치 있습니다.
참고 자료
- SGLang vs vLLM in 2026: Which Inference Engine Wins? | Kanerika
- vLLM vs SGLang: Which Inference Engine Should You Use in 2026? | Yotta Labs
- SGLang vs vLLM in 2026: Benchmarks, Architecture, and When to Use Each | Particula
- vLLM vs SGLang vs LMDeploy: Fastest LLM Inference Engine in 2026 | n1n.ai
- vLLM vs TensorRT-LLM vs SGLang: H100 Benchmarks (2026) | Spheron
- When to Choose SGLang Over vLLM: Multi-Turn Conversations and KV Cache Reuse | RunPod
- Benchmark: SGLang vs. vLLM Scaling under High Concurrency | GitHub Issues
- SGLang GitHub Repository
- vLLM Official Documentation
- Performance improvements with speculative decoding in vLLM | Red Hat Developer
- SGLang Development Roadmap 2026 Q1 | GitHub
- Benchmarking LLM Inference: vLLM vs SGLang vs Ollama on NVIDIA Blackwell | Joshua8.AI