[내용이 도움이 되셨으면, 하트와 구독 부탁드립니다. 감사합니다.]

*협찬
https://m.smartstore.naver.com/digshop

디그샵 : 네이버쇼핑 스마트스토어

반려동물의 모든 것

smartstore.naver.com


*CUDNN_STATUS_SUCCESS

- Framework와 Cuda 간 Version이 호환되지 않는 문제일 수 있음

- AIops Version : cuda 9.0, cudnn 7, Tensorflow 1.8.0, 

- Minsky Version : cuda , cudnn

 

 

*torch gpu 확인 명령어

- torch.cuda.get_device_name(0) : Tesla V100-PCIE-16GB

- torch.cuda.is_available() : True

- torch.cuda.device_count() : 1

 

*정리

- DataParallel은 PyTorch에서 제공하는 가장 기본적인 방법이지만 GPU 메모리 불균형 문제가 생김

- Custom DataParallel의 경우 GPU 메모리 문제를 어느정도 해결해주지만 GPU를 제대로 활용하지 못한다는 문제

- Distributed DataParallel은 원래 분산학습을 위해 만들어진 PyTorch의 기능이지만 multi-GPU 학습에도 사용할 수 있고 메모리 불균형 문제와 GPU를 활용하지 못하는 문제가 없음. 하지만 간간히 문제가 발생하기 때문에

- Nvidia에서 만든 Apex를 이용해서 multi-GPU 학습

 

*pytorch multi-gpu (Data Parallel)

- 러닝을 여러 개의 GPU에서 사용하려면 일단 모델을 각 GPU에 복사해서 할당해야 한다.

- 그리고 iteration을 할 때마다 batch를 GPU의 개수만큼 나눕니다.

- 이렇게 나누는 과정을 ‘scatter’ 한다고 하며 실제로 Data Parallel에서 scatter 함수를 사용해서 이 작업을 수행

- 이렇게 입력을 나누고 나면 각 GPU에서 forward 과정을 진행

- 각 입력에 대해 모델이 출력을 내보내면 이제 이 출력들을 하나의 GPU로 모음

- 이렇게 tensor를 하나의 device로 모으는 것은 ‘gather’ 이라고 한다.

- 보통 딥러닝에서는 모델의 출력과 정답을 비교하는 loss function이 있다.

- Loss function을 통해 loss를 계산하면 back-propagation을 할 수 있다.

- Back-propagation은 각 GPU에서 수행하며 그 결과로 각 GPU에 있던 모델의 gradient를 구할 수 있다.

- 만약 4개의 GPU를 사용한다면 4개의 GPU에 각각 모델이 있고 각 모델은 계산된 gradient를 가지고 있다.

- 이제 모델을 업데이트하기 위해 각 GPU에 있는 gradient를 또 하나의 GPU로 모아서 업데이트를 한다.

- 만약 Adam과 같은 optimizer를 사용하고 있다면 gradient로 바로 모델을 업데이트하지 않고 추가 연산을 한다.

- 이러한 Data Parallel 기능은 코드 한 줄로 간단히 사용 가능

- import torch.nn as nn

- model = nn.DataParallel(model)

- nn.DataParallel로 model을 감싸면 학습을 할 때 다음과 같은 작업을 하는 것임

- replicate → scatter → parallel_apply → gather 순서대로 진행

- Gather가 하나의 gpu로 각 모델의 출력을 모아주기 때문에 하나의 gpu의 메모리 사용량이 많을 수 밖에 없음

 

- 만약 0번 GPU가 1, 2, 3번 GPU에 비해 6G 정도 더 많은 메모리를 사용하고 있으면

- 이렇게 하나의 GPU가 상대적으로 많은 메모리를 사용하면 batch size를 많이 키울 수 없다.

- 딥러닝에서 batch size는 학습 성능에 영향을 주는 경우가 많기 때문에 메모리 사용 불균형은 꼭 해결해야할 문제임

- 또한 학습을 더 빨리하고 싶어서 multi-GPU를 쓰는 경우도 많다.

- 학습이 오래 걸릴 경우 batch size 차이로 1주일을 더 학습시켜야 하는 상황이 될 수도 있다.

 

- 메모리 불균형 문제를 제일 간단히 해결하는 방법은 단순히 출력을 다른 GPU로 모으는 것

