☰
8张V100实测:vLLM部署大模型推理与吞吐调优全记录
2026/10/17 14:47:14 网站建设 项目流程

两年前我还在为一个“资源不够还要上线大模型服务”的项目头疼,手里正好就是8张V100 32G。当时很多人听到 V100 第一反应是“这卡太老了,跑不动大模型”,但我实测下来,用 vLLM 做推理部署,把吞吐、并发、显存规划都调到位之后,它应付真实业务请求完全能打。这篇文章就复盘那次部署和吞吐测试的完整过程,包括为什么选 V100、并行策略怎么定、vLLM 参数怎么调、压测数据怎么解读,以及一路上踩过的各种坑。

先交代一下背景:这不是一个跑单条 prompt 看手感的 Demo,而是一个要接真实业务的推理服务,请求特征是长短混杂、并发不低、还要控制首字延迟。硬件是 8 张 V100 32G,软件栈是 vLLM + 某开源 7B 对话模型(FP16 精度)。为什么要用 V100?因为很多公司机房里有存量 V100,或者二手市场里 V100 32G 的价格确实诱人,与其等预算买新卡,不如先把现有资源榨干。这篇文章适合正在做推理服务选型、或者手里有老卡想跑大模型的工程师参考,看完你就能照着搭一套、也照着想明白怎么测吞吐。

1. 项目背景:8张V100能撑起大模型业务吗

1.1 为什么选V100而不等新卡

V100 是 2017 年的架构,放到今天工艺和算力都不算先进。但它有个很现实的优势:存量极大。很多公司前几年采购的深度学习服务器,标配就是 8 卡 V100,这些机器跑训练可能已经吃力,但跑推理,尤其是用 vLLM 这类优化过的推理框架,还远没到退休的时候。

另一个关键点是 V100 有 32GB 显存版本。大模型推理最怕的不是算力不够,而是显存放不下权重和 KV Cache。V100 32G 单卡可以完整放下一个 7B 模型的 FP16 权重(约 14GB),还能剩下十几个 GB 给 KV Cache,这让它成了“老卡跑新模型”的性价比之选。

这里要提醒一句,V100 和 V100S 的 32GB 版本有一些区别,核心架构都是 Volta(sm_70),但对 vLLM 来说,兼容性表现差距不大。我当时测试用的是普通 V100 32G 和 V100S 32G 混插,实测性能差异在 5% 以内,不影响整体结论。

1.2 8张卡的总账:算力、显存与带宽

我们先把账算清楚。8 张 V100 32G,总显存 256GB,这个数字听起来很夸张,能塞下一个 70B 模型(FP16 权重约 140GB),但别高兴太早,还得看算力和带宽。

V100 的 FP16 Tensor Core 算力约 125 TFLOPS,作为对比,A100 是 312 TFLOPS,H100 已经到 989 TFLOPS。也就是说,单卡算力大约是 A100 的四成。更关键的是 V100 不支持 BF16,只支持 FP16,这对模型精度处理和 vLLM 某些默认行为有影响,后面会细说。

带宽方面,V100 32G 的 HBM2 显存带宽是 900GB/s,NVLink 2.0 卡间互联带宽约 300GB/s。如果是 PCIe 版本,卡间通信只有 PCIe 带宽,这直接决定了并行策略的选择。下面这张表是当时整理的关键参数对比:

参数V100 32GA100 40G说明
架构Volta (sm_70)Ampere (sm_80)架构差异影响算子支持
FP16 Tensor Core125 TFLOPS312 TFLOPS单卡算力差距约 2.5 倍
显存32GB HBM240GB HBM232G 版本对推理更友好
显存带宽900GB/s1555GB/s影响长序列生成效率
BF16 支持不支持支持大模型微调常用 BF16
NVLink 带宽300GB/s600GB/s影响张量并行效率

算完这笔账,结论就清晰了:8 张 V100 32G 跑 7B 级别模型非常合适,跑 13B 也能接受,跑 30B 就得精打细算,硬上 70B 可以启动但吞吐会很惨。这次项目最终选定了 7B 模型,不是不想跑更大的,而是要在实时业务延迟和吞吐之间找平衡。

2. 部署方案设计:并行策略与环境搭建

2.1 模型选型与并行策略:先定路线再动手

大模型推理部署,最忌讳上来就开跑。第一步要确定并行策略,这决定了后续所有参数怎么调。vLLM 里有两种并行方式:张量并行和 Data 并行(可以组合使用)。

