第四章:FPGA平台部署

好,咱们进入FPGA部署这一章。说实话,很多做AI的同学一听到FPGA就觉得头大——硬件描述语言、时序约束、资源利用率……听着就劝退。但我想说,别怕。FPGA部署其实没那么玄乎,尤其是现在有了HLS和Vitis AI这些工具链,门槛已经降了很多。

我个人习惯把FPGA部署分成三个层次:硬件架构理解高层次综合AI工具链使用。咱们一层层来拆。

4.1 FPGA架构基础

先聊聊FPGA到底是个什么东西。说白了,它就是一盒乐高积木。你买回来的时候,里面的逻辑单元、DSP块、BRAM都是散的,你想搭什么电路就搭什么电路。跟CPU不一样,CPU是固定好的——指令怎么取、怎么译码、怎么执行,都是焊死的。FPGA呢?你可以自己定义。

FPGA的核心部件有这么几个:

  • 逻辑单元(Logic Cells):也叫LUT+FF,用来实现组合逻辑和时序逻辑。我刚开始做的时候,总觉得这东西不够用,后来发现其实是代码写得不够优化。
  • DSP块:专门做乘加运算的硬核。做AI推理的时候,卷积运算全靠它。我记得有一次项目,DSP块用超了,只能降频跑,那叫一个难受。
  • BRAM/URAM:片上存储。速度极快,但容量有限。模型参数放不下的时候,就得往DDR里塞,延迟就上来了。
  • 可编程互联:这些乐高块之间的连线。嗯,这里要注意——布线资源有时候比逻辑资源更稀缺。

为什么会这样?因为FPGA的布线是固定的,你逻辑放得再漂亮,布不通也是白搭。我在项目中遇到过好几次,综合通过、实现报错,一看全是布线拥塞。

小技巧:做FPGA部署前,先看看目标芯片的DSP数量BRAM容量。这两个往往是瓶颈。比如Xilinx的ZCU102,DSP有2520个,BRAM有32.1Mb,心里先有个数。

4.2 HLS高层次综合

好,架构看完了,怎么编程?传统做法是写Verilog或VHDL,但说实话,那玩意儿写起来太痛苦了。一个卷积核,C语言三行搞定,Verilog能写三百行。所以就有了HLS——高层次综合。

HLS允许你用C/C++写代码,然后自动转成硬件描述。听起来很美好对吧?但我要泼一盆冷水:HLS不是魔法。你写出来的C代码如果不考虑硬件特性,综合出来的电路效率会非常低。

举个例子,我早期做过一个项目,用HLS写了一个简单的矩阵乘法。C代码跑仿真没问题,但综合出来的延迟高得离谱。后来一查,发现是循环没有做流水线优化。HLS默认是串行执行的,你得手动加#pragma HLS pipeline才能让它并行。

常用的HLS优化指令:

指令 作用 使用场景
#pragma HLS pipeline 循环流水线化 卷积、矩阵运算
#pragma HLS unroll 循环展开 小循环、固定步长
#pragma HLS array_partition 数组分块 提高BRAM读写带宽
#pragma HLS dataflow 任务级流水 多模块并行

看一段实际代码,一个简单的卷积层HLS实现:

void conv2d(
    float in[IN_SIZE][IN_SIZE][IN_CH],
    float out[OUT_SIZE][OUT_SIZE][OUT_CH],
    float weight[K][K][IN_CH][OUT_CH],
    float bias[OUT_CH]
) {
#pragma HLS INTERFACE m_axi port=in depth=100000
#pragma HLS INTERFACE m_axi port=out depth=100000
#pragma HLS INTERFACE s_axilite port=return

    for (int row = 0; row < OUT_SIZE; row++) {
        for (int col = 0; col < OUT_SIZE; col++) {
            for (int ch_out = 0; ch_out < OUT_CH; ch_out++) {
#pragma HLS PIPELINE
                float sum = bias[ch_out];
                for (int kx = 0; kx < K; kx++) {
                    for (int ky = 0; ky < K; ky++) {
                        for (int ch_in = 0; ch_in < IN_CH; ch_in++) {
                            sum += in[row+kx][col+ky][ch_in] * weight[kx][ky][ch_in][ch_out];
                        }
                    }
                }
                out[row][col][ch_out] = sum;
            }
        }
    }
}

注意看,我在最内层的循环前加了PIPELINE指令。这样综合工具就会尝试让每个时钟周期都输出一个结果。如果不加,那就要等所有循环跑完才能输出,延迟差几十倍。

