코딩하는 해맑은 거북이

[PyTorch] PyTorch 활용하기 본문

Python/Tensorflow | PyTorch

[PyTorch] PyTorch 활용하기

#CJE 2023. 3. 16.
본 게시물의 내용은 '부스트캠프 AI Tech - PyTorch(최성철)' 강의를 듣고 작성하였다.
해당 글은 아래의 3가지를 다룬다.
1. Multi-GPU 학습
  ▶ 개념정리
  ▶ Model parallel
  ▶ Data parallel
2. Hyperparameter Tuning
  ▶ Hyperparameter Tuning
  ▶ Ray
3. PyTorch Troubleshooting
  ▶ OOM(Out Of Memory)이 해결하기 어려운 이유
  그 외에 발생할 수 있는 문제들

1. Multi-GPU 학습

▶ 개념정리

- Single(1개) vs.Multi(2개이상)
- GPU vs. Node(시스템, 1대의 컴퓨터)
- Single Node Single GPU (1대의 컴퓨터, 1개의 GPU)
- Single Node Multi GPU (1대의 컴퓨터, 여러개의 GPU)
- Multi Node Multi GPU (여러대의 컴퓨터, 여러개의 GPU)

 

 

▶ Model parallel

- 다중 GPU에 학습을 분산하는 두 가지 방법
  1) 모델을 나누기

  2) 데이터를 나누기
- 모델을 나누는 것은 생각보다 예전부터 썼음 (alexnet)
- 모델의 병목, 파이프라인의 어려움 등으로 인해 모델 병렬화는 고난이도 과제

class ModelParallelResNet50(ResNet):
    def __init__(self, *args, **kwargs): 
        super(ModelParallelResNet50,self).__init_(
            Bottleneck, [3, 4, 6, 3], num_classes=num_classes, *args, **kwargs)
        self.seq1 = nn.Sequential(
            self.conv1, self.bn1, self.relu, self.maxpool, self.layer1, self.layer2
        ).to('cuda:0')	# 첫번째 모델을 cuda 0에 할당
        self.seq2 = nn.Sequential(
            self.layer3, self.layer4, self.avgpool,
        ).to('cuda:1')	# 두번째 모델을 cuda 1에 할당
        
        self.fc.to('cuda:1')
        
    def forward(self, x):	# 두 모델을 연결하기
        x = self.seq2(self.seq1(x).to('cuda:1'))
        return self.fc(x.view(x.size(0), -1))

 

▶ Data parallel

- 데이터를 나눠 GPU에 할당후 결과의 평균을 취하는 방법
- minibatch 수식과 유사한데 한번에 여러 GPU에서 수행

- PyTorch에서는 아래 두 가지 방식을 제공
  1) DataParallel­ : 단순히 데이터를 분배한후 평균을 취함

  → GPU 사용 불균형 문제 발생, Batch 사이즈 감소 (한 GPU가 병목), GIL
  2) DistributedDataParallel­ : 각 CPU마다 process 생성하여 개별 GPU에 할당
  → 기본적으로 DataParallel로 하나 개별적으로 연산의 평균을 냄

# DataParallel 함수를 이용하면 Data parellel을 사용할 수 있음
parallel_model = torch.nn.DataParallel(model) # Encapsulate the model

predictions = parallel_model(inputs). # Forward pass on multi-GPUs
loss = loss_function(predictions, labels) # Compute loss function 
loss.mean().backward() # Average GPU-losses +backward pass 
optimizer.step() # Optimizer step
predictions = parallel_model(inputs) # Forward pass with new parameters
# Distributed Data Parallel
# Sampler를 사용
train_sampler = torch.utils.data.distributed.DistributedSampler(train_data) 
shuffle = False
pin_memory = True	# pin_memory=True로 설정

trainloader = torch.utils.data.DataLoader(train_data, batch_size=20, shuffle=True 
			pin_memory=pin_memory, num_workers=3,

shuffle=shuffle, sampler=train_sampler)

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


def main():
	n_gpus = torch.cuda.device_count()
	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 = ...
    
    batch_size = int(batch_size / n_gpus)
    num_worker = int(num_worker / n_gpus)
    
    # 멀티프로세싱 통신 규약 정의
    torch.distributed.init_process_group(backend='nccl’, 
    		init_method='tcp://127.0.0.1:2568’, world_size=n_gpus, rank=gpu)
    
    model = MODEL
    torch.cuda.set_device(gpu) model = model.cuda(gpu)
    
    # DistributedDataParallel 정의
    model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[gpu])

 

2. Hyperparameter Tuning

▶ Hyperparameter Tuning

cf) 학습이 안나올때 방법 1. 모델을 변경 / 2. 데이터를 추가 / 3. Hyperparameter Tuning

   → 3가지 중 2번째가 가장 좋은 성능을 낸다.

- 모델 스스로 학습하지 않는 값은 사람이 지정 (learning rate, 모델의 크기, optimizer 등)
- 하이퍼 파라메터에 의해서 값의 크게 좌우 될 때도 있음 (요즘은 그닥?)
- 마지막 0.01을 쥐어짜야 할 때 도전해볼만! (끝날때까지 끝난게 아닐때 사용함.. 요즘엔 이전처럼 많은 힘을 쏟진않음)

- 가장 기본적인 방법 2가지 - Grid Search(일정한 범위로 자르는것), Random Search(말그대로 아무거나)

- 최근에는 베이지안 기반 기법들이 주도 (ex. 2018 BOHB 논문)

 

 

▶ Ray

