반응형

Introduction

  • 딥러닝 학습을 잘(?)한다는 것을 정의하기는 어렵지만, 더 빠른 시간 안에 많은 양을 학습하는 것은 매우 중요하다.
  • 딥러닝의 모델은 다수의 layer로 구성되어 있기 때문에, 각 layer의 결과가 데이터가 어느 형태로 존재하는지, 어느 layer가 병목 현상인지를 파악하는 것이 까다롭다.
  • 특히, 최근에는 모델을 직접 코드로 구현하기보다는,  pre-trained model을 사용하는 경우가 많은데, 사전에 사용하는 모델의 구조를 알지 못하면, 내부 동작을 제대로 파악할 수 없다.
  • Pytorch에서는 이러한 모델의 구조와 각 layer에서의 cost를 profiling 할 수 있는 torch profiler를 지원한다.

 

Code Sample

  • torch profiler 테스트를 위한 resnet18을 이용한 CIFAR-10 classification code이다. 
  • 모델을 직접 코드로 구현한 것이 아닌, torchvision에서 load 하였다.
  • 사용 상황을 가정하자면, load 한 모델의 구조를 모르거나, 모델에 부하가 존재하는 부분을 tuning 해야 하는데, 어느 layer를 바꿔야 할지 모르는 상황이다. 
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim

TRAIN_EPOCH = 10
TRAIN_PRINT_FREQUENCY = 200

if __name__ == '__main__':
    transform = transforms.Compose([
        transforms.RandomHorizontalFlip(),
        transforms.RandomCrop(32, padding=4),
        transforms.ToTensor(),
        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
    ])

    trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
    trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2)

    testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
    testloader = torch.utils.data.DataLoader(testset, batch_size=100, shuffle=False, num_workers=2)

    model = torchvision.models.resnet18(pretrained=False)
    num_features = model.fc.in_features
    model.fc = nn.Linear(num_features, 10)

    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model = model.to(device)

    for epoch in range(TRAIN_EPOCH):
        running_loss = 0.0
        for i, data in enumerate(trainloader, 0):
            inputs, labels = data[0].to(device), data[1].to(device)
            optimizer.zero_grad()
            outputs = model(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()

            running_loss += loss.item()
            if i % TRAIN_PRINT_FREQUENCY == TRAIN_PRINT_FREQUENCY - 1:
                print(f'Epoch: {epoch + 1}, Batch: {i + 1}, Loss: {running_loss / 200:.3f}')
                running_loss = 0.0
    print("Training finished.")

    correct = 0
    total = 0
    with torch.no_grad():
        for data in testloader:
            images, labels = data[0].to(device), data[1].to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()

    accuracy = correct / total
    print(f'Test Accuracy: {accuracy:.2%}')

 

Torch Profiler

[Setup]

  • Pytorch에서는 1.8 버전 이상부터 torch의 profiling을 위한 torch.profiler를 제공한다. 따라서, Torch 버전이 1.8 이상인 경우에는 별도의 설치가 필요 없다. 
  • 현재 설치된 Torch 버전을 잘 모른다면, Python에서 아래 명령어를 통해 확인해 보자.
import torch
print(torch.__version__)
  • 만약, torch 버전이 1.8 미만에 torch 버전을 바꿔도 문제가 없는 상황이라면, 아래 명령어를 통해 torch 버전을 업그레이드해 준다. 
pip install --upgrade torch torchvision

 

[사용법]

  • 사용방법은 매우 간단하다. 우선 torch.profiler를 import 하고, 콘텍스트 관리자(with 절)를 이용하여, profiling을 위한 부분을 감싸주면 된다. (함수 전체에 대한 profiling은 profile with 절을 @profile(acitivities~)와 같은 decorator로 처리할 수 있다.)
  • 아래는 sample code 중, train에 대한 profiling을 위한 소스이다. 주의할 점은, profiling에 memory가 많이 소모되기 때문에, train epoch을 1로 낮춰놓고 profiling을 하는 것이 좋다. (어차피, 같은 동작이 반복되기 때문에, input 하나만을 측정해도 별 문제는 없다.)
  • 모델을 GPU에서 돌려서  "ProfilerActivity.CUDA "를 포함했지만, CPU로 돌리는 환경에서는 해당 인자를 생략해도 된다. (다만, GPU 환경에서는 CPU, CUDA 인자 모두 필요함)
...
from torch.profiler import profile, record_function, ProfilerActivity
...

    with profile(activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA], record_shapes=True) as prof:
        for epoch in range(TRAIN_EPOCH):
            running_loss = 0.0
            for i, data in enumerate(trainloader, 0):
                inputs, labels = data[0].to(device), data[1].to(device)
                optimizer.zero_grad()
                with record_function("model_train"):
                    outputs = model(inputs)
                loss = criterion(outputs, labels)
                loss.backward()
                optimizer.step()

                running_loss += loss.item()
                if i % TRAIN_PRINT_FREQUENCY == TRAIN_PRINT_FREQUENCY - 1:
                    print(f'Epoch: {epoch + 1}, Batch: {i + 1}, Loss: {running_loss / 200:.3f}')
                    running_loss = 0.0
    print(prof.key_averages().table(sort_by="self_cpu_time_total"))
    print("Training finished.")

[인자 설명]

  • activities : list 형태로 입력받는다. 어떤 활동을 profiling 할 것인지를 지정한다. 가능한 활동은 다음과 같다.
    • ProfilerActivity.CPU : CPU 작업(연산, 함수 호출)에 대한 프로파일링, CPU 시간, 메모리 사용량등을 제공
    • ProfilerActivity.CUDA  : CUDA 작업(GPU 연산, 호출)에 대한 프로파일링, GPU 시간, 메모리 사용량등을 제공
  • record_shapes : bool 형태, 각 layer의 입력(input)을 기록할지 여부
  • profile_memory : bool 형태, memory를 profiling 할지 여부, False로 설정하면 time에 대한 profiling만 진행한다.
  • on_trace_ready : Profiling 결과가 준비되었을 때, 호출될 callback 함수를 지정할 수 있음. on_trace_ready 옵션을 통해, 함수를 사전 정의해, profiling 결과 등을 file 형태로 떨굴 수 있다.
  ...
    with profile(activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA], record_shapes=True,
                 profile_memory=True, on_trace_ready=finish_profiler) as prof:
  ...
  • with_stack : bool 형태, 함수 호출 stack 정보를 표기할지에 대한 여
  • with_flops : bool 형태, 실제로 계산 비용을 FLOPs로 측정한 결과 
  • with_modules : bool 형태, profiling 결과에 연산의 호출 stack에 대한 module의 계층 구조를 기록해 줌. (어떤 연산이 어떤 연산의 내부에서 호출되었는지를 나타내줌)

 

 

[결과 출력]

  • 결과는 다음과 같은 명령어로 호출할 수 있다.
print(prof.key_averages().table(sort_by="self_cpu_time_total"))
  • 결과는 table 형태로 보이는데, 아래와 같이 다양한 옵션들을 사용할 수 있다. (key_averages() 내의 인자 형태로 들어감)
    • group_by_input_shape : True로 설정하면, 동일한 입력 모양을 가진 연산 또는, 함수 호출을 grouping 할 수 있다. (모델의 input 사이즈를 보려면, 해당 옵션을 True 설정해야 한다.)
    • group_by_stack_n : 연산 또는 함수의 stack의 상위 n 단계만을 기준으로 grouping 할지 지정하는 인자
  • table의 인자도 지정할 수 있는데, table의 출력에 대한 옵션을 지정한다.
    • sort_by : table을 어떤 기준으로 order by 할지 (default : None)
    • row_limit : 몇 개까지 표시할지
    • header : header를 표시할지 (default : None)
    • top_level_events_only : 해당 옵션을 True 설정하면, 최상위 호출 단계까지만 표시
  • 아래는 sample code에 대해 profiling을 수행한 결과이다. (CPU 시간이 큰 10개만 추출)

  • 결과에서 보이는 각 칼럼은 다음과 같다.  
  • CPU Time 관련 
    • Self CPU % : 연산 or 함수 호출이 소비한 CPU 시간의 백분율 (전체 실행 시간에서 해당 연산이 소요한 CPU 시간)
    • Self CPU : 해당 연산 or 함수 호출이 소비한 총 CPU 시간
    • CPU  total % : 해당 연산 or  함수 호출과 그 하위 호출에서 소요된 총 CPU 시간의 백분율
    • CPU total : 해당 연산 or 함수 호출과 그 하위 호출에 의해 사용된 총 CPU 시간
    • CPU time avg : 해당 연산 or 함수 호출의 평균 CPU 시간 (평균적으로 해당 연산이 소요되는 시간)
  • CUDA Time 관련  
    • Self CUDA : 해당 연산 or 함수 호출이 소비한 총 CUDA 시간
    • Self CUDA % : 해당 연산 or 함수 호출이 소비한 총 CUDA 시간의 백분율
    • CUDA total : 해당 연산 or 함수호출과 그 하위 호출에서 소요된 총 CUDA 시간
    • CUDA time avg : 해당 연산 or 함수호출과 그 하위 호출에서 소요된 평균 CUDA 시간
    • # of Calls : 해당 연산 또는 함수 호출의 호출 횟수
  • Model Input 관련 
    • Input Shapes : record shapes를 True로 하고, key_averages에 group_by_input_shape를 true로 지정한 경우에만 보인다. 각 연산의 input shape이 보인다.
  • CPU memory 관련 (snapshot 형태기 때문에 사용 전과 후의 memory 사용 delta값이 나온다. 즉, 음수가 될 수 있다.) 
    • CPU Mem : 연산 or 함수 호출이 소비한 CPU의 메모리 총 용량 
    • Self CPU Mem : 연산 or 함수 호출이 직접적으로 사용한 CPU 메모리 용량
  • CUDA memory 관련 (snapshot 형태기 때문에 사용전과 후의 memory 사용 delta값이 나온다. 즉, 음수가 될 수 있다.)
    • CUDAMem : 연산 or 함수 호출이 소비한 CUDA의 메모리 총 용량 
    • Self CUDAMem : 연산 or 함수 호출이 직접적으로 사용한 CPU 메모리 용량
  • 연산량 관련
    • Total MFLOPs : 연산 or 함수 호출이 실행될 때, 총 수행된 MFLOPs 수 

 

