首页 > 基础资料 博客日记

【读书笔记】【CUDA编程指南】CUDA简介

2026-04-06 22:30:02基础资料围观1

极客资料网推荐【读书笔记】【CUDA编程指南】CUDA简介这篇文章给大家,欢迎收藏极客资料网享受知识的乐趣

第1章 CUDA介绍——深入理解CUDA编程模型

本文基于 CUDA Programming Guide Release 13.2 第1章整理,覆盖 GPU 硬件架构、线程执行模型、内存体系以及 CUDA 编译链的完整知识体系,适合有一定 C++ 基础、希望入门 GPU 编程的读者。
本文真人进行创作,AI进行润色和排版


目录


1. GPU 计算的崛起

GPU(图形处理器)最初是为实时 3D 渲染设计的专用定制硬件,负责固定功能的并行图形流水线加速。到了 2003 年,图形渲染管线的部分阶段变得完全可编程,开发者可以为场景中的每一个像素或顶点运行自定义代码。

这一可编程能力引发了研究者的想象:能否把 GPU 强大的并行计算能力用到图形以外的领域?2006 年,NVIDIA 正式推出 CUDA(Compute Unified Device Architecture,统一计算设备架构),使得开发者可以脱离图形 API,将任意计算工作负载交给 GPU 来加速。

自此,GPU 计算被广泛用于:

  • 流体动力学、能量传输等科学仿真
  • 数据库与大数据分析
  • 图像分类、扩散模型、大语言模型等 AI 技术

GPU 的可编程性已成为现代 AI 基础设施不可或缺的基石。


2. GPU vs CPU:设计哲学的根本差异

GPU 和 CPU 在设计目标上有着根本差异。

维度 CPU GPU
核心目标 最快执行单条串行线程(低延迟) 同时执行数千线程(高吞吐)
并发线程数 几十个 数万至数十万个
晶体管分配 大量用于缓存和流控制 大量用于数据计算单元
单线程性能 相对弱
总计算吞吐量 相对低 极高

下图直观展示了两者的晶体管分配差异:

CPU vs GPU 晶体管分配对比

CPU 以缓存和流控制(Control)为主,GPU 则将绝大多数晶体管投入到计算核心(ALU)。

这一差异决定了 GPU 非常适合数据并行(Data-Parallel)的计算场景:大量数据做同样的操作,而不需要复杂的串行依赖关系。


3. 快速开始:使用 GPU 的多种途径

直接编写 CUDA 代码并不是使用 GPU 的唯一方式。根据场景不同,有以下几种由浅入深的途径:

使用现成库

NVIDIA 提供了大量针对各 GPU 架构深度优化的库,往往比自己实现的性能更好:

库名 用途
cuBLAS 线性代数(矩阵乘法等)
cuFFT 快速傅里叶变换
cuDNN 深度神经网络
CUTLASS 模板化矩阵乘法

使用 AI 框架

PyTorch、TensorFlow 等框架在底层调用上述库来实现 GPU 加速,用户无需感知 CUDA 细节。

使用领域专用语言(DSL)

  • NVIDIA Warp:面向物理仿真的 Python DSL,编译至 CUDA 执行。
  • OpenAI Triton:面向深度学习算子开发的 Python DSL,编译至 PTX/CUDA。

以上途径均构建于 CUDA 平台之上。本文后续聚焦于直接编写 CUDA 代码的核心概念。


4. CUDA 编程模型

4.1 异构系统:Host 与 Device

CUDA 编程模型假设一个异构计算系统,即同时包含 CPU 和 GPU 的系统。

术语 含义
Host CPU 及其直接连接的内存(主机内存)
Device GPU 及其直接连接的显存(设备内存)
Host Code 运行在 CPU 上的代码
Device Code / Kernel 运行在 GPU 上的代码

CUDA 应用始终从 CPU 开始执行。Host Code 通过 CUDA API 完成三件事:

  1. 在 Host 内存和 Device 内存之间拷贝数据
  2. 启动(Launch)核函数(Kernel)在 GPU 上并行执行
  3. 等待数据拷贝或 GPU 执行完成

CPU 和 GPU 可以同时运行,最优性能通常来自最大化两者的利用率。

4.2 GPU 硬件:SM 与 GPC

对于 CUDA 编程而言,GPU 可以被理解为以下硬件层次:

GPU
└── GPC(图形处理集群,Graphics Processing Cluster)×N
    └── SM(流多处理器,Streaming Multiprocessor)×M
        ├── 本地寄存器文件(Register File)
        ├── 统一数据缓存(Unified Data Cache)
        │   ├── L1 Cache(自动缓存)
        │   └── Shared Memory(可编程共享内存)
        └── 多个功能计算单元(ALU 等)

