TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

vLLM 与 SGLang 的对比,以及如何正确进行基准测试

状态:草稿(2026-10-07 更新),部分反应了实际测量结果。 本文基于官方文档的描述编写,直接实测部分仅限于引用第 04 篇实验的部分以及第 5 节末尾的「实测:相同模型、相同 GPU 下的 vLLM 对比 SGLang」(一个小模型,一张 GPU)。大模型、多 GPU 以及其他工作负载下的对比属于未实测内容。请先阅读文末的「确认的版本与日期」以及「未确认的内容」。编写规则:以实测数据为基础,未能测出的内容标注为「未实测」。

目录

选择哪个引擎不取决于个人喜好,而取决于工作负载形态与测量结果。本文将整理 vLLM 与 SGLang 如何在底层设计上区别对待前缀缓存、目前两者的收敛程度、针对不同工作负载应该首先怀疑什么,并探讨如何设计和解读支持这些判断的基准测试。作者从未直接运维或测量过 SGLang。本文没有关于两个引擎优劣的自主实测,仅以官方文档、源码、论文以及作者在自身实验环境下使用 vLLM 的经验作为编写依据。

读完本文后,你将能够在面试中解释以下内容:

  1. 块哈希链(vLLM)与基数树(Radix Tree,SGLang)的区别,并能够指出目前两者的功能已有很大程度的重合。
  2. 针对前缀复用、结构化输出、模型支持等不同工作负载形态的选择标准及其局限性。
  3. 包含开环负载、泊松到达、前缀缓存陷阱、百分位数的基准测试设计,以及将结果换算为 GPU 数量的流程。

1. 设计哲学

vLLM 是源自 PagedAttention 论文(SOSP 2023)的通用高吞吐量引擎。在 V1 架构中,API 服务器进程通过 ZMQ 与引擎核心进程(调度器与 KV 管理器)通信,引擎核心管理每个 GPU 一个的 Worker(官方架构文档)。自 v0.29.0 起,Model Runner V2 成为默认值(发布说明、部分 ROCm 模型等除外)。

SGLang 论文(arXiv 2312.07104)最初作为一个能高效运行 LLM 程序的系统,而非单纯的引擎。其前端语言提供生成(gen, select) 与并行控制 (fork, join) 原语,而在运行时则采用两种技术。RadixAttention 在请求结束后仍将提示词与生成结果的 KV 以 LRU 方式保留在基数树中,并优先处理匹配前缀最长的请求,从而实现缓存感知的调度。压缩有限状态机(Compressed Finite State Machine)则将由约束决定的多个 Token 在单步内解码。论文声称吞吐量最高提升 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,这意味着前缀匹配可以精确到 Token 级别,这是基于文档默认值的推论;而 vLLM 仅在填满的块(16 个 Token)级别进行命中(第 04 篇)。近期的 SGLang 发布说明记录,在 v0.5.19 中,统一基数树(Unified Radix Tree)成为了所有配置的默认项;在 v0.5.21 中,前缀缓存变为了 Rust 核心的默认功能(不支持的配置将回退到 Python),并且旧实现(SWARadixCache, MambaRadixCache, HiRadixCache) 已被移除。与此同时,vLLM 也默认开启了前缀缓存并支持结构化输出(xgrammar、guidance)。因此,设计哲学的差异并不等于功能有无的差异。

2. 功能对比表

这仅代表文档中存在该条目,不代表成熟度或性能的优劣。对比标准为 vLLM v0.30.0 文档与 SGLang v0.5.21 文档。

领域 vLLM SGLang
前缀缓存 块哈希、LRU、默认开启。容量扩展有 OffloadingConnector, LMCache 基数树(Radix Tree), --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 Schema、正则表达式、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 建模后端 --model-impl transformers

vLLM 文档指出,PD(Prefill 与 Decode)分离并不能提高吞吐量,其目的仅限于分别调整 TTFT 与 ITL 以及控制尾部延迟。

3. 选择标准:按工作负载形态分类

