TensorFlow Serving:深度学习模型在生产环境的部署&上线

忘是亡心i 2022-09-10 11:26 583阅读 0赞

TensorFlow Serving简单来说就是一个适合在生产环境中对tensorflow深度学习模型进行部署,然后可以非常方便地通过restful形式的接口进行访问。

除此之外,它拥有许多有点:

  1. 支持配置文件的定期轮询更新(periodically poll for updated),无需重新启动;
  2. 优秀的模型版本控制;
  3. 支持并发;
  4. 支持批处理;
  5. 基于docker,部署简单。

(这些优点我们在下面会逐一提到)

安装

官方极力推荐通过docker的方式进行安装,所以,

  1. 首先我们需要进行docker的安装。
  2. 拉取TensorFlow Serving的docker镜像和仓库

    1. docker pull tensorflow/serving
  3. 使用tensorflow官方自带的模型进行测试。克隆相关的git仓库

    1. git clone https://github.com/tensorflow/serving
  4. 启动TensorFlow Serving的docker容器,开启tensorflow模型的接口服务

    1. TESTDATA="$(pwd)/serving/tensorflow_serving/servables/tensorflow/testdata"
    2. docker run -t --rm -p 8501:8501 \
    3. -v "$TESTDATA/saved_model_half_plus_two_cpu:/models/half_plus_two" \
    4. -e MODEL_NAME=half_plus_two \
    5. tensorflow/serving &
  5. 最后,我们就可以访问模型的接口了(该demo模型是一个简单的对输入的一半加上2)

    (接口地址:http://localhost:8501/v1/models/half\_plus\_two:predict

    参数:{“instances”: [1.0, 2.0, 5.0]})

    1. curl -d '{"instances": [1.0, 2.0, 5.0]}' \
    2. -X POST http://localhost:8501/v1/models/half_plus_two:predict

个性定制模型

保存pb模型

  1. """ 将计算图以pb格式进行保存,用于tf-serving """
  2. import tensorflow.compat.v1 as tf
  3. # import tensorflow as tf
  4. # tf2,否则placeholde会报错
  5. tf.disable_eager_execution()
  6. ############# 在这里定义你的模型 ###########
  7. x1 = tf.placeholder(tf.float32, [None, 3], name='x1')
  8. inputs_id = tf.placeholder(tf.int32, [None, 3], name='x2')
  9. out = tf.add(tf.multiply(x1, 0.5), 2)
  10. embedding = tf.get_variable("embedding_table", shape=[100, 10])
  11. pre = tf.nn.embedding_lookup(embedding, inputs_id)
  12. ############# 在这里定义你的模型 ###########
  13. sess = tf.Session()
  14. sess.run(tf.global_variables_initializer())
  15. # 将张量转化为tensor_info
  16. tensor_info_x1 = tf.saved_model.utils.build_tensor_info(x1)
  17. tensor_info_inputs_id = tf.saved_model.utils.build_tensor_info(inputs_id)
  18. tensor_info_out = tf.saved_model.utils.build_tensor_info(out)
  19. tensor_info_pre = tf.saved_model.utils.build_tensor_info(pre)
  20. # 创建SavedModelBuilder,指定保存路径
  21. builder = tf.saved_model.builder.SavedModelBuilder("serving-model/3")
  22. """ 接口传参 {"instances": [{"x1": [1.0, 2.0, 5.0],"inputs_id": [1, 2, 3]}]},此时签名定义只能为默认的"serving_default" {"inputs": {"x1": [1.0, 2.0, 5.0],"inputs_id": [1, 2, 3]}} 使用自定义的签名 {"instances": [{"x1": [1.0, 2.0, 5.0],"inputs_id": [1, 2, 3]}], "signature_name": "my_signature"} 返回:{"predictions":[{"out":......., "pre":......}]} """
  23. # 指定接口的输入以及返回
  24. prediction_signature = (
  25. tf.saved_model.signature_def_utils.build_signature_def(
  26. inputs={ 'x1': tensor_info_x1, "inputs_id": tensor_info_inputs_id},
  27. outputs={ 'out': tensor_info_out, "pre": tensor_info_pre},
  28. method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME))
  29. # 定义签名
  30. legacy_init_op = tf.group(tf.tables_initializer(), name='legacy_init_op')
  31. builder.add_meta_graph_and_variables(
  32. sess, [tf.saved_model.tag_constants.SERVING],
  33. signature_def_map={
  34. # 使用自定义的签名:my_signature
  35. # 'my_signature':
  36. # prediction_signature,
  37. # 使用tensorflow默认的签名:serving_default
  38. tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:
  39. prediction_signature,
  40. },
  41. legacy_init_op=legacy_init_op)
  42. # 模型保存
  43. builder.save()
  44. print('Done exporting!')