Torch 모델에서 torch Profiling을 통해, 부하가 되는 부분이나, Layer의 input size 등을 확인할 수 있다. 해당 profiling은 모델에서 부하가 되는 부분을 개선하거나, 하드웨어 확장에 대한 의사결정, Batch size 조절 등 다양한 model 개선에 사용될 수 있다.

반응형

Introduction

  • Python으로 짜인 Code를 서비스하다 보면, CPU 100%나 Memory Fault, 실행시간이 길어지는 등 다양한 문제를 만나게 된다. 
  • 자신이 개발한 코드에서는 직감적으로 어느 부분이 문제가 될지를 간파할 수 있지만, 다른 사람이 짠 코드에서 문제에 원인이 되는 부분을 찾아내기는 매우 어렵다. 
  • 일반적으로 가장 쉽게 떠올릴수 있는 방법은 실행시간은 time 모듈을 이용한 print 디버깅이나 unittest, CPU나 memory는 작업 관리자를 통해 확인하는 방법이다. 하지만, 이 방법들은 대략적인 정도만 알아낼 수 있고, 어느 부분이 문제가 있는지 진단하기 매우 어렵다.
  • Python에서는 Profiling을 위한 다양한 도구들을 가지고 있어, code 분석이 매우 용이하다. 어떤 것들이 있는지 확인해보자!

 

Code Sample

  • 각 도구들을 Test 하기 위한 sample code이다. 
  • code는 각각 validation_check, data_preprocessing, outlier_remove, data_sort, data_cal_half_avg 함수를 거쳐 최종 결과를 내도록 되어있다. 
import numpy as np

def data_validation_check(sensor_value):
    try:
        for i in sensor_value.split("|"):
            float(i)
        return True
    except:
        print("Error")
        return False


def data_preprocessing(sensor_value):
    sensor_value = sensor_value.split("|")
    sensor_value = list(map(float, sensor_value))

    return sensor_value


def outlier_remove(sensor_value):
    data_mean = np.mean(sensor_value)
    data_std = np.std(sensor_value)

    lower_bound = data_mean - 3 * data_std
    upper_bound = data_mean + 3 * data_std

    sensor_value = [i for i in sensor_value if lower_bound < i and upper_bound > i]
    return sensor_value


def data_sort(sensor_value):
    return np.sort(sensor_value)


def data_cal_half_avg(sensor_value):
    return np.mean(sensor_value[int(len(sensor_value) * 0.5):])


def run(sensor_value):
    if data_validation_check(sensor_value):
        sensor_value = data_preprocessing(sensor_value)
        sensor_value = outlier_remove(sensor_value)
        sensor_value = data_sort(sensor_value)
        sol = data_cal_half_avg(sensor_value)
        return sol
    else:
        return "Error!"


if __name__ == '__main__':
    sensor_value = "|".join([str(i) for i in range(10000000)])
    print(run(sensor_value))

 

memory_profiler : Memory Profiling 

  • Python은 머신러닝 같은 데이터 처리를 위한 언어로 자주 사용되기 때문에, 메모리 관련된 이슈에 자주 직면하게 된다. 
  • 따라서, 어느 부분이 memory를 많이 소모하는지 확인이 필요한 경우가 많다.
  • Python에서는 "memory_profiler"를 통해 memory 사용량을 측정할 수 있다.

 

[설치 방법]

  • 설치 방법은 매우 간단하다. pip을 이용하여 설치한다. 
pip install memory_profiler

[사용 방법]

  • 사용 방법도 매우 간단하다. memory_profiler의 profiler을 import 하고, memory profiling을 하고자 하는 함수에 "@profiler" 데코레이터를 사용하고, 프로그램을 실행하면 끝난다.

 

[사용 예시]

from memory_profiler import profile
import numpy as np

@profile
def data_validation_check(sensor_value):
    try:
        for i in sensor_value.split("|"):
            float(i)
        return True
    except:
        print("Error")
        return False
...

 

[결과]

  • 결과는 다음과 같이, 테이블 형태로 터미널에 출력된다. 

  • 각 칼럼은 다음을 의미한다.
    • Line # : code 내 몇 번째 줄인 지 
    • Mem Usage : 해당 라인이 실행되기 전의 메모리 사용량
    • Increment : 해당 라인의 실행으로 추가적으로 사용된 메모리의 양
    • Occurrences : 각 라인이 실행된 횟수
    • Line Contents : 라인 코드 내용
  • 즉, memory profiler는 각 라인이 수행되기 전과 후를 스냅숏으로 메모리의 증분값을 보여주어, memory 사용량을 나타낸다. (따라서, memory를 해제하는 경우 등에는 음수값이 나올 수 있다.)
  • memory_profiler의 결과를 file 형태로 저장하기 위해서는, logger를 사용하거나, 아래와 같이 프로그램 수행 결과를 txt 형태로 내리도록 하면 된다.
python -m memory_profiler main.py > log.txt
  • memory_profiler를 run 한 후, 아래 명령어로 그래프를 그릴 수 있는데, 사실 이 그래프로 뭘 알 수 있는지는 의문이다. (그냥 시간에 따른 메모리 사용량만 표시된다.) 
mprof plot -o memory_profiler_result.png

 

[Sample 수행 결과]

...

Filename: main.py

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
    14    118.7 MiB    118.7 MiB           1   @profile
    15                                         def data_preprocessing(sensor_value):
    16    807.1 MiB    688.3 MiB           1       sensor_value = sensor_value.split("|")
    17    501.2 MiB   -305.8 MiB           1       sensor_value = list(map(float, sensor_value))
    18                                         
    19    501.2 MiB      0.0 MiB           1       return sensor_value


Filename: main.py

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
    21    501.2 MiB    501.2 MiB           1   @profile
    22                                         def outlier_remove(sensor_value):
    23    501.3 MiB      0.1 MiB           1       data_mean = np.mean(sensor_value)
    24    501.3 MiB      0.0 MiB           1       data_std = np.std(sensor_value)
    25                                         
    26    501.3 MiB      0.0 MiB           1       lower_bound = data_mean - 3 * data_std
    27    501.3 MiB      0.0 MiB           1       upper_bound = data_mean + 3 * data_std
    28                                         
    29    579.9 MiB     78.5 MiB    10000003       sensor_value = [i for i in sensor_value if lower_bound < i and upper_bound > i]
    30    579.9 MiB      0.0 MiB           1       return sensor_value


Filename: main.py

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
    32    503.5 MiB    503.5 MiB           1   @profile
    33                                         def data_sort(sensor_value):
    34    579.9 MiB     76.3 MiB           1       return np.sort(sensor_value)


Filename: main.py

Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
    36    197.5 MiB    197.5 MiB           1   @profile
    37                                         def data_cal_half_avg(sensor_value):
    38    197.5 MiB      0.0 MiB           1       return np.mean(sensor_value[int(len(sensor_value) * 0.5):])


7499999.5
mprof: Sampling memory every 0.1s
running new process
running as a Python program...
  • 사실, 언뜻 생각하기엔 sort에서 가장 많은 memory가 사용될 것이라고 생각했지만, 의외로 outlier 제거를 위한 순회나, split등에서 많이 사용된다는 것을 알 수 있다.

[주의점]

  • memory의 profile은 memory의 snapshot과 기록에 많은 추가 시간이 소요되기 때문에, memory profile과 실행시간 측정은 동시에 진행하면 안 된다.
  • memory가 snapshot 형태로 기록되기 때문에, memory 소요값이 절대적이지 않고, 실행 환경 등에 따라 다르다는 점을 꼭 기억하자!

 

Execution Time Profiling  : line_profiler

  • Execution Time은 Python에서 가장 민감한 부분이기도 하다. 
  • 보통 time 모듈을 이용하여 디버깅을 진행하기도 하는데, 매구 간마다 디버깅을 위해 시간을 찍는 것도 매우 비효율적이다.
  • 이런 비효율을 덜어줄 수 있는 Execution Time profiling 도구 line_profiler이다. 

[설치 방법]

  • 설치 방법은 memory_profiler처럼 pip을 이용하여 설치한다. 
pip install line_profiler

[사용 방법]

  • 사용 방법은 더 간단하다. 실행 시간을 측정하고 싶은 함수에 "@profile" 데코레이터를 넣어주고, 터미널에서 아래 명령어를 실행해 주면 된다. 
kernprof -l -v main.py

 

[사용 예시]

# memory_profiler가 import 안되도록 한번 더 확인!
import numpy as np

@profile
def data_validation_check(sensor_value):
    try:
        for i in sensor_value.split("|"):
            float(i)
        return True
    except:
        print("Error")
        return False
...

 

[결과]

  • 결과는 다음과 같이, 테이블 형태로 터미널에 출력된다. 

  • 우선 맨 위에, 시간 unit과 각 함수 total 수행 시간이 표시된다. (전체 total 수행 시간이 아니다.)
  • 아래 각 칼럼은 다음을 의미한다.
    • Line # : code 내 몇 번째 줄인 지 
    • Hits: 각 라인이 실행된 횟수
    • Time : 수행 시간
    • Per Hit: 각 실행당 걸린 시간
    • % Time : 함수 내 실행 시간에서 차지하는 퍼센트
    • Line Contents : 라인 코드 내용
  • line_profiler의 결과를 file 형태로 저장하기 위해서는, 아래 명령어를 사용하면 된다. line_profiler를 실행하면, 실행 파일에 대한 lprof의 파일 결과가 떨어지는데, 이를 text 파일로 떨구면 된다.
