资讯动态

vLLM与SGLang并发性能实验:可复现基准测试与调优指南

发布时间:2026/10/26 4:49:42 来源:尧图企业网站定制
1. 并发实验的起点为什么性能数字总是对不上做推理服务优化的人大概都经历过这种场景你在自己的机器上跑 vLLM测出来 QPS 是 18.5延迟 P99 是 320ms兴冲冲地把配置发给同事他在另一台机器上一跑QPS 只有 11.2P99 直接飙到 800ms。然后你们开始互相怀疑——是不是你那边 GPU 型号不一样是不是驱动版本有差异是不是模型权重加载方式不同折腾半天最后发现只是max_num_seqs一个参数没对齐。这不是个例。vLLM 和 SGLang 作为当前最主流的两个推理引擎在并发场景下的性能表现受太多因素影响批处理策略、KV Cache 管理方式、调度器实现、GPU 互联拓扑、甚至 CUDA Graph 的捕获时机。任何一个环节的差异都会让最终的数字产生巨大偏移。更麻烦的是很多人在做实验时只记录了“QPS”和“延迟”两个结果指标却忽略了中间过程的可观测性导致数字一旦对不上根本无从排查。这篇文章想聊的就是这件事怎么让 vLLM 和 SGLang 的并发实验从“跑个分”变成“可复现、可解释”的工程实践。我会从实验设计、环境控制、参数对齐、指标采集、结果分析这几个维度展开把我在实际项目中踩过的坑和总结的方法论完整分享出来。适合正在做推理服务选型、性能调优、或者需要给团队输出可信 benchmark 报告的朋友参考。无论你是刚接触 vLLM 的新手还是已经在生产环境跑 SGLang 的老手应该都能从中找到一些可以直接抄作业的东西。2. 实验设计先行把“跑个分”变成可复现的工程流程2.1 为什么大多数并发实验从一开始就不可复现我见过太多团队做推理性能测试的方式是这样的找一台空闲机器装好 vLLM拿一个开源模型用默认参数启动服务然后拿一个压测脚本打流量记录吞吐和延迟完事。整个过程没有任何版本锁定、没有环境快照、没有参数记录甚至连用的哪个模型 revision 都没写清楚。这种实验的问题在于它产生的数字只对“那一次运行”有效。换一天再跑可能因为 GPU 温度不同导致频率波动数字就变了。换一个人来跑可能因为 pip 安装的依赖版本不同性能又变了。更隐蔽的是vLLM 和 SGLang 都在快速迭代同一个版本号下的不同 commit 可能就有显著的性能差异。要让实验可复现第一件事就是建立“实验清单”的概念。每次实验必须记录以下信息类别必须记录的内容记录方式硬件环境GPU 型号、数量、互联方式、CPU 型号、内存容量实验前手动确认并写入报告软件环境驱动版本、CUDA 版本、Python 版本、推理引擎版本及 commit用脚本自动采集模型信息模型名称、revision、量化方式、权重格式从模型配置文件读取启动参数所有非默认参数、环境变量启动脚本完整保存压测配置并发数、输入输出长度分布、请求速率压测脚本参数化采集指标吞吐、延迟分位数、GPU 利用率、显存占用统一采集工具这张表看起来简单但真正每次都完整执行的人少之又少。我的做法是把这些检查项写成一个preflight.sh脚本每次实验前跑一遍自动生成一份环境快照。这样即使过了几个月回头看也能知道当时的确切环境。2.2 控制变量哪些因素必须锁死哪些可以放开做并发实验最核心的原则是控制变量。但推理场景的变量太多不可能全部锁死所以要区分“必须锁死”和“可以放开”两类。必须锁死的因素包括GPU 型号和数量、驱动和 CUDA 版本、推理引擎版本、模型权重、量化方式、张量并行度、KV Cache 数据类型。这些因素一旦变化性能数字就没有可比性。特别是张量并行度很多人会忽略这一点——TP1 和 TP2 在同样的硬件上吞吐可能差 30% 以上因为通信开销和计算分布完全不同。可以放开的因素包括请求的输入输出长度分布、并发数、请求到达模式。这些正是实验要探索的自变量需要系统性地变化。但即使是这些“放开”的变量也要保证在对比不同引擎时使用完全相同的分布。这里有个容易踩的坑输入输出长度分布。很多人压测时用固定长度比如输入 128 token、输出 128 token这样测出来的数字很漂亮但和真实场景差距很大。真实场景的输入长度往往服从长尾分布输出长度也不固定。我的建议是至少准备三组分布短输入短输出如 64/64、中等长度如 512/256、长输入长输出如 2048/512分别测试。这样才能看出引擎在不同负载特征下的表现差异。2.3 实验矩阵设计用最少的运行次数覆盖最多的场景全因子实验的成本太高。假设你有 3 个并发级别、3 种输入输出分布、2 个引擎、2 种 TP 配置全跑一遍就是 36 次实验每次至少 10 分钟加起来 6 个小时。如果再加上不同的调度参数时间会爆炸。我的做法是分阶段做实验。第一阶段做“基线对比”固定一组典型参数只对比 vLLM 和 SGLang 在相同硬件上的表现目的是建立初步认知。第二阶段做“参数敏感性”选定一个引擎系统性地变化关键参数如max_num_seqs、gpu_memory_utilization、chunked_prefill开关找出性能拐点。第三阶段做“场景验证”用最接近真实业务的负载分布验证前两阶段结论是否成立。这样下来总实验次数可以控制在 15 次以内但覆盖的信息量足够支撑决策。关键是每个阶段的目标要明确不要为了“多测几个点”而浪费机器时间。3. vLLM 与 SGLang 的核心差异理解数字背后的机制3.1 调度策略差异如何影响并发表现vLLM 和 SGLang 在调度层面的设计哲学不同这是导致性能数字差异的根本原因之一。vLLM 采用的是 PagedAttention 加连续批处理continuous batching的方案。它的调度器以“迭代”为单位每个迭代会尽可能多地把等待中的请求塞进当前批次直到达到max_num_seqs或显存上限。这种策略的优点是吞吐高因为 GPU 计算资源被充分利用缺点是延迟可能不稳定因为一个长请求会拖慢同批次的其他请求。SGLang 则引入了 RadixAttention 和更细粒度的调度。它的核心创新在于前缀缓存prefix caching和更灵活的请求组合方式。在多轮对话、共享系统提示词这类场景下SGLang 能显著减少重复计算从而降低延迟、提升吞吐。但在纯粹的独立请求场景下两者的差距可能并不明显。理解这个差异之后你就知道为什么同样的并发数下两个引擎的延迟分布可能完全不同。vLLM 的 P99 可能更高因为它倾向于把请求攒成大批次SGLang 的 P50 可能更低因为它的前缀缓存让短请求更快完成。如果你只看平均延迟可能会得出错误的结论。3.2 KV Cache 管理显存效率决定并发上限KV Cache 是推理引擎最核心的资源。并发数能开多大本质上取决于 KV Cache 能装下多少请求。vLLM 的 PagedAttention 把 KV Cache 分成固定大小的块block按需分配。块大小默认是 16 个 token可以通过block_size参数调整。块越小显存碎片越少但管理开销越大块越大管理简单但可能浪费显存。在实际测试中我发现对于长序列场景把block_size从 16 调到 32 可以减少约 5% 的显存碎片但吞吐提升并不明显因为管理开销也增加了。SGLang 的 RadixAttention 则用基数树radix tree来组织 KV Cache天然支持前缀共享。这意味着如果多个请求有相同的前缀比如相同的 system prompt它们可以共享同一份 KV Cache显存占用大幅降低。在客服机器人、代码助手这类场景下这个优势非常明显。这里有个实操要点测试并发上限时不要只看“能启动多少并发”而要看“在满足延迟 SLA 的前提下能稳定支撑多少并发”。我通常会用逐步加压的方式从低并发开始每次增加 10%观察 P99 延迟的变化。当 P99 超过目标值的 1.5 倍时就认为达到了实际并发上限。3.3 无 NVLink 环境下的性能衰减实测这是很多人关心的问题如果没有 NVLink只有 PCIe 互联vLLM 和 SGLang 的性能会受多大影响我在一台 4 卡 A100 的机器上做过对比测试分别用 NVLink 模式和 PCIe 模式跑张量并行。测试模型是 Qwen 系列的 14B 模型TP4输入 512 token输出 256 token并发数从 1 逐步加到 64。并发数NVLink 吞吐 (tok/s)PCIe 吞吐 (tok/s)衰减比例11421382.8%889275615.2%161456112322.9%321987134532.3%642234142136.4%可以看到低并发时 NVLink 和 PCIe 的差距不大因为通信量小。但随着并发增加张量并行需要频繁地做 all-reduce 通信PCIe 的带宽瓶颈就暴露出来了。到 64 并发时PCIe 模式的吞吐只有 NVLink 的 63.6%。这个数据对选型很有参考意义。如果你只有 PCIe 机器要么降低张量并行度比如用 TP2 而不是 TP4要么接受吞吐损失。SGLang 在无 NVLink 环境下的表现略好于 vLLM因为它的调度器更倾向于减少跨卡通信但差距在 5% 以内不算决定性优势。4. 实操过程从零搭建可复现的并发实验环境4.1 环境准备与版本锁定第一步是准备一个干净的环境。我强烈建议用 Docker 或者 conda 来隔离环境避免污染宿主机上已有的 Python 包。这里有个很多人踩过的坑安装 vLLM 会改变已经安装好的 torch 版本。因为 vLLM 对 torch 版本有严格要求pip 安装时会自动升级或降级 torch导致其他依赖 torch 的项目崩溃。我的做法是为每个推理引擎创建独立的 conda 环境conda create -n vllm-exp python3.10 -y conda activate vllm-exp pip install vllm0.6.3安装完成后立即用pip freeze requirements-vllm.txt锁定所有依赖版本。SGLang 同理单独建一个环境conda create -n sglang-exp python3.10 -y conda activate sglang-exp pip install sglang0.3.5 pip freeze requirements-sglang.txt这样两个环境互不干扰也方便后续复现。如果需要切换直接conda activate对应环境即可。注意不要在同一台机器上同时用两个环境跑压测因为 GPU 资源会互相争抢。要么串行执行要么用不同的 GPU 分组。4.2 启动参数配置与对齐启动 vLLM 服务时关键参数包括python -m vllm.entrypoints.openai.api_server \ --model /path/to/model \ --tensor-parallel-size 2 \ --max-num-seqs 128 \ --gpu-memory-utilization 0.90 \ --block-size 16 \ --enable-chunked-prefill \ --max-model-len 4096 \ --port 8000SGLang 的启动方式类似python -m sglang.launch_server \ --model-path /path/to/model \ --tp-size 2 \ --max-running-requests 128 \ --mem-fraction-static 0.90 \ --chunked-prefill-size 4096 \ --port 8000这里要注意参数的对齐。vLLM 的max_num_seqs对应 SGLang 的max_running_requestsgpu_memory_utilization对应mem_fraction_static。虽然名字不同但含义基本一致。做对比实验时这些参数必须设置成相同的值否则数字没有可比性。还有一个容易忽略的点max_model_len和context_length。如果模型支持 32K 上下文但你只设了 4KKV Cache 的分配策略会完全不同。我建议在实验报告中明确写出这个值并且在不同引擎间保持一致。4.3 压测工具选择与流量生成压测工具的选择直接影响结果的可信度。我试过几种方案locust灵活可以自定义请求分布但 Python 实现的并发能力有限高并发时自身可能成为瓶颈。wrk/ab性能好但不支持流式输出无法准确测量首 token 延迟。vLLM 自带的 benchmark_serving.py专门为推理场景设计支持流式输出和多种指标采集推荐使用。SGLang 自带的 bench_serving.py功能类似参数略有不同。我的建议是统一用 vLLM 的benchmark_serving.py来压测两个引擎因为它的指标定义清晰输出格式统一。使用时指定请求分布python benchmark_serving.py \ --backend openai \ --base-url http://localhost:8000 \ --model /path/to/model \ --dataset-name random \ --random-input-len 512 \ --random-output-len 256 \ --num-prompts 500 \ --request-rate 10 \ --seed 42--seed 42很重要它保证每次生成的请求序列完全一致这样不同引擎跑出来的数字才有可比性。--request-rate控制请求到达速率设为inf表示尽可能快地发送用来测最大吞吐设为固定值则用来测特定负载下的延迟。4.4 指标采集与数据记录压测工具输出的指标包括请求吞吐requests/s、token 吞吐tokens/s、首 token 延迟TTFT、每 token 延迟TPOT、端到端延迟的 P50/P90/P99。这些指标要全部记录不能只看吞吐。除了压测工具的输出还要采集 GPU 侧的指标。我通常用nvidia-smi dmon或者dcgm来记录 GPU 利用率、显存占用、功耗、温度。这些数据能帮助解释性能差异——比如两个引擎吞吐差 20%如果 GPU 利用率也差 20%那说明是计算资源利用效率的问题如果利用率相同但吞吐不同那可能是调度或通信的问题。数据记录建议用 CSV 格式每次实验一行包含所有环境信息、参数、指标。这样后续可以用 pandas 做分析画图也方便。5. 常见问题与排查技巧实录5.1 性能数字波动大的排查思路性能数字波动超过 10% 时按以下顺序排查GPU 频率是否稳定用nvidia-smi -q -d CLOCK查看当前频率。如果 GPU 因为温度过高而降频性能会明显下降。解决方法是确保散热良好或者用nvidia-smi -lgc锁定频率。是否有其他进程占用 GPUnvidia-smi查看是否有残留进程。有时候上一次实验的进程没完全退出会占用显存和计算资源。请求分布是否一致检查压测脚本的 seed 是否固定输入输出长度是否和上次一致。KV Cache 是否命中如果两次实验之间没有重启服务第二次可能命中了前缀缓存导致数字偏乐观。建议每次实验前重启服务。网络或 CPU 瓶颈如果压测客户端和服务端不在同一台机器网络延迟会影响 TTFT。CPU 如果太弱tokenize 和 detokenize 也会成为瓶颈。5.2 显存不足OOM的预防与处理OOM 是并发实验中最常见的问题。预防措施包括把gpu_memory_utilization从 0.95 降到 0.90留出缓冲。降低max_num_seqs减少同时处理的请求数。缩短max_model_len减少 KV Cache 的最大占用。如果模型支持启用量化如 AWQ、GPTQ显存占用可以降低一半左右。如果已经 OOMvLLM 和 SGLang 的报错信息不同。vLLM 通常会提示 CUDA out of memory 并给出当前分配的 block 数SGLang 会提示 mem_fraction_static is too high。根据提示调整对应参数即可。5.3 两个引擎数字差异过大的解释框架当你发现 vLLM 和 SGLang 的性能差异超过预期时可以从以下维度分析差异来源vLLM 特点SGLang 特点影响程度前缀缓存需要显式开启默认开启高共享前缀场景调度粒度迭代级请求级中通信优化标准 all-reduce定制通信低NVLink 下显存管理PagedAttentionRadixAttention中CUDA Graph支持支持低大部分情况下差异主要来自前缀缓存和调度策略。如果你的场景有大量共享前缀SGLang 的优势会非常明显如果是完全独立的请求两者差距通常在 10% 以内。5.4 实验报告模板与复现清单最后分享一个我常用的实验报告模板确保每次实验都能被复现实验编号EXP-2024-001 日期2024-XX-XX 目的对比 vLLM 和 SGLang 在 TP2 下的并发性能 环境 - GPUA100 80G x 4 - 驱动535.XX - CUDA12.2 - vLLM0.6.3 - SGLang0.3.5 - 模型Qwen2.5-14B-Instruct 参数 - TP2 - max_num_seqs / max_running_requests128 - gpu_memory_utilization / mem_fraction_static0.90 - max_model_len4096 - 输入/输出长度512/256 - 并发数1, 8, 16, 32, 64 - 请求数500 - seed42 结果 附 CSV 和图表 结论 基于数据的分析这个模板看起来繁琐但坚持用几次之后你会发现排查问题的时间大幅减少。因为所有变量都被记录在案任何异常都能快速定位。6. 让数字说话从实验数据到决策依据6.1 如何解读吞吐-延迟曲线吞吐和延迟的关系不是线性的。在低并发区增加并发会提升吞吐延迟基本不变到了某个拐点吞吐增长放缓延迟开始上升再往上吞吐可能不升反降延迟急剧恶化。这个拐点就是系统的“最佳工作点”。对比 vLLM 和 SGLang 时不要只看最大吞吐而要看在目标延迟 SLA 下的吞吐。比如你的业务要求 P99 延迟不超过 500ms那么就要找到两个引擎各自在 P99500ms 时的吞吐这个数字才是真正有决策价值的。我通常会画一张图横轴是吞吐纵轴是 P99 延迟两个引擎各画一条曲线。曲线越靠左上越好高吞吐低延迟。如果两条曲线交叉说明在不同负载下各有优势需要根据实际业务负载来选择。6.2 成本换算每百万 token 的硬件成本性能数字最终要换算成成本才有意义。假设一台 4 卡 A100 的机器每小时成本是 20 元云厂商按需价格vLLM 在最佳工作点下的吞吐是 2000 tokens/s那么每百万 token 的成本是20 元/小时 ÷ 3600 秒 × 1,000,000 ÷ 2000 2.78 元/百万 token如果 SGLang 的吞吐是 2200 tokens/s成本就是 2.53 元/百万 token。差距看起来不大但在大规模部署下10% 的成本差异就是真金白银。这个换算还能帮你判断是否值得上更贵的硬件。比如 NVLink 机器比 PCIe 机器贵 30%但吞吐只提升 15%那就不划算。反之如果吞吐提升 50%那就值得投资。6.3 从实验到生产的参数迁移实验环境的最佳参数不一定适合生产环境。生产环境的请求分布更复杂可能有突发流量也可能有长尾请求。我的建议是实验得出的max_num_seqs作为生产值的上限实际设置时留 20% 余量。gpu_memory_utilization在生产环境降到 0.85避免突发流量导致 OOM。开启监控和自动扩缩容根据实际负载动态调整实例数。定期复跑实验因为引擎版本更新可能改变性能特征。6.4 持续复现把实验变成日常流程一次性的实验价值有限真正有价值的是持续复现的能力。我建议把实验脚本化、自动化每次引擎版本更新或者硬件变更时自动跑一遍基准测试生成对比报告。这样你能第一时间发现性能回归也能积累长期的数据趋势。具体做法是用 CI/CD 工具如 Jenkins、GitLab CI定时触发实验脚本结果自动写入数据库用 Grafana 或类似工具做可视化。这套流程搭建一次后续维护成本很低但收益很大。我个人在实际操作中的体会是推理性能实验最怕的不是数字不好看而是数字不可信。一个不可信的数字比没有数字更危险因为它可能导致错误的决策。把实验做扎实把变量控制好把过程记录下来这样得出的结论才能经得起推敲。后续如果要做更深入的调优比如针对特定模型结构优化 kernel或者尝试不同的量化方案这套实验框架也能直接复用不用从头再来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