购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.1 面向经典体系结构的性能优化

从经典体系结构的角度,提升硬件性能主要有三类方法:第一类方法是将更多的精力用于发掘各种并行性;第二类方法是引入新的存储层次来缓解访存速度和存储容量之间的矛盾;第三类方法则是通过定制化的方法来提升硬件处理领域相关应用的性能。

1.1.1 并行性发掘

应用程序中的并行性可以分为数据级并行(data level parallelism)和任务级并行(task level parallelism)。其中,数据级并行是指同时操作多个数据,任务级并行则是通过多个并行而且独立的任务处理多个数据。在此基础上,计算机硬件可以通过指令级并行(instruction level parallelism)、单指令多数据并行(single instruction multiple data parallelism)、线程级并行(thread level parallelism)和请求级并行(request level parallelism)四种方式来实现数据级并行和任务级并行。

1.指令级并行

一个处理器核心通常是指能够独立地从至少一个指令流获取和执行指令的处理单元,通常包含取指单元、译码单元、执行单元、访存单元和程序计数器和寄存器文件等逻辑单元。指令级并行是指在单个处理器核心中,通过同时执行多条指令的方式来提高处理速度。通过巧妙地设计流水线结构,可以大幅度地提升单个处理器核心的指令级并行度。然而,受限于硬件复杂度,处理器中正在执行的指令总数与流水线的深度和个数成正比。处理器中正在运行的指令总数决定了处理器的复杂度。

指令级并行是传统系统结构领域中较为成熟的一种并行方式,代表性的技术包括流水线(pipeline)、多发射(multiple issue)、同时多线程(simultaneous multithreading)和超长指令字(very long instruction word)等。其中,流水线并行是将每个功能单元通过分时复用的方式在不同的指令之间共享,是一种时间上的并行;而多发射则是在每个时钟周期发射多条指令到多个流水线中并行执行,是一种空间上的并行;将时间和空间维度的并行组合起来就形成了同时多线程技术和超长指令字技术。

2.单指令多数据并行

单指令多数据并行是一种空间维度上的并行处理模式,典型的实现方式是在硬件中集成向量运算部件,用单条向量指令同时处理多个数据,例如Intel的SSE/AVX、ARM的NEON/SVE等。这种并行方式不仅可以充分挖掘程序中潜在的并行性,还可以精简指令序列。向量化不需要对硬件结构特别是流水线结构做大量的修改,通常只需要修改数据通路的位宽。对于图1.1所示的代码,如果运行在普通的标量处理器上,编译器需要生成一个循环,通过16次迭代来完成整个计算任务,一次迭代完成一个元素的自增运算。如果运行在向量宽度为4的向量处理器上,编译器只需要生成4条向量加法指令即可完成计算,不需要插入任何分支跳转指令。

图1.1 循环级并行的基本模式

3.线程级并行

线程级并行既可以在单处理器核心内实现,也可以在多处理器核心内实现。在支持硬件多线程技术的单处理器中,可以通过硬件多线程或者同时多线程等技术来提高资源利用率和计算效率。但是,在设计复杂度和功耗墙问题的共同约束下,提升单处理器的性能已经非常困难。一个自然的想法就是在单个芯片上集成多个处理器核心,通过挖掘多个处理器核心之间的线程级并行能力来提升总体的计算能力。

在线程级并行模式中,不同执行单元的指令流相互独立,可以以同步或者异步执行的方式处理各自的数据。主要的实现方式有对称多处理器(Symmetric Multi-Processing,SMP)结构、分布式共享存储(Distributed Shared Memory,DSM)结构、大规模并行处理器(Massively Parallel Processing,MPP)结构、集群(cluster)。与线程级并行紧密相关的是被GPU架构普遍采用的单指令多线程技术,即多个线程以锁步的形式执行相同的指令,但是每个线程处理不同的数据。

4.请求级并行

线程级并行要求多个线程或进程在紧耦合的硬件系统中通过协作的方式完成一个计算任务,而请求级并行则是让多个独立并且可以并行工作的线程或进程完成各自的计算任务。请求级并行在Flynn分类法 [32] 中属于多指令多数据(Multiple Instructions Multiple Data,MIMD)并行。与指令级并行、单指令多数据并行和线程级并行相比,请求级并行的粒度更粗,而且通常需要程序员和操作系统的密切配合才能实现,而前面三种并行方式则更多地依赖编译器和底层硬件的支持。

1.1.2 存储层次结构

随着体系结构技术的飞速发展,处理器执行指令的速度远远超过了主存的访问速度。这种日益拉大的速度差异对计算机体系结构的发展产生了巨大的影响。为了弥合这种巨大的鸿沟,处理器中必须要设置由不同容量和不同访问速度的存储器构成的存储层次。存储层次可以提高程序性能的原因是保证了CPU大部分数据的访问时延都比较低。随着处理器计算速度和访存速度的差距越来越大,存储层次结构的设计显得越来越重要。

