Global Interpritor Lock


GIL. Global Interpritor Lock.

Global Interpritor Lock(GIL)이란 Python에서 오직 하나의 Thread만 동작하도록 컨트롤하는 Lock(또는 Mutex)이다.
이 말은 타임라인상에 오직 하나의 Thread만 실행될 수 있다는 것을 말한다. Single Thread를 사용하는 코드에서는 그렇게 큰 영향을 주지 않지만, Multi-Thread를 사용하는 코드에서는 병목현상을 일으킬 수 있다.

GIL이 어떻게 동작하는지 한번 살펴보자

위에서 이야기 한 것처럼, GIL에서는 오직 하나의 Thread만 활성화되어서 코드를 실행하는데 이는 아래의 그림처럼 그려낼 수 있다.

GIL

Thread가 전환 될 경우에는 기존의 Thread의 작동이 멈추고 동작을 시작하게 된다.
기존의 하던 작업들을 멈추고 다른 Thread로 넘겨야 하기 때문에 context switch가 일어나게 된다.

그러면 왜 GIL을 사용할까?

먼저 왜 GIL을 사용하는지 알기 위해서는 Python의 Garbege Collector(GC)에 대해 알고 있어야 한다. (GC에 대한 자세한 내용은 추후에 다룰 예정이다) 고급언어인 python에서는 C언어와 다르게 자동으로 메모리 관리를 해주는 GC라는 것을 사용한다.

이 GC는 오브젝트가 얼마나 많이 참조(실행)가 되었는지를 카운팅하는 reference count 라는 것으로 메모리에서 삭제할지 유지할지를 결정한다. 하지만 동시에 여러군데에서 참조가 이루어 질 경우 Race Condition 등의 문제들이 발생 할 수 있기 때문에 이를 방지하기위해 Mutex나 Semaphore등의 Lock이 필요했는데 이것을 Python에 존재하는 수많은 Object에 전부 적용하는것은 성능상으로도 좋지 않을 뿐만 아니라, Deadlock 같은 매우 치명적인 위험상황이 발생 할 수 있었다

그래서 선택한것이 따로 Object에 Lock을 두지 않고, 타임라인에서 실행되는 Thread를 딱 하나만 두도록 Interpritor를 Mutex로 잠궈버렸다. 이렇게 되면 여러곳에서 동시 참조가 되지않으니 성능의 하락 없이 위의 동시참조의 문제를 해결하게 된것이다

그러면 python에서는 병렬화된 코드는 어떻게 써야하나

  1. Multi Process를 사용한다
    Thread를 막아버린것 이기 때문에 Multi Processing을 사용하면 잘 돌아간다. 물론 Process간에 공유자원을 가지기 위해서는 많은 작업들이 필요로 하기 때문에 context switching이 발생하여 Thread에 비해 속도가 늦을 것이다. 하지만 Windows는 OS 보안상 이유로 이걸 막아버렸다.
  2. Multi Threading을 사용한다
    아까 Multi Threading을 막아놨다고 했는데 뭔 소리냐 할 수 있지만, CPU에서 대부분의 연산이 돌아가는 코드를 제외한 I/O Bound 계열의 문제들은 file system과 Network의 하위 컴포넌트에서 돌아가기 때문에 Single보다 더 빠르게 진행 할 수 있다.
  3. 굳이 사용하고 싶다면 PyPi나 Jython 같은 다른 Python implementation으로 사용하는 방법도 존재하지만, 그 코드가 python에서와 똑같이 동작할 보장은 없다
  • 그리고 numpy나 Scipy등의 ML에서 많이 사용하는 Module들은 C기반으로 만들어져서 GIL의 굴레에서 자유롭게 연산이 된다고 한다.

reference

Week2 Homework


1. 기초과제

  • 과제 내용
    • Pytorch의 Custom Model 제작에 필요한 nn.Module 함수들에 대한 공부
    • Dataset과 Dataloader의 구현
  • 결과 및 회고

