做一些在树莓派zero w
上面的实验,这里记录一下整体流程。
准备工作
在 官网 下载
Raspberry Pi Imager
安装 Raspberry Pi OS
。
下载
operating system image
,考虑到其较低的硬件配置(1GHz
单核处理器和 512MB 内存),选择使用了 Raspberry Pi OS
(Legacy),这是 A stable legacy version of Raspberry Pi OS
Bullseye,具体信息是:
Raspberry Pi OS (Legacy) Lite
Release date: October 22nd 2024
System: 32-bit
Kernel version: 6.1
Debian version: 11 (bullseye)
Size: 366MB
Show SHA256 file integrity hash:
45dd65d579ec2b106a1e3181032144406eab61df892fcd2da8d83382fa4f7e51
烧录过程参照官网教程一步一步走就好了。
然而没能连上网(猜测在烧录工具里设置的 wifi
密码保存是乱码),于是在Boot目录中创建名为
wpa_supplicant.conf
文件。
将下面内容直接粘贴进去
1 | country=CN |
使用 Angry IP Scanner
软件扫描了一下,确定了树莓派连接wifi后的IP,顺利开始 ssh
连接开发之旅。
树莓派zero w
https://www.raspberrypi.com/products/raspberry-pi-zero-w/
树莓派 Zero 是树莓派系列中小尺寸高性价比的卡片式开发主板。树莓派 Zero 采用的是 ARM11 内核的 BCM2835 处理器,经济适用。树莓派 Zero W 是在 Zero 原版的基础上板载无线网卡,支持蓝牙和 WiFi 功能,Zero WH 是在 Zero W 的基础上加焊了 40PIN 排针,方便用户的调试和使用。
主要功能特性有:
802.11 b/g/n wireless LAN
Bluetooth 4.1
Bluetooth Low Energy (BLE)
1GHz, single-core CPU
512MB RAM
Mini HDMI® port and micro USB On-The-Go (OTG) port
Micro USB power
HAT-compatible 40-pin header
Composite video and reset headers
CSI camera connector
架构信息
1 | pi@raspberrypi:~ $ uname -m |
- armv6l:表示 ARMv6 架构(通常是树莓派 Zero、Zero W)。
- armv7l:表示 ARMv7 架构(树莓派 2)。
- aarch64 或 armv8:表示 64 位的 ARMv8 架构(树莓派 3、树莓派 4,如果运行的是 64 位操作系统)。
CPU 信息
1 | pi@raspberrypi:~ $ cat /proc/cpuinfo |
- half:半精度浮点运算支持(16 位浮点数)。
- thumb:支持 Thumb 16 位压缩指令集,节省代码空间。
- fastmult:支持快速整数乘法运算,提升性能。
- vfp:支持向量浮点运算硬件,加速浮点计算。
- edsp:增强的 DSP 指令集,用于加速数字信号处理。
- java:支持 Jazelle 指令集扩展,用于加速 Java 字节码执行。
- tls:支持线程本地存储,用于多线程编程。
这些特性使得 ARMv6 处理器在树莓派 Zero W 上能够兼具浮点计算、数字信号处理和节省内存空间的能力,适合嵌入式和轻量级的多任务应用。
主频
1 | pi@raspberrypi:~ $ vcgencmd measure_clock arm |
当前是700MHz,实验了一下,当CPU推理模型时,会变成
1 GHz
,如文档所说:
> It runs at 700MHz when idle and 1000MHz when busy (the core is
above 50% utilised).
温度
1 | vcgencmd measure_temp |
如果温度接近或超过 80°C,说明需要更好的散热。
交叉编译
树莓派 Zero W 使用 BCM2835 芯片,架构为 ARMv6,因此需要 ARMv6 兼容的交叉编译工具链。
我实验了官网的交叉编译链接
路径:
tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/
arm-linux-gnueabihf-gcc
(ARMv6 兼容的 GCC 编译器)arm-linux-gnueabihf-g++
(ARMv6 兼容的 G++ 编译器)
配置了树莓派的交叉编译链,编译报错:
1 | .../gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-gcc: cannot execute binary file |
这是因为arm-linux-gnueabihf-gcc 是针对 Linux 系统的二进制文件,macOS 的架构不支持直接运行此工具链。
于是在mac上装一个docker
docker version
Client:
Version: 27.3.1
API version: 1.47
Go version: go1.22.7
Git commit: ce12230
Built: Fri Sep 20 11:38:18 2024
OS/Arch: darwin/arm64
Context: default
启动一个 Linux 容器,并挂载项目目录:
1 | docker run --platform linux/amd64 -it -v /Users/zhaodongyu/Projects:/Projects --name aidget ubuntu:20.04 /bin/bash |
这会在 Docker 容器中将您的项目目录挂载到 /Projects。
注意,一定要加 --platform linux/amd64
! 我是 MAC M1
电脑,这个选项可以强制 Docker 使用 x86-64 模拟环境。不然会报错
rosetta error: failed to open elf at /lib64/ld-linux-x86-64.so.2
。(被这个问题恶心了很久)
经过实验,官方的交叉编译链编译出的可执行程序在树莓派
zero w
上运行是有问题的。
参考官方交流文档在这里 下载1.0.0 交叉编译链。
- Target triplet:armv6-rpi-linux-gnueabihf
- GCC 12.4
ABI BREAK: the armv6-rpi-linux-gnueabihf toolchain is now built using -march=armv6 instead of -march=armv6kz to provide compatibility with the ARMv6 version of Raspberry Pi OS. This may affect the ABI, so you are advised to recompile all code and dependencies after upgrading. (See tttapa/RPi-Cross-Cpp-Development#4 (comment) for more information.)
下载后解压:
tar -xf x-tools-aarch64-rpi3-linux-gnu-armv6-rpi-linux-gnueabihf-gcc12.tar.xz
在容器中安装相关工具:
1 | apt update |
这些标志的含义如下:
- -march=armv6:指定 ARMv6 架构,适用于树莓派 Zero 和 Pi 1。
- -mfloat-abi=hard:使用硬件浮点 ABI。
- -mfpu=vfp:指定使用 VFP(Vector Floating Point)浮点单元。
- -marm:强制使用 ARM 指令集,而非 Thumb 指令集。
软件信息
树莓派的gcc版本(bullseye):
1 | pi@raspberrypi:~ $ gcc --version |
加速?
处理器性能:BCM2835 使用的是 1GHz 的单核 ARM11 处理器,架构为 ARMv6,比现代 ARM 处理器(ARMv7、ARMv8 等)落后,缺少现代处理器中的许多优化指令集(如 NEON SIMD 指令)。
由于 CMSIS-NN 主要优化的是 ARM Cortex-M 系列处理器,对 ARMv6 并没有专门的加速支持。然而,CMSIS-NN 的优化策略(如量化、卷积优化、循环展开和指令调度)可以为 ARMv6 上的神经网络推理提供启发。
2024-11-17
tinyengine是个很好的推理框架,我想跟它 PK 推理数据,经过一番移植遇到各种各样的问题,包括且不限于 get_kernel_buffer undefined, arm cortex-M7/M4 cores,针对性做了一些加速算子,移植起来确实需要些精力,给他改得算子性能差了的话又胜之不武,于是放弃。如果能够 PK,这一定是一个强有力的对手!
我发现它使用tf_convertor.parseOperatorInfo()解析模型要比我用flatbuffer解析简洁很多,我也要优化一下我的转换脚本。
tf_convertor.parseOperatorInfo() 是 TensorFlow 提供的 Python API,简化了对 TFLite 模型的操作和解析,提供高级封装,方便直接读取 TFLite 模型中的算子信息。
直接解析 FlatBuffer,TFLite 模型的文件格式是 FlatBuffer 二进制文件,直接解析可以获取所有模型信息,包括 TensorFlow 工具链不支持的部分。
对比总结
特性 | tf_convertor.parseOperatorInfo() | 解析 FlatBuffer |
---|---|---|
开发难度 | 简单,高度封装 | 高,需解析 FlatBuffer 结构 |
灵活性 | 较低,依赖 TensorFlow 提供的功能 | 高,可访问底层和自定义信息 |
性能 | 较低,依赖 TensorFlow 工具链运行 | 高,直接操作二进制数据 |
依赖 | TensorFlow 工具链 | FlatBuffer 库,无需 TensorFlow |
适用场景 | 快速开发、标准模型解析 | 需要深度分析或轻量化项目 |
既然项目是要让大家使用,使用 tf_convertor.parseOperatorInfo() 快速获取基本信息确实是更好的选择,决定改成这种方式,如果这种方式搞不定,再换成解析 FlatBuffer。
本来想用tinyengine的几个tflite作为实验模型,既然也用不了这个框架,那还是改用tflite的一些公开模型吧。所以PK的主力对象又换成了tflite for micro(没什么挑战)
搭建TFLM
1 | cd Projects/RaspberryPi/tflm |
commit b23864944c0bc6c2603408754fd2b9b3dbaa862d
在tflite-micro/tensorflow/lite/micro/tools/make/targets/
目录下新建rpi_zero_w_makefile.inc
内容为:
1 | TARGET_TOOLCHAIN_PREFIX := ~/x-tools/armv6-rpi-linux-gnueabihf/bin/armv6-rpi-linux-gnueabihf- |
编译静态库:
make -f tensorflow/lite/micro/tools/make/Makefile TARGET=rpi_zero_w microlite
得到静态库:
x-tools/armv6-rpi-linux-gnueabihf/bin/armv6-rpi-linux-gnueabihf-ar: creating gen/rpi_zero_w_x86_64_default_gcc/lib/libtensorflow-microlite.a
为了公平竞争,编译BUILD_TYPE := release
版本,编译时没什么问题,运行时会遇到报错:
undefined reference to `MicroPrintf(char const*, ...)'
include/tensorflow/lite/micro/micro_mutable_op_resolver.h里面关于MicroPrintf的代码注释掉即可编译。
数据分析
lenet-5 模型
1 | pi@raspberrypi:~/lenet5 $ size empty |
filename | text | data | bss | dec | invoke time | heap(B) | stack(B) | 备注 |
---|---|---|---|---|---|---|---|---|
empty | 2191 | 340 | 4 | 2535 | 27368 | 2292 | ||
tfml_default | 117353 | 252116 | 16 | 369485 | 67880 | 42464 | ||
tfml_release | 85925 | 252100 | 12 | 338037 | 67880 | 42464 | ||
aidget | x | x | x | x | x | |||
tfml_default | 115162 | 251776 | 12 | 18.37 ms | 40512 | 40172 | -empty | |
tfml_release | 83734 | 251416 | 8 | 17.11 ms | 40512 | 40172 | -empty |
- text
- 表示代码段(.text)的大小。
- 包含程序的可执行指令。
- 较大时,说明程序包含更多功能或逻辑。
- data
- 表示已初始化全局和静态变量的大小。
- 包含变量的初始值。
- bss
- 表示未初始化或默认初始化为零的全局和静态变量的大小。
- 仅分配内存,不包含实际数据。
- dec
- 总大小(text + data + bss),以字节为单位。
- hex
- dec 的十六进制表示。
速度测试
1 | pi@raspberrypi:~/lenet5 $ ./tfml_release |
heap测试
1 |
|
1 | Node CONV_2D (number 0) invoke time: 11.220000 |
index | operator | tflm | aidget | aidget |
---|---|---|---|---|
0 | CONV_2D | 11.22 | 14.502 | 6.038 |
1 | TANH | 1.351 | 2.725 | 0.334 |
2 | MUL | 0.506 | 0.269 | 0.193 |
3 | ADD | 0.387 | 2.725 | 0.176 |
4 | MAX_POOL_2D | 0.294 | 1.114 | 0.496 |
5 | CONV_2D | 5.136 | 11.357 | 5.152 |
6 | TANH | 0.469 | 0.857 | 0.119 |
7 | MUL | 0.178 | 0.094 | 0.066 |
8 | ADD | 0.128 | 0.092 | 0.059 |
9 | MAX_POOL_2D | 0.100 | 0.155 | 0.161 |
10 | RESHAPE | 0.013 | ||
11 | FULLY_CONNECTED | 1.234 | 3.805 | 1.163 |
12 | TANH | 0.049 | 0.075 | 0.013 |
13 | FULLY_CONNECTED | 0.272 | 0.385 | 0.251 |
14 | TANH | 0.038 | 0.059 | 0.011 |
15 | FULLY_CONNECTED | 0.038 | 0.036 | 0.027 |
16 | SOFTMAX | 0.013 | 0.010 | 0.006 |
https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.md
https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/README.md
MobileNets are small, low-latency, low-power models parameterized to meet the resource constraints of a variety of use cases. They can be built upon for classification, detection, embeddings and segmentation similar to how other popular large scale models, such as Inception, are used. MobileNets can be run efficiently on mobile devices with TensorFlow Lite.
MobileNetV2 and MobilenetV3 networks.
Model | Quantized | Million MACs | Million Parameters | Top-1 Accuracy |
---|---|---|---|---|
float_v1_1.0_224 | uint8 | 569 | 4.24 | 70.9 |
float_v2_1.0_224 | uint8 | 300 | 3.47 | 71.8 |
float_v3_large_224 | uint8 | 217 | 5.4 | 75.2 |
float_v3_small_224 | uint8 | 66 | 2.9 | 67.5 |
https://github.com/tensorflow/tflite-micro/blob/main/tensorflow/lite/micro/examples/micro_speech/models/micro_speech_quantized.tflite
lenet5