Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
43f261d
add support for perf test to run plugin ep
chilo-ms Jul 12, 2025
8b982ab
Unregister all registered plugin EP libraries before program exit
chilo-ms Jul 12, 2025
7fd4713
remove unnecessary code
chilo-ms Jul 12, 2025
de32583
fix compile error
chilo-ms Jul 13, 2025
43ef004
Merge branch 'main' into chi/add_plugin_ep_for_perf_test
chilo-ms Jul 16, 2025
4a9c5a3
Replace getopt with cxxopts and include --list_devices and --select_d…
chilo-ms Jul 18, 2025
6cbc3ea
fix build errors on Linux
chilo-ms Jul 18, 2025
eb2220e
address build erros/warnings
chilo-ms Jul 19, 2025
3d3dacb
remove test code
chilo-ms Jul 19, 2025
aa085cb
continue the rest of converting getopt to cxxopts
chilo-ms Jul 19, 2025
c5fd68c
address compile warning
chilo-ms Jul 19, 2025
5fb5693
update usage explanation
chilo-ms Jul 19, 2025
f673e0e
remove test code
chilo-ms Jul 19, 2025
3607d68
add -DCXXOPTS_NO_RTTI for minimal build
chilo-ms Jul 21, 2025
e03d00b
address lintrunner issue
chilo-ms Jul 21, 2025
0736200
fix type
chilo-ms Jul 21, 2025
0396a27
fix typos
chilo-ms Jul 21, 2025
fe9e1de
add back getopts for the build which disables exceptions
chilo-ms Jul 21, 2025
8af32b3
add define for DISABLE_EXCEPTIONS
chilo-ms Jul 21, 2025
bd133cf
Merge branch 'main' into chi/add_plugin_ep_for_perf_test
chilo-ms Jul 22, 2025
51b3412
address reviewer's comments
chilo-ms Jul 23, 2025
e281ca5
revert code back
chilo-ms Jul 23, 2025
bdfd3f5
address reviewer's comments
chilo-ms Jul 24, 2025
22d3b80
address reviewer's comments
chilo-ms Jul 24, 2025
2cd8a30
address lint issue
chilo-ms Jul 24, 2025
ad4d8a8
add '\n' for fprintf
chilo-ms Jul 24, 2025
3e92522
address reviewers' comments
chilo-ms Jul 25, 2025
d2fb335
fix some issues
chilo-ms Jul 25, 2025
ad0ac64
update option usage example
chilo-ms Jul 25, 2025
ec5e973
revert usage description
chilo-ms Jul 25, 2025
2396d2c
Use abseil (ABSL Flags) instead of cxxopts
chilo-ms Jul 30, 2025
fdd95ea
update cmake file
chilo-ms Jul 30, 2025
3131024
get correct positional options
chilo-ms Jul 30, 2025
17eaccf
revert deps.txt
chilo-ms Jul 30, 2025
0550d6c
use PathString typedef
chilo-ms Jul 30, 2025
6cbb20f
address some of the reviewer's comments
chilo-ms Aug 1, 2025
706b1c8
fix build issues
chilo-ms Aug 1, 2025
991bb41
address reveiwer's comments
chilo-ms Aug 1, 2025
01da1a5
Alias '-h' to '--help' and remove showUsage function
chilo-ms Aug 5, 2025
3bf3183
supress mem leak info
chilo-ms Aug 5, 2025
a00c352
remove calling showUsage
chilo-ms Aug 5, 2025
7236e44
fix build issue for Linux
chilo-ms Aug 5, 2025
02499f8
fix build issue in Linux
chilo-ms Aug 5, 2025
461d57d
clean up
chilo-ms Aug 5, 2025
1cfa077
clean up
chilo-ms Aug 5, 2025
8e48b80
Add flags_internal::FlagImpl::Init to the filter of the mem check
chilo-ms Aug 5, 2025
849c0ba
Merge branch 'main' into chi/add_plugin_ep_for_perf_test
chilo-ms Aug 6, 2025
330d8ed
address reviewer's comments
chilo-ms Aug 6, 2025
a848340
Remove the filtering of expected mem leak with ABSL flags, will handl…
chilo-ms Aug 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmake/onnxruntime_unittests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1258,7 +1258,7 @@ if (NOT onnxruntime_ENABLE_TRAINING_TORCH_INTEROP)
onnx_test_runner_common onnxruntime_test_utils onnxruntime_common
onnxruntime onnxruntime_flatbuffers onnx_test_data_proto
${onnxruntime_EXTERNAL_LIBRARIES}
${GETOPT_LIB_WIDE} ${SYS_PATH_LIB} ${CMAKE_DL_LIBS})
absl::flags absl::flags_parse ${SYS_PATH_LIB} ${CMAKE_DL_LIBS})
if(NOT WIN32)
if(onnxruntime_USE_SNPE)
list(APPEND onnxruntime_perf_test_libs onnxruntime_providers_snpe)
Expand All @@ -1278,7 +1278,7 @@ if (NOT onnxruntime_ENABLE_TRAINING_TORCH_INTEROP)
target_link_libraries(onnxruntime_perf_test PRIVATE debug dbghelp advapi32)
endif()
else()
target_link_libraries(onnxruntime_perf_test PRIVATE onnx_test_runner_common ${GETOPT_LIB_WIDE} ${onnx_test_libs})
target_link_libraries(onnxruntime_perf_test PRIVATE onnx_test_runner_common absl::flags absl::flags_parse ${onnx_test_libs})
endif()
set_target_properties(onnxruntime_perf_test PROPERTIES FOLDER "ONNXRuntimeTest")

