TensorFlow Serving简单来说就是一个适合在生产环境中对tensorflow深度学习模型进行部署,然后可以非常方便地通过restful形式的接口进行访问。
除此之外,它拥有许多有点:
(这些优点我们在下面会逐一提到)
官方极力推荐通过docker的方式进行安装,所以,
首先我们需要进行docker的安装。
拉取TensorFlow Serving的docker镜像和仓库
docker pull tensorflow/serving
使用tensorflow官方自带的模型进行测试。克隆相关的git仓库
git clone https://github.com/tensorflow/serving
启动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 &
最后,我们就可以访问模型的接口了(该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格式进行保存,用于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 &
解释一下:
模型部署上线之后,就可以通过接口进行访问了。
接口地址: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
模型加载时,因为是懒加载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()
需要安装cuda、nvidia-docker,网上教程很多,大家随便搜下都有的。