
拜“人工智能”与“深度学习”之所赐,GPU应用看起来很夯,大家抢着挖矿让显卡一卡难求,搞得最近Nvidia和AMD的股价涨得很凶。
随着1999年8月31日发表的GeForce 256(NV10),Nvidia创造了“GPU”(Graphic Processing Unit)这个看起来好像非常伟大的名词,其近20年演进,颠覆了众人对“处理器”一词的传统认知,而追求制图处理器泛用化的GPGPU,在Nvidia的推波助澜下,更激发了无数人对未来的期望、憧憬、与幻想。
部分读者可能并未经历过显卡芯片市场,从百家争鸣,一路仿佛x86处理器的市场发展,走上Nvidia与后来被AMD并购的ATI,双雄相争的过往,寄望可递上一块让你挺立于没顶时代洪流中的踏脚石,也希望可以顺便唤回老读者学生时代的珍贵回忆。
据考证,Nvidia对GPU一词的最初技术定义是“整合的3D转换(Transform)、打光(Lighting)、三角设定(Triangle Setup)/裁切(Clipping)与成像引擎(Rendering Engine),每秒能处理至少1千万个多边形的单芯片处理器”。
说穿了,这只是蓄意突显当时只有 Nvidia率先支援DirectX 7.0硬件Transform & Lighting(Hardware T&L)的营销手法,刻意集中打击产品发展与公司营运已陷入困境的衰退霸权3dfx。
笔者还依稀记得,3dfx曾经花了很大工夫在网站首页用酷炫的Flash拼命宣传“Banshee 2”(Voodoo 3 的昵称,根本只是补回第二个TMU的Banshee)与VSA-100叠叠乐的Voodoo 4/5系列,其1,600×1,200 60fps和反锯齿的实用性。
3dfx在2000年破产后,即被Nvidia并购,其末代产品“Rampage”核心设计团队,后来接手操刀由IBM 代工制造、Nvidia GPU历史不朽名作GeForce 6(NV40)系列,拯救了深陷NV30灾难的Nvidia。
总而言之,现在只要是制图芯片,GPU几乎等同于代名词,以理论的角度来看,GPU的本质到底是什么?
GPU本质上多样化的平行性,涵盖了指令阶层、多执行绪、SIMD(单指令多数据流)、与以多处理器环境的MIMD(多指令多数据流),那我们该以哪个角度来理解GPU?
直接在这里讲结论:GPU是“由数个多执行绪架构的SIMD处理器(如Nvidia的SM/SMX/SMM,AMD的SIMD Engine/CU),所组成的MIMD多处理器环境”,硬件层级可简述如下:

GPU 的灵魂在于“被执行绪区块排程器(Thread Block Scheduler)指派给执行该代码的多执行绪SIMD处理器(Multi-thread SIMD Processor),对GPU以巨大的缓存器档案,掩盖存储器延迟”,那层看起来很像MIMD的外皮,完全不是重点,请对GPU的认知,聚焦在SIMD和多执行绪上。
为何GPU的多执行绪像纺纱机般地千丝万缕?

答案很简单:需要同时执行滔滔不绝的多执行绪隐藏存储器延迟。
CPU通常使用快取存储器来减少存取主存储器的次数,防止存储器延迟拖慢指令管线执行效率,但GPU因平行处理与存取材质,制图系统的存储器往往“高带宽,高延迟” ,需多执行绪“交替掩护”,隐藏存储器存取延迟,如第一个执行绪等待存储器存取结果时,开始执行第二个执行绪,维持执行单元满载。
不像CPU藉由分支预测减少管线停滞,GPU多半使用类似上述处理存储器延迟的方式,减轻分支指令造成的性能损伤,但一般而言,通常GPU处理分支指令的性能比CPU来得差。
相异于泛用CPU的指令执行流程,制图管线并非“一条肠子通到底”,横跨数个计算特性相异的功能阶段,要稳定执行性能,需更有效精确掌握所有功能单元的执行状态与资源分配。
简而言之,“以硬件为基础的多重执行绪管理与排程”是近代GPU的最重要技术核心,当同时数几百条、个别代表一个像素(Pixel)的执行绪,去绘制同一张画面,每个SIMD执行绪区块、等同一个8-32像素的处理时,光要有效同步化,避免少数执行绪拖垮整体性能,就是非常不简单的挑战,而充分将GPU的巨大计算潜力发挥到极限,更是举足轻重。
SIMD三种常见形式的异同?