Expand Down
833 changes: 457 additions & 376 deletions onnxruntime/test/perftest/command_args_parser.cc

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion onnxruntime/test/perftest/command_args_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ struct PerformanceTestConfig;

class CommandLineParser {
public:
static void ShowUsage();
static bool ParseArguments(PerformanceTestConfig& test_config, int argc, ORTCHAR_T* argv[]);
};

Expand Down
95 changes: 95 additions & 0 deletions onnxruntime/test/perftest/common_utils.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "test/perftest/utils.h"
#include "test/perftest/strings_helper.h"
#include <core/platform/path_lib.h>

#include <cstdint>

#include <filesystem>

namespace onnxruntime {
namespace perftest {
namespace utils {

void ListEpDevices(const Ort::Env& env) {
std::vector<Ort::ConstEpDevice> ep_devices = env.GetEpDevices();

for (size_t i = 0; i < ep_devices.size(); ++i) {
auto device = ep_devices[i];
std::string device_info_msg = "===== device id " + std::to_string(i) + " ======\n";
device_info_msg += "name: " + std::string(device.EpName()) + "\n";
device_info_msg += "vendor: " + std::string(device.EpVendor()) + "\n";

auto metadata = device.EpMetadata();
std::unordered_map<std::string, std::string> metadata_entries = metadata.GetKeyValuePairs();
if (!metadata_entries.empty()) {
device_info_msg += "metadata:\n";
}

for (auto& entry : metadata_entries) {
device_info_msg += " " + entry.first + ": " + entry.second + "\n";
}
device_info_msg += "\n";
fprintf(stdout, "%s", device_info_msg.c_str());
}
}

void RegisterExecutionProviderLibrary(Ort::Env& env, PerformanceTestConfig& test_config) {
if (!test_config.plugin_ep_names_and_libs.empty()) {
std::unordered_map<std::string, std::string> ep_names_to_libs;
ParseSessionConfigs(ToUTF8String(test_config.plugin_ep_names_and_libs), ep_names_to_libs);
if (ep_names_to_libs.size() > 0) {
for (auto& pair : ep_names_to_libs) {
const std::filesystem::path library_path = pair.second;
const std::string registration_name = pair.first;
Ort::Status status(Ort::GetApi().RegisterExecutionProviderLibrary(env, registration_name.c_str(), ToPathString(library_path.string()).c_str()));
if (status.IsOK()) {
test_config.registered_plugin_eps.push_back(registration_name);
} else {
fprintf(stderr, "Can't register %s plugin library: %s\n", registration_name.c_str(), status.GetErrorMessage().c_str());
}
}
}
}
}

void UnregisterExecutionProviderLibrary(Ort::Env& env, PerformanceTestConfig& test_config) {
for (auto& registration_name : test_config.registered_plugin_eps) {
Ort::Status status(Ort::GetApi().UnregisterExecutionProviderLibrary(env, registration_name.c_str()));
if (!status.IsOK()) {
fprintf(stderr, "%s", status.GetErrorMessage().c_str());
}
}
}

std::vector<std::string> ConvertArgvToUtf8Strings(int argc, ORTCHAR_T* argv[]) {
std::vector<std::string> utf8_args;
utf8_args.reserve(argc);
for (int i = 0; i < argc; ++i) {
std::string utf8_string = ToUTF8String(argv[i]);

// Abseil flags doens't natively alias "-h" to "--help".
// We make "-h" alias to "--help" here.
if (utf8_string == "-h" || utf8_string == "--h") {
utf8_args.push_back("--help");
} else {
utf8_args.push_back(utf8_string);
}
}
return utf8_args;
}

std::vector<char*> CStringsFromStrings(std::vector<std::string>& utf8_args) {
std::vector<char*> utf8_argv;
utf8_argv.reserve(utf8_args.size());
for (auto& str : utf8_args) {
utf8_argv.push_back(&str[0]);
}
return utf8_argv;
}

} // namespace utils
} // namespace perftest
} // namespace onnxruntime
28 changes: 27 additions & 1 deletion onnxruntime/test/perftest/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <random>
#include "command_args_parser.h"
#include "performance_runner.h"
#include "utils.h"
#include "strings_helper.h"
#include <google/protobuf/stubs/common.h>