- multi-node multi processing 지원 모듈
- ML/DL의 병렬 처리를 위해 개발된 모듈
- 기본적으로 현재의 분산병렬 ML/DL 모듈의 표준
- Hyperparameter Search를 위한 다양한 모듈 제공

data_dir = os.path.abspath("./data")
load_data(data_dir)
config = {  # config 에 search space 지정
    "l1": tune.sample_from(lambda _: 2 ** np.random.randint(2, 9)),
    "l2": tune.sample_from(lambda _: 2 ** np.random.randint(2, 9)),
    "lr": tune.loguniform(1e-4, 1e-1),
    "batch_size": tune.choice([2, 4, 8, 16])
}
scheduler = ASHAScheduler(  # 학습 스케줄링 알고리즘 지정
    metric="loss",
    mode="min",
    max_t=max_num_epochs,
    grace_period=1,
    reduction_factor=2)
reporter = CLIReporter( # 결과 출력 양식 지정
    # parameter_columns=["l1", "l2", "lr", "batch_size"],
    metric_columns=["loss", "accuracy", "training_iteration"])

# 병렬 처리 양식으로 학습 시행
result = tune.run(
    partial(train_cifar, data_dir=data_dir),
    resources_per_trial={"cpu": 2, "gpu": gpus_per_trial},
    config=config,
    num_samples=num_samples,
    scheduler=scheduler,
    progress_reporter=reporter)

 

 

3. PyTorch Troubleshooting

▶ OOM(Out Of Memory)이 해결하기 어려운 이유

- 왜 발생했는지 알기 어려움
- 어디서 발생했는지 알기 어려움
- Errorbacktracking이 이상한데로 감
- 메모리의 이전상황의 파악이 어려움

→ 단순한 해결법 : batch size를 줄이고, GPU clean하고, Run 한다.

 

 

** 그 외에 발생할 수 있는 문제들

▶ GPUUtil 사용하기

- nvidia-smi처럼 GPU의 상태를 보여주는 모듈
- Colab은 환경에서 GPU상태 보여주기 편함
- iter마다 메모리가 늘어나는지 확인!!

!pip install GPUtil
import GPUtil
GPUtil.showUtilization()

 

 

▶ torch.cuda.empty_cache() 써보기

- 사용되지 않은 GPU상 cache를 정리
- 가용 메모리를 확보

- del 과는 구분이 필요 (del: 관계를 끊어줌)
- reset 대신 쓰기 좋은 함수

import torch
from GPUtil import showUtilization as gpu_usage

print("Initial GPU Usage")
gpu_usage()

tensorList = []
for x in range(10):
	tensorList.append(torch.randn(10000000,10).cuda())
    
print("GPU Usage after allcoating a bunch of Tensors")
gpu_usage()

del tensorList

print("GPU Usage after deleting the Tensors")
gpu_usage()

print("GPU Usage after emptying the cache")
torch.cuda.empty_cache()

gpu_usage()

 

 

▶ trainning loop에 tensor로 축적 되는 변수는 확인할 것

- tensor로 처리된 변수는 GPU상에 메모리 사용
- 해당 변수 loop안에 연산에 있을 때 GPU에 computational graph를 생성(메모리 잠식)

total_loss = 0
for i in range(10000):
    optimizer.zero_grad()
    output = model(input)
    loss = criterion(output)
    loss.backward()
    optimizer.step()
    total_loss += loss

- 1-dtensor의 경우 python기본 객체로 변환하여 처리할 것

total_loss = 0
for x in range(10):
    # assume loss is computed
    iter_loss = torch.randn(3,4).mean()
    iter_loss.requires_grad = True
    total_loss += iter_loss

 

 

▶ del 명령어를 적절히 사용하기

- 필요가 없어진 변수는 적절한 삭제가 필요함
- python의 메모리 배치 특성상 loop이 끝나도 메모리를 차지함

 

 

▶ 가능 batch사이즈 실험해보기

- 학습시 OOM이 발생했다면 batch 사이즈를 1로 해서 실험해보기

oom = False

try:
	run_model(batch_size)
except RuntimeError: # Out of memory
	oom = True
    
if oom:
    for _ in range(batch_size):
    	run_model(1)

 

 

▶ torch.no_grad() 사용하기

- Inference 시점에서는 torch.no_grad() 구문을 사용
- backward pass으로 인해 쌓이는 메모리에서 자유로움

with torch.no_grad():
    for data, target in test_loader:
        output = network(data)
        test_loss += F.nll_loss(output, target, size_average=False).item()
        pred = output.data.max(1, keepdim=True)[1]
        correct += pred.eq(target.data.view_as(pred)).sum()

 

▶ 예상치 못한 에러 메세지

- OOM 말고도 유사한 에러들이 발생
- CUDNN_STATUS_NOT_INIT이나 device-side-assert 등
- 해당 에러도 cuda와 관련하여 OOM의 일종으로 생각될 수 있으며, 적절한 코드 처리의 필요함

 

 

▶ 그외

- colab에서 너무 큰 사이즈는 실행하지 말 것 (linear, CNN, LSTM)
- CNN의 대부분의 에러는 크기가 안 맞아서 생기는 경우 (torchsummary 등으로 사이즈를 맞출 것)
- tensor의 float precision을 16bit로 줄일 수도 있음

 

'Python > Tensorflow | PyTorch' 카테고리의 다른 글

[PyTorch] 사칙연산  (0) 2023.03.17
[PyTorch] PyTorch 구조2  (0) 2023.03.15
[PyTorch] PyTorch 구조1  (0) 2023.03.13
[PyTorch] PyTorch 기본  (0) 2023.03.13
[Tensorflow] Tensorflow 정의 및 동작 방식  (0) 2021.03.19
Comments