1 TinyMaix简介
TinyMaix是国内sipeed团队开发一个轻量级AI推理框架,TinyMaix 是面向单片机的超轻量级的神经网络推理库,即 TinyML 推理库,可以让你在任意单片机上运行轻量级深度学习模型。
- 关键特性
- 核心代码少于 400行(tm_layers.c+tm_model.c+arch_cpu.h), 代码段(.text)少于3KB
- 低内存消耗,甚至 Arduino ATmega328 (32KB Flash, 2KB Ram) 都能基于 TinyMaix 跑 mnist(手写数字识别)
- 支持 INT8/FP32/FP16 模型,实验性地支持 FP8 模型,支持 keras h5 或 tflite 模型转换
- 支持多种芯片架构的专用指令优化: ARM SIMD/NEON/MVEI,RV32P, RV64V
- 友好的用户接口,只需要 load/run 模型~
- 支持全静态的内存配置(无需 malloc )
- 即将支持 MaixHub 在线模型训练
1.1 TinyMaix核心API
TinyMaix框架对上层应用程序提供的核心API主要位于代码仓的tinymaix.h文件中,核心API如下:
/******************************* MODEL FUNCTION ************************************/
tm_err_t tm_load (tm_mdl_t* mdl, const uint8_t* bin, uint8_t*buf, tm_cb_t cb, tm_mat_t* in); //load model
void tm_unload(tm_mdl_t* mdl); //remove model
tm_err_t tm_preprocess(tm_mdl_t* mdl, tm_pp_t pp_type, tm_mat_t* in, tm_mat_t* out); //preprocess input data
tm_err_t tm_run (tm_mdl_t* mdl, tm_mat_t* in, tm_mat_t* out); //run model
/******************************* UTILS FUNCTION ************************************/
uint8_t TM_WEAK tm_fp32to8(float fp32);
float TM_WEAK tm_fp8to32(uint8_t fp8);
/******************************* STAT FUNCTION ************************************/
#if TM_ENABLE_STAT
tm_err_t tm_stat(tm_mdlbin_t* mdl); //stat model
#endif
主要分为三类:
- 模型函数,包括模型加载、卸载、预处理、推理;
- 工具函数,包含FP32和uint8的互转;
- 统计函数,用于输出模型中间层信息;
这里的模型,通常是预训练模型经过脚本转换生成的TinyMaix格式的模型;
1.2 TinyMaix底层依赖
TinyMaix可以简单理解为一个矩阵和向量计算库,目前已支持如下几种计算硬件:
#define TM_ARCH_CPU (0)
#define TM_ARCH_ARM_SIMD (1)
#define TM_ARCH_ARM_NEON (2)
#define TM_ARCH_ARM_MVEI (3)
#define TM_ARCH_RV32P (4)
#define TM_ARCH_RV64V (5)
#define TM_ARCH_CSKYV2 (6)
#define TM_ARCH_X86_SSE2 (7)
对于ARM-Cortex系列MCU,可以支持纯CPU计算和SIMD计算。其中CPU计算部分无特殊依赖(计算代码均使用标准C实现)。SIMD部分,部分计算代码使用了C语言内嵌汇编实现,需要CPU支持相应的汇编指令,才可以正常编译、运行。
TinyMaix的示例代码依赖于精准计时和打印输出能力,具体是项目的tm_port.h中的几个宏定义:
#define TM_GET_US() ((uint32_t)((uint64_t)clock()*1000000/CLOCKS_PER_SEC))
#define TM_DBGT_INIT() uint32_t _start,_finish;float _time;_start=TM_GET_US();
#define TM_DBGT_START() _start=TM_GET_US();
#define TM_DBGT(x) {_finish=TM_GET_US();\
_time = (float)(_finish-_start)/1000.0;\
TM_PRINTF("===%s use %.3f ms\n", (x), _time);\
_start=TM_GET_US();}
2 TinyMaix移植
将TinyMaix源码克隆到本地:
# git clone https://github.com/sipeed/TinyMaix.git
将TinyMaix拷贝到一个工程实例中,然后将源码添加到工程中。
还需要拷贝CIFAR10实例的头文件到应用文件目录中。
还需修改`tm_port.h文件。
最后就是修改主函数。
#include <stdio.h>
#include "board.h"
#include "hpm_debug_console.h"
#include "tinymaix.h"
#include "cifar10_q.h"
#define LED_FLASH_PERIOD_IN_MS 300
#define IMG_L 32
#define IMG_CH 3
#define CLASS_NUM 10
#include "pic.h"
char* labels[CLASS_NUM] = {"airplane", "automobile", "bird", "cat", "deer", "dog", "frog", "hrose", "ship", "truck"};
static tm_err_t layer_cb(tm_mdl_t* mdl, tml_head_t* lh)
{
int h = lh->out_dims[1];
int w = lh->out_dims[2];
int ch= lh->out_dims[3];
mtype_t* output = TML_GET_OUTPUT(mdl, lh);
return TM_OK;
TM_PRINTF("Layer %d callback ========\n", mdl->layer_i);
#if 1
for(int y=0; y<h; y++)
{
TM_PRINTF("[");
for(int x=0; x<w; x++)
{
TM_PRINTF("[");
for(int c=0; c<ch; c++)
{
#if TM_MDL_TYPE == TM_MDL_FP32
TM_PRINTF("%.3f,", output[(y*w+x)*ch+c]);
#else
TM_PRINTF("%.3f,", TML_DEQUANT(lh,output[(y*w+x)*ch+c]));
#endif
}
TM_PRINTF("],");
}
TM_PRINTF("],\n");
}
TM_PRINTF("\n");
#endif
return TM_OK;
}
static void parse_output(tm_mat_t* outs)
{
tm_mat_t out = outs[0];
float* data = out.dataf;
float maxp = 0;
int maxi = -1;
for(int i=0; i<CLASS_NUM; i++)
{
printf("%d: %.3f\n", i, data[i]);
if(data[i] > maxp)
{
maxi = i;
maxp = data[i];
}
}
TM_PRINTF("### Predict output is: Class %d, %s, prob %.3f\n", maxi, labels[maxi], maxp);
return;
}
int main(int argc, char** argv)
{
board_init();
board_init_led_pins();
board_timer_create(LED_FLASH_PERIOD_IN_MS, board_led_toggle);
printf("benchmark start...\n");
TM_DBGT_INIT();
TM_PRINTF("cifar10 demo\n");
tm_mdl_t mdl;
tm_mat_t in_uint8 = {3,IMG_L,IMG_L,IMG_CH, {(mtype_t*)pic}};
tm_mat_t in = {3,IMG_L,IMG_L,IMG_CH, {NULL}};
tm_mat_t outs[1];
tm_err_t res;
tm_stat((tm_mdlbin_t*)mdl_data);
res = tm_load(&mdl, mdl_data, NULL, layer_cb, &in);
if(res != TM_OK)
{
TM_PRINTF("tm model load err %d\n", res);
return -1;
}
#if (TM_MDL_TYPE == TM_MDL_INT8) || (TM_MDL_TYPE == TM_MDL_INT16)
res = tm_preprocess(&mdl, TMPP_UINT2INT, &in_uint8, &in);
#else
res = tm_preprocess(&mdl, TMPP_UINT2FP01, &in_uint8, &in);
#endif
TM_DBGT_START();
res = tm_run(&mdl, &in, outs);
TM_DBGT("tm_run");
if(res==TM_OK) parse_output(outs);
else TM_PRINTF("tm run error: %d\n", res);
tm_unload(&mdl);
return 0;
}
最后编译下载,打印信息如下。
当然啦,TinyMaix还有其他测试用例,感兴趣的可以试试。
TinyMaix四种常用的基准测试模型的基准测试如下:
- mnist——手写数字识别模型,输入28x28x1
- cifar——10分类模型,输入32x32x3
- vww——人体检测二分类模型,输入96x96x3,输出有无人
- mbnet——1000分类模型,输入128x128x3