状态:草稿(2026-10-07 更新),部分反应了实际测量结果。 本文基于官方文档的描述编写,直接实测部分仅限于引用第 04 篇实验的部分以及第 5 节末尾的「实测:相同模型、相同 GPU 下的 vLLM 对比 SGLang」(一个小模型,一张 GPU)。大模型、多 GPU 以及其他工作负载下的对比属于未实测内容。请先阅读文末的「确认的版本与日期」以及「未确认的内容」。编写规则:以实测数据为基础,未能测出的内容标注为「未实测」。
目录
- 1. 设计哲学
- 2. 功能对比表
- 3. 选择标准:按工作负载形态分类
- 4. 启动服务器并通过 OpenAI 兼容 API 调用
- 5. 如何正确进行基准测试
- 6. 动手尝试
- 7. 面试高频问题
- 8. 常见误区
- 参考资料
- 确认的版本与日期,以及未确认的内容
选择哪个引擎不取决于个人喜好,而取决于工作负载形态与测量结果。本文将整理 vLLM 与 SGLang 如何在底层设计上区别对待前缀缓存、目前两者的收敛程度、针对不同工作负载应该首先怀疑什么,并探讨如何设计和解读支持这些判断的基准测试。作者从未直接运维或测量过 SGLang。本文没有关于两个引擎优劣的自主实测,仅以官方文档、源码、论文以及作者在自身实验环境下使用 vLLM 的经验作为编写依据。
读完本文后,你将能够在面试中解释以下内容:
- 块哈希链(vLLM)与基数树(Radix Tree,SGLang)的区别,并能够指出目前两者的功能已有很大程度的重合。
- 针对前缀复用、结构化输出、模型支持等不同工作负载形态的选择标准及其局限性。
- 包含开环负载、泊松到达、前缀缓存陷阱、百分位数的基准测试设计,以及将结果换算为 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 可以使用清空缓存的
vllm bench sweep serve或重启服务器,而 SGLang 的bench_serving包含--flush-cache,它会调用 SGLang 服务器的/flush_cache以及 vLLM 服务器的/reset_prefix_cache(SGLang 文档和 vLLMVLLM_SERVER_DEV_MODE=1注释说明,vLLM 必须是envs.py才能打开)。 - 更改随机种子,或者使用有意包含前缀共享的数据集(vLLM 的
prefix_repetition、SGLang 的generated-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 就是最大值。由于 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 |
- 吞吐量几乎完全相同(在并发 32 时 vLLM 高出 3%,其余在 1% 以内)。我们的理解是,由于该模型的 token 生成受限于内存带宽(第 21 篇),因此无论哪个引擎都会触及相同的硬件极限。这意味着选择引擎并不是为了这种小模型的纯粹吞吐量。在大模型中情况则有所不同(见下文“大模型”)。多 GPU 情况未进行测量(未实测)。
- 每个 KV 缓存 token 的大小相同,但容量取决于配置。 SGLang 的日志显示为 25,282 个 token(K 1.35 + V 1.35GB),vLLM 为 22,048 个 token。每个 token 的字节数(约 114.7KB)与公式相符,因此容量差异源于内存配置的不同。GPU 内存用量方面,SGLang 为 7,373 MiB,vLLM 为 6,168 MiB,在相同的显卡上,SGLang 的配置占用了更多内存。
- TTFT 和尾部延迟的形态有所不同。 在并发 32 时,SGLang 的 TTFT 中位数为 1,093ms,长于 vLLM(669ms),而 ITL p99 为 33ms,短于 vLLM(224ms)。在并发 64 时,TTFT 较低的是 vLLM 这一侧(2.2 秒对 4.0 秒,不过 vLLM 在不同服务器之间波动较大),而 ITL p99 较低的是 SGLang(101ms 对 231ms)。在初次测得的值中,并发 64 的 TTFT 也是 SGLang 较低,这是因为上面提到的第一个服务器运行较慢。这表明新请求的输入处理与解码的交织方式存在差异,但我们并未对根本原因进行深入剖析(推测)。
- 前缀缓存的效果在两款引擎中表现一致。 在具有 3,000 个公共 token 前缀的情况下,首次请求的 TTFT 为:vLLM 394ms,SGLang 399ms,后续请求则分别为 28ms 和 36ms。如果将其关闭(
--no-enable-prefix-caching,--disable-radix-cache),则会分别回落至 393ms 和 399~416ms。
大模型(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 |
- 在小模型中表现相似的吞吐量在大模型中拉开了差距。 在并发 8 时,SGLang 高出 11%,在并发 32 时高出 23%~39%(并发 32 下的两个 vLLM 值分别对应 KV 为 8,880 个 token 的服务器和 KV 为 13,232 个 token 的服务器。如果对比 KV 规模相近的组,则是 365 对 450,即高出 23%)。我们没有对原因进行深入剖析。这有可能是读取 fp8 权重的内核存在差异,但未能得到证实(推测)。
- 插队时的最大 ITL 表现出与 1.7B 相同的趋势。 vLLM 为 602ms(两次均如此),SGLang 为 770~772ms,vLLM 表现更低(1.7B 为 227ms 对约 460ms)。插队的 3,500 个 token 输入本身的 TTFT 为:vLLM 1.22 秒,SGLang 0.78~0.80 秒,SGLang 更快(1.7B 两次均约为 0.47 秒,没有差异)。虽然可以将其解读为“由于更快结束,解码的空闲时间更长”,但我们并未对原因进行深入剖析(推测)。
- vLLM 在并发 32 下的 TTFT 每次服务器启动时表现不同,分别为 7.2 秒和 3.4 秒。 KV 为 8,880 个 token 的服务器较慢,这与在 1.7B 中观察到的现象(第 14 篇)一致。这也是为什么绝不能仅凭单次启动的服务器测得的值来比较引擎。
结构化输出(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 |
- 在此条件下,约束解码的代价小到无法测出(在误差范围内的 -3% 到 +5% 之间)。这意味着语法掩码计算相对于 GPU 的 token 生成而言非常微小。
- 即使没有约束,所有输出也都完全有效。 由于该模型在简单的 Schema 下仅凭提示词就命中了 128/128 个结果,因此约束带来的收益(格式保证)在此次测试中没有体现出来。在格式错误频发的小模型或复杂的 Schema 下情况可能会有所不同,但我们没有进行测试(未实测)。由于每次测得的值均为单次结果,因此重复误差未知。
- 测试过程中遇到的状况。 当首次以 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,被解读为生成单个 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 |
- 在 SGLang 的默认配置下,即使减小块大小,卡顿也没有减少。 将块大小减小到 512 后,最大 ITL 仍为 472ms,这与单次处理 3,500 Token 所需的时间(约 470ms)相同。这被解读为在输入处理结束之前,解码处于闲置状态。
--enable-mixed-chunk开启时 它与单个块的时间(2048 Token 时约为 230ms,512 Token 时约为 69ms)相同,该值与 vLLM 的结果一致。文档中的选项说明(将块和解码混合在同一个批次中)与测试结果相符。 - vLLM 似乎默认将块与解码混合。 因为默认配置下的卡顿 227ms 与 2,048 Token 块的时间(约 250ms)相符。不过,vLLM 的默认块大小并未出现在日志中,因此这只是推测。
- 我们确认了 SGLang 的
--chunked-prefill-size自动决定值。 在此 GPU 上,日志中记录为 2048(文档表格中为 None),并且还记录了max_prefill_tokens=16384。在具有不同内存大小的 GPU 上可能会有所不同(未实际测量)。 - 在同时使用混合块和 512 的服务器上,对并发 32 进行了三次服务器测试,结果显示 吞吐量为 835~837 tok/s(默认 SGLang 为 807~809),TTFT 中位数为 321~323ms(默认 1,089~1,094ms),ITL p99 为 66~67ms(默认 31~35ms),三次测试的结果在 1% 的误差范围内相同。减小块大小并进行混合的设置,带来了将 TTFT 缩短约 3.4 倍,但代价是将 ITL 尾部延迟增加约 2 倍的权衡。vLLM 的
--max-num-batched-tokens 512在相同负载下的吞吐量为 834~835,TTFT 为 335~338ms,ITL p99 为 70ms(默认 224ms)。我们没有剖析为什么会出现这种情况(推测)。 - 服务器准备时间相似。 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))
请以本文的标准评估第 4 篇的实验并找出其弱点。并发度为 1、固定文档、重复使用相同的文档、文档数量为 10 个导致 p99 成为最大值,这些都是问题所在。改进任务包括并发度 4(实验设置的 --max-num-seqs=4)、泊松到达率、长度分布以及前缀命中率记录。该实验在临时 Pod 中运行,本文未重新运行。风险在于与同一节点上其他工作负载的内存争用以及 GPU 占用,Pod 会被 --rm 删除,若残留则会在 1 小时后终止。
7. 面试常见问题
- vLLM 和 SGLang 的前缀缓存有何不同? vLLM 每隔 16 个 Token 的块就会在映射中查询将父哈希连接起来的哈希链,并从空闲块队列中通过 LRU 逐出。SGLang 将 Token 序列存储在基数树(Radix Tree)中,从叶子节点开始逐出,其默认 page-size 为 1。目前 vLLM 也默认开启此功能,因此两者的差异与其说是有无该功能,不如说是在粒度、调度策略和逐出策略上的不同。
- 如何验证 SGLang 在智能体(Agent)工作负载中表现更好的说法? 在相同的硬件、模型和精度下,使用相同的客户端以开放循环发送实际轮次结构(turn-structured)的追踪(Trace)。同时记录前缀命中率,并比较 p99 TTFT 和满足 SLO 的吞吐量。论文中的数据是与 vLLM v0.2.5 相比得出的,因此不能直接应用到当前版本中。
- 开放循环和封闭循环分别在什么时候使用? 为了找出容量和 SLO,可以使用开放循环不断提高请求率来寻找激增点。封闭循环用于模拟上层系统限制并发的环境,并了解在过载情况下延迟不再增长的极限。
- 基准测试结果异常优秀。应该怀疑什么? 检查是否由于重复相同的提示词而命中了前缀缓存、是否剔除了预热阶段、输出是否因 EOS 而过早结束,或者请求率是否因为
inf导致 TTFT 中掺杂了等待时间。请同时确认命中率指标以及输入和输出 Token 的分布。 - 如何将基准测试结果转换为 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 文档确认),以及基准测试说明中模拟的现实契合度。开放循环过载下延迟随时间增加这一点是基于模拟和排队论推导得出的,未通过查阅其他来源进行确认。