一、手动编译 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(切换成功)