DDP

DDP (분산 데이터 병렬)

DistributedDataParallel (DDP)은 각 GPU에 모델의 전체 복사본을 유지해요. 각 GPU는 서로 겹치지 않는 데이터 샤드(shard)에 대해 forward pass와 backward pass를 수행해요. 옵티마이저 단계 전에 all-reduce가 모든 GPU의 그라디언트를 평균해 줘서 모든 모델 복사본이 동일하게 유지되도록 해줘요. 모델이 단일 GPU에 들어갈 수 있다면 DDP를 사용하면 되요.

출처: 문서

본문

                         ┌─────────────────┐
                         │  training data  │
                         └────────┬────────┘
               ┌──────────────────┼──────────────────┐
               │ shard 0          │ shard 1          │ shard 2
               ▼                  ▼                  ▼
        ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
        │   model     │    │   model     │    │   model     │
        │  (copy 0)   │    │  (copy 1)   │    │  (copy 2)   │
        │   GPU 0     │    │   GPU 1     │    │   GPU 2     │
        └──────┬──────┘    └──────┬──────┘    └──────┬──────┘
               │ grads            │ grads            │ grads
               └──────────────────┼──────────────────┘
                               all-reduce
                          (average gradients)
               ┌──────────────────┼──────────────────┐
               ▼                  ▼                  ▼
        ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
        │  optimizer  │    │  optimizer  │    │  optimizer  │
        │    step     │    │    step     │    │    step     │
        └─────────────┘    └─────────────┘    └─────────────┘
          (identical)        (identical)        (identical)

Accelerate 같은 멀티프로세스 런처로 실행하면 DDP가 자동으로 활성화돼요.

# 4 GPUs on one machine
accelerate launch --num_processes 4 train.py

DDP 구성하기

이 TrainingArguments를 전달해서 DDP 동작을 제어할 수 있어요.

  • gradient_accumulation_steps()는 all-reduce를 언제 수행할지 결정해요. Trainer는 중간 누적 단계에서는 all-reduce를 건너뛰고 마지막 micro-batch에서만 실행해요. 예를 들어 gradient_accumulation_steps=4라면 all-reduce는 backward pass가 4번 일어날 때마다 실행돼요.
  • ~TrainingArguments.ddp_find_unused_parameters는 forward pass가 끝났을 때 autograd 그래프를 순회하면서 그라디언트를 받지 못할 파라미터를 찾아 ready 상태로 표시해서 all-reduce를 막지 않도록 해줘요. gradient_checkpointing()과 함께는 사용하지 마세요. 그라디언트 체크포인팅이 중간 활성화(activation)를 버려두고 필요할 때 다시 계산하기 때문이에요.
  • ~TrainingArguments.ddp_bucket_cap_mb는 backward pass 동안 그라디언트를 단일 all-reduce로 묶기 위한 버킷 크기예요. 버킷이 클수록 all-reduce 호출 횟수가 줄고 실행 시작 오버헤드도 작아져요.
  • ~TrainingArguments.ddp_broadcast_buffers는 매 forward pass 시작 시 rank 0에서 다른 모든 rank로 모델 버퍼(BatchNorm의 running statistics 같은)를 동기화해요. 모델이 LayerNorm만 사용한다면 비활성화해도 돼요. gradient_checkpointing()과 함께는 사용하지 마세요.
  • ~TrainingArguments.ddp_backend는 통신 백엔드를 설정해요. NVIDIA GPU에는 "nccl"을 (기본값이자 가장 빠른 옵션), CPU 학습이나 디버깅에는 "gloo"를, 그 외 하드웨어에는 "xccl", "hccl", "cncl"을 사용해요.
  • ddp_timeout()은 모든 프로세스와 연산(all-reduce, broadcast)이 완료될 때까지의 시간 제한을 설정해요. 대형 모델을 천천히 로딩할 때처럼 프로세스가 멈추면, timeout이 무한정 막히는 대신 에러를 발생시켜 줘요.
from transformers import TrainingArguments

args = TrainingArguments(
    ...,
    gradient_accumulation_steps=4,
    ddp_backend="nccl",
    ddp_find_unused_parameters=False,
    ddp_bucket_cap_mb=25,
    ddp_broadcast_buffers=True,
    ddp_timeout=1800,
)

더 알아보기 (Learn more)

  • 단일 GPU에 들어가기엔 너무 큰 모델을 학습할 때는 FSDP를 확인해 보세요.
  • ZeRO 최적화와 offloading은 DeepSpeed를 확인해 보세요.
  • DDP가 어떻게 동작하는지에 대해 더 자세히 알고 싶다면 The Ultra-Scale Playbook의 Data Parallelism 장을 읽어 보세요.