第1章:GPU 基础概念
在写第一行代码之前,我们先理解什么是 GPU,以及为什么它适合做并行计算
本章定位
本章负责建立 GPU kernel 的第一层直觉:为什么 GPU 适合并行、什么是 kernel、thread/block/grid、GPU 内存大概怎么分层。遇到术语不稳时,先查 GPU 初学者术语表。
配套主文档:
1.1 什么是 GPU?
生活中的比喻
想象你要计算 1000 道数学题:
CPU 就像一个超级聪明的数学老师
- 他做题很快
- 但一次只能做一道题
- 1000 道题需要一道一道做完
GPU 就像 1000 个小学生
- 每个学生单独看,做题速度一般
- 但可以 1000 个学生同时做
- 1000 道题可以同时完成
这和计算机有什么关系?
CPU(中央处理器):
- 只有几十个核心(比如 8 核、16 核)
- 每个核心很强,能处理复杂任务
- 适合:运行操作系统、打开软件、处理复杂逻辑
GPU(图形处理器):
- 有几千个核心(比如 3072 个、10240 个)
- 每个核心比较简单
- 适合:同时做大量简单计算
为什么叫”图形”处理器?
最初 GPU 是用来渲染游戏画面的。游戏画面由几百万个像素组成,每个像素的颜色需要计算。虽然每个计算很简单,但数量巨大。GPU 就是为了这种任务设计的。
后来人们发现,很多科学计算、AI 计算也是”大量简单计算”,所以 GPU 被广泛应用到这些领域。
1.2 CPU vs GPU 详细对比
架构对比图
CPU 架构(少而精):
┌─────────────────────────────────────┐
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐│
│ │核心 1│ │核心 2│ │核心 3│ │核心 4││
│ │(强大)│ │(强大)│ │(强大)│ │(强大)││
│ └──────┘ └──────┘ └──────┘ └──────┘│
│ │
│ ┌─────────────────────────────────┐│
│ │ 大容量缓存 ││
│ │ (快速访问常用数据) ││
│ └─────────────────────────────────┘│
│ │
│ ┌─────────────────────────────────┐│
│ │ 控制单元(智能) ││
│ │ 分支预测、乱序执行、流水线... ││
│ └─────────────────────────────────┘│
└─────────────────────────────────────┘
GPU 架构(多而简):
┌─────────────────────────────────────┐
│ 核心核心核心核心核心核心核心核心核心 │
│ 核心核心核心核心核心核心核心核心核心 │
│ 核心核心核心核心核心核心核心核心核心 │
│ ...成千上万个简单核心... │
│ │
│ 小容量缓存(每个核心组共享) │
│ │
│ 简单控制单元(大家做同样的事) │
└─────────────────────────────────────┘
性能指标对比
| 指标 | CPU | GPU | 说明 |
|---|---|---|---|
| 核心数 | 4-64 个 | 1000-10000+ 个 | GPU 核心数量多得多 |
| 单核性能 | 高 | 低 | CPU 单核更强 |
| 总计算能力 | 中等 | 极高 | 并行时 GPU 更强 |
| 内存带宽 | ~100 GB/s | ~1000 GB/s | GPU 内存更快 |
| 时钟频率 | 3-5 GHz | 1-2 GHz | CPU 频率更高 |
| 适合任务 | 复杂逻辑 | 并行计算 | 各有所长 |
1.3 什么是并行计算?
串行 vs 并行
串行计算(CPU 擅长):
任务1 → 任务2 → 任务3 → 任务4 → 完成
时间: 1秒 1秒 1秒 1秒 总共4秒
并行计算(GPU 擅长):
任务1 ┐
任务2 │→ 同时执行 → 完成
任务3 │ 总共1秒
任务4 ┘
什么样的任务适合 GPU?
适合 GPU 的任务特点:
- 数据量大:需要处理很多数据
- 计算简单:每个数据的计算不复杂
- 相互独立:数据之间不需要等待
例子:图像处理
- 一张 4K 图片有 830 万个像素
- 每个像素需要做同样的处理(比如变亮)
- 每个像素的处理很简单
- 像素之间相互独立
- → 非常适合 GPU!
例子:大模型推理
- GPT 生成的每个 token 需要大量矩阵计算
- 矩阵乘法就是很多简单的加法和乘法
- → 非常适合 GPU!
不适合 GPU 的任务:
- 复杂的逻辑判断(很多 if-else)
- 需要大量数据共享和同步
- 数据量很小
1.4 GPU 编程的基本概念
什么是 Kernel?
Kernel = 在 GPU 上运行的函数
普通函数:在 CPU 上运行
Kernel:在 GPU 上运行,可以被成千上万个线程同时执行
一个简单的例子
假设我们要把一个数组的每个元素都乘以 2:
CPU 方式(串行):
// 在 CPU 上一个一个处理
void multiply_cpu(float* data, int n) {
for (int i = 0; i < n; i++) {
data[i] = data[i] * 2; // 一次处理一个
}
}
// 100万个元素,需要循环100万次GPU 方式(并行):
// 在 GPU 上,100万个线程同时执行
__global__ void multiply_gpu(float* data) {
int i = threadIdx.x; // 每个线程知道自己处理哪个元素
data[i] = data[i] * 2; // 每个线程处理一个元素
}
// 启动100万个线程,同时执行!
multiply_gpu<<<1, 1000000>>>(data);线程、线程块、网格
这是 CUDA 编程最重要的概念之一:
Grid(网格)
│ 整个 GPU 计算任务
│
├── Block 0(线程块)
│ ├── Thread 0
│ ├── Thread 1
│ ├── Thread 2
│ └── ...(最多1024个线程)
│
├── Block 1(线程块)
│ ├── Thread 0
│ ├── Thread 1
│ └── ...
│
└── Block N(线程块)
└── ...
比喻:
- Grid = 一个工厂
- Block = 一个车间
- Thread = 一个工人
每个工人(线程)做同样的事,但处理不同的数据。
为什么需要线程块?
你可能会问:为什么不直接用几万个线程?
原因:
- 硬件限制:每个 Block 最多 1024 个线程
- 协作需要:同一 Block 内的线程可以共享内存、同步
- 灵活调度:GPU 可以把不同的 Block 分配给不同的处理器
1.5 GPU 内存基础
GPU 内存的层次
┌─────────────────────────────────────┐
│ 全局内存 (Global Memory) │ ← 最大,最慢
│ 显存 (VRAM) │ 几十GB
│ 带宽: 1-2 TB/s │
├─────────────────────────────────────┤
│ 共享内存 (Shared Memory) │ ← 快10-20倍
│ 用户可管理 │ 每个Block ~100KB
│ 带宽: ~20 TB/s │
├─────────────────────────────────────┤
│ 寄存器 (Registers) │ ← 最快
│ 每个-thread 独立 │ 每个线程 ~255个
│ 带宽: 最高 │
└─────────────────────────────────────┘
内存访问速度对比
| 内存类型 | 延迟(时钟周期) | 比喻 |
|---|---|---|
| 寄存器 | 1 | 从口袋里拿东西 |
| 共享内存 | ~20 | 从桌上拿东西 |
| 全局内存 | ~400 | 从另一个房间拿东西 |
优化原则:尽量使用更快、更近的内存。
1.6 什么是 CUDA?
CUDA 简介
CUDA = NVIDIA 开发的 GPU 编程平台
- 全称:Compute Unified Device Architecture
- 只能在 NVIDIA 的 GPU 上使用
- 使用 C/C++ 语法,加上一些特殊关键字
CUDA 程序的基本结构
// 1. 在 GPU 上运行的函数(Kernel)
__global__ void my_kernel(float* data) {
int idx = threadIdx.x;
data[idx] = data[idx] * 2;
}
// 2. 主程序在 CPU 上运行
int main() {
// 分配 CPU 内存
float* h_data = (float*)malloc(1000 * sizeof(float));
// 分配 GPU 内存
float* d_data;
cudaMalloc(&d_data, 1000 * sizeof(float));
// 把数据从 CPU 拷贝到 GPU
cudaMemcpy(d_data, h_data, 1000 * sizeof(float), cudaMemcpyHostToDevice);
// 启动 Kernel(1000个线程)
my_kernel<<<1, 1000>>>(d_data);
// 把结果从 GPU 拷贝回 CPU
cudaMemcpy(h_data, d_data, 1000 * sizeof(float), cudaMemcpyDeviceToHost);
// 释放内存
cudaFree(d_data);
free(h_data);
return 0;
}关键字解释
| 关键字 | 含义 | 例子 |
|---|---|---|
__global__ | 定义 Kernel 函数 | __global__ void my_kernel() |
__device__ | 在 GPU 上运行的普通函数 | __device__ float add(float a, float b) |
__host__ | 在 CPU 上运行(默认) | __host__ void cpu_func() |
threadIdx.x | 当前线程在 Block 内的索引 | 第3个线程,threadIdx.x = 2 |
blockIdx.x | 当前 Block 在 Grid 内的索引 | 第2个 Block,blockIdx.x = 1 |
blockDim.x | Block 内线程数量 | 如果每个 Block 256个线程,blockDim.x = 256 |
1.7 学习路线预览
学完本章后,你已经理解了:
- ✅ GPU 是什么,为什么适合并行计算
- ✅ CPU 和 GPU 的区别
- ✅ 什么是 Kernel、线程、线程块
- ✅ GPU 内存的层次结构
- ✅ CUDA 程序的基本结构
接下来,我们将在 第2章 中:
- 搭建开发环境
- 编写第一个 CUDA 程序
- 深入理解线程模型
💡 本章要点总结
- GPU 适合并行计算:大量简单的任务同时执行
- Kernel 是 GPU 函数:可以被成千上万个线程同时执行
- 线程组织成 Block,Block 组织成 Grid
- 内存有层次:寄存器最快,全局内存最慢
- CUDA 是 NVIDIA 的 GPU 编程平台
❓ 常见问题
Q:我需要买 NVIDIA 显卡吗? A:如果只是学习,可以用 Google Colab 的免费 GPU。如果要认真做开发,建议买一张 RTX 3060 或更高。
Q:AMD 显卡能用 CUDA 吗? A:不能。CUDA 只支持 NVIDIA 显卡。AMD 有自己的 ROCm 平台,但教程和生态不如 CUDA 完善。
Q:我需要学多少数学? A:基础的线性代数(矩阵乘法)就够用。深入学习时可能需要更多数学知识。
📝 课后练习
- 用自己的话解释 CPU 和 GPU 的区别(不要用专业术语)
- 想想生活中有哪些任务是”串行”的,哪些是”并行”的
- 为什么 GPU 不能完全取代 CPU?
- 如果有 100 万个数字要相加,用 GPU 怎么做?(只需要描述思路)