Skip to content

Commit d08403c

Browse files
Add support for selection policy delegate (based on PR #24653) (#24638)
### Description Adds support for selection policy delegate directly to the release branch. This is necessary to avoid having to update C# bindings (which are in main but not in the release branch) Based on #24635 ### Motivation and Context <!-- - Why is this change required? What problem does it solve? - If it fixes an open issue, please link to the issue here. -->
1 parent 93f85fb commit d08403c

12 files changed

Lines changed: 285 additions & 50 deletions

File tree

include/onnxruntime/core/session/onnxruntime_c_api.h

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ typedef enum OrtExecutionProviderDevicePolicy {
438438
* \param max_ep_devices The maximum number of devices that can be selected in the pre-allocated array.
439439
Currently the maximum is 8.
440440
* \param num_ep_devices The number of selected devices.
441+
* \param state Opaque pointer. Required to use the delegate from other languages like C# and python.
441442
*
442443
* \return OrtStatus* Selection status. Return nullptr on success.
443444
* Use CreateStatus to provide error info. Use ORT_FAIL as the error code.
@@ -449,7 +450,8 @@ typedef OrtStatus* (*EpSelectionDelegate)(_In_ const OrtEpDevice** ep_devices,
449450
_In_opt_ const OrtKeyValuePairs* runtime_metadata,
450451
_Inout_ const OrtEpDevice** selected,
451452
_In_ size_t max_selected,
452-
_Out_ size_t* num_selected);
453+
_Out_ size_t* num_selected,
454+
_In_ void* state);
453455

454456
/** \brief Algorithm to use for cuDNN Convolution Op
455457
*/
@@ -5127,18 +5129,30 @@ struct OrtApi {
51275129

51285130
/** \brief Set the execution provider selection policy for the session.
51295131
*
5130-
* Allows users to specify a device selection policy for automatic execution provider (EP) selection,
5131-
* or provide a delegate callback for custom selection logic.
5132+
* Allows users to specify a device selection policy for automatic execution provider (EP) selection.
5133+
* If custom selection is required please use SessionOptionsSetEpSelectionPolicyDelegate instead.
51325134
*
51335135
* \param[in] session_options The OrtSessionOptions instance.
51345136
* \param[in] policy The device selection policy to use (see OrtExecutionProviderDevicePolicy).
5135-
* \param[in] delegate Optional delegate callback for custom selection. Pass nullptr to use the built-in policy.
51365137
*
51375138
* \since Version 1.22
51385139
*/
51395140
ORT_API2_STATUS(SessionOptionsSetEpSelectionPolicy, _In_ OrtSessionOptions* session_options,
5140-
_In_ OrtExecutionProviderDevicePolicy policy,
5141-
_In_opt_ EpSelectionDelegate* delegate);
5141+
_In_ OrtExecutionProviderDevicePolicy policy);
5142+
5143+
/** \brief Set the execution provider selection policy delegate for the session.
5144+
*
5145+
* Allows users to provide a custom device selection policy for automatic execution provider (EP) selection.
5146+
*
5147+
* \param[in] session_options The OrtSessionOptions instance.
5148+
* \param[in] delegate Delegate callback for custom selection.
5149+
* \param[in] delegate_state Optional state that will be passed to the delegate callback. nullptr if not required.
5150+
*
5151+
* \since Version 1.22
5152+
*/
5153+
ORT_API2_STATUS(SessionOptionsSetEpSelectionPolicyDelegate, _In_ OrtSessionOptions* session_options,
5154+
_In_ EpSelectionDelegate delegate,
5155+
_In_opt_ void* delegate_state);
51425156

51435157
/** \brief Get the hardware device type.
51445158
*

include/onnxruntime/core/session/onnxruntime_cxx_api.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,8 +1103,10 @@ struct SessionOptionsImpl : ConstSessionOptionsImpl<T> {
11031103
const std::unordered_map<std::string, std::string>& ep_options);
11041104

11051105
/// Wraps OrtApi::SessionOptionsSetEpSelectionPolicy
1106-
SessionOptionsImpl& SetEpSelectionPolicy(OrtExecutionProviderDevicePolicy policy,
1107-
EpSelectionDelegate* delegate = nullptr);
1106+
SessionOptionsImpl& SetEpSelectionPolicy(OrtExecutionProviderDevicePolicy policy);
1107+
1108+
/// Wraps OrtApi::SessionOptionsSetEpSelectionPolicyDelegate
1109+
SessionOptionsImpl& SetEpSelectionPolicy(EpSelectionDelegate delegate, void* state = nullptr);
11081110

11091111
SessionOptionsImpl& SetCustomCreateThreadFn(OrtCustomCreateThreadFn ort_custom_create_thread_fn); ///< Wraps OrtApi::SessionOptionsSetCustomCreateThreadFn
11101112
SessionOptionsImpl& SetCustomThreadCreationOptions(void* ort_custom_thread_creation_options); ///< Wraps OrtApi::SessionOptionsSetCustomThreadCreationOptions

include/onnxruntime/core/session/onnxruntime_cxx_inline.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,9 +1150,14 @@ inline SessionOptionsImpl<T>& SessionOptionsImpl<T>::AppendExecutionProvider_V2(
11501150
}
11511151

11521152
template <typename T>
1153-
inline SessionOptionsImpl<T>& SessionOptionsImpl<T>::SetEpSelectionPolicy(OrtExecutionProviderDevicePolicy policy,
1154-
EpSelectionDelegate* delegate) {
1155-
ThrowOnError(GetApi().SessionOptionsSetEpSelectionPolicy(this->p_, policy, delegate));
1153+
inline SessionOptionsImpl<T>& SessionOptionsImpl<T>::SetEpSelectionPolicy(OrtExecutionProviderDevicePolicy policy) {
1154+
ThrowOnError(GetApi().SessionOptionsSetEpSelectionPolicy(this->p_, policy));
1155+
return *this;
1156+
}
1157+
1158+
template <typename T>
1159+
inline SessionOptionsImpl<T>& SessionOptionsImpl<T>::SetEpSelectionPolicy(EpSelectionDelegate delegate, void* state) {
1160+
ThrowOnError(GetApi().SessionOptionsSetEpSelectionPolicyDelegate(this->p_, delegate, state));
11561161
return *this;
11571162
}
11581163

onnxruntime/core/framework/session_options.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ struct EpSelectionPolicy {
9696
// and no selection policy was explicitly provided.
9797
bool enable{false};
9898
OrtExecutionProviderDevicePolicy policy = OrtExecutionProviderDevicePolicy_DEFAULT;
99-
EpSelectionDelegate* delegate{};
99+
EpSelectionDelegate delegate{};
100+
void* state{nullptr}; // state for the delegate
100101
};
101102

102103
/**

onnxruntime/core/session/abi_session_options.cc

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,12 +367,24 @@ ORT_API_STATUS_IMPL(OrtApis::SetDeterministicCompute, _Inout_ OrtSessionOptions*
367367
}
368368

369369
ORT_API_STATUS_IMPL(OrtApis::SessionOptionsSetEpSelectionPolicy, _In_ OrtSessionOptions* options,
370-
_In_ OrtExecutionProviderDevicePolicy policy,
371-
_In_opt_ EpSelectionDelegate* delegate) {
370+
_In_ OrtExecutionProviderDevicePolicy policy) {
372371
API_IMPL_BEGIN
373372
options->value.ep_selection_policy.enable = true;
374373
options->value.ep_selection_policy.policy = policy;
374+
options->value.ep_selection_policy.delegate = nullptr;
375+
options->value.ep_selection_policy.state = nullptr;
376+
return nullptr;
377+
API_IMPL_END
378+
}
379+
380+
ORT_API_STATUS_IMPL(OrtApis::SessionOptionsSetEpSelectionPolicyDelegate, _In_ OrtSessionOptions* options,
381+
_In_opt_ EpSelectionDelegate delegate,
382+
_In_opt_ void* state) {
383+
API_IMPL_BEGIN
384+
options->value.ep_selection_policy.enable = true;
385+
options->value.ep_selection_policy.policy = OrtExecutionProviderDevicePolicy_DEFAULT;
375386
options->value.ep_selection_policy.delegate = delegate;
387+
options->value.ep_selection_policy.state = state;
376388
return nullptr;
377389
API_IMPL_END
378390
}

onnxruntime/core/session/inference_session.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3264,6 +3264,7 @@ common::Status InferenceSession::SaveModelMetadata(const onnxruntime::Model& mod
32643264

32653265
// save model metadata
32663266
model_metadata_.producer_name = model.ProducerName();
3267+
model_metadata_.producer_version = model.ProducerVersion();
32673268
model_metadata_.description = model.DocString();
32683269
model_metadata_.graph_description = model.GraphDocString();
32693270
model_metadata_.domain = model.Domain();
@@ -3428,6 +3429,10 @@ const Model& InferenceSession::GetModel() const {
34283429
return *model_;
34293430
}
34303431

3432+
const Environment& InferenceSession::GetEnvironment() const {
3433+
return environment_;
3434+
}
3435+
34313436
SessionIOBinding::SessionIOBinding(InferenceSession* session) : sess_(session) {
34323437
ORT_ENFORCE(session->NewIOBinding(&binding_).IsOK());
34333438
}

onnxruntime/core/session/inference_session.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ struct ModelMetadata {
7878
ModelMetadata& operator=(const ModelMetadata&) = delete;
7979

8080
std::string producer_name;
81+
std::string producer_version;
8182
std::string graph_name;
8283
std::string domain;
8384
std::string description;
@@ -601,6 +602,7 @@ class InferenceSession {
601602
#endif
602603

603604
const Model& GetModel() const;
605+
const Environment& GetEnvironment() const;
604606

605607
protected:
606608
#if !defined(ORT_MINIMAL_BUILD)

onnxruntime/core/session/onnxruntime_c_api.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3013,6 +3013,7 @@ static constexpr OrtApi ort_api_1_to_22 = {
30133013
&OrtApis::GetEpDevices,
30143014
&OrtApis::SessionOptionsAppendExecutionProvider_V2,
30153015
&OrtApis::SessionOptionsSetEpSelectionPolicy,
3016+
&OrtApis::SessionOptionsSetEpSelectionPolicyDelegate,
30163017

30173018
&OrtApis::HardwareDevice_Type,
30183019
&OrtApis::HardwareDevice_VendorId,
@@ -3062,7 +3063,7 @@ static_assert(offsetof(OrtApi, AddExternalInitializersFromFilesInMemory) / sizeo
30623063
// no additions in version 19, 20, and 21
30633064
static_assert(offsetof(OrtApi, SetEpDynamicOptions) / sizeof(void*) == 284, "Size of version 20 API cannot change");
30643065

3065-
static_assert(offsetof(OrtApi, GetEpApi) / sizeof(void*) == 316, "Size of version 22 API cannot change");
3066+
static_assert(offsetof(OrtApi, GetEpApi) / sizeof(void*) == 317, "Size of version 22 API cannot change");
30663067

30673068
// So that nobody forgets to finish an API version, this check will serve as a reminder:
30683069
static_assert(std::string_view(ORT_VERSION) == "1.22.0",

onnxruntime/core/session/ort_apis.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,8 +576,11 @@ ORT_API_STATUS_IMPL(SessionOptionsAppendExecutionProvider_V2, _In_ OrtSessionOpt
576576
size_t num_ep_options);
577577

578578
ORT_API_STATUS_IMPL(SessionOptionsSetEpSelectionPolicy, _In_ OrtSessionOptions* sess_options,
579-
_In_ OrtExecutionProviderDevicePolicy policy,
580-
_In_opt_ EpSelectionDelegate* delegate);
579+
_In_ OrtExecutionProviderDevicePolicy policy);
580+
581+
ORT_API_STATUS_IMPL(SessionOptionsSetEpSelectionPolicyDelegate, _In_ OrtSessionOptions* sess_options,
582+
_In_ EpSelectionDelegate delegate,
583+
_In_opt_ void* state);
581584

582585
// OrtHardwareDevice accessors.
583586
ORT_API(OrtHardwareDeviceType, HardwareDevice_Type, _In_ const OrtHardwareDevice* device);

onnxruntime/core/session/provider_policy_context.cc

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ std::vector<const OrtEpDevice*> OrderDevices(const std::vector<const OrtEpDevice
9494
bool bIsDefaultCpuEp = IsDefaultCpuEp(b);
9595
if (!aIsDefaultCpuEp && !bIsDefaultCpuEp) {
9696
// neither are default CPU EP. both do/don't match vendor.
97-
// TODO: implement tie-breaker for this scenario. arbitrarily prefer the first for now
98-
return true;
97+
// TODO: implement tie-breaker for this scenario. arbitrarily sort by ep name
98+
return a->ep_name < b->ep_name;
9999
}
100100

101101
// one is the default CPU EP
@@ -104,31 +104,57 @@ std::vector<const OrtEpDevice*> OrderDevices(const std::vector<const OrtEpDevice
104104

105105
return sorted_devices;
106106
}
107+
108+
OrtKeyValuePairs GetModelMetadata(const InferenceSession& session) {
109+
OrtKeyValuePairs metadata;
110+
auto status_and_metadata = session.GetModelMetadata();
111+
112+
if (!status_and_metadata.first.IsOK()) {
113+
return metadata;
114+
}
115+
116+
// use field names from onnx.proto
117+
const auto& model_metadata = *status_and_metadata.second;
118+
metadata.Add("producer_name", model_metadata.producer_name);
119+
metadata.Add("producer_version", model_metadata.producer_version);
120+
metadata.Add("domain", model_metadata.domain);
121+
metadata.Add("model_version", std::to_string(model_metadata.version));
122+
metadata.Add("doc_string", model_metadata.description);
123+
metadata.Add("graph_name", model_metadata.graph_name); // name from main GraphProto
124+
metadata.Add("graph_description", model_metadata.graph_description); // descriptions from main GraphProto
125+
for (const auto& entry : model_metadata.custom_metadata_map) {
126+
metadata.Add(entry.first, entry.second);
127+
}
128+
129+
return metadata;
130+
}
107131
} // namespace
108132

109133
// Select execution providers based on the device policy and available devices and add to session
110134
Status ProviderPolicyContext::SelectEpsForSession(const Environment& env, const OrtSessionOptions& options,
111135
InferenceSession& sess) {
112-
ORT_ENFORCE(options.value.ep_selection_policy.delegate == nullptr,
113-
"EP selection delegate support is not implemented yet.");
114-
115136
// Get the list of devices from the environment and order them.
116137
// Ordered by preference within each type. NPU -> GPU -> NPU
117138
// TODO: Should environment.cc do the ordering?
118-
const auto& execution_devices = OrderDevices(env.GetOrtEpDevices());
139+
std::vector<const OrtEpDevice*> execution_devices = OrderDevices(env.GetOrtEpDevices());
119140

120141
// The list of devices selected by policies
121142
std::vector<const OrtEpDevice*> devices_selected;
122143

123144
// Run the delegate if it was passed in lieu of any other policy
124145
if (options.value.ep_selection_policy.delegate) {
125-
auto policy_fn = options.value.ep_selection_policy.delegate;
146+
auto model_metadata = GetModelMetadata(sess);
147+
OrtKeyValuePairs runtime_metadata; // TODO: where should this come from?
148+
126149
std::vector<const OrtEpDevice*> delegate_devices(execution_devices.begin(), execution_devices.end());
127150
std::array<const OrtEpDevice*, 8> selected_devices{nullptr};
128-
129151
size_t num_selected = 0;
130-
auto* status = (*policy_fn)(delegate_devices.data(), delegate_devices.size(),
131-
nullptr, nullptr, selected_devices.data(), selected_devices.size(), &num_selected);
152+
153+
EpSelectionDelegate delegate = options.value.ep_selection_policy.delegate;
154+
auto* status = delegate(delegate_devices.data(), delegate_devices.size(),
155+
&model_metadata, &runtime_metadata,
156+
selected_devices.data(), selected_devices.size(), &num_selected,
157+
options.value.ep_selection_policy.state);
132158

133159
// return or fall-through for both these cases
134160
// going with explicit failure for now so it's obvious to user what is happening
@@ -142,6 +168,12 @@ Status ProviderPolicyContext::SelectEpsForSession(const Environment& env, const
142168
if (num_selected == 0) {
143169
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "EP selection delegate did not select anything.");
144170
}
171+
172+
// Copy the selected devices to the output vector
173+
devices_selected.reserve(num_selected);
174+
for (size_t i = 0; i < num_selected; ++i) {
175+
devices_selected.push_back(selected_devices[i]);
176+
}
145177
} else {
146178
// Create the selector for the chosen policy
147179
std::unique_ptr<IEpPolicySelector> selector;

0 commit comments

Comments
 (0)