GPU 神经网络硬件Tensor Core加速器(三)

AI 模型中往往包含大量的矩阵乘运算,该算子的计算过程表现为较高的内存搬移和计算密度需求,所以矩阵乘的效率是 AI 芯片设计时性能评估的主要参考依据。本节我们一起来看一下矩阵乘运算在 AI 芯片的具体过程,了解它的执行性能是如何被优化实现的。

从卷积到矩阵乘

AI 模型中的卷积层的实现定义大家应该都已经比较熟悉了,卷积操作的过程大概可以描述为按照约定的窗口大小和步长,在 Feature Map 上进行不断地滑动取数,窗口内的 Feature Map 和卷积核进行逐元素相乘,再把相乘的结果累加求和得到输出 Feature Map 的每个元素结果。卷积到矩阵乘的的转换关系示意如下图。

其中逐元素相乘,再累加的过程就是上节提到的一个计算单位:MACs,矩阵乘的 MACs 数对最终性能具有重要影响。通过将输入数据(Feature Map)和卷积核数据进行重排,卷积操作本质上可以等效理解为矩阵乘操作。

假设卷积的输入和输出的特征图维度用(IH, IW), (OH, OW)表示,卷积核窗口的数据维度用(KH, KW)表示,输入通道是 IC,输出通道是 OC,输入输出特征图和卷积核数据维度重排的转化对应关系如下公式,对输入数据的重排的过程称为 Im2Col,同理把转换后矩阵乘的数据排布方式再换回卷积输入的过程称为 Col2Im。

input:(IC,IH,IW)→(OH∗OW,KH∗KW∗IC)filter:(OC,KH,KW,IC)→(OC,KH∗KW∗IC)output:(OC,OH,OW)→(OC,OH∗OW)​input:(IC,IH,IW)→(OH∗OW,KH∗KW∗IC)filter:(OC,KH,KW,IC)→(OC,KH∗KW∗IC)output:(OC,OH,OW)→(OC,OH∗OW)​​

更具体的,假设卷积核的维度(2, 2),输入特征图维度(3, 3),输入和输出通道都是 1,对一个无 padding,stride=1 的卷积操作,输出特征图是(2, 2),所以输入卷积核转换为矩阵乘排布后的行数是 2∗2=42∗2=4,列数为 2∗2∗1=42∗2∗1=4。下图是对应的卷积到矩阵乘的转换示意,输入、输出特征图和卷积核都用不同的颜色表示,图中数字表示位置标记。

比如输入特征图的排布转换过程:第 1 个输出对应输入特征图的窗口数据标记为 1, 2, 4, 5;第 2 个输出对应的输入特征图窗口数据标记为 2, 3, 5, 6;第 3 个输出对应的输入特征图窗口数据标记为 4, 5, 7, 8;第 4 个输出对应的输入特征图窗口数据标记为 5, 6, 8, 9。矩阵乘的维度对应关系如下。

input:(OH∗OW,KH∗KW∗IC)→(4,4)filter:(OC,KH∗KW∗IC)→(1,4)output:(OC,OH∗OW)→(1,4)​input:(OH∗OW,KH∗KW∗IC)→(4,4)filter:(OC,KH∗KW∗IC)→(1,4)output:(OC,OH∗OW)→(1,4)​​

矩阵乘分块 Tilling

上面介绍了卷积到矩阵乘的转换过程,我们可以发现,转换后的矩阵乘的维度非常大,而芯片里的内存空间往往是有限的(成本高),表现为越靠近计算单元,带宽越快,内存越小。为了平衡计算和内存加载的时间,让算力利用率最大化,AI 芯片往往会进行由远到近,多级内存层级的设计方式,达到数据复用和空间换时间的效果。根据这样的设计,矩阵乘实际的数据加载和计算过程将进行分块 Tiling 处理。

假设用 CHW 表示上面转换公式中的 KH∗KW∗ICKH∗KW∗IC 的值,M 表示 OC,N 表示 OH∗OWOH∗OW,矩阵乘的输入特征图维度是 (CHW, N),矩阵乘的卷积核维度是(M, CHW),输出矩阵维度是(M, N),可以同时在 M,N,CHW 三个维度进行 Tiling,每次计算过程分别加载一小块的特征图和卷积核数据计算,比如在 M,N,CHW 三个维度各分了 2 小块,得到完成的输出特征图需要进行 8 次的数据加载和计算。下图中的 Step1, Step2 展示了两次数据加载可以完成一个输出 Tile 块的计算过程。

矩阵乘的库

矩阵乘作为 AI 模型中的重要性能算子,CPU 和 GPU 的平台上都有专门对其进行优化实现的库函数。比如 CPU 的 OpenBLAS, Intel MKL 等,GPU 的 cuBLAS, cuDNN 等。实现的方法主要有 Loop 循环优化 (Loop Tiling)和多级缓存 (Memory Hierarchy)。

其两者的实现逻辑大概分为如下 2 步,关于 Kernel 实现优化的技术细节在[推理引擎]章节进一步展开。

  1. Lib 感知相乘矩阵的 Shape
  2. 选择最优的 Kernel 实现来执行

下图展示了对矩阵乘进行 Loop 循环优化和多级缓存结合的实现流程。

左边是共 6 级 Loop 循环展开的伪代码,右边是 Loop 对应多级存储的数据 Tilling 和搬移过程,假设矩阵乘 A,B,C 对应维度是(m, k, n)。

  • Loop5, Loop4, Loop3 对应把矩阵在 n, k, m 维度进行 Tilling 的切分,Tilling 后维度大小分别是 nc, kc, mc。
  • Loop2, Loop1 分别将 Tilling 后的 nc, mc 维度再一次 Tilling,Tilling 后维度大小分别是 nr, mr。
  • Loop0 对 kc 维度进行展开,实现累加求和的过程,得到(mr, nr)大小输出矩阵的部分和。

图中不同的颜色框指代了在不同存储层级上的数据计算,不同颜色块表示该块数据的存储位置。结合不同存储层级的内存空间和数据搬移带宽大小,将不同大小的 A,B 矩阵的 Tilling 块放在不同的存储层级上,可以平衡 AI 芯片执行矩阵乘任务时的时间和空间开销,提升整体算力利用率。比如,对(mr, nr)的计算过程,通过将 B 矩阵的(kc,nr)加载 1 次到 L1 cache 中,每次从 L2 cache 加载 A 矩阵的(mr, kc)大小到计算模块,进行计算,假设 mc 切分了 3 个 mr,则 B 矩阵的(kc, nr)就在 L1 中被重复利用了 3 次。这种用空间换时间或者用时间换空间的策略是进行算子性能优化的主要方向。

矩阵乘的优化

矩阵乘作为计算机科学领域的一个重要基础操作,有许多优化算法可以提高其效率。下面我们对常见的矩阵乘法优化算法做一个整体的归类总结。

  1. 基本的循环优化:通过调整循环顺序、内存布局等手段,减少缓存未命中(cache miss)和数据依赖,提高缓存利用率,从而加速矩阵乘法运算。

  2. 分块矩阵乘法(Blocked Matrix Multiplication):将大矩阵划分成小块,通过对小块矩阵进行乘法运算,降低了算法的时间复杂度,并能够更好地利用缓存。

  3. SIMD 指令优化:利用单指令多数据(SIMD)指令集,如 SSE(Streaming SIMD Extensions)和 AVX(Advanced Vector Extensions),实现并行计算,同时处理多个数据,提高计算效率。

  4. SIMT 多线程并行化:利用多线程技术,将矩阵乘法任务分配给多个线程并行执行,充分利用多核处理器的计算能力。

  5. 算法改进:如 Fast Fourier Transform 算法,Strassen 算法、Coppersmith-Winograd 算法等,通过矩阵分解和重新组合,降低了算法的时间复杂度,提高了计算效率。

这些优化算法通常根据硬件平台、数据规模和计算需求选择不同的策略,以提高矩阵乘法运算的效率。在具体的 AI 芯片或其它专用芯片里面,对矩阵乘的优化实现主要就是减少指令开销,可以表现为两个方面:

  1. **让每个指令执行更多的 MACs 计算。**比如 CPU 上的 SIMD/Vector 指令,GPU 上的 SIMT/Tensor 指令,NPU 上 SIMD/Tensor,Vector 指令的设计。

  2. **在不增加内存带宽的前提下,单时钟周期内执行更多的 MACs。**比如英伟达的 Tensor Core 中支持低比特计算的设计,对每个 cycle 执行 512bit 数据的带宽前提下,可以执行 64 个 8bit 的 MACs,大于执行 16 个 32bit 的 MACs。

