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(切换成功)
相关推荐
飞哥数智坊6 小时前
从CodeBuddy翻车到MasterGo救场,我的小程序UI终于焕然一新
人工智能
AKAMAI8 小时前
跳过复杂环节:Akamai应用平台让Kubernetes生产就绪——现已正式发布
人工智能·云原生·云计算
新智元10 小时前
阿里王牌 Agent 横扫 SOTA,全栈开源力压 OpenAI!博士级难题一键搞定
人工智能·openai
新智元10 小时前
刚刚,OpenAI/Gemini 共斩 ICPC 2025 金牌!OpenAI 满分碾压横扫全场
人工智能·openai
机器之心10 小时前
OneSearch,揭开快手电商搜索「一步到位」的秘技
人工智能·openai
阿里云大数据AI技术10 小时前
2025云栖大会·大数据AI参会攻略请查收!
大数据·人工智能
YourKing11 小时前
yolov11n.onnx格式模型转换与图像推理
人工智能
sans_11 小时前
NCCL的用户缓冲区注册
人工智能
sans_11 小时前
三种视角下的Symmetric Memory,下一代HPC内存模型
人工智能
算家计算11 小时前
模糊高清修复真王炸!ComfyUI-SeedVR2-Kontext(画质修复+P图)本地部署教程
人工智能·开源·aigc