diff --git a/debug/accuracy_tools/ptdbg_ascend/CMakeLists.txt b/debug/accuracy_tools/ptdbg_ascend/CMakeLists.txt index f4b3ccd3a53136e229050b09ee57c1fe1c218f6c..b517cbbd1e00e06b97de5543ecc190129a8ed57a 100644 --- a/debug/accuracy_tools/ptdbg_ascend/CMakeLists.txt +++ b/debug/accuracy_tools/ptdbg_ascend/CMakeLists.txt @@ -16,4 +16,4 @@ add_custom_target(ptdbg_ascend ALL VERBATIM ) -install(CODE "execute_process(COMMAND ${PYTHON_BIN_PATH} -m pip install ${CMAKE_BINARY_DIR}/ptdbg_ascend/dist/ptdbg_ascend-3.2-py3-none-any.whl --upgrade)") +install(CODE "execute_process(COMMAND ${PYTHON_BIN_PATH} -m pip install ${CMAKE_BINARY_DIR}/ptdbg_ascend/dist/ptdbg_ascend-3.4-py3-none-any.whl --upgrade)") diff --git a/debug/accuracy_tools/ptdbg_ascend/README.md b/debug/accuracy_tools/ptdbg_ascend/README.md index 729c54f8ef5a48686860ccf2d375103ace1ba4d7..658dbcff3a70370b3b0a04fbb7ed1203b0ee6c09 100644 --- a/debug/accuracy_tools/ptdbg_ascend/README.md +++ b/debug/accuracy_tools/ptdbg_ascend/README.md @@ -10,10 +10,12 @@ | ptdbg_ascend版本 | 发布日期 | 支持PyTorch版本 | 下载链接 | 参考指南 | 校验码 | | ---------------- | --------- | -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | + | 3.4 | 2023-9-15 | 1.8.1/1.11.0/2.0/2.1 | [ptdbg_ascend-3.4-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/3.0/ptdbg_ascend-3.4-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v3.4](doc/ptdbg_ascend精度工具功能说明_v3.4.md) | 2e39cd0606070c48026c5d70921c379021a29bcc0a3b709716e0a041faab9797 | + | 3.3 | 2023-9-7 | 1.8.1/1.11.0/2.0/2.1 | [ptdbg_ascend-3.3-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/3.0/ptdbg_ascend-3.3-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v3.3](doc/ptdbg_ascend精度工具功能说明_v3.3.md) | 7ac4f83c9c9cacc5b5f255076ae503758c3766de7b8e13f169a81b66644a4914 | | 3.2 | 2023-8-17 | 1.8.1/1.11.0/2.0/2.1 | [ptdbg_ascend-3.2-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/3.0/ptdbg_ascend-3.2-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v3.2](doc/ptdbg_ascend精度工具功能说明_v3.2.md) | 0116f66c7c893fc171bfa86e12ecfbf9cd062aedd176a0e67befb880b995f472 | | 3.1 | 2023-8-02 | 1.8.1/1.11.0/2.0 | [ptdbg_ascend-3.1-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/3.0/ptdbg_ascend-3.1-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v3.1](doc/ptdbg_ascend精度工具功能说明_v3.1.md) | ef0dd5f96faf3576466545f082383eece409f25642a9bc4d0efc944969c1445a | | 2.0 | 2023-7-07 | 1.8.1/1.11.0/2.0 | [ptdbg_ascend-2.0-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/2.0/ptdbg_ascend-2.0-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v2.0](doc/ptdbg_ascend精度工具功能说明_v2.0.md) | 85e046f133f0f40ed660337ce8207249b1dac47ac668910625bea49809f31d66 | - | 1.0 | 2023-3-30 | 1.8.1/1.11.0 | [ptdbg_ascend-1.0-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/1.0/ptdbg_ascend-1.0-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v1.0](https://gitee.com/ascend/tools/blob/master/ptdbg_ascend/doc/ptdbg_ascend%E7%B2%BE%E5%BA%A6%E5%B7%A5%E5%85%B7%E5%8A%9F%E8%83%BD%E8%AF%B4%E6%98%8E_v1.0.md) | 0559e12ba7accf80d182f227698163ee0de88bf86b1e9cd9f33b16fdead14759 | + | 1.0 | 2023-3-30 | 1.8.1/1.11.0 | [ptdbg_ascend-1.0-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/1.0/ptdbg_ascend-1.0-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v1.0](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend%E7%B2%BE%E5%BA%A6%E5%B7%A5%E5%85%B7%E5%8A%9F%E8%83%BD%E8%AF%B4%E6%98%8E_v1.0.md) | 0559e12ba7accf80d182f227698163ee0de88bf86b1e9cd9f33b16fdead14759 | 2. whl包校验。 @@ -99,7 +101,7 @@ ptdbg_ascend为PyTorch精度工具,用来进行PyTorch整网API粒度的数据 ### 环境准备 - 通过pip安装环境依赖wheel、numpy、pandas(1.3.5及以上版本)和pyyaml。 -- ptdbg_ascend与PyTorch有严格的版本配套关系,使用工具前,您需要确保已经正确安装了PyTorch v1.8.1、PyTorch v1.11.0或PyTorch v2.0.0版本: +- ptdbg_ascend与PyTorch有严格的版本配套关系,使用工具前,您需要确保已经正确安装了PyTorch v1.8.1、PyTorch v1.11.0、PyTorch v2.0.0或PyTorch v2.1.0版本: - CPU或GPU环境:请至[PyTorch官网](https://www.pytorch.org)下载并安装。 - NPU环境:请参见《[CANN软件安装指南](https://www.hiascend.com/document/detail/zh/canncommercial/63RC1/envdeployment/instg/instg_000002.html)》“安装开发环境 > 在昇腾设备上安装 > 安装深度学习框架 > 安装PyTorch”章节进行安装。 @@ -115,12 +117,14 @@ ptdbg_ascend精度工具的安装方式包括:**下载whl包安装**和**源 请通过下表链接下载ptdbg_ascend精度工具whl包,推荐下载最新版本。 - | ptdbg_ascend版本 | 发布日期 | 支持PyTorch版本 | 下载链接 | 校验码 | 参考指南 | + | ptdbg_ascend版本 | 发布日期 | 支持PyTorch版本 | 下载链接 | 参考指南 | 校验码 | | ---------------- | --------- | -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | + | 3.4 | 2023-9-15 | 1.8.1/1.11.0/2.0/2.1 | [ptdbg_ascend-3.4-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/3.0/ptdbg_ascend-3.4-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v3.4](doc/ptdbg_ascend精度工具功能说明_v3.4.md) | 2e39cd0606070c48026c5d70921c379021a29bcc0a3b709716e0a041faab9797 | + | 3.3 | 2023-9-7 | 1.8.1/1.11.0/2.0/2.1 | [ptdbg_ascend-3.3-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/3.0/ptdbg_ascend-3.3-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v3.3](doc/ptdbg_ascend精度工具功能说明_v3.3.md) | 7ac4f83c9c9cacc5b5f255076ae503758c3766de7b8e13f169a81b66644a4914 | | 3.2 | 2023-8-17 | 1.8.1/1.11.0/2.0/2.1 | [ptdbg_ascend-3.2-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/3.0/ptdbg_ascend-3.2-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v3.2](doc/ptdbg_ascend精度工具功能说明_v3.2.md) | 0116f66c7c893fc171bfa86e12ecfbf9cd062aedd176a0e67befb880b995f472 | | 3.1 | 2023-8-02 | 1.8.1/1.11.0/2.0 | [ptdbg_ascend-3.1-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/3.0/ptdbg_ascend-3.1-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v3.1](doc/ptdbg_ascend精度工具功能说明_v3.1.md) | ef0dd5f96faf3576466545f082383eece409f25642a9bc4d0efc944969c1445a | | 2.0 | 2023-7-07 | 1.8.1/1.11.0/2.0 | [ptdbg_ascend-2.0-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/2.0/ptdbg_ascend-2.0-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v2.0](doc/ptdbg_ascend精度工具功能说明_v2.0.md) | 85e046f133f0f40ed660337ce8207249b1dac47ac668910625bea49809f31d66 | - | 1.0 | 2023-3-30 | 1.8.1/1.11.0 | [ptdbg_ascend-1.0-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/1.0/ptdbg_ascend-1.0-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v1.0](https://gitee.com/ascend/tools/blob/master/ptdbg_ascend/doc/ptdbg_ascend精度工具功能说明_v1.0.md) | 0559e12ba7accf80d182f227698163ee0de88bf86b1e9cd9f33b16fdead14759 | + | 1.0 | 2023-3-30 | 1.8.1/1.11.0 | [ptdbg_ascend-1.0-py3-none-any.whl](https://ptdbg.obs.myhuaweicloud.com/package/ptdbg_ascend/1.0/ptdbg_ascend-1.0-py3-none-any.whl) | [ptdbg_ascend精度工具功能说明_v1.0](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend精度工具功能说明_v1.0.md) | 0559e12ba7accf80d182f227698163ee0de88bf86b1e9cd9f33b16fdead14759 | 2. whl包校验。 @@ -174,7 +178,7 @@ ptdbg_ascend精度工具的安装方式包括:**下载whl包安装**和**源 2. 下载源码。 ```bash - git clone https://gitee.com/ascend/tools.git + git clone https://gitee.com/ascend/att.git ``` 3. 配置安装环境。 @@ -233,7 +237,7 @@ ptdbg_ascend精度工具的安装方式包括:**下载whl包安装**和**源 pip3 install ./ptdbg_ascend/dist/ptdbg_ascend-{version}-py3-none-any.whl --upgrade --force-reinstall ``` -完成ptdbg_ascend安装后,可以进行PyTorch精度数据的dump和、比对和溢出检测等操作,详细介绍请参见《[PyTorch精度工具使用指南](https://gitee.com/ascend/tools/tree/master/ptdbg_ascend/doc)》。 +完成ptdbg_ascend安装后,可以进行PyTorch精度数据的dump和、比对和溢出检测等操作,详细介绍请参见《[PyTorch精度工具使用指南](https://gitee.com/ascend/att/tree/master/debug/accuracy_tools/ptdbg_ascend/doc)》。 ## 贡献 diff --git a/debug/accuracy_tools/ptdbg_ascend/RELEASE.md b/debug/accuracy_tools/ptdbg_ascend/RELEASE.md index f37c0731e82732251f248d8a8e2e113cb60b2018..9aad36c69e235266fd8db8ee14ad5ad110433a82 100644 --- a/debug/accuracy_tools/ptdbg_ascend/RELEASE.md +++ b/debug/accuracy_tools/ptdbg_ascend/RELEASE.md @@ -1,4 +1,4 @@ -# Release 3.2 +# Release 3.4 This is the initial release of Pytorch precision compare tools which was designed by the researchers and engineers in Huawei Technologies Co.,Ltd. \ No newline at end of file diff --git a/debug/accuracy_tools/ptdbg_ascend/doc/FAQ.md b/debug/accuracy_tools/ptdbg_ascend/doc/FAQ.md index 4ec3b2d30b7cb3727ac935064bd4223058e08469..639f07c46022393db798fa630aa3e13816ced65f 100644 --- a/debug/accuracy_tools/ptdbg_ascend/doc/FAQ.md +++ b/debug/accuracy_tools/ptdbg_ascend/doc/FAQ.md @@ -1,5 +1,13 @@ ## FAQ +## 工具使用 +### 1.环境变量方式导入ptdbg_ascend +当需要使用export att/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/common的目录下,手动添加一个version.py,并加上以下版本号信息,其中‘3.4’为当前ptdbg_ascend的版本 +``` +__version__ = '3.4' +``` + +## 异常情况 ### 1. 单机多卡场景dump目录下只生成一个rank目录或pkl文件格式损坏 **故障现象** @@ -31,3 +39,62 @@ CANN软件版本较低导致不兼容。 **故障处理** 升级新版CANN软件版本。 +### 3. torch_npu._C._clear_overflow_npu() RuntimeError NPU error,error code is 107002 +如果运行溢出检测功能遇到这个报错,采取以下解决方法: +如果是单卡运行,添加如下代码,0是卡号,选择自己空闲的卡号。 + +``` +torch.npu.set_device('npu:0') +``` +如果多卡运行,请在代码中修改对应卡号,比如进程使用卡号为{rank}时可以添加如下代码: + +``` +torch.npu.set_device(f'npu:{rank}') +``` +如果运行精度比对功能遇到这个报错,尝试安装最新版本的ptdbg_ascend + +### 4. 运行compare.py时报错:json.decoder.JSONDecodeError: Extra data: line 1 column 37(char 36) + +遇到这种情况,先更新工具版本为最新版本,再重新运行训练代码dump数据,再用新的dump数据进行精度比对,如果最新版本未能解决问题,请联系ptdbg工具开发人员。 + +### 5. AssertionError: assert set(WrapTensorOps) <= set(_tensor_ops) + +遇到这种情况,先检查安装的torch版本,建议先更新工具版本为2.2以上,版本2.2的工具支持torch1.8、1.11和2.0 + +### 6. dump得到的VF_lstm_99_forward_input.1.0.npy、VF_lstm_99_forward_input.1.1.npy类似的数据是否正常? + +带1.0/1.1/1.2后缀的npy是正常现象,例如当输入数据为[[tensor1, tensor2, tensor3]]会生成这样的后缀 + +### 7. dump数据时,dump输出目录只得到了.npy文件,不生成pkl文件 + +- 检查set_dump_switch("ON"),set_dump_switch("OFF")是否都配置了; +- 如果都配置了,观察模型运行日志结尾是否打印“Dump switch is turned off”,如果没有,则表明代码没有执行到set_dump_switch("OFF"),请检查模型代码中是否有exit()操作。 + +### 8. 进行compare报错:The current file contains stack information, please turn on the stack_mode +在比对脚本中,设置stack_mode=True,例如: + +``` +from ptdbg_ascend import * +dump_result_param={ +"npu_pkl_path": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl", +"bench_pkl_path": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl", +"npu_dump_data_dir": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump", +"bench_dump_data_dir": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump", +"is_print_compare_log": True +} +compare(dump_result_param, "./output", stack_mode=True) +``` +### 9. dump指定反向API的ACL级别的数据报错:NameError:name 'torch_npu' is not defined + +- 如果是npu环境,请安装torch_npu; +- 如果是gpu环境,暂不支持dump指定API的ACL级别的数据 + +### 10. 配置dump_path后,使用工具报错:[ERROR]The file path /home/xxx/dump contains special characters + +- 请检查你设置的dump绝对路径是否包含特殊字符,确保路径名只包含大小写字母、数字、下划线、斜杠、点和短横线 +- 注意,如果你执行脚本的路径为/home/abc++/,你设置的dump_path="./dump",工具实际校验的路径为绝对路径/home/abc++/dump,++为特殊字符,会引发本条报错 + +### 11. 遇到报错'IsADirectoryError: [Errno 21] Is a directory: '/data/rank0/api_stack_xxx'' + +- 请检查register_hook是否写在了set_dump_path前面,register_hook必须在set_dump_path后调用 +- 请检查是否写了多个register_hook或者set_dump_path,如有,请保留一个register_hook或者set_dump_path 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_v3.1.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_v3.1.md" index 164b6b1e8de88aba988bea324e132db688038144..494102b72a25360f426d440a8924f01452cfdcf4 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_v3.1.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_v3.1.md" @@ -527,7 +527,7 @@ register_hook(model, hook, overflow_nums=overflow_nums, dump_mode=dump_mode, dum | overflow_nums | 控制溢出次数,表示第N次溢出时,停止训练,过程中检测到溢出API对应ACL数据均dump。参数示例:overflow_nums=3。配置overflow_check时可配置,默认不配置,即检测到1次溢出,训练停止。 | 否 | | dump_mode | 控制针对溢出API的dump模式。可取值"api"或"acl",配置acl时表示dump ACL级别的溢出数据,此时set_dump_path参数不生效,dump数据目录由dump_config的.json文件配置,参数示例:dump_mode="acl"。默认不配置,即dump API级别的溢出数据。 | 否 | | dump_config | acl dump的配置文件。dump_mode="acl"时,该参数必选;dump_mode="api"时,该参数不选。参数示例:dump_config='./dump.json'。 | 否 | -| rank | 控制dump数据保存的rank目录名称。参数示例:rank=1。默认不配置,即自动读取dump数据所属的卡并保存在该卡对应的rank目录下。目录结构参见“**dump数据存盘说明**”。
多卡情况下,可能出现工具识别rank出错,导致dump数据保存到错误的rank目录下,此时需要根据“**[rank_id获取方法](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/rank_id%E8%8E%B7%E5%8F%96%E6%96%B9%E6%B3%95.md)**”配置该参数,以获取正确的rank_id;工具可正确识别rank_id时无须配置该参数。 | 否 | +| rank | 控制dump数据保存的rank目录名称。参数示例:rank=1。默认不配置,即自动读取dump数据所属的卡并保存在该卡对应的rank目录下。目录结构参见“**dump数据存盘说明**”。
多卡情况下,可能出现工具识别rank出错,导致dump数据保存到错误的rank目录下,此时需要根据“**[rank_id获取方法](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/rank_id获取方法.md)**”配置该参数,以获取正确的rank_id;工具可正确识别rank_id时无须配置该参数。 | 否 | **函数示例** 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_v3.2.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_v3.2.md" index 7ee76e187f4d5084b25f039086d2c7b04adb4789..d47f44b2b8c76a6cff9080f70b0f7daee669409e 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_v3.2.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_v3.2.md" @@ -362,14 +362,19 @@ register_hook需要在set_dump_path之后调用,也需要在每个进程上被 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为正常溢出还是非正常溢出,从而帮助用户快速判断。 + 针对输入正常但输出存在溢出的API,会在训练执行目录下将溢出的API信息按照前向和反向分类,dump并保存为`forward_info_{pid}.json`和`backward_info_{pid}.json`,前向过程溢出的API可通过 [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 -backward ./backward_info_0.json + python run_overflow_check.py -forward ./forward_info_{pid}.json ``` + 反向过程溢出的API暂不支持这一功能。 + + 当重复执行溢出检测dump操作时,需要删除上一次dump目录下的溢出检测dump数据,否则将因重名而报错。 **注意事项** @@ -389,7 +394,7 @@ PrecisionDebugger模块包含dump和溢出检测功能的总体配置项。可 **原型** ```python -PrecisionDebugger(dump_path=None, hook_name=None, rank=None): +PrecisionDebugger(config=DebuggerConfig(dump_path=None, hook_name=None, rank=None)): ``` **参数说明** @@ -398,7 +403,7 @@ PrecisionDebugger(dump_path=None, hook_name=None, rank=None): | --------- | ------------------------------------------------------------ | -------- | | dump_path | 设置dump数据目录路径,参数示例:"./dump_path"。dump_path的父目录须为已存在目录。
默认在指定的dump_path路径下生成`ptdbg_dump_{version}`目录,并在该目录下生成`dump.pkl`文件以及`dump`数据文件保存目录。
当**configure_hook**函数配置了mode参数时,`dump.pkl`文件以及`dump`数据文件保存目录名称添加mode参数值为前缀,详情请参见“**dump数据存盘说明**”。 | 是 | | hook_name | dump模式,可取值dump和overflow_check,表示dump和溢出检测功能,二选一。 | 是 | -| rank | 指定对某张卡上的数据进行dump或溢出检测,默认未配置(表示dump所有卡的数据),须根据实际卡的Rank ID配置。 | 否 | +| rank | 指定对某张卡上的数据进行dump或溢出检测,默认未配置(表示dump所有卡的数据)。应配置为大于0的正整数,且须根据实际卡的Rank ID配置,若所配置的值大于实际训练所运行的卡的Rank ID,则dump数据为空,比如当前环境Rank ID为0~7,实际训练运行0~3卡,此时若配置Rank ID为4或不存在的10等其他值,此时dump数据为空。 | 否 | ### configure_hook函数(可选) @@ -518,7 +523,7 @@ configure_hook可配置多种dump模式,示例如下: debugger.configure_hook(mode="acl", acl_config="./dump.json") ``` - 该场景**PrecisionDebugger**模块的dump_path参数不生效,由acl_config中的dump.json文件配置溢出数据目录。 + 该场景会在原有数据基础上,额外在dump.json文件配置的dump_path目录下生成一份ACL算子数据,该数据可通过“**ptdbg_ascend.parse**”工具进行解析。 仅支持NPU环境。 @@ -560,7 +565,7 @@ debugger.stop() ```python from ptdbg_ascend import * - debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump") + debugger = PrecisionDebugger(config=DebuggerConfig(dump_path="./dump_path", hook_name="dump")) # 模型初始化 # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() @@ -575,7 +580,7 @@ debugger.stop() ```python from ptdbg_ascend import * - debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="overflow_check") + debugger = PrecisionDebugger(config=DebuggerConfig(dump_path="./dump_path", hook_name="overflow_check")) # 模型初始化 # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() @@ -784,7 +789,7 @@ register_hook(model, hook, overflow_nums=overflow_nums, dump_mode=dump_mode, dum register_hook(model, overflow_check, dump_mode='acl', dump_config='./dump.json') ``` - 该场景set_dump_path不生效,由dump_config中的dump.json文件配置溢出数据目录。 + 该场景会在原有数据基础上,额外在dump.json文件配置的dump_path目录下生成一份ACL算子数据,该数据可通过“**ptdbg_ascend.parse**”工具进行解析。 仅支持NPU环境。 diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/ptdbg_ascend\347\262\276\345\272\246\345\267\245\345\205\267\345\212\237\350\203\275\350\257\264\346\230\216_v3.3.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_v3.3.md" new file mode 100644 index 0000000000000000000000000000000000000000..6ae6c2a0e244f567523540a03ecc03f4367bc507 --- /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_v3.3.md" @@ -0,0 +1,1562 @@ +# **PyTorch精度工具使用指南** + +本文主要介绍PyTorch精度工具精度工具ptdbg_ascend的使用以及精度比对场景示例。 + +ptdbg_ascend工具的原理及安装请参见《[PyTorch精度工具](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/README.md)》。 + +## PyTorch精度比对总体流程 + +1. 准备CPU或GPU训练工程。 + +2. 在环境下安装ptdbg_ascend工具。 + +3. 在训练脚本内插入ptdbg_ascend工具dump接口。 + +4. 执行训练dump数据。 + +5. 将CPU或GPU训练工程迁移为NPU训练工程。 + + 请参见《[PyTorch模型迁移和训练指南](https://www.hiascend.com/document/detail/zh/canncommercial/63RC1/modeldevpt/ptmigr/ptmigr_0001.html)》。 + +6. 在NPU环境下安装ptdbg_ascend工具。 + +7. 在NPU训练脚本内插入ptdbg_ascend工具dump接口。 + +8. NPU环境下执行训练dump数据。 + +9. 创建并配置精度比对脚本,例如compare.py。 + +10. 执行CPU或GPU dump与NPU dump数据的精度比对。 + +11. 比对结果分析。 + +## 场景化示例 + +本章节主要介绍通过ptdbg_ascend工具进行精度比对和分析,主要使用“**CPU或GPU及NPU精度数据dump**”和“**CPU或GPU与NPU精度数据比对**”章节中介绍的ptdbg_ascend工具接口。 + +### 单卡场景精度比对 + +**精度分析建议** + +PyTorch训练场景的精度问题分析建议参考以下思路进行精度比对和比对结果分析: + +1. 整网比对:dump整网数据并进行精度比对,初步定位异常范围。 +2. 缩小范围:根据Accuracy Reached or Not找出不符合精度标准的API。 +3. 范围比对:对不符合精度标准的API重新dump。 +4. 分析原因并优化:分析API精度不符合标准的原因并进行优化调整。 +5. 整网比对:重新进行整网比对,判断优化后的API是否已符合精度标准以及是否出现新的精度问题。 +6. 重复1~5步,直到不存在精度问题为止。 + +**精度分析示例** + +1. dump整网数据。 + + 分别dump CPU或GPU以及NPU数据,在PyTorch训练脚本插入dump接口,示例代码如下(下面以NPU为例,CPU或GPU dump基本相同): + + ```python + from ptdbg_ascend import * + + # 在main函数开始前固定随机数 + seed_all() + + # 配置dump数据目录路径和名称 + set_dump_path("./npu_dump", dump_tag='all') + + # 注册dump回调函数 + register_hook(model, acc_cmp_dump) + + ... + + # 在第一个迭代开始的位置开启dump和堆栈模式,同时为保证数据完整性开启dump bool和整型的tensor以及浮点、bool和整型的标量 + set_dump_switch("ON", mode="api_stack", filter_switch="OFF") + + ... + + # 在第一个迭代结束的位置关闭dump + set_dump_switch("OFF") + ``` + +2. 比对整网数据。 + + 第1步中的NPU dump数据文件为npu_dump.pkl,假设NPU dump npy数据目录为npu_dump,GPU dump数据文件为gpu_dump.pkl,GPU dump npy数据目录为gpu_dump。 + + 创建并配置精度比对脚本,以创建compare.py为例,示例代码如下: + + ```python + from ptdbg_ascend import * + dump_result_param={ + "npu_pkl_path": "./npu_dump/all_v2.0/rank0/api_stack_dump.pkl", + "bench_pkl_path": "./gpu_dump/all_v2.0/rank0/api_stack_dump.pkl", + "npu_dump_data_dir": "./npu_dump/all_v2.0/rank0/api_stack_dump", + "bench_dump_data_dir": "./gpu_dump/all_v2.0/rank0/api_stack_dump", + "is_print_compare_log": True + } + compare(dump_result_param, "./output") + ``` + + 执行比对: + + ```bash + python3 compare.py + ``` + + 在output目录下生成结果文件,包括:`compare_result_{timestamp}.csv`和`advisor_{timestamp}.txt` + +3. 找出存在问题的API。 + + 1. 根据`advisor_{timestamp}.txt`或打屏信息的提示,可找到存在精度问题的算子(Suspect Nodes)和专家建议(Expert Advice) + + ![auto_analyze_log](img/auto_analyze_log.png) + + 2. 根据第2步结果文件`compare_result_{timestamp}.csv`中的Accuracy Reached or No字段显示为NO的API,针对该API执行后续比对操作,分析该API存在的精度问题。 + +4. (可选)提取指定API的堆栈信息和dump数据统计信息。 + + 通过parse接口可以清晰的显示特定API的堆栈信息和dump数据统计信息,结合堆栈信息分析代码中可能存在的精度问题。 + + 创建并配置提取脚本,以创建parse.py为例,示例代码如下: + + ```python + from ptdbg_ascend import * + + # 提取dump信息中第1次调用的API:Torch_batch_normal的堆栈信息及数据统计信息 + parse("./npu_dump/all_v2.0/rank0/api_stack_dump.pkl", "Torch_batch_normal_1_forward") + ``` + + 执行提取: + + ```bash + python3 parse.py + ``` + + + +5. (可选)指定API dump数据。 + + - dump指定前向API的ACL级别数据 + + ```python + from ptdbg_ascend import * + + # 固定随机数,开启确定性计算 + seed_all(mode=True) + set_dump_path("./dump_path", dump_tag='forward') + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + + # dump指定前向API的ACL级别数据、bool和整型的tensor以及浮点、bool和整型的标量 + set_dump_switch("ON", mode="acl", scope=["Tensor_permute_1_forward"], filter_switch="OFF") + + ... + + set_dump_switch("OFF") + ``` + + - dump指定反向API的ACL级别数据 + + ```python + from ptdbg_ascend import * + + # 固定随机数,开启确定性计算 + seed_all(mode=True) + set_dump_path("./dump_path", dump_tag='backward') + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + + # dump指定反向API的ACL级别数据、bool和整型的tensor以及浮点、bool和整型的标量 + set_dump_switch("ON", mode="acl", scope=["Functional_conv2d_1_backward"], filter_switch="OFF") + set_backward_input(["./npu_dump/all_v2.0/rank0/api_stack_dump/Functional_conv2d_1_backward_input.0.npy"]) + + ... + + set_dump_switch("OFF") + ``` + +6. (可选)重新比对。 + + 根据第4或5步的dump数据重新配置compare.py并执行比对,可以对单API模型进行问题复现。 + +**注意事项** + +* dump_mode="acl"场景下,会增加npu的内存消耗,请谨慎开启。 +* 部分API存在调用嵌套关系,比如functional.batch_norm实际调用torch.batch_norm,该场景会影响acl init初始化多次,导致功能异常。 + +### 多卡场景精度比对 + +精度工具支持多卡场景的精度比对,多卡场景的dump步骤与单卡场景完全一致,请参见“**单卡场景精度比对**”章节,不同的是多卡数据精度比对时需要使用“compare_distributed”函数进行比对。如下示例: + +说明:多机多卡场景需要每个设备单独执行比对操作。 + +假设NPU dump npy数据目录为npu_dump/dump_conv2d_v1.0,GPU dump npy数据目录为gpu_dump/dump_conv2d_v1.0。 + +1. 创建比对脚本,例如compare_distributed.py,拷贝如下代码。 + + ```python + from ptdbg_ascend import * + compare_distributed('./npu_dump/ptdbg_dump_v2.0', './gpu_dump/ptdbg_dump_v2.0', './output') + ``` + +2. 执行比对: + + ```bash + python3 compare_distributed.py + ``` + +两次运行须用相同数量的卡,传入`compare_distributed`的两个文件夹下须有相同个数的rank文件夹,且不包含其他无关文件,否则将无法比对。 + +**多卡set_dump_path注意事项** + +多卡一般为多进程,须保证每个进程都正确调用set_dump_path,或把set_dump_path插入到import语句后,如: + +```python +from ptdbg_ascend import * +seed_all() +set_dump_path('./dump_resnet') +``` + +如此可保证set_dump_path在每个进程都被调用。 + +**多卡register_hook注意事项** + +register_hook需要在set_dump_path之后调用,也需要在每个进程上被调用,建议在搬运模型数据到卡之后调用。识别方法如下: + +- 找到训练代码中遍历epoch的for循环或遍历数据集的for循环,把register_hook放到循环开始前即可。 +- 找到训练代码中调用DDP或者DistributedDataParallel的代码行,把register_hook放到该代码行所在的代码块之后。 +- 若代码中均无以上两种情况,需要保证register_hook在模型定义之后插入,并配置rank参数。rank参数获取rank_id请参见“**[rank_id获取方法](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/rank_id获取方法.md)**”。 + +### NPU vs NPU精度比对 + +对于NPU vs NPU场景,是针对同一模型,进行迭代(模型、API版本升级或设备硬件升级)时存在的精度下降问题,对比相同模型在迭代前后版本的API计算数值,进行问题定位。 + +一般情况下迭代涉及NPU自定义算子,因此,可以仅dump NPU自定义算子进行比对。比对精度问题分析请参见“**单卡场景精度比对**”章节。 + +工具当前支持dump NPU自定义算子如下: + +| 序号 | NPU自定义算子 | +| :--- | ----------------------------------- | +| 1 | torch_npu.one_ | +| 2 | torch_npu.npu_sort_v2 | +| 3 | torch_npu.npu_transpose | +| 4 | torch_npu.npu_broadcast | +| 5 | torch_npu.npu_dtype_cast | +| 6 | torch_npu.empty_with_format | +| 7 | torch_npu.npu_one_hot | +| 8 | torch_npu.npu_stride_add | +| 9 | torch_npu.npu_ps_roi_pooling | +| 10 | torch_npu.npu_roi_align | +| 11 | torch_npu.npu_nms_v4 | +| 12 | torch_npu.npu_iou | +| 13 | torch_npu.npu_nms_with_mask | +| 14 | torch_npu.npu_pad | +| 15 | torch_npu.npu_bounding_box_encode | +| 16 | torch_npu.npu_bounding_box_decode | +| 17 | torch_npu.npu_batch_nms | +| 18 | torch_npu.npu_slice | +| 19 | torch_npu._npu_dropout | +| 20 | torch_npu.npu_indexing | +| 21 | torch_npu.npu_ifmr | +| 22 | torch_npu.npu_max | +| 23 | torch_npu.npu_scatter | +| 24 | torch_npu.npu_layer_norm_eval | +| 25 | torch_npu.npu_alloc_float_status | +| 26 | torch_npu.npu_get_float_status | +| 27 | torch_npu.npu_clear_float_status | +| 28 | torch_npu.npu_confusion_transpose | +| 29 | torch_npu.npu_bmmV2 | +| 30 | torch_npu.fast_gelu | +| 31 | torch_npu.npu_sub_sample | +| 32 | torch_npu.npu_deformable_conv2d | +| 33 | torch_npu.npu_mish | +| 34 | torch_npu.npu_anchor_response_flags | +| 35 | torch_npu.npu_yolo_boxes_encode | +| 36 | torch_npu.npu_grid_assign_positive | +| 37 | torch_npu.npu_normalize_batch | +| 38 | torch_npu.npu_masked_fill_range | +| 39 | torch_npu.npu_linear | +| 40 | torch_npu.npu_bert_apply_adam | +| 41 | torch_npu.npu_giou | +| 42 | torch_npu.npu_ciou | +| 43 | torch_npu.npu_ciou_backward | +| 44 | torch_npu.npu_diou | +| 45 | torch_npu.npu_diou_backward | +| 46 | torch_npu.npu_sign_bits_pack | +| 47 | torch_npu.npu_sign_bits_unpack | + +### 通信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 | batch_isend_irecv | +| 9 | isend | +| 10 | irecv | +| 11 | scatter | +| 12 | reduce_scatter | + +### 溢出检测场景 + +溢出检测是针对NPU的PyTorch API,检测是否存在溢出的情况。当前仅支持识别aicore浮点溢出。 + +溢出检测原理:针对溢出阶段,开启acl dump模式,重新对溢出阶段执行,落盘数据。 + +建议按照如下步骤操作: + +1. 在NPU环境下安装ptdbg_ascend工具。 + +2. 在NPU训练脚本内插入ptdbg_ascend工具溢出检测接口。 + + - 示例1:全量溢出检测 + + ```python + from ptdbg_ascend import * + seed_all() + ... + # 设置检测到3次溢出后退出训练 + register_hook(model, overflow_check, overflow_nums=3) + + ... + ``` + + 多卡使用时各卡单独计算溢出次数。 + + - 示例2:dump指定API的ACL级别溢出数据 + + ```python + from ptdbg_ascend import * + seed_all() + ... + # 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() + ... + # 设置检测到3次溢出后退出训练 + register_hook(model, overflow_check) + + ... + ``` + + 2. dump指定反向API的ACL级别的溢出数据 + + ```python + from ptdbg_ascend import * + seed_all() + ... + # dump指定反向API的ACL级别溢出数据 + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + set_dump_switch("ON", mode="acl", scope=["Functional_conv2d_1_backward"]) + set_backward_input(["./npu_dump/ptdbg_dump_v2.0/rank0/dump/Functional_conv2d_1_backward_input.0.npy"]) + ``` + + 针对前向溢出API,可以通过overflow_nums,配置允许的溢出次数,并将每次溢出API的全部ACL数据dump下来,到达指定溢出次数后停止,停止后会看到堆栈打印包含如下字段。 + + ```bash + ValueError: [overflow xxx times]: dump file is saved in 'xxxxx.pkl'. + ``` + + 其中xxx times为用户设置的次数,xxxxx.pkl为文件生成路径。 + +3. NPU环境下执行训练dump溢出数据。 + + 针对输入正常但输出存在溢出的API,会训练执行目录下将溢出的API信息dump并保存为`forward_info_{pid}.json`和`backward_info_{pid}.json`,通过 [Ascend模型精度预检工具](https://gitee.com/ascend/att/tree/master/debug/accuracy_tools/api_accuracy_checker)对json文件进行解析,输出溢出API为正常溢出还是非正常溢出,从而帮助用户快速判断。 + + 精度预检工具执行命令如下: + + ```bash + # 下载att代码仓后执行如下命令 + export PYTHONPATH=$PYTHONPATH:$ATT_HOME/debug/accuracy_tools/ + cd $ATT_HOME/debug/accuracy_tools/api_accuracy_checker/run_ut + python run_overflow_check.py -forward ./forward_info_0.json + ``` + + 反向过程溢出的API暂不支持精度预检功能。 + + 当重复执行溢出检测dump操作时,需要删除上一次dump目录下的溢出检测dump数据,否则将因重名而报错。 + +**注意事项** + +* dump_mode="acl"场景下,会增加npu的内存消耗,请谨慎开启。 +* 部分API存在调用嵌套关系,比如functional.batch_norm实际调用torch.batch_norm,该场景会影响acl init初始化多次,导致功能异常。 + +## debugger方式dump和溢出检测(推荐) + +### PrecisionDebugger模块 + +**功能说明** + +PrecisionDebugger模块包含dump和溢出检测功能的总体配置项。可以指定dump目录,设置dump或溢出检测功能,指定dump的卡和迭代。 + +可以在from ptdbg_ascend import *和模型初始化之间的任意位置添加该模块。 + +**原型** + +```python +PrecisionDebugger(dump_path=None, hook_name=None, rank=None, step=[], enable_dataloader=False): +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ----------------- | ------------------------------------------------------------ | -------- | +| dump_path | 设置dump数据目录路径,参数示例:"./dump_path"。dump_path的父目录须为已存在目录。
默认在指定的dump_path路径下生成`ptdbg_dump_{version}`目录,并在该目录下生成`dump.pkl`文件以及`dump`数据文件保存目录。
当**configure_hook**函数配置了mode参数时,`dump.pkl`文件以及`dump`数据文件保存目录名称添加mode参数值为前缀,详情请参见“**dump数据存盘说明**”。 | 是 | +| hook_name | dump模式,可取值dump和overflow_check,表示dump和溢出检测功能,二选一。 | 是 | +| rank | 指定对某张卡上的数据进行dump或溢出检测,默认未配置(表示dump所有卡的数据),须根据实际卡的Rank ID配置。应配置为大于0的正整数,且须根据实际卡的Rank ID配置,若所配置的值大于实际训练所运行的卡的Rank ID,则dump数据为空,比如当前环境Rank ID为0~7,实际训练运行0~3卡,此时若配置Rank ID为4或不存在的10等其他值,此时dump数据为空。 | 否 | +| step | 指定dump某个step的数据。 | 否 | +| enable_dataloader | 自动控制开关,可取值True或False,配置为True后自动识别dump step参数指定的迭代,并在该迭代执行完成后退出训练,此时start和stop函数可不配置,配置为False则需要配置start和stop函数并在最后一个stop函数后或一个step结束的位置添加debugger.step()。 | 否 | + +### configure_hook函数(可选) + +**功能说明** + +设置dump范围。 + +建议在**PrecisionDebugger**模块与模型初始化之间的任意位置添加,不添加此函数时默认使用mode="api_stack" dump整网数据。 + +**原型** + +dump: + +```python +debugger.configure_hook(mode="api_stack", scope=[], api_list=[], filter_switch="ON", acl_config=None, backward_input=[], input_output_mode=["all"], summary_only=False) +``` + +溢出检测: + +```python +debugger.configure_hook(mode=None, acl_config=None, overflow_nums=1) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ----------------- | ------------------------------------------------------------ | -------- | +| mode | dump模式。可取值"all"、"list"、"range"、"stack"、"acl"、"api_list"、"api_stack",各参数含义请参见本节的“**函数示例**”。参数示例:mode="list"。默认为api_stack。该参数配置值将作为dump数据文件名的前缀,详情请参见“**dump数据存盘说明**”。 | 否 | +| scope或api_list | dump范围。根据model配置的模式选择dump的API范围,mode="api_list"时,需要配置api_list=[],其他模式有需要时配置scope=[]。参数示例:scope=["Tensor_permute_1_forward", "Tensor_transpose_2_forward"]、api_list=["relu"]。默认为空。 | 否 | +| filter_switch | dump bool和整型的tensor以及浮点、bool和整型的标量的过滤开关。可取值"ON"(表示开启过滤,即不dump)或"OFF"(表示关闭过滤)。参数示例:filter_switch="OFF"。PrecisionDebugger模块hook_name=dump时,默认不配置,即filter_switch="ON",表示过滤上述数据;PrecisionDebugger模块hook_name=overflow_check时,默认不配置,即filter_switch="OFF",表示dump上述数据。 | 否 | +| acl_config | acl dump的配置文件。mode="acl"时,该参数必选;mode为其他值时,该参数不选。参数示例:acl_config='./dump.json'。dump.json配置文件详细介绍请参见“**dump.json配置文件说明**”。 | 否 | +| backward_input | 该输入文件为首次运行训练dump得到反向API输入的.npy文件。例如若需要dump Functional_conv2d_1 API的反向过程的输入输出,则需要在dump目录下查找命名包含Functional_conv2d_1、backward和input字段的.npy文件。 | 否 | +| input_output_mode | dump数据过滤。可取值"all"、"forward"、"backward"、"input"和"output",表示仅保存dump的数据中文件名包含"forward"、"backward"、"input"和"output"的前向、反向、输入或输出的.npy文件。参数示例input_output_mode=["backward"]或input_output_mode=["forward", "backward"]。默认为all,即保存所有dump的数据。除了all参数只能单独配置外,其他参数可以自由组合。 | 否 | +| summary_only | dump npy文件过滤,可取值True或False,配置为True后仅dump保存API统计信息的pkl文件,参数示例:summary_only=False,默认为False。 | 否 | +| overflow_nums | 控制溢出次数,表示第N次溢出时,停止训练,过程中检测到溢出API对应ACL数据均dump。参数示例:overflow_nums=3。配置overflow_check时可配置,默认不配置,即检测到1次溢出,训练停止。 | 否 | + +**函数示例** + +configure_hook可配置多种dump模式,示例如下: + +说明:以下均以dump部分API数据为例,API名可以从首次dump整网数据的结果csv文件中的NPU Name或Bench Name列获取。 + +- 示例1:dump指定API列表 + + ```python + debugger.configure_hook(mode="list", scope=["Tensor_permute_1_forward", "Tensor_transpose_2_forward", "Torch_relu_3_backward"]) + ``` + +- 示例2:dump指定范围 + + ```python + debugger.configure_hook(mode="range", scope=["Tensor_abs_1_forward", "Tensor_transpose_3_forward"]) + ``` + +- 示例3:STACK模式,只dump堆栈信息 + + ```python + debugger.configure_hook(mode="stack", scope=["Tensor_abs_1_forward", "Tensor_transpose_3_forward"]) + ``` + +- 示例4:dump指定前向API的ACL级别数据 + + ```python + debugger.configure_hook(mode="acl", scope=["Tensor_permute_1_forward"], acl_config="./dump.json") + ``` + +- 示例4:dump指定反向API的ACL级别数据 + + ```python + debugger.configure_hook(mode="acl", scope=["Functional_conv2d_1_backward"], acl_config="./dump.json", backward_input=["./npu_dump/dump_conv2d_v2.0/rank0/dump/Functional_conv2d_1_backward_input.0.npy"]) + ``` + +- 示例5:dump指定某一类API的API级别输入输出数据 + + ```python + debugger.configure_hook(mode="api_list", api_list=["relu"]) + ``` + + mode="api_list"时不配置scope。 + +- 示例6:dump全部API级别输入输出数据以及相应堆栈信息 + + ```python + debugger.configure_hook(mode="api_stack") + ``` + + mode="api_stack"时不配置scope。 + +- 示例7: dump全部API级别输入输出数据并包含bool和整型的tensor以及浮点、bool和整型的标量,配置为OFF,会dump bool和整型数据 + + ```python + debugger.configure_hook(filter_switch="OFF") + ``` + + 配置filter_switch="OFF"同时也可以配置mode、scope和api_list,除dump ACL级别数据。 + +- 示例8:仅保存dump的数据文件名包含“backward”的反向.npy文件 + + ```python + debugger.configure_hook(input_output_mode=["backward"]) + ``` + +- 示例9:仅dump pkl文件 + + ```python + debugger.configure_hook(summary_only=True) + ``` + +- 示例10:溢出检测dump + + ```python + debugger.configure_hook(overflow_nums=1) + ``` + + dump执行时会在**PrecisionDebugger**模块的dump_path参数指定的目录下生成ptdbg_dump_{version}目录,保存溢出数据。 + + 多卡场景时,需要检测到至少有一张卡溢出次数达到overflow_nums时,训练结束。 + + 仅支持NPU环境。 + +- 示例11:dump指定API的ACL级别溢出数据 + + ```python + debugger.configure_hook(mode="acl", acl_config="./dump.json") + ``` + + 该场景会在原有数据基础上,额外在dump.json文件配置的dump_path目录下生成一份ACL算子数据,该数据可通过“**ptdbg_ascend.parse**”工具进行解析。 + + 仅支持NPU环境。 + +### start函数(可选) + +**功能说明** + +dump或溢出检测启动函数。 + +在模型初始化之后的任意位置添加。 + +**原型** + +```python +debugger.start() +``` + +该函数为类函数,可以使用debugger.start()也可以使用PrecisionDebugger.start()。 + +### stop函数(可选) + +**功能说明** + +dump或溢出检测停止函数。 + +在**start**函数之后的任意位置添加。 + +**原型** + +```python +debugger.stop() +``` + +该函数为类函数,可以使用debugger.stopt()也可以使用PrecisionDebugger.stop()。 + +### 示例代码(自动模式) + +- 示例1:开启dump + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0,2], enable_dataloader=True) + ``` + +- 示例2:开启溢出检测dump + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="overflow_check", step=[0,2], enable_dataloader=True) + ``` + +### 示例代码(手动模式) + +一般情况下使用自动模式可以快速方便进行dump操作,但个别大模型可能在部分卡的训练操作中没有调用dataloader,这会导致自动模式无法dump指定迭代的数据,此时需要关闭自动模式手动在迭代前后插入start()和stop()函数,并在最后一个一个stop函数后或一个step结束的位置添加debugger.step()以标识dump结束。 + +- 示例1:开启dump + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump") + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.step() + ``` + +- 示例2:开启溢出检测dump + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="overflow_check") + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.step() + ``` + +## CPU或GPU及NPU精度数据dump + +### 总体说明 + +- 本节主要介绍CPU或GPU及NPU精度数据dump所需要的函数以及示例。 + +- ptdbg_ascend工具默认情况下仅dump PyTorch模型的API输入输出数据进行精度比对,若在比对结果中发现某个API下可能存在ACL的精度问题,那么可以选择dump该API的ACL级别数据进行精度分析。 + +- 某些torch api的输出不是Tensor类型的数据。对于此类API的反向过程进行ACL dump,工具会在运行日志中给出对应的Warning(is not of tensor type and cannot be automatically derived)提示。如若想要进行该类API反向ACL dump,可以通过手动构建单API用例的方式进行ACL dump,具体用例可参见“**[反向ACL dump用例说明](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/%E5%8F%8D%E5%90%91ACL%20dump%E7%94%A8%E4%BE%8B%E8%AF%B4%E6%98%8E.md)**”。 + +- 工具性能:dump数据量较小时(小于5G),参考dump速度0.1GB/s;dump数据量较大时,参考dump速度0.2GB/s。 + 推荐环境配置:独占环境,CPU核心数192,固态硬盘(IO速度参考:固态硬盘 > 500MB/s,机械硬盘60 ~ 170MB/s)。 + + 用户环境性能弱于标准约束或非独占使用的比对速度酌情向下浮动。Dump速度的计算方式:Dump数据量/(单个step添加Dump耗时-原始单个step耗时)。 + +### 约束 +- 进行CPU或GPU数据dump时,请安装torch包而非torch_npu包,避免工具无法识别使用场景,导致失败。 + +- TASK_QUEUE_ENABLE环境变量会导致API下发和执行异步进行,因此在ACL dump前需要将TASK_QUEUE_ENABLE关闭,即export TASK_QUEUE_ENABLE=0。 + +- 不建议在PyTorch训练脚本中同时添加dump接口和性能数据采集(如Ascend PyThon Profiler)接口,二者可能相互影响导致数据不准确。 + +### seed_all + +**功能说明** + +固定随机数。通过固定随机数保证模型的输入或输出一致。在训练主函数开始前调用,避免随机数固定不全。 + +dump操作必选。 + +**函数原型** + +```python +seed_all(seed=1234, mode=False) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------ | ------------------------------------------------------------ | -------- | +| seed | 随机数种子。参数示例:seed=1000。默认值为:1234。 | 否 | +| mode | 确定性计算模式。可配置True或False。参数示例:mode=True。默认为False。
即使在相同的硬件和输入下,API多次执行的结果也可能不同,开启确定性计算是为了保证在相同的硬件和输入下,API多次执行的结果相同。
确定性计算会导致API执行性能降低,建议在发现模型多次执行结果不同的情况下开启。
rnn类算子、ReduceSum、ReduceMean等算子可能与确定性计算存在冲突,若开启确定性计算后多次执行的结果不相同,则考虑存在这些算子。 | 否 | + +**函数示例** + +seed_all函数的随机数种子,取默认值即可,无须配置;第二个参数默认关闭,不开启确定性计算时也无须配置。 + +- 示例1:仅固定随机数,不开启确定性计算 + + ```python + seed_all() + ``` + +- 示例2:固定随机数,开启确定性计算 + + ```python + seed_all(mode=True) + ``` + +**固定随机数范围** + +seed_all函数可固定随机数的范围如下表。 + +| API | 固定随机数 | +| ---------------------------------------- | --------------------------- | +| os.environ['PYTHONHASHSEED'] = str(seed) | 禁止Python中的hash随机化 | +| random.seed(seed) | 设置random随机生成器的种子 | +| np.random.seed(seed) | 设置numpy中随机生成器的种子 | +| torch.manual_seed(seed) | 设置当前CPU的随机种子 | +| torch.cuda.manual_seed(seed) | 设置当前GPU的随机种子 | +| torch.cuda.manual_seed_all(seed) | 设置所有GPU的随机种子 | +| torch_npu.npu.manual_seed(seed) | 设置当前NPU的随机种子 | +| torch_npu.npu.manual_seed_all(seed) | 设置所有NPU的随机种子 | +| torch.backends.cudnn.enable=False | 关闭cuDNN | +| torch.backends.cudnn.benchmark=False | cuDNN确定性地选择算法 | +| torch.backends.cudnn.deterministic=True | cuDNN仅使用确定性的卷积算法 | + +需要保证CPU或GPU以及NPU的模型输入完全一致,dump数据的比对才有意义,seed_all并不能保证模型输入完全一致,如下表所示场景需要保证输入的一致性。 + +| 场景 | 固定方法 | +| --------------- | ------------- | +| 数据集的shuffle | 关闭shuffle。 | +| dropout | 关闭dropout。 | + +关闭shuffle示例: + +```python +train_loader = torch.utils.data.DataLoader( + train_dataset, + batch_size = batch_size, + shuffle = False, + num_workers = num_workers +) +``` + +关闭dropout: + +在使用from ptdbg import *后,工具会自动将torch.nn.functional.dropout、torch.nn.functional.dropout2d、torch.nn.functional.dropout3d、torch.nn.Dropout、torch.nn.Dropout2d、torch.nn.Dropout3d的接口参数p置为0。 + +### set_dump_path + +**功能说明** + +设置dump数据目录。建议在seed_all函数之后调用且需要保证训练进程能够调用该函数;多卡时须保证每个进程都能调用该函数。 + +dump操作必选。 + +**函数原型** + +```python +set_dump_path(fpath=None, dump_tag='ptdbg_dump') +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| -------- | ------------------------------------------------------------ | -------- | +| fpath | 设置dump数据目录路径。参数示例:'./dump_path'。dump_path须为已存在目录。
默认在指定的dump_path路径下生成`ptdbg_dump_{version}`目录,并在该目录下生成`dump.pkl`文件以及`dump`数据文件保存目录。
当set_dump_switch函数配置了mode参数时,`dump.pkl`文件以及`dump`数据文件保存目录名称添加mode参数值为前缀,详情请参见“**dump数据存盘说明**” | 是 | +| dump_tag | 设置dump数据目录名称。参数示例:dump_tag='dump_conv2d'。默认dump数据目录命名为ptdbg_dump_{version}。
{version}为当前安装ptdbg_ascend工具版本。目录结构参见“**dump数据存盘说明**”。
配置该参数会将生成的`ptdbg_dump_{version}`目录名称变更为dump_tag配置的值,如`dump_conv2d_{version}`。 | 否 | + +**函数示例** + +- 示例1:设置dump数据目录路径 + + ```python + set_dump_path('./dump_path') + ``` + +- 示例2:设置dump数据目录名称 + + ```python + set_dump_path('./dump_path', dump_tag='dump_conv2d') + ``` + + +若以相同的dump数据目录多次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, rank=0) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------- | ------------------------------------------------------------ | -------- | +| model | model对象。 | 是 | +| hook | 注册工具的dump和溢出检测钩子。可取值overflow_check和acc_cmp_dump,二选一。 | 是 | +| overflow_nums | 控制溢出次数,表示第N次溢出时,停止训练,过程中检测到溢出API对应ACL数据均dump。参数示例:overflow_nums=3。配置overflow_check时可配置,默认不配置,即检测到1次溢出,训练停止。 | 否 | +| dump_mode | 控制针对溢出API的dump模式。可取值"api"或"acl",配置acl时表示dump ACL级别的溢出数据,此时set_dump_path参数不生效,dump数据目录由dump_config的.json文件配置,参数示例:dump_mode="acl"。默认不配置,即dump API级别的溢出数据。 | 否 | +| dump_config | acl dump的配置文件。dump_mode="acl"时,该参数必选;dump_mode="api"时,该参数不选。参数示例:dump_config='./dump.json'。 | 否 | +| rank | 控制dump数据保存的rank目录名称。参数示例:rank=1。默认不配置,即自动读取dump数据所属的卡并保存在该卡对应的rank目录下。目录结构参见“**dump数据存盘说明**”。
多卡情况下,可能出现工具识别rank出错,导致dump数据保存到错误的rank目录下,此时需要根据“**[rank_id获取方法](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/rank_id获取方法.md)**”配置该参数,以获取正确的rank_id;工具可正确识别rank_id时无须配置该参数。 | 否 | + +**函数示例** + +- 示例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="ON", dump_mode=["all"], summary_only=False): +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| --------------- | ------------------------------------------------------------ | -------- | +| switch | dump开关。可取值"ON"或"OFF"。须在选定dump开始的位置配置set_dump_switch("ON");dump结束的位置设置set_dump_switch("OFF")。 | 是 | +| mode | dump模式。可取值"all"、"list"、"range"、"stack"、"acl"、"api_list"、"api_stack",各参数含义请参见本节的“**函数示例**”。参数示例:mode="list"。默认为all。该参数配置值将作为dump数据文件名的前缀,详情请参见“**dump数据存盘说明**”。 | 否 | +| scope或api_list | dump范围。根据model配置的模式选择dump的API范围。参数示例:scope=["Tensor_permute_1_forward", "Tensor_transpose_2_forward"]、api_list=["relu"]。默认为空。 | 否 | +| filter_switch | dump bool和整型的tensor以及浮点、bool和整型的标量的过滤开关。可取值"ON"或"OFF"。参数示例:filter_switch="OFF"。默认不配置,即filter_switch="ON",表示过滤上述数据。 | 否 | +| dump_mode | dump数据过滤。可取值"all"、"forward"、"backward"、"input"和"output",表示仅保存dump的数据中文件名包含"forward"、"backward"、"input"和"output"的前向、反向、输入或输出的.npy文件。参数示例dump_mode=["backward"]或dump_mode=["forward", "backward"]。默认为all,即保存所有dump的数据。除了all参数只能单独配置外,其他参数可以自由组合。 | 否 | +| summary_only | dump npy文件过滤,可取值True或False,配置为True后仅dump保存API统计信息的pkl文件,参数示例:summary_only=False,默认为False。 | 否 | + +**推荐配置** + +```python +set_dump_switch("ON", mode="api_stack", filter_switch="OFF") +``` + +开启dump数据和堆栈模式,同时为保证数据完整性开启dump bool和整型的tensor以及浮点、bool和整型的标量。 + +**函数示例** + +set_dump_switch可配置多种dump模式,示例如下: + +说明:以下均以dump部分API数据为例,API名可以从首次dump整网数据的结果csv文件中的NPU Name或Bench Name列获取。 + +- 示例1:dump指定API列表 + + ```python + set_dump_switch("ON", mode="list", scope=["Tensor_permute_1_forward", "Tensor_transpose_2_forward", "Torch_relu_3_backward"]) + ``` + +- 示例2:dump指定范围 + + ```python + set_dump_switch("ON", mode="range", scope=["Tensor_abs_1_forward", "Tensor_transpose_3_forward"]) + ``` + +- 示例3:STACK模式,只dump堆栈信息 + + ```python + set_dump_switch("ON", mode="stack", scope=["Tensor_abs_1_forward", "Tensor_transpose_3_forward"]) + ``` + +- 示例4:dump指定前向API的ACL级别数据 + + ```python + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + set_dump_switch("ON", mode="acl", scope=["Tensor_permute_1_forward"]) + ``` + + 需要配置register_hook的dump_mode='acl'和dump_config配置文件。 + +- 示例4:dump指定反向API的ACL级别数据 + + ```python + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + set_dump_switch("ON", mode="acl", scope=["Functional_conv2d_1_backward"]) + set_backward_input(["./npu_dump/dump_conv2d_v2.0/rank0/dump/Functional_conv2d_1_backward_input.0.npy"]) + ``` + + 需要配置register_hook的dump_mode='acl'和dump_config配置文件,并通过set_backward_input设置反向API输入的.npy文件。 + +- 示例5:dump指定某一类API的API级别输入输出数据 + + ```python + set_dump_switch("ON", mode="api_list", api_list=["relu"]) + ``` + + mode="api_list"时不配置scope。 + +- 示例6:dump全部API级别输入输出数据以及相应堆栈信息 + + ```python + set_dump_switch("ON", mode="api_stack") + ``` + + mode="api_stack"时不配置scope。 + +- 示例7: dump全部API级别输入输出数据并包含bool和整型的tensor以及浮点、bool和整型的标量,配置为OFF,会dump bool和整型数据 + + ```python + set_dump_switch("ON", filter_switch="OFF") + ``` + + 配置filter_switch="OFF"同时也可以配置mode、scope和api_list,除dump ACL级别数据。 + +- 示例8:仅保存dump的数据文件名包含“backward”的反向.npy文件 + + ```python + set_dump_switch("ON", dump_mode=["backward"]) + ``` + +- 示例9:仅dump pkl文件 + + ```python + set_dump_switch("ON", summary_only=True) + ``` + +以上示例均需要在结束dump的位置插入set_dump_switch("OFF")。 + +set_dump_switch配置mode为all或api_stack时,结束dump后,在dump目录下会自动生成compare_data.py比对脚本模板,示例如下: + +```python +from ptdbg_ascend import compare + +pkl_path = "%s" +dump_data_dir = "%s" + +dump_path_param = { + "npu_pkl_path": , + "bench_pkl_path": , + "npu_dump_data_dir": , + "bench_dump_data_dir": , + "is_print_compare_log": True +} + +compare(dump_path_param, output_path="", stack_mode="%s") +``` + +pkl_path和dump_data_dir字段会自动识别pkl和dump目录的路径,用户需要判断当前dump的环境是NPU、CPU或GPU,并将pkl_path和dump_data_dir字段填入下方dump_path_param函数对应的字段中,例如当前设备为NPU,那么填写方式如下: + +```python +from ptdbg_ascend import compare + +pkl_path = "%s" +dump_data_dir = "%s" + +dump_path_param = { + "npu_pkl_path": pkl_path, + "bench_pkl_path": , + "npu_dump_data_dir": dump_data_dir, + "bench_dump_data_dir": , + "is_print_compare_log": True +} + +compare(dump_path_param, output_path="", stack_mode="%s") +``` + +此时,另一侧数据的路径,需要用户另外识别并填入。 + +### set_overflow_check_switch + +**功能说明** + +置溢出检测范围。默认不配置该函数,全量进行溢出检测。 + +仅支持NPU环境。 + +**函数原型** + +```python +set_overflow_check_switch(switch, filter_switch='OFF') +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------- | ------------------------------------------------------------ | -------- | +| switch, | 检测开关。可取值"ON"或"OFF"。如果只在特定的step溢出检测,则在期望溢出检测的step位置开始前插入set_overflow_check_switch("ON"),在step结束的位置插入set_overflow_check_switch("OFF")。 | 是 | +| filter_switch | dump bool和整型的tensor以及浮点、bool和整型的标量的过滤开关。可取值"ON"或"OFF"。参数示例:filter_switch="ON"。默认不配置,即filter_switch="OFF",表示dump上述数据。 | 否 | + +**函数示例** + +- 示例1:指定范围溢出检测 + + ```python + register_hook(model, overflow_check) + set_overflow_check_switch("ON") + + ... + + set_overflow_check_switch("OFF") + ``` + + 该场景set_dump_path不生效,dump执行时会在当前目录自动生成ptdbg_dump_{version}目录,保存溢出数据。 + +- 示例2:前向API的ACL级别范围溢出检测 + + ```python + register_hook(model, overflow_check, dump_mode='acl', dump_config='./dump.json') + set_overflow_check_switch("ON") + + ... + + set_overflow_check_switch("OFF") + ``` + + 该场景set_dump_path不生效,由dump_config中的dump.json文件配置溢出数据目录。 + +### set_backward_input + +**功能说明** + +设置反向ACL级别dump时需要的反向输入的.npy文件。 + +**函数原型** + +```python +set_backward_input(backward_input) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| -------------- | ------------------------------------------------------------ | -------- | +| backward_input | 该输入文件为首次运行训练dump得到反向API输入的.npy文件。例如若需要dump Functional_conv2d_1 API的反向过程的输入输出,则需要在dump目录下查找命名包含Functional_conv2d_1、backward和input字段的.npy文件。 | 是 | + +**函数示例** + +```python +register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') +set_dump_switch("ON", mode="acl", scope=["Functional_conv2d_1_backward"]) +set_backward_input(["./npu_dump/dump_conv2d_v2.0/rank0/dump/Functional_conv2d_1_backward_input.0.npy"]) +``` + +## dump.json配置文件说明 + +**dump.json配置示例** + +```python +{ + "dump": + { + "dump_list":[], + "dump_path":"./dump/output", + "dump_mode":"all", + "dump_op_switch":"on" + } +} +``` + +**dump.json参数说明** + +| 字段名 | 说明 | +| -------------- | ------------------------------------------------------------ | +| dump_list | 待dump数据的API模型。为空,无需配置。 | +| dump_path | dump数据文件存储到运行环境的目录,主要用于指定ACL dump数据路径。支持配置绝对路径或相对路径。dump_path须为已存在目录。 | +| dump_mode | dump数据模式,配置如下:
- output:dump API的输出数据。默认值。
- input:dump API的输入数据。
- all:dump API的输入、输出数据。 | +| dump_op_switch | 单API模型dump数据开关,配置如下: * off:关闭单API模型dump,默认值。 * on:开启单API模型dump。 | + +**dump目录说明** + +配置register_hook的dump_config后,采集的dump数据会在{dump_path}/{time}/{deviceid}/{model_id}目录下生成,例如“/home/HwHiAiUser/output/20200808163566/0/0” + +```bash +├── 20230131172437 +│   └── 1 +│   ├── 0 +│   │   ├── Add.Add.45.0.1675157077183551 +│   │   ├── Cast.trans_Cast_0.31.0.1675157077159449 +│   │   ├── Cast.trans_Cast_5.43.0.1675157077180129 +│   │   ├── MatMul.MatMul.39.0.1675157077172961 +│   │   ├── Mul.Mul.29.0.1675157077155731 +│   │   ├── NPUAllocFloatStatus.NPUAllocFloatStatus.24.0.1675157077145262 +│   │   ├── TransData.trans_TransData_1.33.0.1675157077162791 +│   │   └── TransData.trans_TransData_4.41.0.1675157077176648 +│   ├── 1701737061 +│   │   └── Cast.trans_Cast_2.35.0.1675157077166214 +│   ├── 25 +│   │   └── NPUClearFloatStatus.NPUClearFloatStatus.26.0.1675157077150342 +│   └── 68 +│   └── TransData.trans_TransData_3.37.0.1675157077169473 +``` + +## dump数据存盘说明 + +dump结果目录结构示例如下: + +```bash +├── dump_path +│ └── ptdbg_dump_{version} +│ ├── rank0 +│ │ ├── dump +| | | ├── Tensor_permute_1_forward.npy +| | | ... +| | | └── Fcuntion_linear_5_backward_output.npy +│ │ └── dump.pkl +│ ├── rank1 +| | ├── dump +| | | └── ... +| | └── dump.pkl +│ ├── ... +│ | +| └── rank7 +``` + +其中ptdbg_dump_{version}为未设置set_dump_path的dump_tag参数时的默认命名;rank为设备上各卡的ID,每张卡上dump的数据会生成对应dump目录,可由register_hook函数的rank参数控制rank目录名称。 + +当使用debugger方式dump数据时,配置了PrecisionDebugger模块的step=[]参数,dump结果目录则以step为父目录,例如配置step=[0,1,2]时,dump结果目录为: + +``` +├── dump_path +│ └── step0 +│ | └── ptdbg_dump_{version} +│ | | ├── rank0 +│ | | ├── ... +│ | | ├── rank7 +| ├── step1 +| | | ├── ... +│ └── step2 +``` + +**精度比对dump场景** + +精度比对dump场景的结果如下: + +* dump.pkl文件:包含dump数据的API名称、dtype、 shape以及各数据的max、min、mean统计信息。 + +* dump目录:目录下为npy格式的dump数据。 + + npy文件保存的前缀和PyTorch对应关系如下 + + | 前缀 | Torch模块 | + | ---------- | ------------------- | + | Tensor | torch.Tensor | + | Torch | torch | + | Functional | torch.nn.functional | + | NPU | NPU亲和算子 | + | VF | torch._VF | + +当set_dump_switch或configure_hook配置mode参数(例如:mode="api_stack" )时,dump结果的文件名会添加api_stack前缀,dump结果如下: + +* api_stack_dump.pkl +* api_stack_dump目录 + +**溢出检测dump场景** + +register_hook设置了overflow_check时,检测API溢出,dump结果的文件名固定为Overflow_info_{timestamp},dump结果如下: + +* Overflow_info_{timestamp}.pkl +* Overflow_info_{timestamp}目录 + +## CPU或GPU与NPU精度数据比对 + +### 总体说明 + +- 本节主要介绍CPU或GPU与NPU精度数据比对的函数以及示例。 + +- 比对函数均通过单独创建精度比对脚本执行,可支持单卡和多卡场景的精度数据比对。 + +- 工具性能:比对数据量较小时(参考值单份文件小于10GB),参考比对速度0.1GB/s;比对数据量较大时,参考比对速度0.3GB/s。 + 推荐环境配置:独占环境,CPU核心数192,固态硬盘(IO速度参考:固态硬盘 > 500MB/s,机械硬盘60 ~ 170MB/s)。 + + 用户环境性能弱于标准约束或非独占使用的比对速度酌情向下浮动。比对速度的计算方式:两份比对文件大小/比对耗时。 + +### 约束 + +- NPU自研API,在CPU或GPU若没有对应的API,该API的dump数据不比对。 + +- NPU与CPU或GPU的计算结果误差可能会随着模型的执行不断累积,最终会出现同一个API因为输入的数据差异较大而无法比对的情况。 + +- CPU或GPU与NPU中两个相同的API会因为调用次数不同导致无法比对或比对到错误的API,不影响整体运行,该API忽略。 + +### compare_distributed + +**功能说明** + +将CPU或GPU与NPU的dump文件进行比对,支持单卡和多卡,可同时比对多卡的dump数据。多机场景需要每个设备单独执行比对操作。可自动检索和匹配对应卡和进程所dump的数据文件,再调用compare进行比对。单机单卡时与compare函数二选一。 + +**函数原型** + +```python +compare_distributed(npu_dump_dir, bench_dump_dir, output_path, **kwargs) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| -------------- | ------------------------------------------------------------ | -------- | +| npu_dump_dir | 配置NPU环境下的dump目录,即set_dump_path函数的dump_tag参数对应的目录名称。参数示例:'./npu_dump/dump_conv2d_v2.0'。 | 是 | +| bench_dump_dir | 配置CPU、GPU或NPU环境下的dump目录,即set_dump_path函数的dump_tag参数对应的目录名称。参数示例:'./gpu_dump/dump_conv2d_v2.0'。 | 是 | +| output_path | 配置比对结果csv文件存盘目录。需要预先创建output_path目录。参数示例:'./output'。文件名称基于时间戳自动生成,格式为:`compare_result_rank{npu_ID}-rank{cpu/gpu/npu_ID}_{timestamp}.csv`。 | 是 | +| **kwargs | 支持compare的所有可选参数。 | 否 | + +**函数示例** + +创建比对脚本,例如compare_distributed.py,拷贝如下代码,具体参数请根据实际环境修改。 + +```python +from ptdbg_ascend import * +compare_distributed('./npu_dump/ptdbg_dump_v2.0', './gpu_dump/ptdbg_dump_v2.0', './output') +``` + +### compare + +**功能说明** + +将CPU或GPU与NPU的dump文件进行比对,仅支持单机单卡。 + +**函数原型** + +```python +compare(input_param, output_path, stack_mode=False, auto_analyze=True, suffix='', fuzzy_match=False) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------ | ------------------------------------------------------------ | -------- | +| input_param | 配置dump数据文件及目录。配置参数包括:
- "npu_pkl_path":指定NPU dump目录下的.pkl文件。参数示例:"npu_pkl_path": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl"。必选。
- "bench_pkl_path":指定CPU、GPU或NPU dump目录下的.pkl文件。参数示例:"bench_pkl_path": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl"。必选。
- "npu_dump_data_dir":"指定NPU dump目录下的dump数据目录。参数示例:"npu_dump_data_dir": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump"。必选。
- "bench_dump_data_dir":"指定CPU、GPU或NPU dump目录下的dump数据目录。参数示例:"npu_dump_data_dir": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump"。必选。
- "is_print_compare_log":配置是否开启日志打屏。可取值True或False。可选。 | 是 | +| output_path | 配置比对结果csv文件存盘目录。参数示例:'./output'。文件名称基于时间戳自动生成,格式为:`compare_result_{timestamp}.csv`。 | 是 | +| stack_mode | 配置stack_mode的开关。仅当dump数据时配置set_dump_switch的mode="api_stack"时需要开启。参数示例:stack_mode=True,默认为False。 | 否 | +| auto_analyze | 自动精度分析,开启后工具自动针对比对结果进行分析,识别到第一个精度不达标节点(在比对结果文件中的“Accuracy Reached or Not”列显示为No),并给出问题可能产生的原因(打屏展示并生成advisor_{timestamp}.txt文件)。可取值True或False,参数示例:auto_analyze=False,默认为True。 | 否 | +| suffix | 标识比对结果的文件名。配置的suffix值在比对结果文件名的compare_result和{timestamp}中间插入,例如:`compare_result_{suffix}_{timestamp}`。默认为空。 | 否 | +| fuzzy_match | 模糊匹配。开启后,对于网络中同一层级且命名仅调用次数不同的API,可匹配并进行比对。可取值True或False,参数示例:fuzzy_match=True,默认为False。 | 否 | + +**函数示例** + +单机单卡场景下创建比对脚本,例如compare.py,拷贝如下代码,具体参数请根据实际环境修改。 + +```python +from ptdbg_ascend import * +dump_result_param={ +"npu_pkl_path": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl", +"bench_pkl_path": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl", +"npu_dump_data_dir": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump", +"bench_dump_data_dir": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump", +"is_print_compare_log": True +} +compare(dump_result_param, "./output", stack_mode=True) +``` + +### parse + +**功能说明** + +解析并提取dump信息中的堆栈信息及数据统计信息。 + +**函数原型** + +```python +parse(pkl_file, moudule_name_prefix) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------------- | ------------------------------------------------------------ | -------- | +| pkl_file | 指定dump数据文件中的pkl文件名。参数示例:"./npu_dump/ptdbg_dump_v2.0/rank0/dump.pkl"。 | 是 | +| moudule_name_prefix | 指定待提取的API接口前缀。参数示例:"Torch_norm_1_forward"。 | 是 | + +**函数示例** + +创建堆栈信息及数据统计信息提取脚本,例如parse.py,拷贝如下代码,具体参数请根据实际环境修改。 + +```python +from ptdbg_ascend import * +parse("./npu_dump/ptdbg_dump_v2.0/rank0/dump.pkl", "Torch_batch_normal_1_forward") +``` + +### 计算精度评价指标 + +PyTorch精度比对是以CPU或GPU的计算结果为标杆,计算Cosine(余弦相似度)、MaxAbsErr(最大绝对误差)和MaxRelativeErr(最大相对误差),根据这两个结果判断API在运行时是否存在精度问题。 + +计算精度评价指标: + +1. Cosine:通过计算两个向量的余弦值来判断其相似度,数值越接近于1说明计算出的两个张量越相似,实际可接受阈值为大于0.99。在计算中可能会存在nan,主要由于可能会出现其中一个向量为0。 + +2. MaxAbsErr:当最大绝对误差越接近0表示其计算的误差越小,实际可接受阈值为小于0.001。 + +3. MaxRelativeErr:当最大相对误差越接近0表示其计算的误差越小。 + + 当dump数据中存在0或Nan时,比对结果中最大相对误差则出现inf或Nan的情况,属于正常现象。 + +精度比对结果csv文件中只需要通过Accuracy Reached or Not来判断计算精度是否达标,判断标准如下: + +1. Cosine < 0.99 且 MaxAbsError > 0.001时,精度不达标,标记为“No”。 +2. Cosine < 0.9,精度不达标,标记为“No”。 +3. MaxAbsError > 1,精度不达标,标记为“No”。 +4. 其余情况下记为精度达标,标记为“Yes”。 + +## ptdbg_ascend.parse数据解析功能 + +ptdbg_ascend.parse为命令行交互式界面解析工具,提供更多的数据解析功能并且展示结果。 + +主要的使用场景包括: + +- 支持指定ACL层级算子数据比对。 +- 支持指定ACL层级算子数据转换及展示。 +- 支持交互式指定pkl文件中API对应dump数据查看。 +- 支持API进行可选层级比对和打印(统计级和像素级)。 + +安装ptdbg_ascend工具后,可以通过使用命令 **python -m ptdbg_ascend.parse** 进入交互式界面,可在parse的界面中执行Shell命令,以及上述场景的相关解析命令。Ctrl+C可以退出该界面。 + +### ACL层级算子数据比对 + +- 依赖:CANN包中的msaccucmp工具。 + +- 输入以下比对命令进行数据比对。 + + ```bash + vc -m my_dump_path -g golden_dump_path [-out output_path] + ``` + + | 参数名称 | 说明 | 是否必选 | + | -------- | ------------------------------------------------------------ | -------- | + | -m | 待比对dump数据目录。 | 是 | + | -g | dump数据目录。 | 是 | + | -out | 结果输出目录。 | 否 | + | -asc | 指定msaccucmp路径,默认路径为:/usr/local/Ascend/ascend-toolkit/latest/tools/operator_cmp/compare/msaccucmp.py。 | 否 | + + - 输出结果:result_{timestamp}.csv文件。 + - 若指定-out参数需要用户传入输出路径,并且路径需要已存在。 + - 若未指定输出目录, 则比对结束后将结果保存在默认目录 “./parse_data/comapre_result”中,比对结束后会打印log提示输出结果存放路径。 + +**示例** + +```bash +# 传入待比对数据目录以及标杆数据目录 +Parse >>> vc -m ./my_dump_path -g ./golden_data_path +...... +# 比对结果打印 +[INFO] The comparison result have been written to "./parse_data/compare_result/result_20230818104735.csv". +[INFO] The command was completed and took 6 seconds. +[INFO] Compare finished!! +``` + +### ACL算子数据的npy转换 + +- 依赖:CANN包中的msaccucmp工具。 + +- 输入以下转换命令进行数据转换, 将ACL级别dump数据转为npy文件。 + + ```bash + dc -n file_name/file_path [-f format] [-out output_path] + ``` + + | 参数名称 | 说明 | 是否必选 | + | -------- | ------------------------------------------------------------ | -------- | + | -n | 需转换的dump数据文件或dump数据文件目录。 | 是 | + | -f | 开启format转换,指定该参数时需要配置format格式,若未指定该参数,则直接转换为npy格式。 | 否 | + | -out | 结果输出目录。 | 否 | + | -asc | 指定msaccucmp路径,默认路径为:/usr/local/Ascend/ascend-toolkit/latest/tools/operator_cmp/compare/msaccucmp.py | 否 | + + [^]: 若传入单个dump文件,则转换单个文件,若传入dump文件目录则转换目录下所有dump文件。 + + - 输出结果:npy文件。 + - 若指定-out参数需要用户传入输出路径,并且路径需要已存在。 + - 若未指定输出目录, 则比对结束后将结果保存在默认目录 “./parse_data/convert_result”中,比对结束后会打印log提示输出结果存放路径及转换结果。 + +- 输入以下命令,展示npy数据统计信息。 + + ```bash + pt -n file_path + ``` + + | 参数名称 | 说明 | 是否必选 | + | -------- | ------------- | -------- | + | -n | npy文件路径。 | 是 | + + 打印统计信息:shape, dtype, max, min和mean。 + +**示例1** + +```bash +# 传入需转换的dump文件目录 +Parse >>> dc -n ./dump_data/ +...... +# 转换结果 +╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ SrcFile: ./dump_data/ +│ - Add.fp32_vars_add_2fp32_vars_Relu_9.31.5.1636595794731103.input.0.npy │ +│ - Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.output.0.npy │ +│ - Add.fp32_vars_add_2fp32_vars_Relu_9.31.5.1636595794731103.input.1.npy │ +│ - Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.input.1.npy │ +│ - Add.fp32_vars_add_3fp32_vars_Relu_12.40.5.1636595794846124.input.1.npy │ +│ - Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.input.0.npy │ +│ - Add.fp32_vars_add_3fp32_vars_Relu_12.40.5.1636595794846124.input.0.npy │ +│ - Add.fp32_vars_add_2fp32_vars_Relu_9.31.5.1636595794731103.output.0.npy │ +│ - Add.fp32_vars_add_3fp32_vars_Relu_12.40.5.1636595794846124.output.0.npy │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ +``` + +**示例2** + +```bash +# 查看某个dump数据块的数据信息 +# 默认会将数据中的tensor保存成 txt +Parse >>> pt -n ./parse_data/dump_convert/Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.output.0.npy +...... +# 打印统计信息 +[Shape: (1, 16, 56, 56, 16)] [Dtype: float16] [Max: 452.0] [Min: -408.5] [Mean: -3.809] +Path: ./parse_data/dump_convert/Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.input.0.npy +TextFile:./parse_data/dump_convert/Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.input.0.npy.txt +``` + +### pkl文件中指定API的dump数据信息查看 + +- 输入以下命令,解析并输出pkl文件中指定api的统计信息。 + + ```bash + pk -f pkl_path -n api_name + ``` + + | 参数名称 | 说明 | 是否必选 | + | -------- | ----------------- | -------- | + | -f | 指定pkl文件路径。 | 是 | + | -n | 指定API名称。 | 是 | + + - 输出结果:打印统计信息(shape, dtype, max和min mean)。 + - 若pkl文件中存在相应的堆栈信息,则会打印堆栈信息。 + +**示例** + +```bash +# 传入pkl文件及api名称 +Parse >>> pk -f ./torch_dump/ptdbg_v3.2/rank0/api_stack_dump.pkl -n Functional_conv2d_0_forward +...... +# 打印统计信息及堆栈(pkl文件不包含堆栈则不会打印堆栈) + +Statistic Info: + [Functional_conv2d_0_forward_input.0][dtype: torch.float32][shape: [2, 1, 2, 2]][max: 1.576936960220337][min: -0.9757485389709473][mean: 0.4961632490158081] + [Functional_conv2d_0_forward_input.1][dtype: torch.float32][shape: [2, 1, 2, 2]][max: 0.20064473152160645][min: -0.47102075815200806][mean: -0.20796933770179749] + [Functional_conv2d_0_forward_input.2][dtype: torch.float32][shape: [2]][max: 0.17380613088607788][min: -0.16853803396224976][mean: 0.0026340484619140625] + [Functional_conv2d_0_forward_output][dtype: torch.float32][shape: [2, 2, 1, 1]][max: 0.02364911139011383][min: -1.762906551361084][mean: -0.6710853576660156] +``` + +### API可选层级比对 + +- 输入以下命令, 进行统计级和像素级比对。 + + ```bash + cn -m my_data*.npy -g gloden*.npy [-p num] [-al atol] [-rl rtol] + ``` + + - 统计级比对:对tensor整体进行余弦值及相对误差的计算。 + - 像素级比对:对输入的两个npy文件进行逐元素比对。若两个tensor对应元素的相对误差或绝对误差大于**误差阈值**(-al和-rl配置)则被标记为错误数据。 + + | 参数名称 | 说明 | 是否必选 | + | -------- | ----------------------------------------------- | -------- | + | -m | 待比对数据。 | 是 | + | -g | 标杆数据。 | 是 | + | -p | 设置比对结束后打印错误元素的个数,默认值20。 | 否 | + | -al | 判定数据存在精度问题的绝对误差阈值,默认0.001。 | 否 | + | -rl | 判定数据存在精度问题的相对误差阈值,默认0.001。 | 否 | + | -s | 将npy文件保存成txt文件,用于查看,默认开启。 | 否 | + + 输出结果: + + - 统计级比对结果。 + - 两个文件的统计信息(shape, dtype, max, min和mean)。 + - 错误数据打印表格。 + +**示例** + +```bash +# 对比两个tensor的数据 +Parse >>> cn -m Add.InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.323.1619494134703053.output.0.npy -g InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.0.1619492699305998.npy -p 10 -s -al 0.002 -rl 0.005 + Error Item Table Top Item Table +┏━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓ ┏━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ +┃ Index ┃ Left ┃ Right ┃ Diff ┃ ┃ Index ┃ Left ┃ Right ┃ Diff ┃ +┡━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩ ┡━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ +│ 155 │ 0.024600908 │ 0.022271132 │ 0.002329776 │ │ 0 │ -0.9206961 │ -0.9222216 │ 0.0015255213 │ +│ 247 │ 0.015752593 │ 0.017937578 │ 0.0021849852 │ │ 1 │ -0.6416973 │ -0.64051837 │ 0.0011789203 │ +│ 282 │ -0.0101207765 │ -0.007852031 │ 0.0022687456 │ │ 2 │ -0.35383835 │ -0.35433492 │ 0.0004965663 │ +│ 292 │ 0.019581757 │ 0.02240482 │ 0.0028230622 │ │ 3 │ -0.18851271 │ -0.18883198 │ 0.00031927228 │ +│ 640 │ -0.06593232 │ -0.06874806 │ 0.0028157383 │ │ 4 │ -0.43508735 │ -0.43534422 │ 0.00025686622 │ +│ 1420 │ 0.09293677 │ 0.09586689 │ 0.0029301196 │ │ 5 │ 1.4447614 │ 1.4466647 │ 0.0019032955 │ +│ 1462 │ -0.085207745 │ -0.088047795 │ 0.0028400496 │ │ 6 │ -0.3455438 │ -0.3444429 │ 0.0011008978 │ +│ 1891 │ -0.03433288 │ -0.036525503 │ 0.002192624 │ │ 7 │ -0.6560242 │ -0.6564579 │ 0.0004336834 │ +│ 2033 │ 0.06828873 │ 0.07139922 │ 0.0031104907 │ │ 8 │ -2.6964858 │ -2.6975214 │ 0.0010356903 │ +│ 2246 │ -0.06376442 │ -0.06121233 │ 0.002552092 │ │ 9 │ -0.73746175 │ -0.73650354 │ 0.00095820427 │ +└───────┴───────────────┴──────────────┴──────────────┘ └───────┴─────────────┴─────────────┴───────────────┘ +╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Left: | +│ |- NpyFile: ./dump/temp/decode/Add.InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.323.1619494134703053.output.0.npy | +│ |- TxtFile: ./dump/temp/decode/Add.InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.323.1619494134703053.output.0.npy.txt | +│ |- NpySpec: [Shape: (32, 8, 8, 320)] [Dtype: float32] [Max: 5.846897] [Min: -8.368301] [Mean: -0.72565556] | +│ DstFile: │ +│ |- NpyFile: ./dump/cpu/InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.0.1619492699305998.npy | +│ |- TxtFile: ./dump/cpu/InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.0.1619492699305998.npy.txt | +│ |- NpySpec: [Shape: (32, 8, 8, 320)] [Dtype: float32] [Max: 5.8425903] [Min: -8.374472] [Mean: -0.7256237] │ +│ NumCnt: 655360 │ +│ AllClose: False │ +│ CosSim: 0.99999493 │ +│ ErrorPer: 0.023504638671875 (rl= 0.005, al= 0.002) │ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +``` + +## FAQ + +[FAQ](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/FAQ.md) 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_v3.4.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_v3.4.md" new file mode 100644 index 0000000000000000000000000000000000000000..4a8ce75df45ab0cded29874c81ecd2054c2c9ff7 --- /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_v3.4.md" @@ -0,0 +1,1568 @@ +# **PyTorch精度工具使用指南** + +本文主要介绍PyTorch精度工具精度工具ptdbg_ascend的使用以及精度比对场景示例。 + +ptdbg_ascend工具的原理及安装请参见《[PyTorch精度工具](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/README.md)》。 + +## PyTorch精度比对总体流程 + +1. 准备CPU或GPU训练工程。 + +2. 在环境下安装ptdbg_ascend工具。 + +3. 在训练脚本内插入ptdbg_ascend工具dump接口。 + +4. 执行训练dump数据。 + +5. 将CPU或GPU训练工程迁移为NPU训练工程。 + + 请参见《[PyTorch模型迁移和训练指南](https://www.hiascend.com/document/detail/zh/canncommercial/63RC1/modeldevpt/ptmigr/ptmigr_0001.html)》。 + +6. 在NPU环境下安装ptdbg_ascend工具。 + +7. 在NPU训练脚本内插入ptdbg_ascend工具dump接口。 + +8. NPU环境下执行训练dump数据。 + +9. 创建并配置精度比对脚本,例如compare.py。 + +10. 执行CPU或GPU dump与NPU dump数据的精度比对。 + +11. 比对结果分析。 + +## 场景化示例 + +本章节主要介绍通过ptdbg_ascend工具进行精度比对和分析,主要使用“**CPU或GPU及NPU精度数据dump**”和“**CPU或GPU与NPU精度数据比对**”章节中介绍的ptdbg_ascend工具接口。 + +### 单卡场景精度比对 + +**精度分析建议** + +PyTorch训练场景的精度问题分析建议参考以下思路进行精度比对和比对结果分析: + +1. 整网比对:dump整网数据并进行精度比对,初步定位异常范围。 +2. 缩小范围:根据Accuracy Reached or Not找出不符合精度标准的API。 +3. 范围比对:对不符合精度标准的API重新dump。 +4. 分析原因并优化:分析API精度不符合标准的原因并进行优化调整。 +5. 整网比对:重新进行整网比对,判断优化后的API是否已符合精度标准以及是否出现新的精度问题。 +6. 重复1~5步,直到不存在精度问题为止。 + +**精度分析示例** + +1. dump整网数据。 + + 分别dump CPU或GPU以及NPU数据,在PyTorch训练脚本插入dump接口,示例代码如下(下面以NPU为例,CPU或GPU dump基本相同): + + ```python + from ptdbg_ascend import * + + # 在main函数开始前固定随机数 + seed_all() + + # 配置dump数据目录路径和名称 + set_dump_path("./npu_dump", dump_tag='all') + + # 注册dump回调函数 + register_hook(model, acc_cmp_dump) + + ... + + # 在第一个迭代开始的位置开启dump和堆栈模式,同时为保证数据完整性开启dump bool和整型的tensor以及浮点、bool和整型的标量 + set_dump_switch("ON", mode="api_stack", filter_switch="OFF") + + ... + + # 在第一个迭代结束的位置关闭dump + set_dump_switch("OFF") + ``` + +2. 比对整网数据。 + + 第1步中的NPU dump数据文件为npu_dump.pkl,假设NPU dump npy数据目录为npu_dump,GPU dump数据文件为gpu_dump.pkl,GPU dump npy数据目录为gpu_dump。 + + 创建并配置精度比对脚本,以创建compare.py为例,示例代码如下: + + ```python + from ptdbg_ascend import * + dump_result_param={ + "npu_pkl_path": "./npu_dump/all_v2.0/rank0/api_stack_dump.pkl", + "bench_pkl_path": "./gpu_dump/all_v2.0/rank0/api_stack_dump.pkl", + "npu_dump_data_dir": "./npu_dump/all_v2.0/rank0/api_stack_dump", + "bench_dump_data_dir": "./gpu_dump/all_v2.0/rank0/api_stack_dump", + "is_print_compare_log": True + } + compare(dump_result_param, "./output") + ``` + + 执行比对: + + ```bash + python3 compare.py + ``` + + 在output目录下生成结果文件,包括:`compare_result_{timestamp}.csv`和`advisor_{timestamp}.txt` + +3. 找出存在问题的API。 + + 1. 根据`advisor_{timestamp}.txt`或打屏信息的提示,可找到存在精度问题的算子(Suspect Nodes)和专家建议(Expert Advice) + + ![auto_analyze_log](img/auto_analyze_log.png) + + 2. 根据第2步结果文件`compare_result_{timestamp}.csv`中的Accuracy Reached or No字段显示为NO的API,针对该API执行后续比对操作,分析该API存在的精度问题。 + +4. (可选)提取指定API的堆栈信息和dump数据统计信息。 + + 通过parse接口可以清晰的显示特定API的堆栈信息和dump数据统计信息,结合堆栈信息分析代码中可能存在的精度问题。 + + 创建并配置提取脚本,以创建parse.py为例,示例代码如下: + + ```python + from ptdbg_ascend import * + + # 提取dump信息中第1次调用的API:Torch_batch_normal的堆栈信息及数据统计信息 + parse("./npu_dump/all_v2.0/rank0/api_stack_dump.pkl", "Torch_batch_normal_1_forward") + ``` + + 执行提取: + + ```bash + python3 parse.py + ``` + + + +5. (可选)指定API dump数据。 + + - dump指定前向API的ACL级别数据 + + ```python + from ptdbg_ascend import * + + # 固定随机数,开启确定性计算 + seed_all(mode=True) + set_dump_path("./dump_path", dump_tag='forward') + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + + # dump指定前向API的ACL级别数据、bool和整型的tensor以及浮点、bool和整型的标量 + set_dump_switch("ON", mode="acl", scope=["Tensor_permute_1_forward"], filter_switch="OFF") + + ... + + set_dump_switch("OFF") + ``` + + - dump指定反向API的ACL级别数据 + + ```python + from ptdbg_ascend import * + + # 固定随机数,开启确定性计算 + seed_all(mode=True) + set_dump_path("./dump_path", dump_tag='backward') + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + + # dump指定反向API的ACL级别数据、bool和整型的tensor以及浮点、bool和整型的标量 + set_dump_switch("ON", mode="acl", scope=["Functional_conv2d_1_backward"], filter_switch="OFF") + set_backward_input(["./npu_dump/all_v2.0/rank0/api_stack_dump/Functional_conv2d_1_backward_input.0.npy"]) + + ... + + set_dump_switch("OFF") + ``` + +6. (可选)重新比对。 + + 根据第4或5步的dump数据重新配置compare.py并执行比对,可以对单API模型进行问题复现。 + +**注意事项** + +* dump_mode="acl"场景下,会增加npu的内存消耗,请谨慎开启。 +* 部分API存在调用嵌套关系,比如functional.batch_norm实际调用torch.batch_norm,该场景会影响acl init初始化多次,导致功能异常。 + +### 多卡场景精度比对 + +精度工具支持多卡场景的精度比对,多卡场景的dump步骤与单卡场景完全一致,请参见“**单卡场景精度比对**”章节,不同的是多卡数据精度比对时需要使用“compare_distributed”函数进行比对。如下示例: + +说明:多机多卡场景需要每个设备单独执行比对操作。 + +假设NPU dump npy数据目录为npu_dump/dump_conv2d_v1.0,GPU dump npy数据目录为gpu_dump/dump_conv2d_v1.0。 + +1. 创建比对脚本,例如compare_distributed.py,拷贝如下代码。 + + ```python + from ptdbg_ascend import * + compare_distributed('./npu_dump/ptdbg_dump_v2.0', './gpu_dump/ptdbg_dump_v2.0', './output') + ``` + +2. 执行比对: + + ```bash + python3 compare_distributed.py + ``` + +两次运行须用相同数量的卡,传入`compare_distributed`的两个文件夹下须有相同个数的rank文件夹,且不包含其他无关文件,否则将无法比对。 + +**多卡set_dump_path注意事项** + +多卡一般为多进程,须保证每个进程都正确调用set_dump_path,或把set_dump_path插入到import语句后,如: + +```python +from ptdbg_ascend import * +seed_all() +set_dump_path('./dump_resnet') +``` + +如此可保证set_dump_path在每个进程都被调用。 + +**多卡register_hook注意事项** + +register_hook需要在set_dump_path之后调用,也需要在每个进程上被调用,建议在搬运模型数据到卡之后调用。识别方法如下: + +- 找到训练代码中遍历epoch的for循环或遍历数据集的for循环,把register_hook放到循环开始前即可。 +- 找到训练代码中调用DDP或者DistributedDataParallel的代码行,把register_hook放到该代码行所在的代码块之后。 +- 若代码中均无以上两种情况,需要保证register_hook在模型定义之后插入,并配置rank参数。rank参数获取rank_id请参见“**[rank_id获取方法](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/rank_id获取方法.md)**”。 + +### NPU vs NPU精度比对 + +对于NPU vs NPU场景,是针对同一模型,进行迭代(模型、API版本升级或设备硬件升级)时存在的精度下降问题,对比相同模型在迭代前后版本的API计算数值,进行问题定位。 + +一般情况下迭代涉及NPU自定义算子,因此,可以仅dump NPU自定义算子进行比对。比对精度问题分析请参见“**单卡场景精度比对**”章节。 + +工具当前支持dump NPU自定义算子如下: + +| 序号 | NPU自定义算子 | +| :--- | ----------------------------------- | +| 1 | torch_npu.one_ | +| 2 | torch_npu.npu_sort_v2 | +| 3 | torch_npu.npu_transpose | +| 4 | torch_npu.npu_broadcast | +| 5 | torch_npu.npu_dtype_cast | +| 6 | torch_npu.empty_with_format | +| 7 | torch_npu.npu_one_hot | +| 8 | torch_npu.npu_stride_add | +| 9 | torch_npu.npu_ps_roi_pooling | +| 10 | torch_npu.npu_roi_align | +| 11 | torch_npu.npu_nms_v4 | +| 12 | torch_npu.npu_iou | +| 13 | torch_npu.npu_nms_with_mask | +| 14 | torch_npu.npu_pad | +| 15 | torch_npu.npu_bounding_box_encode | +| 16 | torch_npu.npu_bounding_box_decode | +| 17 | torch_npu.npu_batch_nms | +| 18 | torch_npu.npu_slice | +| 19 | torch_npu._npu_dropout | +| 20 | torch_npu.npu_indexing | +| 21 | torch_npu.npu_ifmr | +| 22 | torch_npu.npu_max | +| 23 | torch_npu.npu_scatter | +| 24 | torch_npu.npu_layer_norm_eval | +| 25 | torch_npu.npu_alloc_float_status | +| 26 | torch_npu.npu_get_float_status | +| 27 | torch_npu.npu_clear_float_status | +| 28 | torch_npu.npu_confusion_transpose | +| 29 | torch_npu.npu_bmmV2 | +| 30 | torch_npu.fast_gelu | +| 31 | torch_npu.npu_sub_sample | +| 32 | torch_npu.npu_deformable_conv2d | +| 33 | torch_npu.npu_mish | +| 34 | torch_npu.npu_anchor_response_flags | +| 35 | torch_npu.npu_yolo_boxes_encode | +| 36 | torch_npu.npu_grid_assign_positive | +| 37 | torch_npu.npu_normalize_batch | +| 38 | torch_npu.npu_masked_fill_range | +| 39 | torch_npu.npu_linear | +| 40 | torch_npu.npu_bert_apply_adam | +| 41 | torch_npu.npu_giou | +| 42 | torch_npu.npu_ciou | +| 43 | torch_npu.npu_ciou_backward | +| 44 | torch_npu.npu_diou | +| 45 | torch_npu.npu_diou_backward | +| 46 | torch_npu.npu_sign_bits_pack | +| 47 | torch_npu.npu_sign_bits_unpack | + +### 通信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 | batch_isend_irecv | +| 9 | isend | +| 10 | irecv | +| 11 | scatter | +| 12 | reduce_scatter | + +### 溢出检测场景 + +溢出检测是针对NPU的PyTorch API,检测是否存在溢出的情况。当前仅支持识别aicore浮点溢出。 + +溢出检测原理:针对溢出阶段,开启acl dump模式,重新对溢出阶段执行,落盘数据。 + +建议按照如下步骤操作: + +1. 在NPU环境下安装ptdbg_ascend工具。 + +2. 在NPU训练脚本内插入ptdbg_ascend工具溢出检测接口。 + + - 示例1:全量溢出检测 + + ```python + from ptdbg_ascend import * + seed_all() + # 配置溢出数据目录路径和名称 + set_dump_path("./overflow_dump") + ... + # 设置检测到3次溢出后退出训练 + register_hook(model, overflow_check, overflow_nums=3) + + ... + ``` + + 多卡使用时各卡单独计算溢出次数。 + + - 示例2:dump指定API的ACL级别溢出数据 + + ```python + from ptdbg_ascend import * + seed_all() + # 配置溢出数据目录路径和名称 + set_dump_path("./overflow_dump") + ... + # dump指定API的ACL级别溢出数据 + register_hook(model, overflow_check, dump_mode='acl', dump_config='./dump.json') + + # 在期望溢出检测的step位置开始前打开溢出检测开关 + set_overflow_check_switch("ON") + + ... + + # 在step结束的位置关闭溢出检测开关 + set_overflow_check_switch("OFF") + + ... + ``` + + - 示例3:dump指定反向API的ACL级别的溢出数据 + + 1. 进行全量溢出检测 + + ```python + from ptdbg_ascend import * + seed_all() + # 配置溢出数据目录路径和名称 + set_dump_path("./overflow_dump") + ... + # 设置检测到3次溢出后退出训练 + register_hook(model, overflow_check) + + ... + ``` + + 2. dump指定反向API的ACL级别的溢出数据 + + ```python + from ptdbg_ascend import * + seed_all() + # 配置溢出数据目录路径和名称 + set_dump_path("./overflow_dump") + ... + # dump指定反向API的ACL级别溢出数据 + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + set_dump_switch("ON", mode="acl", scope=["Functional_conv2d_1_backward"]) + set_backward_input(["./npu_dump/ptdbg_dump_v2.0/rank0/dump/Functional_conv2d_1_backward_input.0.npy"]) + ``` + + 针对前向溢出API,可以通过overflow_nums,配置允许的溢出次数,并将每次溢出API的全部ACL数据dump下来,到达指定溢出次数后停止,停止后会看到堆栈打印包含如下字段。 + + ```bash + ValueError: [overflow xxx times]: dump file is saved in 'xxxxx.pkl'. + ``` + + 其中xxx times为用户设置的次数,xxxxx.pkl为文件生成路径。 + +3. NPU环境下执行训练dump溢出数据。 + + 针对输入正常但输出存在溢出的API,会训练执行目录下将溢出的API信息dump并保存为`forward_info_{pid}.json`和`backward_info_{pid}.json`,通过 [Ascend模型精度预检工具](https://gitee.com/ascend/att/tree/master/debug/accuracy_tools/api_accuracy_checker)对json文件进行解析,输出溢出API为正常溢出还是非正常溢出,从而帮助用户快速判断。 + + 精度预检工具执行命令如下: + + ```bash + # 下载att代码仓后执行如下命令 + export PYTHONPATH=$PYTHONPATH:$ATT_HOME/debug/accuracy_tools/ + cd $ATT_HOME/debug/accuracy_tools/api_accuracy_checker/run_ut + python run_overflow_check.py -forward ./forward_info_0.json + ``` + + 反向过程溢出的API暂不支持精度预检功能。 + + 当重复执行溢出检测dump操作时,需要删除上一次dump目录下的溢出检测dump数据,否则将因重名而报错。 + +**注意事项** + +* dump_mode="acl"场景下,会增加npu的内存消耗,请谨慎开启。 +* 部分API存在调用嵌套关系,比如functional.batch_norm实际调用torch.batch_norm,该场景会影响acl init初始化多次,导致功能异常。 + +## debugger方式dump和溢出检测(推荐) + +### PrecisionDebugger模块 + +**功能说明** + +PrecisionDebugger模块包含dump和溢出检测功能的总体配置项。可以指定dump目录,设置dump或溢出检测功能,指定dump的卡和迭代。 + +可以在from ptdbg_ascend import *和模型初始化之间的任意位置添加该模块。 + +**原型** + +```python +PrecisionDebugger(dump_path=None, hook_name=None, rank=None, step=[], enable_dataloader=False): +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ----------------- | ------------------------------------------------------------ | -------- | +| dump_path | 设置dump数据目录路径,参数示例:"./dump_path"。
默认在dump_path目录下生成`ptdbg_dump_{version}`目录,并在该目录下生成`dump.pkl`文件以及`dump`数据文件保存目录。
当**configure_hook**函数配置了mode参数时,`dump.pkl`文件以及`dump`数据文件保存目录名称添加mode参数值为前缀,详情请参见“**dump数据存盘说明**”。
未配置dump_path时,也可以通过环境变量ASCEND_WORK_PATH配置dump路径,此时dump数据将落盘在${ASCEND_WORK_PATH}/dump_data下,自定义配置dump_path优先级高于环境变量,dump_path和环境变量需要二选一。 | 否 | +| hook_name | dump模式,可取值dump和overflow_check,表示dump和溢出检测功能,二选一。 | 是 | +| rank | 指定对某张卡上的数据进行dump或溢出检测,默认未配置(表示dump所有卡的数据),须根据实际卡的Rank ID配置。应配置为大于0的正整数,且须根据实际卡的Rank ID配置,若所配置的值大于实际训练所运行的卡的Rank ID,则dump数据为空,比如当前环境Rank ID为0~7,实际训练运行0~3卡,此时若配置Rank ID为4或不存在的10等其他值,此时dump数据为空。 | 否 | +| step | 指定dump某个step的数据。 | 否 | +| enable_dataloader | 自动控制开关,可取值True或False,配置为True后自动识别dump step参数指定的迭代,并在该迭代执行完成后退出训练,此时start和stop函数可不配置,配置为False则需要配置start和stop函数并在最后一个stop函数后或一个step结束的位置添加debugger.step()。 | 否 | + +### configure_hook函数(可选) + +**功能说明** + +设置dump范围。 + +建议在**PrecisionDebugger**模块与模型初始化之间的任意位置添加,不添加此函数时默认使用mode="api_stack" dump整网数据。 + +**原型** + +dump: + +```python +debugger.configure_hook(mode="api_stack", scope=[], api_list=[], filter_switch="ON", acl_config=None, backward_input=[], input_output_mode=["all"], summary_only=False) +``` + +溢出检测: + +```python +debugger.configure_hook(mode=None, acl_config=None, overflow_nums=1) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ----------------- | ------------------------------------------------------------ | -------- | +| mode | dump模式。可取值"all"、"list"、"range"、"stack"、"acl"、"api_list"、"api_stack",各参数含义请参见本节的“**函数示例**”。参数示例:mode="list"。默认为api_stack。该参数配置值将作为dump数据文件名的前缀,详情请参见“**dump数据存盘说明**”。 | 否 | +| scope或api_list | dump范围。根据model配置的模式选择dump的API范围,mode="api_list"时,需要配置api_list=[],其他模式有需要时配置scope=[]。参数示例:scope=["Tensor_permute_1_forward", "Tensor_transpose_2_forward"]、api_list=["relu"]。默认为空。 | 否 | +| filter_switch | dump bool和整型的tensor以及浮点、bool和整型的标量的过滤开关。可取值"ON"(表示开启过滤,即不dump)或"OFF"(表示关闭过滤)。参数示例:filter_switch="OFF"。PrecisionDebugger模块hook_name=dump时,默认不配置,即filter_switch="ON",表示过滤上述数据;PrecisionDebugger模块hook_name=overflow_check时,默认不配置,即filter_switch="OFF",表示dump上述数据。 | 否 | +| acl_config | acl dump的配置文件。mode="acl"时,该参数必选;mode为其他值时,该参数不选。参数示例:acl_config='./dump.json'。dump.json配置文件详细介绍请参见“**dump.json配置文件说明**”。 | 否 | +| backward_input | 该输入文件为首次运行训练dump得到反向API输入的.npy文件。例如若需要dump Functional_conv2d_1 API的反向过程的输入输出,则需要在dump目录下查找命名包含Functional_conv2d_1、backward和input字段的.npy文件。 | 否 | +| input_output_mode | dump数据过滤。可取值"all"、"forward"、"backward"、"input"和"output",表示仅保存dump的数据中文件名包含"forward"、"backward"、"input"和"output"的前向、反向、输入或输出的.npy文件。参数示例input_output_mode=["backward"]或input_output_mode=["forward", "backward"]。默认为all,即保存所有dump的数据。除了all参数只能单独配置外,其他参数可以自由组合。 | 否 | +| summary_only | dump npy文件过滤,可取值True或False,配置为True后仅dump保存API统计信息的pkl文件,参数示例:summary_only=False,默认为False。 | 否 | +| overflow_nums | 控制溢出次数,表示第N次溢出时,停止训练,过程中检测到溢出API对应ACL数据均dump。参数示例:overflow_nums=3。配置overflow_check时可配置,默认不配置,即检测到1次溢出,训练停止。 | 否 | + +**函数示例** + +configure_hook可配置多种dump模式,示例如下: + +说明:以下均以dump部分API数据为例,API名可以从首次dump整网数据的结果csv文件中的NPU Name或Bench Name列获取。 + +- 示例1:dump指定API列表 + + ```python + debugger.configure_hook(mode="list", scope=["Tensor_permute_1_forward", "Tensor_transpose_2_forward", "Torch_relu_3_backward"]) + ``` + +- 示例2:dump指定范围 + + ```python + debugger.configure_hook(mode="range", scope=["Tensor_abs_1_forward", "Tensor_transpose_3_forward"]) + ``` + +- 示例3:STACK模式,只dump堆栈信息 + + ```python + debugger.configure_hook(mode="stack", scope=["Tensor_abs_1_forward", "Tensor_transpose_3_forward"]) + ``` + +- 示例4:dump指定前向API的ACL级别数据 + + ```python + debugger.configure_hook(mode="acl", scope=["Tensor_permute_1_forward"], acl_config="./dump.json") + ``` + +- 示例4:dump指定反向API的ACL级别数据 + + ```python + debugger.configure_hook(mode="acl", scope=["Functional_conv2d_1_backward"], acl_config="./dump.json", backward_input=["./npu_dump/dump_conv2d_v2.0/rank0/dump/Functional_conv2d_1_backward_input.0.npy"]) + ``` + +- 示例5:dump指定某一类API的API级别输入输出数据 + + ```python + debugger.configure_hook(mode="api_list", api_list=["relu"]) + ``` + + mode="api_list"时不配置scope。 + +- 示例6:dump全部API级别输入输出数据以及相应堆栈信息 + + ```python + debugger.configure_hook(mode="api_stack") + ``` + + mode="api_stack"时不配置scope。 + +- 示例7: dump全部API级别输入输出数据并包含bool和整型的tensor以及浮点、bool和整型的标量,配置为OFF,会dump bool和整型数据 + + ```python + debugger.configure_hook(filter_switch="OFF") + ``` + + 配置filter_switch="OFF"同时也可以配置mode、scope和api_list,除dump ACL级别数据。 + +- 示例8:仅保存dump的数据文件名包含“backward”的反向.npy文件 + + ```python + debugger.configure_hook(input_output_mode=["backward"]) + ``` + +- 示例9:仅dump pkl文件 + + ```python + debugger.configure_hook(summary_only=True) + ``` + +- 示例10:溢出检测dump + + ```python + debugger.configure_hook(overflow_nums=1) + ``` + + dump执行时会在**PrecisionDebugger**模块的dump_path参数指定的目录下生成ptdbg_dump_{version}目录,保存溢出数据。 + + 多卡场景时,需要检测到至少有一张卡溢出次数达到overflow_nums时,训练结束。 + + 仅支持NPU环境。 + +- 示例11:dump指定API的ACL级别溢出数据 + + ```python + debugger.configure_hook(mode="acl", acl_config="./dump.json") + ``` + + 该场景会在原有数据基础上,额外在dump.json文件配置的dump_path目录下生成一份ACL算子数据,该数据可通过“**ptdbg_ascend.parse**”工具进行解析。 + + 仅支持NPU环境。 + +### start函数(可选) + +**功能说明** + +dump或溢出检测启动函数。 + +在模型初始化之后的任意位置添加。 + +**原型** + +```python +debugger.start() +``` + +该函数为类函数,可以使用debugger.start()也可以使用PrecisionDebugger.start()。 + +### stop函数(可选) + +**功能说明** + +dump或溢出检测停止函数。 + +在**start**函数之后的任意位置添加。 + +**原型** + +```python +debugger.stop() +``` + +该函数为类函数,可以使用debugger.stopt()也可以使用PrecisionDebugger.stop()。 + +### 示例代码(自动模式) + +- 示例1:开启dump + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump", step=[0,2], enable_dataloader=True) + ``` + +- 示例2:开启溢出检测dump + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="overflow_check", step=[0,2], enable_dataloader=True) + ``` + +### 示例代码(手动模式) + +一般情况下使用自动模式可以快速方便进行dump操作,但个别大模型可能在部分卡的训练操作中没有调用dataloader,这会导致自动模式无法dump指定迭代的数据,此时需要关闭自动模式手动在迭代前后插入start()和stop()函数,并在最后一个一个stop函数后或一个step结束的位置添加debugger.step()以标识dump结束。 + +- 示例1:开启dump + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="dump") + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.step() + ``` + +- 示例2:开启溢出检测dump + + ```python + from ptdbg_ascend import * + debugger = PrecisionDebugger(dump_path="./dump_path", hook_name="overflow_check") + + # 模型初始化 + # 下面代码也可以用PrecisionDebugger.start()和PrecisionDebugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.start() + + # 需要dump的代码片段1 + + debugger.stop() + debugger.step() + ``` + +## CPU或GPU及NPU精度数据dump + +### 总体说明 + +- 本节主要介绍CPU或GPU及NPU精度数据dump所需要的函数以及示例。 + +- ptdbg_ascend工具默认情况下仅dump PyTorch模型的API输入输出数据进行精度比对,若在比对结果中发现某个API下可能存在ACL的精度问题,那么可以选择dump该API的ACL级别数据进行精度分析。 + +- 某些torch api的输出不是Tensor类型的数据。对于此类API的反向过程进行ACL dump,工具会在运行日志中给出对应的Warning(is not of tensor type and cannot be automatically derived)提示。如若想要进行该类API反向ACL dump,可以通过手动构建单API用例的方式进行ACL dump,具体用例可参见“**[反向ACL dump用例说明](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/%E5%8F%8D%E5%90%91ACL%20dump%E7%94%A8%E4%BE%8B%E8%AF%B4%E6%98%8E.md)**”。 + +- 工具性能:dump数据量较小时(小于5G),参考dump速度0.1GB/s;dump数据量较大时,参考dump速度0.2GB/s。 + 推荐环境配置:独占环境,CPU核心数192,固态硬盘(IO速度参考:固态硬盘 > 500MB/s,机械硬盘60 ~ 170MB/s)。 + + 用户环境性能弱于标准约束或非独占使用的比对速度酌情向下浮动。Dump速度的计算方式:Dump数据量/(单个step添加Dump耗时-原始单个step耗时)。 + +### 约束 +- 进行CPU或GPU数据dump时,请安装torch包而非torch_npu包,避免工具无法识别使用场景,导致失败。 + +- TASK_QUEUE_ENABLE环境变量会导致API下发和执行异步进行,因此在ACL dump前需要将TASK_QUEUE_ENABLE关闭,即export TASK_QUEUE_ENABLE=0。 + +- 不建议在PyTorch训练脚本中同时添加dump接口和性能数据采集(如Ascend PyThon Profiler)接口,二者可能相互影响导致数据不准确。 + +### seed_all + +**功能说明** + +固定随机数。通过固定随机数保证模型的输入或输出一致。在训练主函数开始前调用,避免随机数固定不全。 + +dump操作必选。 + +**函数原型** + +```python +seed_all(seed=1234, mode=False) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------ | ------------------------------------------------------------ | -------- | +| seed | 随机数种子。参数示例:seed=1000。默认值为:1234。 | 否 | +| mode | 确定性计算模式。可配置True或False。参数示例:mode=True。默认为False。
即使在相同的硬件和输入下,API多次执行的结果也可能不同,开启确定性计算是为了保证在相同的硬件和输入下,API多次执行的结果相同。
确定性计算会导致API执行性能降低,建议在发现模型多次执行结果不同的情况下开启。
rnn类算子、ReduceSum、ReduceMean等算子可能与确定性计算存在冲突,若开启确定性计算后多次执行的结果不相同,则考虑存在这些算子。 | 否 | + +**函数示例** + +seed_all函数的随机数种子,取默认值即可,无须配置;第二个参数默认关闭,不开启确定性计算时也无须配置。 + +- 示例1:仅固定随机数,不开启确定性计算 + + ```python + seed_all() + ``` + +- 示例2:固定随机数,开启确定性计算 + + ```python + seed_all(mode=True) + ``` + +**固定随机数范围** + +seed_all函数可固定随机数的范围如下表。 + +| API | 固定随机数 | +| ---------------------------------------- | --------------------------- | +| os.environ['PYTHONHASHSEED'] = str(seed) | 禁止Python中的hash随机化 | +| random.seed(seed) | 设置random随机生成器的种子 | +| np.random.seed(seed) | 设置numpy中随机生成器的种子 | +| torch.manual_seed(seed) | 设置当前CPU的随机种子 | +| torch.cuda.manual_seed(seed) | 设置当前GPU的随机种子 | +| torch.cuda.manual_seed_all(seed) | 设置所有GPU的随机种子 | +| torch_npu.npu.manual_seed(seed) | 设置当前NPU的随机种子 | +| torch_npu.npu.manual_seed_all(seed) | 设置所有NPU的随机种子 | +| torch.backends.cudnn.enable=False | 关闭cuDNN | +| torch.backends.cudnn.benchmark=False | cuDNN确定性地选择算法 | +| torch.backends.cudnn.deterministic=True | cuDNN仅使用确定性的卷积算法 | + +需要保证CPU或GPU以及NPU的模型输入完全一致,dump数据的比对才有意义,seed_all并不能保证模型输入完全一致,如下表所示场景需要保证输入的一致性。 + +| 场景 | 固定方法 | +| --------------- | ------------- | +| 数据集的shuffle | 关闭shuffle。 | +| dropout | 关闭dropout。 | + +关闭shuffle示例: + +```python +train_loader = torch.utils.data.DataLoader( + train_dataset, + batch_size = batch_size, + shuffle = False, + num_workers = num_workers +) +``` + +关闭dropout: + +在使用from ptdbg import *后,工具会自动将torch.nn.functional.dropout、torch.nn.functional.dropout2d、torch.nn.functional.dropout3d、torch.nn.Dropout、torch.nn.Dropout2d、torch.nn.Dropout3d的接口参数p置为0。 + +### set_dump_path + +**功能说明** + +设置数据保存目录。建议在seed_all函数之后调用且需要保证训练进程能够调用该函数;多卡时须保证每个进程都能调用该函数。 + +**函数原型** + +```python +set_dump_path(fpath=None, dump_tag='ptdbg_dump') +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| -------- | ------------------------------------------------------------ | -------- | +| fpath | 设置数据目录路径。参数示例:'./dump_path'。
默认在dump_path目录下生成`ptdbg_dump_{version}`目录,并在该目录下生成`dump.pkl`文件以及`dump`数据文件保存目录。
当set_dump_switch函数配置了mode参数时,`dump.pkl`文件以及`dump`数据文件保存目录名称添加mode参数值为前缀,详情请参见“**dump数据存盘说明**”。
未配置fpath时,也可以通过环境变量ASCEND_WORK_PATH配置dump路径,此时数据将落盘在${ASCEND_WORK_PATH}/dump_data下,自定义配置dump_path优先级高于环境变量,fpath和环境变量需要二选一。 | 否 | +| dump_tag | 设置数据目录名称。参数示例:dump_tag='dump_conv2d'。默认数据目录命名为ptdbg_dump_{version}。
{version}为当前安装ptdbg_ascend工具版本。目录结构参见“**dump数据存盘说明**”。
配置该参数会将生成的`ptdbg_dump_{version}`目录名称变更为dump_tag配置的值,如`dump_conv2d_{version}`。 | 否 | + +**函数示例** + +- 示例1:设置数据目录路径 + + ```python + set_dump_path('./dump_path') + ``` + +- 示例2:设置数据目录名称 + + ```python + set_dump_path('./dump_path', dump_tag='dump_conv2d') + ``` + + +若以相同的数据目录多次dump,则会因同名导致覆盖;多次dump建议配置不同的dump_tag。 + +### register_hook + +**功能说明** + +注册工具钩子函数。在set_dump_path之后调用。 + +dump操作必选。 + +**函数原型** + +```python +register_hook(model, hook, overflow_nums=overflow_nums, dump_mode=dump_mode, dump_config=dump_config_file, rank=0) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------- | ------------------------------------------------------------ | -------- | +| model | model对象。 | 是 | +| hook | 注册工具的dump和溢出检测钩子。可取值overflow_check和acc_cmp_dump,二选一。 | 是 | +| overflow_nums | 控制溢出次数,表示第N次溢出时,停止训练,过程中检测到溢出API对应ACL数据均dump。参数示例:overflow_nums=3。配置overflow_check时可配置,默认不配置,即检测到1次溢出,训练停止。 | 否 | +| dump_mode | 控制针对溢出API的dump模式。可取值"api"或"acl",配置acl时表示dump ACL级别的溢出数据,此时set_dump_path参数不生效,dump数据目录由dump_config的.json文件配置,参数示例:dump_mode="acl"。默认不配置,即dump API级别的溢出数据。 | 否 | +| dump_config | acl dump的配置文件。dump_mode="acl"时,该参数必选;dump_mode="api"时,该参数不选。参数示例:dump_config='./dump.json'。 | 否 | +| rank | 控制dump数据保存的rank目录名称。参数示例:rank=1。默认不配置,即自动读取dump数据所属的卡并保存在该卡对应的rank目录下。目录结构参见“**dump数据存盘说明**”。
多卡情况下,可能出现工具识别rank出错,导致dump数据保存到错误的rank目录下,此时需要根据“**[rank_id获取方法](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/rank_id获取方法.md)**”配置该参数,以获取正确的rank_id;工具可正确识别rank_id时无须配置该参数。 | 否 | + +**函数示例** + +- 示例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="ON", dump_mode=["all"], summary_only=False): +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| --------------- | ------------------------------------------------------------ | -------- | +| switch | dump开关。可取值"ON"或"OFF"。须在选定dump开始的位置配置set_dump_switch("ON");dump结束的位置设置set_dump_switch("OFF")。 | 是 | +| mode | dump模式。可取值"all"、"list"、"range"、"stack"、"acl"、"api_list"、"api_stack",各参数含义请参见本节的“**函数示例**”。参数示例:mode="list"。默认为all。该参数配置值将作为dump数据文件名的前缀,详情请参见“**dump数据存盘说明**”。 | 否 | +| scope或api_list | dump范围。根据model配置的模式选择dump的API范围。参数示例:scope=["Tensor_permute_1_forward", "Tensor_transpose_2_forward"]、api_list=["relu"]。默认为空。 | 否 | +| filter_switch | dump bool和整型的tensor以及浮点、bool和整型的标量的过滤开关。可取值"ON"或"OFF"。参数示例:filter_switch="OFF"。默认不配置,即filter_switch="ON",表示过滤上述数据。 | 否 | +| dump_mode | dump数据过滤。可取值"all"、"forward"、"backward"、"input"和"output",表示仅保存dump的数据中文件名包含"forward"、"backward"、"input"和"output"的前向、反向、输入或输出的.npy文件。参数示例dump_mode=["backward"]或dump_mode=["forward", "backward"]。默认为all,即保存所有dump的数据。除了all参数只能单独配置外,其他参数可以自由组合。 | 否 | +| summary_only | dump npy文件过滤,可取值True或False,配置为True后仅dump保存API统计信息的pkl文件,参数示例:summary_only=False,默认为False。 | 否 | + +**推荐配置** + +```python +set_dump_switch("ON", mode="api_stack", filter_switch="OFF") +``` + +开启dump数据和堆栈模式,同时为保证数据完整性开启dump bool和整型的tensor以及浮点、bool和整型的标量。 + +**函数示例** + +set_dump_switch可配置多种dump模式,示例如下: + +说明:以下均以dump部分API数据为例,API名可以从首次dump整网数据的结果csv文件中的NPU Name或Bench Name列获取。 + +- 示例1:dump指定API列表 + + ```python + set_dump_switch("ON", mode="list", scope=["Tensor_permute_1_forward", "Tensor_transpose_2_forward", "Torch_relu_3_backward"]) + ``` + +- 示例2:dump指定范围 + + ```python + set_dump_switch("ON", mode="range", scope=["Tensor_abs_1_forward", "Tensor_transpose_3_forward"]) + ``` + +- 示例3:STACK模式,只dump堆栈信息 + + ```python + set_dump_switch("ON", mode="stack", scope=["Tensor_abs_1_forward", "Tensor_transpose_3_forward"]) + ``` + +- 示例4:dump指定前向API的ACL级别数据 + + ```python + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + set_dump_switch("ON", mode="acl", scope=["Tensor_permute_1_forward"]) + ``` + + 需要配置register_hook的dump_mode='acl'和dump_config配置文件。 + +- 示例4:dump指定反向API的ACL级别数据 + + ```python + register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') + set_dump_switch("ON", mode="acl", scope=["Functional_conv2d_1_backward"]) + set_backward_input(["./npu_dump/dump_conv2d_v2.0/rank0/dump/Functional_conv2d_1_backward_input.0.npy"]) + ``` + + 需要配置register_hook的dump_mode='acl'和dump_config配置文件,并通过set_backward_input设置反向API输入的.npy文件。 + +- 示例5:dump指定某一类API的API级别输入输出数据 + + ```python + set_dump_switch("ON", mode="api_list", api_list=["relu"]) + ``` + + mode="api_list"时不配置scope。 + +- 示例6:dump全部API级别输入输出数据以及相应堆栈信息 + + ```python + set_dump_switch("ON", mode="api_stack") + ``` + + mode="api_stack"时不配置scope。 + +- 示例7: dump全部API级别输入输出数据并包含bool和整型的tensor以及浮点、bool和整型的标量,配置为OFF,会dump bool和整型数据 + + ```python + set_dump_switch("ON", filter_switch="OFF") + ``` + + 配置filter_switch="OFF"同时也可以配置mode、scope和api_list,除dump ACL级别数据。 + +- 示例8:仅保存dump的数据文件名包含“backward”的反向.npy文件 + + ```python + set_dump_switch("ON", dump_mode=["backward"]) + ``` + +- 示例9:仅dump pkl文件 + + ```python + set_dump_switch("ON", summary_only=True) + ``` + +以上示例均需要在结束dump的位置插入set_dump_switch("OFF")。 + +set_dump_switch配置mode为all或api_stack时,结束dump后,在dump目录下会自动生成compare_data.py比对脚本模板,示例如下: + +```python +from ptdbg_ascend import compare + +pkl_path = "%s" +dump_data_dir = "%s" + +dump_path_param = { + "npu_pkl_path": , + "bench_pkl_path": , + "npu_dump_data_dir": , + "bench_dump_data_dir": , + "is_print_compare_log": True +} + +compare(dump_path_param, output_path="", stack_mode="%s") +``` + +pkl_path和dump_data_dir字段会自动识别pkl和dump目录的路径,用户需要判断当前dump的环境是NPU、CPU或GPU,并将pkl_path和dump_data_dir字段填入下方dump_path_param函数对应的字段中,例如当前设备为NPU,那么填写方式如下: + +```python +from ptdbg_ascend import compare + +pkl_path = "%s" +dump_data_dir = "%s" + +dump_path_param = { + "npu_pkl_path": pkl_path, + "bench_pkl_path": , + "npu_dump_data_dir": dump_data_dir, + "bench_dump_data_dir": , + "is_print_compare_log": True +} + +compare(dump_path_param, output_path="", stack_mode="%s") +``` + +此时,另一侧数据的路径,需要用户另外识别并填入。 + +### set_overflow_check_switch + +**功能说明** + +置溢出检测范围。默认不配置该函数,全量进行溢出检测。 + +仅支持NPU环境。 + +**函数原型** + +```python +set_overflow_check_switch(switch, filter_switch='OFF') +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------- | ------------------------------------------------------------ | -------- | +| switch, | 检测开关。可取值"ON"或"OFF"。如果只在特定的step溢出检测,则在期望溢出检测的step位置开始前插入set_overflow_check_switch("ON"),在step结束的位置插入set_overflow_check_switch("OFF")。 | 是 | +| filter_switch | dump bool和整型的tensor以及浮点、bool和整型的标量的过滤开关。可取值"ON"或"OFF"。参数示例:filter_switch="ON"。默认不配置,即filter_switch="OFF",表示dump上述数据。 | 否 | + +**函数示例** + +- 示例1:指定范围溢出检测 + + ```python + register_hook(model, overflow_check) + set_overflow_check_switch("ON") + + ... + + set_overflow_check_switch("OFF") + ``` + + 该场景set_dump_path不生效,dump执行时会在当前目录自动生成ptdbg_dump_{version}目录,保存溢出数据。 + +- 示例2:前向API的ACL级别范围溢出检测 + + ```python + register_hook(model, overflow_check, dump_mode='acl', dump_config='./dump.json') + set_overflow_check_switch("ON") + + ... + + set_overflow_check_switch("OFF") + ``` + + 该场景set_dump_path不生效,由dump_config中的dump.json文件配置溢出数据目录。 + +### set_backward_input + +**功能说明** + +设置反向ACL级别dump时需要的反向输入的.npy文件。 + +**函数原型** + +```python +set_backward_input(backward_input) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| -------------- | ------------------------------------------------------------ | -------- | +| backward_input | 该输入文件为首次运行训练dump得到反向API输入的.npy文件。例如若需要dump Functional_conv2d_1 API的反向过程的输入输出,则需要在dump目录下查找命名包含Functional_conv2d_1、backward和input字段的.npy文件。 | 是 | + +**函数示例** + +```python +register_hook(model, acc_cmp_dump, dump_mode='acl', dump_config='./dump.json') +set_dump_switch("ON", mode="acl", scope=["Functional_conv2d_1_backward"]) +set_backward_input(["./npu_dump/dump_conv2d_v2.0/rank0/dump/Functional_conv2d_1_backward_input.0.npy"]) +``` + +## dump.json配置文件说明 + +**dump.json配置示例** + +```python +{ + "dump": + { + "dump_list":[], + "dump_path":"./dump/output", + "dump_mode":"all", + "dump_op_switch":"on" + } +} +``` + +**dump.json参数说明** + +| 字段名 | 说明 | +| -------------- | ------------------------------------------------------------ | +| dump_list | 待dump数据的API模型。为空,无需配置。 | +| dump_path | dump数据文件存储到运行环境的目录,主要用于指定ACL dump数据路径。支持配置绝对路径或相对路径。dump_path须为已存在目录。 | +| dump_mode | dump数据模式,配置如下:
- output:dump API的输出数据。默认值。
- input:dump API的输入数据。
- all:dump API的输入、输出数据。 | +| dump_op_switch | 单API模型dump数据开关,配置如下: * off:关闭单API模型dump,默认值。 * on:开启单API模型dump。 | + +**dump目录说明** + +配置register_hook的dump_config后,采集的dump数据会在{dump_path}/{time}/{deviceid}/{model_id}目录下生成,例如“/home/HwHiAiUser/output/20200808163566/0/0” + +```bash +├── 20230131172437 +│   └── 1 +│   ├── 0 +│   │   ├── Add.Add.45.0.1675157077183551 +│   │   ├── Cast.trans_Cast_0.31.0.1675157077159449 +│   │   ├── Cast.trans_Cast_5.43.0.1675157077180129 +│   │   ├── MatMul.MatMul.39.0.1675157077172961 +│   │   ├── Mul.Mul.29.0.1675157077155731 +│   │   ├── NPUAllocFloatStatus.NPUAllocFloatStatus.24.0.1675157077145262 +│   │   ├── TransData.trans_TransData_1.33.0.1675157077162791 +│   │   └── TransData.trans_TransData_4.41.0.1675157077176648 +│   ├── 1701737061 +│   │   └── Cast.trans_Cast_2.35.0.1675157077166214 +│   ├── 25 +│   │   └── NPUClearFloatStatus.NPUClearFloatStatus.26.0.1675157077150342 +│   └── 68 +│   └── TransData.trans_TransData_3.37.0.1675157077169473 +``` + +## dump数据存盘说明 + +dump结果目录结构示例如下: + +```bash +├── dump_path +│ └── ptdbg_dump_{version} +│ ├── rank0 +│ │ ├── dump +| | | ├── Tensor_permute_1_forward.npy +| | | ... +| | | └── Fcuntion_linear_5_backward_output.npy +│ │ └── dump.pkl +│ ├── rank1 +| | ├── dump +| | | └── ... +| | └── dump.pkl +│ ├── ... +│ | +| └── rank7 +``` + +其中ptdbg_dump_{version}为未设置set_dump_path的dump_tag参数时的默认命名;rank为设备上各卡的ID,每张卡上dump的数据会生成对应dump目录,可由register_hook函数的rank参数控制rank目录名称。 + +当使用debugger方式dump数据时,配置了PrecisionDebugger模块的step=[]参数,dump结果目录则以step为父目录,例如配置step=[0,1,2]时,dump结果目录为: + +``` +├── dump_path +│ └── step0 +│ | └── ptdbg_dump_{version} +│ | | ├── rank0 +│ | | ├── ... +│ | | ├── rank7 +| ├── step1 +| | | ├── ... +│ └── step2 +``` + +**精度比对dump场景** + +精度比对dump场景的结果如下: + +* dump.pkl文件:包含dump数据的API名称、dtype、 shape以及各数据的max、min、mean统计信息。 + +* dump目录:目录下为npy格式的dump数据。 + + npy文件保存的前缀和PyTorch对应关系如下 + + | 前缀 | Torch模块 | + | ---------- | ------------------- | + | Tensor | torch.Tensor | + | Torch | torch | + | Functional | torch.nn.functional | + | NPU | NPU亲和算子 | + | VF | torch._VF | + +当set_dump_switch或configure_hook配置mode参数(例如:mode="api_stack" )时,dump结果的文件名会添加api_stack前缀,dump结果如下: + +* api_stack_dump.pkl +* api_stack_dump目录 + +**溢出检测dump场景** + +register_hook设置了overflow_check时,检测API溢出,dump结果的文件名固定为Overflow_info_{timestamp},dump结果如下: + +* Overflow_info_{timestamp}.pkl +* Overflow_info_{timestamp}目录 + +## CPU或GPU与NPU精度数据比对 + +### 总体说明 + +- 本节主要介绍CPU或GPU与NPU精度数据比对的函数以及示例。 + +- 比对函数均通过单独创建精度比对脚本执行,可支持单卡和多卡场景的精度数据比对。 + +- 工具性能:比对数据量较小时(参考值单份文件小于10GB),参考比对速度0.1GB/s;比对数据量较大时,参考比对速度0.3GB/s。 + 推荐环境配置:独占环境,CPU核心数192,固态硬盘(IO速度参考:固态硬盘 > 500MB/s,机械硬盘60 ~ 170MB/s)。 + + 用户环境性能弱于标准约束或非独占使用的比对速度酌情向下浮动。比对速度的计算方式:两份比对文件大小/比对耗时。 + +### 约束 + +- NPU自研API,在CPU或GPU若没有对应的API,该API的dump数据不比对。 + +- NPU与CPU或GPU的计算结果误差可能会随着模型的执行不断累积,最终会出现同一个API因为输入的数据差异较大而无法比对的情况。 + +- CPU或GPU与NPU中两个相同的API会因为调用次数不同导致无法比对或比对到错误的API,不影响整体运行,该API忽略。 + +### compare_distributed + +**功能说明** + +将CPU或GPU与NPU的dump文件进行比对,支持单卡和多卡,可同时比对多卡的dump数据。多机场景需要每个设备单独执行比对操作。可自动检索和匹配对应卡和进程所dump的数据文件,再调用compare进行比对。单机单卡时与compare函数二选一。 + +**函数原型** + +```python +compare_distributed(npu_dump_dir, bench_dump_dir, output_path, **kwargs) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| -------------- | ------------------------------------------------------------ | -------- | +| npu_dump_dir | 配置NPU环境下的dump目录,即set_dump_path函数的dump_tag参数对应的目录名称。参数示例:'./npu_dump/dump_conv2d_v2.0'。 | 是 | +| bench_dump_dir | 配置CPU、GPU或NPU环境下的dump目录,即set_dump_path函数的dump_tag参数对应的目录名称。参数示例:'./gpu_dump/dump_conv2d_v2.0'。 | 是 | +| output_path | 配置比对结果csv文件存盘目录。需要预先创建output_path目录。参数示例:'./output'。文件名称基于时间戳自动生成,格式为:`compare_result_rank{npu_ID}-rank{cpu/gpu/npu_ID}_{timestamp}.csv`。 | 是 | +| **kwargs | 支持compare的所有可选参数。 | 否 | + +**函数示例** + +创建比对脚本,例如compare_distributed.py,拷贝如下代码,具体参数请根据实际环境修改。 + +```python +from ptdbg_ascend import * +compare_distributed('./npu_dump/ptdbg_dump_v2.0', './gpu_dump/ptdbg_dump_v2.0', './output') +``` + +### compare + +**功能说明** + +将CPU或GPU与NPU的dump文件进行比对,仅支持单机单卡。 + +**函数原型** + +```python +compare(input_param, output_path, stack_mode=False, auto_analyze=True, suffix='', fuzzy_match=False) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------ | ------------------------------------------------------------ | -------- | +| input_param | 配置dump数据文件及目录。配置参数包括:
- "npu_pkl_path":指定NPU dump目录下的.pkl文件。参数示例:"npu_pkl_path": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl"。必选。
- "bench_pkl_path":指定CPU、GPU或NPU dump目录下的.pkl文件。参数示例:"bench_pkl_path": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl"。必选。
- "npu_dump_data_dir":"指定NPU dump目录下的dump数据目录。参数示例:"npu_dump_data_dir": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump"。必选。
- "bench_dump_data_dir":"指定CPU、GPU或NPU dump目录下的dump数据目录。参数示例:"npu_dump_data_dir": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump"。必选。
- "is_print_compare_log":配置是否开启日志打屏。可取值True或False。可选。 | 是 | +| output_path | 配置比对结果csv文件存盘目录。参数示例:'./output'。文件名称基于时间戳自动生成,格式为:`compare_result_{timestamp}.csv`。 | 是 | +| stack_mode | 配置stack_mode的开关。仅当dump数据时配置set_dump_switch的mode="api_stack"时需要开启。参数示例:stack_mode=True,默认为False。 | 否 | +| auto_analyze | 自动精度分析,开启后工具自动针对比对结果进行分析,识别到第一个精度不达标节点(在比对结果文件中的“Accuracy Reached or Not”列显示为No),并给出问题可能产生的原因(打屏展示并生成advisor_{timestamp}.txt文件)。可取值True或False,参数示例:auto_analyze=False,默认为True。 | 否 | +| suffix | 标识比对结果的文件名。配置的suffix值在比对结果文件名的compare_result和{timestamp}中间插入,例如:`compare_result_{suffix}_{timestamp}`。默认为空。 | 否 | +| fuzzy_match | 模糊匹配。开启后,对于网络中同一层级且命名仅调用次数不同的API,可匹配并进行比对。可取值True或False,参数示例:fuzzy_match=True,默认为False。 | 否 | + +**函数示例** + +单机单卡场景下创建比对脚本,例如compare.py,拷贝如下代码,具体参数请根据实际环境修改。 + +```python +from ptdbg_ascend import * +dump_result_param={ +"npu_pkl_path": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl", +"bench_pkl_path": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump.pkl", +"npu_dump_data_dir": "./npu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump", +"bench_dump_data_dir": "./gpu_dump/ptdbg_dump_v2.0/rank0/api_stack_dump", +"is_print_compare_log": True +} +compare(dump_result_param, "./output", stack_mode=True) +``` + +### parse + +**功能说明** + +解析并提取dump信息中的堆栈信息及数据统计信息。 + +**函数原型** + +```python +parse(pkl_file, moudule_name_prefix) +``` + +**参数说明** + +| 参数名 | 说明 | 是否必选 | +| ------------------- | ------------------------------------------------------------ | -------- | +| pkl_file | 指定dump数据文件中的pkl文件名。参数示例:"./npu_dump/ptdbg_dump_v2.0/rank0/dump.pkl"。 | 是 | +| moudule_name_prefix | 指定待提取的API接口前缀。参数示例:"Torch_norm_1_forward"。 | 是 | + +**函数示例** + +创建堆栈信息及数据统计信息提取脚本,例如parse.py,拷贝如下代码,具体参数请根据实际环境修改。 + +```python +from ptdbg_ascend import * +parse("./npu_dump/ptdbg_dump_v2.0/rank0/dump.pkl", "Torch_batch_normal_1_forward") +``` + +### 计算精度评价指标 + +PyTorch精度比对是以CPU或GPU的计算结果为标杆,计算Cosine(余弦相似度)、MaxAbsErr(最大绝对误差)和MaxRelativeErr(最大相对误差),根据这两个结果判断API在运行时是否存在精度问题。 + +计算精度评价指标: + +1. Cosine:通过计算两个向量的余弦值来判断其相似度,数值越接近于1说明计算出的两个张量越相似,实际可接受阈值为大于0.99。在计算中可能会存在nan,主要由于可能会出现其中一个向量为0。 + +2. MaxAbsErr:当最大绝对误差越接近0表示其计算的误差越小,实际可接受阈值为小于0.001。 + +3. MaxRelativeErr:当最大相对误差越接近0表示其计算的误差越小。 + + 当dump数据中存在0或Nan时,比对结果中最大相对误差则出现inf或Nan的情况,属于正常现象。 + +精度比对结果csv文件中只需要通过Accuracy Reached or Not来判断计算精度是否达标,判断标准如下: + +1. Cosine < 0.99 且 MaxAbsError > 0.001时,精度不达标,标记为“No”。 +2. Cosine < 0.9,精度不达标,标记为“No”。 +3. MaxAbsError > 1,精度不达标,标记为“No”。 +4. 其余情况下记为精度达标,标记为“Yes”。 + +## ptdbg_ascend.parse数据解析功能 + +ptdbg_ascend.parse为命令行交互式界面解析工具,提供更多的数据解析功能并且展示结果。 + +主要的使用场景包括: + +- 支持指定ACL层级算子数据比对。 +- 支持指定ACL层级算子数据转换及展示。 +- 支持交互式指定pkl文件中API对应dump数据查看。 +- 支持API进行可选层级比对和打印(统计级和像素级)。 + +安装ptdbg_ascend工具后,可以通过使用命令 **python -m ptdbg_ascend.parse** 进入交互式界面,可在parse的界面中执行Shell命令,以及上述场景的相关解析命令。Ctrl+C可以退出该界面。 + +### ACL层级算子数据比对 + +- 依赖:CANN包中的msaccucmp工具。 + +- 输入以下比对命令进行数据比对。 + + ```bash + vc -m my_dump_path -g golden_dump_path [-out output_path] + ``` + + | 参数名称 | 说明 | 是否必选 | + | -------- | ------------------------------------------------------------ | -------- | + | -m | 待比对dump数据目录。 | 是 | + | -g | dump数据目录。 | 是 | + | -out | 结果输出目录。 | 否 | + | -asc | 指定msaccucmp路径,默认路径为:/usr/local/Ascend/ascend-toolkit/latest/tools/operator_cmp/compare/msaccucmp.py。 | 否 | + + - 输出结果:result_{timestamp}.csv文件。 + - 若指定-out参数需要用户传入输出路径,并且路径需要已存在。 + - 若未指定输出目录, 则比对结束后将结果保存在默认目录 “./parse_data/comapre_result”中,比对结束后会打印log提示输出结果存放路径。 + +**示例** + +```bash +# 传入待比对数据目录以及标杆数据目录 +Parse >>> vc -m ./my_dump_path -g ./golden_data_path +...... +# 比对结果打印 +[INFO] The comparison result have been written to "./parse_data/compare_result/result_20230818104735.csv". +[INFO] The command was completed and took 6 seconds. +[INFO] Compare finished!! +``` + +### ACL算子数据的npy转换 + +- 依赖:CANN包中的msaccucmp工具。 + +- 输入以下转换命令进行数据转换, 将ACL级别dump数据转为npy文件。 + + ```bash + dc -n file_name/file_path [-f format] [-out output_path] + ``` + + | 参数名称 | 说明 | 是否必选 | + | -------- | ------------------------------------------------------------ | -------- | + | -n | 需转换的dump数据文件或dump数据文件目录。 | 是 | + | -f | 开启format转换,指定该参数时需要配置format格式,若未指定该参数,则直接转换为npy格式。 | 否 | + | -out | 结果输出目录。 | 否 | + | -asc | 指定msaccucmp路径,默认路径为:/usr/local/Ascend/ascend-toolkit/latest/tools/operator_cmp/compare/msaccucmp.py | 否 | + + [^]: 若传入单个dump文件,则转换单个文件,若传入dump文件目录则转换目录下所有dump文件。 + + - 输出结果:npy文件。 + - 若指定-out参数需要用户传入输出路径,并且路径需要已存在。 + - 若未指定输出目录, 则比对结束后将结果保存在默认目录 “./parse_data/convert_result”中,比对结束后会打印log提示输出结果存放路径及转换结果。 + +- 输入以下命令,展示npy数据统计信息。 + + ```bash + pt -n file_path + ``` + + | 参数名称 | 说明 | 是否必选 | + | -------- | ------------- | -------- | + | -n | npy文件路径。 | 是 | + + 打印统计信息:shape, dtype, max, min和mean。 + +**示例1** + +```bash +# 传入需转换的dump文件目录 +Parse >>> dc -n ./dump_data/ +...... +# 转换结果 +╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ SrcFile: ./dump_data/ +│ - Add.fp32_vars_add_2fp32_vars_Relu_9.31.5.1636595794731103.input.0.npy │ +│ - Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.output.0.npy │ +│ - Add.fp32_vars_add_2fp32_vars_Relu_9.31.5.1636595794731103.input.1.npy │ +│ - Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.input.1.npy │ +│ - Add.fp32_vars_add_3fp32_vars_Relu_12.40.5.1636595794846124.input.1.npy │ +│ - Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.input.0.npy │ +│ - Add.fp32_vars_add_3fp32_vars_Relu_12.40.5.1636595794846124.input.0.npy │ +│ - Add.fp32_vars_add_2fp32_vars_Relu_9.31.5.1636595794731103.output.0.npy │ +│ - Add.fp32_vars_add_3fp32_vars_Relu_12.40.5.1636595794846124.output.0.npy │ +╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯ +``` + +**示例2** + +```bash +# 查看某个dump数据块的数据信息 +# 默认会将数据中的tensor保存成 txt +Parse >>> pt -n ./parse_data/dump_convert/Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.output.0.npy +...... +# 打印统计信息 +[Shape: (1, 16, 56, 56, 16)] [Dtype: float16] [Max: 452.0] [Min: -408.5] [Mean: -3.809] +Path: ./parse_data/dump_convert/Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.input.0.npy +TextFile:./parse_data/dump_convert/Add.fp32_vars_add_1fp32_vars_Relu_6.24.5.1636595794631347.input.0.npy.txt +``` + +### pkl文件中指定API的dump数据信息查看 + +- 输入以下命令,解析并输出pkl文件中指定api的统计信息。 + + ```bash + pk -f pkl_path -n api_name + ``` + + | 参数名称 | 说明 | 是否必选 | + | -------- | ----------------- | -------- | + | -f | 指定pkl文件路径。 | 是 | + | -n | 指定API名称。 | 是 | + + - 输出结果:打印统计信息(shape, dtype, max和min mean)。 + - 若pkl文件中存在相应的堆栈信息,则会打印堆栈信息。 + +**示例** + +```bash +# 传入pkl文件及api名称 +Parse >>> pk -f ./torch_dump/ptdbg_v3.2/rank0/api_stack_dump.pkl -n Functional_conv2d_0_forward +...... +# 打印统计信息及堆栈(pkl文件不包含堆栈则不会打印堆栈) + +Statistic Info: + [Functional_conv2d_0_forward_input.0][dtype: torch.float32][shape: [2, 1, 2, 2]][max: 1.576936960220337][min: -0.9757485389709473][mean: 0.4961632490158081] + [Functional_conv2d_0_forward_input.1][dtype: torch.float32][shape: [2, 1, 2, 2]][max: 0.20064473152160645][min: -0.47102075815200806][mean: -0.20796933770179749] + [Functional_conv2d_0_forward_input.2][dtype: torch.float32][shape: [2]][max: 0.17380613088607788][min: -0.16853803396224976][mean: 0.0026340484619140625] + [Functional_conv2d_0_forward_output][dtype: torch.float32][shape: [2, 2, 1, 1]][max: 0.02364911139011383][min: -1.762906551361084][mean: -0.6710853576660156] +``` + +### API可选层级比对 + +- 输入以下命令, 进行统计级和像素级比对。 + + ```bash + cn -m my_data*.npy -g gloden*.npy [-p num] [-al atol] [-rl rtol] + ``` + + - 统计级比对:对tensor整体进行余弦值及相对误差的计算。 + - 像素级比对:对输入的两个npy文件进行逐元素比对。若两个tensor对应元素的相对误差或绝对误差大于**误差阈值**(-al和-rl配置)则被标记为错误数据。 + + | 参数名称 | 说明 | 是否必选 | + | -------- | ----------------------------------------------- | -------- | + | -m | 待比对数据。 | 是 | + | -g | 标杆数据。 | 是 | + | -p | 设置比对结束后打印错误元素的个数,默认值20。 | 否 | + | -al | 判定数据存在精度问题的绝对误差阈值,默认0.001。 | 否 | + | -rl | 判定数据存在精度问题的相对误差阈值,默认0.001。 | 否 | + | -s | 将npy文件保存成txt文件,用于查看,默认开启。 | 否 | + + 输出结果: + + - 统计级比对结果。 + - 两个文件的统计信息(shape, dtype, max, min和mean)。 + - 错误数据打印表格。 + +**示例** + +```bash +# 对比两个tensor的数据 +Parse >>> cn -m Add.InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.323.1619494134703053.output.0.npy -g InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.0.1619492699305998.npy -p 10 -s -al 0.002 -rl 0.005 + Error Item Table Top Item Table +┏━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓ ┏━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ +┃ Index ┃ Left ┃ Right ┃ Diff ┃ ┃ Index ┃ Left ┃ Right ┃ Diff ┃ +┡━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩ ┡━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ +│ 155 │ 0.024600908 │ 0.022271132 │ 0.002329776 │ │ 0 │ -0.9206961 │ -0.9222216 │ 0.0015255213 │ +│ 247 │ 0.015752593 │ 0.017937578 │ 0.0021849852 │ │ 1 │ -0.6416973 │ -0.64051837 │ 0.0011789203 │ +│ 282 │ -0.0101207765 │ -0.007852031 │ 0.0022687456 │ │ 2 │ -0.35383835 │ -0.35433492 │ 0.0004965663 │ +│ 292 │ 0.019581757 │ 0.02240482 │ 0.0028230622 │ │ 3 │ -0.18851271 │ -0.18883198 │ 0.00031927228 │ +│ 640 │ -0.06593232 │ -0.06874806 │ 0.0028157383 │ │ 4 │ -0.43508735 │ -0.43534422 │ 0.00025686622 │ +│ 1420 │ 0.09293677 │ 0.09586689 │ 0.0029301196 │ │ 5 │ 1.4447614 │ 1.4466647 │ 0.0019032955 │ +│ 1462 │ -0.085207745 │ -0.088047795 │ 0.0028400496 │ │ 6 │ -0.3455438 │ -0.3444429 │ 0.0011008978 │ +│ 1891 │ -0.03433288 │ -0.036525503 │ 0.002192624 │ │ 7 │ -0.6560242 │ -0.6564579 │ 0.0004336834 │ +│ 2033 │ 0.06828873 │ 0.07139922 │ 0.0031104907 │ │ 8 │ -2.6964858 │ -2.6975214 │ 0.0010356903 │ +│ 2246 │ -0.06376442 │ -0.06121233 │ 0.002552092 │ │ 9 │ -0.73746175 │ -0.73650354 │ 0.00095820427 │ +└───────┴───────────────┴──────────────┴──────────────┘ └───────┴─────────────┴─────────────┴───────────────┘ +╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ +│ Left: | +│ |- NpyFile: ./dump/temp/decode/Add.InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.323.1619494134703053.output.0.npy | +│ |- TxtFile: ./dump/temp/decode/Add.InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.323.1619494134703053.output.0.npy.txt | +│ |- NpySpec: [Shape: (32, 8, 8, 320)] [Dtype: float32] [Max: 5.846897] [Min: -8.368301] [Mean: -0.72565556] | +│ DstFile: │ +│ |- NpyFile: ./dump/cpu/InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.0.1619492699305998.npy | +│ |- TxtFile: ./dump/cpu/InceptionV3_InceptionV3_Mixed_7a_Branch_0_add_3.0.1619492699305998.npy.txt | +│ |- NpySpec: [Shape: (32, 8, 8, 320)] [Dtype: float32] [Max: 5.8425903] [Min: -8.374472] [Mean: -0.7256237] │ +│ NumCnt: 655360 │ +│ AllClose: False │ +│ CosSim: 0.99999493 │ +│ ErrorPer: 0.023504638671875 (rl= 0.005, al= 0.002) │ +╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ +``` + +## FAQ + +[FAQ](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/doc/FAQ.md) diff --git "a/debug/accuracy_tools/ptdbg_ascend/doc/\345\234\250\347\272\277\347\262\276\345\272\246\346\257\224\345\257\271.md" "b/debug/accuracy_tools/ptdbg_ascend/doc/\345\234\250\347\272\277\347\262\276\345\272\246\346\257\224\345\257\271.md" new file mode 100644 index 0000000000000000000000000000000000000000..b0a98e8852ff0d2bb395b07eb5ffa3a537b95928 --- /dev/null +++ "b/debug/accuracy_tools/ptdbg_ascend/doc/\345\234\250\347\272\277\347\262\276\345\272\246\346\257\224\345\257\271.md" @@ -0,0 +1,89 @@ +# **PyTorch在线精度比对工具使用指南** + +本文主要介绍ptdbg_ascend 在线精度比对功能使用,ptdbg_ascend工具的原理及安装请参见《[PyTorch精度工具](https://gitee.com/ascend/att/blob/master/debug/accuracy_tools/ptdbg_ascend/README.md)》。本节只介绍在线精度比对部分 + +## PyTorch NPU在线精度比对总体流程 + +1. 准备NPU训练工程。 + +2. 在环境下安装ptdbg_ascend工具。 + +3. 在训练脚本内插入ptdbg_ascend工具在线比对接口。 + +4. 执行训练在线精度比对NPU和CPU执行结果 + +5. 比对结果分析。 + +## PyTorch NPU在线精度比 +### 总体说明 +- 本节主要介绍NPU精度比对所需要的函数以及示例。 +- 在线精度比对工具通过截获Pytorch框架中部分Aten Ir及其输入输出,并将输入转到CPU执行,在线比对NPU和CPU的输出结果 + +### 约束 + +- Pytorch 只支持2.0及其以上版本 +- 只支持Aten Ir级在线精度比对,所有Aten Ir可以通过dir(torch.ops.aten)查看,其中部分IR不支持 在线比对:Aten Ir无对应CPU实现;NPU/CPU同AtenIR 实现逻辑不一致,导致同输入不同输出 +- 正反向不支持同时在线精度比对,不支持跨step在线精度比对 + + +### 场景示例 +1.在线精度比对 + ```python +from ptdbg_ascend import seed_all +from ptdbg_ascend.online_dispatch import PtdbgDispatch + + # 在main函数开始前固定随机数 + seed_all() + + + ... + + # 在需要调试精度的正向或反向代码前设置 + # 正向示例 + with PtdbgDispatch(dump_mode="auto", dump_path="/home/dump"): + output = model_cpu(inputs) + # 反向示例 + with PtdbgDispatch(dump_mode="auto", dump_path="/home/dump"): + loss.backward() + ``` + + +2.找出精度不达标的Aten IR + +执行过程中会打屏Failed,Failed在比对结果csv中的Accuracy Reached or Not列标记为No,并在Dump目录下存盘精度不达标Aten IR的输入输出 +![图片说明](http://image.huawei.com/tiny-lts/v1/images/d83d564e337e80c7cfb557ca3600d0d4_1689x178.png@900-0-90-f.png) + +精度不达标的判断条件如下: + +1) Cosine < 0.99 且 MaxAbsError > 0.001时,精度不达标 + +2) Cosine < 0.9,精度不达标 + +3) MaxAbsError > 1,精度不达标 + + + + + +### 在线精度比对参数设置说明 + + | 参数名称 | 说明 | 是否必选 | + | -------- | ----------------------------------------------- | -------- | + | dump_mode| 可取值"all"、"list"、"auto"、"off",默认值为 off,不Dump数据 | 否 | + | api_list | dump_mode设置为list时设置,需要Dump Aten ir api名字,默认为None,Aten ir名字可以通过dir(torch.ops.aten)查看 | 否 | + | dump_path| dump文件生成的路径 | 是 | + | tag | 传入tag字符串,成为dump文件夹名一部分,默认为None | 否 | + | process_num | 多进程并发数默认为0| 否 | + | debug | debug信息打印,默认为False | 否 | +### dump数据存盘说明 +存盘文件夹名:ptdbg+版本号+tag+rank卡号+时间戳,下划线连接tag为用户输入标记,默认无。 +子目录下会有1个比对csv结果文件,npu文件夹下包含Aten IR在npu上的输入输出,cpu文件夹下只包含cpu输出 + + ptdbg_v3.2_rank4_20230911170521 + ├── compare_result_rank4_20230911170521.csv + ├── cpu + │   ├── native_batch_norm_backward_10_output.0.npy + │ ............ + └── npu + ├── native_batch_norm_backward_10_input.0.npy + ............ diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/advisor/advisor.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/advisor/advisor.py index 4f597b7c4df6095ea9b9fb6e31fa8d831a53d70f..754152169e6465ff3b80a445fe7a53ec13cb421a 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/advisor/advisor.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/advisor/advisor.py @@ -58,28 +58,45 @@ class Advisor: utils.check_file_or_directory_path(self.input_file) utils.check_file_size(self.input_file, Const.ONE_GB) - @staticmethod - def filter_data(pd_data): - """ - filter some apis cannot be fixed - """ - result = pd_data[~pd_data[CompareConst.NPU_NAME].str.contains(AdvisorConst.BATCH_NORM)] - return result - - @staticmethod - def gen_advisor_message(node_name): + def gen_advisor_message(self, node_name): if AdvisorConst.FORWARD in node_name: if AdvisorConst.INPUT in node_name: message = AdvisorConst.FORWARD_INPUT_SUGGEST else: message = AdvisorConst.FORWARD_OUTPUT_SUGGEST + message = self.deterministic_advisor(message, node_name) else: if AdvisorConst.INPUT in node_name: message = AdvisorConst.BACKWARD_INPUT_SUGGEST else: message = AdvisorConst.BACKWARD_OUTPUT_SUGGEST + message = self.deterministic_advisor(message, node_name) + message = self.batch_norm_advisor(message, node_name) return message + @staticmethod + def deterministic_advisor(message, node_name): + for api_name in AdvisorConst.NEED_DETERMINISTIC_API: + if api_name in node_name: + return AdvisorConst.DETERMINISTIC_SUGGEST + return message + + @staticmethod + def batch_norm_advisor(message, node_name): + if AdvisorConst.FUNC_BATCH_NORM in node_name and AdvisorConst.FORWARD_INPUT_1 in node_name: + message = AdvisorConst.BATCH_NORM_SUGGEST + return message + + @staticmethod + def analyze_unmatched(analyze_data): + accuracy_unmatched = analyze_data[analyze_data[CompareConst.ACCURACY] == CompareConst.ACCURACY_CHECK_UNMATCH] + num_unmatch = len(accuracy_unmatched) + if num_unmatch != 0: + for i in range(len(accuracy_unmatched)): + item = analyze_data.iloc[i] + print_warn_log("The tensor name matches but the shape or dtype does not match: {}" + .format(item[CompareConst.NPU_NAME])) + def gen_advisor_result(self, pd_data): first_failing_data = pd_data.iloc[0] node_name = first_failing_data[CompareConst.NPU_NAME] @@ -88,23 +105,13 @@ class Advisor: print_warn_log("Find %s accuracy not reached, the line is %s" % (node_name, index)) result = AdvisorResult(node_name, index, message) return result - - def analyze_unmatched(self, analyze_data): - accuracy_unmatched = analyze_data[analyze_data[CompareConst.ACCURACY] == CompareConst.ACCURACY_CHECK_UNMATCH] - num_unmatch = len(accuracy_unmatched) - if num_unmatch != 0: - for i in range(len(accuracy_unmatched)): - item = analyze_data.iloc[i] - print_warn_log("The tensor name matches but the shape or dtype does not match: {}"\ - .format(item[CompareConst.NPU_NAME])) def analysis(self): self._check_result_file() analyze_data = self._parse_input_file() print_info_log("Start analyzing the comparison result: %s" % self.input_file) self.analyze_unmatched(analyze_data) - accuracy_not_reached = analyze_data[analyze_data[CompareConst.ACCURACY] == CompareConst.ACCURACY_CHECK_NO] - failing_data = self.filter_data(accuracy_not_reached) + failing_data = analyze_data[analyze_data[CompareConst.ACCURACY] == CompareConst.ACCURACY_CHECK_NO] if failing_data.empty: print_info_log("All data from api input/output accuracy reached") result = AdvisorResult(AdvisorConst.NO_ERROR_API, AdvisorConst.NO_ERROR_API, AdvisorConst.NO_ERR_SUGGEST) diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/advisor/advisor_const.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/advisor/advisor_const.py index 1a6b5ea4b775235adc5e6c34e2f93be367ae3bf6..fecf7b8aa9e2fd9ac9be66d72bec98c811718696 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/advisor/advisor_const.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/advisor/advisor_const.py @@ -40,8 +40,16 @@ class AdvisorConst: FORWARD_OUTPUT_SUGGEST = "This is a forward API computation error. Check the computation implementation." BACKWARD_INPUT_SUGGEST = "Check whether the forward computation result is affected." BACKWARD_OUTPUT_SUGGEST = "This is a backward API computation error. Check the computation implementation." - - # cannot be fixed api + BATCH_NORM_SUGGEST = "Torch API batch_norm input not fixed, the following suggestions may fix it:\n" \ + "1. If use torch.nn.functional.batch_norm, you can set parameter training=False.\n" \ + "2. If use torch.nn.BatchNormXXX, you can set parameter affine=False.\n" \ + "3. Use seed_all(mode=True) to enable deterministic computing." + DETERMINISTIC_SUGGEST = "This torch api may be uncertainty in the calculation, " \ + "can seed_all(mode=True) to enable deterministic computing." + + FUNC_BATCH_NORM = "Functional_batch_norm" + FORWARD_INPUT_1 = "forward_input.1" + NEED_DETERMINISTIC_API = ["conv2d", "conv3d", "matmul", "nll_loss", "layer_norm", "lstm"] BATCH_NORM = "batch_norm" # name keyword diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/common/utils.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/common/utils.py index fa66cafaa1269a643df4437c8e2c4841f6267b9e..6197fcd1b44cb301496c37e641134c723acf49f3 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/common/utils.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/common/utils.py @@ -80,6 +80,8 @@ class Const: API_LIST = "api_list" API_STACK = "api_stack" DUMP_MODE = [ALL, LIST, RANGE, STACK, ACL, API_LIST, API_STACK] + AUTO = "auto" + ONLINE_DUMP_MODE = [ALL, LIST, AUTO, OFF] API_PATTERN = r"^[A-Za-z0-9]+[_]+([A-Za-z0-9]+[_]*[A-Za-z0-9]+)[_]+[0-9]+[_]+[A-Za-z0-9]+" WRITE_FLAGS = os.O_WRONLY | os.O_CREAT @@ -93,6 +95,9 @@ class Const: FILE_NAME_LENGTH = 255 DIRECTORY_LENGTH = 4096 + # env dump path + ASCEND_WORK_PATH = "ASCEND_WORK_PATH" + DUMP_DIR = "dump_data" class CompareConst: """ @@ -194,13 +199,16 @@ class DumpException(CompareException): def make_dump_path_if_not_exists(dump_path): - # 之前应该已经验证过dump_path的上层文件夹存在 - dump_root, dump_dir = os.path.split(dump_path) if not os.path.exists(dump_path): - Path(dump_path).mkdir(mode=0o750, exist_ok=True) + try: + Path(dump_path).mkdir(mode=0o750, exist_ok=True, parents=True) + except OSError as ex: + print_error_log( + 'Failed to create {}.Please check the path permission or disk space .{}'.format(dump_path, str(ex))) + raise CompareException(CompareException.INVALID_PATH_ERROR) else: if not os.path.isdir(dump_path): - print_error_log((f"{dump_path} already exists and is not a directory.")) + print_error_log('{} already exists and is not a directory.'.format(dump_path)) def _print_log(level, msg): @@ -241,6 +249,10 @@ def print_warn_log(warn_msg): def check_mode_valid(mode, scope=[], api_list=[]): + if not isinstance(scope, list): + raise ValueError("scope param set invalid, it's must be a list.") + elif not isinstance(api_list, list): + raise ValueError("api_list param set invalid, it's must be a list.") mode_check = { Const.ALL: lambda: None, Const.RANGE: lambda: ValueError("set_dump_switch, scope param set invalid, it's must be [start, end].") if len(scope) != 2 else None, @@ -273,7 +285,7 @@ def check_dump_mode_valid(dump_mode): if 'forward' not in dump_mode and 'backward' not in dump_mode: dump_mode.extend(['forward', 'backward']) if 'all' in dump_mode or set(["forward", "backward", "input", "output"]).issubset(set(dump_mode)): - return ['all'] + return ["forward", "backward", "input", "output"] return dump_mode def check_summary_only_valid(summary_only): @@ -483,16 +495,6 @@ def save_numpy_data(file_path, data): np.save(file_path, data) -def parse_arg_value(values): - """ - parse dynamic arg value of atc cmdline - """ - value_list = [] - for item in values.split(Const.SEMICOLON): - value_list.append(parse_value_by_comma(item)) - return value_list - - def parse_value_by_comma(value): """ parse value by comma, like '1,2,4,8' @@ -528,7 +530,7 @@ def get_time(): def format_value(value): - return '{:.6f}'.format(value) + return '{:.12f}'.format(value) def torch_device_guard(func): diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/compare/acc_compare.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/compare/acc_compare.py index 6683fd1808b47442f0f1243b577677ff1225d05a..1607a0772dfffb941aa58737b4715b6a339c7d7f 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/compare/acc_compare.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/compare/acc_compare.py @@ -144,7 +144,8 @@ def check_type_shape_match(npu_struct, bench_struct): shape_match = npu_shape == bench_shape type_match = npu_type == bench_type if not type_match: - if [npu_type, bench_type] in [["torch.float16", "torch.float32"], ["torch.float32", "torch.float16"]]: + if [npu_type, bench_type] in [["torch.float16", "torch.float32"], ["torch.float32", "torch.float16"], + ["torch.float16", "torch.bfloat16"], ["torch.bfloat16", "torch.float16"]]: type_match = True else: type_match = False diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/debugger/debugger_config.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/debugger/debugger_config.py index 7c92c3c1245cfb9687ed5a37f5491badaf3ee936..7278dfb26e64ec2869f1fc877c7b56bacaca143f 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/debugger/debugger_config.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/debugger/debugger_config.py @@ -3,31 +3,36 @@ from ..common.utils import print_warn_log class DebuggerConfig: - def __init__(self, dump_path, hook_name, rank=None, step=[0]): + def __init__(self, dump_path, hook_name, rank=None, step=[]): self.dump_path = dump_path self.hook_name = hook_name self.rank = rank self.step = step + self.check() if self.step: self.step.sort() - self.check() def check(self): - dump_root = os.path.split(self.dump_path)[0] - if not os.path.exists(dump_root): - raise ValueError("dump path {} does not exist".format(dump_root)) + self._check_hook_name() + self._check_rank() + self._check_step() + return True + + def _check_hook_name(self): if self.hook_name not in ["dump", "overflow_check"]: - raise ValueError("hook_name should be in ['dump', 'overflow_check']".format(self.hook_name)) - if self.rank is not None and not isinstance(self.rank, int): - raise ValueError("rank {} should be int".format(self.rank)) - elif isinstance(self.rank, int): - print_warn_log(f"Rank argument is provided. Only rank {self.rank} data will be dumpped.") + raise ValueError(f"hook_name should be in ['dump', 'overflow_check'], got {self.hook_name}") + + def _check_rank(self): + if self.rank is not None: + if not isinstance(self.rank, int) or self.rank < 0: + raise ValueError(f"rank {self.rank} must be a positive integer.") + else: + print_warn_log(f"Rank argument is provided. Only rank {self.rank} data will be dumpped.") + + def _check_step(self): if not isinstance(self.step, list): - raise ValueError("step {} should be list".format(self.step)) - if len(self.step) == 0: - raise ValueError("step {} should not be empty".format(self.step)) + raise ValueError(f"step {self.step} should be list") for s in self.step: if not isinstance(s, int): - raise ValueError("step element {} should be int".format(s)) - return True + raise ValueError(f"step element {s} should be int") diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/debugger/precision_debugger.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/debugger/precision_debugger.py index e9db590c67860d18cfab3b4ec3cb945e77030e79..3dadd540dc4dcc47aec7138adc78c8c56991889c 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/debugger/precision_debugger.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/debugger/precision_debugger.py @@ -1,6 +1,7 @@ import os -from ..common.utils import Const, make_dump_path_if_not_exists, print_error_log, print_info_log -from ..dump.dump import DumpUtil, acc_cmp_dump, write_to_disk +import torch +from ..common.utils import Const, check_switch_valid, generate_compare_script, check_is_npu +from ..dump.dump import DumpUtil, acc_cmp_dump, write_to_disk, get_pkl_file_path from ..dump.utils import set_dump_path, set_dump_switch_print_info, generate_dump_path_str, \ set_dump_switch_config, set_backward_input from ..overflow_check.utils import OverFlowUtil @@ -13,74 +14,98 @@ from .debugger_config import DebuggerConfig class PrecisionDebugger: first_start = True hook_func = None + config = None - # 提供两种使用方式:逐个传参和构造config后传config,看哪种使用方式更受欢迎,之后只保留一种 - def __init__(self, dump_path=None, hook_name=None, rank=None, step=[0], config=None): - if config is None: - if dump_path is None or hook_name is None: - err_msg = "You must provide dump_path and hook_name argument to PrecisionDebugger\ - when config is not provided." - raise Exception(err_msg) - self.config = DebuggerConfig(dump_path, hook_name, rank, step) - else: - self.config = config - print_info_log("Debugger gets config, it will override preceding arguments.") - - self.configure_hook = self.get_configure_hook(config.hook_name) + def __init__(self, dump_path=None, hook_name=None, rank=None, step=[], enable_dataloader=False): + if hook_name is None: + err_msg = "You must provide hook_name argument to PrecisionDebugger\ + when config is not provided." + raise Exception(err_msg) + self.config = DebuggerConfig(dump_path, hook_name, rank, step) + self.configure_hook = self.get_configure_hook(self.config.hook_name) self.configure_hook() - DumpUtil.target_iter = config.step - DumpUtil.target_rank = config.rank - make_dump_path_if_not_exists(config.dump_path) - set_dump_path(config.dump_path) - if config.hook_name == "overflow_check": - PrecisionDebugger.hook_func = overflow_check - else: - PrecisionDebugger.hook_func = acc_cmp_dump + DumpUtil.target_iter = self.config.step + DumpUtil.target_rank = self.config.rank + set_dump_path(self.config.dump_path) + PrecisionDebugger.hook_func = overflow_check if self.config.hook_name == "overflow_check" else acc_cmp_dump + if enable_dataloader: + DumpUtil.iter_num -= 1 + torch.utils.data.dataloader._BaseDataLoaderIter.__next__ = iter_tracer(torch.utils.data.dataloader._BaseDataLoaderIter.__next__) def get_configure_hook(self, hook_name): - if hook_name == "dump": - return self.configure_full_dump - elif hook_name == "overflow_check": - return self.configure_overflow_dump - else: - raise ValueError("hook name {} is not in ['dump', 'overflow_check']".format(hook_name)) + hook_dict = {"dump": self.configure_full_dump, "overflow_check": self.configure_overflow_dump} + return hook_dict.get(hook_name, lambda: ValueError("hook name {} is not in ['dump', 'overflow_check']".format(hook_name))) def configure_full_dump(self, mode='api_stack', scope=[], api_list=[], filter_switch=Const.ON, input_output_mode=[Const.ALL], acl_config=None, backward_input=[], summary_only=False): set_dump_switch_config(mode=mode, scope=scope, api_list=api_list, filter_switch=filter_switch, dump_mode=input_output_mode, summary_only=summary_only) - if mode == 'acl' and acl_config is None: - raise ValueError("acl_config must be configured when mode is 'acl'") - elif mode == 'acl' and acl_config is not None: + if mode == 'acl': + if not acl_config: + raise ValueError("acl_config must be configured when mode is 'acl'") DumpUtil.dump_config = acl_config - if mode == 'acl' and 'backward' in scope and not backward_input: - raise ValueError("backward_input must be configured when mode is 'acl' and scope contains 'backward'") - elif mode == 'acl' and 'backward' in scope and backward_input: - set_backward_input(backward_input) + if not scope or not isinstance(scope, list) or len(scope) != 1: + raise ValueError("scope must be congfigured as a list with one api name") + if isinstance(scope[0], str) and 'backward' in scope[0] and not backward_input: + raise ValueError("backward_input must be configured when scope contains 'backward'") + elif 'backward' in scope[0]: + set_backward_input(backward_input) - def configure_overflow_dump(self, mode="api", acl_config=None, overflow_nums=1): + def configure_overflow_dump(self, mode="api", acl_config=None, overflow_nums=1, filter_switch = Const.OFF): if mode == "acl": DumpUtil.dump_switch_mode = mode DumpUtil.dump_config = acl_config if acl_config is None: raise ValueError("acl_config must be configured when mode is 'acl'") - if isinstance(overflow_nums, int): + if isinstance(overflow_nums, int) and overflow_nums >= -1: OverFlowUtil.overflow_nums = overflow_nums else: raise ValueError("overflow_nums must be int") + check_switch_valid(filter_switch) + OverFlowUtil.overflow_filter_switch = filter_switch @classmethod def start(cls): - if cls.first_start: - register_hook_core(cls.hook_func) - cls.first_start = False - DumpUtil.dump_switch = "ON" - dump_path_str = generate_dump_path_str() - set_dump_switch_print_info("ON", DumpUtil.dump_switch_mode, dump_path_str) + if DumpUtil.iter_num in DumpUtil.target_iter or len(DumpUtil.target_iter) == 0: + if cls.first_start: + register_hook_core(cls.hook_func) + cls.first_start = False + DumpUtil.dump_switch = "ON" + OverFlowUtil.overflow_check_switch = "ON" + dump_path_str = generate_dump_path_str() + set_dump_switch_print_info("ON", DumpUtil.dump_switch_mode, dump_path_str) + elif len(DumpUtil.target_iter) != 0: + if DumpUtil.iter_num > max(DumpUtil.target_iter): + PrecisionDebugger.stop() + raise Exception("ptdbg: exit after iteration {}".format(DumpUtil.target_iter)) + else: + cls.stop() @classmethod def stop(cls): DumpUtil.dump_switch = "OFF" + OverFlowUtil.overflow_check_switch = "OFF" dump_path_str = generate_dump_path_str() set_dump_switch_print_info("OFF", DumpUtil.dump_switch_mode, dump_path_str) write_to_disk() + if check_is_npu() and DumpUtil.dump_switch_mode in [Const.ALL, Const.API_STACK, Const.LIST, Const.RANGE]: + generate_compare_script(DumpUtil.dump_data_dir, get_pkl_file_path(), DumpUtil.dump_switch_mode) + + @classmethod + def step(cls): + DumpUtil.dump_init_enable = True + DumpUtil.iter_num += 1 + HOOKModule.module_count = {} + + @staticmethod + def incr_iter_num_maybe_exit(): + PrecisionDebugger.step() + PrecisionDebugger.start() + +def iter_tracer(func): + def func_wrapper(*args, **kwargs): + PrecisionDebugger.stop() + result = func(*args, **kwargs) + PrecisionDebugger.incr_iter_num_maybe_exit() + return result + return func_wrapper \ No newline at end of file diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/dump/dump.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/dump/dump.py index 2b1b5a967754ed08d789510ef6dd7ae0a7da8ada..53001c1571416a53a007a378be6d10b3fbb7f2a4 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/dump/dump.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/dump/dump.py @@ -18,11 +18,12 @@ import inspect import json import os -import stat import numpy as np import torch import threading +from pathlib import Path + try: import torch_npu except ImportError: @@ -30,7 +31,7 @@ except ImportError: else: is_gpu = False -from .utils import DumpUtil, _set_dump_switch4api_list, make_dump_data_dir, get_tensor_rank, create_dirs_if_not_exist +from .utils import DumpUtil, check_if_in_api_list, make_dump_data_dir, get_tensor_rank, create_dirs_if_not_exist from ..common.utils import print_warn_log, Const, print_info_log, modify_dump_path from ..dump.utils import check_writable @@ -42,6 +43,7 @@ backward_threading_id = 0 api_list = [] thread_lock = threading.Lock() pkl_name = "" +rank = os.getpid() multi_output_apis = ["_sort_", "npu_flash_attention"] class DataInfo(object): @@ -143,51 +145,81 @@ def dump_data(dump_file_name, dump_step, prefix, data_info): def dump_stack_info(name_template, dump_file): stack_str = [] - for (_, path, line, func, code, _) in inspect.stack()[3:]: - if code: - stack_line = [path, str(line), func, code[0].strip() if code else code] - else: - stack_line = [path, str(line), func, code] - stack_str.append(stack_line) + try: + for (_, path, line, func, code, _) in inspect.stack()[3:]: + if code: + stack_line = [path, str(line), func, code[0].strip() if code else code] + else: + stack_line = [path, str(line), func, code] + stack_str.append(stack_line) + except Exception as e: + print_warn_log("Dump stack info failed, error: {}".format(e)) + stack_str.append('') prefix = name_template.format("stack_info") if DumpUtil.dump_switch_mode in Const.DUMP_MODE: if json_dump_condition(prefix): - if Const.ALL in DumpUtil.dump_mode: + complement_set = set(['forward', 'backward', 'input', 'output']) - set(DumpUtil.dump_mode) + if not any(mode in prefix for mode in complement_set): api_list.append([prefix, stack_str]) - else: - for mode in DumpUtil.dump_mode: - if mode in prefix: - api_list.append([prefix, stack_str]) else: api_list.append([prefix, stack_str]) def dump_api_tensor(dump_step, in_feat, name_template, out_feat, dump_file): - if Const.BACKWARD in name_template and Const.FORWARD not in DumpUtil.dump_mode: + if Const.BACKWARD in name_template and Const.BACKWARD in DumpUtil.dump_mode: if 'input' in DumpUtil.dump_mode: dump_tensor(out_feat, name_template.format("input"), dump_step, dump_file) if 'output' in DumpUtil.dump_mode: dump_tensor(in_feat, name_template.format("output"), dump_step, dump_file) - if Const.ALL in DumpUtil.dump_mode: - dump_tensor(out_feat, name_template.format("input"), dump_step, dump_file) - dump_tensor(in_feat, name_template.format("output"), dump_step, dump_file) - elif Const.BACKWARD not in name_template and Const.BACKWARD not in DumpUtil.dump_mode: + elif Const.BACKWARD not in name_template and Const.FORWARD in DumpUtil.dump_mode: if 'input' in DumpUtil.dump_mode: dump_tensor(in_feat, name_template.format("input"), dump_step, dump_file) if 'output' in DumpUtil.dump_mode: dump_tensor(out_feat, name_template.format("output"), dump_step, dump_file) - if Const.ALL in DumpUtil.dump_mode: - dump_tensor(in_feat, name_template.format("input"), dump_step, dump_file) - dump_tensor(out_feat, name_template.format("output"), dump_step, dump_file) +def rename_(): + global rank + global pkl_name + if rank is not None and pkl_name is not None: + if DumpUtil.target_iter: + dir_name = os.path.join(DumpUtil.dump_root, "step{}".format(DumpUtil.iter_num), "rank{}".format(os.getpid())) + new_name = os.path.join(DumpUtil.dump_root, "step{}".format(DumpUtil.iter_num), "rank{}".format(rank)) + else: + dir_name = os.path.join(DumpUtil.dump_root, "rank{}".format(os.getpid())) + new_name = os.path.join(DumpUtil.dump_root, "rank{}".format(rank)) + if not os.path.exists(new_name) and os.path.exists(dir_name): + _, file_name = os.path.split(pkl_name) + os.rename(dir_name, new_name) + pkl_name = os.path.join(new_name, file_name) def dump_acc_cmp(name, in_feat, out_feat, dump_step, module): dump_file = DumpUtil.get_dump_path() dump_file = modify_dump_path(dump_file, DumpUtil.dump_switch_mode) - _set_dump_switch4api_list(name) + if DumpUtil.dump_switch_mode == Const.API_LIST and not check_if_in_api_list(name): + return if DumpUtil.get_dump_switch(): - rank = get_tensor_rank(in_feat, out_feat) + global rank + dump_dir, dump_filename = os.path.split(dump_file) + if DumpUtil.target_iter: + dump_dir = os.path.join(dump_dir, "step{}".format(DumpUtil.iter_num)) + if not os.path.exists(dump_dir): + Path(dump_dir).mkdir(mode=0o750, exist_ok=True) + dump_file = os.path.join(dump_dir, dump_filename) + rank_this = get_tensor_rank(in_feat, out_feat) + DumpUtil.dump_root = os.path.dirname(DumpUtil.dump_path) + if rank_this is not None and rank != rank_this: + rank = rank_this + rename_() + if not DumpUtil.dump_init_enable: + if '.pkl' in dump_filename: + npy_dir = dump_filename[:-4] + else: + npy_dir = dump_filename + if DumpUtil.target_iter: + DumpUtil.dump_data_dir = os.path.join(DumpUtil.dump_root, "step{}".format(DumpUtil.iter_num), "rank{}".format(rank), npy_dir) + else: + DumpUtil.dump_data_dir = os.path.join(DumpUtil.dump_root, "rank{}".format(rank), npy_dir) if DumpUtil.target_rank is not None: if rank != DumpUtil.target_rank: return @@ -306,6 +338,7 @@ def acc_cmp_dump(name, **kwargs): def write_to_disk(): + global api_list if api_list: with open(pkl_name, 'a') as f: try: @@ -313,7 +346,7 @@ def write_to_disk(): f.write('\n') except: raise Exception("write to disk failed") - + api_list = [] def get_pkl_file_path(): return pkl_name diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/dump/utils.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/dump/utils.py index bc2313448eb29a4b4eae2577acd0696d911d0044..0657c7b7cc383e21927a5534fd2675a125b16e47 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/dump/utils.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/dump/utils.py @@ -1,14 +1,13 @@ import os import shutil import sys -import re from pathlib import Path import torch from ..dump import dump from ..common.utils import print_error_log, CompareException, DumpException, Const, get_time, print_info_log, \ check_mode_valid, get_api_name_from_matcher, check_switch_valid, check_dump_mode_valid, check_summary_only_valid, generate_compare_script, \ - check_is_npu, check_file_valid + check_is_npu, check_file_valid, make_dump_path_if_not_exists from ..common.version import __version__ @@ -17,6 +16,7 @@ range_begin_flag, range_end_flag = False, False class DumpUtil(object): + dump_root = None dump_data_dir = None dump_path = None dump_switch = None @@ -31,21 +31,10 @@ class DumpUtil(object): dump_config = None dataloader_iter = 0 target_iter = None + iter_num = 0 target_rank = None summary_only = False - @staticmethod - def incr_iter_num_maybe_exit(): - if DumpUtil.target_iter is None: - return - if DumpUtil.dataloader_iter == DumpUtil.target_iter: - set_dump_switch("ON") - elif DumpUtil.dataloader_iter > DumpUtil.target_iter: - raise Exception("Ptdbg: exit after iteration {}".format(DumpUtil.target_iter)) - else: - set_dump_switch("OFF") - DumpUtil.dataloader_iter += 1 - @staticmethod def set_dump_path(save_path): DumpUtil.dump_path = save_path @@ -141,15 +130,10 @@ class DumpUtil(object): def set_dump_path(fpath=None, dump_tag='ptdbg_dump'): - if fpath is None: - raise RuntimeError("set_dump_path '{}' error, please set a valid filename".format(fpath)) - return + fpath = load_env_dump_path(fpath) check_file_valid(fpath) real_path = os.path.realpath(fpath) - if not os.path.isdir(real_path): - print_error_log( - "set_dump_path '{}' error, the path is not a directory please set a valid directory.".format(real_path)) - raise DumpException(DumpException.INVALID_PATH_ERROR) + make_dump_path_if_not_exists(real_path) DumpUtil.set_dump_path(real_path) DumpUtil.dump_dir_tag = dump_tag @@ -171,7 +155,7 @@ def get_tensor_rank(in_feat, out_feat): if in_rank is None: out_rank = get_tensor_rank_single(out_feat) if out_rank is None: - return 0 + return None return out_rank return in_rank @@ -205,6 +189,8 @@ def set_dump_switch(switch, mode=Const.ALL, scope=[], api_list=[], filter_switch except (CompareException, AssertionError) as err: print_error_log(str(err)) sys.exit() + if not DumpUtil.dump_path: + set_dump_path() DumpUtil.set_dump_switch(switch, summary_only=summary_only) dump_path_str = generate_dump_path_str() if switch == "OFF": @@ -242,10 +228,13 @@ def set_dump_switch_print_info(switch, mode, dump_path_str): print_info_log("The number of matched dump is {}".format(dump_count)) -def _set_dump_switch4api_list(name): - if DumpUtil.dump_api_list: - api_name = get_api_name_from_matcher(name) - DumpUtil.dump_switch = "ON" if api_name in DumpUtil.dump_api_list else "OFF" +def check_if_in_api_list(name): + if not DumpUtil.dump_api_list: + return False + for api in DumpUtil.dump_api_list: + if api.lower() in name.lower(): + return True + return False def set_backward_input(backward_input): @@ -267,7 +256,7 @@ def make_dump_data_dir(dump_file_name): def make_dump_dirs(): dump_file_name, dump_file_name_body = "dump.pkl", "dump" - dump_root_dir = DumpUtil.dump_path if DumpUtil.dump_path else "./" + dump_root_dir = load_env_dump_path(DumpUtil.dump_path) tag_dir = os.path.join(dump_root_dir, DumpUtil.dump_dir_tag + f'_v{__version__}') Path(tag_dir).mkdir(mode=0o750, parents=True, exist_ok=True) DumpUtil.dump_dir = tag_dir @@ -282,3 +271,20 @@ def check_writable(dump_file): dump_file)) raise DumpException(DumpException.INVALID_PATH_ERROR) + +def load_env_dump_path(dump_path): + if not dump_path: + dump_path = os.getenv(Const.ASCEND_WORK_PATH) + if dump_path: + try: + dump_path = os.path.join(str(dump_path), Const.DUMP_DIR) + except TypeError: + print_error_log("Generating dump path from environment variables ASCEND_WORK_PATH failed.") + raise DumpException(DumpException.INVALID_PATH_ERROR) + else: + print_error_log("Dump path is None, you can configure it in the following ways:\n" + "1. Configure set_dump_path function.\n" + "2. Configure the dump_path parameter of PrecisionDebugger.\n" + "3. Set environment variables ASCEND_WORK_PATH.") + raise DumpException(DumpException.INVALID_PATH_ERROR) + return dump_path diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/hook_module.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/hook_module.py index 0de75ffe9af22a6b4ecb7b44148eefd00357b68b..a3cb10bf4f7dfff8a9dbf8021de82391ed3ddcea 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/hook_module.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/hook_module.py @@ -22,31 +22,43 @@ import torch import torch.nn as nn import torch.utils.hooks as full_hooks -module_count = {} - +g_stop_hook = False class HOOKModule(nn.Module): - + module_count = {} def __init__(self, hook) -> None: super(HOOKModule, self).__init__() self.has_overflow = False self.input_args = tuple() self.input_kwargs = dict() - prefix = "" - if hasattr(self, "prefix_op_name_"): - prefix = self.prefix_op_name_ - if prefix not in module_count: - module_count[prefix] = 1 - prefix += '0_' - else: - module_count[prefix] += 1 - prefix = prefix + str(module_count[prefix] - 1) + '_' + if not g_stop_hook: + prefix = "" + if hasattr(self, "prefix_op_name_"): + prefix = self.prefix_op_name_ + + if prefix not in HOOKModule.module_count: + HOOKModule.module_count[prefix] = 1 + prefix += '0_' + else: + HOOKModule.module_count[prefix] += 1 + prefix = prefix + str(HOOKModule.module_count[prefix] - 1) + '_' - self.register_forward_hook(hook(prefix + "forward")) - self.register_backward_hook(hook(prefix + "backward")) + self.register_forward_hook(hook(prefix + "forward")) + self.register_backward_hook(hook(prefix + "backward")) def __call__(self, *input, **kwargs): + changed = False + global g_stop_hook + if not g_stop_hook: + g_stop_hook = True + changed = True + result = self._call_func(*input, **kwargs) + if changed: + g_stop_hook = False + return result + + def _call_func(self, *input, **kwargs): full_backward_hooks, non_full_backward_hooks = [], [] if len(self._backward_hooks) > 0: full_backward_hooks, non_full_backward_hooks = self._get_backward_hooks() @@ -91,4 +103,4 @@ class HOOKModule(nn.Module): functools.update_wrapper(wrapper, hook) grad_fn.register_hook(wrapper) self._maybe_warn_non_full_backward_hook(input, result, grad_fn) - return result + return result \ No newline at end of file diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/register_hook.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/register_hook.py index 85a5b3516d861a8a55ca1e2f64cc89d189e5b127..0d69d465d134688d0ac1777972750faa15869393 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/register_hook.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/register_hook.py @@ -19,8 +19,9 @@ import functools import os import torch +import torch.distributed as dist -from . import wrap_torch, wrap_functional, wrap_tensor, wrap_vf +from . import wrap_torch, wrap_functional, wrap_tensor, wrap_vf, wrap_distributed from .hook_module import HOOKModule from .wrap_functional import remove_dropout from ..common.utils import check_file_or_directory_path, print_error_log, CompareException, Const, \ @@ -55,6 +56,16 @@ def initialize_hook(hook): if attr_name.startswith("wrap_"): setattr(torch.nn.functional, attr_name[5:], getattr(wrap_functional.HOOKFunctionalOP, attr_name)) + wrap_distributed.wrap_distributed_ops_and_bind(hook) + for attr_name in dir(wrap_distributed.HOOKDistributedOP): + if attr_name.startswith("wrap_"): + setattr(dist, attr_name[5:], getattr(wrap_distributed.HOOKDistributedOP, attr_name)) + setattr(dist.distributed_c10d, attr_name[5:], getattr(wrap_distributed.HOOKDistributedOP, attr_name)) + if not is_gpu: + setattr(torch_npu.distributed, attr_name[5:], getattr(wrap_distributed.HOOKDistributedOP, attr_name)) + setattr(torch_npu.distributed.distributed_c10d, attr_name[5:], + getattr(wrap_distributed.HOOKDistributedOP, attr_name)) + wrap_vf.wrap_vf_ops_and_bind(hook) for attr_name in dir(wrap_vf.HOOKVfOP): if attr_name.startswith("wrap_"): @@ -66,9 +77,12 @@ def initialize_hook(hook): if attr_name.startswith("wrap_"): setattr(torch_npu, attr_name[5:], getattr(wrap_npu_custom.HOOKNpuOP, attr_name)) -def add_clear_overflow(func): +def add_clear_overflow(func, pid): first_module = True def clear_overflow_wrapper(*args, **kwargs): + child_pid = os.getpid() + if pid != child_pid: + return func(*args, **kwargs) nonlocal first_module if first_module: torch_npu._C._clear_overflow_npu() @@ -107,7 +121,7 @@ def register_hook_core(hook, **kwargs): "please check the version of software torch_npu.") # In NPU scene, clear the overflow flag before overflow detection if need_clear: - HOOKModule.__init__ = add_clear_overflow(HOOKModule.__init__) + HOOKModule.__init__ = add_clear_overflow(HOOKModule.__init__, pid) elif "acc_cmp_dump" in hook_name: remove_dropout() diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/support_wrap_ops.yaml b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/support_wrap_ops.yaml index cbc3dd21611abfa2c0ff22d217d367faee7dfac5..e0c5c4f52491ddcbbe5d84ea2b651427391f3cca 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/support_wrap_ops.yaml +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/support_wrap_ops.yaml @@ -1051,4 +1051,18 @@ torch_npu: - npu_diou_backward - npu_sign_bits_pack - npu_sign_bits_unpack - - npu_flash_attention \ No newline at end of file + - npu_flash_attention + +distributed: + - send + - recv + - broadcast + - all_reduce + - reduce + - all_gather + - gather + - batch_isend_irecv + - isend + - irecv + - scatter + - reduce_scatter \ No newline at end of file diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/wrap_distributed.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/wrap_distributed.py new file mode 100644 index 0000000000000000000000000000000000000000..c96c0efe317b2340e5b6650d31d20cae669bb6e7 --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/hook_module/wrap_distributed.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +# Copyright (C) 2022-2023. Huawei Technologies Co., Ltd. All rights reserved. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" + +import os + +import torch.distributed as dist +import yaml + +from .hook_module import HOOKModule +from ..common.utils import torch_device_guard + + +cur_path = os.path.dirname(os.path.realpath(__file__)) +yaml_path = os.path.join(cur_path, "support_wrap_ops.yaml") +with open(yaml_path, 'r') as f: + WrapDistributedOps = yaml.safe_load(f).get('distributed') + + +distributed_func = {} +for f in dir(dist): + distributed_func[f] = getattr(dist, f) + + +def get_distributed_ops(): + global WrapDistributedOps + _all_distributed_ops = dir(dist) + return set(WrapDistributedOps) & set(_all_distributed_ops) + + +class HOOKDistributedOP(object): + pass + + +class DistributedOPTemplate(HOOKModule): + def __init__(self, op_name, hook): + self.op_name_ = op_name + self.prefix_op_name_ = "Distributed_" + str(op_name) + "_" + super().__init__(hook) + + @torch_device_guard + def forward(self, *args, **kwargs): + return distributed_func.get(self.op_name_)(*args, **kwargs) + + +def wrap_distributed_op(op_name, hook): + def distributed_op_template(*args, **kwargs): + return DistributedOPTemplate(op_name, hook)(*args, **kwargs) + + return distributed_op_template + + +def wrap_distributed_ops_and_bind(hook): + _distributed_ops = get_distributed_ops() + for op_name in _distributed_ops: + setattr(HOOKDistributedOP, "wrap_" + str(op_name), wrap_distributed_op(op_name, hook)) diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/.keep b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/.keep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/__init__.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..22fbaf78b415325f9459b8b44b79c12510937c4a --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/__init__.py @@ -0,0 +1,6 @@ +from signal import signal, SIGPIPE, SIG_DFL +from .dispatch import PtdbgDispatch +signal(SIGPIPE, SIG_DFL) + + +__all__ = ["PtdbgDispatch"] diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/dispatch.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/dispatch.py new file mode 100644 index 0000000000000000000000000000000000000000..6b3c98c33e8d8b607ff441ea0086dfab202ea15e --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/dispatch.py @@ -0,0 +1,217 @@ +import os +import time +import yaml +import json +from pathlib import Path +from multiprocessing import Manager, Pool +import torch +from torch.utils._python_dispatch import TorchDispatchMode + +try: + import torch_npu +except ImportError: + is_npu = False +else: + is_npu = True + +from ..common.utils import Const, CompareConst, add_time_as_suffix, check_file_or_directory_path +from ..common.version import __version__ +from .dump_compare import dispatch_workflow, dispatch_multiprocess, error_call, TimeStatistics, \ + DispatchRunParam, save_csv +from .utils import get_callstack, data_to_cpu, logger_debug, logger_error, logger_warn, logger_logo, get_sys_info + + +class PtdbgDispatch(TorchDispatchMode): + def __init__(self, dump_mode=Const.OFF, api_list=None, debug=False, dump_path=None, tag=None, process_num=0): + super(PtdbgDispatch, self).__init__() + logger_logo() + if not is_npu: + logger_error("Please confirm you run environment installed torch_npu!") + return + + if dump_path is None: + logger_error("Please set dump_path when dump_mode is config!") + check_file_or_directory_path(dump_path, True) + + self.device_id = torch_npu._C._npu_getDevice() + self.dump_mode = dump_mode + self.dump_api_list = self.get_dump_api(api_list) + self.debug_flag = debug + self.api_index = 0 + self.single_api_index_dict = {} + self.device_dump_path_cpu = None + self.device_dump_path_npu = None + self.all_summery = [] + self.call_stack_list = [] + # guarantee file uniqueness + time.sleep(1) + time_now = time.strftime("%Y%m%d%H%M%S", time.localtime(time.time())) + if tag is None: + dir_name = f'ptdbg_v{__version__}_rank{self.device_id}_{time_now}' + else: + dir_name = f'ptdbg_v{__version__}_{tag}_rank{self.device_id}_{time_now}' + self.root_path = os.path.join(os.path.realpath(dump_path), dir_name) + self.root_cpu_path = os.path.join(self.root_path, f'cpu') + self.root_npu_path = os.path.join(self.root_path, f'npu') + file_name = add_time_as_suffix(f'compare_result_rank{self.device_id}') + self.csv_path = os.path.join(self.root_path, file_name) + Path(self.root_cpu_path).mkdir(mode=0o750, parents=True, exist_ok=True) + Path(self.root_npu_path).mkdir(mode=0o750, parents=True, exist_ok=True) + + self.aten_ops_blacklist = [] + yaml_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "unsupport_torch_ops.yaml") + with open(yaml_path, 'r') as f: + self.aten_ops_blacklist = yaml.safe_load(f).get('aten') + + self.process_num = process_num + self.lock = None + if process_num > 0: + self.pool = Pool(process_num) + self.lock = Manager().Lock() + + if debug: + logger_debug(f'Main pid:{os.getpid()} device:{self.device_id} dump_list:{self.dump_api_list} ' + f'dump_mode:{self.dump_mode} cpu_path[{self.root_cpu_path}], npu_path[{self.root_npu_path}], ' + f'process[{process_num}]') + + @staticmethod + def get_dump_api(api_list): + aten_api_list = dir(torch.ops.aten) + dump_api_list = [] + if api_list is not None: + for aten_api in api_list: + if aten_api in aten_api_list: + dump_api_list.append(aten_api) + else: + logger_warn(f'{aten_api} is not aten api will not dump, please refer to torch.ops.aten') + return dump_api_list + + def get_dump_flag(self, aten_api): + dump_flag = False + auto_dump_flag = False + if self.dump_mode == Const.ALL: + dump_flag = True + if self.dump_mode == Const.LIST and aten_api in self.dump_api_list: + dump_flag = True + if self.dump_mode == Const.AUTO: + auto_dump_flag = True + return dump_flag, auto_dump_flag + + @staticmethod + def check_fun(func, run_param): + if hasattr(torch.ops.aten, run_param.aten_api): + aten_func = getattr(torch.ops.aten, run_param.aten_api) + if hasattr(aten_func, run_param.aten_api_overload_name): + aten_overload_func = getattr(aten_func, run_param.aten_api_overload_name) + if id(aten_overload_func) == id(func): + run_param.func_namespace = "aten" + return True + return False + + def __exit__(self, exc_type, exc_val, exc_tb): + super().__exit__(exc_type, exc_val, exc_tb) + + if not is_npu: + return + logger_debug(f'start write compare csv: Rank[{self.device_id}], Pid[{os.getpid()}') + + if self.process_num > 0: + self.pool.close() + self.pool.join() + summery_path = os.path.join(self.root_cpu_path, f'summery.json') + if not os.path.exists(summery_path): + logger_error("Please check train log, An exception may have occurred!") + return + check_file_or_directory_path(summery_path, False) + fp_handle = open(summery_path, "r") + while True: + json_line_data = fp_handle.readline() + if json_line_data == '\n': + continue + if len(json_line_data) == 0: + break + msg = json.loads(json_line_data) + self.all_summery[msg[0]] = msg[1] + fp_handle.close() + + if self.debug_flag: + input_num = 0 + output_num = 0 + total_num = 0 + + for list_data in self.all_summery: + for data in list_data: + logger_debug(f'summery: Device[{self.device_id}], Pid[{os.getpid()}], Data[{data}]') + if "_input" in data[CompareConst.NPU_NAME]: + input_num = input_num + 1 + if "_output" in data[CompareConst.NPU_NAME]: + output_num = output_num + 1 + total_num = total_num + 1 + logger_debug(f'Dispatch exit: Device[{self.device_id}], Pid[{os.getpid()} Input[{input_num}] ' + f'Output[{output_num}] Total[{total_num}] API_Total[{self.api_index}]]') + + save_csv(self.all_summery, self.call_stack_list, self.csv_path) + + def __torch_dispatch__(self, func, types, args=(), kwargs=None): + if not is_npu: + logger_error("Please confirm you run environment installed torch_npu!") + return func(*args, **kwargs) + + aten_api = func.__name__.split(".")[0] + aten_api_overload_name = func.__name__.split(".")[1] + + if aten_api in self.aten_ops_blacklist: + npu_out = func(*args, **kwargs) + return npu_out + + call_stack = get_callstack() + self.call_stack_list.append(call_stack) + self.api_index += 1 + if aten_api not in self.single_api_index_dict: + self.single_api_index_dict[aten_api] = 1 + else: + self.single_api_index_dict[aten_api] += 1 + + run_param = DispatchRunParam(self.debug_flag, self.device_id, self.root_npu_path, self.root_cpu_path, + self.process_num) + run_param.dump_flag, run_param.auto_dump_flag = self.get_dump_flag(aten_api) + run_param.func_name = func.__name__ + run_param.aten_api = aten_api + run_param.aten_api_overload_name = aten_api_overload_name + run_param.single_api_index = self.single_api_index_dict[aten_api] + run_param.api_index = self.api_index + + if self.debug_flag: + logger_debug(f'Dispatch Info: Rank[{self.device_id}], Pid[{os.getpid()}], Func[{func.__name__}], ' + f'Name[{run_param.aten_api}_{run_param.single_api_index}], ' + f'Count[{self.api_index}], Sys[{get_sys_info()}]') + + cpu_args = [] + cpu_kwargs = [] + data_to_cpu(args, 0, cpu_args) + data_to_cpu(kwargs, 0, cpu_kwargs) + cpu_args = cpu_args[0] + cpu_kwargs = cpu_kwargs[0] + + with TimeStatistics("NPU RUN", run_param): + npu_out = func(*args, **kwargs) + npu_out_cpu = [] + data_to_cpu(npu_out, 0, npu_out_cpu) + npu_out_cpu = npu_out_cpu[0] + + if self.process_num == 0: + self.all_summery.append([]) + run_param.process_flag = False + dispatch_workflow(run_param, cpu_args, cpu_kwargs, self.all_summery, func, npu_out_cpu, self.lock) + else: + self.lock.acquire() + self.all_summery.append([]) + self.lock.release() + run_param.process_flag = True + if self.check_fun(func, run_param): + self.pool.apply_async(func=dispatch_multiprocess, + args=(run_param, cpu_args, cpu_kwargs, self.all_summery, npu_out_cpu, self.lock), + error_callback=error_call) + else: + logger_error("can not get correct function please set process_num=0") + return npu_out diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/dump_compare.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/dump_compare.py new file mode 100644 index 0000000000000000000000000000000000000000..0dc7555f5a741578306436a07e80a809b10f67d8 --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/dump_compare.py @@ -0,0 +1,298 @@ +import os +import json +import copy +from datetime import datetime +import numpy as np +import pandas as pd +import torch +from ..common.utils import Const, CompareConst, add_time_as_suffix +from ..compare.acc_compare import cosine_similarity, get_max_abs_err, get_max_relative_err, check_accuracy +from .utils import np_save_data, logger_debug, logger_error, logger_user, COLOR_RED, COLOR_GREEN, COLOR_RESET, \ + CSV_COLUMN_NAME + + +class DispatchRunParam: + def __init__(self, debug_flag, device_id, root_npu_path, root_cpu_path, process_num): + # static parameters are initialized by constructors, and dynamic parameters are constructed at run time + self.debug_flag = debug_flag + self.device_id = device_id + self.root_npu_path = root_npu_path + self.root_cpu_path = root_cpu_path + self.process_num = process_num + self.process_flag = None + self.func_name = None + self.func_namespace = None + self.aten_api = None + self.aten_api_overload_name = None + self.single_api_index = None + self.api_index = None + self.dump_flag = None + self.auto_dump_flag = None + + +class TimeStatistics: + def __init__(self, name_tag, run_param, timeout=5): + self.debug = run_param.debug_flag + if self.debug: + self.fun = run_param.func_name + self.device = run_param.device_id + self.process = run_param.process_num + self.index = run_param.single_api_index + self.tag = name_tag + self.timeout = timeout + + def __enter__(self): + if self.debug: + self.time = datetime.now() + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.debug: + cost_time = datetime.now() - self.time + time_cost = f'Time[{self.tag}]: Dev[{self.device}], Pid[{os.getpid()}], Fun[{self.fun}], ' \ + f'Id[{self.index}], time[{cost_time}]' + hot_time_cost = "Hotspot " + time_cost + + if cost_time.total_seconds() > self.timeout: + logger_debug(hot_time_cost) + else: + logger_debug(time_cost) + + +def get_compare_result(npu_data, cpu_data): + # Do not modify the original data, output delay dump + if isinstance(npu_data, torch.Tensor): + npu_npy = npu_data.numpy() + cpu_npy = cpu_data.numpy() + # Do not check dtype, there maybe type cast + if npu_npy.size == 0 or cpu_npy.size == 0: + return "unsupported", 0, 0, "This is empty data, can not compare." + + if npu_npy.shape != cpu_npy.shape: + return CompareConst.SHAPE_UNMATCH, CompareConst.SHAPE_UNMATCH, CompareConst.SHAPE_UNMATCH, \ + "Shape of NPU and bench Tensor do not match. Skipped." + + npu_npy = npu_npy.reshape(-1).astype(float) + cpu_npy = cpu_npy.reshape(-1).astype(float) + err_msg = "" + max_abs_err, _ = get_max_abs_err(npu_npy, cpu_npy) + max_relative_err, message = get_max_relative_err(npu_npy, cpu_npy) + if npu_npy.shape == 0: + return "unsupported", max_abs_err, max_relative_err, "This is type of scalar data, can not compare." + + cos_sim, message = cosine_similarity(npu_npy, cpu_npy) + err_msg += message + + return cos_sim, max_abs_err, max_relative_err, err_msg + else: + npu_npy = np.array(npu_data).astype(float) + cpu_npy = np.array(cpu_data).astype(float) + max_abs_err, _ = get_max_abs_err(npu_npy, cpu_npy) + max_relative_err, _ = get_max_relative_err(npu_npy, cpu_npy) + + return "unsupported", max_abs_err, max_relative_err, "This is type of scalar data, can not compare." + + +def save_summery(run_param, npu_data, cpu_data, prefix, summery_list, compute_flag): + data_dict = dict() + data_dict[CompareConst.NPU_NAME] = prefix + data_dict[CompareConst.BENCH_NAME] = prefix + data_dict[CompareConst.NPU_MAX] = [] + data_dict[CompareConst.NPU_MIN] = [] + data_dict[CompareConst.NPU_MEAN] = [] + data_dict[CompareConst.BENCH_MAX] = [] + data_dict[CompareConst.BENCH_MIN] = [] + data_dict[CompareConst.BENCH_MEAN] = [] + + if isinstance(npu_data, torch.Tensor) and npu_data.numel() != 0: + data_dict[CompareConst.NPU_DTYPE] = str(npu_data.dtype) + data_dict[CompareConst.NPU_SHAPE] = str(list(npu_data.shape)) + data_dict[CompareConst.BENCH_DTYPE] = str(cpu_data.dtype) + data_dict[CompareConst.BENCH_SHAPE] = str(list(cpu_data.shape)) + # the same process can not call torch api which may capture by torch_dispatch + if run_param.process_flag: + data_dict[CompareConst.NPU_MAX] = torch.max(npu_data.float()).numpy().tolist() + data_dict[CompareConst.NPU_MIN] = torch.min(npu_data.float()).numpy().tolist() + data_dict[CompareConst.NPU_MEAN] = torch.mean(npu_data.float()).numpy().tolist() + if compute_flag: + data_dict[CompareConst.BENCH_MAX] = torch.max(cpu_data.float()).numpy().tolist() + data_dict[CompareConst.BENCH_MIN] = torch.min(cpu_data.float()).numpy().tolist() + data_dict[CompareConst.BENCH_MEAN] = torch.mean(cpu_data.float()).numpy().tolist() + else: + data_dict[CompareConst.BENCH_MAX] = data_dict[CompareConst.NPU_MAX] + data_dict[CompareConst.BENCH_MIN] = data_dict[CompareConst.NPU_MIN] + data_dict[CompareConst.BENCH_MEAN] = data_dict[CompareConst.NPU_MEAN] + else: + data_dict[CompareConst.NPU_DTYPE] = str(type(npu_data)) + data_dict[CompareConst.NPU_SHAPE] = str([]) + data_dict[CompareConst.BENCH_DTYPE] = str(type(cpu_data)) + data_dict[CompareConst.BENCH_SHAPE] = str([]) + if run_param.process_flag: + data_dict[CompareConst.NPU_MAX] = cpu_data + data_dict[CompareConst.NPU_MIN] = cpu_data + data_dict[CompareConst.NPU_MEAN] = cpu_data + data_dict[CompareConst.BENCH_MAX] = cpu_data + data_dict[CompareConst.BENCH_MIN] = cpu_data + data_dict[CompareConst.BENCH_MEAN] = cpu_data + + # when need to compute, caller guarantee npu_data and cpu_data is the same type + if compute_flag: + data_dict[CompareConst.COSINE], data_dict[CompareConst.MAX_ABS_ERR], data_dict[CompareConst.MAX_RELATIVE_ERR], \ + data_dict[CompareConst.ERROR_MESSAGE] = get_compare_result(npu_data, cpu_data) + + data_dict[CompareConst.ACCURACY] = check_accuracy(data_dict[CompareConst.COSINE], + data_dict[CompareConst.MAX_ABS_ERR]) + else: + data_dict[CompareConst.COSINE] = 1 + data_dict[CompareConst.MAX_ABS_ERR] = 0 + data_dict[CompareConst.MAX_RELATIVE_ERR] = 0 + data_dict[CompareConst.ERROR_MESSAGE] = None + data_dict[CompareConst.ACCURACY] = CompareConst.ACCURACY_CHECK_YES + + summery_list.append(data_dict) + + if data_dict[CompareConst.ACCURACY] == CompareConst.ACCURACY_CHECK_NO: + logger_user(f'rank{run_param.device_id} {prefix} index={run_param.single_api_index}, ' + f'overload={run_param.aten_api_overload_name}, shape={data_dict[CompareConst.NPU_SHAPE]} ' + f'{COLOR_RED}Failed{COLOR_RESET}, Cosine={data_dict[CompareConst.COSINE]},' + f'MaxAbsErr={data_dict[CompareConst.MAX_ABS_ERR]} ') + return False + logger_user(f'rank{run_param.device_id} {prefix} index={run_param.single_api_index}, ' + f'shape={data_dict[CompareConst.NPU_SHAPE]} {COLOR_GREEN}Pass{COLOR_RESET}') + return True + + +def support_basic_type(data): + if isinstance(data, (bool, int, float, torch.Tensor)): + return True + return False + + +def dump_data(data, prefix, dump_path): + if isinstance(data, (tuple, list)) and data: + for i, item in enumerate(data): + dump_data(item, "{}.{}".format(prefix, i), dump_path) + return + elif support_basic_type(data): + if isinstance(data, torch.Tensor) and data.is_meta: + return + # dump data may greater than summery_list collect + np_save_data(data, prefix, dump_path) + + +def compare_data(run_param, npu_data, cpu_data, prefix, summery_list, compute_flag): + if isinstance(npu_data, (tuple, list)) and npu_data: + accuracy_reached = True + for i, (npu_item, cpu_item) in enumerate(zip(npu_data, cpu_data)): + result = compare_data(run_param, npu_item, cpu_item, "{}.{}".format(prefix, i), summery_list, compute_flag) + accuracy_reached = accuracy_reached and result + return accuracy_reached + elif support_basic_type(npu_data): + if isinstance(npu_data, torch.Tensor) and npu_data.is_meta: + return True + if type(npu_data) != type(cpu_data): + logger_warn(f'{prefix} can not compare npu type={str(type(npu_data))} cpu type={str(type(cpu_data))}') + + return True + return save_summery(run_param, npu_data, cpu_data, prefix, summery_list, compute_flag) + return True + + +def save_temp_summery(api_index, single_api_summery, path, lock): + summery_path = os.path.join(path, f'summery.json') + lock.acquire() + with open(summery_path, "a") as f: + json.dump([api_index, single_api_summery], f) + f.write('\n') + lock.release() + + +def dispatch_workflow(run_param, cpu_args, cpu_kwargs, all_summery, func, npu_out_cpu, lock): + with TimeStatistics("CPU RUN", run_param): + cpu_out = func(*cpu_args, **cpu_kwargs) + + single_api_summery = [] + + prefix_input = f'{run_param.aten_api}_{run_param.single_api_index}_input' + prefix_output = f'{run_param.aten_api}_{run_param.single_api_index}_output' + with TimeStatistics("COMPARE INPUT", run_param): + # assume the input is the same + compare_data(run_param, cpu_args, cpu_args, prefix_input, single_api_summery, False) + if len(cpu_kwargs) > 0: + for k, v in cpu_kwargs.items(): + # kwargs_prefix_name must be the same as the name when dump_data + kwargs_prefix_name = prefix_input + f'_{k}' + compare_data(run_param, v, v, kwargs_prefix_name, single_api_summery, False) + + accuracy_reached = False + with TimeStatistics("COMPARE OUTPUT", run_param): + accuracy_reached = compare_data(run_param, npu_out_cpu, cpu_out, prefix_output, single_api_summery, True) + + # user set dump or auto mode will dump + if run_param.dump_flag or (run_param.auto_dump_flag and not accuracy_reached): + with TimeStatistics("DUMP INPUT", run_param): + dump_data(cpu_args, prefix_input, run_param.root_npu_path) + if len(cpu_kwargs) > 0: + for k, v in cpu_kwargs.items(): + kwargs_prefix_name = prefix_input + f'_{k}' + dump_data(v, kwargs_prefix_name, run_param.root_npu_path) + + with TimeStatistics("DUMP OUTPUT", run_param): + dump_data(cpu_out, prefix_output, run_param.root_cpu_path) + dump_data(npu_out_cpu, prefix_output, run_param.root_npu_path) + + if run_param.process_num == 0: + all_summery[run_param.api_index - 1] = copy.deepcopy(single_api_summery) + else: + save_temp_summery(run_param.api_index - 1, single_api_summery, run_param.root_cpu_path, lock) + + +def get_torch_func(run_param): + if hasattr(torch.ops, run_param.func_namespace): + ops_func = getattr(torch.ops, run_param.func_namespace) + if hasattr(ops_func, run_param.aten_api): + ops_aten_func = getattr(ops_func, run_param.aten_api) + if hasattr(ops_aten_func, run_param.aten_api_overload_name): + ops_aten_overlaod_func = getattr(ops_aten_func, run_param.aten_api_overload_name) + return ops_aten_overlaod_func + return None + + +def dispatch_multiprocess(run_param, cpu_args, cpu_kwargs, all_summery, npu_out_cpu, lock): + torch_func = get_torch_func(run_param) + if torch_func is None: + logger_error(f'can not find suitable call api:{run_param.aten_api}') + else: + dispatch_workflow(run_param, cpu_args, cpu_kwargs, all_summery, torch_func, npu_out_cpu, lock) + + +def error_call(err): + logger_error(f'multiprocess {err}') + + +def save_csv(all_summery, call_stack_list, csv_path): + df = pd.DataFrame(columns=CSV_COLUMN_NAME) + + for index, list_data in enumerate(all_summery): + for data in list_data: + csv_row_data = {CompareConst.NPU_NAME: data[CompareConst.NPU_NAME], + CompareConst.BENCH_NAME: data[CompareConst.BENCH_NAME], + CompareConst.NPU_DTYPE: data[CompareConst.NPU_DTYPE], + CompareConst.BENCH_DTYPE: data[CompareConst.BENCH_DTYPE], + CompareConst.NPU_SHAPE: data[CompareConst.NPU_SHAPE], + CompareConst.BENCH_SHAPE: data[CompareConst.BENCH_SHAPE], + CompareConst.NPU_MAX: data[CompareConst.NPU_MAX], + CompareConst.NPU_MIN: data[CompareConst.NPU_MIN], + CompareConst.NPU_MEAN: data[CompareConst.NPU_MEAN], + CompareConst.BENCH_MAX: data[CompareConst.BENCH_MAX], + CompareConst.BENCH_MIN: data[CompareConst.BENCH_MIN], + CompareConst.BENCH_MEAN: data[CompareConst.BENCH_MEAN], + CompareConst.COSINE: data[CompareConst.COSINE], + CompareConst.MAX_ABS_ERR: data[CompareConst.MAX_ABS_ERR], + CompareConst.MAX_RELATIVE_ERR: data[CompareConst.MAX_RELATIVE_ERR], + CompareConst.ACCURACY: data[CompareConst.ACCURACY], + CompareConst.STACK: call_stack_list[index], + CompareConst.ERROR_MESSAGE: data[CompareConst.ERROR_MESSAGE]} + row_df = pd.DataFrame.from_dict(csv_row_data, orient='index').T + df = pd.concat([df, row_df]) + + df.to_csv(csv_path, index=False) diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/unsupport_torch_ops.yaml b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/unsupport_torch_ops.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b660e46c118ae6d242223e8c3e6ee46d9ef2a3dc --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/unsupport_torch_ops.yaml @@ -0,0 +1,26 @@ +aten: + - _local_scalar_dense + - _to_copy + - _unsafe_view + - clone + - contiguous + - copy_ + - detach + - empty + - index_put_ + - lift_fresh + - max_pool2d_with_indices_backward # shape unmatch + - mul_ + - native_batch_norm_backward + - ones + - ones_like + - permute + - scalar_tensor + - select + - to + - transpose + - unbind + - view + - zero_ + - zeros + - zeros_like \ No newline at end of file diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/utils.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..fb3fc6dc8d04f2f1f4f98702c148bfb3b8a995e2 --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/online_dispatch/utils.py @@ -0,0 +1,159 @@ +import os +import inspect +import logging +import psutil +import torch +import numpy as np +from ..common.utils import print_error_log, CompareConst + +cpu_device = torch._C.device("cpu") +COLOR_RED = '\033[31m' +COLOR_GREEN = '\033[32m' +COLOR_YELLOW = '\033[33m' +COLOR_BLUE = '\033[34m' +COLOR_PURPLE = '\033[35m' +COLOR_CYAN = '\033[36m' +COLOR_GRAY = '\033[37m' +COLOR_RESET = '\033[0m' + +COMPARE_LOGO = ''' + _ _ + ___ _ __ | (_)_ __ ___ ___ ___ _ __ ___ _ __ __ _ _ __ ___ + / _ \\| '_ \\| | | '_ \\ / _ \\ / __/ _ \\| '_ ` _ \\| '_ \\ / _` | '__/ _ \\ +| (_) | | | | | | | | | __/ | (_| (_) | | | | | | |_) | (_| | | | __/ + \\___/|_| |_|_|_|_| |_|\\___| \\___\\___/|_| |_| |_| .__/ \\__,_|_| \\___| + |_| +''' + +CSV_COLUMN_NAME = [CompareConst.NPU_NAME, + CompareConst.BENCH_NAME, + CompareConst.NPU_DTYPE, + CompareConst.BENCH_DTYPE, + CompareConst.NPU_SHAPE, + CompareConst.BENCH_SHAPE, + CompareConst.NPU_MAX, + CompareConst.NPU_MIN, + CompareConst.NPU_MEAN, + CompareConst.BENCH_MAX, + CompareConst.BENCH_MIN, + CompareConst.BENCH_MEAN, + CompareConst.COSINE, + CompareConst.MAX_ABS_ERR, + CompareConst.MAX_RELATIVE_ERR, + CompareConst.ACCURACY, + CompareConst.STACK, + CompareConst.ERROR_MESSAGE] + + +def get_callstack(): + callstack = [] + for (_, path, line, func, code, _) in inspect.stack()[2:]: + if code: + stack_line = [path, str(line), func, code[0].strip() if code else code] + else: + stack_line = [path, str(line), func, code] + callstack.append(stack_line) + return callstack + + +def np_save_data(data, file_name, data_path): + try: + if hasattr(data, "numpy"): + data = data.numpy() + dump_path = os.path.join(data_path, f'{file_name}.npy') + np.save(dump_path, data) + except Exception as e: + print_error_log("save numpy failed, error: {}".format(e)) + finally: + pass + + +def data_to_cpu(data, deep, data_cpu): + global cpu_device + list_cpu = [] + if isinstance(data, torch.Tensor): + if data.device == cpu_device: + tensor_copy = data.clone().detach() + else: + tensor_copy = data.cpu() + if tensor_copy.dtype in [torch.float16, torch.half]: + tensor_copy = tensor_copy.float() + + if deep == 0: + data_cpu.append(tensor_copy) + return tensor_copy + elif isinstance(data, list): + for v in data: + list_cpu.append(data_to_cpu(v, deep+1, data_cpu)) + if deep == 0: + data_cpu.append(list_cpu) + return list_cpu + elif isinstance(data, tuple): + for v in data: + list_cpu.append(data_to_cpu(v, deep+1, data_cpu)) + tuple_cpu = tuple(list_cpu) + if deep == 0: + data_cpu.append(tuple_cpu) + return tuple_cpu + elif isinstance(data, dict): + dict_cpu = {} + for k, v in data.items(): + dict_cpu[k] = data_to_cpu(v, deep+1, data_cpu) + if deep == 0: + data_cpu.append(dict_cpu) + return dict_cpu + elif isinstance(data, torch._C.device): + return cpu_device + else: + return data + + +def get_mp_logger(): + logger = logging.getLogger(__name__) + if not logger.handlers: + logger.setLevel(logging.INFO) + handler = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s %(message)s') + logger.propagate = True + handler.setFormatter(formatter) + logger.addHandler(handler) + return logger.info + + +def logger_debug(mesg): + logger = get_mp_logger() + logger(f'DEBUG ' + mesg) + + +def logger_info(mesg): + logger = get_mp_logger() + logger(f'INFO ' + mesg) + + +def logger_warn(mesg): + logger = get_mp_logger() + logger(f'{COLOR_YELLOW}WARNING {mesg} {COLOR_RESET}') + + +def logger_error(mesg): + logger = get_mp_logger() + logger(f'{COLOR_RED}ERROR {mesg} {COLOR_RESET}') + + +def logger_user(mesg): + logger = get_mp_logger() + logger(mesg) + + +def logger_logo(): + logger_user(f'{COLOR_CYAN}{COMPARE_LOGO} {COLOR_RESET}') + + +def get_sys_info(): + mem = psutil.virtual_memory() + cpu_percent = psutil.cpu_percent(interval=1) + sys_info = f'Total: {mem.total / 1024 / 1024:.2f}MB '\ + f'Free: {mem.available / 1024 / 1024:.2f} MB '\ + f'Used: {mem.used / 1024 / 1024:.2f} MB '\ + f'CPU: {cpu_percent}% ' + return sys_info diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/overflow_check/info_dump.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/overflow_check/info_dump.py index 204f3de46098b63267f56c56c46a4aa28cebafb0..c8a2c5d26ba431d7b96714b57e394f6357b301bc 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/overflow_check/info_dump.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/overflow_check/info_dump.py @@ -76,15 +76,16 @@ class APIInfo: else: dump_path = "./" - api_args = self.api_name + '*' + str(self.args_num) + api_args = self.api_name + '.' + str(self.args_num) + rank = arg.device.index if self.is_forward: - forward_real_data_path = os.path.join(dump_path, 'forward_real_data') + forward_real_data_path = os.path.join(dump_path, 'forward_real_data', f"rank{rank}") if not os.path.exists(forward_real_data_path): os.makedirs(forward_real_data_path, 0o755) file_path = os.path.join(forward_real_data_path, f'{api_args}.npy') else: - backward_real_data_path = os.path.join(dump_path, 'backward_real_data') + backward_real_data_path = os.path.join(dump_path, 'backward_real_data', f"rank{rank}") if not os.path.exists(backward_real_data_path): os.makedirs(backward_real_data_path, 0o755) file_path = os.path.join(backward_real_data_path, f'{api_args}.npy') diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/overflow_check/overflow_check.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/overflow_check/overflow_check.py index 60693298e22a5c8c9528bdedd3cbdd1b2dd86133..561b118e8541bafd4ffb171eb7329c94401636c6 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/overflow_check/overflow_check.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/overflow_check/overflow_check.py @@ -1,7 +1,6 @@ import os -import glob import torch - +from pathlib import Path from ..common.utils import print_warn_log, get_time, print_info_log from ..dump.dump import forward_init_status, forward_acl_dump from .utils import OverFlowUtil, dump_overflow @@ -22,6 +21,7 @@ forward_api_info = {} backward_api_info = {} FORWARD_REAL_DATA_PATH = os.path.join('./', 'forward_real_data') BACKWARD_REAL_DATA_PATH = os.path.join('./', 'backward_real_data') +rank = os.getpid() def check_overflow_environment(pid): @@ -75,7 +75,6 @@ def check_data_overflow(x): def check_path(apis, path): return any(api in path for api in apis) - def overflow_check(name, **kwargs): overflow_nums = OverFlowUtil.overflow_nums pid = kwargs.get('pid') @@ -86,11 +85,25 @@ def overflow_check(name, **kwargs): def overflowcheck_hook(module, in_feat, out_feat): if not check_overflow_environment(pid): return - rank = get_tensor_rank(in_feat, out_feat) + dump_file = DumpUtil.get_dump_path() + global rank + if DumpUtil.target_iter: + dump_dir, dump_filename = os.path.split(dump_file) + dump_dir = os.path.join(dump_dir, "step{}".format(DumpUtil.iter_num)) + if not os.path.exists(dump_dir): + Path(dump_dir).mkdir(mode=0o750, exist_ok=True) + dump_file = os.path.join(dump_dir, dump_filename) + rank_this = get_tensor_rank(in_feat, out_feat) + DumpUtil.dump_root = os.path.dirname(DumpUtil.dump_path) + if rank_this is not None and rank != rank_this: + rank = rank_this + dump.rename_() if DumpUtil.target_rank is not None: if rank != DumpUtil.target_rank: return - dump_path = create_dirs_if_not_exist(rank, DumpUtil.dump_path) + dump_path = create_dirs_if_not_exist(rank, dump_file) + global pkl_name + pkl_name = dump_path dump_dir = os.path.split(dump_path)[0] global api_overflow global forward_api_info @@ -132,7 +145,6 @@ def overflow_check(name, **kwargs): if dump_mode == "acl": acl_dump(module, module_name) dump.write_to_disk() - dump.api_list.clear() # clear overflow flag for the next check torch_npu._C._clear_overflow_npu() if not OverFlowUtil.check_overflow_dump_times(overflow_nums): @@ -144,14 +156,6 @@ def overflow_check(name, **kwargs): .format(OverFlowUtil.real_overflow_dump_times, os.path.realpath(dump_file_name))) return - def delete_forward_npy(api_overflow_list, api_info): - for path in glob.glob(FORWARD_REAL_DATA_PATH + "/*.npy"): - if not check_path(api_overflow_list, path): - os.remove(os.path.abspath(path)) - for key in list(api_info.keys()): - if key not in api_overflow: - del forward_api_info[key] - def overflow_type_judge(in_feat, out_feat, module_name): if module_name.endswith(Const.BACKWARD): check_feat = out_feat diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/overflow_check/utils.py b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/overflow_check/utils.py index cff9c07efae812ff9462326be8de2d01758a7790..1a26c5ac04e770e76fd7a3b01bb88b34d4bcaa20 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/overflow_check/utils.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/ptdbg_ascend/overflow_check/utils.py @@ -1,10 +1,5 @@ -import json -import os -import stat import torch -import numpy as np - from ..common.utils import Const, check_switch_valid from ..dump.dump import dump_stack_info, get_scalar_data_info, dump_data, \ get_not_float_tensor_info, get_float_tensor_info @@ -34,10 +29,12 @@ class OverFlowUtil(object): @staticmethod def check_overflow_dump_times(need_dump_times): + if need_dump_times == -1: + return True return OverFlowUtil.real_overflow_dump_times < need_dump_times -def set_overflow_check_switch(switch, filter_switch=Const.ON): +def set_overflow_check_switch(switch, filter_switch=Const.OFF): check_switch_valid(switch) check_switch_valid(filter_switch) diff --git a/debug/accuracy_tools/ptdbg_ascend/src/python/setup.py b/debug/accuracy_tools/ptdbg_ascend/src/python/setup.py index 90311ae94e98c1d11e4f44e5ff4f8243a0b8fa7c..5de547bd225e053cd09dd9e01cff549484864a1f 100644 --- a/debug/accuracy_tools/ptdbg_ascend/src/python/setup.py +++ b/debug/accuracy_tools/ptdbg_ascend/src/python/setup.py @@ -20,7 +20,7 @@ from pathlib import Path import stat import os -VERSION = '3.2' +VERSION = '3.4' def generate_ptdbg_ascend_version(): ptdbg_ascend_root = Path(__file__).parent diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/debugger/test_debugger_config.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/debugger/test_debugger_config.py new file mode 100644 index 0000000000000000000000000000000000000000..37d9caf374089a83b9f6f561cfccf9d08e25d28d --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/debugger/test_debugger_config.py @@ -0,0 +1,35 @@ +import unittest +from ptdbg_ascend.debugger.debugger_config import DebuggerConfig + +class TestDebuggerConfig(unittest.TestCase): + def setUp(self): + self.dump_path = "/path/to/dump" + self.hook_name = "dump" + self.rank = 0 + self.step = [1, 2, 3] + + def test_init(self): + debugger_config = DebuggerConfig(self.dump_path, self.hook_name, self.rank, self.step) + self.assertEqual(debugger_config.dump_path, self.dump_path) + self.assertEqual(debugger_config.hook_name, self.hook_name) + self.assertEqual(debugger_config.rank, self.rank) + self.assertEqual(debugger_config.step, self.step) + + def test_check_hook_name(self): + debugger_config = DebuggerConfig(self.dump_path, self.hook_name, self.rank, self.step) + with self.assertRaises(ValueError): + debugger_config.hook_name = "invalid_hook_name" + debugger_config._check_hook_name() + + def test_check_rank(self): + debugger_config = DebuggerConfig(self.dump_path, self.hook_name, self.rank, self.step) + with self.assertRaises(ValueError): + debugger_config.rank = -1 + debugger_config._check_rank() + + def test_check_step(self): + debugger_config = DebuggerConfig(self.dump_path, self.hook_name, self.rank, self.step) + with self.assertRaises(ValueError): + debugger_config.step = "invalid_step" + debugger_config._check_step() + diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/debugger/test_precision_debugger.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/debugger/test_precision_debugger.py new file mode 100644 index 0000000000000000000000000000000000000000..f2c82a938c2126fb31f448e70b24e900dffbe11a --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/debugger/test_precision_debugger.py @@ -0,0 +1,51 @@ +import unittest +from unittest.mock import patch, MagicMock +from ptdbg_ascend.debugger.precision_debugger import PrecisionDebugger +from ptdbg_ascend.dump.dump import DumpUtil + +class TestPrecisionDebugger(unittest.TestCase): + + def setUp(self): + self.precision_debugger = PrecisionDebugger(dump_path='test_path', hook_name='dump') + + def test_init(self): + self.assertEqual(self.precision_debugger.config.dump_path, 'test_path') + self.assertEqual(self.precision_debugger.config.hook_name, 'dump') + + def test_get_configure_hook_dump(self): + hook = self.precision_debugger.get_configure_hook('dump') + self.assertEqual(hook, self.precision_debugger.configure_full_dump) + + def test_get_configure_hook_overflow(self): + hook = self.precision_debugger.get_configure_hook('overflow_check') + self.assertEqual(hook, self.precision_debugger.configure_overflow_dump) + + def test_configure_full_dump(self): + self.assertRaises(ValueError, self.precision_debugger.configure_full_dump, mode='acl', acl_config=None) + + def test_configure_overflow_dump(self): + self.assertRaises(ValueError, self.precision_debugger.configure_overflow_dump, overflow_nums='invalid') + + @patch('ptdbg_ascend.debugger.precision_debugger.register_hook_core') + def test_start(self, mock_register_hook_core): + PrecisionDebugger.first_start = True + PrecisionDebugger.start() + mock_register_hook_core.assert_called_once() + + @patch('ptdbg_ascend.debugger.precision_debugger.write_to_disk') + def test_stop(self, mock_write_to_disk): + PrecisionDebugger.stop() + mock_write_to_disk.assert_called_once() + + def test_step(self): + initial_iter_num = DumpUtil.iter_num + PrecisionDebugger.step() + self.assertEqual(DumpUtil.iter_num, initial_iter_num + 1) + + @patch('ptdbg_ascend.debugger.precision_debugger.PrecisionDebugger.step') + @patch('ptdbg_ascend.debugger.precision_debugger.PrecisionDebugger.start') + def test_incr_iter_num_maybe_exit(self, mock_start, mock_step): + PrecisionDebugger.incr_iter_num_maybe_exit() + mock_step.assert_called_once() + mock_start.assert_called_once() + diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_register_hook.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_register_hook.py new file mode 100644 index 0000000000000000000000000000000000000000..7f91d6bd9cf21a17f626d09dbcc0584afa7f1bfb --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_register_hook.py @@ -0,0 +1,14 @@ +import unittest +from unittest.mock import patch, MagicMock +from ptdbg_ascend.hook_module import register_hook + +class TestRegisterHook(unittest.TestCase): + + def setUp(self): + self.model = MagicMock() + self.hook = MagicMock() + + def test_register_hook(self): + with patch('ptdbg_ascend.hook_module.register_hook.register_hook_core') as mock_core: + register_hook.register_hook(self.model, self.hook) + mock_core.assert_called_once() \ No newline at end of file diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_distributed.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_distributed.py new file mode 100644 index 0000000000000000000000000000000000000000..5ae5fc7c0b903d1e320d6f77324e24cb696802e4 --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_distributed.py @@ -0,0 +1,29 @@ +import unittest +import torch.distributed as dist +from ptdbg_ascend.hook_module.wrap_distributed import * + +class TestWrapDistributed(unittest.TestCase): + def setUp(self): + self.hook = lambda x: x + + def test_get_distributed_ops(self): + ops = get_distributed_ops() + self.assertIsInstance(ops, set) + + def test_DistributedOPTemplate(self): + op_name = 'all_reduce' + if op_name in get_distributed_ops(): + op = DistributedOPTemplate(op_name, self.hook) + self.assertEqual(op.op_name_, op_name) + + def test_wrap_distributed_op(self): + op_name = 'all_reduce' + if op_name in get_distributed_ops(): + wrapped_op = wrap_distributed_op(op_name, self.hook) + self.assertTrue(callable(wrapped_op)) + + def test_wrap_distributed_ops_and_bind(self): + wrap_distributed_ops_and_bind(self.hook) + for op_name in get_distributed_ops(): + self.assertTrue(hasattr(HOOKDistributedOP, "wrap_" + str(op_name))) + diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_functional.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_functional.py new file mode 100644 index 0000000000000000000000000000000000000000..58ce97582fae218cb88ee0b9c57768613772611c --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_functional.py @@ -0,0 +1,20 @@ +import unittest +import torch +from ptdbg_ascend.hook_module import wrap_functional as wf + +class TestWrapFunctional(unittest.TestCase): + + def test_remove_dropout(self): + input_tensor = torch.randn(20, 16) + wf.remove_dropout() + output_tensor = torch.nn.functional.dropout(input_tensor) + self.assertTrue(torch.equal(input_tensor, output_tensor)) + + def test_get_functional_ops(self): + expected_ops = {'relu', 'sigmoid', 'softmax'} + actual_ops = wf.get_functional_ops() + self.assertTrue(expected_ops.issubset(actual_ops)) + + def test_wrap_functional_ops_and_bind(self): + wf.wrap_functional_ops_and_bind(None) + self.assertTrue(hasattr(wf.HOOKFunctionalOP, 'wrap_relu')) \ No newline at end of file diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_tensor.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_tensor.py new file mode 100644 index 0000000000000000000000000000000000000000..201e2989f0c4cd282ccfc93150323cacd8dd84c4 --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_tensor.py @@ -0,0 +1,29 @@ +import unittest +import torch +import yaml +from ptdbg_ascend.hook_module.wrap_tensor import get_tensor_ops, HOOKTensor, TensorOPTemplate, wrap_tensor_op, wrap_tensor_ops_and_bind + +class TestWrapTensor(unittest.TestCase): + + def setUp(self): + self.hook = lambda x: x + + def test_get_tensor_ops(self): + result = get_tensor_ops() + self.assertIsInstance(result, set) + + def test_HOOKTensor(self): + hook_tensor = HOOKTensor() + self.assertIsInstance(hook_tensor, HOOKTensor) + + def test_TensorOPTemplate(self): + tensor_op_template = TensorOPTemplate('add', self.hook) + self.assertEqual(tensor_op_template.op_name_, 'add') + + def test_wrap_tensor_op(self): + wrapped_op = wrap_tensor_op('add', self.hook) + self.assertTrue(callable(wrapped_op)) + + def test_wrap_tensor_ops_and_bind(self): + wrap_tensor_ops_and_bind(self.hook) + self.assertTrue(hasattr(HOOKTensor, 'wrap_add')) \ No newline at end of file diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_torch.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_torch.py new file mode 100644 index 0000000000000000000000000000000000000000..0c5a997f307f82857bff62043c6e7a3f8e88397f --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_torch.py @@ -0,0 +1,34 @@ +import unittest +import torch +import yaml +from ptdbg_ascend.hook_module.wrap_torch import * + +class TestWrapTorch(unittest.TestCase): + + def setUp(self): + self.hook = lambda x: x + self.op_name = 'add' + self.torch_op = wrap_torch_op(self.op_name, self.hook) + + def test_get_torch_ops(self): + ops = get_torch_ops() + self.assertIsInstance(ops, set) + self.assertIn(self.op_name, ops) + + def test_TorchOPTemplate(self): + template = TorchOPTemplate(self.op_name, self.hook) + self.assertEqual(template.op_name_, self.op_name) + self.assertEqual(template.prefix_op_name_, "Torch_" + str(self.op_name) + "_") + + def test_input_param_need_adapt(self): + template = TorchOPTemplate(self.op_name, self.hook) + self.assertFalse(template.input_param_need_adapt()) + + def test_forward(self): + template = TorchOPTemplate(self.op_name, self.hook) + result = template.forward(torch.tensor([1, 2, 3]), torch.tensor([4, 5, 6])) + torch.testing.assert_allclose(result, torch.tensor([5, 7, 9])) + + def test_wrap_torch_ops_and_bind(self): + wrap_torch_ops_and_bind(self.hook) + self.assertTrue(hasattr(HOOKTorchOP, "wrap_" + self.op_name)) \ No newline at end of file diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_vf.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_vf.py new file mode 100644 index 0000000000000000000000000000000000000000..b873c1126cc8b4e3fb1e34406893c0e810ef4afc --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/hook_module/test_wrap_vf.py @@ -0,0 +1,11 @@ +import unittest +import torch +from ptdbg_ascend.hook_module import wrap_vf + +class TestWrapVf(unittest.TestCase): + def setUp(self): + self.hook = lambda x: x + + def test_get_vf_ops(self): + ops = wrap_vf.get_vf_ops() + self.assertIsInstance(ops, list) \ No newline at end of file diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/overflow/test_info_dump.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/overflow/test_info_dump.py new file mode 100644 index 0000000000000000000000000000000000000000..96f3abd0ef66c2ac184beef191f34d9f92ae8d9a --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/overflow/test_info_dump.py @@ -0,0 +1,92 @@ +import unittest +import torch +import os +from ptdbg_ascend.overflow_check import info_dump + +class TestInfoDump(unittest.TestCase): + + def setUp(self): + self.tensor = torch.tensor([1.0, 2.0, 3.0]) + self.file_path = './test.npy' + self.api_info = info_dump.APIInfo('test_api', True) + self.forward_api_info = info_dump.ForwardAPIInfo('test_api', True, (1, 2, 3), {'a': 1, 'b': 2}) + self.backward_api_info = info_dump.BackwardAPIInfo('test_api', (1, 2, 3)) + self.dump_path = './' + self.json_file_path = os.path.join(self.dump_path, 'test.json') + + def tearDown(self): + if os.path.exists(self.file_path): + os.remove(self.file_path) + + def test_write_npy(self): + npy_path = info_dump.write_npy(self.file_path, self.tensor) + self.assertTrue(os.path.exists(npy_path)) + + def test_APIInfo_init(self): + self.assertEqual(self.api_info.api_name, 'test_api') + self.assertEqual(self.api_info.is_forward, True) + + def test_analyze_element(self): + result = self.api_info.analyze_element(self.tensor) + self.assertIsInstance(result, dict) + self.assertEqual(result['type'], 'torch.Tensor') + + def test_analyze_tensor(self): + result = self.api_info.analyze_tensor(self.tensor, False) + self.assertIsInstance(result, dict) + self.assertEqual(result['type'], 'torch.Tensor') + + def test_analyze_builtin(self): + result = self.api_info.analyze_builtin(5) + self.assertIsInstance(result, dict) + self.assertEqual(result['type'], 'int') + + def test_transfer_types(self): + result = self.api_info.transfer_types(5, 'int') + self.assertIsInstance(result, int) + self.assertEqual(result, 5) + + def test_is_builtin_class(self): + result = self.api_info.is_builtin_class(5) + self.assertTrue(result) + + def test_analyze_device_in_kwargs(self): + result = self.api_info.analyze_device_in_kwargs('cpu') + self.assertIsInstance(result, dict) + self.assertEqual(result['type'], 'torch.device') + + def test_analyze_dtype_in_kwargs(self): + result = self.api_info.analyze_dtype_in_kwargs(torch.float32) + self.assertIsInstance(result, dict) + self.assertEqual(result['type'], 'torch.dtype') + + def test_get_tensor_extremum(self): + result = self.api_info.get_tensor_extremum(self.tensor, 'max') + self.assertEqual(result, 3.0) + + def test_get_type_name(self): + result = self.api_info.get_type_name(str(type(self.tensor))) + self.assertEqual(result, 'torch.Tensor') + + def test_ForwardAPIInfo_init(self): + self.assertEqual(self.forward_api_info.api_name, 'test_api') + self.assertEqual(self.forward_api_info.is_forward, True) + + def test_BackwardAPIInfo_init(self): + self.assertEqual(self.backward_api_info.api_name, 'test_api') + self.assertEqual(self.backward_api_info.is_forward, False) + + def test_write_api_info_json(self): + info_dump.write_api_info_json(self.forward_api_info) + self.assertTrue(os.path.exists(os.path.join(self.dump_path, f'forward_info_{self.forward_api_info.rank}.json'))) + + def test_write_json(self): + info_dump.write_json(self.json_file_path, {'test': 'data'}) + self.assertTrue(os.path.exists(self.json_file_path)) + + def test_initialize_output_json(self): + try: + info_dump.initialize_output_json() + except ValueError as e: + self.assertTrue(str(e).startswith('file')) + diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/overflow/test_overflow_utils.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/overflow/test_overflow_utils.py index f084ca211304c331a225b14119dc646b6cd09273..7dbf9ba460ee74f2745ce0f2bb7de7eb1ef079d6 100644 --- a/debug/accuracy_tools/ptdbg_ascend/test/ut/overflow/test_overflow_utils.py +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/overflow/test_overflow_utils.py @@ -50,7 +50,7 @@ class TestUtilsMethods(unittest.TestCase): def test_set_overflow_check_switch_success2(self): utils.set_overflow_check_switch(ON) self.assertEqual(OverFlowUtil.overflow_check_switch, ON) - self.assertEqual(OverFlowUtil.overflow_filter_switch, ON) + self.assertEqual(OverFlowUtil.overflow_filter_switch, OFF) def test_set_overflow_check_switch_success3(self): utils.set_overflow_check_switch(ON, ON) diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/parse_tool/test_compare.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/parse_tool/test_compare.py new file mode 100644 index 0000000000000000000000000000000000000000..207a8cbf09ea0c7dde6c4a59df34095ace832489 --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/parse_tool/test_compare.py @@ -0,0 +1,37 @@ +import unittest +import numpy as np +from ptdbg_ascend.parse_tool.lib.compare import Compare + +class TestCompare(unittest.TestCase): + def setUp(self): + self.compare = Compare() + + def test_call_msaccucmp(self): + result = self.compare.call_msaccucmp + self.assertIsNotNone(result) + + def test_npu_vs_npu_compare(self): + my_dump_path = 'path_to_my_dump' + golden_dump_path = 'path_to_golden_dump' + result_dir = 'path_to_result_dir' + self.compare.npu_vs_npu_compare(my_dump_path, golden_dump_path, result_dir) + + def test_compare_vector(self): + my_dump_path = 'path_to_my_dump' + golden_dump_path = 'path_to_golden_dump' + result_dir = 'path_to_result_dir' + result = self.compare.compare_vector(my_dump_path, golden_dump_path, result_dir) + self.assertIsNotNone(result) + + def test_convert_dump_to_npy(self): + dump_file = 'path_to_dump_file' + data_format = 'data_format' + output = 'path_to_output' + self.compare.convert_dump_to_npy(dump_file, data_format, output) + + def test_convert(self): + dump_file = 'path_to_dump_file' + data_format = 'data_format' + output = 'path_to_output' + result = self.compare.convert(dump_file, data_format, output) + self.assertIsNotNone(result) diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/parse_tool/test_parse_tool.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/parse_tool/test_parse_tool.py new file mode 100644 index 0000000000000000000000000000000000000000..cd024a44019eb89cfb54b0f15a09619dbaf60b50 --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/parse_tool/test_parse_tool.py @@ -0,0 +1,12 @@ +import unittest +from unittest.mock import patch, MagicMock +from ptdbg_ascend.parse_tool.lib.parse_tool import ParseTool + +class TestParseTool(unittest.TestCase): + def setUp(self): + self.parse_tool = ParseTool() + + @patch('ptdbg_ascend.parse_tool.lib.parse_tool.Util.create_dir') + def test_prepare(self, mock_create_dir): + self.parse_tool.prepare() + mock_create_dir.assert_called_once() \ No newline at end of file diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/parse_tool/test_util.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/parse_tool/test_util.py new file mode 100644 index 0000000000000000000000000000000000000000..c497ec60669e9e642cc96174a2c211a229bead9b --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/parse_tool/test_util.py @@ -0,0 +1,47 @@ +import unittest +from unittest.mock import patch, MagicMock +import numpy as np +from ptdbg_ascend.parse_tool.lib import utils + +class TestUtils(unittest.TestCase): + def setUp(self): + self.util = utils.Util() + + def test_execute_command(self): + with patch('subprocess.run') as mocked_run: + mocked_run.return_value.returncode = 0 + result = self.util.execute_command('echo hello') + self.assertEqual(result, 0) + + def test_check_msaccucmp(self): + with patch('subprocess.run') as mocked_run: + mocked_run.return_value.returncode = 0 + result = self.util.check_msaccucmp('msaccucmp.py') + self.assertEqual(result, 'msaccucmp.py') + + def test_gen_npy_info_txt(self): + data = np.array([1, 2, 3]) + result = self.util.gen_npy_info_txt(data) + self.assertEqual(result, '[Shape: (3,)] [Dtype: int64] [Max: 3] [Min: 1] [Mean: 2.0]') + + def test_save_npy_to_txt(self): + data = np.array([1, 2, 3]) + with patch('numpy.savetxt') as mocked_savetxt: + self.util.save_npy_to_txt(data, 'test.txt') + mocked_savetxt.assert_called_once() + + def test_check_path_valid(self): + with patch('os.path.exists') as mocked_exists: + mocked_exists.return_value = True + self.util.check_path_valid('valid_path') + + def test_npy_info(self): + data = np.array([1, 2, 3]) + result = self.util.npy_info(data) + self.assertEqual(result, ((3,), np.dtype('int64'), 3, 1, 2.0)) + + def test_check_path_format(self): + with patch('os.path.isfile') as mocked_isfile: + mocked_isfile.return_value = True + self.util.check_path_format('file.txt', '.txt') + diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/parse_tool/test_visualization.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/parse_tool/test_visualization.py new file mode 100644 index 0000000000000000000000000000000000000000..b537cb076f5340eae7f853402f79359f563c1784 --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/parse_tool/test_visualization.py @@ -0,0 +1,30 @@ +import unittest +import numpy as np +from ptdbg_ascend.parse_tool.lib.visualization import Visualization + +class TestVisualization(unittest.TestCase): + def setUp(self): + self.visualization = Visualization() + + def test_print_npy_summary(self): + np.save('test.npy', np.array([1, 2, 3, 4, 5])) + try: + self.visualization.print_npy_summary('test.npy') + except Exception as e: + self.fail(f"print_npy_summary raised exception {e}") + + def test_print_npy_data(self): + np.save('test.npy', np.array([1, 2, 3, 4, 5])) + try: + self.visualization.print_npy_data('test.npy') + except Exception as e: + self.fail(f"print_npy_data raised exception {e}") + + def test_parse_pkl(self): + with open('test.pkl', 'w') as f: + f.write('["api_name", [], "", "", "", ["", "", ""]]') + try: + self.visualization.parse_pkl('test.pkl', 'api_name') + except Exception as e: + self.fail(f"parse_pkl raised exception {e}") + diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/test_acc_compare.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/test_acc_compare.py index 7c28922a740cd320e4d05b9cf44f1b647ae4eda0..a35dd542b2d60dbc7255152f52656f6a39b65419 100644 --- a/debug/accuracy_tools/ptdbg_ascend/test/ut/test_acc_compare.py +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/test_acc_compare.py @@ -98,7 +98,7 @@ class TestUtilsMethods(unittest.TestCase): n_value = np.array([1, 2, 3]) b_value = np.array([1, 2, 3]) max_relative_err, message = compare.get_max_relative_err(n_value, b_value) - self.assertEqual(max_relative_err, "0.000000") + self.assertEqual(max_relative_err, "0.000000000000") self.assertEqual(message, "") def test_check_op(self): diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/test_advisor.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/test_advisor.py index 0bee5d4afbe0782e58d12ef04b8b243a7a5084bf..71980f3ace07b28ed5152ad56159cd3c5ca7b7ba 100644 --- a/debug/accuracy_tools/ptdbg_ascend/test/ut/test_advisor.py +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/test_advisor.py @@ -29,7 +29,3 @@ class TestAdvisor(unittest.TestCase): advisor.analysis() filenames = os.listdir(self.output_path) self.assertEqual(len(filenames), 1) - - def test_analysis_when_accuracy_and_npu_name_not_in_csv(self): - advisor = Advisor("resources/compare/compare_result_without_accuracy.csv", self.output_path) - self.assertRaises(AttributeError, advisor.analysis) diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/test_common_util.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/test_common_util.py index 5fc5ae51ea7828a2dd683bf9baf4304026b33ee0..61cbf29f2135e30389eedea511b1c4431ee2fbe1 100644 --- a/debug/accuracy_tools/ptdbg_ascend/test/ut/test_common_util.py +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/test_common_util.py @@ -27,34 +27,30 @@ class TestCommonUtilsMethods(unittest.TestCase): self.assertEqual(mode_check("api_list",api_list=["relu"]), None) self.assertEqual(mode_check("api_stack"), None) self.assertRaises(common.CompareException, mode_check, "api_stack_123") - - def test_parse_arg_value(self): - data = [[1, 2, 4, 8]] - self.assertEqual(common.parse_arg_value("1,2,4,8"), data) - + def test_parse_value_by_comma(self): data = [1, 2, 4, 8] self.assertEqual(common.parse_value_by_comma("1,2,4,8"), data) - + def test_get_data_len_by_shape(self): getshape = common.get_data_len_by_shape data = [1, 2, 4, 8] self.assertEqual(getshape(data), 64) data = [-1, 2, 4, 8] self.assertEqual(getshape(data), -1) - + def test_add_time_as_suffix(self): name = "op_cmp" csv_name = '{}_{}.csv'.format(name, time.strftime("%Y%m%d%H%M%S", time.localtime(time.time()))) self.assertEqual(common.add_time_as_suffix(name), csv_name) - + def test_get_time(self): time = datetime.now(tz=timezone.utc).strftime("%Y%m%d_%H%M%S") self.assertEqual(common.get_time(), time) - + def test_format_value(self): value = 12345.6789 - format_value = '{:.6f}'.format(value) + format_value = '{:.12f}'.format(value) self.assertEqual(common.format_value(value), format_value) def test_modify_dump_path(self): @@ -67,7 +63,7 @@ class TestCommonUtilsMethods(unittest.TestCase): def test_execute_command(self): pass - + def test_save_numpy_data(self): pass @@ -79,7 +75,7 @@ class TestCommonUtilsMethods(unittest.TestCase): def test_get_process_rank(self): pass - + def test_check_file_size(self): pass diff --git a/debug/accuracy_tools/ptdbg_ascend/test/ut/test_dump.py b/debug/accuracy_tools/ptdbg_ascend/test/ut/test_dump.py new file mode 100644 index 0000000000000000000000000000000000000000..804f8ff5414ab2ea2273a0f71b36a3162eeb9633 --- /dev/null +++ b/debug/accuracy_tools/ptdbg_ascend/test/ut/test_dump.py @@ -0,0 +1,54 @@ +import unittest +import torch +import numpy as np +from ptdbg_ascend.dump.dump import * + +class TestDump(unittest.TestCase): + + def setUp(self): + self.tensor = torch.tensor([1.0, 2.0, 3.0]) + self.scalar = 5.0 + self.prefix = "test_prefix" + self.dump_step = 1 + self.dump_file_name = "test_file" + + def test_get_not_float_tensor_info(self): + data_info = get_not_float_tensor_info(self.tensor) + self.assertEqual(data_info.save_data.tolist(), self.tensor.numpy().tolist()) + self.assertEqual(data_info.summary_data, [3.0, 1.0, 2.0]) + self.assertEqual(data_info.dtype, 'torch.float32') + self.assertEqual(data_info.shape, (3,)) + + def test_get_scalar_data_info(self): + data_info = get_scalar_data_info(self.scalar) + self.assertEqual(data_info.data, self.scalar) + self.assertEqual(data_info.save_data, self.scalar) + self.assertEqual(data_info.summary_data, [self.scalar, self.scalar, self.scalar]) + self.assertEqual(data_info.dtype, '') + self.assertEqual(data_info.shape, '[]') + + def test_get_float_tensor_info(self): + data_info = get_float_tensor_info(self.tensor) + self.assertEqual(data_info.save_data.tolist(), self.tensor.numpy().tolist()) + self.assertEqual(data_info.summary_data, [3.0, 1.0, 2.0]) + self.assertEqual(data_info.dtype, 'torch.float32') + self.assertEqual(data_info.shape, (3,)) + + def test_get_tensor_data_info(self): + tensor_max = 3.0 + tensor_min = 1.0 + tensor_mean = 2.0 + data_info = get_tensor_data_info(self.tensor, tensor_max, tensor_min, tensor_mean) + self.assertEqual(data_info.save_data.tolist(), self.tensor.numpy().tolist()) + self.assertEqual(data_info.summary_data, [tensor_max, tensor_min, tensor_mean]) + self.assertEqual(data_info.dtype, 'torch.float32') + self.assertEqual(data_info.shape, (3,)) + + def test_json_dump_condition(self): + result = json_dump_condition(self.prefix) + self.assertEqual(result, False) + + def test_get_pkl_file_path(self): + result = get_pkl_file_path() + self.assertEqual(result, "") +