- 디폴트로 설정되어있는 GPU의 경우 gradient 또한 해당 GPU로 모이기 때문에 다른 GPU에 비해 메모리 사용량이 상당히 많다.

- 따라서 출력을 다른 GPU로 모으면 메모리 사용량의 차이를 줄일 수 있다.

- 다음 코드와 같이 간단하게 출력을 모으고 싶은 GPU 번호를 설정하면 된다.

- import os

- import torch.nn as nn

- os.environ['CUDA_VISIBLE_DEVICES'] = '0, 1, 2, 3'

- model = nn.DataParallel(model, output_device=1)

 

- output_device를 설정하고 다시 학습을 시작하면 GPU 사용량이 달라진 것을 알 수 있다.

- 0번 GPU의 메모리 사용량은 줄고 1번 GPU의 메모리 사용량은 는다.

- 하지만 여전히 균형하게 사용하지 않는 것을 볼 수 있다.

- 모델 출력의 크기는 batch size에 따라 달라진다.

- 이대로 batch size를 늘리면 1번 GPU의 메모리 사용량은 점점 늘어나게 된다.

- 따라서 이 방법은 일시적으로 문제를 해결하는 것 같아 보여도 적절한 해결 방법은 아니다.

- 게다가 GPU-Util을 보면 GPU를 제대로 활용하지 못하는 것을 확인할 수 있다.

 