因单指令多数据(SIMD)可单指令启动多笔数据计算,比起每道数据计算都需要执行一道指令的多指令多数据(MIMD)享有更多潜在的能量效率。
另外,就程序设计师的角度,SIMD对上MIMD的最大优点,不外乎可以延续既有的循序性思考,却可利用数据阶层的平行化计算提升性能。在设法理解GPU的本质和优点前,绝不可忽略这点。
时下常见的SIMD有三种:
历史悠久的向量架构电脑,像1976年的Cray-1超级电脑与后代,和今天依然很有名的NEC SX系列向量处理器。
近20年来蓬勃发展的多媒体SIMD指令集延伸,想必各位对PowerPC的AltiVec及x86指令集一路从MMX、3D Now!、SSE、SSE2、SSE3、SSE4、AVX、AVX2到AVX-512的演进一点不陌生。
近十年来从起步跌跌撞撞,到今日看似有模有样的GPGPU。
这三种架构看起来很像,但本质上有太多根本性的差异,先简单解释SIMD最简洁美观的向量电脑,再以其为基准点,去观察这三者的异同,更能体验GPU 微架构暗藏的“撇步”,这是厂商的营销简报不会告诉你的奥秘。

向量(Vector)计算是一种简单到有时候还真的不知道该如何好好阐述的平行计算方式。在科学计算中,有大量彼此不相关的数据进行同一种计算,正好便于单一指令,对不同的数据,执行相同的重复操作。
向量电脑撷取以不同“跨度”(Stride)散布于存储器各处的数据元素,“聚集”(Gather)于大型的循序式缓存器档案,存取非循序的存储器位置,并将全部元素重塑为紧密结构,在这些缓存器中进行数据计算,再将计算结构“分散”(Scatter)回存储器中,像多维阵列和稀疏矩阵,就是能够充分发挥向量电脑威力的场合。
为了说明向量处理的基本概念,下面举一个回圈操作范例,将阵列B内的16个元素个别加上1,再将结果存入阵列A:
I = 1,16
A (I) = B (I 2) 1
其中常数1和阵列A/B中的每个元素是“纯量”(Scalar),通常一条纯量指令只能处理一个或一对计算元,处理n个或n对计算元,至少就需要n个指令。
在一般的电脑上,就会变成这样的程序回圈:
I = 1
取出B (I 2)
计算B (I 2) 1
存入 A (I) = B (I 2) 1
I = I 1
连续跑15次,直到I=16为止。不仅费时,且可能因处理器的分支预测错误,而拖累管线性能(在这范例,理论上会发生一次)。
但导入向量计算“整排处理”,事情就简单了:
取出 B (I 2)
计算 B (I 2) 1
存入 A (I) = B (I 2) 1
第一个指令取出阵列B的16个元素,第二个指令对阵列B的每个元素分别加上1,第三个指令则将结果存入阵列A。三个指令即可搞定,更不会制造分支负担。
由此可见,向量计算有非常明显的优点:
向量处理流程比传统的“纯量”(Scalar)计算,可避开修改回圈控制变数与条件判断。请别忘了泛用CPU的世界,还有让人感到极度恶心“分支伤害”、“分支预测”与“分支预测错误的折损”。
因一个指令就可做到数十、数百个指令才能完成的计算,减少指令数,可节约反覆从存储器撷取指令并解码再执行的带宽及时间,利于打造复数执行管线架构。拜“计算方向固定”与“不同向量计算指令之间,并无控制流程相依性”之所赐,也较易于打造更深的指令执行管线。
相对于多执行绪的GPU,向量电脑采取截然不同的方法隐藏存储器延迟,藉由存储器与向量缓存器之间的区块传送,向量载入与储存都只付出一次性的延迟。
存储器存取模式与行为也是固定的,不需要考虑太多特殊情况,如少量却频繁的随机存取,也降低了设计最佳化存储器系统的门槛。
抽丝剥茧后,才会看到向量电脑真正的奥妙之处,以及GPU以不同奇计淫巧继承而来的特色。
首先,程序同时需要长短不一、在执行期间会受到变更的向量长度,该怎么办?这时候向量长度缓存器(VLR,Vector-Length Register)就派上用场了,编译器设定VLR 的值,就可以控制任何向量计算的长度(当然,不能超过向量缓存器的最大长度) ,包含载入与储存。
这更意味着向量电脑极为重大的优势:向量缓存器的长度可以在后来的产品时代继续成长,不需要变更指令集,就可持续提升计算能力,但反观受制于现有CPU指令集编码格式与缓存器数量限制的SIMD多媒体指令集,每当变更向量长度,就得大兴土木,叠床架屋个几百个指令(如你感到好奇,可自行拿起计算机算算Intel从MMX到AVX-512增加多少SIMD指令)。
GPU则是将通过硬件机能,将巨大的向量长度,拆散到数以万计的缓存器,没有这样的困扰。
更何况,GPU因具有I/O装置的特质,提供了介于编译器和硬件之间的间接性,让PTX变成与底层硬件脱钩的虚拟指令集(类似Java的Binary Code),而非会绑死相容性的二进位机器码,日后要“动摇国本”,难度也远比CPU简单,像Nvidia与AMD历代GPU微架构多年来吃饱没事基因突变,但也完全没有影响到软体堆叠,驱动程序品质就再看看了。
其次,所谓“博观而约取,厚积而薄发”,我们需要指定一道向量指令内,替向量回圈中的每一个元素计算进行条件式执行,例如:先对1 3 5 元素作加法,再让2 4 6 元素作减法,接着再让剩下的元素作乘法,该怎么作?向量遮罩缓存器(Vector-Mask Register)就堂堂登场了,任何被执行的向量指令,只会处理向量遮罩缓存器中那些被指定为1的元素,而被指定为0的元素则不受任何影响。
乍看之下,编译器让遮罩中充满了大量的0是非常浪费计算性能的蠢事,但这种设计消除了传统泛用CPU的分支处理与控制相依性,速度反而更快。很不幸的,基于和无法“不需要变更指令集,就可以提升向量处理性能”的相同理由,SIMD多媒体指令集多半没有这样的设计。
那需管理数以千计Stream Processor的GPU有没有向量遮罩缓存器呢?答案是:有,但是你看不见,GPU是用内部硬件功能提供向量遮罩,遮蔽掉特定的Stream Processor,而不像向量电脑是让软体编译器去直接操作缓存器。
最后,向量电脑一次性从散落在存储器各处的计算元素“聚集”至向量缓存器内,计算结束后再一次性“分散”回存储器四处,存储器子系统需同时处理多个存储器载入与回存的能力,大量独立运行的Memory Bank与预先撷取机能,指令集也需提供索引(Index)式载入与储存等精巧的定址模式,增加向量编译器能够将代码向量化的机会,尽管执行起来通常远比非索引的状况慢得多。通常向量电脑会配置专属的控制处理器(Control Processor)来辅助,如为索引式载入与储存,自动递增存储器地址。
论存储器子系统,Cray在2002年发表Cray X1向量计算超级电脑所采用的Multi-Streaming Processor(MSP)是很鲜明的案例:一个X1节点,由四组MSP、16组存储器控制器与32片存储器卡板所组成。
并不是把一堆处理器核心硬塞到单芯片内,看起来有很好的表面理论计算性能,就可称之为“单芯片超级电脑”,空有计算性能没用,内部的汇流排与存储器子系统更需密切配合。至于时下GPU存储器子系统的性能水准,除了延迟很长,相信就没有太多需要特别挑剔的地方了。
所有载入都是聚集、所有储存都是分散的GPU,GPU程序设计师需保证所有聚集中和分散中的所有地址,都是指向相邻的位置,GPU硬件中扮演着跟向量电脑控制处理器相同角色的“执行绪区块排程器”,需在执行期间辨认出这些地址的序列,确认他们是否相邻,将聚集与分散,转变为高效率的跨度式存储器存取。
泛用CPU的SIMD多媒体指令集上的聚集分散式存储器存取呢?很抱歉,原先几乎没有这些宝具,是近期才慢慢的补完。以x86指令集为例,演进如下,性能怎样,在此不做评论:
2011年1月:Sandy Bridge微架构:AVX指令集刚问世时,没有聚集和分散指令。
2011年6月:Xeon Phi x100系列(Knights Corner):其VPU指令集(IMCI,Intel Many Core Instructions)提供聚集和分散指令,但Xeon Phi x100只能作为辅助处理器,不能执行x86指令。
2013年6月:Haswell微架构:AVX2指令集新增聚集指令。
2013年6月:Xeon Phi x200系列(Knights Landing):要直接跟GPGPU打对台,就得硬着头皮全上了。
1.AVX-512F指令集同时支援聚集与分散指令。
2.AVX-512PF指令集增加聚集与分散的预先撷取(Prefetch)版本。
3.AVX-512CD指令集增加侦测地址冲突(Address Conflict)的分散指令。
一路看下来,SIMD三种型态摆在一起比比看,受制于现有CPU指令集的包袱,相较简洁的向量电脑和仰仗先进硬件承先启后的GPU,带有强烈“附赠”色彩的SIMD多媒体指令集看起来似乎有点废废的,但也并非毫无一无可取之处,因近代多工作业系统的迫切需求,虚拟存储器管理就是CPU最强的地方,尤其是按需求分页(Demand Paging)的功能,也是GPU双雄努力补强中的弱点。
如何从“多执行绪SIMD”的角度观察GPU全貌?