在典型的现代处理器中,每个CPU核有私有的L1 Cache,一组CPU核有一个共享的L2 Cache,而L3 Cache通常会被所有的处理器核共享,Cache的访问速度往往与容量成反比。容量越大,访问速度越低。由于程序中的时间局部性和空间局部性,Cache可以作为一个有效的硬件结构将经常使用的数据保持在离处理器较近的位置。为此,Cache需要尽可能多地保留最近使用的数据,在容量有限的前提下尽可能地替换出最近很少使用的数据。现代体系结构中的Cache通常是由硬件自动管理的,但是为了极致优化性能,程序员和编译器仍需要知道存储层次的相关信息。除了硬件自动管理的Cache以外,现代体系结构往往还有软件自主管理的便签式存储器(scratchpad memory)。

存储层次设计一个非常重要的方面是对数据一致性的维护。便签式存储器的管理和一致性维护通常由软件完成,而对Cache的管理和一致性维护则由硬件完成。为了降低硬件成本,简化存储管理和数据一致性的维护,经典体系结构的存储层次通常设计成金字塔形。在金字塔形存储层次中,越靠近运算部件的存储器,容量越小,速度越快,成本越高;位于金字塔不同层次的存储器,只和它相邻的上下层存储器交换数据;存储层次之间的数据一致性由Cache一致性协议来保证。

然而,在一些领域专用架构中,金字塔形存储层次往往无法满足具体应用的访存需求。例如,对于大规模流式计算来说,因为数据的空间局部性和时间局部性较差,Cache的访存加速效果也会大打折扣。另外,对于同时存在多条具有不同特征的数据流的应用场景,对于流不敏感的金字塔形存储层次也不能很好地满足不同数据流的访存需求。例如,在深度神经网络的前向计算过程中,卷积层和全连接层的权值为常量而且数据量较小,而不同的输入和输出神经元则是变量,而且数据量通常很大,本层的输出神经元又会成为下一层的输入神经元。从宏观上看,领域专用架构的存储层次仍然是金字塔形结构,由片外的低速、大容量存储器和片上的高速、小容量存储器组成;但是,在微观结构上,通常还会设计一些非金字塔形的存储结构来更好地适应具体领域的计算和访存模式。

1.1.3 领域专用架构

领域专用架构(domain-specific architecture)专用性强,反而可以使得逻辑设计更加趋于简单,在设计思路上也更加开放,不用受到传统指令集和生态因素的制约,已经在大数据处理、数字信号处理、密码学、高性能计算、图形学、图像处理和人工智能等领域获得了广泛的应用。领域专用处理器一般会根据应用的具体特点,定制运算单元,简化控制逻辑,设计与领域计算特征相适应的存储结构和数据通路,虽然牺牲了通用性和灵活性,却获得了较高的性能和能效比。特别是在第三次人工智能浪潮中,涌现出大量的面向人工智能应用的领域专用处理器,这些人工智能处理器在功耗、性能和集成度等方面较传统的CPU、GPU和FPGA都有很大的优势。

领域专用架构体现出领域的专注性。以引爆第三次人工智能浪潮的深度学习算法为例,其主要特点是计算量和输入与输出(Input and Output,IO)数据量都比较大,而且并行度较高。这要求面向人工智能应用的领域专用处理器(简称人工智能处理器)在存储结构、带宽和算力配置以及互连结构上做大量的定制化设计。为了满足海量数据在计算单元和存储单元之间的高速传输需求,人工智能处理器不仅要具备与计算模式匹配的存储结构,还要在计算单元和存储单元之间具有高速的通信链路。为了满足深度学习的算力需求和计算模式需求,人工智能处理器不仅要集成大规模的并行计算单元,还要能够高效地处理深度学习算法中常见的卷积、全连接和池化等操作。为了适应深度学习算法的典型计算模式,人工智能处理器在结构设计时还要考虑将不同的计算单元和存储模块有机地结合在一起,尽量降低相关操作之间的数据共享开销。

可以说深度学习算法既是计算密集的,又是访存密集的。其中计算密集的代表是卷积运算,而访存密集的代表则是全连接运算。

卷积神经网络(Convolutional Neural Network,CNN)的主要计算量也都来自卷积层。以GoogleNet为例,卷积计算量会占到总计算量的90%左右。因此,加速卷积运算是提升深度学习应用性能的核心任务。卷积层的输入是神经元和权值,经过图1.2所示的 N 重循环处理后,得到输出神经元。

图1.2 卷积运算的循环表示(1)

