<返回更多

如何提升Tensorflow服务性能

2019-03-29    
加入收藏

Tensorflow已经成长为事实上的机器学习(ML)平台,在业界和研究领域都很流行。对Tensorflow的需求和支持促成了大量围绕训练和服务机器学习(ML)模型的OSS库、工具和框架。Tensorflow服务是一个构建在分布式生产环境中用于服务机器学习(ML)模型的推理方面的项目。

今天,我们将重点讨论通过优化预测服务器和客户机来提高延迟的技术。模型预测通常是“在线”操作(在关键的应用程序请求路径上),因此我们的主要优化目标是以尽可能低的延迟处理大量请求。

首先让我们快速概述一下Tensorflow服务。

什么是Tensorflow服务?

Tensorflow Serving提供灵活的服务器架构,旨在部署和服务机器学习(ML)模型。一旦模型被训练过并准备用于预测,Tensorflow服务就需要将模型导出为Servable兼容格式。

Servable是封装Tensorflow对象的中心抽象。例如,模型可以表示为一个或多个可服务对象。因此,Servables是客户机用来执行计算(如推理)的底层对象。可服务的大小很重要,因为较小的模型使用更少的内存、更少的存储空间,并且将具有更快的加载时间。Servables希望模型采用SavedModel格式,以便使用Predict API加载和服务。

如何提升Tensorflow服务性能

 

Tensorflow Serving将核心服务组件放在一起,构建一个gRPC/HTTP服务器,该服务器可以服务多个ML模型(或多个版本)、提供监视组件和可配置的体系结构。

Tensorflow服务与Docker

让我们使用标准Tensorflow服务(无CPU优化)获得基线预测性能延迟指标。

首先,从Tensorflow Docker hub中提取最新的服务镜像:

docker pull tensorflow/serving:latest

出于本文的目的,所有容器都在4核15GB Ubuntu 16.04主机上运行。

将Tensorflow模型导出为SavedModel格式

使用Tensorflow训练模型时,输出可以保存为变量检查点(磁盘上的文件)。可以通过恢复模型检查点或其转换的冻结图(二进制)直接运行推理。

为了使用Tensorflow服务来提供这些模型,必须将冻结图导出为SavedModel格式。Tensorflow文档提供了以SavedModel格式导出训练模型的示例。

我们将使用深度残差网络(ResNet)模型,该模型可用于对ImageNet的1000个类的数据集进行分类。下载预训练的ResNet-50 v2模型(https://github.com/tensorflow/models/tree/master/official/resnet#pre-trained-model),特别是channels_last(NHWC) convolution SavedModel,它通常更适合CPU。

复制下列结构中的RestNet模型目录:

如何提升Tensorflow服务性能

 

Tensorflow Serving期望模型采用数字排序的目录结构来管理模型版本控制。在这种情况下,目录1/对应于模型版本1,其中包含模型体系结构saved_model.pb以及模型权重(变量)的快照。

加载并提供SavedModel

以下命令在docker容器中启动Tensorflow服务模型服务器。为了加载SavedModel,需要将模型的主机目录挂载到预期的容器目录中。

docker run -d -p 9000:8500  
 -v $(pwd)/models:/models/resnet -e MODEL_NAME=resnet 
 -t tensorflow/serving:latest
如何提升Tensorflow服务性能

 

检查容器日志显示,ModelServer正在运行,准备在gRPC和HTTP端点上为resnet模型提供推理请求:

I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: resnet version: 1}
I tensorflow_serving/model_servers/server.cc:286] Running gRPC ModelServer at 0.0.0.0:8500 ... 
I tensorflow_serving/model_servers/server.cc:302] Exporting HTTP/REST API at:localhost:8501 ...
如何提升Tensorflow服务性能

 

预测客户端

Tensorflow Serving将API服务模式定义为协议缓冲区(protobufs)。预测API的gRPC客户端实现打包为tensorflow_serving.apisPython包。我们还需要tensorflowpython包来实现实用功能。

让我们安装依赖项来创建一个简单的客户端:

virtualenv .env && source .env/bin/activate &&  
 pip install numpy grpcio opencv-python tensorflow tensorflow-serving-api
