手里攒了 8 张 V100 32G 的人这几年多少有点五味杂陈。一方面这卡当年也是旗舰出身32G 显存放到今天依然能打另一方面大模型时代一来各种新方案的默认前提都变成了 A100、H100V100 似乎一下子就沦为能跑但很吃力的边缘角色。直到我真正接手一个业务侧的推理部署任务用 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-size2。这样每卡权重约 14GB剩余显存可以全部规划给 KV Cache。用 TP2 之后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 权重 28GBTP2 后单卡权重 14GBKV Cache 还能留出十几 GB完全够支撑不错的并发。只有当你想在 V100 上硬塞 65B 以上的大模型时才需要认真考虑 8bit4bit 我基本不建议老卡的解码速度本来就慢量化对 decode 阶段的 token 生成速度帮助很有限与其冒险降质量不如换个小一点的模型保体验。2.3 KV Cache 显存怎么估算vLLM 会把显存里除了权重和激活之外的可用部分都优先分配给 KV Cache所以能塞多大上下文和能开多大并发是同一件事。估算公式大致是单条 KV Cache 占用 2K 和 V× 层数 × 头数 × 头维度 × 字节数。以 14B 模型为例假设 40 层、40 个头、头维度 128FP16 下每 token 的 KV 大约 0.8MB1 万 token 的上下文窗口就是 8GB。这个数在配 max-model-len 时必须算进去。这也就解释了为什么我建议把 gpu-memory-utilization 控制在 0.90 而不是 0.95V100 的显存碎片比新卡明显设太高之后并发请求稍微波动几个就可能直接触发 CUDA OOM。留出 5% 到 10% 的余量让页分配器有腾挪空间比把每个 MB 都榨干更稳妥。3. 8 卡拓扑怎么切单副本、多副本和参数取舍3.1 按模型规模决定 Tensor Parallel 的度8 张 V100 32G 有几种常见切法一张卡跑 7B 以下的小模型、两张卡切一个 14B 模型、四张卡切一个 34B 模型、八张卡切一个 70B 模型。大方向就是让权重显存除以卡数之后还能给 KV Cache 和激活留出足够空间。但注意切分的卡数不是越多越好张量并行本身有通信开销V100 的卡间互联带宽相对新卡也不占优势所以不要盲目切到 8。我们实测 14B 模型用 TP2 比 TP4 的端到端吞吐高了约 15% 到 20%原因在于切到 4 卡后每一层 Transformer 都要跨卡做 attention 和 MLP 的 all-reduce 通信单 token 生成时间被明显拖长。只有当你手里的模型大到两张卡放不下时才需要往 TP4 或 TP8 走但要接受单卡 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 个副本每副本 TP2max-model-len4096max-num-seqs256gpu-memory-utilization0.90权重 FP16开启 prefix caching。并发单副本吞吐 tokens/s总吞吐 tokens/sTTFT P95 (ms)ITL P95 (ms)135140780528120480920583226010401350666435014002200781283901560520092这张表里最有信息量的不是最高吞吐而是拐点。并发从 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 副本每副本 TP28 卡铺满权重格式FP16不做量化上下文长度max-model-len4096批处理窗口max-num-seqs256显存管理gpu-memory-utilization0.90前缀缓存enable-prefix-cachingtrue网关并发上限控制在 64 到 128 之间超过 128 直接排队等待这套参数在线上跑出的效果是总吞吐稳定在 1400 tokens/s 到 1600 tokens/sTTFT P95 在 2 秒左右ITL P95 约 80ms。作为一套全老卡的推理集群这个表现已经能支撑一个中等体量的真实业务了。最后说点个人体会。V100 这批卡硬件底子是老的但它最大的本钱是显存够大、存量够多。大模型推理这个场景里算力不够可以用并发策略和请求分布来弥补显存不够才是真没救。vLLM 这类框架把显存效率提上来之后老卡的实际地位比我预想中高不少。如果你是刚接触这套组合别一上来就追量化、追超大模型先把批处理窗口、KV Cache 规划、副本数量这三个基本盘调对再谈吞吐。反正我实测下来这三件事做好了8 张 V100 的吞吐完全够一个中等业务线上使用。