跳到主要内容
版本:0.13.0
备注

单击 此处 下载完整的示例代码

8. 创建使用 microTVM 的 MLPerfTiny 提交

作者:Mehrdad Hessar

本教程展示了如何使用 microTVM 构建 MLPerfTiny 提交。该教程演示了从 MLPerfTiny 基准模型中导入一个 TFLite 模型,使用 TVM 进行编译,并生成一个可以刷写到支持 Zephyr 的板上的 Zephyr 项目,以使用 EEMBC runner 对模型进行基准测试的步骤。

安装 microTVM Python 依赖项​

TVM 不包含用于 Python 串行通信的软件包,因此在使用 microTVM 之前,我们必须安装它。我们还需要 TFLite 来加载模型。

pip install pyserial==3.5 tflite==2.1
import os
import pathlib
import tarfile
import tempfile
import shutil

安装 Zephyr​

# 安装 west 和 ninja
python3 -m pip install west
apt-get install -y ninja-build

# 安装 ZephyrProject
ZEPHYR_PROJECT_PATH="/content/zephyrproject"
export ZEPHYR_BASE=${ZEPHYR_PROJECT_PATH}/zephyr
west init ${ZEPHYR_PROJECT_PATH}
cd ${ZEPHYR_BASE}
git checkout v3.2-branch
cd ..
west update
west zephyr-export
chmod -R o+w ${ZEPHYR_PROJECT_PATH}

# 安装 Zephyr SDK
cd /content
ZEPHYR_SDK_VERSION="0.15.2"
wget "https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v${ZEPHYR_SDK_VERSION}/zephyr-sdk-${ZEPHYR_SDK_VERSION}_linux-x86_64.tar.gz"
tar xvf "zephyr-sdk-${ZEPHYR_SDK_VERSION}_linux-x86_64.tar.gz"
mv "zephyr-sdk-${ZEPHYR_SDK_VERSION}" zephyr-sdk
rm "zephyr-sdk-${ZEPHYR_SDK_VERSION}_linux-x86_64.tar.gz"

# 安装 Python 依赖项
python3 -m pip install -r "${ZEPHYR_BASE}/scripts/requirements.txt"

注意:仅在您有意使用 CMSIS-NN 代码生成器生成此提交时安装 CMSIS-NN。

安装 Install CMSIS-NN​

CMSIS_SHA="51263182d16c92649a48144ba56c0945f9fce60e"
CMSIS_URL="http://github.com/ARM-software/CMSIS_5/archive/${CMSIS_SHA}.tar.gz"
export CMSIS_PATH=/content/cmsis
DOWNLOAD_PATH="/content/${CMSIS_SHA}.tar.gz"
mkdir ${CMSIS_PATH}
wget ${CMSIS_URL} -O "${DOWNLOAD_PATH}"
tar -xf "${DOWNLOAD_PATH}" -C ${CMSIS_PATH} --strip-components=1
rm ${DOWNLOAD_PATH}

CMSIS_NN_TAG="v4.0.0"
CMSIS_NN_URL="https://github.com/ARM-software/CMSIS-NN.git"
git clone ${CMSIS_NN_URL} --branch ${CMSIS_NN_TAG} --single-branch ${CMSIS_PATH}/CMSIS-NN

导入 Python 依赖​

import tensorflow as tf
import numpy as np

import tvm
from tvm import relay
from tvm.relay.backend import Executor, Runtime
from tvm.contrib.download import download_testdata
from tvm.micro import export_model_library_format
import tvm.micro.testing
from tvm.micro.testing.utils import (
create_header_file,
mlf_extract_workspace_size_bytes,
)

导入 Visual Wake Word Model​

首先,从 MLPerfTiny 下载并导入 Visual Wake Word (VWW) TFLite 模型。该模型最初来自 MLPerf Tiny 仓库。我们还捕获了来自 TFLite 模型的元数据信息,如输入/输出名称、量化参数等,这些信息将在接下来的步骤中使用。

