目录

06 - Tensorflow Serving 使用说明

本文大部分是对官网的描述做些备注和个人理解。如有需要,请直接查看官网原文。

https://tensorflow.google.cn/serving/serving_basic?hl=zh-CN (官方网址)

前置知识: 建议阅读 05 - Tensorflow 模型保存与恢复

1. 准备工作

一般来说,算法开发人员负责设计和训练模型,而运维人员(或业务开发人员)负责把模型导出并部署到线上。那么在设计时,由于个人习惯或各部门规范不同,模型所使用的输入名称有可能会相差甚远。更不用说有时候网络结构是从网上找来的。以上的种种情况,都会导致 Tensor 的输入输出名称难以做到统一,形成规范,给部署带来一定的麻烦。

因此,Tensorflow 支持在使用 saved_model 模块导出模型时,给输入和输出起个“别名”,达到不改变模型源码的情况下,形成一套规范的目的。

请看以下示例,使用 saved_model 导出模型的一般性代码为:

import tensorflow as tf
 
builder = tf.saved_model.builder.SavedModelBuilder(export_dir="/path/to/save/model")
 
builder.add_meta_graph_and_variables(
        sess, [tf.saved_model.tag_constants.SERVING],
        strip_default_attrs=True)
 
builder.save()

此时,如果原来模型的 input_tensor 为 “X”,output_tensor 为 “Y”,那么,我们只能通过文档或者其他方式告诉以后的使用者,输入输出的名称和形状分别是什么。

使用 saved_model 导出带签名(signature)的模型:

import tensorflow as tf
 
# 假设模型的输入是X,最终输出是Y
# X = tf.placeholder(dtype=tf.float32, shape=[None, 10], name='X')
# Y = tf.nn.softmax(..., name='Y')
 
# 注意:这里的导出路径,官方建议加上版本号(必须是数字),后续 tensorflow serving 需要。
builder = tf.saved_model.builder.SavedModelBuilder(export_dir="/path/to/save/model/version")
 
# 使用 build_tensor_info 为原来的输入输出 tensor 生成 tensor_info 信息。
tensor_info_x = tf.saved_model.utils.build_tensor_info(X)
tensor_info_y = tf.saved_model.utils.build_tensor_info(Y)
 
# 定义一个 signature
# 其中 inputs 和 outputs 都是字典,分别定义了模型的输入和输出。key 是别名, value 是上面定义的 tensor_info。
# method_name 为方法名,可以自定义,也可以使用官方预定义的常量。
my_signature = (
        tf.saved_model.signature_def_utils.build_signature_def(
            inputs={'input': tensor_info_x},
            outputs={'ouput': tensor_info_y},
            method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME))
 
# 摘自官方示例,暂时未知此处作用
legacy_init_op = tf.group(tf.tables_initializer(), name='legacy_init_op')
 
# 此处是重点,多增加了一个 signature_def_map 参数。 key 是 signature_def 名称, value 是 signature 定义。
builder.add_meta_graph_and_variables(
        sess, [tf.saved_model.tag_constants.SERVING],
        signature_def_map={
            tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: my_signature,
        },
        legacy_init_op=legacy_init_op)
 
builder.save()

此时,导出的模型已经包含相应的签名信息,还做到了输入输出重命名。最重要的是,即使不需要文档,也可以知道输入输出的名称和形状。 验证方法如下:

# 使用 saved_model_cli 命令可以查看模型的签名信息
saved_model_cli show --dir /path/to/save/model --all
 
# 上面命令的输出示例如下:
 
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:
 
signature_def['serving_default']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['input'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 10)
        name: X:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['output'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 1)
        name: Y:0
  Method name is: tensorflow/serving/predict

其中,

  1. 第一行输出的 tag-set: 'serve' 是 builder.add_meta_graph_and_variables 时的第二个参数: tf.saved_model.tag_constants.SERVING
  2. signature_def['serving_default'] 是 builder.add_meta_graph_and_variables 参数 signature_def_map 中的 key
  3. 后面的 inputs、outputs、mdthod name 是定义 signature 时相应的值。

