龙空技术网

如何提升Tensorflow服务性能

林小婵的店 4825

前言:

而今你们对“tensorflow优化算法”大致比较看重,姐妹们都需要学习一些“tensorflow优化算法”的相关内容。那么小编也在网摘上收集了一些对于“tensorflow优化算法””的相关内容,希望小伙伴们能喜欢,姐妹们快快来学习一下吧!

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

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

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

什么是Tensorflow服务?

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

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

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模型(),特别是channels_last(NHWC) convolution SavedModel,它通常更适合CPU。

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

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

检查容器日志显示,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 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

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

#!/usr/bin/env pythonfrom __future__ import print_functionimport argparseimport numpy as npimport timett = time.time()import cv2import tensorflow as tffrom grpc.beta import implementationsfrom tensorflow_serving.apis import predict_pb2from tensorflow_serving.apis import prediction_service_pb2parser = 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()

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

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

total time: 2.56152906418s

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

对于单个请求,这种预测延迟是不可接受的。然而,这并非完全出乎意料;服务于二进制文件的默认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指令集:

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

USER=$1 TAG=$2 TF_SERVING_VERSION_GIT_BRANCH="r1.13" git clone --branch="$TF_SERVING_VERSION_GIT_BRANCH" 

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/bashUSER=$1TAG=$2TF_SERVING_VERSION_GIT_BRANCH="r1.13"git clone --branch="${TF_SERVING_VERSION_GIT_BRANCH}" "--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 .

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

intra_op_parallelism_threads

控制用于并行执行单个操作的最大线程数。用于并行化具有子操作的操作,这些子操作本质上是独立的。

inter_op_parallelism_threads

控制用于并行执行独立不同操作的最大线程数。Tensorflow Graph上的操作彼此独立,因此可以在不同的线程上运行。

两个选项的默认值都设置为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

容器日志不应再显示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/serving存储库并将以下protobuf文件复制到客户端项目中:

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

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

使用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 $pdone

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

from tensorflow_serving.apis import predict_pb2 from tensorflow_serving.apis import prediction_service_pb2

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

from protos.tensorflow_serving.apis import predict_pb2 from protos.tensorflow_serving.apis import prediction_service_pb2

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

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

import tensorflow as tf ...tensor = tf.contrib.util.make_tensor_proto(features) request.inputs['inputs'].CopyFrom(tensor)

使用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 prototensor_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)

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

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

total time: 0.58314920859s

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

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

优化预测吞吐量

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

服务器端批处理

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

通过设置--enable_batching和--batching_parameters_file标记来启用batching。可以按SessionBundleConfig的定义设置批处理参数()。对于仅CPU系统,请考虑设置num_batch_threads可用的核心数。

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

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

使用异步客户机请求填充服务器端上的batches通过将模型图组件放在CPU / GPU上来加速批处理在从同一服务器提供多个模型时交错预测请求强烈建议对“离线”高容量推理处理进行批处理

客户端批处理

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

由于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)

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

硬件加速

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

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

标签: #tensorflow优化算法 #ubuntuprotoc升级