导读:
本文将以下三个方面开展介绍:
- DeepRec 背景(咱们为什么要做 DeepRec)
- DeepRec 性能(设计动机和实现)
- DeepRec 社区(最新公布的 2206 版本次要性能)
DeepRec 背景介绍
咱们为什么须要稠密模型引擎?TensorFlow 目前的社区版本是可能反对稠密场景的,然而在以下三个方面存在一些性能上的短板:
- 晋升模型成果的稠密训练性能;
- 晋升模型迭代效率的训练性能;
- 稠密模型的部署。
因而咱们提出了 DeepRec,其功能定位在稠密场景做深度的优化。
DeepRec 所做的工作次要在四大方面:稠密性能、训练性能、Serving、以及部署 & ODL。
DeepRec 在阿里巴巴外部的利用次要在举荐(猜你喜爱)、搜寻(主搜)、广告(直通车和定向)等几个外围场景。咱们也给云上的一些客户提供了局部稠密场景的解决办法,为其模型成果和迭代效率的晋升带来了很大帮忙。
DeepRec 性能介绍
DeepRec 的性能次要分为以下五大方面:稠密性能 Embedding,训练框架(异步、同步),Runtime(Executor、PRMalloc),图优化(结构化模型,SmartStage),serving 部署相干性能。
1. Embedding
Embedding 局部将介绍以下 5 个子性能:
1.1 动静弹性特色(EV)
上图的右边是 TensorFlow 反对稠密性能的次要形式。用户首先定义固定的 shape 的 Tensor,稠密的特色通过 Hash+Mod 的形式 map 到刚刚定义的 Tensor 上。这个逻辑上有 4 个问题:
- 稠密特色的抵触,Hash+Mod 的形式容易引入特色抵触,这会导致无效特色的隐没,进而影响成果;
- 存储局部会导致内存的节约,有局部内存空间不会被应用到;
- 固定的 shape,一旦 Variable 的 shape 固定了,将来无奈更改;
- 低效的 IO,如果用户用这种形式定义 Variable,必须通过全量的形式导出,如果 Variable 的维度很大,那么无论导出还是加载都是非常耗时的,但咱们在稠密的场景其实变动的局部是很少的。
在这种状况下,DeepRec 定义的 EmbeddingVariable 设计的原理是:将动态的 Variable 转化为动静的相似 HashTable 的存储,每来一个 key,新创建一个 Embedding,这样就人造地解决了特色抵触的问题。通过这样的设计,当特色特地的多的时候,EmbeddingVariable 无序的扩张,内存耗费也会变得很大,因而 DeepRec 引入了以下两个性能:特色准入和特色淘汰。它们都能无效的避免特色扩大到很大的维度。在搜寻和举荐这样的稠密场景,有些长尾特色被模型训练的次数非常少。因而特色准入能通过 CounterFilter 或者 BloomFilter 的形式对特色进入 EmbeddingVariable 设置一个门槛;在模型导出 Checkpoint 的时候也会有特色淘汰的性能,工夫上比拟老的特色也会被淘汰。这在阿里外部某个举荐业务 AUC 晋升 5‰,在云上某举荐业务 AUC 晋升 5‰,pvctr 也有晋升 4%。
1.2 基于特征频率的动静弹性维度特色(FAE)
通常状况下同一个特色对应的 EmbeddingVariable 会被设置为同一个维度,如果 EmbeddingVariable 被设置一个较高的维度,低频的特色内容容易导致过拟合,并且会耗费大量的内存。相同的如果维度设置的过低,高频的特色内容则有可能因为表白的能力有余而影响模型的成果。FAE 的性能则提供了对于同一个特色里,依据不同特色冷热来配置不同的维度。这样让模型主动进行训练时第一个是模型的成果能失去保障,第二个也能解决训练对资源的应用。这是对于 FAE 性能的出发点的介绍。这个性能的应用目前是让用户传入一个维度和统计的算法,FAE 主动依据实现的算法来产生不同的 EmbeddingVariable;前面 DeepRec 打算在零碎外部自适应的发现去调配特色的维度,从而进步用户的易用性。
1.3 自适应 EmbeddingVariable
这个性能和第二个性能有些相似,都是以定义高低频的关系作为出发点。当后面提到的 EV 特地大时,咱们会看到内存占用特地高。在 Adaptive Embedding Variable 中咱们用两个 Variable 来表白,如右图展现。咱们会定义其中一个 Variable 为动态的,低频的特色会尽可能映射到这个 Variable 上;另外一个则定义为动静弹性维度特色,用于高频局部的特色。Variable 的外部反对低频和高频特色动静的转换,这样的长处是极大升高了系统对内存的应用。例如某个特色训练后第一维可能有靠近 10 亿,而重要的特色只有 20%-30%,通过这种自适应的形式后,能够不须要那么大的维度,进而极大的升高了对内存的应用。咱们在理论利用发现对模型的精度影响是很小的。
1.4 Multi-Hash Variable
这个性能是为了解决特色抵触的问题。咱们原来是通过一个 Hash+Mod 的形式解决特色抵触,当初用两个或多个 Hash+Mod 去失去 Embedding,并且随后对失去的 Embedding 做 Reduction,这样的益处是能用更少的内存来解决特色抵触的问题。
1.5 Embedding 多级混合存储
这一性能的出发点同样也是发现 EV 在特色个数多的时候,内存开销非常大,训练的时候 worker 占用的内存可能达到了几十上百 G。咱们发现,特色实际上遵循典型的幂律散布。思考到这个特色点,咱们将热点特色放到 CPU 这样更贵重的资源,而绝对长尾低频的特色则放到绝对便宜的资源中。如右图,有 DRAM、PMEM、SSD 三种构造,PMEM 是英特尔提供的速度介于 DRAM 和 SSD 之间,但容量很大。咱们目前反对 DRAM-PMEM、DRAM-SSD、PMEM-SSD 的混合,也在业务上获得了成果。云上有个业务 原来用 200+ 多 CPU 分布式训练,当初应用多级存储后改成了单机 GPU 训练。
以上是对 Embedding 所有性能的介绍。咱们做这些性能的动机是因为 TensorFlow 的几个问题(次要是特色抵触),咱们解决的计划是动静弹性特色和 Multi-Hash 特色,针对动静弹性特色内存开销较大的问题,咱们又开发了特色准入和特色淘汰的性能;针对特色频次,咱们开发了 3 组性能:动静弹性维度和自适应动静弹性特色是从维度的方向解决的问题,多级混合存储则是从软硬件的方向解决的问题。
2. 训练框架
第二个要介绍的性能是训练框架,分为异步和同步两个方向来介绍。
2.1 异步训练框架 StarServer
在超大规模工作状况下,上千个 worker,原生 TensorFlow 存在的问题是:线程调度非常低效,要害门路开销凸显,另外小包通信非常频繁,这些都成为了分布式通信的瓶颈。
StarServer 在图的线程调度、内存的优化方面做得很好,将框架中 Send/Recv 批改为了 Push/Pull 语义,PS 在执行的时候应用了 lockless 的办法,极大地提高了执行的效率。咱们比照原生框架有数倍的性能晋升,并且在外部 3Kworker 左右的数量能达到线性的扩大。
2.2 同步训练框架 HybridBackend,
这是咱们为同步训练开发的计划,它反对数据并行和模型并行混合分布式训练。数据读取通过数据并行来实现,模型并行能反对大参数量训练,最初应用数据并行做浓密计算。咱们针对不同 EmbeddingLookup 的特色,做了多路 Lookup 合并的优化,分组优化,还利用了 GPU Direct RDMA 的长处,基于网络拓扑的感知,设计整个同步的框架。
3. Runtime
第三个大方面的性能是 Runtime,次要介绍 PRMalloc 和 Executor 优化。
3.1 PRMalloc
首先是内存调配,内存调配在 TensorFlow 和 DeepRec 中都是无处不在的,咱们首先在稠密训练中发现,大块内存调配造成了大量的 minorpagefault,此外在多线程的调配中也存在并发调配的问题。咱们在 DeepRec 中针对稠密训练前向反向的特点,设计了针对深度学习的内存调配计划,称为 PRMalloc。它进步了内存使用率和零碎的性能。在图中能够看到次要的一块是 MemoryPlanner,它的作用是在模型训练的前 k 轮的 minibatch 先统计以后训练的特点,每次须要调配多少 Tensor,将这些行为记录通过 bin 的 buffer 记录下来,并且做相应的优化。在 k 步后,咱们将其利用,从而极大缩小上述的问题。咱们在 DeepRec 的应用中发现,这能大大减少 minorpagefault 的呈现,缩小了内存的应用,训练速度也失去了 1.6 倍的减速。
3.2 Executor 优化
TensorFlow 原生的 Executor 的实现非常简略,首先对 DAG 做拓扑排序,随后将 Node 插入到执行队列中,通过 Task 利用 Executor 调度。这样的实现没有联合业务思考,ThreadPool 默认应用了 Eigen 线程池,若线程负载不平均,会产生大量的线程间抢占 Steal,带来极大开销。咱们在 DeepRec 中定义调度更平均,同时定义了要害门路使得在调度的时候有肯定的优先级程序,来执行 Op。最终 DeepRec 也提供了多种包含基于 Task,SimpleGraph 的调度策略。
4. 图优化相干的性能
4.1 结构化特色
这是从业务启发的一个性能。咱们发现在搜寻场景下,不论是训练还是推理,样本往往是 1 个 user 对应多个 item,多个 label 的特点。原来的解决形式会视为多个样本,这样 user 的存储是冗余的,咱们为了节俭这部分开销,自定义了存储格局来做这部分优化。如果这些样本在一个 minibatch 中是同一个 user,局部 user 网络和 item 网络会别离计算,最初在做相应的逻辑计算,这样能节俭计算开销。所以咱们别离从存储和计算端做了结构化的优化。
4.2 SmartStage
咱们看到稠密模型的训练通常包含样本的读取,EmbeddingLookup,还有 MLP 的网络计算。样本的读取和 Embedding 查找往往不是计算密集型的,并不能无效利用计算资源。原生框架提供的 prefetch 接口尽管能肯定水平上实现异步操作,然而咱们在 EmbeddingLookup 过程中设计局部简单的子图,这些不能通过 TensorFlow 的 prefetch 实现流水线。TensorFlow 提供的流水线性能,理论应用中须要用户显示的指定 stage 边界,一方面会进步应用难度,另一方面因为 stage 的精度不够,无奈准确到 op 级别。对于 High Level 的 API 用户无奈手动插入,会导致很多步调并行化。下图是 SmartStage 的具体操作,它会将 Op 主动的归类到不同的 Stage,使得并发的流水线能失去性能的晋升。咱们在 ModelZoo 里模型的测试成果最大减速比能达到 1.1-1.3。
5. Serving
5.1 模型增量导出及加载
一开始在介绍 Embedding 的时候其中一个重要的点是低效的 IO,如果将后面提到动静弹性性能利用后,咱们人造能做增量的导出。只有在图中退出已经拜访的稠密 ID,那么在增量导出的时候就能精确的导出这部分咱们须要的 ID。咱们做这个性能有两个出发点:首先,模型训练时咱们原有的办法,在每个 step 导出全量的模型导出,在程序中断 restore 时候也是 restore checkpoint,最差的时候可能损失两个 checkpoint 区间所有的后果,有了增量导出,咱们对于 dense 局部会全量导出,sparse 局部是增量导出,这在理论场景 10 分钟的增量导出能很大水平节约 restore 带来的损失;另外,增量导出的场景是在线 serving,如果每次都全量加载,那么对于稠密场景,模型非常大,每次加载都须要消耗很长时间,如果要做在线学习会很艰难,所以增量导出也会用到 ODL 场景。
5.2 ODL
最右边是样本解决,高低两局部是离线和在线的训练,左边是 serving。这外面利用了很多 PAI 的组件来实现 Pipeline 的结构。
DeepRec 社区
社区方面,咱们在 6 月份公布了新版本 2206,次要包含以下新性能: