Opara:一种基于算子并行性的DNN推理加速框架
Opara:一种基于算子并行性的DNN推理加速框架
Opara是一种资源感知和干扰感知的DNN Operator并行调度框架,用于加速GPU上的DNN推理。它由模型分析器、算子启动器、流分配器和图捕获器四个组件组成,通过优化算子执行顺序和GPU资源利用,显著提升了DNN推理性能。
Opara快速解读
Opara接收用户提供的深度神经网络(DNN)模型和输入张量(即推理数据)。根据DNN模型中算子依赖的有向无环图(DAG),流分配器首先通过流分配算法确定算子应分配到的执行流。随后,模型分析器在模型分析过程中收集每个算子的资源需求。基于这些资源需求,算子启动器进一步采用资源感知的算子启动算法优化GPU上算子的启动顺序。最后,图捕获器结合流分配方案和算子启动顺序,生成并行化的CUDA图,从而实现高效的DNN推理。
Paper概况
总体动机
主流深度学习框架中DNN算子传统的顺序执行模式,由于模型结构日益复杂,算子种类繁多,即使启用算子融合,也无法充分利用GPU资源。此外,并行执行场景中算子启动顺序不足可能会导致GPU资源浪费和算子之间意外的性能干扰。
背景与需要注意的问题
- 在传统方式中,每个内核(kernel)都需要独立启动,并在CUDA流中按顺序排队,这会产生较高的启动开销。每次启动内核都涉及CPU与GPU间的交互,如配置、同步和数据传递,这些过程积累起来会造成时间损耗。CUDA Graph允许将多个内核和操作符放在一个执行图(execution graph)中,一次性启动多个操作符,从而减少了启动次数,显著降低了总启动开销。
- 主流的深度学习框架按照拓扑排序顺序依次执行DNN操作符,这种顺序执行无法充分利用GPU资源【论文中作者通过实验证明】。且算子启动顺序不足可能会导致DNN推理延迟高达29%,这是由于CUDA内核的非抢占特性造成的GPU阻塞以及可并行算子之间的性能干扰。
技术路线
Opara首先使用CUDA Streams和CUDA Graph自动并行执行多个算子。为了进一步加快DNN推理速度,Opara利用算子的资源需求明智地调整GPU上的算子启动顺序,重叠计算密集型和内存密集型算子的执行。我们以非侵入的方式实现并开源了基于PyTorch的Opara原型。
结果
在代表性的DNN和基于Transformer的模型上进行的广泛原型实验表明,Opara的性能分别比PyTorch中的默认顺序CUDA图形和最先进的算子并行系统高出1.68倍和1.29倍,运行时开销仍可接受。
流分配算法(流分配器)
提出了一种轻量级流分配算法,无需对计算图进行任何修改或转换。如果算子没有依赖关系(即其执行不依赖于其他算子的输出),可以独立执行,则这些算子会被贪心算法分配到多个CUDA流中并行执行,从而利用GPU的多流特性最大化算子并行性。
如果算子之间存在数据依赖关系(即一个算子的输出是另一个算子的输入),它们必须按顺序执行。为了避免额外的同步开销,这些算子会被分配到同一个CUDA流中,这样它们会按顺序在同一流中执行,无需显式的同步操作。
模型分析器
在GPU上,通常将任务划分成多个块(block)来并行处理。每个块内的线程(thread)执行的是相同的代码,只是每个线程处理不同的数据。这种执行方式叫做SIMT(Single Instruction, Multiple Threads),即单一指令多线程执行模式。尽管每个块处理不同的数据,但它们执行的指令相同,因此它们所需的GPU资源(如线程数、内存等)也会是相同的。这种特性有助于在GPU上高效地进行并行计算,因为可以较容易地进行资源的调度和分配。
资源和干扰感知的算子启动算法(算子启动器)
算子启动命令不足会显著影响DNN推理延迟。为了确定最佳启动顺序,简单的解决方案是迭代模型DAG的所有可能的拓扑排序顺序,并选择推理延迟最低的顺序。然而,这种方法涉及选择入度为零的节点并删除相应的顶点及其连接的边。假设模型DAG中存在n个算子,则遍历所有拓扑排序顺序的时间复杂度为O(n!),这也是一个NP难问题[17]。因此,转而设计一种启发式算子启动算法来解决这样一个复杂的问题。
文中设计了一种资源和干扰感知的算子启动算法,明智地优先启动具有少量GPU资源需求的算子,从而有效减轻GPU资源碎片和性能干扰,同时减少DNN推理延迟。算子的此类资源需求在实践中可以通过轻量级推理分析来获得。为减轻操作符之间的性能干扰,算法还将计算密集型和内存密集型操作符的执行重叠,利用离线收集的操作符分类表区分操作符类型。
Opara
实现了Opara的原型(https://github.com/icloud-ecnu/Opara)作为PyTorch 2.0的插件模块,以并行化DNN算子的执行。它可以通过捕获流分配计划和优化的算子启动顺序来生成并行化的CUDA Graph,以减轻算子启动和函数调用的开销。
实验
在NVIDIA A100-PCIe-40GB GPU和NVIDIA GeForce RTX 2080 SUPER-8GB GPU上进行实验。我们基于CUDA 11.7、cuDNN 8.5.0实现Opara,并作为PyTorch 2.0的插件模块。我们的实验采用了6个代表性的DNN模型,包括Inception-v3[5]、GoogLeNet、DeepFM[18]、NASNet5、BERT和T56。前三个模型在RTX 2080 GPU上执行,后三个模型在A100 GPU上运行。
实验包含5.2 Opara的有效性【GPU利用率和内存消耗、操作执行时间、Opara在基于Transformer的模型上的有效性、不同批量大小下的吞吐量、Opara在资源充足的高端GPU上的有效性】;5.3 Opara的运行时开销。
图5(a) - 端到端推理延迟:
- Opara相较于五个基线模型(如PyTorch、CUDA Graph、ONNX Runtime等)展示了显著的推理速度提升,特别是在六个代表性DNN模型上,Opara的加速比最高达到10.97倍。
- Opara通过利用CUDA图消除了算子启动和函数调用的开销,并通过算子并行化进一步加速了模型推理,克服了操作符融合无法最大化GPU资源的局限性。
图5(b) - GPU利用率和内存消耗:
- Opara在GPU利用率上的表现明显优于五个基线模型,特别是在与默认CUDA图比较时,Opara在多个模型上提升了GPU利用率,最高提升为126%。
- Opara通过并行执行算子和优化算子调度顺序,减少了GPU空闲时间,显著提升了GPU的使用效率。此外,尽管并行执行需要更多内存,但Opara通过高效的调度平衡了GPU资源和内存消耗。
图6 - 算子执行时间线:
- 图6展示了Opara在推理过程中的算子执行时间线。与CUDA图和Nimble相比,Opara通过更多的流和合理的算子调度,最大化了算子的并行性,显著减少了推理延迟。
- Opara避免了Nimble中由于较少流导致的GPU空闲时间,以及CUDA图中算子按序列执行导致的长推理时间。
图7(a) 和 7(b) - 处理Transformer模型时的优化效果:
- 图7展示了Opara如何通过优化算子调度和算子特性(如计算密集型算子与内存密集型算子的重叠执行),进一步提高推理速度。特别是在T5模型中,Opara通过并行化调度和资源优化,提升了推理速度。
- 对于BERT模型,虽然Opara没有获得比Nimble显著的提升,但与PyTorch和CUDA图相比,Opara依然实现了1.08×到4.06×的加速。
图8 - 不同批次大小下的吞吐量:
- 图8验证了Opara在不同批次大小下的吞吐量优势,特别是在批次大小为1和32时,Opara相较于默认CUDA图分别提升了1.41×和1.09×。这说明Opara的算子并行化和资源优化策略可以有效提高吞吐量,并且在较大批次大小下依然能够保持一定的性能提升。
图9 - 高端GPU(A100)上的性能提升:
- 图9显示了Opara在高端GPU(如A100)上的性能提升,尤其在批次较大的情况下,Opara实现了1.47×到1.18×的推理加速。由于A100提供了更多的计算资源,Opara能够进一步发挥算子并行化的优势,超越其他基线方法。
表1 - 流分配算法的效率:
- 表1展示了Opara流分配算法的计算效率。与Nimble相比,Opara在流分配算法中的计算时间显著减少,表明Opara在算子调度和流分配上的高效性,能够有效减少推理延迟。
结论
本文介绍了Opara的设计与实现,这是一种轻量级操作符调度框架,旨在加速GPU上的DNN推理。通过减少操作符之间的同步开销,Opara设计了一种流分配算法,能够自动将无依赖关系的操作符分配到不同的CUDA流中,从而实现高效的操作符并行。此外,Opara利用非侵入式的推理分析,精确选择合适的操作符启动顺序,以减轻干扰并最大化GPU利用率。大量原型实验表明,与最新的操作符并行系统相比,Opara能将DNN推理性能提高最多29%。未来,我们计划在以下几个方向上扩展Opara:(1)构建分析模型以研究操作符间并行导致的性能干扰;(2)探索Opara在加速更大规模模型(如GPT-3、LLaMA)方面的有效性。