1. 宏观架构:从流水线专家到吞吐量怪兽
要理解 GPU 硬件,首先要对比它与 CPU 在晶体管分配上的本质区别。
CPU:延迟导向型(Latency-Oriented)
CPU 的核心目标是“尽快完成每一个任务”。为了实现这一点,它消耗了大量的晶体管在以下组件:
巨大的 L1/L2/L3 缓存:为了减少访问内存的延迟。
复杂的控制单元(Control Unit):包括分支预测(Branch Prediction)、乱序执行(Out-of-Order Execution)、指令重排等,旨在保证流水线不空转。
强大的算术单元(ALU):虽然数量少,但单个时钟频率极高。
GPU:吞吐量导向型(Throughput-Oriented)
GPU 放弃了对“单个任务”速度的追求,转而追求“同一时间内完成任务的总量”。
极致缩水的控制逻辑:GPU 几乎没有分支预测和乱序执行能力。
极致扩张的 ALU:它将省下的晶体管全部变成了计算单元。
2. 微观单元:流多处理器(SM)的内部构造
如果你拆开一块 NVIDIA GPU,你会发现它由多个GPC(Graphics Processing Clusters)组成,而 GPC 内部最核心的计算单元就是SM (Streaming Multiprocessor)。
SM 的解剖图
每个 SM 就像是一个自治的微型工厂,它通常包含:
CUDA Cores:负责基本的浮点(FP32/FP64)和整数(INT32)运算。
Tensor Cores:专门用于深度学习中的矩阵乘加运算($D = A \times B + C$)。
Special Function Units (SFU):处理昂贵的超越函数,如
sin,cos,exp,sqrt。Register File:极其庞大且快速的寄存器堆。
Shared Memory/L1 Cache:程序员可控的高速缓冲区。
3. 执行引擎:SIMT(单指令多线程)模型
这是 CUDA 硬件视角的灵魂。
在 CPU 中,我们熟悉 SIMD(单指令多数据,如 Intel 的 AVX 扩展)。而 NVIDIA 提出了SIMT。
区别点:在 SIMD 中,你显式地操作向量寄存器(比如一次加 8 个数);在 SIMT 中,你只需要写简单的标量代码(
c = a + b),硬件会自动将 32 个线程组织在一起,发送同一条指令给 32 个 CUDA Core 执行。
硬件视角下的线程执行:
当一个 SM 接收到一个线程块(Block)时,它会将这些线程划分为多个 Warp(线程束,固定为 32 个线程)。
SM 的**调度器(Warp Scheduler)**会每时钟周期检查哪些 Warp 准备好了(数据已到位),然后直接把指令发射出去。
4. 调度机制:零开销线程切换(Zero-Overhead Switching)
这是 GPU 隐藏延迟的杀手锏。
在 CPU 中,线程切换(Context Switch)是昂贵的,需要保存寄存器状态到内存,耗费数千个时钟周期。
而在 GPU 硬件中:
寄存器是静态分配的:当一个 Block 开始运行时,它所需要的所有寄存器都会在 SM 的物理寄存器堆中占好位置。
切换代价为零:如果 Warp 0 正在等待从显存读取数据(这可能需要 400 个周期),调度器会立即(在 1 个周期内)切换到 Warp 1。只要 SM 中有足够多的 Warp 在轮转,计算单元就能始终保持满载。
5. 指令发射与延迟隐藏的数学逻辑
为什么我们在代码里要设置N = 10,000,000这么大的数?
从硬件视角看,GPU 需要足够的**“占用率”(Occupancy)**来覆盖延迟。
假设:
一次显存访问延迟为 $400$ 个时钟周期。
每个 Warp 执行一条指令需要 $4$ 个时钟周期。
为了让计算核心在等待数据的过程中不空转,我们需要:
这意味着硬件需要同时维持 100 个 Warp(即 3200 个线程)的状态,才能“填满”这 400 个周期的空档。这就是为什么GPU 喜欢大规模并行——如果你只给它 100 个线程,它大部分时间都在歇着。
6. 现代演进:从算力到张量(Tensor Cores)
在近几年的硬件视角中,CUDA 核心不再是唯一的英雄。
Tensor Cores:这是为了 AI 时代设计的“重型武器”。传统的 CUDA Core 一个周期算一次乘法,而 Tensor Core 一个周期可以处理一个小型的 $4 \times 4$ 矩阵乘法。
硬件加速的异步拷贝:最新的架构(如 Hopper/Blackwell)甚至允许数据在不经过 CPU 介入的情况下,直接在不同层级的存储间移动。
总结:第一视角的硬核结论
从硬件视角看,CUDA 编程就是“用空间换取时间,用并行掩盖延迟”。
极致缩水:砍掉逻辑控制,追求单位面积下的算力最大化。
极致扩张:通过数以千计的寄存器支撑数万个线程同时驻留。
深度思考:
如果你在核函数里定义了太多的局部变量,导致每个线程占用的寄存器数量过多,硬件视角下会发生什么?
SM 的寄存器总量是固定的。如果每个士兵背的包太重,一个班(SM)能容纳的士兵数量就会减少,进而导致延迟无法被掩盖。