小结与思考

  • AI 模型的矩阵乘运算通过优化内存搬移和计算密度来提升芯片性能,涉及卷积到矩阵乘的转换及分块处理。

  • 矩阵乘的优化在软件层面包括减少 MACs、循环优化和内存优化,硬件层面则涉及提高 PE 运算速度、增加并行计算能力和提升 PE 利用率。

  • 专用数学库如 OpenBLAS、Intel MKL、cuBLAS 和 cuDNN 通过循环优化和多级缓存管理来提升矩阵乘性能。

  • 高效的矩阵乘法优化算法,如 Blocked Matrix Multiplication 和 SIMD/SIMT 技术,以及硬件上的 Tensor Core 设计,共同作用于提升 AI 芯片的计算效率。

Tensor Core 是用于加速深度学习计算的关键技术,其主要功能是执行神经网络中的矩阵乘法和卷积运算。通过利用混合精度计算和张量核心操作,Tensor Core 能够在较短的时间内完成大量矩阵运算,从而显著加快神经网络模型的训练和推断过程。具体来说,Tensor Core 采用半精度(FP16)作为输入和输出,并利用全精度(FP32)进行存储中间结果计算,以确保计算精度的同时最大限度地提高计算效率。

与传统的 CUDA Core 相比,Tensor Core 在每个时钟周期能执行多达 4x4x4 的 GEMM(general matrix multiply)运算,相当于同时进行 64 个浮点乘法累加(FMA)运算。这种并行计算的方式极大地提高了计算速度,使得神经网络模型的训练和推断能够更加高效地进行。

计算原理

首先来回顾一下 Tensor Core 的计算原理,如图所示,深绿色的矩阵是一个 4x4 的矩阵 A,紫色的矩阵是一个 4x4 的矩阵 B,两个矩阵相乘再加上一个绿色的矩阵 C。所谓混合精度就是指在计算的过程当中使用 FP16 去计算,但是存储的时候使用 FP32 或者 FP16 进行存储。

Tensor Core 计算

通常在真实的数学计算时,是把矩阵 A 的一行乘以矩阵 B 的一列然后再加上矩阵 C 的单独一个元素得到 D 矩阵的一个元素,其公式如下所示。

D0,0=A0,0∗B0,0+A0,1∗B1,0+A0,2∗B2,0+A0,3∗B3,0+C0,0D0,0​=A0,0​∗B0,0​+A0,1​∗B1,0​+A0,2​∗B2,0​+A0,3​∗B3,0​+C0,0​

然而在英伟达的 GPU Tensor Core 中并不是一行一行的计算,而是整个矩阵进行计算,我们可以看看官方给出的 Tensor Core 的计算模拟图。

Tensor Core FMA

如上图所示,左边为没有 Tensor Core 的 Pascal 架构,其运行原理是一个元素跟一行进行相乘,每个时钟周期执行 4 次相乘得到一列的数据。右边则为具有 Tensor Core 的 Volta 的架构,其 Tensor Core 计算的过程是把整个矩阵 A 和矩阵 B 进行相乘然后得到一个矩阵的输出。

因此,Volta V100 GPU 的吞吐量与 Pascal P100 GPU 相比,每个 SM 的 AI 吞吐量提升 8 倍,此外得益于 Volta 架构在 SM 数量和核心设计上的优化,总体上提升 12 倍。

指令流水

指令流水是一种提高处理器执行指令效率的技术,其基本原理是将一条指令的操作分成多个细小的步骤,每个步骤由专门的电路完成。这些步骤通过流水线的方式连续执行,从而实现了指令的并行处理。

Tensor Core 模拟电路

如上图所示为 Tensor Core 的模拟电路示意图,在图中有两个不同的符号,其中一个是加号(+),表示矩阵加计算操作,一个是乘号(x),表示矩阵乘计算操作,这两个是矩阵计算中的基本操作。另外,在这个电路图中,绿色长方块代表的是计算中的寄存器,下面横着的为 16 位的寄存器,用于存储输入和中间数据,竖着的为 32 位的寄存器,用于存储累积结果或中间高精度数据。

假如我们要在 Tensor Core 里面去实现上述简单的矩阵相乘计算,即把矩阵 A 的一行乘以矩阵 B 的一列(这里先假设矩阵的长度都为 4,先忽略切分矩阵的过程)。那么在这个电路示意图里,是如何实现这个计算的呢?

实际上,在 Tensor Core 计算中,其输入为 16bit 的数据,在进行乘加计算后,每一个计算都需要有一个简单 32 位寄存器来存储中间的数据,如上图所示,可以看到这些高精度存储寄存器离实际的计算是非常近的。通过这种方式,可以简单的实现 A 矩阵的一行乘以 B 矩阵的一列。

在 GPU 的 V100 里面,它实际的计算是一个矩阵跟另外一个矩阵直接相乘得到一个新的矩阵,而上面模拟电路演示的提到的只是其中一行跟其中一列进行 FMA 得到中间一个元素的过程。

那么一个矩阵中更多元素的是如何进行计算的呢?

Tensor Core 矩阵模拟电路

上面展示的矩阵 A 的一行跟矩阵 B 的一列进行 FMA,得到一个元素 D0,0D0,0​,即图中的第一个蓝色方块,那么整个矩阵计算时的电路图则如上图所示。它是一个简单的电路拼接,就是将 A 矩阵的每一行跟 B 矩阵的每一列进行相乘就可以得到整个矩阵的每一个元素。

那这个时候,对应 A 矩阵的寄存器,就应该是一堆寄存器,对应 B 矩阵的寄存器,也应该是一堆。这些寄存器被组织成阵列形式,以便能够并行地读取和计算。矩阵中的每一个元素都可以进行 FMA 计算,从而大大提高了计算效率。

下面我们再来了解下指令流水的 Pipline 是如何组织起来的。当我们进行一个 Fp32 标量元素乘加操作的指令时,如下图所示。

Tensor Core 标量流水线

但实际上,Tensor Core 里面的乘法计算只有 Fp16,存储或者加法计算的时候是用到 Fp32 的,于是可以把刚才的一个乘法计算把它节省掉。那么如果实现两个元素相乘,就可以把两条流水并行起来,这个就是指令的流水,如下图所示。

Tensor Core 两个元素流水线

在 Tensor Core 实际计算的时候,实现输出一个元素的计算,即用 A 的一行乘以 B 的一列,就要有四条 Pipeline 的流水线。

Tensor Core 一行 x 一列流水线

如上图所示,通过上面的绿色指令流水计算出了 D0,0D0,0​,通过黄色的流水线计算出了 D0,1D0,1​,接下来想要把所有的元素计算出来,就有大量的指令的流水去拼接。

Tensor Core 矩阵流水线

如上图所示,数据在流水线中的读写操作是有一定规律的。在乘法计算阶段,需要从存储单元中读取数据进行计算;而在计算完成后的 Round 阶段,则需要将计算结果写回存储单元。因此,在流水线的某个时刻,整个过程会涉及四个数据:从寄存器读取到计算单元的两个数据,以及计算结果存储回寄存器的两个数据。

通过大量的指令流水处理,整个 Tensor Core 的运算过程得以实现。这种流水线操作的设计使得数据的读取、计算和写入能够高效地交替进行,从而充分利用硬件资源,提升计算效率。流水线技术的运用不仅使得 Tensor Core 的计算更加快速和高效,同时也为深度学习应用提供了强大的计算支持。

线程执行

在整体 CUDA 软件设计方面,其目的是与 GPU 计算和存储分层结构相匹配。英伟达 CUDA 对于 Tensor Core 的定义主要是通过 CUDA 提供一种通用的编程范式,即所谓的 General Programming。为了更高效地利用 Tensor Core,CUDA 允许开发者利用通用编程模型来调用 Tensor Core 的硬件功能,以实现高效的并行计算。

假设我们 Tensor Core 中的 D = A * B + C 计算,简化为 C = A * B。在实际应用中,由于 Tensor Core 只能处理 4x4 的简单计算,不可能直接将整个大矩阵载入 Tensor Core 中进行运算。因此,需要将矩阵切片,并将其分配到 Thread Block(线程块)中。

接着,在软件层面,会定义一个 Warp(线程束),将这些切片矩阵分配给不同的 Warp。最终,通过线程的执行,实现了对矩阵乘法的并行计算,充分利用了 Tensor Core 的计算能力。这种分块、分配和并行执行的方式有效地利用了硬件资源,提高了计算效率,从而加速了深度学习和科学计算等领域的应用。

下面我们将详细展开 Tensor Core 是如何完成大的矩阵计算的。

Block-level 矩阵乘

