From 6e00a9310bf8828a14625a11a0c5c419dec0ce6d Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Tue, 5 Dec 2023 14:03:01 +0800 Subject: [PATCH 01/16] =?UTF-8?q?[ptdbg=5Fascend]=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=89=8B=E5=86=8C=E6=96=B0=E5=A2=9Esummary=5Fcompare=E5=8F=82?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...03\275\350\257\264\346\230\216_v4.0.T3.md" | 1666 +++++++++++++++++ 1 file changed, 1666 insertions(+) create mode 100644 "debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" new file mode 100644 index 000000000..86ed4d935 --- /dev/null +++ "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" @@ -0,0 +1,1666 @@ +# **PyTorch精度工具使用指南** + +本文主要介绍PyTorch精度工具精度工具ptdbg_ascend的使用以及精度比对场景示例。 + +ptdbg_ascend工具的原理及安装请参见《[PyTorch精度工具](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/README.md)》。 + +## PyTorch精度比对总体流程 + +1. 准备CPU或GPU训练工程。 + +2. 在环境下安装ptdbg_ascend工具。 + +3. 在训练脚本内插入ptdbg_ascend工具dump接口。 + +4. 执行训练dump数据。 + +5. 将CPU或GPU训练工程迁移为NPU训练工程。 + + 请参见《[PyTorch模型迁移和训练指南](https://www.hiascend.com/document/detail/zh/canncommercial/63RC1/modeldevpt/ptmigr/ptmigr_0001.html)》。 + +6. 在NPU环境下安装ptdbg_ascend工具。 + +7. 在NPU训练脚本内插入ptdbg_ascend工具dump接口。 + +8. NPU环境下执行训练dump数据。 + +9. 创建并配置精度比对脚本,例如compare.py。 + +10. 执行CPU或GPU dump与NPU dump数据的精度比对。 + +11. 比对结果分析。 + +## 场景化示例 + +本章节主要介绍通过ptdbg_ascend工具进行精度比对和分析,主要使用“**CPU或GPU及NPU精度数据dump**”和“**CPU或GPU与NPU精度数据比对**”章节中介绍的ptdbg_ascend工具接口。 + +### 单卡场景精度比对 + +**精度分析建议** + +PyTorch训练场景的精度问题分析建议参考以下思路进行精度比对和比对结果分析: + +1. 整网比对:dump整网数据并进行精度比对,初步定位异常范围。 +2. 缩小范围:根据Accuracy Reached or Not找出不符合精度标准的API。 +3. 范围比对:对不符合精度标准的API重新dump。 +4. 分析原因并优化:分析API精度不符合标准的原因并进行优化调整。 +5. 整网比对:重新进行整网比对,判断优化后的API是否已符合精度标准以及是否出现新的精度问题。 +6. 重复1~5步,直到不存在精度问题为止。 + +**精度分析示例** + +1. dump整网数据。 + + 分别dump CPU或GPU以及NPU数据,在PyTorch训练脚本插入dump接口,示例代码如下(下面以NPU为例,CPU或GPU dump基本相同): + + ```python + from ptdbg_ascend import * + + # 在main函数开始前固定随机数 + seed_all() + + # 配置dump数据目录路径和名称 + set_dump_path("./npu_dump", dump_tag='all') + + # 注册dump回调函数 + register_hook(model, acc_cmp_dump) + + ... + + # 在第一个迭代开始的位置开启dump和堆栈模式,同时为保证数据完整性开启dump bool和整型的tensor以及浮点、bool和整型的标量 + set_dump_switch("ON", mode="api_stack", filter_switch="OFF") + + ... + + # 在第一个迭代结束的位置关闭dump + set_dump_switch("OFF") + ``` + +2. 比对整网数据。 + + 第1步中的NPU dump数据文件为npu_dump.pkl,假设NPU dump npy数据目录为npu_dump,GPU dump数据文件为gpu_dump.pkl,GPU dump npy数据目录为gpu_dump。 + + 创建并配置精度比对脚本,以创建compare.py为例,示例代码如下: + + ```python + from ptdbg_ascend import * + dump_result_param={ + "npu_pkl_path": "./npu_dump/all_v2.0/rank0/api_stack_dump.pkl", + "bench_pkl_path": "./gpu_dump/all_v2.0/rank0/api_stack_dump.pkl", + "npu_dump_data_dir": "./npu_dump/all_v2.0/rank0/api_stack_dump", + "bench_dump_data_dir": "./gpu_dump/all_v2.0/rank0/api_stack_dump", + "is_print_compare_log": True + } + compare(dump_result_param, "./output") + ``` + + 执行比对: + + ```bash + python3 compare.py + ``` + + 在output目录下生成结果文件,包括:`compare_result_{timestamp}.csv`和`advisor_{timestamp}.txt` + +3. 找出存在问题的API。 + + 1. 根据`advisor_{timestamp}.txt`或打屏信息的提示,可找到存在精度问题的算子(Suspect Nodes)和专家建议(Expert Advice) + + ![auto_analyze_log](img/auto_analyze_log.png) + + 2. 根据第2步结果文件`compare_result_{timestamp}.csv`中的Accuracy Reached or No字段显示为NO的API,针对该API执行后续比对操作,分析该API存在的精度问题。 + +4. (可选)提取指定API的堆栈信息和dump数据统计信息。 + + 通过parse接口可以清晰的显示特定API的堆栈信息和dump数据统计信息,结合堆栈信息分析代码中可能存在的精度问题。 + + 创建并配置提取脚本,以创建parse.py为例,示例代码如下: + + ```python + from ptdbg_ascend import * + + # 提取dump信息中第1次调用的API:Torch_batch_normal的堆栈信息及数据统计信息 + parse("./npu_dump/all_v2.0/rank0/api_stack_dump.pkl", "Torch_batch_normal_1_forward") + ``` + + 执行提取: + + ```bash + python3 parse.py + ``` + + + +5. (可选)指定API dump数据。 + + - dump指定前向API的ACL级别数据 + + ```python + from ptdbg_ascend import * + + # 固定随机数,开启确定性计算 + seed_all(mode=True) + set_dump_path("./dump_path", dump_tag='forward') + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + + # dump指定前向API的ACL级别数据、bool和整型的tensor以及浮点、bool和整型的标量 + set_dump_switch("ON", mode="acl", scope=["Tensor_permute_1_forward"], filter_switch="OFF") + + ... + + set_dump_switch("OFF") + ``` + + - dump指定反向API的ACL级别数据 + + ```python + from ptdbg_ascend import * + + # 固定随机数,开启确定性计算 + seed_all(mode=True) + set_dump_path("./dump_path", dump_tag='backward') + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + + # dump指定反向API的ACL级别数据、bool和整型的tensor以及浮点、bool和整型的标量 + set_dump_switch("ON", mode="acl", scope=["Functional_conv2d_1_backward"], filter_switch="OFF") + set_backward_input(["./npu_dump/all_v2.0/rank0/api_stack_dump/Functional_conv2d_1_backward_input.0.npy"]) + + ... + + set_dump_switch("OFF") + ``` + +6. (可选)重新比对。 + + 根据第4或5步的dump数据重新配置compare.py并执行比对,可以对单API模型进行问题复现。 + +**注意事项** + +* dump_mode="acl"场景下,会增加npu的内存消耗,请谨慎开启。 +* 部分API存在调用嵌套关系,比如functional.batch_norm实际调用torch.batch_norm,该场景会影响acl init初始化多次,导致功能异常。 + +### 多卡场景精度比对 + +精度工具支持多卡场景的精度比对,多卡场景的dump步骤与单卡场景完全一致,请参见“**单卡场景精度比对**”章节,不同的是多卡数据精度比对时需要使用“compare_distributed”函数进行比对。如下示例: + +说明:多机多卡场景需要每个设备单独执行比对操作。 + +假设NPU dump npy数据目录为npu_dump/dump_conv2d_v1.0,GPU dump npy数据目录为gpu_dump/dump_conv2d_v1.0。 + +1. 创建比对脚本,例如compare_distributed.py,拷贝如下代码。 + + ```python + from ptdbg_ascend import * + compare_distributed('./npu_dump/ptdbg_dump_v2.0', './gpu_dump/ptdbg_dump_v2.0', './output') + ``` + +2. 执行比对: + + ```bash + python3 compare_distributed.py + ``` + +两次运行须用相同数量的卡,传入`compare_distributed`的两个文件夹下须有相同个数的rank文件夹,且不包含其他无关文件,否则将无法比对。 + +**多卡set_dump_path注意事项** + +多卡一般为多进程,须保证每个进程都正确调用set_dump_path,或把set_dump_path插入到import语句后,如: + +```python +from ptdbg_ascend import * +seed_all() +set_dump_path('./dump_resnet') +``` + +如此可保证set_dump_path在每个进程都被调用。 + +**多卡register_hook注意事项** + +register_hook需要在set_dump_path之后调用,也需要在每个进程上被调用,建议在搬运模型数据到卡之后调用。识别方法如下: + +- 找到训练代码中遍历epoch的for循环或遍历数据集的for循环,把register_hook放到循环开始前即可。 +- 找到训练代码中调用DDP或者DistributedDataParallel的代码行,把register_hook放到该代码行所在的代码块之后。 +- 若代码中均无以上两种情况,需要保证register_hook在模型定义之后插入,并配置rank参数。rank参数获取rank_id请参见“**[rank_id获取方法](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/rank_id获取方法.md)**”。 + +### NPU vs NPU精度比对 + +对于NPU vs NPU场景,是针对同一模型,进行迭代(模型、API版本升级或设备硬件升级)时存在的精度下降问题,对比相同模型在迭代前后版本的API计算数值,进行问题定位。 + +一般情况下迭代涉及NPU自定义算子,因此,可以仅dump NPU自定义算子进行比对。比对精度问题分析请参见“**单卡场景精度比对**”章节。 + +工具当前支持dump NPU自定义算子如下: + +| 序号 | NPU自定义算子 | +| :--- | ----------------------------------- | +| 1 | torch_npu.one_ | +| 2 | torch_npu.npu_sort_v2 | +| 3 | torch_npu.npu_transpose | +| 4 | torch_npu.npu_broadcast | +| 5 | torch_npu.npu_dtype_cast | +| 6 | torch_npu.empty_with_format | +| 7 | torch_npu.npu_one_hot | +| 8 | torch_npu.npu_stride_add | +| 9 | torch_npu.npu_ps_roi_pooling | +| 10 | torch_npu.npu_roi_align | +| 11 | torch_npu.npu_nms_v4 | +| 12 | torch_npu.npu_iou | +| 13 | torch_npu.npu_nms_with_mask | +| 14 | torch_npu.npu_pad | +| 15 | torch_npu.npu_bounding_box_encode | +| 16 | torch_npu.npu_bounding_box_decode | +| 17 | torch_npu.npu_batch_nms | +| 18 | torch_npu.npu_slice | +| 19 | torch_npu._npu_dropout | +| 20 | torch_npu.npu_indexing | +| 21 | torch_npu.npu_ifmr | +| 22 | torch_npu.npu_max | +| 23 | torch_npu.npu_scatter | +| 24 | torch_npu.npu_layer_norm_eval | +| 25 | torch_npu.npu_alloc_float_status | +| 26 | torch_npu.npu_get_float_status | +| 27 | torch_npu.npu_clear_float_status | +| 28 | torch_npu.npu_confusion_transpose | +| 29 | torch_npu.npu_bmmV2 | +| 30 | torch_npu.fast_gelu | +| 31 | torch_npu.npu_sub_sample | +| 32 | torch_npu.npu_deformable_conv2d | +| 33 | torch_npu.npu_mish | +| 34 | torch_npu.npu_anchor_response_flags | +| 35 | torch_npu.npu_yolo_boxes_encode | +| 36 | torch_npu.npu_grid_assign_positive | +| 37 | torch_npu.npu_normalize_batch | +| 38 | torch_npu.npu_masked_fill_range | +| 39 | torch_npu.npu_linear | +| 40 | torch_npu.npu_bert_apply_adam | +| 41 | torch_npu.npu_giou | +| 42 | torch_npu.npu_ciou | +| 43 | torch_npu.npu_diou | +| 44 | torch_npu.npu_sign_bits_pack | +| 45 | torch_npu.npu_sign_bits_unpack | +| 46 | torch_npu.npu_flash_attention | +| 47 | torch_npu.npu_scaled_masked_softmax | +| 48 | torch_npu.npu_rotary_mul | +| 49 | torch_npu.npu_roi_align | +| 50 | torch_npu.npu_roi_alignbk | +| 51 | torch_npu.npu_ptiou | +| 52 | torch_npu.npu_fusion_attention | + +### 通信API的数据dump + +通信类API数据可以使用全量dump方式获取,若只dump通信类API数据,可以使用如下示例: + +```python +debugger.configure_hook(mode="api_list", api_list=["distributed"]) +``` + +或 + +```python +set_dump_switch("ON", mode="api_list", api_list=["distributed"]) +``` + +通信类API支持列表: + +| 序号 | Distributed | +| :--- | -------------------- | +| 1 | send | +| 2 | recv | +| 3 | broadcast | +| 4 | all_reduce | +| 5 | reduce | +| 6 | all_gather | +| 7 | gather | +| 8 | isend | +| 9 | irecv | +| 10 | scatter | +| 11 | reduce_scatter | +| 12 | _reduce_scatter_base | +| 13 | _all_gather_base | + +### 溢出检测场景 + +溢出检测是针对NPU的PyTorch API,检测是否存在溢出的情况。当前仅支持识别aicore浮点溢出。 + +溢出检测原理:针对溢出阶段,开启acl dump模式,重新对溢出阶段执行,落盘数据。 + +建议按照如下步骤操作: + +1. 在NPU环境下安装ptdbg_ascend工具。 + +2. 在NPU训练脚本内插入ptdbg_ascend工具溢出检测接口。 + + - 示例1:全量溢出检测 + + ```python + from ptdbg_ascend import * + seed_all() + # 配置溢出数据目录路径和名称 + set_dump_path("./overflow_dump") + ... + # 设置检测到3次溢出后退出训练 + register_hook(model, overflow_check, overflow_nums=3) + + ... + ``` + + 多卡使用时各卡单独计算溢出次数。 + + - 示例2:dump指定API的ACL级别溢出数据 + + ```python + from ptdbg_ascend import * + seed_all() + # 配置溢出数据目录路径和名称 + set_dump_path("./overflow_dump") + ... + # dump指定API的ACL级别溢出数据 + register_hook(model, overflow_check, dump_mode='acl', dump_config='./dump.json') + + # 在期望溢出检测的step位置开始前打开溢出检测开关 + set_overflow_check_switch("ON") + + ... + + # 在step结束的位置关闭溢出检测开关 + set_overflow_check_switch("OFF") + + ... + ``` + + - 示例3:dump指定反向API的ACL级别的溢出数据 + + 1. 进行全量溢出检测 + + ```python + from ptdbg_ascend import * + seed_all() + # 配置溢出数据目录路径和名称 + set_dump_path("./overflow_dump") + ... + # 设置检测到3次溢出后退出训练 + register_hook(model, overflow_check) + + ... + ``` + + 2. dump指定反向API的ACL级别的溢出数据 + + ```python + from ptdbg_ascend import * + seed_all() + # 配置溢出数据目录路径和名称 + set_dump_path("./overflow_dump") + ... + # dump指定反向API的ACL级别溢出数据 + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + set_dump_switch("ON", mode="acl", scope=["Functional_conv2d_1_backward"]) + set_backward_input(["./npu_dump/ptdbg_dump_v2.0/rank0/dump/Functional_conv2d_1_backward_input.0.npy"]) + ``` + + 针对前向溢出API,可以通过overflow_nums,配置允许的溢出次数,并将每次溢出API的全部ACL数据dump下来,到达指定溢出次数后停止,停止后会看到堆栈打印包含如下字段。 + + ```bash + ValueError: [overflow xxx times]: dump file is saved in 'xxxxx.pkl'. + ``` + + 其中xxx times为用户设置的次数,xxxxx.pkl为文件生成路径。 + +3. NPU环境下执行训练dump溢出数据。 + + 针对输入正常但输出存在溢出的API,会训练执行目录下将溢出的API信息dump并保存为`forward_info_{pid}.json`和`backward_info_{pid}.json`,通过 [Ascend模型精度预检工具](https://gitee.com/ascend/att/tree/master/debug/accuracy_tools/api_accuracy_checker)对json文件进行解析,输出溢出API为正常溢出还是非正常溢出,从而帮助用户快速判断。 + + 精度预检工具执行命令如下: + + ```bash + # 下载att代码仓后执行如下命令 + export PYTHONPATH=$PYTHONPATH:$ATT_HOME/debug/accuracy_tools/ + cd $ATT_HOME/debug/accuracy_tools/api_accuracy_checker/run_ut + python run_overflow_check.py -forward ./forward_info_0.json + ``` + + 反向过程溢出的API暂不支持精度预检功能。 + + 当重复执行溢出检测dump操作时,需要删除上一次dump目录下的溢出检测dump数据,否则将因重名而报错。 + +**注意事项** + +* dump_mode="acl"场景下,会增加npu的内存消耗,请谨慎开启。 +* 部分API存在调用嵌套关系,比如functional.batch_norm实际调用torch.batch_norm,该场景会影响acl init初始化多次,导致功能异常。 +* 混合精度动态loss scale场景下,正常训练会有"Gradient overflow. SKipping step"日志,添加溢出检测后日志消失,可以通过设置环境变量export OVERFLOW_DEBUG_MODE_ENABLE=1,并将register_hook位置调整amp.initialize之前解决。此功能需要cann包配套支持,不支持版本执行报错EZ3003。 + +## debugger方式dump和溢出检测(推荐) + +### PrecisionDebugger模块 + +**功能说明** + +PrecisionDebugger模块包含dump和溢出检测功能的总体配置项。可以指定dump目录,设置dump或溢出检测功能,指定dump的卡和迭代。 + +可以在from ptdbg_ascend import *和模型初始化之间的任意位置添加该模块。 + +**原型** + +```python +PrecisionDebugger(dump_path=None, hook_name=None, rank=None, step=[], enable_dataloader=False): +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ----------------- | ------------------------------------------------------------ | -------- | +| dump_path | 设置dump数据目录路径,参数示例:"./dump_path"。
默认在dump_path目录下生成`ptdbg_dump_{version}`目录,并在该目录下生成`dump.pkl`文件以及`dump`数据文件保存目录。
当**configure_hook**函数配置了mode参数时,`dump.pkl`文件以及`dump`数据文件保存目录名称添加mode参数值为前缀,详情请参见“**dump数据存盘说明**”。
未配置dump_path时,也可以通过环境变量ASCEND_WORK_PATH配置dump路径,此时dump数据将落盘在${ASCEND_WORK_PATH}/dump_data下,自定义配置dump_path优先级高于环境变量,dump_path和环境变量需要二选一。 | 否 | +| hook_name | dump模式,可取值dump和overflow_check,表示dump和溢出检测功能,二选一。 | 是 | +| rank | 指定对某张卡上的数据进行dump或溢出检测,默认未配置(表示dump所有卡的数据),须根据实际卡的Rank ID配置。应配置为大于0的正整数,且须根据实际卡的Rank ID配置,若所配置的值大于实际训练所运行的卡的Rank ID,则dump数据为空,比如当前环境Rank ID为0~7,实际训练运行0~3卡,此时若配置Rank ID为4或不存在的10等其他值,此时dump数据为空。 | 否 | +| step | 指定dump某个step的数据,默认未配置,须指定为训练脚本中存在的step。step为list格式,可配置逐个step,例如:step=[0,1,2];也可以配置step范围,例如:step=list(range(0,9)),表示dump第0到第8个step。 | 否 | +| enable_dataloader | 自动控制开关,可取值True(开启)或False(关闭),默认为False。配置为True后自动识别dump step参数指定的迭代,并在该迭代执行完成后退出训练,此时start和stop函数可不配置,开启该开关要求训练脚本是通过torch.utils.data.dataloader方式加载数据;配置为False则需要配置start和stop函数,并在最后一个stop函数后或一个step结束的位置添加debugger.step()。 | 否 | + +### configure_hook函数(可选) + +**功能说明** + +设置dump范围。 + +建议在**PrecisionDebugger**模块与模型初始化之间的任意位置添加,不添加此函数时默认使用mode="api_stack" dump整网数据。 + +**原型** + +dump: + +```python +debugger.configure_hook(mode="api_stack", scope=[], api_list=[], filter_switch="OFF", acl_config=None, backward_input=[], input_output_mode=["all"], summary_only=False) +``` + +溢出检测: + +```python +debugger.configure_hook(mode=None, acl_config=None, overflow_nums=1, need_replicate=False) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ----------------- | ------------------------------------------------------------ | -------- | +| mode | dump模式。可取值"all"、"list"、"range"、"stack"、"acl"、"api_list"、"api_stack",各参数含义请参见本节的“**函数示例**”。参数示例:mode="list"。默认为api_stack。该参数配置值将作为dump数据文件名的前缀,详情请参见“**dump数据存盘说明**”。 | 否 | +| scope或api_list | dump范围。根据model配置的模式选择dump的API范围,mode="api_list"时,需要配置api_list=[],其他模式有需要时配置scope=[]。参数示例:scope=["Tensor_permute_1_forward", "Tensor_transpose_2_forward"]、api_list=["relu"]。默认为空。 | 否 | +| filter_switch | dump bool和整型的tensor以及浮点、bool和整型的标量的过滤开关。可取值"ON"(表示开启过滤,即不dump)或"OFF"(表示关闭过滤)。参数示例:filter_switch="ON"。默认不配置,即filter_switch="OFF",表示dump上述数据。 | 否 | +| acl_config | acl dump的配置文件。mode="acl"时,该参数必选;mode为其他值时,该参数不选。参数示例:acl_config='./dump.json'。dump.json配置文件详细介绍请参见“**dump.json配置文件说明**”。 | 否 | +| backward_input | 该输入文件为首次运行训练dump得到反向API输入的.npy文件。例如若需要dump Functional_conv2d_1 API的反向过程的输入输出,则需要在dump目录下查找命名包含Functional_conv2d_1、backward和input字段的.npy文件。 | 否 | +| input_output_mode | dump数据过滤。可取值"all"、"forward"、"backward"、"input"和"output",表示仅保存dump的数据中文件名包含"forward"、"backward"、"input"和"output"的前向、反向、输入或输出的.npy文件。参数示例input_output_mode=["backward"]或input_output_mode=["forward", "backward"]。默认为all,即保存所有dump的数据。除了all参数只能单独配置外,其他参数可以自由组合。 | 否 | +| summary_only | dump npy文件过滤,可取值True或False,配置为True后仅dump保存API统计信息的pkl文件,参数示例:summary_only=False,默认为False。 | 否 | +| overflow_nums | 控制溢出次数,表示第N次溢出时,停止训练,过程中检测到溢出API对应ACL数据均dump。参数示例:overflow_nums=3。配置overflow_check时可配置,默认不配置,即检测到1次溢出,训练停止,配置为-1时,表示持续检测溢出直到训练结束。 | 否 | +| need_replicate | 过程dump数据生成开关,执行溢出检测时,dump目录下会生成forward_real_data和backward_real_data的过程dump数据目录,可取值True(生成)或False(不生成),默认不生成。 | 否 | + +**函数示例** + +configure_hook可配置多种dump模式,示例如下: + +说明:以下均以dump部分API数据为例,API名可以从首次dump整网数据的结果csv文件中的NPU Name或Bench Name列获取。 + +- 示例1:dump指定API列表 + + ```python + debugger.configure_hook(mode="list", scope=["Tensor_permute_1_forward", "Tensor_transpose_2_forward", "Torch_relu_3_backward"]) + ``` + +- 示例2:dump指定范围 + + ```python + debugger.configure_hook(mode="range", scope=["Tensor_abs_1_forward", "Tensor_transpose_3_forward"]) + ``` + +- 示例3:STACK模式,只dump堆栈信息 + + ```python + debugger.configure_hook(mode="stack", scope=["Tensor_abs_1_forward", "Tensor_transpose_3_forward"]) + ``` + +- 示例4:dump指定前向API的ACL级别数据 + + ```python + debugger.configure_hook(mode="acl", scope=["Tensor_permute_1_forward"], acl_config="./dump.json") + ``` + +- 示例4:dump指定反向API的ACL级别数据 + + ```python + debugger.configure_hook(mode="acl", scope=["Functional_conv2d_1_backward"], acl_config="./dump.json", backward_input=["./npu_dump/dump_conv2d_v2.0/rank0/dump/Functional_conv2d_1_backward_input.0.npy"]) + ``` + +- 示例5:dump指定某一类API的API级别输入输出数据 + + ```python + debugger.configure_hook(mode="api_list", api_list=["relu"]) + ``` + + mode="api_list"时不配置scope。 + +- 示例6:dump全部API级别输入输出数据以及相应堆栈信息 + + ```python + debugger.configure_hook(mode="api_stack") + ``` + + mode="api_stack"时不配置scope。 + +- 示例7: dump全部API级别输入输出数据并包含bool和整型的tensor以及浮点、bool和整型的标量,配置为OFF,会dump bool和整型数据 + + ```python + debugger.configure_hook(filter_switch="OFF") + ``` + + 配置filter_switch="OFF"同时也可以配置mode、scope和api_list,除dump ACL级别数据。 + +- 示例8:仅保存dump的数据文件名包含“backward”的反向.npy文件 + + ```python + debugger.configure_hook(input_output_mode=["backward"]) + ``` + +- 示例9:仅dump pkl文件 + + ```python + debugger.configure_hook(summary_only=True) + ``` + +- 示例10:溢出检测dump + + ```python + debugger.configure_hook(overflow_nums=1) + ``` + + dump执行时会在**PrecisionDebugger**模块的dump_path参数指定的目录下生成ptdbg_dump_{version}目录,保存溢出数据。 + + 多卡场景时,需要检测到至少有一张卡溢出次数达到overflow_nums时,训练结束。 + + 仅支持NPU环境。 + +- 示例11:dump溢出API的ACL级别数据 + + ```python + debugger.configure_hook(mode="acl", acl_config="./dump.json") + ``` + + 该场景会在原有数据基础上,额外在dump.json文件配置的dump_path目录下生成一份ACL算子数据,该数据可通过“**ptdbg_ascend.parse**”工具进行解析。 + + 仅支持NPU环境。 + +### start函数(可选) + +**功能说明** + +dump或溢出检测启动函数。 + +在模型初始化之后的任意位置添加。 + +**原型** + +```python +debugger.start() +``` + +该函数为类函数,可以使用debugger.start()也可以使用PrecisionDebugger.start()。 + +### stop函数(可选) + +**功能说明** + +dump或溢出检测停止函数。 + +在**start**函数之后的任意位置添加。 + +**原型** + +```python +debugger.stop() +``` + +该函数为类函数,可以使用debugger.stop()也可以使用PrecisionDebugger.stop()。 + +### 示例代码(自动模式) + +- 示例1:开启dump + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0,2], enable_dataloader=True) + # 请勿将以上初始化流程插入到循环代码中 + ``` + +- 示例2:开启溢出检测dump + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="overflow_check", step=[0,2], enable_dataloader=True) + # 请勿将以上初始化流程插入到循环代码中 + ``` + +### 示例代码(手动模式) + +一般情况下使用自动模式可以快速方便进行dump操作,但个别大模型可能在部分卡的训练操作中没有调用dataloader,这会导致自动模式无法dump指定迭代的数据,此时需要关闭自动模式手动在迭代前后插入start()和stop()函数,并在最后一个stop函数后或一个step结束的位置添加debugger.step()以标识dump结束。 + +- 示例1:开启dump + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0]) + # 请勿将以上初始化流程插入到循环代码中 + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.step() + ``` + +- 示例2:开启溢出检测dump + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="overflow_check", step=[0]) + # 请勿将以上初始化流程插入到循环代码中 + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.step() + ``` + +## CPU或GPU及NPU精度数据dump + +### 总体说明 + +- 本节主要介绍CPU或GPU及NPU精度数据dump所需要的函数以及示例。 + +- ptdbg_ascend工具默认情况下仅dump PyTorch模型的API输入输出数据进行精度比对,若在比对结果中发现某个API下可能存在ACL的精度问题,那么可以选择dump该API的ACL级别数据进行精度分析。 + +- 某些torch api的输出不是Tensor类型的数据。对于此类API的反向过程进行ACL dump,工具会在运行日志中给出对应的Warning(is not of tensor type and cannot be automatically derived)提示。如若想要进行该类API反向ACL dump,可以通过手动构建单API用例的方式进行ACL dump,具体用例可参见“**[反向ACL dump用例说明](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/%E5%8F%8D%E5%90%91ACL%20dump%E7%94%A8%E4%BE%8B%E8%AF%B4%E6%98%8E.md)**”。 + +- 工具性能:dump数据量较小时(小于5G),参考dump速度0.1GB/s;dump数据量较大时,参考dump速度0.2GB/s。 + 推荐环境配置:独占环境,CPU核心数192,固态硬盘(IO速度参考:固态硬盘 > 500MB/s,机械硬盘60 ~ 170MB/s)。 + + 用户环境性能弱于标准约束或非独占使用的比对速度酌情向下浮动。Dump速度的计算方式:Dump数据量/(单个step添加Dump耗时-原始单个step耗时)。 + +### 约束 +- 进行CPU或GPU数据dump时,请安装torch包而非torch_npu包,避免工具无法识别使用场景,导致失败。 + +- TASK_QUEUE_ENABLE环境变量会导致API下发和执行异步进行,因此在ACL dump前需要将TASK_QUEUE_ENABLE关闭,即export TASK_QUEUE_ENABLE=0。 + +- 不建议在PyTorch训练脚本中同时添加dump接口和性能数据采集(如Ascend PyThon Profiler)接口,二者可能相互影响导致数据不准确。 + +### seed_all + +**功能说明** + +固定随机数。通过固定随机数保证模型的输入或输出一致。在训练主函数开始前调用,避免随机数固定不全。 + +dump操作必选。 + +**函数原型** + +```python +seed_all(seed=1234, mode=False) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------ | ------------------------------------------------------------ | -------- | +| seed | 随机数种子。参数示例:seed=1000。默认值为:1234。 | 否 | +| mode | 确定性计算模式。可配置True或False。参数示例:mode=True。默认为False。
即使在相同的硬件和输入下,API多次执行的结果也可能不同,开启确定性计算是为了保证在相同的硬件和输入下,API多次执行的结果相同。
确定性计算会导致API执行性能降低,建议在发现模型多次执行结果不同的情况下开启。
rnn类算子、ReduceSum、ReduceMean等算子可能与确定性计算存在冲突,若开启确定性计算后多次执行的结果不相同,则考虑存在这些算子。 | 否 | + +**函数示例** + +seed_all函数的随机数种子,取默认值即可,无须配置;第二个参数默认关闭,不开启确定性计算时也无须配置。 + +- 示例1:仅固定随机数,不开启确定性计算 + + ```python + seed_all() + ``` + +- 示例2:固定随机数,开启确定性计算 + + ```python + seed_all(mode=True) + ``` + +**固定随机数范围** + +seed_all函数可固定随机数的范围如下表。 + +| API | 固定随机数 | +| ---------------------------------------- | --------------------------- | +| os.environ['PYTHONHASHSEED'] = str(seed) | 禁止Python中的hash随机化 | +| random.seed(seed) | 设置random随机生成器的种子 | +| np.random.seed(seed) | 设置numpy中随机生成器的种子 | +| torch.manual_seed(seed) | 设置当前CPU的随机种子 | +| torch.cuda.manual_seed(seed) | 设置当前GPU的随机种子 | +| torch.cuda.manual_seed_all(seed) | 设置所有GPU的随机种子 | +| torch_npu.npu.manual_seed(seed) | 设置当前NPU的随机种子 | +| torch_npu.npu.manual_seed_all(seed) | 设置所有NPU的随机种子 | +| torch.backends.cudnn.enable=False | 关闭cuDNN | +| torch.backends.cudnn.benchmark=False | cuDNN确定性地选择算法 | +| torch.backends.cudnn.deterministic=True | cuDNN仅使用确定性的卷积算法 | + +需要保证CPU或GPU以及NPU的模型输入完全一致,dump数据的比对才有意义,seed_all并不能保证模型输入完全一致,如下表所示场景需要保证输入的一致性。 + +| 场景 | 固定方法 | +| --------------- | ------------- | +| 数据集的shuffle | 关闭shuffle。 | +| dropout | 关闭dropout。 | + +关闭shuffle示例: + +```python +train_loader = torch.utils.data.DataLoader( + train_dataset, + batch_size = batch_size, + shuffle = False, + num_workers = num_workers +) +``` + +关闭dropout: + +在使用from ptdbg import *后,工具会自动将torch.nn.functional.dropout、torch.nn.functional.dropout2d、torch.nn.functional.dropout3d、torch.nn.Dropout、torch.nn.Dropout2d、torch.nn.Dropout3d的接口参数p置为0。 + +### set_dump_path + +**功能说明** + +设置数据保存目录。建议在seed_all函数之后调用且需要保证训练进程能够调用该函数;多卡时须保证每个进程都能调用该函数。 + +**函数原型** + +```python +set_dump_path(fpath=None, dump_tag='ptdbg_dump') +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| -------- | ------------------------------------------------------------ | -------- | +| fpath | 设置数据目录路径。参数示例:'./dump_path'。
默认在dump_path目录下生成`ptdbg_dump_{version}`目录,并在该目录下生成`dump.pkl`文件以及`dump`数据文件保存目录。
当set_dump_switch函数配置了mode参数时,`dump.pkl`文件以及`dump`数据文件保存目录名称添加mode参数值为前缀,详情请参见“**dump数据存盘说明**”。
未配置fpath时,也可以通过环境变量ASCEND_WORK_PATH配置dump路径,此时数据将落盘在${ASCEND_WORK_PATH}/dump_data下,自定义配置dump_path优先级高于环境变量,fpath和环境变量需要二选一。 | 否 | +| dump_tag | 设置数据目录名称。参数示例:dump_tag='dump_conv2d'。默认数据目录命名为ptdbg_dump_{version}。
{version}为当前安装ptdbg_ascend工具版本。目录结构参见“**dump数据存盘说明**”。
配置该参数会将生成的`ptdbg_dump_{version}`目录名称变更为dump_tag配置的值,如`dump_conv2d_{version}`。 | 否 | + +**函数示例** + +- 示例1:设置数据目录路径 + + ```python + set_dump_path('./dump_path') + ``` + +- 示例2:设置数据目录名称 + + ```python + set_dump_path('./dump_path', dump_tag='dump_conv2d') + ``` + + +若以相同的数据目录多次dump,则会因同名导致覆盖;多次dump建议配置不同的dump_tag。 + +### register_hook + +**功能说明** + +注册工具钩子函数。在set_dump_path之后调用。 + +dump操作必选。 + +**函数原型** + +```python +register_hook(model, hook, overflow_nums=overflow_nums, dump_mode=dump_mode, dump_config=dump_config_file) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------- | ------------------------------------------------------------ | -------- | +| hook | 注册工具的dump和溢出检测钩子。可取值overflow_check(表示溢出检测)和acc_cmp_dump(表示dump数据),二选一。 | 是 | +| overflow_nums | 控制溢出次数,表示第N次溢出时,停止训练,过程中检测到溢出API对应ACL数据均dump。参数示例:overflow_nums=3。配置overflow_check时可配置,默认不配置,即检测到1次溢出,训练停止,配置为-1时,表示持续检测溢出直到训练结束。 | 否 | +| dump_mode | 控制针对溢出API的dump模式。可取值"api"或"acl",配置acl时表示dump ACL级别的溢出数据,此时set_dump_path参数不生效,dump数据目录由dump_config的.json文件配置,参数示例:dump_mode="acl"。默认不配置,即dump API级别的溢出数据。 | 否 | +| dump_config | acl dump的配置文件。dump_mode="acl"时,该参数必选;dump_mode="api"时,该参数不选。参数示例:dump_config='./dump.json'。 | 否 | + +**函数示例** + +- 示例1:注册工具钩子函数 + + ```python + register_hook(model, acc_cmp_dump) + ``` + +- 示例2:dump指定API的ACL级别数据 + + ```python + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + ``` + + 需要配置set_dump_switch的mode="acl"以及scope指定为前向或反向API,请参见“**set_dump_switch”**的示例。 + + 该场景set_dump_path不生效,由dump_config中的dump.json文件配置dump数据目录。 + +- 示例3:溢出检测dump + + ```python + register_hook(model, overflow_check, overflow_nums=3) + ``` + + dump执行时会在set_dump_path的fpath参数指定的目录下生成ptdbg_dump_{version}目录,保存溢出数据。 + + 多卡场景时,需要检测到至少有一张卡溢出次数达到overflow_nums时,训练结束。 + + 仅支持NPU环境。 + +- 示例4:dump指定API的ACL级别溢出数据 + + ```python + register_hook(model, overflow_check, dump_mode='acl', dump_config='./dump.json') + ``` + + 该场景会在原有数据基础上,额外在dump.json文件配置的dump_path目录下生成一份ACL算子数据,该数据可通过“**ptdbg_ascend.parse**”工具进行解析。 + + 仅支持NPU环境。 + +### set_dump_switch + +**功能说明** + +设置dump范围。建议在register_hook函数之后的脚本内任意位置插入,但进行精度问题排查建议参照“场景化示例 > 单卡场景精度比对”章节的顺序,先从第一个迭代开始的位置调用并dump整网数据。 + +dump操作必选。 + +**函数原型** + +```python +def set_dump_switch(switch, mode="all", scope=[], api_list=[], filter_switch="OFF", dump_mode=["all"], summary_only=False): +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| --------------- | ------------------------------------------------------------ | -------- | +| switch | dump开关。可取值"ON"或"OFF"。须在选定dump开始的位置配置set_dump_switch("ON");dump结束的位置设置set_dump_switch("OFF")。 | 是 | +| mode | dump模式。可取值"all"、"list"、"range"、"stack"、"acl"、"api_list"、"api_stack",各参数含义请参见本节的“**函数示例**”。参数示例:mode="list"。默认为all。该参数配置值将作为dump数据文件名的前缀,详情请参见“**dump数据存盘说明**”。 | 否 | +| scope或api_list | dump范围。根据model配置的模式选择dump的API范围。参数示例:scope=["Tensor_permute_1_forward", "Tensor_transpose_2_forward"]、api_list=["relu"]。默认为空。 | 否 | +| filter_switch | dump bool和整型的tensor以及浮点、bool和整型的标量的过滤开关。可取值"ON"或"OFF"。参数示例:filter_switch="ON"。默认不配置,即filter_switch="OFF",表示dump上述数据。 | 否 | +| dump_mode | dump数据过滤。可取值"all"、"forward"、"backward"、"input"和"output",表示仅保存dump的数据中文件名包含"forward"、"backward"、"input"和"output"的前向、反向、输入或输出的.npy文件。参数示例dump_mode=["backward"]或dump_mode=["forward", "backward"]。默认为all,即保存所有dump的数据。除了all参数只能单独配置外,其他参数可以自由组合。 | 否 | +| summary_only | dump npy文件过滤,可取值True或False,配置为True后仅dump保存API统计信息的pkl文件,参数示例:summary_only=False,默认为False。 | 否 | + +**推荐配置** + +```python +set_dump_switch("ON", mode="api_stack", filter_switch="OFF") +``` + +开启dump数据和堆栈模式,同时为保证数据完整性开启dump bool和整型的tensor以及浮点、bool和整型的标量。 + +**函数示例** + +set_dump_switch可配置多种dump模式,示例如下: + +说明:以下均以dump部分API数据为例,API名可以从首次dump整网数据的结果csv文件中的NPU Name或Bench Name列获取。 + +- 示例1:dump指定API列表 + + ```python + set_dump_switch("ON", mode="list", scope=["Tensor_permute_1_forward", "Tensor_transpose_2_forward", "Torch_relu_3_backward"]) + ``` + +- 示例2:dump指定范围 + + ```python + set_dump_switch("ON", mode="range", scope=["Tensor_abs_1_forward", "Tensor_transpose_3_forward"]) + ``` + +- 示例3:STACK模式,只dump堆栈信息 + + ```python + set_dump_switch("ON", mode="stack", scope=["Tensor_abs_1_forward", "Tensor_transpose_3_forward"]) + ``` + +- 示例4:dump指定前向API的ACL级别数据 + + ```python + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + set_dump_switch("ON", mode="acl", scope=["Tensor_permute_1_forward"]) + ``` + + 需要配置register_hook的dump_mode='acl'和dump_config配置文件。 + +- 示例4:dump指定反向API的ACL级别数据 + + ```python + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + set_dump_switch("ON", mode="acl", scope=["Functional_conv2d_1_backward"]) + set_backward_input(["./npu_dump/dump_conv2d_v2.0/rank0/dump/Functional_conv2d_1_backward_input.0.npy"]) + ``` + + 需要配置register_hook的dump_mode='acl'和dump_config配置文件,并通过set_backward_input设置反向API输入的.npy文件。 + +- 示例5:dump指定某一类API的API级别输入输出数据 + + ```python + set_dump_switch("ON", mode="api_list", api_list=["relu"]) + ``` + + mode="api_list"时不配置scope。 + +- 示例6:dump全部API级别输入输出数据以及相应堆栈信息 + + ```python + set_dump_switch("ON", mode="api_stack") + ``` + + mode="api_stack"时不配置scope。 + +- 示例7: dump全部API级别输入输出数据并包含bool和整型的tensor以及浮点、bool和整型的标量,配置为OFF,会dump bool和整型数据 + + ```python + set_dump_switch("ON", filter_switch="OFF") + ``` + + 配置filter_switch="OFF"同时也可以配置mode、scope和api_list,除dump ACL级别数据。 + +- 示例8:仅保存dump的数据文件名包含“backward”的反向.npy文件 + + ```python + set_dump_switch("ON", dump_mode=["backward"]) + ``` + +- 示例9:仅dump pkl文件 + + ```python + set_dump_switch("ON", summary_only=True) + ``` + +以上示例均需要在结束dump的位置插入set_dump_switch("OFF")。 + +set_dump_switch配置mode为all或api_stack时,结束dump后,在dump目录下会自动生成compare_data.py比对脚本模板,示例如下: + +```python +from ptdbg_ascend import compare + +pkl_path = "%s" +dump_data_dir = "%s" + +dump_path_param = { + "npu_pkl_path": , + "bench_pkl_path": , + "npu_dump_data_dir": , + "bench_dump_data_dir": , + "is_print_compare_log": True +} + +compare(dump_path_param, output_path="", stack_mode="%s") +``` + +pkl_path和dump_data_dir字段会自动识别pkl和dump目录的路径,用户需要判断当前dump的环境是NPU、CPU或GPU,并将pkl_path和dump_data_dir字段填入下方dump_path_param函数对应的字段中,例如当前设备为NPU,那么填写方式如下: + +```python +from ptdbg_ascend import compare + +pkl_path = "%s" +dump_data_dir = "%s" + +dump_path_param = { + "npu_pkl_path": pkl_path, + "bench_pkl_path": , + "npu_dump_data_dir": dump_data_dir, + "bench_dump_data_dir": , + "is_print_compare_log": True +} + +compare(dump_path_param, output_path="", stack_mode="%s") +``` + +此时,另一侧数据的路径,需要用户另外识别并填入。 + +### set_overflow_check_switch + +**功能说明** + +置溢出检测范围。默认不配置该函数,全量进行溢出检测。 + +仅支持NPU环境。 + +**函数原型** + +```python +set_overflow_check_switch(switch, filter_switch='OFF') +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------- | ------------------------------------------------------------ | -------- | +| switch, | 检测开关。可取值"ON"或"OFF"。如果只在特定的step溢出检测,则在期望溢出检测的step位置开始前插入set_overflow_check_switch("ON"),在step结束的位置插入set_overflow_check_switch("OFF")。 | 是 | +| filter_switch | dump bool和整型的tensor以及浮点、bool和整型的标量的过滤开关。可取值"ON"或"OFF"。参数示例:filter_switch="ON"。默认不配置,即filter_switch="OFF",表示dump上述数据。 | 否 | + +**函数示例** + +- 示例1:指定范围溢出检测 + + ```python + register_hook(model, overflow_check) + set_overflow_check_switch("ON") + + ... + + set_overflow_check_switch("OFF") + ``` + + 该场景set_dump_path不生效,dump执行时会在当前目录自动生成ptdbg_dump_{version}目录,保存溢出数据。 + +- 示例2:前向API的ACL级别范围溢出检测 + + ```python + register_hook(model, overflow_check, dump_mode='acl', dump_config='./dump.json') + set_overflow_check_switch("ON") + + ... + + set_overflow_check_switch("OFF") + ``` + + 该场景set_dump_path不生效,由dump_config中的dump.json文件配置溢出数据目录。 + +### set_backward_input + +**功能说明** + +设置反向ACL级别dump时需要的反向输入的.npy文件。 + +**函数原型** + +```python +set_backward_input(backward_input) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| -------------- | ------------------------------------------------------------ | -------- | +| backward_input | 该输入文件为首次运行训练dump得到反向API输入的.npy文件。例如若需要dump Functional_conv2d_1 API的反向过程的输入输出,则需要在dump目录下查找命名包含Functional_conv2d_1、backward和input字段的.npy文件。 | 是 | + +**函数示例** + +```python +register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') +set_dump_switch("ON", mode="acl", scope=["Functional_conv2d_1_backward"]) +set_backward_input(["./npu_dump/dump_conv2d_v2.0/rank0/dump/Functional_conv2d_1_backward_input.0.npy"]) +``` + +## dump.json配置文件说明 + +**dump.json配置示例** + +```python +{ + "dump": + { + "dump_list":[], + "dump_path":"./dump/output", + "dump_mode":"all", + "dump_op_switch":"on" + } +} +``` + +**dump.json参数说明** + +| 字段名 | 说明 | +| -------------- | ------------------------------------------------------------ | +| dump_list | 待dump数据的API模型。为空,无需配置。 | +| dump_path | dump数据文件存储到运行环境的目录,主要用于指定ACL dump数据路径。支持配置绝对路径或相对路径。dump_path须为已存在目录。 | +| dump_mode | dump数据模式,配置如下:
- output:dump API的输出数据。默认值。
- input:dump API的输入数据。
- all:dump API的输入、输出数据。 | +| dump_op_switch | 单API模型dump数据开关,配置如下: * off:关闭单API模型dump,默认值。 * on:开启单API模型dump。 | + +**dump目录说明** + +配置register_hook的dump_config后,采集的dump数据会在{dump_path}/{time}/{deviceid}/{model_id}目录下生成,例如“/home/HwHiAiUser/output/20200808163566/0/0” + +```bash +├── 20230131172437 +│   └── 1 +│   ├── 0 +│   │   ├── Add.Add.45.0.1675157077183551 +│   │   ├── Cast.trans_Cast_0.31.0.1675157077159449 +│   │   ├── Cast.trans_Cast_5.43.0.1675157077180129 +│   │   ├── MatMul.MatMul.39.0.1675157077172961 +│   │   ├── Mul.Mul.29.0.1675157077155731 +│   │   ├── NPUAllocFloatStatus.NPUAllocFloatStatus.24.0.1675157077145262 +│   │   ├── TransData.trans_TransData_1.33.0.1675157077162791 +│   │   └── TransData.trans_TransData_4.41.0.1675157077176648 +│   ├── 1701737061 +│   │   └── Cast.trans_Cast_2.35.0.1675157077166214 +│   ├── 25 +│   │   └── NPUClearFloatStatus.NPUClearFloatStatus.26.0.1675157077150342 +│   └── 68 +│   └── TransData.trans_TransData_3.37.0.1675157077169473 +``` + +## 模块级精度数据dump + +### 总体说明 + +大模型场景下,通常不是简单的利用自动迁移能力实现GPU到NPU的训练脚本迁移,而是会对NPU网络进行一系列针对性的适配,因此,常常会造成迁移后的NPU模型存在部分子结构不能与GPU原始模型完全对应。模型结构不一致导致API调用类型及数量不一致,若直接按照API粒度进行精度数据dump和比对,则无法完全比对所有的API。 + +本节介绍的功能是对模型中的大粒度模块进行数据dump,使其比对时,对于无法以API粒度比对的模块可以直接以模块粒度进行比对。 + +模块指的是继承自nn.Module类模块,通常情况下这类模块就是一个小模型,可以被视为一个整体,dump数据时以模块为粒度进行dump。 + +### module_dump + +**功能说明** + +开启模块级精度数据dump。 + +模块级精度数据dump时必选。 + +**函数原型** + +```python +module_dump(module, module_name) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ----------- | ------------------------------------------------------------ | -------- | +| module | 网络中实例化好的nn.Module类模块的model对象。 | 是 | +| module_name | 用户自定义的该model名称。主要用于dump数据文件的命名,便于在比对时识别模块级数据。 | 是 | + +### module_dump_end + +**功能说明** + +结束模块级精度数据dump。 + +模块级精度数据dump时必选。 + +**函数原型** + +```python +module_dump_end() +``` + +### 示例代码 + +```python +# 根据需要import包 +import os +import torch +import torch.nn as nn +import torch_npu +from ptdbg_ascend import * + +torch.npu.set_device("npu:0") +# 定义一个简单的网络 +class ModuleOP(nn.Module): + def __init__(self) -> None: + super().__init__() + self.linear_1 = nnLinear(in_features=2, out_features=2) + self.linear_2 = nnLinear(in_features=2, out_features=1) + self.relu = nn.Relu() + def forward(self, x): + x1 = self.linear_1(x) + x1 = self.linear_2(x1) + r1 = self.relu(x2) + return r1 + +if __name__ == "__main__": + module = ModeleOP + + # 注册工具 + set_dump_path("./dump_data/npu") + set_dump_switch("ON") + register_hook(module, acc_cmp_dump) + + x = torch.randn(2, 2) + + module_dump(module, "MyModule") # 开启模块级精度数据dump + out = module(x) + module_dump_end() # 结束模块级精度数据dump + loss = out.sum() + loss.bachward() + set_dump_switch("OFF") +``` + +## dump数据存盘说明 + +dump结果目录结构示例如下: + +```bash +├── dump_path +│ └── ptdbg_dump_{version} +│ ├── rank0 +│ │ ├── dump +| | | ├── Tensor_permute_1_forward.npy +| | | ├── MyModule_0_forward_input.npy # 开启模块级精度数据dump时存在模块级的dump数据文件 +| | | ... +| | | └── Fcuntion_linear_5_backward_output.npy +│ │ └── dump.pkl +│ ├── rank1 +| | ├── dump +| | | └── ... +| | └── dump.pkl +│ ├── ... +│ | +| └── rank7 +``` + +其中ptdbg_dump_{version}为未设置set_dump_path的dump_tag参数时的默认命名;rank为设备上各卡的ID,每张卡上dump的数据会生成对应dump目录。 + +当使用debugger方式dump数据时,配置了PrecisionDebugger模块的step=[]参数,dump结果目录则以step为父目录,例如配置step=[0,1,2]时,dump结果目录为: + +``` +├── dump_path +│ └── step0 +│ | └── ptdbg_dump_{version} +│ | | ├── rank0 +│ | | ├── ... +│ | | ├── rank7 +| ├── step1 +| | | ├── ... +│ └── step2 +``` + +**精度比对dump场景** + +精度比对dump场景的结果如下: + +* dump.pkl文件:包含dump数据的API名称、dtype、 shape以及各数据的max、min、mean统计信息。 + +* dump目录:目录下为npy格式的dump数据。 + + npy文件保存的前缀和PyTorch对应关系如下 + + | 前缀 | Torch模块 | + | ---------- | ------------------- | + | Tensor | torch.Tensor | + | Torch | torch | + | Functional | torch.nn.functional | + | NPU | NPU亲和算子 | + | VF | torch._VF | + +当set_dump_switch或configure_hook配置mode参数(例如:mode="api_stack" )时,dump结果的文件名会添加api_stack前缀,dump结果如下: + +* api_stack_dump.pkl +* api_stack_dump目录 + +**溢出检测dump场景** + +register_hook设置了overflow_check时,检测API溢出,dump结果的文件名格式为:`{api_type}___{api_name}___{API调用次数}_{前向反向}_{当前溢出次数}`,dump结果示例如下: + +* `Tensor___add___1_forward_1.pkl` +* `Tensor___add___1_forward_1`目录 + +## CPU或GPU与NPU精度数据比对 + +### 总体说明 + +- 本节主要介绍CPU或GPU与NPU精度数据比对的函数以及示例。 + +- 比对函数均通过单独创建精度比对脚本执行,可支持单卡和多卡场景的精度数据比对。 + +- 工具性能:比对数据量较小时(参考值单份文件小于10GB),参考比对速度0.1GB/s;比对数据量较大时,参考比对速度0.3GB/s。 + 推荐环境配置:独占环境,CPU核心数192,固态硬盘(IO速度参考:固态硬盘 > 500MB/s,机械硬盘60 ~ 170MB/s)。 + + 用户环境性能弱于标准约束或非独占使用的比对速度酌情向下浮动。比对速度的计算方式:两份比对文件大小/比对耗时。 + +### 约束 + +- NPU自研API,在CPU或GPU若没有对应的API,该API的dump数据不比对。 + +- NPU与CPU或GPU的计算结果误差可能会随着模型的执行不断累积,最终会出现同一个API因为输入的数据差异较大而无法比对的情况。 + +- CPU或GPU与NPU中两个相同的API会因为调用次数不同导致无法比对或比对到错误的API,不影响整体运行,该API忽略。 + +### compare_distributed + +**功能说明** + +将CPU或GPU与NPU的dump文件进行比对,支持单卡和多卡,可同时比对多卡的dump数据。多机场景需要每个设备单独执行比对操作。可自动检索和匹配对应卡和进程所dump的数据文件,再调用compare进行比对。单机单卡时与compare函数二选一。 + +**函数原型** + +```python +compare_distributed(npu_dump_dir, bench_dump_dir, output_path, **kwargs) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| -------------- | ------------------------------------------------------------ | -------- | +| npu_dump_dir | 配置NPU环境下的dump目录,即set_dump_path函数的dump_tag参数对应的目录名称。参数示例:'./npu_dump/dump_conv2d_v2.0'。 | 是 | +| bench_dump_dir | 配置CPU、GPU或NPU环境下的dump目录,即set_dump_path函数的dump_tag参数对应的目录名称。参数示例:'./gpu_dump/dump_conv2d_v2.0'。 | 是 | +| output_path | 配置比对结果csv文件存盘目录。需要预先创建output_path目录。参数示例:'./output'。文件名称基于时间戳自动生成,格式为:`compare_result_rank{npu_ID}-rank{cpu/gpu/npu_ID}_{timestamp}.csv`。 | 是 | +| **kwargs | 支持compare的所有可选参数。 | 否 | + +**函数示例** + +创建比对脚本,例如compare_distributed.py,拷贝如下代码,具体参数请根据实际环境修改。 + +```python +from ptdbg_ascend import * +compare_distributed('./npu_dump/ptdbg_dump_v2.0', './gpu_dump/ptdbg_dump_v2.0', './output') +``` + +### compare + +**功能说明** + +将CPU或GPU与NPU的dump文件进行比对,仅支持单机单卡。 + +**函数原型** + +```python +compare(input_param, output_path, stack_mode=False, auto_analyze=True, fuzzy_match=False, summary_compare=False) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| --------------- | ------------------------------------------------------------ | -------- | +| input_param | 配置dump数据文件及目录。配置参数包括:
- "npu_pkl_path":指定NPU dump目录下的.pkl文件。参数示例:"npu_pkl_path": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl"。必选。
- "bench_pkl_path":指定CPU、GPU或NPU dump目录下的.pkl文件。参数示例:"bench_pkl_path": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl"。必选。
- "npu_dump_data_dir":"指定NPU dump目录下的dump数据目录。参数示例:"npu_dump_data_dir": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump"。必选。
- "bench_dump_data_dir":"指定CPU、GPU或NPU dump目录下的dump数据目录。参数示例:"npu_dump_data_dir": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump"。必选。
- "is_print_compare_log":配置是否开启日志打屏。可取值True或False。可选。 | 是 | +| output_path | 配置比对结果csv文件存盘目录。参数示例:'./output'。文件名称基于时间戳自动生成,格式为:`compare_result_{timestamp}.csv`。 | 是 | +| stack_mode | 配置stack_mode的开关。仅当dump数据时配置set_dump_switch的mode="api_stack"时需要开启。参数示例:stack_mode=True,默认为False。 | 否 | +| auto_analyze | 自动精度分析,开启后工具自动针对比对结果进行分析,识别到第一个精度不达标节点(在比对结果文件中的“Accuracy Reached or Not”列显示为No),并给出问题可能产生的原因(打屏展示并生成advisor_{timestamp}.txt文件)。可取值True或False,参数示例:auto_analyze=False,默认为True。 | 否 | +| fuzzy_match | 模糊匹配。开启后,对于网络中同一层级且命名仅调用次数不同的API,可匹配并进行比对。可取值True或False,参数示例:fuzzy_match=True,默认为False。 | 否 | +| summary_compare | 比对dump.pkl文件中的统计值,开启后的比对结果文件生成Max diff、Min diff和Mean diff,表示NPU dump数据中API的输入或输出与标杆数据输入或输出的最大最小平均值的差。可以通过该值判断API是否存在精度问题:当某个API的输入和输出的Max diff、Min diff和Mean diff均为0或无限趋于0,那么可以判断该API无精度问题,反之则可能存在精度问题。可取值True或False,参数示例:summary_compare=True,默认为False。 | 否 | + +**函数示例** + +单机单卡场景下创建比对脚本,例如compare.py,拷贝如下代码,具体参数请根据实际环境修改。 + +```python +from ptdbg_ascend import * +dump_result_param={ +"npu_pkl_path": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl", +"bench_pkl_path": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl", +"npu_dump_data_dir": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump", +"bench_dump_data_dir": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump", +"is_print_compare_log": True +} +compare(dump_result_param, "./output", stack_mode=True) +``` + +### parse + +**功能说明** + +解析并提取dump信息中的堆栈信息及数据统计信息。 + +**函数原型** + +```python +parse(pkl_file, moudule_name_prefix) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------------- | ------------------------------------------------------------ | -------- | +| pkl_file | 指定dump数据文件中的pkl文件名。参数示例:"./npu_dump/ptdbg_dump_v2.0/rank0/dump.pkl"。 | 是 | +| moudule_name_prefix | 指定待提取的API接口前缀。参数示例:"Torch_norm_1_forward"。 | 是 | + +**函数示例** + +创建堆栈信息及数据统计信息提取脚本,例如parse.py,拷贝如下代码,具体参数请根据实际环境修改。 + +```python +from ptdbg_ascend import * +parse("./npu_dump/ptdbg_dump_v2.0/rank0/dump.pkl", "Torch_batch_normal_1_forward") +``` + +### 计算精度评价指标 + +PyTorch精度比对是以CPU或GPU的计算结果为标杆,计算Cosine(余弦相似度)、MaxAbsErr(最大绝对误差)和MaxRelativeErr(最大相对误差),根据这两个结果判断API在运行时是否存在精度问题。 + +计算精度评价指标: + +1. Cosine:通过计算两个向量的余弦值来判断其相似度,数值越接近于1说明计算出的两个张量越相似,实际可接受阈值为大于0.99。在计算中可能会存在nan,主要由于可能会出现其中一个向量为0。 + +2. MaxAbsErr:当最大绝对误差越接近0表示其计算的误差越小,实际可接受阈值为小于0.001。 + +3. MaxRelativeErr:当最大相对误差越接近0表示其计算的误差越小。 + + 当dump数据中存在0或Nan时,比对结果中最大相对误差则出现inf或Nan的情况,属于正常现象。 + +精度比对结果csv文件中只需要通过Accuracy Reached or Not来判断计算精度是否达标,判断标准如下: + +1. Cosine < 0.99 且 MaxAbsError > 0.001时,精度不达标,标记为“No”。 +2. Cosine < 0.9,精度不达标,标记为“No”。 +3. MaxAbsError > 1,精度不达标,标记为“No”。 +4. 其余情况下记为精度达标,标记为“Yes”。 + +## ptdbg_ascend.parse数据解析功能 + +ptdbg_ascend.parse为命令行交互式界面解析工具,提供更多的数据解析功能并且展示结果。 + +主要的使用场景包括: + +- 支持指定ACL层级算子数据比对。 +- 支持指定ACL层级算子数据转换及展示。 +- 支持交互式指定pkl文件中API对应dump数据查看。 +- 支持API进行可选层级比对和打印(统计级和像素级)。 + +安装ptdbg_ascend工具后,可以通过使用命令 **python -m ptdbg_ascend.parse** 进入交互式界面,可在parse的界面中执行Shell命令,以及上述场景的相关解析命令。Ctrl+C可以退出该界面。 + +### ACL层级算子数据比对 + +- 依赖:CANN包中的msaccucmp工具。 + +- 输入以下比对命令进行数据比对。 + + ```bash + vc -m my_dump_path -g golden_dump_path [-out output_path] + ``` + + | 参数名称 | 说明 | 是否必选 | + | -------- | ------------------------------------------------------------ | -------- | + | -m | 待比对dump数据目录。 | 是 | + | -g | dump数据目录。 | 是 | + | -out | 结果输出目录。 | 否 | + | -asc | 指定msaccucmp路径,默认路径为:/usr/local/Ascend/ascend-toolkit/latest/tools/operator_cmp/compare/msaccucmp.py。 | 否 | + + - 输出结果:result_{timestamp}.csv文件。 + - 若指定-out参数需要用户传入输出路径,并且路径需要已存在。 + - 若未指定输出目录, 则比对结束后将结果保存在默认目录 “./parse_data/comapre_result”中,比对结束后会打印log提示输出结果存放路径。 + +**示例** + +```bash +# 传入待比对数据目录以及标杆数据目录 +Parse >>> vc -m ./my_dump_path -g ./golden_data_path +...... +# 比对结果打印 +[INFO] The comparison result have been written to "./parse_data/compare_result/result_20230818104735.csv". +[INFO] The command was completed and took 6 seconds. +[INFO] Compare finished!! +``` + +### ACL算子数据的npy转换 + +- 依赖:CANN包中的msaccucmp工具。 + +- 输入以下转换命令进行数据转换, 将ACL级别dump数据转为npy文件。 + + ```bash + dc -n file_name/file_path [-f format] [-out output_path] + ``` + + | 参数名称 | 说明 | 是否必选 | + | -------- | ------------------------------------------------------------ | -------- | + | -n | 需转换的dump数据文件或dump数据文件目录。 | 是 | + | -f | 开启format转换,指定该参数时需要配置format格式,若未指定该参数,则直接转换为npy格式。 | 否 | + | -out | 结果输出目录。 | 否 | + | -asc | 指定msaccucmp路径,默认路径为:/usr/local/Ascend/ascend-toolkit/latest/tools/operator_cmp/compare/msaccucmp.py | 否 | + + [^]: 若传入单个dump文件,则转换单个文件,若传入dump文件目录则转换目录下所有dump文件。 + + - 输出结果:npy文件。 + - 若指定-out参数需要用户传入输出路径,并且路径需要已存在。 + - 若未指定输出目录, 则比对结束后将结果保存在默认目录 “./parse_data/convert_result”中,比对结束后会打印log提示输出结果存放路径及转换结果。 + +- 输入以下命令,展示npy数据统计信息。 + + ```bash + pt -n file_path + ``` + + | 参数名称 | 说明 | 是否必选 | + | -------- | ------------- | -------- | + | -n | npy文件路径。 | 是 | + + 打印统计信息:shape, dtype, max, min和mean。 + +**示例1** + +```bash +# 传入需转换的dump文件目录 +Parse >>> dc -n ./dump_data/ +...... +# 转换结果 +╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ SrcFile: ./dump_data/ +│ - Add.fp32_vars_add_2fp32_vars_Relu_9.31.5.1636595794731103.input.0.npy │ +│ - Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.output.0.npy │ +│ - Add.fp32_vars_add_2fp32_vars_Relu_9.31.5.1636595794731103.input.1.npy │ +│ - Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.input.1.npy │ +│ - Add.fp32_vars_add_3fp32_vars_Relu_12.40.5.1636595794846124.input.1.npy │ +│ - Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.input.0.npy │ +│ - Add.fp32_vars_add_3fp32_vars_Relu_12.40.5.1636595794846124.input.0.npy │ +│ - Add.fp32_vars_add_2fp32_vars_Relu_9.31.5.1636595794731103.output.0.npy │ +│ - Add.fp32_vars_add_3fp32_vars_Relu_12.40.5.1636595794846124.output.0.npy │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ +``` + +**示例2** + +```bash +# 查看某个dump数据块的数据信息 +# 默认会将数据中的tensor保存成 txt +Parse >>> pt -n ./parse_data/dump_convert/Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.output.0.npy +...... +# 打印统计信息 +[Shape: (1, 16, 56, 56, 16)] [Dtype: float16] [Max: 452.0] [Min: -408.5] [Mean: -3.809] +Path: ./parse_data/dump_convert/Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.input.0.npy +TextFile:./parse_data/dump_convert/Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.input.0.npy.txt +``` + +### pkl文件中指定API的dump数据信息查看 + +- 输入以下命令,解析并输出pkl文件中指定api的统计信息。 + + ```bash + pk -f pkl_path -n api_name + ``` + + | 参数名称 | 说明 | 是否必选 | + | -------- | ----------------- | -------- | + | -f | 指定pkl文件路径。 | 是 | + | -n | 指定API名称。 | 是 | + + - 输出结果:打印统计信息(shape, dtype, max和min mean)。 + - 若pkl文件中存在相应的堆栈信息,则会打印堆栈信息。 + +**示例** + +```bash +# 传入pkl文件及api名称 +Parse >>> pk -f ./torch_dump/ptdbg_v3.2/rank0/api_stack_dump.pkl -n Functional_conv2d_0_forward +...... +# 打印统计信息及堆栈(pkl文件不包含堆栈则不会打印堆栈) + +Statistic Info: + [Functional_conv2d_0_forward_input.0][dtype: torch.float32][shape: [2, 1, 2, 2]][max: 1.576936960220337][min: -0.9757485389709473][mean: 0.4961632490158081] + [Functional_conv2d_0_forward_input.1][dtype: torch.float32][shape: [2, 1, 2, 2]][max: 0.20064473152160645][min: -0.47102075815200806][mean: -0.20796933770179749] + [Functional_conv2d_0_forward_input.2][dtype: torch.float32][shape: [2]][max: 0.17380613088607788][min: -0.16853803396224976][mean: 0.0026340484619140625] + [Functional_conv2d_0_forward_output][dtype: torch.float32][shape: [2, 2, 1, 1]][max: 0.02364911139011383][min: -1.762906551361084][mean: -0.6710853576660156] +``` + +### API可选层级比对 + +- 输入以下命令, 进行统计级和像素级比对。 + + ```bash + cn -m my_data*.npy -g gloden*.npy [-p num] [-al atol] [-rl rtol] + ``` + + - 统计级比对:对tensor整体进行余弦值及相对误差的计算。 + - 像素级比对:对输入的两个npy文件进行逐元素比对。若两个tensor对应元素的相对误差或绝对误差大于**误差阈值**(-al和-rl配置)则被标记为错误数据。 + + | 参数名称 | 说明 | 是否必选 | + | -------- | ----------------------------------------------- | -------- | + | -m | 待比对数据。 | 是 | + | -g | 标杆数据。 | 是 | + | -p | 设置比对结束后打印错误元素的个数,默认值20。 | 否 | + | -al | 判定数据存在精度问题的绝对误差阈值,默认0.001。 | 否 | + | -rl | 判定数据存在精度问题的相对误差阈值,默认0.001。 | 否 | + | -s | 将npy文件保存成txt文件,用于查看,默认开启。 | 否 | + + 输出结果: + + - 统计级比对结果。 + - 两个文件的统计信息(shape, dtype, max, min和mean)。 + - 错误数据打印表格。 + +**示例** + +```bash +# 对比两个tensor的数据 +Parse >>> cn -m Add.InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.323.1619494134703053.output.0.npy -g InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.0.1619492699305998.npy -p 10 -s -al 0.002 -rl 0.005 + Error Item Table Top Item Table +┏━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓ ┏━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ +┃ Index ┃ Left ┃ Right ┃ Diff ┃ ┃ Index ┃ Left ┃ Right ┃ Diff ┃ +┡━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩ ┡━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ +│ 155 │ 0.024600908 │ 0.022271132 │ 0.002329776 │ │ 0 │ -0.9206961 │ -0.9222216 │ 0.0015255213 │ +│ 247 │ 0.015752593 │ 0.017937578 │ 0.0021849852 │ │ 1 │ -0.6416973 │ -0.64051837 │ 0.0011789203 │ +│ 282 │ -0.0101207765 │ -0.007852031 │ 0.0022687456 │ │ 2 │ -0.35383835 │ -0.35433492 │ 0.0004965663 │ +│ 292 │ 0.019581757 │ 0.02240482 │ 0.0028230622 │ │ 3 │ -0.18851271 │ -0.18883198 │ 0.00031927228 │ +│ 640 │ -0.06593232 │ -0.06874806 │ 0.0028157383 │ │ 4 │ -0.43508735 │ -0.43534422 │ 0.00025686622 │ +│ 1420 │ 0.09293677 │ 0.09586689 │ 0.0029301196 │ │ 5 │ 1.4447614 │ 1.4466647 │ 0.0019032955 │ +│ 1462 │ -0.085207745 │ -0.088047795 │ 0.0028400496 │ │ 6 │ -0.3455438 │ -0.3444429 │ 0.0011008978 │ +│ 1891 │ -0.03433288 │ -0.036525503 │ 0.002192624 │ │ 7 │ -0.6560242 │ -0.6564579 │ 0.0004336834 │ +│ 2033 │ 0.06828873 │ 0.07139922 │ 0.0031104907 │ │ 8 │ -2.6964858 │ -2.6975214 │ 0.0010356903 │ +│ 2246 │ -0.06376442 │ -0.06121233 │ 0.002552092 │ │ 9 │ -0.73746175 │ -0.73650354 │ 0.00095820427 │ +└───────┴───────────────┴──────────────┴──────────────┘ └───────┴─────────────┴─────────────┴───────────────┘ +╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Left: | +│ |- NpyFile: ./dump/temp/decode/Add.InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.323.1619494134703053.output.0.npy | +│ |- TxtFile: ./dump/temp/decode/Add.InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.323.1619494134703053.output.0.npy.txt | +│ |- NpySpec: [Shape: (32, 8, 8, 320)] [Dtype: float32] [Max: 5.846897] [Min: -8.368301] [Mean: -0.72565556] | +│ DstFile: │ +│ |- NpyFile: ./dump/cpu/InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.0.1619492699305998.npy | +│ |- TxtFile: ./dump/cpu/InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.0.1619492699305998.npy.txt | +│ |- NpySpec: [Shape: (32, 8, 8, 320)] [Dtype: float32] [Max: 5.8425903] [Min: -8.374472] [Mean: -0.7256237] │ +│ NumCnt: 655360 │ +│ AllClose: False │ +│ CosSim: 0.99999493 │ +│ ErrorPer: 0.023504638671875 (rl= 0.005, al= 0.002) │ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +``` + +## FAQ + +[FAQ](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/FAQ.md) -- Gitee From e8f10abd49598ebe073b445b1a16e7d646bd3aae Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Tue, 5 Dec 2023 15:20:34 +0800 Subject: [PATCH 02/16] =?UTF-8?q?[ptdbg=5Fascend]=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=89=8B=E5=86=8C=E6=96=B0=E5=A2=9Emodel=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" index 86ed4d935..094434800 100644 --- "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" +++ "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" @@ -441,7 +441,7 @@ PrecisionDebugger模块包含dump和溢出检测功能的总体配置项。可 **原型** ```python -PrecisionDebugger(dump_path=None, hook_name=None, rank=None, step=[], enable_dataloader=False): +PrecisionDebugger(dump_path=None, hook_name=None, rank=None, step=[], enable_dataloader=False, model=None): ``` **参数说明** @@ -453,6 +453,7 @@ PrecisionDebugger(dump_path=None, hook_name=None, rank=None, step=[], enable_dat | rank | 指定对某张卡上的数据进行dump或溢出检测,默认未配置(表示dump所有卡的数据),须根据实际卡的Rank ID配置。应配置为大于0的正整数,且须根据实际卡的Rank ID配置,若所配置的值大于实际训练所运行的卡的Rank ID,则dump数据为空,比如当前环境Rank ID为0~7,实际训练运行0~3卡,此时若配置Rank ID为4或不存在的10等其他值,此时dump数据为空。 | 否 | | step | 指定dump某个step的数据,默认未配置,须指定为训练脚本中存在的step。step为list格式,可配置逐个step,例如:step=[0,1,2];也可以配置step范围,例如:step=list(range(0,9)),表示dump第0到第8个step。 | 否 | | enable_dataloader | 自动控制开关,可取值True(开启)或False(关闭),默认为False。配置为True后自动识别dump step参数指定的迭代,并在该迭代执行完成后退出训练,此时start和stop函数可不配置,开启该开关要求训练脚本是通过torch.utils.data.dataloader方式加载数据;配置为False则需要配置start和stop函数,并在最后一个stop函数后或一个step结束的位置添加debugger.step()。 | 否 | +| model | 开启model模式,传入网络模型实例化的对象,配置该参数后,dump操作仅dump网络中init方法里调用的方法(nn.model类),不会对所有API进行dump。参数示例: model=net,net为网络模型实例化的对象名称。默认未配置。
配置该参数时,PrecisionDebugger模块请在模型实例化之后调用。
该模式不支持“溢出检测”和“模块级精度数据dump”。 | 否 | ### configure_hook函数(可选) @@ -835,9 +836,10 @@ register_hook(model, hook, overflow_nums=overflow_nums, dump_mode=dump_mode, dum | 参数名 | 说明 | 是否必选 | | ------------- | ------------------------------------------------------------ | -------- | +| model | 传入网络模型实例化的对象。参数示例: model=net,net为网络模型实例化的对象名称。 | 是 | | hook | 注册工具的dump和溢出检测钩子。可取值overflow_check(表示溢出检测)和acc_cmp_dump(表示dump数据),二选一。 | 是 | | overflow_nums | 控制溢出次数,表示第N次溢出时,停止训练,过程中检测到溢出API对应ACL数据均dump。参数示例:overflow_nums=3。配置overflow_check时可配置,默认不配置,即检测到1次溢出,训练停止,配置为-1时,表示持续检测溢出直到训练结束。 | 否 | -| dump_mode | 控制针对溢出API的dump模式。可取值"api"或"acl",配置acl时表示dump ACL级别的溢出数据,此时set_dump_path参数不生效,dump数据目录由dump_config的.json文件配置,参数示例:dump_mode="acl"。默认不配置,即dump API级别的溢出数据。 | 否 | +| dump_mode | 控制针对溢出API的dump模式,可取值"model"、"acl"或"api"。配置为"model"时,表示开启model模式,dump操作仅dump网络中init方法里调用的方法(nn.model类),不会对所有API进行dump,不支持“溢出检测”和“模块级精度数据dump”;配置acl时,表示dump ACL级别的溢出数据,此时set_dump_path参数不生效,dump数据目录由dump_config的.json文件配置。参数示例:dump_mode="acl"。默认不配置,即dump API级别的溢出数据。 | 否 | | dump_config | acl dump的配置文件。dump_mode="acl"时,该参数必选;dump_mode="api"时,该参数不选。参数示例:dump_config='./dump.json'。 | 否 | **函数示例** -- Gitee From a5a74c3a052a32487d62b469a959314429507067 Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Wed, 6 Dec 2023 09:57:52 +0800 Subject: [PATCH 03/16] =?UTF-8?q?[att]=E7=B2=BE=E5=BA=A6=E9=A2=84=E6=A3=80?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E7=99=BD=E5=90=8D=E5=8D=95=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E4=BB=A5=E5=8F=8AATT=E6=B1=87=E6=80=BB?= =?UTF-8?q?=E9=A1=B5=E6=B5=81=E7=A8=8B=E5=9B=BE=E5=8F=98=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api_accuracy_checker/README.md | 74 ++++++++++++++---- .../model_training_migration_process.png | Bin 48390 -> 50877 bytes 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/debug/accuracy_tools/api_accuracy_checker/README.md b/debug/accuracy_tools/api_accuracy_checker/README.md index 7a5bc6113..cd3e8e131 100644 --- a/debug/accuracy_tools/api_accuracy_checker/README.md +++ b/debug/accuracy_tools/api_accuracy_checker/README.md @@ -64,18 +64,7 @@ Ascend模型精度预检工具能在昇腾NPU上扫描用户训练模型中所 forward_info与stack_info中的key值一一对应,用户可根据forward_info中API的key在stack_info中查询到其调用栈及代码行位置。 - 若有需要,用户可以通过msCheckerConfig.update_config来配置dump路径以及开启真实数据模式,在训练脚本中加入如下示例代码: - - ```Python - from api_accuracy_checker.dump import msCheckerConfig - msCheckerConfig.update_config(dump_path="my/dump/path", real_data=True, target_iter=[1]) - ``` - - | 参数名称 | 说明 | 是否必选 | - | ----------- | ------------------------------------------------------------ | -------- | - | dump_path | 设置dump路径,须为已存在目录,默认为当前目录。 | 否 | - | real_data | 真实数据模式,可取值True或False,默认为False,配置为True后开启真实数据模式,dump信息增加forward_real_data和backward_real_data目录,目录下保存每个API输入的具体数值。开启真实数据模式目前仅支持单卡,且会存盘较多数据,可能对磁盘空间有较大冲击。 | 否 | - | target_iter | 指定dump某个step的数据,默认为[1],须指定为训练脚本中存在的step。target_iter为list格式,可配置逐个step,例如:target_iter=[0,1,2];也可以配置step范围,例如:target_iter=list(range(0,9)),表示dump第0到第8个step。 | 否 | + 若有需要,用户可以通过msCheckerConfig.update_config来配置dump路径以及开启真实数据模式、指定dump某个step或配置API dump白名单,详细请参见“**msCheckerConfig.update_config**”。 3. 将API信息输入给run_ut模块运行精度检测并比对,运行如下命令: @@ -102,11 +91,68 @@ Ascend模型精度预检工具能在昇腾NPU上扫描用户训练模型中所 ``` 数据默认会存盘到'./ut_error_data'路径下(相对于启动run_ut的路径),有需要的话,用户可以通过msCheckerConfig.update_config来配置保存路径,参数为error_data_path +## msCheckerConfig.update_config + +**功能说明** + +配置精度预检dump时的属性。 + +可选配置。 + +**函数原型** + +```python +msCheckerConfig.update_config(dump_path="./", real_data=False, target_iter=[1], white_list=[]) +``` + +**参数说明** + +| 参数名称 | 说明 | 是否必选 | +| ----------- | ------------------------------------------------------------ | -------- | +| dump_path | 设置dump路径,须为已存在目录,默认为当前目录。 | 否 | +| real_data | 真实数据模式,可取值True或False,默认为False,表示随机数据模式,配置为True后开启真实数据模式,dump信息增加forward_real_data和backward_real_data目录,目录下保存每个API输入的具体数值。开启真实数据模式目前仅支持单卡,且会存盘较多数据,可能对磁盘空间有较大冲击。 | 否 | +| target_iter | 指定dump某个step的数据,默认为[1],须指定为训练脚本中存在的step。target_iter为list格式,可配置逐个step,例如:target_iter=[0,1,2];也可以配置step范围,例如:target_iter=list(range(0,9)),表示dump第0到第8个step。 | 否 | +| white_list | API dump白名单,指定dump具体API数据,也可以直接配置预检的API白名单,详细请参见“**API预检白名单**”。参数示例:white_list=["conv1d", "conv2d"]。 | 否 | + +**函数示例** + +seed_all函数的随机数种子,取默认值即可,无须配置;第二个参数默认关闭,不开启确定性计算时也无须配置。 + +- 示例1:配置dump路径以及开启真实数据模式 + + ```python + from api_accuracy_checker.dump import msCheckerConfig + msCheckerConfig.update_config(dump_path="my/dump/path", real_data=True) + ``` + +- 示例2:指定dump某个step + + ```python + from api_accuracy_checker.dump import msCheckerConfig + msCheckerConfig.update_config(target_iter=[0,1,2]) + ``` + ## API预检白名单 -精度预检工具可以对指定API进行预检操作,只需要修改att\debug\accuracy_tools\api_accuracy_checker\hook_module目录下的support_wrap_ops.yaml文件。 +精度预检工具可以对指定API进行预检操作,可以使用如下方式: + +- 方式一: + + 修改att/debug/accuracy_tools/api_accuracy_checker目录下config.yaml文件的white_list参数,配置需要预检的API名称。 + +- 方式二: + + 在dump时的训练脚本中直接添加白名单参数,只dump指定的API数据,示例代码如下: + + ```python + from api_accuracy_checker.dump import msCheckerConfig + msCheckerConfig.update_config(white_list=[conv1d, conv2d]) + ``` + +说明: -support_wrap_ops.yaml文件当前记录所有PyTorch API名称,可以直接编辑该文件,删除不需要的API,保留需要预检的API。 +- 配置的API名称须存在于att\debug\accuracy_tools\api_accuracy_checker\hook_module目录下的support_wrap_ops.yaml文件下。 +- 方式一时建议先完成全量API dump操作后,再配置config.yaml文件指定需要预检的API。 ## API预检指标 diff --git a/debug/resources/model_training_migration_process.png b/debug/resources/model_training_migration_process.png index 15f24c81992cd46bc9f37aedfb4a2d2b5f1fd62a..503302cdc4e75fe163a2726f9d58381629c746cb 100644 GIT binary patch literal 50877 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 z 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 gzr&}*v~Gc}LNQeXe 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 -- Gitee From 1c4893a88c36edd69ae9f26e4bf23508f0fdb31d Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Wed, 6 Dec 2023 10:41:24 +0800 Subject: [PATCH 04/16] =?UTF-8?q?[api=5Faccuracy=5Fchecker]=E9=A2=84?= =?UTF-8?q?=E6=A3=80=E5=B7=A5=E5=85=B7=E8=B5=84=E6=96=99=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=84=8F=E8=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api_accuracy_checker/README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/debug/accuracy_tools/api_accuracy_checker/README.md b/debug/accuracy_tools/api_accuracy_checker/README.md index cd3e8e131..28ea3b4e6 100644 --- a/debug/accuracy_tools/api_accuracy_checker/README.md +++ b/debug/accuracy_tools/api_accuracy_checker/README.md @@ -35,12 +35,16 @@ Ascend模型精度预检工具能在昇腾NPU上扫描用户训练模型中所 import api_accuracy_checker.dump ``` - 工具默认抓取训练的**第二个迭代**并且在第二个迭代后会报错退出训练进程,可通过target_iter参数配置。报错信息如下,这个报错仅用于停止训练,属于正常现象: + 工具默认抓取训练的**第二个迭代**并且在第二个迭代后会报错退出训练进程,可通过target_iter参数配置。 + + **报错信息如下,这个报错仅用于停止训练,属于正常现象**: ```bash Exception: Model pretest: exit after iteration 1. ``` + - 若报错信息不一致,可能是由于服务器的其他错误信息覆盖导致,可以尝试查找报错信息中的Exception。 + - 若训练脚本中的代码不是通过torch.utils.data.dataloader来加载数据或在部分流水并行、张量并行场景下,工具的开关无法在每张卡上自动打开,导致多卡训练dump结果只有一组json,那么需要在训练代码中添加打开工具开关的调用: ```Python @@ -82,7 +86,7 @@ Ascend模型精度预检工具能在昇腾NPU上扫描用户训练模型中所 | -j或--jit_compile | 开启jit编译。 | 否 | | -d或--device | 指定Device ID,选择UT代码运行所在的卡,默认值为0。 | 否 | - run_ut执行结果包括accuracy_checking_result.csv和accuracy_checking_details.csv两个文件。accuracy_checking_result.csv是API粒度的,标明每个API是否通过测试。建议用户先查看accuracy_checking_result.csv文件,对于其中没有通过测试的或者特定感兴趣的API,根据其API name字段在accuracy_checking_details.csv中查询其各个输出的达标情况以及比较指标。API达标情况介绍请参考“**API预检指标**”。 + run_ut执行结果包括`accuracy_checking_result_{timestamp}.csv`和`accuracy_checking_details_{timestamp}.csv`两个文件。`accuracy_checking_result_{timestamp}.csv`是API粒度的,标明每个API是否通过测试。建议用户先查看`accuracy_checking_result_{timestamp}.csv`文件,对于其中没有通过测试的或者特定感兴趣的API,根据其API name字段在`accuracy_checking_details_{timestamp}.csv`中查询其各个输出的达标情况以及比较指标。API达标情况介绍请参考“**API预检指标**”。 4. 如果需要保存比对不达标的输入和输出数据,可以在run_ut执行命令结尾添加-save_error_data,例如: @@ -146,17 +150,17 @@ seed_all函数的随机数种子,取默认值即可,无须配置;第二个 ```python from api_accuracy_checker.dump import msCheckerConfig - msCheckerConfig.update_config(white_list=[conv1d, conv2d]) + msCheckerConfig.update_config(white_list=["conv1d", "conv2d"]) ``` 说明: - 配置的API名称须存在于att\debug\accuracy_tools\api_accuracy_checker\hook_module目录下的support_wrap_ops.yaml文件下。 -- 方式一时建议先完成全量API dump操作后,再配置config.yaml文件指定需要预检的API。 +- 方式一和方式二都可以在dump时设置并控制dump对应的API,默认情况下dump所有API数据,若在dump操作时没有配置白名单,那么可以在执行run_ut模块前使用方式一配置白名单。 ## API预检指标 -API预检通过测试,则在accuracy_checking_details.csv文件中的“pass”列标记“pass”,否则标记“error”或“warning”,详细规则如下: +API预检通过测试,则在`accuracy_checking_details_{timestamp}.csv`文件中的“pass”列标记“pass”,否则标记“error”或“warning”,详细规则如下: 1. 余弦相似度 > 0.99:≤ 0.99为不达标,标记“error”,> 0.99达标,进行下一步; 2. 最大绝对误差 < 0.001:< 0.001达标,标记“pass”,≥ 0.001为不达标,进行下一步; @@ -164,11 +168,11 @@ API预检通过测试,则在accuracy_checking_details.csv文件中的“pass - 对于float16和bfloat16数据:双百指标不通过,标记“error”;双百指标通过,双千指标不通过,标记“warning”;双百、双千指标均通过,标记“pass”。 - 对于float32和float64数据:双千指标不通过,标记“error”;双千指标通过,双万指标不通过,标记“warning”;双千、双万指标均通过,标记“pass”。 -4. 在accuracy_checking_result.csv中以“Forward Test Success”和“Backward Test Success”字段统计该算子前向反向输出的测试结果,对于标记“pass”的算子,则在accuracy_checking_result.csv中标记“TRUE”表示测试通过,对于标记“error”或“warning”的算子,则在accuracy_checking_result.csv中标记“FALSE”表示测试不通过。由于一个算子可能有多个前向或反向的输入或输出,那么该类算子的输入或输出中必须全为“pass”,才能在accuracy_checking_result.csv中标记“TRUE”,只要有一个输入或输出标记“error”或“warning”,那么在accuracy_checking_result.csv中标记“FALSE”。 +4. 在`accuracy_checking_result_{timestamp}.csv`中以“Forward Test Success”和“Backward Test Success”字段统计该算子前向反向输出的测试结果,对于标记“pass”的算子,则在`accuracy_checking_result_{timestamp}.csv`中标记“TRUE”表示测试通过,对于标记“error”或“warning”的算子,则在`accuracy_checking_result_{timestamp}.csv`中标记“FALSE”表示测试不通过。由于一个算子可能有多个前向或反向的输入或输出,那么该类算子的输入或输出中必须全为“pass”,才能在`accuracy_checking_result_{timestamp}.csv`中标记“TRUE”,只要有一个输入或输出标记“error”或“warning”,那么在`accuracy_checking_result_{timestamp}.csv`中标记“FALSE”。 双百、双千、双万精度指标是指NPU的Tensor中的元素逐个与对应的标杆数据对比,相对误差大于百分之一、千分之一、万分之一的比例占总元素个数的比例小于百分之一、千分之一、万分之一。 -# 解析工具 +# 溢出解析工具 针对训练过程中的溢出检测场景,对于输入正常但输出存在溢出的API,会在训练执行目录下将溢出的API信息按照前向和反向分类,dump并保存为`forward_info_{pid}.json`和`backward_info_{pid}.json`,前向过程溢出的API可通过该工具对`forward_info_{pid}.json`进行解析,输出溢出API为正常溢出还是非正常溢出,从而帮助用户快速判断。 -- Gitee From e8e89afd2f9cf870c5590aa4ae161b253e7eb771 Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Wed, 6 Dec 2023 10:51:47 +0800 Subject: [PATCH 05/16] =?UTF-8?q?[api=5Faccuracy=5Fchecker]=E9=A2=84?= =?UTF-8?q?=E6=A3=80=E5=B7=A5=E5=85=B7=E8=B5=84=E6=96=99=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=84=8F=E8=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debug/accuracy_tools/api_accuracy_checker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug/accuracy_tools/api_accuracy_checker/README.md b/debug/accuracy_tools/api_accuracy_checker/README.md index 28ea3b4e6..40704cf3c 100644 --- a/debug/accuracy_tools/api_accuracy_checker/README.md +++ b/debug/accuracy_tools/api_accuracy_checker/README.md @@ -116,7 +116,7 @@ msCheckerConfig.update_config(dump_path="./", real_data=False, target_iter=[1], | dump_path | 设置dump路径,须为已存在目录,默认为当前目录。 | 否 | | real_data | 真实数据模式,可取值True或False,默认为False,表示随机数据模式,配置为True后开启真实数据模式,dump信息增加forward_real_data和backward_real_data目录,目录下保存每个API输入的具体数值。开启真实数据模式目前仅支持单卡,且会存盘较多数据,可能对磁盘空间有较大冲击。 | 否 | | target_iter | 指定dump某个step的数据,默认为[1],须指定为训练脚本中存在的step。target_iter为list格式,可配置逐个step,例如:target_iter=[0,1,2];也可以配置step范围,例如:target_iter=list(range(0,9)),表示dump第0到第8个step。 | 否 | -| white_list | API dump白名单,指定dump具体API数据,也可以直接配置预检的API白名单,详细请参见“**API预检白名单**”。参数示例:white_list=["conv1d", "conv2d"]。 | 否 | +| white_list | API dump白名单,指定dump具体API数据,也可以直接配置预检的API白名单,详细请参见“**API预检白名单**”。参数示例:white_list=["conv1d", "conv2d"]。默认未配置白名单,即dump全量API数据并进行全量API预检。 | 否 | **函数示例** -- Gitee From c96e8fd7bc3070a37b3fd04ec34b3d9811b755b0 Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Wed, 6 Dec 2023 11:08:36 +0800 Subject: [PATCH 06/16] =?UTF-8?q?[api=5Faccuracy=5Fchecker]=E9=A2=84?= =?UTF-8?q?=E6=A3=80=E5=B7=A5=E5=85=B7=E8=B5=84=E6=96=99=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=84=8F=E8=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debug/accuracy_tools/api_accuracy_checker/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debug/accuracy_tools/api_accuracy_checker/README.md b/debug/accuracy_tools/api_accuracy_checker/README.md index 40704cf3c..5442dd499 100644 --- a/debug/accuracy_tools/api_accuracy_checker/README.md +++ b/debug/accuracy_tools/api_accuracy_checker/README.md @@ -116,7 +116,7 @@ msCheckerConfig.update_config(dump_path="./", real_data=False, target_iter=[1], | dump_path | 设置dump路径,须为已存在目录,默认为当前目录。 | 否 | | real_data | 真实数据模式,可取值True或False,默认为False,表示随机数据模式,配置为True后开启真实数据模式,dump信息增加forward_real_data和backward_real_data目录,目录下保存每个API输入的具体数值。开启真实数据模式目前仅支持单卡,且会存盘较多数据,可能对磁盘空间有较大冲击。 | 否 | | target_iter | 指定dump某个step的数据,默认为[1],须指定为训练脚本中存在的step。target_iter为list格式,可配置逐个step,例如:target_iter=[0,1,2];也可以配置step范围,例如:target_iter=list(range(0,9)),表示dump第0到第8个step。 | 否 | -| white_list | API dump白名单,指定dump具体API数据,也可以直接配置预检的API白名单,详细请参见“**API预检白名单**”。参数示例:white_list=["conv1d", "conv2d"]。默认未配置白名单,即dump全量API数据并进行全量API预检。 | 否 | +| white_list | API dump白名单,指定dump具体API数据,也可以直接配置预检的API白名单,详细请参见“**API预检白名单**”。参数示例:white_list=["conv1d", "conv2d"]。默认未配置白名单,即dump全量API数据。 | 否 | **函数示例** @@ -156,7 +156,7 @@ seed_all函数的随机数种子,取默认值即可,无须配置;第二个 说明: - 配置的API名称须存在于att\debug\accuracy_tools\api_accuracy_checker\hook_module目录下的support_wrap_ops.yaml文件下。 -- 方式一和方式二都可以在dump时设置并控制dump对应的API,默认情况下dump所有API数据,若在dump操作时没有配置白名单,那么可以在执行run_ut模块前使用方式一配置白名单。 +- 方式一和方式二都可以在dump时设置并控制dump对应的API,默认情况下没有配置白名单,dump所有API数据,若在dump操作时没有配置白名单,那么可以在执行run_ut模块前使用方式一配置白名单。 ## API预检指标 -- Gitee From 9bd8bcc328cebd0dc1428b990d24f266081108bd Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Wed, 6 Dec 2023 14:03:53 +0800 Subject: [PATCH 07/16] =?UTF-8?q?[ptdbg=5Fascend]=E4=BD=8E=E9=94=99?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" | 2 +- ...\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" index 700ab067f..03fd9b9d6 100644 --- "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" +++ "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" @@ -1228,7 +1228,7 @@ class ModuleOP(nn.Module): return r1 if __name__ == "__main__": - module = ModeleOP + module = ModuleOP # 注册工具 set_dump_path("./dump_data/npu") diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" index 094434800..c40e89024 100644 --- "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" +++ "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" @@ -1230,7 +1230,7 @@ class ModuleOP(nn.Module): return r1 if __name__ == "__main__": - module = ModeleOP + module = ModuleOP # 注册工具 set_dump_path("./dump_data/npu") -- Gitee From b1c083dd04078fe0212d7cf86aa087dfe4d50b89 Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Wed, 6 Dec 2023 15:40:22 +0800 Subject: [PATCH 08/16] =?UTF-8?q?[ptdbg=5Fascend]=E4=BD=8E=E9=94=99?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" | 10 +++++----- ...237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" index 03fd9b9d6..a796bfb36 100644 --- "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" +++ "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" @@ -1211,6 +1211,7 @@ import os import torch import torch.nn as nn import torch_npu +import torch.nnnfunctional as F from ptdbg_ascend import * torch.npu.set_device("npu:0") @@ -1218,13 +1219,12 @@ torch.npu.set_device("npu:0") class ModuleOP(nn.Module): def __init__(self) -> None: super().__init__() - self.linear_1 = nnLinear(in_features=2, out_features=2) - self.linear_2 = nnLinear(in_features=2, out_features=1) - self.relu = nn.Relu() + self.linear_1 = nn.Linear(in_features=2, out_features=2) + self.linear_2 = nn.Linear(in_features=2, out_features=1) def forward(self, x): x1 = self.linear_1(x) - x1 = self.linear_2(x1) - r1 = self.relu(x2) + x2 = self.linear_2(x1) + r1 = F.relu(x2) return r1 if __name__ == "__main__": diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" index c40e89024..aaebd917f 100644 --- "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" +++ "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" @@ -1213,6 +1213,7 @@ import os import torch import torch.nn as nn import torch_npu +import torch.nnnfunctional as F from ptdbg_ascend import * torch.npu.set_device("npu:0") @@ -1220,13 +1221,12 @@ torch.npu.set_device("npu:0") class ModuleOP(nn.Module): def __init__(self) -> None: super().__init__() - self.linear_1 = nnLinear(in_features=2, out_features=2) - self.linear_2 = nnLinear(in_features=2, out_features=1) - self.relu = nn.Relu() + self.linear_1 = nn.Linear(in_features=2, out_features=2) + self.linear_2 = nn.Linear(in_features=2, out_features=1) def forward(self, x): x1 = self.linear_1(x) - x1 = self.linear_2(x1) - r1 = self.relu(x2) + x2 = self.linear_2(x1) + r1 = F.relu(x2) return r1 if __name__ == "__main__": -- Gitee From 562cc5852172285f1302e10f1f1d266344882aa8 Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Wed, 6 Dec 2023 16:08:15 +0800 Subject: [PATCH 09/16] =?UTF-8?q?[ptdbg=5Fascend]=E4=BD=8E=E9=94=99?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" | 6 +++--- ...\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" index a796bfb36..b8af59ef3 100644 --- "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" +++ "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" @@ -1211,7 +1211,7 @@ import os import torch import torch.nn as nn import torch_npu -import torch.nnnfunctional as F +import torch.nnfunctional as F from ptdbg_ascend import * torch.npu.set_device("npu:0") @@ -1228,7 +1228,7 @@ class ModuleOP(nn.Module): return r1 if __name__ == "__main__": - module = ModuleOP + module = ModuleOP() # 注册工具 set_dump_path("./dump_data/npu") @@ -1241,7 +1241,7 @@ if __name__ == "__main__": out = module(x) module_dump_end() # 结束模块级精度数据dump loss = out.sum() - loss.bachward() + loss.backward() set_dump_switch("OFF") ``` diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" index aaebd917f..6953f3e41 100644 --- "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" +++ "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" @@ -1213,7 +1213,7 @@ import os import torch import torch.nn as nn import torch_npu -import torch.nnnfunctional as F +import torch.nnfunctional as F from ptdbg_ascend import * torch.npu.set_device("npu:0") @@ -1230,7 +1230,7 @@ class ModuleOP(nn.Module): return r1 if __name__ == "__main__": - module = ModuleOP + module = ModuleOP() # 注册工具 set_dump_path("./dump_data/npu") @@ -1243,7 +1243,7 @@ if __name__ == "__main__": out = module(x) module_dump_end() # 结束模块级精度数据dump loss = out.sum() - loss.bachward() + loss.backward() set_dump_switch("OFF") ``` -- Gitee From 6e56c08e0c1de69d8678f4f417dc0f4309a8ae09 Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Wed, 6 Dec 2023 16:11:55 +0800 Subject: [PATCH 10/16] =?UTF-8?q?[ptdbg=5Fascend]=E4=BD=8E=E9=94=99?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" | 2 +- ...\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" index b8af59ef3..a41c34834 100644 --- "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" +++ "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T2.md" @@ -1211,7 +1211,7 @@ import os import torch import torch.nn as nn import torch_npu -import torch.nnfunctional as F +import torch.nn.functional as F from ptdbg_ascend import * torch.npu.set_device("npu:0") diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" index 6953f3e41..a21e31bcc 100644 --- "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" +++ "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" @@ -1213,7 +1213,7 @@ import os import torch import torch.nn as nn import torch_npu -import torch.nnfunctional as F +import torch.nn.functional as F from ptdbg_ascend import * torch.npu.set_device("npu:0") -- Gitee From 58473d69338a5a2eb498080de01b049341d8df0e Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Thu, 7 Dec 2023 15:41:31 +0800 Subject: [PATCH 11/16] =?UTF-8?q?[ptdbg=5Fascend]=E4=BD=8E=E9=94=99?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debug/accuracy_tools/api_accuracy_checker/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/debug/accuracy_tools/api_accuracy_checker/README.md b/debug/accuracy_tools/api_accuracy_checker/README.md index 5442dd499..e65cf6aca 100644 --- a/debug/accuracy_tools/api_accuracy_checker/README.md +++ b/debug/accuracy_tools/api_accuracy_checker/README.md @@ -120,8 +120,6 @@ msCheckerConfig.update_config(dump_path="./", real_data=False, target_iter=[1], **函数示例** -seed_all函数的随机数种子,取默认值即可,无须配置;第二个参数默认关闭,不开启确定性计算时也无须配置。 - - 示例1:配置dump路径以及开启真实数据模式 ```python -- Gitee From 8420e489c78ade78dfc8ae63efb061172a9f8268 Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Thu, 7 Dec 2023 16:07:14 +0800 Subject: [PATCH 12/16] =?UTF-8?q?[ptdbg=5Fascend]=E4=BD=8E=E9=94=99?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debug/accuracy_tools/api_accuracy_checker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug/accuracy_tools/api_accuracy_checker/README.md b/debug/accuracy_tools/api_accuracy_checker/README.md index e65cf6aca..0a8e9f393 100644 --- a/debug/accuracy_tools/api_accuracy_checker/README.md +++ b/debug/accuracy_tools/api_accuracy_checker/README.md @@ -114,7 +114,7 @@ msCheckerConfig.update_config(dump_path="./", real_data=False, target_iter=[1], | 参数名称 | 说明 | 是否必选 | | ----------- | ------------------------------------------------------------ | -------- | | dump_path | 设置dump路径,须为已存在目录,默认为当前目录。 | 否 | -| real_data | 真实数据模式,可取值True或False,默认为False,表示随机数据模式,配置为True后开启真实数据模式,dump信息增加forward_real_data和backward_real_data目录,目录下保存每个API输入的具体数值。开启真实数据模式目前仅支持单卡,且会存盘较多数据,可能对磁盘空间有较大冲击。 | 否 | +| real_data | 真实数据模式,可取值True或False,默认为False,表示随机数据模式,配置为True后开启真实数据模式,dump信息增加forward_real_data和backward_real_data目录,目录下保存每个API输入的具体数值。 | 否 | | target_iter | 指定dump某个step的数据,默认为[1],须指定为训练脚本中存在的step。target_iter为list格式,可配置逐个step,例如:target_iter=[0,1,2];也可以配置step范围,例如:target_iter=list(range(0,9)),表示dump第0到第8个step。 | 否 | | white_list | API dump白名单,指定dump具体API数据,也可以直接配置预检的API白名单,详细请参见“**API预检白名单**”。参数示例:white_list=["conv1d", "conv2d"]。默认未配置白名单,即dump全量API数据。 | 否 | -- Gitee From 16936422f5a60022a1854ca64baae6c95a9ae4be Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Thu, 7 Dec 2023 16:31:08 +0800 Subject: [PATCH 13/16] =?UTF-8?q?[ptdbg=5Fascend]=E4=BD=8E=E9=94=99?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...03\275\350\257\264\346\230\216_v4.0.T3.md" | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" index a21e31bcc..71003983a 100644 --- "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" +++ "b/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v4.0.T3.md" @@ -1376,19 +1376,18 @@ compare_distributed('./npu_dump/ptdbg_dump_v2.0', './gpu_dump/ptdbg_dump_v2.0', **函数原型** ```python -compare(input_param, output_path, stack_mode=False, auto_analyze=True, fuzzy_match=False, summary_compare=False) +compare(input_param, output_path, stack_mode=False, auto_analyze=True, fuzzy_match=False) ``` **参数说明** -| 参数名 | 说明 | 是否必选 | -| --------------- | ------------------------------------------------------------ | -------- | -| input_param | 配置dump数据文件及目录。配置参数包括:
- "npu_pkl_path":指定NPU dump目录下的.pkl文件。参数示例:"npu_pkl_path": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl"。必选。
- "bench_pkl_path":指定CPU、GPU或NPU dump目录下的.pkl文件。参数示例:"bench_pkl_path": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl"。必选。
- "npu_dump_data_dir":"指定NPU dump目录下的dump数据目录。参数示例:"npu_dump_data_dir": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump"。必选。
- "bench_dump_data_dir":"指定CPU、GPU或NPU dump目录下的dump数据目录。参数示例:"npu_dump_data_dir": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump"。必选。
- "is_print_compare_log":配置是否开启日志打屏。可取值True或False。可选。 | 是 | -| output_path | 配置比对结果csv文件存盘目录。参数示例:'./output'。文件名称基于时间戳自动生成,格式为:`compare_result_{timestamp}.csv`。 | 是 | -| stack_mode | 配置stack_mode的开关。仅当dump数据时配置set_dump_switch的mode="api_stack"时需要开启。参数示例:stack_mode=True,默认为False。 | 否 | -| auto_analyze | 自动精度分析,开启后工具自动针对比对结果进行分析,识别到第一个精度不达标节点(在比对结果文件中的“Accuracy Reached or Not”列显示为No),并给出问题可能产生的原因(打屏展示并生成advisor_{timestamp}.txt文件)。可取值True或False,参数示例:auto_analyze=False,默认为True。 | 否 | -| fuzzy_match | 模糊匹配。开启后,对于网络中同一层级且命名仅调用次数不同的API,可匹配并进行比对。可取值True或False,参数示例:fuzzy_match=True,默认为False。 | 否 | -| summary_compare | 比对dump.pkl文件中的统计值,开启后的比对结果文件生成Max diff、Min diff和Mean diff,表示NPU dump数据中API的输入或输出与标杆数据输入或输出的最大最小平均值的差。可以通过该值判断API是否存在精度问题:当某个API的输入和输出的Max diff、Min diff和Mean diff均为0或无限趋于0,那么可以判断该API无精度问题,反之则可能存在精度问题。可取值True或False,参数示例:summary_compare=True,默认为False。 | 否 | +| 参数名 | 说明 | 是否必选 | +| ------------ | ------------------------------------------------------------ | -------- | +| input_param | 配置dump数据文件及目录。配置参数包括:
- "npu_pkl_path":指定NPU dump目录下的.pkl文件。参数示例:"npu_pkl_path": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl"。必选。
- "bench_pkl_path":指定CPU、GPU或NPU dump目录下的.pkl文件。参数示例:"bench_pkl_path": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl"。必选。
- "npu_dump_data_dir":"指定NPU dump目录下的dump数据目录。参数示例:"npu_dump_data_dir": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump"。必选。
- "bench_dump_data_dir":"指定CPU、GPU或NPU dump目录下的dump数据目录。参数示例:"npu_dump_data_dir": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump"。必选。
- "is_print_compare_log":配置是否开启日志打屏。可取值True或False。可选。 | 是 | +| output_path | 配置比对结果csv文件存盘目录。参数示例:'./output'。文件名称基于时间戳自动生成,格式为:`compare_result_{timestamp}.csv`。 | 是 | +| stack_mode | 配置stack_mode的开关。仅当dump数据时配置set_dump_switch的mode="api_stack"时需要开启。参数示例:stack_mode=True,默认为False。 | 否 | +| auto_analyze | 自动精度分析,开启后工具自动针对比对结果进行分析,识别到第一个精度不达标节点(在比对结果文件中的“Accuracy Reached or Not”列显示为No),并给出问题可能产生的原因(打屏展示并生成advisor_{timestamp}.txt文件)。可取值True或False,参数示例:auto_analyze=False,默认为True。 | 否 | +| fuzzy_match | 模糊匹配。开启后,对于网络中同一层级且命名仅调用次数不同的API,可匹配并进行比对。可取值True或False,参数示例:fuzzy_match=True,默认为False。 | 否 | **函数示例** @@ -1406,6 +1405,10 @@ dump_result_param={ compare(dump_result_param, "./output", stack_mode=True) ``` +### pkl文件比对 + +若使用**compare**或**compare_distributed**函数创建的比对脚本中,input_param参数只配置了npu_pkl_path和bench_pkl_path,那么可以进行pkl文件的比对,此时比对dump.pkl文件中的统计值,开启后的比对结果文件生成Max diff、Min diff和Mean diff,表示NPU dump数据中API的输入或输出与标杆数据输入或输出的最大最小平均值的差。可以通过该值判断API是否存在精度问题:当某个API的输入和输出的Max diff、Min diff和Mean diff均为0或无限趋于0,那么可以判断该API无精度问题,反之则可能存在精度问题。 + ### parse **功能说明** -- Gitee From 94b94ddaa0e53f488ec7fad8c79348d8604fd609 Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Fri, 8 Dec 2023 10:22:15 +0800 Subject: [PATCH 14/16] =?UTF-8?q?[api=5Faccuracy=5Fchecker]=E6=BA=A2?= =?UTF-8?q?=E5=87=BA=E8=A7=A3=E6=9E=90=E5=B7=A5=E5=85=B7=E6=8F=8F=E8=BF=B0?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debug/accuracy_tools/api_accuracy_checker/README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/debug/accuracy_tools/api_accuracy_checker/README.md b/debug/accuracy_tools/api_accuracy_checker/README.md index 0a8e9f393..4e1b66e06 100644 --- a/debug/accuracy_tools/api_accuracy_checker/README.md +++ b/debug/accuracy_tools/api_accuracy_checker/README.md @@ -172,13 +172,11 @@ API预检通过测试,则在`accuracy_checking_details_{timestamp}.csv`文件 # 溢出解析工具 -针对训练过程中的溢出检测场景,对于输入正常但输出存在溢出的API,会在训练执行目录下将溢出的API信息按照前向和反向分类,dump并保存为`forward_info_{pid}.json`和`backward_info_{pid}.json`,前向过程溢出的API可通过该工具对`forward_info_{pid}.json`进行解析,输出溢出API为正常溢出还是非正常溢出,从而帮助用户快速判断。 +针对训练过程中的溢出检测场景(参见[ptdbg_ascend精度工具功能说明](https://gitee.com/ascend/att/tree/master/debug/accuracy_tools/ptdbg_ascend/doc)中的"溢出检测场景"进行溢出检测dump),对于输入正常但输出存在溢出的API,会在训练执行目录下将溢出的API信息按照前向和反向分类,dump并保存为`forward_info_{pid}.json`和`backward_info_{pid}.json`,前向过程溢出的API可通过该工具对`forward_info_{pid}.json`进行解析,输出溢出API为正常溢出还是非正常溢出,从而帮助用户快速判断。 工具支持PyTorch版本:1.8.1/1.11.0/2.0/2.1。 -参见[ptdbg_ascend精度工具功能说明](https://gitee.com/ascend/att/tree/master/debug/accuracy_tools/ptdbg_ascend/doc)中的"溢出检测场景"进行溢出检测dump。 - -若dump结果生成`forward_info_{pid}.json`和`backward_info_{pid}.json`文件,则使用本工具进行解析。操作步骤如下: +若溢出检测场景dump结果生成`forward_info_{pid}.json`和`backward_info_{pid}.json`文件,则使用本工具进行解析。操作步骤如下: 1. 安装预检工具 @@ -217,12 +215,12 @@ API预检通过测试,则在`accuracy_checking_details_{timestamp}.csv`文件 1. run ut过程中出现报错:ERROR:Got unsupported ScalarType BFloat16 - 答:请使用最新版本的工具 + 答:请使用最新版本的工具。 2. Dropout算子,CPU和NPU的随机应该不一样,为什么结果比对是一致的? - 答:这个结果是正常的,工具对该算子有特殊处理,只判定位置为0的位置比例大约和设定p值相当 + 答:这个结果是正常的,工具对该算子有特殊处理,只判定位置为0的位置比例大约和设定p值相当。 3. 为什么浮点型数据bench和npu的dtype不一致? - 答:对于fp16的数据,cpu会上升一个精度fp32去计算,这是和算子那边对齐的精度结论,cpu用更高精度去计算会更接近真实值 + 答:对于fp16的数据,cpu会上升一个精度fp32去计算,这是和算子那边对齐的精度结论,cpu用更高精度去计算会更接近真实值。 -- Gitee From 221bf80504c358482a04f77ab4380897e838495a Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Fri, 8 Dec 2023 11:18:34 +0800 Subject: [PATCH 15/16] =?UTF-8?q?[ptdbg=5Fascend]=E4=BD=8E=E9=94=99?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debug/accuracy_tools/api_accuracy_checker/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debug/accuracy_tools/api_accuracy_checker/README.md b/debug/accuracy_tools/api_accuracy_checker/README.md index 4e1b66e06..71b477a0d 100644 --- a/debug/accuracy_tools/api_accuracy_checker/README.md +++ b/debug/accuracy_tools/api_accuracy_checker/README.md @@ -60,7 +60,7 @@ Ascend模型精度预检工具能在昇腾NPU上扫描用户训练模型中所 上述代码要添加在迭代前向的代码段中,或者说是遍历数据集循环的代码段中。如对于GPT-3可以添加在pretrain_gpt.py 的forward_step函数中。之后工具会适配这个场景开关的自动打开。 - dump信息默认会存盘到“./”路径下(相对于启动训练的路径),包括: + dump信息默认会存盘到“./step1”路径下(相对于启动训练的路径),包括: - forward_info_{pid}.json:前向API信息文件。 - backward_info_{pid}.json:反向API信息文件。 @@ -93,7 +93,7 @@ Ascend模型精度预检工具能在昇腾NPU上扫描用户训练模型中所 ```bash python run_ut.py -forward ./forward_info_0.json -backward ./backward_info_0.json -save_error_data ``` - 数据默认会存盘到'./ut_error_data'路径下(相对于启动run_ut的路径),有需要的话,用户可以通过msCheckerConfig.update_config来配置保存路径,参数为error_data_path + 数据默认会存盘到'./ut_error_data'路径下(相对于启动run_ut的路径),有需要的话,用户可以通过msCheckerConfig.update_config来配置保存路径,参数为error_data_path。 ## msCheckerConfig.update_config -- Gitee From fdd911120ea165b7505737905474003633e989ee Mon Sep 17 00:00:00 2001 From: user_10012209 <734267852@qq.com> Date: Fri, 8 Dec 2023 11:20:51 +0800 Subject: [PATCH 16/16] =?UTF-8?q?[ptdbg=5Fascend]=E4=BD=8E=E9=94=99?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api_accuracy_checker/README.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/debug/accuracy_tools/api_accuracy_checker/README.md b/debug/accuracy_tools/api_accuracy_checker/README.md index 71b477a0d..035f50fc9 100644 --- a/debug/accuracy_tools/api_accuracy_checker/README.md +++ b/debug/accuracy_tools/api_accuracy_checker/README.md @@ -224,3 +224,40 @@ API预检通过测试,则在`accuracy_checking_details_{timestamp}.csv`文件 3. 为什么浮点型数据bench和npu的dtype不一致? 答:对于fp16的数据,cpu会上升一个精度fp32去计算,这是和算子那边对齐的精度结论,cpu用更高精度去计算会更接近真实值。 + +4. Tensor 魔法函数具体对应什么操作? + + 答: + + | Tensor魔法函数 | 具体操作 | + | --------------- | ---------------- | + | `__add__` | + | + | `__and__` | & | + | `__bool__` | 返回Tensor布尔值 | + | `__div__` | / | + | `__eq__` | == | + | `__ge__` | >= | + | `__gt__` | > | + | `__iadd__` | += | + | `__iand__` | &= | + | `__idiv__` | /= | + | `__ifloordiv__` | //= | + | `__ilshift__` | <<= | + | `__imod__` | %= | + | `__imul__` | *= | + | `__ior__` | \|= | + | `__irshift__` | >>= | + | `__isub__` | -= | + | `__ixor__` | ^= | + | `__lshift__` | << | + | `__matmul__` | 矩阵乘法 | + | `__mod__` | % | + | `__mul__` | * | + | `__nonzero__` | 同`__bool__` | + | `__or__` | \| | + | `__radd__` | +(反向) | + | `__rmul__` | *(反向) | + | `__rshift__` | >> | + | `__sub__` | - | + | `__truediv__` | 同`__div__` | + | `__xor__` | ^ | -- Gitee