工作负载 优先关注点
前缀几乎不重叠的单次请求 由于没有前缀缓存带来的收益,引擎之间的差异仅在于内核与调度器的开销。必须进行直接测量。
长公共系统提示词、重复的 RAG 文档 前缀缓存是核心。两个引擎均支持,若容量不足,可考虑卸载(vLLM)或 HiCache(SGLang)。第 04 篇的实验即属于这种情况。
智能体(Agent)、多轮对话(每轮历史记录增长) 重用上一轮的 KV 是核心。SGLang 将其作为设计中心,并且文档中提供了会话感知的基数缓存与 agentic-trace 基准数据集。vLLM 也可以通过前缀缓存实现,但未测量过同类工作负载下的优劣。
以结构化输出为主 由于两个引擎支持相同的 xgrammar,比起引擎本身,更应关注 Schema 的复杂度与后端。
快速上手最新的大型 MoE 模型 查看是否有针对特定模型的 Cookbook 以及经过验证的运行食谱(Recipe)。
在 Kubernetes 上运行 vLLM 文档包含 llm-d、production-stack、KServe、KubeRay、AIBrix、Dynamo 的集成条目,SGLang 则拥有 Model Gateway 与 Kubernetes 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 中获取缓存命中 Token 数量。

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 关闭)。 预热(Warmup)。 vLLM 的 --num-warmups 默认值为 0,SGLang 的 --warmup-requests 默认值为 1。首次请求中会混入编译和 CUDA 图(Graph)的开销。

最常见的陷阱:前缀缓存。 vLLM 基准测试文档指出,在同一台服务器上 vllm bench serve 警告称,重复该操作会使前沿提示词留在前缀缓存中,从而虚增吞吐量,使用固定随机种子的合成数据集也是如此。第 04 篇的文章展示了其规模。在重新读取相同文档的第二轮传递中,TTFT p50 从 575.5ms 降至 48.6ms(LMCache,并发 1,1.5B 模型单次测得的值)。重复操作造成的差异可能会大于引擎差异。应对方法如下:

关注分位数,而不是平均值。 vLLM 的默认输出是 --metric-percentiles 默认 99(包含平均值和中位数),SGLang 则是平均值、中位数和 p99。如果需要 p95,可以像 --metric-percentiles 50,95,99 那样指定。同时也要看样本量。如果请求数为 100,p99 由前一两个值决定,而第 04 篇的实验每轮只有 10 个请求,所以 p95 就是最大值。由于 vLLM 文档指出 TTFT、TPOT 和 ITL 的定义可能因工具而异,因此使用相同的客户端测试两个引擎会更公平。你可以用 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 个 token,通过流式传输测量 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。由于两个选项的含义不同, 内存配置并不完全相同。 下方的输入 512 表格是 通过三次重启服务器测量得到的数据,数值为三次测量的中位数。初次测得的值与重复三次的值相差在 1% 以内,但下方 vLLM 并发 64 的情况除外。输入 2,048 的表格是未经重复、仅测量一次的值。

输入 512,输出 128 并发 1 并发 8 并发 32 并发 64
吞吐量 (tok/s) vLLM / SGLang 66 / 66 384 / 386 831 / 809 842 / 836
TTFT 中位数 vLLM / SGLang 73 / 89ms 349 / 454ms 669 / 1,093ms 2.2 / 4.0s
ITL p99 vLLM / SGLang 16 / 15ms 19 / 19ms 224 / 33ms 231 / 101ms

并发 64 下的 vLLM 栏数据每次启动服务器时都不同。 在三次服务器运行中,吞吐量分别为 792、842、843 tok/s,TTFT 中位数分别为 5.7、2.1、2.2 秒。只有首次启动的服务器较慢,服务器日志显示其 KV 缓存为 22,048 个 token(其余两次为 26,560 个 token)。尽管选项相同,但由于 vLLM 会通过测量内存来确定 KV 的空间,因此每次启动服务器时的容量都不同(从 22,048 到 27,504 个 token不等)。在并发 64 请求几乎将缓存填满(每个请求约 640 个 token)的这个节点上,20% 的容量差异导致 TTFT 发生了 2.7 倍的变化(第 14 篇)。SGLang 在三次运行中均为 25,282 个 token,数值也都在 1% 以内保持一致。上表中 vLLM 并发 64 的数据是三次测量的中位数,这一格的重复误差较大。

输入 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

大模型(Qwen3-4B-FP8)中的差异

我们在 Qwen3-4B-FP8(使用 fp8 权重以装入 8GB 显存,体量约为 1.7B bf16 的 2.4 倍)上重启服务器两次,重复了相同的测试。由于精度不同,这不能与 1.7B 进行直接对比,而应被理解为“同一模型下的两个引擎”对比。两个引擎的上下文长度均为 4,096,最大并发为 64,服务器日志中的 KV 缓存大小:vLLM 分别为 8,880 个 token 和 13,232 个 token,SGLang 两次均为 13,036 个 token。

