\u200E
飞桨框架v2.3一键开启全自动性能优化,媲美专家级优化效果
发布日期:2022-08-18T03:06:50.000+0000 浏览量:1687次

2022年5月,飞桨框架2.3版本正式发布,创新性地提出了业界首个全流程硬件感知的性能自动调优解决方案,支持“一键开启”的极简操作方式,使开发者无需对模型代码进行任何改动,就可以充分发挥框架和硬件的极致性能。大量模型实验表明,自动调优可达到持平甚至超越经验丰富的性能优化专家手工调整的效果,解决了开发者性能优化费时费力的问题。

深度学习模型的训练往往需要耗费大量机器资源和时间,因此众多AI开发者非常迫切地追求极致性能。通过性能优化,一方面可以缩短模型效果验证的周期,提高研发效率;另一方面也会节约大量的资金成本并有效减少碳排放。例如OpenAI设计并发布的GPT-3模型,据估算一次训练成本高达400余万美元,以致于即使研发人员在训练过程中发现了一个错误,但由于成本原因也并未进行修复。

考虑到性能提升带来的巨大收益,目前主流深度学习框架都会结合硬件提供各种加速方案,比如多进程异步数据读取、自动混合精度、更优的算子实现等等。但由于模型结构的多样性和实际运行环境的复杂性,统一的加速方案往往无法发挥出极致的性能,比如数据读取的进程数设置高度依赖CPU配置,设置不当反而会降低性能;再比如混合精度虽然能加速,但需要模型在数据布局和大小方面符合硬件的特定要求,一旦开发者没有注意到这些限制,即使通过框架开启了混合精度的逻辑,也无法起到实际加速效果。因此,长期以来,开发者要想发挥框架的极致性能,需要对各种加速策略的原理甚至框架实现机制都有非常深入的了解,并且需要根据模型和硬件环境反复调整尝试,甚至还需要用到专业的性能分析工具才能确认瓶颈点。这无疑给开发者带来了巨大的学习成本以及调试成本,特别是在更换硬件后(比如使用了其他型号的CPU或GPU等),可能还需要重新分析和调整才能达到最优性能。

针对开发者在性能优化方面的痛点问题,飞桨框架2.3版本依据模型整体性能关键点推出了业界首个全流程自动调优方案,在训练时框架可根据不同模型针对不同硬件自动调整运行配置、选择最优算子内核(Kernel)等,使模型获得最佳性能,其效果可以与经验丰富的性能优化专家持平,很好地解决了在各种硬件平台针对不同任务的模型手工调优难的问题。

模型训练主要性能瓶颈分析

通常我们会以训练精度、训练速度、所需资源来评价一个模型训练性能的优劣。在模型训练收敛的迭代次数不变的情况下,训练速度可以用吞吐量(Instances Per Second,IPS)即每秒处理的样本数目来直观表示。模型的吞吐量会受到多种因素影响,我们首先从硬件层面来分析,看看是哪些情况影响了硬件的执行效率。以下面训练任务的时间轴(timeline)示意图为例,我们可以将常见的性能瓶颈归类为3个方面:

I/O瓶颈:

在模型训练流程中,首先会将磁盘上存储的数据样本读取到CPU内存中,一般还会在CPU上完成数据的预处理,最后将处理好的数据从CPU内存拷贝到GPU内存中,由GPU完成计算。如下图所示,当数据处理过程较慢,则会引起GPU计算等待(图中 reader_cost 部分),造成I/O瓶颈。

CPU瓶颈

由于GPU运行速度较快,如果CPU端处理任务耗时太久,造成GPU等待,就会拖慢训练速度。如下图示例中Op3的CPU处理时间过高,导致了这段时间内GPU在等待CPU处理结束,从而影响了性能。

GPU瓶颈

虽然GPU提供了很高的算力,但如果GPU算子实现不够高效,也会导致性能较差。如下图示例中Op4的GPU处理时间明显很长,可能存在较多的优化空间。

明确了训练任务的性能瓶颈后,我们再来看看在框架层面以及模型层面有哪些关键点是影响速度的主要因素。通过对各类模型实际训练情况的深入分析,我们总结了影响性能的5个关键点:
  • 数据读取

很多框架都提供了多进程数据读取的功能,但进程数的设置完全依赖用户经验,设置不当反而还会导致性能变差

  • 配置参数

尤其在使用混合精度训练时,需要使Tensor的布局和大小等设置满足硬件限制,目前需要开发人员手工调整

  • 算子性能

大多数框架基本都会用cuDNN、cuBLAS等高性能加速库,但是在各种硬件、各种尺寸和数据类型的场景下,单一加速库也难以保障性能保持最优,不同场景下可能需要使用不同的算子实现,这也需要开发者有足够的经验以及分析能力才能确认

  • 框架调度

一些模型计算开销并不大,这种场景下框架执行调度的开销会对模型性能产生明显影响

  • 模型实现

有些模型实现中可能引入了无关操作,或者未使用一些算法策略加速等,由于完全依赖开发者的实现方式,框架层面一般没有太好的解决办法
通过进一步分析,我们发现数据读取、参数配置和算子性能这三点对模型训练性能的影响范围最大,约50%以上模型的主要瓶颈都与这3点有关。从影响幅度来看,数据读取、模型实现和参数配置对模型性能的影响幅度超过1倍。因此,我们尝试通过自动化的方式来对上述这几个方面进行优化,减少对开发者经验的依赖,降低开发者手工调优的成本。

多层次性能自动调优技术

在不影响模型训练效果的前提下,针对影响范围较大的数据读取、配置参数和算子性能这几个关键问题,飞桨框架2.3版本实现了3个层面的自动调优,其核心技术是通过收集运行时信息、资源使用情况,自动选择最优配置。
  • 数据读取参数自动调整

针对不同硬件环境下默认的DataLoader参数配置无法发挥硬件最优性能的问题,在训练开始前,飞桨根据模型的输入数据、训练的硬件对DataLoader参数自动搜索,在训练过程中,使用调优后的配置读取数据。

  • 数据布局(Layout)自动切换

针对不同硬件环境下开发者需要手工调整数据布局才能充分发挥硬件加速效果的问题,在训练过程中,飞桨自动选择最佳数据布局,并实现用户无感知的数据布局自动切换。
  • 多版本算子Kernel自动选择

针对各种数据类型、输入尺寸和配置参数下,单一的算子内核难以保证在所有场景下达到最佳性能的问题,飞桨引入调优区间的概念,将整个训练过程分为观察阶段、调优阶段、应用阶段。观察阶段,使用默认实现便于用户调试;调优阶段,使用穷举搜索法选择代价最小的Kernel并进行缓存;应用阶段,依据算子配置从缓存中获取算法,未命中则使用默认实现。基于该功能,模型在输入尺寸固定或动态变化的场景下均可通过算法搜索获得性能提升。


数据读取参数自动调优

目前主流深度学习框架都在数据读取方面进行了底层优化,比如通过多进程并发处理数据读取、预处理以及组batch等任务,通过共享内存加速进程间数据传输,通过异步操作来消除数据拷贝过程的耗时,从而实现异步、并发的方式加速数据读取、预处理和数据拷贝等数据I/O任务。显然,并发对数据读取的性能有很大作用,但大多数框架对并发度的设置(num_workers)仅提供了一个基础的默认值,比如飞桨和PyTorch的默认值都为"0",即仅采用主线程读取数据,此默认设置难以在各种硬件环境下达到最优的性能。考虑到机器设备的CPU核数量有限,数据读取的并发数并非越大越好,过大的并发数会加重CPU进程调度的负担,导致整体性能反而降低。由于最优并发度的设置与硬件相关,因此大多数框架目前都将此设置交给开发者决策,尽管开发者可以通过手工调整提升读取性能,但仍需多次测试才能找到最合适的num_workers参数,无疑增加了模型训练的操作成本。

为了方便开发者在训练模型时获得最佳的数据读取性能,飞桨框架2.3版本设计了一套自动调整num_workers参数的机制。模型启动训练时,框架会自动地从模型配置文件中读取num_workers参数,从设备读取可用CPU核数,同时从训练数据集中随机抽样构建子集,并在正式训练开始前完成最优参数的搜索过程,整个搜索过程可以秒级完成,基本达到开发者无感知的状态。具体来说,开启数据读取自动调优后,飞桨会采用 "爬山法" 反复调整读取数据子集时的num_workers参数,并统计读取时间开销,直至得到最佳参数值为止。随后,框架自动替换num_workers参数并执行实际训练过程。

计算机视觉类的任务由于图像数据的读取和预处理开销较高,如果DataLoader未启用多进程异步数据读取,则非常容易出现I/O瓶颈。以目标检测的JDE模型为例,通过性能分析我们发现模型的数据读取开销占比高达51.71%,使用自动调优功能后,模型的数据读取降低至0.56%,同时吞吐量从原始的 9.14 samples/s 提升到13.60samples/s,性能提升了49%。


数据布局(Layout)自动调优

深度学习框架中针对多维数组通常支持通道在前(NCHW)和通道在后(NHWC)两种格式,在不同硬件上数据排布方式的选择会对性能产生较大的影响。下图实验是Conv算子在不同数据布局和数据类型下的前向运算耗时,结果表明在V100 GPU上固定输入、输出通道数及卷积核尺寸的前提下,FP32计算采用NCHW数据布局性能最优,而FP16计算则是采用NHWC最优。因此当一个使用NCHW数据布局的FP32模型想要开启混合精度模式来进行加速的时候,要么需要开发者手动对数据布局进行调整,要么需要额外在相关算子前后插入Transpose操作对数据布局进行转换,但这样又会带来额外的转换开销。

目前主流的框架对于API默认的数据布局设置都是NCHW,但在混合精度训练的大量模型案例中,我们发现开发者经常由于不了解加速限制而未使用最佳数据布局,导致虽然从框架层面启用了混合精度,但仍然没有加速效果。另外,为了充分发挥性能,模型中的算子最好使用统一的数据布局,以避免算子之间频繁发生数据布局转换。飞桨数据布局自动调整的策略会在模型运行过程中,首先获取原始的数据布局配置和运行设备特性,然后依据先验知识确定最佳数据布局,根据对数据布局的敏感度,在算子运行前执行转换过程,最后使用转换后的最佳数据布局作为输入完成算子的计算。

下图是数据布局自动调优的示意图,以ResNet50模型为例,根据算子计算功能和性能对数据布局的敏感度,我们设计了3类转换规则:
  • 功能和性能与数据布局强相关的算子:

例如Conv,这类算子会在输入不是最佳数据布局时,将输入转换为最佳数据布局:

  • 功能与数据布局强相关,性能影响较弱的算子:

例如Flatten,由于计算过程涉及到张量维度配置,其功能会受数据布局影响。根据输入的数据布局,可以对start和stop的axis做调整达到等效的结果,而在无法通过调整算子参数达到等效时,则需要将输入转换回原始的数据布局进行计算:

  • 功能和性能与数据布局无关的算子:
例如Relu,这类算子无需做数据布局的转换,只需要将输入的数据布局传递给输出。

在自动调优的过程中,飞桨会引入最小的转换代价使模型在最佳数据布局下运行,换取更大的性能收益。使用V100 GPU进行ResNet50的混合精度训练,在模型默认数据布局为NCHW的情况下,开启自动调优后吞吐量提升了26.8%,与手工调优性能持平。


多版本算子内核自动选择

为了充分发挥硬件性能,各个硬件都会提供高性能加速库,比如NVIDIA GPU提供了cuDNN,cuBLAS等多个加速库。此外,还有很多第三方高性能加速库提供了跨平台高性能实现,比如TensorFlow和飞桨都采用Eigen来实现部分算子。但由于各个任务中模型的复杂性,各个算子的配置也会出现各种情况,因此某一个加速库也经常存在无法在所有配置和场景下都达到最优性能的情况。针对这种情况,一些框架在算子实现层面,除了引入高性能库提供的版本外,还会增加一些补充实现,来覆盖高性能库实现性能不佳的场景。比如深度学习模型中常用的AdaptiveAvgPool2D算子,在采用cuDNN实现时,大部分场景下都能达到很好的性能,但我们发现对特定shape的Tensor(如[408, 64, 112, 112] 等)进行计算时,cuDNN提供的前向计算性能非常好,但是反向性能较差,耗时约为飞桨中提供的基于CUDA自有实现版本的2倍,对于这种情况,很难通过框架层面统一设置是否使用cuDNN来达到最优性能。而且开发者需要使用专业性能分析工具,才能确定不同内核实现在具体配置下的性能差异,然后手动指定内核选择方案,这样的性能优化成本显然对绝大多数开发者而言是无法接受的。
此外,计算机视觉类任务中最常用的Conv算子,cuDNN接口内封装了多种算法类型,在不同的输入条件下性能不一,并且提供了穷举式搜索和启发式搜索两种算法选择模式。针对数据集尺寸变化范围小的场景可使用穷举搜索功能,对每一种Conv算子的输入获取对应的最高效算法,提升计算速度;但当数据集的尺寸变化范围广时,穷举式搜索则会引发大量的算法选择开销,反而降低整体训练速度。因此大多数框架仍然将决策权留给了开发者,需要开发者通过经验或反复尝试来确认是否开启,不仅增加了开发者的调试成本,而且一旦设置不当,还会导致巨大的性能损失。
针对上述问题,飞桨框架2.3版本提供了一套算子内核自动选择的机制,能够充分挖掘算子的性能潜力,实现手写CUDA Kernel、硬件加速库Kernel、第三方高性能库Kernel等多版本Kernel的动态选择,在各种输入条件下,都能选取到最优 Kernel,同时做到了整个过程对开发者透明的使用体验。

在实现方案上,算子内核自动选择功能由观察阶段、优化阶段、执行阶段组成,各阶段的主要功能如下:

观察阶段

全部算子走入默认算法,保证确定性的计算过程,确保开发者在开发和调试过程的体验与通常开发一致。

优化阶段

当训练过程进入优化step区间时,自动地从算子层和框架层2个方面展开优化:
  • 算子层

框架自动采用穷举计算模式,针对当前算子的输入调用全部可用算法计算,记录并比较各算法的性能,从中选出最高效算法后,利用Cache机制将当前计算场景与被选中算法进行绑定,保证当前计算场景在后续复现时,算子内可以立即查询Cache并执行最高性能算法。

  • 框架层
飞桨会在优化阶段的每个step计算结束后,统计并实时反馈当前的OP平均Cache命中率,直到达到优化的目标step后结束优化过程,转入执行阶段。

执行阶段

根据cache信息调用最佳算法完成算子计算,若在后续计算中面对数据变换等情形时,则走入默认计算分支,避免庞大的算法选择开销。

算子内核自动选择机制可有效应对各类变尺度数据集训练场景,挖掘算子内各算法性能潜力,在各类训练场景下获得良好的性能收益。例如PaddleDetection套件中数据shape变化非常大的JDE模型,可以发现开启算子内核自动选择功能后,模型的训练性能可以获得大幅提升,且超越了手动设置cuDNN启发式搜索模式等各种手动设置的性能。

此外,飞桨提供的算子内核自动选择机制在设计上不仅覆盖了框架原生的多种算子实现,还覆盖飞桨深度学习编译器中生成和优化的算子,结合编译器对算子的自动优化能力,可以达到更好的性能。在ResNet50模型上的测试表明,性能已经可以持平业界最优编译器优化的结果。

一键开启自动调优


飞桨框架2.3版本可以通过以下方法:

paddle.incubate.autotune.set_config(config=None)

一键开启所有自动调优功能。也可以根据调试的需求自定义调优功能的开和关:

  • kernel

当开启自动调优,将使用穷举搜索的方法在调优的迭代区间内为算子选择最佳算法,并将其缓存下来。可以通过enable参数开启kernel的调优功能,如果需要调试则可以通过tuning_range参数设置自动调优开始和结束的迭代区间。
  • layout

当开启自动调优,将根据设备和数据类型确定最优的数据布局如NCHW或者NHWC。当原始的layout设置并非最优时,将会自动进行layout的切换以提升模型的性能。
  • dataloader

当开启自动调优,将自动选择最佳的num_workers替换原始的配置。
config = {
    "kernel": {
        "enable"True,
        "tuning_range": [15],
    "layout": {
        "enable"True,

    "dataloader": {
        "enable"True,

paddle.incubate.autotune.set_config(config)

通过实验对比,飞桨框架自动调优后的训练性能,可以持平或超越专家级手工配置优化的效果,而且在不少模型上相对飞桨或其它框架的默认配置都有较大幅度的性能提升。

结语

飞桨框架2.3版本发布的多层次性能自动调优技术,可以一键开启优化,自动适配不同硬件环境,大幅降低开发者性能优化所需的技术门槛,自动调优后的训练性能可以达到与专家级手工配置优化持平或超越的效果,发挥框架的极致性能。当下,飞桨正进一步强化自动调优能力,以期在更多的模型和硬件场景下发挥更显著的作用。未来会将多层次性能自动调优功能默认开启,并加入更多的自动调优策略,期待更多的开发者能够应用此功能提高模型训练效率。


拓展阅读




关注【飞桨PaddlePaddle】公众号

获取更多技术内容~