Zhao Dongyu's Blog

A life which is unexamined is not worth living.

0%

树莓派Zero实验

做一些在树莓派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
2
3
4
5
6
7
8
country=CN
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
ssid="power_cube"
psk="22222222"
}

使用 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
2
pi@raspberrypi:~ $ uname -m
armv6l
  • armv6l:表示 ARMv6 架构(通常是树莓派 Zero、Zero W)。
  • armv7l:表示 ARMv7 架构(树莓派 2)。
  • aarch64 或 armv8:表示 64 位的 ARMv8 架构(树莓派 3、树莓派 4,如果运行的是 64 位操作系统)。

CPU 信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pi@raspberrypi:~ $ cat /proc/cpuinfo
processor : 0
model name : ARMv6-compatible processor rev 7 (v6l)
BogoMIPS : 697.95
Features : half thumb fastmult vfp edsp java tls
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xb76
CPU revision : 7

Hardware : BCM2835
Revision : 9000c1
Serial : 000000008caea6be
Model : Raspberry Pi Zero W Rev 1.1
  • half:半精度浮点运算支持(16 位浮点数)。
  • thumb:支持 Thumb 16 位压缩指令集,节省代码空间。
  • fastmult:支持快速整数乘法运算,提升性能。
  • vfp:支持向量浮点运算硬件,加速浮点计算。
  • edsp:增强的 DSP 指令集,用于加速数字信号处理。
  • java:支持 Jazelle 指令集扩展,用于加速 Java 字节码执行。
  • tls:支持线程本地存储,用于多线程编程。

这些特性使得 ARMv6 处理器在树莓派 Zero W 上能够兼具浮点计算、数字信号处理和节省内存空间的能力,适合嵌入式和轻量级的多任务应用。

主频

1
2
pi@raspberrypi:~ $ vcgencmd measure_clock arm
frequency(48)=700000000

当前是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
2
3
4
docker run --platform linux/amd64 -it -v /Users/zhaodongyu/Projects:/Projects --name aidget ubuntu:20.04 /bin/bash
docker start aidget
docker exec -it aidget /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
2
3
4
5
apt update
apt install make
apt install -y binutils

# apt install -y gcc-arm-linux-gnueabihf

这些标志的含义如下:

  • -march=armv6:指定 ARMv6 架构,适用于树莓派 Zero 和 Pi 1。
  • -mfloat-abi=hard:使用硬件浮点 ABI。
  • -mfpu=vfp:指定使用 VFP(Vector Floating Point)浮点单元。
  • -marm:强制使用 ARM 指令集,而非 Thumb 指令集。

软件信息

树莓派的gcc版本(bullseye):

1
2
3
4
5
pi@raspberrypi:~ $ gcc --version
gcc (Raspbian 10.2.1-6+rpi1) 10.2.1 20210110
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

加速?

处理器性能: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
2
cd Projects/RaspberryPi/tflm
git clone https://github.com/tensorflow/tflite-micro.git

commit b23864944c0bc6c2603408754fd2b9b3dbaa862d

tflite-micro/tensorflow/lite/micro/tools/make/targets/目录下新建rpi_zero_w_makefile.inc

内容为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
TARGET_TOOLCHAIN_PREFIX := ~/x-tools/armv6-rpi-linux-gnueabihf/bin/armv6-rpi-linux-gnueabihf-

CXXFLAGS += $(FLAGS_GCC)
CCFLAGS += $(FLAGS_GCC)

PLATFORM_FLAGS = \
-DTF_LITE_MCU_DEBUG_LOG \
-mfloat-abi=hard \
-mfpu=vfp \
-funsigned-char \
-mlittle-endian \
-Wno-type-limits \
-Wno-unused-private-field \
-fomit-frame-pointer \
-MD \
-march=armv6 \
-marm

ifneq ($(PIC),)
PLATFORM_FLAGS += -fpic
endif

# Common + C/C++ flags
CXXFLAGS += $(PLATFORM_FLAGS)
CCFLAGS += $(PLATFORM_FLAGS)

CC := $(TARGET_TOOLCHAIN_PREFIX)gcc
CXX := $(TARGET_TOOLCHAIN_PREFIX)g++
AR := $(TARGET_TOOLCHAIN_PREFIX)ar

编译静态库:

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
2
3
4
5
6
7
8
9
10
pi@raspberrypi:~/lenet5 $ size empty 
text data bss dec hex filename
2191 340 4 2535 9e7 empty
pi@raspberrypi:~/lenet5 $ size tfml_default
text data bss dec hex filename
117353 252116 16 369485 5a34d tfml_default
pi@raspberrypi:~/lenet5 $ size tfml_release
text data bss dec hex filename
85925 252100 12 338037 52875 tfml_release

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
2
3
4
5
6
pi@raspberrypi:~/lenet5 $ ./tfml_release 
Time taken: 17.111680108 seconds
output = 0.100160
pi@raspberrypi:~/lenet5 $ ./tfml_default
Time taken: 18.373274514 seconds
output = 0.100160

heap测试

1
2
3
4
5
6
7
8
9
10
11
12

pi@raspberrypi:~/lenet5 $ valgrind --tool=massif --stacks=yes --massif-out-file=massif_output_empty.out ./empty

pi@raspberrypi:~/lenet5 $ valgrind --tool=massif --stacks=yes --massif-out-file=massif_output_tfml_default.out ./tfml_default_1

pi@raspberrypi:~/lenet5 $ valgrind --tool=massif --stacks=yes --massif-out-file=massif_output_tfml_release.out ./tfml_release_1


pi@raspberrypi:~/lenet5 $ ms_print massif_output_empty.out
pi@raspberrypi:~/lenet5 $ ms_print massif_output_tfml_default.out
pi@raspberrypi:~/lenet5 $ ms_print massif_output_tfml_release.out

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Node CONV_2D (number 0) invoke time: 11.220000
Node TANH (number 1) invoke time: 1.351000
Node MUL (number 2) invoke time: 0.506000
Node ADD (number 3) invoke time: 0.387000
Node MAX_POOL_2D (number 4) invoke time: 0.294000
Node CONV_2D (number 5) invoke time: 5.136000
Node TANH (number 6) invoke time: 0.469000
Node MUL (number 7) invoke time: 0.178000
Node ADD (number 8) invoke time: 0.128000
Node MAX_POOL_2D (number 9) invoke time: 0.100000
Node RESHAPE (number 10) invoke time: 0.013000
Node FULLY_CONNECTED (number 11) invoke time: 1.234000
Node TANH (number 12) invoke time: 0.049000
Node FULLY_CONNECTED (number 13) invoke time: 0.272000
Node TANH (number 14) invoke time: 0.038000
Node FULLY_CONNECTED (number 15) invoke time: 0.038000
Node SOFTMAX (number 16) invoke time: 0.013000
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

Thanks for your support.