Skip to content

Commit e0786fe

Browse files
authored
ORT perf test support for plugin EP (microsoft#25374)
### Description Add support for onnxruntime_perf_test to register plugin EP dll and run plugin EP. As support for plugin execution providers (EPs) requires additional options and most single-character options have already been used, multi-character options are now necessary to ensure clarity and readability. Therefore, support for `Abseil flags` is added, which enables multi-character options and provides cross-platform compatibility. **New options:** - `--plugin_ep_libs [registration names and libraries]` Specifies a list of plugin execution provider (EP) registration names and their corresponding shared libraries to register. [Usage]: `--plugin_ep_libs "plugin_ep_name_1|plugin_ep_1.dll plugin_ep_name_2|plugin_ep_2.dll ... "` - `--plugin_eps [Plugin EPs]` Specifies a semicolon-separated list of plugin execution providers (EPs) to use. [Usage]: `--plugin_eps "plugin_ep_1;plugin_ep_2;... "` - `--plugin_ep_options [EP options]` Specifies provider options for each EP listed in --plugin_eps. Options (key-value pairs) for each EP are separated by space and EPs are separated by semicolons. [Usage]: `--plugin_ep_options "ep_1_option_1_key|ep_1_option_1_value ...;ep_2_option_1_key|ep_2_option_1_value ...;..."` or `--plugin_ep_options ";ep_2_option_1_key|ep_2_option_1_value ...;..."` or `--plugin_ep_options "ep_1_option_1_key|ep_1_option_1_value ...;;ep_3_option_1_key|ep_3_option_1_value ...;..."` - `--list_ep_devices` Prints all available device indices and their properties (including metadata). This option makes the program exit early without performing inference. - ` --select_ep_devices [list of device indices]` A semicolon-separated list of device indices to add to the session and run with. **Usage:** 1. Use `--plugin_ep_libs` and `--list_ep_devices` to list all the devices. ````sh --list_ep_devices --plugin_ep_libs "TensorRTEp|C:\TensorRTEp.dll example_ep|C:\example_plugin_ep.dll" ```` It will print the devices info ```` ===== EP device id 0 ====== name: CPUExecutionProvider vendor: Microsoft metadata: version: 1.23.0 ===== EP device id 1 ====== name: example_ep vendor: Contoso metadata: supported_devices: CrackGriffin 7+ version: 0.1.0 ===== EP device id 2 ====== name: TensorRTEp vendor: Nvidia metadata: gpu_type: data center version: 0.1.0 ```` 2. Use `--select_ep_devices` to select the device by index. And add `--plugin_eps` to specify the EP name. The EP name should match the name when ep library passes in to create the ep factory. ````sh --plugin_ep_libs "TensorRTEp|C:\TensorRTEp.dll" --select_ep_devices 2 --plugin_eps TensorRTEp -r 1 C:\mul_op\mul_1.onnx ```` 3. Or simply use `-e` to specify the EP name. ORT perf test will add all the devices created by the plugin EP. The EP name should match the name when ep library passes in to create the ep factory. ````sh --plugin_ep_libs "TensorRTEp|C:\TensorRTEp.dll" --plugin_eps TensorRTEp -r 1 C:\mul_op\mul_1.onnx ````
1 parent ee794af commit e0786fe

File tree

10 files changed

+722
-383
lines changed

10 files changed

+722
-383
lines changed

cmake/onnxruntime_unittests.cmake

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,7 +1258,7 @@ if (NOT onnxruntime_ENABLE_TRAINING_TORCH_INTEROP)
12581258
onnx_test_runner_common onnxruntime_test_utils onnxruntime_common
12591259
onnxruntime onnxruntime_flatbuffers onnx_test_data_proto
12601260
${onnxruntime_EXTERNAL_LIBRARIES}
1261-
${GETOPT_LIB_WIDE} ${SYS_PATH_LIB} ${CMAKE_DL_LIBS})
1261+
absl::flags absl::flags_parse ${SYS_PATH_LIB} ${CMAKE_DL_LIBS})
12621262
if(NOT WIN32)
12631263
if(onnxruntime_USE_SNPE)
12641264
list(APPEND onnxruntime_perf_test_libs onnxruntime_providers_snpe)
@@ -1278,7 +1278,7 @@ if (NOT onnxruntime_ENABLE_TRAINING_TORCH_INTEROP)
12781278
target_link_libraries(onnxruntime_perf_test PRIVATE debug dbghelp advapi32)
12791279
endif()
12801280
else()
1281-
target_link_libraries(onnxruntime_perf_test PRIVATE onnx_test_runner_common ${GETOPT_LIB_WIDE} ${onnx_test_libs})
1281+
target_link_libraries(onnxruntime_perf_test PRIVATE onnx_test_runner_common absl::flags absl::flags_parse ${onnx_test_libs})
12821282
endif()
12831283
set_target_properties(onnxruntime_perf_test PROPERTIES FOLDER "ONNXRuntimeTest")
12841284

onnxruntime/test/perftest/command_args_parser.cc

Lines changed: 457 additions & 376 deletions
Large diffs are not rendered by default.

onnxruntime/test/perftest/command_args_parser.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ struct PerformanceTestConfig;
1111

1212
class CommandLineParser {
1313
public:
14-
static void ShowUsage();
1514
static bool ParseArguments(PerformanceTestConfig& test_config, int argc, ORTCHAR_T* argv[]);
1615
};
1716

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#include "test/perftest/utils.h"
5+
#include "test/perftest/strings_helper.h"
6+
#include <core/platform/path_lib.h>
7+
8+
#include <cstdint>
9+
10+
#include <filesystem>
11+
12+
namespace onnxruntime {
13+
namespace perftest {
14+
namespace utils {
15+
16+
void ListEpDevices(const Ort::Env& env) {
17+
std::vector<Ort::ConstEpDevice> ep_devices = env.GetEpDevices();
18+
19+
for (size_t i = 0; i < ep_devices.size(); ++i) {
20+
auto device = ep_devices[i];
21+
std::string device_info_msg = "===== device id " + std::to_string(i) + " ======\n";
22+
device_info_msg += "name: " + std::string(device.EpName()) + "\n";
23+
device_info_msg += "vendor: " + std::string(device.EpVendor()) + "\n";
24+
25+
auto metadata = device.EpMetadata();
26+
std::unordered_map<std::string, std::string> metadata_entries = metadata.GetKeyValuePairs();
27+
if (!metadata_entries.empty()) {
28+
device_info_msg += "metadata:\n";
29+
}
30+
31+
for (auto& entry : metadata_entries) {
32+
device_info_msg += " " + entry.first + ": " + entry.second + "\n";
33+
}
34+
device_info_msg += "\n";
35+
fprintf(stdout, "%s", device_info_msg.c_str());
36+
}
37+
}
38+
39+
void RegisterExecutionProviderLibrary(Ort::Env& env, PerformanceTestConfig& test_config) {
40+
if (!test_config.plugin_ep_names_and_libs.empty()) {
41+
std::unordered_map<std::string, std::string> ep_names_to_libs;
42+
ParseSessionConfigs(ToUTF8String(test_config.plugin_ep_names_and_libs), ep_names_to_libs);
43+
if (ep_names_to_libs.size() > 0) {
44+
for (auto& pair : ep_names_to_libs) {
45+
const std::filesystem::path library_path = pair.second;
46+
const std::string registration_name = pair.first;
47+
Ort::Status status(Ort::GetApi().RegisterExecutionProviderLibrary(env, registration_name.c_str(), ToPathString(library_path.string()).c_str()));
48+
if (status.IsOK()) {
49+
test_config.registered_plugin_eps.push_back(registration_name);
50+
} else {
51+
fprintf(stderr, "Can't register %s plugin library: %s\n", registration_name.c_str(), status.GetErrorMessage().c_str());
52+
}
53+
}
54+
}
55+
}
56+
}
57+
58+
void UnregisterExecutionProviderLibrary(Ort::Env& env, PerformanceTestConfig& test_config) {
59+
for (auto& registration_name : test_config.registered_plugin_eps) {
60+
Ort::Status status(Ort::GetApi().UnregisterExecutionProviderLibrary(env, registration_name.c_str()));
61+
if (!status.IsOK()) {
62+
fprintf(stderr, "%s", status.GetErrorMessage().c_str());
63+
}
64+
}
65+
}
66+
67+
std::vector<std::string> ConvertArgvToUtf8Strings(int argc, ORTCHAR_T* argv[]) {
68+
std::vector<std::string> utf8_args;
69+
utf8_args.reserve(argc);
70+
for (int i = 0; i < argc; ++i) {
71+
std::string utf8_string = ToUTF8String(argv[i]);
72+
73+
// Abseil flags doens't natively alias "-h" to "--help".
74+
// We make "-h" alias to "--help" here.
75+
if (utf8_string == "-h" || utf8_string == "--h") {
76+
utf8_args.push_back("--help");
77+
} else {
78+
utf8_args.push_back(utf8_string);
79+
}
80+
}
81+
return utf8_args;
82+
}
83+
84+
std::vector<char*> CStringsFromStrings(std::vector<std::string>& utf8_args) {
85+
std::vector<char*> utf8_argv;
86+
utf8_argv.reserve(utf8_args.size());
87+
for (auto& str : utf8_args) {
88+
utf8_argv.push_back(&str[0]);
89+
}
90+
return utf8_argv;
91+
}
92+
93+
} // namespace utils
94+
} // namespace perftest
95+
} // namespace onnxruntime