python -m line_profiler main.py.lprof > log.txt

 

[Sample 수행 결과]

  • 첫 생각과는 다르게, validation check가 가장 많은 시간이 소요되는 것을 확인할 수 있다. 
Timer unit: 1e-06 s

Total time: 4.83922 s
File: main.py
Function: data_validation_check at line 3

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     3                                           @profile
     4                                           def data_validation_check(sensor_value):
     5         1          0.5      0.5      0.0      try:
     6  10000000    1970775.1      0.2     40.7          for i in sensor_value.split("|"):
     7  10000000    2868439.0      0.3     59.3              float(i)
     8         1          1.5      1.5      0.0          return True
     9                                               except:
    10                                                   print("Error")
    11                                                   return False

Total time: 1.48381 s
File: main.py
Function: data_preprocessing at line 13

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    13                                           @profile
    14                                           def data_preprocessing(sensor_value):
    15         1     369882.5 369882.5     24.9      sensor_value = sensor_value.split("|")
    16         1    1113930.3 1113930.3     75.1      sensor_value = list(map(float, sensor_value))
    17                                           
    18         1          1.3      1.3      0.0      return sensor_value

Total time: 2.66128 s
File: main.py
Function: outlier_remove at line 20

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    20                                           @profile
    21                                           def outlier_remove(sensor_value):
    22         1     337871.3 337871.3     12.7      data_mean = np.mean(sensor_value)
    23         1     366792.1 366792.1     13.8      data_std = np.std(sensor_value)
    24                                           
    25         1          5.7      5.7      0.0      lower_bound = data_mean - 3 * data_std
    26         1         10.6     10.6      0.0      upper_bound = data_mean + 3 * data_std
    27                                           
    28         1    1956595.8 1956595.8     73.5      sensor_value = [i for i in sensor_value if lower_bound < i and upper_bound > i]
    29         1          0.7      0.7      0.0      return sensor_value

Total time: 0.415683 s
File: main.py
Function: data_sort at line 31

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    31                                           @profile
    32                                           def data_sort(sensor_value):
    33         1     415683.2 415683.2    100.0      return np.sort(sensor_value)

Total time: 0.003097 s
File: main.py
Function: data_cal_half_avg at line 35

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    35                                           @profile
    36                                           def data_cal_half_avg(sensor_value):
    37         1       3097.0   3097.0    100.0      return np.mean(sensor_value[int(len(sensor_value) * 0.5):])

 

[주의점]

  • memory profiler와 마찬가지로, line_profiler로 수행시간을 차분하여, 라인 별 수행시간을 구하는 것이다. 따라서, 절대적이지 않고 수행 환경에 따라 달라진다. 

 

Process Profiling  : py-spy

  • CPU는 운영체제의 스케줄링이나 프로세스 등에 따라 동적으로 변하기 때문에, 함수마다의 수행시간을 정확히 측정하는 것은 매우 어렵다.
  • 따라서, CPU는 리눅스 명령어나 윈도 작업관리자를 통해, 프로그램 수행 후 observation 형태로 간접적으로 파악하는 방법 밖에 없다.
  • 또한, CPU는 사용량이 많더라도, 조치하기가 매우 어렵다. 따라서, 너무 CPU 사용량이 많은 부분만 확인하는 정도의 이상감지용 지표로 활용하는 것이 좋다.
  • CPU 사용률을 직접 측정하기는 어렵지만, 각 Process에 걸리는 부하를 간접적으로 알 수 있는 도구가 있는데, 바로 py-spy이다. 

[설치 방법]

  • 설치 방법은 앞선 profiler들처럼 pip을 이용하여 설치한다. 
pip install py-spi

[사용 방법]

  • 사용 방법은 단순히 아래 명령어를 터미널에 입력해 주면 된다.
py-spy record -o profile.svg -- python main.py

 

[결과]

  • 결과는 위에서 지정한 profile.svg(이름은 바꿔도 된다.) 파일의 스택 플레임 그래프 형태로 떨어진다.

  • 결과는 다음의 형태를 가진다. 
    • 함수 호출 스택이 위에서부터 바깥쪽의 함수를 의미한다. 예를 들어, 예제의 run 함수 →  data_preprocessing 함수 → split 함수 형태로 위부터 아래로 표시된다.
    • Box 표시 : 각 함수가 Box로 표시된다. Box의 크기가 해당 함수의 소비 시간을 나타낸다. 따라서, 상위 함수는 하위 여러 함수들의 박스들로 구성된다. 
    • 색상 : 어두운 색상에 있는 함수일수록 깊은 호출 스택을 의미한다. 
  • 일반적으로, 다음과 같은 결과 해석이 가능하다.
    • 우선 Box가 큰 함수의 부분이 부하의 원인이 되는 경우가 많기 때문에 주목해서 봐야 한다.
    • Box가 큰 함수들 중, 호출 스택이 깊은 함수들은 여러 번 중첩되는 경우가 많은데, 이 부분의 중첩을 줄여야 개선이 가능하다.
    • 다른 profiler들과 다르게, 내부의 import 된 함수 단위까지 표시가 되기 때문에, 어떤 구조로 함수가 호출되는지 이해가 쉽다.

 

[주의점]

  • 사실, 수행 시간을 통해, 간접적으로 프로세스의 중첩이나, 부하를 확인하는 것이기 때문에, CPU 사용률과 직접적인 연관이 없다. (참고용으로만 사용하는 것이 좋다.)

 

 

 

이 밖에, Python 내장 profiler인 CProfile 같은 Profiler와, Palanteer, Pyinstrument 등의 Profiler 들도 존재한다. 하지만, 프로그램의 수행결과로 논문을 쓸 것이 아니라면, 다음과 같은 툴로도 충분하다고 생각한다. 

반응형

 

 

InstructGPT 배경 설명

  • InstructGPT는 2022년 OpenAI에서 발표한 논문으로, ChatGPT의 근간이 되는 모델이다.
  • 사실, GPT가 ChatGPT라는 제품 형태로 출시될 수 있던 이유는 해당 논문의 접근 방법(법적인 문제나, 거짓말등을 완화할 수 있는 방법)을 선택한 것이 크지 않나 싶다.

 

Abstract

  • Language Model(LM)의 크기가 커진다고 해서, 사용자의 의도를 더 잘 따르게 되는 것은 아니다. (LM이 거짓말이나, toxic한 말들을 생성할 수 있다.)
  • 이 논문에서는 인간의 feedback을 학습하여, LM이 인간의 의도에 align 될 수 있는 방법을 제시한다.
  • 레이블러가 작성한 prompts와 OpenAI API를 통해 수집된 prompts를 데이터셋으로 사용하여, GPT-3을 supervised learning 방식으로 fine-tuning 한다. 
  • 그 후에 model이 생성한 ouput들을 인간의 feedback으로 순위를 매겨서, 추가 fine-tuning 과정에 사용한다.
  • 최종 모델을 InstructGPT라고 한다. 
  • InstructGPT는 GPT-3보다  NLP 성능은 아주 조금 떨어지지만, 신뢰도와 toxic reduction 관점에서 더 우수하다. 

 

Introduction

[배경]

  • LM에 prompt를 제공하는 방식은 많이 사용되고 있다. 하지만, 이런 모델들은 거짓말이나, 편향적 혹은 toxic 한 말 등 의도하지 않은 대로 표현한다. (Chat-GPT를 사용해 본 사람들은 무슨 말인지 알 것이다.)
  • 이것은 LM의 objective 때문인데, 단순히 webpage 등에서 가져온 문장들로 next token을 예측하는 방식으로 학습되기 때문에, user의 의도는 고려되지 않는다.
  • 저자들은 이러한 user 의도를 고려하지 않는 것을, LM objective가 "misaligned"되어 있다고 말하고, 실제 application에서 활용되기 위해서는 해당 부분에 대한 고려가 필요하다고 한다.

[논문 소개]

  • 논문에서는 LM과 user들의 의도의 align을 위해, 명확한 지시를 제공하는 explicit intention과, 신뢰성있는 문장을 사용할 것 등과 같은 implicit intention을 모두 고려해야 한다고 한다.
  • 이를 위해, fine-tuning 방법을 사용했고, 특히, human feedback에 대한 강화학습을 통해, GPT-3을 fine-tuning하여 다양한 class의 instruction을 따르도록 했다.
  •  이 과정에서 인간이 선호도에 따라 reward를 주었다. 
  • 과정을 소개하면,
    1. screening test를 통해, 40명의 labeler를 고용했다. 
    2. OpenAI API를 통해 수집된 prompts와 labeler가 작성한 prompts를 통해 desired ouput에 대한 데이터셋을 생성하고, supervised learning baseline을 학습하는 데 사용한다.
    3. 그다음, 인간이 model의 ouput들을 평가한 데이터셋을 생성한다.
    4. 데이터셋을 이용하여 인간의 선호도에 따른 reward model(RM)을 학습한다.
    5. 마지막으로, RM을 reward function으로 PPO 알고리즘을 사용하여, supervised learning baseline을 fine-tuning한다. 
  • 이렇게 만들어진 최종 모델을 InstructGPT로 명명한다.

[Evaluation]

  • 주로, 모델은 labeler들이 testset에 대한 model의 ouput을 평가한다.이때, 평가자는 학습과정에 참가하지 않았던 사람이다. 
  • 다양한 NLP task에 대한 evaluation도 진행한다.

 