避坑指南:我曾经在HLS里写过递归函数,结果综合出来一个巨大的状态机,资源直接爆了。HLS不支持递归,也不支持动态内存分配。写代码的时候脑子里要时刻想着:这行代码综合出来是什么电路?

4.3 Vitis AI工具链使用

好了,HLS讲完了,但咱们做AI部署的,总不能手写每个算子吧?这时候Vitis AI就派上用场了。它是Xilinx推出的AI推理工具链,说白了就是帮你把训练好的模型转成能在FPGA上跑的指令流。

Vitis AI的流程大致是这样的:

  1. 模型量化:把FP32的权重转成INT8。FPGA做INT8运算比FP32快得多,资源也省。
  2. 编译:把量化后的模型编译成DPU(深度学习处理单元)能识别的指令。
  3. 部署:把编译好的文件烧到FPGA上,跑推理。

我个人觉得,Vitis AI最方便的地方在于它支持PyTorch和TensorFlow。你训练好的模型,直接导出成ONNX或者XModel,丢给Vitis AI就行。不用自己写硬件代码,这点对算法工程师特别友好。

举个例子,部署一个ResNet-50:

# 1. 量化
vai_q_tensorflow quantize \
    --input_frozen_graph frozen_graph.pb \
    --input_nodes input \
    --output_nodes softmax_tensor \
    --input_shapes ?,224,224,3 \
    --calib_dataset ./calib_data \
    --output_dir ./quantized

# 2. 编译
vai_c_tensorflow \
    --frozen_pb ./quantized/deploy_model.pb \
    --arch /opt/vitis_ai/compiler/arch/DPUCZDX8G/ZCU102/arch.json \
    --output_dir ./compiled \
    --net_name resnet50

# 3. 运行推理(Python API)
from vitis_ai.python import Runner

runner = Runner("./compiled/resnet50.xmodel")
input_data = preprocess(image)
output_data = runner.run(input_data)
result = postprocess(output_data)

看到没?三行Python就搞定了推理。但这里有个坑——量化校准。你量化的时候需要提供校准数据集,这个数据集要能代表真实推理时的数据分布。我见过有人随便拿几张图做校准,结果量化后精度掉了5个点,那叫一个惨。

建议:校准数据集至少准备500-1000张图,覆盖各种场景。如果模型是做人脸检测的,就别拿风景图做校准。数据分布不对,量化就是白做。

4.4 模型编译与部署

最后一步,编译和部署。Vitis AI的编译器会把模型解析成DPU指令,然后生成一个.xmodel文件。这个文件就是FPGA上跑的“可执行程序”。

编译的时候有几个参数要特别注意:

  • DPU架构:不同的FPGA芯片有不同的DPU配置。ZCU102用的是DPUCZDX8G,而Alveo卡用的是DPUCAHX8H。别搞混了。
  • 并行度:可以设置DPU同时处理多少个卷积核。并行度越高,吞吐量越大,但资源消耗也越大。我一般从8开始试,不够再加。
  • 输入输出形状:要跟模型定义一致。有一次我改了输入尺寸忘了改编译参数,跑出来的结果全是错的。

部署的时候,通常有两种方式:

方式 特点 适用场景
嵌入式部署 FPGA上跑Linux,直接调用DPU驱动 边缘设备、嵌入式系统
PCIe加速卡 FPGA插在服务器上,通过PCIe通信 数据中心、云端推理

嵌入式部署的话,你得先给FPGA烧一个包含DPU的bitstream,然后启动Linux,再加载模型。PCIe方式就简单一些,插上卡,装好驱动,直接调用API就行。

我记得有一次做嵌入式部署,板子死活起不来。查了半天,发现是DDR配置不对。FPGA的DDR控制器需要跟板子上的DDR颗粒匹配,频率、时序、型号,差一点都不行。从那以后,我每次拿到新板子,第一件事就是确认DDR配置。

总结一下FPGA部署的关键点:

  • 先搞清楚芯片资源,别超了
  • HLS写代码要想着硬件,加好pipeline和unroll
  • Vitis AI量化时校准数据集要靠谱
  • 编译参数跟模型定义要一致
  • 部署前先确认DDR和时钟配置

FPGA部署这条路,说难也难,说简单也简单。难在硬件细节多,简单在工具链已经帮你做了大部分工作。你只要把上面这些点都踩一遍,基本就能跑起来了。嗯,下一章咱们聊聊GPU部署,那个又是另一番天地了。