콘텐츠로 이동

파이토치 자연어처리 BERT-base

이 튜토리얼에서는 자연어처리 모델 중 하나인 BERT의 마스크드 언어모델(masked language model) 태스크를 사용합니다. 마스크드 언어모델은 문장 내에서 마스킹 된 단어를 예측하는 태스크입니다. 이 튜토리얼을 통해 RBLN 컴파일러를 사용하여 허깅페이스 BERT-base 모델을 컴파일하고 RBLN 런타임을 사용하여 추론하는 방법을 배울 수 있습니다.

이 튜토리얼은 두 단계로 구성되어 있습니다:

  1. 파이토치 BERT-base 모델을 컴파일하고 로컬 저장소에 저장하는 방법
  2. 컴파일된 모델을 로드하고 추론하는 방법

사전 준비

시작하기에 앞서 아래의 파이썬 패키지들이 설치되어 있는지 확인합니다:

참고

세부 사항을 생략하고, 빠르게 컴파일 및 추론하는 방법을 확인하려면 요약을 참고하세요. 컴파일 및 추론을 위한 모든 코드가 정리되어있어 빠르게 프로젝트를 시작 할 수 있습니다.

네이티브 RBLN API

1단계. 컴파일 방법

모델 준비

허깅페이스에서 제공하는 transformers 라이브러리를 통해 BertForMaskedLM 모델을 로드합니다.

1
2
3
4
5
6
7
import torch
from transformers import BertForMaskedLM
import rebel  # RBLN 컴파일러

# 허깅페이스 파이토치 BERT-base 모델 준비
bert_model = BertForMaskedLM.from_pretrained("bert-base-uncased", return_dict=False)
bert_model.eval()

모델 컴파일

rebel.compile_from_torch() 함수를 이용하여 준비 된 파이토치 모델(torch.nn.Module)을 컴파일 할 수 있습니다.

# 모델 컴파일
MAX_SEQ_LEN = 128
input_info = [
    ("input_ids", [1, MAX_SEQ_LEN], "int64"),
    ("attention_mask", [1, MAX_SEQ_LEN], "int64"),
    ("token_type_ids", [1, MAX_SEQ_LEN], "int64"),
]
compiled_model = rebel.compile_from_torch(
    bert_model,
    input_info,
    # 호스트에 NPU가 설치되어 있는 경우, 아래의 `npu`인자는 명시하지 않아도 자동으로 감지됩니다. 
    npu="RBLN-CA12",
)

호스트 머신에 NPU가 설치되어 있는 경우, NPU를 자동으로 감지하여 사용하기 때문에 rebel.compile_from_torch 함수에 npu 인자를 생략할 수 있습니다. NPU가 설치되지 않은 호스트 머신에서 컴파일을 수행할 경우 컴파일 함수에 npu 인자를 명시해야 합니다. 그렇지 않으면 에러가 발생합니다.

현재 지원하는 NPU는 RBLN-CA02, RBLN-CA12입니다. 사용하려는 NPU의 이름은 NPU가 설치된 호스트 머신에서 rbln-stat 명령어를 통해 확인할 수 있습니다.

컴파일된 모델 저장

compiled_model.save() 함수를 통해 컴파일된 모델을 로컬 저장소에 저장할 수 있습니다.

# 컴파일된 모델 로컬 저장소에 저장
compiled_model.save("bert_base.rbln")

2단계. 추론 방법

RBLN 런타임 모듈 rebel.Runtime()을 통해 이전 단계에서 컴파일된 모델을 로드하고 추론할 수 있습니다.

입력 데이터 준비

transformers 라이브러리에서 제공하는 BertTokenizer를 사용하여 입력문자열을 토큰화하고, 마스크드 언어모델의 입력문자열로 사용하기 위해 사전처리 과정을 수행합니다.

1
2
3
4
5
6
7
8
9
import torch
from transformers import BertTokenizer, pipeline
import rebel  # RBLN 런타임

# 입력 데이터 준비
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
text = "the color of rose is [MASK]." 
MAX_SEQ_LEN = 128
inputs = tokenizer(text, return_tensors="pt", padding="max_length", max_length=MAX_SEQ_LEN)

추론

RBLN 런타임 모듈 rebel.Runtime()을 통해 컴파일된 모델을 로드하고, RBLN 런타임 모듈의 멤버함수 run()을 통해 추론할 수 있습니다. 사전처리 된 입력 정보들을 numpy 배열로 변환하여 run() 함수의 입력으로 넣어줍니다.

1
2
3
4
5
# 컴파일된 모델 불러오기
module = rebel.Runtime("bert_base.rbln", tensor_type="pt")

# 추론
out = module(**inputs)

print(module)을 통해 로드된 모델의 입출력 형태 및 모델 크기 등 요약된 정보를 확인할 수 있습니다.

결과 확인

