DNN in GPU/CPU Clusters

A Unified Architecture for Accelerating Distributed DNN Training in Heterogeneous GPU/CPU Clusters

运行DNN训练任务的数据中心集群本质上是异构的。他们有用于计算的gpu和cpu,以及用于分布式训练的网络带宽。然而,现有的分布式DNN训练架构all-reduce和Parameter Server (PS)不能充分利用这些异构资源。本文提出一种新的分布式DNN训练架构BytePS。BytePS可以利用集群中空闲的CPU和带宽资源来加速在gpu上运行的分布式DNN训练任务。它提供了一个既被证明是最优的又被证明是统一的通信框架——现存的all-reduce和PS成为BytePS的两种特殊情况。为了达到实践证明的最优性,BytePS进一步拆分了参数优化器的功能。它引入了一个用于聚合梯度的Summation Service抽象,这对所有优化器都是通用的。求和服务可以通过AVX指令进行加速,可以在cpu上高效运行,而与DNN模型相关的优化算法可以在gpu上进行加速。字节码可以加速主要框架的DNN训练,包括TensorFlow, PyTorch和MXNet。对于多达256个gpu的代表性DNN训练作业,BytePS的性能比最先进的开源all-reduce和PS分别提高了84%和245%。

DNN的训练过程大致分为三个步骤:(1)前向传播(FP),批量地输入训练数据,计算最后结果的损失函数(loss function);(2)反向传播(BP),通过损失函数计算出每个参数的梯度;(3)参数更新,通过得到的梯度,使用求和的方法更新各个参数。并且,在该步骤中会用到一些优化器(如SGD,Adam等)。训练的过程是一个重复以上三个步骤的迭代过程。本文主要优化的是分布式的参数更新过程,提高了GPU节点和CPU节点上的CPU利用率以及网络利用率。

在分布式训练的环境下,数据并行是一种常用的并行方法,即每台机器上都有相同的模型(即相同的一组参数),并行地使用数据进行训练,最后需要收集各个参数的梯度,以更新每台机器上的模型。分布式的参数更新有以下两种方式:(假设模型总的大小为M,一个节点的网络带宽为B。)

  • All-reduce。如下图所示,使用这种方法会将模型的参数n份在n个不同的GPU节点上(每个节点的参数大小为M/n),分布式地进行模型更新。一种常用的All-reduce算法是Ring All-Reduce:(a)每个节点会维护自己负责更新的一部分参数,把其他参数发给下一个节点,在下一个节点进行加和(reduce)后再发给下一个节点,到最后一个节点则能算出总和。(b)算出总和的节点将自己负责的参数发给其他节点。(c)每个节点会有更新后的参数。这样,每个GPU节点的通信量(包括收和发)为2(n-1)M/n。

  • Parameter Server (PS)。另一种方式如下图所示,除了有GPU节点外,还有CPU节点,这些CPU节点会集中式地收集所有GPU节点上的参数并且计算。这个CPU节点可以是真的单独的CPU节点(a),也可以是GPU节点上空闲的CPU(b)。这两种情况下,每个GPU节点的通信录分别为nM/kB(k为单独CPU节点的数量)和2(n-1)M/n(和All-reduce)一样。

通过比较通信量可得到PS的方法更优,但由于实际实现上的瓶颈,导致性能不尽人意。同时,本文的工作也发现,

 如下图所示,BodyPS的架构混合使用了CPU节点上的CPU以及GPU节点上的GPU。文章提到,之前提到的All-Reduce和PS两种方式,是BodyPS架构的两种特例。

通过测试比较,BodyPS的性能能够比之前的方法更能逼近理论性能上界。

Heterogeneity-Aware Cluster Scheduling Policies for Deep Learning Workloads

Deepak Narayanan and Keshav Santhanam, Stanford University and Microsoft Research;Fiodar Kazhamiaka, Stanford University;Amar Phanishayee, Microsoft Research;Matei Zaharia, Stanford University

这项工作由来自Stanford的博士生Deepak Narayanan报告。现在的DL训练集群往往配备了多种异构的加速器,例如GPU、TPU、FPGA甚至专用的ASIC等,即使是GPU也有多种不同的型号,不同的加速器在性能以及成本上会有比较大的差异,不同的DL模型在不同加速器上训练的吞吐量也有明显差异,本项工作提出了Gavel调度器,能够在调度DL training任务时考虑不同加速器的异构性,并且将传统的调度策略建模成一个优化问题,通过求解优化问题得到最优的资源分配方式

首先,Gavel提出了如何对一个异构环境下的调度问题进行建模,用一个矩阵X表示模型在不同加速器上的时间片分配,其中X(m,j)表示模型m在加速器j上分配的时间片占比,如下图中job0的60%时间运行在V100 GPU上,40%的时间运行在P100 GPU上,对应的,P100 GPU中有40%的时间在运行job0,60%的时间在运行job1

用矩阵T表示每个模型在不同加速器上的吞吐量,其中T(m,j)表示模型m在加速器j上的吞吐量(每秒的迭代次数),最后用T(m,j)与X(m,j)乘积之和表示每个模型的effective throughput。同样的,如果一个GPU可以同时训练多个模型或者一个模型需要使用多个GPU,也可以使用这个方法建模,具体方法可以参考论文。

这样一个调度问题就可以被建模为在已知矩阵T的情况下对矩阵X的优化问题,不同的调度策略可以对应不同的优化目标以及约束,例如最大化总effective throughput的调度策略可以建模为:

作者也在文中给出了多个常见的调度策略的在Gavel模型下的优化目标(详情可参阅原文):

在计算出矩阵X后,Gavel使用一个基于轮询的调度机制为每个模型分配资源,在每一个round结束后,会根据X(m, j)/模型m在加速器j上已经分配的时间片计算优先级,根据优先级高低决定下一轮的资源分配:

