大模型多卡部署实战:显存估算、TP/PP并行选型与vLLM调优
2026/9/20 3:22:41 网站建设 项目流程

1. 单卡跑不动的临界点:先确认你真的需要多卡

1.1 用显存公式算一笔账,而不是靠感觉

前段时间帮一个朋友调试 27B 模型的部署,他手头是两台双卡机器,每张卡 24G 显存,跑的还是 RTX 4090。他一上来就跟我说:"27B 模型用 4bit 量化,怎么着也能塞进单卡吧。"这个想法我见过太多次了——问题在于,很多人只算了权重大小,完全没有把 KV cache 和激活值算进去。

先说最常用的估算方法。以 bf16 精度为例,1B 参数量大约对应 2GB 显存,所以一个 27B 模型仅权重就要 54GB。如果按他说的 4bit 量化,权重确实能压到 15GB 左右,单卡 24G 看上去还有富余。但模型部署不是只放权重就结束的,KV cache 才是那个藏在后面的显存刺客。粗略计算方式是:2(key 和 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 或双卡 80G
70B140GB约 24GB-40GB4 卡 80G 起步

这张表只是一个估算,不同模型的层数、KV head 数差异很大,实际显存会有波动,但它至少能帮你避免"看着权重能装下就下单"的误判。

1.2 单卡部署的三个真实瓶颈:容量、带宽、并发

很多人以为上多卡纯粹是为了放下超大模型,其实不完全是。即便模型能塞进单卡,你依然可能被另外两个瓶颈卡住,而且它们比显存容量更隐蔽。

第一个是显存容量,这个最好理解。但更隐蔽的是,量化压下来的显存往往被 KV cache 池重新吃掉。你可以把 KV cache 理解成服务端的"临时座位",座位越多,能同时服务的请求就越多。单卡 KV cache 池被压得很小后,几个并发请求进来,后面的全部排队,首 token 延迟直接飙到几十秒,用户体验就是"卡死了"。

第二个是显存带宽。大模型推理在 decode 阶段本质上是 memory-bound 任务,每生成一个 token,都要把模型权重从显存里完整读一遍。单张 A100 的 HBM 带宽约 2TB/s,RTX 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 层放卡 A,40-79 层放卡 B。它把模型切成流水线,不同设备处理不同的 micro-batch,通信开销比 TP 小很多,但代价是流水线气泡——每个阶段在开始等待和收尾清空时,总有一部分算力是闲置的。模型层数越少、切分越细,气泡浪费越明显。

data parallel,也就是 DP,则是所有卡各放一个完整模型副本,把请求分散到多卡上独立推理。它用于扩展吞吐,而不是摊薄单个模型的显存。vLLM 里还实现了更高效的调度方式,会动态把请求分给当前负载较低的设备,避免简单静态切分导致的负载不均。

expert parallel 即 EP,是针对 MoE 架构模型(比如 Mixtral、DBRX 这类带 expert 的模型)的专用并行。思路是把不同的 expert 模块放到不同卡上,router 网络根据 token 的输入自动路由到对应 expert 计算。普通 Dense 模型用不上 EP,但如果你的模型是 MoE 而且单卡放不下全部 expert,EP 几乎是必经之路。

四种方式对比如下:

并行方式切分粒度主要收益最大代价适用场景
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 级别的高速网络。普通万兆以太网做全量 TP,all-reduce 开销会吞掉大部分提速收益。
  • MoE 模型单卡放不下全部 expert 时,再考虑 EP。vLLM 新版本已经能自动调度 expert placement,但上线前还是要手动确认各卡显存是否均衡。

我见过最典型的错误是:服务器上 8 张卡,为了"充分利用",直接把 tensor-parallel-size 设成 8,结果多机之间走的是万兆以太网,每个 batch 都卡在 all-reduce 通信上,最终吞吐比单机 TP=2 还差。分布式并行本质是在显存容量、通信开销、计算效率三者之间取平衡,这个平衡点必须用本机实测数据来找,不能只看卡的数量拍脑袋。

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 开头表示走 NVLink,PIX 或 PHB 表示走 PCIe。如果两张卡之间没有 NVLink,TP=2 的收益会明显打折扣,你对性能的预期也要相应调低。很多人在这一步就开始踩坑:明明插了 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 没有报显存错误,说明 TP=2 已经生效。这时候打开nvidia-smi观察两张卡,能看到模型权重各占一半,显存接近均匀分布。如果其中一张卡占用 90%,另一张只有 40%,通常不是权重切分的问题,而是 KV cache 分配策略把缓存池集中到了某张卡上。遇到这种反常的分配,先检查max-model-lenmax-num-seqs的配比,再考虑用--kv-transfer-config这类高级选项手动调整。

3.3 跨节点部署:Ray 集群的正确用法

跨节点部署比单机多卡复杂一个量级。vLLM 的分布式调度依赖 Ray,很多教程写的是"起个 Ray 然后直接跑",实际操作时有不少细节。

第一步,选一台机器作为 Ray head:

ray start --head --port=6379

第二步,其他机器作为 worker 加入同一集群:

ray start --address=192.168.1.100:6379 --redis-password='your-password'

第三步,在所有节点上执行ray status,确认 GPU 列表完整,再在 head 节点启动 vLLM,tensor-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 半虚拟化机制暴露 GPU,nvidia-smi能正常识别两张卡,但当 tensor-parallel-size 大于 1 时,vLLM 初始化 NCCL 经常会卡住或直接报超时。原因是 WSL2 拿不到真实的卡间拓扑信息,NVLink 拓扑识别基本是错的,NCCL 无法决定走 P2P 还是走共享内存。

如果你的目的只是验证"多卡能不能跑通",可以临时设置两个环境变量规避大部分问题:

export NCCL_P2P_DISABLE=1 export NCCL_SHM_DISABLE=1

这两个变量的含义是禁用 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-lengpu-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 默认 8000,sglang 默认也是 8000,ollama 是 11434。同一台机器上同时启动,后启动的要么改端口,要么直接启动失败。
  • CUDA 依赖冲突:vLLM 对 flash-attn 的版本要求非常严格,sglang 需要的是另一套 flashinfer。在同一个虚拟环境里硬装,经常出现"装好了 sglang 把 vllm 搞崩"的情况,反向亦然。
  • 显存争抢:如果不设置CUDA_VISIBLE_DEVICES,多个推理框架会同时看到所有卡,各自把显存占满。即使他们处理的模型完全不同,也会互相干扰,导致谁都用不舒服。

我的建议非常直接:生产环境用 Docker 隔离,一个容器一个推理框架,通过端口和 GPU 编号区分;对比测试也尽量分开机器跑。这些工具本身都很优秀,冲突往往不是工具的问题,而是运行环境太拥挤了。

5. 压测与调优:从"能跑通"到"跑得满"

5.1 先搞清楚该量什么指标

多卡部署跑通只是第一步,更关键的是验证分布式到底带来了多少收益。很多人只看"每秒生成了多少 token",这远远不够。我建议至少关注四个指标:

  • TTFT(Time to First Token):从请求发出到返回第一个 token 的时间,直接决定交互体验。
  • TPOT(Time 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 开始,配合压测逐步上调,直到显存水位接近上限且不再出现大量排队。

最后才考虑并行参数。如果 TP=2 比 TP=1 的吞吐提升不到 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 的区别搞清楚,再摸清你的卡间通信到底快不快。先把 TP=2 跑通,再逐步往上加,每加一档都做一次压测,用数据决定下一步,而不是凭感觉。这样以后就算模型从 27B 换到 70B,整套流程还是能很快迁移过去。

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

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

立即咨询