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
其中,
- 第一行输出的 tag-set: 'serve' 是
builder.add_meta_graph_and_variables
时的第二个参数: tf.saved_model.tag_constants.SERVING - signature_def['serving_default'] 是
builder.add_meta_graph_and_variables
参数 signature_def_map 中的 key - 后面的 inputs、outputs、mdthod name 是定义 signature 时相应的值。
2. 安装使用 Tensorflow serving
使用上一节中介绍的导出方式以后,我们手上就有带签名的模型文件了。接下来就是运行 Tensorflow serving。
安装 Tensorflow serving 的方式主要有三种 参见官网:
- 使用 Docker 镜像
- 使用 APT 安装
- 源码编译
这里我们直接用 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
说明:
- 模型需要挂载到 /models 目录下,接着是模型名称(model name),接着是版本号。 版本号必须要有且是数字,可以随便设置一个数字,这里暂定版本号为1。
- -e MODEL_NAME 的值必须跟上面挂载设置的模型名称一致。
- ModelServer 监听 8500, HTTP/REST API 监听 8501。
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
说明:
- config 文件指定了多个模型的 name 和 base_path (需要是绝对路径)
- –model_config_file 指定配置文件路径,该参数必须放在最后,它会传递到镜像里的 tensorflow_model_server 中。该参数设置以后,tensorflow_model_server 会自动忽略上一节中设置的 MODEL_NAME 参数。
- 配置文件方式的提交是在 2018-09-08,因此需要晚于此时间的镜像才能运行成功。如不确定,可以运行
tensorflow/serving:nightly
镜像。
3. 开发 Client 代码
服务端已准备完毕,下面是客户端的开发。
客户端可以通过两种方式与服务端通信:
gRPC
(下面会简单介绍)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)