Skip to content

Commit 7f49d4f

Browse files
authored
Reapply "Update default executor runner with new optional options"
Differential Revision: D82212827 Pull Request resolved: #14207
1 parent 892e70f commit 7f49d4f

File tree

9 files changed

+234
-61
lines changed

9 files changed

+234
-61
lines changed

backends/arm/scripts/build_executor_runner_vkml.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ fi
6464

6565
echo "Building with extra flags: ${build_with_etdump_flags} ${extra_build_flags}"
6666
cmake \
67+
-Wall \
68+
-Werror \
6769
-DCMAKE_BUILD_TYPE=${build_type} \
6870
-DEXECUTORCH_BUILD_EXTENSION_DATA_LOADER=ON \
6971
-DEXECUTORCH_BUILD_EXTENSION_MODULE=ON \

backends/arm/test/ops/test_acos.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# LICENSE file in the root directory of this source tree.
55
from typing import Tuple
66

7+
import pytest
78
import torch
89

910
from executorch.backends.arm.test import common
@@ -102,8 +103,12 @@ def test_acos_vgf_FP(test_data: Tuple):
102103
[],
103104
[],
104105
tosa_version="TOSA-1.0+FP",
106+
run_on_vulkan_runtime=True,
105107
)
106-
pipeline.run()
108+
try:
109+
pipeline.run()
110+
except FileNotFoundError as e:
111+
pytest.skip(f"VKML executor_runner not found - not built - skip {e}")
107112

108113

109114
@common.parametrize("test_data", test_data_suite)
@@ -115,5 +120,9 @@ def test_acos_vgf_INT(test_data: Tuple):
115120
[],
116121
[],
117122
tosa_version="TOSA-1.0+INT",
123+
run_on_vulkan_runtime=True,
118124
)
119-
pipeline.run()
125+
try:
126+
pipeline.run()
127+
except FileNotFoundError as e:
128+
pytest.skip(f"VKML executor_runner not found - not built - skip {e}")

backends/arm/test/ops/test_add.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,7 @@ def test_add_tensor_u85_INT_2(test_data: input_t2):
202202
pipeline.run()
203203

204204

