Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

version_0 model deploy with torch_serve #31

Closed
ArtemisDicoTiar opened this issue Jul 30, 2021 · 22 comments
Closed

version_0 model deploy with torch_serve #31

ArtemisDicoTiar opened this issue Jul 30, 2021 · 22 comments
Assignees
Projects

Comments

@ArtemisDicoTiar
Copy link
Member

ArtemisDicoTiar commented Jul 30, 2021

TL;DR

버전별로 모델을 서비스하고 버전별 A/B Test를 진행하기 위해 백엔드를 직접 다 만들기보단 해당 작업이 가능한 프레임 워크가 있다면 그걸 사용해보자. 오 torch_serve로 가능하다. 일단 version_0모델로 적용해보자.

Why?

현재 deploy되어 있는 version_0의 경우, 플라스크로 구현이 되어 있어 오직 한가지 모델만 Serving할 수 있다.
하지만 앞으로 데이터셋의 확장, 모델의 개선 그리고 피쳐 엔지니어링 등이 진행 됨에 따라 버전별로 모델이 새로 생성 될 것이다.
버전이 올라갈때마다 퍼포먼스가 무조건 개선 된다면 좋겠지만 항상 그럴수는 없을 것이다.
그러면 Front에 서비스 중인 모델의 버전을 이전 버전으로 롤백해야하는데 그럴때마다 플라스크를 수정하기보다 여러버전의 모델을 등록하고 필요에 따라 각 버전의 모델을 이용할 수 있는 것이 좋을 것이다.

위 아이디어의 origin

작년 여름에 네이버에서 인턴을 했을 때, 개발 → 스테이징 → 서비스, 세가지 단계로 모델을 공개/구분. (물론 개발 단계에서는 생성되는 모델이 많았을 것 하지만 실제 서비스, 스테이징으로 공개 되는 버전을 몇개 없었을 것.)
개발단계에서 구축 완료된 모델은 스테이징으로 올라가는 데 스테이징의 모델이 서비스에서 나오는 모델보다 성능이 좋지 않다면 (이때의 성능은 모델 training에서 사용되는 metrics가 아닌 특정 검색문장에 대한 속담 결과를 A/B test 진행해서 비교한다.) 스테이징에 있는 모델이 서비스 단계로 올라가지 못함.

현재 우리의 프로젝트는 작은 시작 단계이지만 얼마 안 있어 프런트, 백엔드 그리고 stroyteller의 forward-dictionary까지 구축하게 되면 웹 서비스 로직이 꽤 커질 것이다. 아무리 백엔드를 깔끔하고 reusable하게 짠다 하더라도 모델이 새로 생성 될때마다 백엔드 로직을 건드리는 건 꽤 귀찮은 작업이 될것이다. (infer 로직이 변경되면 백엔드에서의 infer로직도 수정되어야한다. 물론 이것도 클래스로 래핑해서 재사용하면 되지만...)

torch_serve를 이용하면 해당 방법이 가능하다고 한다.

+ alpha

구현방법을 공부하며 실험하던중 "Management API"라는 게 있어 무엇인가하고 보니...
torch_serve가 돌고 있는 서버가 있을 때 해당 서버에 REST API 를 통해서 모델을 추가, 워커 개수 조절 그리고 모델 제거 등등이 가능하다.
모델을 외부에 저장해둔 경우, 해당 주소를 입력해서 넣어주면 알아서 서버에 다운 받고 추가된다. (와...! 이게 제일 힘들었는데 ㅠㅠ 안그래도 도커내에서 다운받고 서비스하면 모델 다운로드하는 데에 걸리는 시간이 제일 길다. 앞으로 모델이 여러개 추가되서 모델별 비교 실험을 한다고 했을 때 백엔드 도커에 매 deploy마다 다운로드 받는 다고 하면... 음 끔찍하다 ㅋㅋㅋㅋ)

What?

torch_serve로 모델이 infer하게 해보자. 버전별 컨트롤을 하게 해준다. 일단은 version_0부터.

@ArtemisDicoTiar ArtemisDicoTiar self-assigned this Jul 30, 2021
@ArtemisDicoTiar
Copy link
Member Author

모델 생성

