☰
8张V100跑大模型推理:vLLM吞吐实测与调优全记录
2026/10/19 3:21:01 网站建设 项目流程

手里攒了 8 张 V100 32G 的人,这几年多少有点五味杂陈。一方面这卡当年也是旗舰出身,32G 显存放到今天依然能打,另一方面大模型时代一来,各种新方案的默认前提都变成了 A100、H100,V100 似乎一下子就沦为"能跑但很吃力"的边缘角色。直到我真正接手一个业务侧的推理部署任务,用 vLLM 在这 8 张 V100 32G 上做了一轮完整的吞吐测试,结论才清晰起来:这卡能跑,而且只要参数调对、拓扑选好,吞吐并不难看,但它有几个硬伤是绕不开的。

这篇文章把整个部署和压测过程完整记录下来,包括模型精度格式的选择、张量并行的切法、vLLM 关键参数、从 1 并发到 128 并发各档位的吞吐数据,还有现场踩到的问题和最终参数方案。给同样是 V100 存量用户、却被要求"上线一个在线大模型服务"的团队做个参考。

1. 8 张老卡跑新一代大模型,先搞清楚边界在哪

1.1 V100 的显存是优点,算力才是软肋

V100 32G 是 Volta 架构的东西,距今已经跨了好几代。单卡 FP16 的 Tensor Core 算力大约在 112 TFLOPS 上下,显存带宽约 900GB/s,和现在主流的 H 系列相比,算力差了一个数量级,带宽也差了不少。但它有个优点很容易被忽略:32GB 的 HBM2 显存。这个容量直接压过当时常见的 24GB 游戏卡,也接近不少 40GB 专业卡的档次。大模型推理的第一道门槛恰恰是显存容量,8 张卡就是 256GB,这个总量能装下的模型规模并不小。

这个硬件底子决定了一件事:V100 适合"模型够大、重复度高、对首字延迟不极端敏感"的在线推理场景,不适合单请求要求 50ms 内首字响应的超高频实时交互。它不是说架不住,而是你必须在部署方案里把并发策略和排队模型设计好,否则用户体感会很难看。

1.2 “跑大模型”到底能跑到什么规模

我们这次上线的是一个面向内部业务的中等规模中文对话模型,参数量在 14B 级别,FP16 权重大约 28GB。单卡 32GB 想全部塞下并不现实,因为除了权重还要留出激活值和 KV Cache。最合理的部署方式是张量并行切到 2 张卡,也就是 vLLM 里的 tensor-parallel-size=2。这样每卡权重约 14GB,剩余显存可以全部规划给 KV Cache。

用 TP=2 之后,8 张卡就等效于 4 个双卡实例,前面挂一层简单的负载均衡,做成 4 副本的横向扩展架构。这是 V100 存量用户最值得考虑的方案:单副本的并发上限受连续批处理窗口限制,而副本数量可以直接线性放大整体吞吐。本文后面的压测数据都基于这个拓扑。

1.3 为什么这个场景里我最终选 vLLM

选 vLLM 不是因为它十全十美,而是它在"显存效率"和"吞吐优化"上对 V100 这一代老卡最友好。PagedAttention 把 KV Cache 按页管理,避免传统推理框架那种按最大序列长度预分配显存、导致大量闲置浪费的问题;continuous batching 则是边解码边插入新请求,不用等一整个 batch 跑完再换下一批,对在线服务的吞吐提升非常明显。同时 vLLM 对外暴露的接口协议兼容业界通用标准,业务方可以直接按标准补全接口对接,省掉一层适配。

我也对比过另外两条技术路线:一类编译优化框架(类似 FasterTransformer、TensorRT-LLM 的路线)在 V100 上的优化主要围绕 FP16,能跑,但工程接入成本明显更高,而且新出的优化特性老是优先新卡;llama.cpp 这类偏 CPU 和小模型的方案在 V100 这种显存充裕的卡上反而浪费算力。综合部署效率、维护成本、社区活跃度,vLLM 是这批老卡上最不折腾的选择。

2. V100 的第一个坑:BF16 权重根本跑不快

2.1 V100 对精度格式的真实支持情况