205-
# TODO/MLETORCH-1282: remove once inputs are not hard coded to ones
206-
skip_keys = {"5d_float", "1d_ones", "1d_randn"}
207-
filtered_test_data = {k: v for k, v in Add.test_data.items() if k not in skip_keys}
208-
209-
210-
@common.parametrize("test_data", filtered_test_data)
205+
@common.parametrize("test_data", Add.test_data)
211206
@common.SkipIfNoModelConverter
212207
def test_add_tensor_vgf_FP(test_data: input_t1):
213208
pipeline = VgfPipeline[input_t1](
@@ -224,7 +219,7 @@ def test_add_tensor_vgf_FP(test_data: input_t1):
224219
pytest.skip(f"VKML executor_runner not found - not built - skip {e}")
225220

226221

227-
@common.parametrize("test_data", filtered_test_data)
222+
@common.parametrize("test_data", Add.test_data)
228223
@common.SkipIfNoModelConverter
229224
def test_add_tensor_vgf_INT(test_data: input_t1):
230225
pipeline = VgfPipeline[input_t1](

backends/arm/test/runner_utils.py

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,48 @@ def run_target(
223223
elif target_board == "vkml_emulation_layer":
224224
return run_vkml_emulation_layer(
225225
executorch_program_manager,
226+
inputs,
226227
intermediate_path,
227228
elf_path,
228229
)
229230

230231

232+
def save_inputs_to_file(
233+
exported_program: ExportedProgram,
234+
inputs: Tuple[torch.Tensor],
235+
intermediate_path: str | Path,
236+
):
237+
input_file_paths = []
238+
input_names = get_input_names(exported_program)
239+
for input_name, input_ in zip(input_names, inputs):
240+
input_path = save_bytes(intermediate_path, input_, input_name)
241+
input_file_paths.append(input_path)
242+
243+
return input_file_paths
244+
245+
246+
def get_output_from_file(
247+
exported_program: ExportedProgram,
248+
intermediate_path: str | Path,
249+
output_base_name: str,
250+
):
251+
output_np = []
252+
output_node = exported_program.graph_module.graph.output_node()
253+
for i, node in enumerate(output_node.args[0]):
254+
output_shape = node.meta["val"].shape
255+
output_dtype = node.meta["val"].dtype
256+
tosa_ref_output = np.fromfile(
257+
os.path.join(intermediate_path, f"{output_base_name}-{i}.bin"),
258+
_torch_to_numpy_dtype_dict[output_dtype],
259+
)
260+
261+
output_np.append(torch.from_numpy(tosa_ref_output).reshape(output_shape))
262+
return tuple(output_np)
263+
264+
231265
def run_vkml_emulation_layer(
232266
executorch_program_manager: ExecutorchProgramManager,
267+
inputs: Tuple[torch.Tensor],
233268
intermediate_path: str | Path,
234269
elf_path: str | Path,
235270
):
@@ -239,7 +274,7 @@ def run_vkml_emulation_layer(
239274
`intermediate_path`: Directory to save the .pte and capture outputs.
240275
`elf_path`: Path to the Vulkan-capable executor_runner binary.
241276
"""
242-
277+
exported_program = executorch_program_manager.exported_program()
243278
intermediate_path = Path(intermediate_path)
244279
intermediate_path.mkdir(exist_ok=True)
245280
elf_path = Path(elf_path)
@@ -251,26 +286,29 @@ def run_vkml_emulation_layer(
251286
with open(pte_path, "wb") as f:
252287
f.write(executorch_program_manager.buffer)
253288

254-
cmd_line = [str(elf_path), "-model_path", pte_path]
289+
output_base_name = "out"
290+
out_path = os.path.join(intermediate_path, output_base_name)
291+
292+
cmd_line = f"{elf_path} -model_path {pte_path} -output_file {out_path}"
293+
294+
input_string = None
295+
input_paths = save_inputs_to_file(exported_program, inputs, intermediate_path)
296+
for input_path in input_paths:
297+
if input_string is None:
298+
input_string = f" -inputs={input_path}"
299+
else:
300+
input_string += f",{input_path}"
301+
if input_string is not None:
302+
cmd_line += input_string
303+
cmd_line = cmd_line.split()
304+
255305
result = _run_cmd(cmd_line)
256306

257-
result_stdout = result.stdout.decode() # noqa: F841
258307
# TODO: MLETORCH-1234: Support VGF e2e tests in VgfPipeline
259308
# TODO: Add regex to check for error or fault messages in stdout from Emulation Layer
260-
# Regex to extract tensor values from stdout
261-
output_np = []
262-
matches = re.findall(
263-
r"Output\s+\d+:\s+tensor\(sizes=\[(.*?)\],\s+\[(.*?)\]\)",
264-
result_stdout,
265-
re.DOTALL,
266-
)
267-
268-
for shape_str, values_str in matches:
269-
shape = list(map(int, shape_str.split(",")))
270-
values = list(map(float, re.findall(r"[-+]?\d*\.\d+|\d+", values_str)))
271-
output_np.append(torch.tensor(values).reshape(shape))
309+
result_stdout = result.stdout.decode() # noqa: F841
272310

273-
return tuple(output_np)
311+
return get_output_from_file(exported_program, intermediate_path, output_base_name)
274312

275313

276314
def run_corstone(
@@ -312,14 +350,10 @@ def run_corstone(
312350
with open(pte_path, "wb") as f:
313351
f.write(executorch_program_manager.buffer)
314352

315-
# Save inputs to file
316-
input_names = get_input_names(exported_program)
317-
input_paths = []
318-
for input_name, input_ in zip(input_names, inputs):
319-
input_path = save_bytes(intermediate_path, input_, input_name)
320-
input_paths.append(input_path)
353+
input_paths = save_inputs_to_file(exported_program, inputs, intermediate_path)
321354

322-
out_path = os.path.join(intermediate_path, "out")
355+
output_base_name = "out"
356+
out_path = os.path.join(intermediate_path, output_base_name)
323357

324358
cmd_line = f"executor_runner -m {pte_path} -o {out_path}"
325359
for input_path in input_paths:
@@ -401,18 +435,7 @@ def run_corstone(
401435
f"Corstone simulation failed:\ncmd: {' '.join(command_args)}\nlog: \n {result_stdout}\n{result.stderr.decode()}"
402436
)
403437

404-
output_np = []
405-
output_node = exported_program.graph_module.graph.output_node()
406-
for i, node in enumerate(output_node.args[0]):
407-
output_shape = node.meta["val"].shape
408-
output_dtype = node.meta["val"].dtype
409-
tosa_ref_output = np.fromfile(
410-
os.path.join(intermediate_path, f"out-{i}.bin"),
411-
_torch_to_numpy_dtype_dict[output_dtype],
412-
)
413-
414-
output_np.append(torch.from_numpy(tosa_ref_output).reshape(output_shape))
415-
return tuple(output_np)
438+
return get_output_from_file(exported_program, intermediate_path, output_base_name)
416439

417440

418441
def prep_data_for_save(

examples/portable/executor_runner/executor_runner.cpp

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* Copyright (c) Meta Platforms, Inc. and affiliates.
3-
* Copyright 2024-2025 Arm Limited and/or its affiliates.
43
* All rights reserved.
4+
* Copyright 2024-2025 Arm Limited and/or its affiliates.
55
*
66
* This source code is licensed under the BSD-style license found in the
77
* LICENSE file in the root directory of this source tree.
@@ -18,6 +18,7 @@
1818
* all fp32 tensors.
1919
*/
2020

21+
#include <fstream>
2122
#include <iostream>
2223
#include <memory>
2324

@@ -49,6 +50,16 @@ DEFINE_string(
4950
model_path,
5051
"model.pte",
5152
"Model serialized in flatbuffer format.");
53+
DEFINE_string(inputs, "", "Comma-separated list of input files");
54+
DEFINE_string(
55+
output_file,
56+
"",
57+
"Base name of output file. If not empty output will be written to the file(s).");
58+
59+
DEFINE_bool(
60+
print_all_output,
61+
false,
62+
"Prints all output. By default only first and last 100 elements are printed.");
5263
DEFINE_uint32(num_executions, 1, "Number of times to run the model.");
5364
#ifdef ET_EVENT_TRACER_ENABLED
5465
DEFINE_string(etdump_path, "model.etdump", "Write ETDump data to this path.");
@@ -58,6 +69,8 @@ DEFINE_int32(
5869
-1,
5970
"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.");
6071

72+
using executorch::aten::ScalarType;
73+
using executorch::aten::Tensor;
6174
using executorch::extension::FileDataLoader;
6275
using executorch::runtime::Error;
6376
using executorch::runtime::EValue;
@@ -70,6 +83,8 @@ using executorch::runtime::MethodMeta;
7083
using executorch::runtime::Program;
7184
using executorch::runtime::Result;
7285
using executorch::runtime::Span;
86+
using executorch::runtime::Tag;
87+
using executorch::runtime::TensorInfo;
7388

7489
/// Helper to manage resources for ETDump generation
7590
class EventTraceManager {
@@ -156,6 +171,31 @@ int main(int argc, char** argv) {
156171
"FileDataLoader::from() failed: 0x%" PRIx32,
157172
(uint32_t)loader.error());
158173

174+
std::vector<std::string> inputs_storage;
175+
std::vector<std::pair<char*, size_t>> input_buffers;
176+
177+
std::stringstream list_of_input_files(FLAGS_inputs);
178+
std::string token;
179+
180+
while (std::getline(list_of_input_files, token, ',')) {
181+
std::ifstream input_file_handle(token, std::ios::binary | std::ios::ate);
182+
if (!input_file_handle) {
183+
ET_LOG(Error, "Failed to open input file: %s\n", token.c_str());
184+
return 1;
185+
}
186+
187+
std::streamsize file_size = input_file_handle.tellg();
188+
input_file_handle.seekg(0, std::ios::beg);
189+
190+
inputs_storage.emplace_back(file_size, '\0');
191+
if (!input_file_handle.read(&inputs_storage.back()[0], file_size)) {
192+
ET_LOG(Error, "Failed to read input file: %s\n", token.c_str());
193+
return 1;
194+
}
195+
196+
input_buffers.emplace_back(&inputs_storage.back()[0], file_size);
197+
}
198+
159199
// Parse the program file. This is immutable, and can also be reused between
160200
// multiple execution invocations across multiple threads.
161201
Result<Program> program = Program::load(&loader.get());
@@ -254,15 +294,17 @@ int main(int argc, char** argv) {
254294
// Run the model.
255295
for (uint32_t i = 0; i < FLAGS_num_executions; i++) {
256296
ET_LOG(Debug, "Preparing inputs.");
257-
// Allocate input tensors and set all of their elements to 1. The `inputs`
297+
// Allocate input tensors and set all of their elements to 1 or to the
298+
// contents of input_buffers if available. The `inputs`
258299
// variable owns the allocated memory and must live past the last call to
259300
// `execute()`.
260301
//
261302
// NOTE: we have to re-prepare input tensors on every execution
262303
// because inputs whose space gets reused by memory planning (if
263304
// any such inputs exist) will not be preserved for the next
264305
// execution.
265-
auto inputs = executorch::extension::prepare_input_tensors(*method);
306+
auto inputs = executorch::extension::prepare_input_tensors(
307+
*method, {}, input_buffers);
266308
ET_CHECK_MSG(
267309
inputs.ok(),
268310
"Could not prepare inputs: 0x%" PRIx32,
@@ -295,10 +337,66 @@ int main(int argc, char** argv) {
295337
ET_LOG(Info, "%zu outputs: ", outputs.size());
296338
Error status = method->get_outputs(outputs.data(), outputs.size());
297339
ET_CHECK(status == Error::Ok);
298-
// Print the first and last 100 elements of long lists of scalars.
299-
std::cout << executorch::extension::evalue_edge_items(100);
300-
for (int i = 0; i < outputs.size(); ++i) {
301-
std::cout << "Output " << i << ": " << outputs[i] << std::endl;
340+
341+
if (FLAGS_output_file.size() > 0) {
342+
for (int i = 0; i < outputs.size(); ++i) {
343+
if (outputs[i].isTensor()) {
344+
Tensor tensor = outputs[i].toTensor();
345+
346+
char out_filename[255];
347+
snprintf(out_filename, 255, "%s-%d.bin", FLAGS_output_file.c_str(), i);
348+
ET_LOG(Info, "Writing output to file: %s", out_filename);
349+
FILE* out_file = fopen(out_filename, "wb");
350+
fwrite(tensor.const_data_ptr<char>(), 1, tensor.nbytes(), out_file);
351+
fclose(out_file);
352+
}
353+
}
354+
}
355+
356+
if (FLAGS_print_all_output) {
357+
for (int i = 0; i < outputs.size(); ++i) {
358+
if (outputs[i].isTensor()) {
359+
Tensor tensor = outputs[i].toTensor();
360+
361+
for (int j = 0; j < tensor.numel(); ++j) {
362+
if (tensor.scalar_type() == ScalarType::Int) {
363+
printf(
364+
"Output[%d][%d]: (int) %d\n",
365+
i,
366+
j,
367+
tensor.const_data_ptr<int>()[j]);
368+
} else if (tensor.scalar_type() == ScalarType::Float) {
369+
printf(
370+
"Output[%d][%d]: (float) %f\n",
371+
i,
372+
j,
373+
tensor.const_data_ptr<float>()[j]);
374+
} else if (tensor.scalar_type() == ScalarType::Char) {
375+
printf(
376+
"Output[%d][%d]: (char) %d\n",
377+
i,
378+
j,
379+
tensor.const_data_ptr<int8_t>()[j]);
380+
} else if (tensor.scalar_type() == ScalarType::Bool) {
381+
printf(
382+
"Output[%d][%d]: (bool) %s (0x%x)\n",
383+
i,
384+
j,
385+
tensor.const_data_ptr<int8_t>()[j] ? "true " : "false",
386+
tensor.const_data_ptr<int8_t>()[j]);
387+
}
388+
}
389+
} else {
390+
printf("Output[%d]: Not Tensor\n", i);
391+
}
392+
}
393+
} else {
394+
// Print the first and last 100 elements of long lists of scalars.
395+
std::cout << executorch::extension::evalue_edge_items(100);
396+
397+
for (int i = 0; i < outputs.size(); ++i) {
398+
std::cout << "OutputX " << i << ": " << outputs[i] << std::endl;
399+
}
302400
}
303401

304402
if (tracer.get_event_tracer()) {

0 commit comments

Comments
 (0)