Week 1
- 复习 C/C++ (pointers, memory, functions).
- 安装 NVIDIA CUDA Toolkit + drivers.
- 运行一个CUDA例子 (
deviceQuery
,vectorAdd
).
学习目标
- 巩固:回顾 CUDA 编程所必需的 C/C++ 核心概念。
- 安装:成功安装 NVIDIA CUDA 工具包和兼容的显卡驱动。
- 验证:编译并运行官方 CUDA 示例,确保环境配置正确。
- 理解:掌握最基本的 CUDA 编程模型和核心概念。
第 1 步:巩固 C/C++ 核心知识 (Pointers, Memory, Functions)
CUDA C++ 是 C++ 的一个扩展。在深入学习 CUDA 之前,必须对以下几个 C/C++ 概念有扎实的理解,因为您将频繁地手动管理主机(CPU)和设备(GPU)之间的内存。
1.1 指针 (Pointers)
指针是一个存储内存地址的变量。在 CUDA 中,您会用到指向 CPU 内存的指针(主机指针)和指向 GPU 内存的指针(设备指针)。
核心概念回顾:
- 声明 :
int* ptr;
声明一个指向整数的指针。 - 取地址 (
&
) :int var = 10; ptr = &var;
ptr
现在存储着var
的内存地址。 - 解引用 (
*
) :*ptr = 20;
通过指针修改了var
的值,现在var
变成了 20。
C++
c
// C++ Pointer Refresher
#include <iostream>
void main() {
int value = 10;
int* pointerToValue = &value; // 指针存储了 value 的地址
std::cout << "Original value: " << value << std::endl;
std::cout << "Address of value (&value): " << &value << std::endl;
std::cout << "Pointer holds address: " << pointerToValue << std::endl;
// 使用指针修改值
*pointerToValue = 50;
std::cout << "New value after dereferencing: " << value << std::endl;
}
1.2 内存管理 (Stack vs. Heap)
- 栈 (Stack) : 用于存储局部变量和函数调用信息。内存由编译器自动管理,函数返回时自动释放。速度快,但空间有限。
- 堆 (Heap) : 用于动态分配内存,大小和生命周期由程序员手动控制。空间大,但需要显式地申请和释放,否则会导致内存泄漏。
在 CUDA 中,cudaMalloc()
类似于 C 语言的 malloc()
,它在 GPU 的全局内存(一种堆内存) 中分配空间。
核心概念回顾:
-
C 语言方式 (
malloc
/free
) :C
cint* arr = (int*) malloc(10 * sizeof(int)); // 在堆上分配10个整数的空间 // ... 使用 arr ... free(arr); // 必须手动释放
-
C++ 方式 (
new
/delete
) :C++
arduinoint* arr = new int[10]; // 在堆上分配10个整数的空间 // ... 使用 arr ... delete[] arr; // 必须手动释放数组
1.3 函数与函数指针
函数是执行特定任务的代码块。在 CUDA 中,您将编写在 GPU 上执行的特殊函数,称为"核函数 (Kernel)"。理解普通 C++ 函数的传值和传引用对于理解如何将数据传递给核函数至关重要。
核心概念回顾:
- 传值 (Pass-by-Value) : 函数接收的是实参的副本。
- 传指针 (Pass-by-Pointer) : 函数接收的是实参的地址,可以修改原始数据。
- 传引用 (Pass-by-Reference, C++) : 类似于传指针,但语法更简洁。
C++
c
// C++ Function Refresher
#include <iostream>
void modifyValue(int* ptr) {
*ptr = 100; // 通过指针修改原始数据
}
int main() {
int myVar = 5;
std::cout << "Original myVar: " << myVar << std::endl;
modifyValue(&myVar); // 传递 myVar 的地址
std::cout << "Modified myVar: " << myVar << std::endl; // 输出 100
return 0;
}
第 2 步:安装 NVIDIA CUDA Toolkit 和驱动
这是最关键的一步。驱动程序让操作系统能够与 NVIDIA GPU 通信,而 CUDA Toolkit 提供了编译器(NVCC)、库和 API,让您能够编写 GPU 程序。
2.1 检查硬件
首先,确保您的计算机上有一个 NVIDIA 的 CUDA-Capable GPU。
- Windows: 打开"设备管理器" > "显示适配器",查看是否有 NVIDIA GeForce, RTX, Quadro, Tesla, 等型号。
- Linux : 在终端中运行
lspci | grep -i nvidia
。
2.2 下载 CUDA Toolkit
- 访问官方网站:NVIDIA CUDA Toolkit Download
- 根据您的操作系统(Windows, Linux, macOS)、架构 (x86_64)、发行版和版本进行选择。
- 建议选择
network
(网络) 安装程序,它会下载最新的组件。
2.3 安装步骤
-
Windows:
- 运行下载的
.exe
文件。 - 选择"Express (推荐)"安装选项。这会同时安装 CUDA 工具包和最新的兼容驱动程序。
- 按照向导完成安装。安装程序会自动设置系统环境变量(如
CUDA_PATH
)。
- 运行下载的
-
Linux (以 Ubuntu 为例) :
-
官网会提供一系列终端命令。严格按照顺序 复制并执行它们。这通常包括添加 NVIDIA 的软件源、更新包列表以及安装
cuda
包。 -
安装命令示例:
Bash
arduinowget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb sudo dpkg -i cuda-keyring_1.1-1_all.deb sudo apt-get update sudo apt-get -y install cuda-toolkit-12-5
-
安装完成后,根据提示将 CUDA 路径添加到您的
~/.bashrc
文件中:Bash
bashecho 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc source ~/.bashrc
-
2.4 验证安装
安装完成后,打开一个新的终端(或命令提示符)并运行以下命令:
-
验证驱动程序:
Bash
nvidia-smi
如果此命令成功运行并显示您的 GPU 信息和驱动版本,说明驱动已正确安装。
-
验证 CUDA 编译器 (NVCC) :
Bash
cssnvcc --version
此命令应显示您安装的 NVCC 编译器版本。
如果这两个命令都成功执行,您的 CUDA 环境已准备就绪!
第 3 步:运行 CUDA 示例
CUDA Toolkit 自带了许多示例代码,是验证环境和学习的最佳起点。
3.1 找到示例代码
- Windows :
C:\ProgramData\NVIDIA Corporation\CUDA Samples\v<version>
- Linux :
~/NVIDIA_CUDA-<version>_Samples
或/usr/local/cuda/samples
将示例目录复制到一个您有写入权限的位置,例如您的用户主目录。
Bash
bash
# Linux
cuda-install-samples-12.5.sh ~/
cd ~/NVIDIA_CUDA-12.5_Samples/
3.2 编译并运行 deviceQuery
deviceQuery
会枚举系统中的所有 CUDA 设备并显示其属性。
-
进入
deviceQuery
目录:Bash
bashcd 1_Utilities/deviceQuery
-
编译:
- Linux : 运行
make
- Windows : 打开该目录下的 Visual Studio
.sln
文件并生成项目。
- Linux : 运行
-
运行:
Bash
bash./deviceQuery
您应该会看到类似下面的输出,列出了您的 GPU 名称、计算能力、内存大小等。最重要的是最后一行:
erlang... Device 0: "NVIDIA GeForce RTX 4080" CUDA Driver Version / Runtime Version 12.5 / 12.5 ... Result = PASS
3.3 编译并运行 vectorAdd
vectorAdd
是一个经典的并行计算入门程序,它在 GPU 上执行两个向量的相加。
-
返回示例根目录,进入
vectorAdd
目录:Bash
bashcd ../../0_Simple/vectorAdd
-
编译 (同上,
make
或 Visual Studio)。1 -
运行:2
Bash
bash./vectorAdd
如果一切正常,您会看到:3
css[Vector addition of 50000 elements] Copy input data from the host memory to the CUDA device CUDA kernel launch with 196 blocks of 256 threads Copy output data from the CUDA device to the host memory Test PASSED Done
看到 Result = PASS
和 Test PASSED
意味着您的 CUDA 环境和硬件工作完全正常!
第 4 步:阅读《CUDA by Example》第 1-2 章
现在您的环境已经就绪,是时候理解背后的理论了。请阅读《CUDA by Example: An Introduction to General-Purpose GPU Programming》的前两章。
关键概念总结 (Chapters 1-2)
-
CPU (Host) vs. GPU (Device) : CUDA 编程涉及主机和设备两端。代码在主机上启动,但计算密集型部分(称为核函数)被发送到设备上执行。
-
核函数 (Kernel) : 使用
__global__
修饰符声明的函数。当主机调用核函数时,它会在 GPU 上由成百上千个线程并行执行。 -
基本 CUDA 流程:
- 分配内存 : 在主机 (
malloc
/new
) 和设备 (cudaMalloc()
) 上都分配内存。 - 传输数据 : 使用
cudaMemcpy()
将输入数据从主机内存复制到设备内存。 - 执行核函数 : 主机调用核函数
kernel_name<<<...>>>()
在设备上执行计算。 - 传回数据 : 使用
cudaMemcpy()
将计算结果从设备内存复制回主机内存。 - 释放内存 : 释放主机 (
free
/delete
) 和设备 (cudaFree()
) 上的内存。
- 分配内存 : 在主机 (
-
线程层次结构 : CUDA 使用 Grid -> Block -> Thread 的层次结构来组织线程。您在调用核函数时指定要启动多少个线程块 (Block) 以及每个块包含多少个线程 (Thread)。
完成以上所有步骤后,您将拥有一个正常工作的 CUDA 开发环境,并对并行计算的基本模型有了初步的了解,为后续更深入的学习奠定了坚实的基础。祝您学习顺利!