一些童鞋可能知道,AWS Inferentia是AWS自行设计研发的AI专用芯片,可以用相较于传统GPU更高的性能和更低的成本进行机器学推理。目前一些AWS EC2实例已经配备了这块芯片。不过具体来说,它的性能到底有多强?成本又能低多少?本文将通过人体姿态估计的实例向大家展示。
在本文中,我们将使用AWS Neuron编译一套开源的TensorFlow版本的OpenPose,并针对基于AWS Inferentia的实例对推理性能进行调优。与基于GPU的实例相比,我们需要在本演练中设置基准测试环境、衡量图像处理管道的吞吐量,并量化该系统的性价比改进情况。
关于OpenPose
人体姿态估计,属于一类机器学习(ML)与计算机视觉(CV)相结合的技术应用方向,可用于支持行人意图预估、AR和体育赛事中的运动跟踪等多种用例。姿态估计的核心在于识别图像(关节与关键点)的具体坐标,将这些坐标串连起来以构成一个人的骨骼表示。对身体姿态的准确表达,将成为机器人交互设计乃至运动姿态量化等应用目标的实现前提。
目前市面上存在诸多可用于人体姿态估计的方法,其中OpenPose采取深度学习凭借自下而上的方法(由卡耐基梅隆大学认知计算实验室于2018年发布)吸引到众多拥趸。OpenPose是一种多人2D姿态估计模型,其中采用一种称为“部位亲和域”(PAF)的技术对身体各部位进行关联,并借此在图像上建立起多个单独的骨架结构。在这种自下而上的方法中,模型能够识别出各个关键点,并据此将骨架结构拼凑在一起。
为了实现这一目标,OpenPose使用了两步式流程。首先,它使用VGG-19模型提取图像特征,并将这些特征传递至并行运行的一对卷积神经网络(CNN)当中。
其中一套CNN负责计算置信度图,借此检测图像中的各个身体部位。另一套CNN则计算PAF,并将各个部分合并起来,构成人体骨骼结构。我们可以多次重复这些并行分支,借此完善置信度图与PAF预测结果。
下图所示,为来自VGG的特征F,此特征被馈送至OpenPose模型的PAF与置信度图分支当中。(来源:使用部位亲和域进行实时多人2D姿态估计)
原始OpenPose代码依赖于Caffe模型与预统计的C++库。出于使用便捷性与可移植性的考虑,我们在本轮演练中使用GitHub repo中的tf-pose-estimation基于TensorFlow 1.15重新实现了OpenPose的神经网络。此Repo还提供ML管道脚本,大家可以用OpenPose对图像及视频进行预处理与后处理。
先决条件
在本轮演练中,需要准备一个有权访问AWS管理控制台的AWS账户,同时保证有权限使用公开IP创建Amazon Elastic Compute Cloud (Amazon EC2) 实例和创建Amazon Simple Storage Service (Amazon S3)存储桶。
另外,如果熟悉如何使用AWS Deep Learning AMI与Jupyter notebooks中的Conda环境则更好,但并非硬性要求。
关于AWS Inferentia与Neuron SDK
AWS Inferentia芯片是一款由AWS定制构建,旨在提供更高推理性能的芯片方案,能够立足云端实现最低推理成本,并帮助大家轻松将ML集成至标准应用程序功能当中。
AWS Neuron是一款软件开发套件(SDK),由编译器、运行时以及配置文件工具共同组成,这些工具可进一步优化Inferentia芯片的ML推理性能。Neuron已经与TensorFlow、PyTorch以及MXNet等流行ML框架顺畅集成,且预装在AWS Deep Learning AMI中。在AWS Inferentia上部署深度学习模型的具体方式,与其他平台上的类似环境无甚区别,可以轻松享受到这款芯片带来的性能提升与成本优化。
在AWS Neuron GitHub上发布的最新Neuron版本,增加了对更多模型(包括OpenPose)的支持选项,我们将在后文中重点加以介绍。此外,Neuron新版本还将Neuron PyTorch升级到最新稳定版(1.5.1),允许大家在AWS Inferentia上编译并运行更多模型。
使用Neuron SDK编译TensorFlow OpenPose模型
我们可以在AWS中设置EC2实例,借此开启模型编译流程。本文建议大家使用z1d.xlarge实例类型,其拥有出色的单核性能与内存容量。在US East(北弗吉尼亚州)区域内,使用AWS Deep Learning AMI(Ubuntu 18.04)的29.0版本(ami-043f9aeaf108ebc37)。此AMI预打包有Neuron SDK以及AWS Inferentia所需的Neuron运行时。
关于在EC2实例上运行AWS Deep Learning AMI的更多详细信息,请参阅启动并配置DLAMI。
在通过SSH接入该实例时,可以激活aws_neuron_tensorflow_p36 Conda环境,并将Neuron编译器更新至最新版本。编译脚本的正常运行,依赖于requirements-compile.txt文件中列出的具体要求。关于编译脚本与需求文件,请参阅GitHub repo。我们可以使用以下代码,将其下载并安装至目标环境当中:
source activate aws_neuron_tensorflow_p36
pip install neuron-cc --upgrade --extra-index-url=https://pip.repos.neuron.amazonaws.com
git clone https://github.com/aws/aws-neuron-sdk.git /tmp/aws-neuron-sdk && cp /tmp/aws-neuron-sdk/src/examples/tensorflow//* . && rm -rf /tmp/aws-neuron-sdk/
pip install -r requirements-compile.txt
接下来,我们就可以开始编译过程。我们可以编译tf-pose-estimation网络冻结图,在GitHub repo上可以找到。下面将下载到的原始脚本调整为单行wget命令:
wget -c --tries=2 $( wget -q -O - http://www.mediafire.com/file/qlzzr20mpocnpa3/graph_opt.pb | grep -o 'http*://download[^"]*' | tail -n 1 ) -O graph_opt.pb
当下载完成后,运行convert_graph_opt.py脚本以为AWS Inferentia芯片编译。由于Neuron属于提前(AOT)编译器,大家需要在编译之前定义特定的图像大小。我们可以使用net_resolution参数(例如net_resolution=656x368)来调整网络输入图像的分辨率。
编译后的模型可以在推理运行过程中接受任意批次大小的输入。此属性可以对模型的大规模部署进行基准测试;但是,示例tf-pose-estimation repo中用于图像及视频处理管道的批次大小设置为1。
要开始编译流程,请输入以下代码:
python convert_graph_opt.py graph_opt.pb graph_opt_neuron_656x368.pb
编译流程最多可能需要20分钟。在此期间,编译器会优化TensorFlow图运算并为已保存的模型生成AWS Inferentia版本。在编译过程中,系统会保留详尽的编译日志,例如:
2020-07-15 21:44:43.008627: I bazel-out/k8-opt/bin/tensorflow/neuron/convert/segment.cc:460] There are 11 ops of 7 different types in the graph that are not compiled by neuron-cc: Const, NoOp, Placeholder, RealDiv, Sub, Cast, Transpose, (For more information see https://github.com/aws/aws-neuron-sdk/blob/master/release-notes/neuron-cc-ops/neuron-cc-ops-tensorflow.md).
INFO:tensorflow:fusing subgraph neuron_op_ed41d2deb8c54255 with neuron-cc
INFO:tensorflow:Number of operations in TensorFlow session: 474
INFO:tensorflow:Number of operations after tf.neuron optimizations: 474
INFO:tensorflow:Number of operations placed on Neuron runtime: 465
在评估编译后的模型性能前,需要切换至由AWS Inferentia芯片提供支持的EC2 Inf1实例。要在两个实例之间共享编译完成的模型,请使用以下代码创建一个S3存储桶:
aws s3 mb s3://
aws s3 cp graph_opt_neuron_656x368.pb s3:///graph_model.pb
在AWS EC2 Inf1实例上使用Jupyter notebook对推理时间进行基准测试
在将编译完成的graph_model.pb模型保存在S3存储桶后,我们需要修改GitHub repo上的ML管道脚本,借此根据图像与视频估计人体姿态。
要设置基准Inf1实例,可以重复以上在z1d实例上配置编译的步骤。大家可以使用相同的AMI,只需要注意将实例类型更改为inf1.xlarge。本文建议使用g4dn.xlarge实例,在设置基本不变的情况下,这样的类型选择将帮助我们更直接地将运行在GPU上的基础tf-pose-estimation模型性能与AWS Inferentia编译模型进行比较。
通过本文,我们将使用Jupyter Lab服务器与目标实例及模型进行交互。关于在Amazon EC2上配置Jupyter Lab的更多详细信息,请参阅如何设置Jupyter Notebook服务器。
为tf-pose设置Conda环境
在登录至Jupyter Lab服务器之后,即可克隆包含有TensorFlow版OpenPose的GitHub repo。
在Jupyter Launcher页面中的Other之下,选择Terminal。
在终端内,激活包含有Neuron SDK的aws_neuron_tensorflow_p36环境。环境激活与克隆操作需要使用以下代码:
conda activate aws_neuron_tensorflow_p36
git clone https://github.com/ildoonet/tf-pose-estimation.git
cd tf-pose-estimation
在克隆完成之后,建议按照软件包安装说明进行操作,分步完成Repo安装。在同一终端屏幕内,可以通过安装GitHub repo中requirements.txt文件所列出的opencv-python与依赖项进行环境定制。
需要运行两条Pip命令:第一条命令负责处理opencv-python;第二条负责完成requirements.txt中指定的安装步骤:
pip install opencv-python
pip install -r requirements.txt
现在,大家可以开始构建Notebook了。
在Repo的root目录中,选择Notebook,Environment(conda_aws_neuron_tensorflow_p36)以创建一个新的Jupyter notebook。在Notebook的第一个单元格中,导入run.py脚本中定义的库 —— 此脚本将作为图像处理的参考管道。在后续单元格中,创建一个记录器以记录基准测试结果。详见以下代码:
import argparse
import logging
import sys
import time
from tf_pose import common
import cv2
import numpy as np
from tf_pose.estimator import TfPoseEstimator
from tf_pose.networks import get_graph_path, model_wh
logger = logging.getLogger('TfPoseEstimatorRun')
logger.handlers.clear()
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
定义主要推理函数main()与辅助绘图函数plotter()。这些函数是直接从run.py处复制的OpenPose推理管道。我们需要对其做出简单修改,即添加repeats参数,借此依次运行多个推理步骤并改进对平均模型吞吐量的度量能力(以每张图像的处理秒数为单位):
def main(argString='--image ./images/contortion1.jpg --model cmu', repeats=10):
parser = argparse.ArgumentParser(description='tf-pose-estimation run')
parser.add_argument('--image', type=str, default='./images/apink2.jpg')
parser.add_argument('--model', type=str, default='cmu',
help='cmu / mobilenet_thin / mobilenet_v2_large / mobilenet_v2_small')
parser.add_argument('--resize', type=str, default='0x0',
help='if provided, resize images before they are processed. '
'default=0x0, Recommends : 432x368 or 656x368 or 1312x736 ')
parser.add_argument('--resize-out-ratio', type=float, default=2.0,
help='if provided, resize heatmaps before they are post-processed. default=1.0')
args = parser.parse_args(argString.split())
w, h = model_wh(args.resize)
if w == 0 or h == 0:
e = TfPoseEstimator(get_graph_path(args.model), target_size=(432, 368))
else:
e = TfPoseEstimator(get_graph_path(args.model), target_size=(w, h))
# estimate human poses from a single image !
image = common.read_imgfile(args.image, None, None)
if image is None:
logger.error('Image can not be read, path=%s' % args.image)
sys.exit(-1)
t = time.time()
for _ in range(repeats):
humans = e.inference(image, resize_to_default=(w > 0 and h > 0), upsample_size=args.resize_out_ratio)
elapsed = time.time() - t
logger.info('%d times inference on image: %s at %.4f seconds/image.' % (repeats, args.image, elapsed/repeats))
image = TfPoseEstimator.draw_humans(image, humans, imgcopy=False)
return image, e
def plotter(image):
try:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(12,12))
a = fig.add_subplot(1, 1, 1)
a.set_title('Result')
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
except Exception as e:
logger.warning('matplitlib error, %s' % e)
cv2.imshow('result', image)
cv2.waitKey()
另外,如果比较喜欢冒险,也可以根据run_video.py或run_directory.py修改用于推理视频或批量图像的代码结构。本轮演练仅供参考,大家不妨放松心态,尽情尝试!
其中main()函数会将GitHub repo中Test Inference部分描述的参数字符串作为输入。要测试Notebook的实现效果,请使用一组参考参数(请使用原始下载脚本以确保下载cmu模型):
img, e = main('--model cmu --resize 656x368 --image=./images/ski.jpg --resize-out-ratio 2.0')
plotter(img)
日志内容显示,第一个多人姿态分析任务已经完成:
‘[TfPoseEstimatorRun] [INFO] 10 times inference on image: ./images/ski.jpg at 1.5624 seconds/image.’
可以看到,当前吞吐量每秒不足一帧(FPS),代表性能表现比较差。在这种情况下,我们实际是未使用GPU资源运行TesnorFlow图–model cmu。很多朋友都清楚,这类模型在CPU上无法获得最佳运行性能。如果重复设置步骤,并选择使用搭载英伟达T4 GPU的g4dn.xlarge实例,则结果将大为不同:
‘[TfPoseEstimatorRun] [INFO] 10 times inference on image: ./images/ski.jpg at 0.1708 seconds/image’
结果显示5.85 FPS,性能得到了显著提升。
使用Neuron编译完成的CMU模型
到这里,我们已经使用了repo自带的模型工件。接下来,我们换一种方式:不再使用原始下载脚本来获取CMU模型,而是将Neuron编译完成的模型复制到./models/graph/cmu/graph_model.pb,而后再次运行测试:
aws s3 cp s3:///graph_opt.pb ./models/graph/cmu/graph_model.pb
如果之前已经对未经Neuron编译的模型进行过测试,请确保在Notebook上重新启动Python内核。重新启动内核,能够保证关闭所有TensorFlow会话,为基准测试提供完全等效的空白环境。再次运行同一Notebook,将得到以下日志条目:
‘[TfPoseEstimatorRun] [INFO] 10 times inference on image: ./images/ski.jpg at 0.1709 seconds/image.’
结果显示,与g4dn.xlarge实例相比,新环境能够在降低约30%运营成本的前提下实现相同的帧速率。这意味着将工作负载转移至基于AWS Inferentia的实例虽然无法在吞吐量层面带来直观可见的性能提升,但却确实具有成本优势。例如,当迁移至AWS Inferentia时,Amazon Alexa文本到语音转换团队得以将推理成本降低达50%。
我们决定剖析已编译的图版本,并进一步寻求对OpenPose管道进行端到端推理性能调优的空间。Neuron与TensorFlow相集成之后,可实现对本地配置文件库的访问。要剖析Neuron编译图,我们使用TensorFlow Python分析器,在estimator方法上检测TensorFlow会话所运行的命令:
from tensorflow.core.protobuf import config_pb2
from tensorflow.python.profiler import model_analyzer, option_builder
run_options = config_pb2.RunOptions(trace_level=config_pb2.RunOptions.FULL_TRACE)
run_metadata = config_pb2.RunMetadata()
peaks, heatMat_up, pafMat_up = self.persistent_sess.run(
[self.tensor_peaks, self.tensor_heatMat_up, self.tensor_pafMat_up], feed_dict={
self.tensor_image: [img], self.upsample_size: upsample_size
},
options=run_options, run_metadata=run_metadata
)
options = option_builder.ProfileOptionBuilder.time_and_memory()
model_analyzer.profile(self.persistent_sess.graph, run_metadata, op_log=None, cmd='scope', options=options)
The model_analyzer.profile method prints on StdErr the time and
其中model_analyzer.profile方法会在StdErr上输出TensorFlow图上各项操作所对应的时间与内存消耗。使用原始代码,Neuron操作与平滑操作占据整个图运行时间中的大部分比例。以下StdErr日志输出结果显示,总图运行时间为108.02毫秒,其中平滑操作耗费43.07毫秒:
node name | requested bytes | total execution time | accelerator execution time | cpu execution time
_TFProfRoot (--/16.86MB, --/108.02ms, --/0us, --/108.02ms)
…
TfPoseEstimator/conv5_2_CPM_L1/weights/neuron_op_ed41d2deb8c54255 (430.01KB/430.01KB, 58.42ms/58.42ms, 0us/0us, 58.42ms/58.42ms)
…
smoothing (0B/2.89MB, 0us/43.07ms, 0us/0us, 0us/43.07ms)
smoothing/depthwise (2.85MB/2.85MB, 43.05ms/43.05ms, 0us/0us, 43.05ms/43.05ms)
smoothing/gauss_weight (47.50KB/47.50KB, 18us/18us, 0us/0us, 18us/18us)
…
其中,OpenPose中平滑方法提供针对置信度图计算得出的高斯模糊结果。通过对此项计算进行优化,我们能够从端到端姿态估计当中榨取出更好的性能。我们将estimator.py脚本上的平滑器filter参数从25修改为5。在使用新配置之后,总运行时间降低至67.44毫秒,其中平滑计算如今只需要2.37毫秒 —— 时间节约量达37%!而在g4dn实例上,相同的优化对于运行时间几乎没有任何影响。大家还可以更改同一参数,并通过本地副本重新安装tf-pose-estimation repo的方式优化端到端管道版本。
我们使用七种不同的实例类型与大小运行了同一项基准测试,借此评估端到端图像处理管道的性能与推理成本优化效果。为了进行直观比较,我们还纳入了Amazon EC2按需实例的计费指标。
结果显示,即使是Inf1实例中最小的xlarge实例,其吞吐量也要比g4dn实例中最强劲的8xlarge实例高2倍,且处理1000张图像的成本仅为后者的十二分之一。对比最优的两个选项,inf1.xlarge和g4dn.xlarge(成本最低的GPU选项),Inf1能够将每1000张图像的处理成本较g4dn.xlarge降低72%,相当于性价比提升3.57倍。下表整理了此次基准测试的各项发现:
inf1.xlarge | inf1.2xlarge | inf1.6xlarge | g4dn.xlarge | _g4dn.2xlarge | _g4dn.4xlarge | _g4dn.8xlarge | |
---|---|---|---|---|---|---|---|
图像处理时间(秒/图像) | 0.0703 | 0.0677 | 0.0656 | 0.1708 | 0.1526 | 0.1477 | 0.1427 |
吞吐量(FPS) | 14.22 | 14.77 | 15.24 | 5.85 | 6.55 | 6.77 | 7.01 |
1000张图像处理时间(秒) | 70.3 | 67.7 | 65.6 | 170.8 | 152.6 | 147.7 | 142.7 |
按需实例价格 | $ 0.368 | $ 0.584 | $ 1.904 | $ 0.526 | $ 0.752 | $ 1.204 | $ 2.176 |
每1000张图像处理成本 | $ 2.176 | $ 0.011 | $ 0.035 | $ 0.025 | $ 0.032 | $ 0.049 | $ 0.086 |
以下图表,总结了xlarge与2xlarge实例在处理1000张图像时的吞吐量与成本情况。
通过在端到端管道中引入数据并行处理方法,我们又进一步降低了Inf1实例的图像处理成本并提升了tf-pose-estimation吞吐量。上表中列出的数值,与单一AWS Inferentia处理核心(即Neuron核心)的使用方式有关。基准实例中包含4个核心,因此仅使用其中1个显然会产生资源浪费。我们使用Python joblib库以比较粗糙的方式将main ()函数调用实现并发扩展至四个线程当中。这种模式能够将吞吐量提升至56.88FPS,并将每1000张图像的成本降低至0.002美元以下。这意味着更好的批处理策略可以让OpenPose在AWS Inferentia实例上的运行性价比得到更进一步的改善。
规模较大的CMU模型还能提供更好的姿态估计性能。例如,大家可以在包含多重景深与相应拍摄对象的场景下(参见下图),使用Neuron SDK编译模型进行多姿态检测。
安全关闭并进行资源清理
在Amazon EC2控制台上选择编译与推理实例,而后从Actions下拉菜单中选择Terminate。编译之后的模型将被保存在s3://_
总结
在本文中,我们分步完成了对OpenPose TensorFlow版开源模型的编译,更新自定义端到端图像处理管道,并体验了能够在EC2 Infi1实例之上对ML推理时间做出分析及深度优化的工具。在调优之后,Neuron编译的TensorFlow模型较现有费率最低的GPU实例实现72%的成本节约,且性能仍旧保持一致。本文中阐述的各项操作步骤,也适用于其他ML模型类型与框架。关于更多详细信息,请参阅AWS Neuron SDK GitHub repo。
感兴趣的朋友可以参阅关于AWS Inferentia芯片与Amazon EC2 Inf1实例的更多详细信息,使用Neuron SDK在AWS Inferentia上运行自己的定制化ML管道。