为方便读者理解,这里举两个范例, Nvidia和AMD。Nvidia G80(Tesla 1.0微架构,GeForce 8系列)家族执行CUDA 时,一个多执行绪SIMD处理器(SM,Streaming Multiprocessor),包含8个Stream Processor(SP,又称为CUDA Core),此外还包含8,192个缓存器、16kB共享区域存储器与共用的快取存储器,如常数、材质等。每个执行绪对应一个Stream Processor。
CUDA的执行绪有三个阶层,对应GPU的硬件层级,由大到小:

这里需要特别注意:从CUDA的程序模型角度来看,8个Stream Processor组成是四个一组,实际上可看成两组4D的SIMD处理器。
为什么4个一组?道理也很简单,3D图像的“颜色”和“位置”,都需要四个数值,分别是RGB三原色加上Alpha半透明通道、与XYZ三维坐标与W远近参数,各需要四次计算。同理可证,这也是AMD近代GPU的底层计算单元是4D 1D或4D的缘故,而不是像啥5D 6D 7D这些乍看之下还以为是Canon单眼型号的模样。
每个计算都最少有4时脉周期的延迟,起码要执行32个执行绪(8SP x 4 时脉周期)才可能隐藏计算延迟,初代CUDA以硬件自动分组、以32个执行绪为单位(Warp),“塞”给一个SM。如转换成两个4D SIMD处理器的角度,就是需要两组16个执行绪,也就是两组“半个 Warp”(Half-Warp)。
想得简单一点,一个Warp代表画面的一块小方格。
所有执行绪被加以区块化,并以32执行绪为组群而执行。如一个Warp内的执行绪碰到停滞,例如等待显卡存储器,此时可切到另一个Warp的执行绪,掩盖存储器延迟。一旦区块内的执行绪并非32的倍数,SM的执行绪排程器会将剩下的执行绪包成一个Warp,浪费GPU的计算能力,这是开发CUDA程序时需要特别注意的地方。
同一个Warp内的执行绪,将“执行相同的代码”,但“处理不同的数据”,这时候,你就看见SIMD的灵魂了。
但执行绪的数量受制于缓存器总量,G80一个SM总计有8,192个缓存器,假如一个执行绪占用16个缓存器,单一SM的执行绪上限是512(512执行绪×16 缓存器=8,192),等于16个Warp,如一超过,就需要将一部分的数据搬移到GPU外部的显卡存储器,降低执行效率,在撰写CUDA程序时,并不能无止尽的增加执行绪的数量,而共享的资源,如区域存储器和快取,也是开发程序时需深思熟虑之处。
事实上,每个SM可以管理的区块和执行绪都有其上限,也跟着GPU微架构时代而演进,例如:
Tesla 1.0(G80)是8个区块、768执行绪(24 Warp)
Tesla 2.0(GT100)是8个区块、1,024执行绪(32 Warp)
后来的Fermi增加到8个区块、1,536执行绪(48 Warp)
Maxwell之后到Pascal激增到32个区块、2,048执行绪(64 Warp)
对技术再不敏感的读者,在这里也可以察觉一个显而易见的残酷事实:对GPU底层架构不够熟悉,根本写不出像样的高效率程序,充分榨干GPU的计算潜能。
GPU双雄如何定义如此诡异的计算架构?

