DeepLearning/Service

nginx와 gunicorn을 활용한 multi gpu 환경에서의 딥러닝 서비스

seokhyun2 2020. 4. 26. 22:02

https://seokhyun2.tistory.com/44

 

딥러닝 모델 서빙과 병렬처리

저도 회사에서 딥러닝 모델 서빙을 하고 있는데, 서빙을 하다보니까 제일 처음에 부딪히는 문제가 병렬처리였습니다. 그래서 이번 포스팅에서는 딥러닝 모델을 서빙할 때 어떻게 병렬처리를 해줘야 할 지 이야기를..

seokhyun2.tistory.com

위의 글에서, 파이썬에서의 병렬처리와 딥러닝 모델 서빙을 하는 방법에 대해서 다뤘습니다.

flask와 gunicorn을 활용하면 쉽게 멀티프로세싱을 활용할 수 있다고 소개해드렸는데, 이번에는 nginx까지 같이 활용하여 multi gpu 환경에서 딥러닝 모델을 서비스하는 방법을 소개해드리겠습니다.

 

nginx는 비동기 이벤트 기반의 웹서버로 reverse proxy 기능을 제공합니다.

gunicorn만 활용할 경우, 특정 gpu에 할당할 수 없습니다.. CUDA_VISIBLE_DEVICES 옵션을 활용하면 특정 gpu에 할당하여 프로세스를 실행할 수 있습니다.

딥러닝 연산의 경우 1개의 gpu만 활용하여 인퍼런스를 수행하는 것이 빠르기 때문에, 각 프로세스는 1개의 gpu만 사용하기 위하여 특정 gpu를 활용하여 프로세스가 실행하도록 하는 것이 좋습니다.

 

tensorflow의 경우에는 여러 개의 gpu가 있는 서버에서 gunicorn만 활용하여 실행할 경우에는 모든 gpu에 메모리를 다 할당받고 모든 gpu를 적절하게 사용을 하지 못합니다.

 

그래서 서버를 실행할 때, gpu 1개에 대해서 하나의 gunicorn 서버를 실행하고, 각 gunicorn 서버들을 하나로 활용할 수 있게 하기 위하여 nginx를 활용합니다.

 

이전 글에서는 아래와 같이 gunicorn 서버로 실행을 하였습니다. 

gunicorn flask_server:app -b 0.0.0.0:2431 -w 4

 

이번에는 특정 gpu를 할당하여 gunicorn 서버를 실행해보겠습니다.

CUDA_VISIBLE_DEVICES 옵션을 붙이면 아래와 같이 실행할 수 있습니다.

CUDA_VISIBLE_DEVICES=0 gunicorn flask_server:app -b 0.0.0.0:2431 -w 4
CUDA_VISIBLE_DEVICES=1 gunicorn flask_server:app -b 0.0.0.0:2432 -w 4

위와 같이 실행하면, 0번 GPU를 활용하여 4개의 프로세스를 실행하고 1번 GPU를 활용하여 또 4개의 프로세스를 실행하게 됩니다.

이렇게 실행을 하면 2431과 2432를 활용하는 2개의 gunicorn 서버가 실행이 되고 총 worker는 8개가 됩니다.

여기서 2431 서버와 2432 서버 2개가 실행되고 있으면, 사용자 입장에서는 둘 중에 어떤 것을 골라서 써야할 지 알 수가 없습니다.

그래서 2개의 서버를 nginx의 reverse proxy 기능을 활용하여 묶어줄 수 있습니다.

사용자는 nginx 서버에 요청을 하고, nginx 서버는 로드 밸런싱을 수행하여 2431 서버와 2432 서버에 골고루 요청하여 결과를 받아서 nginx 서버가 사용자에게 다시 결과를 요청하는 구조입니다.

그림으로는 아래와 같은 구조가 됩니다.

참고로 nginx는 여러가지 방법의 로드 밸런싱을 제공하며, 기본적으로는 라운드 로빈 방식을 활용합니다.

 

nginx를 사용하는 방법에 대해서도 알아봐야겠죠?

nginx는 그냥 설치해서 사용할 수도 있고, 도커를 활용할 수도 있습니다.

도커를 활용하여 nginx 서버를 설치하고 사용해보겠습니다.

먼저 nginx 도커 이미지를 받아야 합니다.

아래와 같이 이미지를 쉽게 다운로드 받을 수 있습니다.

docker pull nginx

nginx를 실행하기 전에, 2개의 gunicorn 서버를 연결해주기 위해서, 아래와 같이 conf 파일을 작성해주어야합니다.

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;

    upstream myserver {
        server !server_ip!:2431;
        server !server_ip!:2432;
    }
    server {
        listen 2430;
        location / {
            proxy_pass http://myserver;
        }
    }
}

 

해당 conf 에서 아래쪽의 upstream myserver 부터가 로드밸런싱을 위해 설정하는 부분입니다.

그 아래의 server를 먼저 보시면 listen 2430이라고 적혀있는 부분은, port에 해당하는 부분이며 밑에 proxy_pass를 통해 바로 위에 upstream으로 선언되어 있는 myserver에 요청을 하는 방식입니다.

!server_ip! 부분에는 서비스가 떠있는 서버의 ip를 적어주시면 되는데, 꼭 nginx와 동일한 서버일 필요는 없습니다.

한 가지 주의할 점은 도커를 활용하면 컨테이너는 새로운 ip를 가지게 되므로, localhost를 사용하면 안된다는 점입니다.

localhost를 사용하려면 컨테이너 하나에서 nginx와 딥러닝 서버가 모두 같이 실행이 되어야합니다.

 

이제 실행을 해보겠습니다. 실행은 아래와 같이 도커 컨테이너를 실행해주시면됩니다.

docker run --name nginx -v $PWD/nginx.conf:/etc/nginx/nginx.conf:ro -d -p 2430:2430 nginx

-d는 백그라운드에서 실행하겠다는 뜻이며, nginx라는 이름으로 컨테이너를 생성해주었습니다.

위에서 작성한 nginx.conf 파일을 컨테이너 안에 넣어주고 2430이라는 포트도 오픈해주어야합니다.

 

위와 같이 실행하면, 이제 2431이나 2432로 요청해야 결과를 받을 수 있었지만 2430으로 요청하면 2431과 2432에서 적절하게 로드 밸런싱을 하여 결과를 받아오게 됩니다.

 

정리하면, 딥러닝 모델을 서비스 할 때 속도를 위해서는 단일 gpu를 활용하여 inference 하는 것이 좋습니다.

그래서 multi gpu 서버에서는 CUDA_VISIBLE_DEVICES 옵션을 활용하여 각 gpu에 gunicorn 서버를 실행합니다.

여러 개의 gunicorn 서버를 하나로 동작시키기 위해서 nginx를 활용할 수 있습니다.