Arm Ethos-U 后端教程¶
在本教程中,您将学习如何为一个简单的 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 CPU 和 Ethos-U NPU(机器学习处理器)的嵌入式平台。本教程将向您展示如何在两者上运行 PyTorch 模型。
我们将使用 固定虚拟平台 (FVP),模拟 Corstone-300 (cs300) 和 Corstone-320 (cs320) 系统。由于我们将使用 FVP(可以将其视为虚拟硬件),因此本教程不需要任何实际的嵌入式硬件。
软件¶
首先,您需要安装 ExecuTorch。如果您尚未安装,请按照推荐的教程进行设置,以建立一个可用的 ExecuTorch 开发环境。
要生成可在嵌入式平台(实际或虚拟)上运行的软件,我们需要一个用于交叉编译的工具链和一个 Arm Ethos-U 软件开发套件,其中包括用于 Ethos-U NPU 的 Vela 编译器。
在接下来的部分,我们将逐步介绍下载上述每个依赖项的步骤。
设置开发者环境¶
在本节中,我们将进行一次性设置,例如下载和安装运行此教程中的 ExecuTorch 程序所需的平台支持文件。
为此,我们将使用 `examples/arm/setup.sh` 脚本以自动化方式拉取每个项目。建议在 conda 环境中运行脚本。
examples/arm/setup.sh --i-agree-to-the-contained-eula
成功执行后,您可以直接进入下一步。
如前所述,我们目前仅支持基于 Linux 的平台,其处理器架构为 x86_64 或 aarch64。让我们确保我们确实在支持的平台上。
uname -s
# Linux
uname -m
# x86_64 or aarch64
接下来,我们将逐步介绍 `setup.sh` 脚本执行的步骤,以便更好地理解开发设置。
下载并设置 Corstone-300 和 Corstone-320 FVP¶
固定虚拟平台 (FVP) 是对流行系统配置的预配置、功能准确的模拟。在本教程中,我们感兴趣的是 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 的其余裸金属堆栈。
这些工具链可在此处找到。在本教程中,我们将使用 GCC 13.3.rel1,目标为 `arm-none-eabi`。与 FVP 一样,`setup.sh` 脚本也会为您下载工具链。请参阅 `setup_toolchain` 函数。
设置 Arm Ethos-U 软件开发¶
此 git 仓库是所有 Arm Ethos-U 软件的根目录。它有助于我们下载所需的存储库并将它们放置在树状结构中。有关更多详细信息,请参阅 setup 脚本的 `setup_ethos_u` 函数。
完成此操作后,您应该拥有一个可用的 FVP 模拟器、一个可用于交叉编译的功能性工具链,以及一个为裸金属开发准备好的 Ethos-U 软件开发设置。
安装 Vela 编译器¶
完成后,脚本将通过为您安装 Vela 编译器来完成设置,详细信息请参阅 `setup_vela` 函数。
安装 TOSA 参考模型¶
这是设置过程的最后一步,使用 `setup_tosa_reference_model` 函数 `setup.sh` 脚本将为您安装 TOSA 参考模型。
设置完成后,如果一切顺利,您的顶层开发目录可能看起来像这样,
.
├── arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi # for x86-64 hosts
├── arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi.tar.xz
├── ethos-u
│ ├── core_platform
│ ├── core_software
│ ├── fetch_externals.py
│ └── [...]
├── FVP-corstone300
│ ├── FVP_Corstone_SSE-300.sh
│ └── [...]
├── FVP-corstone320
│ ├── FVP_Corstone_SSE-320.sh
│ └── [...]
├── FVP_corstone300.tgz
├── FVP_corstone320.tgz
└── setup_path.sh
注意事项:¶
`setup.sh` 脚本生成了一个 `setup_path.sh` 脚本,您每次重新启动 shell 时都需要对其进行 source(导入)。
例如,运行 `source executorch/examples/arm/ethos-u-scratch/setup_path.sh`
由于 `setup.sh` 会下载并设置所需的 Arm 工具链,请确保通过调用
`which arm-none-eabi-gcc`
来使用它。它应该在 `executorch` 项目中显示 `arm-none-eabi-gcc`,而不是 ` /usr/bin` 中的任何内容,类似于
`<EXECUTORCH_ROOT>/examples/arm/ethos-u-scratch/arm-gnu-toolchain-13.3.rel1-aarch64-arm-none-eabi/bin/arm-none-eabi-gcc` 或 `<EXECUTORCH_ROOT>/examples/arm/ethos-u-scratch/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc`
否则,您可能需要卸载 `arm-none-eabi-gcc` 或确保在您的 $PATH 环境变量中,它被排在项目中的 `arm-none-eabi-gcc` 之后。
将 PyTorch 模型转换为 `.pte` 文件¶
`.pte` 是 ExecuTorch Ahead-of-Time (AoT) 管道生成的二进制文件,它接收 PyTorch 模型(一个 torch.nn.Module),导出它,运行各种传递,最后将其序列化为 `.pte` 文件格式。此二进制文件通常由 ExecuTorch Runtime 使用。此文档更深入地介绍了 ExecuTorch 的 AoT 和 Runtime 软件堆栈。
在本节中,我们将主要关注 AoT 流程,最终目标是生成一个 `.pte` 文件。有一系列导出配置可用于在运行时定位不同的后端。对于每个配置,AoT 流程将生成一个唯一的 `.pte` 文件。我们将探索几种不同的配置,生成不同的 `.pte` 文件,这对于我们的 Corstone-300 系统和可用的处理元素特别有用。
在开始之前,让我们先讨论我们将要使用的 PyTorch 模块。
PyTorch 示例模块¶
我们将使用几个简单的 PyTorch 模块来探索端到端流程。这些模块将在整个教程中以各种不同的方式使用,并根据它们的 `
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
在 Python 环境中(在同一个开发 Linux 机器上)运行它,您将获得预期的输出。
>>> 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
在 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-U 上运行网络的**数据类型**,因为它是一个仅限整数的协处理器。对于这个例子,您显式地使用整数类型;对于此类流程的典型用法,网络是在浮点数下构建和训练的,然后从浮点数量化为整数以进行高效推理。
MobileNetV2 Module¶
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_arm_ethos-u55-128.pte
代理工作流¶
与 Arm 合作,您为 ExecuTorch 引入了一个新的 Arm 后端代理。此代理目前处于积极开发阶段,截至本文撰写时,其功能集有限。
通过在 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` 文件。注意 `--delegate` 选项用于启用 `to_backend` 调用。
python3 -m examples.arm.aot_arm_compiler --model_name="add" --delegate
# should produce ./add_arm_delegate_ethos-u55-128.pte
代理量化工作流¶
可以使用 aot_arm_compiler 来生成 `.pte` 文件。
python3 -m examples.arm.aot_arm_compiler --model_name="mv2" --delegate --quantize
# should produce ./mv2_arm_delegate_ethos-u55-128.pte
完成后,您应该有三个不同的 `.pte` 文件。
第一个包含 SoftmaxModule,没有后端代理。
第二个包含 AddModule,启用了 Arm Ethos-U 后端代理。
第三个包含量化 MV2Model,也启用了 Arm Ethos-U 后端代理。
现在,让我们尝试在裸金属环境中,在 Corstone-300 和 Corstone-320 平台上运行这些 `.pte` 文件。
获取裸金属可执行文件¶
在本节中,您将了解构建运行时应用程序所需的步骤。然后将其运行在目标设备上。在 executorch 存储库中,有一个功能性脚本可以执行完全相同的步骤。它位于 `executorch/examples/arm/run.sh`。您将使用它来构建必要的组件,并最终在 FVP 上运行先前生成的 PTE 文件。
默认情况下,`run.sh` 将使用 `arm_test/` 作为构建和输出文件夹,您将在其中找到构建的工件。如果需要,可以使用 `--et_build_root` 和 `--output` 标志来控制/覆盖此设置。
例如,运行 `examples/arm/run.sh --model_name=add --target=ethos-u85-128` 将生成如下的 pte 和 elf 文件。
arm_test/add/add_arm_delegate_ethos-u85-128.pte
arm_test/add/cmake-out/arm_executor_runner
另外,在开始之前,请确保您已完成 ExecuTorch cmake 构建设置,以及前面描述的开发环境设置说明。
下面的框图在高级别上演示了各种构建工件是如何生成并链接在一起以生成最终的裸金属可执行文件的。
提示
`run.sh` 脚本中的 `generate_pte_file` 函数根据 `--model_name` 输入参数提供的模型生成 `.pte` 文件。
生成 ExecuTorch 库¶
ExecuTorch 的 CMake 构建系统会生成一组构建组件,这些组件对于在 Ethos-U SDK 的 Corstone FVPs 的裸金属环境中构建 ExecuTorch 运行时至关重要。
本文档提供了每个单独构建组件的详细概述。要运行 `.pte` 文件的任何一个变体,您都需要一套核心库。这是列表,
libexecutorch.a
libportable_kernels.a
libportable_ops_lib.a
要运行包含 Arm 后端代理调用指令的 `.pte` 文件,您需要 Arm 后端代理运行时库,即,
libexecutorch_delegate_ethos_u.a
这些库由 `backends/arm/scripts/build_executorch.sh` 和 `backends/arm/scripts/build_portable_kernels.sh` 脚本生成,后者由 `run.sh` 脚本调用。
`--portable_kernels` 标志可用于在运行 `backends/arm/scripts/build_portable_kernels.sh` 时设置构建标志 `EXECUTORCH_SELECT_OPS_LIST`,这将决定构建中包含多少可移植算子并在运行时可用。它必须与 `.pte` 文件的要求匹配,否则您将在运行时收到 `Missing Operator` 错误。
例如,在上面的命令行中,要运行 SoftmaxModule,您只包含了 softmax CPU 算子。类似地,要以非代理方式运行 AddModule,您需要 add op 等。正如您可能已经意识到的那样,对于将由 Arm 后端代理执行的代理算子,您不需要将这些算子包含在此列表中。这仅适用于*非代理*算子。
构建 executor_runner 裸金属应用程序¶
SDK 目录与前面准备的相同。并且,您将传递上面生成的 `.pte` 文件(任意一个)。
请注意,如果您想更改模型或 `.pte` 文件,则必须生成一个新的 `executor-runner` 二进制文件。这是 Corstone-300/Corstone-320 平台上的受限裸金属运行时环境的约束。
此步骤由 build_executor_runner.sh 脚本执行,该脚本由 backends/arm/scripts 中的 run.sh 调用。
提示
`run.sh` 脚本接受 `--target` 选项,该选项提供了一种提供特定目标(Corstone-300 (ethos-u55-128) 或 Corstone-320 (ethos-u85-128))的方法。
在 Corstone FVP 平台上运行¶
一旦 elf 文件准备好,无论使用哪个 `.pte` 文件变体来生成裸金属 elf,`run.sh` 都将通过 `backends/arm/scripts/run_fvp.sh` 脚本为您运行 FVP。
自动 FVP 选择¶
要使用编译器标志和目标运行特定的测试模型
./run.sh --model_name=mv2 --delegate --quantize --target=ethos-u85-128
要运行特定的测试模型和目标
./run.sh --model_name=mv2 --delegate --target=ethos-u85-128
要以迭代循环方式运行所有测试模型,只需运行
./run.sh
请注意,您可以串联使用 `build_executor_runner.sh` 和 `run_fvp.sh` 脚本,方法是传递相关的 --target 参数(例如,--target=ethos-u55-128),系统将自动选择正确的 FVP 二进制文件。有关更多详细信息,请参阅运行时集成部分。
手动 FVP 二进制选择¶
如果您为 Ethos delegate U55/U65 目标构建(例如,使用 `--target=ethos-u55-128` 或 `--target=ethos-u65-256` 配合 `build_executor_runner.sh` 和 `run_fvp.sh`),您应该使用相应的 FVP 二进制文件。
对于 U55
examples/arm/ethos-u-scratch/FVP-corstone300/models/Linux64_GCC-9.3/FVP_Corstone_SSE-300_Ethos-U55
对于 U65
examples/arm/ethos-u-scratch/FVP-corstone300/models/Linux64_GCC-9.3/FVP_Corstone_SSE-300_Ethos-U65
并且,如果您不为 Ethos 目标构建,请使用
examples/arm/ethos-u-scratch/FVP-corstone320/models/Linux64_GCC-9.3/FVP_Corstone_SSE-320
以下是一个使用示例
ethos_u_build_dir=examples/arm/executor_runner/
elf=$(find ${ethos_u_build_dir} -name "arm_executor_runner")
FVP_Corstone_SSE-320 \
-C mps4_board.subsystem.ethosu.num_macs=128 \
-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
验证 FVP 执行成功¶
运行 FVP 命令后,无论是自动还是手动,如果执行成功,您应该在 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 硬件的部分。
这两种流程都在不断发展,支持更多的用例和更好的性能。