부스트캠프 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

  • 병렬적으로 처리되는 부분이 겹쳐야 좋은 병렬 처리가 가능

    • 그림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에 데이터가 전달된다.
    • 코드

      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]))