福生无量摸鱼天尊

【CUDA从入门到入土】二、CUDA调试和必知必会 & Nsight Computer 入门

2025/08/18
449
0

上文中,我们运行了一个简单的cuda函数,并且一次过的将其运行了起来,这次,我们需要补充一些基础的概念,通过概念和框架的建立,我们才能走的更远,高屋建瓴的认识更多。

vscode的CUDA调试

继续沿用上次的hello world代码,可能名字有点修改,问题不大,请务必看清楚文件名!

新建一个result目录:

sudo mkdir –m 777 buiild

测试一下没问题:

(base) moyu@DESKTOP-5C0FGMS:~/cuda_study$ nvcc -g -G -O0 -o ./build/hello ./course1/hello.cu 
nvcc warning : Support for offline compilation for architectures prior to '<compute/sm/lto>_75' will be removed in a future release (Use -Wno-deprecated-gpu-targets to suppress warning).
(base) moyu@DESKTOP-5C0FGMS:~/cuda_study$ ./build/hello 
CPU: Hello world!
thread idx: 96
thread idx: 97
thread idx: 98

cuda代码爆红问题

原因是vscodec_cpp_properties.json没有锁定到对应的目录,把cudainclude导进来,还有把编译的地址改到nvcc上:

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/usr/local/cuda/include"
            ],
            "defines": [],
            "compilerPath": "/usr/local/cuda/bin/nvcc",
            "cStandard": "c17",
            "cppStandard": "gnu++17",
            "intelliSenseMode": "linux-gcc-x64"
        }
    ],
    "version": 4
}

CMake

先安装,不安装可能会报错:

sudo apt install libssl-dev

进入CMake官网,选择最新的下载版本,下载(收手吧阿祖,先往下看,我炸锅了,别你也中招了)

直接拖到vscode中,不要放在源代码下进行解压,丢到别的地方,然后解压,安装:

mv cmake-4.1.0.tar.gz ../
tar -zxvf cmake-4.1.0.tar.gz
cd cmake-4.1.0

./bootstrap --prefix=/usr/local/cmake

make
make install

炸锅了,不要用4,版本太高了,cuda报错了(难绷)

mv cmake-3.28.4.tar.gz ../
tar -zxvf cmake-3.28.4.tar.gz 
cd cmake-3.28.4

./bootstrap --prefix=/usr/local/cmake

make
sudo make install

bash中写入cmake

# 1、打开环境配置文件
vim ~/.bashrc
# 2、写入环境  这里是上面指定安装目录的文件地址
export PATH="/usr/local/cmake/bin:$PATH" 
# 3、激活环境
source ~/.bashrc

检查是否安装成功:

# 查看版本
cmake --version
# 查看位置
which cmake

如果报错:

CMake Error at Utilities/cmcurl/CMakeLists.txt:993 (message):
  Could not find OpenSSL.  Install an OpenSSL development package or
  configure CMake with -DCMAKE_USE_OPENSSL=OFF to build without OpenSSL.

请安装上面的libssl-dev

安装插件

vscode均可正常安装上面的插件,如果是trae或者cursor安装插件可以看这篇博客。请注意,在vscode中安装插件只是能让vscode嗅探到对应的环境并且接受数据,不代表ubunt安装了cmakeubuntu里就有了cmake,该安装还是要安装的。

vscode 调试报错 Warning:Debug interface is not enabled.

根据cuda的文档显示,如果window连接wsl使用debug,需要在注册表中进行配置,在:

HKEY_LOCAL_MACHINE\SOFTWARE\NVIDIA Corporation\

中新加一个GPUDebugger 项,然后加入一个EnableInterface(DWORD) ,将其配置为1。

配置launch.json

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "CUDA C++: Launch",
            "type": "cuda-gdb",
            "request": "launch",
            "program": "/home/moyu/cuda_study/build/hello"
        }
    ]
}

注意,这里的program配置成可执行文件的目录,也就是刚刚./hello运行的文件

配置settings.json

{
    "files.associations": {
        "iostream": "cpp"
    },
    "cmake.debugConfig": {
        "miDebuggerPath": "/usr/local/cuda-12.9/bin/cuda-gdb",
    },
    "cmake.sourceDirectory": "/home/moyu/cuda_study/course1"
}

这里的cuda-gdb目录可以用 whereis cuda-gdb来找到

配置CMakeLists.txt

cmake_minimum_required(VERSION 3.17.0)
project(testdebug VERSION 0.1.0 LANGUAGES CUDA CXX C)

find_package(CUDAToolkit)


set(CMAKE_CUDA_STANDARD 11)

add_executable(hello hello.cu)
target_link_libraries(hello PRIVATE CUDA::cudart ${CUDA_cublas_LIBARAY})
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    target_compile_options(hello PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:-G>)
endif()

注意,这里的add_executable要指向文件,请务必留意名字

F5 调试

摁下F5,开始调试,如果右下角出现CUDAGrid编号,即为成功

