본문 바로가기

TECH

[2021.07.26] Parallelformers: 빅모델 배포를 향한 여정_튜닙

 

Parallelformers Presentation.pdf
9.85MB

안녕하세요. 저는 TUNiB의 머신러닝 엔지니어 고현웅입니다. 얼마 전에 저희 TUNiB에서 웹서버 배포를 위한 효율적인 모델 병렬처리 라이브러리인 Parallelformers를 대중에 공개했습니다. 이번 글에서는 Parallelformers의 탄생기와 동작 메커니즘에 대한 이야기를 다루고자 합니다. 만약 글을 보시고 궁금증이 생기시면 얼마든지 kevin.ko@tunib.ai로 문의해주세요.

Parallelformers의 등장 배경을 소개합니다.

TUNiB은 높은 수준으로 인간과 소통할 수 있는 인공지능 대화 모델을 만들고 있습니다. 저희는 본격적인 개발에 앞서 최근에 출시되었던 다양한 모델들을 먼저 테스트 해 보고 좋은 대화란 무엇인지에 대해 먼저 생각해볼 필요가 있다고 생각했습니다.

요즘 AI 분야의 트렌드는 모델의 크기를 크게 키우는 것인데요. 이와 비슷하게 대화 모델들도 그 사이즈가 점점 커지고 있는 추세입니다. 저희가 테스트 해보기로 결정한 모델들 역시 사이즈가 컸으며, 이들을 서버에 배포하기 위해서는 이들을 담아낼 수 있는 커다란 용량을 가진 GPU가 필요했습니다. 특히, 그 중 가장 큰 Blenderbot 9B 모델은 순수하게 모델의 용량만 17.6GB이며 실제로 추론을 하려면 약 22GB라는 어마무시한 GPU 용량을 필요로 했죠.

[그림1] Blenderbot 9B의 어마무시한 사이즈 ... 😲

TUNiB의 엔지니어들은 Google Cloud Platform (이하 GCP)를 사용하고 있습니다. 조사 결과 현재 GCP에 존재하는 GPU 중 22GB 이상의 용량을 가진 것은 A100 (40GB) 뿐이였습니다. 저희는 Blenderbot 9B 뿐만 아니라 여러가지 다양한 대화 모델들을 테스트 하려고 했기 때문에 실제로는 3대의 A100이 필요했습니다. 문제는 3대의 A100을 이용하려면 시간 당 약 11$, 하루에 약 264$의 비용이 발생하며 한 달에 약 8,000$라는 매우 큰 비용이 발생한다는 것이였습니다.

비용 문제에 대해 고민을 하던 도중, 저희는 용량이 작은 여러 대의 GPU를 이용하면 훨씬 저렴하게 배포할 수 있다는 사실을 알아냈습니다. 예를 들어, 동일한 120GB의 GPU가 필요하다고 할 때, A100 (40GB) 3대가 아닌 T4 (15GB) 8대를 이용하면 8,000$의 비용을 약 1,600$ 수준으로 절감 시킬 수 있습니다. 이러한 이유로 저희는 모델을 여러 대의 작은 GPU에 쪼개서 배포하기로 결정하였습니다.

그러나 결코 쉽지 않은 병렬화... 😅

이렇게 모델을 여러 개로 쪼개서 여러 대의 GPU에 업로드 하는 기술을 바로 '모델 병렬화'라고 합니다. 모델을 병렬화 하게 되면 커다란 모델도 여러 개의 작은 GPU로 처리할 수 있기 때문에 비용 문제를 고민하던 저희에게 좋은 솔루션이였죠. 그러나 모델 병렬화 기술을 적용하는 것은 상당히 고난도의 작업이기 때문에 저희는 직접 모델 병렬화를 구현하기 보다는 기존에 나온 도구를 활용하고자 했습니다.

[그림2] 모델 병렬화

저희는 대표적인 병렬처리 도구인 Microsoft의 DeepSpeed와 Nvidia의 Faster Transformer을 테스트 하였습니다. 이들의 추론 엔진은 Megatron LM 기반의 모델 병렬화 기능과 커널 융합을 통한 추론 속도 개선 기능을 제공하고 있습니다. 여기에서 커널 융합이란 여러 개의 GPU 연산을 하나로 합쳐서 연산의 속도를 크게 개선시켜주는 기술입니다.

[그림3] 커널 융합의 예시

그러나 실제로 이러한 병렬화 도구를 배포에 활용하는 데에는 몇가지 문제가 존재했습니다. 그 중 가장 큰 문제는 (1) 이들이 지원하고 있는 모델의 수가 매우 적다는 것입니다. 현재 DeepSpeed와 Faster Transformer는 각각 3개의 모델을 지원하고 있는데, 아쉽게도 제가 병렬화 하고 싶었던 모델인 Blenderbot은 지원하지 않았습니다.