using namespace onnxruntime;
Expand All @@ -19,7 +21,7 @@ int real_main(int argc, char* argv[]) {
g_ort = OrtGetApiBase()->GetApi(ORT_API_VERSION);
perftest::PerformanceTestConfig test_config;
if (!perftest::CommandLineParser::ParseArguments(test_config, argc, argv)) {
perftest::CommandLineParser::ShowUsage();
fprintf(stderr, "%s", "See 'onnxruntime_perf_test --help'.");
return -1;
}
Ort::Env env{nullptr};
Expand All @@ -41,6 +43,30 @@ int real_main(int argc, char* argv[]) {
if (failed)
return -1;
}

if (!test_config.plugin_ep_names_and_libs.empty()) {
perftest::utils::RegisterExecutionProviderLibrary(env, test_config);
}

// Unregister all registered plugin EP libraries before program exits.
// This is necessary because unregistering the plugin EP also unregisters any associated shared allocators.
// If we don't do this and program returns, the factories stored inside the environment will be destroyed when the environment goes out of scope.
// Later, when the shared allocator's deleter runs, it may cause a segmentation fault because it attempts to use the already-destroyed factory to call ReleaseAllocator.
// See "ep_device.ep_factory->ReleaseAllocator" in Environment::CreateSharedAllocatorImpl.
auto unregister_plugin_eps_at_scope_exit = gsl::finally([&]() {
if (!test_config.registered_plugin_eps.empty()) {
perftest::utils::UnregisterExecutionProviderLibrary(env, test_config); // this won't throw
}
});

if (test_config.list_available_ep_devices) {
perftest::utils::ListEpDevices(env);
if (test_config.registered_plugin_eps.empty()) {
fprintf(stdout, "No plugin execution provider libraries are registered. Please specify them using \"--plugin_ep_libs\"; otherwise, only CPU may be available.\n");
}
return 0;
}

std::random_device rd;
perftest::PerformanceRunner perf_runner(env, test_config, rd);

Expand Down
78 changes: 78 additions & 0 deletions onnxruntime/test/perftest/ort_test_session.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,84 @@ OnnxRuntimeTestSession::OnnxRuntimeTestSession(Ort::Env& env, std::random_device
: rand_engine_(rd()), input_names_(m.GetInputCount()), input_names_str_(m.GetInputCount()), input_length_(m.GetInputCount()) {
Ort::SessionOptions session_options;

// Add EP devices if any (created by plugin EP)
if (!performance_test_config.registered_plugin_eps.empty()) {
std::vector<Ort::ConstEpDevice> ep_devices = env.GetEpDevices();
// EP -> associated EP devices (All OrtEpDevice instances must be from the same execution provider)
std::unordered_map<std::string, std::vector<Ort::ConstEpDevice>> added_ep_devices;
std::unordered_set<int> added_ep_device_index_set;

auto& ep_list = performance_test_config.machine_config.plugin_provider_type_list;
std::unordered_set<std::string> ep_set(ep_list.begin(), ep_list.end());

// Select EP devices by provided device index
if (!performance_test_config.selected_ep_device_indices.empty()) {
std::vector<int> device_list;
device_list.reserve(performance_test_config.selected_ep_device_indices.size());
ParseEpDeviceIndexList(performance_test_config.selected_ep_device_indices, device_list);
for (auto index : device_list) {
if (static_cast<size_t>(index) > (ep_devices.size() - 1)) {
fprintf(stderr, "%s", "The device index provided is not correct. Will skip this device id.");
continue;
}

Ort::ConstEpDevice& device = ep_devices[index];
if (ep_set.find(std::string(device.EpName())) != ep_set.end()) {
if (added_ep_device_index_set.find(index) == added_ep_device_index_set.end()) {
added_ep_devices[device.EpName()].push_back(device);
added_ep_device_index_set.insert(index);
fprintf(stdout, "[Plugin EP] EP Device [Index: %d, Name: %s] has been added to session.\n", index, device.EpName());
}
} else {
std::string err_msg = "[Plugin EP] [WARNING] : The EP device index and its corresponding OrtEpDevice is not created from " +
performance_test_config.machine_config.provider_type_name + ". Will skip adding this device.\n";
fprintf(stderr, "%s", err_msg.c_str());
}
}
} else {
// Find and select the OrtEpDevice associated with the EP in "--plugin_eps".
for (size_t index = 0; index < ep_devices.size(); ++index) {
Ort::ConstEpDevice& device = ep_devices[index];
if (ep_set.find(std::string(device.EpName())) != ep_set.end()) {
added_ep_devices[device.EpName()].push_back(device);
fprintf(stdout, "EP Device [Index: %d, Name: %s] has been added to session.\n", static_cast<int>(index), device.EpName());
}
}
}

if (added_ep_devices.empty()) {
ORT_THROW("[ERROR] [Plugin EP]: No matching EP devices found.");
}

std::string ep_option_string = ToUTF8String(performance_test_config.run_config.ep_runtime_config_string);

// EP's associated provider option lists
std::vector<std::unordered_map<std::string, std::string>> ep_options_list;
ParseEpOptions(ep_option_string, ep_options_list);

// If user only provide the EPs' provider option lists for the first several EPs,
// add empty provider option lists for the rest EPs.
if (ep_options_list.size() < ep_list.size()) {
for (size_t i = ep_options_list.size(); i < ep_list.size(); ++i) {
ep_options_list.emplace_back(); // Adds a new empty map
}
} else if (ep_options_list.size() > ep_list.size()) {
ORT_THROW("[ERROR] [Plugin EP]: Too many EP provider option lists provided.");
}

// EP -> associated provider options
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> ep_options_map;
for (size_t i = 0; i < ep_list.size(); ++i) {
ep_options_map.emplace(ep_list[i], ep_options_list[i]);
}

for (auto& ep_and_devices : added_ep_devices) {
auto& ep = ep_and_devices.first;
auto& devices = ep_and_devices.second;
session_options.AppendExecutionProvider_V2(env, devices, ep_options_map[ep]);
}
}

provider_name_ = performance_test_config.machine_config.provider_type_name;
std::unordered_map<std::string, std::string> provider_options;
if (provider_name_ == onnxruntime::kDnnlExecutionProvider) {
Expand Down
37 changes: 37 additions & 0 deletions onnxruntime/test/perftest/strings_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#include "strings_helper.h"
#include "core/common/common.h"
#include "core/common/parse_string.h"
#include "core/common/string_utils.h"

namespace onnxruntime {
namespace perftest {
Expand Down Expand Up @@ -53,5 +55,40 @@ void ParseSessionConfigs(const std::string& configs_string,
session_configs.insert(std::make_pair(std::move(key), std::move(value)));
}
}

void ParseEpOptions(const std::string& input, std::vector<std::unordered_map<std::string, std::string>>& result) {
auto tokens = utils::SplitString(input, ";", true);

for (const auto& token : tokens) {
result.emplace_back(); // Adds a new empty map
if (!token.empty()) {
ParseSessionConfigs(std::string(token), result.back()); // only parse non-empty
}
// if token is empty, we still get an empty map in `result`
}
}

void ParseEpList(const std::string& input, std::vector<std::string>& result) {
std::stringstream ss(input);
std::string token;

while (std::getline(ss, token, ';')) {
if (!token.empty()) {
result.push_back(token);
}
}
}

void ParseEpDeviceIndexList(const std::string& input, std::vector<int>& result) {
std::stringstream ss(input);
std::string item;

while (std::getline(ss, item, ';')) {
if (!item.empty()) {
int value = ParseStringWithClassicLocale<int>(item);
result.push_back(value);
}
}
}
} // namespace perftest
} // namespace onnxruntime
7 changes: 7 additions & 0 deletions onnxruntime/test/perftest/strings_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>

namespace onnxruntime {
namespace perftest {

void ParseSessionConfigs(const std::string& configs_string,
std::unordered_map<std::string, std::string>& session_configs,
const std::unordered_set<std::string>& available_keys = {});

void ParseEpList(const std::string& input, std::vector<std::string>& result);

void ParseEpOptions(const std::string& input, std::vector<std::unordered_map<std::string, std::string>>& result);

void ParseEpDeviceIndexList(const std::string& input, std::vector<int>& result);
} // namespace perftest
} // namespace onnxruntime
9 changes: 7 additions & 2 deletions onnxruntime/test/perftest/test_configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct ModelInfo {
struct MachineConfig {
Platform platform{Platform::kWindows};
std::string provider_type_name{onnxruntime::kCpuExecutionProvider};
std::vector<std::string> plugin_provider_type_list;
};

struct RunConfig {
Expand All @@ -59,8 +60,8 @@ struct RunConfig {
bool set_denormal_as_zero{false};
std::basic_string<ORTCHAR_T> ep_runtime_config_string;
std::unordered_map<std::string, std::string> session_config_entries;
std::map<std::basic_string<ORTCHAR_T>, int64_t> free_dim_name_overrides;
std::map<std::basic_string<ORTCHAR_T>, int64_t> free_dim_denotation_overrides;
std::map<std::string, int64_t> free_dim_name_overrides;
std::map<std::string, int64_t> free_dim_denotation_overrides;
std::string intra_op_thread_affinities;
bool disable_spinning = false;
bool disable_spinning_between_run = false;
Expand All @@ -74,6 +75,10 @@ struct PerformanceTestConfig {
ModelInfo model_info;
MachineConfig machine_config;
RunConfig run_config;
std::basic_string<ORTCHAR_T> plugin_ep_names_and_libs;
std::vector<std::string> registered_plugin_eps;
std::string selected_ep_device_indices;
bool list_available_ep_devices = false;
};

} // namespace perftest
Expand Down
13 changes: 12 additions & 1 deletion onnxruntime/test/perftest/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// Licensed under the MIT License.

#pragma once

#include "test/perftest/test_configuration.h"
#include <core/session/onnxruntime_cxx_api.h>
#include <memory>

namespace onnxruntime {
Expand All @@ -22,6 +23,16 @@ class ICPUUsage {

std::unique_ptr<ICPUUsage> CreateICPUUsage();

std::vector<std::string> ConvertArgvToUtf8Strings(int argc, ORTCHAR_T* argv[]);

std::vector<char*> CStringsFromStrings(std::vector<std::string>& utf8_args);

void RegisterExecutionProviderLibrary(Ort::Env& env, PerformanceTestConfig& test_config);

void UnregisterExecutionProviderLibrary(Ort::Env& env, PerformanceTestConfig& test_config);

void ListEpDevices(const Ort::Env& env);

} // namespace utils
} // namespace perftest
} // namespace onnxruntime
Loading