并行与分布式计算
(AI 方向)期中复习
从「为什么需要并行」到 GPU 异构编程、高性能算子、AI 编译器、再到分布式训练与集合通信——本站按课件逐章梳理,含图示、公式、内嵌选择题、作业精解与互动自测,零基础也能看懂。
内容来源约定:正文知识点均引用自课程课件(PDF:
new-pdc00~10、作业一~五)。凡编者补充标记或解析中标注「[自注]」的内容,为编者为帮助理解而补充,非课件原文,请以课件与老师讲授为准。
课程结构(来自 syllabus)
课件 new-pdc00-syllabus 把课程组织为五个模块(本期中复习覆盖前四个模块的已讲内容 Lect01–10):
| 模块 | 主题 | 并行发生在哪一层 | 对应讲次 |
|---|---|---|---|
| 模块一 | AI 系统与异构计算基础 | GPU/NPU「设备」内部,以及「主机—设备」之间 | L01–L03 |
| 模块二 | 高性能 AI 算子与算子工程 | 算子内部的并行及其典型优化 | L04–L07 |
| 模块三 | AI 编译器原理与优化 | 通过编译实现自动/半自动并行化 | L08 |
| 模块四 | 分布式并行训练/推理与通信 | 计算卡之间、计算节点之间 | L09–L10 |
| 模块五 | 性能评估与 AI 辅助 kernel 生成 | 下一代「自动」并行化 | (后续) |
复习路线图
建议按「概念 → 单卡编程 → 算子优化 → 编译自动化 → 多卡分布式 → 通信」的顺序理解,每一层都在解决上一层撞到的「墙」。
为什么要并行(L01)
Amdahl / Gustafson 定律、内存墙、CPU 为何失败、GPU 的崛起。
怎样写单卡 GPU 程序(L02–L03)
host/device、thread/block/grid、SIMT、occupancy、合并访存、bank conflict;归约与分块卷积实例。
怎样优化关键算子(L04–L07)
Attention 与 FlashAttention、Roofline、Img2Col/Winograd、算子融合、Tensor Core 演进。
怎样让编译器自动优化(L08)
计算图、torch.compile、MLIR/TVM/Triton、图优化与调度。
怎样跨多卡多机训练(L09)
数据并行 + ZeRO/FSDP、张量并行、流水线并行、3D/5D 混合、自动并行。
多卡之间怎样通信(L10)
集合通信原语、Ring/树 All-Reduce、α-β 模型、MPI 与 NCCL。
考点权重(编者估计)
CUDA 编程模型与优化
线程层次、SIMT、occupancy、合并访存、bank conflict、归约/分块。
分布式并行与通信
DP/TP/PP、ZeRO、Ring All-Reduce、α-β 通信代价、ZeRO 显存计算。
高性能算子
Attention/FlashAttention、Roofline、算子融合、Tensor Core。
定律与 AI 编译器
Amdahl/Gustafson、计算图、torch.compile、图优化。
L01 · 引论:为什么需要并行
来源:new-pdc01-intro.pdf
1.1 AI 的硬件需求与三大瓶颈
- 模型参数呈指数增长(BERT 0.34B → GPT-3 175B → DeepSeek-V3 671B(MoE) → GPT-4.5 ≈12.8T(MoE))。
- 算力需求增速超过摩尔定律。
- 通信规模成为新的关键瓶颈。
CPU 为什么「失败」于 AI
① 冯·诺依曼瓶颈:数据在「处理单元」与「存储单元」之间不断搬运,消耗的能量甚至超过实际计算本身;
② 性能缩放终结:Dennard 缩放结束、摩尔定律带来的性能红利终结。
加之 CPU 的顺序设计与 AI(如矩阵乘)固有的大规模并行不匹配——课件称 CPU 在神经网络负载上利用率仅 5–10%。
GPU 的崛起是「意外的英雄」:它本为图形(矩阵/向量运算)而生,恰与深度学习的数学一致;2007 年 CUDA 让 GPU 可用于通用计算,训练时间从「周」降到「小时」。
1.2 「内存墙」:参数 vs 显存
结论:大规模、高带宽的互连是必需的。
1.3 并行能帮上忙吗?两条定律
Amdahl 定律(问题规模固定,并行的「死刑判决」):
$$S=\dfrac{1}{(1-p)+\dfrac{p}{n}}\xrightarrow{\;n\to\infty\;}\dfrac{1}{1-p}$$
Gustafson 定律(问题规模随处理器扩展,并行的「救赎」):
$$S=(1-p)+n\,p = 1 + p\,(n-1)$$
其中 $s$ 为加速比,$p$ 为可并行比例,$n$ 为处理器数。
课件例子:10 个处理器、80% 并行、20% 串行,Amdahl 给出 $S=\dfrac{1}{0.2+0.8/10}=3.57$,远达不到 10×。现实中还有数据依赖、I/O 瓶颈等进一步削弱并行度。
🧮 加速比计算器 编者补充
输入并行比例 $p$ 与处理器数 $n$,同时看 Amdahl 与 Gustafson 结果:
并行比例 p(0~1): 处理器数 n:
L02 · 异构编程:CUDA 核心
来源:new-pdc02-hetero.pdf
2.1 主机、设备与互连
- host ≈ CPU;device ≈ GPU(在 OpenCL/ROCm 中 CPU 也可作设备)。
- host–device 互连:PCIe、NVLink-C2C。课件数据:x86–5090 走 PCIe 5.0 约 64 GB/s;Grace-Hopper NVLink C2C 900 GB/s;Grace-Blackwell 1.8 TB/s。
- device–device 互连:PCIe、NVLink、RDMA。NVLink 1.0(2016,P100)双向 160 GB/s,已快于 PCIe 3.0 x16(32 GB/s)。
- GPUDirect:Storage(NVMe→GPU 一次拷贝)、P2P(GPU 间直接 Load/Store)。
2.2 线程层次:表达并行
| 层次 | 说明 |
|---|---|
| Thread | 最小执行单位(但不是最小调度单位) |
| Block(线程块) | 一组线程,可通过 shared memory 与 __syncthreads() 协作;最多 3 维 |
| Grid | 线程块的集合;由 CPU 启动、在 GPU 执行;最多 3 维 |
| Warp(硬件) | 实际调度单位:NVIDIA 32 线程 / AMD wavefront 64 |
线程通过内置变量定位:threadIdx、blockIdx、blockDim、gridDim。全局 id 常写作 i = threadIdx.x + blockIdx.x * blockDim.x。
__global__ void myKernel(int N, double *d_a) {
int i = threadIdx.x + blockIdx.x * blockDim.x;
if (i < N) d_a[i] *= 2.0;
}
// 启动:
dim3 threads(256,1,1);
dim3 blocks((N+255)/256,1,1);
myKernel<<<blocks,threads>>>(N, a);
2.3 SIMD / SPMD / SIMT 三者辨析
| 名词 | 是什么 | 要点 |
|---|---|---|
| SIMD | 一种体系结构 | 单条指令流的 SIMD 指令作用于多数据(如 AVX-512) |
| SPMD | 一种编程范式 | 同一程序跑在所有处理器上,用条件语句区分行为 |
| SIMT | 既是编程模型也是执行模型 | 可独立看待每个线程,也可把线程灵活分组成 warp 以获得 SIMD 收益 |
2.4 占用率(Occupancy):三大资源限制
每 SM 的活跃线程数受以下三者最小值限制:
例:16384 寄存器/SM,每线程 35 个 → 至多 468 线程;块大小 512 不可行。
每 SM 仅几十~一百多 KB,限制每 SM 可驻留的块数。
硬件每块最大线程数、每 SM 最大线程/warp/块数。
2.5 控制分歧、谓词执行
warp 内线程锁步执行。若走不同分支(divergence),硬件用掩码屏蔽无效路径,各路径被串行执行。短分支可用谓词执行(predication):执行后丢弃无效结果,避免分支跳转。建议把分支控制在 warp 粒度(整 warp 走同一路径则不分歧)。
2.6 访存优化:合并访问与 bank conflict
- 合并访存(coalescing):总线按对齐宽度(如 32 字节)访问;同一 warp 的线程若访问落入同一段,多次访问被合并成更少请求,提升带宽利用率。
- 线程映射(thread mapping):行主序数据下,应让 warp 内连续线程映射到列(第二维),实现合并访问;矩阵乘「映射 2」远优于「映射 1」即源于此。
- 矩阵转置:无论怎么映射,读/写总有一个不合并;用 shared memory 缓冲并重排线程映射,可让读写都合并(工作组需为方形)。
- Bank conflict:shared memory 分 bank,多个线程命中同一 bank 的不同地址 → 串行化;命中不同 bank(无冲突)或全部命中同一地址(广播)→ 无延迟。
L03 · CUDA 实例:归约与分块卷积
来源:new-pdc03-cuda-examples.pdf
3.1 并行归约(reduction)
从 atomicAdd 到树形归约
| 方案 | 问题/特点 |
|---|---|
| atomicAdd 基线 | 所有线程对同一全局地址 atomicAdd → 串行化,吞吐 ≈ 1 加法/~600 周期,$O(N)$ 串行深度,浪费并行 |
| 树形归约 | 每个 block 在 shared memory 中归约($\log_2 B$ 轮),线程 0 写一个部分和;最后 CPU 或再启 kernel 做最终归约。深度 $O(\log N)$,工作量 $O(N)$ |
// 关键机制
extern __shared__ int sdata[]; // block 内共享
sdata[tid] = (i < n) ? g_idata[i] : 0; // 越界填 0(加法单位元)
__syncthreads(); // 每轮共享内存写后必须同步
for (int s = blockDim.x/2; s > 0; s >>= 1) {
if (tid < s) sdata[tid] += sdata[tid + s]; // 连续寻址
__syncthreads();
}
if (tid == 0) g_odata[blockIdx.x] = sdata[0];
两个关键优化
交错
idx = 2*s*tid:s 增大时多个线程命中同一 bank → 高达 16 路 bank conflict。连续
if(tid<s) sdata[tid]+=sdata[tid+s]:活跃线程映射到不同 bank,无冲突。
版本 A
tid % (2*s)==0:warp 内一半活跃一半空闲 → 分歧,串行执行两分支。版本 B
tid < s:整 warp 要么全活跃要么全空闲 → 空闲 warp 不被调度,无分歧。
为什么不在设备端做全局同步?
① 硬件成本过高(80+ SM、每 SM 多常驻块);
② 死锁风险:若启动块数超过 GPU 并发常驻能力,运行块等待被挂起块会死锁。
解决:用 kernel 结束作为天然全局同步点 → 多级归约(Level 0 大量块各归约一部分,Level 1 少量块再归约)。
3.2 分块卷积(tiled convolution)
- 瓶颈:内存带宽 → 用分块算法 + shared memory 复用。
- 输入 tile vs 输出 tile:输入 tile 维度 = 输出 tile 维度 + $2\times$ filter radius(边界需邻接 tile 的 halo 元素;越界 ghost 元素填 0)。
- 滤波器放在 constant memory;输入 tile 放 shared memory,多个线程复用。
- 尺寸不匹配的处理:启动足够线程加载整个输入 tile,再用子集线程计算并写出输出(会有 if 与控制分歧)。
算术访存比($M\times M$ 滤波器):
无分块:$\dfrac{2M^2\,\text{OP}}{4M^2\,\text{B}} = 0.5\ \text{OP/B}$
分块(输入 tile $T$,输出 $T-M+1$):$\dfrac{(T-M+1)^2\cdot 2M^2}{T^2\cdot 4} = 0.5M^2\left(1-\dfrac{M-1}{T}\right)^2$
$M=5,\ T=64$ 时 ≈ 10.98 OP/B,约 22× 提升。
tid % (2*stride)==0)基础上,写出使用 shared memory 的优化版本,并说明减少了哪些开销、仍存在哪些问题。优化版本核心
__global__ void reduceOptimized(float* input, float* output, int N) {
extern __shared__ float sdata[];
int tid = threadIdx.x;
int i = blockIdx.x * blockDim.x + threadIdx.x;
sdata[tid] = (i < N) ? input[i] : 0.0f; // 1) 先搬到 shared
__syncthreads();
for (int s = blockDim.x/2; s > 0; s >>= 1) { // 2) 连续寻址 + warp 感知
if (tid < s) sdata[tid] += sdata[tid + s];
__syncthreads();
}
if (tid == 0) output[blockIdx.x] = sdata[0];
}
减少了哪些开销 / 为什么更快
- 归约循环全程在片上 shared memory,避免反复读写慢速 global memory(带宽/延迟大幅下降)。
- 连续寻址消除 bank conflict;
tid<s让活跃线程连续,避免 warp 内分歧。 - 不再对全局数据反复读改写(naive 版直接改 input 且带分歧)。
仍存在的两个性能问题
- 每个 block 只算出一个部分和,仍需第二级归约(再启 kernel 或 CPU 求和)。
- 当 $s\le 32$ 进入单个 warp 时,循环仍每轮
__syncthreads(),可用 last-warp unrolling(volatile)省去同步;以及每线程只处理一个元素时加载阶段算术强度低,可用「每线程处理多个元素(grid-stride)」提高利用率。
说明:代码与结论依据课件 reduction 章节,具体实现细节为 [自注] 补充。
L04 · Attention 与 FlashAttention
来源:new-pdc04-attention.pdf
4.1 自注意力与 Q/K/V
把自注意力看作信息检索:每个 token 生成三个向量——Query「我在找什么」、Key「我包含什么」、Value「我提供的内容」。
$$S = \dfrac{QK^T}{\sqrt{d}},\quad P = \text{softmax}(S),\quad O = P\,V$$
$Q,K,V$ 均为 $n\times d$;$S,P$ 为 $n\times n$;输出 $O$ 为 $n\times d$。
4.2 复杂度与「内存墙」
痛点:显存随序列长度 平方增长($N$ 翻倍 → 显存 4 倍),长上下文易 OOM;且标准实现把 $S$ 写回 HBM 又立刻读回做 softmax,把时间花在搬数据而非计算。
4.3 GPU 存储层次(A100 例)
| 层级 | 容量 | 带宽 |
|---|---|---|
| HBM2e(全局) | 40/80 GB | 1.5–2 TB/s |
| L2 Cache | 40 MB | ~5 TB/s |
| L1/Shared(每 SM) | 192 KB | 19 TB/s |
SRAM 比 HBM 快约 10×,但容量很小 → 不能整张 $N\times N$ 矩阵放进去。
4.4 FlashAttention:两大支柱
Tiling(分块)
把 Q(按行)、K/V(按列)切块,双重循环逐块加载到 SRAM,点积、online softmax、加权和全在片上完成,不存 $N\times N$ 矩阵。只把最终 $O$($N\times d$)写回 HBM。
Recomputation(重算)
前向不存 S、P,反向时重新计算,以省显存。配合 kernel fusion,把整个逻辑融成一个 kernel。
4.5 Online Softmax
分块的困境:softmax 需要全局最大值(数值稳定)与全局求和(归一化),但处理第一块时看不到后续块。
Online Softmax:边遍历分块边维护「运行最大值」与「运行和」,当新块带来更大的 max 时,对已累加的结果按比例缩放校正,从而只看部分数据也能得到正确 softmax。
(1) 访存量数量级
标准 Attention 必须把 $N\times N$ 的 $S$、$P$ 写出再读回,主导项为 $O(N^2)$ 个元素(即 $\sim N^2\times 2$ 字节量级);FlashAttention 不落地 $N\times N$,只反复读写 $Q,K,V,O$(各 $N\times d$),主导项为 $O(Nd)$。
(2) N → 4N 的增长倍数
- 标准 Attention:主导项 $\propto N^2$ → 增大 16×。
- FlashAttention:主导项 $\propto N$($d$ 不变)→ 增大 4×。
说明:课件给出「$O(N^2)\to O(N)$」的结论;上面用数量级表达,精确常数依赖分块细节,为 [自注]。
L05 · 卷积、非线性与算子融合
来源:new-pdc05-conv-nonlinear-fusion.pdf
5.1 Roofline 与算子分类
Roofline 用「算力 (FLOPS)」与「内存带宽」两条天花板,结合算子的算术强度判断瓶颈。课件强调:算力增速远超带宽增速 → 「内存墙」使计算核常等数据。
| 类型 | 特征 | 例子 |
|---|---|---|
| Compute-bound | 计算量 ≫ 访存 | 大矩阵乘、多通道卷积 |
| Memory-bound | 访存 ≫ 计算 | ReLU、Add、BatchNorm、Dropout 等 element-wise |
5.2 卷积三种实现
| 特征 | 直接卷积 | Img2Col + GEMM | Winograd F(2×2,3×3) |
|---|---|---|---|
| 计算复杂度 | 高 | 中(GEMM 优化) | 低(乘法最少) |
| 内存开销 | 极小 | 大(数据冗余) | 中(需变换矩阵) |
| 实现难度 | 简单 | 中等 | 复杂 |
| 数值精度 | 最高 | 高 | 较低(浮点误差累积) |
| 核心限制 | 慢 | 高内存消耗 | 限 stride=1 & 3×3 |
- Img2Col:把空间卷积转成 GEMM → 调用 cuBLAS/MKL 等高度优化库;代价是数据冗余。
- Winograd $F(2,3)$:常规 6 次乘法 → 4 次乘法 + 11 加 + 2 移位。tile 越大乘法省得越多,但变换复杂度与数值误差也越大($F(4×4,3×3)$ 理论省 6×,但变换会吃掉收益)。
5.3 Element-wise vs Reduction
| 特征 | Element-wise | Reduction(树形) |
|---|---|---|
| 计算模式 | $N\to N$ | $N\to 1$ |
| 线程通信 | 完全独立 | 需 shuffle / shared memory 交换 |
| 典型算子 | ReLU、Vector Add、Dropout | LayerNorm、Softmax、点积 |
原子操作的代价:atomicAdd 虽正确,但需锁定内存位置、read-modify-write 串行化,GPU 并行退化为单核性能,只适合稀疏访问。满足结合律的归约应用树形结构($O(N)$ 工作量、$O(\log N)$ 深度)。
5.4 算子融合(Operator Fusion)
课件反例:XLA 贪心合并 Add+Copy 因布局不一致导致 9× 性能下降;PyTorch Fused AdamW 在 A40 上寄存器近翻倍、kernel 启动过多反而变慢。要在「省带宽」与「保并行」间找甜点。
融合 Softmax 例:Load(协作读入 shared)→ Sync & Reduce(片上多轮,CUDA 用 __syncthreads(),Triton 自动)→ Write(仅最终结果写回)。把 Max、Sub、Exp、Sum 融成一个 kernel;连续寻址避免 bank conflict;Brent 定理:用 $O(N/\log N)$ 线程每线程串行处理 $O(\log N)$ 元素,总代价 $O(N)$。
L07 · Tensor Cores 的演进
来源:new-pdc07-tensor-cores.pdf
7.1 性能第一性原理
- Amdahl 定律:增加算力只能加速并行部分。
- 算术强度(roofline 分析)。
- 数据移动是「原罪」:计算便宜、搬数据昂贵 → 几乎所有 Tensor Core 优化都伴随访存改进。
7.2 MMA 与存储层次
MMA(multiply-accumulate)计算 $D = A\times B + C$,$A$ 为 $M\times K$、$B$ 为 $K\times N$、$C/D$ 为 $M\times N$,形状记作 $M\times N\times K$。数据在 SMEM ↔ RF ↔ Tensor Core 间流动。
7.3 演进总趋势
| 架构 | 关键进展 |
|---|---|
| Volta SM70(2017) | Tensor Core 起源(受 Google TPU 启发);warp 级 HMMA;传统 SIMT 做 MMA 时指令开销远超运算本身 |
| Turing SM75(2018) | 支持 INT8/INT4/Binary 低精度;ldmatrix 访存 |
| Ampere SM80(2020, A100) | 异步拷贝 cp.async:GMEM 直接进 SMEM,省寄存器;支持稀疏 MMA |
| Hopper SM90(2022) | TMA(单线程发起大块异步拷贝,自动算地址);wgmma(warpgroup 级);Thread Block Cluster + 分布式共享内存 DSMEM;TMA Multicast;异步事务屏障 |
| Blackwell(2024) | 专用 Tensor Memory(TMEM,256KB/SM);第五代 tcgen05.mma(操作数移出寄存器、单线程发起);MMA.2SM(两 SM 协作);原生细粒度量化(MX 格式) |
7.4 TMA(Hopper)要点
- 专用硬件,加速 GMEM↔SMEM 的大块异步拷贝(
cp.async.bulk)。 - CTA 内单线程即可发起,自动处理 stride/offset/边界,释放线程做别的事。
- 对小请求反而延迟高(地址生成开销)→ 适合大块拷贝以摊薄开销(如 LLM 推理中按 16B 倍数分块)。
mma_sync 完成 16×16×16 次 FMA。比较 FMA 总数与指令条数,分析 Tensor Core 的指令层面优势,并解释为何能在更少时钟周期完成。FMA 总数(两者相同)
指令条数
- SIMT:每条指令 1 FMA → 需 $2^{24}\approx 1.68\times10^7$ 条指令。
- Tensor Core:每条
mma_sync完成 $16\times16\times16=4096$ FMA → 需 $2^{24}/4096 = 4096$ 条指令。
为何更少时钟周期?
Tensor Core 是专用矩阵乘加硬件:单条指令驱动一整个 $16^3$ 的乘加阵列并行完成,避免了 SIMT 把同样运算拆成上千万条标量指令、每条都要取指/译码/发射的巨大开销(课件 Volta 动机:传统 SIMT 的 MMA 指令开销远超运算本身)。
L08 · AI 编译器
来源:new-pdc08-ai-compilers.pdf
8.1 通用编译流程
总目标:最小化内存占用、提升执行效率、可扩展到多异构节点。
8.2 计算图:静态 vs 动态
| 静态图(TensorFlow 1.x) | 动态图(PyTorch) | |
|---|---|---|
| 构建 | 先完整构建再计算 | 边算边构建(on-the-fly) |
| 优点 | 可离线整图优化、通常更快 | 可变长输入灵活、易调试 |
| 缺点 | 处理可变长数据不便 | 留给图优化的时间有限 |
计算图:节点=张量,边=函数;用于表示反向传播的链式法则,便于自动优化与 lowering。
8.3 torch.compile 三大组件
| 组件 | 职责 |
|---|---|
| TorchDynamo | 重写 Python 字节码,抽取计算图,首次运行输出前向静态图 |
| AOTAutograd | 提前(ahead-of-time)追踪反向图,与前向图同时 |
| TorchInductor | 优化图并生成硬件代码,做算子融合等 |
.numpy()、print 等 Python 副作用),会:①提交已追踪的图编译;②回退普通解释器执行该段;③之后再开新图。结果是函数被切成多个子图,子图衔接有调度开销、可能错过跨图优化。纯 tensor 操作(如
x*2、x.reshape(-1))不会 graph break;x.numpy()、if x.sum()>0(依赖值的控制流)、print(x) 会。
8.4 IR 层次与 MLIR / TVM / Triton
- MLIR:可复用的多层 IR/Pass,多 dialect 共存(tensor/arith/math/linalg…),逐层 lowering + pattern rewrite;可在合适层次做优化(如 linalg 检测矩阵两次转置,affine 做 loop tiling)。
- TVM:Relay(函数式、静态类型 IR)→ TE → TIR;AutoTVM(模板搜索)→ Auto-scheduler/Ansor(无需手写模板)。
- Triton:以 block/tile 为粒度、类 NumPy 风格写 GPU 代码,自动处理线程/warp、向量化、shared memory 分配;autotuner 只调 tile 大小,比 TVM 简单。编译流程:Triton IR → TritonGPU IR → LLVM IR → PTX。
- TileLang:三层接口(硬件无关/带 tile 库/带线程原语),tile 作为一等对象。
8.5 图优化分层
| 层次 | 优化 |
|---|---|
| 节点级 | 常量折叠、强度削减($x/2\to x\times0.5$)、no-op 消除、零维张量消除 |
| 块级 | 代数化简、算子下沉、算子融合 |
| 数据流级 | 公共子表达式消除(CSE)、死代码消除(DCE)、布局变换 |
| 硬件相关 | kernel auto-tuning、内存规划(复用/对齐/池化) |
算子调度:Tiling(分块利用局部性)、Reorder(重排、利用结合律 $A@(B@v) < (A@B)@v$)、Split。
矩阵乘 $P_{a\times b}\cdot Q_{b\times c}$ 的乘加次数约 $a\cdot b\cdot c$。
顺序一:$(A@B)@v$
顺序二:$A@(B@v)$
顺序二约为顺序一的一半。结论:先做能快速「降维」的乘法(这里先把 $B@v$ 压成向量)更省 FLOPs,这正是 Reorder/结合律优化的价值。
L09 · 训练/推理中的并行:DP / TP / PP / 混合
来源:new-pdc09a~d(data / tensor / pipeline / hybrid parallelism)
9.1 三条正交的并行轴
| 并行 | 切分对象 | 通信原语 | 物理映射 |
|---|---|---|---|
| 数据并行 DP | 全局 batch(复制模型) | (Sparse)All-Reduce | 全局集群 |
| 张量并行 TP | 层内运算(hidden 维) | Dense All-Reduce | 节点内(NVLink) |
| 流水线并行 PP | 层间(深度) | 点对点 Send/Recv | 节点间(InfiniBand) |
9.2 训练显存从哪来
完整显存 = 模型参数(FP16) + 梯度(FP16) + 主权重(FP32) + Adam 动量(FP32) + Adam 方差(FP32) + 激活(随 batch 增长)。
9.3 数据并行与 ZeRO
- DDP:每 GPU 完整模型副本,处理不重叠 mini-batch,反向后 All-Reduce 同步梯度。梯度分桶减少网络调用、与反向计算重叠。
- ZeRO-1:分片优化器状态;ZeRO-2:再分片梯度;ZeRO-3(FSDP):连参数一起分片,按需 all-gather 取参、用完即弃。
| 通信原语 | 通信量 | 显存 | |
|---|---|---|---|
| 朴素 DDP | 1 次 All-Reduce | 2×#params | $(4+K)\times$#params |
| ZeRO-1 | Reduce-Scatter + All-Gather | 2×#params | $(4+K/N)\times$#params |
| ZeRO-3/FSDP | All-Gather×2 + Reduce-Scatter | 3×#params | 全部按 $N$ 分片 |
K 为每参数优化器状态字节数(FP32 动量典型 12B)。
9.4 张量并行(Megatron)
LLM 参数大多在线性投影(Attention 的 Q/K/V/O,MLP 的上/下投影),即 GEMM $Y=XW$。MLP 块切法:
- 第一个矩阵 $A$ 按列切 → 各列输出可独立过 GeLU(无需同步)。
- 第二个矩阵 $B$ 按行切 → 块乘积无需中途同步。
- 前向 $f$ 为恒等、$g$ 为 all-reduce;反向相反。
9.5 流水线并行
| 调度 | 峰值激活 | 要点 |
|---|---|---|
| GPipe(F-then-B) | $O(M)$ | 所有 M 个前向完成才开始反向,须缓存 M 份激活,易 OOM |
| 1F1B(PipeDream) | $O(K)$ | 预热后交替「一前一后」,激活用完即弃,峰值由流水级数 K 界定 |
| Interleaved 1F1B | 更小气泡 | 每 GPU 分多个虚拟 stage(2-4),不增大 M 也能减小气泡 |
| Zero-bubble / DualPipe | ≈0 气泡 | 分离阻塞/非阻塞计算、ILP/启发式调度 |
为什么用 PP:省显存(vs DDP),通信只依赖激活、点对点(vs FSDP/TP),适合慢速链路(跨节点)。局限:降气泡需大 M,但全局 batch 受收敛限制,M 大则 micro-batch 太小、浪费 Tensor Core。
9.6 混合并行(3D / 5D)与自动并行
- 3D:内层 TP(节点内 NVLink)× 中层 PP(跨节点 IB)× 外层 DP(全局)。案例 MT-NLG 530B:8-way TP × 35-way PP × 16-way DP = 4480 A100。
- 5D:新增 CP(上下文并行,ring 交换 KV 防长序列 OOM)与 EP(专家并行,All-to-All 路由 MoE)。TotalGPUs = TP×CP×EP×PP×DP。
- 自动并行:FlexFlow(SOAP:Sample=DP/Operation=PP/Attribute=CP/Parameter=TP + MCMC)、Alpa(层级搜索:intra-op ILP + inter-op DP)、GSPMD、Unity、Galvatron。
(1) Baseline DDP($1\text{B}=10^9$,1GB≈$10^9$B 口径)
- 模型参数 FP16:$2\Psi = 14\text{GB}$
- 梯度 FP16:$2\Psi = 14\text{GB}$
- 优化器状态:$K\Psi = 12\times7 = 84\text{GB}$
- 合计:$(2+2+12)\times7 = 112\text{GB}$
(2) ZeRO 各阶段(仅对应分片项 ÷ $N_d=64$)
| 策略 | 公式 | 结果 |
|---|---|---|
| Baseline | $(2+2+K)\Psi$ | 112 GB |
| ZeRO-1 | $2\Psi+2\Psi+\frac{K\Psi}{N_d}$ | $14+14+84/64 \approx 29.3\text{GB}$ |
| ZeRO-2 | $2\Psi+\frac{(2+K)\Psi}{N_d}$ | $14+(2+12)\times7/64 \approx 15.5\text{GB}$ |
| ZeRO-3 | $\frac{(2+2+K)\Psi}{N_d}$ | $112/64 \approx 1.75\text{GB}$ |
(3) ZeRO-1 通信量
Baseline DDP 每步 All-Reduce 通信 $2\times2\Psi$ 字节。ZeRO-1 改为 Reduce-Scatter + All-Gather,总通信量与 Baseline 相同(同为 2× #params 量级)——显存大幅下降而通信量不变,所以课件说「ZeRO-1 是免费的显存收益」。
说明:GB 换算按 $10^9$ 口径,与课件一致取近似;激活显存另算。
L10 · 通信原语、MPI 与集合通信库
来源:new-pdc10a~c(communication primitives / MPI & CCL)
10.1 通信数据通路
- Scale-up(节点内):NVLink、HCCS、Unified Bus。
- Scale-out(跨节点):InfiniBand (IB)、RoCE(RDMA NIC)。
- 集合通信涉及一组进程(如 AllReduce、AllGather、Broadcast);点对点:Send/Recv(双边)、Put/Get(单边)。
10.2 七大集合通信原语
| 原语 | 语义 |
|---|---|
| Broadcast | root 的 N 元素缓冲复制到所有 rank |
| Reduce | 跨设备归约(sum/min/max)结果存到 root |
| Gather | 从 k 个 rank 各收 N 值,拼成 k·N 存到 root |
| Scatter | root 把 N·k 值分发,每 rank 得 N 值 |
| AllReduce | 归约结果分发到每个 rank |
| AllGather | k 个 rank 各 N 值拼成 k·N 并分发给所有 rank(按 rank 序) |
| ReduceScatter | 归约后按 rank 切块分散,每 rank 得一块 |
| AllToAll | 每 rank 把第 j 块发给 rank j,第 i 块来自 rank i(用于 FFT、转置、MoE 路由) |
10.3 α-β 通信模型与 Broadcast
两节点间传输 $n$ 字节耗时:$T = \alpha + n\beta$($\alpha$ 启动延迟,$\beta$ 每字节耗时 = 带宽倒数)。
Broadcast 两个下界:α 项 $\ge \lceil\log_2 p\rceil\alpha$(每轮知道消息的节点最多翻倍);β 项 $\ge n\beta$。
MST(最小生成树)算法:$\lceil\log_2 p\rceil(\alpha+n\beta)$,对 α 项达下界,β 项差一个对数因子(大消息可用流水线改进)。
10.4 Ring All-Reduce 与双二叉树
双二叉树 All-Reduce(NCCL):没有 rank 在两棵树上都是内部节点 → 全带宽 + 对数延迟,适合中小消息。
10.5 MPI 基础
| 最小例程 | 作用 |
|---|---|
| MPI_Init / MPI_Finalize | 初始化 / 结束 MPI |
| MPI_Comm_size | 进程总数 |
| MPI_Comm_rank | 本进程编号(rank) |
| MPI_Send / MPI_Recv | 点对点发送 / 接收 |
编译:mpicc -o foo foo.c(mpicc 只是 wrapper);运行:mpirun -np 8 ./foo。SPMD:-np 8 ./hello;MPMD:-np 1 ./master : -np 7 ./slave。MPI 名称:MPI_Bcast / MPI_Reduce / MPI_Allgather / MPI_Reduce_scatter / MPI_Allreduce / MPI_Alltoall。
(1) 发送量
每阶段都是 $N-1$ 轮、每轮发送 $M/N$ 字节:
- Reduce-Scatter:每节点发送 $(N-1)\cdot M/N$ 字节
- All-Gather:同样 $(N-1)\cdot M/N$ 字节
- 全程:$2(N-1)\cdot M/N$ 字节
(2) α≈0 时总耗时
(3) 代入数值
作业精解索引
各次作业的代表性大题已分散在对应讲次,点击展开解答。下面是速查表(题面来自作业 PDF,解答中标 [自注] 者为编者补充推导)。
| 作业 | 主题 | 代表题 | 位置 |
|---|---|---|---|
| 作业一 | Amdahl/Gustafson + CUDA 归约 | shared memory 归约改写 | 见 L03 |
| 作业二 | FlashAttention / occupancy / Tensor Core | HBM 访存量、WMMA 指令效率 | 见 L04、L07 |
| 作业三 | torch.compile / Triton / Inductor | 矩阵链乘 FLOPs、graph break | 见 L08 |
| 作业四 | 3D 并行与 ZeRO 显存 | ZeRO 显存计算、3D 通信组 | 见 L09 |
| 作业五 | 集合通信库 (CCL) | Ring All-Reduce 通信分析 | 见 L10 |
关键看「每 SM 最大常驻块数」「每 SM 最大常驻线程数」两个硬件上限(随计算能力不同)。8×128 = 1024 线程。
- (a) CC 1.0:每 SM 最多 8 块、但最大常驻线程仅 768 → 1024 > 768,不可行(线程上限是瓶颈)。
- (b) CC 1.2:最大常驻线程 1024、最多 8 块 → 8 块×128=1024 恰好可行,占用率 1024/1024 = 100%。
- (c) CC 3.0:最大常驻线程 2048、最多 16 块 → $1024 \le 2048$ 可行,占用率 1024/2048 = 50%。
说明:各计算能力的具体上限为课程/CUDA 文档常用数值,属 [自注];考试以课件/老师给定参数为准。
- TP 通信组:TP=2 → 2 块 GPU(同一节点内、走 NVLink,做 Dense All-Reduce)。
- DP 通信组:DP=4 → 4 块 GPU(同一 TP/PP 位置、不同数据副本,做 All-Reduce 同步梯度)。
- PP 直接通信:与该 GPU 处于相邻 stage 的上游一块、下游一块(点对点 Send/Recv 传激活/梯度)。
为何 TP 限节点内:TP 需高频 Dense All-Reduce,跨节点 IB 延迟会把 GPU 利用率打垮;PP/DP 通信稀疏或点对点,更能容忍跨节点延迟。若 TP 跨节点 → 每次层内运算都被网络延迟阻塞,严重降速。
互动自测
📝 自测题库(覆盖 L01–L10)
考前速查
核心公式
Amdahl:$S=\dfrac{1}{(1-p)+p/n}$,上限 $\dfrac{1}{1-p}$
Gustafson:$S=1+p(n-1)$
Attention 复杂度:$O(n^2 d)$;FlashAttention HBM:$O(N^2)\to O(N)$
卷积算术访存比(分块):$0.5M^2(1-(M-1)/T)^2$
α-β 模型:$T=\alpha+n\beta$;MST 广播 $\lceil\log_2 p\rceil(\alpha+n\beta)$
Ring All-Reduce 每节点发送:$2(p-1)\,n/p \xrightarrow{p\to\infty} 2n$
All-Reduce = Reduce-Scatter + All-Gather
易混对照
架构 / 范式 / 既编程又执行模型
前三是编程层次,warp 是硬件调度单位(NV=32)
前者针对 global,后者针对 shared
GEMM/卷积 vs ReLU/Add/Norm
分片:优化器 / +梯度 / +参数(FSDP)
激活 O(M) vs O(K)
TP 节点内 NVLink / PP 跨节点 IB
每节点一块 vs 每节点完整结果
转 GEMM(冗余) vs 减乘法(误差)
记忆口诀 编者补充
算子省带宽「三连」:分块上 SRAM、算子融合、避免 atomic 串行化。
分布式「显存换通信」:DP 省不了显存 → ZeRO 分片(1/2 免费、3 多 1× 通信)→ TP 切 GEMM(节点内)→ PP 切层(跨节点)。
通信「黄金等式」:All-Reduce = Reduce-Scatter + All-Gather;Ring 带宽最优、延迟随 p;树形延迟对数。