大模型多卡部署实战:显存估算、TP/PP并行选型与vLLM调优
1. 单卡跑不动的临界点先确认你真的需要多卡1.1 用显存公式算一笔账而不是靠感觉前段时间帮一个朋友调试 27B 模型的部署他手头是两台双卡机器每张卡 24G 显存跑的还是 RTX 4090。他一上来就跟我说27B 模型用 4bit 量化怎么着也能塞进单卡吧。这个想法我见过太多次了——问题在于很多人只算了权重大小完全没有把 KV cache 和激活值算进去。先说最常用的估算方法。以 bf16 精度为例1B 参数量大约对应 2GB 显存所以一个 27B 模型仅权重就要 54GB。如果按他说的 4bit 量化权重确实能压到 15GB 左右单卡 24G 看上去还有富余。但模型部署不是只放权重就结束的KV cache 才是那个藏在后面的显存刺客。粗略计算方式是2key 和 value 各一份× batch size × 上下文长度 × 层数 × KV head 数 × head 维度 × 2 字节。对一个 27B 级别的模型来说4K 上下文、并发 8 个请求KV cache 占用轻松超过 20GB。也就是说哪怕权重量化到 15GB整卡 24G 依然会被瞬间打爆。所以我现在遇到到底要不要上多卡的问题都是让对方先老老实实算三笔账权重显存参数量 × 精度字节数bf16 约等于参数量 × 2。KV cache 显存按照最大并发数和最大上下文长度来估不要按平均值算。激活值和其他 buffer和 batch size、序列长度相关通常预留 10% 到 20% 的余量。把这三笔加起来如果已经逼近单卡显存的 80% 以上我的建议就是直接上多卡别纠结。强行量化到 4bit 确实能压权重但 KV cache 和激活值不会消失而且量化精度损失在某些业务场景里完全不可接受。省了几张卡的采购成本最后换来的是用户可感知的回复质量下降这笔账不划算。下面给出一张常见的模型规模对照表方便你快速对号入座模型参数量bf16 权重占用典型 KV cache 需求4K 上下文8 并发最低配置建议7B-8B14GB-16GB约 8GB-12GB单卡 24G 起步14B28GB约 12GB-16GB单卡 40G 或双卡27B-32B54GB-64GB约 16GB-24GB双卡 48G 或双卡 80G70B140GB约 24GB-40GB4 卡 80G 起步这张表只是一个估算不同模型的层数、KV head 数差异很大实际显存会有波动但它至少能帮你避免看着权重能装下就下单的误判。1.2 单卡部署的三个真实瓶颈容量、带宽、并发很多人以为上多卡纯粹是为了放下超大模型其实不完全是。即便模型能塞进单卡你依然可能被另外两个瓶颈卡住而且它们比显存容量更隐蔽。第一个是显存容量这个最好理解。但更隐蔽的是量化压下来的显存往往被 KV cache 池重新吃掉。你可以把 KV cache 理解成服务端的临时座位座位越多能同时服务的请求就越多。单卡 KV cache 池被压得很小后几个并发请求进来后面的全部排队首 token 延迟直接飙到几十秒用户体验就是卡死了。第二个是显存带宽。大模型推理在 decode 阶段本质上是 memory-bound 任务每生成一个 token都要把模型权重从显存里完整读一遍。单张 A100 的 HBM 带宽约 2TB/sRTX 4090 约 1TB/s这个数字就决定了每秒钟最多能产出多少个 token是物理上限。你可以用一个小实验验证单卡跑 8B 模型无论怎么调参decode 速度的波动范围都很小就是因为带宽已经打满了。第三个是并发能力。vLLM 的 continuous batching 能大幅提升吞吐但前提是显存里有足够的 KV cache 空间容纳更多请求。显存一满批大小上不去吞吐自然受限。这时候光靠调参已经解决不了问题要么降低并发预期要么加卡摊薄负载。这三件事摞在一起结论就很清晰多卡部署不是大模型专属而是高并发在线服务的常态选型。哪怕你只是部署一个 8B 模型只要有长上下文和几十路并发需求单卡一样会顶不住。2. 并行策略选型TP、PP、DP、EP 到底怎么配对2.1 四种并行方式各自的本质提到多卡很多人第一反应是把模型平均分到每张卡上但怎么分技术路径完全不同选错了性能差距能到一倍以上。tensor parallel简称 TP是把每一层的权重矩阵按行或列切开分布到多张卡上。每张卡只持有模型权重的一部分计算时通过卡间通信做 all-reduce 汇总结果。它的核心价值是解决单卡放不下的问题同时因为每层计算被拆分到多卡并行执行对吞吐也有正向帮助。但代价是通信非常频繁每过一层就要做一次全量同步所以 TP 对卡间带宽极其敏感NVLink 环境下效率很高走 PCIe 就会明显掉速。pipeline parallel简称 PP则是按层切分。比如一个 80 层的模型两张卡就是 0-39 层放卡 A40-79 层放卡 B。它把模型切成流水线不同设备处理不同的 micro-batch通信开销比 TP 小很多但代价是流水线气泡——每个阶段在开始等待和收尾清空时总有一部分算力是闲置的。模型层数越少、切分越细气泡浪费越明显。data parallel也就是 DP则是所有卡各放一个完整模型副本把请求分散到多卡上独立推理。它用于扩展吞吐而不是摊薄单个模型的显存。vLLM 里还实现了更高效的调度方式会动态把请求分给当前负载较低的设备避免简单静态切分导致的负载不均。expert parallel 即 EP是针对 MoE 架构模型比如 Mixtral、DBRX 这类带 expert 的模型的专用并行。思路是把不同的 expert 模块放到不同卡上router 网络根据 token 的输入自动路由到对应 expert 计算。普通 Dense 模型用不上 EP但如果你的模型是 MoE 而且单卡放不下全部 expertEP 几乎是必经之路。四种方式对比如下并行方式切分粒度主要收益最大代价适用场景TP权重矩阵解决单卡放不下通信频繁对 NVLink 依赖高单机多卡首选PP模型层降低单卡显存压力流水线气泡多机联合部署DP完整副本提升吞吐显存浪费每卡都要完整模型模型能放入单卡但并发高EPexpert 模块支持超大 MoE 模型路由和通信复杂MoE 模型多卡2.2 vLLM 里的实际操作建议和参数级别vLLM 里并行策略映射到参数上最核心的就是--tensor-parallel-size和--pipeline-parallel-size在代码里也经常简写为 TP 和 PP。还有一些场景会用到数据并行vLLM 内部自动处理通常不需要手动配置。我的选型经验可以浓缩成三条单机多卡无脑先试 TP。同一台机器上的卡要么走 NVLink要么至少走 PCIe switch通信延迟可控TP 带来的显存和吞吐收益最直接。绝大多数生产案例单机 2 到 8 卡TP 都是主力方案。跨机多卡优先考虑 PP或者在每台机器内部开 TP机器之间走 PP。不要轻易把 TP 开到跨节点除非你确定节点之间有 RoCE 或 InfiniBand 级别的高速网络。普通万兆以太网做全量 TPall-reduce 开销会吞掉大部分提速收益。MoE 模型单卡放不下全部 expert 时再考虑 EP。vLLM 新版本已经能自动调度 expert placement但上线前还是要手动确认各卡显存是否均衡。我见过最典型的错误是服务器上 8 张卡为了充分利用直接把 tensor-parallel-size 设成 8结果多机之间走的是万兆以太网每个 batch 都卡在 all-reduce 通信上最终吞吐比单机 TP2 还差。分布式并行本质是在显存容量、通信开销、计算效率三者之间取平衡这个平衡点必须用本机实测数据来找不能只看卡的数量拍脑袋。3. 多卡部署完整落地从单机命令到跨节点集群3.1 环境准备确认你的卡值得信任动手部署之前先花五分钟确认环境。vLLM 的vllm serve命令已经把部署流程简化了很多不需要再手动写 Python 脚本。但环境问题不会消失尤其是分布式场景问题比单机多一个量级。第一步永远是检查驱动和 CUDA 版本。vLLM 的预编译包通常绑定 CUDA 11.8 或 12.1驱动版本太老会导致运行时直接报 CUDA driver version is insufficient 之类的错误。在命令行里执行nvidia-smi看右上角的 CUDA Version只要高于 12.0基本都能满足。第二步检查卡间拓扑。执行nvidia-smi topo -m输出会展示每张卡之间的通信方式。NV 开头表示走 NVLinkPIX 或 PHB 表示走 PCIe。如果两张卡之间没有 NVLinkTP2 的收益会明显打折扣你对性能的预期也要相应调低。很多人在这一步就开始踩坑明明插了 8 张卡拓扑矩阵却显示卡 0 和卡 1 走 PCIe那就要怀疑是不是 PCIe switch 配置出问题了。第三步是装对版本。vLLM 对 torch、transformers 这几兄弟的依赖版本相当敏感我的经验是最好用官方 Docker 镜像或者新建一个干净的虚拟环境先装 vllm再装其他推理相关组件。一个环境里同时装多套推理框架的做法后面会单独讲这里先记住一个原则环境越干净分布式部署的排障成本越低。3.2 单机多卡最重要的一条命令单机多卡是绝大多数人的起点vLLM 的 API server 可以直接指定并行度不需要额外启动任何集群组件。最典型的启动命令长这样vllm serve /data/models/Qwen3-27B \ --tensor-parallel-size 2 \ --dtype bfloat16 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --max-num-seqs 32逐个解释关键参数。tensor-parallel-size 2表示把模型权重切到两张卡上这是分布式部署的开关。dtype bfloat16比 fp16 更省显存同时多数现代 GPU 对 bf16 的计算支持也更稳。max-model-len 8192控制最大上下文长度这个值别随手设大——它直接决定 KV cache 的预留空间设得越大可用的 KV cache 池就越大但相应也会吃掉更多显存。gpu-memory-utilization 0.9是给 CUDA context 和各种临时 buffer 留出 10% 的余地。很多人会改成 1.0我强烈不建议多卡场景下某张卡先 OOM 的案例大部分都和这个参数拉满有关。启动之后vLLM 默认监听 8000 端口接口兼容 OpenAI 格式直接用一个 curl 就能验证curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: Qwen3-27B, messages: [{role: user, content: 你好介绍一下你自己}] }返回 JSON 没有报显存错误说明 TP2 已经生效。这时候打开nvidia-smi观察两张卡能看到模型权重各占一半显存接近均匀分布。如果其中一张卡占用 90%另一张只有 40%通常不是权重切分的问题而是 KV cache 分配策略把缓存池集中到了某张卡上。遇到这种反常的分配先检查max-model-len和max-num-seqs的配比再考虑用--kv-transfer-config这类高级选项手动调整。3.3 跨节点部署Ray 集群的正确用法跨节点部署比单机多卡复杂一个量级。vLLM 的分布式调度依赖 Ray很多教程写的是起个 Ray 然后直接跑实际操作时有不少细节。第一步选一台机器作为 Ray headray start --head --port6379第二步其他机器作为 worker 加入同一集群ray start --address192.168.1.100:6379 --redis-passwordyour-password第三步在所有节点上执行ray status确认 GPU 列表完整再在 head 节点启动 vLLMtensor-parallel-size填跨节点总卡数。但这里有个非常大的陷阱默认配置下vLLM 会把 TP 直接扩展到所有节点上。跨节点全 TP 对网络通信是毁灭性的每层都要做 all-reduce哪怕只有 40Gbps 的网卡也很容易让通信时间超过计算时间。我在第 2 章已经说过跨节点更推荐节点内 TP 跨节点 PP组合。比如两台机器各 2 卡、总共 4 卡可以这样启动vllm serve /data/models/Qwen3-27B \ --tensor-parallel-size 2 \ --pipeline-parallel-size 2 \ --dtype bfloat16含义是每个节点内部两张卡做 TP节点之间做 PP。PP 的通信量远小于 TP但代价是流水线气泡。如果你的业务是 RAG 或 Agent 这类对首 token 延迟敏感的交互式场景跨节点通信的 RTT 会直接影响每次请求的体验所以跨节点部署一定要想清楚网络质量不够就不要强行把所有资源拼在一起。有时候用两台机器分别部署两个独立服务再在前面架一个负载均衡反而是更合理的架构。4. 绕不开的坑WSL2 通信、显存分配、框架版本冲突4.1 WSL2 下的分布式推理能用但别天真虽然我不建议把生产环境放在 WSL2 里但很多读者确实想先在 Windows 上验证功能。vLLM 0.29 之后对 WSL2 的兼容性改善了不少分布式部署还是有不少暗坑。最大的坑是 NCCL 通信。WSL2 通过 GPU-PV 半虚拟化机制暴露 GPUnvidia-smi能正常识别两张卡但当 tensor-parallel-size 大于 1 时vLLM 初始化 NCCL 经常会卡住或直接报超时。原因是 WSL2 拿不到真实的卡间拓扑信息NVLink 拓扑识别基本是错的NCCL 无法决定走 P2P 还是走共享内存。如果你的目的只是验证多卡能不能跑通可以临时设置两个环境变量规避大部分问题export NCCL_P2P_DISABLE1 export NCCL_SHM_DISABLE1这两个变量的含义是禁用 GPU 间直接内存访问退化为通过主机内存中转。这样 vLLM 能跑起来但通信效率会显著下降我实测吞吐只有原生 Linux 环境的 60% 到 80%损失幅度取决于显卡型号。这个妥协只适合验证功能千万不要拿来做压测更不能上生产。想在 Windows 上做正经的多卡部署我更推荐 WSL2 里装 Docker Desktop用官方 vLLM 镜像把 GPU 直通进去。这样至少能保证 CUDA 版本和依赖是干净的排查问题时不会被 Windows 驱动层的问题干扰。记住一个事实vLLM 这类框架的官方支持矩阵里WSL2 从来不是第一优先级你在上面的每一次报错都可能在原生 Linux 环境里不复存在。4.2 显存分配不均与神秘的 OOM多卡部署最开心的时刻是模型加载成功最糟心的时刻是某个请求过来之后其中一张卡 OOM 了另外几张卡还剩不少显存。这种分配不均问题我排查过很多次成因主要有三个。第一个是权重切分粒度导致的固有差异。TP 对不同模块的切分方式并不一致Embedding 和 LM Head 这类模块通常只在部分卡上持有副本所以首卡和尾卡的显存天然偏高。模型规模越小这种差异看起来越夸张。解决办法是心里有数不一定需要消除只要确认没有某张卡特别接近上限就可以。第二个是 KV cache 的预留策略。vLLM 启动时会根据max-model-len和gpu-memory-utilization计算每张卡的 KV cache 池大小。如果max-model-len设得过大每张卡都会预留大量空余显存给 KV cache而实际请求根本用不到那么多看起来就像显存空间还很大但是被预留下去了。解决办法是精确设置上下文长度按业务最长输入来算不要留 10 倍余量。第三个是 flash-attention 的临时 buffer 波动。某些版本的 flash-attn 在分布式模式下会因为序列长度波动申请很大的临时显存导致偶发 OOM。这种 OOM 的典型特征是跑一会儿才爆而不是启动就爆。遇到这种情况我一般先把max-num-seqs降下来再把gpu-memory-utilization调到 0.85 左右给临时 buffer 让出空间。不要一上来就怀疑权重分布先做减法往往比做加法更有效。4.3 和 sglang、ollama 混用的冲突现场很多人在选型时会把 vLLM、sglang、ollama 装到一起做对比踩坑概率相当高。sglang 和 vLLM 是同一赛道的竞品sglang 的 radix cache 和激进调度在某些场景下吞吐表现更好但 vLLM 胜在生态更成熟、API 兼容性更好、分布式部署的资料和工具链也更全面。ollama 则适合单机单卡做轻量验证开箱即用但多卡场景下基本只能看到卡数量无法精细控制并行策略。实际混用会遇到三个非常典型的冲突端口冲突vLLM 默认 8000sglang 默认也是 8000ollama 是 11434。同一台机器上同时启动后启动的要么改端口要么直接启动失败。CUDA 依赖冲突vLLM 对 flash-attn 的版本要求非常严格sglang 需要的是另一套 flashinfer。在同一个虚拟环境里硬装经常出现装好了 sglang 把 vllm 搞崩的情况反向亦然。显存争抢如果不设置CUDA_VISIBLE_DEVICES多个推理框架会同时看到所有卡各自把显存占满。即使他们处理的模型完全不同也会互相干扰导致谁都用不舒服。我的建议非常直接生产环境用 Docker 隔离一个容器一个推理框架通过端口和 GPU 编号区分对比测试也尽量分开机器跑。这些工具本身都很优秀冲突往往不是工具的问题而是运行环境太拥挤了。5. 压测与调优从能跑通到跑得满5.1 先搞清楚该量什么指标多卡部署跑通只是第一步更关键的是验证分布式到底带来了多少收益。很多人只看每秒生成了多少 token这远远不够。我建议至少关注四个指标TTFTTime to First Token从请求发出到返回第一个 token 的时间直接决定交互体验。TPOTTime Per Output Token生成每个 token 的耗时影响用户看到的打字机速度并发时通常比单请求时高。吞吐量单位时间完成的请求数和 token 总数必须用并发压测去测单请求测出来的数字没有参考价值。显存水位每张卡的显存占用曲线用来判断 KV cache 池是否够用。vLLM 自带一套压测脚本日常排查我主要用benchmark_serving.py。一个比较实用的测试命令是python benchmark_serving.py \ --backend vllm \ --model Qwen3-27B \ --tokenizer /data/models/Qwen3-27B \ --num-prompts 200 \ --request-rate 20 \ --port 8000重点看输出里的 TTFT 平均值、P99 值和整体吞吐。如果 TTFT 的 P99 比平均值高出很多说明存在明显排队优先调max-num-seqs和 KV cache 池大小如果吞吐上不去再考虑并行配置网络问题。5.2 从能跑通到跑得满的实际调参顺序调优不是玄学按顺序排查能省很多时间。我的操作顺序是先调gpu-memory-utilization。从 0.95 往下试结合显存水位找到当前配置下能给 KV cache 的最大空间。这个参数直接决定并发上限优先级最高。再调max-num-seqs。它表示 vLLM 同时最多处理多少个请求序列。设太小并发一高就排队设太大KV cache 被迅速瓜分OOM 风险上升。一般从 16 或 32 开始配合压测逐步上调直到显存水位接近上限且不再出现大量排队。最后才考虑并行参数。如果 TP2 比 TP1 的吞吐提升不到 20%但显存压力也没少太多说明通信已经成为瓶颈。这时候可以试试更小的并发 batch或者改成流水线并行而不是继续加大 TP。对于前缀复用需求比较高的业务比如 RAG 场景里大量请求共享同一段文档前缀vLLM 的 prefix caching 会自动生效。开启后相同前缀的请求在命中缓存时 TTFT 会大幅下降。压测时不要用完全随机的问题去测这个功能否则测不出真实收益。还有一个容易被忽略的技巧模型权重格式对加载速度影响很大。safetensors 格式支持并行加载配合--load-format safetensors和足够的 CPU 核数模型启动时间能从几分钟缩短到几十秒。分布式环境下每张卡都在加载自己那份权重磁盘 IO 和 CPU 解压会成为启动瓶颈这一步优化对频繁重启调试的场景帮助非常大。用一张表汇总常用参数方便查阅参数作用建议tensor-parallel-size张量并行卡数单机内设置别跨节点pipeline-parallel-size流水线并行段数跨节点时使用gpu-memory-utilization显存利用率上限0.85-0.95别拉满max-model-len最大上下文长度按业务实际算别留过大余量max-num-seqs最大并发序列数从 16/32 开始压测调整load-format权重加载格式safetensors 优先最后说一点个人体会。很多人一开始会把分布式推理想得很神秘实际上大多数场景就是把模型切开放到多张卡上再把通信开销控制住这件事。我不建议所有项目都无脑上多卡——如果你的模型量化后能塞进单卡并发量也不大单卡部署的稳定性和调试效率反而更高。但如果你明确知道模型装不下或者并发预期很高尽早切到多卡不要试图靠压缩上下文长度和调低并发来硬扛。vLLM 已经把分布式部署的门槛压得很低核心就是把 TP 和 PP 的区别搞清楚再摸清你的卡间通信到底快不快。先把 TP2 跑通再逐步往上加每加一档都做一次压测用数据决定下一步而不是凭感觉。这样以后就算模型从 27B 换到 70B整套流程还是能很快迁移过去。