小H把这首诗读给方老师听,方老师笑了:其实你看GPU里面寄存器和其他存储子系统的组织方式,跟CPU中,存储子系统的组织方式相比,还真就很类似这首诗描述的情景呢!
那么,GPU的存储子系统设计,到底做了什么样的变革,才会变成“有朝一日倒过来”呢?
原来,我们通过学习了这两期,逐渐理解了,GPU线程的组织协同方式,与多核CPU的组织协同方式的最大差异:
多核CPU的每个核有自己的指令译码、发射单元,自己的ALU,自己的寄存器列(Register File),特别是有自己的指令指针(Instruction Pointer),可以让不同的CPU核心在同一时刻运行不同的指令;
多核多线程的CPU(如x86、MIPS等)的每个核有多个指令译码、执行单元、多个寄存器列,也就是所谓的多线程。为了降低成本,每个核的多个线程共用一个ALU。从操作系统的视角,看见的是多个vCPU,这是因为操作系统眼里的多个vCPU,只需要拥有独立的寄存器列、独立的指令译码核执行单元,就是可以独立执行一个程序的所谓vCPU。而多个vCPU可以共用一个ALU。这就是所谓的“超线程”。
为了实现部分运算的并行加速,一些CPU还内置了一些特殊指令,能够在一条指令中计算多次运算,比如把4个16bit的浮点数放到一个64bit的寄存器(所谓的packed float),然后进行算术运算,一次性计算4组浮点数的加减乘除等基本运算。这就是所谓的SIMD(single instruction multiple data)。
无论是否支持SIMD指令,多核CPU和多核多线程CPU,它们可以在同一时刻执行不同的指令,这就是所谓的MIMT(Multiple instruction multiple thread,多指令多线程)。
而GPU的组织协同方式则是SIMT,也就是说,在GPU中,多个CUDA Core或Tensor Core(在CUDA编程框架中称为Thread)虽然可以按照warp(线程束)来进行划分,每个GPU可以有多个warp,但每个warp中的多个thread,在同一个时刻只可以执行同一条指令。这是因为,每个warp中,只有一个指令指针。在同一个时刻,这个Warp中,被发射到所有Thread的指令都是同一条,Thread只能通过谓词寄存器来判断是否应当执行这条指令。
在GPU中,除了各个计算单元(CUDA Core和Tensor Core)的组织协同方式与CPU有较大的差异以外,存储系统的组织也与CPU完全不同。
我们在以前的专题中提到过:CPU相关的存储子系统是一个金字塔型:
最上层为寄存器,数量约数十个,容量约数百Byte,寄存器的每个bit大概需要数十个晶体管实现,其存取速度与CPU的时钟完全同步,可以在一条指令流水线的一个步骤中执行完毕;
寄存器之下为缓存(Cache),容量为KB或MB级别,缓存使用SRAM实现,速度低于寄存器,每个bit使用4-8个晶体管实现,存取与CPU的时钟基本同步;
缓存之下为内存RAM,目前其容量可达数十到数千GB级别,内存使用DRAM实现,每bit为1个晶体管,存取需要100个以上CPU时钟周期;
在内存之下还有SSD盘、HDD盘等不同等级的持久化存储器。
它们构成这样一个金字塔型。
但我们在GPU的相关文档中,看到的GPU的存储子系统并非如此。
如图,H100的每个SM的一个象限中,有16384个寄存器,每个32bit,总共是64KB。
整个SM内部有4个象限,共享256K的L1 Cache。我们注意到每个象限中的寄存器大小为64K,也就是说,整个SM有4个64K的寄存器列,共256K,大小与L1 Cache相同。
H100中共有132个SM,共享60MB的缓存,每个缓存分配到约0.45MB的缓存。
此外,在每个象限中还有少量的L0 Cache,其大小没有任何公开资料披露。
总体上,GPU的存储子系统组织如上图,其Register File容量远大于L0 Cache,也就成了“下边细来上边粗”。
GPU的设计者是基于什么考虑,才把Register File设计得这么大呢?
请看下期。