3、FINN工作流入门:从ONNX模型到FPGA部署的完整流程概览

好,咱们正式开始聊FINN的工作流。

说实话,我第一次接触FINN的时候,也被它那一长串流程给唬住了。从训练好的模型,到最后在FPGA上跑起来,中间要经过七八个步骤。但你把它拆开看,其实就三件事:模型准备、编译优化、生成部署。今天我就带你走一遍这个完整流程,让你心里有个底。

3.1 整体流程长什么样?

先给你一张全景图。FINN的工作流大致是这样的:

ONNX模型 → 预处理 → 量化 → 编译 → 打包 → 部署到FPGA

嗯,看着简单吧?但每一步里面都有不少门道。我习惯把这个流程分成两个阶段:前端处理后端生成

  • 前端处理:把浮点模型转成定点模型,说白了就是量化。
  • 后端生成:把量化后的模型映射到FPGA的硬件资源上,生成比特流。

你想想看,这跟传统的软件编译流程其实很像。前端是语言无关的优化,后端是目标平台相关的代码生成。只不过FINN的后端生成的是硬件,不是指令。

3.2 第一步:模型准备与预处理

一切从ONNX模型开始。ONNX是微软牵头搞的一个开放格式,好处是主流框架都能导出。我个人习惯用PyTorch训练,然后转成ONNX。

这里有个坑,我踩过好几次——ONNX算子兼容性。FINN支持的算子有限,像Conv、ReLU、MaxPool这些没问题,但你要是用了自定义算子,就得自己写映射规则。

注意: 不是所有ONNX模型都能直接喂给FINN。建议先用 finn.core.onnx_utils 检查一下算子支持情况。

预处理阶段主要做两件事:

  1. 折叠BatchNorm:把BN层融合到Conv层里,减少计算量。
  2. 删除冗余节点:比如一些恒等映射的节点,删掉不影响精度。

我曾经接手过一个项目,模型里有一堆没用的Reshape节点,预处理后模型大小直接缩了30%。

3.3 第二步:量化——从浮点到定点

量化是FINN的核心,也是最有意思的一步。

为什么一定要量化?因为FPGA擅长做整数运算,浮点运算太费资源了。你想想看,一个浮点乘法器占的资源,够做好几个整数乘法器了。

FINN支持两种量化方式:

量化方式 位宽 适用场景
均匀量化 1-8 bit 大多数CNN模型
非均匀量化 可变 对精度敏感的场景

我个人建议,刚开始先用均匀量化,位宽从8bit开始往下试。我做过一个实验,8bit量化精度损失不到0.5%,但资源节省了4倍。

小技巧: 量化时记得做校准(calibration)。拿一小部分训练数据跑一遍,统计激活值的分布,这样量化效果更好。

3.4 第三步:编译——把计算图变成硬件描述

量化完了,接下来就是编译。这一步FINN会把计算图拆成一个个计算节点,然后映射到FPGA上的硬件模块。

编译过程大概分这几步:

  • 图优化:合并相邻的Conv+ReLU,减少数据搬运。
  • 内存规划:决定哪些数据放BRAM,哪些放DRAM。
  • 流水线划分:把计算图切成多个阶段,每个阶段对应一个硬件加速器。

我记得第一次跑编译的时候,等了快一个小时。后来发现是内存规划没做好,数据搬运太频繁。调整了一下流水线深度,编译时间降到了15分钟。

编译完成后,FINN会生成一个HLS(高层次综合)工程。说白了,就是一堆C++代码,描述了每个硬件模块的行为。

3.5 第四步:打包与部署

最后一步,把HLS工程综合成比特流,烧到FPGA上。

这一步其实跟普通的FPGA开发流程一样:

  1. 用Vivado HLS把C++代码转成RTL。
  2. 用Vivado做综合、布局布线。
  3. 生成比特流,下载到板卡。

但FINN帮你自动化了大部分工作。你只需要跑一个命令:

finn.build.build_fpga_model(model, platform="Pynq-Z1")

嗯,就这么简单。但背后发生的事情可不少。FINN会自动调用Vivado,处理时序约束,生成驱动代码。我第一次用的时候,看到终端里刷刷刷地跑Vivado,心里还挺感慨的——以前手工做这些,至少得折腾一周。

避坑指南: 我曾经因为板卡驱动没装对,折腾了两天。建议先跑一下FINN自带的测试例程,确认环境没问题再部署自己的模型。

3.6 一个完整的例子

说了这么多,不如看个实际例子。假设你有一个训练好的CNN模型,想部署到Pynq-Z1上。完整的FINN工作流是这样的:

# 1. 导入模型
import onnx
model = onnx.load("my_model.onnx")

# 2. 预处理
from finn.core.onnx_utils import fold_batchnorm
model = fold_batchnorm(model)

# 3. 量化
from finn.quantization import quantize_model
quantized_model = quantize_model(model, bit_width=4)

# 4. 编译
from finn.compile import compile_model
compiled_model = compile_model(quantized_model, target="Pynq-Z1")

# 5. 生成比特流
from finn.build import build_fpga_model
build_fpga_model(compiled_model, output_dir="./output")

你看,核心代码就这么几行。但每一步背后都有大量的优化和检查。我建议你第一次跑的时候,把日志级别调到INFO,看看每一步都干了什么。

3.7 小结

FINN的工作流,说白了就是把软件模型翻译成硬件电路。你不需要懂VHDL或Verilog,但得理解量化、流水线、内存规划这些概念。

我个人觉得,FINN最大的价值不是自动化,而是让你能快速迭代。以前改一个模型结构,得重新写RTL、重新仿真,至少一周。现在改个参数,重新跑一遍流程,半天就搞定了。

下一章,我会带你手把手搭建FINN的开发环境。到时候咱们真刀真枪地干一场。