<aside> 🎯

并行策略回答的是一个很基础的问题:模型的不同部分,怎么切到多张卡上。注意力、MoE 专家、层、请求、序列都可以用不同的方式切,而每一种切法,都会换来一种固定的集合通信。这一页我们就把「切法 → 通信原语 → 通信量」这条链一直手推到底。

</aside>

术语与口径表(全页公式统一使用,推理阶段没有反向传播、不同步梯度,所有公式只算前向)

符号 含义 备注 / 典型值
B Batch Size,一次前向里的请求数 decode 时等于并发请求数
S Sequence Length,本次前向处理的序列长度 prefill 是几千到几十万;decode 恒为 1
B·S 本次前向的 token 总数 所有通信量都正比于它,是最该先估的量
H Num Heads,注意力头数 TP 切的就是这个维度,所以要求 N 整除 H
D Hidden Dim per Head,每个头的维度 常见 128
H·D 模型隐藏层总维度 DeepSeek-V3 是 7168,Llama-3-70B 是 8192
I FFN 中间层扩展维度 稠密模型约 4H·D 或 SwiGLU 的 8/3 H·D;DeepSeek expert 是 2048
N 参与当前这种并行的 GPU 数(Rank Count) 注意每种并行的 N 可以不同:tp_size、ep_size、pp_size 各算各的
K MoE 的 Top-K 路由系数 DeepSeek-V3 是 8;EP 通信量正比于 K
M 某次通信里单卡手上要参与通信的那份数据大小 AllReduce 时 M 是完整隐藏状态;All-to-All 时 M 是本地那份 token
Φ 单卡在单个 Transformer layer 里传输的数据元素个数 本页所有通信量的统一单位;换成字节再乘 dtype 宽度(BF16 是 2,FP8 是 1)

所有 Φ 公式里的系数(那些 2、4 和 (N-1)/N)都不是并行策略自己带的,而是它用的通信原语带的。原语层面的推导放在最后一节「补充知识:Ring 式集合通信」,第一次读可以先记结论,回头再看怎么来的。

1. 参数总览(均来自官方文档)

参数 作用 默认 面试要点
--tensor-parallel-size / --tp-size / --tp 张量并行:把每层的矩阵切到多卡 1 每层两次 AllReduce,对互联带宽敏感;多机时跨节点 TP 会很慢
--data-parallel-size / --dp-size 数据并行:整个模型复制多份,各算各的请求 1 推理期零通信;官方建议用 SGLang Model Gateway(SMG)而不是内置 DP
--pipeline-parallel-size / --pp-size 流水并行:按层切成多段 1 只在段边界传一次激活,通信量最小,适合跨节点;代价是流水线气泡
--pp-max-micro-batch-size、--pp-async-batch-depth、--enable-dynamic-chunking PP 的微批次、异步深度、动态分块 None / 0 / False 动态 chunk 的目的是让每个 chunk 执行时间一致,减少流水线气泡
--enable-dp-attention 只对注意力部分做数据并行,其余仍用 TP/EP False 必须同时设 --dp-size 大于 1,且 tp_size 能被 dp_size 整除
--ep-size / --ep(参见 MoE 文档) 专家并行:把 MoE 专家权重分布到多卡 — DeepEP、Mooncake、NIXL-EP等 all-to-all 后端目前只支持 ep_size 等于 tp_size
--moe-data-parallel-size / --moe-dp-size MoE 部分的数据并行度 1 与 DPA 组合时理解注意力与 MoE 两套并行域
--attention-context-parallel-size / --attn-cp-size 注意力上下文并行(prefill 侧的 CP/SP) 1 长上下文场景,把序列维度切开;对应 Ulysses / Ring Attention 那一类方案
--dcp-size / --decode-context-parallel-size Decode Context Parallel,专为 MLA 模型设计 1 配套 --dcp-comm-backend(ag_rs、a2a、fi_a2a)的通信选择
--nnodes --node-rank --dist-init-addr 多机部署参数 1 / 0 / None 每台机器都启动一个进程,指定同一个初始化地址;遇到死锁可尝试 --disable-cuda-graph
--load-balance-method 内置 DP 的负载均衡策略 auto 可选 auto、round_robin、follow_bootstrap_room、total_requests、total_tokens;total_tokens 只能在 DP attention 下使用
--enable-prefill-delayer DPA 下延迟 prefill 以减少空闲 False 配套 --prefill-delayer-max-delay-passes(默认 30)等,解决各 DP rank 步调不一致的等待问题

