1. GPU架构基础:从硬件角度看GPU与CPU的区别
大家好,我是你们的GPU带宽课讲师。今天咱们聊点硬核的——GPU到底长什么样?
很多人觉得GPU就是显卡,CPU就是处理器,两者都能算数。嗯,这话没错,但太笼统了。我做了这么多年GPU优化,最深的感触是:不理解硬件,你就写不出高性能的CUDA代码。
1.1 CPU vs GPU:设计哲学完全不同
先问一个问题:为什么CPU核心数只有几个,GPU却有几千个?
答案很简单——设计目标不同。
- CPU:擅长处理复杂、串行的任务。它把大量晶体管花在控制逻辑和缓存上,为了快速响应单个线程。
- GPU:擅长处理大量、并行的简单任务。它把晶体管堆在计算单元上,牺牲了单线程性能,换来了恐怖的吞吐量。
我打个比方:CPU就像一位全能博士,什么难题都能解,但一次只能处理一个。GPU就像一千个小学生,每个人只会做加减法,但一千个人一起算,速度反而更快。
核心区别总结:
| 维度 | CPU | GPU |
|---|---|---|
| 核心数 | 4-32个 | 数千个 |
| 控制逻辑占比 | ~40% | ~5% |
| 缓存大小 | 大(MB级) | 小(KB级) |
| 适合任务 | 串行、分支密集 | 并行、数据密集 |
| 延迟容忍 | 低(追求低延迟) | 高(靠并行隐藏延迟) |
我个人习惯用一句话概括:CPU是低延迟的,GPU是高吞吐的。你想想看,如果你要渲染一帧4K画面,几百万个像素点,CPU一个个算得算到猴年马月?GPU几千个线程一起上,瞬间搞定。
1.2 SM:GPU的“小团队”
GPU内部不是一盘散沙。它被划分成一个个SM(Streaming Multiprocessor,流式多处理器)。
SM是什么?你可以把它理解成一个独立的小型处理器。每个SM有自己的寄存器、共享内存、调度器、以及一堆CUDA Core。
我在项目中遇到过一个问题:某次做矩阵乘法优化,发现性能上不去。查了半天,原来是线程块(Block)分配得太小,导致SM空闲。每个SM能同时驻留多个线程块,如果块太小,SM的“肚子”填不饱,计算资源就浪费了。
避坑指南:我曾经以为线程块越大越好,结果发现共享内存不够用,导致内核启动失败。后来我养成了习惯:先查显卡的SM规格,再设计线程块大小。
每个SM内部有多个Warp(线程束),通常是32个线程一组。这是GPU调度的基本单位。为什么是32?嗯,这是NVIDIA的设计选择,咱们记住就行。
1.3 CUDA Core:别被名字骗了
CUDA Core,听起来像是一个完整的CPU核心?其实不是。
CUDA Core本质上是一个浮点/整数运算单元,它没有复杂的控制逻辑,也没有独立的大缓存。它只能执行简单的算术指令。
我举个例子:一个SM里有128个CUDA Core,它们共享同一个指令调度器。也就是说,这128个Core在同一时刻执行同一条指令,只是操作不同的数据。这就是所谓的SIMT(单指令多线程)模型。
说白了,CUDA Core就是一群“听话的工人”,老板(调度器)喊一声“加1”,所有人同时给自己的数据加1。效率极高,但灵活性差——如果遇到if-else分支,部分工人就得“摸鱼”等待。
注意:不同代GPU的CUDA Core能力不同。比如,Volta架构引入了独立的整数单元,可以跟浮点运算并行。我建议你写代码前,先查一下目标显卡的架构特性。
1.4 显存层级结构:速度与容量的博弈
GPU的显存不是一块大平层。它分了好几层,每一层的速度和容量都不一样。
我画了一张图,帮你理清这个结构:
从上往下看:
- 全局显存:最大,但最慢。所有线程都能读写。我们平时说的“显存带宽”,主要就是指这一层的带宽。
- L2缓存:芯片级共享,比全局显存快一些。但容量有限,只有几MB。
- SM内部存储:包括共享内存、寄存器、L1缓存。这是最快的,但只属于当前SM。
我记得有一次做带宽测试,发现实际带宽只有理论值的30%。排查下来,问题出在全局显存访问模式上——线程访问的地址不连续,导致缓存利用率极低。后来改成合并访问(coalesced access),带宽直接翻了三倍。
关键原则:尽量让数据待在离计算单元近的地方。能用共享内存就别用全局显存,能用寄存器就别用共享内存。这是GPU优化的第一性原理。
1.5 实战:如何查询你的GPU架构
说了这么多理论,咱们来点实际的。怎么知道你的GPU有几个SM?每个SM有多少CUDA Core?
用nvidia-smi只能看到显存和利用率,看不到架构细节。我推荐用CUDA自带的工具:
// 查询GPU设备属性
cudaDeviceProp prop;
cudaGetDeviceProperties(&prop, 0);
printf("GPU名称: %s\n", prop.name);
printf("SM数量: %d\n", prop.multiProcessorCount);
printf("每个SM的CUDA Core数: %d\n", prop.maxThreadsPerMultiProcessor / 32);
printf("全局显存: %.2f GB\n", prop.totalGlobalMem / 1024.0 / 1024.0 / 1024.0);
printf("共享内存每SM: %.2f KB\n", prop.sharedMemPerMultiprocessor / 1024.0);
printf("寄存器每SM: %d\n", prop.regsPerMultiprocessor);
我个人习惯把这个信息打印在程序开头,方便调试时对照。你想想看,如果你不知道SM数量,怎么决定线程块的数量?
小技巧:我曾经在RTX 3090上写代码,以为它有82个SM,每个SM128个Core。结果一查,Ampere架构每个SM有128个CUDA Core,但还有额外的Tensor Core。嗯,这个细节在优化矩阵乘法时特别重要。
1.6 本章小结
好了,咱们把GPU架构的底牌翻了一遍:
- CPU和GPU的设计哲学不同,一个求快,一个求多
- SM是GPU的基本调度单元,每个SM有自己的“小仓库”
- CUDA Core就是算术单元,别想得太复杂
- 显存层级结构决定了数据访问的代价
说白了,理解这些硬件细节,不是为了考试,而是为了写出跑得快的代码。下一章咱们会深入带宽测试,到时候你会发现,今天讲的这些全是基础。
想要更多资料,联系可可