vLLM 与 SGLang 并发实验:如何让性能数字可复现、可解释

本实验借助 AI 完成工程实现与验证。下文区分已验证的观察、待验证的假设和结果的适用边界。

一、问题:性能对比首先要保证比较条件一致

参与 LLM 推理框架贡献时,性能类修改经常需要回答:这次修改到底带来了什么变化?

如果两次测试使用不同模型、请求长度、客户端或运行参数,即使数字变好了,也很难判断变化来自代码还是测试条件。

为后续性能 PR 建立可复用的验证基础,我搭建了一个小型 benchmark 工程,第一版只比较 vLLM 和 SGLang:

  • 同一台机器、同一张 GPU。
  • 同一个模型快照。
  • 输入长度 1024,输出长度 256。
  • 并发分别为 1、8、32、64。
  • 收集 TTFT、TPOT、ITL、输出吞吐量、GPU 显存和 GPU 利用率。
  • 保存原始结果、运行环境,并自动生成表格和图。

这次实验的目标是建立受控基线。结果只适用于本文的硬件、模型和配置。

二、复现:固定模型、环境和请求矩阵

硬件与软件环境

项目 配置
GPU NVIDIA RTX 4060 Laptop,8188 MiB
GPU 驱动 616.64
PyTorch 2.13.0+cu130
PyTorch CUDA 13.0
模型 Qwen/Qwen2.5-0.5B-Instruct
数据类型 float16
最大上下文长度 2048
显存配置比例 0.45
Tensor Parallel 1
Prefix Cache 关闭
CUDA Graph 关闭

模型固定到以下 revision:

复制代码
7ae557604adf67be50417f59c2c2f167def9a775

实际运行的引擎 commit:

makefile 复制代码
vLLM:
ced6857afa0ea7b2e3f0846a62e1394e90f15607

SGLang:
af5c0659c7c133b77aa0e1557c6db83a449d093d

两套引擎使用同一份固定版本的 vLLM 官方 benchmark 客户端,通过 OpenAI-compatible completions 接口发起请求。

客户端运行时版本为 vLLM 0.30.0,同时保存安装包元数据,避免只记录仓库版本。

Workload

项目 设置
输入 随机合成 prompt,1024 tokens
输出 256 tokens
并发 1、8、32、64
每组重复次数 3
每次正式测试请求数 64
正式测试前预热 16 个请求
seed 20261002
temperature 0
ignore EOS 开启

两个引擎、四档并发、每档三次重复,总计:

ini 复制代码
2 × 4 × 3 = 24 次正式运行
24 × 64 = 1536 个正式请求

还需要说明后端选择:本次 vLLM 关闭 FlashInfer sampling,SGLang 使用 Triton attention 和 PyTorch sampling。因此,这是一组明确配置下的比较,不能代表两个引擎各自默认配置或最佳调优结果。

工程入口为:

lua 复制代码
python3 study.py config.local.json

配置文件需要填写实际环境、模型路径和启动参数。脚本负责启动服务、等待就绪、运行客户端、采样 GPU 指标并生成报告,只清理自己启动的进程组。

三、Root Cause:为什么只有最终数字还不够

本次没有修复某个引擎的单一性能 bug,也没有通过 profiling 确认具体瓶颈。工程首先处理的是测试证据容易失真的几个环节。

1. 请求长度不等于实际生成长度

设置输出长度为 256,不代表每个请求实际生成了 256 tokens。

提前 EOS、请求失败或统计口径变化,都可能改变工作量。吞吐量变高时,必须先确认服务确实完成了相同数量、相同长度的请求。

因此,报告生成前会检查实际输入和输出长度,以及成功请求数。

2. 工作树 commit 不一定对应实际安装版本

仓库 checkout 到某个 commit,并不能单独证明运行中的 Python 包就来自该 commit。

本次同时记录引擎源码版本、运行时版本和安装包元数据。来源不明确的数据不能作为有效比较结果。

3. GPU 利用率不能直接证明瓶颈

GPU 利用率是设备采样指标。它无法单独说明某个 kernel 已经饱和,也无法直接区分排队、prefill、decode 或 CPU 调度开销。

本次显存和利用率均为整张设备的指标,还可能包含 Windows、WSL 和桌面进程的影响。它们不是引擎进程独占的资源统计。

四、修改:把比较条件变成工程约束

工程主要实现了四部分。

首先,统一客户端和 workload,避免两个引擎使用不同请求生成方式或统计口径。

其次,保存服务启动日志、版本信息、原始客户端结果和 GPU 采样。GPU 采样间隔为 0.5 秒,通过正式测试的开始标记与运行时长近似对齐采样窗口。

然后,在生成报告前检查数据完整性。请求失败、长度不一致、指标非有限值、关键环境信息缺失或源码状态不满足要求时,不用默认值填补结果,而是拒绝生成有效比较。

最后,自动输出:

  • Markdown 实验报告。
  • CSV 和 JSON 结果。
  • PNG 和 SVG 图。
  • 原始文件的 SHA-256 校验信息。

报告中的延迟和吞吐量采用"三次运行各自均值的中位数"。

图中的误差线表示三次运行的最小值到最大值,不是置信区间。

五、测试:先验证数据可信,再解释性能

完整实验完成了 24 次正式运行、1536 个请求,实际输入和输出长度符合 1024/256 的要求。

工程工具和健康检查相关测试共 9 项通过。

搭建过程中也遇到了环境问题,包括 CUDA 编译工具与依赖版本匹配、首次启动编译耗时,以及服务健康检查超时。处理这些问题后,保留了对应日志和健康检查回归验证。

