본문 바로가기
kubenetes

MLflow와 KServe를 이용한 모델 배포 가이드

by kyeongseo.oh 2024. 9. 26.

KServe를 사용해 MLflow를 사용해 빌드한 모델을 배포하는 방법에 대해 소개한다.

 

1. 버전 호환성 및 시그니처 제한사항

MLflow 버전에 따라 signature에 제한사항이 있다. KServe v0.13 에서 사용하는 MLServer 이미지(index.docker.io/seldonio/mlserver)에 포함된 MLflow 버전은 2.3.1이다.

지금 설치해서 사용하고 있는 MLflow의 버전이 2.15.1라, 이 두 버전 사이의 차이를 설명한다.

 

MLflow 2.3.1

  • feature 이름 정보를 유지하면서 model signature를 정의할 수 있다.
  • ColSpec을 사용하여 각 특성의 이름과 데이터 타입을 명시적으로 지정할 수 있다.

MLflow 2.15.1

  • 전체 입력을 하나의 텐서로 취급하는 TensorSpec만 사용 가능하다.
  • feature 이름 정보가 signature에 포함되지 않는다.
  • input schema에 tensor가 아닌 값을 지정하면 pod에서 `TypeError: __init__() got an unexpected keyword argument 'required'` 오류가 발생한다.

2. 모델 생성 및 MLflow로 저장

MLflow Tracking Server 사용이 가능한 경우는 `mlflow.sklearn.save_model()`을 사용하고, 사용이 불가능한 경우에는

`mlflow.sklearn.log_model()`를 사용한다.

 

MLflow 2.3.1 사용 시:

pip install mlflow[extra]==2.3.1

 

save_model 메서드를 사용해 모델 파일을 로컬 `./v231` 경로에 저장한다.

import mlflow
from sklearn import svm
from sklearn import datasets
from mlflow.models.signature import infer_signature
import pandas as pd

# load data
iris = datasets.load_iris()
X, y = iris.data, iris.target

# 모델 생성 및 학습
clf = svm.SVC(gamma='scale')
clf.fit(X, y)

# 예측 수행
pred = clf.predict(X)

# signature 생성
iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
signature = infer_signature(iris_df, y)

# 모델 저장
mlflow.sklearn.save_model(
    clf,
    path="./v231",
    signature=signature,
)

 

MLflow 2.15.1 사용 시:

pip install mlflow[extra]==2.15.1

 

autolog 메서드를 사용해 MLflow tracking server에 모델을 저장한다.

import mlflow

from sklearn import svm, datasets

mlflow.set_tracking_uri("http://mlflow.dd.io")

mlflow.set_experiment("Default")

mlflow.sklearn.autolog()

# Load wine quality dataset
iris = datasets.load_iris()
X, y = iris.data, iris.target

# Start a run and train a model
with mlflow.start_run(run_name="mlflow-kserve"):
    clf = svm.SVC(gamma='scale')
    clf.fit(X, y)

 

3. KServe를 사용해 모델을 배포하기 위해 모델을 스토리지에 업로드한다.

MLflow 2.3.1 사용 시:

`./v231` 경로에 생성된 파일을 모두 스토리지에 업로드한다. 이 예제에서는 `s3://sandbox/iris_svm/v231`에 업로드했다.

 

 

MLflow 2.15.1 사용 시:

tracking server에 의해 `s3://mlflow/0/ab2a8bf253ac46789339e4cba0df8804/artifacts/model` 모델이 자동으로 업로드된다.

 

4. S3 접근을 위한 서비스 계정을 생성

저장소에 저장되어 있는 모델 파일에 접근하기 위한 Secret과 ServiceAccount을 생성한다.

AWS_ACCESS_KEY_ID와 AWS_SECRET_ACCESS_KEY는 base64로 인코딩된 값을 입력한다.

apiVersion: v1
kind: Secret
metadata:
  name: s3-secret
  namespace: kubeflow-user-example-com
  annotations:
    serving.kserve.io/s3-endpoint: http://10.0.2.3 # 실제 MinIO 엔드포인트로 변경
    serving.kserve.io/s3-usehttps: "0" # MinIO가 HTTP를 사용하는 경우
type: Opaque
data:
  AWS_ACCESS_KEY_ID: dkwzQW92VXBsakVqRnVmd3UzSUE=
  AWS_SECRET_ACCESS_KEY: WVdMNWhzMkI4Q0xxYVhzN0tHcDBxemY2SnRrMmxmQ0NnT0pMUFQ5eA==
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: s3-sa
  namespace: kubeflow-user-example-com
secrets:
  - name: s3-secret

 

5. KServe InferenceService 정의 및 적용

MLflow 2.3.1 사용 시:

apiVersion: "serving.kserve.io/v1beta1"
kind: "InferenceService"
metadata:
  name: "iris-svm-v231"
spec:
  predictor:
    serviceAccountName: s3-sa
    model:
      modelFormat:
        name: mlflow
      protocolVersion: v2
      storageUri: "s3://sandbox/iris_svm/v231"

 

MLflow 2.15.1 사용 시:

apiVersion: "serving.kserve.io/v1beta1"
kind: "InferenceService"
metadata:
  name: "iris-svm-v2151"
