Bootstrap

Seldon 使用 (五): engine & graph

在上文,我们提到每个SeldonDeployment(简称sdep)的pod中都包含一个engine容器。这个engine容器,是由seldon-manager服务为每个sdep插入的,用于满足复杂推理场景的需求,如合并(Combine)多个模型推理结果,推理请求处理(Transform-Input),推理结果转换(Transform-Output),以及推理请求路由等对模型推理的请求和结果进行编排

1 示例一(Route):多臂赌博机模型服务

该示例是由seldon官方提供的,详细可见。创建sdep定义文件egreedy.yaml,如下:

apiVersion: machinelearning.seldon.io/v1alpha2
kind: SeldonDeployment
metadata:
  name: egreedy
spec:
  predictors:
  - componentSpecs:
    - spec:
        containers:
        - image: seldonio/mock_classifier:1.9.1
          name: classifier-1
          env:
          - name: PREDICTIVE_UNIT_HTTP_SERVICE_PORT
            value: "9000"
        - image: seldonio/mock_classifier:1.9.1
          name: classifier-2
          env:
          - name: PREDICTIVE_UNIT_HTTP_SERVICE_PORT
            value: "9001"
        - image: seldonio/mock_classifier:1.9.1
          name: classifier-3
          env:
          - name: PREDICTIVE_UNIT_HTTP_SERVICE_PORT
            value: "9002"
        - image: seldonio/mab_epsilon_greedy:1.9.1
          name: eg-router
          env:
          - name: PREDICTIVE_UNIT_HTTP_SERVICE_PORT
            value: "9003"    
     graph:
      children:
      - name: classifier-1
        type: MODEL
      - name: classifier-2
        type: MODEL
      - name: classifier-3
        type: MODEL
      name: eg-router
      parameters:
      - name: n_branches
        type: INT
        value: '3'
      - name: epsilon
        type: FLOAT
        value: '0.3'
      - name: verbose
        type: BOOL
        value: '1'
      type: ROUTER
    svcOrchSpec:
      env:
      - name: SELDON_ENABLE_ROUTING_INJECTION
        value: 'true'
    name: multi-models-predictor
    replicas: 1

说明如下:

  • graph定义中模型推理的入口是name指定的eg-router容器,该容器使用多臂赌博机算法,从三个childen容器中选择一个模型服务,然后将推理请求发送给该childen容器模型服务处理

  • graph中定义的eg-router容器和三个children容器,分别和ComponentSpec中定义了四个容器一一对应

  • eg-router容器的多臂赌博机算法,是使用1-epislon的概率选择使用历史最佳模型,或者epislon概率选择其他模型服务

当pod启动后,使用kubectl describe pod可以看到,该pod内有五个容器(container)。除了上述4个容器外,还有一个seldon-container-engine容器。engine容器负责编排上述四个容器,根据graph指定的类型(type)为ROUTER,将先请求发送给eg-router获取childern容器的index,然后根据这个index获取该容器的端口,完成最后的推理请求

可使用如下脚本,测试推理服务。其中sys.argv[1]参数,使用podIP+engine端口(可通过kubectl get pod -owide |grep seldon),或者serviceIP+engine端口(可通过命令kubectl get svc -owide |grep seldon获得)都可以。注:engine端口号是8000。

import requests
import sys

url = "http://%s/api/v1.0/predictions" % sys.argv[1]
req = {"data": {"ndarray": [[1.0, 2.0, 5.0]]}}
res_raw = requests.post(url, json=req)
print('status_code:', res_raw.status_code)
print(res_raw.text)

返回结果如下所示。注,本次请求使用的是classifier-3模型服务的推理结果。

{
  "data":{
    "names":["proba"],"ndarray":[[0.43782349911420193]]
  },
  "meta":{
    "requestPath":{"classifier-3":"seldonio/mock_classifier:1.9.1"}
  }
}

