[Pytorch] 분산 학습 2: DP와 DDP 비교
카테고리: Pytorch
DataParallel (DP)
DataParallel 이란?
DP는 단일 프로세스 안에서 하나의 메인 GPU가 모델의 소유권을 가지며, forward 시점마다 모델을 다른 GPU로 복제(copy) 하여 미니배치를 장치별 마이크로배치로 나눈 뒤 병렬로 추론을 수행하고, backward에서 각 보조 GPU의 그라디언트를 메인 GPU로 모아 집계한 후 메인 GPU에서만 optimizer.step()
을 호출하는 방식이다. 이때 파라미터의 실제 원본은 메인 GPU가 가지고 있으며, 다른 GPU에는 매 스텝마다 임시 복제본이 만들어진다. 따라서 DP는 싱글 노드만을 지원한다.
싱글 노드 vs 멀티 노드
간단히 말해 멀티 노드는 여러 대의 물리/가상 머신(서버)을 함께 묶어 한 번에 학습하는 구성을 뜻한다. 여기서 노드(node) 는 GPU들이 장착된 하나의 서버를 의미하며, 싱글 노드는 한 대의 서버 안에서 여러 GPU를 쓰는 경우, 멀티 노드는 두 대 이상 서버의 GPU들을 모두 묶어 하나의 분산 학습 작업으로 쓰는 경우를 말한다.
DP의 문제점
DP방식에서 통신은 주로 PCIe/NVLink를 통한 장치 간 텐서 복사에 의존하며, Python 단일 프로세스·멀티스레드 모델이므로 GIL 영향과 호스트 측 스케줄링 오버헤드가 비교적 크다. 결과적으로 대형 모델·대형 배치·다수 GPU 조합에서 확장 효율이 급격히 떨어지며, CPU가 병목이 되기 쉽고, 메모리 파편화나 불균형 로드 문제가 자주 나타난다.
요약하자면, 하나의 GPU에 데이터들이 모두 모였다가 분배되고 각각의 GPU에서 연산되었다가 정보들을 다시 하나의 GPU로 모으게 되고 이러한 작업을 다시 역으로 진행함으로써 모든 GPU에 모델을 학습하게 되는데 이때 하나의 GPU로 모이게 되는 상황에서 위의 이미지와 같은 VRAM 몰림현상이 발생한다. 참고로, Python의 고질적인 멀티 스레드 불가능 문제 때문에 무조건 DP보다는 DDP를 사용하는 것이 권장된다.
Distributed DataParallel (DDP)
Distributed DataParallel 이란?
DDP는 DP의 단점을 커버하고자 나온 방식으로, GPU 당 1프로세스(=1 rank) 원칙을 따르는 멀티프로세스 병렬 학습 프레임워크이며, 각 프로세스가 자신의 GPU에 모델의 온전한 사본을 상시 보유하고 독립적으로 forward/backward를 수행한다. 그라디언트는 NCCL 기반 All-Reduce로 동일 계층의 버킷 단위로 효율적으로 합산되며, PyTorch는 그래디언트 버킷팅과 통신-계산 오버랩을 통해 통신 비용을 숨긴다.
각 프로세스가 자체적으로 optimizer.step()
을 호출하되, 통신으로 동기화된 동일 그라디언트를 사용하므로 모든 복제본의 파라미터가 단계마다 동일하게 유지된다. 이 구조는 선형에 가까운 스케일링과 우수한 재현성/안정성을 제공하며, 멀티 노드까지 자연스럽게 확장된다. 즉, 정확히 모든 GPU에서 동일한 VRAM을 사용하며, 각 GPU에 모델을 replica하여 Optimizer + Gradient들을 모두 처리한다.
DDP의 단점
하지만, DDP에도 단점은 존재한다.
- 동적 그래프/미사용 파라미터 민감성
- 조건 분기 등으로 일부 파라미터가 특정 스텝에 사용되지 않으면 훈련이 즉시 중단될 수 있음이다.
- 완화책은
find_unused_parameters=True
이지만 성능 저하와 통신 오버헤드 증가가 있다.
- 멀티프로세스 디버깅 난이도
- 프로세스별 로깅·예외 추적이 복잡하며, 하나의 랭크가 OOM/에러로 NCCL 집단 통신이 교착되면 전 랭크가 함께 멈출 수 있다.
rank==0
만 출력/저장을 수행하도록 코드 분기, 타임아웃·에러 전파 처리, tqdm/print 남용 금지 등이 필요하다.
- 모델 병렬과의 결합 복잡성
- 거대 모델에서는 DDP 단독으로는 부족하며 FSDP/텐서/파이프라인 병렬과 혼합 설계가 필요해 시스템 복잡도가 증가한다.
Summary
- DP: 메인 GPU 하나에만 모델 원본을 올려두고, 매 스텝 forward 시에만 다른 GPU로 임시 복제하여 연산을 병렬화한 뒤, 각 보조 GPU의 그라디언트를 메인 GPU로 모아 집계하고 **메인 GPU에서만
optimizer.step()
을 수행해 메인 GPU의 파라미터만 실제로 갱신한다. 다음 스텝에서도 다시 forward 시 복제본이 만들어져 사용된다이다. - DDP: GPU당 1프로세스로 각 GPU가 모델의 사본을 상시 보유하고 독립적으로 forward/backward를 수행한다. backward 끝에 NCCL All-Reduce로 모든 그라디언트를 평균한 뒤, 각 프로세스가 로컬에서
optimizer.step()
을 각각 수행한다. 이때 모든 프로세스가 동일한(평균된) 그라디언트를 쓰므로 모든 모델 사본이 매 스텝 동일한 값으로 갱신되어 완벽히 동기화된다이다. 즉, “각 GPU가 제멋대로 따로 업데이트”하는 것이 아니라, 동일 업데이트를 각자 적용하는 구조이다.
댓글 남기기