[결과]

  • Labeler들은 InstructGPT의 ouput을 GPT-3의 것보다 선호한다.
  • InstructGPT의 output은 GPT-3의 것보다 신뢰도가 높다.
  • InstructGPT는 toxicity 면에서 GPT-3보다 조금 개선되었지만, bias 측면에서는 차이가 없다.
  • 인간의 feedback에 대한 강화학습 부분을 수정하여, NLP dataset에서의 성능하락을 최소화했다.
  • Evaluation에서 training set 생성에 참여하지 않은 사람들에게서도 InstructGPT가 좋은 평가를 받은 것은 선호도의 일반화를 의미한다.
  • Public NLP dataset은 InstructGPT가 사용되는 방식을 정확히 반영하지 못한다.
  • InstructGPT가 인간의 feedback에 의한 강화학습에 포함되지 않는 지시에도 일반적으로 잘 따른다. 즉, 훈련에 사용된 분포에만 국한하는 게 아니라, 그 밖에 존재하는 분포에서도 지시에 잘 따른다.
  • 하지만, InstructGPT는 아직 완벽하지 않다.

 

Methods and Experimental details

[Method]

  •  크게 3개의 step을 통해 학습했다. (2, 3번은 계속 반복된다.)
    1. demonstration에 대한 데이터를 모으고, supervised 방식으로 학습한다. (기존 LM 방식)
    2. comparision 데이터를 모으고, reward model을 학습한다.
    3. PPO 알고리즘을 사용하여, LM을 fine-tuning 한다. 

[Dataset]

  • Dataset을 수집한 내용이 나온다. 기본적으로 OpenAI API를 통해 text prompts를 구성했고, user ID당 200개까지만 prompts를 제한하였다. userID 기준으로 testset을 분리했다. (혹시 몰라서, 개인정보로 filtering을 한번 더 했다. 1인이 다계정을 사용하는 것을 막으려고 한 것 같다.)
  • 데이터셋은 다음과 같이 구성되어 있다.
    • Plain : labeler들에게 무작위 task에 대한 질문을 한 결과
    • Few-Shot : labeler들이 만든 지시문과 지시문에 대한 여러 개의 질문과 대답 쌍
    • User-based : User들이 사용 사례를 정하고, 그에 대한 prompts를 생성

[Task]

  • 데이터셋에 포함된 prompt들은 generation, question answering, dialog, summarization, extractions 등 다양하다. 

 

[Model]

 

  • 기본 모델은 GPT-3 pretrained model을 사용한다. GPT-3을 기반으로 아래의 3가지 다른 technique을 사용하여 model을 학습한다.

2023.06.12 - [NLP 논문] - GPT-3 (Language Models are Few-Shot Learners) 논문 리뷰

 

GPT-3 (Language Models are Few-Shot Learners) 논문 리뷰

GPT-3 배경 설명 GPT-3은 요즘 많이 사용하는 ChatGPT의 근간이 된 논문으로, 2020년 OpenAI에서 NIPS에 발표한 논문이다. Language Model의 parameter가 꾸준히 늘어가는 추세였는데, GPT-3에서는 기존의 가장 큰

devhwi.tistory.com

 

1. Supervised fine-tuning (SFT)

  • labeler의 demonstrations를 supervised-learning으로 학습한다. (16 epochs, cosine learning rate decay, dropout : 0.2)

 

2. Reward Modeling (RM)

  • SFT model의 마지막 layer를 없애서 사용한다.
  • 동일 input에 대한 2개의 model ouput들 사이의 comparision을 학습한다. (rewards는 다른 응답에 비해 선호될 log odds로 정함) 
  • 몇 개의 ouput에 대해서 비교할 것인지를 K라고 하면, 비교는 k*(k-1)/2번만큼 이뤄져야 한다.
  • 이때, 단순히 전체 prompt 간의 비교 값을 shuffle 하여 학습하면, overfitting이 발생한다. 이를 해결하기 위해, 한 batch에 하나의 prompt에 대한 모든 comparison이 담기도록 학습했다. 
  • 학습을 위한 loss function은 다음과 같다.

r(x, y) :  scalar output of the reward model from x,y

y_w: 더 선호되는 ouput

y_l : 덜 선호되는 output

 

 

3. Reinforcement Learning (RL)

  • SFT model을 PPO(Proximal Policy Optimization) 알고리즘을 통해 학습한다. 
  • 아래의 objective가 최대화되는 방향으로 RL 학습을 진행한다.

π^RL : RL policy로 학습된 모델

π^SFT : SFT로 학습된 모델

  • 해당 논문에서는 γ=0으로 한다.

 

[Evaluation]

  • 모델이 얼마나 "align" 되었는지 확인하기 위해, align의 의미를 먼저 정의한다.
  • InstructGPT의 목적이 user의 의도를 반영과 일치하는 언어 모델을 만드는 것이기 때문에, model의 align을 helpful, honest, harmless로 정의한다. 
  • 모델이 helpful을 평가하기 위해서는 labeler의 선호도 평가에 의존한다.
  • 모델의 honest를 평가하기 위해서, 모델이 진실을 답하는지 거짓을 답하는지 평가한다. 이를 위해, closed doamin task에 대해 모델이 거짓말하는 정도를 평가하고, TruthfulQA 데이터셋을 이용하여 평가한다. 
  • 모델의 harm을 평가하기 위해, labeler가 ouput이 context에 적절한지 여부, 특정 집단에도 ㅐ한 비하를 포함하였는지, 성적이거나 차별적인 콘텐츠를 포함하였는지의 여부를 평가한다. 또한, RealToxicityPrompts와 CrowS-Pairs 데이터셋을 이용하여 평가한다.
  • InstructGPT의 의도에 맞게 fine-tuning 하는 것은 GPT-3 모델의 성능을 떨어뜨릴 수 있다. 이를 평가하기 위해, GPT-3에 제출된 prompt를 이용하여 성능을 평가한다. 또한, public NLP dataset으로도 평가한다.

 

Results

  • Labeler들이 InstructGPT의 output을 GPT-3의 것보다 선호한다. 

  • InstructGPT 모델은 신뢰도 측면에서 GPT-3에 비해 향상이 있었다.

  • InstructGPT 모델은 GPT-3보다 toxicity를 줄일 수 있었지만, bias는 줄이지 못했다.

  • InstructGPT는 GPT-3에 비해, align-tax(align을 위해 NLP의 성능감소가 생기는 것)가 있지만, 그렇게 크지 않다.

그 외 

  • 논의해 볼 사항과 미래 영향, 해결해야 할 과제들이 적혀있다. (철학적인 내용인 것 같아 별도로 적지는 않는다.)

 

Reference

Ouyang, Long, et al. "Training language models to follow instructions with human feedback." Advances in Neural Information Processing Systems 35 (2022): 27730-27744.

 

 

총평

Chat-GPT의 가장 큰 단점으로 지적받는 '거짓말'에 대한 해결 방법을 제안한 논문이다. 이 논문을 읽기 전에는 단순히 학습 데이터를 filtering 하는 것이 방법 아닐까 하는 생각이 있었는데, 데이터를 filtering 하면 cost가 매우 크고, 데이터가 편향될 수 있어서 이 문제를 어떻게 풀까 고민했었다. 근데 이 논문은 매우 간단한 방법으로 (물론 prompt 등 데이터 생성은 간단하지 않지만) 이 문제를 완화하였다. 물론, 아직 갈 길이 멀지만, GPT-1부터 논문의 마지막 부분에 항상 존재하던, 발생할 수 있는 문제들에 대해 해결을 방법을 제안한 의미 있는 논문이라고 생각한다. 

반응형

 

 

PaLM 배경 설명

  • PaLM은 google에서 2022년에 발표한 LLM 관련 논문이다.
  • GPT 이후의 NLP 분야의 거의 모든 논문이 그랬듯, Model Paramter를 더 크게 늘렸고, GPT-3의 흐름을 따라, task-specific 한 model이 아닌, 다양한 NLP 분야를 cover 하는 모델로 학습된다.
  • 사실 GPT 등장 이후로 LLM 모델에서의 구조 변화는 크게 없다. Model parameter를 계속 늘리고, 이에따라 성능은 계속 좋아진다.  따라서, 기존 LLM과 달라진 부분에 집중하여 논문 리뷰를 시작한다.

 

Abstract

  • Language Model은 few-shot 방식을 사용하여, task-specific 학습에 필요한 데이터의 양을 줄이면서, 좋은 성능을 보여줬다.
  • few-shot에서 scaling의 효과를 확인하기 위해, 540 billions parameter로 구성된 Transformer 기반의 Pathways Language Model(PaLM)을 소개한다.
  • 이 논문에서는 PaLM을 multiple TPU Pods 들을 효율적으로 학습할 수 있는 "Pathway" 방법을 이용하여 학습한다.
  • 여러 language understanding과 generation 분야에서 SOTA를 달성하여, scaling의 장점을 보여준다. 
  • 특히, 몇가지 task들에서는 finetuned 방식의 SOTA를 능가하는 좋은 성능을 보여주고, BIC-bench에서 인간의 능력을 뛰어넘는다.
  • BIC-bench에서 model의 scale에 따라 performance가 discontinuous 한 향상을 보여준다. 
  • 또한, PaLM은 multilingual task와 code 생성에서 좋은 성능을 보인다. 

Introduction

  • 최근(이 당시) 몇년동안, 언어 이해와 생성 분야에서 large language model을 pre-training과 fine-tuning 방식으로 학습하는 방식으로 큰 성능 향상을 이뤘다.
  • 하지만, 이러한 모델들은 finetuning 과정에서 상당한 양의 task-specific 데이터를 필요로 하고, task에 맞는 학습을 위한 cost가 든다. 
  • GPT-3은 large autoregressive language model들로 few-shot을 진행하는 방식으로 학습하여, 많은 양의 task-specific 데이터와 model parameter updating 과정이 필요 없도록 하였다. (GPT-3 관련 내용은 아래 링크 참조)

2023.06.12 - [NLP 논문] - GPT-3 (Language Models are Few-Shot Learners) 논문 리뷰

 

