MLOPS/SERVING

Triton model serving VS Django REST API serving

개발허재 2022. 4. 21. 13:44

위 그림과 같이 수많은 앱과 서비스가 생겨나고 머신러닝 기술이 발전하면서 하나의 앱에 여러 모델들의 inference를 수행하는 경우가 있다.

하나의 앱이 여러 모델을 타야하는 경우, 기존에는 어떠한 독립적인 inference 전용 API를 따로 구성하여 코드를 작성하고 프레임워크 별로 세팅을 각각 해줘야하는 번거로움이 있었다.

요즘처럼 앱의 빠른 배포와 여러 서비스의 적용 및 테스트가 빈번한 시대에는 꼭 필요한 것이 있었다.

바로 Triton inference server 이었다.


트라이톤 서버를 선택하게된 계기

Django 프레임워크를 사용하며 torch 모델을 script화 하지않고 그대로 배포했었다. 이로인해 발생한 문제점으로 Tensorflow, Torch, ONNX, TensorRT 등의 모델들을 하나의 프로세스에서 서로 다른 딥러닝 프레임워크 모델들을 띄우는데 충돌이 있었으며, 토치 모델을 그대로 올렸기 때문에 발생한 메모리 이슈, c++ 이 아닌 python 백엔드에서 실행함으로써 생긴 오버헤드 이슈가 있었다.

따라서, 나는 모델의 서비스배포단에서는 분명 다른 방법으로 서빙할 것이라고 생각했다.

 

그렇게 발견한 것이 Tensorflow Serving 과 TorchServe 이었다.

텐서플로우 서빙은 c++ , 토치서브는 자바로 구성되어 있기 때문에 파이썬 백엔드에서 발생하는 오버헤드를 피할 수 있다는 장점이 있다.

또한, Django 프레임워크의 각 세팅파일들을 구성하지 않아도 되고, uwsgi+Django 와 같이 프로세스를 띄울 수 있는 다른 서버가 없어도 된다. 

하지만, 나는 첫번째로 겪은 문제에서 발생했던 서로 다른 모델을 동시에 처리할 수 있는 하나의 서버를 필요로 했고 이 또한 나의 궁금증을 해결해 주지 못하였다.

 

열심히 찾아본 결과, 나의 궁금증을 해결해 줄 Triton server를 발견할 수 있었다.

트라이톤 서버는 첫번째로 Uwsgi+Django와 같이 세팅의 번거로움과 코드없이 모델과 config 만 있으면 서빙이 가능하며(pre/post process도 ensemble model로 배포가 가능하지만 코드작성은 필요)

Tensorflow, Torch, ONNX, TensorRT 등의 서로 다른 프레임 워크의 모델들을 동시에 한 프로세스에 올릴 수 있다고 했다.

더 자세한 트라이톤 서버의 장점은 아래 트라이톤 서버 공식 깃헙의 document를 읽으면 알 수 있다.

https://github.com/triton-inference-server

 

Triton Inference Server

Triton Inference Server has 27 repositories available. Follow their code on GitHub.

github.com

 

따라서, 나는 트라이톤 서버를 모델 서빙을 위한 서버로 사용하기로 했다.

 

실제로, 이 Triton Server 가 너무 궁금해서 Nvidia에서 주최하는 개발자 밋업 웨비나에도 참석하여 강의를 들었다....!

아직 nvidia-developer-ai-kr SLACK에서 많은 정보가 공유되고 있다. 관심있는 사람이라면 아래 초대링크로 들어와도 좋다.

https://join.slack.com/t/nv-ai-developer-kr/shared_invite/zt-19sucbd1d-wDHQ5j0jaYgBcJkuCooixQ

 

Slack

nav.top { position: relative; } #page_contents > h1 { width: 920px; margin-right: auto; margin-left: auto; } h2, .align_margin { padding-left: 50px; } .card { width: 920px; margin: 0 auto; .card { width: 880px; } } .linux_col { display: none; } .platform_i

