8 卡跑完 13B 训练:数据并行、张量并行与流水线并行的实测对比
触发点很具体。8 卡机器上跑 13B,最直觉的做法是套一层数据并行,脚本起来不到两分钟就炸,报的是 CUDA out of memory,Tried to allocate 2.00 GiB,而此前已经分配到 79.8 GiB。第一反应是减批大小,从 16 减到 8 再减到 1,还是炸。这才反应过来占显存的不是激活,是优化器状态。于是干脆把三条并行路线都跑一遍,条件固定,只换切分方式。
一、实验设置
下面的所有数字都在这套条件下取的,换掉任何一项就不具备可比性。
- 硬件是 8 张 A100 80GB,单机 NVLink 全互联,不跨机
- 模型 13B,bf16 前向反向,AdamW,权重衰减 0.1
- 序列长度 2048,全局批大小固定 64,梯度累积只在流水线方案里用
- 每套方案跑 300 步,取第 100 到 300 步的平均步时
- 显存峰值取 torch.cuda.max_memory_allocated 的最大值,通信占比取 nsys 里 NCCL kernel 累计时间除以步时
全局批大小固定这一点很关键。市面上很多并行方案的对比数字互相不可比,一方偷偷把批大小放大四倍换吞吐,最后比出来的差异跟切分方式没关系。换掉显卡型号或者互联方式,绝对数字会变,但判断顺序不变。
二、纯数据并行配 ZeRO-1:显存账最直观
先把账算清楚。13B 参数在 bf16 下权重占 26GB,AdamW 的 fp32 主权重 52GB,一阶、二阶动量各 52GB,合起来 182GB,还没算激活和临时缓冲。8 张卡各存一份完整状态,等于每张卡都要扛 182GB,80GB 的卡必然炸,这就是那句 tried to allocate 2 GiB 的真正来源。
ZeRO-1 只切优化器状态,每卡 182 除以 8 约 23GB,再加上完整的 26GB 权重,实测显存峰值 71GB,能跑,但余量只剩 9GB,序列一拉到 4096 就重新 OOM。
- 单卡显存峰值 71GB,通信占比 8.3%
- 全卡吞吐 15200 tokens/s
- 每步一次梯度 all-reduce,单机 NVLink 下这部分开销几乎被计算盖住
- 步时波动小,300 步的标准差不超 4%,这是它最大的优点
数据并行的通信量只跟参数量有关,跟切几路无关,所以它永远是通信最省的那条路。前提只有一个,塞得下。
三、张量并行 8 路:显存立刻舒服,吞吐掉得也干脆
张量并行把每层的权重矩阵按列或按行切开,8 路之后每卡只留 26/8 约 3.3GB 权重,加上切开的优化器状态,实测显存峰值 24GB,序列拉到 8192 也不紧张。
代价在通信次数。每层前向要做两次 all-reduce,反向再来两次,通信从每步一次变成每层四次,13B 大约 40 层,量级完全不同。
- 单卡显存峰值 24GB,通信占比 34%
- 全卡吞吐 8900 tokens/s,比纯数据并行低 41%
- 单步里 NCCL 累计约 410ms,计算部分只有 790ms
- 把 all-reduce 换成 reduce-scatter 加 all-gather 的组合,吞吐回到 9600,提升 8%
张量并行的切分粒度太细,通信次数与层数成正比。它在单机 NVLink 内还划得来,跨机这么切基本等于把时间全送给网络。
再补一组跨机的反例。把这套张量并行 8 路改成跨两台机器,每机 4 卡,25Gbps 网络,吞吐直接掉到 3100 tokens/s,通信占比冲到 71%。同一份代码只把切分从机内搬到机间,性能差了三倍,切法必须跟着拓扑走。
四、流水线并行 4 段乘数据并行 2 路:气泡率是可算的
流水线把模型按层切成 4 段,每组卡跑一段,组内再做 2 路数据并行。每卡权重 26/4 等于 6.5GB,优化器状态也跟着分段,实测显存峰值 38GB。
它的问题不在显存,在气泡。段数 p 为 4、micro-batch 数 m 为 8 时,气泡率是 (p-1)/(m+p-1),等于 27.3%,超过四分之一的时间有卡在空转。
- 单卡显存峰值 38GB,通信占比 12%
- 全卡吞吐 11700 tokens/s
- micro-batch 从 8 提到 16,气泡率降到 15.8%,吞吐升到 13100,显存峰值同时涨到 54GB
- 段数从 4 降到 2,气泡率降到 11.8%,但每卡权重回到 13GB,显存峰值 61GB
这三个参数互相咬。段数涨,显存降但气泡涨;micro-batch 涨,气泡降但显存涨。没有单点最优,只有一组勉强能接受的折中。
顺便提一句 ZeRO-3,它按层切参数,实测每卡 41GB、吞吐 14300 tokens/s。同样是把状态切开,它比张量并行 8 路高出 60% 的吞吐,原因就是通信次数少了一个量级。
五、选型边界
把三套方案的数字并排看,判断顺序其实很固定。先看显存放不放得下,再看通信代价,最后才谈吞吐。
- 单卡放得下整个训练状态,就走纯数据并行或 ZeRO-1,别做任何并行切分,切了纯亏通信
- 单卡放不下但模型能进单机,优先 ZeRO-3,不要第一反应上张量并行
- 必须跨机时,切法只能沿层走,也就是流水线加数据并行,张量并行最多切到单机内 8 路
- 70B 以上一般是张量并行 8 路乘流水线 4 段再乘数据并行,任一段单独放大都会撞上显存或者气泡
所有并行方案的本质都是拿通信时间换显存。先算清通信占比,再决定拿什么换,比照着别人的配置抄要靠谱得多。
小结
这次对比最有用的一条经验是,报错里那句 tried to allocate 2 GiB 会骗人。看着像激活不够,其实是优化器状态把底座占满了,这时候减批大小只会在原地打转。先把每张卡的显存账拆开算清楚,权重、主权重、动量、激活分开记,再用单卡放不放得下、要不要跨机这两个问题定位路线,选型的纠结基本就消失了。
- 点赞
- 收藏
- 关注作者
评论(0)