spec:
  predictor:
    serviceAccountName: s3-sa
    model:
      modelFormat:
        name: mlflow
      protocolVersion: v2
      storageUri: "s3://mlflow/0/ab2a8bf253ac46789339e4cba0df8804/artifacts/model"

 

6. 모델 메타데이터 확인

signature에 따라 모델 메타데이터 및 입력 데이터 구조가 변경된다. 추론 요청을 보내기 위해 모델의 메타 데이터를 확인한다.

 

  • MLflow 2.3.1 및 ColSpec 사용한 모델의 메타데이터 확인:
curl http://iris-svm-v231.kubeflow-user-example-com.10.0.2.6.sslip.io/v2/models/iris-svm-v231

 

  • return 값
더보기

{
  "name": "iris-svm-v231",
  "versions": [],
  "platform": "",
  "inputs": [
    {
      "name": "sepal length (cm)",
      "datatype": "FP64",
      "shape": [
        -1
      ],
      "parameters": {
        "content_type": "np"
      }
    },
    {
      "name": "sepal width (cm)",
      "datatype": "FP64",
      "shape": [
        -1
      ],
      "parameters": {
        "content_type": "np"
      }
    },
    {
      "name": "petal length (cm)",
      "datatype": "FP64",
      "shape": [
        -1
      ],
      "parameters": {
        "content_type": "np"
      }
    },
    {
      "name": "petal width (cm)",
      "datatype": "FP64",
      "shape": [
        -1
      ],
      "parameters": {
        "content_type": "np"
      }
    }
  ],
  "outputs": [
    {
      "name": "output-0",
      "datatype": "INT32",
      "shape": [
        -1
      ],
      "parameters": {
        "content_type": "np"
      }
    }
  ],
  "parameters": {
    "content_type": "pd"
  }
}

 

  • MLflow 2.15.1 및 TensorSpec 사용한 모델의 메타데이터 확인:
curl http://iris-svm-v2151.kubeflow-user-example-com.10.0.2.6.sslip.io/v2/models/iris-svm-v2151

 

  • return 값
더보기

{
  "name": "iris-svm-v2151",
  "versions": [],
  "platform": "",
  "inputs": [
    {
      "name": "input-0",
      "datatype": "FP64",
      "shape": [
        -1,
        4
      ],
      "parameters": {
        "content_type": "np"
      }
    }
  ],
  "outputs": [
    {
      "name": "output-0",
      "datatype": "INT32",
      "shape": [
        -1
      ],
      "parameters": {
        "content_type": "np"
      }
    }
  ],
  "parameters": {
    "content_type": "np"
  }
}

 

7. 예측 요청 보내기

InferenceService endpoint로 추론 요청을 보낸다. signature에 따라 input data의 포맷이 달라지므로, 모델 메타데이터를 통해 관련 내용을 확인한 후 예측 요청을 보내야한다.

 

  • MLflow 2.3.1 및 ColSpec 사용한 모델에 추론 요청:
import requests
import json
from sklearn import datasets
import pandas as pd
from pprint import pprint

# Iris 데이터셋 로드
iris = datasets.load_iris()

# 3건만 예측에 사용
iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names).head(3)

sklear_iris_input = {"inputs": []}

for column in iris_df.columns:
    sklear_iris_input["inputs"].append({
        "name": column,
        "shape": [len(iris_df)],
        "datatype": "FP64",
        "data": iris_df[column].tolist()
    })

resp = requests.post(
    "http://iris-svm-v231.kubeflow-user-example-com.10.0.2.6.sslip.io/v2/models/iris-svm-v231/infer", 
    data=json.dumps(sklear_iris_input)
)   
kserve_result = resp.json()

pprint(kserve_result)

 

  • return 값
{'id': 'e137e52a-dbec-4c79-9110-2c4262611e19',
 'model_name': 'iris-svm-v231',
 'outputs': [{'data': [0, 0, 0],
              'datatype': 'INT32',
              'name': 'output-1',
              'shape': [3, 1]}],
 'parameters': {}}

 

  • MLflow 2.15.1 및 TensorSpec 사용한 모델의 메타데이터 확인:
import requests
import json
import numpy as np
from sklearn import datasets
from pprint import pprint

# Iris 데이터셋 로드
iris = datasets.load_iris()
input_data = iris.data[:3]

# KServe 요청 형식에 맞게 데이터 구성 (단일 텐서로)
request_data = {
    "inputs": [
        {
            "name": "input-0",
            "shape": list(input_data.shape),
            "datatype": "FP64",
            "data": input_data.tolist()
        }
    ]
}

# KServe 엔드포인트 URL
url = "http://iris-svm-v2151.kubeflow-user-example-com.10.0.2.6.sslip.io/v2/models/iris-svm-v2151/infer"

# 요청 보내기
resp = requests.post(url, json=request_data)
pprint(resp.json())

 

  • return 값
{'id': 'e8488d10-53c6-466f-a25b-f1f601bc225c',
 'model_name': 'iris-svm-v2151',
 'outputs': [{'data': [0, 0, 0],
              'datatype': 'INT32',
              'name': 'output-1',
              'shape': [3, 1]}],
 'parameters': {}}

댓글