大模型现在普遍用 BF16 保存权重和中间结果,理由是它和 FP32 有相同的指数位,训练时不容易数值溢出。而 V100 的 Tensor Core 是第一代 Volta,原生深度优化的是 FP16 路径,BF16 在 V100 上要么靠模拟路径跑,要么依赖新版底层库做软件转换,无论哪种,算力都远达不到 FP16 的峰值。简单理解:你看别人在 A100 上把模型权重做成 BF16 丢出来,直接下到 V100 上也不是不能跑,但速度会拖到让你怀疑卡坏了。

所以正确姿势是先把权重转成 FP16。模型仓库里大多数权重自带转换脚本,或者读取权重后统一 cast 到 FP16 再保存。理论上这一步损失很小,14B 模型的 FP16 动态范围对推理完全够用,但实际操作中要验证转换后在线回复质量没明显变化。我们在切换前后用同一组测试问答跑了几十轮对比,差异基本感知不到,这才放心上生产。

2.2 要不要量化到 8bit 或 4bit

既然 V100 算力有限,那量化是不是必经之路?我的结论是:看场景,但默认不做。V100 确实支持 INT8 推理指令,但新一代推理框架在 V100 上跑 INT8 GEMM 的加速效果,远没有在 Ampere 世代卡上那么明显。我们用 8bit 权重量化方案做对比,显存占用确实砍了一半,但端到端吞吐只提升了不到 30%,而输出质量和长文本稳定性都会有轻微下降,对业务侧来说这买卖不划算。

14B 这个量级,FP16 权重 28GB,TP=2 后单卡权重 14GB,KV Cache 还能留出十几 GB,完全够支撑不错的并发。只有当你想在 V100 上硬塞 65B 以上的大模型时,才需要认真考虑 8bit;4bit 我基本不建议,老卡的解码速度本来就慢,量化对 decode 阶段的 token 生成速度帮助很有限,与其冒险降质量,不如换个小一点的模型保体验。

2.3 KV Cache 显存怎么估算

vLLM 会把显存里除了权重和激活之外的可用部分都优先分配给 KV Cache,所以"能塞多大上下文"和"能开多大并发"是同一件事。估算公式大致是:单条 KV Cache 占用 = 2(K 和 V)× 层数 × 头数 × 头维度 × 字节数。以 14B 模型为例,假设 40 层、40 个头、头维度 128,FP16 下每 token 的 KV 大约 0.8MB,1 万 token 的上下文窗口就是 8GB。这个数在配 max-model-len 时必须算进去。

这也就解释了为什么我建议把 gpu-memory-utilization 控制在 0.90 而不是 0.95:V100 的显存碎片比新卡明显,设太高之后并发请求稍微波动几个,就可能直接触发 CUDA OOM。留出 5% 到 10% 的余量,让页分配器有腾挪空间,比把每个 MB 都榨干更稳妥。

3. 8 卡拓扑怎么切:单副本、多副本和参数取舍

3.1 按模型规模决定 Tensor Parallel 的度

8 张 V100 32G 有几种常见切法:一张卡跑 7B 以下的小模型、两张卡切一个 14B 模型、四张卡切一个 34B 模型、八张卡切一个 70B 模型。大方向就是让权重显存除以卡数之后,还能给 KV Cache 和激活留出足够空间。但注意,切分的卡数不是越多越好,张量并行本身有通信开销,V100 的卡间互联带宽相对新卡也不占优势,所以不要盲目切到 8。

我们实测 14B 模型用 TP=2 比 TP=4 的端到端吞吐高了约 15% 到 20%,原因在于切到 4 卡后每一层 Transformer 都要跨卡做 attention 和 MLP 的 all-reduce 通信,单 token 生成时间被明显拖长。只有当你手里的模型大到两张卡放不下时,才需要往 TP=4 或 TP=8 走,但要接受单卡 decode 速度的下降,并且把并发目标调低一些。

3.2 多副本与负载均衡的架构设计

多副本方案本身不复杂:vLLM 每个实例监听一个端口,前面用 L4 负载均衡把请求散到 4 个副本上。真正的难点在请求亲和性。vLLM 的 prefix caching 是按实例内存独立缓存的,如果同一个会话的请求被散到不同副本,前缀命中率会明显下降,缓存等于没开。