nv-ai-developer-kr.slack.com


실제 서비스 배포해보기까지의 경험 및 시행착오

먼저, 나는 도커를 활용하여 컨테이너를 띄웠다.

실제로 내가 했던 경험은 아래 링크를 들어가면 볼 수 있다.

https://jeawoo0594.tistory.com/27

 

도커를 활용한 Triton Inference Server 구축

Triton Inference Server GitHub 주소: https://github.com/triton-inference-server/server GitHub - triton-inference-server/server: The Triton Inference Server provides an optimized cloud and edge infe..

jeawoo0594.tistory.com

  

나는 처음에는 트라이톤 서버를 컨테이너로 구축한 뒤 단순히 모델만 올리고 프로세스를 실행하고 

전/후처리는 기존 API 서버에서 처리하는 방식으로 했었다. 하지만, 이 방식은 request(요청) 와 response(응답) 데이터가 너무 커서 오버헤드를 초래하는 문제가 발생했다.

 

따라서, 나는 ensemble model 을 활용하여 preprocess + model + postprocess 의 전과정을 트라이톤 서버에서 처리하였다.

ensemble model은 데이터 전처리 -> 추론 -> 후처리와 같이 여러 모델을 포함하는 절차를 캡슐화하는 데 사용된다. 이러한 목적으로 앙상블 모델을 사용하면 중간 텐서를 전송하는 오버헤드를 방지하고 Triton으로 보내야 하는 요청 수를 최소화할 수 있다.

자세한 내용은 아래 링크를 참조하면 된다.

https://github.com/triton-inference-server/server/blob/main/docs/architecture.md#ensemble-models

 

GitHub - triton-inference-server/server: The Triton Inference Server provides an optimized cloud and edge inferencing solution.

The Triton Inference Server provides an optimized cloud and edge inferencing solution. - GitHub - triton-inference-server/server: The Triton Inference Server provides an optimized cloud and edge i...

github.com

 

내가 작성한 ensemble_model의 config 파일은 아래와 같다.

name: "ensemble_ocr"
platform: "ensemble"
input [
  {
    name: "preprocess_in_0"
    data_type: TYPE_STRING
    dims: [ -1, 1 ]
  }
]
output [
   {
     name: "postprocess0_out_0"
     data_type: TYPE_STRING
     dims: [ -1, 2 ]
    }
]
ensemble_scheduling {
  step [
    {
      model_name: "preprocess"
      model_version: -1
      input_map [{
        key: "INPUT__0"
        value: "preprocess_in_0"
      }]
      output_map [{
        key: "OUTPUT__0"
        value: "preprocess_out_0"
      },
      {
        key: "OUTPUT__1"
        value: "preprocess_out_1"
      }]
    },
    {
      model_name: "craft"
      model_version: -1
      input_map [{
        key: "input__0"
        value: "preprocess_out_0"
      }]
      output_map [{
        key: "output__0"
        value: "craft_out_0"
      },
      {
        key: "output__1"
        value: "craft_out_1"
        }]
      },
     {
      model_name: "refinenet"
      model_version: -1
      input_map [{
        key: "INPUT__0"
        value: "craft_out_0"
      },
      {
        key: "INPUT__1"
        value: "craft_out_1"
      }]
      output_map [{
        key: "OUTPUT__0"
        value: "refinenet_out_0"
        }]
      },
      {
      model_name: "postprocess_0"
      model_version: -1
      input_map [{
        key: "INPUT__0"
        value: "craft_out_0"
      },
      {
        key: "INPUT__1"
        value: "refinenet_out_0"
      },
      {
        key: "INPUT__2"
        value: "preprocess_in_0"
        },
      {
        key: "INPUT__3"
        value: "preprocess_out_1"
      }
      ]
      output_map [{
        key: "OUTPUT__0"
        value: "postprocess0_out_0"
      }]
      }
    ]
}

이렇게 보니까 생각보다 길다...