乐趣区

关于性能优化:用户实践-基于-MegEngine-移动端-CPU-的深度学习模型推理性能优化

用户实际系列,将收录 MegEngine 用户在框架实际过程中的心得体会文章,心愿可能帮忙有同样应用场景的小伙伴,更好地理解和应用 MegEngine ~

作者:王雷 | 旷视科技 研发工程师

背景

随着人工智能技术的倒退及应用领域的不断扩大,算力较弱的挪动设施成为模型推理的重要运算载体,优化其推理性能因而成为重要的工程问题。个别认为,让模型运行于 GPU 上会比运行于 CPU 上具备较大的劣势,获得可观的性能晋升。这通常是真实情况,然而,在工程实际中咱们也发现,对于某些模型维度较小的模型,在挪动设施上,GPU 运行并没有带来性能的晋升,而且还额定引入了兼容性的问题。所以,在某些利用场景下,咱们须要以 CPU 为运行载体,尝试各种办法,以晋升模型推理性能。

咱们在优化某关键点模型推理性能的工程实际中,基于 MegEngine 推理引擎,发现有两个优化办法比拟无效,NCHW44 和 Record。本文将对它们的原理和应用办法做比拟具体的阐明。

NCHW44 优化

原理

家喻户晓,减少计算的并行水平是晋升计算速度的重要伎俩。在 CPU 上,这就须要应用 SIMD 指令 ——Single Instruction, Multiple Data,单指令多数据,即执行单条指令,操作实现多个数据的运算。例如执行加法运算,如果应用非 SIMD 指令即个别的加法指令,每次只能操作一个数,而在模型推理中,这个数常常是 8 位、16 位,最大不过是 32 位的浮点数,这对于古代 64 位的寄存器来说,的确有些节约。如果在寄存器中存储多个数,一条指令实现运算,则能成倍的晋升计算速度。在 x86 CPU 上,SIMD 的实现是 SSE、AVX 等指令集,而在 ARM CPU 上,则是 NEON 指令集。而且 CPU 还提供了 SIMD 指令的专用寄存器,在 x86 平台上,寄存器位数为 128 位、256 位,甚至是 512 位,在 ARM 平台上,寄存器位数是 128 位,这样就能够一次实现 4 个 float32 数据的运算。因而,如果能想方法在模型推理运算中尽可能多的应用 SIMD,就能晋升推理的性能。

咱们来看一下在模型推理中应用 SIMD 会遇到什么问题。通常,张量在内存中的存储形式为 NCHW(即每个通道的行列数据间断排布,再顺序存储各个通道),如在解决常见的卷积操作时,卷积核的尺寸可能多种多样,比方 3×3,那么每次须要取一行的 3 个间断的像素数据与卷积核相应地位数据相乘(再解决其余列和通道),而对应 SIMD 指令,其应用的寄存器通常为 128 位,应用 float32 的话也须要一次解决 4 个数据能力充分发挥其劣势,而这四个数据必须在内存中处于相邻地位,所以这种计算形式大大限度了 SIMD 指令。

作为改良,在 NCHW44(也称 NC4HW4)布局下,同一地位(HW)的 4 个通道的数据被间断排列到了一起,在卷积操作时它们一起参加计算,每次 SIMD 指令执行能够将它们一起载入寄存器,这样就晋升了计算效率。下图示意了 NCHW44 的数据存储排列形式。

实际

MegEngine 反对两种形式应用 NCHW44 优化:

1. 离线 dump(序列化)成 NCHW44 模型,推理时 MegEngine 会主动判断出它的排列形式,执行相应的算子实现。上面两个用于 dump 的办法

megengine.jit.trace.dump

megengine.core.tensor.megbrain_graph.optimize_for_inference

反对关键字参数 enable_nchw44,将参数值设为 True,所输入的就是 NCHW44 的模型。

对应的,如果想通过 load_and_run 事后测试性能,能够在应用 sdk/load-and-run/dump_with_testcase_mge.py 脚本时增加参数 —enable-nchw44,生成的模型即是可被 load_and_run 加载执行的 nchw44 模型。

2. 在线开启转换,dump 模型时不做 nchw44 配置,运行时通过 option 开启转换:

serialization::GraphLoader::LoadConfig load_config;

load_config.comp_graph = ComputingGraph::make();

auto &&graph_opt = ret.load_config.comp_graph->options();

graph_opt.graph_opt.enable_nchw44();

对应的,如果想通过 load_and_run 事后测试性能,能够在执行 load_and_run 时,增加命令行参数 —enable-nchw44。

两种形式能够联合具体的应用状况来抉择:如果咱们开发的 sdk 或 app 可能加载多个模型,有些应用 NCHW44 而有些不应用,比拟适宜抉择离线形式;如果因为某些起因,咱们无奈从新 dump 模型(比方原始的模型文件失落),则只能抉择在线形式。

成果

在咱们的工程实际中,某模型在以后支流 android 手机上的推理速度,大略有 20%-30% 左右的晋升。

record 优化

原理

当 MegEngine 执行推理时,底层执行的是动态图,它的执行序列是确定的。对于图中的每个算子,执行都要分为两个步骤:筹备 kernel 和理论执行。在筹备 kernel 阶段,MegEngine 会根据 filter size、stride、shape 等信息决定要执行的算法,也就是抉择要执行的函数,即 kernel(对于卷积运算,可能会有多种不同的实现)。在执行阶段,再理论调用这些函数。

如果抉择所需的根据不变(理论状况中次要是 shape 不要变),那么这个筹备 kernel 的过程就只需被执行一次,并把抉择的各个函数对象记录到一个列表中,当前再执行的时候,间接程序地从列表中取出函数对象,执行即可。这样就节俭了后续各次执行时筹备 kernel 的工夫。这也就是 record 这个名字的含意所在。

目前 MegEngine 存在两种级别的 record。record1 次要是为了减速执行,原理如上所述;record2 次要是为了节俭内存,如果 shape 不变,MegEngine 能够析构图上存储的一些信息(这些信息能够在 shape 扭转时用来做 shape 的推导)。对于咱们心愿晋升计算性能的场景,个别 record1 比拟适合。

留神 record 的一个最重要的限度条件是 shape 不能扭转。对于某些检测模型,可能须要根据输出图的尺寸,对模型进行 resize,这种状况就无奈应用 record。对于输出长宽和通道数不变的模型,仍需注意,batch 参数(即 NCHW 中的 N)也不能变,这是可能被疏忽的。另外,模型加载后,在第一次运行之前,咱们还是能够扭转 shape 的,只有第一次运行之后不再扭转 shape,就不影响 record 的应用。

除了 shape 不变这个条件之外,还有一些限度条件:

  1. 所有的算子不能依赖动态内存调配,因为记录的函数对象还蕴含输入输出的指针,动态内存状况下会发生变化;
  2. Host 端的输入输出指针不能变;
  3. 同步只能产生在网络执行的开端,即不能在网络执行过程中,在某两头节点执行同步;
  4. 整个图中不能存在多个 compnode。

这些条件对于个别的应用,根本能够满足。

实际

在 option 中开启

serialization::GraphLoader::LoadConfig load_config;

load_config.comp_graph = ComputingGraph::make();

auto &&graph_opt = load_config.comp_graph->options();

graph_opt.comp_node_seq_record_level = 1; // 2

对应的,如果想通过 load_and_run 事后测试性能,能够在执行 load_and_run 时,增加命令行参数 –record-comp-seq 或 –record-comp-seq2。

成果

在咱们的工程实际中,某模型在以后支流 android 手机上的推理速度,大略有 10% 左右的晋升。

总结

本文从原理和应用方面介绍了 MegEngine 的 NCHW44 和 record 两个优化办法,它们只是咱们在优化某关键点模型推理性能时尝试发现比拟无效的两个办法。优化办法的有效性取决于模型的特点,因而对于具体的模型,能够尝试 MegEngine 的其余优化选项,抉择比拟适合的办法。当然,优化是多方面的,除了模型推理自身之外,优化预处理和后处理,缩小数据复制,对于 Android 设施正当的设置 CPU 亲缘性等等,也是能够尝试和思考的计划。

附:

GitHub:MegEngine 天元

官网:MegEngine- 深度学习,简略开发

欢送退出 MegEngine 技术交换 QQ 群:1029741705

退出移动版