Seldon Core – Tensorflow Serving

학습이 완료된 Tensorflow 모델을 저장 한 경우, Seldon의 사전 패키지 된 TensorFlow 서버를 사용하여 간단히 배포 할 수 있습니다.

전제 조건

  • REST의 경우 다음에 대한 파라미터를지정해야합니다.
    • signature_name
    • model_name
apiVersion: machinelearning.seldon.io/v1alpha2
kind: SeldonDeployment
metadata:
  name: tfserving
spec:
  name: mnist
  predictors:
  - graph:
      children: []
      implementation: TENSORFLOW_SERVER
      modelUri: pvc://seldon-models-pvc/tensorflow/mnist/model
      name: mnist-model
      parameters:
        - name: signature_name
          type: STRING
          value: serving_default
        - name: model_name
          type: STRING
          value: mnist-model
    name: default
    replicas: 1

  • GRPC의 경우 다음에 대한 파라미터를 지정해야합니다.
    • signature_name
    • model_name
    • model_input
    • model_output
kind: SeldonDeployment
metadata:
  name: tfserving
spec:
  name: mnist
  predictors:
  - graph:
      children: []
      implementation: TENSORFLOW_SERVER
      modelUri: pvc://seldon-models-pvc/tensorflow/mnist/modell
      name: mnist-model
      endpoint:
        type: GRPC
      parameters:
        - name: signature_name
          type: STRING
          value: serving_default
        - name: model_name
          type: STRING
          value: mnist-model
        - name: model_input
          type: STRING
          value: images
        - name: model_output
          type: STRING
          value: scores
    name: default
    replicas: 1

모델 생성

Tensorflow 서버를 테스트 하려면 먼저 파이썬을 사용하여 간단한 Tensorflow 모델을 생성해야 합니다.

텐서플로우 모델을 만들고 훈련하기 위한 고수준 API인 tf.keras를 사용합니다. 모델의 save() 메소드를 이용하여, 전체 모델을 지정한 위치에 저장합니다. 여기에는 가중치, 모델 구성 등이 포함됩니다. 모델을 저장할 때 주의해야할 점은 모델 저장 위치의 마지막 디렉토리에 모델의 버전이 포함되어야 합니다. 모델 버전은 숫자를 사용해야합니다.

케라스의 데이터셋 중의 하나인 mnist 데이터를 분류하는 모델을 작성해 보겠습니다.

from __future__ import absolute_import, division, print_function, unicode_literals

import argparse
import os

import tensorflow as tf

def train():
    print("TensorFlow version: ", tf.__version__)

    parser = argparse.ArgumentParser()
    parser.add_argument('--model_path', default='/mnt/pv/tensorflow/mnist/model', type=str)
    args = parser.parse_args()

    version = 1
    export_path = os.path.join(args.model_path, str(version))

    (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
    x_train, x_test = x_train / 255.0, x_test / 255.0

    model = tf.keras.models.Sequential([
        tf.keras.layers.Flatten(input_shape=(28, 28)),
        tf.keras.layers.Dense(128, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(10, activation='softmax')
    ])

    model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=0.01),
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])

    print("Training...")
    training_history = model.fit(x_train, y_train, batch_size=64, epochs=10,
                                 validation_split=0.2)

    print('\\nEvaluate on test data')
    results = model.evaluate(x_test, y_test, batch_size=128)
    print('test loss, test acc:', results)

    model.save(export_path)
    print('"Saved model to {}'.format(export_path))

if __name__ == '__main__':
    train()

생성 된 모델을 사용하여 Tensorflow 서버를 실행하고 예측을 수행 할 수 있습니다. 모델은 PV, S3 호환 가능 개체 저장소, Azure Blob 저장소 또는 Google Cloud Storage에 있을 수 있습니다.

모델 저장하기

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

모델 코드 작성하기

mnist 이미지를 분류하는 모델입니다. 모델을 저장할 위치를 --model_path 파라미터로 입력받게 하였습니다.

tensorflow_mnist.py

from __future__ import absolute_import, division, print_function, unicode_literals

import argparse
import os

import tensorflow as tf

def train():
    print("TensorFlow version: ", tf.__version__)

    parser = argparse.ArgumentParser()
    parser.add_argument('--model_path', default='/mnt/pv/tensorflow/mnist/model', type=str)
    args = parser.parse_args()

    version = 1
    export_path = os.path.join(args.model_path, str(version))

    (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
    x_train, x_test = x_train / 255.0, x_test / 255.0

    model = tf.keras.models.Sequential([
        tf.keras.layers.Flatten(input_shape=(28, 28)),
        tf.keras.layers.Dense(128, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(10, activation='softmax')
    ])

    model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=0.01),
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])

    print("Training...")
    training_history = model.fit(x_train, y_train, batch_size=64, epochs=10,
                                 validation_split=0.2)

    print('\\nEvaluate on test data')
    results = model.evaluate(x_test, y_test, batch_size=128)
    print('test loss, test acc:', results)

    model.save(export_path)
    print('"Saved model to {}'.format(export_path))

if __name__ == '__main__':
    train()

컨테이너 이미지를 만들기

컨테이너 이미지를 만들기 위한 Dockerfile 입니다. 텐서플로우를 기본 이미지로 사용합니다.

Dockerfile

FROM tensorflow/tensorflow:2.1.0-py3

RUN mkdir -p /app
ADD tensorflow_mnist.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'tensorflow-mnist-job-{uuid.uuid4().hex[:4]}'

