DP All-Reduce and Overlap

trainingdistributedall-reduceoverlap

naive DP는 backward가 모두 끝난 뒤 gradient all-reduce를 시작한다.

backward all layers -> all-reduce all gradients -> optimizer update

이 방식은 단순하지만 느리다. all-reduce 동안 GPU가 계산을 하지 못하고 기다리기 때문이다.

Naive DP

backward L4backward L3backward L2backward L1 all-reduce all gradients

Overlap + bucket

compute
backward L4backward L3backward L2backward L1
network
bucket A bucket B bucket C
뒤 layer의 gradient가 준비되면 bucket 단위 all-reduce를 먼저 시작해, 앞 layer의 backward 계산 시간 뒤에 통신을 숨긴다.

Backward는 뒤 layer부터 끝난다

모델이 layer 1, 2, 3, 4로 되어 있다고 하자.

forward는 앞에서 뒤로 간다.

layer 1 -> layer 2 -> layer 3 -> layer 4

backward는 반대로 간다.

layer 4 -> layer 3 -> layer 2 -> layer 1

그러면 layer 4의 gradient는 전체 backward가 끝나기 전에 이미 준비된다. 이 gradient를 기다리게 둘 필요가 없다.

layer 4 gradient ready -> layer 4 gradient all-reduce 시작
layer 3 backward 진행 중 -> layer 4 통신도 뒤에서 진행 중

이렇게 계산과 통신을 겹치는 것을 communication-computation overlap이라고 한다.

Bucketing

gradient tensor 하나가 생길 때마다 all-reduce를 호출하면 통신 호출이 너무 많다.

그래서 여러 gradient를 bucket에 모아두고, bucket 단위로 all-reduce한다.

small gradients -> bucket -> one all-reduce

bucket이 너무 작으면 호출 횟수가 많아진다. bucket이 너무 크면 bucket이 찰 때까지 기다려야 하므로 overlap 기회가 줄어든다.

Gradient accumulation과 같이 쓸 때

gradient accumulation 중간 step마다 all-reduce를 하면 낭비다. optimizer update는 마지막에 한 번만 하기 때문이다.

accum step 1: backward, no sync
accum step 2: backward, no sync
accum step 3: backward, all-reduce
optimizer update

PyTorch DDP에서는 보통 no_sync()로 중간 accumulation step의 gradient synchronization을 끈다.

확인

  • naive DP는 언제 all-reduce를 시작하는가?
  • overlap 방식은 왜 layer 4 gradient부터 먼저 통신할 수 있는가?
  • gradient accumulation 중간 step마다 all-reduce하면 왜 낭비인가?