快捷方式

ExecuTorch 运行时概述

本文档讨论了 ExecuTorch 运行时的设计,该运行时在边缘设备(如智能手机、可穿戴设备和嵌入式设备)上执行 ExecuTorch 程序文件。主执行 API 的代码位于 executorch/runtime/executor/

在阅读本文档之前,我们建议您阅读 ExecuTorch 的工作原理

在最高级别,ExecuTorch 运行时负责

  • 加载由模型降低过程的 to_executorch() 步骤生成的二进制 .pte 程序文件。

  • 执行实现降低模型的一系列指令。

请注意,截至 2023 年底,ExecuTorch 运行时仅支持模型推理,尚不支持训练。

此图显示了导出和执行 ExecuTorch 程序的高级流程以及涉及的组件

High-level diagram of the ExecuTorchRuntime

运行时还负责

  • 管理加载和执行期间使用的内存,可能跨越多个内存库,如 SRAM 和 DRAM。

  • 将像 "aten::add.out" 这样的符号运算符名称映射到实现这些运算符语义的具体 C++ 函数或 内核

  • 将模型的预定部分调度到 后端代理 以进行加速。

  • 可选地在加载和执行期间收集 性能分析数据

设计目标

ExecuTorch 运行时旨在运行在各种边缘设备上,从现代智能手机 CPU 到资源受限的微控制器和 DSP。它对 委托 执行到一个或多个后端以利用特定于体系结构的优化和现代异构体系结构具有一流的支持。它足够小巧且便携,可以直接在没有操作系统、动态内存或线程的裸机嵌入式环境中运行。

低执行开销

内存

  • 核心运行时库在不构建内核或后端的情况下,大小不到 50kB。

  • 常量张量直接指向 .pte 文件数据,避免了对该数据的复制。这些数据块的对齐方式可以在 .pte 创建时进行调整。

  • 后端代理可以选择在模型初始化后卸载其预编译数据,从而减少峰值内存使用量。

  • 可变张量内存布局是在提前计划好的,并打包到一小部分用户分配的缓冲区中,从而提供对内存位置的细粒度控制。这在具有异构内存层次结构的系统上尤其有用,允许将数据放置到(例如)靠近将对数据进行操作的核心处理器的 SRAM 或 DRAM 上。

CPU

  • 模型执行是一个简单的循环,遍历一组指令,其中大多数是内核和后端代理的函数指针。这使得执行开销很小,每操作在微秒到纳秒之间。

  • 操作的实现(如“add”或“conv3d”)可以针对特定目标系统完全定制,而无需修改原始模型或生成的 .pte 文件。

熟悉的 PyTorch 语义

ExecuTorch 是 PyTorch 堆栈中的一个一流组件,并在可能的情况下重用 API 和语义。

  • ExecuTorch 使用的 C++ 类型与核心 PyTorch 的 c10::at:: 库中的相应类型源代码兼容,ExecuTorch 提供了 aten_bridge 来在两者之间进行转换。这对于已经使用 PyTorch C++ 类型的项目很有帮助。

  • aten::addaten::sigmoid 这样的运算符的语义在 ExecuTorch 和核心 PyTorch 之间是相同的。ExecuTorch 提供了一个测试框架来确保这一点,并帮助测试这些运算符的未来实现。

可移植代码和架构

ExecuTorch 运行时在设计时就考虑了可移植性,以便用户可以为各种目标系统构建它。

C++ 语言注意事项

  • 代码与 C++11 兼容,可以与旧的工具链一起使用。

  • 运行时不使用异常或 RTTI,尽管它并不与它们冲突。

  • 代码与 GCC 和 Clang 兼容,并且也已使用几个专有的嵌入式工具链构建。

  • 该仓库提供了 CMake 和 buck2 两种构建系统,以方便集成。

操作系统注意事项

运行时不直接进行系统调用。所有对内存、文件、日志和时钟的访问都通过 运行时平台抽象层 (PAL) 和注入的接口(如 DataLoaderMemoryAllocator)进行抽象。请参阅 运行时 API 参考 以了解更多信息。

应用程序可以通过 MemoryManagerMemoryAllocatorHierarchicalAllocatorDataLoader 类控制所有内存分配。核心运行时不直接调用 malloc()new,也不调用像 std::vector 这样的在后台进行分配的类型。这使得它能够

  • 在没有堆的环境中运行,但如果需要,仍然可以使用堆。

  • 避免在模型加载和执行期间对堆进行同步。

  • 控制使用哪个内存区域来存储不同类型的数据。例如,一组可变张量可以存储在 SRAM 中,而另一组可以存储在 DRAM 中。

  • 轻松监控运行时使用的内存量。

但是,请注意,特定的内核或后端实现可能会使用任意的运行时或操作系统功能。用户应仔细检查他们使用的内核和后端库的文档。

线程注意事项

核心运行时不进行线程或锁定,也不使用线程局部变量。但是,它与更高级别的同步配合良好。

  • 每个 Program 实例都是不可变的,因此完全线程安全。多个线程可以同时访问单个 Program 实例。

  • 每个 Method 实例都是可变的,但它是自包含的,因此条件线程安全。多个线程可以同时访问和执行独立的 Method 实例,但对单个实例的访问和执行必须序列化。

但是,请注意

  • Program::load_method() 期间可能会读取两个全局表:内核注册表和后端注册表。

    • 实际上,这些表格只在进程/系统加载时修改,并在第一个Program加载之前被冻结。但一些应用程序可能需要了解这些表格,特别是如果它们在进程/系统加载时间之后手动修改它们。

  • 特定的内核或后端实现可能会有自己的线程限制。用户应该仔细检查他们使用的内核和后端库的文档。

文档

访问 PyTorch 的全面开发者文档

查看文档

教程

获取针对初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并获得问题的解答

查看资源