如何提升Tensorflow服务性能

 

该ResNet-50 v2模型期望在channels_last(NHWC)格式的数据结构中使用浮点Tensor输入。因此,使用opencv-python读取输入图像,opencv-python以float32数据类型加载到numpy数组(height x width x channels)中。下面的脚本创建预测客户端存根,将JPEG图像数据加载到numpy数组中,转换为张量原型,提出gRPC预测请求:

#!/usr/bin/env python
from __future__ import print_function
import argparse
import numpy as np
import time
tt = time.time()
import cv2
import tensorflow as tf
from grpc.beta import implementations
from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2
parser = argparse.ArgumentParser(description='incetion grpc client flags.')
parser.add_argument('--host', default='0.0.0.0', help='inception serving host')
parser.add_argument('--port', default='9000', help='inception serving port')
parser.add_argument('--image', default='', help='path to JPEG image file')
FLAGS = parser.parse_args()
def main(): 
 # create prediction service client stub
 channel = implementations.insecure_channel(FLAGS.host, int(FLAGS.port))
 stub = prediction_service_pb2.beta_create_PredictionService_stub(channel)
 
 # create request
 request = predict_pb2.PredictRequest()
 request.model_spec.name = 'resnet'
 request.model_spec.signature_name = 'serving_default'
 
 # read image into numpy array
 img = cv2.imread(FLAGS.image).astype(np.float32)
 
 # convert to tensor proto and make request
 # shape is in NHWC (num_samples x height x width x channels) format
 tensor = tf.contrib.util.make_tensor_proto(img, shape=[1]+list(img.shape))
 request.inputs['input'].CopyFrom(tensor)
 resp = stub.Predict(request, 30.0)
 
 print('total time: {}s'.format(time.time() - tt))
 
if __name__ == '__main__':
 main()
如何提升Tensorflow服务性能

 

使用输入JPEG图像运行客户机的输出如下所示:

python tf_serving_client.py --image=images/pupper.jpg 

total time: 2.56152906418s

输出张量的预测结果为整数值和特征概率

如何提升Tensorflow服务性能

 

对于单个请求,这种预测延迟是不可接受的。然而,这并非完全出乎意料;服务于二进制文件的默认Tensorflow目标是针对最广泛的硬件范围,以涵盖大多数用例。您可能已经从标准的Tensorflow服务容器日志中注意到:

I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA

这表示Tensorflow服务二进制文件在不兼容的CPU平台上运行,并未进行优化。

构建CPU优化服务二进制

根据Tensorflow文档,建议从源代码编译Tensorflow,并在运行二进制文件的主机平台的CPU上使用所有可用的优化。Tensorflow构建选项公开了一些标志,以支持构建特定于平台的CPU指令集:

如何提升Tensorflow服务性能

 

在本例中,我们将使用1.13:

USER=$1 
TAG=$2 
TF_SERVING_VERSION_GIT_BRANCH="r1.13" 
git clone --branch="$TF_SERVING_VERSION_GIT_BRANCH" https://github.com/tensorflow/serving
如何提升Tensorflow服务性能

 

Tensorflow服务开发镜像使用Bazel作为构建工具。处理器特定CPU指令集的构建目标可以指定如下:

TF_SERVING_BUILD_OPTIONS="--copt=-mavx --copt=-mavx2 --copt=-mfma --copt=-msse4.1 --copt=-msse4.2"

如果memory是约束,则可以使用--local_resources=2048,.5,1.0 flag 限制内存密集型构建过程的消耗。

以开发镜像为基础构建服务镜像:

#!/bin/bash
USER=$1
TAG=$2
TF_SERVING_VERSION_GIT_BRANCH="r1.13"
git clone --branch="${TF_SERVING_VERSION_GIT_BRANCH}" https://github.com/tensorflow/serving
TF_SERVING_BUILD_OPTIONS="--copt=-mavx --copt=-mavx2 --copt=-mfma --copt=-msse4.1 --copt=-msse4.2"
cd serving && 
 docker build --pull -t $USER/tensorflow-serving-devel:$TAG 
 --build-arg TF_SERVING_VERSION_GIT_BRANCH="${TF_SERVING_VERSION_GIT_BRANCH}" 
 --build-arg TF_SERVING_BUILD_OPTIONS="${TF_SERVING_BUILD_OPTIONS}" 
 -f tensorflow_serving/tools/docker/Dockerfile.devel .
