0%

CUDA 编程-基础与实践 01:GPU 硬件与 CUDA 程序开发工具

本系列文章是《CUDA 编程-基础与实践》的读书笔记。CUDA 是目前最为流行的 GPU 高性能计算的开发工具之一,该书通过大量实例系统地讲述 CUDA 编程的重要方面。

基于 CPU(central processing unit,中央处理器)和 GPU(graphics processing unit,图形处理器)的异构计算(heterogeneous computing)已逐步发展为高性能计算(high performance computing)领域的主流模式。很多超级计算机都大量使用了 GPU。CUDA(compute unified device architecture)作为 GPU 高性能计算的主要开发工具之一,已经在计算机、物理、化学、生物、材料等众多领域发挥了重要作用。

GPU 硬件简介

GPU 是英文 graphics processing unit 的首字母缩写,意为图形处理器。GPU 也常被称为显卡(graphics card)。与它对应的一个概念是 CPU,即 central processing unit(中央处理器)的首字母缩写。CPU 和 GPU 的显著区别是:

  • 一个典型的 CPU 拥有少数几个快速的计算核心,而一个典型的 GPU 拥有几百到几千个不那么快速的计算核心
  • CPU 中有更多的晶体管用于数据缓存和流程控制,但 GPU 中有更多的晶体管用于算术逻辑单元

所以,GPU 是靠众多的计算核心来获得相对较高的计算性能的。下图说明了(非集成) GPU 和 CPU 在硬件架构上的显著区别:

GPU 计算不是指单独的 GPU 计算,而是指 CPU + GPU 的异构(heterogeneous)计算。一块单独的 GPU 是无法独立地完成所有计算任务的,它必须在 CPU 的调度下才能完成特定任务。一块单独的 GPU 无法独立完成任务,主要受到以下物理和架构层面的限制:

  • 无法运行操作系统:Windows、Linux 等操作系统都是编译并运行在 CPU 架构(如 x86、ARM)上的。GPU 没有整套的控制单元来管理系统资源、文件系统或网络
  • 不擅长串行逻辑:一个复杂的程序往往包含大量的 if-else 条件判断、循环控制和串行代码。如果把这些交给 GPU,GPU 的核心会因为 分支分歧(Branch Divergence) 而陷入严重的等待,效率反而不如 CPU
  • 依赖 CPU 作为入口:所有的程序启动、数据加载(从硬盘到内存),最初都是由 CPU 发起并控制的

在由 CPU 和 GPU 构成的异构计算平台中:

  • 通常将起控制作用的 CPU 称为主机(host),将起加速作用的 GPU 称为设备(device)
  • 主机和(非集成)设备都有自己的 DRAM(dynamic random-access memory,动态随机存取内存),它们之间一般由 PCIe 总线(peripheral component interconnect express bus)连接

一个标准的 CPU + GPU 协同任务,通常遵循以下 “四步走” 流程:

1
2
3
4
5
6
7
8
9
10
[ 步骤 1 ] CPU 将数据从内存复制到显存 (Host -> Device)


[ 步骤 2 ] CPU 发射指令,启动 GPU 上的计算核函数 (Kernel)


[ 步骤 3 ] GPU 并行执行海量计算,CPU 此时可以并行做其他事或等待


[ 步骤 4 ] GPU 计算完毕,CPU 将结果从显存复制回内存 (Device -> Host)

由于这种异构架构的存在,PCIe 总线(连接 CPU 内存与 GPU 显存的通道)的带宽往往会成为整个系统的瓶颈。数据在 CPU 和 GPU 之间搬运的时间,有时甚至超过了 GPU 本身的计算时间。为了解决这个问题,业界一直在不断优化:

  • NVLink 技术:NVIDIA 开发的高速互联通道,极大提升了 GPU 之间以及 CPU 与 GPU 之间的数据传输速度
  • 统一内存(Unified Memory):在软件层面将 CPU 内存和 GPU 显存映射到同一个虚拟地址空间,让开发者不再需要手动频繁地复制数据,由系统底层自动按需调度
  • 硬件融合(如 Grace Hopper / APU):将 CPU 和 GPU 直接封装在同一块芯片或基板上,共享超高带宽的内存,让 异构 在物理上走得更近