command = ["python", "tensorflow_mnist.py", "--model_path", "/mnt/pv/tensorflow/mnist/models"]
output_map = {
    "Dockerfile": "Dockerfile",
    "tensorflow_mnist.py": "tensorflow_mnist.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="tensorflow-mnist",
                           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을 실행하면 쿠버네티스 잡이 생성되고, 학습이 완료된 모델이 지정한 경로에 저장됩니다.

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

SeldonDeployment 생성

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

기본적으로 모델 서버는 로드한 모델의 predict_proba 메소드를 호출합니다. 만약 다른 메소드를 사용하고 싶다면 파라미터로 변경할 수 있습니다. 예를 들어 predict 메소드를 호출하게 하라면, parameters 섹션에 method 란 이름으로 값을 지정해 주면 됩니다. 다음 예제는 predict 메소드를 호출하게 설정하였습니다.

tensorflow-mnist.yaml

apiVersion: machinelearning.seldon.io/v1alpha2
kind: SeldonDeployment
metadata:
  name: tensorflow-mnist
spec:
  name: mnist
  predictors:
  - graph:
      children: []
      implementation: TENSORFLOW_SERVER
      modelUri: pvc://seldon-models-pvc/tensorflow/mnist/model
      name: mnist-model
      parameters:
        - name: signature_name
          type: STRING
          value: serving_default
        - name: model_name
          type: STRING
          value: mnist-model
    name: default
    replicas: 1

SeldonDeployment 를 생성합니다.

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

kubectl -n admin apply -f tensorflow-mnist.yaml

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

kubectl -n admin get seldondeployment tensorflow-mnist -o yaml

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

apiVersion: machinelearning.seldon.io/v1
kind: SeldonDeployment
metadata:
  ...
spec:
  ...
status:
  deploymentStatus:
    mnist-default-725903e:
      availableReplicas: 1
      replicas: 1
  serviceStatus:
    mnist-default-mnist-model-seldonio-tfserving-proxy-rest-0-7:
      httpEndpoint: mnist-default-mnist-model-seldonio-tfserving-proxy-rest-0-7.admin:9000
      svcName: mnist-default-mnist-model-seldonio-tfserving-proxy-rest-0-7
    tensorflow-mnist-mnist-default:
      grpcEndpoint: tensorflow-mnist-mnist-default.admin:5001
      httpEndpoint: tensorflow-mnist-mnist-default.admin:8000
      svcName: tensorflow-mnist-mnist-default
  state: Available

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

예측 실행하기

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

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

데이터의 크기가 크기 때문에 git 에 있는 파일을 다운받아서 사용해주세요.

mnist-input.json

{
  "data": {
    "ndarray": [
      [...],
       ...
      [...]
    ]
  }
}

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

MODEL_NAME=tensorflow-mnist
NAMESPACE=admin

INPUT_PATH=@./mnist-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/tensorflow-mnist/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: 5725
> Expect: 100-continue
> 
< HTTP/1.1 100 Continue
* We are completely uploaded and fine
< 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: 470
< date: Thu, 09 Apr 2020 15:51:27 GMT
< x-envoy-upstream-service-time: 142
< server: istio-envoy
< 
{
  "meta": {
    "puid": "ufdopha1s5gnemt86h06d4jg5e",
    "tags": {
    },
    "routing": {
    },
    "requestPath": {
      "mnist-model": "seldonio/tfserving-proxy_rest:0.7"
    },
    "metrics": []
  },
  "data": {
    "names": ["t:0", "t:1", "t:2", "t:3", "t:4", "t:5", "t:6", "t:7", "t:8", "t:9"],
    "ndarray": [[3.01518681E-4, 1.00308341E-6, 4.13124333E-4, 0.00133548444, 4.15516388E-6, 7.8677E-5, 5.88266346E-7, 0.996478, 3.98369411E-5, 0.00134761049]]
  }
}

KFServing InferenceService 배포와 예측 – Tensorflow

Tensorflow를 사용하는 InferenceService

학습이 완료된 Tensorflow 모델을 저장 한 경우, KFServing은 TensorFlow Serving을 사용하여 모델 서버를 배포 해 줍니다.

모델 생성

Tensorflow 서버를 테스트 하려면 먼저 파이썬을 사용하여 간단한 Tensorflow 모델을 생성해야 합니다.

텐서플로우 모델을 만들고 훈련하기 위한 고수준 API인 tf.keras를 사용합니다. 모델의 save() 메소드를 이용하여, 전체 모델을 지정한 위치에 저장합니다. 여기에는 가중치, 모델 구성 등이 포함됩니다. 모델을 저장할 때 주의해야할 점은 모델 저장 위치의 마지막 디렉토리에 모델의 버전이 포함되어야 합니다. 모델 버전은 숫자를 사용해야합니다.

케라스의 데이터셋 중의 하나인 mnist 데이터를 분류하는 모델을 작성해 보겠습니다.

from __future__ import absolute_import, division, print_function, unicode_literals

import argparse
import os

import tensorflow as tf


def train():
    print("TensorFlow version: ", tf.__version__)

    parser = argparse.ArgumentParser()
    parser.add_argument('--model_path', default='/mnt/pv/models/tensorflow/mnist', type=str)
    args = parser.parse_args()

    version = 1
    export_path = os.path.join(args.model_path, str(version))

    (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
    x_train, x_test = x_train / 255.0, x_test / 255.0

    model = tf.keras.models.Sequential([
        tf.keras.layers.Flatten(input_shape=(28, 28)),
        tf.keras.layers.Dense(128, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(10, activation='softmax')
    ])

    model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=0.01),
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])

    print("Training...")
    training_history = model.fit(x_train, y_train, batch_size=64, epochs=10,
                                 validation_split=0.2)

    print('\\nEvaluate on test data')
    results = model.evaluate(x_test, y_test, batch_size=128)
    print('test loss, test acc:', results)

    model.save(export_path)
    print('"Saved model to {}'.format(export_path))


if __name__ == '__main__':
    train()

생성 된 모델을 사용하여 Tensorflow 서버를 실행하고 예측을 수행 할 수 있습니다. 모델은 PV, S3 호환 가능 개체 저장소, Azure Blob 저장소 또는 Google Cloud Storage에 있을 수 있습니다.

모델 저장하기

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

모델 코드 작성하기

mnist 이미지를 분류하는 모델입니다. 모델을 저장할 위치를 --model_path 파라미터로 입력받게 하였습니다.

tensorflow_mnist.py

from __future__ import absolute_import, division, print_function, unicode_literals

import argparse
import os

import tensorflow as tf


