部署模型到 Maix-I(M1) K210 系列开发板
欢迎修改和补充
一般使用 tensorflow
训练出浮点模型, 再使用转换工具将其转换成 K210
所支持的 Kmodel
模型,然后将模型部署到 K210
开发板上。
K210 上的 KPU
K210
上的 AI 硬件加速单元取名为KPU
,KPU
实现了 卷积、批归一化、激活、池化 这 4 种基础操作的硬件加速, 但是它们不能分开单独使用,是一体的加速模块。
所以, 在 KPU 上面推理模型, 以下要求:
内存限制
K210 有 6MB 通用 RAM 和 2MB KPU 专用 RAM。模型的输入和输出特征图存储在 2MB KPU RAM 中。权重和其他参数存储在 6MB 通用 RAM 中,在转换模型时,会打印模型使用的内存大小以及临时最大内存使用情况。
哪些算子可以被 KPU 完全加速?
nncase 支持的算子:
- nncase v0.2.0 支持的算子: https://github.com/kendryte/nncase/blob/master/docs/tflite_ops.md
- nncase v0.1.0 支持的算子: https://github.com/kendryte/nncase/tree/v0.1.0-rc5
下面的约束需要全部满足。
- 特征图尺寸:输入特征图小于等于 320x240 (宽x高) 同时输出特征图大于等于 4x4 (宽x高),通道数在 1 到 1024。
- Same 对称 paddings (TensorFlow 在 stride=2 同时尺寸为偶数时使用非对称 paddings)。
- 普通 Conv2D 和 DepthwiseConv2D,卷积核为 1x1 或 3x3,stride 为 1 或 2。
- 最大池化 MaxPool (2x2 或 4x4) 和 平均池化 AveragePool (2x2 或 4x4)。
- 任意逐元素激活函数 (ReLU, ReLU6, LeakyRelu, Sigmoid...), KPU 不支持 PReLU。
哪些算子可以被 KPU 部分加速?
- 非对称 paddings 或 valid paddings 卷积, nncase 会在其前后添加必要的 Pad 和 Crop(可理解为 边框 与 裁切)。
- 普通 Conv2D 和 DepthwiseConv2D,卷积核为 1x1 或 3x3,但 stride 不是 1 或 2。 nncase 会把它分解为 KPUConv2D 和一个 StridedSlice (可能还需要 Pad)。
- MatMul 算子, nncase 会把它替换为一个 Pad(到 4x4)+ KPUConv2D(1x1 卷积和) + Crop(到 1x1)。
- TransposeConv2D 算子, nncase 会把它替换为一个 SpaceToBatch + KPUConv2D + BatchToSpace。
以上说明来自这里
训练出浮点模型
对于 K210, 建议使用 TensorFlow,因为它的转换工具对其支持最好。
tensorflow 举个例子, 两分类模型, 这里是随便叠的层结构
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
input_shape = (240, 320, 3)
model = tf.keras.models.Sequential()
model.add(layers.ZeroPadding2D(input_shape = input_shape, padding=((1, 1), (1, 1))))
model.add(layers.Conv2D(32, (3,3), padding = 'valid', strides = (2, 2)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu')); #model.add(MaxPool2D());
model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
model.add(layers.Conv2D(32, (3,3), padding = 'valid',strides = (2, 2)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
model.add(layers.Conv2D(32, (3,3), padding = 'valid',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
model.add(layers.Conv2D(32, (3,3), padding = 'valid',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
model.add(layers.Conv2D(32, (3,3), padding = 'valid',strides = (2, 2)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(32, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.ZeroPadding2D(padding=((1, 1), (1, 1))));
model.add(layers.Conv2D(64, (3,3), padding = 'valid',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(64, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Conv2D(64, (3,3), padding = 'same',strides = (1, 1)));model.add(layers.BatchNormalization());model.add(layers.Activation('relu'));
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(2))
model.add(layers.Activation('softmax'))
model.summary()
model.compile(
loss ='sparse_categorical_crossentropy',
optimizer = 'adam',
metrics =['accuracy'])
mode.fit(...)
这里你可能注意到了, 在 conv
层中stride != 1
时, 都加了一个 zeropadding
层, 这是 K210 硬件支持的模式, 如果不这样做, 转换成 V3 模型时(使用 nncase v0.1.0 RC5) 则直接报错, 使用 V4 模型(nncase V0.2.0转换)可以通过,但是是使用软件运算的, 会消耗大量内存和时间, 会发现内存占用大了很多!!! 所以设计模型时也需要注意
转换工具
使用 K210 芯片官方提供的 nncase 工具来进行转换。
需要注意的是,工具版本更新迭代比较多, K210
属于第一代芯片,算子支持有限,并且内存只有6MiB(通用)+2MiB(AI专用)
内存,所以最新版本的工具可能并不是最好的选择,根据需求选择合适的版本。
由于代码更新, 在过程中模型格式产生了两个大版本, V3
和 V4
, 其中 V3
模型是指用 nncase v0.1.0 RC5 转换出来的模型; V4
模型指用 nncase v0.2.0 转换出来的模型,以及 V5 或更新版本等等。
两者有一定的不同,所以现在两者共存, V3
代码量更少,占用内存小,效率也高,但是支持的算子少; V4
支持的算子更多,但是都是软件实现的,没有硬件加速,内存使用更多,所以各有所长。 MaixPy
的固件也可以选择是否支持 V4
, 如果你的模型 V3
能够满足算子支持,强烈建议用 V3
,在遇到算子不支持而且一定要用那个算子时再用V4
。
运行测试模型
比如使用 MaixPy
固件, 将模型放到 SD 卡, 然后使用代码加载
import KPU as kpu
import image
m = kpu.load("/sd/test.kmodel")
img = image.Image("/sd/test.jpg")
img = img.resize(224, 224)
img.pix_to_ai()
feature_map = kpu.forward(m, img)
p_list = feature_map[:]
更多参考
上传分享到 MaixHub
可以上传分享你的模型到到 MaixHub 的模型库,可以让更多人发现并使用你的模型~ 一起做出更多有趣的项目吧!(K210 模型支持加密分享)
另外你也可以使用 MaixHub 的模型库,下载别人分享的模型,或者使用在线训练出的模型,直接使用或者参考模型结构。