作为图片届的“Twitter”,Pinterest首页展现给用户的图片也离不开背地的举荐模型。近期,其工程团队通过将机器学习服务从CPU转移到GPU上,使得Pinterest能够上线比之前大100倍的举荐模型。上线大模型给模型品质带来了阶跃式的晋升,最终将Pinterest首页feed流的用户活跃度进步了16%。

在本文中,他们分享了如何只通过渺小的投入和提早的老本实现这一优化指标,这包含优化特定算子,整合内存传输,通过CUDA Graph技术在设施上执行动态图,以及分布式系统的从新设置。他们的实践证明,要想进步模型成果,模型参数就得变大,而应用GPU服务计划的经济效益远超CPU服务计划。

起源|Pinterest Engineering
翻译|郑泽康

Pinterest的使命是给每个人带来发明他们所热爱生活的灵感。为了实现这一使命,咱们所有产品中都蕴含的一个要害组件就是各式各样的举荐模型,它们负责在适合的工夫给适合的人展现适合的内容。咱们的举荐模型是通过高级算法进行训练的一系列机器学习模型,用于了解用户在Pinterest上破费工夫时的行为,这些举荐模型是通过定制的机器学习模型服务器(Scorpion Model Server, 即SMS)来运行的。

SMS 上须要面对十分艰巨的技术挑战,基于3000多亿个Pin的语料库,它必须在毫秒内为4亿多个用户提供相干举荐。之前,SMS在CPU上进行模型推理,其内核通过了多年的优化以满足咱们对提早和基础设施老本的严格要求,但即便最新一代的CPU也简直达到了SMS服务的极限。咱们必须十分审慎地确保,每次因模型变动而带来的提早和基础设施老本的减少都是荒诞不经的。

机器学习畛域里模型参数和计算量减少的趋势让问题变得更加严厉。在举荐零碎中,具备1000亿参数量的模型曾经很常见,并在业内常被提及。

在Pinterest,咱们采纳了略微不同的形式,通过应用诸如Transformer的古代模型架构来扩充模型。在更大模型下,咱们立刻观测到模型准确率产生的量变——其大幅晋升了Pinner(译注:Pinterest用户)的参与度。然而,在CPU服务器上运行这些古代模型架构简直让老本和提早都晋升了40倍,这一代价难以承受。因而,咱们转而寻求应用GPU来减速模型推理,从而能够应用正当的老本来运行这些模型。

1

优化

当咱们尝试那些开箱即用的GPU服务时,很快意识到在经济高效地利用GPU运行举荐模型服务之前须要对其优化。咱们首先应用剖析工具来剖析在模型推理过程中产生了什么,在仔细观察剖析后果时,咱们留神到工夫线上有大量的小CUDA Kernel在执行。

这是举荐零碎模型的预期行为,其中数百个特色在模型前期阶段进行特色拼接之前须要独自解决。然而,对于大量的小算子,启动CUDA Kernel带来的开销比计算开销还要低廉。与训练时的batchsize相比,模型服务时的batchsize绝对更小,进而加剧了这一问题。

上图别离是模型优化前和优化后的剖析后果。CUDA Kernel工夫线(用红框突出显示)表明,Kernel的启动开销(蓝色块之间的间隙)显著缩小,因而GPU失去了更好得利用,它将大部分工夫破费在Kernel执行中。

2

缩小小op的数量