张量并行(TP)是把一个模型切到多张卡上,每张卡持有部分权重,推理时卡间要频繁同步,适合单卡放不下的大模型。Data 并行(DP)是每张卡放一个完整模型副本,各自处理不同请求,互不干扰,吞吐最高。8 卡 V100 跑 7B 模型,单卡显存完全够放权重和 KV Cache,所以直接 DP=8,每卡独立服务,吞吐最大化。

同样的逻辑扩展到不同模型规模,选型策略如下表:

模型规模权重显存估算建议并行策略单卡剩余显存(按32G算)实际体验
7B FP16约14GBDP=8约16GB给KV Cache吞吐优先,推荐
13B FP16约26GBDP=2 + TP=4约6GB给KV Cache可用,并发受限
30B FP16约60GBDP=2 + TP=4(4卡一组)权重占15GB/卡勉强,需控序列长度
70B FP16约140GBTP=8权重占17.5GB/卡能跑,吞吐很低

这次项目只用了 7B 模型,DP=8 方案。有个细节值得注意:DP 模式下,vLLM 每个副本是独立的推理实例,如果你用并发请求,最好在网关层做负载均衡,让请求均匀分布到 8 个实例上。

2.2 软件栈版本搭配:CUDA、PyTorch、vLLM 的兼容性

老卡部署大模型,软件版本比新卡敏感得多。V100 是 sm_70 架构,太新的 CUDA 和 PyTorch 版本理论上支持,但 vLLM 迭代很快,某些新版本对旧架构的编译支持反而变差,甚至直接编译失败。

我当时用的组合是 CUDA 11.8 + PyTorch 2.1 + vLLM 0.4.x,这套组合对 sm_70 支持比较完整。vLLM 0.4 之后的部分版本对旧架构做了裁剪,如果你安装最新版遇到“no kernel image available”或编译报错,大概率是架构不匹配。环境搭建推荐直接用官方 Docker 镜像,省去编译痛苦,命令大致长这样:

# 拉取带 CUDA 11.8 基础环境的 vLLM 镜像(以 v0.4.2 为例) docker pull vllm/vllm-openai:v0.4.2 # 启动容器,挂载模型目录,暴露 8000 端口 docker run --gpus all \ --ipc=host \ --shm-size=64g \ -v /data/models:/models \ -p 8000:8000 \ vllm/vllm-openai:v0.4.2 \ --model /models/your-7b-model \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.92 \ --max-model-len 8192

注意 V100 不支持 BF16,加载模型时要么显式指定 FP16(vLLM 默认会用 FP16),要么用 FP32,但显存占用翻倍不划算。如果模型权重本身就是 BF16 格式,需要先转换回 FP16,否则部分算子可能出现精度异常。这个坑我们在测试早期踩过,转回来之后一切正常。

2.3 显存预算:权重、KV Cache、激活值的账本

显存规划是部署前必须做的功课,公式很简单:单卡显存 = 模型权重 + KV Cache + 激活值 + 碎片预留。

以 7B FP16 为例,权重约 14GB。KV Cache 取决于序列长度、层数、注意力头数和并发请求数。一个粗略算法:对于 7B 模型(假设 32 层、8 个 KV head、head_dim 128),每个请求每 token 的 KV Cache 大小约等于 32 层 × 2 (K和V) × 8 head × 128 dim × 2 bytes = 131072 bytes,也就是 128KB/token。如果平均序列长度 2048,单个请求就需要 256MB KV Cache。32GB 显存减去 14GB 权重、预留 2GB 激活值,还剩 16GB 能给 KV Cache,大约可以支撑 60 个 2048 长度并发请求,很宽裕。

注意,vLLM 的 KV Cache 是按页分配的,实际使用量可能略低于上述估算,但预算时按上限算更稳妥。我建议用--gpu-memory-utilization 0.92,给 PyTorch 运行时和碎片留 8% 余量,太激进设到 0.97 容易跑几天后偶发 OOM。--max-model-len也要控制,如果业务不需要超长上下文,设 4096 或 8192 就好,这个值越大,KV Cache 预留越多,并发能力越差。

3. vLLM 调优实录:参数与机制

3.1 理解 PagedAttention:显存是这么分的

PagedAttention 是 vLLM 的核心机制,理解它才能理解参数为什么这么调。传统推理框架会给每个请求预分配完整的 KV Cache 空间,类似操作系统里给进程预分配一整块内存,请求多了显存迅速爆掉。

PagedAttention 模仿操作系统虚拟内存的分页管理,把 KV Cache 切成固定大小的块(默认 block_size 为 16 个 token),按需分配,用完了再继续分配下一页。这带来两个好处:显存浪费少,因为不需要给每个请求预留完整空间;而且 KV Cache 碎片可以复用。打个比方,传统方式是每个人住一整层楼,vLLM 是几十个人合住酒店,按天订房,入住率自然高。