在 Tensor Core 中一个矩阵乘的计算,也就是所谓的 GEMM,其实一次只能计算一个小的矩阵块。在实际的运算中,也就需要把矩阵 A 切分出来一个小块,把矩阵 B 也切分出来个小块,算出来一个小的矩阵 C,如下图所示。

Block-level 的矩阵乘

那这个时候在整体的软件编程的时,就会沿着每一个维度,如上图的 N 维和 K 维进行拆分为小的矩阵进行计算,最后对结果进行累积。

for (int mb = O; mb < M; mb += Mtile)
  for (int nb = O; nb < N; nb += NtiLe)
    for(int kb = O; kb < K; kb += Ktile)
    {
      //compute Mtile-by-Ntile-by-Ktile matrix product
      for (int k = O; k < Ktile; ++k)
        for(int i= O; i< Mtile; ++i)
          for (int j= O; j< Ntile; ++j)
          {
            int row = mb + i;
            int col = nb + j;
            C[row][col] += A[row][kb + k] * B[kb + k][col];
          }
    }

如上面代码所示,我们沿每个维度将循环嵌套 Loop nest 划分为块 blocks,然后划分成为 Mtile-by-Ntile 的独立矩阵乘,最后通过累积 Mtile-by-Ntile-by-Ktile 的矩阵乘积来计算每个乘积。

在 GPU 计算时,使用 CUDA kernel grid,将 CUDA 线程块分配给输出矩阵 D 的每个分区,CUDA 线程块并行计算 Mtile-by-Ntile-by-Ktile 矩阵乘,在 K 维上进行迭代,执行累积 Mtile-by-Ntile-by-Ktile 矩阵乘的结果。可以看出这里计算主要是在线程块,也就是 Thread Block,面去进行并行计算的。

Warp-level 矩阵乘

Warp-level 的矩阵乘

在 CUDA 编程模型中,当我们在线程块(Block)内执行矩阵乘法操作时,这些操作实际上是在 Warp 级别上被分配和执行的。Warp 是 GPU 上的一个执行单元,它由固定数量的线程(通常是 32 个线程)组成,这些线程协同工作以执行相同的指令。

在进行矩阵乘法时,为了加速计算并减少内存访问延迟,通常会将矩阵 A 和矩阵 B 的部分数据加载到共享内存(Shared Memory,简称 SMEM)中。共享内存是线程块内所有线程都可以访问的一块快速内存,它允许线程之间进行数据交换和协作,而不必每次都从全局内存(Global Memory)中读取数据。

在矩阵乘法中,每个 Warp 会负责计算结果矩阵 C 的一个或多个部分。这通常通过将结果矩阵 C 的不同块分配给不同的 Warp 来实现,每个 Warp 独立地计算其分配到的部分。由于 Warp 内的线程是同步执行的,因此它们可以共同协作,使用共享内存中的数据来完成它们的计算任务。这种分配方式充分利用了 GPU 的并行计算能力,并减少了内存访问的延迟,从而提高了矩阵乘法的性能。

当执行矩阵乘法时,Warp 中的线程会协同工作,完成一系列乘法和加法操作。这个过程涉及到从共享内存(SMEM)加载数据到寄存器(RF)进行计算,然后将结果存储回寄存器或全局内存中。

Warp-level 的矩阵乘展开

下面我们详细解释一下这个过程,首先,多个线程组成一个 Warp,协同工作以处理矩阵乘法中的一部分。这些线程共同合作,通过执行一系列乘法和加法操作,能够高效地计算出结果。

其次,为了进行计算,Warp 中的线程需要从共享内存中加载矩阵 A 和 B 的片段到它们的寄存器中。这些片段是矩阵的一小部分,加载到寄存器中可以实现快速的数据访问和计算。这要求数据从共享内存到寄存器的加载速度足够快,以避免计算线程等待数据,从而保持计算的高效性。

然后,每个线程在寄存器上执行矩阵乘法操作,计算结果矩阵 C 的一个或多个元素。这些元素暂存于线程的寄存器中,直到所有必要的乘法和加法操作完成。在计算过程中,为了最大化线程的计算效率,共享内存中的数据按照特定维度(K 维)进行排序。这种排序方式有助于减少内存访问延迟,使得线程能够更高效地访问所需的数据。

Thread-level 矩阵乘

在 Tensor Core 里面并行执行的就是上述的形式,矩阵 A 乘以矩阵 B 等于 C 矩阵这么一个简单最核心操作。

Tensor Core 是英伟达 GPU 的硬件,CUDA 编程模型提供了 WMMA(Warp-level Matrix Multiply-Accumulate)API,这个 API 是专门为 Tensor Core 设计的,它允许开发者在 CUDA 程序中直接利用 Tensor Core 的硬件加速能力。通过 WMMA API,开发者可以执行矩阵乘法累积操作,并管理数据的加载、存储和同步。

在 GEMM(General Matrix Multiply,通用矩阵乘法)的软硬件分层中,数据复用是一个非常重要的概念。由于矩阵通常很大,包含大量的数据,因此有效地复用这些数据可以显著提高计算效率。在每一层中,都会通过不同的内存层次结构(如全局内存、共享内存和寄存器)来管理和复用数据。

Thread-level 的矩阵乘

具体来说,大矩阵通常被分割成小块,并存储在全局内存中。然后,在计算过程中,这些小块数据会被加载到共享内存或寄存器中,以便进行高效的计算。通过这种方式,可以最大限度地减少内存访问延迟,并提高计算吞吐量。
在计算完每一小块矩阵乘法后,得到的结果通常也是一小块数据。为了得到最终的完整矩阵乘法结果,需要将所有小块结果累积起来。这通常涉及到将中间结果从寄存器或共享内存写回到全局内存中,并在必要时进行进一步的同步和累加操作。

最终,通过一系列这样的计算和数据管理操作,可以完成整个 GEMM 计算任务,并将结果写回到输出矩阵 C 中。整个过程充分利用了 Tensor Core 的硬件加速能力和 CUDA 编程模型的灵活性,从而实现了高效的矩阵乘法计算。

累积矩阵结果

下面我们再来详细展开 Tensor Core 是如何完成累积矩阵并写出最终结果的。

Tensor Core 累积矩阵并写出最终结果

存在 register file 中的临时结果的回传是通过 WMMA 的 API 完成的,在 Tensor Core 提供的 WMMA API 里面有个 store matrix sync 的 API。这个 API 工作就是把所有的数据都搬到共享内存,也就是 SMEM 里面。

在 SMEM 里会做大量的累积的操作,把所有的数据这些数据累积起来后,再存放在全局内存里面。通过全局内存把一个块一个块的拼接起来,把数据回传写出结果。

整体计算过程

下面我们来总结一下整个的计算过程:

Tensor Core 计算全流程

首先,矩阵在进行 GEMM 计算之前会被分块,这些分块后的矩阵存储在全局内存(Global Memory)中。全局内存是 GPU 上最大的内存区域,用于存储计算过程中需要访问的大量数据。

接下来,在计算开始前,程序会将需要参与计算的矩阵分块加载到共享内存(Shared Memory)中。共享内存是每个线程块中所有线程都可以访问的低延迟内存池,它使得同一线程块中的线程能够高效地共享和重用数据。

然后,当实际执行矩阵乘法运算时,线程会将共享内存中的数据加载到其私有的寄存器(Register)中。寄存器是 GPU 上访问速度最快的内存空间,每个线程都有自己独立的寄存器文件。在 Tensor Core 中执行矩阵乘法运算时,数据会存储在 Tensor Core 的寄存器文件中,并在这里进行计算。

计算完成后,结果会写回到共享内存中,而不是继续存储在 Tensor Core 的寄存器文件中。这是因为共享内存更适合用于中间结果的存储和线程间的数据交换。在写回共享内存的过程中,通过 CUDA 的 WMMA API 提供了诸如 store matrix sync 等函数,用于确保数据的正确同步和累积。

在共享内存中进行结果累积后,这些累积的结果最终会被写回到全局内存中。这个过程可能涉及多个线程块的协作,因为整个矩阵乘法运算可能需要多个线程块共同完成。通过全局内存,不同线程块计算得到的结果块可以被拼接起来,形成最终的完整矩阵乘法结果。

小结与思考

  • Tensor Core 的计算效率:Tensor Core 通过混合精度计算,利用 FP16 进行矩阵乘法运算并用 FP32 存储中间结果,大幅提高了神经网络模型训练和推断的计算速度。

  • Tensor Core 的并行计算能力:与 CUDA Core 相比,Tensor Core 能在每个时钟周期内执行更多的 GEMM 运算,等效于同时进行 64 个浮点乘法累加(FMA)操作,显著增强了并行处理能力。

  • Tensor Core 的计算过程:涉及将大矩阵分块、数据加载到共享内存和寄存器、在 Tensor Core 中进行计算、结果回存到共享内存并最终写回全局内存,整个过程通过 WMMA API 和 CUDA 编程技术实现高效的数据管理和计算。