transformers 라이브러리의 fill-mask 파이프라인을 사용하여 추론 결과 로짓값 out 으로부터 입력 문자열의 [MASK] 위치에 해당하는 단어를 디코딩할 수 있습니다.

1
2
3
# 결과 확인
unmasker = pipeline("fill-mask", model="bert-base-uncased", framework="pt")
print(unmasker.postprocess({"input_ids": inputs.input_ids, "logits": out}))

최종 결과는 아래와 유사합니다:

1
2
3
4
5
6
7
[
    {'score': 0.23562490940093994, 'token': 2317, 'token_str': 'white', 'sequence': 'the color of rose is white.'},
    {'score': 0.10957575589418411, 'token': 2417, 'token_str': 'red', 'sequence': 'the color of rose is red.'},
    {'score': 0.08016733080148697, 'token': 2304, 'token_str': 'black', 'sequence': 'the color of rose is black.'},
    {'score': 0.07074742764234543, 'token': 3756, 'token_str': 'yellow', 'sequence': 'the color of rose is yellow.'},
    {'score': 0.05175992473959923, 'token': 2630, 'token_str': 'blue', 'sequence': 'the color of rose is blue.'},
]

요약

허깅페이스에서 제공하는 파이토치 BERT-base 모델의 컴파일을 위한 완성된 코드는 아래와 같습니다:

import torch
from transformers import BertForMaskedLM
import rebel # RBLN 컴파일러

# 허깅페이스 파이토치 BERT-base 모델 준비
bert_model = BertForMaskedLM.from_pretrained("bert-base-uncased", return_dict=False)
bert_model.eval()

# 모델 컴파일
MAX_SEQ_LEN = 128
input_info = [
    ("input_ids", [1, MAX_SEQ_LEN], "int64"),
    ("attention_mask", [1, MAX_SEQ_LEN], "int64"),
    ("token_type_ids", [1, MAX_SEQ_LEN], "int64"),
]
compiled_model = rebel.compile_from_torch(
    bert_model,
    input_info,
    # 호스트에 NPU가 설치되어 있는 경우, 아래의 `npu`인자는 명시하지 않아도 자동으로 감지됩니다. 
    npu="RBLN-CA12",
)

# 컴파일된 모델 로컬 저장소에 저장
compiled_model.save("bert_base.rbln")

컴파일된 BERT-base 모델의 추론을 위한 완성된 코드는 아래와 같습니다:

import torch
from transformers import BertTokenizer, pipeline
import rebel  # RBLN 런타임

# 입력 데이터 준비
MAX_SEQ_LEN = 128
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
text = "the color of rose is [MASK]." 
inputs = tokenizer(text, return_tensors="pt", padding="max_length", max_length=MAX_SEQ_LEN)

# 컴파일된 모델 불러오기
module = rebel.Runtime("bert_base.rbln", tensor_type="pt")

# 추론
out = module(**inputs)

# 결과 확인
unmasker = pipeline("fill-mask", model="bert-base-uncased", framework="pt")
print(unmasker.postprocess({"input_ids": inputs.input_ids, "logits": out}))

torch.compile() API

RBLN SDK는 자체 API뿐만 아니라 PyTorch의 torch.compile 기능도 지원합니다. 이 통합을 통해 개발자는 PyTorch의 Just-In-Time(JIT) 컴파일을 활용하여 RBLN SDK 내에서 최적화된 모델 실행을 구현할 수 있습니다. torch.compile을 사용하는 모든 워크플로우에 RBLN의 커스텀 백엔드를 통합하면, RBLN의 기본 기능과 완벽하게 호환되면서도 향상된 성능을 얻을 수 있습니다.

이 가이드는 torch.compile() API를 사용하여 Hugging Face BERT-base 모델을 컴파일하고 실행하는 방법을 보여줍니다.

모델 준비

먼저, HuggingFace transformers 라이브러리에서 BertForMaskedLM 모델을 가져와 인스턴스화합니다. 이 단계는 RBLN의 네이티브 API에서 사용하는 과정과 동일합니다.

1
2
3
4
5
6
7
import torch
from transformers import BertForMaskedLM
import rebel  # RBLN 컴파일러 임포트

# Hugging Face BERT-base 모델 인스턴스화
bert_model = BertForMaskedLM.from_pretrained("bert-base-uncased")
bert_model.eval()

입력 준비

다음으로, 마스크된 언어 모델링 작업을 위해 입력 텍스트 시퀀스를 토큰화하여 입력 데이터를 준비합니다. 이 과정도 네이티브 API를 사용하는 것과 동일합니다.

from transformers import BertTokenizer

# 토크나이저 로드
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")

# 입력 텍스트 정의
text = "The color of a rose is [MASK]."

# 입력 토큰화 및 준비
MAX_SEQ_LEN = 128
inputs = tokenizer(text, return_tensors="pt", padding="max_length", max_length=MAX_SEQ_LEN)

모델 컴파일 및 실행

