Seldon Core – MLflow Server

학습이 완료된 MLflow모델을 저장 한 경우, Seldon의 사전 패키지 된 MLflow 서버를 사용하여 간단히 배포 할 수 있습니다. 그리고 conda.yaml 파일을 이용하면, MLflow 서버의 초기화 중 Conda 환경을 만들 수 도 있습니다.

전제 조건

MLflow 서버를 사용하려면 다음 전제 조건이 충족되어야합니다.

MFLow에서 제공하는 모델 형태

MLFlow 에서 제공하고 있는 주요 모델 형태는 다음과 같습니다.

  • Python Function (python_function)
  • Keras (keras)
  • PyTorch (pytorch)
  • Scikit-learn (sklearn)
  • Spark MLlib (spark)
  • TensorFlow (tensorflow)
  • XGBoost (xgboost)

Conda 환경 생성

MLflow 서버는 여러가지 머신러닝 프레임워크를 지원합니다. 그래서 모델 서버를 실행할 때 필요한 패키지들을 설치할 필요가 있습니다. MLflow 서버는 conda.yaml 파일을 이용하여 모델 서버에 실행에 필요한 환경을 만듭니다. SeldonDeployment 에 의해서 모델 서버가 생성될 때, 모델 저장 위치에 있는 conda.yaml 파일을 읽어와서 Conda 환경을 구성합니다.

모델 생성

MLflow 서버를 사용하기 위해 파이썬을 사용한 간단한 scikit-learn 모델을 생성한 후, MLFlow를 이용하여 저장하겠습니다.

Scikit-learn의 기본적인 데이터셋 중의 하나인 아이리스 꽃 데이터를 사용하여, 아이리스 꽃을 분류하는 모델을 작성해 보겠습니다. mlflow.sklearn.save_model() 메소드를 이용하여 MLflow 형식으로 모델을 저장합니다.

from joblib import dump
from sklearn import datasets
from sklearn import svm

clf = svm.SVC(gamma='scale')
iris = datasets.load_iris()
X, y = iris.data, iris.target
clf.fit(X, y)
mlflow.sklearn.save_model(clf, path=model_path)

모델 저장하기

쿠버네티스의 퍼시스턴스 볼륨에 모델을 저장해 보겠습니다. PVC 는 앞서 생성한 seldon-models-pvc 을 사용하겠습니다. 모델을 학습시키기 위해서 쿠버네티스 잡(Job)을 사용하겠습니다. Job을 생성할 때 모델을 저장하기 위한 PVC를 마운트 해줍니다.

모델 코드 작성하기

아이리스 꽃을 분류하는 간단한 모델입니다. 모델을 저장할 위치를 --model_path 파라미터로 입력받게 하였습니다.

mlflow_sklearn_iris.py

import argparse
import os

import mlflow
import mlflow.sklearn
from sklearn import datasets
from sklearn import svm

def train():
    parser = argparse.ArgumentParser()
    parser.add_argument('--model_path', default='/mnt/pv/mlflow/sklearn/iris/model', type=str)
    args = parser.parse_args()

    # if not (os.path.isdir(args.model_path)):
    #     os.makedirs(args.model_path)
    # os.rmdir(args.model_path)

    clf = svm.SVC(gamma='scale')
    iris = datasets.load_iris()
    X, y = iris.data, iris.target
    clf.fit(X, y)
    print('Finished Training')

    conda_env = {
        'name': 'mlflow-env',
        'channels': ['defaults'],
        'dependencies': [
            'python=3.7.0',
            'scikit-learn=0.20.3'
        ]
    }

    mlflow.sklearn.save_model(clf, path=args.model_path, conda_env=conda_env)

if __name__ == '__main__':
    train()

컨테이너 이미지를 만들기

컨테이너 이미지를 만들기 위한 Dockerfile 입니다. 파이썬을 기본 이미지로 사용하고, scikit-learn 패키지를 추가로 설치합니다.

Dockerfile

FROM python:3.7-slim

RUN pip install scikit-learn==0.20.3 mlflow

RUN mkdir -p /app
ADD mlflow_sklearn_iris.py /app/

쿠버네티스 잡 실행하기

컨테이너 이미지를 빌드하고, 컨테이너 이미지 레지스트리에 푸시 한 다음, 쿠버네티스 잡(Job)을 생성하겠습니다.

Job을 생성할 때는 모델을 저장하기 위해서 PVC를 마운트 해줍니다. 이 일련의 작업들은 직접 실행 할 수 있습니다. 하지만 좀 더 편하게 하기 위해서 앞서 배운 Kubeflow Fairing을 사용하겠습니다.

다음은 로컬 개발 환경에서 Fairing을 사용하여 컨테이너 이미지를 만들고, 쿠버네티스 잡을 실행하는 예제입니다.

fairing-local-docker.py

import uuid
from kubeflow import fairing
from kubeflow.fairing.kubernetes import utils as k8s_utils

CONTAINER_REGISTRY = 'kangwoo'

namespace = 'admin'
job_name = f'mlflow-sklean-iris-job-{uuid.uuid4().hex[:4]}'

command=["python", "mlflow_sklearn_iris.py", "--model_path", "/mnt/pv/mlflow/sklearn/iris/model"]
output_map = {
    "Dockerfile": "Dockerfile",
    "mlflow_sklearn_iris.py": "mlflow_sklearn_iris.py"
}