觉得G80太老旧?那我们瞧瞧引领Nvidia GPU更大幅接近主流泛用CPU的Fermi微架构(GF100,GeForce GTX 480),其SM组成结构如下:
两组16个Stream Processor(CUDA Core)组成的SIMD计算单元,总共32个,当然你可以把16个SP看成4个4D SIMD处理器。
16个载入/储存单元,一个时脉周期内最多可以执行16个执行绪。
4个特殊功能单元,计算像平方根、倒数、Sin和Cos等三角函数。
32768个32位元宽缓存器。
缓存器和存储器都具备 ECC 纠错机能。
64kB SRAM,可设定为“16kB L1 数据快取存储器+48kB 区域共享存储器”或“48kB L1 数据快取存储器+16kB 区域共享存储器”。
所有SM共用768kB L2数据快取存储器,这让Fermi长得更像CPU。

观念等同于“多执行绪SIMD处理器”的SM,其Stream Processor与缓存器数目,随着不同时代的GPU微架构而有所更动(如Fermi是Tesla 1.0 的四倍),以Fermi为例,任何一个指令都最少需要两个时脉周期来完成,套用G80的状况,等于是需要塞一个打包后的32执行绪(也就是前面提到的Warp),给16个Stream Processor组成的SIMD计算单元,而且要保持满载,一次要塞两个。
为改善硬件使用率,Fermi微架构的每个SIMD处理器,都拥有两个执行绪排程器(也就是负责切Warp的苦主)与两个相对应的指令分派单元,在每两个时脉周期内,从每个执行绪派发一道指令给计算、载入储存与特殊功能单元。