每个 SM 内部的 Unified Data Cache 在物理上是同一块资源,但可以在运行时配置 L1 Cache 和 Shared Memory 的比例分配。

下图展示了 GPU 的完整硬件架构:

GPU硬件架构

GPU 通过 PCIe 或 NVLINK 与 CPU 相连。

4.3 线程层次结构

CUDA 的线程组织是理解编程模型的核心。从小到大依次为:

Thread(线程)
    ↓ ×32
Warp(线程束)
    ↓ ×N
Thread Block(线程块)
    ↓ ×M [可选,计算能力 9.0+]
Cluster(簇)
    ↓ ×K
Grid(网格)

每启动一个 Kernel,整体执行的所有线程构成一个 Grid(网格)

Grid与Thread Block层次结构

内建变量

在 Kernel 内,每个线程通过以下内建变量确定自己的"身份":

变量 类型 含义
threadIdx dim3 线程在其线程块内的位置(x/y/z)
blockIdx dim3 线程块在网格内的位置(x/y/z)
blockDim dim3 线程块的维度大小(x/y/z)
gridDim dim3 网格的维度大小(x/y/z)

线程块和网格均支持 1、2、3 维布局,方便将线程映射到不同形状的数据。

以一维网格为例,计算每个线程的全局唯一索引:

__global__ void kernel() {
    int globalIdx = blockIdx.x * blockDim.x + threadIdx.x;
    // 使用 globalIdx 决定该线程负责处理哪个数据元素
}

线程块的执行规则

  • 同一线程块内的所有线程保证运行在同一个 SM 上,可通过共享内存高效通信与同步。
  • 不同线程块之间相互独立,执行顺序任意,不能有数据依赖关系。
  • 一个 Grid 可以包含数百万个线程块,GPU 的调度器将其分发至可用的 SM 执行。

下图展示了 Grid 中的线程块如何被分配到各 SM 上执行:

线程块调度到SM

每个 SM 同时承载多个线程块,但线程块之间的调度顺序没有任何保证。

线程束(Warp)

线程块内的线程以 32 个为一组组成一个 Warp(线程束)。Warp 是 GPU 调度和执行的基本单元。

最佳实践:线程块的线程总数最好是 32 的整数倍。若不是整数倍,最后一个 Warp 会有若干空闲 Lane,导致计算单元浪费。

Cluster(簇)—— 计算能力 9.0 新增

随着 GPU 内 SM 数量的不断增多,跨线程块的通信如果只能依赖全局内存,效率极低。为此,计算能力 9.0(Hopper 架构,如 H100)引入了簇(Cluster)这一可选分组层级,位于线程块和网格之间。

簇的特性:

  • 同一簇内的所有线程块调度到同一 GPC 内的各 SM 上,且同时执行
  • 簇内不同块的线程可通过 Cooperative Groups API 进行通信与同步。
  • 簇内线程可访问同 GPC 内所有线程块的共享内存,称为分布式共享内存(Distributed Shared Memory)

下图展示了含簇的网格逻辑结构(Grid → Cluster → Block 三层嵌套):

含Cluster的网格结构

下图展示了簇内线程块如何被同时调度到同一 GPC 内的各 SM:

Cluster调度到GPC

4.4 SIMT 执行模型与分支发散

Warp 内 32 个线程以 SIMT(Single Instruction Multiple Threads,单指令多线程) 方式执行:所有线程同时执行同一条指令,但每个线程操作的数据不同(由各自的 threadIdx/blockIdx 决定)。

分支发散(Warp Divergence)

当 Warp 内的线程走上不同的分支时,就会发生分支发散

if (threadIdx.x % 2 == 0) {
    a = r(t);   // 仅偶数 lane 执行
} else {
    a = q(t);   // 仅奇数 lane 执行
}
y = f(a);       // 全部 lane 执行

执行过程:

  1. 先执行 if 分支,奇数 lane 被掩码屏蔽(masked off)等待
  2. 再执行 else 分支,偶数 lane 被掩码屏蔽等待
  3. 最后所有 lane 执行 y = f(a)

这意味着两段代码串行执行,原本并行的能力被折半浪费。下图直观展示了各 Lane 在分支执行时的掩码状态:

Warp Divergence Lane掩码

绿色 Lane 为活跃执行,灰色 Lane 为掩码等待状态。因此,减少 Warp 内分支发散是 CUDA 性能优化的重要目标。

