
1. 多GPU推理为什么要先聊通信1.1 单卡放不下模型时的两个选择最近不少朋友开始折腾 vLLM 多卡部署问题非常统一两张卡明明都有空闲显存跑起来却不如一张卡快明明模型能加载一到生成就报 NCCL 错误明明设置了tensor_parallel_size2日志里却总出现超时和奇怪的传输警告。我后来发现大部分人并不是不会用 API而是不清楚 GPU 之间究竟怎么通信更没理解 AllReduce、NVLink 这几个词到底意味着什么。这篇文章就从一个实际部署者的视角把这些底层通信逻辑从头捋一遍。先说场景。当模型权重超过单卡显存时我们有两个选择一是把模型切成多份分别放到不同 GPU 上这是模型并行二是每张 GPU 都放一份完整模型请求进来以后按负载分散处理这是数据并行。vLLM 里最常见的张量并行属于模型并行它会把每一层的大矩阵按行或者按列切开分配到多张卡上然后在每层前向计算之后把中间结果合并起来。这个“合并”就是通信。听起来好像只是传一份数据但它在每层都会发生而且卡数越多合并操作越频繁通信开销就越不能忽略。1.2 通信开销决定扩展效率多卡推理的加速比不是简单的“卡数翻倍速度翻倍”。假设单卡处理一个请求需要 10 毫秒两张卡理想情况下 5 毫秒能完成但如果每次前向都要等通信结果实际可能变成 7 毫秒甚至更慢。特别是在小 batch 场景下计算本身很短通信占比会被无限放大。很多人遇到的“加卡反而变慢”根源并不在模型也不在 vLLM 配置而在通信链路和并行策略不匹配。所以搞懂 AllReduce 和 NVLink本质上是在搞懂一个问题多卡协作时数据是怎么流动的流动速度是否撑得起算力。单机单卡没有任何通信自然不用关心一旦到了多卡通信就是和算力同等重要的资源。1.3 单机内常见的三种通信路径单机多卡场景下GPU 之间的数据通路主要分三种。第一是 PCIe通用总线所有设备都能用但带宽有限而且是共享的第二是 NVLinkNVIDIA 私有高速互联专门为 GPU 间点对点通信设计第三是跨机器使用的 InfiniBand 或 RoCE 网络一般出现在多机分布式场景。vLLM 在单机多卡启动时会通过 NCCL 自动探测 GPU 拓扑优先选择最快的路径如果两张卡之间有 NVLink 直连就走 NVLink没有就只能退回 PCIe。这个自动选择通常是对的但遇到虚拟化、透传或者奇怪的拓扑时它会走保守路线性能会很难看。2. AllReduce分布式计算的基础原语2.1 AllReduce 到底在做什么AllReduce 的名字听起来很底层但逻辑其实很简单。分布式训练时一个 batch 会被切成很多份分给不同 GPU。每张 GPU 用自己那一份数据做前向和反向算出一份梯度。可模型只有一个参数更新必须基于所有数据的梯度不能让每张卡各更新各的。这时就需要一种机制让所有 GPU 把自己手里的梯度汇总起来再广播回去最终每张卡都拿到完整的全局梯度。这个“汇总再广播”的操作就叫 AllReduce。用生活场景类比几个小组分别统计自己的投票数AllReduce 就是让所有人互相通报最后每个人都知道总票数。关键点不是结果是否正确而是“所有人都知道相同且完整的结果”。这个约束是分布式计算的基本要求。2.2 Ring-AllReduce 是怎么省带宽的最简单的 AllReduce 实现是广播加求和每张卡把自己的数据发给所有其他人同时接收别人的数据本地做累加。N 张卡的情况下总通信量会随着卡数的增加而平方增长卡一多就会爆炸。NCCL 使用的 Ring-AllReduce 缓解了这个问题它把 GPU 排成一个逻辑环把需要归约的数据切成 N 块每张卡只负责把其中一块传给下一张同时从上一张接收数据并累加。跑完一圈每个数据块都被完整归约到了某张卡上接下来再做一次 AllGather把所有结果分发给所有人。Ring 算法的核心思路是“每个节点只和邻居通信”而不是和所有节点通信这样单卡需要搬运的数据总量基本和模型大小同量级不会随卡数平方爆炸。NCCL 还会根据拓扑和节点规模选择 Tree 算法跨机器时按层级归约减少长距离传输。vLLM 之所以能比较高效地跑多卡底层靠的就是这些集合通信算法。2.3 vLLM 推理里为什么也有 AllReduce很多初学者会问推理没有梯度为什么还需要 AllReduce原因是张量并行把同一个线性层切到了多卡上。以 Transformer 的 MLP 为例权重按列切分后每张卡算出的是局部结果而下一层的输入必须包含所有神经元的输出所以必须把多卡的局部结果加起来。这个“加起来”就是一次 AllReduce。Transformer 每一层都会发生多次类似操作Attention 里的 K/V 也需要通过 AllGather 或 Reduce-Scatter 来合并。虽然 vLLM 在代码层封装了这些细节但集合通信带来的延迟和带宽压力是真真切切存在的。2.4 同步是模型结构带来的逻辑约束把同步理解成“为了训练稳定”是不够准确的。只要使用张量并行同步就是强制的逻辑约束。上层计算依赖下层所有切分结果缺任何一个中间值数值就不正确。这和训练还是推理无关更像是串行依赖某一步干不完后面所有步都得等着。vLLM 的调度器再先进也无法绕过这个依赖只能通过优化通信算法来降低等待成本。3. NVLinkGPU 之间的高速专线3.1 NVLink 硬件基础NVLink 是 NVIDIA 专门为多 GPU 通信设计的高速互联目的是绕开 PCIe 的带宽瓶颈。它的带宽比 PCIe 高一个数量级而且支持 GPU 间点对点直接访问对方显存不需要 CPU 参与搬运。以常见的 A100 为例一张卡通过多个 NVLink 链路获得约 600 GB/s 的总带宽到了 H100这个数字提升到约 900 GB/s。相比之下PCIe Gen4 x16 的单向带宽只有约 32 GB/s双向约 64 GB/s。同样一份 AllReduce 数据走 NVLink 的时间可能只有走 PCIe 的十分之一甚至更少。不过要注意NVLink 带宽是聚合值实际能跑多高取决于卡间拓扑和连接方式。不要只看到“支持 NVLink”就认为任意两张卡都是全速互联具体要看拓扑结构。3.2 从 NVLink 到 NVSwitch单张 GPU 的 NVLink 链路数量有限8 卡机器如果只靠点对点连接很难保证任意两张卡都有直连路径。于是 NVIDIA 引入了 NVSwitch用独立交换芯片把多张 GPU 连成全网状。DGX 系列就是典型例子8 张 GPU 通过 NVSwitch 实现全互联任意两张卡之间的通信带宽都很高。普通数据中心里很多机器只有相邻几张卡之间有 NVLink再往外就要走 PCIe甚至还要经过 CPU。这种拓扑差异直接在 vLLM 部署时体现出来。部署前用nvidia-smi topo -m看一张图就能发现哪些卡在一个 NVLink 域里哪些卡之间只有 PCIe。这个命令应该成为多卡部署的“开机第一查”。3.3 NVLink、PCIe、InfiniBand 的直观对比链路类型典型带宽延迟适用范围典型场景PCIe Gen4 x16单向约 32 GB/s微秒级单机内所有设备通用数据传输NCCL 备选路径NVLink 3/4约 600/900 GB/s很低单机内 GPU 之间张量并行、集合通信InfiniBand/RoCE约 25-50 GB/s较低跨机器多机分布式训练与推理这个表格只看量级具体数值因硬件和协议而异。关键信息是NVLink 在单机多卡通信里优势极其明显而 InfiniBand 解决的是跨机器问题两者解决的层次不同。vLLM 的多卡通信需求主要体现在单机内所以 NVLink 直接决定了你的张量并行能有多快。3.4 拓扑对 vLLM 部署的实际影响我在一台号称“8 卡可跑大模型”的机器上踩过坑tensor_parallel_size8启动后NCCL 初始化很慢跑一个 7B 模型都比 2 卡慢。用nvidia-smi topo -m一看8 张卡其实被分成了两组组内 4 张卡有 NVLink组间只能走 PCIe。这种情况下做 8 卡张量并行每次 AllReduce 都要跨低速链路通信开销直接把计算收益吃掉了。更隐蔽的是虚拟化问题。云平台上有些 GPU 是通过透传方式提供的宿主机的 NVLink 不会完整透传进虚拟机。从系统里看确实有 8 张卡但 NCCL 探测不到 NVLink 链接只能退回 PCIe。这种环境里盲目提高并行度很容易适得其反。所以无论是自建机器还是租卡都要先确认真实拓扑再定并行策略。4. vLLM 的多GPU通信实现解析4.1 张量并行怎么切、怎么通信vLLM 的张量并行方式和训练框架基本一致。Transformer 的 Attention 部分QKV 权重按列切分到多张卡输出投影按行切分MLP 部分gate 和 up 投影按列切分down 投影按行切分。前向计算中每处“切分后需要合并”的位置都对应一次集合通信。具体来说MLP 的输出需要一次 AllReduce 才能变成完整结果Attention 在计算前需要把每张卡上的 K/V 通过 AllGather 收集起来让所有卡都能访问完整的上下文。vLLM 在内部调用 NCCL 完成这些操作使用者不需要手写通信代码但理解这些点能帮助你判断当tensor_parallel_size增加时到底哪些环节会被拖慢。4.2 AllGather、Reduce-Scatter 和 AllReduce 的区别很多人习惯把所有卡间同步都叫 AllReduce但 NCCL 实际会使用不同原语。AllGather 是把每张卡的局部数据收集起来最后每张卡都拥有一份完整拼接结果Reduce-Scatter 是先把数据做归约再让每张卡只保留结果中属于自己的那一块AllReduce 可以看成 Reduce 加 AllGather 的组合。FlashAttention 在张量并行实现里通常用 AllGather 加 Reduce-Scatter避免把完整 K/V 广播到所有卡而 MLP 部分则直接用 AllReduce。通信原语核心行为在 Transformer 中的典型位置AllReduce每卡一份数据归约后每卡都拿到完整结果MLP 输出合并、梯度同步AllGather每卡一部分收集后每卡都拿到拼接结果Attention 的 K/V 广播Reduce-Scatter归约后每卡保留自己那部分结果FlashAttention 的张量并行All-to-All每卡的数据按目标分发类似路由MoE 模型的专家并行vLLM 的日志和 profiling 工具里会看到这些名称不用害怕把它们理解成不同类型的“传数据算结果”即可。4.3 vLLM 的 scheduler 与通信之间是什么关系热词里有“vllm scheduler逻辑”这里多说一句。vLLM 的 scheduler 负责决定请求何时进入执行、batch 怎么组织、显存怎么分配它并不直接参与通信原语的执行。但 scheduler 会影响 batch 的大小而 batch 大小直接影响通信效率。原因是相同并行度下batch 越大单次 AllReduce 需要搬运的数据量越大通信延迟被真正传输的数据量摊薄batch 很小时每次通信的固定开销显不出来通信占比反而高。这就是为什么高并发小请求场景下张量并行收益往往不明显而长序列大 batch 场景下NVLink 才能体现出价值。4.4 并行度配置建议结合前面的原理给出我实际部署时的选型逻辑单卡显存足够坚决不开并行。比如用 vLLM 加载 Qwen3-Embedding-0.6B 这类 embedding 模型单卡绰绰有余开了并行只会增加通信开销。单机多卡且有完整 NVLink可以开--tensor-parallel-size 4或 8但必须先确认拓扑。机器是 22 或 44 的 NVLink 分组优先用CUDA_VISIBLE_DEVICES把并行限制在同一组内不要跨组做张量并行。MoE 模型比如 DeepSeek 系列专家路由会产生 All-to-All 通信对带宽要求比 Dense 模型更高NVLink 的宽窄直接决定吞吐。多机部署再考虑流水线并行一般只在机间网络带宽不够时才用单机内仍然优先张量并行。5. 通信瓶颈排查与 Docker 部署实战5.1 怎么判断通信成了瓶颈最简单的方式是压测不同tensor_parallel_size下的吞吐。比如同一个模型分别用 TP2、TP4 跑一次benchmark_serving如果 TP4 的吞吐没有明显提升甚至下降说明通信成了瓶颈。更细的观察可以用NCCL_DEBUGINFO看初始化日志或者用nvidia-smi的 PCIe 读写字段观察卡间流量。如果卡间数据量很大但 GPU 计算单元利用率不高大概率是通信等待。要注意的是通信瓶颈不一定出现在显存带宽上。小 batch 条件下真正的瓶颈往往是通信延迟哪怕每条消息很小来回的次数多了照样会把时间吃掉。这也是为什么小请求场景下盲目加卡会适得其反。5.2 Docker 里跑多卡 vLLM 的通信设置很多人直接跑官方镜像vllm/vllm-openai但docker run --gpus all之后问题不断。最常见的坑是共享内存。NCCL 在单机多卡环境下会使用/dev/shm做进程间共享而 Docker 容器默认只有 64MB。多卡启动时一旦需要共享数据就会报错或者卡死。解决办法是启动时加--shm-size1g或者直接用--ipchost。另一个坑是容器内的驱动环境。要确认容器里nvidia-smi能正常工作如果不行检查 NVIDIA 容器运行时是否安装以及环境变量NVIDIA_DRIVER_CAPABILITIES是否包含compute,utility。很多用户自制的镜像里少了这个变量导致 CUDA 上下文报错和通信代码本身无关。5.3 常见错误速查表现象可能原因解决方法NCCL 初始化超时共享内存不足、网卡不稳定、驱动不匹配加--shm-size检查NCCL_TIMEOUT容器内报unable to create shared memory segmentDocker 默认 /dev/shm 太小加--shm-size1g或--ipchost多卡比单卡慢拓扑不对、batch太小、NVLink 未生效检查nvidia-smi topo -m提高 batch新模型加载报架构错误vLLM 版本太旧不支持模型结构升级到支持该模型的新版镜像NCCL 明明存在但速度极低虚拟化环境没有透传 NVLink降低并行度或改用多副本部署这张表是我多次排查问题后整理的大多数多卡“翻车”案例都能在里面找到位置。5.4 关于镜像版本和模型加载热词里提到docker vllm/vllm-openai:v0.27.1加载qwen3-embedding-0.6b。我建议别用太旧的镜像加载最新模型。vLLM 对新模型架构的支持是逐步合入的0.27.1 这个版本相对较老加载 Qwen3 系列很可能出现“模型架构不被支持”的报错。即使能加载也不能保证推理行为符合预期。另外官方镜像并不内置模型权重你需要把模型目录挂载进容器或者让它从模型仓库下载。Docker 部署多卡时还有一个细节--gpu-memory-utilization不要设置得太满建议留出 10%-15% 的显存余量否则 CUDA 显存碎片和 page cache 问题会在高并发时集中爆发。这个和通信无关但很多用户在排查多卡问题时会被它干扰判断。6. 一个实操案例4 卡部署的通信观察6.1 案例参数与启动命令我用一个约 70B 参数的模型做过一次 4 卡部署测试机器是 4 张 A100。启动前先跑nvidia-smi topo -m确认拓扑输出里卡间是NV而不是PIX或PHB然后执行CUDA_VISIBLE_DEVICES0,1,2,3 vllm serve /models/example-70b \ --tensor-parallel-size 4 \ --max-model-len 4096 \ --gpu-memory-utilization 0.85 \ --enforce-eager我临时加了--enforce-eager只是为了观察执行流程实际生产环境不建议长期开因为 CUDA Graph 对吞吐优化很大。启动日志里可以打开NCCL_DEBUGINFO观察 NCCL 是否成功选择了 NVLink 路径。6.2 遇到的实际问题第一次启动时卡在了 NCCL 初始化阶段日志报unhandled cuda error。排查下来是 Docker 共享内存太小因为我没有加--shm-size。加上之后初始化恢复正常。可随后压测发现4 卡吞吐只比 2 卡提高了一点点。再看拓扑图卡 0 和卡 3 之间实际走的是 PCIe说明 4 张卡不是完整 NVLink 全互联。于是我把tensor_parallel_size改回 2只使用同一 NVLink 组里的两张卡吞吐反而比 4 卡更高。这个案例很直观不是显存不够不是 vLLM 配置错而是通信拓扑没有支撑起并行度。6.3 用 nccl-tests 做一次验证为了把问题看透我跑了一次all_reduce_perf分别测 NVLink 互联和跨 PCIe 两种路径。结果很有意思当单次数据量只有 1MB 时两种路径的耗时差距很小因为这时候通信延迟起主导作用但当数据量到 64MB 以上NVLink 的带宽优势爆发吞吐差距能拉开好几倍。这正好对应大 batch、长序列生成场景通信量越大NVLink 越值钱。而 embedding 小模型、高并发短请求场景往往是延迟敏感而不是带宽敏感NVLink 带来的收益没那么大。这个结论对我后续选型影响很大做长文本生成我会优先保证 NVLink 全互联的拓扑做短请求代理我更看重调度延迟和显存复用。6.4 最后一点个人体会我踩过最大的坑就是把 GPU 数量直接等同于性能保证。多卡通信是一个完整链路AllReduce 保证算法正确NVLink 保证传输速度vLLM 只是把二者封装成了你每天在用的tensor_parallel_size。下次加卡之前先静下心看一眼拓扑再跑一次 benchmark最后再去调--shm-size和并行度。数据不会骗人通信能力决定并行上限这比任何配置技巧都管用。