MLOps 업무를 하면서 반드시 마주치게 되는 인스턴스 권한 관리와 GPU 메모리 사용량 체크 코드 작업하기
MLOps 엔지니어로서 일을 하면서, 최근에는 nvidia gpu를 어떻게하면 ML Researcher 분들에게 GPU를 쪼개서 전달할지에 대해 많은 고민을 하고, 문제를 해결해 최근에는 사내 ML IDE로 Jupyterhub를 배포했다.
GPU를 분할하고, 이 분할된 GPU 인스턴스를 k8s에서 인지시키는 방법은 엔비디아에서 제공하고 있는 MIG(Multi-Instance GPU) 기법이 있어, 이것을 참고하면 될 것이다.
다만, Nvidia의 모든 GPU에 MIG를 지원하지 않는다.
(기업에서 데이터센터형 GPU로 사용하는 A100은 확실하게 지원하고, 위의 텍스트에 걸어놓은 링크에 보면 H100도 지원한다.)
GPU를 쪼개서 사용해야하는 것에는 명확한 이유가 있었다. 현재 회사에서 사내 인프라망을 통합하는 과정을 진행하고 있고, 기존에 회사에 있는 GPU인 A100을 ML 리서처분들이 아무런 제약 없이 사용했었다.
그런데 이러다가 문제가 터졌다.
리서처분들이 서로 model traninig or inference를 하는 과정에서 GPU를 서로 너무 많이 끌어다 쓴 탓에 GPU 서버가 터진 것…(A100으로도 금융 스타트업의 모델링 리소스를 모두 감당하지 못했다)
최근 급격하게 회사가 큰 것도 있지만, GPU를 명확하게 분리해서 나눠주지 않았던 기존의 사용 방식에 문제점을 인지하고, CTO님과 우리 MLops 팀은 움직이기 시작했다.
내가 합류하기 전에 팀에서는 k8s를 사용한 리소스 관리를 먼저 선택해서, k8s는 이미 운영 서버에 배포가 되었던 상태였고, (k8s를 한 번에 설치하는 오픈소스로 kubespray라는 것을 사용하면 매우 편리하다) 여기에 현재 첫번째 단계로 zero to jupyterhub라는 것을 이용해서 모든 딥러닝 리서처님들의 리소스 관리 및 통합을 진행했다.
(Jupyterhub로 리소스를 통합하고, 권한 제공 및 관리하는 방식은 추후에 다시 포스팅으로 다룰 예정이다.)
Jupyterhub를 리서처분들에게 배포하기 전, 가장 큰 첫번째 난관은 GPU 인스턴스를 할당하는 방식이었다.
k8s에서는 이미 CPU, RAM을 pod 단위로 소숫점으로 리소스를 할당하는 방식이 사용되고 있고, 전통적인 컴퓨팅 리소스를 할당하는 것은 아무런 장애가 되지 않았다.
문제는 GPU였다. 최근들어 k8s에서 MLOps 업무에 사용되는 일이 잦아지는데, GPU는 pod 단위로 쪼개서 할당하는 것이 불가능했다. (GPU 메모리가 얼마나 되든 상관 없이 무조건 GPU를 정수 단위로 밖에 할당이 안되었다.)
지금 이 GPU를 pod에 쪼개서 할당하는 것과 jupyterhub에서 리서처분들이 작업을 하는 것이 어떤 상관이 있는지 이해를 못하는 분들이 있어 노파심에 한 가지 추가로 언급을 하자면, 사내 망에 배포된 jupyterhub에 처음 접속하면, 리서처분들의 작업 공간을 생성하기 위해 컴퓨팅 자원을 선택하는 란을 아래 화면처럼 볼 수 있다.
위 옵션 중 하나를 선택하면, 해당 옵션에 맞게 리소스가 인스턴스에 할당되면서, 정해진 리소스 만큼을 사용하면서 모델링 업무에 투입이 되는 것이라 볼 수 있는 것이다.
그래서 상당히 비싼 컴퓨팅 자원인 Nvidia A100 GPU를 모든 리서처분들에게 통으로 한 개씩 제공하는 것은 상당히 무리가 있는 상황이었고 (A100 한대에 3억정도 한다) MLOps팀인 우리는 반드시 이 문제를 해결해야만 했다.
방법을 찾았다. 적용해보자.
사실 가장 이상적인 상황은 쿠버네티스에서 Nvidia GPU를 쪼개서 파드 단위로 할당하는 기능이 있는 것이다.
그러나 위에서 말한 것처럼 이건 불가능했고, 다음 2가지와 같은 상황에서 해결책이 있을 것이라 믿었다.
- 쿠버네티스 관련 오픈 소스 중에 GPU를 vGPU 형태로 소프트웨어에서 인식하게 만드는 방법이 있을 것이다.
- nvidia에서 GPU를 쪼개서 사용하는 소프트웨어를 제공하지 않을까? (본인들도 GPU 비싼 걸 알면, 개발자 지원 잘해주겠지!?)
다행이도 위의 2가지 방법 모두에서 해결책을 찾았고, 우리는 후자를 선택했다.
전자의 경우, 알리바바 클라우드에서 오픈소스로 제공하고 있는 k8s 익스텐션이 있었다.
그러나 우선 해당 레포를 사용하거나 참조하는 사람이 적었고 (얼핏봐도 레포에 star가 너무 적었다.) 무엇보다 Readme.md에서 설명하는 내용을 읽어보니, GPU는 쪼개서 k8s에서 사용이 가능한데, pod에서 limit을 넘어서 사용할 경우, 서버가 터지거나 문제가 발생하는 게 아니라 전체 GPU limit을 기준으로 계속 리소스를 끌어다 사용할 우려가 있어 선택지에서 제외되었다.
(그리고 k8s 공식 익스텐션도 아니고, 여기서 해당 레포 지원을 끊으면, 추후에 우리 팀과 회사에서는 이미 인프라를 이 오픈 소스를 기준으로 GPU 분할 사용을 구성했는데, 기술 부채가 크게 쌓여 더 큰 문제를 만들 수 있을 것이라 우려했다)
그래서 후자를 선택했다.
GPU 제작사에서 지원하고 있고, 꾸준히 업데이트 되고 있으며, k8s와 관계 없이 Nvidia 드라이버가 소프트웨어적으로 GPU를 분할하는 거라, k8s 등 GPU를 인지하는 모듈들이 쪼개진 GPU를 인식한다.
(제일 최적의 상황이고, k8s라는 플랫폼에 대한 의존성이 줄어든다)
이 기술을 Nvidia에서는 MIG라고 하며, vGPU와는 명확하게 다른 개념으로 보인다.
(구글에 좀 찾아보니, MIG는 베어메탈 환경에서 논리적으로 분할하여 사용하게 되며 vGPU 는 하이퍼바이저 위에서 정해진 프로파일대로 할당을 하게 되며 vGPU와는 달리 MIG 는 별도의 라이센스가 필요 없습니다 라고 한다)
덕분에 GPU를 잘 쪼개서 인스턴스에 할당할 수 있게 되었고, 현재는 리서처 분들이 조금씩 사용하고 있다.
하지만 아직 끝난 게 아니다.
서비스 배포하고 나서, 장애가 없으면 되려 불안하다고 해야할까? 역시 문제가 발생했다.
기존 코드에서 잘 돌아가던 GPU 메모리 사용량 측정 방식이 다음과 같은 에러를 발생시켰다.
ValueError: invalid literal for int() with base 10: b’[Insufficient Permissions]’
…음? 권한 에러가 발생했다고?
좀 찾아보니, nvidia GPU 관련 권한 에러가 k8s에서 발생하는 것은 띄운 pod에서 직접적으로 gpu에 접근할 수 있는 방법이 없기 때문이었다.
내가 이해한대로 도식화 한 것을 다음과 같이 표현할 수 있을 것 같다.
와,,, 여기서 권한 에러가 발생할 거라고는 진짜 1도 생각 못했다.
pod 자체를 하나의 인스턴스로 본다면, 확실히 이 인스턴스가 k8s 외부 환경에 위치한 GPU를 직접 식별할 방법이 없다.
그래서 처음에는 pip install pynvml이라는 모듈이 있어서, 이 모듈을 사용해봤다.
해당 모듈로부터 기대했던 점은 현재 인스턴스에 할당된 MIG 인스턴스 자체를 인지하는 부분 (권한 상관 없이) 이었으나,,, 마찬가지로 동일한 에러가 발생했다.
자,,, 여기서 기대할 수 있는 부분은 역시 위에서 그러했던 것처럼,,, nvidia에서 관련 MIG 인지 모듈을 제공하지 않을까에 대한 막연한 기대감이었고, nvidia는 역시나 옳았다.
설마 여기서 nvidia-ml-py라는 모듈이 필요한 모든 사항을 제공하고 있었다.
생성한 인스턴스에서 아래 튜토리얼 코드를 실행해보니, 정상적으로 잘 동작했다.
# !pip install nvidia-ml-py
from pynvml import *
def get_gpu_free_memory():
nvmlInit()
gpu = nvmlDeviceGetHandleByIndex(0)
mig = nvmlDeviceGetMigDeviceHandleByIndex(gpu, 0)
return nvmlDeviceGetMemoryInfo(mig).free
Nvidia가 게임 뿐만 아니라 산업적으로 GPU 표준으로 자리매김한데는 이러한 개발자 지원이 아무래도 한 몫하지 않았다 싶다.
위의 방식이 실행되는 방법을 찾아보니, k8s와는 별개로 현재 인스턴스에서 사용중인 gpu 인식을 하기 위해 nvidia api를 생성해서 호출하는 방식으로 보였다.
모듈의 소스코드가 4000줄 정도 밖에 안되어서, 권한이나 토큰 관련 내용이 없어서 당황했으나, api 호출 형태라는 것은 추후에 팀원이 말해준 것은 안비밀이다.
해당 모듈이 파이썬으로 꾸준히 업데이트는 되고 있는데, 기본적으로 C로 작성되어 있어, 아직 전체 C언어 모듈에서 제공하는 기능을 제공하는 것으로 보이지 않는다. 중간 중간 필요 메소드를 찾아보고 적용하는 과정이 필요해 보인다.
입사한지 이제 갓 한 달이 조금 지났는데, 바로 jupyterhub 배포하고, 오늘은 따끈따끈하게 gpu 로그를 수집해서 grafana & prometheus로 사용량을 트래킹하는 대시보드를 배포하고 왔다.
개인적으로 C언어, 네트워크, 스토리지 (특히, 리눅스 파일 시스템)에 대해 깊은 이해를 필요로 해서 요새 배워 나갈 것이 산더미같이 많다.
아직 MLOps를 위한 전체 파이프라인은 확정 전인데, 스택 중 하나로 Flyte ml을 적용하고 있다.
조만간 jupyterhub, flyte ml, mlflow를 포스팅하기 위해 다시 찾아와야겠다.
일하는 게 무척 잘맞아서 즐겁다.
Ryan