SIMT vs SIMD

SIMD SIMT
控制流 统一,单一路径 每个线程可走独立路径
数据宽度 固定(如 AVX-512 = 512 bit) 无固定宽度
编程抽象 向量寄存器 独立线程

SIMT 给程序员提供了"每个线程独立"的编程抽象,但底层仍是 Warp 粒度的执行。

注意:指南明确建议程序员按照 SIMT 编程模型编写代码,而非依据底层硬件实现细节。硬件在不违反编程模型的前提下可进行优化;若程序依赖未定义的 Warp 执行细节,可能在不同 GPU 架构上产生 UB(未定义行为)。


5. GPU 内存体系

现代计算中,内存的高效利用与计算单元的最大化同等重要。GPU 拥有多种类型的内存,各有其用途和访问特性。

5.1 全局内存(DRAM)

  • GPU 自带的 DRAM 称为全局内存(Global Memory),即通常所说的"显存",所有 SM 均可访问。
  • CPU 的 DRAM 称为系统内存/主机内存(System Memory / Host Memory)
  • 在当前支持的系统中,CPU 和 GPU 共享同一虚拟地址空间,但各自的物理内存相互独立(每个 GPU 的虚拟地址范围唯一且与 CPU 不同)。
  • CUDA 提供 API(如 cudaMalloccudaMemcpy)用于:在 GPU 上分配内存、在 CPU 和 GPU 之间或 GPU 之间进行数据拷贝。

5.2 片上内存

除全局内存外,每个 SM 还有多种片上内存(On-Chip Memory),访问速度极快:

内存类型 归属 访问范围 控制方式 说明
寄存器文件(Register File) 每个 SM 单个线程私有 编译器自动分配 存储线程局部变量,速度最快
共享内存(Shared Memory) 每个 SM 同一线程块(或簇)内全部线程 程序员显式控制 线程间通信的高速通道
L1 Cache 每个 SM 自动(透明) 硬件自动管理 与共享内存共享物理资源(Unified Data Cache)
L2 Cache 整个 GPU 所有 SM 共享 硬件自动管理 比 L1 更大,访问稍慢
常量缓存(Constant Cache) 每个 SM Kernel 内所有线程只读 声明为 __constant__ 缓存全局内存常量及 Kernel 参数

关键资源约束:调度线程块到 SM 时,该线程块所需的寄存器总数(每线程寄存器数 × 线程数)不能超过 SM 的寄存器文件大小;超出则 Kernel 无法启动,需减少线程块大小或每线程寄存器用量。

5.3 统一内存

统一内存(Unified Memory) 是 CUDA 提供的一项特性,允许用 cudaMallocManaged 分配一块 CPU 和 GPU 均可访问的内存。CUDA 运行时或底层硬件负责在需要时将数据迁移到正确的设备。

float *data;
cudaMallocManaged(&data, N * sizeof(float));
// CPU 和 GPU Kernel 均可直接访问 data,无需手动 cudaMemcpy

即使使用统一内存,最优性能的原则仍然是:尽量让数据待在最常访问它的设备上,减少内存迁移次数

另外还有一种映射内存(Mapped Memory),是将 CPU 内存映射为 GPU 可直接访问的地址。但由于访问要跨越 PCIe/NVLINK 总线,延迟高、带宽低,GPU 无法用并行度掩盖这一开销,性能远不如统一内存,一般不推荐作为数据访问的主要方式。


6. CUDA 平台与编译链

6.1 计算能力(Compute Capability)

每块 NVIDIA GPU 都有一个计算能力(Compute Capability,CC)编号,格式为 X.Y(大版本号.小版本号),表示该 GPU 所支持的特性集以及若干硬件参数。

6.2 驱动、Toolkit、Runtime 与 Driver API 的关系

这是初学者最容易混淆的部分,以下分层说明:

┌──────────────────────────────────────────────┐
│          你的 CUDA 应用                       │
├──────────────────────────────────────────────┤
│          CUDA 运行时 API(cudaXxx)            │  ← CUDA Toolkit 的一部分
├──────────────────────────────────────────────┤
│          CUDA 驱动 API(cuXxx)               │  ← 由 NVIDIA 驱动暴露
├──────────────────────────────────────────────┤
│          NVIDIA 驱动(r580 等版本)            │  ← GPU 的"操作系统"
├──────────────────────────────────────────────┤
│          GPU 硬件                             │
└──────────────────────────────────────────────┘
组件 说明
NVIDIA 驱动 GPU 的操作系统,所有 GPU 用途(包括显示、Vulkan、DirectX)的基础,版本号如 r580
CUDA Toolkit 独立软件包,包含头文件、库(cuBLAS 等)和工具(nvcc、nvprof 等)
CUDA 运行时(CUDA Runtime) Toolkit 中的核心库,提供 cudaMalloccudaMemcpy、Kernel Launch 等高层 API
CUDA 驱动 API 由 NVIDIA 驱动暴露的底层 API(函数以 cu 开头),功能更灵活,部分高级特性只有它才有