그 이외에도 DeepSpeed는 프로세스 흐름 상 (2) 모델을 웹 서버에 배포하는 것이 불가능 하였으며, (3) 모델이 반드시 GPU에 업로드 되어 있는 상태에서 병렬화를 시작해야 한다는 문제가 있었습니다. 또한 Faster Transformer는 (4) 사용법이 상당히 복잡하다는 이슈가 있었습니다. 이러한 이유들로 인해 위의 병렬화 도구들을 활용 하는 것은 불가능하다는 결론에 이르렀습니다.

직접 병렬화 도구 개발하기 🔥

저희는 고민 끝에 저희 목적에 맞는 병렬화 도구를 자체 개발하기로 결정하였습니다. 저희에게 필요한 도구는 (1) 다양한 모델을 지원해야 하며 (2) 웹 서버 배포에 활용 할 수 있어야 하고, (3) GPU에서 병렬화를 시작하지 않으며 (4) 손쉬운 사용법을 가진 도구였습니다. 이 네 가지 요소에 초점을 맞춰서 도구를 단계별로 개발하기 시작했습니다.

1단계: 다양한 모델을 병렬화 하기 - No Kernel Fusion

앞서 이야기 했던 것처럼, 기존의 도구들은 모델을 병렬화 하면서 동시에 속도를 개선하기 위해 커널 융합 기술을 사용했습니다. 의심의 여지 없이 커널 융합은 연산 속도를 크게 개선 시킬수 있지만 동시에 모델의 다양성을 제한하는 요소로도 작용하고 있었습니다.

[그림4] 너무나도 다양한 언어 모델들... 😂

위 그림처럼 현재까지 매우 다양한 언어 모델들이 등장했고 이들 대부분은 각각의 고유한 연산을 가지고 있습니다. 예를 들어 BigBird라는 모델은 Random Attention이라는 고유한 연산을 가지고 있죠. 그러나 커널 융합 기법을 이용하는 구조에서는 이런 고유한 연산들 역시 융합 되어야만 합니다. 즉, 각각 모델들이 가지고 있는 고유한 연산들이 전부 커널융합 방식으로 전부 새롭게 구현되어야 하죠. 그러나 위 처럼 언어 모델의 종류가 굉장히 많기 때문에 모든 연산들을 전부 새롭게 구현하는 것은 매우 시간이 많이 오래걸리는 작업이고, 이러한 이유 때문에 기존 도구들은 자주 사용되는 3개의 대표적인 모델만 사용자들에게 제공하고 있던 것이였죠.

저희는 연산의 속도를 개선하기 보다는 다양한 모델을 병렬화 하는 것이 더 중요했기 때문에 커널 융합 대신 기존 모델의 코드를 재활용 하는 방식을 채택했습니다. 기존 도구들이 가지고 있던 모델 병렬화 기능은 그대로 유지하되, 연산을 융합하지 않고 기존의 연산을 그대로 활용 했죠. 이로써 저희는 속도라는 장점은 포기했지만, 대신 다양성이라는 또 다른 무기를 얻을 수 있었어요.

[그림5] Parallelformers가 지원하는 모델의 종류

결과적으로 이러한 방식으로 Huggingface Transformers에서 제공하는 70개의 모델 중 68개의 모델을 병렬화 하는 것에 성공했습니다. 또한 단순히 언어모델 뿐만 아니라 음성 및 비전 모델들도 병렬화가 가능해졌어요.

2단계: 웹 서버에 병렬화 된 모델을 배포하기 - Inversion of Process Control

1단계에서 커널 융합을 사용하지 않음으로써 Blenderbot 등의 다양한 모델들을 병렬화 할 수 있게 되었습니다. 그러나 여전히 이러한 모델들을 웹서버에는 배포할 수 없다는 문제가 있었습니다.

[그림6] DeepSpeed Launcher의 동작방식

DeepSpeed는 프레임워크가 사용자의 전체 코드를 동시에 여러번 실행하도록 설계되어 있습니다. 그 이유는 병렬화 된 각각의 모델 조각들이 여러 개의 GPU에서 동시에 작동해야 하기 때문이죠. 그러나 코드 전체가 반복되면서 몇 가지 문제가 발생하게 되는데, 결과적으로 이러한 문제들이 웹 서버에 병렬화 된 모델을 배포 할 수 없게 만든다는 것을 깨달았습니다.

먼저, 사용자의 코드 중에서 한번만 실행되어야 할 부분까지 여러 번 실행하는 문제가 있습니다. 이로 인해 (1) 모델이 중복 로드 되어 CPU 메모리 사용량이 초과 되며 (2) 서버가 동일한 포트로 여러 번 오픈 되는 오류가 발생됩니다. 또한 프레임워크의 코드에서 병렬화를 시작하기 때문에 (3) 사용자의 코드에서는 병렬화를 해제할 수 없다는 문제도 있었습니다. (부모-자식 프로세스 문제)

[그림7] Inversion of process control의 동작방식

저희는 이러한 문제를 해결하기 위해 기존의 처리 방식을 거꾸로 뒤집었습니다. 저희는 유저의 코드에서 프레임워크의 코드를 동시에 여러번 호출하도록 구조를 변경했고 이러한 방식에 "Inversion of process control"이라는 이름을 붙였습니다.