作者通过在物理集群以及仿真测试中验证了Gavel的性能,在有三种共42块GPU的物理机群中,与其他不关心集群异构特性的调度器相比,Gavel将平均的JCT降低了1.5倍,在多个模型不共享GPU的情况下将makespan降低了1.2倍。

 AntMan: Dynamic Scaling on GPU Clusters for Deep Learning

Overview

AntMan 是发表在 OSDI'20 Machine Learning Session 的论文,主要解决「深度学习的 GPU 集群资源使用率低的问题」。一作是「肖文聪」(Wencong Xiao,https://wencongxiao.github.io/),北航与微软亚洲研究院联培博士,现任职于阿里巴巴 PAI group。代表作包括 AntMan (OSDI'20) 和 Gandiva (OSDI' 18)。

之前读过这篇文章,初读时由于基础知识的缺乏,所以没有再读时体会深刻。现在重读觉得这篇文章一气呵成,尤其是图表的设计自成体系,13 张图和 5 张表勾勒出文章的骨架。本文就主要从这 18 张图表介绍 AntMan 的设计,体会顶会论文是如何炼成的。Let's go!

1. Background

「问题描述」:集群中 GPU 使用率较低。「图1」展示了在实际生产集群中 GPU 资源的使用情况:仅有 20% 的机器,GPU 的显存使用率超过 50%;仅有 10% 的机器,GPU 的计算单元使用率超过 80%。

「原因阐释」:低使用率通常源于两个方面。一是为了避免任务的抢占,集群往往给用户的任务分配独占的 GPU 显卡,但是任务无法充分使用被分配的显存资源或者计算资源;二是对于需要多张显卡执行机器学习任务的模型,他们需要花费较长的时间等待 GPU 的分配。而且等待的过程中已经分配的 GPU 既不会执行任务,也不会释放给其他用户使用。「图2」展示了当用户需要使用多块 GPU 时,GPU 的平均空载时间。

「现有方案及不足」可行的解决方案是允许多个任务使用同一块 GPU 执行任务。但是,packing 一方面会引起计算资源竞争,导致该 GPU 上的任务完成效率同时下降。另一方面则是显存资源竞争,显存资源竞争的后果则是引起 RuntimeError: CUDA out of memory 的问题。

「模型训练的资源使用特点」模型训练的特征 GPU 资源的低使用率与较长的空闲等待时间之外,任务需要的资源也动态变化。「图3」「图4」分别显示了 DeepFM 模型与 ESPnet 模型在模型训练时 GPU 的显存使用与计算单元的使用率动态变化的特点。图4 似乎更有意思。我们知道,模型结构决定了模型的计算特征:计算密集型或者 IO 密集型。「在单个模型的内部, ESPnet 出现了计算密集型与 IO 密集型的分化」: 1200~1400s 是计算密集型(SM util 较高), 1400~1600s 是 IO 密集型(Memory util 较高)。

除了归纳模型训练的资源使用特点,AntMan 还结合了以下实践得出的机会点进行系统的设计:

  • 模型使用的显存比较小。「图5」对一周机器学习的训练任务进行统计,「图5(a)」 显示90%的机器学习训练任务,模型本身占用的显存都小于300MB。
  • 模型训练过程中 mini-batch 的训练时间较短。「图5(b)」 显示每个 mini-batch 的训练时间普遍小于1500毫秒。
  • 训练过程周期性变化。我们可以利用周期性变化的特点,分析在某种资源配置下模型训练的性能,从而对 resource-guarantee 与 opportunistic 的任务提供更合理的资源配置。

2. Design

这一部分主要讨论 AntMan 如何设计应对 GPU 共享的挑战。首先介绍的是对深度学习框架的扩展,从而支持显存和计算单元的动态分配。然后讨论 AntMan 中在 master node 的 Global Scheduler 与在slave node的Local Coordinator 相互协作,共同调度 GPU 资源设计,支持对深度学习框架的扩展。最后介绍上述的调度方案在阿里云平台的实践。

2.1 Dynamic Scheduling

首先介绍显存资源的动态分配。

「图6」对比了现有的深度学习框架与 AntMan 的显存分配设计。「图6(a, b, c)」 展示深度学习框架在在申请显存后,当变量被销毁时,申请的显存不会释放,而是缓存在深度学习框架自己维护的显存池中。例如,这里 https://github.com/pytorch/pytorch/blob/master/c10/cuda/CUDACachingAllocator.cpp 介绍了 PyTorch 是如何将显存据为己有,自主分配。「图6(d, e, f)」 则是 AntMan 的显存机制。当显存资源有限时,AntMan 会降低 opportunistic 任务的显存分配,因此部分的计算张量只能拷贝到 CPU 设备的内存中;当显存资源有盈余时,内存上的张量又会重新拷贝到 GPU 显存中「图7」则是介绍了上述的动态分配显存资源的机制如何和机器学习训练任务结合。

其次介绍计算单元的动态分配。在现代操作系统中cgroup 的设计保证了进程在使用 CPU 计算时的性能隔离,但是 GPU 却没有保证多个进程性能隔离的机制。因此,动态分配计算单元的目的则是保证 opportunistic 的任务不会影响到 resource-guarantee 任务的完成时间 (JCT, job completion time)。

「图8」 展示了动态分配计算资源的方案。当我们在使用 GPU 执行任务时,实际上是使用 GPU 的流处理器 (SM, stream multiprocessor) 执行自定义的 kernel 函数。图上蓝色的方格表示 Job A 的 kernel 函数执行流,Job A 是 resource-guarantee 的任务;绿色方格表示 Job B的 kernel 函数执行流,它是 opportunistic 类型的任务。「图8(a)」 中是 Job A 独占时的 GPU 使用情况,我们发现 GPU 并未被充分使用。在 「图8(b)」 如果我们将 Job B 不加限制地扔到 GPU 上执行时,尽管 GPU 的 cycle 被完全占据了,但是 Job A 的 JCT 大受影响。

AntMan 的计算资源分配如 「图8(c)」 所示。具体的实现方案是在深度学习框架中引入 GpuOpManager模块。它的作用是控制 GPU 的 kernel 函数发送到 GPU 设备频率。GpuOpManager会在 GPU 设备空闲的时候将 opportunistic 任务的 GPU 算子发送到 GPU 上执行,从而不会影响 resource-guarantee 任务的执行效率。

2.2 Collaborative Scheduler

「图9」展示了分别位于 master node 与 slave node 的 Global Scheduler 与 Local Coordinator 相互协作,调度 GPU 资源的过程。整体来说,这一部分的设计中规中矩,master 与 local 相互协作调度的设计受到 Kubernetes 的影响痕迹明显,没有 Dynamic Scheduling 部分的设计巧妙。

Global Scheduler 维护全局的信息,包括硬件设备的信息 (GPU Computing Utilization 与 GPU Memory Usage) 和深度学习任务的执行信息 (mini-batch 的执行时间,最高、最低与平均显存使用信息以及 CPU 端的内存信息),这些信息都是 slave node 反馈给 master node 的。Global Scheduler 利用这些信息更有效的指导深度学习任务的安置与资源分配。

2.3 Scheduling Policy

Design 的最后一部分介绍了资源动态分配与主从节点协作调度在实际生产集群中的实践。

设计的「总目标」是通过引入 opportunistic 类型的任务使用空闲的 GPU cycle,从而提高 GPU 集群的资源使用率。

我们重点关注 Local Coordinator 的设计。Local Coordinator 的设计融入了动态分配显存与计算资源考量,主要考虑了以下三个问题:

  • 如何保证 resource-guarantee 任务的性能?
  • 如何处理 resource-guarantee 任务的需求激增情况?
  • 如何使用贪心的方法最大化 opportunistic 任务的表现?

对于第一个问题,AntMan 的做法是首先保证 resource-guarantee 的任务稳定执行;然后为 opportunistic 的任务分配 GPU 显存和计算单元;最后观察 resource-guarantee 的性能是否受到了影响。判断性能是否受到影响的方法则是观察 mini-batch 的执行时间进行判断。

对于第二个问题,如果是 memory 突变的情况,首先使用 host memory 作为临时存储,减少 opportunistic 任务的显存使用;接着增加 resource-guarantee 任务的显存分配。同样的方法适用于计算单元需求的激增。

最后一个问题的场景是多 GPU 任务在等待 GPU 资源时,已经分配到的 GPU 空闲的情况。对于这个问题,AntMan 的解决方案是使用贪心算法,最大化 GPU 显存的使用效率。「图10」展示了使用贪心算法的现实依据——不同的模型对 GPU 显存的限制变化在性能上的感知差距很大,例如降低 SR 模型 90% 的显存分配仅带来 25% 的性能损失,而降低 ResNet 模型 10% 的显存就引起 60% 的性能损失。因此,AntMan 在 opportunistic 任务竞争的场景下优先将 GPU 分配给能够带来性能提升的任务(ResNet 的优先级高于 SR,“会哭得孩子有奶吃”?)。

3. Evaluation

实验结果分成三个部分。一是在 benchmark 上分析 AntMan 提出的动态资源调度的效果;二是在公开数据集上进行测试;三是在阿里云上带有 5000 块异构的 GPU 集群上的效果测试

3.1 Benchmark

「实验一:Dynamic GPU memory scaling.」

「表1」展示了两个不同的任务,其中 Job A 是 opportunistic 类型的任务,Job B 是 resource guarantee 类型的任务。在第 26 分钟,两个任务会同时执行在显存为 32 GB 的 GPU 上。「表2」提供了不同的解决方案以及对应的结果,其中抢占或者 Pack 会导致其中某一个任务崩溃,而 FIFO 与 UMem 方法不能保证 resource guarantee 任务的效率。AntMan 的动态显存分配满足要求。而且,我们可以发现,Job A 的性能下降也是非常低的。

「实验二:Efficient memory shrinkage and growth.」

「图11」展示了 AntMan 在进行显存的分配与压缩需要的额外开销。其中,「图11(b)」 分别列举了 VGG16,InceptionV3 与 GoogleNet 在显存变化 17GB,16GB和4GB时的开销时间,以此证明显存动态分配的开销微乎其微。

「实验三:Dynamic GPU computation unit scaling.」

为了展示 AntMan 动态的计算资源分配不会影响 resource-guarantee 任务的性能,作者将 AntMan 与 Packing 模式相对比。实验的模型是 ResNet50(opportunistic,计算密集型)与ESPNet(resource-guarantee,非计算密集型)。「图12(a)」 展示直接进行 Packing 会导致 ESPNet 模型的 SM 使用率受到 ResNet50 的约束,而 AntMan 会自适应的调节 ResNet50 的 SM 使用率,确保 ESPNet 模型正常执行。实验结果表明,Packing 模式,ESPNet 的执行时间为 105.2 分钟,严重受到影响;而 AntMan 的执行时间保持在 20 分钟左右,「niubility」

3.2 Trace Experiment

这部分的实验数据来自微软在 Gandiva 中使用的数据集,模型的分布如「表3」。实验对比了 YARN-CS 与 Gandiva,结果在「图13」。实验结果显示 AntMan 可以达到更短的平均任务完成时间 (JCT) 与最大任务完成时间 (makespan)。

3.3 Cluster Experiment

最后一部分实验在案例云的集群中进行测试,集群拥有超过 5000 块 GPU。「表4」展示了部署 AntMan 前(2019年12月)后(2020年4月)任务在分配 GPU 时的等待时间。(表格数据是否存在问题,为什么 2020年4月Average 时间比 90% 和 95% 分位的时间长?)「表5」对10000个训练任务的 mini-batch 训练时间进行分析,发现99%的任务 mini-batch 时间误差在 10 毫秒内,认为是没有收到影响。

4. Conclusion

没啥好总结的,谈一谈我重读这篇文章发现的一些问题与研究点,希望可以抛砖引玉。

  • 「研究如何更好地模型组合共享资源」:什么样的模型结构任务适合去共享 GPU 资源呢?计算密集型与 IO 密集型能否进行更好地组合,以此能够充分利用 GPU 为我们提供的计算单元与显存这两种资源?
  • 「研究减少显存对模型性能的影响」: Scheduling Policy 中多个 opportunistic 任务的显存分配中提到了不同的模型对显存分配降低时的性能损失不同,AntMan 使用的方法是 profile 不同模型在不同显存分配下的性能表现,然后根据测量的结果进行分配?是否用更系统或者理论的方法,通过分析模型的结构来预测性能的变化?
  • 「研究共享任务的计算单元分配问题」:还是上述场景,任务的执行时间和分配的计算单元的速率有很大的关系,AntMan 却没有明确说明这些任务计算单元是如何分配的。有没有分配计算单元的方法,胜过没有任何限制的抢占得到的效率?Dynamic Scheduling 中的计算单元的动态分配值得参考。

HiveD: Sharing a GPU Cluster for Deep Learning with Guarantees

Hanyu Zhao, Peking University and Microsoft; Zhenhua Han, The University of Hong Kong and Microsoft; Zhi Yang, Peking University; Quanlu Zhang, Fan Yang, Lidong Zhou, and Mao Yang, Microsoft; Francis C.M. Lau, The University of Hong Kong; Yuqi Wang, Yifan Xiong, and Bin Wang, Microsoft

本文是北京大学、香港大学和微软合作的项目,提出了一种GPU集群的资源保留框架HiveD。多租户在GPU集群上进行深度学习的训练是一种目前较为常见的使用场景。然而,由于目前GPU集群上的资源分配方式是基于quota的,即每个任务指定所需要使用GPU的数目,导致会出现即使分配了足够的GPU数目,也会导致该任务的等待时间或者执行时间大幅下降,该现象被称为共享异常(Sharing anomaly)。如下图所示,同一个任务在共享的GPU集群下,可能会因此带来8000分钟的等待时间!

究其原因,是因为仅仅指定GPU的数目是不够的。由于不同用户随时可能会要求分配或者释放GPU数目,就会导致出现大量的外部碎片(external fragmentation)。这个外部碎片的概念与内存分配中的概念类似,即随着分配、释放的次数变多,大量的GPU数目可能会变成跨CPU socket、甚至是跨机器。如下图所示,同样是分配2个GPU,如果是跨PCI-e、跨机器,将会分别带来40%和5倍的性能下降。因此,造成了虽然分配了足够的GPU,但是还是会导致性能的大幅下降。同时,由于会有许多1个GPU资源的请求,导致多资源的请求不能很快得到满足。以往的系统,只能通过告知集群管理员,才能进行统一的资源管理。

为了解决上述的外部碎片的问题,HiveD提供了一层被称为VC(虚拟集群)的抽象,让用户更加细粒度的去指定GPU的分配方式,像是在私有集群上进行分配一样,这个目标被称为sharing safety。同时也用了buddy cell allocation的机制,其中cell是指不同层次的GPU资源。其中Level-1到level-5分别是指单个GPU、共享PCI-e的一组GPU、共享CPU socket的一组GPU、同一机器上的GPU、同一机架的GPU。这样,通过制定不同层次的cell数目,来进行虚拟的GPU资源分配,同时映射到物理资源分配,如下图所示。Buddy让人联想到内存分类的buddy allocation,事实上,两者确实是类似的机制。也即在申请Level-k的cell时,如果该level的空闲队列中有空闲资源,则直接分配,否则递归地从Level-(k+1)中拿出一块来进行分裂,并将剩余分裂后的空闲资源放入level-k的空闲队列中。这样能够保证资源分配的局部性。同时,在资源回收的时候,也会去递归地查看是否能够将空闲资源从level-k合并为level-(k+1)。

同时,cell的分配,用户也能够指定其优先级(priority)。在资源分配冲突的情况下,优先级高的任务能够抢占优先级低的任务所需要的GPU。

在测试中,HiveD提供了真实部署和trace simulation两种场景。测试发现,现有的共享训练系统,会带来多达1000分钟的等待时间,而HiveD可以有效减少等待之间。下图给出了一个测试,展示了在三种不同调度器下,使用HiveD框架和不使用HiveD框架的区别。在HiveD下,由于能避免许多1个GPU请求带来的碎片,因此能够大大减少多GPU请求的等待资源时间。

Marius: Learning Massive Graph Embeddings on a Single Machine

Jason Mohoney and Roger Waleffe, University of Wisconsin–Madison; Henry Xu, University of Maryland, College Park; Theodoros Rekatsinas and Shivaram Venkataraman, University of Wisconsin–Madison

本项工作由来自 University of Wisconsin-Madison 的博士生 Jason Mohoney 汇报,如文章题目所示,这项工作提出了一个新的系统 Marius,可以在单机训练大规模 Graph Embeddings。

Marius 主要运用了两个核心的技术,一个是通过 pipeline 的方法进行训练,从而提高资源的利用率以及系统整体的吞吐量;二是提出了 Buffer-aware Edge Traversal Algorithm,减少大规模图数据在内存和磁盘之间swap的次数,降低I/O开销。

Pipelined Training

针对之前第一类系统存在的 Synchronous Training 的问题,本项工作提出的系统 Marius 使用了 pipeline 的方法,将各个计算步骤通过流水线的方式并行。整个 pipeline 包括了五个步骤:

  1. 加载数据
  2. 将数据从 CPU 拷贝到 GPU
  3. 计算梯度
  4. 将数据从 GPU 拷贝到 CPU
  5. 更新参数

将这五个步骤进行流水线并行,可以有效地提高系统的吞吐。然而由于训练过程从同步变成了异步,会引入 staleness 的问题,也就是上一次 mini-batch 训练的结果还没有更新,下一个 mini-batch 的数据就已经传输到 GPU 中了,导致 mini-batch 的参数可能并不是最新的。Marius 通过减少 pipeline 中 mini-batch 的数量来降低出现 staleness 的概率。

Buffer-aware Edge Traversal Algorithm

针对第二类系统存在的 swap 开销大的问题,本项工作提出了 Buffer-aware Edge Traversal Algorithm 算法来遍历图,可以有效地降低 swap 的次数。 BETA 算法的核心思路是尽量复用现有 buffer 中的 node。

上图给出了算法的伪代码,在 L5 开始的 while 循环中主要包含两个 for 循环,第一个 for 循环是固定 buffer 中的前 c-1 个 slot,依次 swap 最后一个 slot;而第二个循环则是更换前 c-1 个固定的 slot。

下图给出了 c=3, p=5 的一个例子。在前四步中,BETA 固定了前两个 slot (0, 1),依次 swap 最后一个 slot (2, 3, 4, 5),接着依次替换了前两个固定的 slot 至 (2, 3),并 swap 最后一个 slot(4, 5),最后将固定的 slot 替换为 (5),并完成了全部 partition pair 的遍历。

下图给出了模拟使用不同图遍历算法得到的 I/O 次数,可以看到 BETA 算法几乎接近了理论最优值。

Evaluation

作者在四个典型的数据集上进行了测试,主要对比了 DGL-KE 和 PBG 两个系统,可以看到 Marius 可以显著的加速 Graph Embedding 的训练。

Conclusion

Marius 是一个在单机训练大规模 Graph Embedding 的系统,其主要运用了两个核心的技术,一个是通过 pipeline 的方法提高系统的吞吐量;二是提出了 Buffer-aware Edge Traversal Algorithm,减少大规模图数据在内存和磁盘之间swap的次数,降低I/O开销。Marius 开源网址 www.marius-project.org

Pollux: Co-adaptive Cluster Scheduling for Goodput-Optimized Deep Learning

Aurick Qiao, Petuum, Inc. and Carnegie Mellon University; Sang Keun Choe and Suhas Jayaram Subramanya, Carnegie Mellon University; Willie Neiswanger, Petuum, Inc. and Carnegie Mellon University; Qirong Ho, Petuum, Inc.; Hao Zhang, Petuum, Inc. and UC Berkeley; Gregory R. Ganger, Carnegie Mellon University; Eric P. Xing, MBZUAI, Petuum, Inc., and Carnegie Mellon University

本项工作解决的是近年非常火的关于深度学习模型训练场景中集群资源调度的问题,并且获得了 Best Paper Award。之前的大多数工作都需要用户预先指定每个 Training Job 所需要集群资源或者自动为每个 Job 静态分配集群资源,本项工作提出了调度器 Pollux,根据每个 Job 当前运行的状态动态调整它们的资源(例如 GPU 的数量)以及它们的训练参数(例如 learning rate 以及 batch size)来最大化整个集群的资源利用效率

Distributed DL Training

这里介绍本文考虑的两个核心的 Metric:System Throughput 以及 Statistical Efficiency。

System Throughput 的定义为单位时间所训练的样本数量,这里考虑主要的影响因素:

  1. 分配的资源(例如 GPU),资源越多 throughput 越高。
  2. batch size 的大小,batch size 越大,throughput 越高。这主要因为大 batch size 的计算时间长,可以减少分布式训练中 synchronization 时间的占比。

因此,在不更换训练算法的前提下,增加计算资源以及提高 batch size 可以有效地提高 throughput。

Statistical Efficiency 的定义为每个训练的样本对整个训练过程的贡献度,这里也主要考虑两个影响因素:

  1. batch size 的大小。一般地,batch size 越大,statistical efficiency 越低;但在不同的训练阶段,batch size 对 statistical efficiency 的影响程度也不同,例如在训练初期的影响较大,而在后期影响较小。
  2. learning rate。一般地,learning rate 越大,statistical efficiency 越高。

因此,增大 batch size 反而会降低 statistical efficiency。

如下图,对于 batch size 的调整需要综合考虑 throughput 和 statistical efficiency。

Key Idea: Goodput,not Throughput

本文核心的 idea 就是用一个新的指标:Goodput 来指导对集群资源的调度。

Goodput 综合考虑了 throughput 和 efficiency,下图给出了本文对 Goodput 的定义。本项工作就是通过调整 a, m, s 三个参数来最大化 Goodput。

Modeling System Throughput

为了最优化 Goodput,需要对 Throughput 和 Efficiency 两个指标进行建模并做实时的评估。下面简单介绍本文对两个指标的建模方式。

对于 Throughput,根据定义,可以通过计算 batch size / 计算每个 iteration 的时间: THROUGHPUT(a,m,s) = M(a,m,s)/T_{iter}(a,m,s)

而其中 T_{iter} 可以通过以下方式来计算:

下图给出了验证上述模型准确性的测试,可以看出这个模型基本上可以很准确地对 Throughput 进行评估。

Modeling Statistical Efficiency

Pollux 将 Efficiency 建模为 EFFICIENCY_t(M_0) 的相对值,并且在调整 batch size 的过程中只考虑 M \ge M_0,因此 Efficiency 的取值范围是 (0, 1],例如如果某个时刻采用 batch size 为 M 时的 Efficiency 是 E,表达的含义是需要用 1/E 的样本来训练才能够达到初始 batch size M_0 用一个样本所能够达到的 progress。

Pollux Architecture

Pollux 是一个深度学习训练任务调度器,负责在一个集群中为每个 DL Job 分配资源,并且调整他们的 batch size 以及 learning rate 来提高 Goodput。

Pollux 主要包含两个组件,全局的 PolluxSched,以及 per Job 的 PolluxAgent。

PolluxAgent 负责收集当前 Job 的 efficiency 和 throughput,并实时地调整 batch size 以最大化 Goodput,且将 Goodput 汇报给 PolluxSched。

PolluxSched 负责为每个 Job 动态地分配 GPU 资源,PolluxSched 的调度目标是最大化 fitness:

Evaluation

作者在 16个节点的集群(每个节点 4 个 T4 GPU)用 8 小时训练了 160 个 DL Job,从下面的表格可以看出,相比其他调度器最优的配置,平均任务完成时间降低了 37% ~ 50%。

Conclusion

本项工作提出的 Pollux 是一个 DL Job 的调度器。Pollux 使用 Goodput 来衡量模型训练的效率,Goodput 同时考虑了 System Throughput 和 Statistical Efficiency;Pollux 并且在 cluster 和 job 两个层次同时对资源的利用进行优化,可以降低 37% - 50% 的平均任务完成时间。

Dorylus: Affordable, Scalable, and Accurate GNN Training with Distributed CPU Servers and Serverless Threads

John Thorpe, Yifan Qiao, Jonathan Eyolfson, and Shen Teng, UCLA; Guanzhou Hu, UCLA and University of Wisconsin, Madison; Zhihao Jia, CMU; Jinliang Wei, Google Brain; Keval Vora, Simon Fraser; Ravi Netravali, Princeton University; Miryung Kim and Guoqing Harry Xu, UCLA

Dorylus是一个利用Serverless做GNN训练的系统,目的是降低GNN计算的成本(Affordability),提高GNN的可扩展性(Scalability)和性能(Performance)。

作者首先将GNN计算总结为四个关键的子过程,GNN训练的每一个循环就是在每一层模型上进行这四个关键的子步骤:

  1. Scatter (SC),每个节点将节点的数据从边发送到邻居节点。
  2. Apply Edge (AE), 在边上对发送的数据根据边的权重进行张量(tensor)计算。
  3. Gather (GA),每个点把从各个边上接收到的数据收集起来。
  4. Apply Vertex (AV),在点上对收集的数据根据点上的权重进行张量计算。

由此可以看出,GNN的四个子步骤包含了不同类型的Workload。其中Scatter和Gather操作需要在庞大的图结构上面进行计算,是Memory intensive的类型;两个Apply操作则是需要进行大量的张量积算,是Compute intensive的类型。那么:

  • 如果使用GPU处理这样的混合类型的Workload,GPU具有的有限的内存资源,限制了GNN的Scalability。此外,GPU高并发的特点更适合进行Apply操作的计算,而Scatter和Gather操作的瓶颈很多时候在于数据通信,GPU并不具备显著优势。GPU的成本也非常的昂贵。
  • 如果使用CPU进行GNN计算,那么则会由于Apply缺乏高并发操作导致训练性能下降。
  • 如果混合CPU和GPU,用CPU进行Scatter和Gather这样的图计算操作,用GPU进行Apply操作的计算。 这样GPU可能会在CPU做计算时停住等待CPU完成任务,这些等待的时间中GPU也会被计费,导致大量的额外成本

因此,作者考虑用Serverless函数作为合适的做Apply操作的基础设施。Serverless函数能够提供很高的并发度,瞬间invoke数以百计甚至千计的并发线程,同时其Pay-as-you-go的特点也可以避免用户在计算资源上浪费成本。然而,使用Serverless函数做计算具有以下两个Challenge:

  1. 每个Serverless函数线程的资源很有限:每个函数只有有限的CPU和memory资源
  2. Serverless函数的网络资源有限,这使得不同计算实体之间的通信开销变得非常高昂。

为了解决Challenge 1,Dorylus对GNN的计算进行了拆分:图操作(graph operation)和张量操作(tensor operation),将张量操作和图操作的依赖关系分离开来,并将graph相关的操作使用CPU进行计算, 将tensor相关的操作使用Serverless函数进行计算。Dorylus的架构如图:

Dorylus中存在Graph server,Lambda threads和Parameter server三个角色。这三个角色在GNN训练中的分工如下流程图所示:

该流程图显示了GNN一个epoch中对一层神经网络进行的正向和反向操作。其中,GA和SC(在图中为灰色)操作在graphserver上进行,当GA和SC做完后,graphserver将准备好的数据(该数据和图结构已经没有了依赖关系)发送给Lambda,由Lambda进行AV和AE(图中为浅红色)操作。最后,Dorylus由Parameter server来保存权重更新(Weights Update,即WU)的结果。

为了解决Challenge 2,作者观察到现在Dorylus的工作包含的四个步骤可以交错进行(interleaving),因此使用pipeline的方式来隐藏网络传输的开销。例如,Graph server首先对0-99号点进行GA操作,然后将结果发送给Lambda让它进行0-99号点的AV操作,自己再开始进行100-199号点的GA操作,依此类推,理想状态下如下图所示:

然而,Apply操作对图上邻居节点的计算时存在依赖的,因此在Apply开始做之前,需要进行一次同步操作,等待所有的节点全部做完,这使得无法简单使用pipeline来隐藏网络开销。因此,Dorylus引入了Bounded asynchronous机制。Dorylus允许不同节点独立进行计算,并且使用stale的节点数据,不过与此同时,Dorylus也限制了上限S,使得系统里的各个计算分支不能比其他分支先领先S个epoch。此外,Dorylus在parameter server处应用了weight stashing的机制,将前向操作中使用的参数缓存下来,并且在后向操作中使用相同版本的参数。最后,Dorylus还对Lambda进行了一些小的优化,比如将最后一层正向操作中的AV操作和第一层反向操作的ΔAV操作融合起来等,从而降低Lambda和CPU server之间的通信开销。

Dorylus在不同的数据集上进行测试,主要关注GNN训练的性能和成本,并且以性价比(Performance-per-dollar,即1 / (Cost * Time))作为其最关注的指标。作者同时实现了一个纯CPU和纯GPU的版本作为比较对象。测试发现,Dorylus最多能比GPU的性价比高4.83倍,比CPU高2.75倍。如Amazon这样更加稀疏的数据集能够更好地体现Dorylus在性价比上的优越性。

不同模型在不同数据集上的性能表现和成本,如下表所示:

GNNAdvisor: An Adaptive and Efficient Runtime System for GNN Acceleration on GPUs

Yuke Wang, Boyuan Feng, Gushu Li, Shuangchen Li, Lei Deng, Yuan Xie, and Yufei Ding, University of California, Santa Barbara

总的来说,GNN也是一种在图结构上,针对图信息进行分析的方法。与传统的图算法比起来,其计算涉及到图结构上的特征向量(feature vectors, 文中又名embeddings)的计算。现有的GNN计算系统,可以被总结为两类:第一类基于传统的图处理框架,在这些框架上扩展神经网络计算相关的功能;第二类与第一类相反,是基于神经网络框架,来支持图结构相关的计算。然而,这两类框架都具有一定的缺陷:没有针对输入的GNN模型的多样新进行区别优化;优化通常针对传统的图算法,并没有针对GNN的特点进行优化;在一些参数设置上通常是硬编码的,而一些和性能相关的信息(如节点的度以及embeddings的长度等)只有在运行中才能获取,因此不能够很好地利用这些信息进行优化,导致不能很好地利用GPU的资源。

因此,作者提出了GNNAdvisor,一个用于在GPU上做GNN加速的运行时系统。GNNAdvisor的总体架构,以及利用GNNAdvisor编写的2层GCN模型如下图所示:

如图所示,GNNAdvisor包含以下几个部件:GNNAdvisor使用PyTorch作为前端;Loader&Extractor对GNN模型和图结构这样输入级的信息进行分析来指导后续的系统级优化;Decider用于自动地对运行时参数进行选择;Kernel & Runtime Crafter动态地对GNN计算kernel进行划分和指定,主要涉及到2D Workload Management和内存优化两方面的方法。后文将对GNNAdvisor的各个部件进行具体介绍。

首先是对于输入级信息的提取和分析。GNNAdvisor主要关注两类信息。第一类为图相关的信息,如节点的度数,embeddings的维度,以及graph community的信息(即和节点邻居相关的信息)。传统的图系统通常让各个节点先load自己的邻居节点数据,然后再独立进行update。但是,GNN的每个节点包含的embeddings中包含了诸多数据,传统方法会引入许多不必要的数据加载。因此,以graph community为粒度进行统一load,可以减少这些不必要的数据加载。如下图所示:

第二类关注的输入信息为GNN模型相关的信息,包括邻居节点aggregation和节点update的相对顺序,以及aggregation的方法,如sum或mean等。以GCN和GIN模型为例,它们的输入层维数都比隐含层的维数高。GCN先做node update,在做aggregation,这样aggregate的时候其维数就是update之后的隐含层的维数;而GIN先做aggregation,因此其维数是输入层的维数。因此,GCN应该对应embeddings维数较小的优化,而GIN则更偏向于embeddings维数较高的优化。

然后,是Kernel & Runtime crafter使用的2D Workload Management技术。2D Workload Management主要分为两部分,第一部分是粗粒度的邻居划分,主要将节点的邻居节点划分为大小相等的组:Neighbor Group (NG),以NG作为workload的基本单位。这样划分,和更粗粒度的划分方式比起来,缓解了图结构skew导致workload大小不规则的特点;和更细粒度的划分方式比起来,减少了管理大量小的workload的开销。如下图所示:

2D Workload Management的第二部分是细粒度的embedding上的划分,如下图所示,一个NG的embeddings被均匀分配到11个连续的线程上,每个线程单独处理一个dimension,用多次循环来处理所有的dimension。

2D Workload Management的前两个部分主要解决如何划分GNN的workload,而接下来则是解决如何将划分好的workload分配给具体的GPU硬件。GNNAdvisor提出了Warp-based Thread Alignment方法,如下图的下半部分所示,将单独每个NG分配给一个单独的warp,尽量避免同一个warp中的thread divergence。同时,不同的warp访存不会相互影响,因此这种方法还可以提高访存的并行度。

Kernel & Runtime crafter还对内存进行了优化。首先,如下图所示,GNNAdvisor利用graph community的相关信息,对图结构的邻接矩阵进行了renumber,来提高其空间/时间上的locality。此外,GNNAdvisor根据warp在block-level的组织结构对GPU共享内存的layout进行了定制化,从而降低了访存的所需的原子操作的数量。

上文提到的2D workload management里面的NG大小和workload per thread都是可以被定制化的参数。Decider根据Loader&Extractor输入的信息和硬件相关的信息,来自动指定上述优化的相关参数。

测试显示,与DGL相比,GNNAdvisor能够在GCN和GIN两种GNN模型的inference上分别达到4.03x和2.02x的性能提升:

在模型训练上,GNNAdvisor能够在GCN和GIN上分别达到1.61x和2.00x的性能提升,如下图所示:

P3: Distributed Deep Graph Learning at Scale

https://www.usenix.org/system/files/osdi21-gandhi.pdf​www.usenix.org/system/files/osdi21-gandhi.pdf

微软做的一篇关于分布式GNN training的work。重点也是关注在大规模分布式的GNN训练下,如何将最重的第一层的计算分摊在所有机器上,从而隐藏掉大量的机器由于feature网络通信带来的性能下降。因为GPU的利用率下降的主要原因也在于mini-batch下网络通信成为瓶颈,后续训练数据不能及时的到GPU进行计算,所以这种方式也可以自然的提升GPU的使用率。

Background: GNN训练不同于传统DNN网络的的一个关键点是: DNN训练数据之间都是独立的,而图结构则是呈现一定的关联性。而且,对于图上的顶点和/或边,一般也都会带有一些高维的feature。一个K层的GNN网络,对于每个节点,都需要1-hop到k-hop的邻居构成该节点的computation graph。所以一般会使用neighborhood sampling来进一步降低整个computation graph的大小,来使其能够fit进GPU里。而且对于biliions node and edge级别的图,分布式训练就不可避免了。

P3的设计主要基于以下三个观察:

  1. 由于data dependency, 在分布式情况下,生成训练computation graph的网络通信占据了整个训练时间的主要部分。
  2. 一些分布式图处理技术比如partition 对于GNN的训练并没有很大帮助。partition的overhead很大,而且一般都是在关注在相邻结构的信息,对于GNN这种需要关注多hops的workload可能并不适用。
  3. 在GNN训练中,GPU的利用率非常低,80%的时间都因为通信的的瓶颈而被堵塞,导致闲置。

P3系统设计Pipelined Push-Pull:

一. 将图的topology和feature分别存储,并且feature按列切分。

图的topology hash partition没什么好说的,把每个点hash 映射到某台机器,同时存储以该点为起始点的edge。但是这个feature的切分相当于对于每个worker,都有完整所有点的feature的某些维(对于一个F维的feature,假设存在N台worker,那么每台机器存储F/N维feature),不需要关心任何图分布的问题。

二. P3的Push-Pull 机制

1. 对于Computation Graph的生成。

采用正常的pull base的机制。从起始seed node开始,每次向外拉取之间相连的邻居的node,重复K次,构建整体K-hop neighborhood信息,不过,这里只需要拿到对应的图结构信息,并不需要拉取对应的feature,所以整体的网络消耗会非常低。

而为了避免正常的拉取全部feature所带来的巨大的网络消耗,这里采用了hybrid parallelism approach(model parallelism + data parallelism), P3将其命名为 push-pull parallelism。

2. 对于Computation Graph的执行。

这里将整个执行流程拆成8步:

  1. 将每台机器生产layer 1computation graph推到所有worker上去。
  2. 因为将feature均匀分布在不同机器上,这里每台机器拿到layer 1computation graph以后就可以开始进行前向传播,拿到一个partial activations结果。 这里就可以类比于model parallel, 因为每台机器只有F/N维的feature,所以相当于只用到了第一层网络的( F/N * Hidden size )个参数。这里因为没有什么网络消耗,所以GPU的使用率(非空闲时间/总时间)也大大提高。
  3. 然后将所有在其他机器的partial activations结果aggregate起来,做一个reduce操作,就得到了对应的第一层输出。
  4. 当然可能也存在一些不可聚合的函数,例如ReLU函数。所以这里后面还需要接一个Layer 1D,进一步的转化结果。后面就可以进行正常的data parallelism。
  5. 最后经过后面K-1层网络,得到最终的embeding,计算出loss,完成前向传播。
  6. 然后到layer 1D之前,完成正常的data parallelism,更新对应的网络参数。
  7. 之后push对应的error gradient到所有机器,又切换回model parallelism。
  8. 然后进行本地对应的模型参数更新。(本地的部分feature对应的部分模型参数。)

为什么说这样做可以节省网络开销呢?

1. 相当于减少了一个hop的feature通信。

2. 只有第一层会这样partially computed and aggregated.

具体的例子:

(我算了一下,它这里的71MB = 188339 * 100 / 1024 / 1024 * 4 相当于把所有点的feature都走网络拉取)。

3. Pipelining

之前的一些设计都是为了减少网络消耗的时间,为了更好的overlap 通信和计算过程,P3采用了类似于PipeDream的pipeline机制。

首先,根据之前的设计,可以将一个mini-batch的计算转化为4个阶段,

  • 1. Model Parallelism in Forward
  • 2. Data Parallelism in Forward
  • 3. Data parallelism in Backward
  • 4. Model parallelism in Backward

然后,根据4个阶段,同时3个minibatch进行计算(没看过pipedream,不知道知道3是怎么算出来的)。可以让两个forward阶段和两个backward阶段同时进行。然后论文也证明了一下这样同时三个minibatch进行计算不会影响收敛和内存消耗并不大。

4. Cache

感觉这里就不是每台机器F/N的feature了。而且尽可能的放,然后再 replicate。看描述都是针对于host memory的,没有什么GPU cache的操作。(sad

5. P3 API

没啥好说的,感兴趣自己了解一下吧。

实验:

(大家现在都是在疯狂的搞大图,但是一般都是随机产生feature。)

因为没有拉feature,只有图结构,所以 computation graph非常快,然后因为其他额外的push-pull操作,导致data copy和compute的时间稍稍有些增加。

讨论和思考 :

1.整体理解下来,设计还是比较清晰的。核心思路就是通过将第一层的计算分散在所有机器,减少网络开销。对于网络消耗,我又仔细想一想,整理了一个公式。n为集群的规模。(理想情况,应该是忽略了一些细节。)

网络传输总量减少网络传输总量减少=k_hop∗feature_dim∗n−1n(k−1_hop)∗hidden_size∗(n−1)=k_hop(k−1_hop)∗feature_dimhidden_size∗1n
首先第一项就是看 k_hop(k−1_hop),第k层邻居数量和第k-1层邻居的数量,所以图的density和fanout的参数设置也可能对于性能的提升也有一定的影响,如果图比较稀疏或者对于深层的节点fanout比较小,那可能性能提升的会变少。
第二项 feature_dimhidden_size 就是5.10讨论的hidden size的问题了。但是根据RoC的观点,实际上layer和hidden_size的增加应该是会让模型性能进一步提升的。所以,这个缺点感觉还挺值得好好思考一下。
第三项就是集群的规模。因为P3其实不论每台机器分到多少feature,由于model parallelism,所以要传输的数据大小是不变的,这样就导致,如果集群数量提升,那么增加的网络消耗是线性的,但是论文实验并没有显示出来(甚至throughput还是线性增长),不过论文中还是谈及了,良心。(还是 n 从2 到 4 变化不太够)

2. 整体GPU使用率提升了,但是利用率看起来还是不太够。这个方向可能还是有很大的提升空间。

3. 以及我还是对它觉得graph partition不行这种观点不太赞同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值