我们使用索引来构建各种模型的提交。索引定义如下:要构建另一个模型,您需要更新模型 URL、简短名称和索引号。

关键词识别(KWS)1

视觉唤醒词(VWW)2

异常检测(AD)3

图像分类(IC)4

如果您想要使用 CMSIS-NN 构建提交,请修改 USE_CMSIS 环境变量。

export USE_CMSIS=1
MODEL_URL = "https://github.com/mlcommons/tiny/raw/bceb91c5ad2e2deb295547d81505721d3a87d578/benchmark/training/visual_wake_words/trained_models/vww_96_int8.tflite"
MODEL_PATH = download_testdata(MODEL_URL, "vww_96_int8.tflite", module="model")

MODEL_SHORT_NAME = "VWW"
MODEL_INDEX = 2

USE_CMSIS = os.environ.get("TVM_USE_CMSIS", False)

tflite_model_buf = open(MODEL_PATH, "rb").read()
try:
import tflite

tflite_model = tflite.Model.GetRootAsModel(tflite_model_buf, 0)
except AttributeError:
import tflite.Model

tflite_model = tflite.Model.Model.GetRootAsModel(tflite_model_buf, 0)

interpreter = tf.lite.Interpreter(model_path=str(MODEL_PATH))
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

input_name = input_details[0]["name"]
input_shape = tuple(input_details[0]["shape"])
input_dtype = np.dtype(input_details[0]["dtype"]).name
output_name = output_details[0]["name"]
output_shape = tuple(output_details[0]["shape"])
output_dtype = np.dtype(output_details[0]["dtype"]).name

# 从 TFLite 模型中提取量化信息。
# 除了异常检测模型外,所有其他模型都需要这样做,
# 因为对于其他模型,我们从主机发送量化数据到解释器,
# 然而,对于异常检测模型,我们发送浮点数据,量化信息
# 在微控制器上进行。
if MODEL_SHORT_NAME != "AD":
quant_output_scale = output_details[0]["quantization_parameters"]["scales"][0]
quant_output_zero_point = output_details[0]["quantization_parameters"]["zero_points"][0]

relay_mod, params = relay.frontend.from_tflite(
tflite_model, shape_dict={input_name: input_shape}, dtype_dict={input_name: input_dtype}
)

定义目标、运行时和执行器

现在我们需要定义目标、运行时和执行器来编译这个模型。在本教程中,我们使用预先编译(Ahead-of-Time,AoT)进行编译,并构建一个独立的项目。这与使用主机驱动模式的 AoT 不同,其中目标会使用主机驱动的 AoT 执行器与主机通信以运行推理。

# 使用 C 运行时 (crt)
RUNTIME = Runtime("crt")

# 使用带有 `unpacked-api=True` 和 `interface-api=c` 的 AoT 执行器。`interface-api=c` 强制
# 编译器生成 C 类型的函数 API,而 `unpacked-api=True` 强制编译器生成最小的未打包格式输入,
# 这减少了调用模型推理层时的堆栈内存使用。
EXECUTOR = Executor(
"aot",
{"unpacked-api": True, "interface-api": "c", "workspace-byte-alignment": 8},
)

# 选择一个 Zephyr 板
BOARD = os.getenv("TVM_MICRO_BOARD", default="nucleo_l4r5zi")

# 使用 BOARD 获取完整的目标描述
TARGET = tvm.micro.testing.get_target("zephyr", BOARD)

编译模型并导出模型库格式

现在,我们为目标编译模型。然后,我们为编译后的模型生成模型库格式。我们还需要计算编译后的模型所需的工作空间大小。

config = {"tir.disable_vectorize": True}
if USE_CMSIS:
from tvm.relay.op.contrib import cmsisnn

config["relay.ext.cmsisnn.options"] = {"mcpu": TARGET.mcpu}
relay_mod = cmsisnn.partition_for_cmsisnn(relay_mod, params, mcpu=TARGET.mcpu)