1 基本网络层数学模型

        输入特征图尺寸为N*H*L,卷积计算通过M个尺寸为K*K*N的3维卷积核完成,每个卷积核在输入特征图上以S为步长滑动并进行3维卷积计算,最终生成尺寸为R*C*M的输出特征图。

                                                CNN中的卷积层计算

                                                 卷积层计算伪代码

二 数据复用

1、输入数据复用

        输入数据复用对输入缓存具有最少的访问次数,其分为3个步骤:①计算核心把输入特征图读入局部输入寄存器;②计算核心充分复用这些输入数据,更新输出缓存中的所有相关的输出部分和;③更新后的输出部分和会重新写会输出缓存。当新的输入数据被读入计算核心时会重复上述3个步骤。

 2、输出数据复用

        输出数据复用对输出缓存具有最少的访问次数,其分为3个步骤:①计算核心把输入特征图的各通道读入局部的输入缓存器;②存储在计算核心输出寄存器中的输出部分和会被充分复用,以完成3为卷积通道方向上的完全累加;③最终的输出特征图会在池化之后再写入输出缓存。计算过程中不会再有其他对输出缓存的访问,对于剩余的输出特征图计算,会重复上述3个步骤。

 3、权重数据复用

        权重数据复用对权重具有最少的访问次数,其分为3个步骤:①计算核心读取Tn个输入特征图分块到局部的输入寄存器;②计算核心利用这些输入数据更新Tm个通道的输出部分和;③存储在权重缓存中的Tm个Tn通道的卷积核权重被充分复用,以更新存储在输出缓存中的Tm个通道的R*C输出部分和。重复上述3个步骤以完成整个卷积层的全部计算。

  4、混合数据复用

        该数据复用模式,将根据每一层单独分配针对该层最优的数据复用模式。因此涉及寻找最优的数据复用模式,需要探索各种分块参数下的不同模式的访存能耗。

三 并行计算

        尽管神经网络中的计算具有很强的可并行性,但是由于受到计算资源和存储资源的限制,往往不能全部同时映射到单个芯片上,因此需要设计一些调度方法将神经网络映射到计算芯片的计算阵列上依次执行。

1、并行计算

探索计算单元PE的并行性,获取高性能。

(1)像素并行性

分为卷积窗口内部并行/卷积窗口间并行

(2)输入通道并行性

(3)输出并行性

2、循环展开

        通过最大化在PE和片上缓存上的数据复用,获取高能效。

        对于某个神经网络加速,通常在有限的片外传输带宽限制下,通过高效的数据调度,驱动尽可能多的计算单元,以实现最高的有效吞吐量,同时要利用数据共享的特性,提高数据重用率,尽可能提升NPU吞吐量和能效。

(1)循环交换(Loop Interchange):优化数据复用模式,减少访存次数

(2)循环分块(Loop Tiling):优化卷积映射方法,提高计算资源利用率

为复用输入特征图,卷积核维度循环须在最内层:即要确定R/C/M/N的展开顺序

先计算输出通道循环M:要求更新权重,重利用输入特征图-时间维度复用输入像素

先计算输入通道循环N:要求更新权重和输入像素

先计算输出特征图高R和宽C:要求更新输入像素,重利用权重-时间维度复用权重

四 应用案例分析

待补充
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/azhgul/article/details/129516459

本文原标题《Implementation of CNN Hetero geneous Scheme Based on Domestic FPGA with RISC-V Soft Core CPU》,发表于“第五届IEEE国际集成电路技术与应用学术会议(ICTA 2022)”。

作者:吴海龙, 李金东, 陈翔,电子与信息工程学院,中山大学,中国

摘要:现场可编程门阵列(FPGA)具有低功耗、高性能和灵活性的特点。FPGA神经网络加速的研究正在兴起,但大多数研究都基于国外的FPGA器件。为了改善国内FPGA的现状,提出了一种新型的卷积神经网络加速器,用于配备轻量级RISC-V软核的国产FPGA(紫光同创PG2L100H)。所提出的加速器的峰值性能达到153.6 GOP/s,仅占用14K LUT(查找表)、32个DRM(专用RAM模块)和208个APM(算术处理模块)。所提出的加速器对于大多数边缘AI应用和嵌入式系统具有足够的计算能力,为国内FPGA提供了可能的AI推理加速方案。

背景

卷积神经网络在机器视觉任务中越来越流行,包括图像分类和目标检测。如何在有限的条件下充分发挥FPGA的最大性能是各研究者的主要方向。如今,大多数CCN使用外国FPGA器件。由于国内FPGA起步较晚,其相关开发工具和设备落后于其他外国制造商。因此,在国内FPGA上构建高性能CNN并替换现有成熟的异构方案是一项具有挑战性的任务。

Zhang[1]于2015年首次对卷积网络推理中的数据共享和并行性进行了深入分析和探索。Guo[2]提出的加速器在214MHz下达到了84.3 GOP/s的峰值性能。2016年,Qiu[3]更深入地探索了使用行缓冲器的加速器。本文提出了一种更高效、更通用的卷积加速器。提出的加速器峰值性能达到153.6GOP/s,仅占用14K LUT、32个DRM和208个APM。本文的章节安排如下,第2节介绍了我们提出的加速器的详细设计以及基于RISC-V的加速器实现的控制调度方案。第3节给出了实验结果。

系统设计

整个RISC-V片上系统设计如图1所示。该系统主要由RISC-V软核CPU、指令/数据存储器、总线桥、外围设备、DMA(直接存储器访问)和卷积加速器组成。

Fig. 1. 片上RISC-V系统设计图

我们的工作主要在三个方面。首先,我们使用软核CPU作为片上系统的主控,控制外设,DMA,CNN加速器来实现数据调度和操作。其次,1D(一维)加速器被设计用于改变缓冲机制。第三,为紫光同创的FPGA设备设计了一个DMA IP,用于卷积加速的应用。

A、RISC-V 软核CPU 架构

  1. 软核。使用RISC-V软核VexRiscv代替Ibex[4]构建RISC-V的片上系统和面向软件的方法可以使VexRiscv具有高度的灵活性和可扩展性。

  2. 接口。I2C和SPI等外围设备通过APB3总线连接到RISC-V软核。DMA和加速器通过PMB总线连接到RISC-V软核。

  3. 指令与数据存储。程序被交叉编译以获得一个特定的文件,该文件由JTAG烧录到片上指令/数据存储器中。

B、CNN 加速器结构

  1. 输入缓存。使用乒乓缓存来实现缓冲区,可以有效地提高吞吐量。

  2. 输出缓存。权重缓存模块由一系列分布式RAM和串行到并行单元组成。

  3. 卷积。图2中的1D卷积模块分为四组,其中包含四个1D卷曲单元。每个单元负责1D卷积的一个信道。

  4. 合并。积分模块有四组加法器树。每组加法器树将每组卷积运算单元的结果相加,得到单向输出结果。

  5. 累加。累加模块中有四组FIFO和四个加法器。加速器一次只能接收四个通道的输入特征图数据。

  6. 量化。该量化模块由乘法单元和移位单元组成。它通过比例变换将24位累加结果重新转换为8位[5]。

  7. 激活。激活功能通过查找由一系列分布式RAM组成的表来实现。它存储ReLu、Leaky ReLu和sigmoid函数的INT8函数表。

  8. 池化。确定当前卷积层是否与池化层级联,然后决定是否使用池化模块来完成池化操作。

  9. 输出缓存。输出缓冲器由FIFO而不是乒乓缓存实现。输出高速缓存FIFO将结果存储回片外存储器,作为下一卷积层的输入。

Fig. 2. CNN 加速器实现

C、DMA 结构

神经网络不仅对计算能力有很高的要求,而且对内存也有很大的需求。中低端FPGA通常需要DDR SRAM(双数据速率同步动态随机存取存储器)来承载整个神经网络和所有中间运算结果的权重。紫光同创的FPGA的DDR3内存驱动器IP为用户提供了简化AXI4总线的内存访问接口。

由于Simpled AXI和AXI之间的标准差异,需要新的DMA设计。DMA设计如下。读和写地址通道由RISC-V软核直接控制。读写数据通道的FIFO用作卷积加速器和DDR3驱动器IP的缓冲器,以完成端口转换。

D、实现细节

1、一维卷积单元阵列设计

