From 0c8384486f2e561bb0846db17752d84243d59675 Mon Sep 17 00:00:00 2001 From: pengxiaopeng <958876660@qq.com> Date: Tue, 18 Jun 2024 20:38:33 +0800 Subject: [PATCH 01/13] =?UTF-8?q?=E6=A2=AF=E5=BA=A6=E7=9B=B8=E4=BC=BC?= =?UTF-8?q?=E6=AF=94=E5=AF=B9=E7=BB=93=E6=9E=9C=E6=8C=89=E6=9D=83=E9=87=8D?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E9=A1=BA=E5=BA=8F=E6=8E=92=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../grad_tool/common/base_comparator.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/debug/accuracy_tools/grad_tool/common/base_comparator.py b/debug/accuracy_tools/grad_tool/common/base_comparator.py index 36ecc320c6..b5dc45b20c 100644 --- a/debug/accuracy_tools/grad_tool/common/base_comparator.py +++ b/debug/accuracy_tools/grad_tool/common/base_comparator.py @@ -3,6 +3,7 @@ from typing import List from abc import ABC, abstractmethod from tqdm import tqdm +import pandas as pd import matplotlib.pyplot as plt from grad_tool.common.constant import GradConst @@ -71,15 +72,35 @@ class BaseComparator(ABC): head_tuple = tuple(['step'] + [str(step) for step in steps]) write_csv(os.path.join(output_dir, "similarities.csv"), [[key] + value], head_tuple) + @staticmethod + def _get_grad_weight_order(path1, path2): + for summary_file in os.listdir(path1): + if not summary_file.endswith(".csv"): + continue + if not os.path.exists(os.path.join(path2, summary_file)): + continue + summary_csv = pd.read_csv(os.path.join(path1, summary_file)) + return summary_csv["param_name"] + raise RuntimeError("no matched grad_summary.csv for comparison, please dump data in same configuration") + + @staticmethod + def _get_name_matched_grad_file(param_name, grad_files): + for grad_file in grad_files: + if param_name == grad_file[:grad_file.rfind('.')]: + return grad_file + raise RuntimeError("no matched grad_file for comparison, please dump data in same configuration") + @classmethod def _calculate_separated_similarities(cls, path1, path2, steps): similarities = {} print_info_log(f"{len(steps)} steps will be compared") + grad_weight_order = cls._get_grad_weight_order(path1, path2) for step in tqdm(steps, desc="culculate similarities (by step)"): grad_files = cls._get_matched_grad_files(path1, path2, step) same_count_summary = 0 total_count_summary = 0 - for grad_file in grad_files: + for grad_name in grad_weight_order: + grad_file = cls._get_name_matched_grad_file(grad_name, grad_files) grad1 = os.path.join(path1, f"step_{step}", grad_file) grad2 = os.path.join(path2, f"step_{step}", grad_file) same_count, total_count = cls._calculate_similarity(grad1, grad2) -- Gitee From 9bcb2b6a5f91b152890acb85cd4cf2d9dd123240 Mon Sep 17 00:00:00 2001 From: cai-weiwei1989 <734267852@qq.com> Date: Wed, 19 Jun 2024 17:43:11 +0800 Subject: [PATCH 02/13] =?UTF-8?q?[ptdbg]=E8=B5=84=E6=96=99=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E6=AF=94=E5=AF=B9=E7=BB=93=E6=9E=9C=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E9=AB=98=E4=BA=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ptdbg_ascend/doc/img/compare_result.png | Bin 0 -> 36669 bytes .../doc/img/compare_result_pkl.png | Bin 25217 -> 36133 bytes ...0\203\275\350\257\264\346\230\216_v6.0.md" | 2322 +++++++++++++++++ 3 files changed, 2322 insertions(+) create mode 100644 debug/accuracy_tools/ptdbg_ascend/doc/img/compare_result.png 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_v6.0.md" diff --git a/debug/accuracy_tools/ptdbg_ascend/doc/img/compare_result.png b/debug/accuracy_tools/ptdbg_ascend/doc/img/compare_result.png new file mode 100644 index 0000000000000000000000000000000000000000..e3a721036335ddaf17dc44dc4ae6fb5b27915979 GIT binary patch literal 36669 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

