CUDA核函数的调试

用Visual Studio进行GPU开发时,断点无法进入CUDA核函数进行调试,因为默认断点在CPU设备上的程序生效,无法访问GPU设备上的内存空间,这个时候需要借助GPU调试工具让断点能够进入CUDA核函数进行调试。网上这方面的资料甚少,要摸索清楚其中的细节需要花很多精力,所以我总结了一份资料以供参考。

1、CUDA调试工具 – Nsight Visual Studio Edition

前往官网进行下载 Nsight Visual Studio Edition调试工具

图片[1]-CUDA核函数的调试-阿尔法欧欧

注意依赖的CUDA版本

图片[2]-CUDA核函数的调试-阿尔法欧欧

鼠标右键查看查看NVIDIA 显示驱动程序版本

图片[3]-CUDA核函数的调试-阿尔法欧欧

所以我这里下载的是2025.2.1版本的Nsight Visual Studio Edition,下载完之后打开Visual Studio,显示如下代表安装成功(我用的Visual Studio2022,不同版本显示地方可能不同)

图片[4]-CUDA核函数的调试-阿尔法欧欧

2、sln项目调试CUDA核函数

配置CUDA依赖(和调试工具无关,这是CUDA程序必须的依赖项)

图片[5]-CUDA核函数的调试-阿尔法欧欧

勾选下面的CUDA依赖项

图片[6]-CUDA核函数的调试-阿尔法欧欧

右键单击包含的.cu文件,然后选择属性,将项目类型更改为CUDA C/C++

图片[7]-CUDA核函数的调试-阿尔法欧欧
图片[8]-CUDA核函数的调试-阿尔法欧欧

操作完之后,右键项目选择“属性”会显示以下项

图片[9]-CUDA核函数的调试-阿尔法欧欧

Debug模式直接调试,无需配置

图片[10]-CUDA核函数的调试-阿尔法欧欧

Release模式下调试,会显示以下情况,通常是由于编译配置或调试设置不正确导致的。

图片[11]-CUDA核函数的调试-阿尔法欧欧

右键项目选择“属性”,将以下项改为“是”即可,使其能够生成设备代码(核函数)的调试信息

图片[12]-CUDA核函数的调试-阿尔法欧欧

3、cmake项目调试CUDA核函数

需要在cmakelists.txt中增加以下内容

# CUDA调试配置
target_compile_options(BinaryClassify PRIVATE
    $<$<AND:$<CONFIG:Debug>,$<COMPILE_LANGUAGE:CUDA>>:-G -g -O0>
    $<$<AND:$<CONFIG:Debug>,$<COMPILE_LANGUAGE:CXX>>:-O0>
)

核心作用仅在 Debug 模式下,为 CUDA 代码和 C++ 代码分别设置针对性的调试编译选项,确保调试时能正确断点(尤其是 CUDA 核函数)。
其中-G -g -O0的意思:
-G:CUDA 编译器(nvcc)专用标志,生成 GPU 核函数(设备代码)的调试信息(没有它,调试器无法识别核函数的断点位置)。
-g:生成 CPU 端代码(主机代码)的调试信息(确保主机端 C++ 代码的断点正常工作)。
-O0:禁用编译器优化(优化会导致源码行与实际执行代码位置不匹配,调试时可能跳行或断点失效)。

完整的CMakeLists.txt文件参考(这是我实际项目中的CMakeLists.txt,根据自己的情况修改)


cmake_minimum_required (VERSION 3.18)

# 变量
# 检查环境变量是否设置
if(NOT DEFINED ENV{CUDA_PATH})
  message(FATAL_ERROR "请设置 CUDA_PATH 环境变量指向 CUDA 安装目录")
endif()
if(NOT DEFINED ENV{TENSORRT_PATH})
  message(FATAL_ERROR "请设置 TENSORRT_PATH 环境变量指向 TensorRT 安装目录")
endif()
if(NOT DEFINED ENV{OPENCV_PATH})
  message(FATAL_ERROR "请设置 OPENCV_PATH 环境变量指向 OpenCV 根目录")
endif()

set(CUDA_PATH $ENV{CUDA_PATH})
set(TENSORRT_PATH $ENV{TENSORRT_PATH})
set(OPENCV_PATH $ENV{OPENCV_PATH})

project (BinaryClassify LANGUAGES CXX CUDA)
set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CXX_STANDARD 17)


# 添加 CUDA 源文件
file(GLOB_RECURSE CURRENT_CUDA_SOURCES 
    "CUDA_FUNC/*.cuh"
    "CUDA_FUNC/*.cu"
)

# 定义可执行目标
add_executable (BinaryClassify 
    "BinClassify/main.cpp"
    "BinClassify/BinaryClassify.cpp"
    "BinClassify/OnnxToTensorRT.cpp"
    ${CURRENT_CUDA_SOURCES}
  "common/utils.h" "common/utils.cpp")


# CUDA调试配置
target_compile_options(BinaryClassify PRIVATE
    $<$<AND:$<CONFIG:Debug>,$<COMPILE_LANGUAGE:CUDA>>:-G -g -O0>
    $<$<AND:$<CONFIG:Debug>,$<COMPILE_LANGUAGE:CXX>>:-O0>
)


# 包含目录
target_include_directories(BinaryClassify PRIVATE
    "${CUDA_PATH}/include"
    "${TENSORRT_PATH}/include"
    "${OPENCV_PATH}/build/include"
    "${OPENCV_PATH}/build/include/opencv2"
    "${CMAKE_CURRENT_SOURCE_DIR}/common"
    "${CMAKE_CURRENT_SOURCE_DIR}/ThirdParty"
    "${CMAKE_CURRENT_SOURCE_DIR}/CUDA_FUNC"
)

