本文针对静态图模式,介绍如何运用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)