2. 심화과제

  • 과제내용
    • Transfer Learning과 weight 초기화 + Ray사용 해보기
  • 결과 및 회고
    • Transfer Learning은 익히 알던 내용이여서 어렵지 않았다
    • weight 초기화를 할때 랜덤으로 초기화 해주는것보다 특정 initialization 을 진행하는것이 더 성능이 잘 나오는것에 대해 알게 되었다
      • 이미 pytorch에서는 layer별로 내부 Method에 적용되어있다고 한다
      • 자세한 내용은 kaiming_uniform_에 대해 찾아보는것을 추천한다
    • Ray는 코드 실행은 해보았지만 colab GPU 사용량 초과로 인해서 강제 종료 당했다
    • 추후에 Linux 환경의 컴퓨터에서 다시한번 실행을 해 볼 예정이다

부스트 캠프 ai tech 2주 4일차 Pytorch (8)


Pytorch Troubleshooting

  • OOM : Out Of Memory
    • GPU의 메모리가 터질때 발생하는 현상…
    • 왜 발생했는지 알기힘듬
    • 메모리의 이전상황의 파악이 어려움
  • OOM의 해결방법
    • 보통 이 아래방법으로 대부분 해결된다
    • Batchsize를 줄여서 메모리 부하를 줄인다
    • torch.cuda.empty_cache()를 이용하여 GPU의 메모리를 clear 한 뒤에 학습시킨다
  • 그 외에 신경쓰면 좋을점
    • GPUtil Module 사용하기
    • tensor.no_grad() 사용하기
    • 적절하게 del 명령어 사용하기
    • 다양한 batchsize로 돌려서 가능한 batchsize 알아보기
    • tensor의 float 32를 float 16으로 줄여보기

reference

부스트 캠프 ai tech 2주 4일차 Pytorch (7)


8. Hyperparameter Tuning

  • Hyperparameter 란?
    • Learning Rate, Model의 inputsize, optimizer, loss function, batchsize 등의 모델이 스스로 학습하지 않는 값을 말한다
    • 이 Hyperparameter를 조절하여 성능을 올리는 방법을 Hyperparameter Tuning이라고 부른다
    • 생각보다 스펙타클하게 성능이 좋아지지는 않는다
  • parameter에 따른 기울기값을 계산한뒤 큰값을 내는(빠르게 학습이 가능한) parameter를 찾는 기법
  • 보통 Learning rate를 찾는데 사용하는 기법이다
  • 특정 간격마다의 값으로 검색하는 Grid Layout과 랜덤한 값으로 검색하는 Random Layout 등 여러가지 방법이 존재한다
  • 최근에는 베이지안 기반의 기법들이 주도하고 있다
    • BOHB(Baysian Optimizer Hyperband)

8.2 Ray 라이브러리

  • ML과 DL의 병렬 처리를 위해 개발된 모듈이다
  • Hyperparameter Search를 위한 다양한 모듈을 제공한다
  • ML과 DL을 위해 개발되긴 했는데 분산처리(Multiprocessing)코드를 단순하고, 범용적으로 작성할 수 있게 도와준다
  • 병렬처리 양식으로 학습을 시행해서 성능이 좋지않은 process들을 제외해 가면서 최적의 hyperparameter를 찾는다
  • 아래쪽의 참고 문서를 보는것을 추천한다

reference

부스트 캠프 ai tech 2주 4일차 Pytorch (6)


7. Multi GPU 학습

  • 데이터의 양이 방대해짐에 따라서 모든 데이터들을 전부 메모리에 올리는것이 물리적으로 힘들고, 시간적으로도 소요가 많이 되어서 이를 해결하기 위해 여러대의 GPU를 병렬적으로 사용하는 방법이다
  • 크게 2가지의 방법으로 나뉜다
    • Model 병렬화 : 모델을 나눠서 학습한다
    • Data 병렬화 : 데이터를 나눠서 학습한다