输入 512,输出 128 并发 1 并发 8 并发 32
吞吐量 (tok/s) vLLM / SGLang 49 / 49 251 / 278 323·365 / 450
TTFT 中位数 vLLM / SGLang 166·168 / 113ms 911 / 720ms 7.2·3.4 / 3.6s
ITL p99 vLLM / SGLang 21 / 20ms 24~27 / 25ms 548·583 / 138ms

结构化输出(JSON Schema 约束)的代价

我们在同一台服务器上对比了开启 SGLang 0.5.21 的 JSON Schema 约束解码(response_format 的 json_schema,语法后端默认为 xgrammar)的请求,以及仅通过提示词请求 JSON 的请求(Qwen3-1.7B,关闭思考,输出约 74 个 token,每个请求的输入各不相同)。

并发 无约束吞吐量 约束吞吐量 有效 JSON(满足 Schema)无约束 / 有约束
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

将权重缩减为 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,被解读为生成单个 Token 时读取的权重字节数减少的效果(第 21 篇的带宽上限公式)。这与第 21 篇中使用 vLLM 测得的 1.7B fp8 的 105.7 tok/s 处于同一水平,因此这种收益是精度的属性,而非引擎的属性。在此次测试中未测量质量(准确率)(未实际测量)。

当长输入介入时:默认行为不同

我们在正在解码的一个请求中插入了 3 个 3,500 Token 的输入请求,并测量了插入区间的最大 Token 间延迟(ITL)。

服务器配置 插入区间最大 ITL 块大小日志
vLLM, --max-num-batched-tokens 512 69ms 512
vLLM, 默认 227ms 无法确认
vLLM, 8192 445ms 8192
SGLang, 默认(首次测试 3 次:459, 457, 464ms;服务器重测三次:462, 462, 462ms) 约 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

本次测试展示的最实用的一点是 默认配置比引擎名称更重要。即使是同名的选项,其默认值也不同,因此若要进行比较,必须通过日志确认并对齐块大小、混合块、内存设置和前缀缓存后才能重新测试。本文第 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))

请以本文的标准评估第 4 篇的实验并找出其弱点。并发度为 1、固定文档、重复使用相同的文档、文档数量为 10 个导致 p99 成为最大值,这些都是问题所在。改进任务包括并发度 4(实验设置的 --max-num-seqs=4)、泊松到达率、长度分布以及前缀命中率记录。该实验在临时 Pod 中运行,本文未重新运行。风险在于与同一节点上其他工作负载的内存争用以及 GPU 占用,Pod 会被 --rm 删除,若残留则会在 1 小时后终止。

7. 面试常见问题

  1. vLLM 和 SGLang 的前缀缓存有何不同? vLLM 每隔 16 个 Token 的块就会在映射中查询将父哈希连接起来的哈希链,并从空闲块队列中通过 LRU 逐出。SGLang 将 Token 序列存储在基数树(Radix Tree)中,从叶子节点开始逐出,其默认 page-size 为 1。目前 vLLM 也默认开启此功能,因此两者的差异与其说是有无该功能,不如说是在粒度、调度策略和逐出策略上的不同。
  2. 如何验证 SGLang 在智能体(Agent)工作负载中表现更好的说法? 在相同的硬件、模型和精度下,使用相同的客户端以开放循环发送实际轮次结构(turn-structured)的追踪(Trace)。同时记录前缀命中率,并比较 p99 TTFT 和满足 SLO 的吞吐量。论文中的数据是与 vLLM v0.2.5 相比得出的,因此不能直接应用到当前版本中。
  3. 开放循环和封闭循环分别在什么时候使用? 为了找出容量和 SLO,可以使用开放循环不断提高请求率来寻找激增点。封闭循环用于模拟上层系统限制并发的环境,并了解在过载情况下延迟不再增长的极限。
  4. 基准测试结果异常优秀。应该怀疑什么? 检查是否由于重复相同的提示词而命中了前缀缓存、是否剔除了预热阶段、输出是否因 EOS 而过早结束,或者请求率是否因为 inf 导致 TTFT 中掺杂了等待时间。请同时确认命中率指标以及输入和输出 Token 的分布。
  5. 如何将基准测试结果转换为 GPU 数量? 将满足 SLO 的最大请求率视作实例容量,用峰值请求率除以容量与目标利用率的乘积,再加上容灾余量。必须基于 p99 而非平均吞吐量,并且如果工作负载形态发生变化,则必须重新测量。

8. 常见误解

参考资料

仅记录亲自查阅过的内容。

确认的版本与日期,以及未确认的内容

登录后即可点赞

评论

还没有评论。

登录后即可发表评论