'데이터 엔지니어'로 성장하기

정리하는 걸 좋아하고, 남이 읽으면 더 좋아함

AI/MLOps

데이터 잡부의 MLOps도전기 (3) - 모델 배포를 위해 FaaS를 도입해보자 #Nuclio

MightyTedKim 2024. 3. 31. 22:49
728x90
반응형

Model의 동적 배포를 위해서, FaaS를 도입한 이야기를 적어봤습니다.
 

글의 구조는 아래와 같습니다.

  1. Nuclio가 무엇인가요?
  2. 왜 굳이 Nuclio인가요?
  3. CVAT과 어떻게 연동을 하나요?

.
글의 타깃 독자는 아래와 같습니다.

  1. Nuclio(FaaS)에 대해 궁금하신 분
  2. Vision ML 인퍼런싱을 고려하는 분
  3. CVAT을 사용하시는 분


지난 포스팅에서는 아래 2가지를 적었고

  1. Vision MLOps 프로젝트 투입: https://mightytedkim.tistory.com/205
  2. 라벨링 툴로 CVAT을 선택한 이유: https://mightytedkim.tistory.com/206

이번에는 `동적 모델링`을 위해 ‘FaaS’인 ‘Nuclio’를 도입한 내용을 적었어요.

1. Nuclio가 뭐에요?

nuclio는 FaaS 툴이에요. (보통 aws lambda로 FaaS에 많이들 익숙하실거에요)

 
제가 생각한 장점은 다음과 같아요

  • 가벼움 (DB도 없음)
  • 트리거 많음(http, kafka, grpc)
  • 성능 좋음 (event 처리 초당 400,000)
  • 연계 많음 (Kubeflow, Jupyter, CVAT, MLRun, Loki)

홈페이지의 장점

단점은 하나에요.

  • 레퍼런스가 별로 없어요 

하지만!! 제가 대신 삽질을 했습니다.

 
 
개발자한테 궁금한거는 slack으로 물어보며 진행했어요 ㅎ 

질문했던 예시

 

2. 왜 굳이 Nuclio인가요?

저 같은 경우 model inferencing을 위해 여러 툴들을 검토했어요.
BentoML, FastAPI 를 고려하다가 BentoML로 거의 기울었는데,
`오픈소스 갯수 최소화` 라는 요구사항이 들어와서 좀 더 가벼운 Nuclio를 선택했어요.
어짜피 CVAT을 라벨링 툴로 선택했으니, 연계가 쉬운 Nuclio가 더 좋기도 하고요.
 
Nuclio는 DB도 없이, 아래 명령어 하나면 끝이에요.

 
구조는 dashboard가 일종의 operator가 되어서 docker나 k8s에 대신 container를 실행해줘요.
이렇게 실행된 container는 아래의 ` Nuclio Processor `에요.

https://github.com/nuclio/nuclio

 
Nuclio Processor를 조금 더 볼게요. Triggerworker가 있어요. 
Trigger는 뭔가 Event 처리하는 것 같고, Worker는 `worker pools` 라는게 나오죠?

Trigger는 event를 받아요. http를 예로 들어볼게요.
아래 코드는 'Segment Anything Model' 을 인퍼런싱하는 코드에요.
handler 함수의 event 파라미터를 넘겨받는게 끝이에요.
 

https://github.com/opencv/cvat/tree/v2.5.2/serverless/pytorch/ultralytics/yolov5/nuclio

 
 
 
이제 Worker를 보죠. 예를 들어 제가 worker 최댓값을 4로 설정하면 이렇게 pool이 생겨요

root@03d88a37705a:/opt/nuclio# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 12:47 ?        00:00:00 processor
root          32       1  2 12:47 ?        00:00:03 /usr/bin/python3 -u /opt/nuclio/_nuclio_wrapper.py --handler main:handler --socket-path /tmp/nuclio-rpc-co4lmibossh000ep38c0.sock --platform-kind local --namespace nuclio --wo
root          33       1  2 12:47 ?        00:00:03 /usr/bin/python3 -u /opt/nuclio/_nuclio_wrapper.py --handler main:handler --socket-path /tmp/nuclio-rpc-co4lmibossh000ep38d0.sock --platform-kind local --namespace nuclio --wo
root          34       1  2 12:47 ?        00:00:03 /usr/bin/python3 -u /opt/nuclio/_nuclio_wrapper.py --handler main:handler --socket-path /tmp/nuclio-rpc-co4lmibossh000ep38dg.sock --platform-kind local --namespace nuclio --wo
root          35       1  2 12:47 ?        00:00:03 /usr/bin/python3 -u /opt/nuclio/_nuclio_wrapper.py --handler main:handler --socket-path /tmp/nuclio-rpc-co4lmibossh000ep38cg.sock --platform-kind local --namespace nuclio --wo
root        1395       0  0 12:49 pts/0    00:00:00 bash
root        1426    1395  0 12:49 pts/0    00:00:00 ps -ef

 
worker 설정 변경은 script에서 할 수도 있고, ui에서 할 수도 있어요

 
그래서 worker가 왜 좋냐. container로 뜨면 tmux 처럼 session이 나눠져있으니 바로 처리해서 응답을 줄 수 있어요.
초당 400,000개까지 가능하고 실제로 실시간 인퍼런싱에 많이 사용이 된다고 해요.

