1. GPU架构基础:从硬件角度看GPU与CPU的区别

大家好,我是你们的GPU带宽课讲师。今天咱们聊点硬核的——GPU到底长什么样?

很多人觉得GPU就是显卡,CPU就是处理器,两者都能算数。嗯,这话没错,但太笼统了。我做了这么多年GPU优化,最深的感触是:不理解硬件,你就写不出高性能的CUDA代码。

1.1 CPU vs GPU:设计哲学完全不同

先问一个问题:为什么CPU核心数只有几个,GPU却有几千个?

答案很简单——设计目标不同。

  • CPU:擅长处理复杂、串行的任务。它把大量晶体管花在控制逻辑和缓存上,为了快速响应单个线程。
  • GPU:擅长处理大量、并行的简单任务。它把晶体管堆在计算单元上,牺牲了单线程性能,换来了恐怖的吞吐量。

我打个比方:CPU就像一位全能博士,什么难题都能解,但一次只能处理一个。GPU就像一千个小学生,每个人只会做加减法,但一千个人一起算,速度反而更快。

核心区别总结:

维度CPUGPU
核心数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的显存不是一块大平层。它分了好几层,每一层的速度和容量都不一样。

我画了一张图,帮你理清这个结构:

GPU显存层级结构 全局显存 (Global Memory) 容量: 4-80 GB | 延迟: 400-800 cycles | 所有线程可访问 L2缓存 (L2 Cache) 容量: 1-6 MB | 延迟: 200-400 cycles | 芯片级共享 SM内部存储 (Per-SM) 共享内存 寄存器文件 L1缓存 共享内存: 48-164 KB | 寄存器: 64K-256K 个 | L1: 24-128 KB 延迟: 1-30 cycles | 仅同一SM内线程可访问 越往下,容量越小,速度越快

从上往下看:

  1. 全局显存:最大,但最慢。所有线程都能读写。我们平时说的“显存带宽”,主要就是指这一层的带宽。
  2. L2缓存:芯片级共享,比全局显存快一些。但容量有限,只有几MB。
  3. 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就是算术单元,别想得太复杂
  • 显存层级结构决定了数据访问的代价

说白了,理解这些硬件细节,不是为了考试,而是为了写出跑得快的代码。下一章咱们会深入带宽测试,到时候你会发现,今天讲的这些全是基础。

想要更多资料,联系可可