2 示例二(Transform):输入输出转换

该示例是seldon官方提供的,详细可查看。创建seldon-model.yaml,如下所示:

apiVersion: machinelearning.seldon.io/v1alpha2
kind: SeldonDeployment
metadata:
  name: seldon-model
spec:
  name: test-deployment
  predictors:
  - componentSpecs:
    - spec:
        containers:
        - name: step_one
          image: seldonio/step_one:1.0
        - name: step_two
          image: seldonio/step_two:1.0
        - name: step_three
          image: seldonio/step_three:1.0
    graph:
      name: step_one
      endpoint:
        type: REST
      type: MODEL
      children:
          name: step_two
          endpoint:
            type: REST
          type: MODEL
          children:
              name: step_three
              endpoint:
                type: REST
              type: MODEL
              children: []
    name: example
    replicas: 1

说明:

  • graph定义的模型推理入口是step_one容器,该容器负载完成transformer-input(即输入特征转换,例如过滤,默认值,标准化等)

  • 然后模型推理服务由step_one容器的children,即step_two容器完成

  • 最后,step_two容器的推理结果,再由它的children,即step_three容器完成

  • graph所定义的三个容器,分别对应到componentSpec中定义的三个容器

  • 上述,step_one, step_two, step_three三个容器的处理过程,同样是由engine容器编排完成的。

3 engine服务如何实现

上面描述的两个推理服务编排(graph),它们都是由engine容器服务实现的。那么engine容器是如何实现的呢。

engine容器实现的服务是一个http服务,我们这里只看它的主要接口predictions。实现如下,详细可查看executor/api/rest/server.go源码文件

func (r *SeldonRestApi) predictions(w http.ResponseWriter, req *http.Request) {
    seldonPredictorProcess := predictor.NewPredictorProcess(ctx, r.Client, logf.Log.WithName(LoggingRestClientName), r.ServerUrl, r.Namespace, req.Header, modelName)
    reqPayload, err := seldonPredictorProcess.Client.Unmarshall(bodyBytes, req.Header.Get(http2.ContentType))
    graphNode = &r.predictor.Graph
    resPayload, err := seldonPredictorProcess.Predict(graphNode, reqPayload)
    r.respondWithSuccess(w, http.StatusOK, resPayload)
}

说明:

  • 对于每个请求,创建一个PredictorProcess。然后将反序列化后的请求,以及Graph,做为参数,调用PredictorProcess.Predict()方法,得到推理结果

Predict()方法处理过程如下,详细可查看executor/predictor/predictor_process.go源码:

func (p *PredictorProcess) Predict(node *v1.PredictiveUnit, msg payload.SeldonPayload) (payload.SeldonPayload, error) {
    tmsg, err := p.transformInput(node, msg)
    cmsg, err := p.predictChildren(node, tmsg)
    response, err := p.transformOutput(node, cmsg)
    return response, err
}

说明:

  • transformInput():如果node的type是MODEL,则调用该节点的Predict()方法处理。如果是TRANSFORMER,则调用该节点的TransformInput()方法处理。

  • predictChildren(): 如果该节点包含children,且type是ROUTER,则调用其route()方法获得index(即后续请求发给哪个children节点)

  • transformOutput(): 如果node的type是TRANFORMER,则调用该节点的TransformOutput()方法转换推理结果

总体,逻辑是比较清晰易懂的。这里只做简单介绍,可大致了解,如需要则可自行阅读源码。

4 总结

seldon通过提供engine容器及其所定义的type和相应的逻辑,一定程度上满足了模型服务编排的需要。

通常编排服务,都会涉及到底层资源的信息,包括服务地址,如何寻址,通信方式等,这些细节,对于不同的底层资源,实现方式会有所不同。但是这些细节,却又不是模型服务所关心的。而engine容器,正好可以满足这种需求,即屏蔽了底层资源细节,同时又满足了模型服务编排(graph)的需求。