torch-model-archiver \
    --model-name wisdomifier \
    --version 0 \
    --model-file ../wisdomify/reverse_dictionary.py \
    --serialized-file ../data/lightning_logs/version_0/checkpoints/wisdomify_def_epoch=19_train_loss=0.00.ckpt \
    --export-path ../data/torchServeModels \
    --extra-files ../conf.json \
    --handler ../wisdomify/main/infer.py

토치 서브 서버 실행

torchserve \
    --start \
    --ncs \
    --model-store ../data/torchServeModels \
    --models wisdomifier.mar

@ArtemisDicoTiar
Copy link
Member Author

핸들러를 그냥 infer파일을 사용했는데 커스텀하게 사용하려면 프레임워크에서 원하는 포맷으로 작성해야하는 거 같다.
backend폴더를 만들어서 서빙하는 코드를 직접 작성해야겠다.

@ArtemisDicoTiar
Copy link
Member Author

ArtemisDicoTiar commented Jul 31, 2021

필요한 파일

  • model-file: (pytorch.nn.module)을 상속받은 클래스 하나만 있는 파일.
  • serialized-fie: .pt, .pth, .ckpt 등 파이토치 모델 saved파일.
  • handler: 토치 서브에서 요구하는 데로 핸들러 작성한 파일

@ArtemisDicoTiar
Copy link
Member Author

생각해보니깐 이거 도커라이즈 하는 방법도 찾아봐야한다.

@ArtemisDicoTiar
Copy link
Member Author

바로 프로젝트 파일에 하지말고 다른 폴더에서 튜토리얼 따라서 실험해야겠다.

@ArtemisDicoTiar
Copy link
Member Author

java version이 openjdk 11이어야한다. 8버전이 minimum dependency 인거 같은데 그렇지 않다. 11이어야 서버 실행이된다.

@ArtemisDicoTiar
Copy link
Member Author

ArtemisDicoTiar commented Jul 31, 2021

torchserve --start --model-store "{ModelStoreDirectory}"
로 토치 서브 서버만 실행시킬수 있다.
model register라는 api가 있어서 나중에 추가할 수 있다.
*management APIs

@ArtemisDicoTiar
Copy link
Member Author

모델 등록만 하면 아래와 같은 자바 에러를 발생시키면서 ping: unhealthy가 된다.

org.pytorch.serve.wlm.WorkerThread - Backend worker monitoring thread interrupted or backend worker process died.
java.lang.InterruptedException
	at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2056)
	at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2133)
	at java.base/java.util.concurrent.ArrayBlockingQueue.poll(ArrayBlockingQueue.java:432)
	at org.pytorch.serve.wlm.WorkerThread.run(WorkerThread.java:188)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:829)

파이썬 에러도 같이 나는 데 아마 이게 문제인거 같다.

Traceback (most recent call last):
2021-07-31 16:15:52,430 [INFO ] W-9002-densenet161_1.0-stdout MODEL_LOG -   File "/usr/local/lib/python3.9/site-packages/ts/model_loader.py", line 81, in load
2021-07-31 16:15:52,430 [INFO ] W-9002-densenet161_1.0-stdout MODEL_LOG -     module, function_name = self._load_handler_file(handler)
2021-07-31 16:15:52,430 [INFO ] W-9002-densenet161_1.0-stdout MODEL_LOG -   File "/usr/local/lib/python3.9/site-packages/ts/model_loader.py", line 121, in _load_handler_file
2021-07-31 16:15:52,430 [INFO ] W-9002-densenet161_1.0-stdout MODEL_LOG -     module = importlib.import_module(module_name)
2021-07-31 16:15:52,430 [INFO ] W-9002-densenet161_1.0-stdout MODEL_LOG -   File "/usr/local/Cellar/python@3.9/3.9.6/Frameworks/Python.framework/Versions/3.9/lib/python3.9/importlib/__init__.py", line 127, in import_module
2021-07-31 16:15:52,430 [INFO ] W-9002-densenet161_1.0-stdout MODEL_LOG -     return _bootstrap._gcd_import(name[level:], package, level)
2021-07-31 16:15:52,430 [INFO ] W-9002-densenet161_1.0-stdout MODEL_LOG -   File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
2021-07-31 16:15:52,430 [INFO ] W-9002-densenet161_1.0-stdout MODEL_LOG -   File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
2021-07-31 16:15:52,430 [INFO ] W-9002-densenet161_1.0-stdout MODEL_LOG -   File "<frozen importlib._bootstrap>", line 984, in _find_and_load_unlocked
2021-07-31 16:15:52,430 [INFO ] W-9008-densenet161_1.0-stdout MODEL_LOG - Backend worker process died.
2021-07-31 16:15:52,430 [INFO ] W-9002-densenet161_1.0-stdout MODEL_LOG - ModuleNotFoundError: No module named 'image_classifier'

