Bolt 是基于华为诺亚方舟实验室研究成果,开源社区孵化的高性能深度学习推理加速库,目前已更新至v1.2.1版本,新版本增加了对QualComm GPU的深度优化。
图0 Bolt v1.2.1版本主要更新 feature
本文从 QualComm GPU 面向深度学习计算优化现状出发,分析当前环境下第三方自主优化面临的挑战,介绍Bolt自研的一套移动端GPU系统性调优机制。
下面图1 为 Bolt 在 Adreno 660 / 650 运行目标分类、分割等一系列模型的性能表现,其中ghostnet/ocr_detect/solov2/ssd为内部模型。比较对象隶属业界开源框架第一梯队,因笔者时间关系只测了一家,就匿名为FrameWorkA。
图1. Bolt QualComm GPU FP16 Test
- Github链接:https://github.com/huawei-noah/bolt;
- 用户交流QQ群:833345709;
背景与现状
QualComm GPU 即高通骁龙芯片搭载的 Adreno 系列的 GPU ,目前最新的骁龙芯片为 SnapDragon 888,搭载 Adreno 660 GPU,根据维基百科(https://en.wikipedia.org/wiki... )给出的半精度浮点数计算峰值可达 3244 Gflops/s。测试 FrameWorkA 在 Adreno 660 GPU卷积计算的性能表现,如图2所示,
图2. FrameWorkA Adreno 660 Conv Test
因测试样本的局限性,上述结果可能无法准确的反应业界优化的最高水平,但相对比较也可以得出以下结论:
使用 OpenCL/OpenGL/Vulkan (NeuralTalk评:看到这里懂的都懂)三种不同的GPU编程框架,优化水平基本一致,OpenCL取得了明显的性能优势;即使是性能最高的OpenCL,对卷积这样的计算密集型算子,性能依然未超过200 Gflops/s,相对官方给出的性能峰值差距巨大;另一方面,QualComm官方在今年5月份针对机器学习计算推出了OpenCL ML SDK(https://developer.qualcomm.co...)。其中提到,从Adreno 660 GPU开始可用的内部扩展指令clqcommlops对算子性能有明显提升。图3为官方给出的算子列表,在adreno 660实测卷积算子(1 64 72 72)(64 64 3 3)(1 1)计算耗时0.23ms,获得了1.5 Tflops/s的性能。但OpenCL ML SDK没有为用户提供可编程接口或者clqcommlops的使用路径,目前仅能调用SDK提供的算子,无法进行自定义计算。
QualComm 官方对 Adreno GPU OpenCL 优化提供的最新文档停留在 2017 年(https://developer.qualcomm.com/qfile/33472/80-nb295-11_a.pdf),即针对Adreno 5xx/4xx的优化方法,针对Kernel级别的优化可总结为以下几点:
- GPU 内存尽量使用具备L1 cache的Image,且满足合并读取与向量化要求;
- 使用寄存器或Local Memory暂存数据,减少对global memory的访问次数,提升计算内存访问比;
- Kernel内避免线程分支与线程同步,尝试循环展开;
- 使用半精度浮点数代替全精度,减少数据读写量;
- 使用暴力搜索寻找对当前 Kernel 最优的 localworksize;
图3. clqcomml_ops受益算子列表
以上几点均为 GPU 性能优化的常用方法,相信资深从业人员也以此为基准进行过反复的迭代优化,但实际效果却差强人意。总结当前QualComm GPU深度学习优化现状,最新的QualComm GPU硬件本身在深度学习计算中可获得超Tflops/s的计算性能,但在当前开发环境下,用户难以通过自主开发享用该性能优势。QualComm GPU对深度学习计算的支持比较封闭,即提供不可用户编程的SDK或者专用硬件,总之业界相关从业人员很难受。
Bolt GPU优化方法
在当前背景下,Bolt通过一套自研的GPU系统性调优机制获得了相对较高的计算性能。图4为Adreno 660 Bolt卷积计算性能,在测试的卷积计算中,最高性能接近600Gflops/s,而最低性能也超过300Gflops/s。
图4. Bolt Adreno 660 Conv Test针对Adreno GPU优化支持,Bolt GPU系统性调优机制实现了以下几点功能:
- Image内存自适应机制:对计算密集型算子的input/output Tensor尽可能使用image存储,以便利用L1 cache获得更高性能。同时自适应判断是否可与其他Tensor复用以减少内存占用;是否满足当前设备的image size limited,如果不满足也能灵活的替换为Buffer,保证计算的顺利进行;
- 基于编译机制的计算Kernel自动生成:通过宏参数与宏函数定义Kernel代码自动生成模板,基于上层实时传递的计算配置信息,例如每个线程负责计算的块大小、Input/Output Tensor类型以及算子融合类别等,自动生成对应宏参数并编译得到具体的Kernel;(NeuralTalk评:有《CLBlast》那个味道了
- Kernel计算方案与线程配置自动调优:在前两点功能的扶持下,Bolt可基于当前算子的实时信息快速生成可能的计算方案,配合算法选择与localworksize选择,在目标硬件上获取性能最佳的方案,且提供了preprocess_ocl工具将调优过程离线化并打包调优结果与相关Kernel Binary信息为动态库。
图 5 为调优整体流程,首先 image 内存自适应机制会根据当前计算硬件以及Tensor size将使用的memory类型传递给infer forward algorithm。其次infer forward algorithm会基于算子参数尝试所有可能的计算方案,找到最佳的计算配置。最后由gcl run kernel selectls确定当前Kernel最佳的localworksize。
图5. Bolt GPU调优机制
上述过程中,生成Kernel编译宏参数即可编译出对应的计算Kernel,实则是因为Bolt在Kernel层面定义了多套Kernel生成模板。
下面讲个例子,以卷积模块”convdirectsh1_qc.cl“(https://github.com/huawei-noa...)为例,该模块可生层stride=1&dilation=1时不同数据复用方案的计算Kernel,对卷积核fw没有限制,fh方向进行了循环展开,目前可支持的最大值为12。input/output/weight的数据排列方式与《Bolt GPU性能优化,让上帝帮忙掷骰子》文中描述一致,均采用了C4的数据格式。
图6描述了该卷积模块的具体实现, KN为单线程output channel方向计算点数,ON为单线程height方向计算点数,单线程计算总点数即为:
FH方向第一个点计算访问比为:
后续计算复用inval已存的数据,仅需从全局内存更新一个点 :
计算访存比为,搜索空间包括:
上层传递具体参数后inferforward_algorithm在以上空间中搜索最佳计算配置。
图6. convdirectsh1_qc模块实现
总结
移动端 GPU 作用于深度学习计算本身还有大量计算潜力有待挖掘,但仍然面临着很多挑战,希望可以早日看到更加统一、高效且透明的编程框架,也希望整个行业生态能越来越好。
文章转载于:NeuralTalk
作者:HUAWEI
推荐阅读