神经网络不仅对计算能力有很高的要求,而且对内存也有很大的需求。中低端FPGA通常需要DDR SRAM(双数据速率同步动态随机存取存储器)来承载整个神经网络和所有中间运算结果的权重。紫光同创的FPGA的DDR3内存驱动器IP为用户提供了简化AXI4总线的内存访问接口。

由于Simpled AXI和AXI之间的标准差异,需要新的DMA设计。DMA设计如下。读和写地址通道由RISC-V软核直接控制。读写数据通道的FIFO用作卷积加速器和DDR3驱动器IP的缓冲器,以完成端口转换。

2、卷积加速器控制

本文提出了一种基于指令队列的设计,以减少RISC-V软核中DMA和加速器的响应延迟。RISC-V CPU可以连续发送多个存储器读写请求指令和多个操作调度控制指令,而不用等待DMA和加速器的反馈。DMA和加速器从队列中获取指令,任务完成后直接从队列中取出下一条指令,无需等待相应的CPU,从而实现低延迟调度。

Fig. 3. 1X3 一维卷积原理图

Fig. 4. 一维卷积单元硬件实现

实现结果和备注

通过在PG2L100H和X7Z020上实现相同配置的CNN加速器,完成了CNN加速器的性能测试,验证了国产FPGA CNN加速方案的可行性。加速器的资源消耗和性能如表I和表II所示。

TABLE I 资源利用

PG2L100H和X7Z020的资源消耗相似。PG2L100H需要额外的逻辑资源来构建VexRiscv CPU,而X7Z020为AXI DMA IP使用更多的逻辑资源。就加速器性能而言,可从表II中看出。由于FPGA器件架构的差异,与X7Z020相比,加速器的卷积运算在PG2L100H上只能在200MHz下实现更好的收敛。RISC-V软核只能在100MHz下实现定时收敛。

TABLE II 性能对比

我们提出了一种基于RISC-V的一维卷积运算的新设计。该加速器在国内FPGA上的实现和部署已经完成,其性能与具有相同规模硬件资源的国外FPGA相当。

本文论证了基于国产FPGA的CNN异构方案的可行性,该研究是国产FPGA应用生态中CNN加速领域的一次罕见尝试。

基于RISC-V软核CPU的国产FPGA CNN异构方案的实现|池化|fpga|cpu|驱动器|存储器|卷积_网易订阅

还可以参考硬件实现:

https://wenku.baidu.com/view/a2b2e12d15fc700abb68a98271fe910ef12daee9.html?_wkts_=1721373683647&bdQuery=CNN%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E5%8A%A0%E9%80%9F+%E7%A1%AC%E4%BB%B6%E4%B8%AD%E7%9A%84%E8%AE%A1%E7%AE%97%E8%BF%87%E7%A8%8B+%E8%8A%AF%E7%89%87

CNN卷积神经网络加速和实际在硬件中的计算过程

2维卷积操作,多通道多输出的卷积示意图


关于图像的解释说明

输入Feature Map维度 W × H × C i n W \times H \times C_{in}W×H×C 
in

 
卷积核 Kernel 维度 W K × H K × C i n × C o u t W_K\times H_K \times C_{in} \times C_{out}W 
K

 ×H 
K

 ×C 
in

 ×C 
out

 
输出Feature Map维度 W × H × C o u t W \times H \times C_{out}W×H×C 
out

 
针对输入Feature Map进行如下的重新排布


关于图像的解释说明

每一个矩形块代表该坐标位置的所有通道的数据的依次排序(举例F00即为立方体中F00位置所有通道C i n C_{in}C 
in

 个数据)
第一横行F00,F01,F02,F10,F11,F12,F20,F21,F22表示第一个卷积计算需要的数据
第二横行F01,F02,F03,F11,F12,F13,F21,F22,F23表示横向滑动后一个步长后的第二个卷积计算需要的数据
省略号表示横向滑动完毕
第四横行F10,F11,F12,F20,F21,F22,F30,F31,F32表示纵向滑动一个步长后的第一个卷积计算需要的数据,之后的数据依次类推
最后经过排布,总共有的行数为输出Feature Map的点的数量W × H W\times HW×H ,我们这里为了简单设置下输入输出的单通道内的点数都是W × H W\times HW×H
最终达到的效果使用更大的空间进行数据的重构,每一个数据被若干次的读取并存放在不同的位置,以方便后续将卷积转变为矩阵乘法
针对卷积核 Kernel 进行如下排布


关于图像的解释说明

每一个不同颜色矩形块代表在对应颜色卷积核中该坐标位置的所有通道的数据的依次排序(举例红色W00即为红色立方体卷积核中W00位置所有通道C i n C_{in}C 
in

 个数据),共有C o u t C_{out}C 
out

 个卷积核
第一列红色W00,W01,W02,W10,W11,W12,W20,W21,W22表示第一个卷积核的权重
第二列黄色W00,W01,W02,W10,W11,W12,W20,W21,W22表示第二个卷积核的权重
省略号表示所有卷积核数据的依次排布
卷积核的数据没有空间倍增,虽然卷积核是滑动的一方,但是实际数据是没有变化的,所以针对所有的数据都是使用相同的权重参数进行计算,无序使用空间倍增换时间
原始卷积对应的操作


输出Feature Map 的第一个通道的第一个点的计算过程(∗ *∗代表所有通道的对应相乘,这里暂不展开)
F 00 ∗ W 00 + F 01 ∗ W 01 + F 02 ∗ W 02 + F 10 ∗ W 10 + F 11 ∗ W 11 + F 12 ∗ W 12 + F 20 ∗ W 20 + F 21 ∗ W 21 + F 22 ∗ W 22 F00*W00+F01*W01+F02*W02\\+F10*W10+F11*W11+F12*W12\\+F20*W20+F21*W21+F22*W22
F00∗W00+F01∗W01+F02∗W02
+F10∗W10+F11∗W11+F12∗W12
+F20∗W20+F21∗W21+F22∗W22

同理,第一个通道第二个点的计算过程,注意W WW的坐标是不变的,只需要改变对应的F FF的坐标即可
F 01 ∗ W 00 + F 02 ∗ W 01 + F 03 ∗ W 02 + F 11 ∗ W 10 + F 12 ∗ W 11 + F 13 ∗ W 12 + F 21 ∗ W 20 + F 22 ∗ W 21 + F 23 ∗ W 22 F01*W00+F02*W01+F03*W02\\+F11*W10+F12*W11+F13*W12\\+F21*W20+F22*W21+F23*W22
F01∗W00+F02∗W01+F03∗W02
+F11∗W10+F12∗W11+F13∗W12
+F21∗W20+F22∗W21+F23∗W22

再同理,第一个通道第二行第一个点的计算过程,注意W WW的坐标是不变的,只需要改变对应的F FF的坐标即可
F 10 ∗ W 00 + F 11 ∗ W 01 + F 12 ∗ W 02 + F 20 ∗ W 10 + F 21 ∗ W 11 + F 22 ∗ W 12 + F 30 ∗ W 20 + F 31 ∗ W 21 + F 32 ∗ W 22 F10*W00+F11*W01+F12*W02\\+F20*W10+F21*W11+F22*W12\\+F30*W20+F31*W21+F32*W22
F10∗W00+F11∗W01+F12∗W02
+F20∗W10+F21∗W11+F22∗W12
+F30∗W20+F31∗W21+F32∗W22

不同卷积核之间的操作类似

卷积转换为乘法


在此矩阵乘法中

左矩阵所有的数据和右矩阵中的第一列做乘法,共有W × H W\times HW×H个输出点,即可得出输出Feature Map第一个通道的所有对应输出
同理左矩阵所有数据和右矩阵第二列做乘法,就会生成后面输出Feature Map的后一个通道
以此类推卷积就会转换为矩阵乘法,核心思想就是空间换时间
参考文献
矩阵卷积、矩阵相乘的转化_anan1205的博客-CSDN博客_卷积矩阵转化为矩阵相乘

CNN之卷积加速_圣空老宅的博客-CSDN博客
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/qq_41554005/article/details/127504810

随着深度学习的飞速发展,对处理器的性能要求也变得越来越高,随之涌现出了很多针对神经网络加速设计的AI芯片。卷积计算是神经网络中最重要的一类计算,本文分析了高性能卷积计算中的数据复用,这是AI芯片设计中需要优化的重点之一,具体思路如下

  • 数据复用的动机
  • 存储-计算分离框架下,针对卷积计算的优化思路
  • 针对卷积计算的硬件架构设计分析
  • 已经面临的挑战和解决方向
  • 神经网络中数据复用的未来

1. 高性能卷积计算中数据复用的动机

