ExecuTorch 运行时概述¶
本文档讨论了 ExecuTorch 运行时的设计,该运行时在智能手机、可穿戴设备和嵌入式设备等边缘设备上执行 ExecuTorch 程序文件。主要执行 API 的代码位于 executorch/runtime/executor/
。
在阅读本文档之前,我们建议您阅读 ExecuTorch 的工作原理。
在最高级别,ExecuTorch 运行时负责
加载模型降低过程的
to_executorch()
阶段生成的二进制.pte
程序文件。执行实现降低模型的一系列指令。
请注意,截至 2023 年底,ExecuTorch 运行时仅支持模型推理,尚未支持训练。
此图显示了导出和执行 ExecuTorch 程序所涉及的高级流程和组件
运行时还负责
管理加载和执行期间使用的内存,可能跨越多个内存库(如 SRAM 和 DRAM)。
将
"aten::add.out"
等符号运算符名称映射到具体的 C++ 函数或 内核,这些函数或内核实现这些运算符的语义。将模型的预定部分分派到 后端委托 以进行加速。
在加载和执行期间可选地收集 分析数据。
设计目标¶
ExecuTorch 运行时旨在运行在各种边缘设备上,从现代智能手机 CPU 到资源受限的微控制器和 DSP。它对 委托 执行到一个或多个后端以利用特定于架构的优化和现代异构架构具有第一流的支持。它足够小巧且可移植,可以直接在没有操作系统、动态内存或线程的裸机嵌入式环境中运行。
低执行开销¶
内存¶
核心运行时库在不构建内核或后端的情况下,大小不到 50kB。
常量张量直接指向
.pte
文件数据,避免复制该数据。这些数据块的对齐可以在.pte
创建时进行调整。后端委托可以选择在模型初始化后卸载其预编译数据,从而减少峰值内存使用量。
可变张量内存布局是在事先计划好的,并打包到一组小的用户分配的缓冲区中,从而提供对内存位置的细粒度控制。这在具有异构内存层次结构的系统中特别有用,允许将数据放置到(例如) SRAM 或 DRAM 中,这些内存靠近将对数据进行操作的核心。
CPU¶
模型执行是遍历指令数组的简单循环,其中大部分指令是内核和后端委托的函数指针。这使得执行开销很小,每个操作在微秒到纳秒的范围内。
操作(如“加法”或“conv3d”)的实现可以针对特定目标系统完全定制,而无需修改原始模型或生成的
.pte
文件。
熟悉的 PyTorch 语义¶
ExecuTorch 是 PyTorch 堆栈中的一个一等公民组件,并在可能的情况下重用 API 和语义。
ExecuTorch 使用的 C++ 类型与核心 PyTorch 的
c10::
和at::
库中的相应类型源代码兼容,ExecuTorch 提供aten_bridge
用于在两者之间进行转换。这对于已经使用 PyTorch C++ 类型的项目可能会有所帮助。ExecuTorch 和核心 PyTorch 之间的
aten::add
和aten::sigmoid
等运算符的语义是相同的。ExecuTorch 提供了一个测试框架来确保这一点,并帮助测试这些运算符的未来实现。
可移植代码和架构¶
ExecuTorch 运行时在设计时就考虑到了可移植性,因此用户可以为各种目标系统构建它。
C++ 语言注意事项¶
代码与 C++17 兼容,可与较旧的工具链一起使用。
运行时不使用异常或 RTTI,尽管它并不反对它们。
代码与 GCC 和 Clang 兼容,并且也已使用几个专有的嵌入式工具链构建。
该仓库提供 CMake 构建系统以简化集成。
操作系统注意事项¶
运行时不进行任何直接的系统调用。所有对内存、文件、日志和时钟的访问都通过 运行时平台抽象层 (PAL) 和注入的接口(如 DataLoader
和 MemoryAllocator
)进行抽象。有关更多信息,请参阅 运行时 API 参考。
应用程序可以通过 MemoryManager
、MemoryAllocator
、HierarchicalAllocator
和 DataLoader
类控制所有内存分配。核心运行时不直接调用 malloc()
或 new
,也不调用诸如 std::vector
之类的在后台进行分配的类型。这使得以下操作成为可能:
在没有堆的环境中运行,但如果需要,仍然可以使用堆。
在模型加载和执行期间避免对堆进行同步。
控制对不同类型数据的内存区域的使用。例如,一组可变张量可以位于 SRAM 中,而另一组则位于 DRAM 中。
轻松监控运行时使用的内存量。
但是,请注意,特定的内核或后端实现可能会使用任意的运行时或操作系统功能。用户应仔细检查其使用的内核和后端库的文档。
线程注意事项¶
核心运行时不执行线程或锁定,也不使用线程本地变量。但是,它与更高级别的同步配合良好。
每个
Program
实例都是不可变的,因此完全线程安全。多个线程可以同时访问单个Program
实例。每个
Method
实例都是可变的,但自成一体,因此有条件地线程安全。多个线程可以同时访问和执行独立的Method
实例,但对单个实例的访问和执行必须是串行的。
但是,请注意
在
Program::load_method()
期间可能会读取两个全局表:内核注册表和后端注册表。在实践中,这些表仅在进程/系统加载时修改,并且在第一个
Program
加载之前实际上已被冻结。但是,某些应用程序可能需要了解这些表,尤其是在它们在进程/系统加载时间后手动修改它们的情况下。
特定的内核或后端实现可能具有自己的线程限制。用户应仔细检查其使用的内核和后端库的文档。
进一步阅读¶
有关 ExecuTorch 运行时的更多详细信息,请参阅