fix: fix grad accumulation/overlap under ZeRO-2 - #213
Conversation
| } | ||
|
|
||
| grad_reduce_dispatched_ = true; | ||
| // TODO(zbl): no need to clear params_with_grad_ here if grad sync is only done on last microbatch |
There was a problem hiding this comment.
我看注释里说如果只在 last microbatch sync 的话,这里就不需要 clear 了,但下面的 clear 没有删,确认下是否需要删除?
|
|
||
| using NoSyncFunc = std::function<std::vector<std::unique_ptr<nn::NoSyncGuard>>()>; | ||
|
|
||
| void SetNoSyncFunc(NoSyncFunc func) { no_sync_func_ = std::move(func); } |
| #include <vector> | ||
|
|
||
| #include "infini_train/include/datatype.h" | ||
| #include "infini_train/include/nn/modules/module.h" |
There was a problem hiding this comment.
前置声明 NoSyncGuard 就行,这里不需要 include module.h
|
|
||
| LOG(INFO) << "Rank " << rank.GlobalRank() << ": start backward"; | ||
| std::unique_ptr<nn::NoSyncGuard> no_sync_guard; | ||
| if (ddp_world_size > 1 && FLAGS_zero_stage >= 1 && micro_step != grad_accum_steps - 1) { |
There was a problem hiding this comment.
zero_stage=0 且开启梯度累积时,仍然会所有 microbatch 都进行梯度通信,但实际上也只需要最后一个 microbatch 发起通信,看看这个 pr 里方不方便一起改了?不好改的话就留个 FIXME 吧
There was a problem hiding this comment.
统一解决了一下,现在三条 DDP 的路径都完成了在梯度累积下的 last microbatch 再发起梯度同步:
- naive DDP(不带分桶):给 DDP class 里加了一个 atomic_bool 的 is_last_microbatch_ 成员,用来标记是否为 last mb;
DDP::SetIsLastMicrobatch()会更新它;然后修改AllReducePostAccumulateHook, 让其额外接受一个 bool 的参数,来决定是否要真的执行 allreduce。
AllReducePostAccumulateHook(
function::ReduceOpType reduce_op,
const ProcessGroup *pg = nullptr,
std::shared_ptr<const std::atomic_bool> enabled = nullptr);-
带分桶 DDP:给 Reducer class 里加了一个 bool 的 is_last_microbatch_ 成员,用来标记是否为 last mb;
DDP::SetIsLastMicrobatch()会更新它;BucketHook 入口Reducer::MarkVariableReadyDense处判断此 bool,来决定是否要继续执行标记 grad ready 开启通信的逻辑。 -
DistOpt-based ZeRO DDP(本 PR 原本核心改动):给 BucketGroup class 里面加了一个 bool 的 is_last_microbatch_ 成员,用来标记是否为 last mb;
DDP::SetIsLastMicrobatch()会更新它;准备更新梯度的时候,在ParamAndGradBucketGroup::RegisterGradReady入口进行判断,决定是否要进行梯度同步。
三条路径运行时互斥,实际运行时只会存在一种 DDP 的实现方式,所以 DDP::SetIsLastMicrobatch() 并不会产生冲突;在存在梯度累积时,三条 DDP 路径都通过本 PR 中核心修改的同一个 NoSyncGuard 来控制“非最后 microbatch 不通信”。
ae24cfc to
a459eff
Compare
背景
先前的实现
is_last_microbatch的处理过于草率,导致目前 ZeRO-2 同时开启梯度累积和overlap_grad_reduce时,当前实现会在第一个 microbatch backward 期间发起 reduce-scatter,并将grad_reduce_dispatched_设置为true。该状态直到
optimizer->step()调用FinishGradSync()后才会重置,因此存在两个问题:temp_full_grad_buffer,造成计算与通信之间的数据竞争。关闭
overlap_grad_reduce时不会触发该问题,因为梯度同步统一在所有 microbatch 完成后的optimizer->step()中执行。修改内容
参考 Megatron-LM 的
no_sync机制,引入真实的is_last_microbatch_控制:Module增加通用的no_sync()接口和 RAIINoSyncGuard。DistributedDataParallel::no_sync()在 guard 生命周期内将 bucket group 的is_last_microbatch_设置为false,退出时恢复为true。overlap_grad_reduce时保持原有行为,由optimizer->step()发起同步。no_sync_func_使用该机制,不直接依赖或包含 DDP 实现。NoSyncGuard。overlap_grad_reduce命令行参数,继续使用 DDP 配置中的默认行为。行为变化
开启梯度累积和
overlap_grad_reduce后,同一个 optimizer step 内的执行过程变为:optimizer->step():等待通信完成并更新参数。这样可以确保 reduce-scatter 读取的是所有 microbatch 累积后的完整梯度,同时避免通信期间继续修改 full gradient buffer。