7.1 Model 병렬화

  • 모델을 나눠서 여러대의 GPU에 올려서 연산하는 방법

  • 모델의 크기가 너무 커서 GPU에 올라가지 않는 상황에서 사용한다

  • 모델의 병목화, 파이프라인 구축 등의 문제로 인해 구현 난이도가 높다

    • pipeline을 제대로 구축하지 않으면 한 GPU가 연산하는동안 다른 GPU가 놀고있는 상황이 발생해 오히려 Single GPU보다 못한 상황이 발생 할 수 있다
  • Model Pipeline Paralle 예시 - Pytorch 공식 모델 병렬화 Tutorial

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class PipelineParallelResNet50(ModelParallelResNet50):
    def __init__(self, split_size=20, *args, **kwargs):
    super(PipelineParallelResNet50, self).__init__(*args, **kwargs)
    self.split_size = split_size

    def forward(self, x):
    splits = iter(x.split(self.split_size, dim=0))
    s_next = next(splits)
    s_prev = self.seq1(s_next).to('cuda:1')
    ret = []

    for s_next in splits:
    # A. s_prev는 두 번째 GPU에서 실행됩니다.
    s_prev = self.seq2(s_prev)
    ret.append(self.fc(s_prev.view(s_prev.size(0), -1)))

    # B. s_next는 A.와 동시에 진행되면서 첫 번째 GPU에서 실행됩니다.
    s_prev = self.seq1(s_next).to('cuda:1')

    s_prev = self.seq2(s_prev)
    ret.append(self.fc(s_prev.view(s_prev.size(0), -1)))

    return torch.cat(ret)
  • 아래의 그림에서 2장의 레이어 사이에 교차하는 부분이 병렬 GPU간의 교차 통신이다

    • Model 병렬화는 꽤 예전 논문인 AlexNet에서도 사용되고 있었다

Alexnet

7.2 Data 병렬화

  • 데이터를 나눠서 GPU에 할당한 후 결과의 평균을 취하는 방법
  • 각 데이터에 대한 연산을 여러 GPU에서 동시에 수행해서 학습의 효율을 높인다
    • 합칠때
  • pytorch에서는 두 가지 방식을 제공한다
    • DataParallel : 단순하게 데이터를 분배한 뒤 평균을 취하는 방식
    • Distributed DataParallel : GPU에서 모든 연산이 끝난뒤에 결과만을 공유하는 방식
  1. DataParallel:
    • 단순하게 GPU에 데이터를 분배한 뒤 평균을 취하는 방식
    • 연산이 끝난뒤에 하나의 GPU에 loss값을 모아서 gradient를 만들고 이것들 다시 다른 GPU에 전달을 해준다
    • 하나의 GPU가 특별하게 자원을 많이 사용하는 문제가 발생할 수 있다
      • 합쳐진 loss연산을 GPU는 더 많은 메모리를 사용하기 때문에 Batch사이즈의 감소등의 방법으로 메모리 부하를 줄여야 할 수도 있다
    • 매우 간단하게 pytorch에서 구현 할 수 있다
      1
      parallel_model = torch.nn.DataParallel(model) # 나머지는 일반 모델과 동일
  2. Distributed DataParallel:
    • GPU에서 모든 연산이 끝난뒤에 결과만을 공유하는 방식
    • Loss, Backward 계산 모두 각각의 GPU에서 이루어지며 연산이 완전히 끝나고 평균값이 결과로 출력된다
    • 개별 GPU마다 CPU에서 프로세스를 생성해서 할당한다 (Multiprocessing)
    • DataParallel과는 다르게 DataLoader에서 Shuffle 대신에 DistributeSampler를 사용하고, pin_memory를 활성화 시킨다

reference

Week2 - Day 4 Review


1. 오늘 하루 한 것

  • 강의
    • pytorch 8강, 9강, 10강
  • 정리
    • pytorch 8강, 9강, 10강

2. 피어세션에서 한 것

  • GIL 관한 이야기
  • FQ

3. 내일 할것

  • 5, 6, 7강 정리 마무리

4. 추가 정리 필요한것

  • 추가 정리
    • 정규식
    • pickles
    • __init__.py, __main__.py
    • numpy, pandas 정리
    • KL diverence
    • 여러가지 활성함수
    • GIL
    • GC
    • datdata augmentation Module

5. 하루 느낀점

  • 조금 피곤하다…. 정리할 것은 많다

Week2 - Day 3 Review


1. 오늘 하루 한 것

  • 강의
    • pytorch 6강, 7강
  • 과제
    • pytorch 기본과제2, 퀴즈2 완료, 심화과제 완료
  • 정리

