共计 3750 个字符,预计需要花费 10 分钟才能阅读完成。
作为图片届的“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…