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

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

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

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

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

安装

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

  1. 首先我们需要进行docker的安装。

  2. 拉取TensorFlow Serving的docker镜像和仓库

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

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

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

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

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

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

个性定制模型

保存pb模型

"""
将计算图以pb格式进行保存,用于tf-serving
"""
import tensorflow.compat.v1 as tf
# import tensorflow as tf

# tf2,否则placeholde会报错
tf.disable_eager_execution()

############# 在这里定义你的模型  ###########
x1 = tf.placeholder(tf.float32, [None, 3], name='x1')
inputs_id = tf.placeholder(tf.int32, [None, 3], name='x2')

out = tf.add(tf.multiply(x1, 0.5), 2)

embedding = tf.get_variable("embedding_table", shape=[100, 10])
pre = tf.nn.embedding_lookup(embedding, inputs_id)
############# 在这里定义你的模型  ###########

sess = tf.Session()
sess.run(tf.global_variables_initializer())

# 将张量转化为tensor_info
tensor_info_x1 = tf.saved_model.utils.build_tensor_info(x1)
tensor_info_inputs_id = tf.saved_model.utils.build_tensor_info(inputs_id)
tensor_info_out = tf.saved_model.utils.build_tensor_info(out)
tensor_info_pre = tf.saved_model.utils.build_tensor_info(pre)

# 创建SavedModelBuilder,指定保存路径
builder = tf.saved_model.builder.SavedModelBuilder("serving-model/3")

"""
接口传参
{"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":......}]}
"""
# 指定接口的输入以及返回
prediction_signature = (
  tf.saved_model.signature_def_utils.build_signature_def(
      inputs={'x1': tensor_info_x1, "inputs_id": tensor_info_inputs_id},
      outputs={'out': tensor_info_out, "pre": tensor_info_pre},
      method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME))

# 定义签名
legacy_init_op = tf.group(tf.tables_initializer(), name='legacy_init_op')
builder.add_meta_graph_and_variables(
    sess, [tf.saved_model.tag_constants.SERVING],
    signature_def_map={
        # 使用自定义的签名:my_signature
        # 'my_signature':
        #     prediction_signature,

        # 使用tensorflow默认的签名:serving_default
        tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:
            prediction_signature,
    },
    legacy_init_op=legacy_init_op)

# 模型保存
builder.save()

print('Done exporting!')

模型的保存格式如下:

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

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

在这里插入图片描述

部署上线

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

docker run -p 8501:8501 \
--mount type=bind,source=/tmp/model,target=/models/myserving \
-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

参数为:

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

或者

{"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"}

模型控制

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

默认最新

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

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

自定义版本

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

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

model_config_list {
  config {
    name: 'myserving'
    base_path: '/models/myserving/'
    model_platform: 'tensorflow'
    model_version_policy {
      specific {
        versions: 1
        versions: 2
      }
    }
    version_labels {
      key: 'stable'
      value: 1
    }
    version_labels {
      key: 'canary'
      value: 2
    }
  }
  config {
    name: 'model2'
    base_path: '/models/model2/'
    model_platform: 'tensorflow'
  }
}

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

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

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

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

model_version_policy: {
            latest: {
                num_versions:5
            }
        }

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

docker run -p 8501:8501 \
--mount type=bind,source=/tmp/model,target=/models/myserving \
-e MODEL_NAME=myserving -t tensorflow/serving \
--model_config_file=/models/models.config \
--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

官方推荐第一种。

轮询更新

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

--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的形式写入以下内容

max_batch_size { value: 128 }
batch_timeout_micros { value: 1000 }
max_enqueued_batches { value: 1000000 }
num_batch_threads { value: 8 }

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

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

max_enqueued_batches:队列的最大数量

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

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

docker run -p 8501:8501 \
--mount type=bind,source=/tmp/model,target=/models/myserving \
-e MODEL_NAME=myserving -t tensorflow/serving \
--model_config_file=/models/models.config \
--allow_version_labels_for_unavailable_models=true \
--enable_batching=true \
--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


def main():
    serving_dir = "serving-model"
    version = "3"

    with tf.python_io.TFRecordWriter(
            os.path.join(serving_dir, version, "assets.extra/tf_serving_warmup_requests")) as writer:
        request = predict_pb2.PredictRequest(
            model_spec=model_pb2.ModelSpec(name="model_name", signature_name='serving_default'),
            inputs={"x1": tf.make_tensor_proto([[1.0, 2.0]], shape=[1, 2]),
                    "inputs_id": tf.make_tensor_proto([[1, 2]], shape=[1, 2])}
        )

        log = prediction_log_pb2.PredictionLog(
            predict_log=prediction_log_pb2.PredictLog(request=request))
        writer.write(log.SerializeToString())


if __name__ == "__main__":
    main()

使用GPU

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

你可能感兴趣的:(tensorflow,深度学习,tensorflow,深度学习,python)