cd serving && 
 docker build -t $USER/tensorflow-serving:$TAG 
 --build-arg TF_SERVING_BUILD_IMAGE=$USER/tensorflow-serving-devel:$TAG 
 -f tensorflow_serving/tools/docker/Dockerfile .
如何提升Tensorflow服务性能

 

ModelServer可以配置tensorflow特定的标志来启用会话并行性。以下选项配置两个线程池来并行执行:

intra_op_parallelism_threads

inter_op_parallelism_threads

两个选项的默认值都设置为0。这意味着,系统会选择一个合适的数字,这通常需要每个CPU核心有一个线程可用。

接下来,与之前类似地启动服务容器,这次使用从源码构建的docker映像,并使用Tensorflow特定的CPU优化标志:

docker run -d -p 9000:8500  
 -v $(pwd)/models:/models/resnet -e MODEL_NAME=resnet 
 -t $USER/tensorflow-serving:$TAG 
 --tensorflow_intra_op_parallelism=4 
 --tensorflow_inter_op_parallelism=4
如何提升Tensorflow服务性能

 

容器日志不应再显示CPU警告警告。在不更改任何代码的情况下,运行相同的预测请求会使预测延迟降低约35.8%:

python tf_serving_client.py --image=images/pupper.jpg 

total time: 1.64234706879s

提高预测客户端的速度

服务器端已针对其CPU平台进行了优化,但超过1秒的预测延迟似乎仍然过高。

加载tensorflow_serving和tensorflow库的延迟成本很高。每次调用tf.contrib.util.make_tensor_proto也会增加不必要的延迟开销。

我们实际上并不需要的tensorflow或tensorflow_serving包进行预测的请求。

如前所述,Tensorflow预测API被定义为protobufs。因此,可以通过生成必要的tensorflow和tensorflow_servingprotobuf python存根来替换这两个外部依赖项。这避免了在客户端本身上Pull整个Tensorflow库。

首先,摆脱tensorflow和tensorflow_serving依赖关系,并添加grpcio-tools包。

pip uninstall tensorflow tensorflow-serving-api &&  
 pip install grpcio-tools==1.0.0
如何提升Tensorflow服务性能

 

克隆tensorflow/tensorflow和tensorflow/serving存储库并将以下protobuf文件复制到客户端项目中:

如何提升Tensorflow服务性能

 

将上述protobuf文件复制到protos/目录中并保留原始路径:

如何提升Tensorflow服务性能

 

