EfficientNet
本教程将引导你如何通过 CPU 或 NPU 在 RevyOS 系统上运行 Efficientnet b0 模型。 EfficientNet 是一种高效的深度神经网络模型,广泛应用于移动设备和嵌入式系统优化。
在按照本教程操作前,请确保你已经完成了环境配置部分的内容。
示例代码获取
本教程配套的示例代码已更新到 Github 中,使用 git
命令将其克隆到本地。
$ git clone https://github.com/zhangwm-pt/lpi4a-example.git
适用于本教程的代码位于 classification/efficientnet
目录下。
模型获取
我们使用的模型来自 efficientnet-pytorch 模型仓库,可以通过以下命令下载 EfficientNet 模型:
$ git clone https://github.com/rwightman/gen-efficientnet-pytorch.git
$ cd gen-efficientnet-pytorch
受版本影响,倘若你的 torch 版本过高,需要进入到 onnx_export.py
文件中修改第97行的 torch_out = torch.onnx._export
为 torch_out = torch.onnx.export
,再进行模型导出。
$ nano onnx_export.py # 将第97行的 torch_out = torch.onnx._export 为 torch_out = torch.onnx.export
$ python3 onnx_export.py --model efficientnet_b0 efficientnet_b0.onnx
如果你在中国大陆访问 GitHub 时遇到网络问题,可以考虑使用网络代理工具来加速访问。
模型基本信息
input name | output name | input shape | layout | channel order | scale value | mean values |
---|---|---|---|---|---|---|
input0 | output0 | 1, 3, 224, 224 | NCHW | RGB | 0.017 | 124, 117, 104 |
转换和编译模型
在 x86 机器上,使用 HHB 工具将 ONNX 模型转换为适用于 RevyOS 的计算图和胶水代码。在进行下面的操作前,请确保你已经按照环境配置部分的内容启动 HHB 容器,并克隆好示例仓库。
HHB 转换模型
在本步骤中,我们会将 onnx
模型转换为 HHB 平台能够使用的格式。
进入 classification/efficientnet
目录,执行以下命令:
$ hhb -D --model-file ./efficientnet_b0.onnx \
--data-scale 0.017 --data-mean "124 117 104" \
--board c920 --postprocess save_and_top5 \
--input-name "input0" --output-name "output0" \
--input-shape "1 3 224 224" --quantization-scheme float16
-D
:指定 HHB 流程执行到生成可执行文件的阶段为止--model-file
:指定输入模型文件--data-mean
:指定均值--data-scale
:指定缩放值--board
:指定目标平台为 C920(CPU) 或 TH1520(NPU)--input-name
: 模型的输入 tensor 名--output-name
:模型的输出 tensor 名--input-shape
:模型的输入 tensor 形状--postprocess
:指定 HHB 生成的胶水代码的后处理行为。save_and_top5
表示保存输出结果,并且打印 top5 结果--quantization-scheme
:指定量化类型为 float16
你可以通过运行 hhb --help
查看所有可用的参数和选项。
命令执行完成后,会在当前目录生成 hhb_out 子目录,里面的包括了 hhb_runtime
model.c
等多个文件:
hhb.bm
:HHB 的模型文件,包括了量化后的权重数据等信息hhb_runtime
:适用于开发板的可执行文件,由目录中的C文件编译而成main.c
:HHB 生成的示例程序的参考入口model.c
:HHB 模型结构表示文件,与模型结构相关model.params
:模型权重文件io.c
:HHB 生成的示例程序,包含读写文件的辅助函数io.h
:HHB 生成的示例程序,包含读写文件的辅助函数声明process.c
:HHB 生成的示例程序,包含图像预处理函数process.h
:HHB 生成的示例程序,包含图像预处理函数声明
更详细的 HHB 选项说明可以参考 HHB 用户手册中的命令行参数说明。
编译应用程序
HHB 生成的胶水代码仅仅能够测试模型的功能。为了完成图像的前处理和后处理,我们利用 OpenCV 编写了一个应用程序来加载模型并执行推理。
在 classification/efficientnet
目录下,使用以下命令编译应用程序:
$ export OPENCV_DIR=../../modules/opencv/ # 配置 OpenCV 的路径
$ riscv64-unknown-linux-gnu-g++ main.cpp -I${OPENCV_DIR}/include/opencv4 -L${OPENCV_DIR}/lib \
-lopencv_imgproc -lopencv_imgcodecs -L${OPENCV_DIR}/lib/opencv4/3rdparty/ \
-llibjpeg-turbo -llibwebp -llibpng -llibtiff -llibopenjp2 -lopencv_core -ldl \
-lpthread -lrt -lzlib -lcsi_cv -latomic -static -o efficientnet_example
本教程的示例代码中调用了 OpenCV 做模型输入的预处理,因此需要链接到预编译好的 OpenCV 静态库。请参照环境配置部分的内容,确保你已经安装了 OpenCV。
- -I../prebuilt_opencv/include/opencv4:头文件的搜索路径,指定到 opencv 的头文件路径
- -L../prebuilt_opencv/lib: 库的搜索路径,指定到预编译好的 opencv 的二进制库路径
- -lopencv_imgproc -lopencv_imgcodecs -lopencv_core:opencv 的库
- -llibjpeg-turbo -llibwebp -llibpng -llibtiff -llibopenjp2 -lcsi_cv: opencv 的依赖库
- -static:指定为静态链接
- -o efficientnet_example:指定生成名为 efficientnet_example 的可执行文件
编译命令正确执行完成后会在示例目录生成 efficientnet_example
文件
上传并运行应用程序
上传到开发板
将本目录中的所有文件打包上传到开发板中。例如,可以使用 scp
命令将文件上传到开发板的 /home/debian/npu
目录下:
$ scp -r ../efficientnet/ debian@<开发板IP>:/home/debian/efficientnet/
你也可以采取其他方式上传文件,如使用 USB 存储设备或通过网络共享等。
运行程序
进入开发板的 /home/debian/efficientnet
目录,确保你已经在 RevyOS 上安装了 SHL 库并配置了 LD_LIBRARY_PATH
。然后运行以下命令:
$ ./efficientnet_example
如果运行时出现以下错误:
hhb_out/hhb_runtime: error while loading shared libraries: libshl_th1520.so.2: cannot open shared object file: No such file or directory
请确保你已经正确配置了 LD_LIBRARY_PATH
。如果仍然出现此情况,请运行 pip show shl-python
检查 shl-python
版本。
如果版本显示为 3.x.x
,说明 SHL 版本太高。Resnet50 程序需要 shl-python
2.x 版本。你需要降级安装 shl-python
版本:
$ pip install shl-python==2.6.17
如果运行时出现以下错误:
FATAL: could not open driver '/dev/vha0': Permission denied
请检查当前用户是否能够读写 NPU 设备 /dev/vha0
。你可以使用以下命令将设备权限改为 0666(所有用户可读写):
$ sudo chmod 0666 /dev/vha0
建议配置 udev
规则来自动设置设备权限。你可以询问 AI 如何配置 udev
规则。
理论上来说,程序的运行时间应当很短。然而,当你实际运行程序时会发现,程序的运行时间可能超过 5 分钟。这不是 NPU 等组件出现了问题,而是因为 HHB 运行时在 NPU 上第一次加载模型时会先进行 JIT 编译,这就导致程序第一次运行时间会很长。
然而,由于 HHB 运行时的设计缺陷,HHB 在每次运行时都会重新 JIT 编译模型,因此每次运行的时间都很长。
有关更多内容,请参考常见问题和解决方案。
样例输出如下:
本教程中输入使用的是一个波斯猫的图片,预期 resnet50 的分类结果是第 283 项最大,对应于 Persian cat
。
$ ./efficientnet_example
********** preprocess image **********
********** run model **********
Run graph execution time: 191.89818ms, FPS=5.21
=== tensor info ===
shape: 1 3 224 224
data pointer: 0x28c5daf0
=== tensor info ===
shape: 1 1000
data pointer: 0x28caaa50
The max_value of output: 9.648438
The min_value of output: -2.396484
The mean_value of output: -0.043881
The std_value of output: 0.778041
============ top5: ===========
283: 9.648438
281: 6.589844
287: 5.355469
282: 4.910156
285: 4.437500
********** postprocess result **********
********** probability top5: **********
n02123394 Persian cat
n02123045 tabby, tabby cat
n02127052 lynx, catamount
n02123159 tiger cat
n02124075 Egyptian cat