2. 피어세션에서 한 것

  • NLP Dataset -> build_vocab_from_iterator 관해서 이야기
  • FQ
    • epoch에서 이뤄지는 모델 학습 과정을 정리해보고 성능을 올리기 위해서 어떤 부분을 먼저 고려하면 좋을지 같이 논의해보세요
      • loss
    • optimizer.zero_grad()를 안하면 어떤 일이 일어날지 그리고 매 batch step마다 항상 필요한지 같이 논의해보세요

3. 내일 할것

  • 심화과제 정리, 나머지 정리 마무리
  • pytorch 8~10강 듣고 정리

4. 하루 느낀점

  • 과제한다고 시간을 많이 소모했다
  • 이제 정리할시간!

부스트 캠프 ai tech 2주 3일차 Pytorch (5)


  • 학습시킨 모델을 다른사람들에게 공유하거나, 보관하기 위에서는 메모리에 있는 Model들을 따로 파일로 만들어서 저장할 필요가 있는데 본 글에서는 저장을 어떻게 해야하는지, 그리고 이를 이용한 Tranfer Learning 에 대해서 다룰 예정이다

5. Pytorch Model Save & Load

  • torch.save()
    • 학습의 결과를 저장하기 위한 함수이다
    • 모델의 Layer들과 Parameter, Buffer를 저장한다
    • 학습 중간중간 Model을 저장해서 최선의 성능을 가지는 결과모델을 선택하는 방식으로 사용 할 수 있다 (Checkpoint)
      • model : 학습한 모델
      • PATH : 모델을 저장할 directory
      1
      2
      3
      4
      # 모델의 weight 만 저장하는 방법
      torch.save(model.state_dict(), PATH)
      # 모델의 weight와 내부모듈 구조, Buffer까지 저장하는 방법
      torch.save(model, PATH)
  • checkpoints
    • 학습의 중간 결과를 저장해서 최선의 성능을 가지는 결과모델을 선택하는 방법
    • 보통 early stopping 기법과 함께 사용한다
      • early stopping : Loss와 Metric값을 지속적으로 확인 하면서 일정 기간이상 줄지 않으면 학습을 멈추는 방법
    • 일반적으로 epoch, loss, mertic을 함께 저장하여 확인한다
1
2
3
4
5
6
7
8
9
10
11
12
torch.save({
'epoch': epoch,
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'loss': epoch_loss,
}, f"saved/checkpoint_model_{epoch}_{epoch_loss/len(dataloader)}_{epoch_acc/len(dataloader)}.pt")

checkpoint = torch.load(PATH)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']

6. Transfer Learning

  • 다른 데이터셋으로 만든 모델을 현재 데이터셋에 맞춰서 다시 학습시키는 방법
    • 일반적으로 큰 데이터셋으로 학습시킨 모델(ex Imagenet 10K로 학습시킨 resnet50 등등)의 성능이 다른 데이터셋에 적용시키는것이 처음부터 학습하는 모델보다 학습이 빠르고, 학습이 잘된다
  • 현재 DeepLearning에서 가장 일반적인 학습 방법이다
  • 기존의 pretrained 된 모델을 backbone 모델이라고 하며 여기서 일부 Layer만 변경시켜서 학습을 수행한다
  • CV : Pytorch 공식 비전 라이브러리 TorchVision 이나 torch image model(timm)을 많이 이용한다
  • NLP : transformer 전문 라이브러리인 HuggingFace를 많이 사용한다

6.1 Freezing

  • pretrained model을 활용할때 모델의 일부분을 freeze 시켜 파라미터의 업데이트가 일어나는것을 막는 방법
  • DeepLearning의 특성상 학습이 계속 진행되면서 파라미터가 바뀌면 과거에 학습했던 정보가 희석되는 현상이 일어나는데 특히 pretrained 모델에게 안좋은 영향을 준다
  • pytorch의 requires_grad를 비활성화 시키거나 hook를 이용해서 backward의 input_grad를 0으로 고정시켜버리는 것으로도 가능하다

reference