vLLM 里跟 PagedAttention 直接相关的参数是--block-size。默认 16 通常够用,不需要调小。块太小虽然减少显存浪费,但索引开销变大,块太大浪费变多。8 卡 V100 场景下,默认值最稳。

3.2 continuous batching 与并发上限

vLLM 另一个杀手锏是 continuous batching。传统动态 batching 是等一个 batch 里所有请求都生成完再换下一批,GPU 会在尾巴上浪费时间。vLLM 的 continuous batching 在每个 token 生成步都检查:有没有请求结束?有没有新请求进来?随时补位,保证 GPU 时刻满负荷。

正因如此,并发参数--max-num-seqs非常关键。这个值决定一个 batch 最多同时处理多少个序列。设太小,GPU 利用不充分;设太大,显存可能爆掉,而且首字延迟急剧上升。7B + 32GB 显存的组合,我测试下来--max-num-seqs设在 32 到 48 之间比较合适,具体看你的平均序列长度。

判断方法很简单:看每卡 GPU 利用率,如果能稳定稳定在 90% 以上,说明 batch 够大;如果只有 50% 上下,试着调大--max-num-seqs,同时观察 TTFT(首 token 延迟)是否还在可接受范围。

另外,vLLM 的调度器还支持--swap-space,当 KV Cache 用尽时可以把旧页换到 CPU 内存。这个功能听起来实用,但生产环境强烈不建议依赖它,因为换入换出会带来几秒甚至几十秒的延迟尖刺。部署时应该把--swap-space设小一些(比如 4GB),让服务在显存不足时直接拒绝新请求,而不是通过 CPU swap 硬撑,这对在线业务更友好。

3.3 量化与优化:老卡上哪些该上、哪些别碰

量化是大模型推理的热门话题,但在 V100 上踩过的坑我必须说清楚:V100 的 Tensor Core 只支持 FP16,不支持 INT8/INT4 计算。你在新卡上用 GPTQ 或 AWQ 量化模型能获得加速,在 V100 上量化反而可能更慢,因为 INT8 计算会退化到非 Tensor Core 路径,还要付出反量化开销。

所以部署 V100 时,最优策略就是 FP16 裸跑,不要折腾量化。至于 KV Cache 量化(比如 8bit KV Cache),V100 也能用,但收益有限,在显存还没打满的情况下优先不开。我自己测试的结果是:7B FP16 + 8bit KV Cache,吞吐提升不到 10%,但偶尔会出现生成质量波动,比较鸡肋。

vLLM 还有一个默认开启的优化是 CUDA Graph,它会把推理图提前捕获,减少 kernel 启动开销。在 V100 上 CUDA Graph 能正常用就正常用,对短 prompt 场景收益明显。但如果你遇到启动时报CUDA graph capture failed或运行中报非法内存访问,可以加--enforce-eager强制关闭,代价是吞吐下降 5% 到 10%。关闭总比不能用强。

另一个值得开的参数是--enable-prefix-caching,这对真实业务特别友好。它是把 Prompt 的公共前缀部分的 KV Cache 缓存下来,多轮对话或固定系统提示词的场景,重复请求只需要计算差异部分,TTFT 能明显下降。我们在客服问答场景实测,开启前缀缓存后 TTFT 平均降了 30% 左右。

4. 实际业务吞吐测试全过程

4.1 压测工具选型与测试场景设计

按部就班做完部署,接下来进入正题:吞量测试。vLLM 官方仓库里带了压测脚本benchmark_serving.py,它会按指定的请求速率和并发数,向服务发 OpenAI 兼容格式的请求,输出吞吐、TTFT、TPOT 等指标。这个脚本适合拿基线,但对真实业务的模拟程度有限,建议先跑它,再用业务真实 Prompt 做二次压测。

除了官方脚本,我们当时还用 Locust 写了一个简单的自定义压测器,作用是模拟带思考停顿的真实用户行为:有的请求很短,有的请求带长文档,重合度不一。

测试场景设计了三个,覆盖典型业务形态:

场景Prompt 长度输出长度并发模型业务模拟目标
短问答100-200 token50-150 token高并发客服、知识库检索问答
长文总结2000-3000 token300-500 token中并发文档摘要、报告生成
多轮对话每轮 300 token,带重复系统提示词100-200 token中高并发助手类产品

强烈建议测试时必须记录 Prompt 和输出的真实 token 数,而不要用用户输入的字数估算,因为中英文 token 比例差异巨大。我们见过一个“长度很短的请求”实际 token 数比预期多三倍,导致 KV Cache 占用和吞吐数据全部偏离预期。

