飞桨 x 昇腾生态适配方案:07_ 性能数据分析
性能调优思路
性能优化是一项系统性工作,建议采用 "分析 - 定位 - 优化" 的流程,通过性能分析工具定位瓶颈后实施针对性优化。
通过 profiling 工具获取算子级性能数据定位性能瓶颈点,主要涉及算子计算时间与调度通信时间。
常用优化策略中,计算时间过长需依靠算子自身优化升级,可收集算子的 shape 和 dtype 向算子开发部门提交工单并跟踪进展;调度过程包含多个环节,目前最突出的问题是重复编译导致的性能损耗,可通过关闭在线编译、替换为更高效的 aclnn 算子、算子融合等手段优化。
性能数据采集
profiler 工具可以采集模型中每个算子的计算时间和调度时间,具体步骤如下:
导入 profiler 工具
代码中导入 paddlepaddle 的 profiler 工具:
初始化 profiler 工具
指定 profiler 工具采集目标:
"targets":设置为 CUSTOM_DEVICE;
"custom_device_types":设置为 NPU。
注:若代码中已有 profiler,需要把as profiler
中的profiler
统一改个名字,并把后续步骤中的profiler
改成此名字,例如:
数据采集
采集时机选择
建议在训练过程中选择稳态阶段进行性能数据采集,具体参考以下准则:
前 100 个 step 通常处于初始化阶段,性能数据不具备代表性,建议在 100step 后开始采集;
若单 epoch 包含 step 数不足 100,则选择 epoch 末尾稳定阶段的 step 数据。
采集代码示例
将 profiler 的 start / stop 逻辑嵌入训练脚本 train.py 中:
具体示例如下:

插入以上代码后,启动训练操作。
性能数据分析
msprof 解析性能数据
训练结束后,会在当前工作路径下生成一个名为 ascend_profiling 的性能数据目录,可使用 CANN 包自带的 msprof 工具对数据进行解析,具体命令如下:
注:/usr/local/Ascend
部分是 CANN 包默认安装路径,如果安装在自定义路径下,此处需要做相应修改。
解析成功后有如下打屏日志:

解析后会生成目录文件如下(多张卡会生成多个文件夹):

打开任意一张卡的解析结果:

需要关注的是最后一个文件夹mindstudio_profiler_output
,打开后需要关注的是红色框圈住的部分:

msprof_*.json #timeline 文件,可以用
chrome://tracing
解析,解析成可视化的时间线,看上去比较的清晰直观op_statistic_*.csv
op_summary_*.csv
op_statistic_*.csv 分析
op_statistic_*.csv 是 NPU 算子的性能统计文件,包含以下关键字段:OP Type:算子类型标识(全局唯一)Ratio (%):算子计算总耗时的占比(性能瓶颈量化指标)Core Type:标识算子执行的计算单元类型 Total Time(us):算子计算总耗时

OP Type 字段
OP Type 字段记录性能分析过程中所有算子的具体名称,通过该字段可快速定位特定算子的性能表现。
Ratio 字段
Ratio(%) 是算子计算总耗时的占比,一般 Ratio(%)不大于 40 问题不大,若占比过大,可能存在算子重复编译问题,可通过设置环境变量export FLAGS_npu_jit_compile=false
来避免重复编译。
以占比排名第一的 TransData 算子为例,被调用次数较多但比例还算是正常。当设置环境变量后,可以发现 TransData OP 的 Ratio(%)从 19.637%下降至 18.176%:

Total Time 字段
Total Time(us)是算子计算总耗时,如果占比很大,或者算子的最大计算时间比平均计算时间大很多,则有可能是算子的调用有问题。
Core Type 字段
Core Type 标识算子执行的 NPU 计算单元类型,主要分为以下四类:
AI_CORE:NPU 高性能计算单元,主要用于卷积、矩阵乘法等计算密集型操作,最大化 AI_CORE 利用率是提升整体性能的关键;
AI_CPU:NPU 通用计算单元,支持全数据类型计算,主要负责标量运算、控制流等轻量级操作,其灵活性高但计算吞吐量低于 AI_CORE;
AI_VECTOR_CORE:AI_CORE 的子单元,负责向量计算,不需要特别关注;
MIX_AIV:混合执行模式,部分计算在 AI_CORE,部分在 AI_CPU,如果其占比不是特别大,也不需要特别关注;
op_summary_*.csv 分析
op_summary_*.csv 文件记录了性能统计周期内的算子执行时序数据,包含以下关键字段:Op Name:当次运算的算子实例名称 OP Type:算子类型标识(对应 op_statistic_*.csv 文件中的 OP Type 字段)Task Duration(us):算子的计算耗时 Task Wait Time(us):算子的调度耗时
Op Name 字段
Op Name 指的是当次运算的算子名称,有两种形式:一种是带有 aclnn 前缀、一种是不带 aclnn 前缀,分别对应 CANN 算子调用的两种方式。

aclop 的优点是支持度比较高,几乎覆盖所有算子;
aclop 的缺点是性能比 aclnn 差;
aclop 有一个更大的问题是在 attribute 属性改变时,会导致算子重复编译,如果重复编译较多,最好调整成 aclnn 相关的算子,能够减少一部分计算时间。
Task Duration 字段
Task Duration(us)指的是算子的计算耗时,即算子在 NPU 侧的计算时间。
通常,通过对 op_summary_*.csv 表格中的 Task Duration (us) 进行降序排列,能够定位主要的性能瓶颈。
若算子 Task Duration(us)时间过长(大于 1000us),则需要重点关注,可以在模型侧查看算子的调用以及它的适配层逻辑是否有问题。
Task Wait Time 字段
Task Wait Time(us)指的是算子的调度耗时,也有等待耗时的意思,即算子从 CPU 下发到 NPU 的时间。
在适配层,Task Wait Time(us) 相对更易于优化。对 Task Wait Time(us) 进行降序处理后会发现,耗时较长的情况主要有以下三种:
前反向中间等待:属正常等待时间;
算子重复编译:最常见的延长 Task Wait Time(us)的原因;
前一算子运行于 CPU:此类情况在性能数据中不会被统计,但会致使当前算子的等待时间大幅增加。
后两种情况是优化的重点。需要先定位等待时间超长的位置,随后在代码中梳理其调用关系,进而实施针对性优化。
评论