4、多卡通信机制:GPU间通信、NCCL、张量并行与流水线并行

多卡 vLLM 推理的性能瓶颈,往往不在 GPU 计算本身,而在 GPU 之间的数据搬运。当模型被切分到多张卡上后,每一次前向传播都会引入跨卡通信开销。理解 GPU 间通信的物理路径、NCCL 的协作方式,以及张量并行与流水线并行在通信模式上的本质差异,是诊断负载均衡问题和优化吞吐量的前提。

4.1 GPU 间通信的物理拓扑与带宽层次

GPU 之间并不共享内存,它们通过特定的物理总线交换数据。现代服务器中,通信路径的带宽差异极大,直接决定了并行策略的适用场景。下图展示了典型 8 卡 A100/H100 服务器中的通信拓扑与带宽层次。

8 卡 GPU 服务器通信拓扑与带宽层次 CPU 0 PCIe Gen4 x16 CPU 1 PCIe Gen4 x16 NVSwitch NVLink 全互联 GPU 0 rank 0 GPU 1 rank 1 GPU 2 rank 2 GPU 3 rank 3 GPU 4 rank 4 GPU 5 rank 5 GPU 6 rank 6 GPU 7 rank 7 NVLink 带宽: 600 GB/s (H100) ~ 900 GB/s (A100) PCIe Gen4: 32 GB/s 跨 NUMA: 需经过 CPU 间 QPI/UPI,延迟显著增加

上图中,GPU 0~5 通过 NVLink 直连 NVSwitch,形成全互联的 NVLink 域;GPU 6 和 GPU 7 虽然也在同一台机器上,但若其 NVLink 连接被 BIOS 或拓扑限制降级为 PCIe 路径,通信带宽会骤降一个数量级。vLLM 启动时会通过 torch.cuda.get_device_name() 和 cudaDeviceGetP2PAttribute 检测设备间的 P2P 能力,若检测失败,则会回退到通过 CPU 内存拷贝的路径,此时通信开销会急剧放大。

4.2 NCCL 通信原语与集合操作

NCCL(NVIDIA Collective Communications Library)是 GPU 间通信的事实标准。它屏蔽了底层拓扑差异,向上提供 AllReduce、AllGather、ReduceScatter 等集合通信原语。vLLM 的张量并行依赖这些原语完成每层 Transformer 的中间结果同步。

以张量并行度为 2 的 AllReduce 为例,其数据流如下:

# 假设两个 GPU 分别持有部分结果
# GPU 0: [A0, B0]   GPU 1: [A1, B1]
# AllReduce 后,两张卡都得到 [A0+A1, B0+B1]

# vLLM 中实际调用(通过 torch.distributed)
torch.distributed.all_reduce(tensor, op=torch.distributed.ReduceOp.SUM)

NCCL 的 AllReduce 在 8 卡全互联拓扑下通常采用 Ring AllReduce 算法:数据被切分为 N 个 chunk,在 GPU 环上依次传递并累加,最终通过一次 AllGather 将完整结果广播给所有 rank。该算法通信量为 2 × (N-1)/N × 数据量,当 N 较大时接近 2 倍数据量。

调优提示: 在 vLLM 中设置 NCCL_P2P_LEVEL=NV 可以强制仅使用 NVLink 进行 P2P 传输,避免误走 PCIe。但若拓扑中确实存在跨 NUMA 的 GPU,该设置可能导致通信失败,需结合 nvidia-smi topo -m 的输出判断。

4.3 张量并行:通信密集型的层内切分

张量并行(Tensor Parallelism, TP)将每一层的权重矩阵按行或列切分到多张 GPU 上,所有 GPU 同时计算同一层,然后通过 AllReduce 同步结果。其特点是通信频率极高——每个 Transformer 层至少需要 2 次 AllReduce(一次在 attention 输出,一次在 MLP 输出)。

在 vLLM 中,TP 大小通过 --tensor-parallel-size 指定。当 TP=4 时,模型每层的前向计算流程如下:

  1. 输入 hidden_states 被切分为 4 份,分别送入 4 张 GPU 的对应权重分片。
  2. 每张 GPU 计算得到部分结果(如 attention 输出的一部分)。
  3. 执行 AllReduce,将 4 份部分结果求和得到完整输出。
  4. 进入 MLP 层,再次切分、计算、AllReduce。

由于每层都有通信,TP 的扩展效率受限于 计算/通信比。对于 7B 模型,TP=2 通常能获得接近线性的加速;但 TP=8 时,通信开销可能占据 30% 以上的时间,导致实际吞吐提升远低于预期。

