Ascend性能调优
概述
本教程介绍如何在Ascend AI处理器上使用MindSpore Profiler进行性能调优。MindSpore Profiler可以为用户提供算子执行时间分析、内存使用分析、AI Core指标分析、Timeline展示等功能,帮助用户分析性能瓶颈、优化训练效率。
操作流程
准备训练脚本
在训练脚本中调用性能调试接口,如mindspore.Profiler以及mindspore.profiler.DynamicProfilerMonitor接口
运行训练脚本
通过MindStudio Insight软件查看性能数据
使用方法
收集训练性能数据有三种方式,用户可以根据不同场景使用Profiler使能方式,以下将介绍不同场景的使用方式。
方式一:修改训练脚本
在训练脚本中添加MindSpore Profiler相关接口,Profiler接口详细介绍请参考MindSpore Profiler参数详解。
自定义训练
MindSpore函数式编程用例使用Profiler进行自定义训练,可以在指定的step区间或者epoch区间开启或者关闭收集Profiler性能数据。
profiler = ms.Profiler(start_profile=False) data_loader = ds.create_dict_iterator() for i, data in enumerate(data_loader): train() if i==100: profiler.start() if i==200: profiler.stop() profiler.analyse()
自定义Callback
对于数据非下沉模式,只有在每个step结束后才有机会告知CANN开启和停止,因此需要基于step开启和关闭。
import os import mindspore as ms from mindspore.communication import get_rank def get_real_rank(): """get rank id""" try: return get_rank() except RuntimeError: return int(os.getenv("RANK_ID", "0")) class StopAtStep(ms.Callback): def __init__(self, start_step, stop_step): super(StopAtStep, self).__init__() self.start_step = start_step self.stop_step = stop_step # 按照rank_id设置性能数据落盘路径 rank_id = get_real_rank() output_path = os.path.join("profiler_data", f"rank_{rank_id}") self.profiler = ms.Profiler(start_profile=False, output_path=output_path) def on_train_step_begin(self, run_context): cb_params = run_context.original_args() step_num = cb_params.cur_step_num if step_num == self.start_step: self.profiler.start() def on_train_step_end(self, run_context): cb_params = run_context.original_args() step_num = cb_params.cur_step_num if step_num == self.stop_step: self.profiler.stop() self.profiler.analyse()
对于数据下沉模式,只有在每个epoch结束后才有机会告知CANN开启和停止,因此需要基于epoch开启和关闭。可根据自定义Callback基于step开启Profiler样例代码修改训练脚本。
class StopAtEpoch(ms.Callback): def __init__(self, start_epoch, stop_epoch): super(StopAtEpoch, self).__init__() self.start_epoch = start_epoch self.stop_epoch = stop_epoch # 按照rank_id设置性能数据落盘路径 rank_id = get_real_rank() output_path = os.path.join("profiler_data", f"rank_{rank_id}") self.profiler = ms.Profiler(start_profile=False, output_path=output_path) def on_train_epoch_begin(self, run_context): cb_params = run_context.original_args() epoch_num = cb_params.cur_epoch_num if epoch_num == self.start_epoch: self.profiler.start() def on_train_epoch_end(self, run_context): cb_params = run_context.original_args() epoch_num = cb_params.cur_epoch_num if epoch_num == self.stop_epoch: self.profiler.stop() self.profiler.analyse()
方式二:动态Profiler使能
mindspore.profiler.DynamicProfilerMonitor提供用户动态修改Profiler配置参数的能力,修改配置时无需中断训练流程,初始化生成的JSON配置文件示例如下。
{
"start_step": -1,
"stop_step": -1,
"aicore_metrics": -1,
"profiler_level": -1,
"profile_framework": -1,
"analyse_mode": -1,
"profile_communication": false,
"parallel_strategy": false,
"with_stack": false,
"data_simplification": true
}
start_step (int, 必选) - 设置Profiler开始采集的步数,为相对值,训练的第一步为1。默认值-1,表示在整个训练流程不会开始采集。
stop_step (int, 必选) - 设置Profiler开始停止的步数,为相对值,训练的第一步为1,需要满足stop_step大于等于start_step。默认值-1,表示在整个训练流程不会开始采集。
aicore_metrics (int, 可选) - 设置采集AI Core指标数据,取值范围与Profiler一致。默认值-1,表示不采集AI Core指标。
profiler_level (int, 可选) - 设置采集性能数据级别,0代表ProfilerLevel.Level0,1代表ProfilerLevel.Level1,2代表ProfilerLevel.Level2。默认值-1,表示不控制性能数据采集级别。
profile_framework (int, 可选) - 设置收集的host信息类别,0代表"all",1代表"time"。默认值-1,表示不采集host信息。
analyse_mode (int, 可选) - 设置在线解析的模式,对应mindspore.Profiler.analyse接口的analyse_mode参数,0代表"sync",1代表"async"。默认值-1,表示不使用在线解析。
profile_communication (bool, 可选) - 设置是否在多设备训练中采集通信性能数据,true代表采集,false代表不采集。默认值false,表示不采集集通信性能数据。
parallel_strategy (bool, 可选) - 设置是否采集并行策略性能数据,true代表采集,false代表不采集。默认值false,表示不采集并行策略性能数据。
with_stack (bool, 可选) - 设置是否采集调用栈信息,true代表采集,false代表不采集。默认值false,表示不采集调用栈。
data_simplification (bool, 可选) - 设置开启数据精简,true代表开启,false代表不开启。默认值true,表示开启数据精简。
样例一:使用model.train进行网络训练,将DynamicProfilerMonitor注册到model.train。
步骤一:在训练代码中添加DynamicProfilerMonitor,将其注册到训练流程。
import numpy as np
from mindspore import nn
from mindspore.train import Model
import mindspore as ms
import mindspore.dataset as ds
from mindspore.profiler import DynamicProfilerMonitor
class Net(nn.Cell):
def __init__(self):
super(Net, self).__init__()
self.fc = nn.Dense(2, 2)
def construct(self, x):
return self.fc(x)
def generator():
for i in range(2):
yield (np.ones([2, 2]).astype(np.float32), np.ones([2]).astype(np.int32))
def train(net):
optimizer = nn.Momentum(net.trainable_params(), 1, 0.9)
loss = nn.SoftmaxCrossEntropyWithLogits(sparse=True)
data = ds.GeneratorDataset(generator, ["data", "label"])
# cfg_path参数为共享配置文件的文件夹路径,多机场景下需要满足此路径所有节点都能访问到
# output_path参数为动态profile数据保存路径
profile_callback = DynamicProfilerMonitor(cfg_path="./dyn_cfg", output_path="./dynprof_data")
model = Model(net, loss, optimizer)
model.train(10, data, callbacks=[profile_callback])
if __name__ == '__main__':
ms.set_context(mode=ms.GRAPH_MODE, device_target="Ascend")
# Train Mode
net = Net()
train(net)
步骤二:拉起训练流程,动态修改配置文件实现动态采集性能数据。拉起训练后,DynamicProfilerMonitor会在指定的cfg_path路径下生成配置文件profiler_config.json,用户可以动态编辑该配置文件,比如修改为下面的配置,表示DynamicProfilerMonitor将会在训练的第10个step开始采集,第10个step停止采集后在线解析。
{
"start_step": 10,
"stop_step": 10,
"aicore_metrics": -1,
"profiler_level": -1,
"profile_framework": -1,
"analyse_mode": 0,
"profile_communication": false,
"parallel_strategy": true,
"with_stack": true,
"data_simplification": false
}
样例二:MindFormers中使用DynamicProfilerMonitor。
步骤一:在MindFormers中添加DynamicProfilerMonitor,将其注册到训练流程。修改mindformers/trainer/trainer.py中的_build_profile_cb函数,将其默认的ProfileMonitor修改为DynamicProfilerMonitor,修改示例如下。
def _build_profile_cb(self):
"""build profile callback from config."""
if self.config.profile:
sink_size = self.config.runner_config.sink_size
sink_mode = self.config.runner_config.sink_mode
if sink_mode:
if self.config.profile_start_step % sink_size != 0:
self.config.profile_start_step -= self.config.profile_start_step % sink_size
self.config.profile_start_step = max(self.config.profile_start_step, sink_size)
logger.warning("profile_start_step should divided by sink_size, \
set profile_start_step to %s", self.config.profile_start_step)
if self.config.profile_stop_step % sink_size != 0:
self.config.profile_stop_step += self.config.profile_stop_step % sink_size
self.config.profile_stop_step = max(self.config.profile_stop_step, \
self.config.profile_start_step + sink_size)
logger.warning("profile_stop_step should divided by sink_size, \
set profile_stop_step to %s", self.config.profile_stop_step)
start_profile = self.config.init_start_profile
profile_communication = self.config.profile_communication
# 添加DynamicProfilerMonitor,替换原有的ProfileMonitor
from mindspore.profiler import DynamicProfilerMonitor
# cfg_path参数为共享配置文件的文件夹路径,多机场景下需要满足此路径所有节点都能访问到
# output_path参数为动态profile数据保存路径
profile_cb = DynamicProfilerMonitor(cfg_path="./dyn_cfg", output_path="./dynprof_data")
# 原始的ProfileMonitor不再使用
# profile_cb = ProfileMonitor(
# start_step=self.config.profile_start_step,
# stop_step=self.config.profile_stop_step,
# start_profile=start_profile,
# profile_communication=profile_communication,
# profile_memory=self.config.profile_memory,
# output_path=self.config.profile_output,
# config=self.config)
self.config.auto_tune = False
self.config.profile_cb = profile_cb
步骤二:在模型的yaml配置文件中开启profile功能后拉起训练,拉起训练后,DynamicProfilerMonitor会在指定的cfg_path路径下生成配置文件profiler_config.json,用户可以动态编辑该配置文件,比如修改为下面的配置,表示DynamicProfilerMonitor将会在训练的第10个step开始采集,第10个step停止采集后在线解析。
{
"start_step": 10,
"stop_step": 10,
"aicore_metrics": -1,
"profiler_level": -1,
"profile_framework": -1,
"analyse_mode": 0,
"profile_communication": false,
"parallel_strategy": true,
"with_stack": true,
"data_simplification": false
}
方式三:环境变量使能
在运行网络脚本前,配置Profiler相关配置项。
export MS_PROFILER_OPTIONS='{"start": true, "output_path": "/XXX", "profile_memory": false, "profile_communication": false, "aicore_metrics": 0, "l2_cache": false}'
start (bool,必选) - 设置为true,表示使能Profiler;设置成false,表示关闭性能数据收集,默认值:false。
output_path (str, 可选) - 表示输出数据的路径(绝对路径)。默认值:"./data"。
op_time (bool, 可选) - 表示是否收集算子性能数据,默认值:true。
profile_memory (bool,可选) - 表示是否收集Tensor内存数据。当值为true时,收集这些数据。使用此参数时,op_time 必须设置成true。默认值:false。
profile_communication (bool, 可选) - 表示是否在多设备训练中收集通信性能数据。当值为true时,收集这些数据。在单台设备训练中,该参数的设置无效。使用此参数时,op_time 必须设置成true。默认值:false。
aicore_metrics (int, 可选) - 设置AI Core指标类型,使用此参数时,op_time 必须设置成true。默认值:0。
l2_cache (bool, 可选) - 设置是否收集l2缓存数据,默认值:false。
timeline_limit (int, 可选) - 设置限制timeline文件存储上限大小(单位M),使用此参数时,op_time 必须设置成true。默认值:500。
data_process (bool, 可选) - 表示是否收集数据准备性能数据,默认值:false。
parallel_strategy (bool, 可选) - 表示是否收集并行策略性能数据,默认值:false。
profile_framework (str, 可选) - 是否需要收集Host侧时间,可选参数为["all", "time", null]。默认值:null。
with_stack (bool, 可选) - 是否收集Python侧的调用栈的数据,此数据在timeline中采用火焰图的形式呈现,使用此参数时, op_time 必须设置成 true 。默认值: false。
离线解析
当Profiler采集性能数据较大时,若在训练过程中直接使用Profiler.analyse()进行在线解析,则可能导致对系统资源占用过大,从而影响训练效率。Profiler提供了离线解析功能,支持采集完成性能数据后,使用Profiler.offline_analyse对采集数据进行离线解析。
训练脚本采集性能数据且不在线解析的部分代码示例如下:
class Net(nn.Cell):
...
def train(net):
...
if __name__ == '__main__':
ms.set_context(mode=ms.GRAPH_MODE, device_target="Ascend")
# Init Profiler
# Note that the Profiler should be initialized before model.train
profiler = ms.Profiler(output_path='/path/to/profiler_data')
# Train Model
net = Net()
train(net) # Error occur.
# Collection end
profiler.stop()
在上述代码采集性能数据后,可以用离线接口来解析数据,示例代码如下:
from mindspore import Profiler
Profiler.offline_analyse(path='/path/to/profiler_data', pretty=False, step_list=None, data_simplification=True)
离线解析接口参数描述如下:
path (str) - 需要进行离线分析的profiling数据路径,指定到profiler上层目录。支持传入单卡和多卡数据路径。
pretty (bool, 可选) - 对json文件进行格式化处理。此参数默认值为 False,即不进行格式化。
step_list (list, 可选) - 只分析指定step的性能数据。此参数默认值为 None,即进行全解析。
data_simplification (bool, 可选) - 数据精简开关功能。默认值为 True,即开启数据精简。
参数注意事项:
step_list参数只在解析graph模式的采集数据时生效,且指定的step必须连续,step范围是从1开始计数的实际采集步数。例如:采集了5个step,则可选范围为[1,2,3,4,5]。
data_simplification参数默认开启,若连续两次离线解析均打开该开关,第一次数据精简会将框架侧采集数据删除,进而导致第二次离线解析框架侧解析结果缺失。
离线解析传入的path路径支持单卡和多卡数据路径,不同场景描述如下。
单卡场景
采用离线解析解析单卡数据时,传入的profiling数据路径/path/to/profiler_data的目录结构如下:
└──── profiler_data
└────profiler
解析的性能数据在/path/to/profiler_data/profiler目录下生成。
多卡场景
采用离线解析解析多卡数据时,传入的profiling数据路径/path/to/profiler_data的目录结构如下:
└──── profiler_data
├────rank_0
│ └────profiler
├────rank_1
│ └────profiler
├────rank_2
│ └────profiler
└────rank_3
└────profiler
解析的性能数据在/path/to/profiler_data/profiler目录下生成。
目录结构
性能数据目录结构例如下:
└──── profiler
├──── container
├──── FRAMEWORK // 框架侧采集的原始数据
│ └──── op_range_*
├──── PROF_{数字}_{时间戳}_{字符串} // msprof性能数据
│ ├──── analyse
│ ├──── device_*
│ ├──── host
│ ├──── mindstudio_profiler_log
│ └──── mindstudio_profiler_output
├──── rank_* // 内存相关的原始数据
│ ├──── memory_block.csv
│ └──── task.csv
├──── rank-*_{时间戳}_ascend_ms // MindStudio Insight可视化交付件
│ ├──── ASCEND_PROFILER_OUTPUT // MindSpore Profiler接口采集的性能数据
│ ├──── profiler_info_*.json
│ └──── profiler_metadata.json // 记录用户自定义的meta数据,调用add_metadata或add_metadata_json接口生成该文件
├──── aicore_intermediate_*_detail.csv
├──── aicore_intermediate_*_type.csv
├──── aicpu_intermediate_*.csv
├──── ascend_cluster_analyse_model-{mode}_{stage_num}_{rank_size}_*.csv
├──── ascend_timeline_display_*.json
├──── ascend_timeline_summary_*.json
├──── cpu_framework_*.txt // 异构场景生成
├──── cpu_ms_memory_record_*.txt
├──── cpu_op_detail_info_*.csv // 异构场景生成
├──── cpu_op_execute_timestamp_*.txt // 异构场景生成
├──── cpu_op_type_info_*.csv // 异构场景生成
├──── dataset_iterator_profiling_*.txt // 数据非下沉场景生成
├──── device_queue_profiling_*.txt // 数据下沉场景生成
├──── dynamic_shape_info_*.json
├──── flops_*.txt
├──── flops_summary_*.json
├──── framework_raw_*.csv
├──── hccl_raw_*.csv // 配置profiler(profiler_communication=True)生成
├──── minddata_aicpu_*.json // 数据下沉场景生成
├──── minddata_cpu_utilization_*.json
├──── minddata_pipeline_raw_*.csv
├──── minddata_pipeline_summary_*.csv
├──── minddata_pipeline_summary_*.json
├──── operator_memory_*.csv
├──── output_timeline_data_*.txt
├──── parallel_strategy_*.json
├──── pipeline_profiling_*.json
├──── profiler_info_*.json
├──── step_trace_point_info_*.json
└──── step_trace_raw_*_detail_time.csv
└──── dataset_*.csv
性能数据文件描述
PROF_XXX目录下为CANN Profiling采集的性能数据,主要保存在mindstudio_profiler_output中,数据介绍在 昇腾社区官网 搜索"性能数据文件参考"查看。
profiler目录下包含csv、json、txt三类文件,覆盖了算子执行时间、内存占用、通信等方面的性能数据,文件说明见下表。
文件名 |
说明 |
---|---|
step_trace_point_info_.json |
step节点对应的算子信息(仅mode=GRAPH,export GRAPH_OP_RUM=0) |
step_trace_raw__detail_time.csv |
每个step的节点的时间信息(仅mode=GRAPH,export GRAPH_OP_RUM=0) |
dynamic_shape_info_.json |
动态shape下算子信息 |
pipeline_profiling_.json |
MindSpore数据处理,采集落盘的中间文件,用户无需关注 |
minddata_pipeline_raw_.csv |
MindSpore数据处理,采集落盘的中间文件,用户无需关注 |
minddata_pipeline_summary_.csv |
MindSpore数据处理,采集落盘的中间文件,用户无需关注 |
minddata_pipeline_summary_.json |
MindSpore数据处理,采集落盘的中间文件,用户无需关注 |
framework_raw_.csv |
MindSpore数据处理中AI Core算子的信息 |
device_queue_profiling_.txt |
MindSpore数据处理,采集落盘的中间文件,用户无需关注(仅数据下沉场景) |
minddata_aicpu_.txt |
MindSpore数据处理中AI CPU算子的性能数据(仅数据下沉场景) |
dataset_iterator_profiling_.txt |
MindSpore数据处理,采集落盘的中间文件,用户无需关注(仅数据非下沉场景) |
aicore_intermediate__detail.csv |
AI Core算子数据 |
aicore_intermediate__type.csv |
AI Core算子调用次数和耗时统计 |
aicpu_intermediate_.csv |
AI CPU算子信息解析后耗时数据 |
flops_.txt |
记录AI Core算子的浮点计算次数(FLOPs)、每秒的浮点计算次数(FLOPS) |
flops_summary_.json |
记录所有算子的总的FLOPs、所有算子的平均FLOPs、平均的FLOPS_Utilization |
ascend_timeline_display_.json |
timeline可视化文件,用于MindStudio Insight可视化 |
ascend_timeline_summary_.json |
timeline统计数据 |
output_timeline_data_.txt |
算子timeline数据,只有AI Core算子数据存在时才有 |
cpu_ms_memory_record_.txt |
内存profiling的原始文件 |
operator_memory_.csv |
算子级内存信息 |
minddata_cpu_utilization_.json |
CPU利用率 |
cpu_op_detail_info_.csv |
CPU算子耗时数据(仅mode=GRAPH) |
cpu_op_type_info_.csv |
具体类别CPU算子耗时统计(仅mode=GRAPH) |
cpu_op_execute_timestamp_.txt |
CPU算子执行起始时间与耗时(仅mode=GRAPH) |
cpu_framework_.txt |
异构场景下CPU算子耗时(仅mode=GRAPH) |
ascend_cluster_analyse_model-xxx.csv |
在模型并行或pipeline并行模式下,计算和通信等相关数据(仅mode=GRAPH) |
hccl_raw_.csv |
基于卡的通信时间和通信等待时间(仅mode=GRAPH) |
parallel_strategy_.json |
算子并行策略,采集落盘中间文件,用户无需关注 |
profiler_info_.json |
Profiler配置等info信息 |
dataset_.csv |
数据处理模块各阶段执行耗时(要收集这部分数据,需要从最开始就开启profiler,至少是第一个step前) |
profiler目录下包括一些csv、json、txt文件,这些文件包含了模型计算过程中算子执行时间、内存占用、通信等性能数据,帮助用户分析性能瓶颈。下面对部分csv、txt文件中的字段进行说明,文件内容主要包括device侧算子(AI Core算子和AI CPU算子)耗时的信息、算子级内存和应用级内存占用的信息。
aicore_intermediate_*_detail.csv文件说明
aicore_intermediate_*detail.csv文件包含基于output_timeline_data*.txt和framework_raw_*.csv中的内容,统计AI Core算子信息。文件中的字段说明参考下表:
字段名 |
字段说明 |
---|---|
full_kernel_name |
device侧执行kernel算子全名 |
task_duration |
算子执行用时 |
execution_frequency |
算子执行频次 |
task_type |
算子的任务类型 |
aicore_intermediate_*_type.csv文件说明
aicore_intermediate_*type.csv文件包括基于output_timeline_data*.txt和framework_raw_*.csv中的内容,统计AI Core算子具体类型的信息。文件中的字段说明参考下表:
字段名 |
字段说明 |
---|---|
kernel_type |
AI Core算子类型 |
task_time |
该类型算子总用时 |
execution_frequency |
该类型算子执行频次 |
percent |
该算子类型的用时的占所有算子总用时的百分比 |
aicpu_intermediate_*.csv文件说明
aicpu_intermediate_*.csv文件包含AI CPU算子的耗时信息。文件中的字段说明参考下表:
字段名 |
字段说明 |
---|---|
serial_num |
AI CPU算子序号 |
kernel_type |
AI CPU算子类型 |
total_time |
算子耗时,等于下发耗时和执行耗时之和 |
dispatch_time |
下发耗时 |
execution_time |
执行耗时 |
run_start |
算子执行起始时间 |
run_end |
算子执行结束时间 |
flops_*.txt文件说明
flops_*.txt文件包含device侧算子的浮点计算次数、每秒浮点计算次数等信息。文件中的字段说明参考下表:
字段名 |
字段说明 |
---|---|
full_kernel_name |
device侧执行kernel算子全名 |
MFLOPs(10^6 cube) |
浮点计算次数(10^6 cube) |
GFLOPS(10^9 cube) |
每秒浮点计算次数(10^9 cube) |
MFLOPs(10^6 vector) |
浮点计算次数(10^6 vector) |
GFLOPS(10^9 vector) |
每秒浮点计算次数(10^9 vector) |
output_timeline_data_*.txt文件说明
output_timeline_data_*.txt文件包括device侧算子的耗时信息。文件中的字段说明参考下表:
字段名 |
字段说明 |
---|---|
kernel_name |
device侧执行kernel算子全名 |
stream_id |
算子所处Stream ID |
start_time |
算子执行开始时间(us) |
duration |
算子执行用时(ms) |
cpu_ms_memory_record_*.txt文件说明
cpu_ms_memory_record_*.txt文件包含应用级内存占用的信息。文件中的字段说明参考下表:
字段名 |
字段说明 |
---|---|
Timestamp |
内存事件发生时刻(ns) |
Total Allocated |
内存分配总额(Byte) |
Total Reserved |
内存预留总额(Byte) |
Total Active |
MindSpore中的流申请的总内存(Byte) |
operator_memory_*.csv文件说明
operator_memory_*.csv文件包含算子级内存占用的信息。文件中的字段说明参考下表:
字段名 |
字段说明 |
---|---|
Name |
内存占用Tensor名 |
Size |
占用内存大小(KB) |
Allocation Time |
Tensor内存分配时间(us) |
Duration |
Tensor内存占用时间(us) |
Allocation Total Allocated |
算子内存分配时的内存分配总额(MB) |
Allocation Total Reserved |
算子内存分配时的内存占用总额(MB) |
Release Total Allocated |
算子内存释放时的内存分配总额(MB) |
Release Total Reserved |
算子内存释放时的内存占用总额(MB) |
Device |
device类型 |