跳入 nncase 的大坑中

在勘智的 K210 处理器上执行深度学习的模型,需要用到官方的 nncase 和 SDK。官方的文档和代码的更新,挺迷的,尤其是对我来说。所以当要做的事情超出了开发板官方给的(过时的)、(简陋)的资料的时候,就需要自己填坑。此外勘智官方的文档也不咋地,不给寄存器相关的手册吧,nncase 之类的工具连个正儿八经的文档也没有。所以在这里整理一下将自己的 PyTorch 模型一步步处理转化到 K210 nncase 模型的过程,整理整理相关的填坑的内容。

SDK 等所需软件安装

整个代码以 PyTorch 为例,因为看到 在 github·nncase·9c0608c:/examples/yolox/tools/decoder.py 中官方在使用 pytorch。此外,使用的 K210 相关的工具都是此时(2021年11月3日)相对最新的或者是还是开发中的。

使用的如下版本:

系统用的是 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 文件,因为新的版本官方并没有提供 nccruntime 相关的压缩包(虽然 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 文件夹下面找到。

这个文件夹里面有三个文件夹 binlibinclude 把他们复制或者通过软连接的方式”复制到 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_namesoutput_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。

输出的这个数据就是神经网络的结果,然后拿来用就可以了(需要编写其他代码来将最终代码转换成相关的结果)。

其他小坑

代码特别慢?

如果代码特别慢的话,可能有两个原因:

  1. 在通过 KPU 执行模型之前,输入的数据被正则化了。因为不管是 ncc 还是 python 脚本中模型转换选项中都有三个关于输入的选项(输入的范围,均值,方差)如果这三个参数被设置了的话,就会执行正则化。这个操作会特别慢,是 nncase 生成的操作,而不是我们自己写的代码。

  2. 模型中有一些计算并没使用 KPU,这个就需要想办法调整模型,让模型能够都跑到 KPU 上。

要怎么检查上面两个问题呢?首先是要先通过其他的方法,确定,明确,模型跑得慢,是因为执行了 kpu_run_kmodel 后,模型存在的问题,在 ncc cli 参数或者是 python 脚本参数中有一个 dump ir 相关的选项,需要将这个选项设置为 True,然后再执行的地方,就会产生一大堆中间信息。其中有用的 codegen 文件夹中的两个文件,这两个文件显示了相关的操作是怎样进行的。其中 main.sched 文件是再 CPU 上执行的,然后 k210_0.sched 是 KPU 相关的执行的内容。

这个文件的名字我这里是这么叫的,不排除 勘智 那帮人修改名称内容。

编译相关

  1. 如果编译速度慢,可以在 make 后面增加 -jN 来进行并行编译,其中一般建议 N 是逻辑处理器数量的两倍(就比如对于 4C8T 的处理器,N建议设置为 16)。

  2. 对于 cmake 来说,如果要指定 C、C++ 编译器的话,在 cmake .. 初始化的时候,增加环境便来来指定编译器,比如:

CC=`which gcc-10` CXX=`which g++-10` cmake ..
  1. 建议采用Python 的 conda 创建一个环境来执行上述的过程。此外,conda 里面还包括 cmake 等工具,能够简化安装避免冲突。

官方代码的作用

  1. 官方代码里面是有使用 GitHub Action 作为 CI 的,然后在 .github 文件夹下面能找见一些脚本,以此”了解“如何对一些代码进行编译。