Host Interface:外包公司的大包工头,负责收发来自甲方CPU的各种订单(多是枯燥无味重复单调,需要大量廉价劳动力,CPU自己不屑于干、做不来的活)并处理GPU在各种订单间的上下文切换;还负责获取来自内存等待加工的原材料(顶点数据、纹理数据、各种buffer等),将其存放到仓库(显存)中。
Input Assembler:负责将甲方给的顶点数据进行简单组装(根据顶点索引与图元类型装配),并搭配上属于它们的顶点属性,才能传给Vetex Work Distribution。
Vertex、Pixel、Compute Work Distribution:三个小包工头,负责将自己领域更加具体的工作分发给底下一大堆流水线工人们去做(从名字就可以看出,他们分别分发顶点、片元和计算着色器的任务)
TPC(Texture Processing Clusters):这个名字比较令人困惑,里面有一个纹理单元和两个负责计算的SM(Streaming Multiprocessor)。都是干活的生力军,上面三个小包工头的活全都是它们完成的,从这里就可以看出所谓的统一的硬件结构是指什么了。这个结构下一章会具体展开,先让我们记住这个啥都能做的“全栈工程师”
Viewport/clip/setup/raster/zcull block:顶点着色器处理完,只是输出一堆NDC坐标(还未透视除法)和一堆与其一起等待光栅化插值的属性,这个模块就是负责这些,流水线中到目前为止都未开放编程的固定功能部分。
ROP(Raster Operations Processor):除了格子间里流水线上的工人,身为码头工人的ROP也是任劳任怨的好员工。他们负责对片元着色器处理后的像素进行测试和装箱:同一个像素位置的深度/模板测试和写入、颜色混合、抗锯齿都由它完成。因为片元着色器处理一个位置的片元时,并不知道该位置其他片元的信息,所以需要有这么一位码头工人在最终将产品(像素颜色)发往仓库前,做最后的统筹合并修缮工作。
L2 Cache、Memory Controller和DRAM:从图上可以看到,每个仓库(DRAM)搭配一个码头(图中未标的Memory Controller)、码头临时库存(L2
Cache)和一个码头工人(ROP),共有6组这样的搭配,每个搭配对应着显存六分之一的物理地址。格子间TPC里并行工作着的SM们发来了一条又一条的数据吞吐请求,会在码头集中处理,这些请求可能会被合并,并根据优先级从仓库获取数据以实现数据传输效率的最大化;被码头工人最终装箱完(颜色混合、硬件抗锯齿)的像素数据也会被码头发往仓库中的帧缓冲。
Geometry Controller:从名称上看,它是光栅化前几何阶段的大管家,负责顶点属性在芯片内的输入输出事宜,几何着色器这种增减顶点改变拓扑结构的骚操作也得麻烦它。不过,顶点着色器和几何着色器的运算指令仍然是由SM执行的,Geometry Controller会把最终结果送到Viewport/clip/setup/raster/zcull block模块去进行光栅化等步骤,或者Stream Out回内存交给程序员们揉搓。(该部件在Fermi架构中被升级成了PolyMorph Engine,我们会在Fermi架构章节里进一步研究)
SMC(SM controller):正如上一章讨论的,Tesla是统一的图形和并行计算架构,顶点、几何、片元着色器,甚至与图形无关的并行运算任务(CUDA),都由同样的硬件SM来运算。SMC负责将来自总部的各种任务,拆分打包成Warp(下一节会详细介绍)交给其中一个部门(SM)处理。除此外,SMC还负责协调SM与共用部门Texture unit之间的工作,以实现对外部纹理资源的获取,而显存中其他非纹理资源的读写甚至原子操作则会通过ROP与外界打交道。整体来看,SMC又负责对接外界资源,又负责内部任务分配,以实现复杂但至关重要的负载平衡,是名副其实的子公司高管。
Texture Unit:包含4个纹理地址生成器和8个滤波单元(支持全速的2:1各项向异性过滤)。与SM内的指令全是标量运算不同,纹理单元的指令源是纹理坐标,输出是经过插值的纹理值(最常见的如RGBA),都是向量。拿到的纹理数据会存Tex L1 cache里,因为千辛万苦从外面搬进来的数据以后没准还有用呢。
SM(Streaming Multiprocessor):终于来到最底层负责运算的部门
I cache(指令cache):一个SM要做的来自SMC的工作(比如一个片元着色器)不是立刻就能完成的,大量的指令是需要被缓存下来,分批执行的。
C cache(常量cache)与Shared Memory(共享内存):下一章我们讲通用计算与各种内存类型时会再详细展开。
MT(multi-threaded) Issue:是的,这是SM部门主管,负责把Warp任务拆碎成一条条指令分给社畜们执行的,是本篇的主角,其对Warp的调度正是GPU并行能力的关键。虽然我们还没正式开始了解其工作机制,但是已然可以窥见,所谓的并行性并不是一堆无组织的群氓聚在一起就能自动实现的,从上至下,从外到里,每层都有其管理者,组织架构极其严密且复杂。
SP(Streaming Processor):干活的主力军,执行最基本的浮点型标量运算,包括add、multiply、multiply-add,以及各种整数的运算。
SFU(Special Function Unit):更为复杂的运算,比如超越函数(指数、对数、三角函数等)、属性插值(将顶点属性插值成光栅化后的每一个片元输入)、透视校正(先插值除以w的属性,插值完再乘回w)
ISA(Instruction Set Architecture):那么都会生成哪些指令呢?主要有三类:
运算:浮点整数等加法乘法、最小最大、比较、类型转换等基础运算以及超越函数等复杂运算。
流控制:分支、调用、返回、中断、同步
内存访问:包括对各类内存的读写、原子操作
SMC拿到一个着色器的所有指令后,每次会把这些指令以32个线程为单位分发给SM,而负责执行完这个着色器所有指令的32线程,就称为一个Warp。指令存在了指令cache里,MT(multi-threaded) Issue每次拿出一条指令发给手底下的SP或SFU们执行。诶?可是一个SM里SP只有8个,怎么运行32个线程呀,答案是每个SP连续干四次不就行了嘛?。SP是负责干活的硬件,线程是逻辑上负责完整运行完一个着色器所有指令的概念,确实没必要一一对应。SFU也是同理
一个MT Issue发号施令一次,手底下的SP或SFU就吭哧吭哧将一条指令合计执行32次,这就是SIMT(Single-instruction, multiplethread),GPU正是基于SIMT而设计的。
由于每个线程输入的数据不同,很有可能会进入不同的分支。
编译器可以进行特殊情况下的分支预测,来判断Warp中所有线程是否必然进入同一分支(比如条件是传入的定值,并非经过实际运算的值)
如果无法预测,则不得不在执行时付出点指令开销来进行Warp Voting,如果投票出来该Warp的所有线程还是只走一条分支,那么就还好,大家可以一起跳过其他所有分支,否则不同分支就只能串行,不进入某一分支的线程将被mask。即使只有一个线程进入了某一个分支,其他31个线程也得等着它执行完,这就是锁步运行。
当前硬件的计算速度比访问内存的速度快几个数量级,瓶颈往往在内存访问上。当SP遇到内存访问指令,会由SMC向外界请求数据(要么通过纹理单元要么通过ROP),那在漫长的等待过程中,SP是不是只能摸鱼了?
这就是延迟隐藏的真相了。是的,公司特意搞了一个指令cache,就是让SM能够存下足够多的Warp(Tesla架构最多24个,这些Warp可以是不同类型的,比如顶点着色器、片元着色器甚至是CUDA程序),以在出现内存阻塞时快速切换到其他可以执行的Warp上,等拿到数据了再切回原来的Warp就行。毕竟,读写数据并不需要SP们费心,有足够多的Warp让SP们007,这家公司才有可能蒸蒸日上。
而是所有元器件尽可能都不要停下来。说到所有元器件,自然MT Issue主管也跑不了,MT Issue从原来分发一个Warp的一条指令,变成了从多个Warp里挑出一个Warp执行其之前执行到的接下来的指令。至于如何调度,有复杂的计分板根据Warp类型、指令类型和公平性原则等来量化每一个待选的Warp,每两个处理器周期,MT Issue就从中挑出一个得分最高的Warp分配给对应的元器件去执行。
每个SP只有8个,做完Warp一条指令需要4个周期,那么MT Issue为什么是两个处理器周期就分发一次指令呢?因为还有SFU啊!SP在运行的时候,SFU可不能闲着,2个周期分发一次指令的频率,就可以保证MT Issue有充足的时间给SFU挑选一个需要其运行的Warp
最后还剩下一个小问题可以探讨,一个Warp线程的多少对性能有什么影响呢?如果粒度太粗,则可供调度的 Warp就会太少,不利于延迟隐藏; 如果粒度太小,则每次切换所执行的线程就太少, 切换 Warp的相对成本就变高了。