模型的保存格式如下:

一个saved_model.pb文件和variables文件夹。

这里需要注意:假如我们想要将模型保存在/tmp/model目录下,那么必须要创建一个版本号目录,如版本号为“1”,那么模型就应保存在/tmp/model/1目录下

在这里插入图片描述

部署上线

此时,我们仅仅需要执行一个docker命令即可实现模型的部署上线了

  1. docker run -p 8501:8501 \
  2. --mount type=bind,source=/tmp/model,target=/models/myserving \
  3. -e MODEL_NAME=myserving -t tensorflow/serving &

解释一下:

  1. -p 8501:8501指容器中服务的端口为8501,然后映射到主机的8501端口(前为主机)
  2. –mount type=bind,source=/tmp/model,target=/models/myserving:将主机的/tmp/model目录挂载到容器/models/myserving目录,此时如果主机该目录下发生改动,那么容器也会随着改变。
  3. -e MODEL_NAME=myserving:将部署的模型命名为:myserving
  4. -t tensorflow/serving:以tensorflow/serving镜像启动该容器

接口访问

模型部署上线之后,就可以通过接口进行访问了。

接口地址:http://localhost:8501/v1/models/myserving:predict

参数为:

  1. { "instances": [{ "x1": [1.0, 2.0, 5.0],"inputs_id": [1, 2, 3]}]}

或者

  1. { "inputs": { "x1": [1.0, 2.0, 5.0],"inputs_id": [1, 2, 3]}}

加入签名的参数形式,验签必须与代码中的签名对应:

  1. { "instances": [{ "x1": [1.0, 2.0, 5.0],"inputs_id": [1, 2, 3]}], "signature_name": "my_signature"}

模型控制

前面我们提到,TensorFlow Serving有着优秀的模型版本控制功能。

默认最新

首先,TensorFlow Serving默认是加载最大版本号的模型。例如,上面我们部署了一个版本号为“1”的模型,如果之后模型又进行更新,版本号升级为“2”,那么仅需要将新的模型拷贝到相同的目录下即可。

比如,这个时候就有两个模型/tmp/model/1和/tmp/model/2,TensorFlow Serving会默认加载版本号“2”的模型。

自定义版本

如果我们想要多个版本的模型同时存在,并且多个模型同时部署,那么也是可以实现的。

在/tmp/model下创建一个models.config文件,以protocol的形式写入以下内容

  1. model_config_list {
  2. config {
  3. name: 'myserving'
  4. base_path: '/models/myserving/'
  5. model_platform: 'tensorflow'
  6. model_version_policy {
  7. specific {
  8. versions: 1
  9. versions: 2
  10. }
  11. }
  12. version_labels {
  13. key: 'stable'
  14. value: 1
  15. }
  16. version_labels {
  17. key: 'canary'
  18. value: 2
  19. }
  20. }
  21. config {
  22. name: 'model2'
  23. base_path: '/models/model2/'
  24. model_platform: 'tensorflow'
  25. }
  26. }

可以看到,里面有两个config,意味着我们同时上线两个模型。

看第一个config:name为模型的名称,base_path为容器中模型的位置,model_platform就设置为tensorflow即可;

model_version_policy不加的话就是默认最新的版本控制策略。specific指定上线的版本,version_labels将版本号映射为对应的key,如stable对应版本号“1”的模型。

也可以是这种形式,加载最新的n个版本的模型:

  1. model_version_policy: {
  2. latest: {
  3. num_versions:5
  4. }
  5. }

然后,在启动容器服务的时候,需要指定配置文件路径,

  1. docker run -p 8501:8501 \
  2. --mount type=bind,source=/tmp/model,target=/models/myserving \
  3. -e MODEL_NAME=myserving -t tensorflow/serving \
  4. --model_config_file=/models/models.config \
  5. --allow_version_labels_for_unavailable_models=true

