-
Notifications
You must be signed in to change notification settings - Fork 683
Update default executor runner with new optional options #14017
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
58998b0
d9d2465
a0faaa0
3a345f3
943493b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
/* | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* Copyright 2024-2025 Arm Limited and/or its affiliates. | ||
* All rights reserved. | ||
* Copyright 2024-2025 Arm Limited and/or its affiliates. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
|
@@ -18,6 +18,7 @@ | |
* all fp32 tensors. | ||
*/ | ||
|
||
#include <fstream> | ||
#include <iostream> | ||
#include <memory> | ||
|
||
|
@@ -49,6 +50,16 @@ DEFINE_string( | |
model_path, | ||
"model.pte", | ||
"Model serialized in flatbuffer format."); | ||
DEFINE_string(inputs, "", "Comma-separated list of input files"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JFYI you can also use bundled-io in the PTE. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, perhaps add that in a separate patch There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Outputs can also be saved with ETDump but that is maybe a bigger/separate patch this file should probably be re-synced with examples/devtools/example_runner/example_runner.cpp someday. Or @digantdesai do you want to avoid adding "inputs" as an argument and only handle it via BundleIO? E.g. is this something you prefere us to fix now in this PR and later after 1.0/GA? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just spoted your "LGTM" so that answers my question :) |
||
DEFINE_string( | ||
output_file, | ||
"", | ||
"Base name of output file. If not empty output will be written to the file(s)."); | ||
|
||
DEFINE_bool( | ||
print_all_output, | ||
false, | ||
"Prints all output. By default only first and last 100 elements are printed."); | ||
DEFINE_uint32(num_executions, 1, "Number of times to run the model."); | ||
#ifdef ET_EVENT_TRACER_ENABLED | ||
DEFINE_string(etdump_path, "model.etdump", "Write ETDump data to this path."); | ||
|
@@ -58,6 +69,8 @@ DEFINE_int32( | |
-1, | ||
"Number of CPU threads for inference. Defaults to -1, which implies we'll use a heuristic to derive the # of performant cores for a specific device."); | ||
|
||
using executorch::aten::ScalarType; | ||
using executorch::aten::Tensor; | ||
using executorch::extension::FileDataLoader; | ||
using executorch::runtime::Error; | ||
using executorch::runtime::EValue; | ||
|
@@ -70,6 +83,8 @@ using executorch::runtime::MethodMeta; | |
using executorch::runtime::Program; | ||
using executorch::runtime::Result; | ||
using executorch::runtime::Span; | ||
using executorch::runtime::Tag; | ||
using executorch::runtime::TensorInfo; | ||
|
||
/// Helper to manage resources for ETDump generation | ||
class EventTraceManager { | ||
|
@@ -156,6 +171,31 @@ int main(int argc, char** argv) { | |
"FileDataLoader::from() failed: 0x%" PRIx32, | ||
(uint32_t)loader.error()); | ||
|
||
std::vector<std::string> inputs_storage; | ||
std::vector<std::pair<char*, size_t>> input_buffers; | ||
|
||
std::stringstream list_of_input_files(FLAGS_inputs); | ||
std::string token; | ||
|
||
while (std::getline(list_of_input_files, token, ',')) { | ||
std::ifstream input_file_handle(token, std::ios::binary | std::ios::ate); | ||
if (!input_file_handle) { | ||
ET_LOG(Error, "Failed to open input file: %s\n", token.c_str()); | ||
return 1; | ||
} | ||
|
||
std::streamsize file_size = input_file_handle.tellg(); | ||
input_file_handle.seekg(0, std::ios::beg); | ||
|
||
inputs_storage.emplace_back(file_size, '\0'); | ||
if (!input_file_handle.read(&inputs_storage.back()[0], file_size)) { | ||
ET_LOG(Error, "Failed to read input file: %s\n", token.c_str()); | ||
return 1; | ||
} | ||
|
||
input_buffers.emplace_back(&inputs_storage.back()[0], file_size); | ||
} | ||
|
||
// Parse the program file. This is immutable, and can also be reused between | ||
// multiple execution invocations across multiple threads. | ||
Result<Program> program = Program::load(&loader.get()); | ||
|
@@ -254,15 +294,17 @@ int main(int argc, char** argv) { | |
// Run the model. | ||
for (uint32_t i = 0; i < FLAGS_num_executions; i++) { | ||
ET_LOG(Debug, "Preparing inputs."); | ||
// Allocate input tensors and set all of their elements to 1. The `inputs` | ||
// Allocate input tensors and set all of their elements to 1 or to the | ||
// contents of input_buffers if available. The `inputs` | ||
// variable owns the allocated memory and must live past the last call to | ||
// `execute()`. | ||
// | ||
// NOTE: we have to re-prepare input tensors on every execution | ||
// because inputs whose space gets reused by memory planning (if | ||
// any such inputs exist) will not be preserved for the next | ||
// execution. | ||
auto inputs = executorch::extension::prepare_input_tensors(*method); | ||
auto inputs = executorch::extension::prepare_input_tensors( | ||
*method, {}, input_buffers); | ||
ET_CHECK_MSG( | ||
inputs.ok(), | ||
"Could not prepare inputs: 0x%" PRIx32, | ||
|
@@ -295,10 +337,67 @@ int main(int argc, char** argv) { | |
ET_LOG(Info, "%zu outputs: ", outputs.size()); | ||
Error status = method->get_outputs(outputs.data(), outputs.size()); | ||
ET_CHECK(status == Error::Ok); | ||
// Print the first and last 100 elements of long lists of scalars. | ||
std::cout << executorch::extension::evalue_edge_items(100); | ||
for (int i = 0; i < outputs.size(); ++i) { | ||
std::cout << "Output " << i << ": " << outputs[i] << std::endl; | ||
|
||
if (FLAGS_output_file.size() > 0) { | ||
for (int i = 0; i < outputs.size(); ++i) { | ||
if (outputs[i].isTensor()) { | ||
Tensor tensor = outputs[i].toTensor(); | ||
|
||
char out_filename[255]; | ||
snprintf(out_filename, 255, "%s-%d.bin", FLAGS_output_file.c_str(), i); | ||
ET_LOG(Info, "Writing output to file: %s", out_filename); | ||
FILE* out_file = fopen(out_filename, "wb"); | ||
auto written_size = | ||
fwrite(tensor.const_data_ptr<char>(), 1, tensor.nbytes(), out_file); | ||
fclose(out_file); | ||
} | ||
} | ||
} | ||
|
||
if (FLAGS_print_all_output) { | ||
for (int i = 0; i < outputs.size(); ++i) { | ||
if (outputs[i].isTensor()) { | ||
Tensor tensor = outputs[i].toTensor(); | ||
|
||
for (int j = 0; j < tensor.numel(); ++j) { | ||
if (tensor.scalar_type() == ScalarType::Int) { | ||
printf( | ||
"Output[%d][%d]: (int) %d\n", | ||
i, | ||
j, | ||
tensor.const_data_ptr<int>()[j]); | ||
} else if (tensor.scalar_type() == ScalarType::Float) { | ||
printf( | ||
"Output[%d][%d]: (float) %f\n", | ||
i, | ||
j, | ||
tensor.const_data_ptr<float>()[j]); | ||
} else if (tensor.scalar_type() == ScalarType::Char) { | ||
printf( | ||
"Output[%d][%d]: (char) %d\n", | ||
i, | ||
j, | ||
tensor.const_data_ptr<int8_t>()[j]); | ||
} else if (tensor.scalar_type() == ScalarType::Bool) { | ||
printf( | ||
"Output[%d][%d]: (bool) %s (0x%x)\n", | ||
i, | ||
j, | ||
tensor.const_data_ptr<int8_t>()[j] ? "true " : "false", | ||
tensor.const_data_ptr<int8_t>()[j]); | ||
} | ||
} | ||
} else { | ||
printf("Output[%d]: Not Tensor\n", i); | ||
} | ||
} | ||
} else { | ||
// Print the first and last 100 elements of long lists of scalars. | ||
std::cout << executorch::extension::evalue_edge_items(100); | ||
|
||
for (int i = 0; i < outputs.size(); ++i) { | ||
std::cout << "OutputX " << i << ": " << outputs[i] << std::endl; | ||
} | ||
} | ||
|
||
if (tracer.get_event_tracer()) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can we do it like this -
executorch/backends/arm/test/tester/test_pipeline.py
Line 285 in 3c533aa
Else I imagine we have to write this for every single test..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes indeed we shall not do a try/except in every unit test. Just wanted to enable a test that tested this. We will either do a central try/except or add xfails for remaining tests in coming patches.