테스트 환경
KServe 0.13에서 테스트를 진행하였고, v2 protocol을 사용했다.
개요
KServe Transformer는 머신러닝 모델의 입력 전처리와 출력 후처리를 담당하는 컴포넌트다.
사용자가 KServe endpoint에 추론 요청을 보내면, transformer는 사용자가 입력한 input 데이터를 모델에 맞게 전처리한 후 predictor로 요청을 전송한다.
추론이 완료되면 predictor는 transformer로 결괏값을 전송하고, transformer는 결괏값을 사용자 친화적인 값으로 수정 후 사용자에게 전달한다.
이 가이드에서는 Iris 꽃 분류 모델을 위한 Transformer를 개발하는 과정을 설명한다.
전처리가 필요하지 않은 데이터라, 전처리 과정은 필요없지만, 구조를 설명하기 위해 단순히 입력받은 값을 그대로 predictor로 전달하도록 했고, 추론의 결과를 카테고리와 매핑해 사용자에게 전달하는 후처리 과정을 추가했다.
Model 준비
아래 링크에서 생성한 모델을 기반으로 transformer를 구현한다.
KServe를 이용한 scikit-learn 모델 배포 및 사용 가이드
Transformer 클래스 구현
전체적인 구조는 아래와 같으며, preprocess와 postprocess 함수에 로직을 구현하면 된다.
전처리, 후처리가 모두 필요한 경우가 아니라면 둘 중 하나만 구현해도 상관없다.
import argparse
from typing import Dict, Union
from kserve import (
Model,
ModelServer,
model_server,
InferInput,
InferRequest,
InferResponse,
)
from kserve.model import PredictorConfig
class IrisTransformer(Model):
def __init__(
self,
name: str,
predictor_host: str,
predictor_protocol: str,
predictor_use_ssl: bool,
):
super().__init__(
name, PredictorConfig(predictor_host, predictor_protocol, predictor_use_ssl)
)
self.ready = True
def preprocess(
self, payload: Union[Dict, InferRequest], headers: Dict[str, str] = None
) -> Union[Dict, InferRequest]:
# InferRequest 객체 처리
data = payload.inputs[0].data
# 모델 입력 형식으로 변환
infer_inputs = [InferInput(name="INPUT__0", datatype="FP32", shape=[len(data), 4], data=data)]
return InferRequest(model_name=self.name, infer_inputs=infer_inputs)
def postprocess(
self, infer_response: Union[Dict, InferResponse], headers: Dict[str, str] = None
) -> Union[Dict, InferResponse]:
categories = ['setosa', 'versicolor', 'virginica']
predictions = infer_response.outputs[0].data
results = [categories[int(pred)] for pred in predictions]
return InferResponse(
model_name=self.name,
infer_outputs=[InferInput(name="OUTPUT__0", datatype="BYTES", shape=[len(results)], data=results)],
response_id=infer_response.id
)
parser = argparse.ArgumentParser(parents=[model_server.parser])
args, _ = parser.parse_known_args()
if __name__ == "__main__":
model = IrisTransformer(
args.model_name,
predictor_host=args.predictor_host,
predictor_protocol=args.predictor_protocol,
predictor_use_ssl=args.predictor_use_ssl,
)
ModelServer().start([model])
이미지 빌드를 위한 requirements.txt 작성
이미지에 설치해야할 파이썬 패키지 리스트를 작성한다.
kserve==0.13.1
argparse
pandas
이미지 빌드를 위한 Dockerfile 작성
KServe에 배포한 모델이 v2 protocol을 사용하기에 `--predictor_protocol`을 v2로 설정했다.
v1 protocol을 사용하는 경우에는 `--predictor_protocol`을 별도로 지정하지 않아도 된다.
FROM python:3.8-slim
COPY requirements.txt .
COPY transformer transformer
RUN pip install -U pip && pip install -r requirements.txt
ENTRYPOINT ["python", "-m", "transformer.iris-transformer", "--predictor_protocol", "v2"]
Docker 이미지 빌드
전체 디렉토리 구조는 다음과 같다.
├── Dockerfile
├── requirements.txt
└── transformer
└── iris-transformer.py
아래의 명령어를 사용해 이미지를 빌드하고, private container registry에 push한다.
docker build -t registry.dd.io/shared/iris-transformer .
docker push registry.dd.io/shared/iris-transformer
Harbor 접근을 위한 서비스 계정을 생성
Harbor에 접근하기 위한 secret을 생성한다.
kubectl create secret generic docker-config-secret --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson -n kubeflow-user-example-com
S3, Harbor에 접근하기 위한 ServiceAccount를 생성한다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: s3-sa
namespace: kubeflow-user-example-com
secrets:
- name: s3-secret
- name: docker-config-secret
InferenceService 생성
Transformer를 포함한 InferenceService를 배포하기 위해 다음과 같은 YAML 파일을 작성
apiVersion: "serving.kserve.io/v1beta1"
kind: "InferenceService"
metadata:
name: "iris-svm-transformer"
spec:
predictor:
serviceAccountName: s3-sa
model:
modelFormat:
name: sklearn
runtime: kserve-sklearnserver
protocolVersion: v2
storageUri: "s3://sandbox/iris_svm/v1/"
transformer:
containers:
- image: registry.dd.io/shared/iris-transformer:latest
name: kserve-container
테스트
배포된 모델에 예측 요청을 보낸다.
import requests
import json
import pandas as pd
from pprint import pprint
df = pd.DataFrame({
'sepal length (cm)': [5.1, 4.9, 4.7],
'sepal width (cm)': [3.5, 3.0, 3.2],
'petal length (cm)': [1.4, 1.4, 1.3],
'petal width (cm)': [0.2, 0.2, 0.2]
})
# KServe v2 프로토콜에 맞는 payload 구성
payload = {
"inputs": [
{
"name": "INPUT__0",
"shape": list(df.shape),
"datatype": "FP32",
"data": df.values.tolist()
}
]
}
resp = requests.post(
"http://iris-svm-transformer.kubeflow-user-example-com.10.0.2.6.sslip.io/v2/models/iris-svm-transformer/infer",
data=json.dumps(payload)
)
pprint(resp.json())
결괏값을 보면 transformer를 통해 카테고리와 매핑된 값이 리턴된 것을 확인할 수 있다.
{'id': '65f0f7d8-8c8a-46b3-a685-5871c59e1ed4',
'model_name': 'iris-svm-transformer',
'model_version': None,
'outputs': [{'data': ['setosa', 'setosa', 'setosa'],
'datatype': 'BYTES',
'name': 'OUTPUT__0',
'parameters': None,
'shape': [3]}],
'parameters': None}
'kubenetes' 카테고리의 다른 글
KServe v2 프로토콜: 모델 메타데이터 (2) | 2024.10.01 |
---|---|
KServe Custom Predictor 이미지 빌드 가이드 - v2 protocol (1) | 2024.10.01 |
KServe Private container registry certificates 설정 (0) | 2024.09.29 |
MLflow와 KServe를 이용한 모델 배포 가이드 (4) | 2024.09.26 |
KServe를 이용한 scikit-learn 모델 배포 및 사용 가이드 (1) | 2024.09.26 |
댓글