부스트 캠프 ai tech 2주 3일차 Pytorch (4)


4. Dataset & DataLoader

  • pytorch에서 생성한 모델을 학습시키기 위해 데이터를 공급해주는 유틸리티

4.1 Dataset

  • Data를 담고 있는 Class
  • pytorch Dataset은 아래와 같이 3가지의 기본 Method로 구성되어있다
  • __init__: 초기화 함수. 필요한 변수들을 선언하고, data를 load하는 부분이다
  • __len__: 데이터의 개수를 반환하는 함수. Dataloader에서 길이등을 반환하는데 쓰인다
  • __get_item__(index): index번째의 data를 반환하는 함수. tensor로 return 해준다.
  • 데이터에 따라 Map style과 iterable style로 나뉜다
    • Map style : 일반적인 data 구조
    • iterable style : random으로 읽기 어렵거나 data에 따라 batchsize가 달라지는 data. 시계열 데이터 등에 적합하다
  • Map style 코드는 아래와 같다
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class BasicDataset(Dataset):
    def __init__(self, path):
    self.data = pd.read_csv(path)
    self.X = self.data.drop(['label'])
    self.y = self.data['label']

    def __get_item__(self, idx):
    return self.X.iloc[idx], self.y[idx]

    def __len__(self):
    return len(self.X)

4.2 DataLoader

  • Dataset을 iterable 하게 사용할 수 있도록 도와주는 Utility
  • data loading 순서 커스터마이징, 자동 batch 설정, Single-Multi process data loading등 여러가지 기능을 지원한다
1
2
3
4
5
DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,
batch_sampler=None, num_workers=0, collate_fn=None,
pin_memory=False, drop_last=False, timeout=0,
worker_init_fn=None, *, prefetch_factor=2,
persistent_workers=False)
  1. dataset
    • torch.utils.data.Dataset parameter
  2. batch_size
    • Data를 불러올 때 배치사이즈를 설정하는 항목
  3. shuffle
    • Data load 순서를 항상 랜덤하게 뽑을지를 결정하는 항목
    • torch.manual_seed 를 통해 랜덤값을 고정할 수도 있다
  4. sampler
    • Data의 index를 컨트롤 하는 방법
    • torch.utils.data.Sampler 객체를 사용한다
    • SequentialSampler : 항상 같은 순서로 elements들을 sampling한다
    • RandomSampler : 랜덤하게 sampling 한다. replacement 가능, random의 범위를 지정 가능하다 (default=len(dataset))
    • SubsetRandomSampler : 랜덤하게 sampling 한다 위의 두 기능은 없다
    • WeigthRandomSampler : 가중치에 따라 뽑히는 확률이 달라진다
    • BatchSampler : Batch단위로 sampling을 해준다
    • DistributedSampler : Multi GPU에서 분산처리를 할때 사용한다
  5. batch_sampler
    • sampler와 같지만 기본적으로 BatchSampler가 적용된 상태이다
  6. num_workers
    • GPU에 Data를 load 할때 사용할 process의 수를 결정한다
  7. collate_fn
    • sample list를 합쳐서 tensor의 minibatch로 바꿔주는 기능. map style의 dataset에서 사용한다
    • 데이터마다의 길이가 다른 NLP에서 많이 사용한다
  8. pin_memory
    • pin memory를 사용하여 GPU에 더 빠르게 data를 load하는 방법.
    • 추가적인 메모리 자원이 필요하다. 보통 parallel 모델에서 많이 사용한다
  9. drop_last
    • Data의 전체 개수가 batchsize로 나누어 떨어지지 않을때 마지막 batch를 drop를 결정하는 parameter

reference

Week2 - Day 2 Review


1. 오늘 하루 한 것

  • 강의
    • pytorch 4, 5강
  • 과제
    • pytorch 기본과제1 끝내기, 기본과제2 중간까지
  • 정리
    • pytorch 기본과제1, 4강, 5강

2. 피어세션에서 한 것

  • 4강 코드가 안돌아가요
  • 코테 문제 코드 리뷰

3. 내일 할것

  • 기본과제2 끝내기

4. 하루 느낀점

  • 오늘은 좀 할만하…지??