遇到 CUDA 非法内存访问

Posted

技术标签:

【中文标题】遇到 CUDA 非法内存访问【英文标题】:CUDA illegal memory access was encountered 【发布时间】:2020-12-27 03:26:03 【问题描述】:

我在自己的笔记本电脑上编写了一个 CUDA 程序,它有 Nvidia GTX 960M。该代码可以正常工作。我还实现了可以在这个线程中找到的错误检查: What is the canonical way to check for errors using the CUDA runtime API?

并且还使用cuda-memcheck测试了代码,报告了0个错误。

我想在具有 Nvidia Titan X 的服务器上测试我的代码。但是 cudaPeekAtLastError() 抛出错误:

遇到非法内存访问

对于我的笔记本电脑和服务器,我使用以下堆分配

cudaDeviceSetLimit(cudaLimitMallocHeapSize, 1024 * 1024 * 1024);

并运行以下线程和块:

int blockSize = 128;
int numBlocks = (nPossibilities + blockSize - 1) / blockSize;

GTX 960M 的计算能力为 5,而 Titan X 的计算能力为 6.1,但根据计算能力表,两者都有最多 32 个活动块和每个多处理器最多 2048 个线程

https://en.wikipedia.org/wiki/CUDA

我在服务器上运行了cuda-memcheck,非法内存访问的问题是由于一个空指针造成的。

为了解决这个问题,我使用以下几行将堆内存大小分配从 1GB 增加到 2GB,问题就解决了:

const size_t malloc_limit = size_t(2048) * size_t(2048) * size_t(2048);
cudaDeviceSetLimit(cudaLimitMallocHeapSize, malloc_limit); 

我的问题是为什么这个问题出现在 Titan X 上,但在 960M 上没有出现?为什么我需要增加为 Titan X 分配的堆内存大小而不是 960M?

如果需要,我可以发布我的代码,但这是一个在内核中包含多个函数调用的大代码。

cuda-memcheck 后的错误如下:

GPUassert:未指定的启动失败 all.cu 779 ========= CUDA-MEMCHECK ========= 无效的 global 写入大小为 8 ========= 在 /home/osa/cuda/all.cu:186:fun(double*, double*, double*, double*, 中的 0x00001130 双*,双*,整数,整数,整数) ========= 块 (193,0,0) 中的线程 (125,0,0) ========= 地址 0x00000000 超出范围 ========= 在内核启动时将主机回溯保存到驱动程序入口点 ========= 主机框架:/usr/lib/i386-linux-gnu/libcuda.so.1 (cuLaunchKernel + 0x2fe) [0x282a4e] ========= 主机帧:./all [0x1dac1] ========= 主机框架:./all [0x382d3] ========= 主机框架:./all [0x9508] ========= 主机框架:./all [0x93c0] ========= 主机帧:./all [0x942d] ========= 主机框架:./all [0x8d7a] ========= 主机框架:/lib/x86_64-linux-gnu/libc.so.6 (__libc_start_main + 0xf0) [0x20840] ========= 主机框架:./all [0x2999] ========= ========= 无效的 global 写入大小为 8 ========= 在 /home/osa/cuda/all.cu:186:fun(double*, double*, double*, double*, 中的 0x00001130 双*,双*,整数,整数,整数) ========= 块 (193,0,0) 中的线程 (124,0,0) ========= 地址 0x00000000 超出范围 ========= 在内核启动时将主机回溯保存到驱动程序入口点 ========= 主机框架:/usr/lib/i386-linux-gnu/libcuda.so.1 (cuLaunchKernel + 0x2fe) [0x282a4e] ========= 主机帧:./all [0x1dac1] ========= 主机框架:./all [0x382d3] ========= 主机框架:./all [0x9508] ========= 主机框架:./all [0x93c0] ========= 主机帧:./all [0x942d] ========= 主机框架:./all [0x8d7a] ========= 主机框架:/lib/x86_64-linux-gnu/libc.so.6 (__libc_start_main + 0xf0) [0x20840] ========= 主机框架:./all [0x2999] ========= ========= 程序命中 cudaErrorLaunchFailure(错误 4),因为 CUDA API 调用“未指定的启动失败” cudaDeviceSynchronize。 ========= 保存主机回溯到错误的驱动程序入口点 ========= 主机框架:/usr/lib/i386-linux-gnu/libcuda.so.1 [0x391b13] ========= 主机框架:./all [0x3c2c6] ========= 主机框架:./all [0x8d83] ========= 主机框架:/lib/x86_64-linux-gnu/libc.so.6 (__libc_start_main + 0xf0) [0x20840] ========= 主机框架:./all [0x2999] ========= ========= 由于对 cudaPeekAtLastError 的 CUDA API 调用出现“未指定的启动失败”,程序命中 cudaErrorLaunchFailure(错误 4)。 ========= 保存主机回溯到错误的驱动程序入口点 ========= 主机框架:/usr/lib/i386-linux-gnu/libcuda.so.1 [0x391b13] ========= 主机帧:./all [0x39b93] ========= 主机框架:./all [0x8d88] ========= 主机框架:/lib/x86_64-linux-gnu/libc.so.6 (__libc_start_main + 0xf0) [0x20840] ========= 主机框架:./all [0x2999] ========= ========= 错误摘要:4 个错误

