Bootstrap

Seldon 使用 (四):内置的推理服务TFServing

Seldon内置(Prepack)支持了一些推理服务类型,包括Tensorflow serving(简称TFServing),Triton inference server, MLflow, sklearn, xgboost。本文重点介绍Tensorflow serving,由点带面地理解内置(Prepack)推理服务如何使用以及实现细节。

1 TFServing服务简介

TFServing是Tensorflow官方提供的模型在线推理服务,开箱即用,具备高性能,低延迟,灵活易扩展,可同时支持多模型多版本,支持GRPC和HTTP。

TFServing服务架构如下图所示,这里摘抄一段官方的处理流程介绍,一个典型的模型加载及调用过程

  • 当Source模块检测到新的模型权重(weights),它会创建一个Loader(指向该模型数据)

  • Source模块通知DynamicManager,告诉它这有一个可用的模型版本(Aspired version)

  • DynamicManager根据VersionPolicy配置,判断是否加载这个新版本

  • DynamicManager调用Loader加载并初始化这个新的模型版本

  • 客户端(client)向DynamicManager请求该模型的handle,DynamicManager返回该模型的最新handle

如需更详细的资料,可查看

2 如何创建和部署TFServing

定义SeldonDeployment文件tfserving-sdep.yaml,如下:

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

说明:

部署该sdep,执行如下命令

kubectl create -f tfserving-sdep.yaml

创建完成后,可通过如下命令,查看到pod及service被创建

# kubectl get sdep
NAME           AGE
tfserving      1m

# kubectl get svc
NAME                              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)     AGE
tfserving-default-mnist-model     ClusterIP   10.254.1.2           9000/TCP    1m

# kubectl get pod
NAME                                           READY   STATUS       RESTARTS   AGE
tfserving-default-0-mnist-model-2e8e-y1ae7     3/3     Running      0          1m

2.1 如何调用推理服务

从上面的service和pod信息,我们可以使用service的cluster IP或者pod的IP,调用这个mnist推理服务。

下面我们通过seldon的API接口,调用推理服务。我们可以构造如下请求:

import json
import random
import sys

import requests

url = "http://%s:9000/api/v1.0/predictions" % sys.argv[1]
data = [
  [random.random() for i in range(784)]
]
# seldon protocol
payload = {"data":{"ndarray": data}}

resp = requests.post(url, json=payload)
print(resp.content)

返回结果如下:

{
   "data":{
     "names":["t:0","t:1","t:2","t:3","t:4","t:5","t:6","t:7","t:8","t:9"],
      "ndarray":[[2.31042499e-20,4.70782478e-30,0.017343564,0.903749764,1.29772914e-33,0.0789065957,9.34007e-14,4.42152e-19,1.50050141e-07,9.49472516e-17]]
    },
    "meta":{}
}

3 Seldon如何实现内置推理服务

seldon的manager服务,即seldon-controller-manager,这是一个k8s的Operator服务,负责管理所有SeldonDeployment(简称sdep)资源的生命周期,包括创建,修改及删除。每当新的sdep资源被创建时,manager服务会为其创建相关的deployment及service。

从上面的pod信息,我们可以看到,当sdep资源创建后,manager服务为其创建了一个pod。这个pod包含了四个container,具体如下内容:

  containers:
  - name: mnist-model
    env:
    - name: PREDICTIVE_UNIT_PARAMETERS
      value: '[{"name":"signature_name","value":"predict_images","type":"STRING"},{"name":"model_name","value":"mnist-model","type":"STRING"},{"name":"rest_endpoint","value":"http://0.0.0.0:2001","type":"STRING"},{"name":"model_name","value":"mnist-model","type":"STRING"}]'
    image: seldonio/tfserving-proxy_rest:1.2.1
  - name: tfserving
    args:
    - /usr/bin/tensorflow_model_server
    - --port=2000
    - --rest_api_port=2001
    - --model_name=mnist-model
    - --model_base_path=/mnt/models
    image: tensorflow-serving:2.1.0
    volumeMounts:
    - mountPath: /mnt/models
      name: tfserving-provision-location
  - name: seldon-container-engine
    args:
    - --sdep
    - tfserving
    - --namespace
    - kubeflow
    - --predictor
    - default
    - --port
    - "8000"
    - --protocol
    - seldon
    - --prometheus_path
    - /prometheus
    image: seldon/seldon-core-executor:1.2.1
  initContainers:
  - args:
    - gs://seldon-models/tfserving/mnist-model
    - /mnt/models
    image: gcr.io/kfserving/storage-initializer:0.2.2
    volumeMounts:
    - mountPath: /mnt/models
      name: tfserving-provision-location

这四个container的作用和关系,分别如下:

  • init容器:根据sdep的参数modelUri,从gs获取模型文件,并保存到/mnt/models

  • tfserving容器:从/mnt/models加载模型,并通过2000/2001端口提供服务

  • mnist-model容器:从其使用的image可知,这是一个proxy服务,主要实现协议转换,即seldon协议请求,转换成tensorflow协议请求

  • engine容器:主要用于支持sdep资源的graph功能,即可以将多个模型服务分别进行打分及组合。将在后续文章继续展开介绍

最后,再从代码的角度看看,manager服务处理sdep的主要过程。如下所示,如同其他的operator一样,manager实现了Reconcile()接口方法:

func (r *SeldonDeploymentReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
    // 获取sdep资源实例的信息
    instance := &machinelearningv1.SeldonDeployment{}
    err := r.Get(ctx, req.NamespacedName, instance)

    // 设置默认值
    instance.Default()

    // 创建所需组件:主要是container
    components, err := r.createComponents(ctx, instance, podSecurityContext, log)
    // 创建服务网络:主要是service
    servicesReady, err := r.createServices(components, instance, false, log)
    // 创建deployment:即将container分配到deployment,并创建
    deploymentsReady, err := r.createDeployments(components, instance, log)

    if deploymentsReady {
        // 完成最后的创建
        err := r.completeServiceCreation(instance, components, log)
    }
}

代码逻辑比较直观,就是分别创建相关的k8s资源。这里主要说一下Default()和createComponents()。在sdep的声明中,我们只需要配置tensorflow模型的名称和文件路径,但manager服务创建了上述四个容器,分别实现了不同的功能:

  • Default()方法,检查sdep的componentSpecs(该示例未使用)是否有定义mnist-model容器,如果没有则创建一个,使用tfserving-proxy镜像。换句话讲,我们可以在componentSpec中自定义该proxy容器,而不使用默认的

  • createComponents()方法,调用addTFServerContainer()分别创建tfserving容器和init容器(同样,我们可以在componentSpecs中自定义)。其中,init容器会使用sdep的modelUri参数,用以下载模型文件。最后,调用addEngineToDeployment()创建engine容器。(关于engine容器,将在后续文章介绍)

tfserving容器监听端口2001,proxy容器实现协议转换将请求发送到该端口。上文的调用示例,访问的是proxy服务的端口,使用的是seldon协议。同样,可以直接访问tfserving端口,使用tensorflow协议。如下:

curl -s  -X POST http://$endpoint:2001/v1/models/mnist-model:predict  \
  -H "Content-Type: application/json" \
  -d '{"instances": [[0.7663934193992182, 0.0880671623857957, 0.0775122634595451,..., 0.4126315414418871, 0.42650235187504826]], "signature_name": "predict_images"}'

返回结果如下:

{
    "predictions": [[1.04614508e-17, 3.39505514e-32, 0.489694506, 0.476404935, 2.44480641e-25, 0.0338525251, 3.64945924e-15, 6.72624967e-15, 4.80203671e-05, 7.55260474e-16]
    ]
}

4 总结

seldon内置的TFServing服务,大大简化了模型推理服务的部署,用户只需要配置模型名称及文件路径即可,而不需要考虑模型文件如何下载,如何做协议转换等服务部署相关问题。通过使用默认配置简化用户输入,同时也提供了可自定义的方式(componentSpecs),以满足特定场景需求,这是很好地设计。