def train():
    print("TensorFlow version: ", tf.__version__)

    parser = argparse.ArgumentParser()
    parser.add_argument('--model_path', default='/mnt/pv/models/tensorflow/mnist', type=str)
    args = parser.parse_args()

    version = 1
    export_path = os.path.join(args.model_path, str(version))

    (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
    x_train, x_test = x_train / 255.0, x_test / 255.0

    model = tf.keras.models.Sequential([
        tf.keras.layers.Flatten(input_shape=(28, 28)),
        tf.keras.layers.Dense(128, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(10, activation='softmax')
    ])

    model.compile(optimizer=tf.keras.optimizers.SGD(learning_rate=0.01),
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])

    print("Training...")
    training_history = model.fit(x_train, y_train, batch_size=64, epochs=10,
                                 validation_split=0.2)

    print('\\nEvaluate on test data')
    results = model.evaluate(x_test, y_test, batch_size=128)
    print('test loss, test acc:', results)

    model.save(export_path)
    print('"Saved model to {}'.format(export_path))


if __name__ == '__main__':
    train()

컨테이너 이미지를 만들기

컨테이너 이미지를 만들기 위한 Dockerfile 입니다. 텐서플로우를 기본 이미지로 사용합니다.

Dockerfile

FROM tensorflow/tensorflow:2.1.0-py3

RUN mkdir -p /app
ADD tensorflow_mnist.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'tensorflow-mnist-job-{uuid.uuid4().hex[:4]}'

command = ["python", "tensorflow_mnist.py", "--model_path", "/mnt/pv/models/tensorflow/mnist"]
output_map = {
    "Dockerfile": "Dockerfile",
    "tensorflow_mnist.py": "tensorflow_mnist.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="tensorflow-mnist",
                           dockerfile_path="Dockerfile")

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

fairing.config.run()

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

Tensorflow을 사용하는 InferenceService 로 예측 하기

InferenceService 생성

InferenceService 매니페스트를 작성합니다. predictor로 tensorflow 를 사용합니다. storageUri 필드로 모델 저장 위치를 지정해 줍니다. pvc 의 이름이 kfserving-models-pvc 이고 저장 위치가 models/tensorflow/mnist/ 이므로, pvc://kfserving-models-pvc/models/tensorflow/mnist/ 라고 지정해 줍니다.

tensorflow.yaml

apiVersion: "serving.kubeflow.org/v1alpha2"
kind: "InferenceService"
metadata:
  name: "tensorflow-mnist"
spec:
  default:
    predictor:
      tensorflow:
        storageUri: "pvc://kfserving-models-pvc/models/tensorflow/mnist/"

만약 GPU 리소스를 사용하려고 한다면, resource 필드에 GPU 를 할당해 주면됩니다.

tensorflow_gpu.yaml

apiVersion: "serving.kubeflow.org/v1alpha2"
kind: "InferenceService"
metadata:
  name: "tensorflow-mnist"
spec:
  default:
    predictor:
      tensorflow:
        storageUri: "pvc://kfserving-models-pvc/models/tensorflow/mnist/"
        resources:
          limits:
            cpu: 100m
            memory: 1Gi
            nvidia.com/gpu: "1"

InferenceService 를 생성합니다.

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

kubectl -n admin apply -f tensorflow.yaml

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

kubectl -n admin get inferenceservice

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

NAME               URL                                                                    READY   DEFAULT TRAFFIC   CANARY TRAFFIC   AGE
tensorflow-mnist   <http://tensorflow-mnist.admin.example.com/v1/models/tensorflow-mnist>   True    100                                70s

예측 실행하기

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

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

데이터의 크기가 크기 때문에 git 에 있는 파일을 다운받아서 사용해주세요.

mnist-input.json

{
  "instances": [
    [
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0,
      0.0
    ],
...
  ]
}

다음은 admin 네임스페이스의 tensorflow-mnist InferenceService 에 예측을 요청하는 예제입니다.

MODEL_NAME=tensorflow-mnist
SERVICE_HOSTNAME=$(kubectl -n admin get inferenceservice tensorflow-mnist -o jsonpath='{.status.url}' | cut -d "/" -f 3)

INPUT_PATH=@./mnist-input.json
curl -v -H "Host: ${SERVICE_HOSTNAME}" http://$CLUSTER_IP/v1/models/$MODEL_NAME:predict -d $INPUT_PATH

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

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /v1/models/tensorflow-mnist:predict HTTP/1.1
> Host: tensorflow-mnist.admin.example.com
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Length: 9866
> Content-Type: application/x-www-form-urlencoded
> Expect: 100-continue
> 
< HTTP/1.1 100 Continue
* We are completely uploaded and fine
< HTTP/1.1 200 OK
< content-length: 185
< content-type: application/json
< date: Sat, 04 Apr 2020 06:38:13 GMT
< server: istio-envoy
< x-envoy-upstream-service-time: 8726
< 
{
    "predictions": [[0.000235743544, 1.21317851e-06, 0.000527939294, 0.00202324125, 1.88633135e-06, 3.09452735e-05, 2.20991225e-07, 0.995925665, 2.64503233e-05, 0.00122672203]
    ]
}

Kubeflow – TensorFlow 학습하기

텐서플로우 알아 보기

텐서플로우(TensorFlow)는 구글에서 만든 오픈소스 딥러닝 프레임워크입니다. 딥러닝 프로그램을 쉽게 구현할 수 있도록 다양한 기능을 제공해주고 있습니다. 텐서플로우 자체는 기본적으로 C++로 구현 되어 있으며, Python, Java, Go 등 다양한 언어를 지원합니다.

TFJob을 이용해서 텐서플로우로 모델을 학습 할 수 있습니다. TFJob은 쿠버네티스에서 분산 또는 비 분산 텐서플로우 작업을 쉽게 실행할 수 있는 쿠버네티스 사용자 리소스(Custom Resource) 입니다.

TFJob

TFJob은 쿠버네티스에서 텐서플로우를 이용한 학습 작업을 할 수 있게 해주는 쿠버네티스 사용자 리소스 입니다. TFJob의 구현은 tf-operator에 있습니다. tf-operator는 TFJob을 관리합니다. 쿠버네티스에 TFJob이 등록되면, 필요한 포드들을 생성하여 작업을 실행할 수 있도록 도와줍니다.

TFJob은 다음과 같이 YAML 형식으로 표현할 수 있는 쿠버네티스 사용자 리소스입니다.

apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
  name: mnist-tfjob-dist
  namespace: admin
spec:
  cleanPodPolicy: Running
  tfReplicaSpecs:
    Chief:
      replicas: 1
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
          name: tensorflow
        spec:
          containers:
          - command:
            - python
            - mnist-dist.py
            image: kangwoo/tfjob-dist:0.0.1
            name: tensorflow
            volumeMounts:
            - mountPath: /app/data
              name: tfjob-data-volume
            workingDir: /app
          restartPolicy: Never
          volumes:
          - name: tfjob-data-volume
            persistentVolumeClaim:
              claimName: tfjob-data-volume
    Worker:
      replicas: 2
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
          name: tensorflow
        spec:
          containers:
          - command:
            - python
            - mnist-dist.py
            image: kangwoo/tfjob-dist:0.0.1
            name: tensorflow
            volumeMounts:
            - mountPath: /app/data
              name: tfjob-data-volume
            workingDir: /app
          restartPolicy: Never
          volumes:
          - name: tfjob-data-volume
            persistentVolumeClaim:
              claimName: tfjob-data-volume

분산 처리 기술을 사용한 모델 학습

쿠버네티스에서 TFJob을 관리하고 있는 tf-operator 는 텐서플로우를 사용한 모델 학습시 분산 처리가 가능하도록 도와주고 있습니다.

텐서플로우에는 분산 처리 기술을 사용하기 위해서 다음과 같은 역할을 가진 프로세스가 존재합니다.

  • Chief: Chief는 학습을 조정하는 역할을 합니다. 그리고 모델의 체크포인트 같은 작업을 수행할 책임이 있습니다.
  • Ps (Paramter Server) : Ps는 파라미터 서버로서, 모델 파라미터에 대한 분산 데이터 저장소를 제공하는 역할을 합니다.
  • Worker : Worker는 실제 모델 학습 작업을 수행하는 역할을 합니다. 경우에 따라서 0번 Worker가 Chief 역할을 수행 할 수 있습니다.
  • Evaluator : Evaluator는 평가 지표를 계산하는 역할을 합니다. 모델을 학습 할 때 Evaluator를 사용하여 평가 지표를 계산할 수 있습니다.

TFJob은 TF_CONFIG라는 환경 변수를 사용하여 프로세스의 역할 정의합니다.

모델 학습을 시작하려면 TF_CONFIG라는 환경 변수에 다음과 같은 변수가 json 형태로 설정되어 있어야 합니다.

{
	"cluster": {
		"worker": ["host1:port", "host2:port", "host3:port"],
	  "ps": ["host4:port", "host5:port"]
   },
	"task": {"type": "worker", "index": 0}
}
  • cluster : 클러스터 내에 있는 서버들의 정보를 지정합니다.
  • task : 클러스터 내에서 현재 작업이 담당한 역할을 지정합니다. 클러스터 내에서 가질 수 있는 역할은 “chief”, “worker”, “ps”, “evaluator” 중 하나입니다. 단, “ps” 역할은 tf.distribute.experimental.ParameterServerStrategy 전략을 사용할 때만 쓸 수 있습니다

TFJob 이라는 쿠터네티스트 사용자 리소스를 생성하여 모델 학습을 진행 경우에는, tf-operator 가 프로세스의 역할에 맞게 자동으로 환경 변수를 설정해 줍니다. 그래서 분산 작업을 위한 별도의 환경 변수 설정 작업을 하지 않아도 됩니다.

다음은 TFJob에서 생성한 포드의 환경 변수를 일부 출력해 본 것입니다.

spec:
  containers:
  - env:
    - name: TF_CONFIG
      value: '{"cluster":{"chief":["mnist-tfjob-dist-chief-0.admin.svc:2222"],"worker":["mnist-tfjob-dist-worker-0.admin.svc:2222","mnist-tfjob-dist-worker-1.admin.svc:2222"]},"task":{"type":"chief","index":0},"environment":"cloud"}'

텐서플로우 학습 작업 실행하기

TFJob을 정의한 후 학습 작업을 생성해 보겠습니다.

모델 코드 작성하기

텐서플로우로 학습할 모델을 작성해 보겠습니다.

다음은 텐서플로우 케라스를 사용하여 mnist 숫자 이미지를 분류하는 파이썬 코드인 mnist-dist.py입니다. Gtf.distribute.experimental.MultiWorkerMirroredStrategy 를 사용해서 분산 학습을 지원하고 있습니다.

mnist-dist.py

from __future__ import absolute_import, division, print_function, unicode_literals

import os
import json
import tensorflow as tf
import tensorflow_datasets as tfds


def build_and_compile_model():
    model = tf.keras.models.Sequential([
        tf.keras.layers.Flatten(input_shape=(28, 28, 1)),
        tf.keras.layers.Dense(128, activation='relu'),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(10)
    ])
    model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                  optimizer=tf.keras.optimizers.Adam(),
                  metrics=['accuracy'])
    return model