2. 安装使用 Tensorflow serving

使用上一节中介绍的导出方式以后,我们手上就有带签名的模型文件了。接下来就是运行 Tensorflow serving。

安装 Tensorflow serving 的方式主要有三种 参见官网

  1. 使用 Docker 镜像
  2. 使用 APT 安装
  3. 源码编译

这里我们直接用 Docker 镜像,简单方便。其他方式请参考官网。

docker pull tensorflow/serving

2.1 单模型运行方式:

docker run -itd -p 8500:8500 8501:8501 \
    -v /local/path/to/model:/models/name/1 \
    -e MODEL_NAME=name \
    tensorflow/serving

说明:

  1. 模型需要挂载到 /models 目录下,接着是模型名称(model name),接着是版本号。 版本号必须要有且是数字,可以随便设置一个数字,这里暂定版本号为1。
  2. -e MODEL_NAME 的值必须跟上面挂载设置的模型名称一致。
  3. ModelServer 监听 8500, HTTP/REST API 监听 8501。
  4. 以上的 Docker 镜像是 CPU 版本的 serving, 如果需要使用 GPU,则参见官网

2.2 多模型运行方式:

有时候我们需要让一个 serving 同时运行多个模型,那么可以通过配置文件的方式实现。

首先新建一个配置文件,如 models.conf,内容如下: (配置文件更多选项参见:网址

# cat models.conf
 
model_config_list: {
  config: {
    name: "model1",
    base_path: "/models/model1",
    model_platform: "tensorflow"
  },
  config: {
    name: "model2",
    base_path: "/models/model2",
    model_platform: "tensorflow"
  }
}

以指定的 config 运行 serving:

docker run -itd -p 8500:8500 8501:8501 \
    -v /local/path/to/model1:/models/model1 \
    -v /local/path/to/model2:/models/model2 \
    -v /local/path/to/models.conf:/models/models.conf \
    tensorflow/serving --model_config_file=/models/models.conf

说明:

  1. config 文件指定了多个模型的 name 和 base_path (需要是绝对路径)
  2. –model_config_file 指定配置文件路径,该参数必须放在最后,它会传递到镜像里的 tensorflow_model_server 中。该参数设置以后,tensorflow_model_server 会自动忽略上一节中设置的 MODEL_NAME 参数。
  3. 配置文件方式的提交是在 2018-09-08,因此需要晚于此时间的镜像才能运行成功。如不确定,可以运行 tensorflow/serving:nightly 镜像。

3. 开发 Client 代码

服务端已准备完毕,下面是客户端的开发。

客户端可以通过两种方式与服务端通信:

  1. gRPC (下面会简单介绍)
  2. REST API (REST API 不进行细讲,参考官网)

以下是一个 Python 版本的示例(C++版本的参见官网):

首先,安装相应的 api:

pip3 install tensorflow-serving-api

然后:

import grpc
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2_grpc
 
def _create_rpc_callback(result_future):
    # 请求回调,成功时从 future.result() 可以拿到结果,异常时从 future.exception() 获取异常结果。
    exception = result_future.exception()
    if exception:
        print(exception)
    else:
        response = np.array(result_future.result().outputs['predict'].int64_val)
        # print(response)
 
def do_inference(host_and_port, input_data):
    # 定义服务器连接
    channel = grpc.insecure_channel(host_and_port)
    # 定义 request
    stub = prediction_service_pb2_grpc.PredictionServiceStub(channel)
    request = predict_pb2.PredictRequest()
 
    # 选择相应的 model 和 signature
    request.model_spec.name = 'model_name'   # model name
    request.model_spec.signature_name = 'serving_default'  # signature def name
 
    # 填充 input 数据
    request.inputs['input'].CopyFrom(
            tf.contrib.util.make_tensor_proto(input_data.astype(np.float32)))
 
    # 开始请求
    result_future = stub.Predict.future(request, timeout=5.0)  # 5 seconds
    result_future.add_done_callback(_create_rpc_callback)