本书中说的 GPU 都是指英伟达(Nvidia)公司推出的 GPU,英伟达公司选择用著名科学家的姓氏作为 GPU 核心架构的代号。计算能力通常用一个形如 X.Y 的主次版本号来表示:

  • 主版本号(X):代表 GPU 的核心架构代号。每当架构发生颠覆性的重大换代,主版本号就会加 1
  • 次版本号(Y):代表在同一代架构下的特性微调、升级或精简
架构代号 计算能力 典型代表 GPU 显卡型号 核心特性
Blackwell 12.0 / 12.1 RTX 5090, RTX 5080, RTX PRO 6000 消费级与工作站主力;第二代 Transformer 引擎,支持 FP4 精度。
Blackwell (数据中心) 10.0 / 10.3 B200, GB200, B300 大规模 AI 训练集群主力;超高带宽 NVLink 互联,支持双芯片缝合技术。
Hopper 9.0 H100, H200, GH200 第一代 Transformer 引擎;硬件原生支持 FP8 精度,专为大模型加速。
Ada Lovelace 8.9 RTX 4090, RTX 4080, L40S, L4 第四代 Tensor Core;引入硬件级 AV1 视频编码器;主攻桌面端渲染与高效推理。
Ampere (精炼版) 8.6 / 8.7 RTX 3090, RTX 3080, Jetson Orin 优化每 SM 内部的 FP32 单元,FP32 计算吞吐量翻倍;Orin 用于智能驾驶。
Ampere (数据中心) 8.0 A100, A30 首次引入 TF32BF16 数据格式;支持 2:4 结构化稀疏;支持 MIG(多实例 GPU)。
Turing 7.5 RTX 2080 Ti, TITAN RTX, T4 首次引入 RT Core(光线追踪核心);Tensor Core 开始支持低精度 INT8/INT4 计算。
Volta 7.0 V100, TITAN V 现代 AI 算力的起点:首次引入 Tensor Core(张量核心);引入独立线程调度。
Pascal 6.0 / 6.1 P100, GTX 1080 Ti, TITAN Xp 首次在数据中心使用 HBM2 显存NVLink;统一内存支持页面迁移引擎。
Maxwell 5.0 / 5.2 GTX 750 Ti, GTX 980 Ti, TITAN X 极大地优化了功耗比;大幅度增加了每个 SM 内部的共享内存容量。
Kepler 3.0 / 3.5 / 3.7 GTX 680, K40, K80 引入了动态并行(允许 GPU 内部启动新核函数);引入 Warp 洗牌指令。
Fermi 2.0 / 2.1 GTX 580, GTX 480 现代 CUDA 的雏形,首次支持完整的硬件级 L1/L2 缓存。
Tesla 1.0 - 1.3 8800 GTX, GTX 280 CUDA 生态的起点,仅支持基本的并行计算,不支持双精度浮点数(FP64)。

在过去,主版本号直接关联 GPU 的核心微架构;而现在,主版本号关联的是基于该架构划分的 产品功能路线(Family Line)。例如现在的 Blackwell 架构时代:B300、B200 与 RTX 5090 拥有相同的微架构代号(Blackwell),但它们的主版本号直接分裂了

  • 数据中心卡(B100, B200, B300)的计算能力是 10.x
  • 消费级/工作站卡(RTX 5090, 5080, PRO 6000)的计算能力是 12.x

CUDA 中的 Compute Capability(计算能力)并不等价于 GPU 的计算性能:

  • Compute Capability 本质上描述的是 GPU 硬件架构所支持的功能集合(feature set),核心关注 GPU 支持哪些指令、硬件特性以及相关限制,例如 Tensor Core 支持、共享内存容量限制或特定精度计算能力等。

  • 而计算性能(performance)则描述硬件在单位时间内完成计算任务的实际吞吐能力

通常,Compute Capability 的版本号越高,表示 GPU 架构越新、支持的特性越丰富,但这并不意味着计算性能一定更强。不同 GPU 往往面向不同应用场景进行优化。例如,数据中心 GPU 与消费级显卡虽然可能属于不同架构代际,但前者在 AI 训练或高性能计算场景下的实际性能可能显著更强。

衡量 GPU 理论计算性能的重要指标之一是浮点运算峰值(Floating-point Operations Per Second,FLOPS),表示 GPU 每秒最多能够执行的浮点运算次数。现代 GPU 的浮点运算能力通常以 teraFLOPS(TFLOPS,10^12 FLOPS)甚至 petaFLOPS(PFLOPS,10^15 FLOPS,通常对应低精度 Tensor 运算)表示。

除了计算吞吐能力,GPU 的显存带宽(memory bandwidth)也是影响性能的重要因素。显存带宽描述了 GPU 与显存之间的数据传输速率。当计算速度高于数据供给速度时,应用程序会受到内存访问瓶颈的限制。

此外,显存容量(memory capacity)也会影响应用程序的运行效果。显存容量决定了模型参数、输入数据以及中间结果是否能够完整驻留在 GPU 中,并影响可支持的模型规模和 batch size,从而间接影响整体性能。

CUDA 程序开发工具

目前,常见的 GPU 编程工具和异构计算框架主要包括以下几类:

  • CUDA(Compute Unified Device Architecture):由 NVIDIA 官方推出的原生异构计算平台与编程模型,是当前 AI 和高性能计算(HPC)领域事实上的行业标准。CUDA 通过对 C/C++、Python 等编程语言的扩展,使开发者能够直接编写运行在 GPU 上的核函数(Kernel),从而充分利用 GPU 的并行计算能力。由于拥有成熟的软件生态(如 cuBLAS、cuDNN、TensorRT 等),CUDA 在深度学习与科学计算领域占据主导地位。

  • OpenCL(Open Computing Language):由 Khronos Group 维护的开放异构计算标准,强调跨平台与跨硬件的可移植性。其核心目标是通过统一的编程模型支持不同厂商的计算设备,包括 NVIDIA GPU、AMD GPU、Intel GPU、CPU,甚至 FPGA 等硬件。相比 CUDA,OpenCL 具有更强的平台无关性,但在生态成熟度和性能优化体验方面通常不如 CUDA。

  • OpenACC(Open Accelerators):一种基于编译指导语句(Directive-based)的高层异构并行编程标准。开发者只需在现有 C/C++ 或 Fortran 代码中添加少量编译指令(pragma),即可将计算任务卸载到 GPU 执行,而无需显式编写底层 GPU 核函数。OpenACC 更强调开发效率与代码迁移成本,常用于科学计算和传统 HPC 应用的 GPU 加速。

CUDA 编程语言最初主要是基于 C 语言的,但目前越来越多地支持 C++ 语言。还有
基于 Fortran 的 CUDA Fortran 版本及由其他编程语言包装的 CUDA 版本,但本书只涉及基
于 C++ 的 CUDA 编程。我们称基于 C++ 的 CUDA 编程语言为 CUDA C++

CUDA 为开发者提供了两个层次的应用程序编程接口(Application Programming Interface,API),分别是:

  • CUDA Driver API:更接近底层硬件与驱动,提供更细粒度的控制能力
  • CUDA Runtime API:Runtime API 则在 Driver API 之上进行了进一步封装,接口更加简洁,开发效率更高,也是 CUDA 开发中最常使用的编程方式

这两种 API 在性能
上几乎没有差别。从程序的可读性来看,使用 CUDA Runtime API 是更好的选择。下图展示了CUDA 开发环境的主要组件:

  • 开发的应用程序是以主机(CPU)为出发
    点的
  • 应用程序可以调用 CUDA Runtime API、CUDA Driver API 及一些已有的 CUDA 库
  • 所有这些调用都将利用设备(GPU)的硬件资源