@tfds.decode.make_decoder(output_dtype=tf.float32)
def decode_image(example, feature):
    return tf.cast(feature.decode_example(example), dtype=tf.float32) / 255


def train():
    print("TensorFlow version: ", tf.__version__)

    tf_config = os.environ.get('TF_CONFIG', '{}')
    print("TF_CONFIG %s", tf_config)
    tf_config_json = json.loads(tf_config)
    cluster = tf_config_json.get('cluster')
    job_name = tf_config_json.get('task', {}).get('type')
    task_index = tf_config_json.get('task', {}).get('index')
    print("cluster={} job_name={} task_index={}}", cluster, job_name, task_index)

    BATCH_SIZE = 64

    tb_dir = '/app/data/logs'
    model_dir = '/app/data/export'
    version = 1
    export_dir = os.path.join(model_dir, str(version))

    strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy()
    mnist = tfds.builder('mnist', data_dir='/app/mnist')
    mnist.download_and_prepare()

    mnist_train, mnist_test = mnist.as_dataset(
        split=['train', 'test'],
        decoders={'image': decode_image()},
        as_supervised=True)
    train_input_dataset = mnist_train.cache().repeat().shuffle(
        buffer_size=50000).batch(BATCH_SIZE)
    # eval_input_dataset = mnist_test.cache().repeat().batch(BATCH_SIZE)

    options = tf.data.Options()
    options.experimental_distribute.auto_shard_policy = tf.data.experimental.AutoShardPolicy.OFF
    train_input_dataset = train_input_dataset.with_options(options)

    print("Training...")

    with strategy.scope():
        multi_worker_model = build_and_compile_model()

    num_train_examples = mnist.info.splits['train'].num_examples
    train_steps = num_train_examples // BATCH_SIZE
    train_epochs = 10

    callbacks = [
        tf.keras.callbacks.TensorBoard(log_dir=tb_dir),
    ]

    history = multi_worker_model.fit(train_input_dataset, epochs=train_epochs, steps_per_epoch=train_steps,
                                     callbacks=callbacks)

    print("\\ntraining_history:", history.history)

    multi_worker_model.save(export_dir)


if __name__ == '__main__':
    train()

모델 컨테이너 이미지 만들기

모델 학습용 컨테이너 이미지를 만들기 위해서 Dockerfile을 생성하겠습니다. 텐서플로우 데이터셋을 사용하기 때문에 pip를 이용해서 추가해주었습니다.

다음은 텐서플로우2.1을 기반 이미지로 해서, 모델 파일을 추가하는 Dockerfile 입니다.

Dockerfile

FROM tensorflow/tensorflow:2.1.0-py3

RUN pip install tensorflow-datasets==2.0.0

RUN mkdir -p /app
ADD mnist-dist.py /app/

다음 명령어를 실행하면 kkangwoo/tfjob-dist:0.0.1 라는 이름의 컨테이너 이미지를 빌드 할 수 있습니다.

docker build -t kangwoo/tfjob-dist:0.0.1 .

빌드한 컨테이너 이미지를 컨테이너 레지스트리로 업로드 하겠습니다.

docker push kangwoo/tfjob-dist:0.0.1

PVC 생성하기

텐서플로우 학습 작업 중 생성되는 매트릭과, 학습이 완료된 모델을 저장하기 위해서 별도의 볼륨을 만들어서 사용하겠습니다.

