[트러블 슈팅] GPU 서버 멀티 프로세스 도입 (gunicorn cuda does not allow reinitialize) - 100-hours-a-week/5-yeosa-wiki GitHub Wiki
1. 개요
- cpu 서버와 동일하게 gpu 서버에도 gunicorn을 이용하여 멀티 프로세스를 도입하려 함
- 그러나 gpu 서버의 cuda 특성 상 gunicorn을 이용하는 상황에서 ‘gunicorn cuda does not allow reinitialize’ 에러 발생
- 이 때문에 최종적으로는 uvicorn 기반의 멀티 프로세스로 아키텍쳐 수정
- 트러블 슈팅 과정 아래에서 서술
2. 트러블 슈팅 흐름
a. 흐름: clip, arcface, yoloface lazy loading으로 수정 → uvicorn 아키텍쳐로 수정
b. clip, arcface, yoloface lazy loading으로 수정

가. 에러 상황
- gpu 서버를 띄우려고 할 때 위와 같은 ‘멀티 프로세스 환경에서 CUDA 재초기화 불가’ 에러 발생
나. 원인: model.to(device) 통해 gpu에 올리려고 하지만 cuda context 없음 → 재초기화 불가
- 모델을 부모 프로세스에 로딩할 때, cuda context 생성이 일어남
- fork() 시스템 콜 호출 시, 가상 메모리 테이블 복제되고 메모리 페이지는 읽기 전용으로 공유됨
- cuda context는 gpu driver 레벨에 존재하기 때문에 fork()의 영향을 받지 않음
- 자식 프로세스는 cuda context가 없는 상태, 그러나 메모리에 cuda 관련 라이브러리 로딩되어 있어
온전히 cuda context 없는 상태로 판단되지 않음
- 자식 프로세스 생성되며 model.to(device)를 통해 메모리에 로딩된 모델을 GPU로 올리기 위해 cuda context를 이용하려 하고 cuda context가 없어서 초기화 시도
- CUDA 런타임 상태의 일부가 남아있어 재초기화로 판단됨 cuda 원칙 상 허용하지 않음
다. 해결: lazy loading으로 변경
- 모델을 서버 띄울 때, 로딩해서 부모 프로세스에서 cuda context 생성이 일어났다고 판단
- 이에 실제 첫 번째 요청이 들어왔을 때 로딩하는 lazy loading 도입
- 이를 통해 자식 프로세스들에서 각각 cuda context 초기화와 cuda 관련 라이브러리 임포트하게 하고자 함
c. Uvicorn 기반 멀티 프로세스 구조로 수정

가. 에러 상황
- lazy loading 방식으로 변경한 후, 서버 띄워졌지만 실제 요청 보냈을 때 CUDA 재초기화 불가라는 동일한 에러 발생
나. 원인: CUDA 관련 라이브러리가 부모 프로세스에서 import되어 CUDA 런타임이 일부 존재
- 모델은 로딩하지 않았음
- 그러나 부모 프로세스에서 아래와 같은 과정 진행됨:
- 라이브러리 import를 통해 메모리에 CUDA 런타임 일부가 생성됨
- 자식 프로세스를 fork() 시스템 콜로 생성할 때, 메모리 공유 일어남
- 서버 띄울 때, model.to(device)를 하지 않기 때문에 CUDA 런타임이 부분적으로 존재하는 문제가 확인되지 않음
- 요청 보냈을 때, model.to(device)가 발생하지만 cuda context가 없는 상태로 초기화 시도
- cuda 원칙 상 CUDA 런타임이 이미 있기 때문에 재초기화라 판단해 에러 발생
다. 해결: uvicorn 기반 멀티 프로세스 구조로 수정
- uvicorn은 spawn() 방식으로 자식 프로세스 생성
- spawn() 시스템 콜은 부모 프로세스의 메모리 복제 및 공유 없이 프로세스를 생성함
- 그렇기에 부모 프로세스의 cuda 라이브러리 및 런타임 로딩과 무관하게 자식 프로세스에서 cuda context 생성 일어남
- 이를 통해 자식 프로세스에서 안전하게 CUDA 런타임을 확보