深度学习的发展过程中,较高的计算量是制约其应用的因素之一。卷积神经网络中,主要计算为三维的卷积计算(后简称为卷积),现有的主流处理器难以高性能,高效能的完成卷积计算。相比一般的通用计算,卷积计算中存在的大量数据复用以及计算的规则性,在硬件的微架构(后简称为架构)设计和计算优化上有很大的优化空间,由此诞生了众多针对深度学习加速的AI芯片。卷积计算过程可以表示如下

for i = 1 : Ho
    for j = 1 : Wo
        for k = 1 : Co
            for l = 1 : Hf
                for m = 1 : Wf
                    for n = 1 : Ci
                        out[i,j,k] += In[i*s+l.j*s+m,n]*F[l,m,n];

其中各参数的含义如下表

数据维度描述
Ho/Wo输出feature map的高和宽
Co输出的channel数目
Hf/Wffilter的高和宽
Ci输入的channel数目
s卷积计算的stride

据此可推算出输入输出数据的数据复用关系,如下表

TypeMem RefsOpsOps/Memory
FilterHfWfCiCo𝐻𝑓𝑊𝑓𝐶𝑖𝐶𝑜HoWoCoHfWfCi𝐻𝑜𝑊𝑜𝐶𝑜𝐻𝑓𝑊𝑓𝐶𝑖HoWo𝐻𝑜𝑊𝑜
InputHoWoCis2𝐻𝑜𝑊𝑜𝐶𝑖𝑠2HoWoCoHfWfCi𝐻𝑜𝑊𝑜𝐶𝑜𝐻𝑓𝑊𝑓𝐶𝑖HfWfCo/s2𝐻𝑓𝑊𝑓𝐶𝑜/𝑠2
OutputHoWoCo𝐻𝑜𝑊𝑜𝐶𝑜HoWoCoHfWfCi𝐻𝑜𝑊𝑜𝐶𝑜𝐻𝑓𝑊𝑓𝐶𝑖HfWfCi𝐻𝑓𝑊𝑓𝐶𝑖

可以看出,卷积计算过程中对Filter,Input和Output的数据均有很高的数据复用;充分利用计算过程中的数据复用是达到高性能和高效能的关键,这主要有以下几个方面原因

  1. 利用数据复用更容易达到设计的峰值性能
  2. 利用数据复用可以降低内存访问,降低功耗
  3. 广义来看,卷积中规则的数据复用,可以降低处理器设计中缓存/控制逻辑的设计复杂度,提高总体的性能

根据Roofline模型很容易解释第一点原因。Roofline模型是一种面向吞吐量的性能评价模型,它指出在理想情况下,处理器性能的理论上界。如下图(Ref. CS217_Lec6)所示,在数据复用不高的情况下,峰值性能受限于内存(Memory-bound)。

访问不同存储的归一化能耗比(Ref. 6)可以解释第二点原因。充分利用数据复用,使得访存多发生在RF等靠近ALU的存储上,可以极大降低功耗。

针对第三点,以AMD ZEN系列CPU为例,Die的结构如下图所示(Ref. Zen - Microarchitectures - AMD - WikiChip)。可以看出FPU和ALU只占CPU面积的很小一部分。大部分面积是控制逻辑的缓存。这意味着如果针对卷积计算这种规则的,具有时间和空间强相关性的,且没有分支跳转的计算设计专门的硬件,可以抛开专用处理器中复杂的控制和缓存设计,减小芯片面积,提升性能,降低功耗。

这些特性表明,卷积计算有很大的优化空间;专门针对卷积计算设计的芯片能极大的提高性能和效能 。

2. 高性能卷积的计算方法

2.1 卷积即矩阵乘法

矩阵-矩阵乘法的应用已经非常广泛,很多线性代数库充分结合了矩阵计算中的数据复用关系和处理器缓存的层次设计结构,对矩阵-矩阵乘法进行了充分的优化(GEMM,通用矩阵乘法)。C=AB+C𝐶=𝐴𝐵+𝐶的矩阵乘法可以表示为

for i = 1 : m
    for j = 1 : n
        for k = 1 : t
            C(i,j) = A(i,k)*B(k,j) + C(i,j)