다음은 100메가의 저장 용량을 가진 볼륨 생성을 요청하는 PVC 매니페스트입니다.

tfjob-pvc.yaml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: tfjob-data-volume
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Mi

kubectl을 사용해서 admin 네임스페이스에 pvc를 생성합니다.

kubectl -n admin apply -f tfjob-pvc.yaml

TFJob 생성하기

tf-operator를 사용해서 텐서플로우로 작성한 모델을 학습 하려면 TFJob을 정의해야합니다.

  1. tfReplicaSpecs를 설정합니다. “tfReplicaSpecs”은 텐서플로우의 분산 학습시 사용하는 프로세스들을 정의하는데 사용합니다. Chief를 1개로 설정하고, Worker를 2개로 설정하였습니다.. Kubeflow 클러스터에 istio가 설치되어 있기 때문에, 자동으로 istio-proxy가 포드에 주입됩니다. 이것을 방지하기 위해서 어노테이션에 sidecar.istio.io/inject: “false” 을 추가해 주었습니다. 그리고 앞에 생성한 볼륨을 마운트 하였습니다.
tfReplicaSpecs:
    Chief:
      replicas: 1
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
          name: tensorflow
        spec:
          containers:
          - command:
            - python
            - mnist-dist.py
            image: kangwoo/tfjob-dist:0.0.1
            name: tensorflow
            volumeMounts:
            - mountPath: /app/data
              name: tfjob-data-volume
            workingDir: /app
          restartPolicy: Never
          volumes:
          - name: tfjob-data-volume
            persistentVolumeClaim:
              claimName: tfjob-data-volume
    Worker:
      replicas: 2
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
          name: tensorflow
        spec:
          containers:
          - command:
            - python
            - mnist-dist.py
            image: kangwoo/tfjob-dist:0.0.1
            name: tensorflow
            volumeMounts:
            - mountPath: /app/data
              name: tfjob-data-volume
            workingDir: /app
          restartPolicy: Never
          volumes:
          - name: tfjob-data-volume
            persistentVolumeClaim:
              claimName: tfjob-data-volume

다음은 TFJob 을 생성한 위한 메니페스트입니다.

tfjob-dist.yaml

apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
  name: mnist-tfjob-dist
spec:
  tfReplicaSpecs:
    Chief:
      replicas: 1
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
          name: tensorflow
        spec:
          containers:
          - command:
            - python
            - mnist-dist.py
            image: kangwoo/tfjob-dist:0.0.1
            name: tensorflow
            volumeMounts:
            - mountPath: /app/data
              name: tfjob-data-volume
            workingDir: /app
          restartPolicy: Never
          volumes:
          - name: tfjob-data-volume
            persistentVolumeClaim:
              claimName: tfjob-data-volume
    Worker:
      replicas: 2
      template:
        metadata:
          annotations:
            sidecar.istio.io/inject: "false"
          name: tensorflow
        spec:
          containers:
          - command:
            - python
            - mnist-dist.py
            image: kangwoo/tfjob-dist:0.0.1
            name: tensorflow
            volumeMounts:
            - mountPath: /app/data
              name: tfjob-data-volume
            workingDir: /app
          restartPolicy: Never
          volumes:
          - name: tfjob-data-volume
            persistentVolumeClaim:
              claimName: tfjob-data-volume

다음 명령어를 실행하면 admin 네임스페이스에 tfjob-dist 이라는 이름의 TFJob을 생성할 수 있습니다.

kubectl -n admin apply -f tfjob-dist.yaml

텐서플로우 학습 작업 확인하기

생성한 TFJob은 다음 명령어를 실행해서 확인 해 볼 수 있습니다.

kubectl -n admin get tfjob

생성된 TFJob이 있다면, 다음과 같은 응답 결과를 얻을 수 있습니다.

NAME               STATE       AGE
mnist-tfjob-dist   Succeeded   19m

TFJob이 생성되면, tf-operator 에 의해서 포드들이 생성됩니다. TFJob 매니페스트에 정의한 개수대로 chief, worker 포드가 생성되게 됩니다.

생성된 포드들은 다음 명령어를 실행해서 확인 해 볼 수 있습니다.

kubectl -n admin get pod -l tf-job-name=mnist-tfjob-dist

생성된 포드들이 남아 있다면, 다음과 같은 응답 결과를 얻을 수 있습니다.

NAME                    READY   STATUS    RESTARTS   AGE
mnist-tfjob-dist-chief-0    0/1     Completed   0          20m
mnist-tfjob-dist-worker-0   0/1     Completed   0          20m
mnist-tfjob-dist-worker-1   0/1     Completed   0          20m

TFJob은 작업이 끝난 후, 관련 포드들을 삭제해버립니다. 그래서 작업이 완료되면 포드가 조회되지 않을 수 있습니다. 작업이 완료되어도 포드들을 남겨 두고 싶다면, TFJob 매니페스트의 spec 부분에 “cleanPodPolicy: None” 를 추가하시면 됩니다.

TFJob spec의 CleanPodPolicy는 작업이 종료 될 때 포드 삭제를 제어할 때 사용합니다. 다음 값들 중 하나를 사용할 수 있습니다.

  • Running : 작업이 완료되었을 때, 실행이 끝난(Completed) 포드들은 삭제하지 않고, 실행중인(Running) 포드들만 삭제합니다.
  • All : 작업이 완료되었을 때, 실행이 끝난 포드들을 즉시 삭제합니다.
  • None : 작업이 완료되어도 포드들을 삭제하지 않습니다.

다음은 cleanPodPolicy를 추가한 메니페스트 예제입니다.

apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
  name: mnist-tfjob-dist
spec:
spec:
  cleanPodPolicy: None
...

TFJob의 작업 상태를 알고 싶으면 describe 명령어를 사용할 수 있습니다.

다음 명령어를 실행하면 admin 네임스페이스에 mnist-tfjob-dist 이라는 이름의 TFJob의 상태를 조회할 수 있습니다.

kubectl -n admin describe tfjob mnist-tfjob-dist

다음은 예제 작업에 대한 샘플 출력입니다.

Name:         mnist-tfjob-dist
Namespace:    admin
Labels:       <none>
Annotations:  ...
API Version:  kubeflow.org/v1
Kind:         TFJob
Metadata:
...
Spec:
...
Status:
  Completion Time:  2020-03-07T02:17:17Z
  Conditions:
    Last Transition Time:  2020-03-07T02:16:10Z
    Last Update Time:      2020-03-07T02:16:10Z
    Message:               TFJob mnist-tfjob-dist is created.
    Reason:                TFJobCreated
    Status:                True
    Type:                  Created
    Last Transition Time:  2020-03-07T02:16:12Z
    Last Update Time:      2020-03-07T02:16:12Z
    Message:               TFJob mnist-tfjob-dist is running.
    Reason:                TFJobRunning
    Status:                False
    Type:                  Running
    Last Transition Time:  2020-03-07T02:17:17Z
    Last Update Time:      2020-03-07T02:17:17Z
    Message:               TFJob mnist-tfjob-dist successfully completed.
    Reason:                TFJobSucceeded
    Status:                True
    Type:                  Succeeded
  Replica Statuses:
    Chief:
      Succeeded:  1
    Worker:
      Succeeded:  2
  Start Time:     2020-03-07T02:16:11Z
Events:        <none>

텐서플로우 학습 작업 삭제하기

작업이 완료되어도 TFJob은 삭제되지 않습니다.

다음 명령어를 실행하면 admin 네임스페이스에 mnist-tfjob-dist이라는 이름의 TFJob을 삭제할 수 있습니다.

kubectl -n admin delete tfjob mnist-tfjob-dist

텐서보드

TensorBoard는 텐서플로우의 실행 및 그래프를 검사하고, 이해하기 위한 웹 응용 도구로서, 텐서플로우에서 기록한 로그를 그래프로 시각화하여 보여줍니다. 텐서보드는 텐서플로우에 포함되어 있습니다.

텐서보드를 실행하기 위해서 쿠버네티스 디플로이먼트 리소스를 정의해 보겠습니다.

“tensorflow/tensorflow:2.1.0-py3” 라는 컨테이너 이미지를 사용하여 텐서보드를 실행하겠습니다. 텐서보드는는 실행 될 때, 주어진 파리미터 값에 의해 로그를 읽어옵니다.

다음은 텐서보드에서 사용하는 몇 가지 파라미터입니다.

  • port : 텐서보드에 접근하기 위한 포트입니다.
  • logdir : 로그가 저장된 디렉토리 경로입니다.
  • path_prefix : 접속 URL에서 사용하는 경로 접두사입니다. 예를 들어 /foo/bar의 경로 접두사를 사용하면, http://localhost/이 아닌 http://localhost:6006/foo/bar/ 를 통해서 TensorBoard에 접속할 수 있습니다.
- command:
        - /usr/local/bin/tensorboard
        - --logdir=/app/data/logs
        - --path_prefix=/namespace/admin/tensorboard/mnist-dist/
        - --port=80

앞서 실행한 TFJob에서 텐서플로우 로그를 쿠버네티스의 퍼시스턴스 볼륨에 저장하였습니다. tfjob-data-volume 라는 퍼시스턴스 볼륨 클레임 이름을 가지고 있습니다. 해당 볼륨을 텐서보드에 마운트 해줍니다.

        volumeMounts:
        - mountPath: /app/data
          name: tfjob-data-volume
      volumes:
      - name: tfjob-data-volume
        persistentVolumeClaim:
          claimName: tfjob-data-volume

다음은 디플로이먼트 매니페스트입니다.

mnist-dist-tensorboard-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: mnist-dist-tensorboard
  name: mnist-dist-tensorboard
  namespace: admin
spec:
  selector:
    matchLabels:
      app: mnist-dist-tensorboard
  template:
    metadata:
      labels:
        app: mnist-dist-tensorboard
    spec:
      serviceAccount: default-editor
      containers:
      - command:
        - /usr/local/bin/tensorboard
        - --logdir=/app/data/logs
        - --path_prefix=/namespace/admin/tensorboard/mnist-dist/
        - --port=80
        image: tensorflow/tensorflow:2.1.0-py3
        name: tensorboard
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /app/data
          name: tfjob-data-volume
      volumes:
      - name: tfjob-data-volume
        persistentVolumeClaim:
          claimName: tfjob-data-volume

kubectl을 사용해서 admin 네임스페이스에 디플로이먼트 리소스를 생성합니다.

kubectl -n admin apply -f mnist-dist-tensorboard-deployment.yaml

텐서보드에 접속하기 위해서 쿠버네티스 서비스를 리소스를 정의합니다.

mnist-dist-tensorboard-service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: mnist-dist-tensorboard
  name: mnist-dist-tensorboard
  namespace: admin
spec:
  ports:
  - name: http
    port: 80
    targetPort: 80
  selector:
    app: mnist-dist-tensorboard
  type: ClusterIP

kubectl을 사용해서 admin 네임스페이스에 서비스 리소스를 생성합니다.

kubectl -n admin apply -f mnist-dist-tensorboard-service.yaml

Kubeflow 클러스터에는 istio가 설치되어 있었습니다. 텐서보드를 istio-ingressgateway를 통해서 접근할 수 있도록 하기위해서 VirtualService 라는 사용자 리소스를 정의하겠습니다.

mnist-dist-tensorboard-virtualservice.yaml

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: mnist-dist-tensorboard
  namespace: admin
spec:
  gateways:
  - kubeflow/kubeflow-gateway
  hosts:
  - '*'
  http:
  - match:
    - uri:
        prefix: /namespace/admin/tensorboard/mnist-dist/
    route:
    - destination:
        host: mnist-dist-tensorboard.admin.svc.cluster.local
    timeout: 300s

kubectl을 사용해서 admin 네임스페이스에 서비스 리소스를 생성합니다.

kubectl -n admin apply -f mnist-dist-tensorboard-virtualservice.yaml

다음 주소로 텐서보드에 접근할 수 있습니다.

http://kubeflow-address/namespace/admin/tensorboard/mnist-dist/

텐서플로우 서빙

Serving은 학습이 완료된 모델을 실제 서비스에 적용하는 것입니다. 텐서플로우 서빙을 이용하면 텐서플로우에서 생성한 모델을 서비스 환경에 쉽고 빠르게 적용할 수 있습니다.

텐서플로우 서빙을 이용해서 모델 서버 실행하기

TensorFlow 모델 서버를 실행하기 위해서 쿠버네티스 디플로이먼트 리소스를 정의해 보겠습니다.

“tensorflow/serving:2.1.0” 라는 컨테이너 이미지를 사용하여 모델 서버를 실행하겠습니다. 모델 서버는 실행 될 때, 주어진 파리미터 값에 의해 모델을 로드하게 됩니다. 모델 서버에 모델이 로드 되면, REST나 GRPC를 사용하여 추론 요청을 시작할 수 있습니다.

다음은 모델 서버에서 사용하는 몇 가지 파라미터입니다.

  • port : GRPC 요청에 사용할 포트입니다.
  • rest_api_port : REST 요청에 사용할 포트입니다.
  • modeL_name : REST 요청의 URL에서 이를 사용합니다.
  • mode_base_path : 모델을 저장 한 디렉토리 경로입니다. containers: – args: – –port=9000 – –rest_api_port=8500 – –model_name=mnist – –model_base_path=/app/data/export command: – /usr/bin/tensorflow_model_server image: tensorflow/serving:2.1.0