4.4 流水线并行:通信稀疏的层间切分

流水线并行(Pipeline Parallelism, PP)将 Transformer 的不同层分配到不同 GPU 上,每张 GPU 只负责计算自己那几层。通信只发生在相邻 stage 的边界——即前一个 stage 的完整输出传递给下一个 stage 作为输入。因此,PP 的通信频率远低于 TP,但单次通信的数据量较大(通常是完整的 hidden state 序列)。

vLLM 对 PP 的支持相对有限,主要面向超大模型场景。PP 的核心挑战在于流水线气泡:当 GPU 0 计算第 1 层时,GPU 1 处于空闲等待状态。vLLM 通过 micro-batch 调度来缓解气泡,但无法完全消除。

对比维度 张量并行 (TP) 流水线并行 (PP)
切分粒度 层内权重矩阵 层间(按层分组)
通信频率 每层 2+ 次 AllReduce 每个 micro-batch 仅 1 次边界传输
单次通信量 与 hidden size 相关,较小 完整序列的 hidden states,较大
主要瓶颈 通信延迟、带宽饱和 流水线气泡、负载不均
适用场景 单机多卡,模型可放入单卡显存 多机多卡,模型超过单卡显存

4.5 案例:TP=4 时 AllReduce 导致的负载不均

在一台 8 卡 A100 服务器上部署 vLLM,设置 --tensor-parallel-size 4,但观察到 GPU 0 和 GPU 1 的利用率(SM 占用率)明显高于 GPU 2 和 GPU 3。通过 nsys profile 抓取 NCCL 事件后发现:

# nsys 输出片段(简化)
[GPU 0]  NCCL Kernel: AllReduce_1  duration=1.2ms
[GPU 1]  NCCL Kernel: AllReduce_1  duration=1.2ms
[GPU 2]  NCCL Kernel: AllReduce_1  duration=0.8ms
[GPU 3]  NCCL Kernel: AllReduce_1  duration=0.8ms

进一步检查 nvidia-smi topo -m 发现,GPU 0/1 与 GPU 2/3 分属两个不同的 NUMA 节点,且 GPU 2/3 的 NVLink 连接被 BIOS 限制为 4 条(而非完整的 12 条)。这导致 GPU 2/3 在 AllReduce 中接收数据的速度更慢,但 NCCL 的 Ring 算法要求所有 rank 同步等待最慢的节点,因此整体通信时间被拉长,且 GPU 0/1 在等待期间出现空闲,表现为利用率波动。

解决方案: 将 TP 大小调整为 2,并分别绑定到同一 NUMA 节点内的 GPU 对(如 GPU 0/1 一组,GPU 2/3 一组),通过 CUDA_VISIBLE_DEVICES=0,1 启动两个 vLLM 实例。这样每个实例的 AllReduce 都在 NVLink 全带宽下完成,整体吞吐反而提升了 40%。

4.6 常见误区与复盘

误区 1:盲目增大 TP 大小。 TP 不是越大越好。当 TP 超过 4 后,AllReduce 的通信量呈线性增长,而计算量不变,导致计算/通信比恶化。对于 7B 模型,TP=4 通常是甜点;对于 70B 模型,TP=8 才可能值得。
误区 2:忽略 NCCL 环境变量。 默认情况下 NCCL 会尝试自动检测拓扑,但检测结果不一定最优。建议显式设置 NCCL_P2P_LEVEL=NV(仅 NVLink)或 NCCL_NET_GDR_LEVEL=PHB(启用 GPUDirect RDMA),并观察 NCCL_DEBUG=INFO 输出的实际通信路径。
误区 3:混淆 TP 与 PP 的通信开销。 在 vLLM 中,PP 的通信虽然频率低,但单次传输的数据量是完整的 KV cache 和 hidden states。如果 PP 的 stage 间使用 PCIe 而非 NVLink,传输 4096 维的 hidden states 可能耗时数毫秒,反而比 TP 的 AllReduce 更慢。

复盘建议: 在诊断多卡负载均衡问题时,第一步永远是查看 nvidia-smi topo -m 确认物理拓扑;第二步用 NCCL_DEBUG=INFO 启动 vLLM,观察实际通信路径是否走 NVLink;第三步用 nsys profile 抓取 NCCL kernel 的耗时分布,定位是否存在慢 rank。只有先厘清通信机制,才能正确判断负载不均的原因是计算不均还是通信不均。