# 指定库文件搜索路径
target_link_directories(BinaryClassify PRIVATE
    "${TENSORRT_PATH}/lib"
    "${OPENCV_PATH}/build/x64/vc15/lib"
    "${CUDA_PATH}/lib/x64"
)

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    target_link_libraries(BinaryClassify PRIVATE
        opencv_world450d.lib
        nvinfer.lib
        nvinfer_plugin.lib
        nvonnxparser.lib
        nvparsers.lib
        cudart.lib
    )
elseif(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
    target_link_libraries(BinaryClassify PRIVATE
        opencv_world450.lib
        nvinfer.lib
        nvinfer_plugin.lib
        nvonnxparser.lib
        nvparsers.lib
        cudart.lib
    )
endif()

# 自动生成CUDA设备代码
set_target_properties(BinaryClassify PROPERTIES
    CUDA_SEPARABLE_COMPILATION ON
)

调试界面如下,断点进入了cuda核函数,大功告成!

图片[13]-CUDA核函数的调试-阿尔法欧欧

4、工具使用教程

可以参考 Nsight Visual Studio Edition官方文档

4.1、Break On Launch选项

其核心作用是:让程序在启动时立即暂停(中断)执行,方便开发者从程序入口开始逐步调试,尤其是针对启动阶段的初始化逻辑(包括 CUDA 上下文创建、设备初始化等早期操作)

图片[14]-CUDA核函数的调试-阿尔法欧欧

使用场景举例:

  • 调试依赖启动顺序的逻辑(如先初始化 TensorRT 引擎,再调用 CUDA 核函数),确保每一步初始化都按预期执行。
  • 当你的 CUDA 程序在启动后很快崩溃(如未进入 main 函数就报错),启用 Break On Launch 可以捕获崩溃前的初始状态;
  • 需验证 CUDA 上下文是否在程序启动时正确创建,可在中断后通过 Nsight 的 CUDA 调试窗口(如 “CUDA Contexts”)查看设备状态;

一般情况不勾选这个选项,勾选之后进入你断点处的CUDA之前可能要经历好多个中断,因为它只要是进入CUDA核函数就会中断,有些核函数只有库没有代码,中断那些库会显示“中断不在模块中”,这可能对于你的调试帮助不大。到你设置的断点后按F5继续时也会经历好多个“中断不在模块中”的提示,也就是要按好多次F5才能进入下一个你设置的断点。
不勾选这个选项后,会干净很多,调试时只会在你核函数断点处中断。

4.2、步进行为

当使用 “单步执行(step)” 时(对应F11),除了调试器当前关注的线程束(focus warp)之外,其他所有线程束都会被冻结(暂停执行)。但当使用 “单步跳过(step over)” 时(对应F10),当前线程块(block)中的所有线程束都被允许继续推进执行(不会被冻结)。这一特性(step over 时允许所有线程束推进)能让关注的线程束(focus warp)顺利通过syncthreads()同步点。
调试时,step会冻结其他线程束(仅跟踪单个线程束),但可能卡在syncthreads()step over会让当前线程块内所有线程束一起推进,确保syncthreads()能正常完成,避免调试时的同步阻塞。这是针对 CUDA 并行代码(依赖线程同步)的特殊调试优化。

4.3、冻结选项

在 CUDA 核函数调试中,“冻结”(Frozen)指的是调试器暂停特定线程 / 线程束 / 线程块的执行状态,使其暂时停止推进指令、修改寄存器或内存数据,仅保留当前的执行上下文(如寄存器值、变量状态、程序计数器位置等)。这一机制是并行调试的核心手段,目的是在多线程并行执行的复杂环境中,隔离并聚焦于需要调试的目标线程(或线程束),避免其他并行单元的执行干扰调试观察。

图片[15]-CUDA核函数的调试-阿尔法欧欧
设置对“运行”命令的作(继续、F5 或运行到光标)对“单步”命令的作(单步执行、单步执行或单步执行)
调度程序锁定恢复全部
Scheduler Locking Resume All
没有什么是冻结的。没有什么是冻结的。
调度程序锁定恢复块
Scheduler Locking Resume Block
当前块之外的所有线程束都被冻结。当前块之外的所有线程束都被冻结。
调度程序锁定恢复线程束
Scheduler Locking Resume Warp
除当前线程束外,所有线程束都被冻结。除当前线程束外,所有线程束都被冻结。
调度程序锁定步进块
Scheduler Locking Step Block
没有什么是冻结的。当前块之外的所有线程束都被冻结。
调度器锁定步长线程束(默认)
Scheduler Locking Step Warp (default)
没有什么是冻结的。除当前线程束外,所有线程束都被冻结。

4.4、“线程”列的颜色图例

图片[16]-CUDA核函数的调试-阿尔法欧欧

4.5、寄存器

在 CUDA 调试器中,“Registers”(寄存器)视图用于展示当前 GPU 寄存器的状态,帮助开发者观察线程执行过程中寄存器值的变化(若值自上次执行中断后发生改变,会以红色高亮显示)。该视图分为 5 个部分,各自对应不同类型的寄存器信息,具体解释如下:

  • SASS 寄存器反映硬件执行的真实状态
  • Predicate 寄存器揭示线程的条件执行状态
  • PTX 和 PTX loc 寄存器则从中间代码层面关联高级语言与底层执行,便于理解编译器行为。
图片[17]-CUDA核函数的调试-阿尔法欧欧

© 版权声明
THE END
喜欢就支持一下吧
点赞0赞赏 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容