4.2 测试执行:逐步加压与数据采集

压测不是一上来就用最高并发,否则你只能拿到一个“服务挂了”的结论,拿不到“服务从什么时候开始劣化”的过程。我们采用逐步加压:先用低并发跑 5 分钟建立基线,再逐步提高并发或请求速率,每个档位跑 10 分钟,采集稳定后的数据。

基准测试脚本的执行方式大致如下:

# 服务端启动(8卡DP=8,每个实例一个卡) # 假设服务监听 8000-8007 端口 # 单实例基线测试:请求速率 1 req/s,共 200 个请求 python3 benchmark_serving.py \ --model /models/your-7b-model \ --tokenizer /models/your-7b-model \ --request-rate 1 \ --num-prompts 200 \ --max-concurrency 8 \ --endpoint /v1/completions \ --base-url http://localhost:8000 # 加压测试:请求速率 8 req/s python3 benchmark_serving.py \ --model /models/your-7b-model \ --tokenizer /models/your-7b-model \ --request-rate 8 \ --num-prompts 500 \ --max-concurrency 32 \ --endpoint /v1/completions \ --base-url http://localhost:8000

整个 8 卡集群测试时,我们在网关层做轮询,让 8 个 vLLM 实例平均分担流量。别小看这一步,如果负载不均,某个实例先到瓶颈,你测出来的整体吞吐会比真实水平低。

采集指标时用nvidia-smi dmon -i 0,1,2,3,4,5,6,7 -c 600记录每卡利用率、显存使用和温度,同时记录服务日志里的排队时间。通过 GPU 利用率和延迟曲线联合分析,定位瓶颈。

4.3 测试结果解读:数据背后藏着什么

当时拿到的一轮典型数据如下(7B FP16、8卡DP、短问答场景):

并发数总吞吐 (tokens/s)QPS (req/s)TTFT (ms)TPOT (ms/token)GPU利用率
8约 220约 1.6310约 4278%
16约 300约 2.2450约 4885%
32约 330约 2.4720约 5590%
48约 340约 2.51100约 6293%

注意这个表格里最核心的发现:并发从 8 涨到 16,总吞吐涨了 36%,但从 32 涨到 48,吞吐只涨了 3%,而 TTFT 从 720ms 恶化到 1100ms。这说明服务在并发 32 左右已经接近饱和,继续加压只是延长排队,不会提升整体吞吐。这个“拐点”就是你业务容量规划的基准线。

实际业务中,建议把服务长期运行在拐点以下 80% 的负载,留出突发流量缓冲。比如拐点在并发 32,平时负载控制在并发 20-24。要解释一下为什么总吞吐不是翻倍增长:V100 的算力有限,batch 增大后每个序列分到的算力减少,生成长度不变的情况下总吞吐当然有上限。这符合 Amdahl 定律式的直觉,但很多人压测前没这个概念,看到吞吐不涨就以为是卡的问题。

另一个值得关注的指标是 TPOT(每个输出 token 的时间)。短问答场景 TPOT 约 42ms/token,意味着用户体验大概每秒能见到 24 个字,对聊天产品够用。长文总结场景的 TPOT 会慢一些,因为长 prompt 的 prefill 阶段占了大量算力,生成阶段 batch 内序列也更长,实测 TPOT 到过 75ms/token,这个档位就得权衡是否限制单请求最大输出长度。

5. 常见问题与排查经验

5.1 V100 专属兼容性报错

老卡最容易在环境层面出问题,而且报错往往不直观。第一次启动 vLLM 时遇到Please install flash-attn或类似错误,别急着装最新版 flash-attn,V100 的 sm_70 架构不在新版 flash-attn 支持清单里。解决办法有三个:一是用 vLLM 官方 Docker 镜像自带的旧版 flash-attn;二是让 vLLM 回退到 xformers 后端;三是编译时显式指定 TORCH_CUDA_ARCH_LIST 包含 7.0,再装兼容版本。我实际用下来,vLLM 0.4.x 对 V100 的 attention 后端选择比较聪明,会在没有 flash-attn 时自动回退,不需要手动干预。

另一个高频报错是no kernel image is available for execution on the device,这个几乎都是 CUDA 版本和驱动不匹配,或者 PyTorch 编译目标没包含 sm_70。检查方法:跑python -c "import torch; print(torch.cuda.get_arch_list())",输出里必须有sm_70,没有就是装错了 PyTorch 版本。