咱们采取的第一个办法是找出缩小小op数量的机会。咱们寻找常被应用的模型组件,并尽可能对其优化。其中一个例子是Embedding的查表模块,它蕴含两个计算步骤:原始ID到索引的查找,索引到Embedding的查找。因为咱们有大量特色,这两步操作会被反复数百次。通过应用cuCollections (https://github.com/NVIDIA/cuC...) 以反对GPU上原始ID的哈希表,并实现了自定义的Embedding查找模块以合并多个查找操作,咱们显著地缩小了op的数量。在执行一些优化后,马上看到了更优的性能体现。

3

合并内存拷贝

同样,当咱们在主机和GPU显存之间搬运Tensor时,也存在整合数据传输的机会。通用举荐模型里的一个候选样例通常会应用数百个特色作为输出,对于每一次推理,各个特色作为一个独自的tensor被拷贝到GPU显存中。尽管在主机和GPU显存之间搬运数据十分快,然而为每个申请调度数百个cudaMemcpy()函数调用的开销很快成为瓶颈。


从主机独自拷贝Tensor到 GPU vs 一次拷贝整个内存缓冲区

为了解决这个问题,咱们利用了一个简略的优化将cudaMemcpy()调用次数从几百次缩小到一次:不再应用Torch框架将Tensor独自挪动到GPU上,而是先将所有Tensor的数据搁置到一块事后调配好的间断内存缓冲区中,并一次性将缓冲区内容拷贝到GPU里,最终通过指向GPU显存缓冲区的不同偏移量来重构失去GPU上的所有张量。

该优化带来的代价是要显式治理事后调配的内存缓冲区的生命周期,以及须要对不同数据类型手动解决GPU显存对齐。但作为后果,P50数据拷贝提早从10ms 升高到1ms以下,这证实了该优化带来的复杂性是能够承受的。

4

利用CUDA Graph

为了进一步优化模型推理过程,咱们应用CUDA Graph(https://pytorch.org/blog/acce...) 来齐全打消残余小op的开销。CUDA Graph容许咱们将模型推理过程捕获为动态图,而不是独自调度。它能够让整个计算作为一个单元进行执行,而不产生任何Kernel启动开销。咱们反对将CUDA Graph作为模型服务的一个新后端。一开始加载模型时,模型服务执行一次模型推理以构建图实例,该实例能够针对实时流量反复执行。


CUDA Graph在一个批处理内(下图)执行Kernel,而不是在一个序列中一一执行(上图),这缩小了Kernel之间CPU启动带来的开销。图表来自:https://pytorch.org/blog/acce...

CUDA Graph本身的一些限度给咱们的模型服务带来了额定的复杂性。其中最大的限度是CUDA Graph要求所有Tensor都具备动态形态和布局,这对动静大小批次和不规则的变长Tensor带来了挑战。然而,咱们置信为了取得更好性能进行的衡量是值得的,并且咱们能够将输出Tensor补齐到精心筛选的动态形态。

5

应用更大的batchsize

最初,咱们从新钻研了SMS为模型推理执行的批处理策略。SMS反对动静批处理,能够让多个申请合并成更大的批次。它通常能带来更好的吞吐量,但代价是须要较短的工夫以期待申请序列收集足够多的项(item)。对于CPU上的ML推断,咱们通常想要将申请分成小批量来进步并行度以减小延时。然而对于GPU,其延时对batchsize并不敏感,应用更大的batchsize对GPU晋升工作负载效率更重要。

这种batchsize的需要使咱们从新思考了SMS里的分布式系统设置。对于CPU上的ML推断,咱们应用scatter-gather构造将原始申请拆分,并在多个叶子结点上并行执行,以取得更小的延时。

此外,该架构容许咱们为每个叶子结点调配一个固定的数据切片,以优化特征提取期间的缓存命中率。然而,因为GPU偏向应用大batchsize,因而删除root layer间接在原始申请中应用大batchsize更有意义。最终咱们应用了CacheLib的混合缓存,它用DRAM和SSD来弥补与scatter-gather 架构设置相比的缓存容量损失。

6

后果

咱们首先测量了模型单次推理的延时。咱们应用c5.18x AWS实例提供CPU服务,g5.4 AWS实例提供GPU服务。

CPU延时随着batchsize线性增长,在较小的batchsize下,GPU延时简直雷同,此时Kernel启动开销占延时主导因素。然而随着batchsize减少,理论计算工夫主导了延时,GPU延时以亚线性模式增长。在实践中,GPU效率晋升的亮点在于SMS能够应用更大的batch工作。联合了所有优化之后,咱们取得了惊人的后果,相比 CPU 服务,GPU服务在较大batchsize下的每批次的提早晋升了100倍以上。

咱们的服务指标也展现了令人印象粗浅的后果。通过优化模型操作,从新设置分布式系统以及优化数据传输并应用CUDA Graph,咱们以30%的更低延时上线了77倍大的模型,并以适当成本增加了20%的吞吐量。

最初,两个数量级效率晋升开启了Pinterest最先进的举荐模型架构。能够看到,模型品质显著地晋升间接转化为更高的用户参与度。在过来的一年,咱们以适当的基础设施老本将一个次要产品的用户参与度晋升了16%。很快,咱们将推出比咱们的CPU模型大100倍的最大模型。

7

论断

将基于CPU模型服务转换成基于GPU服务的过程很简单,但这是咱们在Pinterest应用最先进举荐模型的一个必要步骤。咱们可能以适当的老本提供大100倍的举荐模型,这为咱们的机器学习工程师给Pinner解锁更相干和响应更迅速的举荐内容提供了根底。

(*本文经受权后编译公布,原文:
https://medium.com/pinterest-...*)

欢送下载体验 OneFlow v0.8.0 最新版本: https://github.com/Oneflow-In...