fairing.config.set_preprocessor('python', command=command, path_prefix="/app", output_map=output_map)

fairing.config.set_builder('docker', registry=CONTAINER_REGISTRY, image_name="mlflow-sklean-iris", dockerfile_path="Dockerfile")

fairing.config.set_deployer('job', namespace=namespace, job_name=job_name,
                            pod_spec_mutators=[k8s_utils.mounting_pvc(pvc_name='seldon-models-pvc', pvc_mount_path='/mnt/pv')],
                            cleanup=True, stream_log=True)

fairing.config.run()

fairing을 실행하면 쿠버네티스 잡이 생성되고, 학습이 완료된 모델이 지정한 경로에 저장됩니다.

MLflow을 사용하는 SeldonDeployment 로 배포 하기

SeldonDeployment 생성

SeldonDeployment 매니페스트를 작성합니다. predictor의 구현체를 MLFLOW_SERVER 로 사용합니다. modelUri 필드로 모델 저장 위치를 지정해 줍니다. pvc 의 이름이 seldon-models-pvc 이고 저장 위치가 mlflow/sklearn/iris/model 이므로, pvc://seldon-models-pvc/mlflow/sklearn/iris/model 라고 지정해 줍니다.

mlflow-sklearn-iris.yaml

apiVersion: machinelearning.seldon.io/v1alpha2
kind: SeldonDeployment
metadata:
  name: mlflow-sklearn-iris
spec:
  name: mlflow-sklearn-iris
  predictors:
    - graph:
        children: []
        implementation: MLFLOW_SERVER
        modelUri: pvc://seldon-models-pvc/mlflow/sklearn/iris/model
        name: classifier
      name: default
      replicas: 1

SeldonDeployment 를 생성합니다.

다음은 admin 네임스페이스 SeldonDeployment 를 생성하는 예제입니다.

kubectl -n admin apply -f mlflow-sklearn-iris.yaml

생성한 SeldonDeployment를 조회해 보겠습니다.

kubectl -n admin get seldondeployment mlflow-sklearn-iris -o yaml

SeldonDeployment 가 정상적으로 생성되면 다음과 같은 응답 결과를 확인할 수 있습니다.

apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
  ...
spec:
  ...
status:
  deploymentStatus:
    mlflow-sklearn-iris-default-8c791aa:
      availableReplicas: 1
      replicas: 1
  serviceStatus:
    mlflow-sklearn-iris-mlflow-sklearn-iris-default:
      grpcEndpoint: mlflow-sklearn-iris-mlflow-sklearn-iris-default.admin:5001
      httpEndpoint: mlflow-sklearn-iris-mlflow-sklearn-iris-default.admin:8000
      svcName: mlflow-sklearn-iris-mlflow-sklearn-iris-default
    seldon-d9062e953c9534d009e3dc84a7d3707a:
      httpEndpoint: seldon-d9062e953c9534d009e3dc84a7d3707a.admin:9000
      svcName: seldon-d9062e953c9534d009e3dc84a7d3707a
  state: Available

SeldonDeploymentstateAvailable 이면 예측을 요청 할 수 있습니다.

예측 실행하기

예측을 요청하기 위해서는 모델 서버에 접근해야 합니다. 모델 서버는 ingressgateway 를 통해서 접근할 수 있습니다. ingressgateway 는 모델 서버들을 구분하기 위해서 호스트 이름을 사용합니다. ingressgateway에 접근하 기 위한 주소는 앞서 정의한 CLUSTER_IP 를 사용하겠습니다.

예측을 요청할 데이터를 json 파일로 작성합니다.

iris-input.json

{
  "data": {
    "ndarray": [
      [6.8,  2.8,  4.8,  1.4],
      [6.0,  3.4,  4.5,  1.6]
    ]
  }
}

다음은 admin 네임스페이스의 sklearn-iris SeldonDeployment 에 예측을 요청하는 예제입니다.

MODEL_NAME=mlflow-sklearn-iris
NAMESPACE=admin

INPUT_PATH=@./iris-input.json
curl -v -H "Content-Type: application/json" http://$CLUSTER_IP/seldon/${NAMESPACE}/${MODEL_NAME}/api/v1.0/predictions -d $INPUT_PATH

정상적으로 실행되면 다음과 같은 응답 결과를 확인 할 수 있습니다.

*   Trying 192.168.21.38...
* TCP_NODELAY set
* Connected to 192.168.21.38 (192.168.21.38) port 32380 (#0)
> POST /seldon/admin/mlflow-sklearn-iris/api/v1.0/predictions HTTP/1.1
> Host: 192.168.21.38:32380
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 96
> 
* upload completely sent off: 96 out of 96 bytes
< HTTP/1.1 200 OK
< x-content-type-options: nosniff
< vary: Origin,Access-Control-Request-Method,Access-Control-Request-Headers
< content-type: application/json;charset=utf-8
< content-length: 261
< date: Thu, 09 Apr 2020 16:02:46 GMT
< x-envoy-upstream-service-time: 100
< server: istio-envoy
< 
{
  "meta": {
    "puid": "geuhtnu2stgad08ngp9c0382oi",
    "tags": {
    },
    "routing": {
    },
    "requestPath": {
      "classifier": "seldonio/mlflowserver_rest:0.2"
    },
    "metrics": []
  },
  "data": {
    "names": [],
    "ndarray": [1, 1]
  }
}