如果遇到 CUDA graph 相关报错,直接用--enforce-eager启动,先确保服务能跑起来,再回头优化。曾经遇到一次运行几分钟后整个服务卡死,错误日志里是 CUDA illegal memory access,排查下来是显存碎片导致 CUDA graph 缓存的地址失效。重启服务加上--enforce-eager后问题消失,虽然吞吐掉了 8%,但稳定性优先。

5.2 显存不够时的系统排查思路

V100 跑大模型最容易碰到的就是 OOM。显存不够时第一反应不是加卡,而是先回答三个问题:权重占了多少?KV Cache 上限设了多少?并发上限设了多少?

排查步骤我整理成了固定套路:

  1. nvidia-smi看每卡显存占用,确认是否有进程残留占用显存,测试中经常遇到上一个压测进程没 kill 干净,白白少了几个 GB。
  2. 调小--max-num-seqs,每步减 8,重新压测观察是否还 OOM。
  3. 调小--max-model-len,比如 8192 改为 4096,KV Cache 预算立刻减半。
  4. 如果还不行,检查权重的 dtype,确认是 FP16 而不是 FP32。

这套顺序操作成本从低到高,适合快速定位。有一个注意点:--gpu-memory-utilization在某些 vLLM 版本里对 KV Cache 的分配是“按最大可能值预留”的,不是按实际需要。也就是说,即使当前没有并发请求,KV Cache 也可能已经按最大序列长度和最大并发预留了显存。所以调参后一定要重启服务,别指望热加载。

5.3 吞吐上不去的三个隐藏原因

如果各项指标看起来都正常,但吞吐就是上不去,通常藏在下面三个地方。

第一,卡间通信瓶颈。如果你用了 TP=4 或 TP=8,但 V100 是 PCIe 版本,卡间通信走 PCIe 3.0,带宽只有 32GB/s,频繁同步会让 GPU 大量时间在等待数据。排查方法:跑nvidia-smi topo -m看卡间拓扑,如果是 PCIe Switch 连接而非 NVLink,慎用大 TP 值,改成 DP 优先。

第二,CPU 预处理瓶颈。vLLM 的 tokenizer 和请求调度在 CPU 上执行,如果 CPU 核数不够,GPU 再快也只能等着。排查方法:压测时看 CPU 使用率,如果多个核打满 100%,说明 CPU 成了瓶颈,需要调大--num-cpu-blocks或增加并发线程数,更直接的办法是提升服务所在宿主机的 CPU 配置。

第三,日志和监控拖慢性能。测试时很多人习惯开启 detailed 日志,每秒打印每个请求的完整耗时。在低并发时无感,高并发时日志 I/O 会抢占 CPU 和磁盘,甚至影响网络 IO。压测环境里把访问日志关掉,或者用采样率 10% 的日志策略,吞吐数据会真实很多。

我把常见问题和对应的处理方式整理成了速查表,方便直接抄作业:

现象可能原因处理方式
启动报 flash-attn 相关错误sm_70 架构不支持新版使用官方镜像或 xformers 后端
CUDA graph 捕获失败V100 兼容性问题加 --enforce-eager
偶发 OOMKV Cache 占满调小 --max-num-seqs / --max-model-len
TP 模式吞吐远低预期卡间走 PCIe 无 NVLink改 DP 或减小 TP 值
CPU 打满 GPU 空闲tokenizer/调度瓶颈加 CPU 资源或关详细日志
吞吐抖动剧烈触发 CPU swap调小 --swap-space 至 0

5.4 一点运维层面的额外建议

测试过程中我发现,8 张 V100 跑 DP 模式时,每卡一个实例的运维复杂度比 TP 高,因为 8 个进程要分别监控、分别更新。建议用容器编排工具统一管理,给每个实例打标签,采集指标时把 8 个实例的吞吐、延迟聚合起来看,而不是逐个人肉盯。

健康检查也很重要。vLLM 的/health接口可以配成探活,但真正的业务健康检查建议用真实推理请求:每 30 秒发一个极短的请求,如果连续 3 次失败或超时,自动摘除该实例并在网关层熔断重启。我们当时就是这样做的,8 卡跑了一周,中间有两次单实例卡死,自动重启后业务无感。

最后再分享一个小技巧:压测时如果只想快速验证服务能跑,先把--max-model-len设到 4096、--max-num-seqs设到 16,这样启动快、显存占用低。但正式容量评估一定要按真实业务数据来,否则参数调得太保守,白瞎了 8 张卡的算力。根据自己的压测数据和业务负载曲线,把服务跑在拐点以下 20%,这个度就是最划算的运营位置。

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

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

立即咨询