모델과 입력이 준비되었으면, torch.compile()을 사용하여 모델을 컴파일하고 실행합니다. 이 단계에서는 첫 추론에서 런타임 시 JIT 컴파일을 활성화합니다.

# RBLN 백엔드를 사용하여 모델 컴파일
compiled_model = torch.compile(bert_model, 
                               backend="rbln",  # RBLN 백엔드 지정
                               options={"cache_dir": "./rbln_cache_dir"},  # 컴파일된 아티팩트의 캐시 디렉토리 지정
                               dynamic=False)  # 동적 모양 비활성화 (RBLN 백엔드는 현재 지원하지 않음)

# 컴파일된 모델을 사용하여 추론 실행
logits = compiled_model(**inputs).logits

# 예측된 토큰 디코딩
mask_token_index = (inputs.input_ids == tokenizer.mask_token_id)[0].nonzero(as_tuple=True)[0]
predicted_token_id = logits[0, mask_token_index].argmax(axis=-1)
print(f"예측된 단어: {tokenizer.decode(predicted_token_id)}")

torch.compile() 파라미터 이해하기

backend="rbln":

  • 설명: 모델 컴파일에 사용할 백엔드를 지정합니다.
  • 목적: 이를 "rbln"으로 설정하면 RBLN SDK의 커스텀 백엔드를 활용하여, RBLN 환경 내에서 성능이 최적화된 컴파일 프로세스를 사용할 수 있습니다.

options={"cache_dir": "PATH/TO/rbln_cache_dir", "npu" : "TARGET_NPU_DEVICE"}:

  • 설명: 컴파일 프로세스에 추가 옵션을 제공합니다.
  • 목적:
    • cache_dir : 컴파일된 결과를 저장할 디렉토리를 지정합니다.
      • 사용법: 네이티브 API에서 compiled_model.save("resnet50.rbln")을 사용하는 것과 유사하게, 지정된 경로에 RBLN 아티팩트를 생성합니다.
      • 캐싱: 지정된 디렉토리에 이미 컴파일된 모델이 존재하는 경우, RBLN 백엔드는 모델을 다시 컴파일하지 않고 캐시된 버전을 사용합니다. 이는 모델을 재사용할 때 컴파일 시간과 오버헤드를 줄이는 데 도움이 됩니다.
    • npu : 타겟 NPU 장치를 지정합니다. 장치 식별자를 지정하는 방법에 대한 자세한 내용은 네이티브 API 문서의 npu 옵션을 참조하십시오.

dynamic=False:

  • 설명: 모델이 동적 입력 모양을 지원할지 여부를 나타냅니다.
  • 목적:
    • dynamicFalse로 설정하는 것이 권장되며, RBLN 백엔드는 현재 동적 모양을 지원하지 않습니다.
    • 동작: 이 옵션을 False로 설정하면 모델은 고정된 입력 모양을 가정하며, 다른 모양의 입력은 재컴파일을 트리거합니다. 이를 통해 컴파일이 특정 입력 모양에 대해 최적화되지만, 입력 모양이 변경될 경우 다시 컴파일해야 할 수 있습니다.

요약

torch.compile()을 이용하여 허깅페이스에서 제공하는 파이토치 BERT-base 모델의 컴파일을 위한 완성된 코드는 아래와 같습니다:

import argparse
import torch
from transformers import BertForMaskedLM, BertTokenizer
import rebel  # torch.compile에서 "rbln" 백엔드를 사용하기 위해 필요

def parse_arguments():
    parser = argparse.ArgumentParser(description="RBLN 백엔드를 사용하여 torch.compile로 BERT 모델 실행.")
    parser.add_argument("--model_name", type=str, choices=["base", "large"], default="base", help="BERT 모델 크기: 'base' 또는 'large'.")
    return parser.parse_args()

def main():
    args = parse_arguments()
    model_name = f"bert-{args.model_name}-uncased"
    MAX_SEQ_LEN = 128

    # 모델 인스턴스화 및 컴파일
    model = BertForMaskedLM.from_pretrained(model_name)
    compiled_model = torch.compile(model, backend="rbln", dynamic=False, options={"cache_dir": "./rbln_cache_dir"})

    # 마스크된 언어 모델링을 위한 입력 텍스트 준비
    tokenizer = BertTokenizer.from_pretrained(model_name)
    text = "The color of a rose is [MASK]."
    inputs = tokenizer(text, return_tensors="pt", padding="max_length", max_length=MAX_SEQ_LEN)

    # 컴파일된 모델을 사용하여 추론 실행
    logits = compiled_model(**inputs).logits

    # 예측된 단어 디코딩 및 출력
    mask_token_index = (inputs.input_ids == tokenizer.mask_token_id)[0].nonzero(as_tuple=True)[0]
    predicted_token_id = logits[0, mask_token_index].argmax(axis=-1)
    print(f"예측된 단어: {tokenizer.decode(predicted_token_id)}")

if __name__ == "__main__":
    main()