4、第一个FINN项目:创建项目、导入预训练模型、运行默认编译流程、在FPGA上部署
好,咱们终于到了动手环节。
前面讲了那么多FINN的原理、架构、数据流,说实话,光听不练容易飘。今天我们就来走一遍完整的流程——从零创建一个FINN项目,导入一个现成的预训练模型,跑通默认编译,最后烧到FPGA上看看效果。
我个人习惯,每次学新工具都是先跑通一个最小demo,再回头啃细节。这样心里有底,遇到问题也知道往哪个方向查。
4.1 环境准备与项目创建
先确认你的环境已经装好了FINN。我假设你用的是官方推荐的Docker镜像,或者自己从源码编译的Python环境。
打开终端,激活你的FINN环境:
source finn_env/bin/activate
然后创建一个新目录,作为我们的项目根目录:
mkdir my_first_finn_project
cd my_first_finn_project
FINN的项目结构其实挺清晰的。我个人习惯在项目根目录下放一个build.py脚本,用来驱动整个编译流程。你也可以用Jupyter Notebook,但命令行方式更可控,适合批量实验。
4.2 导入预训练模型
FINN支持多种模型格式,最常用的是ONNX。我们这次用一个在CIFAR-10上训练好的小型CNN,叫cnv_w1a1——权重和激活都是1比特,说白了就是二值神经网络。
为什么选它?因为模型小,编译快,适合第一次跑通流程。
下载模型文件(假设你已经有了cnv_w1a1.onnx),放到项目目录下。然后我们写一个简单的Python脚本来导入它:
import finn.core.onnx_exec as oxe
from finn.core.modelwrapper import ModelWrapper
model = ModelWrapper("cnv_w1a1.onnx")
print("模型输入形状:", model.graph.input)
print("模型输出形状:", model.graph.output)
跑一下,你应该能看到输入是(1, 3, 32, 32),输出是(1, 10)。嗯,CIFAR-10的标准尺寸。
4.3 运行默认编译流程
FINN的编译流程,说白了就是一系列变换步骤。默认流程叫finn_default_build,它会自动把ONNX模型转成可部署的HLS IP核。
我们写一个build.py:
from finn.builder.build import build
build(
onnx_model="cnv_w1a1.onnx",
output_dir="output",
steps="finn_default_build"
)
然后运行:
python build.py
这个过程会持续几分钟到十几分钟,取决于你的机器性能。FINN会依次执行:
- 模型解析与优化:去掉没用的节点,合并算子
- 量化与折叠:把浮点运算转成定点,合并BatchNorm
- 流式化:把计算图转成数据流架构
- HLS代码生成:生成Vivado HLS的C++代码
- IP核打包:生成Vivado IP核
我记得第一次跑的时候,看到终端里刷刷刷的输出,心里还挺激动的。但如果你遇到报错,别慌——八成是环境问题,比如ONNX版本不匹配。
4.4 在FPGA上部署
编译完成后,output目录下会生成一个deploy文件夹。里面包含:
| 文件/文件夹 | 说明 |
|---|---|
pynq_driver.py |
PYNQ驱动脚本,用于在FPGA上加载和运行 |
bitstream.bit |
FPGA比特流文件 |
hwh文件 |
硬件描述文件,PYNQ需要它来识别IP核 |
parameters文件夹 |
模型权重和参数 |
把整个deploy文件夹拷贝到你的PYNQ开发板上(比如Zynq-7020)。然后通过Jupyter或SSH登录到板子,运行:
from pynq import Overlay
from pynq_driver import FINNDriver
overlay = Overlay("bitstream.bit")
driver = FINNDriver(overlay, "parameters/")
# 准备一张测试图片(32x32的CIFAR-10图像)
import numpy as np
input_data = np.random.randn(1, 3, 32, 32).astype(np.float32)
# 推理
output = driver.execute(input_data)
print("推理结果:", np.argmax(output))
如果一切顺利,你会看到输出一个0到9的数字,对应CIFAR-10的类别。
4.5 常见问题与调试
嗯,这里总结几个我踩过的坑:
- 比特流加载失败:检查板子型号是否匹配。Zynq-7020和Zynq-7045的比特流不通用。
- 推理结果全零:大概率是输入数据没有归一化。FINN默认输入范围是0到1,如果你传了0到255的像素值,结果肯定不对。
- 编译时间过长:默认流程会做很多优化。如果只是测试,可以跳过一些步骤,比如
steps="finn_default_build"改成steps=["step_qonnx_to_finn", "step_tidy_up"]。
说实话,第一次跑通整个流程,那种成就感是很实在的。你想想看,一个在电脑上训练的神经网络,现在跑在了一块小小的FPGA上,而且功耗只有几瓦——这就是硬件加速的魅力。
下一章我们会深入FINN的编译选项,教你如何定制自己的编译流程。到时候你会发现,默认流程只是冰山一角。