问题描述
训练条件:
Linux Euler OS x86; 8 显卡; 物理 CPU 2; 每个物理 CPU 中的核数 26; 逻辑 CPU 104; MindSpore 1.2.0 TensorFlow 1.15.0
CPU 训练时长
MindSpore(不绑定核): 33:00 TensorFlow(不绑定核): 01:06 MindSpore(绑定一个逻辑 CPU):3小时左右 TensorFlow(绑定一个逻辑 CPU): 01:47
优化目的
在相同条件下,使用 MindSpore 框架训练网络的时长 小于或等于 使用 TensorFlow 框架训练的时长
问题分析
- 不绑定核的情况下,MindSpore 训练会占用几乎所有56个逻辑 CPU。不知道它为什么会需要如此多的 CPU 资源。而绑定一个逻辑 CPU 的情况下,MindSpore 的训练慢到几乎跑不动
在绑定一个逻辑 CPU 的情况下,通过 MindSpore 配套的网络分析工具 MindInsight 查看具体算子的执行时间信息:
再用 TensorFlow 训练该神经网络,并通过 TensorFlow 的 profiler 工具分析具体算子的执行信息(耗时最长的5个算子):
对比发现,TensorFlow 算子的执行时间都在 us 级别,而 MindSpore 耗时前5的算子的执行时间在 s 级别。相差一百万倍,这显然是不正常的。
阅读 MindSpore 耗时前5的算子(AddN、Relu、Mul、MatMul、Abs)的源代码,发现它们都是通过第三方库 MKL-DNN 实现的。
MKL-DNN 全称 Math Kernel Library for Deep Neural Networks。该库是几年前,由 Intel 官方发布。主要优化了深度学习中一些常用算子在 CPU 上的性能表现。因此不太可能是这个库的实现出了问题。估计是应用这个库的方式出了问题。
咨询负责这部分代码实现的同事,并查阅相关信息,发现在应用该库时,需要设置环境变量:
export OMP_NUM_THREADS = num physical cores
复制设置该环境变量为1(因为该网络绑定一个逻辑 CPU 训练,而一般一个物理 CPU 含有 N 个逻辑 CPU,因此理论上该环境变量应设为 1/N,向上取整为1),测试该神经网络的性能。发现时间性能从原来的3小时提升到了3分13秒,单步执行时间从19260ms左右提升到了6ms左右:
查看算子执行时间的具体信息,发现原先5个耗时最长的算子都有大幅的性能提升,其中 AddN、Relu、Abs、Mul 均达到了 TensorFlow 的性能量级。MatMul 与 TensorFlow的相比仍有差距,但也有不小的提升。
至此,该神经网络的 CPU 训练性能从原来的3小时提升到了3分13秒,训练时间减少了99.97%。但相比 TensorFlow 的 1分47秒,仍有不小差距。
继续分析。从算子时间信息中可以发现,优化 MKL-DNN 后,ReluGrad(Relu 算子的反向) 在算子总耗时中占比最高,为76%。阅读该算子的源代码,发现该算子的实现有很大问题:
该算子在实现多线程时,并没有使用统一的线程池,而是自起了最大线程数的线程。这会导致:
1. 同时起如此多的线程,会造成 CPU 资源浪费; 2. 除此之外,还有可能引起线程竞争; 3. 线程的启用和销毁,也会增加额外的开销;
复制这些都会大大增加 CPU 的负担,降低神经网络的时间性能。
重构这部分代码,使用统一的线程池管理线程,并删除冗余操作。再测试该神经网络的性能。发现时间性能从原来的3分13秒提升到了1分52秒,单步执行时间从6ms左右提升到了3.5ms左右:
查看算子执行时间的具体信息,发现包括 ReluGrad 在内的所有算子,执行时间都降到了 us 级别,与 TensorFlow 的基本相当。
Reference
Accelerating Deep Learning on CPU with Intel MKL-DNN, Apache MXNet, May 11, 2018 Maximize TensorFlow* Performance on CPU: Considerations and Recommendations for Inference Workloads, Intel Official linux top命令查看内存及多核CPU的使用讲述, 长风破浪, 2016-05-20