(如果不加这个配置项–allow_version_labels_for_unavailable_models=true,那么版本号和key的映射关系不能在启动时设置,只能在启动后才能进行设置)

那么,如果访问第一个模型的stable版本,地址则为:

http://localhost:8501/v1/models/myserving/labels/stable:predict

或者

http://localhost:8501/v1/models/myserving/versions/1:predict

官方推荐第一种。

轮询更新

想要对模型的配置文件进行定期轮询更新的话,只需要加上配置项

  1. --model_config_file_poll_wait_seconds=60

这里是设置为60秒一次。

并发和批处理

批处理简单来说就是可以将多个接口的请求合并一个batch,然后模型计算完成之后一起返回。

注意事项:1、未开启batch时,参数输入不会改变shape,直接输入到模型;

2、开启batch时,参数输入需要少第一个维度,即不能包含batch_size这个维度,不然tensorflow serving无法合并batch,会抛出异常 Batching session Run() input tensors must have at least one dimension。

在/tmp/model下创建一个batcj.config文件,仍是protocol的形式写入以下内容

  1. max_batch_size { value: 128 }
  2. batch_timeout_micros { value: 1000 }
  3. max_enqueued_batches { value: 1000000 }
  4. num_batch_threads { value: 8 }

max_batch_size:一个批次允许的最大请求数量

batch_timeout_micros:合并一个批次等待的最长时间,即使该批次的数量未达到max_batch_size,也会立即进行计算(单位是微秒)

max_enqueued_batches:队列的最大数量

num_batch_threads:线程数,在这里体现并发。
这些都是全局设置,针对所有版本的模型!!!

那么,此时启动容器服务的命令就变成

  1. docker run -p 8501:8501 \
  2. --mount type=bind,source=/tmp/model,target=/models/myserving \
  3. -e MODEL_NAME=myserving -t tensorflow/serving \
  4. --model_config_file=/models/models.config \
  5. --allow_version_labels_for_unavailable_models=true \
  6. --enable_batching=true \
  7. --batching_parameters_file=/models/batch.config

数据预热warmup

  • 模型加载时,因为是懒加载lazily initialized,第一次查询时会有很大的延迟,可以通过预热warmup来解决;
  • 增加参数--enable_model_warmup true
  • 每个版本必须准备一份预热数据,名称为tf_serving_warmup_requests,然后放在对应版本的assets.extra目录下;
  • signature_name签名也得跟上述模型导出的对应;
  • 另外一个比较坑的点,模型导出路径必须是空目录,所以必须先导出模型,再生成预热数据,但如果tersorflow serving默认是热部署,导致模型导出后就会立即被加载,预热数据还没生成;
  • 此时需要配合--file_system_poll_wait_seconds,轮询更新模型的时间,官网没看到默认值是多少,但实际使用一般是立即加载。这个时间可以设大一点,留给生成预热数据的时间。

    import os
    import tensorflow as tf
    from tensorflow_serving.apis import model_pb2
    from tensorflow_serving.apis import predict_pb2
    from tensorflow_serving.apis import prediction_log_pb2

  1. def main():
  2. serving_dir = "serving-model"
  3. version = "3"
  4. with tf.python_io.TFRecordWriter(
  5. os.path.join(serving_dir, version, "assets.extra/tf_serving_warmup_requests")) as writer:
  6. request = predict_pb2.PredictRequest(
  7. model_spec=model_pb2.ModelSpec(name="model_name", signature_name='serving_default'),
  8. inputs={ "x1": tf.make_tensor_proto([[1.0, 2.0]], shape=[1, 2]),
  9. "inputs_id": tf.make_tensor_proto([[1, 2]], shape=[1, 2])}
  10. )
  11. log = prediction_log_pb2.PredictionLog(
  12. predict_log=prediction_log_pb2.PredictLog(request=request))
  13. writer.write(log.SerializeToString())
  14. if __name__ == "__main__":
  15. main()

使用GPU

需要安装cuda、nvidia-docker,网上教程很多,大家随便搜下都有的。

发表评论

表情:
评论列表 (有 0 条评论,583人围观)

还没有评论,来说两句吧...

相关阅读