[그림8] 모든 부분이 병렬적으로 동작하는 DeepSpeed와 달리 특정 부분만 병렬적으로 동작하는 Parallelformers

Inversion of process control 방식을 적용하면 사용자의 코드 중에서 여러번 실행되어야 하는 부분만 여러번 실행되도록 만들 수 있습니다. 이를 통해 모델이 중복로드 되거나 서버가 동일한 포트로 여러번 오픈되는 일을 방지 할 수 있었고 결과적으로 병렬화 된 모델을 웹 서버에 성공적으로 배포 할 수 있었습니다. 또한 사용자의 코드에서 부터 병렬화가 시작되기 때문에 사용자가 원할 때 얼마든지 병렬화를 해제 할 수도 있었죠.

3단계: GPU 메모리 문제 해결하기 - Lazy GPU Allocation

Inversion of process control을 통해 다양한 모델들을 웹서버에 배포할 수 있게 되었습니다. 그러나 DeepSpeed는 또 하나의 문제를 가지고 있었습니다.

[그림9] GPU에 모델을 올려놓고 병렬화를 시작하는 DeepSpeed

그것은 바로 GPU에서 병렬화가 시작된다는 점입니다. 일반적으로 작은 GPU에는 커다란 모델을 업로드 할 수 없습니다. 그래서 우리는 이것을 가능하게 만들기 위해 모델을 병렬화 하죠. 그러나 DeepSpeed는 병렬화 이전에 모델이 모두 GPU에 업로드 되어 있어야 합니다. 따라서 GPU가 넉넉한 상황에만 병렬화가 가능하고, 정작 GPU 메모리가 부족하여 반드시 병렬화가 필요한 상황에는 병렬화가 불가능했죠.

[그림10] CPU에 모델을 올려놓고 병렬화를 시작하는 Parallelformers

저희는 병렬화를 CPU에서 시작하도록 구현해서 이 문제를 해결했습니다. 일반적으로 대부분 컴퓨팅 머신은 CPU 메모리의 크기가 GPU 메모리의 크기보다 더 큽니다. 저희는 용량이 큰 CPU 메모리에서 병렬화를 마치고 쪼개진 모델의 조각들을 GPU에 뒤늦게 업로드 시키는 방식을 적용함으로써 GPU의 용량 압박으로부터 비교적 자유로워 질 수 있었습니다.

4단계: 사용하기 쉽게 만들기 - Method Hijacking

결론적으로 저희는 위와 같은 다양한 기법들을 직접 고안하고 구현함으로써 저희에게 꼭 필요한 병렬화 메커니즘을 만들어 낼 수 있었습니다. 그러나 사용자가 이러한 기법들을 모두 직접 처리해야 한다면 사용하기 매우 어렵겠죠. 저희는 사용자가 이러한 메커니즘을 쉽게 활용 할 수 있도록 메서드 하이재킹이라는 메커니즘을 추가로 도입했습니다.

[그림11] 메서드 하이재킹

메서드 하이재킹은 사용자가 기존에 사용하던 함수를 호출하면 그 흐름을 뺏어와서 여러가지 복잡한 작업을 함께 실행해주는 방법입니다. 따라서 사용자는 위와 같이 복잡한 메커니즘에 대해서 잘 모르더라도 기존과 100% 동일한 방식으로 코드를 구현 할 수 있게 되죠.

마치며

많은 노력 끝에 저희는 Parallelformers라는 멋진 병렬화 도구를 탄생시킬 수 있었고, 아래 웹 데모 애플리케이션을 저렴하게 배포 하는 것에 성공했어요. 저희는 계획한대로 A100을 사용하지 않고 저렴한 8대의 T4에 커다란 모델들을 배포 할 수 있었고 이로 인해 비용을 상당 부분 절감 할 수 있었습니다.

[그림12] 드디어 완성된 웹 데모 애플리케이션 😁

Parallelformers는 기술적으로 상당히 어려운 프로젝트였지만 개발하는 기간 동안 배운 것도 많고 느낀 것이 정말 많았던 프로젝트였습니다. 특히 값 비싼 GPU를 보유하고 있지 않으면 요즘 대세인 커다란 모델을 활용 할 수 없다는 점이 가장 안타깝게 느껴졌습니다. 저희는 이러한 안타까움을 조금이나마 해소하기 위해 이 도구를 오픈소스로 공개 하기로 결정하였으며, 이러한 마음이 더 많은 사람에게 닿을 수 있도록 Huggingface Transformers 팀, Microsoft DeepSpeed 팀과 협업에 대한 논의를 시작했습니다. 저희는 가까운 시일 내에 더 많은 사람들이 커다란 모델을 쉽게 이용할 수 있는 세상이 오기를 희망합니다.

긴 글 읽어주셔서 감사합니다. 만약 더 자세한 이론적 배경과 사용법 등에 대해 알고싶으시다면 Github 저장소 혹은 Parallelformers가 소개 되어있는 Huggingface의 공식 문서를 참고해주시길 바랍니다.