CUDA的编译与调试

一、手动编译 CUDA 程序:理解nvcc关键参数

为同时满足 "环境验证" 和 "后续调试" 需求,需用nvcc调试模式编译程序。以下是详细拆解:

1.1 核心编译命令

编译hello_world.cu的命令如下

复制代码
nvcc -g -G -O0 -o hello ./hello_world.cu
参数解析表
参数 作用说明
-g 生成主机端(CPU)代码 的调试信息,确保gdb能识别 CPU 代码的行号、变量
-G 生成设备端(GPU)代码的调试信息(关键!无此参数无法调试 CUDA 核函数)
-O0 关闭所有代码优化(优化会导致行号与执行顺序不匹配,破坏调试准确性)
-o hello 指定输出的可执行文件名为hello
源文件路径 输入的 CUDA 源文件(此处为./hello_world.cu

示例代码

复制代码
#include <cuda_runtime.h>  // CUDA运行时核心头文件
#include <iostream>

// __global__:CUDA核函数修饰符(仅在GPU上执行,CPU可调用)
__global__ void hello_world(void) {
  // GPU线程打印"块ID"和"线程ID"
  printf("block idx:%d thread idx: %d\n", blockIdx.x, threadIdx.x);
  // 仅线程ID为0的GPU线程打印问候语
  if (threadIdx.x == 0) {
    printf("GPU: Hello world!\n");
  }
}

int main(int argc, char **argv) {
  // CPU主线程先打印问候语
  printf("CPU: Hello world!\n");
  
  // 启动CUDA核函数:<<<网格维度, 块维度>>>
  hello_world<<<1, 1>>>();  // 配置:1个块(block)、每个块1个线程(thread)
  
  // 同步GPU与CPU:等待GPU核函数执行完,再继续执行CPU代码
  cudaDeviceSynchronize();
  
  // 检查CUDA调用是否出错(调试必备的错误检查)
  if (cudaGetLastError() != cudaSuccess) {
    std::cerr << "CUDA error: " << cudaGetErrorString(cudaGetLastError())
              << std::endl;
    return 1;
  } else {
    std::cout << "GPU: Hello world finished!" << std::endl;
  }
  
  std::cout << "CPU: Hello world finished!" << std::endl;
  return 0;
}
  • __global__:核函数的专属标识,必须添加此修饰符才能在 GPU 上执行;
  • <<<1, 1>>>:核函数启动配置,第一个参数是 "网格维度"(块的数量),第二个是 "块维度"(每个块的线程数量);
  • cudaDeviceSynchronize():避免 GPU 未执行完时 CPU 已退出,导致 GPU 输出缺失。

二、cuda-gdb调试:定位 GPU 代码问题

当需要调试 GPU 核函数时,cuda-gdb是 NVIDIA 官方提供的专用工具。以下基于已编译的 "调试版本" 程序,演示核心调试步骤:

2.1 启动cuda-gdb

首先加载可执行文件,进入调试交互界面:

复制代码
cuda-gdb ./hello

启动成功后会显示如下提示(表示调试信息已加载):

复制代码
(base) test_fss@node4:~/code/cuda_code$ cuda-gdb ./hello 
...
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./hello...
(cuda-gdb)  # 此处等待输入调试命令

2.2 查看源代码(list命令)

list 行号(简写l 行号)查看指定行附近的代码,例如查看第 5 行(核函数内部):

复制代码
(cuda-gdb) l 5
1       #include <cuda_runtime.h>
2
3       #include <iostream>
4       __global__ void hello_world(void) {
5         printf("block idx:%d thread idx: %d\n", blockIdx.x, threadIdx.x);
6         if (threadIdx.x == 0) {
7           printf("GPU: Hello world!\n");
8         }
9       }
10

通过该命令可确认:调试信息已正确关联源代码,行号匹配无误。

2.3 设置断点(break命令)

在需要暂停的代码行设置断点,例如在第 5 行(核函数的打印语句处):

复制代码
(cuda-gdb) b 5
Breakpoint 1 at 0x8eaa: file ./hello_world.cu, line 5.

b 行号会在指定行的开头设置断点,程序运行到此处会自动暂停。

2.4 运行程序并触发断点(run命令)

输入run(简写r)启动程序,执行到断点时会暂停:

复制代码
(cuda-gdb) run
Starting program: /home/test_fss/code/cuda_code/hello 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
CPU: Hello world!  # CPU代码执行完成,即将启动GPU核函数
[New Thread 0x7ffff21c0000 (LWP 3908127)]  # GPU相关线程启动
[New Thread 0x7ffff0f9c000 (LWP 3908128)]
[Detaching after fork from child process 3908129]
[New Thread 0x7fffe899c000 (LWP 3908268)]
[New Thread 0x7fffd855a000 (LWP 3908269)]
# 切换到GPU线程上下文,暂停在第5行断点
[Switching focus to CUDA kernel 0, grid 1, block (0,0,0), thread (0,0,0), device 0, sm 0, warp 0, lane 0]

CUDA thread hit Breakpoint 1, hello_world<<<(1,1,1),(1,1,1)>>> () at course1/hello_world.cu:5
5         printf("block idx:%d thread idx: %d\n", blockIdx.x, threadIdx.x);

关键提示:Switching focus to CUDA kernel...说明cuda-gdb已成功切换到 GPU 核函数的线程上下文,当前暂停在块 (0,0,0)、线程 (0,0,0)

2.5 查看核函数配置(info cuda kernels

通过该命令可查看当前运行的 CUDA 核函数的 "块 / 线程配置":

复制代码
(cuda-gdb) info cuda kernels
  Kernel Parent Dev Grid Status                 SMs Mask GridDim BlockDim Invocation    
*      0      -   0    1 Active 0x0000000000000000000001 (1,1,1)  (1,1,1) hello_world()

字段解释:

  • GridDim (1,1,1):网格维度为 1(仅 1 个块);
  • BlockDim (1,1,1):块维度为 1(每个块仅 1 个线程);
  • Invocation hello_world():当前运行的核函数是hello_world

注意:若需切换到第 6 个线程(如用户示例),需在核函数启动时配置更多线程(如hello_world<<<1, 10>>>),此时BlockDim会变为(10,1,1),才能切换到thread (6,0,0)

2.6 切换 GPU 线程(cuda block/thread命令)

若核函数配置了多个线程,可通过cuda block (x,y,z) thread (x,y,z)切换到指定线程。例如切换到块 (0,0,0)、线程 (6,0,0)

复制代码
(cuda-gdb) cuda block (0,0,0) thread (6,0,0)
[Switching focus to CUDA kernel 0, grid 1, block (0,0,0), thread (6,0,0), device 0, sm 0, warp 0, lane 6]
5         printf("block idx:%d thread idx: %d\n", blockIdx.x, threadIdx.x);

切换后,cuda-gdb的焦点会转移到指定线程,后续调试操作仅针对该线程。

2.7 查看变量值(print命令)

print(简写p)查看当前线程的blockIdx.x(块 ID)和threadIdx.x(线程 ID),验证线程切换是否成功:

复制代码
(cuda-gdb) p blockIdx.x
$1 = 0  # 当前块ID为0(与切换命令一致)
(cuda-gdb) p threadIdx.x
$2 = 6  # 当前线程ID为6(切换成功)
相关推荐
2501_924877211 小时前
强逆光干扰漏检率↓78%!陌讯多模态融合算法在光伏巡检的实战优化
大数据·人工智能·算法·计算机视觉·目标跟踪
算家计算1 小时前
多模态融合新纪元:Ovis2.5 本地部署教程,实现文本、图像与代码的深度协同推理
人工智能·开源
算家计算1 小时前
全球AI百强榜发布!中国产品占据移动端半壁江山
人工智能·资讯·deepseek
max5006001 小时前
北京大学MuMo多模态肿瘤分类模型复现与迁移学习
人工智能·python·机器学习·分类·数据挖掘·迁移学习
2501_924877351 小时前
智慧零售漏扫率↓79%!陌讯多模态融合算法在智能收银与货架管理的实战解析
大数据·人工智能·算法·目标检测·边缘计算·零售
后端小肥肠2 小时前
公众号想做 10w + 趣味漫画还在卡壳?Coze 工作流从 0 到 1 教你搭,难点全拆 + 一键出稿
人工智能·aigc·coze
xz2024102****2 小时前
吴恩达机器学习作业二:线性可分逻辑回归
人工智能·机器学习·逻辑回归
机器之心2 小时前
AAAI-26投稿量爆炸:近3万篇论文,2万来自中国,评审系统都快崩了
人工智能·openai
AI浩2 小时前
YOLOv8-SMOT:一种高效鲁棒的实时小目标跟踪框架:基于切片辅助训练与自适应关联
人工智能·yolo·目标跟踪
兰亭妙微2 小时前
用户体验设计 | 什么是 AX?从 UX 到 AX 的演进
人工智能·交互·ux·用户体验设计公司