在勘智的 K210 处理器上执行深度学习的模型,需要用到官方的 nncase 和 SDK。官方的文档和代码的更新,挺迷的,尤其是对我来说。所以当要做的事情超出了开发板官方给的(过时的)、(简陋)的资料的时候,就需要自己填坑。此外勘智官方的文档也不咋地,不给寄存器相关的手册吧,nncase 之类的工具连个正儿八经的文档也没有。所以在这里整理一下将自己的 PyTorch 模型一步步处理转化到 K210 nncase 模型的过程,整理整理相关的填坑的内容。
SDK 等所需软件安装
整个代码以 PyTorch 为例,因为看到 在 github·nncase·9c0608c:/examples/yolox/tools/decoder.py 中官方在使用 pytorch。此外,使用的 K210 相关的工具都是此时(2021年11月3日)相对最新的或者是还是开发中的。
使用的如下版本:
- PyTorch(1.6.0+)
- kendryte/nncase:v1.0.0
- kendryte/kendryte-standalone-sdk:develop
系统用的是 Ubuntu 20.04 (amd64)。
安装的方式相对简单,对于 PyTorch 来说,将 PyTorch 1.6.0
或者更新的版本即可。对于 nncase
从 nncase 的 Release 页面下载 v1.0.0 版本对应的 wheel 文件(文件需要和 Python 的版本对应),更新的版本不确定是否支持。对于 kendryte-standalone-sdk
则是需要从 github 下载 develop
分支的版本,我当前测试的版本是 02576ba
。(这个因为是开发分支,所以不稳定是在所难免的。)
所以这里遇到了一大堆的坑,下面来整理一下这些坑怎么填。但是在这之前,还是要整理一下环境的配置,应为太魔幻了。
写在前面,虽然勘智官方并没有什么文档可以参考。但是官方还是有一些代码的,最后一节里面整理了官方代码里面有一定作用的内容。
环境配置
除了像 PyTorch、kFlash 这样官方稳定也能用的软件可以直接安装之外,其他的软件就需要手动编译、安装。
首先可以直接安装的稳定的软件是:
- PyTorch、NumPy 等深度学习和 PC 端图像处理的 Python 包是可以直接安装的
- kFlash (从官方的Release 页面中或者 pypi 上安装)
- GNU Toolchain for K210 可以直接从勘智官网或者是官方的 GitHub 仓库中下载。(也可以自己编译。。)
然后 SDK 和 nncase 需要从官方的 GitHub 仓库下载。需要编译的只有 nncase,并且 nncase 需要编译两次,分别是编译模型转换的代码和单片机上的运行时。
对于 SDK,从用 git 从 https://github.com/kendryte/kendryte-standalone-sdk 这里克隆或者直接从 https://github.com/kendryte/kendryte-standalone-sdk/archive/refs/heads/develop.tar.gz 这里下载。如果使用 git 克隆的话,并不需要切换分支,直接使用默认的 develop 分支即可。
然后下载 nncase v1.0.0
的代码,不能直接使用官方 Release 页面中的 wheel 文件,因为新的版本官方并没有提供 ncc
和 runtime
相关的压缩包(虽然 ncc 可以不用)。
如果是直接下载的话,直接从 Release 页面下载打包好的代码,如果用的是 git,就需要通过 git checkout v1.0.0
切换到 v1.0.0
tag 上对应的代码。
然后就需要编译 nncase,编译 nncase 官方有指南 https://github.com/kendryte/nncase/blob/master/docs/build.md 可以参考,一本上照着抄就可以了。但是对于编译 runtime
官方就没有指明 ”how-to“。这个就需要参考官方的 GitHub Action 代码进行。
然后是 Python Wheel 文件的问题,上面编译 nncase 之后将 build 文件夹重新命名一下(如果要用编译好的内容的话,在 build 文件夹下执行 cmake --install --prefix=../install
然后在 install
文件夹下面你就能找到要的文件了)。然后在 nncase 的目录下(就是有 setup.py 文件的地方)执行 python setup.py bdist_wheel
来生成 wheel 文件(在 dist)文件夹下面。
过程与官方教程编译 nncase 一样,首先建立一个 runtime.build
文件夹,然后在这个文件夹里面执行:
RISCV_ROOT_PATH=xxx cmake .. -DCMAKE_BUILD_TYPE=Release -DK210_SDK_DIR=xx -DBUILDING_RUNTIME=TRUE -DCMAKE_TOOLCHAIN_FILE=xxx/nncase/toolchains/k210.toolchain.cmake
其中 RISCV_ROOT_PATH
这个变量是下载的工具链的位置(不是 gcc 的位置),然后K210_SDK_DIR
就是之前下载的 SDK 的解压的位置,最后 CMAKE_TOOLCHAIN_FILE
就是 nncase 代码下面,有一个叫 toolchains
的文件夹,里面的 k210.toolchain.cmake
。上面的这些的路径最好全都是绝对路径,否则容易报错(其实就是使用相对路径的时候会遇到一些问题,我懒得解决了)。
然后执行 make
进行编译,编译完成之后,在 nncase 的目录下新建一个 runtime.install
的文件夹,然后再 runtime.build
文件夹下面执行 cmake --install . --prefix ../runtime.install
然后 runtime 相关文件就能在 runtime.install
文件夹下面找到。
这个文件夹里面有三个文件夹 bin
、lib
、include
把他们复制或者通过软连接的方式”复制到 SDK 下 nncase 相关的位置 sdk/lib/nncase/v1
(参见 https://github.com/kendryte/kendryte-standalone-sdk/issues/126#issuecomment-916217872)。
这些内容都拼对好了之后,案例来说就能跑了,但是还是会有一些问题(tanshou)。下面的章节来将这些问题。
官方 Demo 测试
官方的 Demo 测试需要在新的 SDK 和 nncase 中才能运行。 nncase 中有一个 examples 文件夹。里面有一些 Demo,但是这些 Demo 并不是全都能用,而是说主要看 yolox。因为里面有一些 Demo 是针对旧的 SDK 和 nncase 设计的。
yolox 的 Demo 文件夹下面有一个 ReadMe 文件,按照那个文件就能得到可以烧录到设备上的代码。
PyTorch 模型设计
K210 并不是支持所有的 PyTorch 和 TensorBoard 的算子,https://github.com/kendryte/nncase/blob/master/docs/FAQ_ZH.md 里面详细列了那些算子是能加速,那些不行,总的来说,就是卷积只能 3 x 3 或者 1 x 1 的,然后输入和输出也有一定的大小限制。
PyTorch 模型转换
模型转换需要将 PyTorch 先转换成 ONNX 文件,参考 PyTorch 官方的文档里面 https://pytorch.org/docs/stable/onnx.html 的相关内容,来将模型转换成 ONNX 文件。
大致来说就是使用 torch.onnx.export
这个 API 就可以:
torch.onnx.export(model, img_input, onnx_file, verbose=True, input_names=input_names, output_names=output_names)
其中 model
是 PyTorch 的对象(nn.Module), img_input 是输入的图片,尺寸一定要正确,输入的数据可以是随机生成的,比如 torch.rand((1,3,224,320))
,然后 input_names
和 output_names
可以不写或者是对应的输入的名称。
onnx_file
是文件名或者一个 Python 的文件或者 IO 对象。所以如果要通过 Python 脚本将 PyTorch 模型转变为 ONNX ,然后再转变为 nncase 模型,就需要使用 io.BytesIO
来产生一个“虚拟”的文件,不需要在硬盘上创建一个文件然后在转换。
转换 onnx 到 kmodel 可以使用 ncc
进行转换。转换的大致命令是:
ncc compile -i onnx -t k210 model.onnx model.kmodel --dataset images --input-shape 1,3,224,320
其中 -i
是输入的模型的类型,可以是 tflite、onnx、caffe,-t
是输出的模型的类型,可以是 k210、k510 或者 cpu。然后后面跟着的就是输出和输出的文件,--dataset
量化过程中使用的图像,然后输入尺寸是 模型输入的尺寸。
当然可以通过 python 脚本使用来转换,官方的 Demo 里面有相关的转换的脚本 https://github.com/kendryte/nncase/blob/master/examples/yolox/tools/compile.py,可以用作参考。甚至可以直接抄,毕竟这个代码里面没有 PyTorch 相关的内容。
设备端代码修改
代码里面模型在执行完成之后,是通过 kpu_get_output()
获取输出的。这个语句的大致用法如下:
kpu_get_output(&obj_detect_task, 0, (uint8_t **)&output0, &output_size0);
其中 obj_detect_task
是定义好的模型相关上下文,一般不需要修改, 第二个参数是指定输出索引的,因为一个模型可以有多个输出,并且是从 0 开始计数的。第三个地方传入的应该是一个指针的地址,这个函数会将模型跑出来的结果的地址赋给这个指针(如果这里看不懂的话,需要详细了解 C 语言及其指针),然后最后一个是一个 size_t
的指针,用来告诉用户输出的大小是多少。输出的数据通常是 uint8_t
也就是一个字节,所以尺寸也是按照字节计算的,如果输出的是浮点数,尺寸就需要除以 4,比如输出的是两个浮点数的话,size 就是 8。
输出的这个数据就是神经网络的结果,然后拿来用就可以了(需要编写其他代码来将最终代码转换成相关的结果)。
其他小坑
代码特别慢?
如果代码特别慢的话,可能有两个原因:
-
在通过 KPU 执行模型之前,输入的数据被正则化了。因为不管是 ncc 还是 python 脚本中模型转换选项中都有三个关于输入的选项(输入的范围,均值,方差)如果这三个参数被设置了的话,就会执行正则化。这个操作会特别慢,是 nncase 生成的操作,而不是我们自己写的代码。
-
模型中有一些计算并没使用 KPU,这个就需要想办法调整模型,让模型能够都跑到 KPU 上。
要怎么检查上面两个问题呢?首先是要先通过其他的方法,确定,明确,模型跑得慢,是因为执行了 kpu_run_kmodel
后,模型存在的问题,在 ncc cli 参数或者是 python 脚本参数中有一个 dump ir
相关的选项,需要将这个选项设置为 True,然后再执行的地方,就会产生一大堆中间信息。其中有用的 codegen
文件夹中的两个文件,这两个文件显示了相关的操作是怎样进行的。其中 main.sched
文件是再 CPU 上执行的,然后 k210_0.sched
是 KPU 相关的执行的内容。
这个文件的名字我这里是这么叫的,不排除 勘智 那帮人修改名称内容。
编译相关
-
如果编译速度慢,可以在 make 后面增加
-jN
来进行并行编译,其中一般建议N
是逻辑处理器数量的两倍(就比如对于 4C8T 的处理器,N建议设置为 16)。 -
对于 cmake 来说,如果要指定 C、C++ 编译器的话,在
cmake ..
初始化的时候,增加环境便来来指定编译器,比如:
CC=`which gcc-10` CXX=`which g++-10` cmake ..
- 建议采用Python 的 conda 创建一个环境来执行上述的过程。此外,conda 里面还包括 cmake 等工具,能够简化安装避免冲突。
官方代码的作用
- 官方代码里面是有使用 GitHub Action 作为 CI 的,然后在 .github 文件夹下面能找见一些脚本,以此”了解“如何对一些代码进行编译。