图形处理器单元 (GPU) 最初是为支持视频游戏而开发的,现在越来越多地用于从机器学习到加密货币挖掘的通用(非图形)应用程序。 与中央处理单元 (CPU) 相比,GPU 可以通过将大部分硬件资源专用于计算来实现更高的性能和效率。 此外,与特定领域的加速器相比,它们的通用可编程性使当代 GPU 对软件开发人员具有吸引力。 本书为那些对研究支持通用计算的 GPU 架构感兴趣的人提供了介绍。 它收集了目前只能在广泛的不同来源中找到的信息。 作者领导了 GPGPU-Sim 模拟器的开发,该模拟器广泛用于 GPU 架构的学术研究。
本书的第一章描述了 GPU 的基本硬件结构,并简要概述了它们的历史。 第 2 章总结了与本书其余部分相关的 GPU 编程模型。 第 3 章探讨了 GPU 计算核心的架构。第 4 章探讨了 GPU 内存系统的架构。 在描述了现有系统的架构之后,第 3 章和第 4 章提供了相关研究的概述。 第 5 章总结了影响计算核心和内存系统的跨领域研究。对于那些希望了解用于加速通用应用程序的图形处理器单元 (GPU) 体系结构的人,以及那些希望了解快速增长的研究主体,探索如何改进 这些 GPU 的架构。
本书探讨了图形处理器单元 (GPU) 的硬件设计。 最初引入 GPU 是为了实现专注于视频游戏的实时渲染。 今天,GPU 无处不在,从智能手机、笔记本电脑、数据中心,一直到超级计算机。
事实上,对 Apple A8 应用处理器的分析表明,与中央处理器单元 (CPU) 内核相比,它为集成 GPU 提供了更多的芯片面积 [A8H]。 对更加逼真的图形渲染的需求是 GPU 创新的最初驱动力 [Montrym and Moreton, 2005]。 虽然图形加速仍然是其主要目的,但 GPU 越来越多地支持非图形计算。 今天受到关注的一个突出例子是越来越多地使用 GPU 来开发和部署机器学习系统 [NVIDIA Corp., 2017]。 因此,本书的重点是与提高非图形应用程序的性能和能源效率相关的功能。
本介绍性章节简要概述了 GPU。 我们从 1.1 节开始,考虑更广泛的计算加速器类别的动机,以了解 GPU 与其他选项的比较。 然后,在第 1.2 节中,我们快速概述了当代 GPU 硬件。 最后,第 1.4 节提供了本书其余部分的路线图。
1.1 THE LANDSCAPE OF COMPUTATION
ACCELERATORS
几十年来,计算系统的后几代表现出每美元的性能呈指数级增长。 根本原因是晶体管尺寸的减小、硬件架构的改进、编译器技术和算法的改进。
据估计,这些性能提升的一半是由于晶体管尺寸的减小导致设备运行速度更快 [Hennessy and Patterson, 2011]。 然而,自 2005 年左右以来,晶体管的缩放未能遵循现在称为 Dennard Scaling 的经典规则 [Dennard et al., 1974]。 一个关键的结果是,随着设备变得更小,时钟频率现在的提高速度要慢得多。 为了提高性能,需要找到更高效的硬件架构。
通过利用硬件专业化,可以将能源效率提高多达 500 [Hameed et al., 2010]。 正如 Hameed 等人所表明的那样,实现这种效率增益有几个关键方面。 转向矢量硬件,例如 GPU 中的硬件,通过消除指令处理的开销,效率提高了大约 10 倍。 硬件专业化的大部分剩余收益是最小化数据移动的结果,这可以通过引入执行多个算术运算的复杂操作来实现,同时避免访问大型存储器阵列,例如寄存器文件。
当今计算机架构师面临的一个关键挑战是找到更好的方法来平衡可以通过使用专用硬件获得的效率收益与支持各种程序所需的灵活性。 在没有架构的情况下,只有可用于大量应用程序的算法才能高效运行。 一个新兴的例子是专门用于支持深度神经网络的硬件,例如谷歌的张量处理单元 [Jouppi et al., 2017]。 虽然机器学习似乎可能占据计算硬件资源的很大一部分,并且这些资源可能会迁移到专用硬件,但我们认为仍然需要有效支持以传统编程语言编写的软件表示的计算。
除了使用 GPU 进行机器学习之外,人们对 GPU 计算产生浓厚兴趣的一个原因是现代 GPU 支持图灵完备编程模型。 通过图灵完备,我们的意思是只要有足够的时间和内存,任何计算都可以运行。
相对于专用加速器,现代 GPU 是灵活的。 对于可以充分利用 GPU 硬件的软件,GPU 的效率可以比 CPU 高一个数量级 [Lee et al., 2010]。 这种灵活性和效率的结合是非常理想的。 因此,许多顶级超级计算机,无论是在峰值性能还是能源效率方面,现在都采用 GPU。 在随后的几代产品中,GPU 制造商改进了 GPU 架构和编程模型,以提高灵活性,同时提高能效。
1.2 GPU HARDWARE BASICS
通常那些第一次接触 GPU 的人会问他们是否最终会完全取代 CPU。这似乎不太可能。在当前系统中,GPU 不是独立的计算设备。相反,它们与 CPU 结合在单个芯片上或通过将仅包含 GPU 的附加卡插入包含 CPU 的系统中。 CPU 负责在 GPU 上启动计算并将数据传入和传出 GPU。 CPU 和 GPU 之间进行这种分工的一个原因是计算的开始和结束通常需要访问输入/输出 (I/O) 设备。虽然一直在努力开发直接在 GPU 上提供 I/O 服务的应用程序编程接口 (API),但到目前为止,这些都假设附近存在 CPU [Kim 等人,2014 年;Silberstein 等人,2013 年]。这些 API 通过提供方便的接口来发挥作用,这些接口隐藏了管理 CPU 和 GPU 之间通信的复杂性,而不是完全消除对 CPU 的需求。为什么不淘汰CPU?用于访问 I/O 设备和以其他方式提供操作系统服务的软件似乎缺乏适合在 GPU 上运行的特性,例如大规模并行性。因此,我们首先考虑 CPU 和 GPU 的交互。
图 1.1 显示了一个包含 CPU 和 GPU 的典型系统的抽象图。左侧是典型的离散 GPU 设置,包括连接 CPU 和 GPU(例如 PCIe)的总线,用于 NVIDIA 的 Volta GPU 等架构,右侧是典型的集成 CPU 和 GPU(例如 AMD 的 Bristol Ridge)的逻辑图APU 或移动 GPU。请注意,包括离散 GPU 的系统具有用于 CPU(通常称为系统内存)和 GPU(通常称为设备内存)的独立 DRAM 内存空间。用于这些存储器的 DRAM 技术通常不同(CPU 为 DDR,GPU 为 GDDR)。 CPU DRAM 通常针对低延迟访问进行了优化,而 GPU DRAM 则针对高吞吐量进行了优化。相比之下,具有集成 GPU 的系统具有单个 DRAM 内存空间,因此必须使用相同的内存技术。由于集成 CPU 和 GPU 经常出现在低功耗移动设备上,因此共享 DRAM 内存通常针对低功耗(例如 LPDDR)进行了优化。
GPU 计算应用程序开始在 CPU 上运行。通常,应用程序的 CPU 部分将分配和初始化一些数据结构。在来自 NVIDIA 和 AMD 的较旧的独立 GPU 上,GPU 计算应用程序的 CPU 部分通常为 CPU 和 GPU 内存中的数据结构分配空间。对于这些 GPU,应用程序的 CPU 部分必须协调数据从 CPU 内存到 GPU 内存的移动。较新的独立 GPU(例如 NVIDIA 的 Pascal 架构)具有软件和硬件支持,可以自动将数据从 CPU 内存传输到 GPU 内存。这可以通过利用 CPU 和 GPU 上的虚拟内存支持来实现 [Gelado et al., 2010]。 NVIDIA 称之为“统一内存”。在 CPU 和 GPU 集成到同一芯片上并共享同一内存的系统上,无需程序员控制从 CPU 内存复制到 GPU 内存。但是,由于 CPU 和 GPU 使用缓存,并且其中一些缓存可能是私有的,因此可能存在缓存一致性问题,硬件开发人员需要解决该问题 [Power et al., 2013b]。
在某些时候,CPU 必须在 GPU 上启动计算。 在当前系统中,这是在 CPU 上运行的驱动程序的帮助下完成的。 在 GPU 上启动计算之前,GPU 计算应用程序指定应在 GPU 上运行哪些代码。 这段代码通常被称为内核(更多细节在第 2 章)。 同时,GPU 计算应用程序的 CPU 部分还指定应该运行多少线程以及这些线程应该在哪里查找输入数据。 要运行的内核、线程数和数据位置通过在 CPU 上运行的驱动程序传送到 GPU 硬件。 驱动程序将翻译信息并将其放置在 GPU 可访问的内存中,在该位置,GPU 被配置为查找它。 然后,驱动程序向 GPU 发出信号,表明它应该运行新的计算。
现代 GPU 由许多内核组成,如图 1.2 所示。 NVIDIA 将这些内核称为流式多处理器,而 AMD 将它们称为计算单元。 每个 GPU 内核执行一个单指令多线程 (SIMT) 程序,该程序对应于已启动在 GPU 上运行的内核。 GPU 上的每个核心通常可以运行一千个线程。 在单核上执行的线程可以通过暂存器内存进行通信,并使用快速屏障操作进行同步。 每个内核通常还包含一级指令和数据缓存。 这些充当带宽过滤器,以减少发送到内存系统较低级别的流量。 当在一级缓存中找不到数据时,内核上运行的大量线程用于隐藏访问内存的延迟。
注:一个core内为SIMT(相当于1000个thread);Core内的thread可以利用内存交流,也可以用 barrier同步;利用多个线程并行计算来掩盖内存访问的延迟
为了维持高计算吞吐量,必须在高计算吞吐量和高内存带宽之间取得平衡。 这反过来需要存储器系统中的并行性。 在 GPU 中,这种并行性是通过包含多个内存通道来提供的。 通常,每个内存通道都与内存分区中的最后一级缓存的一部分相关联。 GPU 内核和内存分区通过片上互连网络(例如交叉开关)连接。 替代组织是可能的。 例如,在超级计算市场上直接与GPU竞争的英特尔至强融核,将末级缓存与核心进行分配。
在高度并行的工作负载上,与超标量乱序 CPU 相比,GPU 可以通过以下方式获得更高的单位面积性能: 将其芯片面积的较大部分专用于算术逻辑单元,而相应地将较少的面积专用于控制逻辑。为了直观地了解 CPU 和 GPU 架构之间的权衡,Guz 等人。 [2009] 开发了一个富有洞察力的分析模型,展示了性能如何随线程数量而变化。为了保持模型简单,他们假设了一个简单的缓存模型,其中线程不共享数据和无限的片外存储器带宽。图 1.3 再现了他们论文中的一个图,说明了他们在模型中发现的一个有趣的权衡。当少量线程共享大缓存时(如多核 CPU 中的情况),性能会随着线程数量的增加而提高。但是,如果线程数增加到缓存无法容纳整个工作集的程度,则性能会下降。随着线程数量的进一步增加,性能随着多线程隐藏长片外延迟的能力而提高。 GPU 架构由该图的右侧表示。 GPU 旨在通过采用多线程来容忍频繁的缓存未命中。
注:GPU相比于CPU的特点:将较大的面积用来做计算逻辑,较少的面积用来做控制逻辑
随着 Dennard Scaling [Horowitz et al., 2005] 的结束,提高能源效率已成为计算机架构研究创新的主要驱动力。 一个关键的观察结果是,访问大内存结构会消耗与计算一样多或更多的能量。 例如,表 1.1 提供了 45 nm 工艺技术中各种操作的能量数据 [Han et al., 2016]。 在提出新颖的 GPU 架构设计时,重要的是要考虑能耗。 为了解决这个问题,最近的 GPGPU 架构模拟器,如 GPGPU-Sim [Bakhoda 等人,2009] 结合了能量模型 [Leng 等人,2013]。
本节简要介绍图形处理单元的历史。计算机图形在 1960 年代出现,其中包括 Ivan Sutherland 的 Sketchpad [Sutherland, 1963] 等项目。从最早期开始,计算机图形就已成为电影动画离线渲染的组成部分,同时也与视频游戏中使用的实时渲染的发展密切相关。早期的视频卡始于 1981 年的 IBM 单色显示适配器 (MDA),它仅支持文本。后来,显卡引入了 2D 和 3D 加速。除了视频游戏,3D 加速器还针对计算机辅助设计。早期的 3D 图形处理器,如 NVIDIA GeForce 256,功能相对固定。 NVIDIA 在 2001 年推出的 GeForce 3 中以顶点着色器 [Lindholm et al., 2001] 和像素着色器的形式向 GPU 引入了可编程性。研究人员通过将矩阵数据映射到纹理中,很快学会了如何使用这些早期的 GPU 实现线性代数并应用着色器 [Krüger 和 Westermann,2003] 以及将通用计算映射到 GPU 的学术工作,这样程序员很快就不需要了解图形 [Buck 等人,2004]。这些努力激励 GPU 制造商直接支持除图形之外的通用计算。第一个这样做的商业产品是 NVIDIA GeForce 8 系列。 GeForce 8 系列引入了多项创新,包括从着色器和暂存器内存写入任意内存地址的能力,以限制早期 GPU 所缺乏的片外带宽。下一个创新是使用 NVIDIA 的 Fermi 架构实现读写数据的缓存。随后的改进包括 AMD 的 Fusion 架构,该架构将 CPU 和 GPU 集成在同一个芯片上,以及能够从 GPU 本身启动线程的动态并行性。最近,NVIDIA 的 Volta 推出了专门针对机器学习加速的 Tensor Cores 等功能。