4

从Core Dump中提取CUDA的报错信息

 2 years ago
source link: https://blog.csdn.net/OneFlow_Official/article/details/126615946
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

从Core Dump中提取CUDA的报错信息

0607ad1584ce15948e77aaaba76987c8.png

近期,Meta AI团队在生产PyTorch AI模型时遇到了一个难题。这一问题由CUDA非法内存访问引起,号称集结了Meta全公司最牛的AI工程师才搞定,这篇博客记录了他们使用CUDA的core dump来确定报错位置所使用的技巧和实践。

作者|Zachary DeVito

翻译|贾川、程浩源

如果GPU读取了无效内存,那么CUDA API将会开始从发生错误的地方开始,后续所有API调用都会返回cudaErrorIllegalAddress:

设备在无效内存地址上使用了加载或存储指令。这使得进程处于不一致的状态,任何后续的CUDA工作都将返回相同的错误。若要继续使用CUDA,进程必须终止并重新启动。

因为CUDA kernel是从CPU异步启动,所以在启动异常kernel的地方不会报告此错误,而是在GPU上实际发生异常并传播到CPU之后的任何CUDA API调用时报告此错误。

当然,要是使用CUDA_LAUNCH_BLOCKING=1环境变量,CUDA就会在kernel启动后运行完成才返回,但这会使得程序运行明显变慢,可能会改变报错时机,以致某些不确定性问题不再被触发。

此外,如果有多个线程使用CUDA API,cudaErrorIllegalAddress可能首先在另一个线程上报错,而不是在启动线程上报错。因此,即使在CUDA_LAUNCH_BLOCKING=1的情况下,我也不信任堆栈跟踪呈现的信息。

相反,对于“非法地址(illegal address)”这一bug,我们希望能找到更多、更准确的报错原因。类似于其他处理器,当故障发生时,GPU上的SM会记录有关故障指令的信息。

不幸的是,我意识到没有进程内的方法可以获取这类信息。我们只能在运行之前,通过将cuda-gdb或cuda-memcheck附加到进程中来访问此类信息。但这对于那些发生率很低的bug来说,在这种模式下重新运行这个进程来重现bug是不切实际的。

幸运的是,通过设置环境变量CUDA_ENABLE_COREDUMP_ON_EXCEPTION=1,我们可以使CUDA在发生异常后生成core dumps来呈现GPU的状态,然后用cuda-gdb来检查该文件。

本文讨论了如何从这些core dumps中生成提取信息,以便在没有调试信息的情况下,也能恢复诸多信息,比如参数值和出错指令等。

1

生成core dumps

在有故障的进程上设置 CUDA_ENABLE_COREDUMP_ON_EXCEPTION=1。如此一来,当故障发生时,它会生成一个core dumps文件cudacoredump.hostname.pid。

2

使用cuda-gdb打开core dumps

这应该报告一些关于故障发生地点的信息:

相关信息如下:

  • 触发Warp Illegal Address的指令地址:The exception was triggered at PC 0x7ff8b63ce440

  • 正在运行的kernel名称:softmax_warp_forward

  • 执行停止的地址:0x00007ff8b63ce570

请注意,GPU的停止地址(...570)是在触发地址(...440)之后。因为内存是异步读取,所以GPU会继续执行指令,之后才能发现故障。在查看寄存器的值时要注意这一点,因为你从中看到的是执行停止时的状态,而错误发生时指令中所使用寄存器的值可能也已经被覆盖。

最后,除非编译生成的代码中包含调试信息,否则将看不到代码行或文件名信息。但通过后续介绍的方法,即使没有如上内容,你也能从转储中恢复大量信息。

3

反汇编kernel

使用disas查看kernel的shader assembly(SASS)列表:

要查看错误指令,请找到与之匹配的PC:

0x00007ff8b63ce440 <+1088>:  LDG.E.U8.SYS R19, [R2+0xe0]

在这种情况下,LDG是“从全局内存加载”,从地址[R2+0xe0]读取1字节(“U8”)到寄存器R19。出错的原因大概是R2+0xe0越界(out of bounds)了。

4

检查寄存器

使用info reg查看所有GPU寄存器的值:

虽然这里能看到R2的值,但其实R2在PC...440和...570之间的值已经被覆盖了,因此我们很难找到故障地址的值。

5

读取GPU内存

使用print从内存中读取值:

6

恢复传递给kernel的参数

kernel的参数在常量“参数”内存中传递。加载它们的指令包括对常量内存的引用,如c[0x0][0x174]:

0x00007ff8b63ce080 <+128>:   IMAD R0, R3.reuse, c[0x0][0x174], R6

可以使用以下方法读取此内存:

要真正获取所有kernel参数的值,我们需要了解它们在内存中的排列方式。假设kernel有参数:

常量内存中参数的布局与将它们放入struct中的布局相同:

这意味着结构体的值通常与其自身大小的下一个倍数对齐(8字节类型与8字节倍数对齐),必要时插入一些填充字节(padding bytes)。

kernel参数的开头不是0x0(低位的地址包含一些关于kernel的额外元数据),你可能需要查看程序集中对c[0x0][...]的所有引用,根据值的使用方式,查看参数缓冲区可能从何处开始。我自己运行时,参数看起来从0x160开始,这是cuda-gdb能对常量内存返回一个合理的值的条件下,对该常量内存的最小引用。

知道了布局和起始地址后,就可以用print来获取值(在print中指定正确的类型):

SASS文档(https://docs.nvidia.com/cuda/cuda-binary-utilities/index.html)有更多关于正在运行的汇编语言的文档,但目前还不甚完善,且会随着GPU的更新换代而有所改变。

(本文经授权后编译发布。原文:

https://github.com/zdevito/zdevito.github.io/blob/main/_posts/2022-07-27-cuda-core-dumps.markdown)

其他人都在看

欢迎体验OneFlow v0.8.0:GitHub - Oneflow-Inc/oneflow: OneFlow is a deep learning framework designed to be user-friendly, scalable and efficient.OneFlow is a deep learning framework designed to be user-friendly, scalable and efficient. - GitHub - Oneflow-Inc/oneflow: OneFlow is a deep learning framework designed to be user-friendly, scalable and efficient.fluidicon.pnghttps://github.com/Oneflow-Inc/oneflow/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK