News

新闻中心

时间:2021-10-29来源:沐曦光启智能研究院科学家 李兆石

图形处理器 (Graphics Processing Unit, GPU) 和通用图形处理器(General-Purpose Graphics Processing Unit, GPGPU)是最近两年芯片设计行业最火热的话题。在国内,以沐曦为代表的多家公司选择做GPU或GPGPU产品,大量的资本涌入使得这一赛道波浪滔天;在国外,GPU和GPGPU行业的老大NVIDIA市值超过五千亿美元,一举超越Intel,成为全球市值最高的半导体设计公司。虽然营收规模上Intel依然是NVIDIA的4倍以上,但很多分析师认为NVIDIA是一家软件/互联网公司,应该对标Amazon、Facebook等公司的市盈率进行估值。


大水漫灌之下,有些人不禁好奇,那个曾经只是用来打打游戏的GPU,怎么突然就好像变成了半导体设计赛道的珠穆朗玛峰?GPGPU相比GPU到底怎么就“通用”了?GPGPU的通用性能不能让它替代CPU?为什么华尔街的分析师这么看好GPU/GPGPU的未来?


而对于GPU/GPGPU赛道上的从业者而言,工作中难免会产生“不知庐山真面目,只缘身在此山中”的感觉。比如为什么GPU单个芯片上随随便便说自己有几千核,而现在最尖端的x86/ARM CPU在单芯片上实现100多核就值得在新闻里大说特说了?GPU的“线程”和CPU的“线程”到底是不是一回事?为什么CUDA相比OpenMP/MPI/OpenCL,看起来在程序员中更加流行?CUDA的“软件生态护城河”到底是什么?为什么想写出高性能的CUDA程序,需要学习这么多特定GPU架构的细节?


为了解答这些疑问,我们需要回溯现代通用处理器体系结构和编程模型协同演化的历程,回到1965年Gordon Moore提出摩尔定律的时候,回到1959年Robert Noyce和Jack Kilby发明集成电路的时候,回到1945年Von Neumann提出以他的名字命名的计算机架构的时候。在旅程的终点,我们会将编程模型的演化历程总结为“编程模型三元悖论(Trilemma of Programming Models)”:对于新的编程模型,无法同时获得高通用性、高开发效率和高执行效率,最多只能同时实现两个目标,而放弃另一个目标。从计算系统中间层对硬件复杂性的处理方法上,我们可以经验性地说明三元悖论的合理性。


0. 引言


All problems in computer science can be solved by another level of indirection, except for the problem of too many layers of indirection.” (计算机科学中几乎所有的难题都可以通过增加一层中间层解决。唯一例外是计算机科学已经有了太多中间层这一难题。)

—— David Wheeler (1927-2004)


(注:David Wheeler是最早在计算机上求解生物学问题的人,并且是计算机编程中closed subroutine的发明人,并且他还是ILLIAC计算机的架构师。我们无法得知他在什么场景下说的这句话。Stroustrup Bjarne在他那本著名的“The C++ Programming Language”的前言引用David Wheeler,并称这句话为软件工程基本原理。在计算机诞生之初,所有的从业者都是“斜杠青年”:每个人都需要即懂软件又懂硬件。但在今天,计算机中大量的中间层隔离了从业者的视野。我一直很好奇,如果先知David Wheeler看到今天的情况,是应该喜于他的预言得到应验,还是应该忧于“计算机科学已经有了太多中间层”呢?)


所谓“通用处理器”与“专用集成电路(ASIC)”,其最大区别在于,通用处理器需要执行用户编写的软件。ASIC 仅针对特定应用,它只需提供专用的应用程序接口(application interface, API),无需考虑程序员如何对其进行编程的问题。而通用处理器的功能,最终是靠程序员来实现的。一套硬件能吸引大量用户投入精力开发软件的一个必要条件是硬件上的软件是向前兼容的:新一代的硬件设计即使发生了翻天覆地的变化,之前用户编写的软件依然可以在新的芯片上正确运行。我们将软件与硬件间进行对话的“语言”称为编程模型


广义的编程模型指的是从应用到芯片之间的所有抽象层次。通用处理器在漫长的发展过程中,逐渐形成了由编程语言、编译器中间表示、指令集架构等抽象层次构成的复杂的层次化中间层(indirection)模型。在这些模型中,上层中间层依次掩盖下层中间层的复杂性,例如,为了掩盖指令集架构层的指令计数器可以任意跳转(如x86指令集中的jump类指令)所带来的复杂流程控制,编程语言层提供了多种流程控制语句,如C语言中的if-else, while和for语句。这样,程序员在开发应用时,只需要面向特定中间层开发应用,而无需考虑底层实现的复杂性。


本专栏将从编程模型入手,以谱系学的视角,抽丝剥茧地分析GPU和GPGPU的前世今生。


本专栏将每月更新一篇,总共5-8篇,视写作进度而定。欢迎各位关注本公众号,获取更多沐曦的新闻和技术分享。


01. 编程模型与软件创新和硬件创新的关系


在过去六十年里,人类创造了一个指数增长的奇观:芯片性能持续指数增长,芯片之上的应用愈发复杂多样。编程模型作为芯片与应用之间的契约,借由契约的前后一致性,确保了过去的应用可以方便地移植在将来的芯片上。但摩尔定律的终结,如釜底抽薪,破坏了计算产业的奇观。对于任何新兴通用处理器架构,如GPGPU,我们必须将芯片设计从旧的契约中解放出来,重新思考芯片、编程模型和应用的关系。


没有新的编程模型,新兴体系结构设计如无源之水,缺少指引硬件设计方向的软件。在体系结构研究中,对硬件范式转换的最直接响应是发明一种新的(领域定制)编程模型。尽管新编程模型在短期内很有吸引力,但这通常意味着程序员必须重新编写代码,并会对软件开发团队带来严重的理解、交流和学习曲线问题。而在硬件架构快速迭代的阶段,直接花费大量人力物力,针对不断演化的体系结构,设计开发自动化的编译器也不现实。因此,新的硬件范式在设计过程中通常都面临无软件可用的困境:目标应用难以对硬件设计中的决策快速响应,从而极大地拉长了新兴体系结构的研发周期。


没有新兴体系结构,新的编程模型设计如无本之木,缺少给编程模型开发提供着力点的硬件。编程模型的作用是掩盖复杂的硬件机制。在摩尔定律对增强通用处理器性能还有效的时代,编程模型的设计远比今天简单。虽然处理器的硬件机制可能在代际之间发生剧变,但是新一代处理器的指令集架构(instruction set architecture, ISA)只需要增加少数几条或几类指令。因此,上一代的编程模型、编译器和编程语言只需要做少许改动便可应用在新一代处理器上。但随着摩尔定律在处理器性能方向上的失效,定制化成为新一代硬件最重要的性能来源。这些定制化硬件很难用一套统一的或者类似的 ISA 进行抽象。所以,不同的新硬件都需要有独特的编程模型。在新兴硬件范式还没有定型之时,编程模型难以明确到底要掩藏哪些硬件机制。


“先有鸡还是先有蛋”的困境不被破局的话,新的通用处理器架构,要么硬件由于缺少软件的反馈而发展停滞,要么软件无法开发利用硬件的创新而举步维艰。这种僵局要求我们从根本上重新思考如何设计、编程和使用新的通用处理器架构。


“前事不忘,后事之师”。我们相信,通过回溯现代通用处理器体系结构和编程模型协同演化的历程,借由对历史的经验反思和概念探讨,可以克服常识的片段零星,进而更为连贯一致地理解GPGPU编程模型的设计方法。


02. 三种路线


正如David Wheeler前半句所预言的,中间层是计算行业增长和生产力进步的主要驱动力。如今大多数计算机科学专业的从业者可能都不知道现代微处理器的工作原理和芯片制造的工艺流程。但是通过维护这些层层相扣的中间层,计算机专业从业者可以在更高的抽象层次,如 python,开展高开发效率的编码工作。由此才产生了今天五彩斑斓的应用万花筒。


1636509978682585.png

图1: 计算机科学中,自顶(应用)向下(芯片)的典型中间层示意


图1展示了当代计算行业中,自顶(应用)向下(芯片)的典型中间层。按照传统的软硬件划分方法,自 ISA以上是软件,ISA 以下是硬件。越靠上的中间层抽象层次越高,程序的开发效率越高;越靠下的中间层复杂度越高,程序的执行效率越高。引入新的中间层的目的,就是要掩盖其下方中间层的复杂性,从而提高开发效率。