2. TP(张量并行)与通信量手推

解决什么问题:单卡放不下稠密模型的权重(Llama-3-70B 光权重 BF16 就是 140 GB),以及小 batch 下想压低单请求延迟——多卡分摊同一份权重的读取,decode 一步的时间就短。

原理(考点:为什么一定是「列切 + 行切」这个顺序):Megatron 的规则只有一条——把两个连续的矩阵乘配成「前一个按列切、后一个按行切」,中间不通信,唯一一次通信推到配对的末尾。这条规则能成立,靠的是三件事咬合在一起。

第一,列切之后不需要通信。Y = X A 里把 A 按列切成 A1、A2,每卡算出的 Y1 = X A1 是 Y 的完整列块——每个元素都已经算完,不缺任何一个加法项,因为矩阵乘在输出的列方向上完全独立。缺的只是「别的列」,而别的列在别的卡手里,此刻并不需要。

第二,行切恰好只要前一步切出来的那部分。Z = Y B 里把 B 按行切成 B1、B2,展开就是 Z = Y1 B1 + Y2 B2。第 i 张卡要算 Yi Bi,它需要的输入正好是自己上一步算出的 Yi。前一步切出来的列块,就是后一步想要的输入,两步的切分方向天生咬合,所以中间那一刀不需要任何数据交换。

第三,代价被推到末尾。Yi Bi 的形状是完整的,但只含求和式里的一项,是部分和;把 N 张卡的部分和相加才是 Z,所以这里必须有一次 AllReduce,而且只需要一次。中间的逐元素算子(SwiGLU、GELU、缩放)不跨列混合,可以跟着列块原地走,不打断这条链;一旦中间插进一个要跨整个 hidden 维求统计量的算子(LayerNorm 那类),链条就断了,必须先把数据聚合起来——这也是 LayerNorm 和残差在标准 TP 里被每卡各存一份的原因(第 6 节的 Megatron-SP 就是来收拾这份冗余的)。

01-tp-col-row-split.svg

落到实际的 Transformer 层里:注意力块里 QKV 投影按列切,权重从 [H·D, 3H·D] 变成每卡 [H·D, (3H/N)·D],每卡拿到若干个完整的头,可以独立算局部 attention,不需要通信;输出投影 W_O 按行切,从 [H·D, H·D] 变成每卡 [(H/N)·D, H·D],每卡算出的是 [B, S, H·D] 的部分和,必须相加才对,于是第一次 AllReduce 出现在这里。FFN 同理:up / gate 按列切成 [H·D, I/N],down 按行切成 [I/N, H·D],down 之后又是部分和,第二次 AllReduce。所以「每层两次 AllReduce」不是实现细节,而是「一层里有两个行切矩阵」的直接结果。

flowchart LR
  X["输入 [B, S, H·D]<br>每卡一份完整副本"] --> QKV["QKV 列切<br>每卡拿 H/N 个头"]
  QKV --> ATT["各卡独立算局部 attention<br>无通信"]
  ATT --> O["W_O 行切<br>输出是部分和"]
  O --> AR1["AllReduce #1"]
  AR1 --> UP["up/gate 列切"]
  UP --> ACT["SwiGLU 逐元素<br>无通信"]
  ACT --> DOWN["down 行切<br>输出是部分和"]
  DOWN --> AR2["AllReduce #2"]

两种切法的语义差别值得单独记住,面试里问「为什么这里要 AllReduce」其实问的就是这张表:

切法 切权重的哪个维 单卡输出 输出语义 末尾要不要通信
列切(column / 输出维) 输出维,如 QKV 的 3H·D、FFN 的 I 形状变窄,输出维只剩 1/N 完整的一部分列,每个元素都算完了 不要(后继行切矩阵正好只要这部分)
行切(row / 输入维) 输入维,如 W_O 的 H·D、FFN down 的 I 形状不变,回到完整 hidden 部分和,只含求和式里的一项 要一次 AllReduce

把顺序反过来会怎样(这是验证自己是不是真懂的好问题):如果先行切再列切,第一步的输出已经是形状完整的部分和,而第二步的列切需要一份正确的完整输入,所以不得不在中间先 AllReduce 一次;而末尾列切的输出又是分片的,下一层要完整 hidden,还得再 AllGather 一次。同一层从一次通信变成两次,而且中间那次卡在关键路径上、前后都不能重叠。所以配对顺序不是风格问题,反了直接翻倍通信。

这条规则延伸出四个常考的角落: