深度学习模型编译技术

举报
ross.xw 发表于 2022/05/06 11:19:25 2022/05/06
【摘要】 从编译优化的角度来分析一下如何进行深度学习模型推理优化。

前言

深度学习模型的开发周期,包括训练阶段和部署阶段。训练阶段,用户需要收集训练数据,定义自己的模型结构,在CPU或者GPU硬件上进行训练,这个过程反复优化,直到训练出满意精度的模型。有了模型之后,我们需要将模型服务部署运行,我们期望服务延迟越低越好,吞吐越高越好。这里会从编译优化的角度来分析一下如何进行深度学习模型推理优化。

1. 推理优化现状


推理优化现状.png

1.1 原生框架部署 - TF/PT

模型使用原生框架进行推理非常便捷,一般不需要进行太多环境适配和开发工作就能将模型部署起来,对于开发者非常友好。但是训练框架(目前主流的TensorFlow/PyTorch/MindSpore等)为了训练开发的便利,存在大量琐碎的算子,使得在batch比较小的时候,推理的性能不是那么高效。

为了提高模型推理的性能,业界近几年开始不断研究模型编译优化技术,旨在不同硬件不同场景中自动的优化推理性能。目前大体可以分成两个大的方向:推理引擎和推理编译器。两者有很多相通的地方,也有各自独特的优势。

1.2 推理引擎 - Library

在人工智能发展的浪潮中,许多公司推出了各家代表的推理优化解决方案,例如NVIDIA基于自家显卡的TensorRT,Intel基于自家处理器的OpenVINO,Huawei基于昇腾芯片的MindSporeLite等等。这些推理引擎往往以library的方式供用户使用,将模型的IR转化为引擎内部的IR表示,然后映射绑定到对应硬件的算子实现,最后使用引擎的runtime来启动推理计算。基于自家的处理器和高度定制的算子实现,推理引擎能够达到非常极致的优化性能。但是推理引擎的架构决定了不能很好的覆盖所有原生框架的算子(模型转化+算子实现覆盖度),在一部分模型推理优化中需要一定的算子开发量,往往也需要有经验的优化工程师的参与才能达到比较好的性能。

为了解决推理引擎算子覆盖度和不同硬件适配的问题,大家逐渐开始了对算子自动代码生成的研究,即推理编译器范畴。

1.3 推理编译器 - Compiler

推理编译器主要是为了解决几个问题:1. 算子代码自动生成;2. IR与不同硬件的适配。

一些常用的ElementWise/Reduction等算子,往往可以通过模板或者一些IR的定义来生成不同硬件的代码,省去了手写算子的人力投入。为了适配不同的硬件,往往编译器也会将IR表示进行分层,大体分为High Level IR和Low Level IR,其中High Level IR表示与硬件解耦,尽可能的表达图和算子的特性,编译器可以基于这一层的IR进行与硬件无关的图优化;Low Level IR则设计和硬件相关,用于针对不同硬件进行图优化和代码生成。

通用编译器架构.png

1.4 写在最后

推理引擎和推理编译器没有优劣之分,两者有各自的优势和适合的场景。两者很多技术是共同的,例如图融合优化;两者也在不断的相互借鉴和融合,例如TensorRT内部的Meylin编译器也能够进行算子代码自动生成。后文中编译优化囊括了推理引擎和推理编译器两种优化。


2. 编译优化

编译优化技术一般可以分为前端优化和后端优化,分别对应High Level IR和Low Level IR,因此前端优化大都与硬件解耦,而后端优化与具体的硬件紧密相关。接下来,会分别介绍一下编译优化前端和后端有哪些优化方式。

The overview of commonly adopted design architecture of DL compilers.PNG

2.1 编译前端

2.1.1 Algebraic Simplification

模型其实是算子的DAG图,算子与算子之间往往可以通过算数等价的方式来进行计算量简化,达到优化推理性能的目的。常见的算术简化包括如下几种:

  • algebraic identification
    • 这里使用layer norm中均值和方差计算进行举例。通过最后一行简化的表示,可以给予硬件同时计算x和x^2均值的可能,提高硬件的利用率。而原始的表示决定了要先计算x的均值后,再计算方差。由于两者是算术等价的,因此选择最佳的算子图表示。
  • strength reduction
    • 在元素计算中,往往可以通过更高效的算子代替比较慢的算子计算,提高算子计算效率。如移位运算代替乘法或者除法操作。
  • computation order
    • 算子的运算顺序可能能够省去不必要的转化操作,如图中的矩阵乘法等价变换。
  • constant folding
    • 当算子输入都为常量tensor的时候,可以离线计算出算子的输出,消减掉该算子,提高运行时推理效率。

前端-Algebraic Simplification.png

2.1.2 Dataflow-level optimizations

除了算子和算术等价的优化外,还可以基于图的结构进行推理优化。例如通过前后算子的内存复用来节省内存占用;通过转化Tensor的组织结构(NCHW)来获得更高效的算子实现;取消推理时无效的算子;更改算子前后位置创造融合机会等。

前端-Dataflow-level optimizations.png

2.1.3 Operation Fusion

算子融合是非常常用的图优化技术。算子融合可以减少不必要的线性计算(例如BN可以提前把scale/var信息更新到conv的weight/bias中),同时可以减少片外带宽存储空间的反复读写,尽可能的将计算在片上计算完成,也减少了kernel的启动开销。

前端-op fusion.png


2.2 编译后端

2.2.1 Hardware Specific Opt

针对特定的硬件,往往可以做计算和访存的定制优化,例如:

原语或指令的映射:将算子拆分到硬件的原语级别,将拆分后的计算映射到对应的原语,硬件的定制的原语具有非常高的计算效率。

访存优化:通过多级存储空间,将DRAM的数据读取到片上寄存器或者共享内存进行计算操作,并通过并发将Load/Store指令与计算指令进行隐藏,提高吞吐。

循环展开:通过将循环展开,可以给予编译器更多指令流水线优化的可能,提高计算效率。

向量化优化:很多硬件都有SIMD或者SIMT结构,可以将并行展开,使用向量化指令提高并发效率。

2.2.2 Lowering

在后端最重要的是lowering设计,即将高层次的表示映射到低层次硬件相关的表示。如果硬件厂商提供了一些底层高性能算子库实现,那么可以直接将算子表示映射到library上面,毕竟手动实现一个高效的gemm/conv还是很有难度的。另外一些ElementWise/Reduction/Transpose等操作,可以通过模板自动生成对应的代码,不用反复实现。最终编译器会有Just-In-Time (JIT) 和Ahead-Of-Time (AOT) 两种方式来输出编译优化后的结构。JIT是在运行时进行优化,AOT是提前做好优化(生成so或者binary)。

后端-lowering.png

3. 优化无止境

高性能并行计算领域有一个经典的定律叫阿姆达尔定律,阿姆达尔定律这一朴素的定律可以帮助我们评估出优化的上限;并指导我们要针对瓶颈进行优化。针对瓶颈进行优化才能事半功倍。优化的流程包括了应用瓶颈分析,针对瓶颈进行并行化加速,优化实现,部署。这个循环可以往复,当一个瓶颈得到优化后,又会有新的瓶颈,直到优化满足我们的需求才结束。深度学习推理优化也如此,一个应用可能瓶颈在逻辑的处理,也可能在模型的计算,在优化前需要对整体进行分析后再开始针对的优化。

推理优化技术也在朝着自动化、低精度、多硬件方向发展中,推动了人工智能深度学习技术在我们的日常生活中的普及,希望人工智能越来越智能和低成本,早日实现科幻电影中的未来世界。

loop and law.png



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。