如果说整个计算行业琳琅满目的应用像是一栋栋鳞次栉比的高楼大厦,那么每一个中间层就是一层楼,编程模型就是黏合这些琼台玉宇的水泥。狭义的编程模型指的是从应用层到微架构层中,层与层之间约定的契约。具体而言,狭义的编程模型规定了上层的哪些行为合法,以及每个行为在下一层的执行机制微架构层到物理层中同样存在类似的契约,例如寄存器传输层到器件层之间使用网表文件作为契约。由于应用开发者不会与这些契约打交道,因此它们不属于本专栏讨论的编程模型范畴。


但是,正如David Wheeler引言的后半句所言,过多的中间层是一个难以解决的问题。这里的一个关键问题在于,每一个中间层的引入,都会对应用在芯片上的性能造成损失中间层越多,性能损失越大。因此,抽象层次极高的编程语言,例如 Python, JavaScript 等,主要的设计目标都是开发效率和应用范围。


图片2.png

图2:Python 中矩阵乘法比同等应用开发者使用高度优化的 C 语言编写的程序慢 100 到60000倍 [1]


为了达到这两个目标,高抽象层次语言具有许多共同的特征:通常是被单线程地解释,具有基于引用计数等简单算法的垃圾回收机制等。因为这些特征,高抽象层次语言的执行效率极为低下。2020 年《科学》杂志刊登了一篇计算机体系结构的论文《顶部还有足够的空间》[1],其中的一个例子是Python 中矩阵乘法比同等应用开发者使用高度优化的 C 语言编写的程序慢 100 到60,000 倍,如图2所示。而且,高抽象层次语言执行时也需要更大的内存。例如,Python 中的整数消耗的是 24个字节,而不是C语言的 4个字节(因为每个对象都携带类型信息,引用计数等),而列表或字典等数据结构的开销则是其 C ++ 开销的 4倍以上。当然,这高抽象层次的语言的设计目标并不是高效地利用硬件。但当芯片的性能不再随着摩尔定律的前进而增长时,高抽象层次语言和高性能语言之间的执行效率差距成为了尚未充分发掘的金矿


根据开发过程中开发者主要跟哪个中间层打交道,我们将计算产业的从业者大致分为四组(图1):硬件开发者,负责设计电路和制造芯片,从电路的层次设计ALU、高速缓存等模块;架构设计师,负责设计微架构和ISA,利用硬件开发者设计的模块搭建计算系统,并将计算系统的功能以ISA或者API的形式提供给上层开发者;编译设计师,根据应用需求和架构特性,负责设计编程语言和编译工具链,从而将应用开发者编写的应用自动地转化为目标架构可以执行的机器码;应用开发者,使用编程语言开发应用。参照之前的定义,广义的编程模型可以看作应用开发者与硬件开发者进行对话的语言,该语言由架构设计师和编译设计师设计。


按照上述四组人马中,由谁负责掩盖复杂的硬件机制,可以简要地归纳出三种编程模型的设计路线。首先,有些硬件机制只需要交给架构设计师考虑,一般不需要编译器的干预。例如,当今流行的领域定制加速单元,通常都是由架构设计师提供一组简单的API或者专用指令,供上层的编译器和应用开发者直接调用。然后,有些硬件机制可以交由编译设计师处理,不需要让应用开发者了解。例如,CPU中成百上千个寄存器,都可以由编译器自动完成分配。最后,很多硬件机制的性能潜力,必须由应用开发者根据应用的需求编写程序,才能充分开发。例如,多线程处理器需要应用开发者使用并行编程语言编写程序,才能利用它们的并发执行机制。


三种设计路线给编程模型带来了截然不同的特征。编程模型的发展历程,就是这三条路线相互角力达到平衡的过程。之后的文章中,我们将沿着时间长河,回溯典型硬件机制的设计动机及其对应编程模型。


03. 参考文献


[1] C. E. Leiserson et al., “There’s plenty of room at the Top: What will drive computer performance after Moore’s law?” Science, vol. 368, no. 6495, Jun. 2020.

[2] 魏少军,刘雷波,朱建锋,邓辰辰,《软件定义芯片》,2021年9月,科学出版社出版。


本文部分内容改编自《软件定义芯片》下册第一章“编程模型”。本文作者负责编写那一章的内容。在此向《软件定义芯片》一书的作者们,以及科学出版社,致以诚挚的谢意。欲知后事如何,各位读者可以继续关注【智算芯闻】栏目,也可自行购买《软件定义芯片》一书,提前获取剧透。

  • 国内商务合作 Business@metax-tech.com
  • 国际商务合作 International.Business@metax-tech.com
  • 媒体合作 PR@metax-tech.com