CUDA 版本由形如 X.Y.Z 的数字表示,但它并不等同于 GPU 的计算能力。可以
这样理解:CUDA 版本是 GPU 软件开发平台的版本,而计算能力对应着 GPU 硬件架构的版本。虽然它们之间没有严格的对应关系,
但一个具有较高计算能力的 GPU 通常需要一个较高的 CUDA 版本才能支持。例如最新的 CUDA toolkit 版本是 13.3.0

CUDA 开发环境搭建

GPU 计算实际上是 CPU+GPU(主机 + 设备)的异构计算。在 CUDA C++ 程
序中,既有运行于主机的代码,也有运行于设备的代码。其中,运行于主机的代码需要由主机
的 C++ 编译器编译和链接
。所以,除了安装 CUDA 工具箱,还需要安装一个主机的 C++ 编译器。

详细的 Linux 安装步骤可以参考 NVIDIA 官方文档:CUDA Installation Guide for Linux

  1. 安装主机(CPU)C++ 编译器

在 Linux 下,最常用的主机编译器是 GCC/G++。由于 CUDA 的 nvcc 编译器在底层会直接调用系统的 g++ 来编译主机端的 C++ 代码,因此必须优先安装。

1
2
sudo apt update
sudo apt install -y build-essential gcc g++ make
  1. 安装 NVIDIA 显卡驱动(系统层底座)

显卡驱动是异构计算的硬件看门人。你可以通过 Ubuntu 自带的包管理器快速安装最适合当前系统硬件的稳定版驱动。

  1. 安装 CUDA Toolkit(软件开发层)

使用 NVIDIA 官方提供的 Runfile (Local) 独立安装包方式,它最不容易与系统的包管理器发生冲突。

  1. 第四步:配置环境变量

安装完成后,CUDA Toolkit 默认会存放在 /usr/local/cuda-12.4/ 目录下。为了让终端和编译器能够找到 nvcc 编译器以及相关的数学库,需要配置环境变量。

1
2
export PATH=/usr/local/cuda/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH

执行如下命令,如果能够正确输出,就说明安装配置 ok

1
2
3
4
5
6
7
# nvcc --version

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2024 NVIDIA Corporation
Built on Thu_Mar_28_02:18:24_PDT_2024
Cuda compilation tools, release 12.4, V12.4.131
Build cuda_12.4.r12.4/compiler.34097967_0

最后我们来运行一个经典的 异构计算 Hello World 源码测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

// 运行在 GPU(Device)上的核函数
__global__ void cuda_hello() {
printf("Hello World from GPU thread %d!\n", threadIdx.x);
}

// 运行在 CPU(Host)上的主函数
int main() {
std::cout << "Hello World from CPU (Host)!" << std::endl;

// 启动 GPU 核函数(1个线程块,10个线程)
cuda_hello<<<1, 10>>>();

// 同步 CPU 和 GPU 的执行,等待 GPU 打印完成
cudaDeviceSynchronize();
return 0;
}

使用 nvcc 进行编译并运行

1
# nvcc hello.cu -o hello_hybrid
1
2
3
4
5
6
7
8
9
10
11
12
 ./hello_hybrid
Hello World from CPU (Host)!
Hello World from GPU thread 0!
Hello World from GPU thread 1!
Hello World from GPU thread 2!
Hello World from GPU thread 3!
Hello World from GPU thread 4!
Hello World from GPU thread 5!
Hello World from GPU thread 6!
Hello World from GPU thread 7!
Hello World from GPU thread 8!
Hello World from GPU thread 9!

用 nvidia-smi 检查与设置设备

可以通过 nvidia-smi(Nvidia’s system management interface)程序检查与设置设备。它包含在 CUDA 开发工具套装内:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
 nvidia-smi