不断的点击单步执行F11,右下角的CUDA Grid就会如下:

再运行就跳出kernel,此时调试正常。

Nsight Computer 入门

首先要打开window的GPU性能计数器,注意这里提示说要window管理员,如果不行请回头看看是不是这一步的问题。

开启之后就可以使用ncuprofiling了。

需要注意的是,下载nsight Computer如果下载最新的版本可能会出现bug,如果出现了奇奇怪怪的bug就不要下载最新的版本,回退一两个版本就行。

这里我们在window安装Nsight computer地址在这。我这里是突然发现Nsight自动变成了WSL2版本,也不知道为什么。

点开,按照下图进行配置:

然后将下面的full勾选上,不然分析少了点东西。

然后点击launch开始分析。

大伙可以随便看看,这里我们可以查看一个点,如下图:

如果没有勾选刚刚的full,那么这里的流程图就不会出现,详情请看文档

这里我们会谈到一些指标,可能有些超过目前的认知,可以先看看。

(🚧施工ing)

Nsight Computer 查看share memory

如果你执行了kernel中带有share memory优化的kernel,那么在Nsight computer里对应下图操作:

可以看到kernel发射多少条指令,发送了多少请求指令。

继续往下滑就可以看到表格了,如果不选all,就看不到下面的表格数据:

在第一个表的最后一列可以直接看到有多少Bank Conficts内存块冲突,优化的时候就要尽可能的减少它。

cuda程序框架

每个cuda代码,基本上都遵从一个基本的框架:

  • 把数据从cpu丢到GPU

  • 从GPU安排流水线计算

  • 把结果从GPU拷贝出来

  • 释放内存

这里难点就是在GPU中安排流水线作业,在多卡环境中,我们定义cpu为host,而GPU为device,而在【CUDA从入门到入土】一、丝滑的CUDA入门中,我们看到了一个kernel为:

#include <cuda_runtime.h>
#include <iostream>
// 这段代码是在gpu当中执行的
__global__ void hello_world(void) {
  printf("thread idx: %d\n", threadIdx.x);
  if (threadIdx.x == 0) {
    printf("GPU: Hello world!\n");
  }
}

int main(int argc, char **argv) {
  printf("CPU: Hello world!\n");
  hello_world<<<1, 10>>>();
  cudaDeviceSynchronize(); // cpu要等待gpu执行结束
  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__ 表示只能从主机端调用,在设备端执行,返回值是void的一个核函数。

而这样的kernel到底是面向block还是thread的呢?

【CUDA从入门到入土】一、丝滑的CUDA入门中,我们已经知道GridDim是用来定义block摆放的样子,而blockDim是用来表示一个block最多有多少个thread,而threadIdx是表示当前是第几个thread。那么一个__global__kernel function是先索引某个threadIdx 的,那肯定是面向thread来编程啦,毕竟thread才是最小的单位,代码里输出的也是threadIdx

kernel编程的是面向thread的。。。。。。。吗?

显然,不完全对,因为blockthread被我们下意识的分开了,但其实他们俩是不能分开来讨论的,blockthread存在的意义,都是为了更好的并发执行运算,他们俩在编写并发程序时,并发优化的方法已经从单一thread优化到了warp,到了block,甚至到了Grid,所以从一开始,我们就不应该割裂开这三个,通过下图我们更能体会其中的含义:

所以本质上,Gridblockthread只是划分数据进行并行的不同单位,通过层级嵌套管理进行分配。

所以当我们拿到一个新的问题的时候,我们要对数据进行划分,由于cudakernel function是面向block的,那么我们画图的时候只需要关注一个block关注的数据,然后写代码的时候由于我们是写一个thread如何并行,那么kernel里是要写threadcode,这点我们在后面会反复提到。

Cuda的block和thread

在第一章的时候,我们提到过一个SM的内有多个Warp scheduler,这里涉及到一个问题,我们设计kernel的时候thread的数量和block的数量关系应该如何呢?

现在已知的条件是一个warp会并发32个thread,根据H100内有4个warp scheduler,也就是能同时并发128个threads。在讨论数量关系的时候,最好就是讨论特殊关系,然后再将特殊关系一般化。

假如说kernel内我设置每个blockthreads就是128,跟block每次并发的个数一样,那会发生什么呢?我们来思考一下这个过程,block内每个thread执行kernel function,从global memory拿数据,放到shared memory,执行运算,把数据写会global memory或者寄存器。这一切都很正常。。。。。吗?

这里我们要谈到cuda一个核心且最重要的宗旨,就是要拉满GPU和利用率,当blockthread执行kernel的时候,需要从global memory取数据放到shared memory,这里存在了一个访存的气泡,而访存的时间和高速并发运算的时间并不是一个维度的概念。

(这里可以设计试验进行验证)

所以我们要拉大blockthreads的数量,最好在四倍以上,这样能保证访存期间SM内一直都有运算指令运行在访存期间,这样就可以拉满GPU利用率。