使用ARM Ethos-U后端构建和运行ExecuTorch¶
在这个教程中,你将学习如何导出一个简单的 PyTorch 模型以供 ExecuTorch Arm Ethos-u 后端委托使用,并在 Corstone FVP 模拟器上运行它。
警告
此 ExecuTorch 后端委托正处于积极开发中。您可能会遇到一些不完善的边缘情况和功能,这些功能可能已文档化或计划中但尚未实现。
提示
如果您已经熟悉这个委托,您可以直接跳转到示例源代码目录 - https://github.com/pytorch/executorch/tree/main/examples/arm
先决条件¶
让我们在开始之前确保你已经具备所需的一切。
硬件¶
要成功完成本教程,您需要一台基于 Linux 的主机机器,其处理器架构为 Arm aarch64 或 x86_64。
目标设备将是一个嵌入式平台,配备 Arm Cortex-M 处理器和 Ethos-U NPU(机器学习处理器)。本教程将向您展示如何在两者上运行 PyTorch 模型。
我们将使用一个固定虚拟平台(FVP),模拟Corstone-300(cs300)和Corstone-320(cs320)系统。由于我们将使用FVP(可以将其视为虚拟硬件),因此本教程不需要任何实际的嵌入式硬件。
软件¶
首先,您需要安装 ExecuTorch。如果您尚未安装,请按照推荐的教程进行操作,以设置一个可用的 ExecuTorch 开发环境。
为了生成可在嵌入式平台(真实或虚拟)上运行的软件,我们需要一个用于交叉编译的工具链以及一个包含 Vela 编译器的 Arm Ethos-U 软件开发套件,该编译器用于 Ethos-U NPU。
在以下部分中,我们将逐步介绍如何下载上述列出的每个依赖项。
设置开发环境¶
在本节中,我们将进行一次性的设置,例如下载和安装必要的软件,以支持本教程中运行 ExecuTorch 程序所需的平台支持文件。
为此,我们将使用 examples/arm/setup.sh 脚本来自动化地拉取每个项目。建议在 conda 环境中运行该脚本。成功执行后,您可以直接进入 下一步。
如前所述,我们目前仅支持基于 Linux 的平台,其处理器架构为 x86_64 或 aarch64。让我们确保我们确实处于一个受支持的平台上。
uname -s
# Linux
uname -m
# x86_64 or aarch64
接下来我们将通过 setup.sh 脚本执行的步骤来更好地理解开发环境的设置。
下载并设置Corstone-300和Corstone-320 FVP¶
固定虚拟平台(FVPs)是流行系统配置的功能性准确模拟。在此教程中,我们关注的是 Corstone-300 和 Corstone-320 系统。我们可以从 Arm 网站下载这些平台。
注意
通过下载和运行FVP软件,您将同意FVP的最终用户许可协议(EULA)。
要下载,我们可以从这里下载Corstone-300 Ecosystem FVP和Corstone-320 Ecosystem FVP。或者setup.sh脚本会在setup_fvp函数下为您完成此操作。
下载并安装 Arm GNU AArch32 原生工具链¶
与FVP类似,我们同样需要一个工具链来交叉编译ExecuTorch运行时、executor-runner裸机应用程序,以及Corstone-300/Corstone-320平台上Cortex-M55/M85 CPU可用的其余裸机栈。
这些工具链可以在 这里 找到。在我们的教程中,我们将使用针对 arm-none-eabi 的 GCC 12.3。就像 FVP 一样,setup.sh 脚本会为您下载工具链。请参阅 setup_toolchain 函数。
设置Arm Ethos-U软件开发¶
此 Git 仓库是所有 Arm Ethos-U 软件的根目录。它有助于我们下载所需的仓库并将其放置在树状结构中。有关更多详细信息,请参阅设置脚本中的 setup_ethos_u 函数。
完成这些步骤后,您应该已经拥有了一个可以正常运行的FVP模拟器、用于交叉编译的功能性工具链,以及为裸机开发准备好的Ethos-U软件开发环境。
安装 Vela 编译器¶
一旦完成,脚本将通过为您安装 Vela 编译器来完成设置,详情请参阅 setup_vela 函数。
安装TOSA参考模型¶
这是设置过程的最后一步,使用 setup_tosa_reference_model 函数 setup.sh 脚本将为您安装 TOSA 参考模型。
在设置结束时,如果一切顺利,你的顶层开发目录可能看起来像这样,
.
├── arm-gnu-toolchain-12.3.rel1-x86_64-arm-none-eabi # for x86-64 hosts
├── ethos-u
│ ├── core_platform
│ ├── core_software
│ ├── fetch_externals.py
│ └── [...]
├── ethos-u-vela
├── FVP-corstone300
│ ├── FVP_Corstone_SSE-300.sh
│ └── [...]
├── FVP-corstone320
│ ├── FVP_Corstone_SSE-320.sh
│ └── [...]
├── FVP_cs300.tgz
├── FVP_cs320.tgz
├── gcc.tar.xz
└── reference_model
将PyTorch模型转换为.pte文件¶
.pte 是由 ExecuTorch 预编译(AoT)管道生成的二进制文件,该管道接收一个 PyTorch 模型(torch.nn.Module),对其进行导出、运行各种优化步骤,最终将其序列化为 .pte 文件格式。此二进制文件通常由 ExecuTorch 运行时使用。本 文档 对 ExecuTorch 软件栈进行了更深入的介绍,涵盖了 AoT 以及运行时的相关内容。
在本节中,我们将主要关注 AoT 流程,最终目标是生成一个 .pte 文件。有一组导出配置可以在运行时针对不同的后端进行优化。对于每个配置,AoT 流程将生成一个独特的 .pte 文件。我们将探索几种不同的配置,以生成不同的 .pte 文件,特别是对于我们 Corstone-300 系统和可用的处理单元来说非常有趣。
在我们开始之前,让我们先谈谈我们将要使用的 PyTorch 模块。
PyTorch 示例模块¶
我们将使用几个简单的 PyTorch 模块来探索端到端的流程。这些模块将在教程的各个部分以不同的方式使用,并通过它们的 <class_name> 来引用。
SoftmaxModule¶
这是一个非常简单的 PyTorch 模块,只包含一个 Softmax 操作符。
import torch
class SoftmaxModule(torch.nn.Module):
def __init__(self):
super().__init__()
self.softmax = torch.nn.Softmax()
def forward(self, x):
z = self.softmax(x)
return z
在相同的开发 Linux 机器上使用 Python 环境运行它,我们得到了预期的输出。
>>> m = SoftmaxModule()
>>> m(torch.ones(2,2))
tensor([[0.5000, 0.5000],
[0.5000, 0.5000]])
AddModule¶
让我们编写另一个简单的 PyTorch 模块,其中只有一个 Add 操作符。
class AddModule(torch.nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return x + x
在相同的开发 Linux 机器上使用 Python 环境运行它,如预期一样,1 + 1 确实产生 2。
>>> m = AddModule()
>>> m(torch.ones(5, dtype=torch.int32)) # integer types for non-quantized Ethos-U delegation
tensor([2, 2, 2, 2, 2], dtype=torch.int32)
请记住这些模块的输入和输出。当我们将它们转换为小写并通过其他方式运行,而不是在这台 Linux 机器上运行时,我们将使用相同的输入,并期望输出与此处显示的一致。
提示
我们需要了解数据类型,以便在 Ethos-U55 上运行网络,因为这是一个仅支持整数的处理器。在本例中,我们显式地使用整数类型。对于此类流的典型使用,网络通常是在浮点数上构建和训练的,然后从浮点数量化为整数,以实现高效的推理。
MobileNetV2 模块¶
MobileNetV2 是一种常用于边缘和移动设备的生产级网络。 它也作为 torchvision 的默认模型可用,因此我们可以使用下面的示例代码加载它。
from torchvision.models import mobilenet_v2 # @manual
from torchvision.models.mobilenetv2 import MobileNet_V2_Weights
mv2 = mobilenet_v2(weights=MobileNet_V2_Weights.DEFAULT)
有关更多详细信息,您可以参考代码片段 此处。
非委托工作流¶
在 ExecuTorch AoT 管道中,其中一个选项是选择后端。ExecuTorch 提供了多种不同的后端。选择后端是可选的,通常是为了针对特定的加速模式或硬件来满足给定模型的计算需求。如果没有选择任何后端,ExecuTorch 运行时将回退到使用默认可用的、高度便携的一组运算符。
在具有专用加速器的平台(如 Ethos-U55)上,预期非委托流程将用于两种主要情况:
当网络设计得非常小,最适合仅在Cortex-M上运行时。
当网络包含既可以针对NPU执行又不能针对NPU执行的操作时,例如Ethos-U55支持整数运算,因此浮点数softmax将回退到CPU上执行。
在此流程中,不使用任何后端委托,以说明 ExecuTorch 运行时以及我们跳过在 .pte 生成期间指定后端的操作符库的可移植性。
以下脚本将作为辅助工具,帮助我们生成 .pte 文件。该脚本位于 examples/arm 目录中。
python3 -m examples.arm.aot_arm_compiler --model_name="softmax"
# This should produce ./softmax.pte
委托工作流¶
与Arm合作,我们引入了一个新的Arm后端委托用于ExecuTorch。此后端正处于积极开发中,并且在撰写本文时只有一小部分功能可用。
在 ExecuTorch AOT 导出管道中包含一个后续步骤以生成 .pte 文件,我们可以启用此后端代理。
from executorch.backends.arm.arm_backend import generate_ethosu_compile_spec
graph_module_edge.exported_program = to_backend(
model.exported_program,
ArmPartitioner(generate_ethosu_compile_spec("ethos-u55-128")))
类似于非委托流程,相同的脚本将作为辅助工具帮助我们生成 .pte 文件。请注意启用 to_backend 调用的 --delegate 选项。
python3 -m examples.arm.aot_arm_compiler --model_name="add" --delegate
# should produce ./add_arm_delegate.pte
委托量化工作流¶
在为MobileNetV2等委托量化网络生成.pte文件之前,我们需要构建quantized_ops_aot_lib
SITE_PACKAGES="$(python3 -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib())')"
CMAKE_PREFIX_PATH="${SITE_PACKAGES}/torch"
cd <executorch_root_dir>
mkdir -p cmake-out-aot-lib
cmake -DCMAKE_BUILD_TYPE=Release \
-DEXECUTORCH_BUILD_XNNPACK=OFF \
-DEXECUTORCH_BUILD_KERNELS_QUANTIZED=ON \
-DEXECUTORCH_BUILD_KERNELS_QUANTIZED_AOT=ON \
-DCMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH" \
-DPYTHON_EXECUTABLE=python3 \
-Bcmake-out-aot-lib \
"${et_root_dir}"
cmake --build cmake-out-aot-lib --parallel -- quantized_ops_aot_lib
在 quantized_ops_aot_lib 构建之后,我们可以运行以下脚本来生成 .pte 文件
python3 -m examples.arm.aot_arm_compiler --model_name="mv2" --delegate --quantize --so_library="$(find cmake-out-aot-lib -name libquantized_ops_aot_lib.so)"
# should produce ./mv2_arm_delegate.pte.pte
在最后,我们应该有三个不同的 .pte 文件。
第一个包含 SoftmaxModule,没有任何后端代理。
第二个包含带有启用 Arm Ethos-U 后端委托的 AddModule。
第三个包含 量化 MV2Model,并且启用了 Arm Ethos-U 后端代理。
现在让我们尝试在Corstone-300和Corstone-320平台上以裸机环境运行这些.pte个文件。
获取裸金属可执行文件¶
在本节中,我们将介绍构建运行时应用程序所需的步骤。然后在目标设备上运行该应用程序。在executorch仓库中,我们有一个功能正常的脚本,它执行完全相同的步骤。它位于executorch/examples/arm/run.sh。我们将使用它来构建必要的组件,并最终在FVP上运行之前生成的PTE文件。
在我们开始之前,请确保您已完成 ExecuTorch cmake 构建设置,并按照 前面 描述的说明设置了开发环境。
下图展示了在高层次上,各种构建产物是如何生成并相互链接以形成最终的裸机可执行文件的。
提示
generate_pte_file 函数在 run.sh 脚本中根据通过 --model_name 输入参数提供的模型生成 .pte 文件
生成 ExecuTorch 库¶
ExecuTorch 的 CMake 构建系统生成一组构建组件,这些组件对我们来说至关重要,以便在用于 Ethos-U SDK 的 Corstone FVPs 的裸机环境中包含并运行 ExecuTorch 运行时。
本文档提供了对每个单独构建组件的详细概述。要运行 .pte 文件的任一变体,我们需要一组核心库。以下是列表,
libexecutorch.alibportable_kernels.alibportable_ops_lib.a
要使用 Arm 后端委托运行一个 .pte 文件,我们需要 Arm 后端委托运行时库,即,
libexecutorch_delegate_ethos_u.a
这些库是在 build_executorch 和 build_quantization_aot_lib 函数以及 run.sh 脚本中生成的。
在此函数中,EXECUTORCH_SELECT_OPS_LIST 将决定构建中包含的可移植操作符的数量,并在运行时可用。它必须与 .pte 文件的要求相匹配,否则您将在运行时收到 Missing Operator 错误。
例如,在上面的命令行中,要运行SoftmaxModule,我们只包含了softmax CPU运算符。同样,以非委托方式运行AddModule时,您需要添加运算符等。如您可能已经意识到的,对于将由Arm后端委托执行的委托运算符,我们不需要将这些运算符包含在此列表中。这仅适用于非委托运算符。
提示
该 run.sh 脚本接受 --portable_kernels 个选项,用于提供一种方法来传入一个逗号分隔的可移植内核列表以供包含。
构建executor_runner裸机应用程序¶
SDK 目录与之前准备的目录相同 之前。我们将传递上面生成的任意一个文件(例如 .pte 文件)。
注意,如果您想更改模型或.pte文件,则必须生成一个新的executor-runner二进制文件。此限制来自我们为Corstone-300/Corstone-320平台提供的受限裸机运行时环境。
这是由 build_executorch_runner 函数在 run.sh 中完成的。
提示
run.sh 脚本接受 --target 个选项,用于指定一个特定的目标,即 Corstone-300(ethos-u55-128) 或 Corstone-320(ethos-u85-128)。
在Corstone FVP平台上运行¶
一旦精灵准备就绪,无论使用哪种 .pte 文件变体生成裸金属精灵。以下命令用于在 Corstone-320 FVP 上运行 MV2Model
ethos_u_build_dir=examples/arm/executor_runner/
elf=$(find ${ethos_u_build_dir} -name "arm_executor_runner")
FVP_Corstone_SSE-320_Ethos-U85 \
-C mps4_board.subsystem.ethosu.num_macs=${num_macs} \
-C mps4_board.visualisation.disable-visualisation=1 \
-C vis_hdlcd.disable_visualisation=1 \
-C mps4_board.telnetterminal0.start_telnet=0 \
-C mps4_board.uart0.out_file='-' \
-C mps4_board.uart0.shutdown_on_eot=1 \
-a "${elf}" \
--timelimit 120 || true # seconds- after which sim will kill itself
如果成功,模拟器应在 shell 中生成类似以下的内容:
I [executorch:arm_executor_runner.cpp:364] Model in 0x70000000 $
I [executorch:arm_executor_runner.cpp:366] Model PTE file loaded. Size: 4425968 bytes.
I [executorch:arm_executor_runner.cpp:376] Model buffer loaded, has 1 methods
I [executorch:arm_executor_runner.cpp:384] Running method forward
I [executorch:arm_executor_runner.cpp:395] Setup Method allocator pool. Size: 62914560 bytes.
I [executorch:arm_executor_runner.cpp:412] Setting up planned buffer 0, size 752640.
I [executorch:ArmBackendEthosU.cpp:79] ArmBackend::init 0x70000070
I [executorch:arm_executor_runner.cpp:445] Method loaded.
I [executorch:arm_executor_runner.cpp:447] Preparing inputs...
I [executorch:arm_executor_runner.cpp:461] Input prepared.
I [executorch:arm_executor_runner.cpp:463] Starting the model execution...
I [executorch:ArmBackendEthosU.cpp:118] ArmBackend::execute 0x70000070
I [executorch:ArmBackendEthosU.cpp:298] Tensor input/output 0 will be permuted
I [executorch:arm_perf_monitor.cpp:120] NPU Inferences : 1
I [executorch:arm_perf_monitor.cpp:121] Profiler report, CPU cycles per operator:
I [executorch:arm_perf_monitor.cpp:125] ethos-u : cycle_cnt : 1498202 cycles
I [executorch:arm_perf_monitor.cpp:132] Operator(s) total: 1498202 CPU cycles
I [executorch:arm_perf_monitor.cpp:138] Inference runtime: 6925114 CPU cycles total
I [executorch:arm_perf_monitor.cpp:140] NOTE: CPU cycle values and ratio calculations require FPGA and identical CPU/NPU frequency
I [executorch:arm_perf_monitor.cpp:149] Inference CPU ratio: 99.99 %
I [executorch:arm_perf_monitor.cpp:153] Inference NPU ratio: 0.01 %
I [executorch:arm_perf_monitor.cpp:162] cpu_wait_for_npu_cntr : 729 CPU cycles
I [executorch:arm_perf_monitor.cpp:167] Ethos-U PMU report:
I [executorch:arm_perf_monitor.cpp:168] ethosu_pmu_cycle_cntr : 5920305
I [executorch:arm_perf_monitor.cpp:171] ethosu_pmu_cntr0 : 359921
I [executorch:arm_perf_monitor.cpp:171] ethosu_pmu_cntr1 : 0
I [executorch:arm_perf_monitor.cpp:171] ethosu_pmu_cntr2 : 0
I [executorch:arm_perf_monitor.cpp:171] ethosu_pmu_cntr3 : 503
I [executorch:arm_perf_monitor.cpp:178] Ethos-U PMU Events:[ETHOSU_PMU_EXT0_RD_DATA_BEAT_RECEIVED, ETHOSU_PMU_EXT1_RD_DATA_BEAT_RECEIVED, ETHOSU_PMU_EXT0_WR_DATA_BEAT_WRITTEN, ETHOSU_PMU_NPU_IDLE]
I [executorch:arm_executor_runner.cpp:470] model_pte_loaded_size: 4425968 bytes.
I [executorch:arm_executor_runner.cpp:484] method_allocator_used: 1355722 / 62914560 free: 61558838 ( used: 2 % )
I [executorch:arm_executor_runner.cpp:491] method_allocator_planned: 752640 bytes
I [executorch:arm_executor_runner.cpp:493] method_allocator_loaded: 966 bytes
I [executorch:arm_executor_runner.cpp:494] method_allocator_input: 602116 bytes
I [executorch:arm_executor_runner.cpp:495] method_allocator_executor: 0 bytes
I [executorch:arm_executor_runner.cpp:498] temp_allocator_used: 0 / 1048576 free: 1048576 ( used: 0 % )
I executorch:arm_executor_runner.cpp:152] Model executed successfully.
I executorch:arm_executor_runner.cpp:156] 1 outputs:
Output[0][0]: -0.749744
Output[0][1]: -0.019224
Output[0][2]: 0.134570
...(Skipped)
Output[0][996]: -0.230691
Output[0][997]: -0.634399
Output[0][998]: -0.115345
Output[0][999]: 1.576386
I executorch:arm_executor_runner.cpp:177] Program complete, exiting.
I executorch:arm_executor_runner.cpp:179]
注意
run.sh 脚本提供了多种选项,用于选择特定的 FVP 目标、使用所需的模型、选择可移植内核,并可以通过 --help 参数进行探索。
要点¶
通过本教程,我们学习了如何使用 ExecuTorch 软件将标准模型从 PyTorch 导出,并在紧凑且功能完整的 ExecuTorch 运行时上运行该模型,从而实现从 PyTorch 到基于 Arm 平台的模型卸载的顺畅路径。
简要总结,主要有两种主要流程:
一种直接流程,通过内置在 ExecuTorch 中的库将工作卸载到 Cortex-M。
一种委托流程,将图划分为适用于Cortex-M的部分以及可以卸载并在Ethos-U硬件上加速的部分。
这两种流程仍在不断发展,使更多用例成为可能并实现更好的性能。