[내용이 도움이 되셨으면, 하트와 구독 부탁드립니다. 감사합니다.]
*협찬
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 |