先给判断:BigMac 多卡训练不建议从流水并行直接起步。推进顺序建议是「单卡单进程 → 单机数据并行 → 两卡流水 → 形状对齐 → 扩到目标卡数」,每一步都设一个能停下来的判定点,满足该步标准再走下一步。多卡阶段暴露的问题,多数不是新问题,而是单卡阶段被掩盖的配置问题在分布式下被放大:随机种子、batch 口径、日志字段、张量形状。把基线固定住,后面每次失败才能判断是退回上一步,还是就地修。
适用场景:单卡能跑但没留可对照基线,想直接上多卡流水并行。操作动作:先固定随机种子、batch 口径与步数跑一次单卡并留日志,再在同一份配置上加数据并行启动,确认进程组建成,然后只用两张卡做最小切分验证,最后扩到目标卡数复跑相同步数。验证方式:每步用日志字段和退出码判断,不靠“进程还在、看着在跑”。风险边界:卡数、显存与模型层结构不同,判定阈值需结合自己的环境确认,这里给的是判断顺序和停点,而非性能承诺。
单卡单进程跑通一次前向反向并保存日志
这一步的目的只有一个:拿到一份可对照的基线。配置要写死随机种子、micro-batch、梯度累积步数和最大步数,不要用默认值。下面的启动骨架只示意参数位置,BigMac 的实际入口模块和参数名请按仓库里的启动脚本替换。
python -m bigmac.train \
`--config` configs/base_single.yaml \
`--seed` 1234 \
`--max-steps` 20 \
`--log-interval` 1 \
`--log-file` logs/base_single.log
日志里建议至少保留这些字段,缺一个后面就少一个对照点:global_step、loss、grad_norm、learning_rate、当前 micro-batch 与梯度累积步数、换算后的真实样本数 per step、输入的 shape 与 dtype、运行设备、参数总量。训练结束把退出码也记下来,并保留一份 checkpoint 或参数摘要,扩卡后用它判断“改动是否只影响并行方式”。
- 基线判定标准:20 步内 loss 为有限值,不出现 NaN 或 inf;
- grad_norm 非零、量级稳定,不出现逐级放大;
- global_step 从 0 连续递增,无跳号、无重复打印;
- 真实样本数 = micro-batch × 梯度累积 × 进程数,这个数在单卡阶段先算好写进日志,后面扩卡要沿用同一口径。
任一条不满足就停在这里修,不要进多卡。
单机多卡改成数据并行,确认进程组能建起来
这一步不要动模型结构,只把并行方式从单进程换成多进程数据并行,目的是把多卡环境问题单独暴露出来。启动方式通常用 torchrun 拉起,进程数等于本机可见卡数:
torchrun `--nnodes` 1 `--nproc`_per_node 4 `--master`_addr 127.0.0.1 \
`--master`_port 29500 -m bigmac.train \
`--config` configs/base_dp.yaml `--seed` 1234 `--max-steps` 20 \
`--log-file` logs/dp_4gpu.log
需要确认的环境变量包括 MASTER_ADDR、MASTER_PORT、WORLD_SIZE、RANK、LOCAL_RANK,以及按 LOCAL_RANK 设置 CUDA_VISIBLE_DEVICES 的逻辑。排查阶段可以先设 NCCL_DEBUG=INFO,看它选了什么通信设备和网卡;如果仓库入口自己解析环境变量,就别手动覆盖 RANK。
建组失败时有几种典型输出,按特征反查比反复重跑快:卡在 init_process_group 直到超时,通常是各 rank 的 master 地址或端口不一致;报端口被占用,换 master_port;报连接被拒绝,一般是 rank0 还没起来其他 rank 先连;世界大小与实际进程数不一致时表现为长时间 hang 而不是报错;NCCL 日志显示找不到可用设备或多个 rank 落到同一张卡,检查 local rank 与设备绑定的映射。数据并行这一步只要进程组建成、20 步跑完、loss 与单卡基线同量级,就可以往下走。
把模型按层切段并只在两张卡上试流水
先用两张卡、两段切分验证切点正确,不要一开始就按目标卡数切。切分配置通常按层或模块前缀描述,下面是一个可替换的配置骨架:
pipeline:
stages: 2
split_points: ["layers.12"] # 在第 12 层之后切开
micro_batches: 4
schedule: 1f1b
两卡启动只需把进程数改成 2,其余配置保持一致:
torchrun `--nnodes` 1 `--nproc`_per_node 2 `--master`_port 29500 \
-m bigmac.train `--config` configs/pp_2stage.yaml \
`--seed` 1234 `--max-steps` 20 `--log-file` logs/pp_2stage.log
期望的日志表现是:每个 rank 打印自己的 stage 编号和负责的层区间;各段参数量相加与单卡参数总量接近(共享权重的 embedding 类参数可能被重复计数,需要单独看);micro-batch 编号按调度顺序推进,不出现某段一直等不到输入;20 步正常结束。如果日志里某个 stage 完全没有前向记录,多数是切点写错,把某一层落到了两段之外。
对齐各段输入输出形状,检查跨段张量传递
流水并行最常见的失败不是通信,而是段间张量形状不匹配,而且往往在切点处的日志里看不出原因。建议在三个位置加形状打印:每段 forward 的入口和出口各打印一次 hidden_states 的 shape、dtype、device;切点边界单独打印一次;最后一段在算 loss 前打印 labels 的 shape。
常见报错文本和处理方向:size mismatch for 某个参数,通常是切分后某段缺少或多出了对应的权重,检查切点是否把一个模块拆到了两段;Expected all tensors to be on the same device,检查跨段传递时张量是否被搬到 CPU 或某段自行指定的设备;mat1 and mat2 shapes cannot be multiplied,多半是序列长度或隐藏维度在段间被拼接方式改变;shape 无效、元素数与预期不符,先看 micro-batch 数量除不尽时的补尾逻辑,以及变长序列是否被 pad 到固定长度。
修正时按顺序处理:先把序列长度统一到固定值,再把共享嵌入与输出头放到同一段,最后检查中间段是否误算了 loss(中间段一般只需要 hidden_states,不需要 labels)。每改一处就回到两卡配置跑一次,不要同时改多项。
扩到目标卡数并固定步数复跑
扩卡时真正需要动的配置项不多,但每一项都会影响对照结果:进程数(nproc_per_node 或等价的卡数参数)、stage 数、每段 micro-batch 数与梯度累积步数、以及用于保持真实样本数不变的换算。如果采用多段流水加数据并行的组合,要把“每段副本数 × 每副本 micro-batch × 累积步数”重新算一遍并写进日志。另外建议确认 checkpoint 只由一个 rank 写、端口不与上一次残留进程冲突。
torchrun `--nnodes` 1 `--nproc`_per_node 8 `--master`_port 29511 \
-m bigmac.train `--config` configs/pp_target.yaml \
`--seed` 1234 `--max-steps` 20 `--log-file` logs/pp_target.log
复跑仍然用与基线相同的步数和种子。判定标准:global_step 总数与基线一致;loss 与 grad_norm 处于同一量级,不要求逐位一致,分布式归约顺序不同会带来小差异;全程无 NaN、无长时间空转的 stage;真实样本数与基线口径对齐。若不满足,按现象退回对应步骤:loss 从第一步就异常,回到形状对齐;loss 前几步正常后发散,先查学习率是否随进程数被缩放;进程组或 hang 问题,回到数据并行那一步单独验证。把这五步的日志都留在仓库里,下次换卡数或换模型结构,直接按这份顺序重跑一遍即可。