在 C++ 中使用 Module 扩展运行 ExecuTorch 模型¶
在《在 C++ 中运行 ExecuTorch 模型教程》中,我们探讨了用于运行导出模型的较低级别 ExecuTorch API。虽然这些 API 提供了零开销、极大的灵活性和控制力,但对于常规使用来说可能显得冗长和复杂。为了简化此过程并模仿 PyTorch 在 Python 中的 eager 模式,我们在常规的 ExecuTorch 运行时 API 上引入了 Module
外观 (facade) API。Module
API 提供了相同的灵活性,但默认使用诸如 DataLoader
和 MemoryAllocator
等常用组件,从而隐藏了大多数复杂的细节。
示例¶
让我们看看如何使用 Module
和 TensorPtr
API 运行从《导出到 ExecuTorch 教程》生成的 SimpleConv
模型。
#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,以便更好地理解其内部工作原理。
API¶
创建 Module¶
创建 Module
对象是一个快速操作,不涉及大量的处理时间或内存分配。除非使用专门的 API 明确请求,否则实际加载 Program
和 Method
会在第一次推理时延迟发生。
Module module("/path/to/model.pte");
强制加载 Method¶
要在任何时候强制加载 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
,包括 tensors、scalars、lists 和其他支持的类型。要为方法设置特定的输入值:
module.set_input("forward", input_value, input_index);
input_value
是一个EValue
,表示您要设置的输入。input_index
是要设置输入的从零开始的索引。
例如,要设置第一个输入 tensor:
module.set_input("forward", tensor_value, 0);
您也可以一次设置多个输入:
std::vector<runtime::EValue> inputs = {input1, input2, input3};
module.set_inputs("forward", inputs);
注意:对于 forward()
方法,可以省略方法名称参数。
通过预设所有输入,您可以在不传递任何参数的情况下执行推理:
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
类型的输出,并且这些输出在模型导出时不能是 memory-planned 的。Memory-planned tensors 在模型导出期间预分配,无法替换。
要为特定方法设置输出 tensor:
module.set_output("forward", output_tensor, output_index);
output_tensor
是一个包含您希望设置为输出的 tensor 的EValue
。output_index
是要设置输出的从零开始的索引。
注意:确保您设置的输出 tensor 与该方法期望的形状和数据类型一致。
对于 forward()
方法,可以省略方法名称;对于第一个输出,可以省略索引。
module.set_output(output_tensor);
注意:预设的输出存储在 Module
中,可以多次重复用于后续执行,就像输入一样。
Result 和 Error 类型¶
大多数 ExecuTorch API 返回 Result
或 Error
类型。
分析 Module 性能¶
使用 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);
}
}
}
结论¶
Module
API 为在 C++ 中运行 ExecuTorch 模型提供了一个简化的接口,非常类似于 PyTorch eager 模式的体验。通过抽象掉较低级别运行时 API 的复杂性,开发者可以专注于模型执行,而无需担心底层细节。