这些验证能够支持"本文记录的请求与指标符合实验约束",但没有覆盖完整模型精度验证、长期稳定性或生产 SLA。

六、Benchmark:并发增加,吞吐与延迟一起上升

TTFT 表示首 token 延迟;TPOT 表示首 token 后的平均每 token 时间;ITL 表示 token 间隔延迟。下表中两项数值一致,是本次结果的表现。

显存与 GPU 利用率是正式测试窗口内的设备级采样统计。每个单元格汇总三次重复运行。

引擎 并发 TTFT ms TPOT ms ITL ms 输出 tok/s 显存 MiB GPU %
vLLM 1 108.797 25.656 25.656 38.265 7602 56.520
vLLM 8 336.269 26.687 26.687 288.611 7632 59.491
vLLM 32 1540.927 36.519 36.519 748.255 7639 76.061
vLLM 64 4483.341 43.594 43.594 1024.441 7632 83.584
SGLang 1 68.901 24.908 24.908 39.870 7762 52.032
SGLang 8 283.826 26.950 26.950 275.718 7753 52.489
SGLang 32 983.756 29.631 29.631 958.525 7738 63.048
SGLang 64 1722.324 32.651 32.651 1630.277 7738 75.027

观察一:提高并发带来了吞吐增长,也增加了延迟

两个引擎都呈现出类似趋势:并发增加时,输出吞吐提高,TTFT 和 TPOT 也上升。

可能的解释是更多请求形成批处理,摊薄了部分执行开销;同时,每个请求需要承担更多等待和共享资源开销。

但这仍是待验证的解释。本次没有做分阶段 profiling,不能据此认定变化具体来自 scheduler、attention kernel 或 prefill/decode 的某一部分。

观察二:高并发结果存在明显重复运行差异

并发 64 时,三次运行的输出吞吐范围为:

bash 复制代码
vLLM:827.42--1271.89 tok/s
SGLang:1577.79--1640.69 tok/s

因此,只展示一次运行或单个汇总值,会遗漏重要信息。

采样中温度和时钟也有变化,但更快的运行并不总对应更高时钟。本次证据不足以把吞吐波动归因于温度或降频。

观察三:有限请求波次不能代表持续服务能力

并发 64 的每次测试只有 64 个正式请求,更接近一轮有限请求波次。

它不能直接回答持续到达流量下的最大承载能力,也不能推导 p99 延迟或 SLA。

此外,两个引擎顺序运行,没有交替配对测试,桌面负载也未完全隔离。这些因素都限制了结果的解释范围。

观察四:显存高不等于请求失败,也不代表还有足够余量

本次有效请求未发生 OOM,但设备级显存占用已较高。

这只能说明当前模型、配置和 workload 成功完成,不能推导更长上下文、更大模型或更高持续负载也能够运行。

七、Maintainer 反馈:目前仍是本地验证基线

这套 benchmark 工程尚未获得 maintainer review,也没有对应的已合并性能 PR。

本文数据没有验证 HiCache、P2P KV offload 等特定功能路径,不能作为这些功能的性能证据。

后续如果某个具体 PR 涉及性能变化,将使用这套工程执行 Before/After:

  1. 固定模型、机器、客户端和配置。
  2. 尽量只改变待验证的代码 commit。
  3. 交替运行 baseline 和修改版本。
  4. 保存原始数据和环境信息。
  5. 结合具体代码路径解释变化。

如果 PR 只修复正确性问题,没有合理的性能影响预期,就优先提供复现与回归测试,不额外制造性能结论。

八、踩坑总结:让结果可以被检查

这次工程最有价值的部分,是把测试条件和结果边界保存下来。

  • 检查实际工作量。 参数写了 1024/256,还要验证请求确实完成了相应长度。
  • 记录实际运行版本。 仓库 commit、安装包和运行时信息需要对应。
  • 保留重复运行差异。 中位数之外,范围同样重要。
  • 区分设备指标与进程指标。 整卡显存和利用率不能直接归因于引擎。
  • 区分观察与原因。 吞吐变化已经测到,具体瓶颈仍需要进一步验证。
  • 明确适用范围。 小模型、笔记本 GPU 和有限请求波次的结论,不能直接推广到生产集群。

后续会围绕真实性能 PR 使用这套基线,输出可检查的 Before/After 证据,并解释变化与代码之间的关系。

参考资料:

相关推荐
bullkingluo1 小时前
从零到一搭建企业级智能问答系统:Ch14 · 三层记忆与断点续跑
架构·llm·agent
10年前端老司机2 小时前
LangChain 五种工具定义方式踩坑总结
python·langchain·llm
AINative软件工程3 小时前
LLM 多租户 Prompt Isolation 工程实践:为 100 个租户定制 Prompt,但别让它们互相污染
后端·llm
stereohomology3 小时前
ZCode 是如何处理超长上下文的
llm·总结·薅羊毛
桃西西呀16 小时前
LangChain 之七:回调与可观测
人工智能·langchain·llm
桃西西呀16 小时前
LangChain 之六:记忆与历史
人工智能·langchain·llm
deepseek231 天前
硬预算帽默认值拆解:AWS 九月上线支出上限、GCP 七月跟进,Agent 时代按量付费必须默认断供
人工智能·llm·云计算·agent·aws
lucas_AI1 天前
别等上下文爆了才压缩:AutoCompact 教编码智能体主动「断舍离」
llm·agent
用户3134672143541 天前
Agent相关 - agent_scratchpad 到底该放哪?一个隐蔽的顺序坑
langchain·llm·agent