一个卷积层包含CO个CI × KH × KW的卷积核,共计CO × CI × KH × KW个权值,每个卷积核对应输出神经元的一个通道。每个输出神经元涉及CI × KH × KW次乘累加运算,每个卷积层总的乘累加运算次数为HO × WO × CO × CI × KH × KW。

卷积运算是一种典型的Stencil运算,Stencil运算的特点是输入输出数据组织成一个多维网格,一个输出数据点的值只与邻近点的输入点有关,虽然每个输出点都可以独立计算,但是运算密度较低,依赖关系复杂。Stencil计算模式在流体力学、元胞自动机、天气预报、粒子模拟仿真、电磁学等领域的数值计算中都有广泛的应用。为了在领域专用处理架构上优化这类程序的性能,需要合理地组织计算次序和数据布局,充分发掘数据局部性和计算并行性。

作为访存密集型的代表运算,全连接运算本质上是矩阵向量运算,可以用图1.3所示的两层嵌套循环来表示。全连接运算的特点是, R C 比较大,因而权值的IO数据量比较大;二维权值中的每个元素都只参与一次乘累加运算,权值局部性比较差,整个算法的计算密度较低。

1.领域专用架构的存储层次

卷积和全连接运算本质上都是在执行乘累加运算,而且都可以转换为矩阵运算。对于全连接运算,可以将 N 维向量看成是 1的矩阵;对于卷积运算,可以利用Img2Col操作 [23] 转换为矩阵运算。除了乘累加运算,深度学习中还有大量的向量对位运算,例如,偏置运算和激活运算。面向人工智能应用的领域专用架构为了能够高效地处理矩阵乘累加运算和向量对位运算,通常会集成专用的矩阵运算单元和向量运算单元,例如华为的达芬奇架构 [54] 和Google的TPU [41]

图1.3 卷积运算的循环表示(2)

为了能够高效地处理深度神经网络中的典型运算,面向深度学习应用的领域专用架构还需要设计适应运算特点的存储层次。图1.4给出了Google TPU的组织结构,作为TPU的核心功能部件的矩阵乘单元(matrix multiply unit),分别从权值队列(weight FIFO)和神经元缓冲区(unified buffer)读取参与矩阵运算的只读权值和输入神经元数据,矩阵运算的结果首先被保存在片上的累加器缓冲区(accumulators)中,经过激活和池化等操作后再写回神经元缓冲区,这样就完成了一轮卷积运算。权值队列从片外的权值存储器(weight memory)读取只读的权值数据,神经元缓冲区则从主机侧读取参与运算的神经元数据。显然,TPU的存储层次已经不再是经典体系结构中的金字塔形结构,而是根据权值和神经元的运算特点,分别设计了专门的数据通路和专属的存储器。类似的设计在其他面向人工智能应用的领域专用架构中也普遍存在,而对于这类非金字塔形存储层次的管理通常由程序员或者编译器完成。

图1.4 Google TPU的组织结构

2.领域专用的计算功能部件

除了像Google TPU这样的领域专用芯片,在具有传统金字塔形存储层次结构的GPU上也为深度学习提供了专用的计算功能部件。Nvidia最先在V100 GPU上集成了用于实现矩阵乘累加运算功能的Tensor Core,可以用于实现形如 D = A × B + C 的矩阵乘累加操作。在CUDA编程模型中可以使用wmma(warp-level matrix multiply and accumulate)系列接口来操纵Tensor Core,每个wmma接口的内部则是通过调用一系列基础的mma(matrix multiply and accumulate)操作来实现矩阵块的乘累加运算。为了将Tensor Core融合到SIMT的编程模型中,CUDA将一个Warp中的32个线程分成8组,每组称为一个线程组(thread group),每两个线程组称为一个线程组对(thread group pair),每个线程组对中的8个线程协作完成一次Tensor Core上的mma操作。一次mma所需要的线程和对应矩阵元素的分布示意如图1.5所示。其中,带颜色的方块表示矩阵的一个元素,带颜色的圆圈表示一个线程;一个方块上的数字表示该矩阵元素由哪个线程执行,圆圈内的数字表示该线程的编号。

图1.5 一次mma所需要的线程和对应矩阵元素的分布示意

操作矩阵 A 或矩阵 B 的一行或一列内相邻的数据元素(在图中用相同的颜色表示)会被批量载入同一个线程的寄存器中,类似地,矩阵块 C 或矩阵 D 中的相邻元素也会被保存在同一个线程的寄存器中。对于任意规模的矩阵乘加操作,由编译器完成从任意规模的输入矩阵到wmma和mma操作支持的矩阵规模的分解和变换。 /Tzf+XK6IjMwOg2zMsp8SgZKDKpouP7AGQF3iGMEmZmgI1RKVBINS97o8QMms90Y

点击中间区域
呼出菜单
上一章
目录
下一章
×

打开