GPT-3 (Language Models are Few-Shot Learners) 논문 리뷰

GPT-3 배경 설명 GPT-3은 요즘 많이 사용하는 ChatGPT의 근간이 된 논문으로, 2020년 OpenAI에서 NIPS에 발표한 논문이다. Language Model의 parameter가 꾸준히 늘어가는 추세였는데, GPT-3에서는 기존의 가장 큰

devhwi.tistory.com

  • 그 후로, GPT-3 기반의 후속 모델(GLam, Gopher, Chinchilla, LaMDA 등)들이 등장해 좋은 성능을 보였다.
  • 이러한 모델들의 성능 향상을 위한 주요 접근 방법은 다음과 같다.
    1. model의 depth와 width를 늘린다. (model size를 늘림)
    2. model의 학습을 위한 token의 양을 증가
    3. 다양한 dataset을 사용하거나, dataset을 정제
    4. sparsely activated module등으로 학습 cost는 유지하면서 model capacity를 증가
  • 이 논문에서도 위의 방식들을 따라, 780 billion token을 이용하여 540 billion parameter를 가진 Transformer 기반 model을 학습한다.
  • 이를 위해, "Pathways"라는 수천개의 acclerator chip들 사이에 large network 학습을 효율적으로 해주는 방식을 도입한다. 
  • 이 논문에서 소개한 모델 PaLM은 natural language , code, mathematical reasoning 등의 task들에서 few-shot 방식의 SOTA를 달성했다. 

 

Model Architecture

  • PaLM은 몇가지 변경을 제외하고. Transformer의 decoder-only 모델을 그대로 따른다. 
  • 기존 Transformer 모델들과 달라진 점은 다음과 같다.
    • SwiGLU Activation : SwiGLU는 Relu를 대체하기 위해, 구글이 개발한 Swish Activation 함수에 GLU(Gated Linea Unit)을 적용한 함수이다. 사용한 이유는 기존 연구들에서 성능이 좋아서라고 한다.
    • Parallel Layers : 병렬 처리를 해, 기존의 Transformer block의 식을 바꿨다. Large Scale에서 15% 정도 학습 속도가 빠르지만, 성능 하락은 모델이 커질수록 거의 없다고 한다. 
      • 기존  $$ y = x+ MLP(LayerNorm(x+Attention(LayerNorm(x)))) $$
      • 변경  $$ y = x+ MLP(LayerNorm(x))+Attention(LayerNorm(x)) $$
    • Multi-Query Attention : query, key, value의 feature size가 [k, h]인 기존 transformer 구조와 달리, key와 value를 [1, h] 크기로 사용한다. (query는 [k, h]) 이것이 model 성능에 영향을 미치지 않으면서, 학습 시간 cost 절감에 큰 역할을 한다. 
    • RoPE Embedding : position embedding에 long-sequence 처리에 유리한 RoPE Embedding을 사용하였다. 
    • Shared Input-Output Embeddings
    • No Biases : Bias를 사용하지 않았다. 이것이 large model 안정성을 높여준다고 한다.
    • Vocabulary : SentencePiece(256k token)을 사용했다. 

 

Model Scale Hyperparameters

  • 3개의 모델(8B, 62B, 540B)을 사용했다. 

 

Training Dataset 

  • PaLM의 pretraining dataset으로는 high-quality의 780 billion의 token이 사용되었다. 
  • dataset은 webpage, books, Wikipedia, news articles, source code, social media conversation등 다양한 소스에서 가져왔다. 
  • 특히, Github에서 Source code를 가져왔는데, Java, HTML, JavaScript, Python, PHP, C# 등등의 196GB의 소스 코드를 포함한다. 또한, Source code 수집 시, 중복을 제거하여 다양한 코드 등을 수집했다.

 

Training Infrastructure

사실 PaLM이 주목받는 이유가 Pathways를 사용하는 해당 Chapter 때문인것 같다. 아마도, Pathways를 논문으로 굳이 읽는 이유는 점점 늘어나는 LLM의 학습을 위해 엔지니어링의 최강자인 Google은 어떤 방법을 사용했을까? 때문일 것이다. 

 

  • 우선, PaLM의 codebase는 JAX와 T5X이고 TPU v4 Pods를 통해 학습되었다. PaLM 540B는 data center network(DCN)로 연결된 2개의 TPU v4 Pods를 통해 model parallelism과 data parallelism을 사용해서 학습되었다. 
  • 각 TPU Pods들은 3072개의 TPU v4 chip들로 구성되어 있고, 768 hosts에 연결되어 있다. (즉, 총 6148개의 chip과 1536개의 host가 존재한다.)
  •  이 논문에서는 pipeline parallelism(layer 등의 단위의 parallelism을 사용)을 사용하지 않은 것을 큰 장점으로 여기는데, pipeline parallelism에는 몇 가지 문제가 있기 때문이다.
    • pipeline 구조에서는 pipeline이 비어서 쉬게되는 devices들이 생긴다.
    • 각 micro-batch의 weight를 불러오기 위해, 높은 memory bandwidth를 요구한다. 
    • 일부 케이스에서는 software complexity가 늘기도 한다. 
  • 이 논문에서는 pipeline 없이 PaLM 540B를 6144개의 TPU chip을 사용하여 학습했다는 것을 강조한다. 
  • 각 TPU v4 Pod은 model parameter들을 full copy 한다. (3072개 chip에 12개 방법의 model parallelism과 256개의 data parallism을 사용한다. 2가지 parallelism을 동시에 사용하는 것을 2D parallelism이라고 한다.)

[학습 방법]

  • forward pass에서는 data parallel axis로 weight를 모으고, 각 layer로부터 하나의 fully sharded activation이 저장된다. 
  • backward pass 때,  forward pass에 사용되었던, activation을 재사용하는 것이 아니라, activation을 다시 계산해서, 재생성하여 사용한다. 이것은 더 큰 배치에서 학습 처리량을 높이도록 해준다. 

[Pathways]

  • PaLM은 이름대로, TPU v4 Pod들에 Pathways system을 사용한다.  PaLM은 Pathways의 client-server 구조를 사용하여 pod level에서의 2가지 방법의 data parallelism을 구현한다. 
  • Python client가 training batch의 절반씩을 각 pod으로 보내고, 각 pod은 forward와 backward computation을 data, model parallelism을 이용해 계산한다.
  • Pod들은 gadient를 다른 pod에 보내고, 각 pod은 local(자신이 만든 것)과 remtoe(다른 pod으로부터 받은 것) gradient를 모아서, parameter update에 사용한다. 
  • 이런 구조는 single Pod을 이용한 학습보다 정확히 2배의 throughput 증가는 일어나지 않고, 약 1.95배 정도의 throughput 증가를 이끄는데, 이것은 network적 이슈와 관련있다. 따라서, network 설계를 열심히 했다고 한다. 

 

 

Training Efficiency

  • 이전의 많은 language model에서는 accelerator efficiency를 hadware FLOPs utilization(HFU)를 통해 진행하였다. 이것은 이론적 peak FLOP 개수로 관찰되는 FLOP 개수를 나눈 것이다.
  • 하지만, 이 지표는 약간의 문제가 있다.
    • hardware FLOP의 갯수는 system-depedent와 implementation-dependent, compiler의 design chice에 따라 달라진다. 
    • 관찰되는 FLOP 갯수는 count와 track에 사용되는 방법론에 따라 달라진다. 이것은 hardware performance를 통해 측정되는 것이 아닌, analytical accounting을 사용하기 때문이다. 
  • 이러한 문제가 있어서, LLM training efficiency를 측정하기에 HFU는 충분치 않다.
  • 이 논문에서는 implementation indepenet 하고 system efficiency에 따라 정확한 비교를 가능하게 해주는 model FLOPs Utilization(MFU)를 제시한다. 
  • MFU는 observed throughput(tokens-per-seconds)를 이론상 최대 system operating peak FLOP으로 나눈 값이다. 
  • 특히, 이론상 최대 throughput은 forward와 backward pass를 계산하면 된다. 
  • 여하튼, 본인들이 만든 지표로 비교해보았을때, PaLM이 가장 효율적인 연산을 하고 있다고 한다. 

 

Evaluation

  • 역시, parameter를 많이 사용해서, 기존 few-shot 기반의 SOTA들을 모두 갱신하였다.

  • 심지어 SuperGLUE등에서는 fine-tuning 기법들과 맞먹을 정도로 좋은 성능을 보여준다. 

  • Reasoning : GSM8K에서 8-shot evaluation을 진행했을 때, 기존 SOTA인 GPT-3을 이긴다.

  • Few-shot의 장점인 다양한 분야에 적용성에서, 놀랄만한 부분은 Code를 고치는 것이나 Translation에서도 좋은 성능을 보인다는 것이다. 

 

Conclusion

  • 이 논문에서는 few-shot LM 방향으로의 연장선인 540B parameter로 구성된 PaLM을 소개한다. 
  • PaLM은 NLP 29개 분야 중 28개에서 few-shot SOTA를 기록했다. 또한, BIG-bench에서 5-shot LaLM은 인간의 평균 performance보다 높은 성능을 보였다. source code 이해나 생성에서도 좋은 성능을 보였다. 
  • reasoning에서 chain-of-though prompting을 사용했을 때, 좋은 모습을 보였다. 
  • 이에 따른 결론은 다음과 같다. 
    • few-shot NLP에서 scale에 따른 성능 향상은 아직 끝나지 않았다. (log-linear하게 증가했다.) 이 성능 향상은 사실은 discontinuous 하다. 
    • reasoning task들에서 보인 성과들은 중요한 의미를 갖는다. 이것은 model의 결정 과정에서 어떤 과정을 이용하여 최종 결과가 나왔는지를 end user에게 설명할 수 있게 할 것이다. 아직 멀었지만, 잘 만든 prompt가 이것을 가속화할 것이라고 한다. 
    •  network architecture와 training schema에 많은 여지를 남겼다. Pathways를 시작으로 많은 구조들이 생겨날 수 있을 것이다. 

 