这就是SIMD计算结构与多执行绪两者结合而来的好处,Nvidia 将CUDA程序设计模型,命名为前所未见的“单指令多执行绪”(SIMT,Single Instruction, Multiple Thread ),而AMD则称之为单指令多重执行样板(SIMI,Single Instruction, Multiple Instance),实际上讲的是一模一样的概念,但撰写计算机结构教科书的大师和开班授课的教授心里怎样五味杂陈,我们就不得而知了,毕竟CPU和GPU在计算机结构的家谱中,并没有共同的祖先。而AMD GPGPU的技术基石GCN(Graphic Core Next)。在GCN问世之前,AMD GPU的Stream Processor是四个绑在一起的VLIW4结构,一道指令同时用到这四个Stream Processor,如须发挥最高利用率,编译器需最佳化排程设法塞好塞满。但GCN可同时执行四个执行绪,一道指令仅用到一个Stream Processor,如果指令都可以在一个时脉周期内完成的话,也可以用好用满。
GPGPU是什么?

GPGPU(General Purpose computing on GPU)直接翻译为“通用图形计算处理器”,讲白话一点,就是让GPU“不务正业”,去做和传统制图无关的通用计算任务,特别是适用于单指令多数据流(SIMD)的高密度浮点计算。
GPGPU并非GPU与生俱来的“本能”,而是随着GPU引进以着色器微中心的可程序化制图管线、陆续支援标准32位元单精度与64位元倍精度浮点标准并符合IEEE 754规范(后来IEEE 754-2008 再加上浮点乘积和)后,才慢慢蕴含通用计算的潜力。
整体来说,GPU适合一次进行大量相同的计算。CPU则较有弹性,能同时进行变化较多的工作。两者相辅相成,没有哪边可以彻底取代对方的可能。
对比“泛用”的CPU,使用GPU来进行计算工作,主要有几个潜在的优势:
更巨量的执行单元:以Nvidia最高阶显卡卡Titan Xp(GP102-450)为例,内含3840个Stream Processor,时脉约1.5GHz,32位元单精确度浮点数的最高理论性能高达12Tflops。高性能泛用GPU 虽然有更高的时脉,但执行单元与理论计算性能却远逊于同时期的GPU,而且GPU多半仍采用较主流桌上型处理器和服务器CPU落后的制程。不意外,CPU的晶体管大多数都砸在控制单元和快取存储器了。
更巨大的存储器带宽:前述的Nvidia Titan Xp有547.7GB/s的理论存储器带宽,而配置HBM2存储器界面的Tesla P100 16GB版本是惊人的720GB/s,Tesla V100更是惊世骇俗的900GB/s,反观世界上最高阶的服务器CPU,如IBM Power9,装上Memory Buffer,最多也“仅”230GB/s(没Memory Buffer 时,8通道DDR4会减半成120GB/s),更不用提Intel Xeon和AMD EPYC这些x86便宜货了。
和高阶CPU相比,价格远较低廉:例如一张Pascal微架构的Titan Xp包括12GB GDDR5X存储器的价格,“才”1,200美元,而Intel Xeon的旗舰8180M一颗就13,011美元,足足可以买10张还有得剩。
当然, GPU也有它的缺点:
平行不够高,性能不会好:GPU的计算单元数量很多,不能高度平行化的工作,所能带来的效益就不高。
浮点不够准,整数不够快:碍于成本和性能,GPU通常只支援不符合IEEE 754的32位元单精确度浮点数, 有些计算的精确度可能较低(这对重视计算输出结果的高性能计算很伤)。Nvidia是迟至Fermi时代的CUDA compute compatibilty v2.0,才算完备IEEE 754,但很多硬件加速的数学方程,其输出结果,并不保证与CPU一致。
此外,许多GPU并没有独立的整数计算单元,整数计算的效率较差,偏偏人工智能和深度学习(可视为类神经网络换个称呼)仍是以整数计算为主的应用。
只适合一路油门踩到底:GPU通常没有分支预测等复杂的辅助流程控制单元(就算有,也会很慢),对分支密集、大量条件判断的应用程序,如数据库,效率会比较差,而且是非常差。
存储器容量受限,升级不易:GPU都会搭配高带宽(加上高延迟)的存储器,如GDDR,但如要处理更大更复杂的问题时,仍需要更多的存储器容量,在增加容量时保有高带宽,并维护存储器数据的可靠性,也是一大挑战。当时下CPU可随时扩充至TB等级的容量,Nvidia Tesla P100和V100也仅16GB。
为实现高带宽,GDDR制图特化存储器直接焊接在与GPU 同一块板子上,毫无日后升级的弹性,就无须浪费篇幅讨论了。
Nvidia CUDA程序分为两个部分:Host端和Device端。Host端是指在CPU执行的部分,而Device端则是在GPU执行的部分,Device端的程序又称为kernel。通常host端程序将数据准备好后,复制到GPU的存储器,再由GPU执行device端程序,完成后由host端程序将结果从显卡的存储器中取回至系统主存储器。由于CPU与GPU之间多半经由PCI Express沟通,降低性能在所难免。
“存储器容量不足且难以升级”与“缺乏对GPU存储器的直接存取”的缺陷,解决方案不外乎“让系统和GPU共享统一的实体存储器,或着在逻辑上是统一的、结合于单一的存储器地址空间”。
Nvidia和AMD的GPU微架构大相径庭,然后光AMD一个时代的产品线,就可以涵盖三到四种不同时期的微架构,而使用Intel的编译器在AMD的CPU执行SPEC CPU就没有这样的困扰。
信息来源:科技新报,如文章给您造成困恼,为此我们深表歉意,欢迎您与我们联系,我们第一时间为您排忧解难!

戳下面的原文阅读,更有料!

