Paddle Lite支持基于arm的fpga zu3/zu5/zu9的模型预测,提供armv8的交叉编译

Lite基于fpga运行模型需要相应的fpga驱动,目前只支持百度edgeboard开发板

Lite实现fpga简介

Lite支持fpga作为后端硬件进行模型推理,其主要特性如下:

  • Lite中fpga的kernel(feed、fetch除外)均以FP16、NHWC的格式作为输入输出格式,所有的weights和bias仍为FP32、NCHW的格式,feed的输入和fetch的输出均为FP32、NCHW格式的数据,在提升计算速度的同时能做到用户对数据格式无感知

  • 对于fpga暂不支持的kernel,均会切回arm端运行,实现arm+fpga混合布署运行

  • 目前fpga成本功耗都较低,Lite基于fpga的模型性能远远好于arm端,可作为边缘设备首选硬件

编译

需要提前准备带有fpgadrv.ko的fpga开发板(如edgeboard开发板)和Lite代码

CMAKE编译选项:

  • 设置LITE_WITH_FPGA=ONLITE_WITH_ARM=ON

其他编译选项与ARM编译相同,可以参考“Paddle Lite在Docker下的ARM编译”。 示例如下:

    cmake .. \
        -DWITH_GPU=OFF \
        -DWITH_MKL=OFF \
        -DWITH_LITE=ON \
        -DLITE_WITH_CUDA=OFF \
        -DLITE_WITH_X86=OFF \
        -DLITE_WITH_ARM=ON \
        -DLITE_WITH_OPENMP=ON   \
        -DLITE_WITH_LIGHT_WEIGHT_FRAMEWORK=ON \
        -DWITH_TESTING=OFF \
        -DLITE_WITH_FPGA=ON \
        -DARM_TARGET_OS=armlinux 
    make publish_inference -j2

Lite提供fpga编译脚本,位于lite/tools/build_fpga.sh,在Lite根目录执行该脚本即可编译

运行示例

  • 运行文件准备

下面以Resnet50模型为例,介绍如何使用edgeboard开发板实现模型运行

#连接开发板,并利用screen命令启动 [本机执行]
screen /dev/cu.SLAB_USBtoUART 115200
#查看开发板ip并ssh登录到开发板,假设开发板ip为192.0.1.1 [本机执行]
ssh root@192.0.1.1

#在开发板上建立目录workspace,拷贝fpga驱动fpgadrv.ko到workspace目录 [开发板执行]
mkdir workspace && scp $DRIVER_PATH/fpgadrv.ko workspace

#将Lite中编译好的测试程序拷贝到开发板workspace目录 [本机执行]
scp $LITE_ROOT/build_fpga/lite/api/test_resnet50_fpga root@$EDGEBOARD_IP:workspace/
#把Resnet50的模型和参数scp到开发板workspace目录 [本机执行]
scp -r $LITE_ROOT/build_fpga/lite/third_party/install/resnet50/ root@$EDGEBOARD_IP:workspace/

#在运行模型前需要加载fpga驱动 [开发板执行]
insmod fpgadrv.ko
#给测试程序添加可运行权限 [开发板执行]
chmod +x test_resnet50_fpga
  • 使用fpga进行模型预测
#以下命令均在开发板上运行
#直接运行单测程序
./test_resnet50_fpga --model_dir=resnet50
#如果需要测试性能,可以用repeats参数设置模型运行次数(如1000),同时可以设置预热次数(如10)来让硬件事先运行到稳定水平
./test_resnet50_fpga --model_dir=resnet50 --repeats=1000 --warmup=10

如何在Code中使用

在Lite中使用fpga与ARM相似,具体的区别如下:

  • 由于fpga运行模式为fp16精度、nhwc布局,所以需要修改相应的valid_placepreferred_place
  • fpga不需要device的初始化和运行模式设置

代码示例:

#include "paddle_api.h"         
#include "paddle_use_kernels.h"  
#include "paddle_use_ops.h"      
#include "paddle_use_passes.h"
using namespace paddle::lite_api;

std::vector<Place> valid_places({Place{TARGET(kFPGA), PRECISION(kFP16), DATALAYOUT(kNHWC)},
Place{TARGET(kHost), PRECISION(kFloat), DATALAYOUT(kNCHW)}});
std::string model_dir = "my_model";
std::string model_file = model_dir + "/model"; 
std::string params_file = model_dir + "/params";

// 1. Set CxxConfig
CxxConfig config;
config.set_model_dir(model_dir);
config.set_model_file(model_file);
config.set_param_file(params_file);
config.set_preferred_place(Place{TARGET(kFPGA), PRECISION(kFP16), DATALAYOUT(kNHWC)});
config.set_valid_places(valid_places);

// 2. Create PaddlePredictor by CxxConfig
predictor = CreatePaddlePredictor<CxxConfig>(config);

// 3. Set input data
std::unique_ptr<Tensor> input_tensor(std::move(predictor->GetInput(0)));
input_tensor->Resize(shape_t({1, 3, 224, 224}));
auto* input = input_tensor->mutable_data<float>();
read_image(value, input);

// 4. Run predictor
for (int i = 0;i < 2; i++) {
   predictor->Run();
}
// 5. Get output
std::unique_ptr<const Tensor> output_tensor
std::move(predictor->GetOutput(0)));