共计 7036 个字符,预计需要花费 18 分钟才能阅读完成。
CTR 模型在互联网的搜寻、举荐、广告等场景有着宽泛的利用。近年来,随着深度神经网络的引入,CTR 模型的推理对硬件算力的要求逐步减少。本文介绍了美团在 CTR 模型优化的实际。通过分析模型构造特点,联合 GPU 硬件架构,咱们设计了一系列流程对模型进行定制优化,达到了升高提早、进步吞吐、节省成本的指标。
1 背景
CTR(Click-Through-Rate)即点击通过率,是指网络广告的点击达到率,即该广告的理论点击次数除以广告的展示量。为 CTR 指标服务的打分模型,个别称为 CTR 模型。咱们能够将此概念进一步扩大到互联网利用中各种预估转化率的模型。CTR 模型在举荐、搜寻、广告等场景被广泛应用。绝对于 CV(计算机视觉)、NLP(天然语音解决)场景的模型,CTR 模型的历史构造比较简单,计算量较小。美团的 CTR 模型始终沿用 CPU 推理的形式。随着近几年深度神经网络的引入,CTR 模型构造逐步趋于简单,计算量也越来越大,CPU 开始不能满足模型对于算力的需要。
而 GPU 领有几千个计算外围,能够在单机内提供密集的并行计算能力,在 CV、NLP 等畛域展现了弱小的能力。通过 CUDA[1]及相干 API,英伟达建设了残缺的 GPU 生态。基于此,美团根底研发平台通过一套计划将 CTR 模型部署到 GPU 上。单从模型预测阶段看,咱们提供的基于英伟达 T4 的 GPU 深度优化计划,在雷同老本束缚下,比照 CPU,晋升了 10 倍的吞吐能力。同时,在典型的搜寻精排场景中,从端到端的维度来看,整体吞吐能力晋升了一倍以上。
除了进步吞吐、降低成本外,GPU 计划还为 CTR 模型的利用带来了额定的可能。例如,在某搜寻框主动补全的场景,因为人造的交互属性,时延要求十分刻薄,一般来说无奈应用简单的模型。而在 GPU 能力的加持下,某简单模型的均匀响应工夫从 15 毫秒升高至 6~7 毫秒,曾经达到了上线要求。
接下来,本文将与大家探讨美团机器学习平台提供的新一代 CTR 预测服务的 GPU 优化思路、成果、劣势与有余,心愿对从事相干工作的同学有所帮忙或者启发。
2 CTR 模型 GPU 推理的挑战
2.1 应用层的挑战
- CTR 模型构造多变,蕴含大量业务相干的构造,同时新的 SOTA 模型也层出不穷,硬件供应商因为人力受限,会重点优化罕用的经典构造,如 ResNet。对于没有收敛的构造,官网没有端到端的优化工具能够反对。
- CTR 模型中通常蕴含较大的 Embedding 表构造,要思考到 Embedding 表存在显寄存不下的状况。
- 在典型的举荐场景中,为了达到更快的 POI 曝光的目标,模型的时效性要求很高,在线模型服务须要提供增量更新模型的能力。
2.2 框架层的挑战
- 算子层面:目前支流的深度学习框架,如 TensorFlow 和 PyTorch,能够说是深度学习第二代框架,它们首先要解决第一代框架 Caffe 的问题,Caffe 有一个显著问题就是 Layer 的粒度过粗,导致那个时代的算法开发者都必须有“本人写自定义层”的能力。TensorFlow 和 PyTorch 都把模型表达能力放在较高的优先级,导致算子粒度比拟小,无论是对 CPU 还是 GPU 架构,都会带来很大的额定开销。
- 框架层面:TensorFlow 和 PyTorch 实质都是训练框架,对算法开发者比拟敌对,但非部署敌对。其中隐含了很多为了不便分布式训练做的设计,比方 TensorFlow 为了不便将 Variable 拆到不同的 PS 上,内置了 Partitioned_Variable 的设计。在基于 GPU 单机预测的场景下,这些构造也会带来额定的开销。
2.3 硬件层的挑战
第一,TensorFlow 的算子粒度划分较细,导致一个模型通常由几千个算子形成,这些算子在 GPU 上的执行转变为对应的 GPU kernel 的执行。kernel 是 GPU 上并行执行的函数。
GPU kernel 大体上能够划分为传输数据、kernel 启动、kernel 计算等几个阶段,其中每个 kernel 的启动须要约 10𝞵𝘀左右。大量的小算子导致每个 kernel 的执行工夫很短,kernel 启动的耗时占了大部分。相邻的 kernel 之间须要通过读写显存进行数据的传输,产生大量的访存开销。而 GPU 的访存吞吐远远低于计算吞吐,导致性能低下,GPU 利用率并不高。
第二,GPU 卡上蕴含多个计算单元,实践上,不同计算单元是能够跑不同 kernel 的,但实际上为了编程简略,CUDA 默认假如在同一时刻一个 Stream 里跑同一个 kernel。尽管能够通过多 Stream 的形式跑,然而多 Steam 之间又短少细粒度的协同机制。
在通过充沛调研与探讨后,咱们决定第一期重点关注 TensorFlow 框架下如何解决常见 CTR 模型构造在英伟达 GPU 上执行效率不高的问题,咱们先将问题收敛为以下两个子问题:
- 算子粒度过细,GPU 执行效率低下。
- 模型构造多变,手工优化投入大,通用性差。
3 优化伎俩
为了解决下面的问题,咱们对业界深度学习加速器进行了一些调研。业界比拟成熟的推理优化计划次要是 TensorRT/XLA/TVM。TensorRT 采纳手工优化,对一些定制的模型构造进行算子交融,并对计算密集型算子(如卷积)进行了高效调优。XLA 是 TensorFlow 内置的编译优化工具,次要针对访存密集型构造,通过编译伎俩,实现算子的交融。TVM[2]具备较全面的优化能力,应用编译伎俩进行算子的交融,同时能够通过机器学习的形式实现计算密集型算子的主动调优。
通过宽泛的调研和比照,咱们最终抉择了 TVM 作为优化工具。TVM 通过编译伎俩,能够较好地应答多变的模型构造,解决了手工优化通用性差的问题。但 TVM 利用在业务模型也存在一系列问题:反对的算子数较少,而且目前对动静 Shape 的反对还不够好。针对这两个问题,咱们将 TVM 和 TensorFlow 联合起来,联合 CTR 模型的构造特点与 GPU 的硬件个性,开发一系列流程,实现了对 CTR 模型的优化。
3.1 算子交融
通过将多个小算子交融为一个语义等价的大算子,能够无效缩小 GPU 上的 kernel 数量。一方面,kernel 数量缩小间接升高了 kernel 发射的开销;另一方面,交融后的大 kernel 执行的计算量减少,防止了多个 kernel 间数据传输导致的频繁访存,进步了计算的访存比。
能够看到,上图中的左右等价构造,左侧的 21 个算子执行的运算,能够在 1 个等价算子中实现。反映到 GPU 的流动上,左侧至多有 21 个 GPU kernel 以及 21 次显存的读写,而右侧只须要执行 1 个 kernel 以及 1 次显存读写。对于每个交融后的算子,须要有对应的 kernel 实现。然而,模型的算子组合是无穷的,对每种交融后算子手工实现 kernel 是不事实的。TVM 通过编译伎俩,能够主动进行算子的交融以及设施代码生成,防止了逐个手写 kernel 的累赘。
3.1.1 TF-TVM 主动切图优化
TensorFlow 模型中,如果蕴含 TVM 不反对的算子,会导致无奈执行 TVM 转换。咱们的思路是将能够用 TVM 优化的局部切出来,转为 TVM 的 engine,其余局部仍然应用 TensorFlow 的算子。在 XLA 和 TRT 转换的时候也有相似问题,咱们剖析了 TF-XLA 和 TF-TRT 二者的实现:
- TF-XLA 的实现计划,在 Grappler[4]优化图之后,有一个 POST_REWRITE_FOR_EXEC(通过这个关键字能够在源码中搜寻到)阶段,在这个阶段,会执行三个针对 Graph 的 Pass,别离是用来标记算子,封装子图,改写子图并构建 LaunchOp。
- TF-TRT 的实现计划,TF-TRT 在 Grappler 中注册了一个优化器,在这个优化器中,找到连通子图,并将其替换为 TRT Engine。
在最终计划实现上,咱们参考了 TF-TRT 的设计。这个设计比照 XLA 的劣势在于 XLA 切图计划与 TensorFlow 源码紧耦合,间接将 XLA 的三个 Pass 嵌入到了启动 Session 的主流程中。而切图策略,优化策略后续会有十分频繁的迭代,咱们不心愿与 TensorFlow 的源码太过耦合。咱们扩大了 TF-TVM 的计划,在理论应用中咱们把这个切图过程为一个独立流程。在模型部署或更新时,主动触发。
在推理阶段,优化过的子图应用 TVM 执行,其余的计算图应用 TensorFlow 原生实现执行,将两者联合共同完成模型的推理。因为 TVM 和 TensorFlow 的 Runtime 各自应用独立的内存治理,数据在不同框架间传输会导致额定的性能开销。为了升高这部分开销,咱们买通了两个框架的底层数据结构,尽可能防止额定的数据拷贝。
3.1.2 计算图等价替换
TensorFlow 模型中过多的不被 TVM 反对的算子会导致 TF-TVM 切图系统,影响最终的优化成果。为了让 TF-TVM 切图尽量大且残缺,以及让 TVM 优化过程中的交融力度更大,咱们对模型中的一些简单构造进行检测,替换为执行更高效或更易于交融的等价构造。
例如,TensorFlow 原生 EmbeddingLookup 构造,为了反对分布式训练,会对 Embedding 表进行切分,产生 DynamicPartition 和 ParallelDynamicStitch 等动静算子。这些动静算子不被 TVM 反对,导致 TF-TVM 图切分过于细碎。为了让 TF-TVM 切图更残缺,咱们通过图替换,对这种构造进行批改,通过将 Embedding 分表提前合并,失去简化的 EmbeddingLookup 构造。
3.2 CPU-GPU 数据传输优化
TVM 优化后的子图被替换为一个节点,该节点在 GPU 上执行,通常有几十甚至几百个输出,该节点的前置输出(如 Placeholder)通常是在 CPU 上执行,会波及屡次的 CPU-GPU 传输。频繁的小数据量传输,无奈充分利用带宽。为了解决这个问题,咱们对模型构造进行批改,在计算图中增加合并与拆分节点,管制切图的地位,缩小数据传输的次数。
一种可能的合并形式是,对这些输出按雷同的 Shape 和 Dtype 进行合并,后续进行拆分,将拆分节点切入 TVM 的子图一起优化。这种形式会导致一些问题,如局部子图的算子交融成果不佳;另一方面,GPU kernel 函数的参数传递内存限度在 4KB,对于 TVM 节点输出十分多的状况(如超过 512 个),会遇到生成代码不非法的状况。
3.3 高频子图手工优化
对于 TVM 无奈反对的子图,咱们对业务中高频应用的构造进行形象,采纳手写自定义算子的形式,进行了高效 GPU 实现。
例如,模型中有局部时序特色应用 String 类型输出,将输出的字符串转为补齐的数字 Tensor,将 int 类型的 Tensor 作为下标进行 Embedding 操作。这部分子图的语义如图,以下简称 SE 构造(StringEmbedding):
这一部分构造,TensorFlow 的原生实现只有基于 CPU 的版本,在数据量较大且并行度较高的情景下,性能降落重大,成为整个模型的瓶颈。为了优化这部分构造的性能,咱们在 GPU 上实现了高效的等价操作。
如图所示,PadString 算子在 CPU 端将多个字符串按最大长度进行补齐,拼接成一个内存间断的 uint8 类型 Tensor,以便一次性传输到 GPU。StringEmbedding 接管到补齐后的字符串后,利用 GPU 并行计算的个性,协同大量线程实现字符串的切分与查表操作。在波及规约求和、求前缀和等要害过程中,应用了 GPU 上的 Reduce/Scan 算法,编码过程应用 warp_shuffle
指令,不同线程通过寄存器替换数据,防止了频繁访存的开销,取得了很好的性能。
GPU Scan 算法示意,一个 8 个元素的前缀和操作,只须要 3 个迭代周期。在一个有几十路相似操作的模型中,手工优化前后的 GPU timeline 对比方下图,能够看到 H2D + StringEmbedding 这部分构造的耗时有很大的缩减,从 42 毫秒缩减到 1.83 毫秒。
除了 StringEmbedding 构造,咱们对 StringSplit + ToNumber + SparseSegmentSqrt、多路并行 StringEmbedding 等构造都进行了高效交融实现,在优化流程中通过构造匹配进行相应的替换。
3.4 CPU-GPU 分流
理论线上的 RPC 申请,每个申请内的样本数(下文称 Batch)是在 [1,MaxValue] 范畴内变动的,MaxValue 受上游业务零碎,其余根底零碎能力等多方面因素制约,绝对固定。如上图所示,以某个搜寻服务为例,咱们统计了线上的 Batch 数值散布,Batch=MaxValue 的申请占比约 45%,Batch=45 占比 7.4%,Batch= 1 占比 2.3%。其余的 Batch 占比从 0.5% 到 1% 不等。对于 GPU 来说,进步单个申请的 Batch 能更好地利用硬件资源,施展 GPU 的并行计算能力,体现出绝对 CPU 更优的提早和吞吐;当 Batch 较小时,GPU 绝对 CPU 的劣势就不显著了(下图是咱们测试同样的模型在固定压力下,CPU/GPU 上提早的变动)。
大部分申请都由 GPU 在做了,CPU 资源有较多空余,咱们将一些小 Batch 的碎申请放在 CPU 运行,这样能够让整个 Worker 的资源利用更加平衡,进步零碎整体的性能。咱们依据测试设定了一个 Batch 阈值,以及计算图在异构硬件上区别执行的判断逻辑:对于小 Batch 的状况,间接在 CPU 上执行计算图,只有 Batch 超过阈值的申请才会在 GPU 上推理。从线上的统计数据来看,整体流量的 77% 跑在 GPU 上,23% 跑在 CPU 上。
在 GPU 的一系列优化策略和动作中,Batch 大小是很重要的信息,不同 Batch 下优化出的 kernel 实现可能是不同的,以达到对应 workload 下最优的计算性能;因为线上的流量特点,发送到 GPU 的申请 Batch 散布比拟细碎,如果咱们针对每个 Batch 都优化一个模型的 kernel 实现显然是不够经济和通用的。因而,咱们设计了一个 Batch 分桶策略,生成 N 个固定 Batch 的优化模型,在理论申请到来时找到 Batch 间隔最近的一个 Bucket,将申请向上 Padding 到对应的 Batch 计算,从而进步了 GPU 的利用效率。
4 压测性能剖析
咱们选取一个模型进行线上性能压测剖析。
- CPU 模型测试环境为 16 核 Intel(R) Xeon(R) Gold 5218 CPU @ 2.30GHz,16G 内存。
- GPU 模型测试环境为 8 核 Intel(R) Xeon(R) Gold 5218 CPU @ 2.30GHz,Tesla T4 GPU,16G 内存。
下图比照了在不同的 QPS 下(x 轴),GPU 模型在各 BatchSize 下的推理时延(y 轴)。GPU 模型在 BatchSize=128 以下,推理耗时差别不显著,较大的 BatchSize 更有利于吞吐;比照 BatchSize=256 的 GPU 模型与 BatchSize 为 25 的 CPU 模型,在 QPS 低于 64 的状况下,二者推理耗时根本持平;QPS 超过 64 的状况下,GPU 的推理时延低于 CPU。GPU 的吞吐相比 CPU 晋升了 10 倍。
同时,咱们能够看到不同曲线的平缓水平,CPU 在 QPS 高出 64 后,时延会迅速回升,GPU 则仍然放弃安稳,直到 QPS 超过 128 才会有显著回升,但仍旧比 CPU 更安稳。
5 整体架构
针对 CTR 模型的构造特点,咱们形象出了一套平台化的通用优化流程。通过对模型构造的剖析,主动利用适合的优化策略,通过性能评估和一致性校验,保障模型的优化成果。
6 不足之处与将来布局
在易用性层面,目前的计划模式是提供了一套在线优化脚本,用户提交模型后,主动优化部署。因为波及对计算图构造的剖析、编辑以及 TVM 的编译等过程,目前的模型优化耗时较长,大部分模型优化耗时在 20 分钟左右。后续须要思考减速 TVM 编译的效率。
在通用性层面,从咱们的理论利用状况来看,TVM 编译优化和高性能手写算子是最次要的收益起源。手工优化很考验开发同学对业务模型的了解和 GPU 编程的能力。编写一个高性能的交融算子曾经不太容易,要做到有肯定的迁徙能力和扩展性则更有难度。
总的来说,CTR 模型推理在 GPU 上将来须要思考的问题还有很多。除了要基于业务了解提供更好的性能外,还要思考模型规模微小后无奈残缺放入显存的问题以及反对在线模型更新的问题。
作者简介
伟龙、小卓、文魁、駃飞、小新等,均来自美团根底研发平台 - 机器学习预测引擎组。
参考资料
[1] CUDA C++ Programming Guide
[2] TVM Documentation
[3] Accelerating Inference In TF-TRT User Guide
[4] TensorFlow graph optimization with Grappler
招聘信息
美团机器学习平台大量岗位继续招聘中,实习、社招均可,坐标北京 / 上海,欢送感兴趣的同学退出咱们,构建多畛域公司级机器学习平台,帮大家吃得更好,生存更好。简历可投递至:wangxin66@meituan.com。
浏览美团技术团队更多技术文章合集
前端 | 算法 | 后端 | 数据 | 平安 | 运维 | iOS | Android | 测试
| 在公众号菜单栏对话框回复【2020 年货】、【2019 年货】、【2018 年货】、【2017 年货】等关键词,可查看美团技术团队历年技术文章合集。
| 本文系美团技术团队出品,著作权归属美团。欢送出于分享和交换等非商业目标转载或应用本文内容,敬请注明“内容转载自美团技术团队”。本文未经许可,不得进行商业性转载或者应用。任何商用行为,请发送邮件至 tech@meituan.com 申请受权。