어떻게 사용되는지 간단한 예시로 설명할게요
Nuclio Processor 가 이미 container로 실행 중이고, 이미지의 좌표들을  받을거에요

Nuclio function response: [
{"confidence": 0.8961721062660217, "label": "person", "points": [671.7879028320312, 395.37213134765625, 810.0, 878.361328125], "type": "rectangle"}, 
{"confidence": 0.8702478408813477, "label": "person", "points": [220.6570587158203, 408.1410217285156, 346.1673278808594, 867.3811645507812], "type": "rectangle"}, 
{"confidence": 0.8515626788139343, "label": "person", "points": [49.2508430480957, 389.99053955078125, 248.07818603515625, 912.4585571289062], "type": "rectangle"}, 
{"confidence": 0.8493338227272034, "label": "bus", "points": [12.65086841583252, 223.37843322753906, 809.7070922851562, 788.5164794921875], "type": "rectangle"}, 
{"confidence": 0.534941554069519, "label": "person", "points": [0.04542803764343262, 552.4113159179688, 67.88233184814453, 875.3746337890625], "type": "rectangle"}
]

 
좌표를 이미지에 뿌리면 아래 그림처럼 나올거고요.

 
샘플 코드에요.

import requests
import base64
import json

# 1. URL of the Nuclio function's HTTP trigger endpoint
## 32768 포트에 nuclio processor가 떠있다고 가정하고, endpoint를 설정해줘요
nuclio_endpoint_url = "http://192.168.219.***:32768"

# 2. Read the binary image data from a file
## image를 읽어서 base64로 만들어줘요
## nuclio processor에서 'image' key 를 읽어서 base64 decode 하도록 코드 짜놨어요. (취향)
with open('bus.jpg', 'rb') as image_file: 
    image_binary = image_file.read()
    # Encode the binary image data to base64
    image_base64 = base64.b64encode(image_binary).decode('utf-8')
data = {'image': image_base64}

# 3. Send the binary image data as part of the POST request
## api 요청을 보냅니다.
response = requests.get(nuclio_endpoint_url, data=json.dumps(data), headers={"Content-Type": "application/json"})

# 4. Check the response
## 받고 확인해요
if response.status_code == 200:
    result = response.text  # Assuming the function returns a response
    print(f"Nuclio function response: {result}")
else:
    print(f"Error: {response.status_code}, {response.text}")

 
nuclio관련 하고 싶은 말은 진짜 많은데... 글이 더 길어질 것 같아서 다음글에서 정리할게요.
 

3. CVAT과 어떻게 연동을 하나요?

그래서 nuclio가 가볍고, 병렬처리 되는건 알겠는데 cvat과는 어떤 관계일까요.
https://github.com/opencv/cvat/tree/develop/cvat/apps
이걸 알기 위해서는 cvat 코드를 뜯어봐야해요.
 
여기도 AWS Lambda가 FaaS인 것처럼 LambdaGateway 라는 class를 사용해요.
cvat/apps/lambda_manager/views.py

 
기본 namespace는 `nuclio`기 때문에 k8s 사용하시는 분들은 그냥 nuclio 명칭 사용하시면 되요.

 
LambdaFunction 클래스는 응답을 받아서 cvat에 맞게 형식을 맞춰줍니다.
그러면 이런식으로 cvat에서 FaaS인 nuclio를 사용할 수 있어요!
 
실제 예시에요
Interactor라는 형식을 이용해, Facebook의 SAM(Segment Anything Model)

 
Detector를 이용한 object detection 

모델은 pod 형태로 배포되고, 관리 페이지에서 설정값을 조절 할 수 있답니다. ㅎ

 
 

마무리

오늘도 혼자 신나서 쓰다가 너무 많이 쓴게 아닐까 싶네요.
그래도 Nuclio 관련 한글 내용은 90%가 다 hello world여서,
실제 사용해본 경험을 공유하는 건 의미가 있을 거라고 생각해요,
 
nuclio는 loki로 로그도 연결할 수 있고, autoscaling도 됩니다.
jupyter를 이용해 배포할 수 있고 kfp 버전만 맞다면 MLRun이라는 툴과 연계도 가능해요.
 
다음 글은 CVAT과 Nuclio를 이용해 라벨링부터 학습까지 연결했는지 적어볼게요 :)

728x90
반응형