with tvm.transform.PassContext(opt_level=3, config=config):
module = tvm.relay.build(
relay_mod, target=TARGET, params=params, runtime=RUNTIME, executor=EXECUTOR
)

temp_dir = tvm.contrib.utils.tempdir()
model_tar_path = temp_dir / "model.tar"
export_model_library_format(module, model_tar_path)
workspace_size = mlf_extract_workspace_size_bytes(model_tar_path)

生成输入/输出头文件

为了使用 AoT 创建 microTVM 独立项目,我们需要生成输入和输出头文件。这些头文件用于将生成的代码中的输入和输出 API 与独立项目的其余部分连接起来。对于此特定提交,我们只需要生成输出头文件,因为输入 API 调用是以不同的方式处理的。

extra_tar_dir = tvm.contrib.utils.tempdir()
extra_tar_file = extra_tar_dir / "extra.tar"

with tarfile.open(extra_tar_file, "w:gz") as tf:
create_header_file(
"output_data",
np.zeros(
shape=output_shape,
dtype=output_dtype,
),
"include/tvm",
tf,
)

创建项目、构建并准备项目 tar 文件

现在我们有了编译后的模型作为模型库格式,可以使用 Zephyr 模板项目生成完整的项目。首先,我们准备项目选项,然后构建项目。最后,我们清理临时文件并将提交项目移动到当前工作目录,可以在开发套件上下载并使用。

input_total_size = 1
for i in range(len(input_shape)):
input_total_size *= input_shape[i]

template_project_path = pathlib.Path(tvm.micro.get_microtvm_template_projects("zephyr"))
project_options = {
"extra_files_tar": str(extra_tar_file),
"project_type": "mlperftiny",
"board": BOARD,
"compile_definitions": [
f"-DWORKSPACE_SIZE={workspace_size + 512}", # Memory workspace size, 512 is a temporary offset
# since the memory calculation is not accurate.
f"-DTARGET_MODEL={MODEL_INDEX}", # Sets the model index for project compilation.
f"-DTH_MODEL_VERSION=EE_MODEL_VERSION_{MODEL_SHORT_NAME}01", # Sets model version. This is required by MLPerfTiny API.
f"-DMAX_DB_INPUT_SIZE={input_total_size}", # Max size of the input data array.
],
}

if MODEL_SHORT_NAME != "AD":
project_options["compile_definitions"].append(f"-DOUT_QUANT_SCALE={quant_output_scale}")
project_options["compile_definitions"].append(f"-DOUT_QUANT_ZERO={quant_output_zero_point}")

if USE_CMSIS:
project_options["compile_definitions"].append(f"-DCOMPILE_WITH_CMSISNN=1")

# 注意:根据您使用的板子可能需要调整这个值。
project_options["config_main_stack_size"] = 4000

if USE_CMSIS:
project_options["cmsis_path"] = os.environ.get("CMSIS_PATH", "/content/cmsis")

generated_project_dir = temp_dir / "project"

project = tvm.micro.project.generate_project_from_mlf(
template_project_path, generated_project_dir, model_tar_path, project_options
)
project.build()

# 清理构建目录和额外的工件
shutil.rmtree(generated_project_dir / "build")
(generated_project_dir / "model.tar").unlink()

project_tar_path = pathlib.Path(os.getcwd()) / "project.tar"
with tarfile.open(project_tar_path, "w:tar") as tar:
tar.add(generated_project_dir, arcname=os.path.basename("project"))

print(f"The generated project is located here: {project_tar_path}")

使用此项目与您的板子

既然我们有了生成的项目,您可以在本地使用该项目将板子刷写并准备好运行 EEMBC runner 软件。要执行此操作,请按照以下步骤操作:

tar -xf project.tar
cd project
mkdir build
cmake ..
make -j2
west flash

现在,您可以按照这些说明将您的板子连接到 EEMBC runner 并在您的板子上对此模型进行基准测试。

下载 Python 源代码:micro_mlperftiny.py

下载 Jupyter notebook:micro_mlperftiny.ipynb