Reference

Chowdhery, Aakanksha, et al. "Palm: Scaling language modeling with pathways." arXiv preprint arXiv:2204.02311 (2022).

 

 

총평

  • 읽은 논문 중, 비교적 최근 논문인데, 이쯤때부터, 모델 크기가 커질수록 성능은 무조건 커진다는 가정하에, 어떻게 빠르고 효율적으로 학습할 것인가 하는 문제에 봉착한 것 같다.
  • 적어도 NLP 분야에서는 앞으로 엔지니어와 H/W의 비중이 점점 더 커질 것 같다. 
반응형

Spark에 대해 집중적으로 공부하기 전에 Spark의 전체적인 구조와 개념들에 대해 알아보자. 


Spark 기본 아키텍처

  • 이전 글에서 언급한대로, Spark는 클러스터 자원 내에서 분산 처리를 위한 프레임워크이다. (물론, 단일 머신 내에서도 사용 가능하다.)
  • Spark는 클러스터 내의 데이터 처리를 총괄하는 클러스터 매니저Spark Application을 요청하고, 클러스터 매니저가 Spark Application에 대한 자원을 할당하는 방식으로 처리된다. 
  • Spark Application은 2종류의 Process로 구성된다.
    • Driver Process : Cluster 내의 단 하나의 노드에서 실행됨. 대한 정보, 사용자 프로그램과 입력에 대한 응답, Excutor Process의 작업에 대한 분석, 배포, 스케줄링 역할을 수행.  
    • Executor Process : Cluster 환경에 맞게 여러 개의 노드에서 실행될 수 있음. Driver Process가 할당한 작업을 처리하고, 진행 상황을 Driver에 전송. 

