• 文档 >
  • 在 C++ 中运行 ExecuTorch 模型教程
快捷方式

在 C++ 中运行 ExecuTorch 模型教程

作者: Jacob Szwejbka

在本教程中,我们将介绍如何使用更详细的底层 API 在 C++ 中运行 ExecuTorch 模型:准备 MemoryManager,设置输入,执行模型并检索输出。但是,如果您正在寻找开箱即用的更简单界面,请考虑尝试 模块扩展教程

有关 ExecuTorch 运行时的概述,请参见 运行时概述,有关每个 API 的更深入文档,请参见 运行时 API 参考这里 是一个功能齐全的 C++ 模型运行器,设置 ExecuTorch 文档展示了如何构建和运行它。

先决条件

您需要一个 ExecuTorch 模型才能继续学习。我们将使用从 导出到 ExecuTorch 教程 生成的模型 SimpleConv

模型加载

运行模型的第一步是加载它。ExecuTorch 使用一个名为 DataLoader 的抽象来处理检索 .pte 文件数据的细节,然后 Program 表示加载的状态。

用户可以定义自己的 DataLoader 来满足其特定系统的需求。在本教程中,我们将使用 FileDataLoader,但您可以查看 示例数据加载器实现 来查看 ExecuTorch 项目提供的其他选项。

对于 FileDataLoader,我们只需将文件路径提供给构造函数。

using executorch::aten::Tensor;
using executorch::aten::TensorImpl;
using executorch::extension::FileDataLoader;
using executorch::extension::MallocMemoryAllocator;
using executorch::runtime::Error;
using executorch::runtime::EValue;
using executorch::runtime::HierarchicalAllocator;
using executorch::runtime::MemoryManager;
using executorch::runtime::Method;
using executorch::runtime::MethodMeta;
using executorch::runtime::Program;
using executorch::runtime::Result;
using executorch::runtime::Span;

Result<FileDataLoader> loader =
        FileDataLoader::from("/tmp/model.pte");
assert(loader.ok());

Result<Program> program = Program::load(&loader.get());
assert(program.ok());

设置 MemoryManager

接下来,我们将设置 MemoryManager

ExecuTorch 的原则之一是让用户控制运行时使用的内存来自何处。现在(2023 年底),用户需要提供 2 种不同的分配器

  • 方法分配器:一个 MemoryAllocator,用于在 Method 加载时分配运行时结构。张量元数据、内部指令链和其他运行时状态等内容来自这里。

  • 计划内存:一个 HierarchicalAllocator,包含一个或多个内存区域,内部可变张量数据缓冲区被放置在此处。在 Method 加载时,内部张量的其数据指针被分配到其中的各种偏移量。这些偏移量的位置以及区域的大小由事先的内存规划决定。

在本示例中,我们将从 Program 动态地检索计划内存区域的大小,但是对于无堆环境,用户可以在事先从 Program 中检索此信息并静态地分配区域。我们还将使用基于 malloc 的分配器作为方法分配器。

// Method names map back to Python nn.Module method names. Most users will only
// have the singular method "forward".
const char* method_name = "forward";

// MethodMeta is a lightweight structure that lets us gather metadata
// information about a specific method. In this case we are looking to get the
// required size of the memory planned buffers for the method "forward".
Result<MethodMeta> method_meta = program->method_meta(method_name);
assert(method_meta.ok());

std::vector<std::unique_ptr<uint8_t[]>> planned_buffers; // Owns the Memory
std::vector<Span<uint8_t>> planned_arenas; // Passed to the allocator

size_t num_memory_planned_buffers = method_meta->num_memory_planned_buffers();

// It is possible to have multiple layers in our memory hierarchy; for example,
// SRAM and DRAM.
for (size_t id = 0; id < num_memory_planned_buffers; ++id) {
  // .get() will always succeed because id < num_memory_planned_buffers.
  size_t buffer_size =
      static_cast<size_t>(method_meta->memory_planned_buffer_size(id).get());
  planned_buffers.push_back(std::make_unique<uint8_t[]>(buffer_size));
  planned_arenas.push_back({planned_buffers.back().get(), buffer_size});
}
HierarchicalAllocator planned_memory(
    {planned_arenas.data(), planned_arenas.size()});

// Version of MemoryAllocator that uses malloc to handle allocations rather then
// a fixed buffer.
MallocMemoryAllocator method_allocator;

// Assemble all of the allocators into the MemoryManager that the Executor will
// use.
MemoryManager memory_manager(&method_allocator, &planned_memory);

加载方法

在 ExecuTorch 中,我们从 Program 以方法粒度加载和初始化。许多程序只有一个方法“forward”。load_method 是完成初始化的地方,从设置张量元数据到初始化委托等。

Result<Method> method = program->load_method(method_name);
assert(method.ok());

设置输入

现在我们有了方法,我们需要在执行推理之前设置其输入。在这种情况下,我们知道我们的模型接受单个 (1, 3, 256, 256) 大小的浮点张量。

根据您的模型是如何进行内存规划的,计划内存可能包含或不包含您的输入和输出的缓冲区空间。

如果输出没有进行内存规划,那么用户需要使用“set_output_data_ptr”设置输出数据指针。在本例中,我们假设我们的模型是在内存计划处理输入和输出的情况下导出的。

// Create our input tensor.
float data[1 * 3 * 256 * 256];
Tensor::SizesType sizes[] = {1, 3, 256, 256};
Tensor::DimOrderType dim_order = {0, 1, 2, 3};
TensorImpl impl(
    ScalarType::Float, // dtype
    4, // number of dimensions
    sizes,
    data,
    dim_order);
Tensor t(&impl);

// Implicitly casts t to EValue
Error set_input_error = method->set_input(t, 0);
assert(set_input_error == Error::Ok);

执行推理

现在我们的方法已加载,输入也已设置,我们可以执行推理。我们通过调用 execute 来实现这一点。

Error execute_error = method->execute();
assert(execute_error == Error::Ok);

检索输出

一旦我们的推理完成,我们就可以检索我们的输出。我们知道我们的模型只返回一个输出张量。这里一个潜在的陷阱是,我们得到的输出由 Method 拥有。用户应该在对输出进行任何修改之前克隆他们的输出,或者如果他们需要它具有与 Method 分开的生命周期。

EValue output = method->get_output(0);
assert(output.isTensor());

结论

本教程演示了如何使用低级运行时 API 运行 ExecuTorch 模型,这些 API 提供了对内存管理和执行的细粒度控制。但是,对于大多数用例,我们建议使用 Module API,它提供了更简化的体验,而不会牺牲灵活性。有关更多详细信息,请查看 Module 扩展教程

文档

访问 PyTorch 的全面开发者文档

查看文档

教程

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

查看教程

资源

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

查看资源