基于FPGA的整数化CNN加速器设计:实现实时交通标志识别
1. 项目概述与核心挑战
在自动驾驶和高级驾驶辅助系统(ADAS)领域,让车辆“看懂”路标是保障行车安全的基础。交通标志识别(TSR)技术,就是通过车载摄像头捕捉图像,并实时、准确地识别出其中的交通标志。近年来,基于卷积神经网络(CNN)的视觉算法在识别精度上取得了巨大突破,但随之而来的问题是:这些模型往往计算量巨大、内存消耗高,很难直接塞进资源受限的车载嵌入式系统里。想象一下,一个需要强大GPU服务器才能流畅运行的模型,要如何在一小块车载芯片上,以每秒几十帧的速度稳定工作?这就是我们面临的现实挑战。
为了解决这个矛盾,业界普遍采用“量化”和“专用硬件加速”两条腿走路的策略。量化,简单说就是把模型从“高精度浮点数”转换成“低精度整数”来运算,能大幅减少计算和存储开销。而FPGA(现场可编程门阵列)因其高度的并行计算能力和可定制性,成为了实现实时推理的理想硬件平台。然而,传统的量化方案和硬件设计在应对复杂的网络结构,尤其是像ResNet中广泛使用的残差块时,会遇到麻烦:残差块中的“跳跃连接”路径需要进行特殊的整数对齐处理,这不仅增加了硬件设计的复杂度,还可能引入额外的内存访问瓶颈,拖慢整体速度。
我这次分享的项目,正是针对这些痛点的一次深度实践。我们设计并实现了一个低成本的、完全基于整数运算的CNN加速器,专门用于实时交通标志识别。核心思路是双管齐下:在软件层面,我们扩展了一种硬件友好的量化方法,确保包括残差块跳跃连接在内的所有操作都能用纯整数算术完成,甚至用简单的移位操作替代了复杂的量化映射过程;在硬件层面,我们提出了一个巧妙的数据流设计方案,将跳跃连接的加法操作从计算单元移到了内存模块内部,优化了数据流,减少了额外的内存占用。最终,在Xilinx ZC706这块开发板上,我们的系统在保持99.07%超高识别精度的同时,跑出了40 FPS的实时帧率和960 MOPS的计算性能。这篇文章,我将带你深入拆解从量化策略、网络结构选型到硬件架构设计的每一个关键决策和实现细节,分享我们在调优过程中踩过的坑和收获的经验。
2. 全整数化CNN的设计与实现
要让CNN在FPGA上跑得既快又省资源,第一步也是最重要的一步,就是让模型彻底“整数化”。这不仅仅是简单地把浮点参数转换成整数,更需要一套完整的、硬件友好的量化方案,确保前向推理过程中的所有运算,包括加法、乘法乃至非线性激活,都能在整数域内无损或低损地进行。
2.1 硬件友好的量化方法:从理论到实践
量化本质上是一个从高精度表示(如FP32)到低精度表示(如INT8)的映射过程。最常用的仿射量化公式是r = sf * (q - zp),其中r是实数值,q是量化后的整数值,sf是缩放因子,zp是零点。传统的量化感知训练(QAT)会同时学习sf和zp这两个参数。
然而,在硬件实现中,乘法和加法都是需要消耗逻辑资源的操作。我们的目标是消除乘法,并用移位代替。为此,我们借鉴并扩展了LLTQ这种硬件友好的量化方法。它的核心思想有两个:
- 将零点
zp固定为0:这直接消除了公式中的减法操作。虽然这理论上会损失一些动态范围,但通过精心训练,我们发现对最终精度影响微乎其微,却换来了硬件逻辑的极大简化。 - 将缩放因子
sf约束为2的幂次方:即令sf = 2^n。这样一来,乘法sf * q就变成了对整数q的移位操作q << n或q >> n。移位在硬件中几乎不消耗资源,速度极快。
在训练时,我们不是直接学习sf,而是学习一个参数α,然后通过公式sf = 2^ceil(log2(abs(α * q_level))) / q_level来计算sf,其中q_level是量化范围(如8位有符号整数的127)。这样训练出的sf自然就是2的幂次方的倒数,其对应的移位位数n就是-ceil(log2(abs(α * q_level)))。
实操心得:在实现量化训练时,一个关键的细节是直通估计器(STE)的使用。在反向传播经过四舍五入(Round)和截断(Clip)这些不可导操作时,梯度会中断。STE的作用就是假装这些操作是恒等映射,让梯度可以继续回传。在PyTorch中,我们可以用
torch.autograd.Function来自定义这些操作的向前和向后行为,在向后时直接传递梯度。
2.2 全整数卷积层与残差块的构建
量化训练完成后,我们得到了所有层的整数权重W_int、整数偏置B_int,以及每一层输入/输出的缩放因子(Sa1,Sa2等)。接下来就是重构出完整的整数化网络。
整数卷积层的结构变得非常规整,如图1所示。它主要由两部分串联:整数卷积核(ICONV)和缩放裁剪块(SC)。ICONV执行整数乘加运算(MAC)。SC块则包含一个整数缩放器(执行移位操作,对应除以sf)和一个整数裁剪器(将结果限制在INT8范围内)。这样,每一层的输入和输出都是统一的8位整数,极大简化了硬件数据通路的设计。
真正的挑战在于残差块。残差结构中的跳跃连接(将输入直接加到后续层的输出上)是保证深度网络训练稳定的关键。但在整数域,直接相加两个来自不同层的张量是行不通的,因为它们的数值可能代表着不同的“尺度”(即具有不同的缩放因子sf)。这就好比你不能直接把以“米”为单位的长度和以“厘米”为单位的长度相加。
我们对比了四种残差块结构(Type-A到Type-D),如图2所示。主要区别在于ReLU激活函数的位置和跳跃路径上是否使用1x1卷积进行维度变换。经过在GTSRB数据集上的实验,Type-B结构取得了最佳的精度(99.07%)。Type-B的特点是将主路径上最后一个卷积后的ReLU移到了跳跃加法之后。这意味着参与加法的一个是经过ReLU的非负激活值(跳跃路径),另一个是卷积输出的、可能包含负值的整数(主路径)。这种信息流的保留可能是其精度更高的原因。
为了实现Type-B残差块的整数化跳跃连接,我们引入了路径等比例缩放机制。核心思想是在加法前,将两条路径的数据通过移位操作,统一到同一个尺度上。具体步骤如下:
- 假设跳跃路径数据为
F2(缩放因子为Sa2),主路径数据为M3(缩放因子为Sa4),目标统一尺度为Sas4(即加法后输出的缩放因子)。 - 对
F2进行缩放:F2_sk = (F2 << Sa2) >> Sas4。先左移Sa2位抵消其原有缩放,再右移Sas4位对齐到目标尺度。 - 对
M3进行缩放:M3_sk = (M3 << Sa4) >> Sas4。 - 现在
F2_sk和M3_sk处于同一尺度,可以进行整数加法:M3_add = F2_sk + M3_sk。 - 加法后,需要将结果缩放回下一层输入所期望的尺度
Sa4:M3_ds = clip((M3_add << Sas4) >> Sa4, R),其中clip是裁剪操作,R是量化范围。
这个过程看似繁琐,但在硬件上,所有的缩放都只是移位操作,开销极小。图4清晰地展示了这一数据流。
注意事项:在训练量化模型时,批量归一化(BN)层的处理至关重要。BN层在训练和推理时的行为不同,且其参数(均值、方差、缩放、偏移)会增加推理时的计算量。标准的做法是在量化训练后、部署前,将BN层的参数“融合”进前一层的卷积权重和偏置中。这不仅能加速推理,还能保持数值上的完全等价。我们就是在量化感知训练之前完成了BN融合,从而得到了一个更干净、更适合硬件实现的网络图。
3. 面向实时TSR的硬件加速器架构设计
有了一个完全整数化的高效模型,下一步就是为其量身打造一个硬件加速器。我们的目标是在资源有限的FPGA上实现高帧率、低延迟的实时推理,这就需要我们在计算并行度、内存访问和数据流之间做出精妙的权衡。
3.1 整体系统架构与并行化策略
整个加速器部署在一个异构SoC(如Xilinx Zynq)上,如图5所示。PS(处理系统,即ARM Cortex-A9核)作为主机,通过AXI-Lite接口控制PL(可编程逻辑,即FPGA)上的CNN加速器逻辑。图像数据、模型参数和中间结果存放在片外DDR内存中,通过高性能的AXI接口与加速器交换。
加速器核心由两大模块构成:内部内存模块(IMM)和处理模块。处理模块进一步细分为主处理模块(MPM)和子处理模块(SPM)。
为了在资源消耗和实时性之间取得平衡,我们在MPM中采用了两种并行策略:
- 核级并行:将一个3x3卷积核的9次乘加运算完全展开,在单个时钟周期内完成。这意味着每个处理单元(PE)内都有9个乘法器和一套加法树,可以一次性消费一个输入通道上一个3x3窗口的所有数据。
- 输出通道并行:同时计算多个输出通道(即多个滤波器)。我们通过部分展开滤波器循环,在MPM中实例化多个PE来实现这一点。PE的数量是一个关键的设计参数,直接影响着吞吐量和资源占用。
这两种策略的组合,使得我们的设计能够高效利用FPGA的并行计算能力,同时避免了像输入通道并行或图像块并行等策略所带来的复杂数据依赖和内存带宽压力。
3.2 核心创新:内部内存跳跃连接路径
这是本设计中最具巧思的部分,专门用于高效处理整数残差块。回顾第2.2节,整数跳跃加法需要从内存中读取两条路径的数据,进行对齐缩放后再相加。一个直观的实现是把加法器放在SPM(负责后处理)里。但这会带来两个问题:1) 需要额外的内部缓存来暂存跳跃路径的数据;2) 在计算过程中需要访问两次外部内存(取主路径和跳跃路径数据),可能成为性能瓶颈。
我们的解决方案是:将跳跃连接加法器从处理模块移入内部内存模块(IMM)中,具体来说是放在像素内存(PXM)单元里,如图6(a)所示。我们称之为“内部内存跳跃连接路径”设计。
其工作流程如下:
- 当控制器发出
ctrl_skip信号时,表明当前层需要执行残差加法。 - PXM会同时从外部内存读取主路径和跳跃路径的中间结果。
- 在PXM内部,专用的硬件逻辑会立即执行上述的路径等比例缩放和加法操作(公式6-9)。
- 加法结果被直接存入FIFO,准备作为下一层卷积的输入。
- 对于非残差层,
ctrl_skip信号无效,PXM只读取主路径数据。
这个设计的优势非常明显:
- 节省内存:无需在PE或SPM中开辟额外的存储空间来缓存跳跃路径数据。
- 优化数据流:将两次外部内存访问合并,并提前完成加法,减轻了处理模块的负担和复杂度。
- 统一设计:使得数据从内存到处理模块的流经路径变得统一,无论是否为残差块,下一层总是从PXM的FIFO中获取处理好的输入数据,简化了控制逻辑。
3.3 处理模块的详细设计
主处理模块(MPM)是计算的引擎,其结构如图6(b)所示。每个PE包含9个乘法器和一个加法树,负责一个输出通道的部分计算。多个PE并行工作,实现输出通道并行。MPM的输出是部分和的累加结果。
子处理模块(SPM)如图6(c)所示,负责卷积之后的所有操作,形成一个流水线阶段。它包括:
- 偏置加法器:为每个输出通道加上整数偏置。
- ReLU激活:整数版本的ReLU非常简单,就是与0比较取最大值。
- 移位器与裁剪器:执行量化所需的缩放(移位)和范围裁剪(CLP)。这是实现不同层之间尺度转换的关键。
- 全局平均池化(GAP):在网络的最后一层,使用GAP模块替代CLP,将特征图空间维度降为1x1。
SPM同样根据PE数量进行了部分展开,以确保能与MPM的计算吞吐率匹配,避免成为流水线的瓶颈。
4. 硬件实现、结果分析与调优经验
理论设计和架构规划最终都需要在硬件上跑通才算数。这部分,我将分享我们的实现平台、资源利用情况、性能数据,以及在整个过程中积累下的宝贵经验和踩过的坑。
4.1 实验设置与平台
- 训练平台:我们在一台配备Intel i9-10940X CPU、128GB RAM和NVIDIA RTX 3090 GPU的工作站上进行模型训练和量化。软件框架采用PyTorch。
- 数据集:使用经典的德国交通标志识别基准(GTSRB)数据集。为了适应嵌入式场景,我们将所有图像下采样到32x32像素。虽然分辨率降低,但通过合理的网络设计,依然能保持极高精度。
- 硬件平台:目标平台是Xilinx ZC706评估板,其PL部分基于Kintex-7 FPGA。设计使用Vivado HLS 2019.2进行高层次综合,然后导入Vivado进行集成、布局布线和生成比特流。
- 网络结构:最终采用的Type-B网络结构如表1所示,包含多个残差块,每经过一个残差块,特征图空间尺寸减半,通道数增加。最终通过一个1x1卷积和全局平均池化得到分类结果。
4.2 资源、性能与能效权衡
我们探索了不同精度(FP32 vs INT8)和不同PE数量下的设计空间,结果总结在表2中。这些数据背后反映出的权衡之道,是硬件设计的关键。
- 精度的影响(INT8 vs FP32):INT8带来的收益是压倒性的。以PE4配置为例,与FP32-PE4相比,INT8-PE4在各类资源上节省了2.7到4.9倍。更重要的是,计算性能提升了2.52倍,能效提升了4.95倍。这直观地展示了量化对于嵌入式硬件的巨大价值。
- 并行度的影响(PE数量):增加PE能提升并行度,但并非线性增长,且受限于资源。对于INT8设计,从PE1到PE4,性能显著提升。但从PE4到PE8,由于受到外部内存带宽的限制,计算性能达到瓶颈,而功耗却大幅增加,导致能效下降。因此,INT8-PE4被确定为最优配置,在性能、资源和功耗之间取得了最佳平衡。
- 功耗分析:我们的设计在ZC706上估计的片上总功耗约为1.998W。需要注意的是,整个评估板系统(包括处理器、外设、风扇)的总功耗约为6.948W,这主要源于开发板本身较高的静态功耗。在实际定制化车载硬件中,这部分功耗可以大幅降低。
4.3 与同类工作的对比
我们将最终的INT8-PE4设计与近年来其他基于嵌入式平台的TSR工作进行了对比,如表3所示。
- 精度:我们的模型达到了99.07%的准确率,在对比的嵌入式实现方案中是最高的。这证明了我们的整数化方法和网络结构设计的有效性。
- 速度与性能:40 FPS的帧率和960 MOPS的计算性能,同样优于表中列出的所有其他设计,实现了真正的实时推理。
- 资源效率:与同样在FPGA上实现的工作相比,我们的设计在LUT、FF、BRAM等关键资源的使用上更为节省,展现了更高的硬件效率。
踩坑实录:内存带宽墙。在早期尝试INT8-PE8设计时,我们预期性能会比PE4再翻一番。但实际测试发现帧率几乎没有提升。通过Vivado的性能分析工具和逻辑分析仪抓取数据,我们发现瓶颈出现在AXI总线对DDR内存的访问上。当PE数量过多时,计算单元消耗数据的速度超过了内存控制器提供数据的速度,大量时间浪费在等待数据上。这个教训告诉我们,在追求计算并行度的同时,必须时刻关注内存子系统的带宽是否匹配。设计时需要仔细估算每一层的数据吞吐需求,并优化数据复用和缓存策略。
4.4 系统演示与实测
图8展示了我们加速器的实际运行演示。系统通过摄像头采集实时视频流,或者读取预存的图片序列,送入FPGA加速器进行推理,并将识别结果(标志类别和置信度)叠加显示在屏幕上。在室内光照均匀的环境下,针对GTSRB数据集中的标志,系统运行稳定,延迟极低,完全满足实时性要求。
实操心得:HLS编码技巧。为了获得最佳的综合结果,在编写C++代码用于HLS综合时,需要注意以下几点:
- 数据位宽精确指定:使用
ap_int<8>、ap_uint<8>等类型明确指定整数位宽,避免综合器推断出不必要的宽位数据路径。- 循环优化指令:合理使用
#pragma HLS PIPELINE和#pragma HLS UNROLL。对于最内层、计算密集的循环(如卷积核计算),我们使用UNROLL完全展开。对于外层的数据加载循环,我们使用PIPELINE实现流水线,以提高吞吐率。- 数组分区:对于内部存储权重或特征图的数组,使用
#pragma HLS ARRAY_PARTITION将其完全分区或循环分区,这能创建多个独立的存储块,允许并行访问,是提高并行性能的关键。- 接口协议:使用
#pragma HLS INTERFACE将顶层函数参数指定为m_axi(用于大量数据传输)或s_axilite(用于控制寄存器),并指定捆绑(bundle)以管理AXI接口数量。
5. 总结与扩展思考
回顾整个项目,我们从整数量化这个算法层面的创新出发,设计了一个与之完美匹配的FPGA硬件架构,最终在交通标志识别这个具体应用上实现了高性能、低成本的实时推理。核心的贡献在于两点:一是将硬件友好的量化方法系统性地扩展到残差网络,解决了整数域跳跃连接的技术难题;二是提出了“内部内存跳跃连接路径”这一硬件设计,优化了数据流,节省了资源。
这个设计框架并不局限于TSR。任何需要低功耗、实时运行的CNN视觉任务,如人脸识别、手势识别、工业质检等,都可以借鉴这套方法。你可以将我们训练好的整数型Type-B网络作为一个高效的骨干网络,替换掉最后的分类头,在你的数据集上进行微调量化。
未来,还有几个值得探索的方向:
- 更低的精度:尝试INT4甚至二值化网络,可以进一步压缩模型、提升能效,虽然这对精度和硬件设计会带来更大挑战。
- 动态精度:根据网络不同层对精度的敏感度,自适应地分配不同的量化位宽(如第一层和最后一层用8位,中间层用4位)。
- 自动化工具链:将我们的量化训练、BN融合、HLS代码生成等步骤打包成更自动化的流程,降低开发门槛。
- 面向更先进架构:将设计迁移到UltraScale+等更先进的FPGA平台,或者探索与ARM Cortex-M系列微控制器的协同,实现极致的能效比。
做硬件加速项目,最大的体会就是“软硬协同”的重要性。算法工程师需要理解硬件的基本约束和优势,硬件工程师也需要了解算法的核心计算模式。两者深度结合,才能碰撞出像“内部内存跳跃连接”这样巧妙的火花。希望这次详细的分享,能为你实现自己的高效边缘AI应用提供一些切实可行的思路和参考。