onnxruntime/test/perftest/main.cc

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include <random>
77
#include "command_args_parser.h"
88
#include "performance_runner.h"
9+
#include "utils.h"
10+
#include "strings_helper.h"
911
#include <google/protobuf/stubs/common.h>
1012

1113
using namespace onnxruntime;
@@ -19,7 +21,7 @@ int real_main(int argc, char* argv[]) {
1921
g_ort = OrtGetApiBase()->GetApi(ORT_API_VERSION);
2022
perftest::PerformanceTestConfig test_config;
2123
if (!perftest::CommandLineParser::ParseArguments(test_config, argc, argv)) {
22-
perftest::CommandLineParser::ShowUsage();
24+
fprintf(stderr, "%s", "See 'onnxruntime_perf_test --help'.");
2325
return -1;
2426
}
2527
Ort::Env env{nullptr};
@@ -41,6 +43,30 @@ int real_main(int argc, char* argv[]) {
4143
if (failed)
4244
return -1;
4345
}
46+
47+
if (!test_config.plugin_ep_names_and_libs.empty()) {
48+
perftest::utils::RegisterExecutionProviderLibrary(env, test_config);
49+
}
50+
51+
// Unregister all registered plugin EP libraries before program exits.
52+
// This is necessary because unregistering the plugin EP also unregisters any associated shared allocators.
53+
// 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.
54+
// 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.
55+
// See "ep_device.ep_factory->ReleaseAllocator" in Environment::CreateSharedAllocatorImpl.
56+
auto unregister_plugin_eps_at_scope_exit = gsl::finally([&]() {
57+
if (!test_config.registered_plugin_eps.empty()) {
58+
perftest::utils::UnregisterExecutionProviderLibrary(env, test_config); // this won't throw
59+
}
60+
});
61+
62+
if (test_config.list_available_ep_devices) {
63+
perftest::utils::ListEpDevices(env);
64+
if (test_config.registered_plugin_eps.empty()) {
65+
fprintf(stdout, "No plugin execution provider libraries are registered. Please specify them using \"--plugin_ep_libs\"; otherwise, only CPU may be available.\n");
66+
}
67+
return 0;
68+
}
69+
4470
std::random_device rd;
4571
perftest::PerformanceRunner perf_runner(env, test_config, rd);
4672

onnxruntime/test/perftest/ort_test_session.cc

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,84 @@ OnnxRuntimeTestSession::OnnxRuntimeTestSession(Ort::Env& env, std::random_device
6262
: rand_engine_(rd()), input_names_(m.GetInputCount()), input_names_str_(m.GetInputCount()), input_length_(m.GetInputCount()) {
6363
Ort::SessionOptions session_options;
6464

65+
// Add EP devices if any (created by plugin EP)
66+
if (!performance_test_config.registered_plugin_eps.empty()) {
67+
std::vector<Ort::ConstEpDevice> ep_devices = env.GetEpDevices();
68+
// EP -> associated EP devices (All OrtEpDevice instances must be from the same execution provider)
69+
std::unordered_map<std::string, std::vector<Ort::ConstEpDevice>> added_ep_devices;
70+
std::unordered_set<int> added_ep_device_index_set;
71+
72+
auto& ep_list = performance_test_config.machine_config.plugin_provider_type_list;
73+
std::unordered_set<std::string> ep_set(ep_list.begin(), ep_list.end());
74+
75+
// Select EP devices by provided device index
76+
if (!performance_test_config.selected_ep_device_indices.empty()) {
77+
std::vector<int> device_list;
78+
device_list.reserve(performance_test_config.selected_ep_device_indices.size());
79+
ParseEpDeviceIndexList(performance_test_config.selected_ep_device_indices, device_list);
80+
for (auto index : device_list) {
81+
if (static_cast<size_t>(index) > (ep_devices.size() - 1)) {
82+
fprintf(stderr, "%s", "The device index provided is not correct. Will skip this device id.");
83+
continue;
84+
}
85+
86+
Ort::ConstEpDevice& device = ep_devices[index];
87+
if (ep_set.find(std::string(device.EpName())) != ep_set.end()) {
88+
if (added_ep_device_index_set.find(index) == added_ep_device_index_set.end()) {
89+
added_ep_devices[device.EpName()].push_back(device);
90+
added_ep_device_index_set.insert(index);
91+
fprintf(stdout, "[Plugin EP] EP Device [Index: %d, Name: %s] has been added to session.\n", index, device.EpName());
92+
}
93+
} else {
94+
std::string err_msg = "[Plugin EP] [WARNING] : The EP device index and its corresponding OrtEpDevice is not created from " +
95+
performance_test_config.machine_config.provider_type_name + ". Will skip adding this device.\n";
96+
fprintf(stderr, "%s", err_msg.c_str());
97+
}
98+
}
99+
} else {
100+
// Find and select the OrtEpDevice associated with the EP in "--plugin_eps".
101+
for (size_t index = 0; index < ep_devices.size(); ++index) {
102+
Ort::ConstEpDevice& device = ep_devices[index];
103+
if (ep_set.find(std::string(device.EpName())) != ep_set.end()) {
104+
added_ep_devices[device.EpName()].push_back(device);
105+
fprintf(stdout, "EP Device [Index: %d, Name: %s] has been added to session.\n", static_cast<int>(index), device.EpName());
106+
}
107+
}
108+
}
109+
110+
if (added_ep_devices.empty()) {
111+
ORT_THROW("[ERROR] [Plugin EP]: No matching EP devices found.");
112+
}
113+
114+
std::string ep_option_string = ToUTF8String(performance_test_config.run_config.ep_runtime_config_string);
115+
116+
// EP's associated provider option lists
117+
std::vector<std::unordered_map<std::string, std::string>> ep_options_list;
118+
ParseEpOptions(ep_option_string, ep_options_list);
119+
120+
// If user only provide the EPs' provider option lists for the first several EPs,
121+
// add empty provider option lists for the rest EPs.
122+
if (ep_options_list.size() < ep_list.size()) {
123+
for (size_t i = ep_options_list.size(); i < ep_list.size(); ++i) {
124+
ep_options_list.emplace_back(); // Adds a new empty map
125+
}
126+
} else if (ep_options_list.size() > ep_list.size()) {
127+
ORT_THROW("[ERROR] [Plugin EP]: Too many EP provider option lists provided.");
128+
}
129+
130+
// EP -> associated provider options
131+
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> ep_options_map;
132+
for (size_t i = 0; i < ep_list.size(); ++i) {
133+
ep_options_map.emplace(ep_list[i], ep_options_list[i]);
134+
}
135+
136+
for (auto& ep_and_devices : added_ep_devices) {
137+
auto& ep = ep_and_devices.first;
138+
auto& devices = ep_and_devices.second;
139+
session_options.AppendExecutionProvider_V2(env, devices, ep_options_map[ep]);
140+
}
141+
}
142+
65143
provider_name_ = performance_test_config.machine_config.provider_type_name;
66144
std::unordered_map<std::string, std::string> provider_options;
67145
if (provider_name_ == onnxruntime::kDnnlExecutionProvider) {

onnxruntime/test/perftest/strings_helper.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
#include "strings_helper.h"
1010
#include "core/common/common.h"
11+
#include "core/common/parse_string.h"
12+
#include "core/common/string_utils.h"
1113

1214
namespace onnxruntime {
1315
namespace perftest {
@@ -53,5 +55,40 @@ void ParseSessionConfigs(const std::string& configs_string,
5355
session_configs.insert(std::make_pair(std::move(key), std::move(value)));
5456
}
5557
}
58+
59+
void ParseEpOptions(const std::string& input, std::vector<std::unordered_map<std::string, std::string>>& result) {
60+
auto tokens = utils::SplitString(input, ";", true);
61+
62+
for (const auto& token : tokens) {
63+
result.emplace_back(); // Adds a new empty map
64+
if (!token.empty()) {
65+
ParseSessionConfigs(std::string(token), result.back()); // only parse non-empty
66+
}
67+
// if token is empty, we still get an empty map in `result`
68+
}
69+
}
70+
71+
void ParseEpList(const std::string& input, std::vector<std::string>& result) {
72+
std::stringstream ss(input);
73+
std::string token;
74+
75+
while (std::getline(ss, token, ';')) {
76+
if (!token.empty()) {
77+
result.push_back(token);
78+
}
79+
}
80+
}
81+
82+
void ParseEpDeviceIndexList(const std::string& input, std::vector<int>& result) {
83+
std::stringstream ss(input);
84+
std::string item;
85+
86+
while (std::getline(ss, item, ';')) {
87+
if (!item.empty()) {
88+
int value = ParseStringWithClassicLocale<int>(item);
89+
result.push_back(value);
90+
}
91+
}
92+
}
5693
} // namespace perftest
5794
} // namespace onnxruntime

onnxruntime/test/perftest/strings_helper.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,19 @@
55
#include <string_view>
66
#include <unordered_map>
77
#include <unordered_set>
8+
#include <vector>
89

910
namespace onnxruntime {
1011
namespace perftest {
1112

1213
void ParseSessionConfigs(const std::string& configs_string,
1314
std::unordered_map<std::string, std::string>& session_configs,
1415
const std::unordered_set<std::string>& available_keys = {});
16+
17+
void ParseEpList(const std::string& input, std::vector<std::string>& result);
18+
19+
void ParseEpOptions(const std::string& input, std::vector<std::unordered_map<std::string, std::string>>& result);
20+
21+
void ParseEpDeviceIndexList(const std::string& input, std::vector<int>& result);
1522
} // namespace perftest
1623
} // namespace onnxruntime

onnxruntime/test/perftest/test_configuration.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ struct ModelInfo {
3535
struct MachineConfig {
3636
Platform platform{Platform::kWindows};
3737
std::string provider_type_name{onnxruntime::kCpuExecutionProvider};
38+
std::vector<std::string> plugin_provider_type_list;
3839
};
3940

4041
struct RunConfig {
@@ -59,8 +60,8 @@ struct RunConfig {
5960
bool set_denormal_as_zero{false};
6061
std::basic_string<ORTCHAR_T> ep_runtime_config_string;
6162
std::unordered_map<std::string, std::string> session_config_entries;
62-
std::map<std::basic_string<ORTCHAR_T>, int64_t> free_dim_name_overrides;
63-
std::map<std::basic_string<ORTCHAR_T>, int64_t> free_dim_denotation_overrides;
63+
std::map<std::string, int64_t> free_dim_name_overrides;
64+
std::map<std::string, int64_t> free_dim_denotation_overrides;
6465
std::string intra_op_thread_affinities;
6566
bool disable_spinning = false;
6667
bool disable_spinning_between_run = false;
@@ -74,6 +75,10 @@ struct PerformanceTestConfig {
7475
ModelInfo model_info;
7576
MachineConfig machine_config;
7677
RunConfig run_config;
78+
std::basic_string<ORTCHAR_T> plugin_ep_names_and_libs;
79+
std::vector<std::string> registered_plugin_eps;
80+
std::string selected_ep_device_indices;
81+
bool list_available_ep_devices = false;
7782
};
7883

7984
} // namespace perftest

onnxruntime/test/perftest/utils.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
// Licensed under the MIT License.
33

44
#pragma once
5-
5+
#include "test/perftest/test_configuration.h"
6+
#include <core/session/onnxruntime_cxx_api.h>
67
#include <memory>
78

89
namespace onnxruntime {
@@ -22,6 +23,16 @@ class ICPUUsage {
2223

2324
std::unique_ptr<ICPUUsage> CreateICPUUsage();
2425

26+
std::vector<std::string> ConvertArgvToUtf8Strings(int argc, ORTCHAR_T* argv[]);
27+
28+
std::vector<char*> CStringsFromStrings(std::vector<std::string>& utf8_args);
29+
30+
void RegisterExecutionProviderLibrary(Ort::Env& env, PerformanceTestConfig& test_config);
31+
32+
void UnregisterExecutionProviderLibrary(Ort::Env& env, PerformanceTestConfig& test_config);
33+
34+
void ListEpDevices(const Ort::Env& env);
35+
2536
} // namespace utils
2637
} // namespace perftest
2738
} // namespace onnxruntime

0 commit comments

Comments
 (0)