这一形式和第1节中卷积的计算方式极其类似。实际上,针对三维的Tensor进行展开(即将卷积计算中的六层循环的内三层进行合并),很容易将卷积计算转化为矩阵乘法计算(im2col)。 其中一种展开方式如下图 (Ref. CS217_Lec9

下图也给出了一个具体的例子(Ref. 在 Caffe 中如何计算卷积?,原始链接High Performance Convolutional Neural Networks forDocument Processing)

此时,卷积计算等价为矩阵乘法计算;而卷积计算中的数据复用关系等价为了矩阵计算中的数据复用关系。矩阵-矩阵乘的复杂度如下所示

Ex.Mem RefsOpsOps/Memory
Matrix-Matrix Mult4n24𝑛22n32𝑛3n/2𝑛/2

现有的处理器架构从存储和计算之间的关系上来看都是类似的,处理器计算性能的上限可以通过计算单元及其运行频率确定。为了高性能的完成矩阵计算,即使得性能达到Roofline模型中峰值,需要对矩阵计算的三层循环进行优化。在进行分析之前,需要明确两点固有的特性

  • 处理器的存储是分层设计的,越靠近计算的单元,带宽越大,容量越小
  • Roofline模型和访存的带宽有关,如下图所示

如果矩阵存储在内存中(DDR),直接按三层循环进行矩阵计算,那么每个周期需要访问a,b,c𝑎,𝑏,𝑐三个数,此时性能会受限于DDR带宽。将矩阵放在片上的Cache中是一个好的选择,但是片上Cache的容量往往较小。解决这一个矛盾的方法是对大的矩阵进行分块,此时矩阵计算可以表示为

for i = 1 : MR : m  // step MR
    for j = 1 : NR : n  // step NR
        for k = 1 : KC : c  // step KC
        // block-dot product in cache
        // Csub_{NR*MR} += Asub_{MR*KC}*B_sub(KC*NR)
        // opt. mirco kernel
            for ir = 1:MR
                for jr = 1:NR
                    for kr = 1: KC
                        Csub(ir,jr) += Asub(ir,kr)*Bsub(kr,jr);

采用这一分块方式,假设矩阵存储在DDR,分块后的矩阵存储在片上Cache;完成MR×NR×NC𝑀𝑅×𝑁𝑅×𝑁𝐶次乘法计算只需要从DDR获取MR×NR+NR×NC+MR×NC𝑀𝑅×𝑁𝑅+𝑁𝑅×𝑁𝐶+𝑀𝑅×𝑁𝐶 个数据,合理的分块能降低对DDR的访问。
针对具有不同存储层次的处理器以及不同大小的矩阵计算而言,有不同的分块方式以达到峰值性能。虽然这一方法针对矩阵计算进行了很好的优化,但对于卷积计算而言,依旧存在缺陷

  • im2col计算过程中针对Tensor进行了Reshape和Duplicate,这会增加带宽的消耗
  • 转化得到的矩阵乘法和传统的矩阵计算维度相差较大,可能无法获得好的性能

由于这些缺陷存在,如果没有针对硬件架构进行特殊的设计,卷积即矩阵乘法的设计思路往往无法达到峰值性能。

2.2 卷积即卷积

采用矩阵-矩阵乘法进行卷积计算忽略了卷积本身的特性。卷积计算实际上是一类很特殊的计算,以一维的离散卷积为例,函数f,g𝑓,𝑔之间的卷积可表示为

(f∗g)[n]=∞∑m=−∞f[m]g[n−m](𝑓∗𝑔)[𝑛]=∑𝑚=−∞∞𝑓[𝑚]𝑔[𝑛−𝑚]

以有限长脉冲响应(Finite impulse response,FIR)滤波器作为离散卷积的一维特例,可表示为

y[n]=N∑i=0bi⋅x[n−i]𝑦[𝑛]=∑𝑖=0𝑁𝑏𝑖⋅𝑥[𝑛−𝑖]

一维的脉动阵列FIR滤波器的一种实现方式,其实现结构如下图(Ref. Wikipedia),采用这种结构,每输入一个x[n]𝑥[𝑛],就能计算得到一个y[n]𝑦[𝑛]。这种特殊的数据复用是根据卷积计算的特殊性质得到的。

在卷积神经网络中,卷积层的计算一般也存在这种数据复用关系(除kernelsize <= stide情况外)。如果能用好这一特性,不再将卷积转化为矩阵乘法,直接计算卷积,能获得更高的性能。为了达到这一目标,和分析矩阵乘法类似,对卷积的六层循环进行分析

for i = 1 : Ho
    for j = 1 : Wo
        for k = 1 : Co
            for l = 1 : Hf
                for m = 1 : Wf
                    for n = 1 : Ci
                        out[i,j,k] += In[i*s+l.j*s+m,n]*F[l,m,n];

类似GEMM,最内层循环会被并行化,为了达到更好的性能,可以对循环顺序进行调整(Ref.9)

  • 为了提高并行性,由于Co循环的各个计算之间相互独立,将其放置到最内层循环
  • 由于Wf,Hf可能会很小,为了保证并行性,将Wo放到Co循环外

完成调整之后,对卷积计算进行分块(Ref. 9)

为了进一步提高访存的效率,Input/Ouput/Filter在内存中的排布方式也需要进行相应的调整,可参见参考文献9,此处不再描述。

无论是将卷积转化为矩阵计算,或者直接计算卷积,都能够通过分块的方式相对高效的完成卷积计算;由于将卷积转化为矩阵计算有一定开销,其性能可能会受到一定影响,但矩阵计算具有更高的灵活性。

3. 高性能卷积的架构设计

3.1 GEMM加速器

显然,实现一个硬的GEMM加速器可以加速矩阵计算,进而加速卷积计算。针对GEMM的分块矩阵(即内层循环)进行硬件加速是一种简单,高效且相对灵活的选择,其优势包括

  • 简单,计算单元实现GEMM的mirco kernel即可,易于集成
  • 灵活,支持所有可用GEMM加速的算法(理论上)
  • 编译器设计和调度有很多参考实现

类似设计包括Nvidia的Volta架构,其加速核心被称为Tensor Core;以及华为的达芬奇架构(Davinci Core),其卷积的计算核心被称为CUBE Core。

其中,Nvidia的每个Tensor Core是一个4×4×44×4×4 的MAC阵列。计算C=AB+C𝐶=𝐴𝐵+𝐶 时,矩阵A,B𝐴,𝐵 中的元素广播到4个不同的MAC上,同时每个四个乘法的结构累加到一起,如下图所示(Ref.Volta Tensor Core GPU Achieves New AI Performance Milestones

达芬奇中的CUBE Core是一个16×16×1616×16×16的MAC阵列(以Davinci Max为例),如下图所示(hotchips31),具有更高的数据复用关系。

Nvidia Volta架构中,Tensor Core仅仅只是一个特殊的计算单元,其地位和FP计算单元一致。

为了保证计算单元之外设计的统一性,送入计算单元的数据位宽完全一致,和普通的FP32计算单元比,Tensor Core在算力上有很大的优势。由下表可以看出,由于数据复用和精度的降低,Tesnor Core的理论性能是FP32的8倍(同频,其中两倍受益于精度的降低)。

Ex.DataWidthData NumOps
FP32256816
Tensor Core25616128

Davinci Core进行了更多的设计,配合完成高性能的卷积计算。MTE中的img2col表明其进行了3D Tensor到Matrix的转换。(Ref. 华为在hotchips详细介绍了达芬奇架构)

前文提到“卷积即矩阵乘法的设计思路无法达到峰值性能”,但有了硬件架构的联合设计,这一结论不再成立。譬如在Davinci Core中,在L0 Buffer进行Img2col可以降低由于im2col增加的访存带宽,合理设计的L0 BufferA/B/C也能应对卷积操作中大量的中间结果。

3.2 脉动阵列

脉动阵列的典型代表是Google TPU,Google TPU中设计的脉动阵列也是针对矩阵乘法设计(虽然有的脉动阵列也可直接计算卷积,但TPU并没有采用这一类设计)。之前的文章有对Google TPU进行细致的分析(动手写一个简单版的谷歌TPU),并实现了一个小规模的基于VLIW+Vector Architecture的TPU(SimpleTPU,VLIW+向量体系结构),可以作为进一步参考。Google TPU的其计算核心为一个256×256256×256的二维脉动阵列,如下图所示(Ref. 4)

另一个类似的设计时Telsa(特斯拉)的Full self-driving(FSD) computer,其内部的Nerual Network Accelerator(NNA)也实现了一个大规模的矩阵乘法器(NNA也可能时直接采用广播的形式,而非脉动形式进行输入)。参考其专利中的说明,其具体实现和Google TPU略有差异。

Telsa FSD NNA Diagram

根据描述,FSD NNA中的Wieght和Activation均通过DATA FORMATTER后送入到MATRIX PROCESSOR中。乘法的结果在MATRIX PROCESSOR中进行累加,计算出最终结果后依次向下移位输出。FSD中有两个NNA,其中MATRIX PROCESSOR的大小均为96×9696×96,和TPU1相比,FSD的主要差异表现在

  • 计算过程中,TPU的weight保持不变,而FSD的partial sum保持不变
  • TPU中的weight保持在DRAM中(片外),FSD保留在SRAM中
  • 由于FSD的weight存储在片上,为了满足输入的数据排布要求,增加了在线的Weight Formatter模块
  • FSD的输入宽度为96,是3的倍数,这是应为在Data Formatter时对kernel进行了展开(3×33×3的kernel中有因子3);而TPU1的宽度为256是2的幂次,kernel的展开很有可能是时间上隐式进行的

针对TPU和FSD这种在数据流中的差异,可以分为Weight Stationary,Output Stationary和No Local Reuse等(Ref. 6)。这种针对Dataflow的分类方法也涉及到数据复用,但主要针对Register File上的复用进行分析,和本文针对计算中存在的数据复用关系进行分析有所不同(一个典型的差别是Ref.6中归类为no local reuse的情况在本文认为是有数据复用的)。

3.3 直接卷积加速器

Eyeriss是一种直接针对卷积计算优化的加速器,和其他加速器不同,Eyeriss针对Convolution Reuse进行了优化。按Ref.6的分类标准,是一种Row Stationary。(Ref. 7)

由于卷积神经网络计算卷积时C方向的特殊性,Convolution Reuse仅在H和W方向存在。以kernelsiz=3,stride=1为例,卷积计算中row方向的数据复用如下图(Ref. 7)

此时ifmap中的元素3被利用了三次,只需要从存储中访问一次元素3,就能完成3次计算。当扩展为二维时,有(Ref. 7)

即ifmap的row方向在不同PE之间进行复用;而实际上ifmap的col方向的数据会暂存在PE内的RF上,col方向的数据也在RF上进行复用。

Eyeriss利用了convolution resue,同时也可以利用其他resue的手段。譬如上图中的filter在水平PE之间复用,psum在垂直的若干个PE之间复用。这意味这Eyeriss相比其他结构做到了更多的数据复用,可以进一步降低功耗。

3.4 基于变化域的卷积计算

一维卷积的计算复杂度为O(n2)𝑂(𝑛2),由于时域卷积等于频域相乘,对于离散信号而言,可以通过快速傅里叶变换(Fast Fourier Transform,FFT)及其逆变换将信号在时域和频域之间变换。而FFT的计算复杂度为O(nlogn)𝑂(𝑛𝑙𝑜𝑔𝑛),当n𝑛取值较大时,其计算复杂度会远低于直接计算一维卷积。

类似的,可以考虑在其他域进行二维/三维卷积的计算;针对卷积神经网络中的加速,有

但若想取得一定的加速比,这些方法对卷积核的大小和步长均有要求;这一类方法均难以适应卷积核日益小型化的发展趋势。

4. 轻量化网络带来的新的设计挑战

当大多数AI芯片中的神经网络加速器还在使用AlexNet/VGG/ResNet跑benchmark时,新的网络层出不穷。一些为了运行在嵌入式设备上而设计的轻量化网络通过压缩卷积中各个维度的计算来降低计算量,这很大程度影响了卷积计算中的数据复用关系。考虑在轻量化网络中使用的Point Wise Conv和Depth Wise Conv,以及延时受限系统中Batch=1的全连接层,有下表

TypeOps/Memory
Point-Wise Convolution LayerInput: Co/s2𝐶𝑜/𝑠2
Filter: HoWo𝐻𝑜𝑊𝑜
Output:Ci𝐶𝑖
Depth-Wise Convolution LayerInput: HfWf/s2𝐻𝑓𝑊𝑓/𝑠2
Filter: HoWo𝐻𝑜𝑊𝑜
Output:HfWf𝐻𝑓𝑊𝑓
Batch=1 Fully Connect LayerInput: Co𝐶𝑜
Filter: 11
Output:Ci𝐶𝑖

而第三节中一些典型的基于数据复用的加速其设计中,若想达到最优性能,对数据复用的要求是

ArchitecturesInput1Input2Output
16×16×1616×16×16 Cube Core161616
256×256256×256 Systolic Array2561350256
Eyeriss V1(Theo. low bound)1684242

数据复用关系的失配会让这些加速器在运行这些特定的Layer时出现严重的效率问题。譬如TPU V1在计算3×33×3 Depthwise Conv时效率不可能超过9/256=3.5%9/256=3.5%。这样低的效率使得这些设计良好的网络带来的数量级的计算量下降变得毫无意义。

从根源上看,Cube Core或者Systolic Array的优化目标都是Matrix-Matrix乘法,而Batch=1得FC和Depthwise Conv更贴近于Matrix-Vector乘法,这在本质上就是不同的。即使在软件层面的优化上,这两个运算也是分别进行优化的(GEMM和GEMV)。

而对于芯片设计而言,已经面临的挑战是如何设计一个在多变的复用关系下均能保证较高效率的神经网络加速器。完成这一目标,至少有两种不同的设计方向

  • 具有灵活互联结构的可重构加速器
  • 标量+矢量+向量的异构加速器

4.1 具有灵活互联结构的可重构加速器

上述的几乎所有的讨论和设计,都能归结到降低数据带宽上。一旦数据带宽降低后,其灵活性就受到了很大的限制,这是无法计算GEMV的原因之一。如果需要加速器能够在各种情况下有良好的表现,最直接的解决方案就是在设计上提供高的带宽和灵活性。
Eyeriss V2就采用这种思路对Eyeriss V1进行改进,以高效支持MobileNet。为了提供高带宽,Eyeriss设计了层次化2D Mesh的Noc(Ref. 8)

设计中一个PE Cluster具有12个PE,12个PE之间互相连接;PE Cluster通过一个2D Mesh进行互联。Global Buffer到PE之间具有丰富的连线,在不同计算模式下可以以Multicast,broadcast和unicast进行数据传输,充分满足不同计算下的数据需求。

尽管Eyeriss V2中并没有提到可重构,但其Noc在计算不同网络时有不同的选通路径,和可重构的思想一致。一般谈到硬件可重构,一般会想到FPGA(Field-Programmable Gate Array,现场可编程大规模逻辑门阵列)。通过FPGA片上丰富的的互联结构和查找表,FPGA理论上可以用于实现各种形式的电路。

FPGA可以创造出各种可能,但FPGA设计上有很多冗余,在布局布线和加载上也会花费较多时间。究其本因,是因为FPGA采用了很底层的基本单元来构建整个电路。与FPGA这种细粒度可重构相对应的是粗粒度可重构网络,粗细度可重构不像FPGA一样可以控制每一个bit的选通,而是提供了一系列基本的计算单元和互联结构。根据算法的需求,可以采用不同的方式构建这些计算单元。譬如Plasticine中的PCU中,每个FU由前向,反馈等多种路径,可以构建不同的计算单元。(Ref. 11)

多个PCU之间通过交换网络和其他PCU及PMU(Memory)相连,可以配合完成不同的操作。这些都以增加片上各个单元之间的互联性为基础。(Ref. 11)

采用可重构设计的还有清华大学微电子系设计的Thinker芯片,具体可参考A High Energy Efficient Reconfigurable Hybrid Neural Network Processor for Deep Learning Applications(Ref11)。这样的具有灵活互联结构的可重构加速器,可以支持矩阵-矩阵乘法,矩阵-向量乘法以及更多的其他计算,具有较强的灵活性。

4.2 标量+矢量+向量的异构加速器

另一种思路是对某些运算做到极致的支持,其他的运算通过CPU或者其他的Core来计算;从华为达芬奇架构,到Google TPU2/3的设计上,都有的体现,华为的达芬奇架构可参见3.1节,Google V2/3的框图如下

Google的框图并没有透露太多的细节,仅仅表面TPU中由于MXU和scalar/vector units;华为的达芬奇架构则指出了

  • Vector:2048bit vector with special functions(activation functions,NMS,ROI,SORT)
  • Cube:4096 FP16 MACs

虽然Vector Unit似乎并不能计算FC Layer或者Depthwise Conv Layer,但这也代表了一种设计方向。当采用3D Matrix Unit进行矩阵计算时,随着N𝑁取值的增加,数据输入带宽随N2𝑁2增长,而MACs的数量随N3𝑁3增长,这表面针对特定的计算进行优化结果可能会更优。当然,这样的设计也有一些缺陷

  • 不同计算单元之间负载可能不均衡,导致计算出现瓶颈
  • 不同计算单元之间的数据传输,调度会变得复杂

当然,这种设计思想并不意味一个加速Core只能加速很受限的计算类型,依旧可以对单个的加速Core进行兼容设计,本节的讨论和4.1并不完全冲突。譬如依旧有一些简单方法可以让一个三维的MAC阵列(类似Cube Core)同时支持GEMM和GEMV操作,但依旧会有一些限制,由于数据复用的不同,这两类运算始终落在Roofline模型的不同位置。

但不管从哪个角度去解决面临的新的挑战,暂时都没有看到一个完美的解决方案。或许我们并不需要一个真正完美的解决方案,能解决一部分问题,创造价值就已经足够了。这里引用Cloud TPU(TPU V2/3)的一些说明

Cloud TPU适合以下工作负载

  • 由矩阵计算主导的模型
  • 主循环内没有自定义TensorFlow操作的模型
  • 需要训练数周或数月的模型
  • 有效批量非常大的大型和极大型模型

Cloud TPU不适合以下工作负载

  • 需要频繁分支或逐项代数主导的现有代数程序
  • 稀疏方式访问内存的工作负载可能不适用于TPU
  • 需要高精度算法的工作负载

显然,Cloud TPU聚焦在大型和极大型模型的加速上,轻量化模型不在考虑范围之内;Cloud TPU甚至明确指出了暂不支持Depthwise Convolution计算。但这些都不影响Cloud TPU的应用和创造价值。

而正因为没有完美的设计,选择和取舍会变得尤为重要。

5. 神经网络中数据复用的展望

虽然在AI芯片设计过程中,都针对算法进行了深度的优化;同时算法也针对芯片实现进行了定点化和低比特量化等工作:这些看似是一个联合优化的过程。但是归根到底,既然是选择为AI应用设计芯片,在设计过程中,算法永远占主导地位。从长远来看,还需要把握算法的发展趋势。

站在硬件设计的角度来看,由于在计算机诞生之初就有了高性能并行计算的需求,很多设计实际上以及出现了很长时间,譬如数据并行的向量体系结构,VLIW,脉动阵列,稀疏计算都有40年以上的历史,2D Mesh互联也至少有30年。从设计上看,新的东西并不是很多,架构成功的关键在于是否顺应了时代的潮流,包括算法/需求的发展和底层硬件技术的发展。

在发展过程中算法和现有的硬件上的分歧往往是存在的,以本文讨论的卷积计算的数据复用为例,这和网络稀疏化的发展方向相悖。如果以极度稀疏化为目标,那么网络计算过程中的数据复用会越来越低。

神经网络中数据复用的未来如何,完全取决于算法的发展。

参考

  1. Hardware Accelerators for Machine Learning (CS 217)
  2. volta-architecture-whitepaper.pdf
  3. Scalable unified architecture for Neural Network computing (Hotchips 31)
  4. In-Datacenter Performance Analysis of a Tensor Processing Unit
  5. ACCELERATED MATHEMATICAL ENGINE(Telsa,US20190026078)
  6. V. Sze, T.-J. Yang, Y.-H. Chen, J. Emer, "Efficient Processing of Deep Neural Networks: A Tutorial and Survey," Proceedings of the IEEE, vol. 105, no. 12, pp. 2295-2329, December 2017
  7. Y.-H. Chen, T. Krishna, J. Emer, V. Sze, "Eyeriss: An Energy-Efficient Reconfigurable Accelerator for Deep Convolutional Neural Networks," IEEE Journal of Solid State Circuits (JSSC), ISSCC Special Issue, Vol. 52, No. 1, pp. 127-138, January 2017.
  8. Y.-H. Chen, T.-J Yang, J. Emer, V. Sze, "Eyeriss v2: A Flexible Accelerator for Emerging Deep Neural Networks on Mobile Devices," to appear in IEEE Journal on Emerging and Selected Topics in Circuits and Systems (JETCAS), June 2019.
  9. Zhang J , Franchetti F , Low T M . High Performance Zero-Memory Overhead Direct Convolutions[J]. 2018.
  10. 华为在hotchips详细介绍了达芬奇架构
  11. Prabhakar R, Zhang Y, Koeplinger D, et al. Plasticine: A Reconfigurable Architecture For Parallel Paterns[C]// Acm/ieee International Symposium on Computer Architecture. 2017.
  12. A High Energy Efficient Reconfigurable Hybrid Neural Network Processor for Deep Learning Applications[J]. IEEE Journal of Solid-State Circuits, 2018, 53(4):968-982.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值