首先,我想说明一下,虽然文章的标题是 How GPU Works,但是我无意再去重复GPU工作的各个stage,流水线这些概念。本文会深入到更底层一点:GPU是如何执行shader的。在本文中,我除了GPU执行shader的方式之外,还有稍微涉及一些多核心,SIMD,超线程这些过去大家看上去貌似非常高大上的概念。

一个简单shader

image-20210330141223637 这就是一个非常简单的diffuse shader(HLSL),是fragment(pixel) shader,每个片元着色器是独立执行的,并不需要特地的去执行并行编程(像CPU多线程那样),但是每个片元着色器确实有自己单独的逻辑控制流。

代码运行的方式

GPU和CPU运行shader的方式差不多,都是先编译成汇编,然后单步执行。 image-20210330141223637 image-20210330135518628.png

然后逐步执行

image-20210330141223637

CPU和GPU架构对比

CPU core vs GPU core

两者对比可以发现,GPU core相比CPU core,去掉了乱序执行,分支预测和内存预取的功能,并去掉了大块的缓存,使相同面积可以容纳更多的核心。而且去除了分支预测,乱序等功能之后也能使程序顺序执行时更高效(代价是分支效率下降,后文会提)。

Two cores (two fragments in parallel)

Two cores (two fragments in parallel)

Sixteen cores (sixteen fragments in parallel)

指令流共享

到此我们可以发现一个现象,就是每个片元执行的代码都是相同的,GPU cores完全可以复用一套指令。为此,GPU工程师们发明了一个极具想象力的设计。

SIMD processing

将ALU和Context增加,使用一个指令来控制多个ALU进行计算。这种技术在CPU那边有个学名,叫 SIMD ,也就是通过一条汇编指令控制多个计算。

img

这样,就实现了译码器和ALU一对多的映射。

img

img

img

各种GPU运算都是这样的

img

在现代GPU中,一般16到64个ALU共享一个指令流

分支运算

上文提到过GPU core相比CPU core,是简化过的,简化掉了分支分支预测的功能,那GPU是如何执行分支语句的呢。了解shader的同学可能也了解,分支语句会让shader的性能极剧下降,在此我也会一起解释。

img

分支语句的位置

img

GPU在编译时会找到分支语句的位置,然后在执行时,对于分支语句,会将各分支分配给不同的ALU来执行。

img

并不是每个ALU的计算结果都有效,而无效的结果Context会负责舍弃。也就是说在最坏情况下,分支语句相比无分支语句仅有1/8的的性能(取决于ALU数)。期望值是1/2,也就是平常大家常说的分支语句会令shader性能折半。

Stalls

Stalls是一个硬件术语,指芯片因为上一条指令执行过慢,而无事可做的情况。回到之前的diffuseshader上。

img

采样是GPU从显存中读取贴图信息的过程,有大量延迟,一般在100到1000个时钟周期。为了防止这个问题,CPU通常采用超大的缓存+超大带宽的方式来减小io时的stalls。

img

这里又是CPU和GPU硬件设计上截然不同的地方,因为Cache不命中会使 Stall 发生(必须从主存储器取值),所以GPU设计时索性去掉了big data cache和Memory pre-fetcher,取而代之的是一种非常巧妙的方法。

img

对于一个GPU core,所需拥有的Context数是ALU数目的数倍。这样,当其中一组Context遭遇采样io时,迅速切换到下一组Context。等到IO结束之后,前一组Context余下的指令才继续执行。大家一般谈论的CPU超线程其实也是这个原理,两个Context共享一个physics core。

img

发生中断时切换Context

img

中断结束后继续执行 图像采样需要数百时钟周期,是通常指令执行的数十到数百倍,因此一个GPU core上拥有的Context数也是ALU的数十倍。刚好符合了实际情况:屏幕像素数远远多于GPU流处理器个数。

img

Multi Contexts

实例:GTX480

img

img

NVIDIA管一个GPU core叫一组SM,一个ALU叫一个CUDA core,CUDA core可以在一个时钟周期内执行一个乘法和一个加法(即一个MAD,Multiply & Add指令)。一组SM有两个Fetch/Decode单元,个人猜测这样就可以在执行分支语句时一人一半。一组SM有48组Context,也就是说,一组SM,可以同时执行 32 * 48 = 1536 个 CUDA 线程。

GTX480一共有15组SM,也就是说GTX480同时可以执行多达 1536 * 15 = 23,040 个 CUDA 线程。可以说非常惊人了。

img

结语

本文介绍了一下DX10/openGL 3.x时代的显卡,也就是SM4.0统一着色架构下的显卡设计。在tensorFlow和RTX相继问世后,NVIDIA也发布了拥有tensor core的titan V和拥有RTX core的RTX2080/2080Ti,面对机器学习和光线追踪这两个if else遍地的情况,GPU的设计也作出了很大的变化,最直接的感受就是分支语句没有性能损失了。相信GPU的设计也更接近CPU了。从当年的Silicon Graphics RealityEngine的众CPU开始,到RTX/tensor core,图形硬件的发展,不也是一种循环吗。

引用:  https://www.tu-ilmenau.de/fileadmin/media/gdv/Lehre/Computergrafik_II/Lehre/03-fatahalian_gpuArchTeraflop_BPS_SIGGRAPH2010.pdf

https://www.cs.cmu.edu/afs/cs/academic/class/15462-f11/www/lec_slides/lec19.pdf

http://www.cs.cmu.edu/afs/cs/academic/class/15462-f12/www/lec_slides/462_gpus.pdf