运行时 API vs 驱动 API:运行时 API 是驱动 API 的高层封装,使用更方便;驱动 API 更底层、更灵活。两者可以在同一应用中混用。

6.3 PTX 虚拟 ISA

PTX(Parallel Thread Execution,并行线程执行) 是 CUDA 平台中往往"隐身"的重要一层——它是 NVIDIA GPU 的虚拟指令集架构(Virtual ISA),可以理解为一种面向 GPU 的高级汇编语言。

PTX 为真实 GPU 的物理指令集提供了一层抽象,使得:

  • 编译器(如 nvcc)先将 C++ 代码编译为 PTX(中间表达),再将 PTX 编译为特定 GPU 的二进制。
  • 其他语言或 DSL(如 Triton)可以生成 PTX,借助 NVIDIA 的工具链完成最终编译。
  • 运行时可通过 NVRTC 库将 CUDA C++ 在线动态编译为 PTX,适合需要生成动态 GPU 代码的场景。

PTX 版本与计算能力对应,例如支持 CC 12.0 所有特性的 PTX 版本称为 compute_120

6.4 cubin、fatbin 与编译产物

术语 含义
cubin PTX 编译后的 GPU 本地二进制,针对特定 SM 版本(如 sm_120
fatbin 一个容器,内部可同时打包多个版本的 cubin 和/或 PTX

一个可执行文件或库同时包含 CPU 二进制代码和 fatbin 容器,运行时根据当前 GPU 从 fatbin 中选取最适合的 cubin 加载执行。

fatbin结构

6.5 二进制兼容性与 PTX 兼容性

cubin 的二进制兼容性规则

同一大版本的计算能力下:

  • GPU 可以加载小版本号小于等于自身的 cubin。

例如,GPU 计算能力为 12.5

cubin 版本 能否加载 原因
sm_115 大版本不同
sm_120 大版本相同,小版本 0 ≤ 5
sm_125 大版本相同,小版本 5 ≤ 5
sm_129 小版本 9 > 5
sm_90 大版本不同

二进制兼容性仅对 nvcc 等 NVIDIA 官方工具生成的二进制有效;手动修改二进制将使兼容性承诺失效。

PTX 的前向兼容性

PTX 可在运行时被驱动 JIT 编译为等于或高于其目标计算能力的任意 cubin。例如,fatbin 中包含 compute_80 的 PTX,可在运行时编译为 sm_120 的 cubin。

这使得应用无需重新编译即可在将来更新的 GPU 架构上运行。

6.6 JIT 编译

JIT(Just-in-Time,即时编译) 指在程序运行时由 NVIDIA 驱动将 PTX 编译为本地 cubin 的过程。

  • 优点:前向兼容未来 GPU;每次升级驱动可自动享受编译器优化改进。
  • 缺点:首次运行会增加程序启动时间。
  • 缓存机制:JIT 编译的结果会被缓存(称为 compute cache),驱动升级后缓存自动失效以触发重新编译。

7. 总结

本章涵盖了理解 CUDA 编程的基础框架:

主题 要点
GPU 设计哲学 高吞吐量,数千线程并行,牺牲单线程性能
异构编程 Host (CPU) 负责调度,Device (GPU) 负责并行计算
线程层次 Thread → Warp(32) → Block → Cluster(CC9.0+) → Grid
内建变量 threadIdx/blockIdx/blockDim/gridDim
SIMT 同 Warp 内同指令异数据,分支发散会降低效率
内存层次 寄存器 > 共享内存/L1 > L2 > 全局内存,速度依次递减
计算能力 决定可用特性,格式 X.Y,与 SM 版本一一对应
编译链 C++ → PTX → cubin,打包进 fatbin,JIT 提供前向兼容

理解上述概念是高效编写 CUDA 程序的前提。后续章节将围绕这些概念展开具体的 C++ API 使用和性能优化实践。


参考资料:NVIDIA CUDA Programming Guide Release 13.2, Chapter 1


文章来源:https://www.cnblogs.com/winteryan/p/19827116
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云