AI VIDEO BRIEFING

파이토치 분산 학습 입문: DataParallel과 DistributedDataParallel 차이와 GIL 병목, CMU 딥러닝 실습

카네기멜런 딥러닝 수업 실습에서 파이토치의 두 가지 데이터 병렬 방식을 같은 코드로 비교했다. 한 줄이면 끝나는 DataParallel이 왜 느린지, 파이썬 GIL과 그래디언트 동기화 방식의 차이가 어디서 갈리는지 짚는다.

GPU 두 장으로 보는 분산 학습 — DataParallel과 DDP는 뭐가 다른가 영상 대표 이미지

핵심 메시지

  • 데이터 병렬은 모델 사본을 GPU마다 두고 배치를 쪼개 동시에 처리하는 방식이다. 여기까지는 두 방법이 같고, 그래디언트를 동기화해 가중치를 갱신하는 4~5단계에서 갈린다.
  • DataParallel은 기존 코드에 한 줄만 덧붙이면 되지만, 파이썬 프로세스 하나가 여러 스레드로 GPU를 관리하는 중앙집중 구조라 GIL 때문에 느려진다. 실무에서 잘 쓰이지 않는 이유다.
  • DistributedDataParallel은 GPU마다 별도 프로세스를 띄우고 all-reduce로 서로 통신해 GIL 제약을 피하며, 여러 대의 머신으로도 확장된다. 대신 초기화 설정이 번거롭다.
  • 같은 CNN과 같은 데이터로 5에폭을 돌린 비교에서 DataParallel은 약 39초, DDP는 29초 이내였다.
  • DDP는 그래디언트는 자동으로 합쳐 주지만 손실값은 그렇지 않다. 프로세스별 손실을 보려면 all-reduce로 직접 모아야 한다는 점이 실수하기 쉬운 지점이다.

쉽게 이해하기

여러 장의 GPU를 쓴다고 학습이 자동으로 빨라지지는 않는다. 카네기멜런대 딥러닝 수업의 조교가 진행한 이 실습은 파이토치에서 데이터 병렬을 구현하는 두 가지 방법을 같은 코드와 같은 데이터로 나란히 돌려 그 차이를 눈으로 보여 준다.

병렬화에는 여러 갈래가 있다. CPU 코어마다 다른 작업을 맡기는 작업 수준 병렬, 모델이 너무 커서 조각내 여러 장치에 나눠 싣는 모델 수준 병렬, 그리고 이 실습이 다루는 데이터 병렬이다. 데이터 병렬의 흐름은 방법과 무관하게 같다. 모델 사본을 각 GPU에 올리고, 배치를 쪼개 나눠 준 뒤, 동시에 순전파와 역전파를 돌린다. 문제는 그다음이다. 각 GPU가 계산한 그래디언트를 어떻게 모아 가중치를 갱신할 것인가.

첫 번째 방법은 구현이 거의 공짜다. 모델을 DataParallel로 감싸는 한 줄이면 나머지 학습 코드를 손대지 않아도 된다. 대신 구조가 중앙집중형이다. 파이썬 프로세스 하나가 여러 스레드로 GPU들을 지휘하고 그래디언트를 한곳에 모아 갱신하는데, 파이썬 인터프리터는 한 번에 하나의 바이트코드만 실행할 수 있다는 전역 인터프리터 잠금 때문에 스레드들이 줄을 서게 된다. 조교가 이 방식을 두고 실무에서는 잘 쓰이지 않는다고 말한 이유가 여기 있다.

두 번째 방법은 설계를 바꾼다. GPU마다 독립된 프로세스를 띄워 각자 순전파와 역전파를 책임지게 하고, 프로세스들이 all-reduce라는 집합 통신으로 그래디언트를 주고받는다. 프로세스가 분리되므로 GIL 병목이 사라지고, 한 대의 머신을 넘어 여러 노드로도 확장된다. 대가는 초기화 부담이다. 노드 안에서 몇 번째 GPU인지를 가리키는 로컬 랭크, 전체에서 몇 번째 프로세스인지를 가리키는 랭크, 총 프로세스 수인 월드 사이즈를 각 프로세스에 알려 주고 통신 백엔드를 초기화해야 한다. 데이터셋은 한 프로세스만 내려받게 하고 배리어로 다른 프로세스들을 대기시키며, 데이터로더에는 분산 샘플러를 붙여 프로세스마다 다른 조각을 보게 한다.

결과는 분명했다. 손글씨 숫자 데이터로 간단한 합성곱 신경망을 다섯 에폭 학습하는 데 DataParallel은 약 39초, DDP는 29초 이내가 걸렸다. 마지막으로 조교는 흔한 함정 하나를 짚는다. DDP는 그래디언트 축약을 자동으로 해 주지만 손실값 집계는 해 주지 않으므로, 프로세스별 손실을 하나로 보려면 직접 all-reduce를 호출해야 한다. 프로세스를 직접 띄우는 multiprocessing spawn 방식도 있지만 랭크와 월드 사이즈를 손수 배정해야 해 권하지 않는다는 조언으로 실습은 끝난다.

주요 인사이트

  • 코드 한 줄로 되는 편의는 대개 구조적 대가를 동반한다. DataParallel의 간결함은 중앙집중 구조에서 나오고, 그 구조가 그대로 GIL 병목이 된다.
  • GPU를 늘리는 문제가 실은 파이썬 런타임 문제로 귀결된다는 점이 이 실습의 핵심이다. 병목이 연산이 아니라 인터프리터에 있었기 때문에 해법도 스레드가 아니라 프로세스 분리였다.
  • all-reduce는 중앙 노드 없이 참여자들이 서로 값을 합치는 집합 통신이다. 중앙집중 집계를 이 방식으로 바꾼 것이 두 방법의 실질적 차이다.
  • 자동으로 처리되는 것과 그렇지 않은 것의 경계를 아는 것이 중요하다. 그래디언트는 자동, 손실은 수동이라는 비대칭이 초심자가 가장 자주 걸리는 지점이다.
  • 대부분의 수업 과제에는 분산 학습이 필요 없다는 전제도 함께 언급된다. 여러 GPU가 필요한 프로젝트 단계에 이르렀을 때 꺼내 쓰는 도구라는 위치 설정이다.

자주 묻는 질문

GIL이 왜 학습 속도에 영향을 주나요?

DataParallel은 파이썬 프로세스 하나가 여러 스레드로 GPU들을 제어합니다. 전역 인터프리터 잠금 때문에 한 시점에 하나의 스레드만 파이썬 바이트코드를 실행할 수 있어, 각 스레드가 내는 중간 명령들이 순차적으로 처리되면서 속도가 떨어집니다.

DDP를 쓰려면 무엇을 준비해야 하나요?

각 프로세스에 로컬 랭크·랭크·월드 사이즈를 알려 주고 프로세스 간 통신 매체를 초기화해야 합니다. 데이터셋은 한 프로세스에서만 준비하고 배리어로 동기화 지점을 잡으며, 데이터로더에는 분산 샘플러를 지정합니다.

손실값도 자동으로 합쳐지나요?

아닙니다. 파이토치의 DDP는 그래디언트 축약은 자동으로 처리하지만 손실 집계는 하지 않습니다. 프로세스별 손실을 하나의 값으로 보려면 all-reduce 같은 집합 통신을 직접 호출해 더해야 합니다.

원문과 출처

이 글은 원본 영상의 자막을 바탕으로 한국어 독자를 위해 요약했습니다. 전체 맥락과 최신 정보는 원문에서 확인하세요.

YouTube 원본 영상 보기 ↗

관련 AI 소식