- DataParallel을 그대로 사용하면서 메모리 불균형의 문제를 해결할 수 있는 방법에 대한 힌트는 PyTorch-Encoding이라는 패키지에 있다.(패키지 링크: https://github.com/zhanghang1989/PyTorch-Encoding).

- 하나의 GPU의 메모리 사용량이 늘어나는 것은 모델의 출력을 하나의 GPU로 모은 것 때문

- 왜 하나의 GPU로 모델의 출력을 모을까?

- 왜냐하면 모델의 출력을 사용해서 loss function을 계산해야 하기 때문

- 모델은 DataParallel을 통해 병렬로 연산할 수 있게 만들었지만 loss function이 그대로이기 때문에 하나의 GPU에서 loss를 계산

- 따라서 loss function 또한 병렬로 연산하도록 만든다면 메모리 불균형 문제를 어느정도 해결할 수 있다.

 

- Loss function을 병렬 연산 가능하게 만드는 방법은 모델을 병렬 연산으로 만드는 방법과 동일

- PyTorch에서는 loss function 또한 하나의 모듈

- 이 모듈을 각 GPU에 replicate 한다.

- 그리고 데이터의 정답에 해당하는 tensor를 각 GPU로 scatter 한다.

- 그러면 loss를 계산하기 위한 모델의 출력, 정답, loss function 모두 각 GPU에서 연산할 수 있도록 바뀐 상태임

- 따라서 각 GPU에서 loss 값을 계산할 수 있음

- 각 GPU에서는 계산한 loss로 바로 backward 연산을 할 수 있음

 

- Loss function을 parallel 하게 만들어서 연산하는 과정을 코드로 보면

- 데이터의 정답에 해당하는 target을 scatter 한 다음에 replicate한 module에서 각각 계산을 한다.

- 계산한 output와 Reduce.apply를 통해 각 GPU에서 backward 연산을 하도록 만든다.

 

- class DataParallelCriterion(DataParallel)

-   forward()

-        targets, kwargs = self.scatter(targets, kwargs, self.device_ids)

-        replicas = self.replicate(self.module, self.device_ids[:len(input)])

-        targets = tuple(targets_per_gpu[0] for targets_per_gpu in targets)

-        outputs = _criterion_parallel_apply(replicas, inputs, targets, kwargs)

-   return Reduce.apply(*outputs) / len(outputs), targets

 

- DataParallelCriterion을 사용할 경우에 일반적인 DataParallel로 모델을 감싸면 안된다.

- DataParallel은 기본적으로 하나의 GPU로 출력을 모으기 때문

- 따라서 Custom DataParallel 클래스인 DataParallelModel을 사용한다.

- DataParallelModel과 DataParallelCriterion을 사용해서 학습하는 과정은 다음과 같다.

- 사용하는 법은 Pytorch-Encoding 패키지에서 parallel.py 파일만 가져와서 학습 코드에서 import 하도록 만들면 된다.

 

- import torch

- import torch.nn as nn

- from parallel import DataParalleModel, DataParallelCriterion

- model = BERT(args)

- model = DataParallelModel(model)

- model.cuda()

- criterion = nn.NLLLoss()

- criterion = DataParallelCriterion(criterion)

- for i, (inputs, labels) in enumerate(trainloader):

-     outputs = model(inputs)

-     loss = criterion(outputs, labels)

-     optimizer.zero_grad()

-     loss.backward()

-     optimizer.step()

 

- 이렇게 학습을 할 경우, DataParallel 만 사용할 때에 비해 1번 GPU와 2번 GPU의 메모리 사용량의 차이가 상당히 줄어든다.

- batch size를 기존에 비해 늘릴 수 있기 때문에 학습 시간도 전체적으로 1/3 정도가 준다.

- 하지만 GPU 성능을 여전히 제대로 활용 못하고 있다.

 

*GPU 성능을 100 %로 끌어 올리려면 어떻게 해야할까?

- main.py를 실행하면 main이 실행되는데 main은 다시 main_worker 들을 multi-processing으로 실행

- GPU 4개를 하나의 노드로 보고 world_size를 설정

- mp.spawn 함수가 4개의 GPU에서 따로 따로 main_worker를 실행

 

- main_worker에서 dist.init_process_group을 통해 각 GPU 마다 분산 학습을 위한 초기화를 실행

- PyTorch의 docs를 보면 multi-GPU 학습을 할 경우 backend로 nccl을 사용하라고 나와있음

- init_method에서 FREEPORT에 사용 가능한 port를 적으면 됨

- 이렇게 분산 학습을 위한 초기화를 하고 나면 분산 학습이 가능

- model에는 DataParallel 대신에 DistributedDataParallel을 사용

- DataParallel에서 언급한 입력을 분산하고 forward 연산을 수행하고 다시 backward 연산을 수행하는 역할을 한다.

- DataLoader가 입력을 각 프로세스에 전달하기 위해서 다음처럼 DistributedSampler를 사용

- DistributedSampler는 DistributedDataParallel과 함께 사용해야 한다.

- 사용 방법은 간단하게 정의해놓은 dataset를 DistributedSampler로 감싸주고 DataLoader에서 sampler에 인자로 넣어준다.

- 그 다음엔 평소에 DataLoader를 사용하듯이 똑같이 사용하면 됨.

 

- DistributedSampler의 내부를 보면

- 각 Sampler는 전체 데이터를 GPU의 개수로 나눈 부분 데이터에서만 데이터를 샘플링

- 부분 데이터를 만들기 위해 전체 데이터셋 인덱스 리스트를 무작위로 섞은 다음에 그 인덱스 리스트를 쪼개서 각 GPU Sampler에 할당

- epoch 마다 각 GPU sampler에 할당되는 인덱스 리스트는 다시 무작위로 달라짐

- 그러기 위해서는 train_sampler.set_epoch(epoch) 명령어를 매 epoch 마다 학습 전에 실행해야 함

 

*Distributed DataParallel 의 문제점

-모델에서 학습에 사용하지 않는 parameter가 있을 경우에 Distributed DataParallel이 문제를 일으킬 수 있다.

-Nvidia에서 만든 Apex라는 패키지를 사용해서 해결

 

*Apex

- Nvidia에서 Apex라는 Mixed Precision 연산을 위한 패키지를 만듬

- 보통 딥러닝은 32 비트 연산을 하는데 16 비트 연산을 사용해서 메모리를 절약하고 학습 속도를 높이겠다는 의도

- Apex에는 Mixed Precision 연산 기능 말고도 Distributed 관련 기능이 포함

- Apex의 Distributed DataParallel 기능을 하는 것이 DDP

- from apex.parallel import DistributedDataParallel as DDP

- DDP로 model을 감싸줌

- 그 이외에는 PyTorch DistributedDataParallel과 동일


[내용이 도움이 되셨으면, 하트와 구독 부탁드립니다. 감사합니다.]

반응형

'스타트업 > AI' 카테고리의 다른 글

[AI] numel  (0) 2020.02.05
[AI] squeeze(), unsqueeze()  (0) 2020.02.04
[AI] cudnn.benchmark = True  (0) 2020.02.03
[AI] pytorch save, load, state_dict  (0) 2020.02.03
[AI] radam  (0) 2020.02.03

+ Recent posts