앞서 TFJob을 사용하여, 학습된 모델을 쿠버네티스의 퍼시스턴스 볼륨에 저장하였습니다. tfjob-data-volume 라는 퍼시스턴스 볼륨 클레임 이름을 가지고 있습니다. 해당 볼륨을 모델 서버에 마운트 해줍니다.

        volumeMounts:
        - mountPath: /app/data
          name: tfjob-data-volume
      volumes:
      - name: tfjob-data-volume
        persistentVolumeClaim:
          claimName: tfjob-data-volume

다음은 디플로이먼트 매니페스트입니다.

mnist-model-server-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: mnist-model-server
  name: mnist-model-server
spec:
  selector:
    matchLabels:
      app: mnist-model-server
  template:
    metadata:
      annotations:
        sidecar.istio.io/inject: "false"
      labels:
        app: mnist-model-server
        version: v1
    spec:
      serviceAccount: default-editor
      containers:
      - args:
        - --port=9000
        - --rest_api_port=8500
        - --model_name=mnist
        - --model_base_path=/app/data/export
        command:
        - /usr/bin/tensorflow_model_server
        image: tensorflow/serving:2.1.0
        imagePullPolicy: IfNotPresent
        livenessProbe:
          initialDelaySeconds: 30
          periodSeconds: 30
          tcpSocket:
            port: 9000
        name: mnist
        ports:
        - containerPort: 9000
        - containerPort: 8500
        volumeMounts:
        - mountPath: /app/data
          name: tfjob-data-volume
      volumes:
      - name: tfjob-data-volume
        persistentVolumeClaim:
          claimName: tfjob-data-volume

kubectl을 사용해서 admin 네임스페이스에 디플로이먼트 리소스를 생성합니다.

kubectl -n admin apply -f mnist-model-server-deployment.yaml

모델 서버에서 정상적으로 모델을 로드 했는지 여부를 확인하려면, 포드의 로그를 조회하면 됩니다.

kubectl -n admin logs -l app=mnist-model-server

정상적으로 모델이 로드 되었다면, 다음과 같은 응답 결과를 학인 수 있습니다.

2020-03-07 00:58:07.813234: I tensorflow_serving/model_servers/server.cc:86] Building single TensorFlow model file config:  model_name: mnist model_base_path: /app/data/export
2020-03-07 00:58:07.814324: I tensorflow_serving/model_servers/server_core.cc:462] Adding/updating models.
2020-03-07 00:58:07.814334: I tensorflow_serving/model_servers/server_core.cc:573]  (Re-)adding model: mnist
2020-03-07 00:58:07.918328: I tensorflow_serving/core/basic_manager.cc:739] Successfully reserved resources to load servable {name: mnist version: 1}
2020-03-07 00:58:07.918351: I tensorflow_serving/core/loader_harness.cc:66] Approving load for servable version {name: mnist version: 1}
2020-03-07 00:58:07.918357: I tensorflow_serving/core/loader_harness.cc:74] Loading servable version {name: mnist version: 1}
2020-03-07 00:58:07.918373: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:31] Reading SavedModel from: /app/data/export/1
2020-03-07 00:58:07.920422: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:54] Reading meta graph with tags { serve }
2020-03-07 00:58:07.920439: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:264] Reading SavedModel debug info (if present) from: /app/data/export/1
2020-03-07 00:58:07.945844: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:203] Restoring SavedModel bundle.
2020-03-07 00:58:07.977914: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:152] Running initialization op on SavedModel bundle at path: /app/data/export/1
2020-03-07 00:58:07.983624: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:333] SavedModel load for tags { serve }; Status: success: OK. Took 65247 microseconds.
2020-03-07 00:58:07.984126: I tensorflow_serving/servables/tensorflow/saved_model_warmup.cc:105] No warmup data file found at /app/data/export/1/assets.extra/tf_serving_warmup_requests
2020-03-07 00:58:07.984397: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: mnist version: 1}
2020-03-07 00:58:07.995156: I tensorflow_serving/model_servers/server.cc:358] Running gRPC ModelServer at 0.0.0.0:9000 ...
2020-03-07 00:58:07.997145: I tensorflow_serving/model_servers/server.cc:378] Exporting HTTP/REST API at:localhost:8500 ...

모델 서버에 접속하기 위해서 쿠버네티스 서비스를 리소스를 정의합니다.

mnist-model-server-service.yaml

apiVersion: v1
kind: Service
metadata:
  labels:
    app: mnist-model-server
  name: mnist-model-server
spec:
  ports:
  - name: grpc-tf-serving
    port: 9000
    targetPort: 9000
  - name: http-tf-serving
    port: 8500
    targetPort: 8500
  selector:
    app: mnist-model-server
  type: ClusterIP

kubectl을 사용해서 admin 네임스페이스에 서비스 리소스를 생성합니다.

kubectl -n admin apply -f mnist-model-server-service.yaml

텐서플로우 서빙에서 로드된 모델에게 요청 하기

아래 예제 코드를 실행하기 위해서는 request와 tensorflow-datasets라는 파이썬 패키지가 필요합니다. 설치가 되어 있지 않다면 다음 명령어로 설치할 수 있습니다.

pip install request
pip install tensorflow-datasets==2.0.0

주피터 노트북 사용자 인터페이스의 메뉴에서 File > New > Notebook 을 클릭하여 노트북 환경에서 새 노트북을 생성하겠습니다.

먼저 요청에 사용할 테스트 데이터를 생성하는 코드를 작성합니다.

import os
import json
import numpy as np
import matplotlib.pyplot as plt
import subprocess
import tensorflow as tf
import tensorflow_datasets as tfds

dataset = tfds.load("mnist", shuffle_files=True, as_supervised=False)
train_dataset, test_dataset = dataset["train"], dataset["test"]
test_data = []
iterator = iter(test_dataset.batch(5))
test_data.append(next(iterator))

이미지와 내용을 화면에 출력하기 위해서 show() 함수를 작성합니다.

def show(idx, title):
  plt.figure()
  plt.imshow(test_images[idx].reshape(28,28))
  plt.axis('off')
  plt.title('\\n\\n{}'.format(title), fontdict={'size': 16})

test_data의 0번째에 있는 이미지와 라벨을 테스트 삼아 출력해 보겠습니다.

show(0, 'An Example Image: {}'.format(test_labels[0]))

모델 서버에 요청할 데이터를 json을 사용해서 작성합니다.

