ML OPS/Inference & Serving

[ML OPS] transformers inference (ft. colab, onnx, gpu)

seokhyun2 2022. 7. 10. 22:11

자연어처리에서는 transformers가 pretrained 모델도 많이 제공하고 있다보니 정말 많이 활용되는 것 같습니다.

ML ops의 역할 중에 하나가, 인퍼런스 속도를 최적화하는 것이라는 생각이 되는데요.

transformers를 간단하게 인퍼런스 해보는 코드에서 시작하여, 실시간 서비스에서 활용할 수 있는 수준까지 개선해보는 과정을 소개해보도록 하겠습니다.

 

0. colab 

오늘 소개하는 내용들은 GPU도 활용해야 하는데, 노트북을 사용하다보니 GPU가 없어서 colab(https://colab.research.google.com/)을 사용해보도록 하겠습니다.

colab에서는 무료로도 간단하게 GPU를 테스트 정도는 해볼 수 있도록 제공하고 있습니다.

colab을 기본적으로 켰을 땐, GPU가 할당되지 않는데 아래 이미지와 같이 런타임 > 런타임 유형 변경을 눌러주시면 GPU 타입을 할당받을 수 있습니다.

참고로 아래 이미지와 같이, TPU도 활용해볼 수 있습니다.

colab은 기본적으로 주피터 노트북처럼 python 환경을 사용할 수 있게 되어있어서, 맨 앞에 !를 붙여서 실행하면 shell command도 사용해볼 수 있습니다.

그래서 아래와 같이 nividia-smi 명령어를 실행하여 gpu가 할당되었는지 확인을 해보시면 됩니다.

그러면 이렇게 Tesla T4 장비가 할당된 것을 확인할 수 있습니다.

 

1. transformers를 활용한 bert inference

transformers를 활용하여 간단하게 bert 모델을 로딩 해보도록 하겠습니다.

참고로 transformers 설치는 아래 명령어를 활용하여 간단하게 설치해주었습니다.

!pip install transformers

간단하게 bert 모델을 불러와서, inference를 해보는 코드는 아래와 같습니다.

from transformers import AutoModel, AutoTokenizer

model = AutoModel.from_pretrained("bert-base-uncased")
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

encoded_input = tokenizer(" ".join(["hello"]*510), return_tensors='pt')
output = model(**encoded_input)

BERT는 최대 input 길이가 512이므로, 최대 길이로 테스트를 해보도록 하겠습니다.

hello를 510개 넣어주면 앞 뒤에 스페셜 토큰을 하나씩 붙여서 512개의 토큰으로 이루어진 input이 생성됩니다.

 

아래와 같이 10번 인퍼런스를 돌리는 속도를 한번 측정해보면 25초나 걸리는 것을 알 수 있네요.

2. TokenizerFast

위에서 인퍼런스 속도에서 왜 토크나이징을 매번 수행했는지, 의아하신 분이 계셨을텐데요.

실제 상황에서는 매번 input이 다르기 때문에 토크나이징을 매번 수행하니까 그 상황을 가정하기도 했고 또 transformers에서는 토크나이징도 최적화 할 수 있기 때문에 그 부분을 보여드리기 위해서 일부러 매번 수행하도록 했습니다.

python은 기본적으로 속도가 느린 편이라서, transformers에서는 토크나이징 속도를 빠르게 해주기 위해서 Fast 버전의 토크나이저를 추가로 제공하고 있습니다.

아래와 같이 초기화해서 사용하시면 되는데, Fast 버전의 토크나이저는 내부적으로 Rust 언어를 활용한다고 합니다.

토크나이저만 Fast 버전으로 변경해주었는데 엄청 단축된 것을 볼 수 있습니다.

하지만 그래도 10번이 10초가 넘는다는 것은, 1번이 1초가 넘는다는 의미니까 많이 느린편입니다.

뒷단에서 배치를 돌리는 정도로 사용은 할 수 있겠지만, 실시간 서비스에서는 매우 사용하기 어려운 속도입니다.

3. ONNX

ONNX로 서빙하는 것은 이전에도 간단하게 소개글(https://seokhyun2.tistory.com/78)을 작성했었는데요.

그 때는, 직접 converting을 했지만, 이번 글에서는 transformers를 활용하고 있는 만큼 transformers에서 제공하는 모듈을 활용해보겠습니다.

 

오늘은 gpu도 활용할 예정이므로 먼저 아래 명령어로 onnxruntime-gpu 라이브러리를 설치해줍니다.

!pip install onnxruntime-gpu

이제 모델을 변환해볼건데, transformers에서는 아래와 같은 명령어를 실행하면 쉽게 컨버팅을 할 수 있습니다.

!python -m transformers.onnx --model=bert-base-uncased onnx

참고로 BERT 같은 경우에는 dynamic 한 input 길이가 지원이 되는데 transformers에서 제공하는 모듈을 활용하면 그런 부분까지 알아서 잘 고려해서 컨버팅을 하고 컨버팅 결과에 대한 validation까지 수행해줍니다.

 

동일하게 속도를 측정을 해보면 ONNX를 적용하면 조금 개선되지만 여전히 실시간 서비스에는 부족하단 생각이 드네요.

4. GPU

이번엔 GPU를 활용해보겠습니다.

그냥 모델을 불러오고 nvidia-smi 명령어로 확인해보았을 때, 아래와 같이 사용 중인 메모리가 0으로 GPU를 사용하지 않는 걸 알 수 있습니다.

GPU를 사용하는 방법은 간단한데요, 모델을 불러와서, .to('cuda')를 호출해주시면 됩니다.

그러면 이제 아래와 같이 GPU에 1720MiB 메모리가 할당된 것을 확인할 수 있습니다.

이제 GPU로 인퍼런스를 해볼껀데, GPU는 주의사항이 몇가지 있습니다.

우선 GPU의 경우에는 최초 인퍼런스가 느린 특징이 있어서 한번 인퍼런스를 먼저 수행해주는 것이 좋습니다.

또, 토크나이징이 완료된 input도 .to("cuda")를 호출해주어야 합니다.

 

그러면 아래와 같이 먼저 한번 호출해주고 속도를 측정해보도록 하겠습니다.

10번 인퍼런스를 수하는데 0.39초 밖에 걸리지 않네요.

그러면 1번은 0.04초 즉 40ms 정도니까 이정도면 BERT 모델을 실시간 서비스에서도 활용할 수 있을 것 같습니다.

 

참고로 Fast 버전의 토크나이저를 사용하는 것은 GPU를 활용할 때는 큰 차이가 없습니다.

CPU에서 인퍼런스 하는 경우에는 모델을 인퍼런스 할 때도, 토큰나이징을 할 때도 CPU를 활용하다보니 resource가 모자란건데, GPU를 활용하는 경우에는 CPU 리소스가 여유롭다보니 토크나이징에 충분히 사용할 수 있기 때문이긴 합니다.

 

ONNX는 GPU도 지원합니다. 아래와 같이 코드를 작성하면 GPU 상에서 ONNX를 사용할 수 있습니다.

# onnx gpu
session = ort.InferenceSession("onnx/model.onnx", providers=['CUDAExecutionProvider'])

# first inference for initializing gpu
encoded_input = fast_tokenizer(" ".join(["hello"]*510), return_tensors='np')
output = session.run(None, input_feed=dict(encoded_input))

 

아래와 같이 성능을 측정해보면, ONNX를 적용하지 않았을 때 0.39 였는데 적용 후에 0.38이라면 별 차이가 없는 것 처럼 느껴질 수 있는데요.

위의 속도는 토큰을 최대 길이 기준으로 테스트 한 것인데, ONNX의 진가는 짧은 텍스트를 인퍼런스 할 때 발휘합니다.

hello 딱 하나만 집어넣으면 속도가 약 3배가 빠른 것을 확인할 수 있습니다.

그래서 GPU를 활용하는 경우에도 ONNX를 사용하시는 것을 권장드립니다.

 

5. 마무리

최근 딥러닝 모델은 무겁다보니, 속도를 어느정도는 최적화해야만 서비스에서 사용할 수 있는 것 같습니다.

오늘은 기본적인 인퍼런스 코드에서 transformers에서 제공되는 Fast 토크나이저를 활용해보고 ONNX도 적용해보면서 속도를 최적화 시켜보았고 더 나아가 실시간 서비스를 할 수 있을 정도의 인퍼런스 속도를 내보기 위해서 GPU에서 인퍼런스 해보았는데요.

 

이 정도면 실시간 서비스에 적용은 할 수 있지만 아무래도 1번을 40ms가 걸린다고 생각하면 1초에 25개의 request 정도 밖에 처리를 못 할것이라 트래픽이 큰 경우에는 서버가 엄청 많이 필요할 수 있습니다.

다음에는 인퍼런스 속도를 최적화하는 추가적인 기법들(아마 조금 더 고급진..?)을 소개해보도록 하겠습니다.

 

이번 글을 작성하면서 활용한 코드는 .py & .ipynb 2가지 버전 모두 아래의 github 링크에 올려두었습니다.
https://github.com/hsh2438/MLops/tree/main/4_transformers_inference