Thu May 28 09:36:53 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.95.05 Driver Version: 580.95.05 CUDA Version: 13.0 |
+-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA GeForce RTX 4090 D Off | 00000000:16:00.0 Off | Off |
| 31% 25C P8 14W / 425W | 19393MiB / 24564MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 1 NVIDIA GeForce RTX 4090 D Off | 00000000:27:00.0 Off | Off |
| 31% 33C P2 52W / 425W | 21526MiB / 24564MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 2 NVIDIA GeForce RTX 4090 D Off | 00000000:49:00.0 Off | Off |
| 30% 25C P8 13W / 425W | 20978MiB / 24564MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 3 NVIDIA GeForce RTX 4090 D Off | 00000000:5A:00.0 Off | Off |
| 31% 23C P8 20W / 425W | 20978MiB / 24564MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 4 NVIDIA GeForce RTX 4090 D Off | 00000000:98:00.0 Off | Off |
| 31% 24C P8 30W / 425W | 15800MiB / 24564MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 5 NVIDIA GeForce RTX 4090 D Off | 00000000:A8:00.0 Off | Off |
| 30% 23C P8 14W / 425W | 24068MiB / 24564MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 6 NVIDIA GeForce RTX 4090 D Off | 00000000:C8:00.0 Off | Off |
| 31% 24C P8 14W / 425W | 13638MiB / 24564MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
| 7 NVIDIA GeForce RTX 4090 D Off | 00000000:D8:00.0 Off | Off |
| 31% 22C P8 11W / 425W | 8822MiB / 24564MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| 0 N/A N/A 235198 C /usr/bin/python3 456MiB |
| 0 N/A N/A 1755987 C /usr/bin/python3 958MiB |
| 0 N/A N/A 2447650 C /usr/bin/python3 8372MiB |
| 0 N/A N/A 2450943 C /usr/bin/python3 8808MiB |
| 0 N/A N/A 3967958 C ...er-4aQw4c0K-py3.12/bin/python 768MiB |
| 1 N/A N/A 3518809 C VLLM::EngineCore 21516MiB |
| 2 N/A N/A 3522679 C VLLM::Worker 20968MiB |
| 3 N/A N/A 3522711 C VLLM::Worker 20968MiB |
| 4 N/A N/A 121774 C VLLM::EngineCore 15790MiB |
| 5 N/A N/A 4046808 C ...er-4aQw4c0K-py3.12/bin/python 24058MiB |
| 6 N/A N/A 411153 C VLLM::EngineCore 13628MiB |
| 7 N/A N/A 422847 C VLLM::EngineCore 8812MiB |
+-----------------------------------------------------------------------------------------+
  • 输出 Driver 版本和该 Driver 所支持的 CUDA 最高版本(并不是系统里已经安装的版本)
  • GPU 设备信息:
    • GPU:每个 GPU 都有编号,从 0 开始
    • Name:NVIDIA GeForce RTX 4090 D
    • Persistence-M:Persistence Mode 是否开启。Off 表示 GPU 闲置时驱动可能卸载,下次使用需重新初始化
    • Bus-Id:PCIe 总线地址,唯一标识物理插槽位置
    • Disp.A:Display Active,是否有显示输出连接
    • Volatile Uncorr. ECC:不可纠正的易失性 ECC 内存错误(仅 Tesla/A100 等支持 ECC 的卡有意义,4090 不支持)
    • Fan:风扇转速百分比
    • Temp:GPU 核心温度,
    • Perf:性能状态:P0 最高性能,P8 最低功耗待机
    • Pwr:Usage/Cap:实际功耗 / 最大功耗上限
    • Memory-Usage:已用显存 / 总显存
    • GPU-Util:GPU 计算核心利用率
    • Compute M:计算模式:Default 允许多进程共享 GPU;Exclusive Process 只允许单进程
    • MIG M:MIG(Multi-Instance GPU)模式,4090 不支持
  • Processes 区域
    • GPU:占用该 GPU 的编号
    • GI/CI:GPU Instance / Compute Instance ID(MIG 相关,此处为 N/A)
    • PID:进程 ID
    • Type:C = Compute(计算类型),G = Graphics(图形类型)
    • Process name:进程名
    • GPU Memory:该进程占用的显存

CUDA 的官方手册

任何资料都不能代替官方文档,官方文档提供了 CUDA 的安装指南、编程指南、 CUDA API 手册等等。