import json
data = json.dumps({"signature_name": "serving_default", "instances": test_images.tolist()})
print('Data: {} ... {}'.format(data[:50], data[len(data)-52:]))

모델 서버에 POST 형식으로 요청 데이터를 전송하고, 예측 결과를 받습니다.

import requests
headers = {"content-type": "application/json"}
json_response = requests.post('<http://mnist-model-server:8500/v1/models/mnist:predict>', data=data, headers=headers)
predictions = json.loads(json_response.text)['predictions']

예측 결과를 show() 함수를 사용하여 화면이 출력합니다.

for i in range(0, len(predictions)):
    show(i, 'The model thought this was number {} , and it was actually number {}'.format(
  np.argmax(predictions[i]), test_labels[i]))

정상적으로 처리 되었다면, 다음과 비슷한 응답 결과를 얻을 수 있습니다.

Keras GPU 사용하기

쿠버네티스(kubernetes) 위에서 Jupyter를 사용하고 있다. Jupyter Notebook 에서 텐서플로-케라스를 사용하고 있고, GPU 4개를 할당하였다. 주피터에서 노트북을 1개 더 생성해서 작업 할 경우 OOM 에러가 발생하였다.

ResourceExhaustedError (see above for traceback): OOM when allocating tensor with shape[3,3,128,128] and type float on /job:localhost/replica:0/task:0/device:GPU:0 by allocator GPU_0_bfc
	 [[node conv2d_3/kernel/Initializer/random_uniform/RandomUniform (defined at <ipython-input-2-355421ac90ac>:1) ]]
Hint: If you want to see a list of allocated tensors when OOM happens, add report_tensor_allocations_upon_oom to RunOptions for current allocation info.

양쪽 노트북에서 모드 GPU:0을 사용해서 생긴 문제인거 같다. 첫번째 노트북에서 GPU:0의 메모리를 거의 풀로 사용해버린 상태에서, 두번째 노트북에서 GPU:0을 사용하려니 메모리가 부족한 상태가 발생한것이다

서버에서 nvidia-smi을 실행해보면, 프로세스는 생성되었지만, 할당된 메모리가 작다는것을 알 수 있다.

Every 2.0s: nvidia-smi                                                                                         Thu Aug  8 10:09:46 2019

Thu Aug  8 08:09:46 2019
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.67       Driver Version: 418.67       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla M40           Off  | 00000000:02:00.0 Off |                    0 |
| N/A   35C    P0    63W / 250W |  11302MiB / 11448MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Tesla M40           Off  | 00000000:82:00.0 Off |                    0 |
| N/A   37C    P0    62W / 250W |    212MiB / 11448MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   2  Tesla M40           Off  | 00000000:85:00.0 Off |                    0 |
| N/A   35C    P0    62W / 250W |    212MiB / 11448MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   3  Tesla M40           Off  | 00000000:86:00.0 Off |                    0 |
| N/A   37C    P0    62W / 250W |    212MiB / 11448MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0     81475      C   /opt/conda/bin/python                      11060MiB |
|    0     93847      C   /opt/conda/bin/python                        229MiB |
|    1     81475      C   /opt/conda/bin/python                         99MiB |
|    1     93847      C   /opt/conda/bin/python                         99MiB |
|    2     81475      C   /opt/conda/bin/python                         99MiB |
|    2     93847      C   /opt/conda/bin/python                         99MiB |
|    3     81475      C   /opt/conda/bin/python                         99MiB |
|    3     93847      C   /opt/conda/bin/python                         99MiB |
+-----------------------------------------------------------------------------+

알아서 노는 GPU를 사용하면 좋으련만… 어쩔수 없이 GPU 디바이스를 직접 지정하여서 사용핬다.

디바이스 목록 보기

from tensorflow.python.client import device_lib
print(device_lib.list_local_devices())

특정 디바이스를 지정해서 모델 처리하기

텐서플로우에서는 tf.device('/gpu:0') 구문으로 특정 GPU 디바이스를 지정해서 모델을 처리할 수 있다.

import tensorflow as tf

with tf.device('/gpu:0'):
    model = keras.models.Sequential()
    model.add(keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
    model.add(keras.layers.MaxPool2D((2, 2)))
    model.add(keras.layers.Conv2D(64, (3, 3), activation='relu'))
    model.add(keras.layers.MaxPool2D((2, 2)))
    model.add(keras.layers.Conv2D(128, (3, 3), activation='relu'))
    model.add(keras.layers.MaxPool2D((2, 2)))
    model.add(keras.layers.Conv2D(128, (3, 3), activation='relu'))
    model.add(keras.layers.MaxPool2D((2, 2)))
    model.add(keras.layers.Flatten())
    model.add(keras.layers.Dense(512, activation='relu'))
    model.add(keras.layers.Dense(1, activation='sigmoid'))
    
    model.compile(loss='binary_crossentropy', optimizer=keras.optimizers.RMSprop(lr=1e-4), metrics=['acc'])
    
    
    train_datagen = keras.preprocessing.image.ImageDataGenerator(rescale=1./255)
    validation_datagen = keras.preprocessing.image.ImageDataGenerator(rescale=1./255)
    
    train_generator = train_datagen.flow_from_directory(train_dir, target_size=(150, 150), batch_size=20, class_mode='binary')
    validation_generator = validation_datagen.flow_from_directory(validation_dir, target_size=(150, 150), batch_size=20, class_mode='binary')
    
    
    history = model.fit_generator(train_generator, steps_per_epoch=100, epochs=30, validation_data=validation_generator, validation_steps=50)

기타 : 멀티 GPU 사용하기

keras.utils을 사용하면, 모델을 처리할 때 GPU를 사용할 수 있게 지정할 수 있다.

model = ...
model = keras.utils.multi_gpu_model(model, gpus=4)
model.compile(loss='binary_crossentropy', optimizer=keras.optimizers.RMSprop(lr=1e-4), metrics=['acc'])
...

Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2 AVX AVX2 FMA

경고 메시지

텐서플로(TensorFlow) 라이브러리를 사용해서, 머신러닝 코드를 실행하다 보면 아래와 같은 경고 메시지가 출력되는 경우가 있다.

I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2 AVX AVX2 FMA

굳이 직역을 해보자면, ‘이 텐서플로 바이너리는 당신의 CPU가 제공하는 명령어(instruction)들을 사용하도록 컴파일되어 있지 않습니다.’ 라는 뜻일 것이다.

해결 방법

해결 방법은 옵션을 추가해서 텐서플로를 다시 빌드 하면 된다.

예를 들어 SSE4.1을 지원하는 CPU라면 --copt=-msse4.1을 추가하면 된다.

참고 자료