부스트캠프 AI Tech 4기
Multi-GPU - PyTorch
쉬엄쉬엄블로그
2023. 5. 22. 12:05
728x90
이 색깔은 주석이라 무시하셔도 됩니다.
오늘날의 딥러닝은 엄청난 데이터와의 싸움
- 엄청난 양의 데이터를 다루고 계산하기 위해 메모리가 크고 성능이 좋은 GPU가 필요함
- 여러 개의 GPU를 통해 더 좋은 성능을 얻을 수 있음
Multi-GPU
어떻게 GPU를 다룰 것인가
개념 정리
- Single vs. Multi
- GPU vs. Node
- Node : system, 컴퓨터 1대
- Single Node Single GPU
- Single Node Mulit GPU
- 보통 multi gpu를 사용하는 환경
- Multi Node Multi GPU
- 활용이 많이 어려움
Model parallel vs Data parallel
- 다중 GPU에 학습을 분산하는 두 가지 방법
- 모델 나누기
- 데이터 나누기
Model parallel
- 모델을 나누는 것은 생각보다 예전부터 썼음
- alexnet
- 모델의 병목, 파이프라인의 어려움 등으로 인해 모델 병렬화는 고난이도 과제
병렬적으로 처리되는 부분이 겹쳐야 좋은 병렬 처리가 가능
- 그림1의 위 예시는 병렬적으로 처리되는 부분이 겹치지 않기 때문에 병목 현상이 발생하게 됨
코드
class ModelParallelResNet50(ResNet): def __init__(sefl, *args, **kwargs): super(ModelParallelResNet, self).__init__( ~~ ) self.seq1 = nn.Sequential( ~~ ).to('cuda:0') # 첫번째 모델을 cuda 0(gpu 0)에 할당 self.seq2 = nn.Sequential( ~~ ).to('cuda:1') # 두번째 모델을 cuda 1(gpu 1)에 할당 self.fc.to('cuda:1') def forward(self, x): x = self.seq2(self.seq1).to('cuda:1') # 두 모델 seq1과 seq2를 cuda 1(gpu 1)에 연결하기 return self.fc(x.view(x.size(0), -1))
Data parallel
- 데이터를 나눠 GPU에 할당 후 결과의 평균을 취하는 방법
- minibatch 수식과 유사한데 한 번에 여러 GPU에서 수행
PyTorch에서는 아래 두 가지 방식을 제공
DataParallel
parallel_model = torch.nn.DataParallel(model) # 모델 캡슐화
- 단순히 데이터를 분배한 후 평균을 취함
- GPU 사용 불균형 문제 발생
- Batch 사이즈 감소 (한 GPU가 병목)
- GIL (Global Interpreter Lock) 문제
- 단순히 데이터를 분배한 후 평균을 취함
DistributedDataParallel
train_sampler = torch.utils.data.distributed.DistributedSampler(train_data) trainloader = torch.utils.data.DataLoader(train_data, batch_size=20, shuffle=False, pin_memory=True, num_workers=4, sampler=train_sampler)
- pin_memory를 True로 설정하면 아래 그림과 같이 pageable memory와 pinned memory를 거쳐서 GPU에 데이터가 전달되는 것이 아니라 pinned memory에서 바로 GPU에 데이터가 전달된다.
- pin_memory를 True로 설정하면 아래 그림과 같이 pageable memory와 pinned memory를 거쳐서 GPU에 데이터가 전달되는 것이 아니라 pinned memory에서 바로 GPU에 데이터가 전달된다.
코드
def main(): n_gpus = torch.cuda.device_count() # GPU 개수(ex: 4) torch.multiprocessing.spawn(main_worker, nprocs=n_gpus, args=(n_gpus, )) def main_worker(gpu, n_gpus): image_size = 224 batch_size = 512 num_worker = 8 epochs = 20 batch_size = int(batch_size / n_gpus) num_worker = int(num_worker / n_gpus) # 멀티프로세싱 통신 규약 정의 torch.distributed.init_process_group(backend='ncc1', init_method='tcp://127.0.0.1:2568', world_size=n_gpus, rank=gpu) torch.cuda.set_device(gpu) model = model.cuda(gpu) # Distributed DataParallel 정의 model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[gpu])
각 CPU마다 process 생성하여 개별 GPU에 할당
- DataParallel을 통해 하나씩 개별적으로 연산한 후 연산들의 평균을 냄
참고 코드
# python의 멀티프로세싱 코드 from multiprocessing import Pool def f(x): return x*x if __name__ == '__main__': with Pool(5) as p: print(p.map(f, [1,2,3]))