@ArtemisDicoTiar
Copy link
Member Author

@eubinecto
Copy link
Member

@ArtemisDicoTiar torch_serve를 왜 적용해야하는지, 이슈 편집해서 설명해줄 수 있어? #30 (comment) 이런식으로

@ArtemisDicoTiar
Copy link
Member Author

ArtemisDicoTiar commented Aug 1, 2021

이미지를 이용한 mar 예시파일은 무조건 에러뜬다...
다른 예시를 사용해보자
아닌가 torchvision없어서 에러나던건가...? 왜 이거 설치하고 나니깐 에러 안뜨지..?

@ArtemisDicoTiar
Copy link
Member Author

ArtemisDicoTiar commented Aug 1, 2021

오오오오 된다 mnist 예제는 잘된다.

curl --location --request GET 'http://127.0.0.1:8080/predictions/mnist' \
--header 'Content-Type: application/json' \
--data-raw '"iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAAAAABXZoBIAAAAw0lEQVR4nGNgGFggVVj4/y8Q2GOR83n+58/fP0DwcSqmpNN7oOTJw6f+/H2pjUU2JCSEk0EWqN0cl828e/FIxvz9/9cCh1zS5z9/G9mwyzl/+PNnKQ45nyNAr9ThMHQ/UG4tDofuB4bQIhz6fIBenMWJQ+7Vn7+zeLCbKXv6z59NOPQVgsIcW4QA9YFi6wNQLrKwsBebW/68DJ388Nun5XFocrqvIFH59+XhBAxThTfeB0r+vP/QHbuDCgr2JmOXoSsAAKK7bU3vISS4AAAAAElFTkSuQmCC"'

이제 이 예제로 공부해보고 우리 모델에 맞춰 적용해봐야겠다.
전에.
Transformer 예제도 해보자
https://github.com/pytorch/serve/tree/master/examples/Huggingface_Transformers

잘 작동한다.
.mar 파일 생성에 필요한 코드 작성만 잘해보자

@ArtemisDicoTiar
Copy link
Member Author

ArtemisDicoTiar commented Aug 1, 2021

build_setting.json

이 파일에는 모델이름, 설명, 버전만 적자.

config.json

여기 파일은 기존에 루트에 있던 config.json을 그대로 쓰자.

@ArtemisDicoTiar
Copy link
Member Author

mar로 패키징한 파일내에서 wisdomify를 로드 하지 못한다.
pytorch/serve#314
를 참고해서 zip파일로 extra-file을 주고, 핸들러에서 unzip한뒤에 import하게 해보자.

@ArtemisDicoTiar
Copy link
Member Author

서버 실행 후 postman으로 요청 날려보니 잘 나온다! XD

Request

curl --location --request GET 'http://127.0.0.1:8080/predictions/wisdomifier' \
--header 'Content-Type: application/json' \
--data-raw '"해가 뜨질 않으니 비타민D라도 먹어야겠다"
'

Raw response

{
  "\uc0b0\ub118\uc5b4 \uc0b0": 0.6700705885887146,
  "\uac00\ub294 \ub0a0\uc774 \uc7a5\ub0a0": 0.24670909345149994,
  "\uac08\uc218\ub85d \ud0dc\uc0b0": 0.04596260190010071,
  "\ub4f1\uc794 \ubc11\uc774 \uc5b4\ub461\ub2e4": 0.03357168659567833,
  "\uc11c\ub2f9\uac1c \uc0bc \ub144\uc774\uba74 \ud48d\uc6d4\uc744 \uc74a\ub294\ub2e4": 0.0014567646430805326,
  "\uc18c\ubb38\ub09c \uc794\uce58\uc5d0 \uba39\uc744 \uac83 \uc5c6\ub2e4": 0.0014231374952942133,
  "\uafe9 \ub300\uc2e0 \ub2ed": 0.000552495417650789,
  "\uc6d0\uc22d\uc774\ub3c4 \ub098\ubb34\uc5d0\uc11c \ub5a8\uc5b4\uc9c4\ub2e4": 0.00014212122187018394,
  "\uace0\ub798 \uc2f8\uc6c0\uc5d0 \uc0c8\uc6b0 \ub4f1 \ud130\uc9c4\ub2e4": 0.00010553316678851843,
  "\ud551\uacc4 \uc5c6\ub294 \ubb34\ub364 \uc5c6\ub2e4": 6.038087121851277e-06
}