为简单起见,predict_service.proto可以简化为仅实现Predict RPC。这样可以避免引入服务中定义的其他RPC的嵌套依赖项。这是简化的一个例子prediction_service.proto(https://gist.github.com/masroorhasan/8e728917ca23328895499179f4575bb8)。

使用grpcio.tools.protoc以下命令生成gRPC python实现:

PROTOC_OUT=protos/ 
PROTOS=$(find . | grep ".proto$") 
for p in $PROTOS; do 
 python -m grpc.tools.protoc -I . --python_out=$PROTOC_OUT --grpc_python_out=$PROTOC_OUT $p
done
如何提升Tensorflow服务性能

 

现在tensorflow_serving可以删除整个模块:

from tensorflow_serving.apis import predict_pb2 
from tensorflow_serving.apis import prediction_service_pb2
如何提升Tensorflow服务性能

 

并替换为生成的protobufs protos/tensorflow_serving/apis:

from protos.tensorflow_serving.apis import predict_pb2 
from protos.tensorflow_serving.apis import prediction_service_pb2
如何提升Tensorflow服务性能

 

导入Tensorflow库是为了使用辅助函数make_tensor_proto,该函数用于将 python / numpy对象封装为TensorProto对象。

因此,我们可以替换以下依赖项和代码段:

import tensorflow as tf 
...
tensor = tf.contrib.util.make_tensor_proto(features) 
request.inputs['inputs'].CopyFrom(tensor)
如何提升Tensorflow服务性能

 

使用protobuf导入并构建TensorProto对象:

from protos.tensorflow.core.framework import tensor_pb2 
from protos.tensorflow.core.framework import tensor_shape_pb2 
from protos.tensorflow.core.framework import types_pb2 
...
# ensure NHWC shape and build tensor proto
tensor_shape = [1]+list(img.shape) 
dims = [tensor_shape_pb2.TensorShapeProto.Dim(size=dim) for dim in tensor_shape] 
tensor_shape = tensor_shape_pb2.TensorShapeProto(dim=dims) 
tensor = tensor_pb2.TensorProto( 
 dtype=types_pb2.DT_FLOAT,
 tensor_shape=tensor_shape,
 float_val=list(img.reshape(-1)))
request.inputs['inputs'].CopyFrom(tensor)
如何提升Tensorflow服务性能

 

完整的python脚本在这里可用(https://gist.github.com/masroorhasan/0e73a7fc7bb2558c65933338d8194130)。运行更新的初始客户端,该客户端将预测请求发送到优化的Tensorflow服务:

python tf_inception_grpc_client.py --image=images/pupper.jpg 

total time: 0.58314920859s

下图显示了针对标准,优化的Tensorflow服务和客户端超过10次运行的预测请求的延迟:

如何提升Tensorflow服务性能

 

从标准Tensorflow服务到优化版本的平均延迟降低了约70.4%。

优化预测吞吐量

Tensorflow服务也可以配置为高吞吐量处理。优化吞吐量通常是为“脱机”批处理完成的,在“脱机”批处理中并不严格要求延迟界限。

服务器端批处理

延迟和吞吐量之间的权衡取决于支持的batching 参数。

通过设置--enable_batching和--batching_parameters_file标记来启用batching。可以按SessionBundleConfig的定义设置批处理参数(https://github.com/tensorflow/serving/blob/d77c9768e33e1207ac8757cff56b9ed9a53f8765/tensorflow_serving/servables/tensorflow/session_bundle_config.proto)。对于仅CPU系统,请考虑设置num_batch_threads可用的核心数。

在服务器端达到全部批处理后,推理请求在内部合并为单个大请求(张量),并在合并的请求上运行一个Tensorflow会话。在单个会话上运行一批请求是CPU/GPU并行性真正能够发挥作用的地方。

使用Tensorflow服务进行批量处理时需要考虑的一些用例:

客户端批处理

在客户端进行批处理将多个输入组合在一起以生成单个请求。

由于ResNet模型需要NHWC格式的输入(第一维是输入数),我们可以将多个输入图像聚合成一个RPC请求:

...
batch = [] 
for jpeg in os.listdir(FLAGS.images_path): 
 path = os.path.join(FLAGS.images_path, jpeg)
 img = cv2.imread(path).astype(np.float32)
 batch.Append(img)
...
batch_np = np.array(batch).astype(np.float32) 
dims = [tensor_shape_pb2.TensorShapeProto.Dim(size=dim) for dim in batch_np.shape] 
t_shape = tensor_shape_pb2.TensorShapeProto(dim=dims) 
tensor = tensor_pb2.TensorProto( 
 dtype=types_pb2.DT_FLOAT,
 tensor_shape=t_shape,
 float_val=list(batched_np.reshape(-1)))
request.inputs['inputs'].CopyFrom(tensor)
如何提升Tensorflow服务性能

 

对于一批N个图像,响应中的输出张量将具有请求批次中相同数量的输入的预测结果,在这种情况下N = 2:

如何提升Tensorflow服务性能

 

硬件加速

对于训练,GPU可以更直观地利用并行化,因为构建深度神经网络需要大量计算才能获得最佳解决方案。

但是,推理并非总是如此。很多时候,当图执行步骤放在GPU设备上时,CNN将会加速推断。然而,选择能够优化价格性能最佳点的硬件需要严格的测试、深入的技术和成本分析。硬件加速并行对于“脱机”推理batch processing更有价值。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>