使用C++中的模块扩展运行一个ExecuTorch模型¶
在在C++中运行ExecuTorch模型教程中,我们探索了用于运行导出模型的底层ExecuTorch API。虽然这些API提供了零开销、极大的灵活性和控制力,但它们对于常规使用来说可能过于冗长和复杂。为了简化这一过程,并使其类似于Python中的PyTorch急切模式,我们在常规ExecuTorch运行时API之上引入了Module外观API。这些Module API提供了相同的灵活性,但默认使用常用的组件如DataLoader和MemoryAllocator, 隐藏了大多数复杂的细节。
示例¶
让我们来看看如何使用Module和TensorPtr API来运行从SimpleConv模型生成的导出到ExecuTorch教程:
#include <executorch/extension/module/module.h>
#include <executorch/extension/tensor/tensor.h>
using namespace ::executorch::extension;
// Create a Module.
Module module("/path/to/model.pte");
// Wrap the input data with a Tensor.
float input[1 * 3 * 256 * 256];
auto tensor = from_blob(input, {1, 3, 256, 256});
// Perform an inference.
const auto result = module.forward(tensor);
// Check for success or failure.
if (result.ok()) {
// Retrieve the output data.
const auto output = result->at(0).toTensor().const_data_ptr<float>();
}
现在的代码简化为创建一个 Module,并在其上调用 forward(),无需任何额外设置。让我们更仔细地查看这些以及其他 Module API,以更好地理解其内部运作机制。
APIs¶
创建一个模块¶
创建一个 Module 对象是一个快速操作,不会涉及显著的处理时间或内存分配。实际加载 Program 和 Method 会在第一次推理时按需延迟进行,除非使用专用API显式请求。
Module module("/path/to/model.pte");
强制加载一个方法¶
为了在任何时间强制加载 Module(以及其底层的 ExecuTorch Program),请使用 load() 函数:
const auto error = module.load();
assert(module.is_loaded());
强制加载特定的 Method,请调用 load_method() 函数:
const auto error = module.load_method("forward");
assert(module.is_method_loaded("forward"));
您也可以使用便捷函数来加载 forward 方法:
const auto error = module.load_forward();
assert(module.is_method_loaded("forward"));
注意: Program 会在任何 Method 加载之前自动加载。如果之前的尝试已成功,后续尝试加载它们将没有效果。
查询元数据¶
使用method_names()函数获取Module中包含的方法名称集合:
const auto method_names = module.method_names();
if (method_names.ok()) {
assert(method_names->count("forward"));
}
注意: method_names() 在首次调用时会强制加载 Program。
为了获取特定方法的杂项元数据,请使用 method_meta() 函数,该函数返回一个 MethodMeta 结构体:
const auto method_meta = module.method_meta("forward");
if (method_meta.ok()) {
assert(method_meta->name() == "forward");
assert(method_meta->num_inputs() > 1);
const auto input_meta = method_meta->input_tensor_meta(0);
if (input_meta.ok()) {
assert(input_meta->scalar_type() == ScalarType::Float);
}
const auto output_meta = method_meta->output_tensor_meta(0);
if (output_meta.ok()) {
assert(output_meta->sizes().size() == 1);
}
}
注意: method_meta() 在首次调用时也会强制加载 Method。
执行推理¶
假设 Program 的方法名及其输入格式已知,您可以使用 execute() 函数通过名称直接运行方法:
const auto result = module.execute("forward", tensor);
对于标准 forward() 方法,上述内容可以简化为:
const auto result = module.forward(tensor);
注意: execute() 或 forward() 在首次调用时会加载 Program 和 Method。因此,第一次推理会更耗时,因为模型是延迟加载并准备执行的,除非之前已显式加载过。
设置输入和输出¶
您可以使用以下 API 为方法设置单独的输入和输出值。
设置输入¶
输入可以是任何 EValue,包括张量、标量、列表和其他支持的类型。要为一个方法设置特定的输入值:
module.set_input("forward", input_value, input_index);
input_value是一个EValue,表示你想要设置的输入。input_index是要设置的输入的零基索引。
例如,设置第一个输入张量:
module.set_input("forward", tensor_value, 0);
你也可以一次设置多个输入:
std::vector<runtime::EValue> inputs = {input1, input2, input3};
module.set_inputs("forward", inputs);
注意: 您可以跳过 forward() 方法的 method name 参数。
通过预先设置所有输入,您可以执行推理而无需传递任何参数:
const auto result = module.forward();
或者只需设置然后部分传递输入:
// Set the second input ahead of time.
module.set_input(input_value_1, 1);
// Execute the method, providing the first input at call time.
const auto result = module.forward(input_value_0);
注意: 预设的输入存储在 Module 中,可以多次用于后续的执行。
请在不再需要时通过将它们设置为默认构造的 EValue 清除或重置输入:
module.set_input(runtime::EValue(), 1);
设置输出¶
只有类型为 Tensor 的输出可以在运行时设置,并且它们在模型导出时不能是内存规划的。内存规划的张量在模型导出期间会被预先分配,无法被替换。
设置特定方法的输出张量:
module.set_output("forward", output_tensor, output_index);
output_tensor是一个EValue,包含你想要设置为输出的张量。output_index是要设置的输出的零基索引。
注意: 确保你设置的输出张量与方法输出的预期形状和数据类型相匹配。
您可以省略方法名 forward() 以及第一个输出的索引:
module.set_output(output_tensor);
注意: 预设的输出存储在 Module 中,可以多次用于后续执行,就像输入一样。
结果和错误类型¶
大多数ExecuTorch API返回的是 Result 或 Error 类型:
模块分析¶
使用 ExecuTorch Dump 来追踪模型执行。创建一个 ETDumpGen 实例并传递给 Module 构造函数。在执行方法后,将 ETDump 数据保存到文件中以进行进一步分析:
#include <fstream>
#include <memory>
#include <executorch/extension/module/module.h>
#include <executorch/devtools/etdump/etdump_flatcc.h>
using namespace ::executorch::extension;
Module module("/path/to/model.pte", Module::LoadMode::MmapUseMlock, std::make_unique<ETDumpGen>());
// Execute a method, e.g., module.forward(...); or module.execute("my_method", ...);
if (auto* etdump = dynamic_cast<ETDumpGen*>(module.event_tracer())) {
const auto trace = etdump->get_etdump_data();
if (trace.buf && trace.size > 0) {
std::unique_ptr<void, decltype(&free)> guard(trace.buf, free);
std::ofstream file("/path/to/trace.etdump", std::ios::binary);
if (file) {
file.write(static_cast<const char*>(trace.buf), trace.size);
}
}
}
结论¶
The Module APIs 提供了一种简化的接口,用于在C++中运行ExecuTorch模型,其体验与PyTorch的即时模式非常相似。通过抽象掉底层运行时API的复杂性,开发者可以专注于模型执行,而无需担心底层细节。