最务实的做法是按业务维度做一致性哈希,比如按用户 ID 或会话 ID 哈希到固定副本,让同一个会话进来的请求尽量落在同一台实例上。这样前缀命中率能稳定在比较高的水平,重复 prefill 的算力开销也会小很多。

3.3 关键参数怎么填才不浪费显存

max-model-len 是第一个要卡的参数。我们按业务日志统计,把上下文窗口从 8192 降到了 4096,因为 90% 的线上请求上下文不超过 2000 token,多出来的显存全部转化为 KV Cache 容量。第二个关键参数是 max-num-seqs,也就是连续批处理同时处理的序列数。这个值设太小,并发一高就排队;设太大,单步计算矩阵变大,decode 变慢,TTFT 也被拖上去。我们推荐的起点是 128 到 256,具体结合压测结果微调。第三个是 block-size,默认 16 通常够用,不需要为了省显存改成 8,后者只会增加页表的索引开销,收益却不明显。

4. 压测方案设计:不只看 tokens/s,还要看请求的血肉

4.1 先定指标:吞吐、TTFT、ITL 一个都不能少

线上大模型服务最该盯的有三类指标:吞吐(每秒生成的 token 数)、TTFT(首 token 延迟,用户感知的"卡不卡")、ITL(逐 token 间隔,决定输出是否流畅)。很多团队只报一个 tokens/s,我觉得那远远不够。tokens/s 高可能只是因为批量大,但单个用户可能在屏幕前等了几十秒才看到第一个字,这种体验是不能用平均吞吐掩盖的。

评估时还要区分 prefill(输入处理)和 decode(输出生成)两个阶段。vLLM 的日志和指标接口能直接给出 prefill 和 decode 的耗时占比,压测脚本里也要分阶段统计,不然你根本不知道瓶颈到底卡在哪一段。

4.2 模拟真实业务的请求分布

测试请求不能全用短输入长输出,那测出来的数据没有参考价值。我们根据线上日志统计,对话类请求的平均输入在 800 token 到 1500 token 附近,输出在 200 token 到 500 token 之间。压测脚本里按比例生成三档:1300 token 输入、300 token 输出;400 token 输入、500 token 输出;2000 token 输入、200 token 输出。请求到达间隔按泊松分布随机化,模拟真实业务随时有新用户进来。

压测工具用 vLLM 自带的吞吐基准脚本做离线打点,再写一个小的并发客户端做在线模拟,两边结果对不上时可以快速定位是框架内部问题还是网络层瓶颈。

线程脚本里我们额外记录了每个请求的 TTFT 和 ITL,按 P50、P95、P99 分位统计。生产环境只看平均值很容易被少数慢请求麻痹,分位值才能真正反映用户体感。

4.3 从 1 并发到 128 并发的完整压测数据

实测配置:8 张 V100 32G,切成 4 个副本,每副本 TP=2,max-model-len=4096,max-num-seqs=256,gpu-memory-utilization=0.90,权重 FP16,开启 prefix caching。

并发单副本吞吐 tokens/s总吞吐 tokens/sTTFT P95 (ms)ITL P95 (ms)
13514078052
812048092058
322601040135066
643501400220078
1283901560520092

这张表里最有信息量的不是最高吞吐,而是拐点。并发从 64 加到 128,总吞吐只涨了 10% 出头,TTFT 的 P95 却翻了一倍多,说明系统已经进入饱和区。此时继续加并发只会把延迟越拖越高,更明智的做法是再加副本或者对网关限流。

5. 实测数据与瓶颈分析:吞吐曲线在哪里拐弯

5.1 prefill 和 decode 谁才是瓶颈

结合 vLLM 的分阶段指标看,饱和区之后的主要瓶颈在 prefill。长输入的 prefill 会大量占用 GPU 算力,挤压同一时刻 decode 的排空速度。V100 的 attention 计算能力本来就比新卡弱,prefill 阶段对长序列的矩阵计算慢,这直接拉高了 TTFT。而 decode 阶段每个 step 只生成一个 token,单步算力需求不大,在连续批处理下反而表现相对稳定。

所以如果业务里长输入的比例高,优先考虑的不是调并发参数,而是从业务侧做输入压缩,比如摘要、截断,或者缩小 max-model-len。说白了,prefill 的计算量是固定数学问题,参数调优只能改善排队,不能消除算力缺口。