Unicode Decoded response

{
  "산넘어 산": 0.6700705885887146,
  "가는 날이 장날": 0.24670909345149994,
  "갈수록 태산": 0.04596260190010071,
  "등잔 밑이 어둡다": 0.03357168659567833,
  "서당개 삼 년이면 풍월을 읊는다": 0.0014567646430805326,
  "소문난 잔치에 먹을 것 없다": 0.0014231374952942133,
  "꿩 대신 닭": 0.000552495417650789,
  "원숭이도 나무에서 떨어진다": 0.00014212122187018394,
  "고래 싸움에 새우 등 터진다": 0.00010553316678851843,
  "핑계 없는 무덤 없다": 6.038087121851277e-06
}

@ArtemisDicoTiar
Copy link
Member Author

이제 이 서버를 도커라이즈해서 띄울 방법을 모색해보자.

@ArtemisDicoTiar
Copy link
Member Author

ArtemisDicoTiar commented Aug 1, 2021

도커라이즈 생각보다 쉽다. 공식 도커 파일이 있어서 해당 이미지를 베이스 이미지로 깔고.
모델 저장할 폴더만 mkdir 로 생성.
CMD로 서버 실행! (실행할때 모델 저장 폴더만 지정)

로컬에서 실험할때는 실행 명령을 이렇게 해서 포트를 여러개 열었는데
ainize에서는 어떻게 할 수 있으려나...?

docker run --rm -d -it -h "localhost" \
-p 8080:8080 \
-p 8081:8081 \
-p 8082:8082 \
--name wisdom_server wisdom_server

일단 당장 생각난 건 앞단에 nginx를 둬서 reverse-proxy하는 건데... 도커 파일에서 하는 거 쪼금 귀찮아서 일단 보류.
ainize slack에 문의함. (직접 여러 포트를 expose하는 방법에 대해)

@ArtemisDicoTiar
Copy link
Member Author

config setting

설정파일을 작성해서 엔드포인트를 직접 지정할 수 있는 거 같다.

@ArtemisDicoTiar
Copy link
Member Author

그냥 nginx이용해서 reverse-proxy하려고 했다.
하지만 방법이 잘 안된다 ㅠㅠ
더군다나 생각해보니 모델 저장하는 공간을 별도의 host공간에 마운트 시켜놔야할 거 같다..
클라우드도 생각해보자.

@ArtemisDicoTiar
Copy link
Member Author

우분투 이미지에 nginx설치해서 백그라운드로 띄우고, torch-serve를 엔트리포인트로 실행하는 방법을 시도했는데 이미지 생성까지는 성공적인데 컨테이너가 실행이 안된다.
ainize 서버에 띄워보고 안되면 방법 바꾸자!

@ArtemisDicoTiar
Copy link
Member Author

ArtemisDicoTiar commented Aug 4, 2021

torchserve에 남긴 이슈!

포트 하나로 통일 해줘라!!! XD
pytorch/serve#1177

@eubinecto eubinecto added this to TODO in sprint2 Aug 13, 2021
@eubinecto eubinecto moved this from TODO to DOING in sprint2 Aug 13, 2021
@ArtemisDicoTiar
Copy link
Member Author

일단 버전 1까지는 플라스크로
추후 GCP GKE혹은 AWS EKS (쿠버네티스)에 배포하게 되면 다시 이슈 파서 진행!

@ArtemisDicoTiar ArtemisDicoTiar moved this from DOING to DONE in sprint2 Aug 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Development

No branches or pull requests

2 participants