本文针对静态图模式,介绍如何运用Dump工具对网络数据进行分析。 分为异步dump和同步dump两种方式。
注:推荐用异步dump。MindSpore默认开启内存复用,而同步dump会关闭内存复用,可能会影响训练状态。
一、异步Dump
大型网络(如Bert Large)使用同步Dump时会导致内存溢出,MindSpore通过异步Dump提供了大型网络的调试能力。
1、创建json格式的配置文件
json文件名称和位置可自定义设置。
{
"common_dump_settings": {
"dump_mode": 0,
"path": "/absolute_path",
"net_name": "ResNet50",
"iteration": "0",
"input_output": 0,
"kernels": ["Default/Conv-op12"],
"support_device": [0,1,2,3,4,5,6,7],
"op_debug_mode": 0
}
}
参数说明:
- dump_mode:设置成0,表示Dump出改网络中的所有算子;设置成1,表示Dump"kernels"里面指定的算子。
- path:Dump保存数据的绝对路径。
- net_name:自定义的网络名称,例如:”ResNet50”。
- iteration:指定需要Dump的迭代。类型为str,用“|”分离要保存的不同区间的step的数据。如”0|5-8|100-120”表示Dump参数初始值,第1个,第6个到第9个, 第101个到第121个step的数据。指定“all”,表示Dump所有迭代的数据。
- input_output:设置成0,表示Dump出算子的输入和算子的输出;设置成1,表示Dump出算子的输入;设置成2,表示Dump出算子的输出。
- kernels:算子的名称列表。开启IR保存开关context.set_context(save_graphs=True)并执行用例,从生成的trace_code_graph_{graph_id}IR文件中获取算子名称。kernels仅支持TBE算子、AiCPU算子、通信算子,若设置成通信算子的名称,将会Dump出通信算子的输入算子的数据。详细说明可以参照教程:如何保存IR。
- support_device:支持的设备,默认设置成0到7即可;在分布式训练场景下,需要dump个别设备上的数据,可以只在support_device中指定需要Dump的设备Id。注:本示例是单卡任务,用默认配置即可。
- enable:开启异步Dump,如果同时开启同步Dump和异步Dump,那么只有同步Dump会生效。
- op_debug_mode:该属性用于算子溢出调试,设置成0,表示不开启溢出;设置成1,表示开启AiCore溢出检测;设置成2,表示开启Atomic溢出检测;设置成3,表示开启全部溢出检测功能。在Dump数据的时候请设置成0,若设置成其他值,则只会Dump溢出算子的数据。
2、设置Dump环境变量
export MINDSPORE_DUMP_CONFIG=/home/ma-user/xxx/data_dump.json
此处路径应为json配置文件的绝对路径。
3、脚本修改
- 设置context.set_context(reserve_class_name_in_scope=False),避免Dump文件名称过长导致Dump数据文件生成失败。
4、运行脚本
启动命令:
python MindSpore_1P.py
异步Dump保存的数据目录结构如下所示:
{path}/
|-- {device_id}/
|-- {new_name}_graph_{graph_id}/
|-- {graph_id}/
|-- {iteration}/
|-- {op_type}.{op_name}.{task_id}.{timestamp}
…
|-- graphs/
ms_output_trace_code_graph_{graph_id}.pb
ms_output_trace_code_graph_{graph_id}.ir
|-- execution_order/
ms_execution_order_graph_{graph_id}.csv
|-- .metadata/
data_dump.json
- path:data_dump.json文件中设置的绝对路径。
- net_name:data_dump.json文件中设置的网络名称。
- device_id:训练的卡号。
- graph_id:训练的图标号。
- iteration:训练的轮次。
- op_type:算子类型。
- op_name:算子名称。
- taskid:任务标号。
- timestamp:时间戳。
5、异步Dump数据分析样例
以Resnet50脚本为例:
def _conv2d(in_channel, out_channel, kernel_size, stride=1, padding=0):
scale = math.sqrt(1/(in_channel*kernel_size*kernel_size))
if padding == 0:
return nn.Conv2d(in_channel, out_channel, kernel_size=kernel_size,
stride=stride, padding=padding, pad_mode='same',
weight_init=mindspore.common.initializer.Uniform(scale=scale))
else:
return nn.Conv2d(in_channel, out_channel, kernel_size=kernel_size,
stride=stride, padding=padding, pad_mode='pad',
weight_init=mindspore.common.initializer.Uniform(scale=scale))
...class ResNet(nn.Cell):
def __init__(self, num_blocks, num_classes=10):
super(ResNet, self).__init__()
self.in_planes = 64
self.conv1 = _conv2d(3, 64, kernel_size=3, stride=1, padding=1)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU()
self.layer1 = self._make_layer(64, num_blocks[0], stride=1)
self.layer2 = self._make_layer(128, num_blocks[1], stride=2)
self.layer3 = self._make_layer(256, num_blocks[2], stride=2)
self.layer4 = self._make_layer(512, num_blocks[3], stride=2)
self.avgpool2d = nn.AvgPool2d(kernel_size=4, stride=4)
self.reshape = mindspore.ops.Reshape()
self.linear = _dense(2048, num_classes)
self.print = P.Print()
self.print_grad = P.InsertGradientOf(self.save_gradient)
def save_gradient(self, dout):
return dout
def _make_layer(self, planes, num_blocks, stride):
strides = [stride] + [1]*(num_blocks-1)
layers = []
for stride in strides:
layers.append(ResidualBlock(self.in_planes, planes, stride))
self.in_planes = EXPANSION*planes
return nn.SequentialCell(*layers)
def construct(self, x):
x = self.conv1(x)
out = self.relu(self.bn1(x))
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.avgpool2d(out)
out = self.reshape(out, (out.shape[0], 2048))
out = self.linear(out)
return out
若用户想查看脚本中第一个卷积算子的权重 :
x = self.conv1(x)
(1) 查找算子对应的数据文件
执行完训练网络后,可以从最终执行图(ms_output_trace_code_graph_{graph_id}.ir文件)中查找到该行代码所对应的多个算子信息,文件内容如下所示:
...
%4(equivoutput) = Conv2D(%1, %3) {instance name: conv2d} primitive_attrs: {visited: true, pri_format: NC1HWC0, IsFeatureMapInputList: (0), out_channel: 64, kernel_size: (3, 3), IsFeatureMapOutput: true, pad_mode: pad, stride: (1, 1, 1, 1), mode: 1, pad: (1, 1, 1, 1), pad_list: (1, 1, 1, 1), group: 1, format: NCHW, dilation: (1, 1, 1, 1), input_names: [x, w], output_names: [output], groups: 1}
: (, ) -> ()
: (, ) -> ()
: (Default/network/_backbone/conv1/Conv2D-op515)# In file /home/ma-user/miniconda3/envs/MindSpore-python3.7-aarch64/lib/python3.7/site-packages/mindspore/nn/layer/conv.py(258)/ output = self.conv2d(x, self.weight)/# In file /home/ma-user/work/xxx/Resnet50_cifar10/youhuahou/src/resnet.py(88)/ x = self.conv1(x)/
...
以上所示文件包括:
- 算子在Host侧(第一行)和Device侧(第二行,有些算子可能不存在)的输入输出情况。
: (, ) -> ()
: (, ) -> ()
- 算子名称
Default/network/_backbone/conv1/Conv2D-op515
- 算子对应训练脚本代码
# In file /home/ma-user/work/xxx/Resnet50_cifar10/src/resnet.py(88)/ x = self.conv1(x)/
根据算子名称中的op515,在Dump生成的数据文件目录({iteration})中,查找对应的Tensor数据文件。搜索到相应的文件名:Conv2D.Default_network__backbone_conv1_Conv2D-op515.58.16.1624605159829577
(2)使用海思Run包中提供的msaccucmp.py ,解析Dump出来的文件 。
注:不同的环境上msaccucmp.py文件所在的路径可能不同,可以通过find命令进行查找:
find ${run_path} -name "msaccucmp.py"
run包的安装路径。
(3)找到msaccucmp.py后,到/absolute_path目录下,运行如下命令解析Dump数据:
python ${The absolute path of msaccucmp.py} convert -d {file path of dump} -out {file path of output}
数据在Device侧的格式可能和Host侧计算图中的定义不同,异步Dump的数据格式为Device侧格式,如果想要转为Host侧格式,可以参考如何进行dump数据文件Format转换。
由于转换中存在FRACTAL_Z to NCHW格式的转换,工具当前未支持,因此需要编写自定义转换脚本,可使用示例代码包中的convert_FRACTAL_Z_to_NCHW.py脚本进行转换。将convert_FRACTAL_Z_to_NCHW.py文件放在新建的format_convert文件夹中。-c后指定为format_convert目录的上一层目录。
执行: (示例中format_convert文件夹新建在../目录下,也可以创建在其他目录下)
python /usr/local/Ascend/toolkit/tools/operator_cmp/compare/msaccucmp.py convert -d ./Conv2D.Default_network__backbone_conv1_Conv2D-op515.58.16.1624605159829577 -out ../output/ -f NCHW -c ../
在./output下生成该算子的所有输入输出数据。每个数据以.npy后缀的文件保存,生成结果如下:
Conv2D.Default_network__backbone_conv1_Conv2D-op515.58.16.1624605159829577.input.0.128x3x32x32.npy
Conv2D.Default_network__backbone_conv1_Conv2D-op515.58.16.1624605159829577.input.1.64x3x3x3.npy
Conv2D.Default_network__backbone_conv1_Conv2D-op515.58.16.1624605159829577.output.0.128x64x32x32.npy
在文件名的末尾可以看到该文件是算子的第几个输入或输出,再结合维度信息可以判断数据的含义:例如,上面三个文件依次表示该卷积算子的输入、权重和输出。
(4)通过numpy.load接口读取要查看的数据
在终端依次输入:
python->import numpy->numpy.load("Conv2D.Default_network__backbone_conv1_Conv2D-op515.58.16.1624605159829577.input.1.64x3x3x3.npy")
输出数据:
array([[[[ 0.01878 , 0.0828 , 0.03955 ],
[ 0.01727 , -0.02939 , 0.05615 ],
[-0.02402 , 0.1508 , 0.1785 ]],
...
[[ 0.1145 , 0.1186 , 0.1643 ],
[-0.148 , -0.1088 , 0.0935 ],
[-0.117 , -0.0822 , -0.1283 ]]]], dtype=float16)
二、同步Dump
1、创建json格式的配置文件
json文件名称和位置可自定义设置。
{
"common_dump_settings": {
"dump_mode": 0,
"path": "/absolute_path",
"net_name": "ResNet50",
"iteration": "0",
"input_output": 0,
"kernels": ["Default/Conv-op12"],
"support_device": [0,1,2,3,4,5,6,7]
},
"e2e_dump_settings": {
"enable": true,
"trans_flag": true
}
}
参数说明:
- dump_mode:设置成0,表示Dump出该网络中的所有算子;设置成1,表示Dump"kernels"里面指定的算子。
- path:Dump保存数据的绝对路径。
- net_name:自定义的网络名称,例如:”ResNet50”。
- iteration:指定需要Dump数据的迭代。类型为str,用“|”分离要保存的不同区间的step的数据。如”0|5-8|100-120”表示Dump参数初始值,第1个,第6个到第9个, 第101个到第121个step的数据。指定“all”,表示Dump所有迭代的数据。
- input_output:设置成0,表示Dump出算子的输入和算子的输出;设置成1,表示Dump出算子的输入;设置成2,表示Dump出算子的输出。该配置参数仅支持Ascend和CPU,GPU只能Dump算子的输出。
- kernels:算子的名称列表。开启IR保存开关context.set_context(save_graphs=True)并执行用例,从生成的IR文件trace_code_graph_{graph_id}中获取算子名称。详细说明可以参照教程:如何保存IR。
- support_device:支持的设备,默认设置成0到7即可;在分布式训练场景下,需要dump个别设备上的数据,可以只在support_device中指定需要Dump的设备Id。该配置参数在CPU上无效,因为CPU下没有device这个概念。注:本示例是单卡任务,用默认配置即可。
- enable:开启E2E Dump,如果同时开启同步Dump和异步Dump,那么只有同步Dump会生效。
- trans_flag:开启格式转换。将设备上的数据格式转换成NCHW格式。若为True,则数据会以Host侧的4D格式(NCHW)格式保存;若为False,则保留Device侧的数据格式。该配置参数在CPU上无效,因为CPU上没有format转换。
2、设置Dump环境变量
export MINDSPORE_DUMP_CONFIG=/home/ma-user/xxx/data_dump.json
此处路径应为json配置文件的绝对路径。
3、脚本修改
- 设置model.train中的dataset_sink_mode参数为False 。同步模式下Dump数据,必须采用非数据下沉模式,以保证可以获取每个step的Dump数据。
- 设置context.set_context(reserve_class_name_in_scope=False)。避免Dump文件名称过长导致Dump数据文件生成失败。
4、运行脚本
启动命令
python MindSpore_1P.py
同步Dump保存的数据目录结构如下所示:
{path}/
|-- {net_name}/
|-- {device_id}/
|-- iteration_{iteration}/
-- {op_name}_{input_output_index}_{shape}_{data_type}_{format}.bin
…
|-- graphs/
ms_output_trace_code_graph_{graph_id}.pb
ms_output_trace_code_graph_{graph_id}.ir
|-- execution_order/
ms_execution_order_graph_{graph_id}.csv
|-- .metadata/
data_dump.json
- path:data_dump.json配置文件中设置的绝对路径。
- net_name:data_dump.json配置文件中设置的网络名称。
- device_id:训练的卡号。
- graph_id:训练的图标号。
- iteration:训练的轮次。
- operator_name:算子名称。
- input_output_index :输入或输出标号,例如output_0表示该文件是该算子的第1个输出Tensor的数据。
- shape: 张量维度信息。
- data_type: 数据类型。
- format: 数据格式。
5、同步Dump数据分析样例
对于Ascend场景,在通过Dump功能将脚本对应的图保存到磁盘上后,会产生最终执行图文件ms_output_trace_code_graph_{graph_id}.ir。该文件中保存了对应的图中每个算子的堆栈信息。
若用户想查看Resnet50脚本(同异步Dump数据分析样例)中第一个卷积算子的权重:
x = self.conv1(x)
(1) 查找算子对应的数据文件
与异步Dump数据分析样例中查找方式相同。搜索到相应的文件名:Default--network-TrainOneStepCell--network-WithLossCell--_backbone-ResNet--conv1-Conv2d--Conv2D-op516_input_1_shape_64_3_3_3_Float32_DefaultFormat.bin
文件名中可以得到如下信息:
- shape: 张量维度是(64,3,3,3);
- data_type: 数据类型为Float32;
通过numpy.fromfile接口,还原数据:
(2)在终端中依次执行:
python->import numpy->array = numpy.fromfile("Default--network-TrainOneStepCell--network-WithLossCell--_backbone-ResNet--conv1-Conv2d--Conv2D- op516_input_1_shape_64_3_3_3_Float32_DefaultFormat.bin", numpy.float32)->numpy.reshape(array, (64,3,3,3))
还原原始shape数据:
array([[[[-0.01689148, 0.05319214, 0.00757217],
[ 0.02868652, 0.00693512, 0.08831787],
[ 0.01548767, 0.20080566, 0.22070312]],
...
[[ 0.09954834, 0.07037354, 0.15905762],
[-0.14477539, -0.11743164, 0.11804199],
[-0.16235352, -0.13134766, -0.13427734]]]], dtype=float32)