在 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 的複雜性,開發者可以專注於模型執行,而無需擔心底層細節。