在我的代码中,计算了最多 19 位数字组合的可能性总数。这个数字决定了线程的总数。可能性是由(2^n)-1计算的,所以如果我选择9位,它将是511,因此该进程将总共执行511个线程。

虽然对于内核配置,我选择块大小为 128,但我也将可能性的数量(nPossibilities)作为参数并在内核内部执行以下操作:

if (idx > 0 && idx < nPossibilities)

 //Do something

在服务器上,代码最多为 15 位,对应于 32,767。 16 及以上会导致问题中发布的错误。对于 16,它将是 65,536。这是否意味着 Titan Xp 大约 32,000 个运行中的线程需要 1GB 及以上的堆,我需要分配更多?但是对于 19 位数字,我总共需要 524,287 个线程!这是很多!那么 1GB 是否足以容纳 ~32,000 个线程,而 2GB 又足以容纳 ~524,000 个线程呢?

我在内核中使用new 分配的变量的大小也取决于位数。我粗略计算了分配变量的大小,15 位为 0.032MB,16 为 0.034MB,19 为 0.0415MB

【问题讨论】:

【参考方案1】:

因为 Titan Xp 比 960M 支持更多的“正在运行”的线程。

大概在您的 CUDA 设备代码中,您正在执行类似 mallocnew 的操作(希望还有 freedelete)。这些分配在设备堆之外,其大小由您为此使用的 CUDA 运行时 API 调用控制:cudaDeviceSetLimit(cudaLimitMallocHeapSize, 1024 * 1024 * 1024);

在给定时间可以在这些 GPU 上运行的最大线程数由 2048*SM 数给出。即使您的特定代码的每个 SM 的占用数小于 2048,无论是在 960M 还是 Titan Xp 上,这个数字(每个 SM 的最大占用线程数)可能都相同。

因此,运行中的线程总数由 SM 的数量决定。 960M 有 5 个 SM,因此它最多可以有 2048x5 = ~10,000 个正在运行的线程(即在某个执行阶段)。 Titan Xp 有 30 个 SM,因此它可以有 2048x30 = ~60,000 个线程在运行。这意味着,如果每个线程执行特定大小的malloc,然后执行free,则在 960M 上的任何时间点都可能有 10,000 个未完成的分配,但在任何时间点都有 60,000 个未完成的分配Titan Xp 上的时间。更多未完成的分配 = 对(设备堆)内存的更多需求。

因此,在 Titan Xp 与 960M 上,您很可能需要更多可用空间用于设备堆。

【讨论】:

感谢您的回答!你的解释很有道理。但是,根据您的回答,我觉得我缺少一些信息,所以我编辑了我的问题。如果您阅读该内容并可能根据新信息编辑您的答案,如果您觉得还需要其他任何东西,将会很有帮助。此外,由于 Titan Xp 最多可以运行 60,000 个线程,如果两者都运行最大线程数,我是否应该期望它比 960M 快 6 倍?

以上是关于遇到 CUDA 非法内存访问的主要内容,如果未能解决你的问题,请参考以下文章

使用存储在另一个数组中的数组索引时,Cuda 非法内存访问错误

CUDA 内核和内存访问(一个内核不完全执行,下一个不启动)

内存溢出,死锁怎么办?教你如何排查

使用cuda c减少计算数组的总和

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

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