상태: 초안 (2026-10-07 갱신), 일부 실측 반영. 공식 문서로 확인한 서술을 바탕으로 쓴 글이며, 직접 잰 실측은 04편의 실험을 인용한 부분과 5절 끝의 「실측: 같은 모델, 같은 GPU 에서의 vLLM 대 SGLang」(작은 모델 한 개, GPU 한 장)뿐입니다. 큰 모델, 다중 GPU, 다른 워크로드에서의 비교는 미실측입니다. 글 끝의 「확인한 버전과 날짜」와 「확인하지 못한 것」을 먼저 읽으십시오. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」으로 표시합니다.
vLLM 과 SGLang 비교, 그리고 벤치마크를 제대로 하는 법
두 엔진을 고르는 일은 취향이 아니라 워크로드 모양과 측정의 문제입니다. 이 글은 vLLM 과 SGLang 이 접두사 캐시를 어떻게 다르게 설계했는지, 지금은 어디까지 수렴했는지, 워크로드별로 무엇을 먼저 의심할지를 정리하고, 그 판단을 뒷받침하는 벤치마크를 어떻게 설계하고 읽는지를 다룹니다. 글쓴이는 SGLang 을 직접 운영하거나 측정한 적이 없습니다. 두 엔진의 우열에 대한 자체 실측은 없고, 공식 문서, 소스, 논문과 필자의 실험 환경에서 vLLM 으로 잰 경험만 근거로 씁니다.
이 글을 읽고 나면 면접에서 설명할 수 있는 것
- 블록 해시 사슬(vLLM)과 라딕스 트리(SGLang)의 차이, 그리고 지금은 기능이 많이 겹친다는 점을 구분해 말할 수 있습니다.
- 접두사 재사용, 구조화 출력, 모델 지원 같은 워크로드 모양별 선택 기준과 그 한계를 말할 수 있습니다.
- 열린 루프 부하, 포아송 도착, 접두사 캐시 함정, 퍼센타일을 포함한 벤치마크 설계와, 결과를 GPU 대수로 바꾸는 절차를 설명할 수 있습니다.
1. 설계 철학
vLLM 은 PagedAttention 논문(SOSP 2023)에서 출발한 범용 고처리량 엔진입니다. V1 구조에서 API 서버 프로세스가 ZMQ 로 엔진 코어 프로세스(스케줄러와 KV 관리자)와 통신하고, 엔진 코어는 GPU 마다 하나인 워커를 관리합니다(공식 아키텍처 문서). v0.29.0 부터 Model Runner V2 가 기본입니다(릴리스 노트, 일부 ROCm 모델 등은 예외).
SGLang 논문(arXiv 2312.07104)은 엔진이 아니라 LLM 프로그램을 효율적으로 실행하는 시스템으로 시작합니다. 프런트엔드 언어는 생성(gen, select)과 병렬 제어(fork, join) 프리미티브를 주고, 런타임에는 두 기법이 있습니다. RadixAttention 은 요청이 끝나도 프롬프트와 생성 결과의 KV 를 라딕스 트리에 LRU 로 유지하고 일치 접두사가 긴 요청을 먼저 처리하는 캐시 인지 스케줄링을 씁니다. 압축 유한 상태 기계는 제약이 결정하는 여러 토큰을 한 걸음에 디코딩합니다. 논문은 처리량 최대 6.4배, 지연 최대 3.7배 감소를 주장합니다. 조건은 대부분 A10G 24GB(일부 A100), Llama-7B/70B, Mixtral-8x7B 등으로 에이전트, JSON 디코딩, RAG, 멀티턴 같은 접두사 재사용이 큰 워크로드이고, 비교한 vLLM 은 v0.2.5 입니다. 논문은 그 시점의 최신 vLLM 에 RadixAttention 이 실험 기능으로 부분 통합되어 이전 버전을 썼다고 각주에 밝힙니다. 현재 v0.30.0 과 비교한 수치가 아닙니다.
vLLM: 블록 해시 사슬 (블록 16 토큰) SGLang: 라딕스 트리 (page-size 기본 1)
요청 A [B0]-[B1]-[B2] root
요청 B [B0]-[B1]-[B3] └ "시스템 프롬프트 ..." (공통 노드 하나)
해시 = H(부모 해시, 블록 토큰, 추가 키) ├ "질문 A ..." -> 생성 결과
해시 맵 조회, 빈 블록 큐에서 LRU 방출 └ "질문 B ..."
리프부터 방출(기본 lru), 사용 중 노드는 잠김
SGLang 문서의 --page-size 기본값이 1 이라 접두사 일치가 토큰 단위로 가능하다는 것은 문서 기본값에서 한 추론이고, vLLM 은 가득 찬 블록(16 토큰) 단위로만 적중합니다(04편). 최근 SGLang 릴리스 노트에는 v0.5.19 에서 통합 라딕스 트리가 모든 구성의 기본이 되었고, v0.5.21 에서 접두사 캐시가 Rust 코어 기본(지원하지 않는 구성은 파이썬으로 폴백)이 되었으며 구형 구현(SWARadixCache, MambaRadixCache, HiRadixCache)이 제거되었다고 적혀 있습니다. 같은 때 vLLM 은 접두사 캐시를 기본으로 켜고 구조화 출력(xgrammar, guidance)을 지원합니다. 따라서 철학 차이가 곧 기능 유무의 차이는 아닙니다.
2. 기능 비교 표
문서에 항목이 있다는 뜻이며 성숙도나 성능의 우열이 아닙니다. 기준은 vLLM v0.30.0 문서와 SGLang v0.5.21 문서입니다.
| 영역 | vLLM | SGLang |
|---|---|---|
| 접두사 캐시 | 블록 해시, LRU, 기본 켜짐. 용량 확장은 OffloadingConnector, LMCache |
라딕스 트리, --radix-eviction-policy lru, lfu, slru, priority, tlru. 계층 캐시 HiCache |
| 요청 순서 | --scheduling-policy fcfs(기본), priority |
--schedule-policy fcfs(기본), lpm(최장 접두사 우선), dfs-weight 등 |
| 추측 디코딩 | EAGLE, MTP, 드래프트 모델, n-gram, suffix 등 | EAGLE-2/3, MTP, 독립 드래프트 모델, NGRAM |
| 양자화 | AWQ, GPTQ, FP8, INT8, bitsandbytes, ModelOpt, GGUF, KV 캐시 FP8 | --quantization awq, fp8, gptq, modelopt_fp4 등, --kv-cache-dtype fp8_e4m3, fp8_e5m2 |
| LoRA | --enable-lora --lora-modules |
--enable-lora, 한 배치 안에서 어댑터 여러 개(S-LoRA, Punica 기법) |
| 멀티모달 | 이미지, 비디오, 오디오 입력 | 비전 API 문서, 멀티모달 모델 |
| 구조화 출력 | xgrammar, guidance (structured_outputs) |
xgrammar(기본), outlines, llguidance. JSON 스키마, 정규식, EBNF |
| MoE | --enable-expert-parallel (EP = TP × DP) |
--ep-size, --attn-dp-size |
| 분산 | TP, PP, DP. PD 분리는 문서에 experimental 로 표기 | TP, DP, EP. SGLang Model Gateway(라우터), PD 분리 문서 |
| 모델 폴백 | Transformers modeling backend | --model-impl transformers |
vLLM 문서는 PD(prefill, decode) 분리가 처리량을 높이지는 않는다고 적고, 목적을 TTFT 와 ITL 의 따로 조정과 꼬리 지연 통제로 한정합니다.
3. 선택 기준: 워크로드 모양별
| 워크로드 | 먼저 볼 것 |
|---|---|
| 접두사가 거의 겹치지 않는 단발 요청 | 접두사 캐시 이득이 없으니 엔진 차이는 커널과 스케줄러 오버헤드뿐입니다. 직접 재야 합니다. |
| 긴 공통 시스템 프롬프트, RAG 문서 반복 | 접두사 캐시가 핵심입니다. 두 엔진 모두 지원하고, 용량이 모자라면 오프로딩(vLLM) 또는 HiCache(SGLang)를 봅니다. 04편의 실험이 이 경우입니다. |
| 에이전트, 멀티턴(턴마다 히스토리가 자람) | 직전 턴의 KV 재사용이 핵심입니다. SGLang 이 설계 중심에 두었고 문서에 세션 인지 라딕스 캐시와 agentic-trace 벤치 데이터셋이 있습니다. vLLM 도 접두사 캐시로 가능하며, 같은 워크로드의 우열은 측정하지 않았습니다. |
| 구조화 출력이 대부분 | 두 엔진이 같은 xgrammar 를 지원하므로 엔진보다 스키마 복잡도와 백엔드를 봅니다. |
| 최신 대형 MoE 모델을 빨리 | 모델별 쿡북과 검증된 실행 레시피가 있는지 봅니다. |
| 쿠버네티스 위 운영 | vLLM 문서에 llm-d, production-stack, KServe, KubeRay, AIBrix, Dynamo 통합 항목이 있고, SGLang 은 Model Gateway 와 쿠버네티스 PD 배포 문서가 있습니다. |
모델 지원 속도는 정량적으로 확인하지 못했습니다. 릴리스 노트로 본 사례는 이렇습니다. Qwen3.8-Flash-Next 와 Hy4-preview 는 vLLM v0.29.0(2026-09-09)이 SGLang v0.5.20(09-18)보다 먼저 신규 모델에 올렸고, GLM-5.3-Flash, K2-Horizon, Nanbeige4.2 는 SGLang v0.5.20 이 vLLM v0.30.0(09-22)보다 먼저였습니다. 릴리스 포함 시점일 뿐 day-0 여부가 아니므로 한쪽이 빠르다는 근거가 되지 못합니다. 두 엔진 모두 Transformers 기반 폴백이 있어 네이티브 구현이 없는 모델도 일단 띄울 수는 있습니다.
4. 서버를 띄우고 OpenAI 호환 API 로 부르기
vllm serve Qwen/Qwen2.5-1.5B-Instruct --max-model-len 8192 # 기본 포트 8000
sglang serve Qwen/Qwen3-0.6B --host 0.0.0.0 --port 30000 # 문서 quickstart (기본 host 127.0.0.1)
curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" \
-d '{"model":"Qwen/Qwen2.5-1.5B-Instruct","messages":[{"role":"user","content":"안녕"}],"max_tokens":32}'
운영 전에 알아 둘 것입니다. vLLM 의 --api-key 는 /v1, /v2, /inference 경로만 인증하고 /invocations 같은 다른 경로는 인증하지 않으므로 이것만으로 보안을 맡기지 말라고 공식 문서가 경고합니다. vLLM 서버는 기본으로 모델 저장소의 generation_config.json 을 적용해 샘플링 기본값이 모델마다 달라질 수 있습니다(--generation-config vllm 으로 끔). SGLang 은 --enable-metrics 를 켜야 Prometheus 지표를 내고, --enable-cache-report 로 응답의 prompt_tokens_details 에 캐시 적중 토큰 수를 받을 수 있습니다.
5. 벤치마크를 제대로 하는 법
워크로드 모양부터 정합니다. 입력과 출력 길이의 분포, 접두사 공유 비율과 인기 분포, 도착 패턴, 지켜야 할 SLO 입니다. 가능하면 실제 로그에서 얻고, 없으면 가정을 적고 민감도를 봅니다.
닫힌 루프와 열린 루프. 닫힌 루프는 동시 사용자 수를 고정하고 응답을 받은 뒤 다음 요청을 보냅니다(--max-concurrency). 서버가 느려지면 부하도 같이 줄어 과부하가 드러나지 않습니다. 열린 루프는 서버 상태와 무관하게 정해진 도착률로 요청을 보냅니다(--request-rate). 아래 모형(설명용 시뮬레이션이며 실측이 아님)은 요청당 0.25초(용량 4 req/s)인 서버 하나를 봅니다.
열린 루프 3 req/s 평균 0.64s p99 2.26s
열린 루프 4.5 req/s 평균 57.43s p99 114.55s <- 용량 초과, 시간이 갈수록 대기열 증가
닫힌 루프 동시 4 평균 1.00s p99 1.00s <- 사용자 수만큼의 대기만 보임
두 도구 모두 --request-rate 의 기본값이 inf 라 모든 요청을 시각 0 에 보냅니다(소스 확인). 최대 처리량은 보이지만 TTFT 에 대기 시간이 섞여 지연 측정으로는 쓸 수 없습니다. 유한한 값을 주면 포아송 과정으로 도착을 만들고, vLLM 의 --burstiness 는 1.0 이 포아송, 작을수록 요청이 한꺼번에 몰리는 감마 분포입니다. --ramp-up-strategy 로 요청률을 서서히 올릴 수 있습니다.
길이 분포. vLLM 의 random 데이터셋은 --random-range-ratio 기본 0.0 이라 길이가 고정입니다. 실제 분포는 ShareGPT 나 자기 로그를 씁니다. 모델이 EOS 로 일찍 끝나면 출력 길이가 달라지므로 길이를 고정하려면 vLLM 은 --ignore-eos 를 씁니다(SGLang 은 기본이 EOS 무시이고 --disable-ignore-eos 로 끕니다). 워밍업. vLLM 은 --num-warmups 기본 0, SGLang 은 --warmup-requests 기본 1 입니다. 첫 요청에는 컴파일과 CUDA 그래프 비용이 섞입니다.
가장 흔한 함정: 접두사 캐시. vLLM 벤치마크 문서는 같은 서버에 vllm bench serve 를 반복하면 이전 실행의 프롬프트가 접두사 캐시에 남아 처리량이 부풀 수 있고, 고정 시드의 합성 데이터셋도 마찬가지라고 경고합니다. 04편의 실험이 그 크기를 보여 줍니다. 같은 문서를 다시 읽은 2차 패스에서 TTFT p50 이 575.5ms 에서 48.6ms 로 줄었습니다(LMCache, 동시성 1, 1.5B 모델의 한 번 잰 값). 반복이 만드는 차이가 엔진 차이보다 클 수 있습니다. 대응은 이렇습니다.
- 실행 사이에 캐시를 비웁니다. vLLM 은 캐시를 비워 주는
vllm bench sweep serve를 쓰거나 서버를 재시작하고, SGLang 의bench_serving에는--flush-cache가 있어 SGLang 서버의/flush_cache를, vLLM 서버의/reset_prefix_cache를 부릅니다(vLLM 은VLLM_SERVER_DEV_MODE=1이어야 열린다고 SGLang 문서와 vLLMenvs.py주석이 안내). - 시드를 바꾸거나, 접두사 공유를 일부러 넣은 데이터셋(vLLM
prefix_repetition, SGLanggenerated-shared-prefix와--gsp-group-distribution zipf)으로 현실적인 적중률을 만듭니다. - 결과에 접두사 적중률(
prefix_cache_hits_total ÷ queries_total)을 같이 적습니다. 운영에서 켤 기능은 켠 채로 비교합니다.
평균이 아니라 분위수. 기본 출력은 vLLM 이 --metric-percentiles 기본 99(평균과 중앙값 포함), SGLang 이 평균, 중앙값, p99 입니다. p95 가 필요하면 --metric-percentiles 50,95,99 처럼 지정합니다. 표본 수도 봅니다. 요청이 100개면 p99 는 상위 한두 개 값이 정하고, 04편의 실험은 패스당 10개라 p95 가 최댓값이었습니다. TTFT, TPOT, ITL 의 정의는 도구마다 다를 수 있다고 vLLM 문서가 밝히므로 같은 클라이언트로 두 엔진을 재는 편이 공정합니다. SGLang 의 bench_serving 은 --backend vllm 으로 vLLM 서버를 부를 수 있습니다. 부하 생성기가 병목이 되지 않도록 서버와 다른 머신에서 돌리고, --max-concurrency 를 쓸 때는 클라이언트 대기 시간(client_queue_time)을 따로 보게 합니다. vLLM 문서는 실서비스 벤치마크에는 GuideLLM 을 권합니다.
도구 이름이 바뀌었습니다. SGLang v0.5.21 소스에서 sglang.bench_serving 은 폐기 경고를 내며 sglang.benchmark.serving 으로 옮겨졌습니다(문서 예시는 아직 옛 이름). vLLM 은 vllm bench serve 입니다.
용량 계획으로 연결합니다. 1) SLO 를 정합니다(예: p99 TTFT 2초, p99 TPOT 50ms). 2) 요청률을 올려 가며 정상 상태 구간의 p99 를 잽니다. 3) SLO 를 지키는 최대 요청률이 한 인스턴스의 용량(goodput 기준)입니다. 4) 인스턴스 수 = 피크 요청률 ÷ (용량 × 목표 사용률)에 장애 대비 여유를 더합니다. 설명용 숫자로 피크 20 req/s, 용량 4 req/s, 목표 사용률 0.7 이면 20 ÷ 2.8 = 7.1 이라 8대에 예비 1대를 더해 9대입니다. 곡선 모양은 PagedAttention 논문의 서술과 같습니다. 요청률이 올라가면 지연이 완만히 늘다가 용량을 넘는 순간 대기열이 계속 자라 지연이 급격히 커집니다.
p99 TTFT
│ /
SLO ┼ - - - - - - - - - - - - - -/- - <- 이 교점의 요청률이 용량
│ _____/
│ ____________________/ (이 지점부터 급증)
└────────────────────────────── 요청률
실측: 같은 모델, 같은 GPU 에서의 vLLM 대 SGLang
같은 모델(Qwen3-1.7B, bf16), 같은 GPU(8GB급 한 장), 같은 클라이언트(요청마다 다른 무작위 입력, 출력 128토큰, 스트리밍으로 TTFT 와 ITL 측정)로 두 엔진을 쟀습니다. vLLM 0.31.0 은 --gpu-memory-utilization 0.85 --max-num-seqs 64, SGLang 0.5.21 은 --mem-fraction-static 0.8 --max-running-requests 64, 문맥 길이는 둘 다 4,096 입니다. 두 옵션의 의미가 달라 메모리 설정이 같지 않습니다. 한 번의 시험 묶음이라 반복 오차는 재지 않았습니다(미실측). vLLM 쪽 값은 14편과 같은 시험입니다.
| 입력 512, 출력 128 | 동시 1 | 동시 8 | 동시 32 | 동시 64 |
|---|---|---|---|---|
| 처리량(tok/s) vLLM / SGLang | 66 / 66 | 383 / 382 | 831 / 808 | 787 / 837 |
| TTFT 중앙 vLLM / SGLang | 73 / 84ms | 351 / 454ms | 666 / 1,073ms | 5.7 / 4.0s |
| ITL p99 vLLM / SGLang | 16 / 15ms | 19 / 19ms | 223 / 33ms | 228 / 101ms |
| 입력 2,048, 출력 128 | 동시 4 | 동시 16 | 동시 32 |
|---|---|---|---|
| 처리량(tok/s) vLLM / SGLang | 151 / 150 | 207 / 219 | 213 / 212 |
| TTFT 중앙 vLLM / SGLang | 0.73 / 0.83s | 4.0 / 2.5s | 12.5 / 12.9s |
- 처리량은 거의 같았습니다(대부분 ±5% 안, 동시 64 만 6%). 이 모델은 토큰 생성이 메모리 대역폭에 묶여 있어서(21편) 어느 엔진이든 같은 하드웨어 한계에 닿기 때문으로 읽습니다. 엔진을 고르는 이유가 이런 작은 모델의 순수 처리량은 아니라는 뜻입니다. 큰 모델이나 다중 GPU 에서는 다를 수 있고 재지 않았습니다(미실측).
- KV 캐시 토큰당 크기는 같았고, 용량은 설정에 따라 달랐습니다. SGLang 로그는 25,282토큰(K 1.35 + V 1.35GB)이었고 vLLM 은 22,048토큰이었습니다. 토큰당 바이트(약 114.7KB)는 식과 일치하므로 용량 차이는 메모리 설정 차이입니다. GPU 메모리 사용량은 SGLang 7,373MiB, vLLM 6,168MiB 로, 같은 카드에서 SGLang 설정이 메모리를 더 많이 가져갔습니다.
- TTFT 와 꼬리 지연의 모양은 달랐습니다. 동시 32 에서 SGLang 의 TTFT 중앙값이 1,073ms 로 vLLM(666ms)보다 길고 ITL p99 는 33ms 로 vLLM(223ms)보다 짧았으며, 동시 64 에서는 SGLang 이 TTFT 와 ITL p99 모두 낮았습니다. 새 요청의 입력 처리를 디코딩과 어떻게 섞는지가 다르다는 단서이지만 원인은 분해하지 않았습니다(추정).
- 접두사 캐시는 둘 다 같은 효과였습니다. 공통 3,000토큰 접두사에서 첫 요청 TTFT 는 vLLM 394ms, SGLang 399ms 이고 이후는 28ms 와 36ms 였습니다. 끄면(
--no-enable-prefix-caching,--disable-radix-cache) 393ms 와 399~416ms 로 돌아갔습니다.
구조화 출력(JSON 스키마 제약)의 비용
SGLang 0.5.21 의 JSON 스키마 제약 디코딩(response_format 의 json_schema, 문법 백엔드 기본 xgrammar)을 켠 요청과 프롬프트로만 JSON 을 요청한 요청을 같은 서버에서 비교했습니다(Qwen3-1.7B, 생각 끄기, 출력 약 74토큰, 요청마다 다른 입력).
| 동시 | 제약 없음 처리량 | 제약 처리량 | 유효한 JSON(스키마 충족) 제약 없음 / 제약 |
|---|---|---|---|
| 1 | 69 tok/s | 67 tok/s(-3%) | 4/4 / 4/4 |
| 8 | 482 tok/s | 486 tok/s | 32/32 / 32/32 |
| 32 | 1,577 tok/s | 1,654 tok/s(+5%) | 128/128 / 128/128 |
- 제약 디코딩의 비용은 이 조건에서 측정되지 않을 만큼 작았습니다(오차 안의 -3%에서 +5%). 문법 마스크 계산이 GPU 의 토큰 생성에 비해 작았다는 뜻입니다.
- 제약 없이도 모두 유효했습니다. 이 모델은 쉬운 스키마에서 프롬프트만으로도 128/128 개를 맞혔으므로, 제약의 이득(형식 보장)은 이 시험에서 드러나지 않았습니다. 형식 오류가 흔한 작은 모델이나 복잡한 스키마에서는 다를 것이고 시험하지 않았습니다(미실측). 한 번씩 잰 값이라 반복 오차는 모릅니다.
- 측정 중 겪은 일. 이 시험을 메모리 비율 0.8 로 처음 돌렸을 때는 첫 요청에서 서버 연결이 끊겼습니다. 비율을 0.7 로 낮춰 다시 돌리니 서버 로그에
Triton kernel ... device-loaded after serving started (free device mem: 0.64 GiB). Pre-load it during engine init to avoid CUDA OOM경고가 찍혔습니다. 제약 디코딩이 서비스 시작 뒤에 GPU 커널을 처음 올리므로 메모리 여유(약 0.6GiB 이상)를 남겨 두지 않으면 첫 요청에서 서버가 죽을 수 있다는 뜻입니다(원인은 경고 문구와 정황이며 0.8 에서의 종료 로그는 남기지 못했습니다).
가중치를 fp8 로 줄이면: 두 엔진 모두 같은 방향
같은 서버 설정에서 SGLang 에 --quantization fp8 만 더했습니다(bf16 가중치 3.28GB 가 대략 절반으로).
| 입력 512, 출력 128 | 동시 1 | 동시 8 | 동시 32 | 동시 64 |
|---|---|---|---|---|
| SGLang bf16 처리량(tok/s) | 66 | 382 | 808 | 837 |
| SGLang fp8 처리량(tok/s) | 100 | 567 | 1,122 | 1,250 |
| 증가 | +52% | +48% | +39% | +49% |
| fp8 의 ITL 중앙(ms) | 9 | 11 | 19 | 27 |
동시 1 에서 66에서 100 tok/s 로 늘어난 것은 토큰 하나를 만들 때 읽는 가중치 바이트가 줄어든 효과로 읽습니다(21편의 대역폭 상한 식). 21편에서 vLLM 으로 잰 1.7B fp8 의 105.7 tok/s 와 같은 수준이라 이 이득은 엔진의 성질이 아니라 정밀도의 성질입니다. 품질(정확도)은 이 시험에서 재지 않았습니다(미실측).
긴 입력이 끼어들 때: 기본 동작이 다르다
디코딩 중인 요청 하나에 3,500토큰 입력 요청 3개를 끼워 넣고 끼운 구간의 최대 토큰 간격(ITL)을 쟀습니다.
| 서버 설정 | 끼운 구간 최대 ITL | 청크 크기 로그 |
|---|---|---|
vLLM, --max-num-batched-tokens 512 |
69ms | 512 |
| vLLM, 기본 | 227ms | 확인 못함 |
| vLLM, 8192 | 445ms | 8192 |
| SGLang, 기본(3회: 459, 457, 464ms) | 약 460ms | 2048(로그) |
SGLang, --chunked-prefill-size 512 |
472ms | 512 |
SGLang, --enable-mixed-chunk |
228ms | 2048 |
SGLang, --enable-mixed-chunk --chunked-prefill-size 512 |
69ms | 512 |
- SGLang 은 기본 설정에서 청크 크기를 줄여도 끊김이 줄지 않았습니다. 청크를 512로 줄였는데 최대 ITL 이 472ms 로, 3,500토큰을 한 번에 처리하는 시간(약 470ms)과 같았습니다. 입력 처리가 끝날 때까지 디코딩이 쉬었다는 뜻으로 읽습니다.
--enable-mixed-chunk를 켜면 청크 하나의 시간(2048토큰이면 약 230ms, 512토큰이면 약 69ms)과 같아졌고, 이 값은 vLLM 의 결과와 같았습니다. 문서의 옵션 설명(청크와 디코딩을 한 배치에 섞는다)이 시험 결과와 맞습니다. - vLLM 은 기본으로 청크를 디코딩과 섞는 것으로 보입니다. 기본 설정의 끊김 227ms 가 2,048토큰 청크의 시간(약 250ms)과 맞기 때문입니다. 다만 vLLM 의 기본 청크 크기는 로그에 나오지 않아 추정입니다.
- SGLang 의
--chunked-prefill-size자동 결정값을 확인했습니다. 이 GPU 에서는 로그에 2048 로 찍혔고(문서 표에는 None),max_prefill_tokens=16384도 함께 찍혔습니다. 다른 메모리 크기의 GPU 에서는 달라질 수 있습니다(미실측). - 혼합 청크와 512 를 함께 쓴 서버에서 동시 32 를 쟀더니 처리량 831 tok/s(기본 SGLang 808), TTFT 중앙 322ms(기본 1,073ms), ITL p99 67ms(기본 33ms)였습니다. 한 번의 값이므로 해석을 단정하지 않고, 청크를 섞는 설정이 TTFT 와 ITL 꼬리를 맞바꾼다는 가능성만 적습니다(미실측).
- 서버 준비 시간은 비슷했습니다. SGLang 은 첫 기동 60초, 재기동 30~36초, vLLM 은 첫 기동 71초, 재기동 35~55초였습니다(모델 파일이 있을 때).
이 시험이 보여 주는 가장 실용적인 점은 엔진 이름보다 기본 설정이 다르다는 것입니다. 같은 이름의 옵션도 기본값이 달라서, 비교를 하려면 청크 크기, 혼합 청크, 메모리 설정, 접두사 캐시를 로그로 확인하고 맞춘 뒤에 재야 합니다. 이 글의 5절 앞부분(벤치마크를 제대로 하는 법)이 말하는 「조건을 맞춘다」가 이 시험에서 구체적인 숫자로 나왔습니다.
6. 직접 해 보기
모두 읽기 전용이거나 로컬 계산입니다.
curl -sL https://raw.githubusercontent.com/vllm-project/vllm/v0.30.0/vllm/benchmarks/serve.py \
| grep -n -E '"--(request-rate|burstiness|max-concurrency|num-warmups|goodput|metric-percentiles)"'
curl -sL https://raw.githubusercontent.com/sgl-project/sglang/v0.5.21/python/sglang/benchmark/serving.py \
| grep -n -E '"--(request-rate|max-concurrency|warmup-requests|flush-cache)"'
5절의 열린 루프와 닫힌 루프 비교는 아래 코드의 출력입니다(실측이 아니라 설명용 시뮬레이션이며, 요청 수를 늘리면 용량 초과 구간의 지연도 함께 커집니다).
import random, statistics as st
random.seed(1)
S, N = 0.25, 4000 # 요청 하나에 0.25초 = 용량 4 req/s, 요청 4000개
def open_loop(rate): # 포아송 도착: 서버가 느려도 요청은 제 시간에 온다
t = free_at = 0.0; lat = []
for _ in range(N):
t += random.expovariate(rate)
free_at = max(t, free_at) + S
lat.append(free_at - t)
return lat
def closed_loop(users): # 사용자 고정: 응답을 받아야 다음 요청을 보낸다
ready, free_at, lat = [0.0] * users, 0.0, []
for _ in range(N):
i = ready.index(min(ready)); t = ready[i]
free_at = max(t, free_at) + S
lat.append(free_at - t); ready[i] = free_at
return lat
def row(name, lat):
s = sorted(lat)
print(f"{name:<14} 평균 {st.mean(lat):6.2f}s p99 {s[int(.99 * (N - 1))]:6.2f}s")
for r in (3, 4.5, 5): row(f"열린 {r} req/s", open_loop(r))
for u in (4, 16): row(f"닫힌 동시 {u}", closed_loop(u))
04편의 실험을 이 글의 기준으로 평가해 약점을 찾아 보십시오. 동시성 1, 고정 문서, 같은 문서 재사용, 문서 10개라 p95 가 최댓값이라는 점이 모두 걸립니다. 개선 과제는 동시성 4(실험 설정의 --max-num-seqs=4), 포아송 도착, 길이 분포, 접두사 적중률 기록입니다. 이 실험은 임시 파드로 돌렸고 이 글은 다시 실행하지 않았습니다. 위험은 같은 노드의 다른 워크로드와의 메모리 경합과 GPU 점유이고, 파드는 --rm 으로 지워지며 남으면 1시간 뒤 종료됩니다.
7. 면접에서 나올 만한 질문
- vLLM 과 SGLang 의 접두사 캐시는 어떻게 다릅니까? vLLM 은 블록 16토큰마다 부모 해시를 이어 붙인 해시 사슬을 맵에서 조회하고 빈 블록 큐에서 LRU 로 내보냅니다. SGLang 은 토큰열을 라딕스 트리에 저장해 리프부터 내보내며 기본 page-size 가 1 입니다. 지금은 vLLM 도 기본으로 켜지므로 차이는 기능 유무보다 입도, 스케줄링 정책, 방출 정책에 있습니다.
- 에이전트 워크로드에서 SGLang 이 낫다는 주장을 어떻게 검증합니까? 같은 하드웨어, 모델, 정밀도에서 같은 클라이언트로 실제 턴 구조의 트레이스를 열린 루프로 보냅니다. 접두사 적중률을 같이 기록하고 p99 TTFT 와 SLO 만족 처리량을 비교합니다. 논문 수치는 vLLM v0.2.5 대비라 현재 버전에는 그대로 적용하지 못합니다.
- 열린 루프와 닫힌 루프는 언제 각각 씁니까? 용량과 SLO 를 알아내려면 열린 루프로 요청률을 올려 가며 급증 지점을 찾습니다. 닫힌 루프는 상위 시스템이 동시성을 제한하는 환경을 흉내 낼 때 쓰며, 과부하에서 지연이 안 늘어 보이는 한계를 압니다.
- 벤치마크 결과가 비정상적으로 좋습니다. 무엇을 의심합니까? 같은 프롬프트 반복으로 접두사 캐시가 적중했는지, 워밍업을 뺐는지, 출력이 EOS 로 일찍 끝났는지, 요청률이
inf라 TTFT 에 대기가 섞였는지를 봅니다. 적중률 지표와 입력, 출력 토큰 분포를 같이 확인합니다. - 벤치마크 결과를 GPU 대수로 어떻게 바꿉니까? SLO 를 지키는 최대 요청률을 인스턴스 용량으로 보고, 피크 요청률을 용량과 목표 사용률의 곱으로 나눈 뒤 장애 대비를 더합니다. 평균 처리량이 아니라 p99 기준이어야 하고, 워크로드 모양이 바뀌면 다시 재야 합니다.
8. 흔한 오해
- "SGLang 이 항상 빠르다(또는 vLLM 이 항상 빠르다)." 워크로드 의존적입니다. 논문의 6.4배는 접두사 재사용이 큰 워크로드에서 vLLM v0.2.5 와 비교한 상한이며, 논문 6.2절은 이 개선이 KV 캐시 재사용만이 아니라 프로그램 내부 병렬성과 더 빠른 제약 디코딩에서도 나온다고 설명합니다. 즉 접두사 캐시만의 효과가 아닙니다. 반대로 출력이 긴 멀티턴 대화에서는 거의 향상이 없다고 같은 절이 적습니다.
- "접두사 캐시는 SGLang 만 있다." vLLM 도 기본으로 켜집니다.
- "최대 처리량이 용량이다." SLO 를 만족하는 처리량(goodput)이 용량입니다.
- "평균 지연으로 비교하면 된다." 꼬리가 서비스 품질을 정하고, 평균은 대기열 급증을 숨깁니다.
- "한 번 돌린 숫자로 정하면 된다." 반복해 분산을 보고, 캐시 상태와 버전을 고정해 기록합니다.
참고 자료
실제로 열어 본 것만 적었습니다.
- SGLang 논문: https://arxiv.org/abs/2312.07104 (본문은 arxiv.org/html 로 확인), PagedAttention 논문: https://arxiv.org/abs/2309.06180
- SGLang 문서(https://docs.sglang.io): get-started/quickstart, advanced_features 의 server_arguments, hyperparameter_tuning, structured_outputs, radix_eviction_policy, lora, speculative_decoding, pd_disaggregation, sgl_model_gateway, developer_guide/bench_serving, supported-models/transformers_fallback, llms.txt
- SGLang 소스: https://github.com/sgl-project/sglang/tree/v0.5.21 의
python/sglang/bench_serving.py,python/sglang/benchmark/serving.py - SGLang 릴리스: https://github.com/sgl-project/sglang/releases (v0.5.19, v0.5.20, v0.5.21)
- vLLM 문서(https://docs.vllm.ai/en/latest/): design/arch_overview, benchmarking/cli, serving/online_serving/openai_compatible_server, features 의 structured_outputs, speculative_decoding, quantization, lora, multimodal_inputs, disagg_prefill, kv_offloading_usage, serving/expert_parallel_deployment, serving/data_parallel_deployment, sitemap.xml
- vLLM 소스: https://github.com/vllm-project/vllm/tree/v0.30.0 의
vllm/benchmarks/serve.py,vllm/benchmarks/datasets/datasets.py,vllm/envs.py. 릴리스: https://github.com/vllm-project/vllm/releases (v0.29.0, v0.30.0)
확인한 버전과 날짜, 확인하지 못한 것
- 확인일은 2026-10-05 입니다. vLLM 최신 안정은 v0.30.0(2026-09-22), SGLang 최신은 v0.5.21(2026-10-02)입니다.
- SGLang 계층 캐시(HiCache)는 이 환경에서 시작되지 못했습니다. 호스트 메모리를 쓰는 계층 캐시를 켜자 서버가 시작 중에
Not enough host memory available. Requesting 1.85 GB but only have -10.74 GB free로 종료했습니다. 소스(pool_host/base.py)를 읽으니 호스트 메모리 가용량에서 항상 10GiB 를 예약분으로 빼고 비교하는 검사(HICACHE_HOST_MEMORY_RESERVE_BYTES)였고, 이 시험의 노드는 RAM 이 약 15GB 이고 가용량이 약 10GB 라 요청 크기와 관계없이 통과할 수 없었습니다. 그래서 vLLM 의 LMCache(8절)와 같은 시나리오 비교는 못 했습니다. 계층 캐시를 쓰려면 호스트 가용 메모리가 10GiB 에 요청량을 더한 값보다 커야 한다는 운영 지침으로 읽을 수 있습니다(소스 확인, 큰 호스트에서는 시험하지 않았습니다). - 확인하지 못한 것: 5절 실측 밖의 조건에서 두 엔진의 현재 버전 성능 비교(논문 수치는 vLLM v0.2.5 기준), SGLang
--chunked-prefill-size자동 결정값의 결정 규칙(이 시험의 8GB급 GPU 에서는 2048 로 찍혔지만 규칙은 확인하지 못함), 큰 모델·다중 GPU 에서의 두 엔진 비교, SGLang 프런트엔드 언어의 현재 활용 수준(문서에 튜토리얼이 있다는 것까지만 확인), 모델 지원의 day-0 여부,/reset_prefix_cache가VLLM_SERVER_DEV_MODE=1에서만 열린다는 점의 직접 실행(환경 변수 주석과 SGLang 문서로만 확인), 벤치마크 설명용 시뮬레이션의 현실 적합성. 열린 루프 과부하에서 지연이 시간이 갈수록 커진다는 것은 시뮬레이션과 일반 대기열 이론에 근거하며 별도 출처를 열어 확인하지 않았습니다.