S~MG@`3A@kORNB!}X;?omS9fr=OXknxYrqcV^ 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 YeHL{k&m-$!;J<_WefJ6WeEO&V2?3Ai5&!@I literal 0 HcmV?d00001 diff --git a/debug/accuracy_tools/ptdbg_ascend/doc/img/compare_result_pkl.png b/debug/accuracy_tools/ptdbg_ascend/doc/img/compare_result_pkl.png index c64e9380c6d9c01bb2ad18c81e430ead0800bb7d..863708bf6daf46985328f0dc42d48f0a5b849af5 100644 GIT binary patch literal 36133 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 literal 25217 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 BKN0`{ 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_v6.0.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_v6.0.md" new file mode 100644 index 0000000000..34761e796c --- /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_v6.0.md" @@ -0,0 +1,2322 @@ +# **PyTorch精度工具使用指南** + +本文主要介绍PyTorch精度工具ptdbg_ascend的使用以及精度比对场景示例。 + +ptdbg_ascend工具的原理及安装请参见《[PyTorch精度工具](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/README.md)》。 + +ptdbg_ascend工具主要支持PyTorch API精度数据dump、溢出检测、精度比对以及parse数据解析功能。其中dump和溢出检测功能支持使用debugger和register_hook方式进行精度数据的dump和溢出检测,推荐使用debugger方式。 + +## 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. 比对结果分析。 + +## 快速入门(debugger方式) + +本章节主要介绍通过ptdbg_ascend工具进行精度比对和分析,主要使用“**debugger方式dump和溢出检测**”和“**CPU或GPU与NPU精度数据比对**”章节中介绍的ptdbg_ascend工具接口。 + +### 单卡场景精度比对 + +**精度分析建议** + +PyTorch训练场景的精度问题分析建议参考以下思路进行精度比对和比对结果分析: + +1. 整网比对:dump整网数据并进行精度比对,初步定位异常范围。 + + 对于模型数据庞大(比如达到T级别)的场景,不推荐直接dump整网比对,整网dump可能导致磁盘不足,需要预留足够的存储空间或者分多次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 * + debugger = PrecisionDebugger(dump_path="./npu_dump", hook_name="dump", step=[0]) + debugger.configure_hook(mode="api_stack") + # 请勿将以上初始化流程插入到循环代码中 + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段2 + + debugger.stop() + debugger.step() + ``` + +2. 比对整网数据。 + + 第1步中的NPU dump数据目录为npu_dump,假设GPU dump数据目录为gpu_dump;dump将生成pkl数据文件api_stack_dump.pkl和npy数据目录api_stack_dump。 + + 创建并配置精度比对脚本,以创建compare.py为例,示例代码如下: + + ```python + from ptdbg_ascend import * + dump_result_param={ + "npu_pkl_path": "./npu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump.pkl", + "bench_pkl_path": "./gpu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump.pkl", + "npu_dump_data_dir": "./npu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump", + "bench_dump_data_dir": "./gpu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump", + "is_print_compare_log": True + } + compare(dump_result_param, "./output", stack_mode=True) + ``` + + 执行比对: + + ```bash + python3 compare.py + ``` + + 在output目录下生成结果文件,包括:`compare_result_{timestamp}.xlsx`和`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}.xlsx`中的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/ptdbg_dump_v4.0/step0/rank0/api_stack_dump.pkl", "Torch.batch.normal.1.forward") + ``` + + 执行提取: + + ```bash + python3 parse.py + ``` + + + +5. (可选)指定API对其底层ACL数据进行dump。 + + - dump指定前向API的ACL级别数据 + + ```python + debugger = PrecisionDebugger(dump_path="./npu_dump", hook_name="dump", step=[0]) + debugger.configure_hook(mode="acl", scope=["Tensor.permute.1.forward"], acl_config='./dump.json') + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段2 + + debugger.stop() + debugger.step() + ``` + + - dump指定反向API的ACL级别数据 + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./npu_dump", hook_name="dump", step=[0]) + # dump指定反向API的ACL级别数据、bool和整型的tensor以及浮点、bool和整型的标量 + debugger.configure_hook(mode="acl", scope=["Functional.conv2d.1.backward"], acl_config="./dump.json", backward_input=["./npu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump/Functional.conv2d.1.backward_input.0.npy"]) + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段2 + + debugger.stop() + debugger.step() + ``` + +6. (可选)重新比对。 + + 根据第4或5步的dump数据重新配置compare.py并执行比对,可以对单API模型进行问题复现。 + +**注意事项** + +* dump_mode="acl"场景下,会增加npu的内存消耗,请谨慎开启。 +* 部分API存在调用嵌套关系,比如functional.batch_norm实际调用torch.batch_norm,该场景会影响acl init初始化多次,导致功能异常。 + +### 溢出检测场景 + +溢出检测是针对NPU的PyTorch API,检测是否存在溢出的情况。当前仅支持识别aicore浮点溢出。 + +溢出检测原理:针对溢出阶段,开启acl dump模式,重新对溢出阶段执行,落盘数据。 + +建议按照如下步骤操作: + +1. 在NPU环境下安装ptdbg_ascend工具。 + +2. 在NPU训练脚本内插入ptdbg_ascend工具溢出检测接口。 + + - 示例1:全量溢出检测 + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./overflow_dump", hook_name="overflow_check", step=[0]) + debugger.configure_hook(overflow_nums=-1) + # 请勿将以上初始化流程插入到循环代码中 + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段2 + + debugger.stop() + debugger.step() + ``` + + 多卡使用时各卡单独计算溢出次数。 + + - 示例2:dump指定前向API的ACL级别溢出数据 + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./overflow_dump", hook_name="overflow_check", step=[0]) + debugger.configure_hook(mode="acl", acl_config="./dump.json") + # 请勿将以上初始化流程插入到循环代码中 + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段2 + + debugger.stop() + debugger.step() + ``` + + - 示例3:dump指定反向API的ACL级别的溢出数据 + + 1. 进行全量溢出检测 + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./overflow_dump", hook_name="overflow_check", step=[0]) + debugger.configure_hook(overflow_nums=-1) + # 请勿将以上初始化流程插入到循环代码中 + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段2 + + debugger.stop() + debugger.step() + ``` + + + + 2. dump指定反向API的ACL级别的溢出数据 + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./overflow_dump", hook_name="dump", step=[0]) + debugger.configure_hook(mode="acl", scope=["Functional.conv2d.1.backward"], acl_config="./dump.json", backward_input=["./overflow_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump/Functional.conv2d.1.backward_input.0.npy"]) + # 请勿将以上初始化流程插入到循环代码中 + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段2 + + debugger.stop() + debugger.step() + ``` + + 针对前向溢出API,可以通过overflow_nums,配置允许的溢出次数,并将每次溢出API的全部ACL数据dump下来,到达指定溢出次数后停止,停止后会看到堆栈打印包含如下字段。 + + ```bash + ValueError: [overflow xxx times]: dump file is saved in '*.pkl'. + ``` + + 其中xxx times为用户设置的次数,*.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。 + +## 场景化示例 + +本章节主要介绍通过ptdbg_ascend工具进行精度比对和分析,主要使用“**CPU或GPU及NPU精度数据dump**”和“**CPU或GPU与NPU精度数据比对**”章节中介绍的ptdbg_ascend工具接口。 + +### 多卡场景精度比对 + +精度工具支持多卡场景的精度比对,多卡场景的dump步骤与单卡场景完全一致,请参见“**单卡场景精度比对**”章节,不同的是多卡数据精度比对时需要使用“compare_distributed”函数进行比对。 + +**大模型场景下dump推荐使用debugger方式的手动模式。** + +如下示例: + +说明:多机多卡场景需要每个设备单独执行比对操作。 + +假设NPU dump npy数据目录为npu_dump/ptdbg_dump_v4.0,GPU dump npy数据目录为gpu_dump/ptdbg_dump_v4.0。 + +1. 创建比对脚本,例如compare_distributed.py,拷贝如下代码。 + + ```python + from ptdbg_ascend import * + compare_distributed('./npu_dump/ptdbg_dump_v4.0/step0', './gpu_dump/ptdbg_dump_v4.0/step0', './output') + ``` + + dump数据目录须指定到step级。 + +2. 执行比对: + + ```bash + python3 compare_distributed.py + ``` + +两次运行须用相同数量的卡,传入`compare_distributed`的两个文件夹下须有相同个数的rank文件夹,且不包含其他无关文件,否则将无法比对。 + +**多卡set_dump_path注意事项** + +多卡一般为多进程,须保证每个进程都正确调用PrecisionDebugger或set_dump_path,或把PrecisionDebugger或set_dump_path插入到import语句后,如: + +```python +from ptdbg_ascend import * +debugger = PrecisionDebugger(dump_path="./npu_dump", hook_name="dump", step=[0]) +``` + +或 + +```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_confusion_transpose | +| 27 | torch_npu.npu_bmmV2 | +| 28 | torch_npu.fast_gelu | +| 29 | torch_npu.npu_sub_sample | +| 30 | torch_npu.npu_deformable_conv2d | +| 31 | torch_npu.npu_mish | +| 32 | torch_npu.npu_anchor_response_flags | +| 33 | torch_npu.npu_yolo_boxes_encode | +| 34 | torch_npu.npu_grid_assign_positive | +| 35 | torch_npu.npu_normalize_batch | +| 36 | torch_npu.npu_masked_fill_range | +| 37 | torch_npu.npu_linear | +| 38 | torch_npu.npu_bert_apply_adam | +| 39 | torch_npu.npu_giou | +| 40 | torch_npu.npu_ciou | +| 41 | torch_npu.npu_diou | +| 42 | torch_npu.npu_sign_bits_pack | +| 43 | torch_npu.npu_sign_bits_unpack | +| 44 | torch_npu.npu_flash_attention | +| 45 | torch_npu.npu_scaled_masked_softmax | +| 46 | torch_npu.npu_rotary_mul | +| 47 | torch_npu.npu_roi_align | +| 48 | torch_npu.npu_roi_alignbk | +| 49 | torch_npu.npu_ptiou | +| 50 | torch_npu.npu_fusion_attention | +| 51 | torch_npu.npu_dropout_with_add_softmax | +| 52 | torch_npu.npu_random_choice_with_mask | +| 53 | torch_npu.npu_rotated_iou | +| 54 | torch_npu.npu_conv2d | +| 55 | torch_npu.npu_conv3d | +| 56 | torch_npu.npu_softmax_cross_entropy_with_logits | +| 57 | torch_npu.npu_all_gather_base_mm | +| 58 | torch_npu.npu_swiglu | +| 59 | torch_npu.npu_rms_norm | +| 60 | torch_npu.npu_mm_reduce_scatter_base | +| 61 | torch_npu.npu_mm_all_reduce_base | +| 62 | torch_npu.npu_conv_transpose2d | +| 63 | torch_npu.npu_convolution | +| 64 | torch_npu.npu_convolution_transpose | +| 65 | torch_npu.npu_min | +| 66 | torch_npu.npu_nms_rotated | +| 67 | torch_npu.npu_reshape | +| 68 | torch_npu.npu_rotated_box_decode | +| 69 | torch_npu.npu_rotated_box_encode | +| 70 | torch_npu.npu_rotated_overlaps | +| 71 | torch_npu.npu_silu | +| 72 | torch_npu.npu_fused_attention_score | +| 73 | torch_npu.npu_multi_head_attention | +| 74 | torch_npu.npu_gru | +| 75 | torch_npu.npu_incre_flash_attention | +| 76 | torch_npu.npu_prompt_flash_attention | +| 77 | torch_npu.npu_lstm | +| 78 | torch_npu.npu_apply_adam | + +### 通信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 | + +### 单卡场景精度比对(register_hook方式) + +**精度分析建议** + +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_v4.0/step0/rank0/api_stack_dump.pkl", + "bench_pkl_path": "./gpu_dump/all_v4.0/step0/rank0/api_stack_dump.pkl", + "npu_dump_data_dir": "./npu_dump/all_v4.0/step0/rank0/api_stack_dump", + "bench_dump_data_dir": "./gpu_dump/all_v4.0/step0/rank0/api_stack_dump", + "is_print_compare_log": True + } + compare(dump_result_param, "./output", stack_mode=True) + ``` + + 执行比对: + + ```bash + python3 compare.py + ``` + + 在output目录下生成结果文件,包括:`compare_result_{timestamp}.xlsx`和`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}.xlsx`中的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_v4.0/step0/rank0/api_stack_dump.pkl", "Torch.batch.normal.1.forward") + ``` + + 执行提取: + + ```bash + python3 parse.py + ``` + +5. (可选)指定API对其底层ACL数据进行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_v4.0/step0/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初始化多次,导致功能异常。 + +### 溢出检测场景(register_hook方式) + +溢出检测是针对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_v4.0/step0/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 '*.pkl'. + ``` + + 其中xxx times为用户设置的次数,*.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, model=None): +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ----------------- | ------------------------------------------------------------ | -------- | +| dump_path | 设置dump数据目录路径,参数示例:"./dump_path"。数据类型:str。
默认在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和溢出检测功能,二选一。参数示例:hook_name="dump"。数据类型:str。 | 是 | +| rank | 指定对某张卡上的数据进行dump或溢出检测,默认未配置(表示dump所有卡的数据),须根据实际卡的Rank ID配置。应配置为大于0的正整数,且须根据实际卡的Rank ID配置,若所配置的值大于实际训练所运行的卡的Rank ID,则dump数据为空,比如当前环境Rank ID为0到7,实际训练运行0到3卡,此时若配置Rank ID为4或不存在的10等其他值,此时dump数据为空。数据类型:int。 | 否 | +| step | 指定dump某个step的数据,默认未配置,表示dump所有step数据。dump特定step时,须指定为训练脚本中存在的step。step为list格式,可配置逐个step,例如:step=[0,1,2];也可以配置step范围,例如:step=list(range(0,9)),表示dump第0到第8个step。数据类型:List[int]。 | 否 | +| enable_dataloader | 自动控制开关,可取值True(开启)或False(关闭),默认为False。配置为True后自动识别dump step参数指定的迭代,并在该迭代执行完成后退出训练,此时start和stop函数可不配置,开启该开关要求训练脚本是通过torch.utils.data.dataloader方式加载数据;配置为False则需要配置start和stop函数,并在最后一个stop函数后或一个step结束的位置添加debugger.step()。数据类型:bool。 | 否 | +| model | 开启init dump模式,传入网络模型实例化的对象,配置该参数后,dump操作仅dump网络中init方法里调用的方法(nn.Module类),不会对所有API进行dump。参数示例: model=net,net为网络模型实例化的对象名称。默认未配置。
配置该参数时,PrecisionDebugger模块请在模型实例化之后调用。数据类型:torch.nn.Module。
该模式不支持“溢出检测”、”ACL级别数据dump“和“模块级精度数据dump”。此模式下dump文件名前缀为网络中定义的模块名或层名。 | 否 | + +#### init dump模式示例代码和数据落盘说明 + +**示例代码** + +```python +import os +import torch +import torch.nn as nn +import torch_npu +from ptdbg_ascend import * + +torch.npu.set_device("npu:0") + + +class Net(nn.Module): + + def __init__(self): + super(Net, self).__init__() + self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5, stride=1, padding=2) + self.relu1 = nn.ReLU() + self.bn1 = nn.BatchNorm2d(16) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + output = self.relu1(x) + return output + +if __name__ == "__main__": + net = Net().npu() + # model参数传入net, 开启init dump 功能 + debugger = PrecisionDebugger(dump_path="./dump", hook_name="dump", model=net) + debugger.configure_hook(mode="api_stack") + debugger.start() + x = torch.randn(1, 1, 28, 28).npu() + out = net(x) + loss = out.sum() + loss.backward() + debugger.stop() +``` + +**落盘数据说明** + +该模式下dump数据命名格式为:`{Layer_name}.{Module_name}.{call_num}.{forward/backward}.{input/output}.npy` + +``` +# 按照上述用例代码进行dump,落盘数据命名示例如下: +conv1.Conv2d.0.forward.input.0.npy +conv1.Conv2d.0.forward.output.npy +relu1.ReLU.0.forward.input.0.npy +....... +bn1.BatchNorm2d.0.backward.output.2.npy +``` + +### 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, summary_mode="all") +``` + +溢出检测: + +```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数据存盘说明**”。数据类型:str。 | 否 | +| 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"]。默认为空。数据类型:List[str]。 | 否 | +| filter_switch | dump bool和整型的tensor以及浮点、bool和整型的标量的过滤开关。可取值"ON"(表示开启过滤,即不dump)或"OFF"(表示关闭过滤)。参数示例:filter_switch="ON"。默认不配置,即filter_switch="OFF",表示dump上述数据。数据类型:str。 | 否 | +| acl_config | acl dump的配置文件。mode="acl"时,该参数必选;mode为其他值时,该参数不选。参数示例:acl_config='./dump.json'。dump.json配置文件详细介绍请参见“**dump.json配置文件说明**”。数据类型:str。 | 否 | +| backward_input | 该输入文件为首次运行训练dump得到反向API输入的.npy文件。例如若需要dump Functional.conv2d.1 API的反向过程的输入输出,则需要在dump目录下查找命名包含Functional.conv2d.1、backward和input字段的.npy文件。数据类型:str。 | 否 | +| 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参数只能单独配置外,其他参数可以自由组合。数据类型:list。 | 否 | +| summary_only | dump npy文件过滤,可取值True或False,配置为True后仅dump保存API统计信息的pkl文件,参数示例:summary_only=False,默认为False。数据类型:bool。 | 否 | +| summary_mode | 控制dump文件输出的模式,可取值md5(dump仅输出包含md5值的pkl文件,用于验证数据的完整性)、summary(dump仅输出包含API统计信息的pkl文件)、all(dump输出包含API统计信息的pkl文件以及具体的npy文件),参数示例:summary_mode="md5",默认为"all"。summary_only=True时,不允许配置该参数。数据类型:str。 | 否 | +| overflow_nums | 控制溢出次数,表示第N次溢出时,停止训练,过程中检测到溢出API对应ACL数据均dump。参数示例:overflow_nums=3。配置overflow_check时可配置,默认不配置,即检测到1次溢出,训练停止,配置为-1时,表示持续检测溢出直到训练结束。数据类型:int。 | 否 | +| need_replicate | 过程dump数据生成开关,执行溢出检测时,dump目录下会生成forward_real_data和backward_real_data的过程dump数据目录,可取值True(生成)或False(不生成),默认不生成。数据类型:bool。 | 否 | + +**函数示例** + +configure_hook可配置多种dump模式,示例如下: + +说明: + +以下均以dump部分API数据为例,API名可以从首次dump整网数据的结果文件中的NPU Name或Bench Name列获取。 + +以下仅为该函数配置示例,完整代码请参见“**示例代码**”章节。 + +- 示例1:dump指定API列表 + + ```python + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0]) + debugger.configure_hook(mode="list", scope=["Tensor.permute.1.forward", "Tensor.transpose.2.forward", "Torch.relu.3.backward"]) + ``` + +- 示例2:dump指定范围 + + ```python + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0]) + debugger.configure_hook(mode="range", scope=["Tensor.abs.1.forward", "Tensor.transpose.3.forward"]) + ``` + +- 示例3:STACK模式,只dump堆栈信息 + + ```python + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0]) + debugger.configure_hook(mode="stack", scope=["Tensor.abs.1.forward", "Tensor.transpose.3.forward"]) + ``` + +- 示例4:dump指定前向API的ACL级别数据 + + ```python + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0]) + debugger.configure_hook(mode="acl", scope=["Tensor.permute.1.forward"], acl_config="./dump.json") + ``` + +- 示例5:dump指定反向API的ACL级别数据 + + ```python + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0]) + debugger.configure_hook(mode="acl", scope=["Functional.conv2d.1.backward"], acl_config="./dump.json", backward_input=["./npu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump/Functional.conv2d.1.backward.input.0.npy"]) + ``` + +- 示例6:dump指定某一类API的API级别输入输出数据 + + ```python + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0]) + debugger.configure_hook(mode="api_list", api_list=["relu"]) + ``` + + mode="api_list"时不配置scope。 + +- 示例7:dump全部API级别输入输出数据以及相应堆栈信息 + + ```python + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0]) + debugger.configure_hook(mode="api_stack") + ``` + + mode="api_stack"时不配置scope。 + +- 示例8: dump全部API级别输入输出数据并包含bool和整型的tensor以及浮点、bool和整型的标量,配置为OFF,会dump bool和整型数据 + + ```python + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0]) + debugger.configure_hook(filter_switch="OFF") + ``` + + 配置filter_switch="OFF"同时也可以配置mode、scope和api_list,除dump ACL级别数据。 + +- 示例9:仅保存dump的数据文件名包含“backward”的反向.npy文件 + + ```python + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0]) + debugger.configure_hook(input_output_mode=["backward"]) + ``` + +- 示例10:仅dump pkl文件 + + ```python + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0]) + debugger.configure_hook(summary_only=True) + ``` + +- 示例11:溢出检测dump + + ```python + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="overflow_check", step=[0]) + debugger.configure_hook(overflow_nums=1) + ``` + + dump执行时会在**PrecisionDebugger**模块的dump_path参数指定的目录下生成ptdbg_dump_{version}目录,保存溢出数据。 + + 多卡场景时,需要检测到至少有一张卡溢出次数达到overflow_nums时,训练结束。 + + 仅支持NPU环境。 + +- 示例11:dump溢出API的ACL级别数据 + + ```python + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="overflow_check", step=[0]) + 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()。 + +### 示例代码(自动模式) + +**需要保证用户训练代码是通过torch.utils.data.dataloader方式加载数据。** + +- 示例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的代码片段2 + + 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的代码片段2 + + debugger.stop() + debugger.step() + ``` + +## register_hook方式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 + +**功能说明** + +固定随机数。通过固定随机数保证模型的输入或输出一致。在训练主函数开始前调用,避免随机数固定不全。 + +使用form ptdbg import *后自动导入该函数,代码无需再次添加,若需要修改随机数种子和确定性计算模式,则需要通过添加该函数修改。 + +**函数原型** + +```python +seed_all(seed=1234, mode=False) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------ | ------------------------------------------------------------ | -------- | +| seed | 随机数种子。参数示例:seed=1000。默认值为:1234。数据类型:int。 | 否 | +| mode | 确定性计算模式。可配置True或False。参数示例:mode=True。默认为False。数据类型:bool。
即使在相同的硬件和输入下,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'。数据类型:str。
默认在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}。数据类型:str。
{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) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------- | ------------------------------------------------------------ | -------- | +| model | 传入网络模型实例化的对象。参数示例: model=net,net为网络模型实例化的对象名称。数据类型:torch.nn.Module。 | 是 | +| hook | 注册工具的dump和溢出检测钩子。可取值overflow_check(表示溢出检测)和acc_cmp_dump(表示dump数据),二选一。数据类型:Callable。 | 是 | +| overflow_nums | 控制溢出次数,表示第N次溢出时,停止训练,过程中检测到溢出API对应ACL数据均dump。参数示例:overflow_nums=3。配置overflow_check时可配置,默认不配置,即检测到1次溢出,训练停止,配置为-1时,表示持续检测溢出直到训练结束。数据类型:int。 | 否 | +| dump_mode | 控制针对溢出API的dump模式,可取值"acl"或"api"。配置acl时,表示dump ACL级别的溢出数据,此时set_dump_path参数不生效,dump数据目录由dump_config的.json文件配置。参数示例:dump_mode="acl"。默认不配置,即dump API级别的溢出数据。数据类型:str。 | 否 | +| dump_config | acl dump的配置文件。dump_mode="acl"时,该参数必选;dump_mode="api"时,该参数不选。参数示例:dump_config='./dump.json'。数据类型:str。 | 否 | + +**函数示例** + +- 示例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")。数据类型:str。 | 是 | +| mode | dump模式。可取值"all"、"list"、"range"、"stack"、"acl"、"api_list"、"api_stack",各参数含义请参见本节的“**函数示例**”。参数示例:mode="list"。默认为"all"。该参数配置值将作为dump数据文件名的前缀,详情请参见“**dump数据存盘说明**”。数据类型:str。 | 否 | +| scope或api_list | dump范围。根据model配置的模式选择dump的API范围。参数示例:scope=["Tensor.permute.1.forward", "Tensor.transpose.2.forward"]、api_list=["relu"]。默认为空。数据类型:List[str]。 | 否 | +| filter_switch | dump bool和整型的tensor以及浮点、bool和整型的标量的过滤开关。可取值"ON"或"OFF"。参数示例:filter_switch="ON"。默认不配置,即filter_switch="OFF",表示dump上述数据。数据类型:str。 | 否 | +| dump_mode | dump数据过滤。可取值"all"、"forward"、"backward"、"input"和"output",表示仅保存dump的数据中文件名包含"forward"、"backward"、"input"和"output"的前向、反向、输入或输出的.npy文件。参数示例dump_mode=["backward"]或dump_mode=["forward", "backward"]。默认为all,即保存所有dump的数据。除了all参数只能单独配置外,其他参数可以自由组合。数据类型:List[str]。 | 否 | +| summary_only | dump npy文件过滤,可取值True或False,配置为True后仅dump保存API统计信息的pkl文件,参数示例:summary_only=False,默认为False。数据类型:bool。 | 否 | + +**推荐配置** + +```python +set_dump_switch("ON", mode="api_stack", filter_switch="OFF") +``` + +开启dump数据和堆栈模式,同时为保证数据完整性开启dump bool和整型的tensor以及浮点、bool和整型的标量。 + +**函数示例** + +set_dump_switch可配置多种dump模式,示例如下: + +说明:以下均以dump部分API数据为例,API名可以从首次dump整网数据的结果文件中的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_v4.0/step0/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 +from ptdbg_ascend.common.file_check_util import FileChecker +import argparse +import os.path + +pkl_path = "%s" +dump_data_dir = "%s" + +parser = argparse.ArgumentParser(description="compare data") +parser.add_argument("--npu_pkl_path", type=str, default=pkl_path, help="npu保存数据的pkl路径") +parser.add_argument("--bench_pkl_path", type=str, default=pkl_path, help="对比数据的pkl路径") +parser.add_argument("--output_path", type=str, default="./", help="导出对比数据的路径") + +args = parser.parse_args() +npu_pkl_path = args.npu_pkl_path +bench_pkl_path = args.bench_pkl_path +output_path = args.output_path + +suffix = ".pkl" +npu_path_checker = FileChecker(npu_pkl_path, "file", "read", suffix) +npu_path_checker.common_check() +bench_path_checker = FileChecker(bench_pkl_path, "file", "read", suffix) +bench_path_checker.common_check() + +npu_dump_data_dir = npu_pkl_path[:-len(suffix)] +bench_dump_data_dir = bench_pkl_path[:-len(suffix)] +if not os.path.exists(npu_dump_data_dir) or not os.path.exists(bench_dump_data_dir): + npu_dump_data_dir = "" + bench_dump_data_dir = "" + +dump_path_param = { + "npu_pkl_path": npu_pkl_path, + "bench_pkl_path": bench_pkl_path, + "npu_dump_data_dir": npu_dump_data_dir, + "bench_dump_data_dir": bench_dump_data_dir, + "is_print_compare_log": True +} + +compare(dump_path_param, output_path=output_path, stack_mode=%s) +``` + +compare_data.py比对脚本模板可以直接使用命令行配置比对参数,不需要通过编辑compare_data.py文件来修改,示例如下: + +```bash +python3 compare_data.py --npu_pkl_path "./npu_dump/ptdbg_dump_v6.0/step0/rank0/api_stack_dump.pkl" --bench_pkl_path "./gpu_dump/ptdbg_dump_v6.0/step0/rank0/api_stack_dump.pkl" --output_path "./output_path" +``` + +该命令行支持--npu_pkl_path、--bench_pkl_path和--output三个比对参数,其中pkl_path两个参数配置后,脚本可以自动识别同级目录下的dump_data目录,若同级目录下不存在dump_data目录,则直接执行“**pkl文件比对**”。仅ptdbg_ascend 6.0或更高版本支持比对命令行配置比对参数。更多介绍请参见“**执行比对操作**”。 + +### 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")。数据类型:str。 | 是 | +| filter_switch | dump bool和整型的tensor以及浮点、bool和整型的标量的过滤开关。可取值"ON"或"OFF"。参数示例:filter_switch="ON"。默认不配置,即filter_switch="OFF",表示dump上述数据。数据类型:str。 | 否 | + +**函数示例** + +- 示例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文件。数据类型:str。 | 是 | + +**函数示例** + +```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_v4.0/step0/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类对象。数据类型:torch.nn.Module。 | 是 | +| module_name | 用户自定义的该model名称。主要用于dump数据文件的命名,便于在比对时识别模块级数据。数据类型:str。 | 是 | + +### module_dump_end + +**功能说明** + +结束模块级精度数据dump。 + +模块级精度数据dump时必选。 + +**函数原型** + +```python +module_dump_end() +``` + +### 示例代码 + +```python +# 根据需要import包 +import os +import torch +import torch.nn as nn +import torch_npu +import torch.nn.functional as F +from ptdbg_ascend import * + +torch.npu.set_device("npu:0") +# 定义一个简单的网络 +class ModuleOP(nn.Module): + def __init__(self) -> None: + super().__init__() + self.linear_1 = nn.Linear(in_features=8, out_features=4) + self.linear_2 = nn.Linear(in_features=4, out_features=2) + def forward(self, x): + x1 = self.linear_1(x) + x2 = self.linear_2(x1) + r1 = F.relu(x2) + return r1 + +if __name__ == "__main__": + module = ModuleOP() + + # 注册工具 + pdbg = PrecisionDebugger("./dump_data/npu", hook_name="dump") + pdbg.start() + + x = torch.randn(10, 8) + module_dump(module, "MyModuleOP") # 开启模块级精度数据dump + out = module(x) + module_dump_end() # 结束模块级精度数据dump + loss = out.sum() + loss.backward() + pdbg.stop() +``` + +## dump数据存盘说明 + +dump结果目录结构示例如下: + +```bash +├── dump_path +│ └── ptdbg_dump_{version} +│ ├── step0 +│ | ├── 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 +│ ├── step1 +│ | ├── ... +│ ├── step2 +``` + +dump过程中,npy文件在对应算子或者模块被执行后就会落盘,而pkl文件则需要在正常执行PrecisionDebugger.stop()或set_dump_switch("OFF")后才会被落盘保存,异常的程序终止会保存终止前被执行算子的相关npy文件,但是不会生成pkl文件。 + +其中`ptdbg_dump_{version}`为默认命名,debugger方式dump不支持修改该文件夹名称,使用set_dump_path函数则支持通过dump_tag参数修改文件夹名称;rank为设备上各卡的ID,每张卡上dump的数据会生成对应dump目录。 + +**精度比对dump场景** + +精度比对dump场景的结果如下: + +* dump.pkl文件:包含dump数据的API名称(命名格式为:`{api_type}.{api_name}.{API调用次数}.{前向反向}.{input/output}.{参数序号}`)、dtype、 shape、各数据的max、min、mean、L2norm统计信息以及当配置summary_mode="md5"时的md5数据。 + + 其中,“参数序号”表示该API下的第n个参数,例如1,则为第一个参数,若该参数为list格式,则根据list继续排序,例如1.1,表示该API的第1个参数的第1个子参数;L2norm表示2范数(平方根)。 + +* dump目录:目录下为npy格式的dump数据。 + + npy文件保存的前缀和PyTorch对应关系如下 + + | 前缀 | Torch模块 | + | ----------- | ------------------- | + | Tensor | torch.Tensor | + | Torch | torch | + | Functional | torch.nn.functional | + | NPU | NPU亲和算子 | + | VF | torch._VF | + | Aten | torch.ops.aten | + | Distributed | torch.distributed | + +当configure_hook或set_dump_switch配置mode参数(例如:mode="api_stack" )时,dump结果的文件名会添加api_stack前缀,dump结果如下: + +* api_stack_dump.pkl +* api_stack_dump目录 + +**溢出检测dump场景** + +PrecisionDebugger模块的hook_name参数或register_hook函数设置了overflow_check时,检测API溢出,dump结果的文件名格式为:`{api_type}.{api_name}.{API调用次数}.{前向反向}.{当前溢出次数}`,dump结果示例如下: + +* `Tensor_add_1_forward_1.pkl` +* `Tensor_add_1_forward_1`目录 + +## 工具支持的API列表 + +ptdbug_ascend工具维护固定的API支持列表,若需要删除或增加dump的API,可以在[support_wrap_ops.yaml](../src/python/ptdbg_ascend/hook_module/support_wrap_ops.yaml)文件内手动修改,如下示例: + +```bash +functional: # functional为算子类别,找到对应的类别,在该类别下按照下列格式删除或添加API + - conv1d + - conv2d + - conv3d +``` + +## 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目录。dump数据目录须指定到step级。参数示例:'./npu_dump/ptdbg_dump_v4.0/step0'。register_hook方式可通过set_dump_path函数的dump_tag参数修改该目录名称。数据类型:str。 | 是 | +| bench_dump_dir | 配置CPU、GPU或NPU环境下的dump目录。参数示例:'./gpu_dump/ptdbg_dump_v4.0/step0'。register_hook方式可通过set_dump_path函数的dump_tag参数修改该目录名称。数据类型:str。 | 是 | +| output_path | 配置比对结果文件存盘目录。需要预先创建output_path目录。参数示例:'./output'。文件名称基于时间戳自动生成,格式为:`compare_result_rank{npu_ID}-rank{cpu/gpu/npu_ID}_{timestamp}.xlsx`。数据类型:str。 | 是 | +| **kwargs | 支持compare的所有可选参数。 | 否 | + +**函数示例** + +创建比对脚本,例如compare_distributed.py,拷贝如下代码,具体参数请根据实际环境修改。 + +```python +from ptdbg_ascend import * +compare_distributed('./npu_dump/ptdbg_dump_v4.0/step0', './gpu_dump/ptdbg_dump_v4.0/step0', './output') +``` + +dump数据目录须指定到step级。 + +### compare + +**功能说明** + +将CPU或GPU与NPU的dump文件进行比对,仅支持单机单卡。 + +**函数原型** + +```python +compare(input_param, output_path, stack_mode=False, auto_analyze=True, fuzzy_match=False) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------ | ------------------------------------------------------------ | -------- | +| input_param | 配置dump数据文件及目录。数据类型:dict。配置参数包括:
"npu_pkl_path":指定NPU dump目录下的.pkl文件。参数示例:"npu_pkl_path": "./npu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump.pkl"。必选。
"bench_pkl_path":指定CPU、GPU或NPU dump目录下的.pkl文件。参数示例:"bench_pkl_path": "./gpu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump.pkl"。必选。
"npu_dump_data_dir":"指定NPU dump目录下的dump数据目录。参数示例:"npu_dump_data_dir": "./npu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump"。可选,仅比对pkl文件时不选。
"bench_dump_data_dir":"指定CPU、GPU或NPU dump目录下的dump数据目录。参数示例:"npu_dump_data_dir": "./gpu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump"。可选,仅比对pkl文件时不选。
"is_print_compare_log":配置是否开启日志打屏。可取值True或False。可选。 | 是 | +| output_path | 配置比对结果文件存盘目录。参数示例:"./output_path",默认为"./"。文件名称基于时间戳自动生成,格式为:`compare_result_{timestamp}.xlsx`。数据类型:str。 | 否 | +| stack_mode | 配置stack_mode的开关。仅当dump数据时配置debugger.configure_hook或set_dump_switch的mode="api_stack"时需要开启。可取值True或False,参数示例:stack_mode=True,默认为False。数据类型:bool。 | 否 | +| auto_analyze | 自动精度分析,开启后工具自动针对比对结果进行分析,识别到第一个精度不达标节点(在比对结果文件中的“Accuracy Reached or Not”列显示为No),并给出问题可能产生的原因(打屏展示并生成advisor_{timestamp}.txt文件)。可取值True或False,参数示例:auto_analyze=False,默认为True。数据类型:bool。 | 否 | +| fuzzy_match | 模糊匹配。开启后,对于网络中同一层级且命名仅调用次数不同的API,可匹配并进行比对。可取值True或False,参数示例:fuzzy_match=True,默认为False。数据类型:bool。 | 否 | + +**函数示例** + +单机单卡场景下创建比对脚本,例如compare.py,拷贝如下代码,具体参数请根据实际环境修改。 + +```python +from ptdbg_ascend import compare +dump_result_param={ +"npu_pkl_path": "./npu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump.pkl", +"bench_pkl_path": "./gpu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump.pkl", +"npu_dump_data_dir": "./npu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump", +"bench_dump_data_dir": "./gpu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump", +"is_print_compare_log": True +} +compare(dump_result_param, output_path="./output_path", stack_mode=True) +``` + +### pkl文件比对 + +若使用**compare**或**compare_distributed**函数创建的比对脚本中,input_param参数只配置了npu_pkl_path和bench_pkl_path或使用summary_only、summary_mode(取值为md5或summary)方式dump时,可以进行pkl文件的比对,此时比对dump.pkl文件中的统计信息,开启后的比对结果文件生成Max diff、Min diff、Mean diff和L2norm diff,表示NPU dump数据中API的输入或输出与标杆数据输入或输出的最大值、最小值、平均值以及L2范数的差。可以通过该值判断API是否存在精度问题:当某个API的输入和输出的Max diff、Min diff、Mean diff和L2norm diff均为0或无限趋于0,那么可以判断该API无精度问题,反之则可能存在精度问题。 + +**比对脚本示例** + +以compare.py为例。 + +```python +from ptdbg_ascend import compare +dump_result_param={ +"npu_pkl_path": "./npu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump.pkl", +"bench_pkl_path": "./gpu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump.pkl", +"is_print_compare_log": True +} +compare(dump_result_param, output_path="./output_path", stack_mode=True) +``` + +**比对结果** + +pkl文件比对同样生成`compare_result_{timestamp}.xlsx`和`advisor_{timestamp}.txt`文件。其中`advisor_{timestamp}.txt`主要对`compare_result_{timestamp}.xlsx`中可能存在精度问题(Result为Waring)的API提出定位建议;`compare_result_{timestamp}.xlsx`主要有如下两种情况: + +- configure_hook配置summary_only=True、summary_mode=summary或不配置前面两个参数直接比对pkl文件: + + ![compare_result_pkl](./img/compare_result_pkl.png) + + 上图是对pkl文件中NPU及标杆API的统计信息进行比对,判断可能存在精度问题的API,文件中记录NPU及标杆API的基本信息和统计信息,其中需要关注Result列,包含结果:Waring(NPU与标杆统计信息的比对中存在相对误差大于0.5,则需要重点检查该API);为空(相对误差小于等于0.5,可以不需要重点关注,但不代表不存在精度问题);Nan(表示统计信息数据没有匹配上)。 + +- configure_hook配置summary_mode=md5: + + ![compare_result_pkl_md5.png](./img/compare_result_pkl_md5.png.png) + + 上图是对pkl文件中NPU及标杆API的MD5信息进行比对,判断API数据的完整性,文件中记录NPU及标杆API的基本信息和MD5信息,其中需要关注Result列,包含结果:Pass(表示NPU与标杆的MD5值一致,即API数据完整);Different(表示NPU与标杆的MD5值不一致,即API数据不完全一致,可以通过NPU_Stack_Info列API调用栈查询该API的详细信息);Nan(表示MD5信息数据没有匹配上)。 + +### parse + +**功能说明** + +解析并提取dump信息中的堆栈信息及数据统计信息。 + +**函数原型** + +```python +parse(pkl_file, module_name_prefix) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------------ | ------------------------------------------------------------ | -------- | +| pkl_file | 指定dump数据文件中的pkl文件名。参数示例:"./npu_dump/ptdbg_dump_v4.0/step0/rank0/dump.pkl"。数据类型:str。 | 是 | +| module_name_prefix | 指定待提取的API接口前缀。参数示例:"Torch.norm.1.forward"。数据类型:str。 | 是 | + +**函数示例** + +创建堆栈信息及数据统计信息提取脚本,例如parse.py,拷贝如下代码,具体参数请根据实际环境修改。 + +```python +from ptdbg_ascend import * +parse("./npu_dump/ptdbg_dump_v4.0/step0/rank0/dump.pkl", "Torch.batch.normal.1.forward") +``` + +### 执行比对操作 + +比对操作通过执行比对脚本启动,根据不同的比对脚本分为如下场景: + +- dump数据时自动生成比对脚本模板,脚本名为compare_data.py,该脚本模板也可以直接手动创建: + + ```python + from ptdbg_ascend import compare + from ptdbg_ascend.common.file_check_util import FileChecker + import argparse + import os.path + + pkl_path = "%s" + dump_data_dir = "%s" + + parser = argparse.ArgumentParser(description="compare data") + parser.add_argument("--npu_pkl_path", type=str, default=pkl_path, help="npu保存数据的pkl路径") + parser.add_argument("--bench_pkl_path", type=str, default=pkl_path, help="对比数据的pkl路径") + parser.add_argument("--output_path", type=str, default="./", help="导出对比数据的路径") + + args = parser.parse_args() + npu_pkl_path = args.npu_pkl_path + bench_pkl_path = args.bench_pkl_path + output_path = args.output_path + + suffix = ".pkl" + npu_path_checker = FileChecker(npu_pkl_path, "file", "read", suffix) + npu_path_checker.common_check() + bench_path_checker = FileChecker(bench_pkl_path, "file", "read", suffix) + bench_path_checker.common_check() + + npu_dump_data_dir = npu_pkl_path[:-len(suffix)] + bench_dump_data_dir = bench_pkl_path[:-len(suffix)] + if not os.path.exists(npu_dump_data_dir) or not os.path.exists(bench_dump_data_dir): + npu_dump_data_dir = "" + bench_dump_data_dir = "" + + dump_path_param = { + "npu_pkl_path": npu_pkl_path, + "bench_pkl_path": bench_pkl_path, + "npu_dump_data_dir": npu_dump_data_dir, + "bench_dump_data_dir": bench_dump_data_dir, + "is_print_compare_log": True + } + + compare(dump_path_param, output_path=output_path, stack_mode=%s) + ``` + + 执行如下命令启动比对操作: + + ```bash + python3 compare_data.py --npu_pkl_path "npu_pkl_path" --bench_pkl_path "bench_pkl_path" --output_path "output_path" + ``` + + 命令行示例:python3 compare_data.py --npu_pkl_path "./npu_dump/ptdbg_dump_v6.0/step0/rank0/api_stack_dump.pkl" --bench_pkl_path "./gpu_dump/ptdbg_dump_v6.0/step0/rank0/api_stack_dump.pkl" --output_path "./output" + + - 该命令行支持--npu_pkl_path、--bench_pkl_path和--output三个**命令行比对参数**,其中pkl_path两个参数配置后,脚本可以自动识别同级目录下的dump_data目录,若同级目录下不存在dump_data目录,则直接执行“**pkl文件比对**”。 + - **命令行比对参数**的优先级高于compare.py比对脚本内的参数,配置命令行比对参数后,不需要通过编辑compare_data.py文件来修改比对参数。 + - **命令行比对参数**均为可选,但若未配置pkl_path两个参数,则需要在比对脚本中配置。 + - 仅ptdbg_ascend 6.0或更高版本支持**命令行比对参数**。 + + | 参数 | 说明 | 是否必选 | + | ---------------- | ------------------------------------------------------------ | -------- | + | --npu_pkl_path | 指定NPU dump目录下的.pkl文件。参数示例:--npu_pkl_path "./npu_dump/ptdbg_dump_v6.0/step0/rank0/api_stack_dump.pkl"。 | 否 | + | --bench_pkl_path | 指定CPU、GPU或NPU dump目录下的.pkl文件。参数示例:--bench_pkl_path "./gpu_dump/ptdbg_dump_v6.0/step0/rank0/api_stack_dump.pkl" | 否 | + | --output_path | 配置比对结果文件存盘目录。参数示例:--output_path "./output",默认为"./"。文件名称基于时间戳自动生成,格式为:`compare_result_{timestamp}.xlsx`。 | 否 | + +- 手动创建比对脚本,自定义脚本名为compare.py: + + ```python + from ptdbg_ascend import compare + dump_result_param={ + "npu_pkl_path": "./npu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump.pkl", + "bench_pkl_path": "./gpu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump.pkl", + "npu_dump_data_dir": "./npu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump", + "bench_dump_data_dir": "./gpu_dump/ptdbg_dump_v4.0/step0/rank0/api_stack_dump", + "is_print_compare_log": True + } + compare(dump_result_param, output_path="./output_path", stack_mode=True) + ``` + + 执行如下命令启动比对操作: + + ```bash + python3 compare.py + ``` + + 比对完成后在output_path输出比对结果文件,包括:`compare_result_{timestamp}.xlsx`和`advisor_{timestamp}.txt` + + 比对结果详细分析请参见“**比对结果分析**”。 + +### 比对结果分析 + +PyTorch精度比对是以CPU或GPU的计算结果为标杆,通过计算精度评价指标判断API在运行时是否存在精度问题。 + +- `advisor_{timestamp}.txt`文件中给出了可能存在精度问题的API的专家建议,可直接打开查看。 + +- `compare_result_{timestamp}.xlsx`文件列出了所有执行精度比对的API详细信息和比对结果,如下示例: + + ![compare_result](./img/compare_result.png) + + 可以从该结果文件中进行“**判断计算精度达标情况**”、“**计算精度评价指标分析**”以及“**异常信息识别**”等分析动作。 + +**判断计算精度达标情况** + +精度比对结果`compare_result_{timestamp}.xlsx`文件中只需要通过Accuracy Reached or Not来判断计算精度是否达标,判断标准如下: + +1. Cosine < 0.99 且 MaxAbsError > 0.001时,精度不达标,标记为“No”。 +2. Cosine < 0.9,精度不达标,标记为“No”。 +3. MaxAbsError > 1,精度不达标,标记为“No”。 +4. 其余情况下记为精度达标,标记为“Yes”。 + +**计算精度评价指标分析** + +1. Cosine:通过计算两个向量的余弦值来判断其相似度,数值越接近于1说明计算出的两个张量越相似,实际可接受阈值为大于0.99。在计算中可能会存在nan,主要由于可能会出现其中一个向量为0。 + +2. MaxAbsErr:当最大绝对误差越接近0表示其计算的误差越小,实际可接受阈值为小于0.001。 + +3. MaxRelativeErr:当最大相对误差越接近0表示其计算的误差越小。 + + 当dump数据中存在0或Nan时,比对结果中最大相对误差则出现inf或Nan的情况,属于正常现象。 + +4. One Thousandth Err Ratio(双千分之一)、Five Thousandths Err Ratio(双千分之五)精度指标:是指NPU的Tensor中的元素逐个与对应的标杆数据对比,相对误差大于千分之一、千分之五的比例占总元素个数的比例小于千分之一、千分之五。该数据仅作为精度下降趋势的参考,并不参与计算精度是否通过的判定。 + +**异常信息识别** + +精度比对结果`compare_result_{timestamp}.xlsx`文件中对于存在异常信息的API会进行着色处理: + +- 红色:代表数据中存在NPU max或NPU min信息存在nan/inf、Max diff存在1e+10、Max diff或Bench max > 0.5、One Thousandth Err Ratio的input > 0.9或output < 0.6等情况。 +- 黄色:代表数据中存在Max diff存在log10(output) - log10(input) >= 1(即输出与输入存在数量级差)、Max diff或Bench max的input > 0.1或input < 0.01、One Thousandth Err Ratio的output - input > 0.1、Cosine的output - input > 0.1等情况。 + +## ptdbg_ascend.parse数据解析功能 + +ptdbg_ascend.parse为命令行交互式界面解析工具,提供更多的数据解析功能并且展示结果。 + +使用场景:本工具主要用于比对前后两次NPU ACL层级dump数据的一致性。 + +### 进入parse交互式界面 + +安装ptdbg_ascend工具后,可以通过使用命令 **python -m ptdbg_ascend.parse** 进入交互式界面,如下所示: + +```bash +python -m ptdbg_ascend.parse +Parse >>> +``` + +可在parse的界面中执行Shell命令,以及如下场景的相关解析命令: + +- 支持指定ACL层级算子数据比对。 +- 支持指定ACL层级算子数据转换及展示。 +- 支持交互式指定pkl文件中API对应dump数据查看。 +- 支持API进行可选层级比对和打印(统计级和像素级)。 + +Ctrl+C可以退出parse交互式界面。不退出parse交互式界面若需要执行非该界面下的内置Shell命令,且命令与parse交互式界面命令冲突时,非该界面命令需要使用run命令,在相关命令前加上run前缀,如下示例: + +```bash +python -m ptdbg_ascend.parse +Parse >>> run vim cli.py +Parse >>> vim cli.py +``` + +以上各场景详细介绍请参见下文章节。 + +### ACL层级算子数据批量转换 + +本功能会将原有待比对dump数据目录下的dump数据按照算子名和时间戳进行梳理并分类,之后再将dump数据转为为npy文件。 + +依赖:CANN包中的msaccucmp工具,需要安装Ascend-CANN-toolkit,详见《[CANN 软件安装指南](https://gitee.com/link?target=https%3A%2F%2Fwww.hiascend.com%2Fdocument%2Fdetail%2Fzh%2Fcanncommercial%2F700%2Fenvdeployment%2Finstg%2Finstg_0001.html)》。 + +输入以下比对命令进行数据转换。 + +```bash +cad -m my_dump_path [-out output_path] [-asc msaccucmp_path] +``` + +| 参数名称 | 说明 | 是否必选 | +| -------- | ------------------------------------------------------------ | -------- | +| -m | 待转换ACL dump数据目录。需要指定到ACL dump数据的deviceid级目录。 | 是 | +| -out | 结果输出目录,须指定已存在的目录,默认为./parse_data/acl_batch_convert。未指定时保存在默认路径下,比对结束后会打印log提示输出结果存放路径。 | 否 | +| -asc | 指定msaccucmp路径,默认路径为:/usr/local/Ascend/ascend-toolkit/latest/tools/operator_cmp/compare/msaccucmp.py。 | 否 | + +**示例** + +```bash +# 传入待比对数据目录 +Parse >>> cad -m /home/xxx/my_dump_path/20000124003856/0 +# 转换结果打印 +...... +╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +# 转换前的dump文件 +│ SrcFile: /home/xxx/my_dump_path/20000124003856/0/272/TransData.trans_TransData_22.112.21.948645536672764 │ +# 转换后的npy文件 +│ - TransData.trans_TransData_22.112.21.948645536672764.output.0.npy │ +│ - TransData.trans_TransData_22.112.21.948645536672764.input.0.npy │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +...... +[INFO] The comparison result have been written to "./parse_data/acl_batch_convert". +``` + +输出结果: + +原dump数据目录: + +```bash +├── /home/xxx/my_dump_path/20000124003856/0/ +│ ├── 272 +│ │ ├── {op_type}.{op_name}.{task_id}.{stream_id}.{timestamp} +│ │ ... +│ ├── 512 +│ ... +``` + +转换后: + +```bash +├── ./parse_data/acl_batch_convert/{timestamp} +│ ├── {op_name1} +│ │ ├── {timestamp1} +│ │ | ├── {op_type}.{op_name}.{task_id}.{stream_id}.{timestamp}.{input/output}.{参数序号}.npy +│ │ | │ ... +│ │ ├── {timestamp2} +│ │ | ... +│ ├── {op_name2} +│ ├── ... +``` + +### ACL层级算子数据比对 + +本功能主要用于比对前后两次NPU ACL层级dump数据的一致性。 + +本功能支持批量比对,若需要进行批量比对,需要先将两份待比对的NPU ACL层级dump数据进行“**ACL层级算子数据批量转换**”,可以使两份数据更好的匹配;若直接进行dump数据的比对,建议只比对单个dump数据文件。 + +输入以下比对命令进行数据比对。 + +```bash +vc -m my_dump_path -g golden_dump_path [-out output_path] [-cmp_path msaccucmp_path] +``` + +| 参数名称 | 说明 | 是否必选 | +| --------- | ------------------------------------------------------------ | -------- | +| -m | 待比对ACL dump数据目录。如果比对单个算子,需要指定到ACL dump数据的model_id级目录;如果批量比对,则指定到cad转换后的timestamp级目录。 | 是 | +| -g | 标杆ACL dump数据目录。如果比对单个算子,需要指定到ACL dump数据的model_id级目录;如果批量比对,则指定到cad转换后的timestamp级目录。 | 是 | +| -out | 结果输出目录,须指定已存在的目录,默认为./parse_data/acl_batch_comapre。未指定时保存在默认路径下,比对结束后会打印log提示输出结果存放路径。 | 否 | +| -cmp_path | 指定msaccucmp路径,默认路径为:/usr/local/Ascend/ascend-toolkit/latest/tools/operator_cmp/compare/msaccucmp.py | 否 | + +输出结果:batch_compare_{timestamp}.csv文件。 + +**示例** + +```bash +# 传入待比对数据目录以及标杆数据目录 +Parse >>> vc -m ./my_dump_path -g ./golden_data_path +[INFO]Compare result is saved in : parse_data/acl_batch_comapre/batch_compare_1707271118.csv +``` + +### ACL算子数据的npy转换 + +依赖:CANN包中的msaccucmp工具,需要安装Ascend-CANN-toolkit,详见《[CANN 软件安装指南](https://gitee.com/link?target=https%3A%2F%2Fwww.hiascend.com%2Fdocument%2Fdetail%2Fzh%2Fcanncommercial%2F700%2Fenvdeployment%2Finstg%2Finstg_0001.html)》。 + +输入以下转换命令进行数据转换, 将ACL级别dump数据转为npy文件。 + +```bash +dc -n file_name/file_path [-f format] [-out output_path] +``` + +| 参数名称 | 说明 | 是否必选 | +| --------- | ------------------------------------------------------------ | -------- | +| -n | 需转换的dump数据文件或dump数据文件目录。 | 是 | +| -f | 开启format转换,指定该参数时需要配置format格式。当前内置的Format转换支持如下类型:
FRACTAL_NZ转换NCHW
FRACTAL_NZ转换成NHWC
FRACTAL_NZ转换ND
HWCN转换FRACTAL_Z
HWCN转换成NCHW
HWCN转换成NHWC
NC1HWC0转换成HWCN
NC1HWC0转换成NCHW
NC1HWC0转换成NHWC
NCHW转换成FRACTAL_Z
NCHW转换成NHWC
NHWC转换成FRACTAL_Z
NHWC转换成HWCN
NHWC转换成NCHW
NDC1HWC0转换成NCDHW | 否 | +| -out | 结果输出目录。 | 否 | +| -cmp_path | 指定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。默认在npy文件路径下将该数据保存为txt文件。 + +**示例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 356b995facd2c292719431526c08411dc486d9dc Mon Sep 17 00:00:00 2001 From: cai-weiwei1989 <734267852@qq.com> Date: Wed, 19 Jun 2024 17:43:20 +0800 Subject: [PATCH 03/13] =?UTF-8?q?[ptdbg]=E8=B5=84=E6=96=99=E8=A1=A5?= =?UTF-8?q?=E5=85=85=E6=AF=94=E5=AF=B9=E7=BB=93=E6=9E=9C=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E9=AB=98=E4=BA=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...50\203\275\350\257\264\346\230\216_v6.0.md" | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 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_v6.0.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_v6.0.md" index 34761e796c..80bdc64ff3 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_v6.0.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_v6.0.md" @@ -1865,7 +1865,8 @@ pkl文件比对同样生成`compare_result_{timestamp}.xlsx`和`advisor_{timesta ![compare_result_pkl](./img/compare_result_pkl.png) 上图是对pkl文件中NPU及标杆API的统计信息进行比对,判断可能存在精度问题的API,文件中记录NPU及标杆API的基本信息和统计信息,其中需要关注Result列,包含结果:Waring(NPU与标杆统计信息的比对中存在相对误差大于0.5,则需要重点检查该API);为空(相对误差小于等于0.5,可以不需要重点关注,但不代表不存在精度问题);Nan(表示统计信息数据没有匹配上)。 - + 同时,需要关注高亮的API,存在红色或黄色高亮的API被认为可能存在问题,具体高亮准则参见“**比对结果分析 > 异常信息识别**”章节。 + - configure_hook配置summary_mode=md5: ![compare_result_pkl_md5.png](./img/compare_result_pkl_md5.png.png) @@ -2026,10 +2027,19 @@ PyTorch精度比对是以CPU或GPU的计算结果为标杆,通过计算精度 **异常信息识别** -精度比对结果`compare_result_{timestamp}.xlsx`文件中对于存在异常信息的API会进行着色处理: +精度比对结果`compare_result_{timestamp}.xlsx`文件中对于存在异常信息的API会进行高亮处理: + +- 红色可能出现的情况有: + - NPU max或NPU min信息中存在nan/inf + - Max diff存在1e+10的值 + - 统计数据中Max diff除以Bench max > 0.5 + - 真实数据中One Thousandth Err Ratio的input > 0.9同时output < 0.6 -- 红色:代表数据中存在NPU max或NPU min信息存在nan/inf、Max diff存在1e+10、Max diff或Bench max > 0.5、One Thousandth Err Ratio的input > 0.9或output < 0.6等情况。 -- 黄色:代表数据中存在Max diff存在log10(output) - log10(input) >= 1(即输出与输入存在数量级差)、Max diff或Bench max的input > 0.1或input < 0.01、One Thousandth Err Ratio的output - input > 0.1、Cosine的output - input > 0.1等情况。 +- 黄色可能出现的情况有: + - Max diff的输出与输入存在数量级差 + - 统计数据Max difff除以Bench max的input > 0.1同时input < 0.01 + - 真实数据One Thousandth Err Ratio的output - input > 0.1 + - 真实数据Cosine的output - input > 0.1 ## ptdbg_ascend.parse数据解析功能 -- Gitee From 9bf8020ab66d435454ddc31b10d015bfdae1bc14 Mon Sep 17 00:00:00 2001 From: cai-weiwei1989 <734267852@qq.com> Date: Fri, 21 Jun 2024 15:28:31 +0800 Subject: [PATCH 04/13] =?UTF-8?q?[atat]PrecisionDebugger=E4=BC=A0=E5=85=A5?= =?UTF-8?q?=E5=8F=82=E6=95=B0model/step/level=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- debug/accuracy_tools/atat/config/README.md | 6 +-- debug/accuracy_tools/atat/pytorch/doc/dump.md | 53 ++++++++++++++++--- ...0\203\275\350\257\264\346\230\216_v6.0.md" | 4 +- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/debug/accuracy_tools/atat/config/README.md b/debug/accuracy_tools/atat/config/README.md index 546cbbde18..0723e44cd2 100644 --- a/debug/accuracy_tools/atat/config/README.md +++ b/debug/accuracy_tools/atat/config/README.md @@ -1,6 +1,6 @@ # 配置文件说明 -当前配置文件主要为PrecisionDebugger接口执行dump或无标杆比对操作时,调用的配置,当PrecisionDebugger接口未指定该配置文件时,使用该文件的默认配置。配置文件详见[config.json](./config.json)。 +当前配置文件主要为PrecisionDebugger接口执行dump或无标杆比对操作时调用的配置,当PrecisionDebugger接口未指定该配置文件时,使用该文件的默认配置。配置文件详见[config.json](./config.json)。 ## 参数说明 @@ -11,8 +11,8 @@ | task | dump的任务类型,str类型。可取值"free_benchmark"(无标杆比对)、"statistics"(仅dump API统计信息,默认值)、"tensor"(dump API统计信息和完全复刻整网的API运行情况的真实数据)、"overflow_check"(溢出检测)。配置示例:"task": "tensor"。根据task参数取值的不同,可以配置不同场景参数,详见:“**task配置为free_benchmark**”,“**task配置为statistics**”,“**task配置为tensor**”,“**task配置为overflow_check**”。 | 否 | | dump_path | 设置dump数据目录路径,str类型。配置示例:"dump_path": "./dump_path"。 | 是 | | rank | 指定对某张卡上的数据进行dump,list[int]类型,默认未配置(表示dump所有卡的数据),须根据实际卡的Rank ID配置。应配置为大于等于0的整数,且须根据实际卡的Rank ID配置,若所配置的值大于实际训练所运行的卡的Rank ID,则dump数据为空,比如当前环境Rank ID为0到7,实际训练运行0到3卡,此时若配置Rank ID为4或不存在的10等其他值,此时dump数据为空。配置示例:"rank": [1]。 | 否 | -| step | 指定dump某个step的数据,list[int]类型,默认未配置,表示dump所有step数据。dump特定step时,须指定为训练脚本中存在的step。step为list格式,可配置逐个step,例如:"step": [0,1,2]。 | 否 | -| level | dump级别,str类型,根据不同级别dump不同数据。可取值"L0"(dump module模块级精度数据,仅PyTorch场景支持,使用背景详见“**模块级精度数据dump说明**”)、"L1"(dump API级精度数据,默认值)、"L2"(dump kernel级精度数据,仅MindSpore场景支持)、"mix"(dump module模块级和API级精度数据)。配置示例:"level": "L1"。 | 否 | +| step | 指定dump某个step的数据,list[int]类型。默认未配置,表示dump所有step数据。dump特定step时,须指定为训练脚本中存在的step。step为list格式,可配置逐个step,例如:"step": [0,1,2]。 | 否 | +| level | dump级别,str类型,根据不同级别dump不同数据。可取值"L0"(dump module模块级精度数据,仅PyTorch场景支持,使用背景详见“**模块级精度数据dump说明**”)、"L1"(dump API级精度数据,默认值)、"L2"(dump kernel级精度数据)、"mix"(dump module模块级和API级精度数据,即"L0"+"L1")。配置示例:"level": "L1"。 | 否 | | seed | 随机种子数,int类型,默认值为:1234。通过固定随机数保证模型的输入或输出一致,可固定的随机数详见“**固定随机数范围**”。配置示例:"seed": 1234。 | 否 | | is_deterministic | 确定性计算模式,bool类型。可取值true(开启)或false(关闭),默认关闭。配置示例:"is_deterministic": true。
即使在相同的硬件和输入下,API多次执行的结果也可能不同,开启确定性计算是为了保证在相同的硬件和输入下,API多次执行的结果相同。
确定性计算会导致API执行性能降低,建议在发现模型多次执行结果不同的情况下开启。
rnn类算子、ReduceSum、ReduceMean等算子可能与确定性计算存在冲突,若开启确定性计算后多次执行的结果不相同,则考虑存在这些算子。 | 否 | | enable_dataloader | 自动控制开关,bool类型。可取值true(开启)或false(关闭),默认为false。配置为True后自动识别step参数指定的迭代,并在该迭代执行完成后退出训练,此时start、stop和step函数可不配置,开启该开关要求训练脚本是通过torch.utils.data.dataloader方式加载数据。仅支持PyTorch单卡训练使用,分布式训练场景下存在数据dump不全问题,**下个版本即将废弃该功能**。 | 否 | diff --git a/debug/accuracy_tools/atat/pytorch/doc/dump.md b/debug/accuracy_tools/atat/pytorch/doc/dump.md index 26cbcc7d73..44a4d09341 100644 --- a/debug/accuracy_tools/atat/pytorch/doc/dump.md +++ b/debug/accuracy_tools/atat/pytorch/doc/dump.md @@ -17,10 +17,10 @@ atat工具主要通过在训练脚本内添加dump接口并启动训练的方式 **原型** ```Python -PrecisionDebugger(config_path=None, task=None, dump_path=None, level=None) +PrecisionDebugger(config_path=None, task=None, dump_path=None, level=None, model=None, step=None) ``` -说明:上述参数除config_path外,其他参数均在[config.json](../../config)文件中可配,此处的参数优先级高于config.json文件中的配置,而config.json文件可以配置更多参数,若需要进行更多场景的精度数据dump,建议配置[config.json](../../config)文件。 +说明:上述参数除config_path和model外,其他参数均在[config.json](../../config)文件中可配,此处的参数优先级高于config.json文件中的配置,而config.json文件可以配置更多参数,若需要进行更多场景的精度数据dump,建议配置[config.json](../../config)文件。 **参数说明** @@ -29,7 +29,48 @@ PrecisionDebugger(config_path=None, task=None, dump_path=None, level=None) | config_path | 指定dump配置文件路径,String类型。参数示例:"./config.json"。未配置该路径时,默认使用../../config目录下的config.json文件的默认配置。 | 否 | | task | dump的任务类型,String类型。可取值"statistics"(仅dump API统计信息)、"tensor"(dump API统计信息和完全复刻整网的API运行情况的真实数据)、"overflow_check"(溢出检测),默认未配置,取"statistics",参数示例:task="tensor"。 | 否 | | dump_path | 设置dump数据目录路径,String类型。参数示例:dump_path="./dump_path"。 | 是 | -| level | dump级别,根据不同级别dump不同数据,String类型。可取值:
"L0":dump module模块级精度数据,仅PyTorch场景支持”。
"L1":dump API级精度数据,默认值。
"L2":dump kernel级精度数据,仅MindSpore场景支持。
"mix":dump module模块级和API级精度数据。
配置示例:level="L1"。 | 否 | +| level | dump级别,根据不同级别dump不同数据,String类型。可取值:
"L0":dump module模块级精度数据,仅PyTorch场景支持”。
"L1":dump API级精度数据,默认值。
"L2":dump kernel级精度数据。
"mix":dump module模块级和API级精度数据。
配置示例:level="L1"。 | 否 | +| model | 指定具体的torch.nn.Module,默认未配置,level配置为"L0"或"mix"时必须配置该参数。配置示例参见“**model配置代码示例**”。 | 否 | +| step | 指定dump某个step的数据,list[int]类型。默认未配置,表示dump所有step数据。dump特定step时,须指定为训练脚本中存在的step。step为list格式,可配置逐个step,例如:step=[0,1,2]。 | 否 | + +#### model配置代码示例 + +示例中定义了一个nn.Module类型的简单网络,在进行数据dump时使用原型函数PrecisionDebugger并传入config_path参数和model参数,其中model参数传入数据的类型为torch.nn.Module类型或torch.nn.Module子类型。 + +```python +#根据需要import包 +import os +import torch +import torch.nn as nn +import torch_npu +import torch.nn.functional as F +from atat.pytorch import PrecisionDebugger + +torch.npu.set_device("npu:0") +#定义一个简单的网络 +class ModuleOP(nn.Module) +def __init__(self) -> None: + super().__init__() + self.linear_1 = nn.Linear(in_features=8,out_features=4) + self.linear_2 = nn.Linear(in_features=4,out_features=2) +def forward(self,x): + x1 = self.linear_1(x) + x2 = self.linear_2(x1) + r1 = F.relu(x2) + return r1 + +if __name__ == "__main__" +module = ModuleOP() + +#注册工具 +debugger = PrecisionDebugger('./config.json',model=module) +debugger.start() +x = torch.randn(10,8) +out = module(x) +loss = out.sum() +loss.backward() +debugger.stop() +``` ### start函数 @@ -42,7 +83,7 @@ PrecisionDebugger(config_path=None, task=None, dump_path=None, level=None) **原型** ```Python -debugger.start(model) +debugger.start() ``` 该函数为类函数,可以使用debugger.start()也可以使用PrecisionDebugger.start()。 @@ -88,12 +129,12 @@ debugger = PrecisionDebugger(config_path="./config.json", dump_path="./dump_path # 模型初始化 # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() -debugger.start(model) +debugger.start() # 需要dump的代码片段1 debugger.stop() -debugger.start(model) +debugger.start() # 需要dump的代码片段2 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_v6.0.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_v6.0.md" index 80bdc64ff3..6a014a1e01 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_v6.0.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_v6.0.md" @@ -2031,13 +2031,13 @@ PyTorch精度比对是以CPU或GPU的计算结果为标杆,通过计算精度 - 红色可能出现的情况有: - NPU max或NPU min信息中存在nan/inf - - Max diff存在1e+10的值 + - Max diff存在大于1e+10的值 - 统计数据中Max diff除以Bench max > 0.5 - 真实数据中One Thousandth Err Ratio的input > 0.9同时output < 0.6 - 黄色可能出现的情况有: - Max diff的输出与输入存在数量级差 - - 统计数据Max difff除以Bench max的input > 0.1同时input < 0.01 + - 统计数据Max diff除以Bench max的input > 0.1同时input < 0.01 - 真实数据One Thousandth Err Ratio的output - input > 0.1 - 真实数据Cosine的output - input > 0.1 -- Gitee From 7f4906644455fa5a21fffd117ec63f2dc419f977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=B3=BD=E4=BB=81?= <519876982@qq.com> Date: Mon, 24 Jun 2024 07:37:40 +0000 Subject: [PATCH 05/13] add debug/OWNERS. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 陈泽仁 <519876982@qq.com> --- debug/OWNERS | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 debug/OWNERS diff --git a/debug/OWNERS b/debug/OWNERS new file mode 100644 index 0000000000..822424569c --- /dev/null +++ b/debug/OWNERS @@ -0,0 +1,10 @@ +options: + no_parent_owners: true +approvers: +- wangchao285 +- kun_8 +- binghamhuang +reviewers: +- lv-kaimeng +- litian_drinksnow +- wangchao285 -- Gitee From bc09b42289e3aa8ef26298699c75617a91da9e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=B3=BD=E4=BB=81?= <519876982@qq.com> Date: Mon, 24 Jun 2024 08:13:08 +0000 Subject: [PATCH 06/13] update debug/OWNERS. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 陈泽仁 <519876982@qq.com> --- debug/OWNERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/debug/OWNERS b/debug/OWNERS index 822424569c..09121722c9 100644 --- a/debug/OWNERS +++ b/debug/OWNERS @@ -4,7 +4,8 @@ approvers: - wangchao285 - kun_8 - binghamhuang +- brightlyking reviewers: - lv-kaimeng - litian_drinksnow -- wangchao285 +- binghamhuang -- Gitee From 0f9d6977a3fa235646710127d3214fe08312e18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=B3=BD=E4=BB=81?= <519876982@qq.com> Date: Mon, 24 Jun 2024 11:54:34 +0000 Subject: [PATCH 07/13] add profiler/OWNERS. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 陈泽仁 <519876982@qq.com> --- profiler/OWNERS | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 profiler/OWNERS diff --git a/profiler/OWNERS b/profiler/OWNERS new file mode 100644 index 0000000000..75b2110f58 --- /dev/null +++ b/profiler/OWNERS @@ -0,0 +1,11 @@ +options: + no_parent_owners: true +approvers: +- xhahn +- aerfaliang +- chenhao_1209 +- feng123www +reviewers: +- sunboquan +- stby +- Seanesmhxocism \ No newline at end of file -- Gitee From c05fead43abd98e3dd7f020f7d03622943db2973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=B3=BD=E4=BB=81?= <519876982@qq.com> Date: Mon, 24 Jun 2024 11:55:47 +0000 Subject: [PATCH 08/13] update profiler/OWNERS. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 陈泽仁 <519876982@qq.com> --- profiler/OWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profiler/OWNERS b/profiler/OWNERS index 75b2110f58..0c09fb8ce4 100644 --- a/profiler/OWNERS +++ b/profiler/OWNERS @@ -8,4 +8,4 @@ approvers: reviewers: - sunboquan - stby -- Seanesmhxocism \ No newline at end of file +- Seanesmhxocism -- Gitee From 5663ed29ebf1a0b19cdf33a4a796442ba01b05c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=B3=BD=E4=BB=81?= <519876982@qq.com> Date: Mon, 24 Jun 2024 12:28:41 +0000 Subject: [PATCH 09/13] add plugins/tensorboard-plugins/ OWNERS. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 陈泽仁 <519876982@qq.com> --- plugins/tensorboard-plugins/ OWNERS | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 plugins/tensorboard-plugins/ OWNERS diff --git a/plugins/tensorboard-plugins/ OWNERS b/plugins/tensorboard-plugins/ OWNERS new file mode 100644 index 0000000000..05f513ee68 --- /dev/null +++ b/plugins/tensorboard-plugins/ OWNERS @@ -0,0 +1,10 @@ +options: + no_parent_owners: true +approvers: +- wo-wenjie +- ly-qianxiao +- leo920320 +reviewers: +- wo-wenjie +- ly-qianxiao +- leo920320 -- Gitee From 27c2a21e196cd4a8e14b89ba95e6a1b11c398108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=B3=BD=E4=BB=81?= <519876982@qq.com> Date: Mon, 24 Jun 2024 12:28:55 +0000 Subject: [PATCH 10/13] update plugins/tensorboard-plugins/ OWNERS. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 陈泽仁 <519876982@qq.com> --- plugins/tensorboard-plugins/ OWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/tensorboard-plugins/ OWNERS b/plugins/tensorboard-plugins/ OWNERS index 05f513ee68..34c383beaf 100644 --- a/plugins/tensorboard-plugins/ OWNERS +++ b/plugins/tensorboard-plugins/ OWNERS @@ -3,7 +3,6 @@ options: approvers: - wo-wenjie - ly-qianxiao -- leo920320 reviewers: - wo-wenjie - ly-qianxiao -- Gitee From c2b8974eba8eed7867453ef1fd7efe2de42741de Mon Sep 17 00:00:00 2001 From: curry3 <485078529@qq.com> Date: Mon, 24 Jun 2024 21:01:17 +0800 Subject: [PATCH 11/13] =?UTF-8?q?=E3=80=90bugfix=E3=80=91=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0pt=E6=96=87=E4=BB=B6=E5=90=8D=E9=95=BF=E5=BA=A6?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../atat/pytorch/common/file_check.py | 9 +++-- .../atat/pytorch/functional/data_processor.py | 36 ++++++++++++------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/debug/accuracy_tools/atat/pytorch/common/file_check.py b/debug/accuracy_tools/atat/pytorch/common/file_check.py index 95285e017f..7acccf918d 100644 --- a/debug/accuracy_tools/atat/pytorch/common/file_check.py +++ b/debug/accuracy_tools/atat/pytorch/common/file_check.py @@ -17,7 +17,7 @@ import os import re -from .log import print_error_log +from .log import print_error_log, print_warn_log from .exceptions import FileCheckException from .utils import Const @@ -278,8 +278,7 @@ def create_directory(dir_path): def check_path_before_create(path): - if len(os.path.realpath(path)) > Const.DIRECTORY_LENGTH or len(os.path.basename(path)) > \ - Const.FILE_NAME_LENGTH: + if path_len_exceeds_limit(path): raise FileCheckException(FileCheckException.ILLEGAL_PATH_ERROR, 'The file path length exceeds limit.') if not re.match(Const.FILE_PATTERN, os.path.realpath(path)): @@ -296,3 +295,7 @@ def change_mode(path, mode): raise FileCheckException(FileCheckException.FILE_PERMISSION_ERROR, 'Failed to change {} authority. {}'.format(path, str(ex))) from ex + +def path_len_exceeds_limit(file_path): + return len(os.path.realpath(file_path)) > Const.DIRECTORY_LENGTH or \ + len(os.path.basename(file_path)) > Const.FILE_NAME_LENGTH diff --git a/debug/accuracy_tools/atat/pytorch/functional/data_processor.py b/debug/accuracy_tools/atat/pytorch/functional/data_processor.py index 340dcd1e3e..1ef1b79acb 100644 --- a/debug/accuracy_tools/atat/pytorch/functional/data_processor.py +++ b/debug/accuracy_tools/atat/pytorch/functional/data_processor.py @@ -7,6 +7,8 @@ from dataclasses import dataclass, asdict import torch_npu from typing import Tuple, List, Dict, Optional, Union from ..common.exceptions import MsaccException +from ..common.file_check import path_len_exceeds_limit, change_mode, FileCheckConst +from ..common.log import print_warn_log from ..common.utils import Const from ..common import recursive_apply_transform from ..functional. json_writer import DataWriter @@ -45,7 +47,7 @@ class ModuleForwardInputsOutputs: return (self.args, ) else: return self.args - + @property def output_tuple(self): if not isinstance(self.output, tuple): @@ -69,7 +71,7 @@ class ModuleBackwardInputsOutputs: return (self.grad_input, ) else: return self.grad_input - + @property def grad_output_tuple(self): if not isinstance(self.grad_output, tuple): @@ -101,10 +103,10 @@ class DataProcessor: # 需要对forward的output进行更改 self._return_forward_new_output = False self._forward_new_output = None - + def if_return_forward_new_output(self): return self._return_forward_new_output - + def get_forward_new_output(self): self._return_forward_new_output = False return self._forward_new_output @@ -156,7 +158,7 @@ class DataProcessor: def update_iter(self, current_iter): self.current_iter = current_iter - + def visit_and_clear_overflow_status(self, api_or_module_name): if self.current_api_or_module_name != api_or_module_name: self.current_api_or_module_name = api_or_module_name @@ -211,7 +213,7 @@ class DataProcessor: single_arg.update({"type": type(arg).__name__}) single_arg.update({"value": arg}) return single_arg - + def _analyze_torch_size(self, arg): single_arg = {} single_arg.update({"type": "torch.Size"}) @@ -286,7 +288,7 @@ class DataProcessor: def analyze_single_element(self, element, suffix_stack): if suffix_stack and suffix_stack[-1] in self.torch_object_key: return self.torch_object_key[suffix_stack[-1]](element) - + if isinstance(element, torch.Size): return self._analyze_torch_size(element) @@ -389,13 +391,17 @@ class DataProcessor: class FullTensorDataProcessor(DataProcessor): - + def _analyze_tensor(self, tensor, suffix): self.data_path = self.data_writer.dump_tensor_data_dir dump_data_name = (self.current_api_or_module_name + Const.SEP + self.api_data_category + Const.SEP + suffix + ".pt") file_path = os.path.join(self.data_writer.dump_tensor_data_dir, dump_data_name) - torch.save(tensor, file_path) + if not path_len_exceeds_limit(file_path): + torch.save(tensor, file_path) + change_mode(file_path, FileCheckConst.DATA_FILE_AUTHORITY) + else: + print_warn_log(f'The file path {file_path} length exceeds limit.') single_arg = super()._analyze_tensor(tensor, suffix) single_arg.update({"data_name": dump_data_name}) return single_arg @@ -415,7 +421,10 @@ class OverflowTensorDataProcessor(DataProcessor): dump_data_name = (self.current_api_or_module_name + Const.SEP + self.api_data_category + Const.SEP + suffix + ".pt") file_path = os.path.join(self.data_writer.dump_tensor_data_dir, dump_data_name) - self.cached_tensors_and_file_paths.update({file_path: tensor}) + if not path_len_exceeds_limit(file_path): + self.cached_tensors_and_file_paths.update({file_path: tensor}) + else: + print_warn_log(f'The file path {file_path} length exceeds limit.') single_arg = super()._analyze_tensor(tensor, suffix) single_arg.update({"data_name": dump_data_name}) return single_arg @@ -438,13 +447,14 @@ class OverflowTensorDataProcessor(DataProcessor): if self.has_overflow: for file_path, tensor in self.cached_tensors_and_file_paths.items(): torch.save(tensor, file_path) + change_mode(file_path, FileCheckConst.DATA_FILE_AUTHORITY) self.inc_and_check_overflow_times() self.cached_tensors_and_file_paths = {} def inc_and_check_overflow_times(self): self.real_overflow_dump_times += 1 if self.overflow_nums == -1: - return + return if self.real_overflow_dump_times >= self.overflow_nums: raise MsaccException(MsaccException.OVERFLOW_NUMS_ERROR, str(self.real_overflow_dump_times)) @@ -455,7 +465,7 @@ class FreeBenchmarkDataProcessor(DataProcessor): def __init__(self, config, data_writer): super().__init__(config, data_writer) self.checker = FreeBenchmarkCheck(config=config) - + def update_iter(self, current_iter): self.current_iter = current_iter self.checker.update_iter(current_iter) @@ -495,7 +505,7 @@ class FreeBenchmarkDataProcessor(DataProcessor): def analyze_backward(self, name, module, module_input_output: ModuleBackwardInputsOutputs): self.checker.backward(name, module, module_input_output.grad_output) return None - + def overflow_debug_mode_enable(): -- Gitee From 0689ccfef971363fe2c108b5c23a6ad3ad66656a Mon Sep 17 00:00:00 2001 From: wangqingcai Date: Tue, 25 Jun 2024 19:52:41 +0800 Subject: [PATCH 12/13] bugfix: remove dropout if dump --- debug/accuracy_tools/atat/pytorch/service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug/accuracy_tools/atat/pytorch/service.py b/debug/accuracy_tools/atat/pytorch/service.py index 32d5c7b48e..76f99e8f38 100644 --- a/debug/accuracy_tools/atat/pytorch/service.py +++ b/debug/accuracy_tools/atat/pytorch/service.py @@ -176,6 +176,6 @@ class Service: api_register.initialize_hook(functools.partial(self.build_hook, BaseScope.Module_Type_API)) api_register.api_modularity() - if "acc_cmp_dump" in hook_name: + if Const.STATISTICS in hook_name or Const.TENSOR in hook_name: remove_dropout() -- Gitee From 8f6ae03e18444805e67936ce8dd83598e9fe26c2 Mon Sep 17 00:00:00 2001 From: wangqingcai Date: Tue, 25 Jun 2024 20:42:42 +0800 Subject: [PATCH 13/13] add enuma value --- debug/accuracy_tools/atat/pytorch/common/utils.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/debug/accuracy_tools/atat/pytorch/common/utils.py b/debug/accuracy_tools/atat/pytorch/common/utils.py index 93d824df5b..8aede6b9af 100644 --- a/debug/accuracy_tools/atat/pytorch/common/utils.py +++ b/debug/accuracy_tools/atat/pytorch/common/utils.py @@ -184,4 +184,11 @@ class Const: MAX_SEED_VALUE = 2**32 - 1 INPLACE_LIST = ["broadcast", "all_reduce", "reduce", "all_gather", "gather", "scatter", "reduce_scatter", - "_reduce_scatter_base", "_all_gather_base", "all_to_all_single"] \ No newline at end of file + "_reduce_scatter_base", "_all_gather_base", "all_to_all_single"] + + TASK_LIST = ["tensor", "statistics", "overflow_check", "free_benchmark"] + LEVEL_LIST = ["L0", "L1", "L2", "mix"] + STATISTICS = "statistics" + TENSOR = "tensor" + OVERFLOW_CHECK = "overflow_check" + FREE_BENCHMARK = "free_benchmark" \ No newline at end of file -- Gitee