2、GPU硬件架构基础:NVIDIA GPU核心架构
各位同学,今天我们来聊聊NVIDIA GPU的硬件架构。说实话,搞多卡驱动适配,不懂硬件架构就是瞎搞。我见过太多人,代码写得飞起,结果性能上不去,最后发现是没搞懂SM怎么调度、显存怎么访问。嗯,咱们今天就把这些硬骨头啃下来。
2.1 SM:GPU的心脏
SM(Streaming Multiprocessor)是NVIDIA GPU最基本的计算单元。你可以把它想象成一个微型处理器,里面塞满了各种计算资源。我个人习惯把SM比作一个工厂车间,CUDA Core就是车间里的工人,Tensor Core是高级技工,而warp scheduler就是车间主任。
每个SM包含:
- CUDA Core:负责常规的浮点和整数运算
- Tensor Core:专门做矩阵乘加运算,AI训练和推理的利器
- Shared Memory:SM内部的高速缓存,线程块之间共享
- Register File:寄存器堆,每个线程的私有存储
- Warp Scheduler:负责调度warp(32个线程为一组)
我在项目中遇到过一个问题:某个kernel在V100上跑得飞快,换到A100反而慢了。查了半天,原来是SM数量翻倍了,但每个SM的shared memory没变,导致线程块数量受限。你看,不懂SM细节就会踩坑。
核心要点:SM的数量和配置决定了GPU的并行计算能力。多卡适配时,要针对不同GPU的SM特性做优化。
2.2 CUDA Core vs Tensor Core
很多新手分不清CUDA Core和Tensor Core的区别。说白了,CUDA Core是通用计算单元,什么活都能干;Tensor Core是专用单元,只干矩阵乘加这一件事,但干得特别快。
| 特性 | CUDA Core | Tensor Core |
|---|---|---|
| 计算类型 | 通用(FP32、INT32等) | 矩阵乘加(FP16、BF16、INT8等) |
| 每SM数量 | 64-128个(取决于架构) | 4-8个(取决于架构) |
| 适用场景 | 通用计算、控制逻辑 | 深度学习、科学计算 |
| 性能 | 单精度浮点运算 | 混合精度运算,吞吐量高数倍 |
举个例子,你写一个简单的向量加法,用CUDA Core就够了。但如果你在做Transformer模型训练,矩阵乘法占了90%的计算量,这时候Tensor Core就能派上大用场。我曾经优化过一个BERT推理任务,把FP32改成FP16混合精度,用上Tensor Core,速度直接翻了3倍。
小技巧:在多卡场景下,尽量让Tensor Core干活,CUDA Core做辅助。这样能最大化利用GPU资源。
2.3 显存层次结构
GPU的显存层次结构,说白了就是一套「数据快递系统」。从最慢的全局显存,到最快的寄存器,每一层都有它的用途。我刚开始做CUDA编程时,总喜欢把所有数据都放全局显存里,结果性能惨不忍睹。
显存层次从慢到快:
- Global Memory(全局显存):容量最大(几十GB),但延迟最高(几百个时钟周期)。所有线程都能访问。
- L2 Cache(二级缓存):所有SM共享,容量几MB,延迟比全局显存低一些。
- L1 Cache / Shared Memory(一级缓存/共享内存):每个SM独有,容量几十KB到几百KB,延迟极低(几十个时钟周期)。
- Register(寄存器):每个线程私有,容量最小(每个线程最多255个32位寄存器),延迟几乎为零。
为什么会这样设计?因为GPU要处理海量数据,不可能把所有数据都放在最快的地方。你想想看,如果每个线程都配几百MB的寄存器,那芯片面积得有多大?成本得有多高?
避坑指南:我曾经在项目里遇到一个bug,kernel里频繁访问全局显存,导致带宽瓶颈。后来把热点数据搬到shared memory里,性能提升了5倍。记住:能放shared memory就别放全局显存,能放寄存器就别放shared memory。
2.4 PCIe与NVLink互联技术
多卡互联,说白了就是让多张GPU能高效地交换数据。目前主流有两种方式:PCIe和NVLink。
PCIe:通用标准,所有GPU都支持。带宽有限(PCIe 4.0 x16约32GB/s),延迟较高。适合数据量不大的场景。
NVLink:NVIDIA私有技术,带宽极高(NVLink 3.0单链路约50GB/s),延迟低。适合大规模数据交换。
我建议在多卡训练时,优先使用NVLink。为什么呢?因为数据并行训练中,梯度同步是最大的瓶颈。用PCIe的话,8张卡同步一次梯度可能要几百毫秒;用NVLink,几十毫秒就搞定了。
下面这张图展示了多卡互联的典型拓扑:
从图中可以看到,GPU之间通过NVLink直连,形成高速互联网络。而CPU通过PCIe与GPU通信,负责控制和管理。这种拓扑结构下,多卡之间的数据交换效率极高。
我记得有一次做8卡训练,一开始用PCIe做梯度同步,结果每轮迭代要等很久。后来改成NVLink直连,训练速度直接翻倍。所以,如果你有条件,一定要用NVLink。
实用建议:在多卡编程中,尽量让数据在GPU之间通过NVLink传输,避免经过CPU中转。这样可以大幅降低延迟。
2.5 实战:多卡环境下的显存管理
好了,理论讲完了,咱们来点实际的。下面这段代码展示了如何在多卡环境下分配和管理显存:
// 多卡显存分配示例
int deviceCount;
cudaGetDeviceCount(&deviceCount);
for (int i = 0; i < deviceCount; i++) {
cudaSetDevice(i);
// 获取显存信息
size_t freeMem, totalMem;
cudaMemGetInfo(&freeMem, &totalMem);
printf("GPU %d: 总显存 %zu MB, 可用 %zu MB\n",
i, totalMem / 1024 / 1024, freeMem / 1024 / 1024);
// 分配显存
float *d_data;
cudaMalloc(&d_data, 1024 * 1024 * sizeof(float)); // 4MB
// 初始化数据
cudaMemset(d_data, 0, 1024 * 1024 * sizeof(float));
// 记得释放
// cudaFree(d_data);
}
这段代码很简单,但有个坑:如果你在多卡环境下忘记调用cudaSetDevice,数据可能会分配到错误的GPU上。我曾经因为这个bug,调试了整整两天。
重要提醒:在多卡编程中,每次操作前都要确认当前设备是否正确。建议使用cudaGetDevice检查当前设备ID。
好了,以上就是GPU硬件架构的核心内容。记住:搞懂SM、显存层次和互联技术,你就能在多卡驱动适配中游刃有余。下次遇到性能问题,先想想是不是硬件层面没用好。
想要更多资料,联系可可