스파크 아키텍처 (출처 : https://learn.microsoft.com/ko-kr/dotnet/spark/what-is-spark)

 

SparkSession

  • Spark 아키텍처를 보면, Driver process 내에 SparkSession이 존재하는 것을 볼 수 있다. 
  • SparkSession은 Spark에서 작업을 수행하기 위한 진입점이다. (이전 버전의 SparkContext, SQL Context 등을 통합)
  • SparkSession은 Spark Application과 데이터 소스와의 연결, Cluster 간의 연결을 관리하고, 데이터 처리를 위한 API를 제공하는 역할을 한다.

 

Spark의  API

  • Spark는 기본적으로 Scala, Java, Python, SQL, R 등의 다양한 언어를 지원한다. 사용자들은 본인들이 편한 언어를 통해서, Spark의 API를 호출하면 된다. 
  • 이것은 Spark Session이 각 언어에서 작성된 코드를 API 형태로 받아, Spark 코드로 변환해주기 때문에 가능하다. (이 때문에, 어느 언어의 API를 사용하던 실행 시간등에 큰 차이가 없다.) 
  • Spark는 추상화 수준에 따라 크게 2가지 종류의 API를 제공한다.
    • 저수준의 비구조적 API : RDD, SparkContext, Accumulator, Broadcast Variable
    • 고수준의 구조적 API : DataSet, DataFrame, SQL Table
  • Spark 초기 버전에는 RDD와 같은 저수준의 비구조적 API가 많이 사용되었지만, 더 복잡하고 낮은 추상화 수준을 가지기 때문에, 최신 버전에서는 DataFrame이나 Dataset과 같은 고수준의 구조적 API가 많이 활용된다. 
  • 실제 고수준의 구조화된 API들도, 실제 연산 시에는 RDD로 처리된다. (Spark 내부적 처리)

 

Spark 데이터 처리 방식

  • Spark는 데이터구조는 기본적으로 불변성의 특성(변경하지 못함)을 가지고 있다. 데이터 변경을 위해서는 원하는 변경 방법을 Spark에 "Transformation"라는 명령을 보내야한다.
  • Spark의 연산은 기본적으로 지연 연산 방식을 사용한다. 데이터에 직접적인 수정을 하지 않고, Raw 데이터에 적용할 Transformation의 실행 계획을 생성하고, 물리적 실행 계획으로 Compile한다.
  • 실행 계획에 대한 실제 연산은 "Action" 명령을 통해 이뤄진다.  (즉, 실제 사용될때 연산이 진행된다.)

 

 

 

반응형

OpenAI

 

GPT-3 배경 설명

  • GPT-3은 요즘 많이 사용하는 ChatGPT의 근간이 된 논문으로, 2020년 OpenAI에서 NIPS에 발표한 논문이다. 
  • Language Model의 parameter가 꾸준히 늘어가는 추세였는데, GPT-3에서는 기존의 가장 큰 모델보다 거의 10배 정도의 많은 parameter를 넣을 정도로 큰 모델을 사용하였다.
  • Model scaling-up을 통해 few-shot에서도 Task-Specific 한 기존의 finetuning 모델들의 성능에 필적하는 성능을 보여주었다. (이 시도가 현재 ChatGPT를 만든 것 같다.)

 

Abstract

  • 최근 NLP task에서 large corpus의 pre-training을 기반으로한 언어모델들이 큰 효과를 내고 있다. 
  • 하지만, 대부분의 모델은 task-agnostic의 재학습이 필요하고, 이 과정에서 task에 맞는 수많은 학습 데이터들이 필요하다.
  • 이 논문에서는 언어모델의 사이즈를 키워서, 현재 task-agnostic SOTA 모델들의 성능에 필적할수 있을 정도로 few-shot 모델 성능을 향상했다.
  • 특히, 175 billion parameter로 구성된 GPT-3을 few-shot으로 학습하였는데, 다양한 NLP 분야에서 좋은 성능을 보였다.
  • 추가적으로 GPT-3은 인간이 작성한 기사와 구분하기 어려운 뉴스 샘플등을 생성할 수 있다는 사실을 발견하였고, 이 발견과 사회적 영향에 대해 논의한다.

Introduction

[문제]

  • 최근 NLP 분야에서의 pre-trained language model의 트렌드는 다양하고 많은 분야에서 큰 향상을 일으켰지만, 이러한 모델들은 task-agnostic 한 방식을 채택하고 있어, 원하는 task에 대한 수많은 데이터셋과 fine-tuning 과정을 필요로 한다.
  • 이러한 방식은 아래의 문제가 있다.
    1. 실용적 관점에서 모든 새로운 task에 대해서 labeling 된 데이터가 필요하여, 언어모델의 확장성을 제한한다. 각 task 학습을 위한 dataset이 필요한데, 그것을 모으는 것은 매우 어렵고, task를 확장할 때마다 반복해야 한다. 
    2. 큰 모델에서 좁은 분포의 데이터를 학습시키면, 잘못된 상관관계를 학습할 수 있다. pre-training 단계와 fine-tuning 단계를 사용한 모델들에서 이런 문제가 있는데, 이러한 모델들은 일반화가 잘 안 되는 문제가 있다.
    3. 인간은 새로운 lagnuage task를 배우기 위해, 많은 데이터를 필요로 하지 않는다는 점이다. 인간의 언어능력에는 일반화와 적용을 자주 활용하는데, NLP 모델도 이러한 인간의 언어 능력과 동일한 수준이 되어야 한다. (task-agnostic은 그렇지 않다는 뜻인 듯하다.)
  • 이러한 문제를 풀기 위해, meta-learning을 활용한 방법들이 있다. 몇 가지 방법들이 등장했지만(특히, GPT-2), fine-tuning 방법에 비해 성능이 매우 떨어진다. 
  • 한편, Language modeling의 최근 트렌드는 model의 capacity를 키우는 것이다. 이러한 트렌드에서 model의 parameter를 키울수록 언어모델의 성능이 향상되는 경향이 있음을 볼 수 있다. (이전까지 17 billion paramter까지 등장)

[모델 소개]

  • 이 논문에서는, 175 billion의 parameter를 사용하는 "GPT-3"이라는 language model을 실험하여, model의 parameter가 커질수록 성능이 향상됨을 확인한다. 
  • 또한, GPT-3을 각각 few-shot learning, one-shot learning, zero-shot learning을 통해 학습하고, 비교해 본다.

[실험 결과]

  • Few-shot에서 단어에서 관계없는 symbol을 지우는 간단한 task를 수행해 보았을 때, task에 대한 설명이 많을수록(zero에서 few shot으로 갈수록), 성능이 향상되었고, model의 parameter가 많을수록 성능이 급격하게 향상되는 것을 보인다.

  • GPT-3이 약한 분야도 있는데, inference task나 reading comprehension과 같은 분야이다.

 

GPT-3 Approach

  • model, data, training을 포함한 pre-training 과정은 model의 크기, dataset의 다양성, 길이, 크기가 커졌다는 것을 빼고는 GPT-2의 방법과 비슷하다.

2023.05.27 - [NLP 논문] - GPT-2 (Language Models are Unsupervised Multitask Learners) 논문 리뷰

 

GPT-2 (Language Models are Unsupervised Multitask Learners) 논문 리뷰

GPT-2 배경 설명 GPT-2는 OpenAI에서 2019년 발표한 논문이다. GPT-2는 기존의 대규모 LM 구조인 GPT-1의 구조를 따르지만, 학습을 Unsupervised Multitask Learning을 사용하여, 범용성 있는 자연어처리를 할 수 있

devhwi.tistory.com

  • in-context learning도 GPT-2 논문의 방법과 비슷하지만, context 내에서 구조적으로 조금 다른 몇 가지 setting을 시도해 볼 수 있다.  task-specific 데이터를 얼마나 활용하냐에 따라, 4가지 setting으로 분류한다.
    • Fine-Tuning (FT) : 최근에 가장 일반적인 방법이다. 원하는 task에 맞는 데이터셋을 통해 학습한다. 이 과정에서 수많은 데이터가 필요하다. FT의 장점은 성능이 매우 좋다는 점이다. 가장 큰 단점은 각 task를 학습할 때마다, 수많은 데이터가 필요하다는 점이다. GPT-3도 FT로 학습할 수 있지만, 논문의 목적은 성능이 아니기 때문에, 별도로 학습하지는 않았다.
    • Few-Shot (FS) : inference 과정에서 conditioning으로 이용할 수 있는 약간의 task에 대한 설명이 주어지지만, 직접 학습에 활용하지는 않는다.  FS는 task에 대한 설명과 함께 task에 대한 K개의 example들이 제공된다. (K를 model의 context window라고 하고, 대략 10~100의 값을 갖는다.) FS의 장점은 task-specific 한 데이터를 많이 줄일 수 있다는 것이고, narrow distribution에서 학습할 수 있는 잘못된 상관관계에 대한 가능성이 줄어든다는 것이다. 단점은 FT 방식의 SOTA에 비해 성능이 떨어진다는 점이다. 또한, task specific한 데이터가 여전히 필요하다는 점이 문제이다. 
    • One-Shot (1S) : task에 대한 example이 하나만 주어진다는 것이 Few-shot과 다른 점이다. 굳이 one-shot을 few-shot과 나누는 이유는 one-shot이 인간의 커뮤니케이션과 비슷하기 때문이다. 
    • Zero-Shot (0S) : 어떤 task인지에 대한  설명만 있고, 아무 example이 주어지지 않는다. 이 방법은 편의성과 확장성, 잘못된 상관관계를 피할 수 있는 점등에서 매우 좋지만, 학습이 매우 어렵다. 

[Model]

  • Sparse Transformer 논문의 sparse attention을 사용한 것 외에는 GPT-2와 같은 모델과 아키텍처를 사용하였다. 
  • model size와 성능 간의 상관관계 확인은 ㄹ위해, 8가지 다른 size의 모델을 사용했다. (125 millions ~ 175 billions)
  • 이 중, 제일 큰 모델을 GPT-3이라고 한다. 

GPT-3 8개의 다른 parameter size 모델

[Training Dataset]

  • 데이터가 풍부한 Common Crawl Dataset을 사용하였지만, 필터링되지 않은 데이터가 많이 섞여있어서, 데이터셋의 품질을 향상하기 위한 3가지 방법을 추가하였다.
    • Common Crawl Dataset에서 high-quality reference corpora와 비슷한 데이터들을 다운로드하였다.
    • 문서 수준에서 퍼지 일치 기반 중복 제거를 활용하여, overfitting 등을 방지하였다.
    • 이미 알려진(앞선 NLP 논문등에서 활용), high-quality reference corpora 데이터들을 데이터셋에 포함하였다.
    • 단순히 양에 따라서 가중치를 둔 것이 아닌, 데이터셋에 품질이 높을수록 높은 가중치를 두었다. (아래 weight in training mix에 해당) 

GPT-3에서 활용한 데이터셋

[Training Process]

  • large model일수록 큰 batch size를 사용하지만, 적은 learning rate를 필요로 한다. 
  • 학습과정에서 gradient noise scale을 측정하여, batch size 선택에 사용하였다.
  • Out of memory를 막기 위해, model parallelism을 사용했다.  

 

Results

  • size가 각기 다른 8개의 GPT-3 모델의 learning curve를 비교하였는데, size가 큰 모델일수록 언어모델의 성능이 향상됨을 보인다.
  • 이 과정에서, traigning compute와 performance는 power-law를 따른다고 알려졌는데, 모델 size가 일정 수준 이상에서는 power-law의 기댓값보다 더 좋은 성능을 보였다. 
  • 이것이 training dataset을 cross-entropy를 통해 학습해서(외워버려서) 그런 것 아닐까 하는 의심이 들 수도 있지, cross-entropy가 다양한 NLP 분야의 task에서 일관적으로 성능 향상을 보임을 보인다. 


1. Language Modeling

  • Penn Tree Bank(PTB) dataset에서 zero-shot perplexity를 계산하였다. 
  • training dataset에 포함된 Wikipedia와 관련된 4가지 task들은 제외했다. 
  • 새로운 SOTA가 되었다.

[LAMBADA]

  • LAMBADA dataset은 text 내에서 long-range dependency를 테스트한다. (context를 읽고, sentence의 마지막 word를 추정하는 문제)
  • Zero-Shot 환경에서 기존 SOTA보다 8%가량 좋은 성능을 보였다.

[HellaSwag]

  • HellaSwag dataset은 story와 몇 가지 instruction이 주어지면, 가장 best의 ending을 뽑는 문제이다. 
  • StoryCloze dataset은 story에 따른 가장 그럴듯한 ending sentence를 뽑는 문제이다.
  • 둘 다, SOTA보다는 떨어지지만, 좋은 성능을 얻었다.


2. Closed Book Question Answering

  • Closed Book Question Answering은 다양한 지식에 대한 context가 없는 답변을 생성하는 Task이다.
  • Model Size가 커졌을 때(GPT-3)에서 SOTA를 넘어서는 결과를 보여주었다. (아마도, task 자체가 광범위함을 포함하고 있어서, task-specific 학습이 크게 힘을 발휘 못하는 것 같다.)


3. Translation

  • Translation의 학습에서는 93% 텍스트가 영어였고, 7% 만 다른 언어들을 포함하였고, 별도의 목적함수를 사용하지 않았다. (그냥 언어 구분 없이 똑같이 학습하였다.)
  • Zero-Shot 환경에서는 기존 Zero-Shot들보다 오히려 성능이 낮지만, Few-Shot 환경에서는 특정 task에 한해서는 Supervised SOTA를 넘기도 하였다. 


4. Winograd-Style Task

  • 해당 Task는 대명사가 지칭하는 것을 맞추는 문제로, 추론 능력을 확인할 수 있다. 
  • SOTA에 비해서는 낮지만, RoBERTa에 근접할 정도로 좋은 성능을 보인다. 


5. Common Sense Reasoning

  • 특정 데이터셋에서는 SOTA를 보였지만, 다른 데이터셋에서는 낮은 성능을 보여주었다. 
  • 전체적으로 OpenBookQA를 제외한 데이터셋에서는 Task 설명이 큰 효과를 보지 못했다.


6. Reading Comprehesion

  • GPT-3이 약한 분야이다. CoQA를 제외하고는 SOTA와 매우 큰 성능 차이가 난다. 


7. Super GLUE

  • BERT와의 비교를 위해 SuperGLUE를 Test 한다. 
  • SOTA에는 못 미치지만, 대부분의 task에서 BERT와 필적하거나, 오히려 더 높은 성능을 보여주기도 한다.


이외, 많은 Task들이 있지만, 대체적으로 비슷한 경향을 보여줘서, Result는 여기까지만 넣도록 하겠다.(힘들다.)

다만, 숫자 연산이나 뉴스 기사 생성, 문법 교정 등 다양한 분야에서 좋은 성능을 보여준다. 

 

Memorization에 대한 검증

  • GPT-3의 학습 데이터가 매우 방대하고, Web Crawling을 통해 만들어졌기 때문에, Training dataset에 원하는 Benchmark의 데이터가 포함되어 있을 가능성이 있다. (Data Contamination이라고 표현한다.) 즉, memorization으로 위의 좋은 performance를 낼 수 있다는 것이다. 
  • Training Curve를 보았을 때, 학습에 따라 Validation Loss와 Train Loss가 비슷한 추세로 줄어드는 것을 볼 수 있는데, 이것은 memorization이 없다는 증거가 된다. (특정 task를 외웠으면, train loss만 급격히 줄어드는 구간이 있을 것이기 때문에)

  • 이것 말고도, memorization을 증명하기 위해, 데이터셋을 clean 하는 실험이 있는데,  실험 내용이 사실 이해가 잘 안 간다. 여하튼 Data를 Clean해도 성능에 딱히 영향이 없기 때문에, memorization은 아니라는 뜻이다.

Limitations

  • GPT-3는 좋은 성능을 보여주었지만, 몇 가지 한계가 있다.
    • 성능적 한계 : 몇가지 NLP task에 대해서는 좋지 않은 성능을 보여준다.
    • 구조 & 알고리즘의 한계 : GPT-3은 bidirectional 구조나 denoising 같은 NLP 분야의 성능을 향상하는 방법들은 고려하지 않았다.  
    • 본질적 한계 : 본 논문은 LM을 scaling up 하는 것에 집중하였는데, pretraing objective에 근본적 한계가 있다. 현재 obejective는 모든 token을 동일한 가중치를 준다. 즉, 중요한 token을 예측하는 것이 NLP 성능 향상에 더 중요하지만, 모두 동일하게 학습한다.  단순 Scaling을 떠나서(한계가 있기 때문에), NLP의 목적을 위한 objective 등을 학습해야 할 것이다.
    • pre-training 과정에서 비효율성 : GPT-3은 인간에 비해 너무 많은 텍스트를 학습한다. 학습과정에서 효율성을 향상해야 한다.
    • Few-Shot learning의 불확실성 : 실제로 GPT-3이 Few-Shot Learning을 통해 학습한 것인지 모호하다. 
    • Expensive Cost :  GPT-3의 parameter가 매우 많기 때문에 training & inference cost가 매우 크다. 
    • 설명 불가능 : 모든 Deep Learning이 겪는 문제처럼 GPT-3도 결과에 대한 해석이 불가능하다. Training data에 대한 bias가 발생할 수 있다.

Reference 

Brown, Tom, et al. "Language models are few-shot learners." Advances in neural information processing systems 33 (2020): 1877-1901.

총평

  • 논문이 매우 길고, 저자도 실험도 매우 많다. 그래서 그런지 읽기 매우 힘들었다. (내 착각인지 모르겠지만, GPT-2와 다르게 논문이 약간 문과틱 감성이 난다.)
  • 실험이 매우 많고, 한계와 사회적 파급력 등에 대해서 매우 자세하고 광범위하게 다뤄서, 정말 좋은 논문이다.
  • 다만, Parameter가 매우 크고, Task example이 정교하게 만들어진 것 같아, 재현을 하기는 어려울 것 같다. 

 

반응형

 

데이터를 다루는 입장에서는 데이터 처리 속도를 신경 쓰지 않을 수 없다. 최근 운영 중인 시스템의 처리 속도를 개선하기 위해 다양한 방법을 고민하던 중,  Spark에 대한 관심이 생겨서, 공부한 내용을 정리해 보기로 한다. 


Introduction

Apache Spark란?

  • Apache Spark는 빅데이터 처리를 위한 오픈소스 분산, 통합 컴퓨팅 시스템이다. 

[통합]

  • Apache Spark 전에는 각기 다른 시스템으로 구성된 데이터의 흐름(SQL 처리, 데이터 스트리밍, 데이터 분석)을  개발자가 직접 조합하여 Application을 만들었다.
  •  Apache Spark 내에서는 다양한 데이터 흐름을 처리하는 시스템 및 API들을 일관성있는 API로 통합하여, 개발자들이 더 쉽고 효율적인 코드를 개발할 수 있도록 하였다. 

[분산]

  • 많은 데이터 분석가 & 데이터 엔지니어들이 Apache Spark에 집중하는 이유는 분산 처리를 이용한 빠른 데이터 처리 때문이다. Apache Spark는 같은 데이터 분산 처리 플랫폼인 Hadoop을 개선한 플랫폼인데, Hadoop과의 차이를 통해서 Spark의 특징을 살펴보자. 
  Hadoop Spark
목적 데이터 분산 처리 & 저장 데이터 분산 처리 & 분석
데이터 처리 모델 HDFS를 사용한 데이터 저장
MapReduce 기반 분산 처리 모델
인메모리 기반 분산 처리 모델
저장소 HDFS HDFS, S3, Elastic Search 등
속도 작은 규모의 데이터셋의 경우 Hadoop이 더 빠를 수 있음
(Spark 메모리 Load 시간 때문)
반복적인 처리가 필요한 작업에서는 Hadoop의 최대 100배 빠름
호환성 별도의 Package와 연계해야함 언어 : Python, R, Java, Scala, SQL 등 지원
분석 : MLlib, GraphX API 등 지원
  • 대체적으로 Spark가 더 좋은 플랫폼이구나라는 생각을 할 수 있지만, Hadoop이 Spark에 비해 갖는 장점이 있는데, 바로 데이터 보존에 대한 안정성이다. Spark는 인메모리 기반의 데이터 처리를 진행하기 때문에, 서버 다운 등의 장애에 취약하다. 따라서, 최근(?)에는 Hadoop과 Spark를 같이 사용하는 경우가 많다.

 

반응형

Introduction

[문제]

  • Python은 매우 간단하고, 응용 모듈이 많아서, 많이 선호되지만, 속도가 중요한 프로그램에서는 항상 문제가 된다.(Python 코드의 속도 튜닝의 끝은 다른 언어로 다시 개발하는 것이라고 할 만큼, Python은 느리다.)
  • 하지만, Python에만 존재하는 응용 패키지들이 많아서, Python 언어를 유지하면서 속도 튜닝이 필요한 경우가 많다.
  • Python이 느린 이유는 많지만, 아래의 이유가 치명적이다.
    1. 인터프리터 언어 : 코드를 한줄 씩 읽고, 해석하는 식으로 동작한다.
    2. 동적 타이핑 : Python은 형식을 지정해주지 않아, 코드 실행 중에 Type 지정이 필요하다.

[Numba]

  • Numba는 이러한 Python의 실행 속도를 개선하기 위한 대표적인 라이브러리로, JIT(just-in-time)이라는 Compiler를 통해, Numpy 배열, 함수, loop의 속도를 개선하였다.
  • 단순히, 패키지 import와 decorator 사용만 하면 되어서, 매우 간단하다. 
  • Numba는 Array 처리 등의 무거운 Python 코드를 동적으로 compile 하여, 기계어로 변환한다. 이 과정에서 type 정보 분석 & 최적화를 하여, 속도를 최적화한다. 

 

Numba 설치 방법

  • Numba의 설치 방법은 매우 간단하다. 
pip install numba

또는

conda install numba

 

Numba 사용

  • numba는 기본적으로 함수에 @jit의 decorator를 넣어주면 된다. 
from numba import jit

@jit
def numba_func(input):
    sol = np.tanh(input)
    return sol
  • numba에는 몇 가지 옵션을 사용할 수 있는데, 각 옵션은 다음과 같다. (해당 옵션등은 함께 적용할 수 있다.)
Option 명 설명 주의점 사용 예시
nopython Python을 interpreter로 처리하지 않고, Compile을 진행 Python에서만 존재하는 라이브러리(pandas 등)를 사용하면 Error가 뜸 @jit(nopython=True)
nogil GIL(Global Interpreter Lock)을 사용하지 않음. Thread 간 안전성 문제가 있을 수 있음, 메모리 사용량이 많아질 수 있음 @jit(nogil=True)
cache Compile 결과를 디스크에 캐싱하여, 이후에 재사용 할 수 있도록 함 Numba 버전이나, 코드 변경 시, 캐시 파일이 의미가 없을 수 있음.  @jit(cache=True)
parallel 병렬 처리를 위해 사용, 반복문과 배열의 연산을 병렬화하여 cpu 코어를 활용  가능 추가적인 메모리 사용과 오버헤드 발생 가능, 병렬화로 인해 항상 성능 향상을 보장하지 못함. @jit(parallel=True)

 

  • 일반적으로 nopython 모드를 True로 하는 경우가 많다. 이것은 Python interpreter와 상호작용을 최소화하여, 함수를 빠르게 수행할 수 있기 때문이다.
  • nopython 모드는 자주 사용되기 때문에 njit decorator를 통해 사용되기도 한다. 
from numba import njit

//@jit(nopython=True)와 같음
@njit
def numba_func(input):
    sol = np.tanh(input)
    return sol

 

Numba 성능 비교

  • H/W와 코드 환경, 연산하는 함수에 따라, 컴파일 및 최적화 정도는 천차만별이다. 따라서, 절댓값이 주목하기보다는 대략적으로 이런 효과가 있구나 정도로 생각해 주길 바란다.
import numpy as np
from numba import jit
import time

@jit
def numba_func(input):
    sol = np.tanh(input)
    return sol 


def no_numba_func(input):
    sol = np.tanh(input)
    return sol 


if __name__  == '__main__':
    data_length = 1000000000
    input_data = np.arange(data_length)

    start_time = time.time()
    numba_func(input_data)
    end_time = time.time()
    print("Elapsed Time (with numba):",end_time-start_time)

    start_time = time.time()
    no_numba_func(input_data)
    end_time = time.time()
    print("Elapsed Time (without numba):",end_time-start_time)
  • 길이가 10억개의 데이터에 대해서 jit과 jit 옵션이 없는 코드를 실행해 보았다. jit을 사용한 것이 빠른 속도를 보여주는 것을 확인할 수 있다.

  • 길이가 1억개의 데이터에 대해서 jit과 jit 옵션이 없는 코드를 실행해 보았다. jit을 사용하지 않은 것이 더 빠른 속도를 보여주는 것을 확인할 수 있다. 

  • 길이가 1000개 정도의 소규모(?) 데이터에서 실행 결과, jit의 overhead가 확실히 존재한다는 것을 확인할 수 있다.

 

→ 다만, JIT의 성능 향상에 대한 연산량은 H/W 등의 실험환경에 크게 영향을 받는다. 꼭, 실제 코드를 돌릴 환경에서 테스트해 보고 적용하는 것을 추천한다.

 

Numba 주의점

  • Numba는 대용량의 연산이 아닌, 소규모의 연산에서는 오히려 느린 성능을 보여준다. 이는 Numba의 JIT 컴파일에 약간의 오버헤드가 있기 때문이다.
  • Numba에서 성능 향상을 보기 위해서는, 최대한 간단하고, 배열 위주의 작업들을 대용량 데이터에서 사용해야한다. 제어 흐름이 복잡한 코드는 최적화에 한계가 있다.
  • nopython 옵션 적용 시, JIT이 컴파일 할 수 없는 경우에는 에러가 뜬다. Input과 Output의 타입, 함수 내의 연산이 명확한 경우에만 사용하도록 한다. 
  • Numba가 효과있는 데이터 양등을 실제 프로그램이 돌아갈 환경에서 실험해 보고, 데이터 연산량을 대략적으로 계산하여,  JIT을 적용한 함수와 적용하지 않는 함수를 각각 놓고 분기를 치는 것도 좋은 방법이다. 
  • 실제로 운영 단에 있는 코드 들에서는 하나의 함수에 복잡한 내용이 섞여있는 경우가 많다. 이러한 경우, numpy나 for문만 별도의 함수로 나눠서 JIT을 적용해줘야한다. 이러한 변경은 최적화에서는 이점이 있을지 모르지만, 가독성에서는 해가될 수 있다.

Numba는 Python의 고질 병인 속도 문제를 해결하기 위해, 등장한 라이브러리다. 비록, 대용량 데이터에서만 효과를 볼 수 있다는 아쉬운 점도 있지만, 이런 옵션이 존재한다는 것이 어딘가 싶다. (사실, 대용량 데이터가 아니면, 굳이 속도 문제가 치명적이진 않을 것이다.)  만약, 운영 환경에서 간혹 존재하는 대용량 데이터에 고통받고 있다면, 예외처리용으로 사용해도 좋을 것 같다. 

+ Recent posts