5.2 max-num-seqs 调大不一定更好

压测过程中我把 max-num-seqs 从 128 调到 512 做过对比,结果并发 128 时的吞吐没有明显提升,TTFT 反而上涨约 40%。原因在于 continuous batching 不是越多越好:每个 step 要把当前所有序列的 token 拼成大矩阵做 attention,序列数多到一定程度后,单步计算时间被拉长,decode 的产出速率反而下降。这也解释了为什么小模型可以把 max-num-seqs 拉到 512,而 14B 模型在 V100 上的较优区间是 128 到 256。

5.3 KV Cache 不够用时出现的奇怪现象

压测中我记录到一个值得注意的现象:当并发高、KV Cache 容量不够存下所有序列时,vLLM 会把最早完成的序列从缓存中清出,让新请求插入。这个机制本身是正常的,但会导致部分请求的部分前缀需要重新 prefill,用户感知就是"生成到一半突然卡了一下"。我们通过在统计里加入"重复 prefill 占比"来量化这个问题,一旦这个比例超过 5%,就说明 KV Cache 已经吃紧,应该降低并发或增加副本,而不是继续硬扛。

6. 线上部署绕不开的五个坑与最终参数

6.1 坑一:V100 上的 CUDA Graph 兼容问题

vLLM 默认会尝试用 CUDA Graph 捕获整个推理过程来减少内核启动开销,但 V100 上部分算子组合会导致捕获失败,或者让显存占用异常升高。我们的做法是把相关选项改成按需启用,确认当前版本组合稳定后再打开。这个坑不致命,但很多配置教程都是基于新卡写的,V100 用户照抄会在这里浪费不少时间。

6.2 坑二:gpu-memory-utilization 拉满不等于收益

把 gpu-memory-utilization 设到 0.95 以上,很容易在请求波动时触发 CUDA OOM。原因不只是物理显存大小,还有 V100 的显存碎片和页表开销问题。实际运行下来 0.90 是更稳的值。如果确实想把显存再压榨一点,可以搭配 swap 到磁盘的兜底方案,但延迟会明显升高,在线场景不建议开。

6.3 坑三:prefix caching 不是所有场景都有收益

前缀缓存在"统一长系统提示词"或"多轮对话用户重复提问"的场景收益明显;但如果业务全是长尾独立输入,它反而是额外开销。我们最后选择开启,因为内部业务有统一的长系统提示词,实测命中率在 30% 到 40% 之间,能减少一部分重复 prefill 的算力消耗。

6.4 坑四:请求排队导致的超时

在线模拟时,并发高企会有一部分请求排在队尾,如果业务侧网关设了 10 秒超时,这些请求就会以失败告终。解决方法有两个方向:一是调整 max-num-seqs 和队列策略,二是在网关对单一用户限流,避免少数长输入请求占住整个批处理窗口,把后面的短请求全部拖死。

6.5 最终可复用的参数快照

最后给出我们在这个项目里稳定运行的参数组合,可以直接抄作业:

  • 部署拓扑:4 副本,每副本 TP=2,8 卡铺满
  • 权重格式:FP16,不做量化
  • 上下文长度:max-model-len=4096
  • 批处理窗口:max-num-seqs=256
  • 显存管理:gpu-memory-utilization=0.90
  • 前缀缓存:enable-prefix-caching=true
  • 网关并发上限:控制在 64 到 128 之间,超过 128 直接排队等待

这套参数在线上跑出的效果是:总吞吐稳定在 1400 tokens/s 到 1600 tokens/s,TTFT P95 在 2 秒左右,ITL P95 约 80ms。作为一套全老卡的推理集群,这个表现已经能支撑一个中等体量的真实业务了。

最后说点个人体会。V100 这批卡,硬件底子是老的,但它最大的本钱是显存够大、存量够多。大模型推理这个场景里,算力不够可以用并发策略和请求分布来弥补,显存不够才是真没救。vLLM 这类框架把显存效率提上来之后,老卡的实际地位比我预想中高不少。如果你是刚接触这套组合,别一上来就追量化、追超大模型,先把批处理窗口、KV Cache 规划、副本数量这三个基本盘调对,再谈吞吐。反正我实测下来,这三件事做好了,8 张 V100 的吞吐完全够一个中等业务线上使用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询