这是新的系列教程,在本教程中,我们将介绍使用 FPGA 实现深度学习的技术,深度学习是近年来人工智能领域的热门话题。
在本教程中,旨在加深对深度学习和 FPGA 的理解。
用 C/C++ 编写深度学习推理代码
高级综合 (HLS) 将 C/C++ 代码转换为硬件描述语言
FPGA 运行验证
在这最后一篇文章中,将描述在推断更大的网络时如何解决计算复杂性增加的问题的常用策略。
低计算成本技术
首先,我们将讨论如何降低计算成本本身。
量化
量化是权重或激活(每层的输入和输出)中比特的减少,通常在 fp32 中构建。众所周知,深度学习在推理过程中能够以比训练过程更低的位精度进行处理,尽管这取决于模型,但即使是 8 位定点数和位数更少的定点数也具有实用的精度。FPGA 与 1 位左右的低精度网络特别兼容,因为可以使用 LUT 将卷积运算替换为查找表。
修剪
修剪是在卷积层等使用的权重矩阵中,稀疏化(移至 0)足够接近 0 的值的过程。足够接近 0 的系数对卷积运算的最终结果影响很小,因此将其设置为 0 不会显着影响推理结果。在实践中,我们会设置剪枝的阈值等参数,给出测试模式,检查允许的误差范围。
修剪主要应用于两个粒度。
1、粗粒:每通道
2、细粒度:单位因子
1 的粗粒度修剪只是简单地删除了通道,因此可以在不特别注意计算硬件的情况下提高速度。另一方面,2的细粒度修剪只会增加矩阵内部0元素的数量,同时保持矩阵的大小不变。
在这里我们将限制在这个级别,但是还有其他方法可以减少计算量,例如拓扑调整可以减少模型本身的计算量。
FPGA 上优化的 DNN 框架
在 GPU 上做深度学习时,无论前端选择哪种框架,后端几乎都是跑NVIDIA 优化过的cuDNN 库(https://developer.nvidia.com/cudnn)。cuDNN 库经过优化,几乎可以榨干 GPU 的峰值性能。出于这个原因,在不实现卷积等功能的情况下在后端使用这些库是很常见的。
FPGA 也是如此,例如 Xilinx 提供了一个名为Vitis-AI的推理框架,而英特尔 FPGA 提供了OpenVINO 工具包。在本节中,根据DPU Vitis-AI 中用于边缘设备
DPU
DPU是Deep Learning Processing Unit的缩写,顾名思义就是深度学习的处理器。与我们目前创建的架构不同,其中电路来处理每一层,DPU 实现了一个巨大的算术单元块,并通过在算术单元块上连续执行每一层的处理来执行推理过程。
DPU的硬件架构如下图所示。如图所示,DPU 具有类似于普通处理器的架构,例如指令调度器。
DPU只支持8bit的量化网络,其量化工具在Vitis-AI(原DNNDK)中提供。
下面我们挑选 DPU 架构中的一些有趣的点简单说一下。
数据并行度提取
在上一篇文章中,我们提取了像素之间和输出通道之间的 2 轴数据并行性以进行加速。DPU 还提取输入通道之间的数据并行性。
DPU 有几种配置,可以根据要实现的芯片大小进行更改,如下表所示。
性能最高的B4096架构共有2048个算子,像素并行度8,输入通道方向16个,输出通道方向16个。虽然有 2048 个运算单元,但总共是 4096 次运算/时钟,因为每个运算单元同时执行乘法和加法。
上次创建的架构中,运算次数最多的卷积层只有4*8=32个运算单元,两个卷积层加起来就有32+16=48个单元,性能简直快了近40倍,区别蛮大的。
用于 DSP 的 DDR(双倍数据速率)
在 DPU 中,通过仅以双倍工作频率运行 DSP 来提高性能,如下图所示。每个周期可能的操作数翻了一番,从而使 DSP 的使用量减半。
DPU方面主要针对Zynq Ultrascale+,工作频率为300~400 MHz。
所以DSP运行在600-800 MHz范围内,速度非常快。
特别是,这种时钟分频的优化在像这次这样用 HLS 开发时很难重现,需要在 RTL 中进行调整。
另外,在像 DPU 这样的架构中,每个周期持续向计算单元提供数据是一个问题,但我的印象是这也得到了很好的优化。这是作者的经验,但是在对1K图像进行3×3卷积时,运算单元能够在90%以上的周期内运行(当通道数是并行数的倍数时)。
由于很难创建优化到这种程度的HLS,因此在 FPGA 上实际执行深度学习时,在某些框架上执行推理会更有效。但是,我认为有些模式在现有框架上无法很好地处理,例如使用更优化的架构来切换每一层的量化位数。在这种情况下,可能需要构建自己的硬件来处理数据。
总结
感谢您阅读到这里。
在本系列教程中,我们专注于在 FPGA 上实际编写代码和执行处理。说到FPGA开发,大家可能会有这样的印象,写RTL很难,还得懂硬件。然而,就像我一开始创建的推理电路一样,如果我不关心性能,我可以将高级综合应用于普通的 C 代码并且它可以工作。此外,在随后的加速中,我们主要通过简单地添加 #pragma. 就能实现 400 倍的显着速度提升。我认为在创建DPU等优化库时仍然需要用RTL编写,但如果目的是在短时间内创建适度优化的库(像这次的HLS)如果使用它,则可以轻松开发一些应用。
此系列文章的代码可从
❝https://github.com/suisuisi/FPGAandCNN/tree/main/DnnKernelHLS
❞
获得。