分布式通信原语与 vLLM 并行策略

深入理解 AllReduce, AllToAll, TP, DP, PP, EP
技术分享 Meetup

目录

通信原语

  • 6 大通信原语详解
  • AllReduce 与 Ring 算法
  • AllToAll 全交换

并行策略与通信量

  • TP/DP/PP/EP 通信详情
  • Prefill-Decode 分离
  • 通信量计算公式

总结

  • 通信量对比
  • 并行策略选择指南

📡 分布式通信原语:全景图

原语定义类比
Broadcast一个节点 → 所有节点广播通知
Reduce所有节点 → 一个节点汇总报表
AllReduce所有节点同步获得归约结果大家报数,都知道总数
Gather收集数据(不合并)收作业
AllToAll每个节点给所有节点发不同数据互相握手
Send/Recv两节点双向通信两人通话
💡 理解 AllReduce

AllReduce = Reduce + Broadcast。先汇总,再把结果同步给所有人。

💡 理解 ReduceScatter

ReduceScatter = Reduce + Scatter。归约后分片,每人拿一块。

🔄 AllReduce = 归约 + 同步广播

🌐 AllReduce = 所有机器一起做归约,结果同步给所有人

所有节点不仅参与计算,最后还同步获得相同的结果!

flowchart LR D0["🖥️ 节点0: 3"] & D1["🖥️ 节点1: 5"] & D2["🖥️ 节点2: 2"] & D3["🖥️ 节点3: 8"] --> AR["🔄 AllReduce
(求和)"] AR --> R["结果: 18"] R --> R0["18 ✓"] & R1["18 ✓"] & R2["18 ✓"] & R3["18 ✓"]

通信量公式

Ring AllReduce: 总通信量 ≈ 2M (与机器数无关) 其中 M = 数据大小

应用场景

  • TP: 每层计算后同步
  • DP 训练: 梯度同步

🔄 AllToAll = 全交换

📡 AllToAll = 每个人都给每个人发不同的消息

AllReduce 是"大家做同样的事,得到同样的结果"。AllToAll 是"大家交换数据,每个人都给其他人发不同的东西"。

flowchart LR G0["GPU0
[a0|a1|a2|a3]"] -->|"a1→GPU1"| G1["GPU1
[b0|b1|b2|b3]"] G1 -->|"b2→GPU2"| G2["GPU2
[c0|c1|c2|c3]"] G2 -->|"c3→GPU3"| G3["GPU3
[d0|d1|d2|d3]"] G3 -->|"d0→GPU0"| G0

通信量

总通信量: N² × M 字节 每人发送: (N-1) × M 字节

主要用途

  • MoE 模型: Token 路由到专家
  • 数据重分区

⚙️ vLLM 并行策略概览

flowchart TB AR["🔄 AllReduce"] --> TP["📦 TP"] A2A["📡 AllToAll"] --> EP["👥 EP"] P2P["📨 Send/Recv"] --> PP["🔗 PP"] NONE["❌ 无通信"] --> DP["📋 DP"] subgraph Info["通信模式"] TPL["Tensor Parallel: 每层2次 AllReduce"] EPL["Expert Parallel: AllToAll 路由"] PPL["Pipeline Parallel: P2P 传激活"] DPL["Data Parallel: 推理无通信"] end

一句话总结

  • TP = AllReduce
  • EP = AllToAll
  • PP = Send/Recv
  • DP 推理 = 无通信

关键问题

  • 通信量多大?
  • 传什么内容?
  • Prefill vs Decode 有何不同?

📦 Tensor Parallel (TP) 通信详情

💡 核心:每层计算后需要 AllReduce 同步

TP 把同一层权重分到不同 GPU,每 GPU 算完后要把结果汇总(AllReduce)。

通信内容

阶段通信类型传输内容
ForwardAllReduce局部输出 → 完整输出
BackwardAllReduce局部梯度 → 完整梯度

通信量公式

# Forward (单层) C_TP_fwd = 2 × B × S × H / P # Backward (单层) C_TP_bwd = 2 × C_TP_fwd B=batch, S=seq_len, H=hidden, P=TP degree
⚠️ 关键:TP 通信量与 batch size × sequence length 成正比,与 GPU 数成反比。

📦 TP: Prefill vs Decode 通信差异

维度PrefillDecode
输入形状[B, S_prompt, H][1, 1, H] 逐 token
通信量大 (S_prompt 长)小 (每次 1 token)
AllReduce 次数每层 1 次每层 1 次
瓶颈带宽敏感延迟敏感

Prefill 特点

  • 一次性处理整个 prompt
  • 序列长,通信量大
  • 适合高带宽互联 (NVLink)

Decode 特点

  • 逐 token 生成
  • 每次通信量小但频繁
  • 对延迟更敏感
💡 实际影响:Prefill 阶段 TP 通信开销明显,Decode 阶段相对较小。Decode 时如果用 Ring Attention,还需要额外传递 K/V 块。

📋 Data Parallel (DP) 通信详情

💡 核心:推理时无通信,训练时用 AllReduce 同步梯度

每个 GPU 持有完整模型副本,处理不同 batch。

通信内容

阶段通信类型传输内容
推理
训练AllReduce梯度
DP AttentionAllReduceK/V 激活

通信量

# 梯度同步 C_DP = model_size × bytes # 7B 模型 (FP16): ~14 GB # 70B 模型 (FP16): ~140 GB # DP Attention (K/V) C_DP_attn = 2 × B × S × H × bytes
✅ DP 推理优势:完全无 GPU 间通信!每个 GPU 独立处理请求,线性扩展吞吐量。

📋 DP Attention 的特殊通信

🔍 问题:跨 GPU 的 Attention 计算需要同步 K/V

当使用 DP Attention 时,不同 GPU 处理不同请求,但 Attention 需要完整的 K/V。

flowchart LR Q0["🖥️ GPU0 Q"] --> A["🔢 Attention 计算"] K0["🖥️ GPU0 K"] -->|"AllReduce"| Kfull["完整 K"] V0["🖥️ GPU0 V"] -->|"AllReduce"| Vfull["完整 V"] Q1["🖥️ GPU1 Q"] --> A K1["🖥️ GPU1 K"] -->|"AllReduce"| Kfull V1["🖥️ GPU1 V"] -->|"AllReduce"| Vfull

为什么需要?

  • 不同 GPU 处理不同请求
  • Attention 需要跨 batch 的 K/V
  • DeepSeek 等 MLA 模型尤其需要

vLLM DP Attention

  • KV Cache 分片存储
  • 避免完整 KV 在 GPU 间复制
  • 节省 MLA 模型内存

🔗 Pipeline Parallel (PP) 通信详情

💡 核心:阶段之间用 Send/Recv 传递激活值

PP 把不同层的 GPU 直连,只在边界传输数据。

flowchart LR G0["🖥️ GPU0
Layer 0-19"] -->|"激活值"| G1["🖥️ GPU1
Layer 20-39"] G1 -->|"激活值"| G2["🖥️ GPU2
Layer 40-59"] G2 -->|"激活值"| G3["🖥️ GPU3
Layer 60-79"]

通信内容

方向传输内容大小估算
Forward激活值 X_outB × S × H_next
Backward梯度B × S × H_current

通信量

# 单层传输 C_PP = B × S × H × bytes # 仅在阶段边界传输 # 通信量远小于 TP

🔗 PP: Prefill vs Decode 通信差异

💡 Prefill 和 Decode 在 PP 中通信模式类似,但数据量差异大

维度PrefillDecode
传输数据完整 prompt 激活单 token 激活
通信频率一次(完整序列)N 次(N 个 token)
KV Cache生成并存储读取历史 + 生成新
瓶颈计算密集访存密集
⚠️ PP 的问题:如果 Prefill 和 Decode 混在同一个 PP 流水线中,会相互阻塞——Prefill 的长序列会等待 Decode 的单 token 交互。
💡 解决方案:PD 分离!把 Prefill 和 Decode 分到不同的 GPU 集群。

👥 Expert Parallel (EP) 通信详情

💡 核心:MoE 模型用 AllToAll 路由 Token 到专家

每个 token 只激活 top-k 个专家,专家分布在不同 GPU。

sequenceDiagram participant T as 📋 Token participant R as 🎯 Router participant G0 as 🖥️ GPU0 participant G1 as 🖥️ GPU1 T->>R: 选择 Expert-0, Expert-1 R->>G0: AllToAll Dispatch R->>G1: AllToAll Dispatch G0->>G0: Expert-0 计算 G1->>G1: Expert-1 计算 G0->>T: AllToAll Combine G1->>T: AllToAll Combine

通信内容

# AllToAll 发送: Token 到 Expert GPU # AllToAll 接收: 结果返回原 GPU C_EP = 2 × tokens × topk × expert_size

EP 挑战

  • 负载均衡(专家选择动态)
  • AllToAll 通信量大
  • 需要高速互联

👥 EP: Prefill vs Decode 通信差异

维度PrefillDecode
Token 数量长序列 (S 个)单 token
AllToAll 规模大(同时路由多 token)小(单 token)
通信量
负载均衡更易不均衡需更好均衡策略
📊 EP 通信量公式:
C_EP = 2 × batch × seq × topk × expert_size # Prefill: seq = prompt_length (可能数千) # Decode: seq = 1
💡 形象理解

Prefill 像一次派对上把几千人分配到不同桌子;Decode 像每次只来一个人去握手。通信量差异巨大!

🔀 Prefill-Decode (PD) 分离

💡 核心思想:把 Prefill 和 Decode 分到不同的 GPU 集群

因为它们的资源需求和通信模式完全不同!

flowchart LR subgraph Prefill["📦 Prefill 集群"] P["高算力 GPU
TP × DP"] end subgraph Transfer["⬇️ KV Cache 传输"] KV["Network"] end subgraph Decode["🎯 Decode 集群"] D["高带宽 GPU
大 Batch DP"] end P -->|"KV Cache"| KV KV -->|"传输"| D

Prefill 集群特点

  • 计算密集 (FLOPs 瓶颈)
  • 需要 TP 提升算力
  • 处理长序列 prompt

Decode 集群特点

  • 带宽密集 (Memory BW 瓶颈)
  • 需要大 batch DP
  • 逐 token 生成

🔀 为什么需要 PD 分离?

Prefill 阶段

特性描述
计算矩阵乘法密集
序列一次性处理整个 prompt
瓶颈算力 (FLOPs)
内存激活值大

Decode 阶段

特性描述
计算轻量但访存密集
序列逐 token 生成
瓶颈带宽 (Memory BW)
内存KV Cache 访问
❌ 传统方式的问题:Prefill 和 Decode 混在同一个集群,互相干扰——长序列 Prefill 会阻塞 Decode 的低延迟响应。
✅ PD 分离的优势:
  • 资源隔离:各用最适合的 GPU 配置
  • 调度灵活:可分别优化
  • 带宽利用率:Decode batch 可放到最大

🔀 PD 分离的节点间通信

📡 主要通信:KV Cache 传输

Prefill 完成后,需要把生成的 KV Cache 传输到 Decode 集群。

KV Cache 大小计算

# 单 token 单层 KV = 2 × num_heads × head_dim = 2 × H (FP16) # 完整序列 KV_total = B × S × 2 × H × num_layers # 示例: LLaMA-7B, 4096 token, FP16 # H=8192, layers=32 # KV = 1 × 4096 × 2 × 8192 × 32 # ≈ 2 GB

通信优化

  • 压缩传输: FP8 或更低精度
  • 流水线: Prefill 输出后立即开始 Decode
  • 部分传输: 只传必要部分
⚠️ 关键瓶颈:KV Cache 跨节点传输量巨大,是 PD 分离的主要开销。需要高速网络 (InfiniBand/NVLink Switch)。

🔀 PD 分离适用的并行策略

📦 Prefill 节点

并行方式原因
TP计算密集,提升单节点算力
DP多 prompt 并行,提高吞吐
PP超大模型可分层(较少用)

🎯 Decode 节点

并行方式原因
DPDecode batch 并行,吞吐关键
EPMoE 模型专用
序列并行长上下文减少显存压力
💡 推荐配置:
  • Prefill: TP × DP (高算力利用率)
  • Decode: 纯 DP (最大化 batch size)

📊 通信量综合对比

策略通信原语传输内容通信量级
TPAllReduce激活值/梯度O(B×S×H/P)
DP (训练)AllReduce梯度O(model_size)
DP (推理)0
PPSend/Recv激活值O(B×S×H)
EPAllToAllToken + Expert 输出O(tokens×topk×expert)
PD 分离NetworkKV CacheO(B×S×H×layers)
pie title 带宽需求排序 "TP" : 40 "EP" : 35 "PD分离 KV传输" : 30 "PP" : 20 "DP推理" : 0

🎯 并行策略选择指南

flowchart TD A{"模型能放入
单GPU?"} -->|是| B{"需要更高
吞吐?"} A -->|否| C{"是 MoE?"} B -->|是| D["✅ DP (推理无通信)"] B -->|否| E["单GPU运行"] C -->|是| F["✅ EP + TP/DP"] C -->|否| G{"需要
跨节点?"} G -->|是| H["✅ TP + PP"] G -->|否| I["✅ TP (NVLink)"]

📋 PD 分离特殊场景

场景推荐方案
低延迟 DecodePD 分离 + Decode 纯 DP
长序列 PrefillPD 分离 + Prefill TP×DP
超长上下文PD 分离 + Decode 序列并行

💡 选择口诀

单卡能放 → DP(无通信)
大模型 → TP(AllReduce)
MoE → EP(AllToAll)
跨节点 → TP+PP
极低延迟 → PD 分离

📝 总结

📡 通信原语

  • AllReduce: 归约 + 同步广播
  • AllToAll: 全交换 (MoE 路由)
  • Send/Recv: P2P (PP 阶段边界)

⚙️ 并行策略

策略通信特点
TPAllReduce层内分片
DP无(推理)副本扩展
PPSend/Recv流水线
EPAllToAllMoE专用

🔀 PD 分离核心价值

  • Prefill: 计算密集 → TP × DP
  • Decode: 带宽密集 → 大 Batch DP
  • 分离后各自优化,效率最大化

📊 关键公式

TP: 2×B×S×H/P
EP: 2×tokens×topk×expert
PD: B×S×2×H×layers
1 / 21