报错解决:CUDA Runtime error(an illegal memory access was encountered, cudaErrorIllegalAddress = 700)

报错解决:CUDA Runtime error(an illegal memory access was encountered, cudaErrorIllegalAddress = 700)

报错

博主在cuda编程时,遇到了报错:

CUDA Runtime error cudaEventSynchronize(end_) # an illegal memory access was encountered, code = cudaErrorIllegalAddress [ 700 ]

原因

出现该现象,在框架稳定的背景下基本上可以确定是网络中有算子踩显存,因此CUDA上报非法内存访问,错误码为700,可能原因如下:

  1. 访问越界问题:算子计算过程中使用的size比申请的显存大了,导致访问越界。
  2. 同步与异步问题:由于GPU的算子执行是host下发到device上异步执行的,host使用了CUDA一些同步接口导致不是device的期望值出现非法内存。

解决

解决方法是首先排查是否是访问越界问题,若不是,接着排查是否是同步与异步问题。

访问越界问题

由于GPU的算子执行是host下发到device上异步执行的,因此执行报错的地方不一定是bug所在,可能是前面的算子有bug,但是device是异步执行的,所以执行到后面才会报错。

因此可以设置环境变量export CUDA_LAUNCH_BLOCKING=1表示阻塞式执行,即一个算子在device执行完成后,host才会下发一个算子到device上执行,这种完全同步执行方式下如果还是报此错误(非法内存访问)的话,配合算子执行日志就可以初步确定是当前执行算子的问题了。

接着,可以利用Compute Sanitizer工具进一步检查,并可以进一步验证是否是此算子的问题。

Compute Sanitizer 是一套工具,可以对代码的功能正确性执行不同类型的检查。调试的一个关键挑战是找到错误的根本原因,解决它通常比追踪它更容易,尤其是在并行执行环境中,因为在这种环境中,错误的来源可能是瞬态的。

Compute Sanitizer 通过检查代码是否存在内存访问违规、竞争条件、对未初始化变量的访问以及同步错误,擅长于根本原因调试。所有这些都可能表现为 bug,但其行为不一定会直接导致源代码中的根本原因。

您可能已经熟悉一种用于调试的工具:cuda-memcheck。但是,该工具已在 CUDA 11.6 中被弃用,并在 ZCk 12.0 及更高版本中被删除。Compute Sanitizer 已取代它的位置,提供了额外的功能,如改进的性能和对 Microsoft hardware-accelerated GPU scheduling 的支持,以及对内存检查之外的功能的更广泛支持。

Compute Sanitizer 中有四个主要工具:

  • memcheck:用于内存访问错误和泄漏检测
  • racecheck:共享内存数据访问危险检测工具
  • initcheck:未初始化的设备全局内存访问检测工具
  • synccheck:用于线程同步危险检测

除了这些工具, Compute Sanitizer 还有一些额外的功能:

  • 用于创建针对 CUDA 应用程序的清理和跟踪工具的 API 。
  • 与 NVIDIA 工具扩展(NVTX)集成
  • Coredump 支持可用于 cuda-gdb

使用方法:compute-sanitizer [options] app_name [app_options]

运行如下命令:

compute-sanitizer --launch-timeout=0 --tool=memcheck ./example > opt.txt 2>&1

打开opt.txt,查看输出:

cat opt.txt

示例输出:

========= COMPUTE-SANITIZER
Before: Array 0, 1 .. N-1: 1.000000 1.000000 1.000000
========= Invalid __global__ read of size 4 bytes
=========     at 0x70 in /home/pgraham/devblog/NCS/example1.cu:8:scaleArray(float *, float)
=========     by thread (255,0,0) in block (3,0,0)
=========     Address 0x7f3aae000ffc is out of bounds
=========     and is 1 bytes after the nearest allocation at 0x7f3aae000000 of size 4092 bytes

首先,由上述输出得到错误 info Invalid __global__ read,因为 GPU 正试图读取某个不是合法地址的全局存储器。然后,可以获得报错代码文件和行号,以及导致此问题的实际线程和块。在这种情况下,example1.cu:8 映射到代码中的array[threadGlobalID] = array[threadGlobalID]*value;

使用printf查看索引的值,观察是否越界。

最简单的修改方法是在数组访问前添加对索引的判断条件:if (threadGlobalID<N)

同步与异步问题

执行上述的操作后问题不复现,也就是同步执行的这种方式下没有问题,基本上可以确定是有算子里依赖同步执行的结果,因为正常device算子执行是异步执行,所以拿的结果不是预期值,同步执行就掩盖了这个问题。出现这种情况,除了阅读代码去确认是哪个算子里有使用同步操作接口外,一般可以通过二分法去加同步流来去定位(如果一个网络中有100个算子,可以在第50个算子加同步流,如果加了同步流没有复现,说明就前面的某个算子有问题了),同步流添加方法:在GPUDeviceContext::LaunchKernel函数中调用DoLaunchKernel后面增加SyncStream()

总结

  1. 算子实现逻辑中针对size需要check,确保访问不越界。
  2. 由于GPU的算子执行是host下发到device上异步执行的,尽量避免使用CUDA同步接口,如果使用的话,需要确保数据是正确的。

参考文献

  1. https://developer.nvidia.com/zh-cn/blog/debugging-cuda-more-efficiently-with-nvidia-compute-sanitizer/
  2. https://www.cnblogs.com/cxl11/articles/15730720.html
  3. https://stackoverflow.com/questions/27277365/unspecified-launch-failure-on-memcpy/27278218#27278218
  4. https://blog.csdn.net/Bit_Coders/article/details/113181262
  5. https://blog.csdn.net/LostUnravel/article/details/133780624
  6. https://forums.developer.nvidia.com/t/compute-sanitizer-not-quite-a-drop-in-replacement-of-cuda-memcheck/225877/6

猜你喜欢

转载自blog.csdn.net/weixin_43603658/article/details/134808971