TensorRT 加速性能分析
Out-of-the-box GPU Performance
模型推理性能是什么意思?在为用户评估潜在的候选项时,不测量数据库查询和预筛选(例如决策树或手动逻辑)的贡献。使用估计器对特征列进行预处理,并通过网络复制输入/结果。
有两个主要推理上下文:
离线推理-一次预先计算多个用户的概率
在线推理-为特定用户实时推荐
因此,可能有兴趣优化三个指标:
吞吐量,例如用户/秒(离线)
单次推理延迟(在线)
满足设置的延迟约束时的吞吐量
在使用TensorFlow的stock实现时的初步观察是,如上所述,请求特征转换中不仅存在冗余,而且这些转换是通过在图中插入几十个操作来实现的,用于诸如维度扩展和范围值检查之类的小任务。虽然这些操作本身在计算上很便宜,但带来的开销最终会导致性能瓶颈,特别是在gpu上。此外,对于简单且基本相同的任务(如矩阵查找),每个功能都有自己的数十到数百个操作链。
通过用一个简洁的配置描述预期的逻辑来消除这个开销,允许以一种融合的、非冗余的、并行的方式实现操作,同时控制执行精度。
除了节省冗余计算和网络I/O之外,将在后面看到,与基于本机CPU的实现相比,此实现利用NVIDIA GPU的卓越并行计算能力提供了大量的推理时间加速。
这种加速解放了数据科学家的手脚,特别是因为使在GPU上扩展模型变得容易,同时仍然满足延迟要求。此外,在部署中,可以看到来自请求级并行的额外吞吐量。
How the Inference API Works
Using the API
虽然将把API的详细概述留给笔记本电脑,但希望提请注意导出过程和传统的TensorFlow估计器导出过程之间的关键相似性和差异,可以在这里找到概述。
导出深度学习模型通常需要指定三个组件:构成模型的操作图、用于执行这些操作的权重以及提供这些操作的输入的大小和类型。在典型的TensorFlow工作流中,这些组件由描述输入和转换的特征列、管理权重的估计器及其描述神经网络图的模型函数(为DNNLinearCombinedClassifier等屏蔽估计器预定义)来定义。导出笔记本中的以下几行值得在此处重新解释,以强调此过程的工作原理:
wide_columns, deep_columns = ...
estimator = tf.estimator.DNNLinearCombinedClassifier(
linear_feature_columns=wide_columns,
dnn_feature_columns=deep_columns,
...)
# infer input shapes and types from feature_columns as a parse_example_spec
parse_example_spec = \
tf.feature_column.make_parse_example_spec(deep_columns + wide_columns)
# expose serialized Example protobuf string input and parse to feature tensors
# with a serving_input_receiver_fn
cpu_serving_input_receiver_fn = \
tf.estimator.export.build_parsing_serving_input_receiver_fn(parse_example_spec)
# export in saved_model format
estimator.export_saved_model('/tmp', cpu_input_serving_receiver_fn)
Listing 1: Pseudo-code for standard export of a TensorFlow Estimator
API以几乎相同的方式使用所有相同的信息,但有几个注意事项。首先,服务输入接收器不是从序列化的protobuf字符串映射到特征张量,而是从特征张量映射到输入到DNN的稠密向量。不幸的是,引擎盖下dnnlinearcombined分类器使用的模型函数不能接受这些向量,因此建立了一个几乎完全相同的副本,可以(这是一个单行变化)并将其替换掉。
第二个需要注意的是,从优化的角度来看,有一些特性的有用信息并没有包含在特性列中。特别是,功能列不包含有关功能所属的类(请求或项)的信息,已经讨论过这些信息的优点。也不会告诉分类功能是一个热门(一次只能接受一个类别,比如一个唯一的ID)还是多个热门(一次可以接受多个类别,比如将餐厅描述为“休闲”和“意大利人”)。一个热分类只是多个热分类的一个特例,但是预先知道这些信息可以让有效地在gpu上实现嵌入查找并优化内存分配。
提供了通过从功能名称到指示请求或项(0或1)或一个热的或多个热的(1或-1)的标志的映射来指定此信息的选项。这些地图可以作为函数或词典提供,如下所示:
# either functions
import re
AD_KEYWORDS = ['ad', 'advertiser', 'campain']
AD_REs = ['(^|_){}_'.format(kw) for kw in AD_KEYWORDS]
AD_RE = re.compile('|'.join(AD_REs))
level_map = lambda name: 0 if re.search(AD_RE, name) is None else 1
# or explicit dicts
from features import \
REQUEST_SINGLE_HOT_COLUMNS, ITEM_SINGLE_HOT_COLUMNS, \
REQUEST_MULTI_HOT_COLUMNS, ITEM_MULTI_HOT_COLUMNS
num_hot_map = {name: 1 for name in REQUEST_SINGLE_HOT_COLUMNS+ITEM_SINGLE_HOT_COLUMNS}
num_hot_map.update({name: -1 for name in REQUEST_MULTI_HOT_COLUMNS + ITEM_MULTI_HOT_COLUMNS})
Listing 2: Mappings for specifying feature class and categorical properties
如果不确定功能是如何归入这些阵营的,那么对于每个映射,总是有一个更一般的情况,所以请将其留空,将假设所有功能都是项级别和多热点(对于分类)。 一旦指定了这个额外的信息,导出过程看起来非常相似:
from model import build_model_fn
from recommender_exporter import RecommenderExporter
wide_columns, deep_columns = ...
estimator = tf.estimator.DNNLinearCombinedClassifier(
linear_feature_columns=wide_columns,
dnn_feature_columns=deep_columns,
...)
# couple extra function calls
model_fn = build_model_fn(...)
exporter = RecommenderExporter(
estimator, # for the weights
deep_columns, # for the inputs and transformations
wide_columns,
model_fn, # for the graph
level_map, # for request vs. item
num_hot_map, # for one vs. multi -hot
wide_combiner=...)
# then the same
gpu_input_serving_receiver_fn = exporter.get_input_serving_receiver_fn()
estimator.export_saved_model('/tmp', gpu_input_serving_receiver_fn)
Listing 3: Pseudo-code for GPU-accelerated model export
这将导出一个TensorFlow保存的模型,并将加速查找操作插入到图中。虽然可以为这个保存的模型提供服务并进行调用,但是由于图所期望的矢量化输入,API并没有那么整洁,而且仍然会招致TensorFlow的开销。为了进一步加速作为TensorRT可执行引擎的图形,只需要将保存的模型冻结到TensorFlow graph def中,TensorFlow为其内置了实用程序,然后利用提供的转换脚本,该脚本使用在graph def中编码的信息来编译TensorRT引擎。有关如何通过简单的Flask应用程序实现这一点的示例,请参见笔记本。端到端,导出管道如下所示:
Figure 3: TensorFlow model conversion for inference.
有两种方法可以部署转换后的模型。首先,如果推理可以在本地发生(与请求来自的服务器相同),可以跳过TensorRT推理服务器,并将自定义类与CUDA和TensorRT部分一起使用。其次,为了远程推理或易于使用,TensorRT推理服务器使用自定义后端来进行自定义CUDA代码和TensorRT引擎推理。
Performance Breakdown
代码支持本地和远程推理。虽然直接的本地推断可以产生最佳的延迟和CPU利用率,但这需要细致的调整。由于远程推理将查询生成和推理计算分离开来,因此更加灵活。NVIDIA的TensorRT推理服务器是一个远程推理解决方案,允许轻松优化,以充分利用可用的计算能力。这是在多个级别上完成的,包括调度传入查询、通过在GPU上托管模型的多个副本来进行管道优化、有效利用所有可用GPU的节点级优化等等。可以无缝地过渡到远程推理,这有助于使用公共节点配置。perf_client test实用程序有助于改变客户端并发性,以便向服务器实例发出多个并发查询,并测量吞吐量和延迟。在这里,提出了吞吐量优化和延迟优化的数据为模型所获得的测试与上述特征。
Figure 4: Data flow for Wide & Deep model inference. Numbers describe computation order
在远程推理场景中,生成的查询需要与远程服务器通信。当传输的数据量小而计算需求大时,这通常不是瓶颈。然而,广度和深度模型可以将许多特性消耗到很少的MLP层中。结果,传输的数据量较大,所需的计算量较小,从而导致网络带宽对观察到的端到端性能产生影响。当计算在gpu上大大加速时,这种情况被夸大了。对于在这里测试的模型,客户机和服务器之间的1Gbps网络在每个请求的较大项目上都会造成性能瓶颈,迁移到10Gbps可以有效地消除这个瓶颈。
下图显示了查询的p99延迟,这是CPU上运行的模型与NVIDIA T4 GPU上运行的模型的每个请求项的函数。GPU运行使用客户机-服务器模型,其中客户机和服务器位于通过1Gbps或10Gbps网络连接的不同节点上。CPU在本地运行,没有任何网络瓶颈。这里选择的CPU是一个24核48线程的Xeon Platinum 8275CL(在c5d.24xlarge AWS实例上可用)。每个查询在CPU的所有核心上被拆分和并行化。使用官方的TensorFlow容器或Intel的TensorFlow容器来测量CPU的性能,基于此,CPU的速度更快。对于GPU运行,每个查询都在单个NVIDIA T4 GPU上进行非公开处理,TensorRT推断服务器参数针对延迟进行了优化。该图显示,当许多项与单个请求配对时,GPU延迟比CPU延迟低一个数量级以上。在每个请求4096个项目时,CPU延迟约为55ms,而GPU延迟在1Gbps网络上约为8ms,在10Gbps网络上约为4ms。即使在每个请求的项目数较低的情况下,GPU的延迟也大大降低,大约为1.2ms,而CPU的延迟大约为15ms,每个请求64个项目。
Figure 5: Comparison of latency between GPU and CPU in a latency optimized configuration for online inference.
如果延迟约束不重要,比如在脱机处理中,那么可以选择TensorRT推理服务器参数来优化吞吐量。下图显示了吞吐量作为模型吞吐量优化配置的每个请求项的函数。与延迟优化的情况一样,客户机和服务器之间的网络带宽也会影响性能。在每个请求的最低项目数下,没有足够的工作使GPU饱和。随着每个请求的项目数增加到256,看到了更好的吞吐量,因为GPU更高效。当进一步增加每个请求的项时,所需的工作量线性增加,所获得的吞吐量相应下降。
Figure 6: Comparison of throughput between GPU and CPU in a throughput optimized configuration. For the 10 Gbps GPU case, the p99 latency for all items per request is always lower than 7ms.
为了演示延迟约束下的吞吐量优化,遍历了客户机和服务器并发性(即,客户机并发等待的请求数,以及服务器允许并行运行的模型数),并选择做出有用折衷的配置。下图显示了每个请求的不同项的吞吐量延迟折衷曲线。当增加客户机和服务器的并发性时,吞吐量会增加,直到GPU的工作饱和为止。在此之后,延迟显著增加,而吞吐量只增加了一点点。
Figure 7: Best latency-throughput tradeoffs at 10 Gbps.
Why does it run so fast?
GPU Preprocessing for TensorFlow feature_columns
and Fused Embedding Lookups
最后但并非最不重要的是,嵌入到多层感知器(MLP)中,该感知器是模型深部的一部分。是广度和深度模型中计算最密集的部分。对于批处理计算,MLP本质上是一个矩阵乘法序列,其中包含激活和其他点操作,如之间的批处理规范化。矩阵乘法需要更多的数学运算(立体缩放)而不是内存运算(二次缩放)。由于GPU被设计为提供更大的计算吞吐量,当在GPU上以高度并行的方式运行时,矩阵乘法通常会看到巨大的性能提升。此外,NVIDIA gpu的Volta和Turing结构具有张量核,在混合或降低精度的情况下进一步加速矩阵乘法。NVIDIA gpu具有使MLP计算非常快速的架构特性。为了利用这些架构特性并获得最高性能,软件堆栈扮演着关键的角色。使用NVIDIA TensorRT,一个在NVIDIA gpu上进行高性能深度学习推理的平台。TensorRT执行图形级优化和特定于体系结构的优化,以生成以高性能方式执行图形的TensorRT引擎。TensorRT的优化基于矩阵的大小和所使用的GPU架构为矩阵乘法选择正确的GPU内核。TensorRT还可以使用混合和降低的精度来利用Tensor核。内存带宽是许多应用程序中日益增长的瓶颈源,因为在大多数体系结构中,计算吞吐量通常比内存带宽大得多。TensorRT拥有强大的优化技术来执行内核融合,从而提高了计算激活函数和点态内核的内存局部性,并进一步加快了流水线的速度。
Accelerated Matrix Multiplies for the MLP
最后但并非最不重要的是,嵌入到多层感知器(MLP)中,该感知器是模型深部的一部分。是广度和深度模型中计算最密集的部分。对于批处理计算,MLP本质上是一个矩阵乘法序列,其中包含激活和其他点操作,如之间的批处理规范化。矩阵乘法需要更多的数学运算(立体缩放)而不是内存运算(二次缩放)。由于GPU被设计为提供更大的计算吞吐量,当在GPU上以高度并行的方式运行时,矩阵乘法通常会看到巨大的性能提升。此外,NVIDIA gpu的Volta和Turing结构具有张量核,在混合或降低精度的情况下进一步加速矩阵乘法。 NVIDIA gpu具有使MLP计算非常快速的架构特性。为了利用这些架构特性并获得最高性能,软件堆栈扮演着关键的角色。使用NVIDIA TensorRT,一个在NVIDIA gpu上进行高性能深度学习推理的平台。TensorRT执行图形级优化和特定于体系结构的优化,以生成以高性能方式执行图形的TensorRT引擎。TensorRT的优化基于矩阵的大小和所使用的GPU架构为矩阵乘法选择正确的GPU内核。TensorRT还可以使用混合和降低的精度来利用Tensor核。内存带宽是许多应用程序中日益增长的瓶颈源,因为在大多数体系结构中,计算吞吐量通常比内存带宽大得多。TensorRT拥有强大的优化技术来执行内核融合,从而提高了计算激活函数和点态内核的内存局部性,并进一步加快了流水线的速度。
API Flexibility and Constraints
除了在实际模型计算时间上的巨大改进之外,API中还有一些设计元素值得强调。首先,与其他尝试加速广度和深度模型(将所有类别嵌入大小对齐以将整个深度嵌入步骤视为单个多个热查找)不同,不假设嵌入的相对大小。这使得数据科学家可以在嵌入大小的空间上自由地进行超参数搜索,而不必担心推理时间性能。不需要预先转换和存储特征,因为转换方法可能会随着模型节奏的变化而变化。这在保持原始模型表示的同时节省了存储成本。在CPU上运行会造成瓶颈。
虽然仍有许多要添加支持的功能列类型,但尽量少对数据进行假设。虽然应该考虑工程化特征将如何影响推断性能,但这种考虑决不应妨碍数据科学家开发有用的特征。这个规则的一个显著的例外是对字符串数据缺乏支持,选择排除字符串数据的前提是,对于大多数用例,字符串数据可以在存储之前转换为整数索引,而不会显著丧失通用性或抽象性。这不仅降低了预处理过程中的存储成本和CPU负载,而且减少了专用推理服务器设置中的网络流量。虽然数据集不包含字符串,但如果关于“整型”的假设被误导,欢迎这种反馈。
在某些情况下,确实要求用户提供有关其数据的假设。这些参数包括编译TensorRT引擎所需的avg_nnz_per_特性和items_per_请求。前者在多个热分类特征中的索引数量在一个实例到下一个实例之间可以按数量级变化,或者在不能预先知道预期的最大索引数量的情况下可能会出现问题。对于候选项来自上游过滤器的情况,后者的约束也面临类似的问题,上游过滤器的候选项数量无法预先知道(尽管在这种情况下有可行的解决方法)需要强调的最后一个API特性是,通过为多个hot特性引入nnz值的规范,使得TensorFlow在其parse_示例op中使用的SparseTensor机器变得不必要了,这在很大程度上消除了tf.示例protobufs作为网络的输入表示,节省时间和计算的重复,建立,序列化,然后立即反序列化数据。
结合TensorRT推理服务器自定义后端插件,这意味着用户可以直接从原始数据表示映射到推荐分数,开销可以忽略不计。这样的数据表示使抽象更加健壮,同时提供了更高的性能。
Looking Forward
对TensorFlow特征列类型的支持仅扩展到Outbrain数据集所需的类型。重申一下,这些是shape=(1,)的数值列,用于标量值数值特征;categorical_column_with_identity,categorical_column_with_hash_bucket,以及要将包装在其中的嵌入列和指示符列。首要任务是扩展对其余特性列的支持,列表的顶部是
· Crossed columns
· Vector-valued numeric columns (len(shape) == 1 and shape[0] > 1)
· categorical_column_with_vocabulary_list (for integer vocabularies)
· bucketized_column
· weighted_categorical_column
在一个以其问题的多样性为标志的领域中,确信需要更多的功能来充分解决用户的用例。将Github的问题提交到这个回购协议中,将是一个很好的方式来建议新的功能,或者帮助重新确定上面给出的路线图的优先级。
英伟达也在致力于加速推荐管道的其他部分。数据接收和预处理(ETL)是推荐系统管道的一个主要组件,RAPIDS可以用来加速GPU上的这些组件。如果还没有,看看在加速推荐系统培训方面的工作,使用Rapids和Pythorch进行RecSys 2019挑战赛。另一项在多个GPU上训练具有大型嵌入表的推荐系统的并行工作也在积极开发中,可以在这里查看:https://github.com/NVIDIA/hugetr
Conclusion
演示了如何为使用gpu的推荐者获得至少10倍的延迟和大约13倍的吞吐量。如此惊人的速度将有助于降低为推荐系统提供实时推理服务的部署成本。通过在早期访问页面注册了解更多关于工作的信息,并尝试交互式的ipython笔记本演示来转换和部署TensorFlow宽和深模型以进行推理。请使用下面的评论告诉计划如何采用和扩展此项目。