From efd4b92a63b01676179e2eff086b55b22e9eff35 Mon Sep 17 00:00:00 2001 From: Xiaoxi Han Date: Tue, 6 Jan 2026 13:28:28 -0800 Subject: [PATCH 01/11] add GetOrtHardwareDevices and GetHardwareDeviceEPIncompatibilityReasons APIs and tests --- .../onnxruntime/core/session/environment.h | 13 + .../core/session/onnxruntime_c_api.h | 106 ++++++ .../core/session/onnxruntime_ep_c_api.h | 41 +++ onnxruntime/core/session/abi_devices.h | 6 + onnxruntime/core/session/environment.cc | 152 +++++--- onnxruntime/core/session/onnxruntime_c_api.cc | 100 ++++++ onnxruntime/core/session/ort_apis.h | 12 + onnxruntime/core/session/plugin_ep/ep_api.cc | 23 +- .../session/plugin_ep/ep_factory_internal.cc | 1 + .../session/plugin_ep/ep_factory_internal.h | 5 + .../plugin_ep/ep_factory_internal_impl.h | 7 + .../plugin_ep/ep_factory_provider_bridge.h | 10 + .../plugin_ep/forward_to_factory_impl.h | 6 + .../library/example_plugin_ep/ep_factory.cc | 25 ++ .../library/example_plugin_ep/ep_factory.h | 5 + onnxruntime/test/autoep/test_execution.cc | 97 ++++++ .../hardware_device_compatibility_test.cc | 329 ++++++++++++++++++ 17 files changed, 892 insertions(+), 46 deletions(-) create mode 100644 onnxruntime/test/framework/hardware_device_compatibility_test.cc diff --git a/include/onnxruntime/core/session/environment.h b/include/onnxruntime/core/session/environment.h index 59ca1a1df762e..08516f6fdf7f9 100644 --- a/include/onnxruntime/core/session/environment.h +++ b/include/onnxruntime/core/session/environment.h @@ -146,6 +146,17 @@ class Environment { return execution_devices_; } + /// Get hardware device incompatibility reasons for a specific EP. + /// @param ep_name The name of the execution provider to check. + /// @param hw The hardware device to check for incompatibility. + /// @param details Output: Incompatibility details including reasons for incompatibility if any. + /// @returns Status indicating success or failure. + Status GetHardwareDeviceEPIncompatibilityReasons(const std::string& ep_name, + const OrtHardwareDevice* hw, + std::unique_ptr& details) const; + + const std::vector& GetSortedOrtHardwareDevices() const; + Status CreateSharedAllocator(const OrtEpDevice& ep_device, OrtDeviceMemoryType mem_type, OrtAllocatorType allocator_type, const OrtKeyValuePairs* allocator_options, OrtAllocator** allocator); @@ -242,6 +253,8 @@ class Environment { DataTransferManager data_transfer_mgr_; // plugin EP IDataTransfer instances + mutable std::vector ort_hardware_devices_; + #endif // !defined(ORT_MINIMAL_BUILD) }; diff --git a/include/onnxruntime/core/session/onnxruntime_c_api.h b/include/onnxruntime/core/session/onnxruntime_c_api.h index 303bb5411ffd9..06c8c55a195e1 100644 --- a/include/onnxruntime/core/session/onnxruntime_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_c_api.h @@ -333,6 +333,7 @@ ORT_RUNTIME_CLASS(ExternalInitializerInfo); ORT_RUNTIME_CLASS(ExternalResourceImporter); // Capability object for external resource import ORT_RUNTIME_CLASS(ExternalMemoryHandle); // EP-imported view of shared external allocation ORT_RUNTIME_CLASS(ExternalSemaphoreHandle); // EP-imported view of shared external semaphore +ORT_RUNTIME_CLASS(DeviceEpIncompatibilityDetails); #ifdef _MSC_VER typedef _Return_type_success_(return == 0) OrtStatus* OrtStatusPtr; @@ -510,6 +511,16 @@ typedef enum OrtExecutionProviderDevicePolicy { OrtExecutionProviderDevicePolicy_MIN_OVERALL_POWER, } OrtExecutionProviderDevicePolicy; +/** \brief Reasons why an execution provider might not be compatible with a device + */ +typedef enum OrtDeviceEpIncompatibilityReason { + OrtDeviceEpIncompatibility_NONE = 0, + OrtDeviceEpIncompatibility_DRIVER_INCOMPATIBLE = 1u << 0, + OrtDeviceEpIncompatibility_DEVICE_INCOMPATIBLE = 1u << 1, + OrtDeviceEpIncompatibility_MISSING_DEPENDENCY = 1u << 2, + OrtDeviceEpIncompatibility_UNKNOWN = 1u << 31 +} OrtDeviceEpIncompatibilityReason; + /** \brief Delegate to allow providing custom OrtEpDevice selection logic * * This delegate is called by the EP selection code to allow the user to provide custom device selection logic. @@ -6784,6 +6795,101 @@ struct OrtApi { ORT_API2_STATUS(SessionGetEpDeviceForOutputs, _In_ const OrtSession* session, _Out_writes_(num_outputs) const OrtEpDevice** outputs_ep_devices, _In_ size_t num_outputs); + /** \brief Get the list of available hardware devices. + * + * Enumerates hardware devices available on the system. Device discovery results + * are stored in the ORT environment. + * + * \param[in] env The OrtEnv instance where device discovery results are stored. + * \param[out] devices The OrtHardwareDevice instances that are available. + * \param[out] num_devices The number of OrtHardwareDevice instances returned. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24. + */ + ORT_API2_STATUS(GetOrtHardwareDevices, _In_ const OrtEnv* env, + _Outptr_ const OrtHardwareDevice* const** devices, + _Out_ size_t* num_devices); + + /** \brief Check for known incompatibility reasons between hardware device and a specific execution provider. + * + * This function checks for known incompatibility reasons between the specified hardware device + * and a specific execution provider. + * If incompatibility reasons are returned, it indicates the device is not compatible. + * However, if no reasons are returned, it doesn't guarantee 100% compatibility for all models, + * as models may have specific requirements. + * + * Note: This method should only be called when the OrtEnv has been initialized with execution + * providers (after RegisterExecutionProviderLibrary is called). + * + * \param[in] env The OrtEnv instance with registered execution providers. + * \param[in] ep_name The name of the execution provider to check. Required and cannot be null or empty. + * \param[in] hw The hardware device to check for incompatibility. + * \param[out] details Compatibility details including reasons for incompatibility if any. + * Must be freed with OrtApi::ReleaseDeviceEpIncompatibilityDetails. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24. + */ + ORT_API2_STATUS(GetHardwareDeviceEPIncompatibilityReasons, _In_ const OrtEnv* env, + _In_ const char* ep_name, + _In_ const OrtHardwareDevice* hw, + _Outptr_ OrtDeviceEpIncompatibilityDetails** details); + + /// \name OrtDeviceEpIncompatibilityDetails + /// Accessor functions for device incompatibility details + /// @{ + + /** \brief Get the incompatibility reasons bitmask from OrtDeviceEpIncompatibilityDetails. + * + * \param[in] details The OrtDeviceEpIncompatibilityDetails instance to query. + * \param[out] reasons_bitmask Pointer to store the bitmask of incompatibility reasons. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24. + */ + ORT_API2_STATUS(DeviceEpIncompatibilityDetails_GetReasonsBitmask, + _In_ const OrtDeviceEpIncompatibilityDetails* details, + _Out_ uint32_t* reasons_bitmask); + + /** \brief Get the notes from OrtDeviceEpIncompatibilityDetails. + * + * \param[in] details The OrtDeviceEpIncompatibilityDetails instance to query. + * \param[out] notes Pointer to the notes string. May be nullptr if no notes are available. + * The returned string is owned by the details object and should not be freed. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24. + */ + ORT_API2_STATUS(DeviceEpIncompatibilityDetails_GetNotes, + _In_ const OrtDeviceEpIncompatibilityDetails* details, + _Outptr_result_maybenull_ const char** notes); + + /** \brief Get the execution provider error code from OrtDeviceEpIncompatibilityDetails. + * + * This allows Independent Hardware Vendors (IHVs) to define their own error codes + * to provide additional details about device incompatibility. + * + * \param[in] details The OrtDeviceEpIncompatibilityDetails instance to query. + * \param[out] error_code Pointer to store the EP-specific error code. A value of 0 indicates no error code was set. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24. + */ + ORT_API2_STATUS(DeviceEpIncompatibilityDetails_GetErrorCode, + _In_ const OrtDeviceEpIncompatibilityDetails* details, + _Out_ int32_t* error_code); + + /** \brief Release an OrtDeviceEpIncompatibilityDetails instance. + * + * \since Version 1.24. + */ + ORT_CLASS_RELEASE(DeviceEpIncompatibilityDetails); /// @} }; diff --git a/include/onnxruntime/core/session/onnxruntime_ep_c_api.h b/include/onnxruntime/core/session/onnxruntime_ep_c_api.h index 0db06f23dcd4a..ad0141bb426c8 100644 --- a/include/onnxruntime/core/session/onnxruntime_ep_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_ep_c_api.h @@ -1408,6 +1408,25 @@ struct OrtEpApi { _Outptr_ OrtKernelImpl** kernel_out); ORT_CLASS_RELEASE(KernelImpl); + /** \brief Create an OrtDeviceEpIncompatibilityDetails instance. + * + * Used by execution provider factories to create incompatibility details in their + * GetHardwareDeviceIncompatibilityReasons implementation. + * + * \param[in] reasons_bitmask Bitmask of OrtDeviceEpIncompatibilityReason values. + * \param[in] error_code EP-specific error code (0 = no error). + * \param[in] notes Optional human-readable notes. Can be null. + * \param[out] details Output parameter set to the new OrtDeviceEpIncompatibilityDetails instance. + * Must be released by ORT using ReleaseDeviceEpIncompatibilityDetails. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24. + */ + ORT_API2_STATUS(CreateDeviceEpIncompatibilityDetails, _In_ uint32_t reasons_bitmask, + _In_ int32_t error_code, + _In_opt_z_ const char* notes, + _Outptr_ OrtDeviceEpIncompatibilityDetails** details); }; /** @@ -2015,6 +2034,28 @@ struct OrtEpFactory { ORT_API2_STATUS(CreateExternalResourceImporterForDevice, _In_ OrtEpFactory* this_ptr, _In_ const OrtEpDevice* ep_device, _Outptr_result_maybenull_ OrtExternalResourceImporterImpl** out_importer); + /** \brief Check for known incompatibility reasons between a hardware device and this execution provider. + * + * This function allows an execution provider to check if a specific hardware device is compatible + * with the execution provider. The EP can return specific incompatibility reasons via the + * OrtDeviceEpIncompatibilityDetails output parameter. + * + * \param[in] this_ptr The OrtEpFactory instance. + * \param[in] hw The hardware device to check for incompatibility. + * \param[out] details Incompatibility details including reasons for incompatibility if any. + * The EP should allocate this using OrtEpApi::CreateDeviceEpIncompatibilityDetails. + * ORT will take ownership and call ReleaseDeviceEpIncompatibilityDetails. + * + * \note Implementation of this function is optional. + * If not implemented, ORT will assume the device is compatible with this EP. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24. + */ + ORT_API2_STATUS(GetHardwareDeviceIncompatibilityReasons, _In_ OrtEpFactory* this_ptr, + _In_ const OrtHardwareDevice* hw, + _Outptr_ OrtDeviceEpIncompatibilityDetails** details); }; #ifdef __cplusplus diff --git a/onnxruntime/core/session/abi_devices.h b/onnxruntime/core/session/abi_devices.h index 571a9eb2a54e2..12107cc4ff8ab 100644 --- a/onnxruntime/core/session/abi_devices.h +++ b/onnxruntime/core/session/abi_devices.h @@ -75,3 +75,9 @@ struct OrtEpDevice { // get/create methods to be as flexible as possible. this helper converts to a non-const factory instance. OrtEpFactory* GetMutableFactory() const { return ep_factory; } }; + +struct OrtDeviceEpIncompatibilityDetails { + uint32_t reasons_bitmask{0}; // Bitmask of OrtDeviceEpIncompatibilityReason values + int32_t error_code{0}; // EP-specific error code (0 = no error) + std::string notes; // Additional human-readable notes +}; diff --git a/onnxruntime/core/session/environment.cc b/onnxruntime/core/session/environment.cc index 9008a906155fd..118dec073a210 100644 --- a/onnxruntime/core/session/environment.cc +++ b/onnxruntime/core/session/environment.cc @@ -637,6 +637,113 @@ Status Environment::UnregisterExecutionProviderLibrary(const std::string& ep_nam return status; } +Status Environment::GetHardwareDeviceEPIncompatibilityReasons( + const std::string& ep_name, + const OrtHardwareDevice* hw, + std::unique_ptr& details) const { + std::lock_guard lock{mutex_}; + + OrtEpFactory* matched_factory = nullptr; + + // Search for a factory whose GetName() matches ep_name exactly. + for (const auto& [registration_name, ep_info] : ep_libraries_) { + for (OrtEpFactory* factory : ep_info->factories) { + if (factory != nullptr && factory->GetName != nullptr) { + const char* factory_name = factory->GetName(factory); + if (factory_name != nullptr && ep_name == factory_name) { + matched_factory = factory; + break; + } + } + } + if (matched_factory != nullptr) { + break; + } + } + + if (matched_factory == nullptr) { + return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, + "No valid factory found for execution provider '", ep_name, "'."); + } + + // Check if the factory implements GetHardwareDeviceIncompatibilityReasons + if (matched_factory->GetHardwareDeviceIncompatibilityReasons != nullptr) { + OrtDeviceEpIncompatibilityDetails* factory_details = nullptr; + OrtStatusPtr status = matched_factory->GetHardwareDeviceIncompatibilityReasons(matched_factory, hw, &factory_details); + + if (status != nullptr) { + return ToStatusAndRelease(status); + } + + if (factory_details != nullptr) { + details.reset(factory_details); + return Status::OK(); + } + } + + // Factory doesn't implement the hook or returned nullptr - create empty details (compatible) + details = std::make_unique(); + details->reasons_bitmask = 0; + details->error_code = 0; + details->notes = ""; + + return Status::OK(); +} + +namespace { +std::vector SortDevicesByType() { + auto& devices = DeviceDiscovery::GetDevices(); + std::vector sorted_devices; + sorted_devices.reserve(devices.size()); + + const auto select_by_type = [&](OrtHardwareDeviceType type) { + for (const auto& device : devices) { + if (device.type == type) { + sorted_devices.push_back(&device); + } + } + }; + + select_by_type(OrtHardwareDeviceType_NPU); + select_by_type(OrtHardwareDeviceType_GPU); + select_by_type(OrtHardwareDeviceType_CPU); + + return sorted_devices; +} + +bool AreVirtualDevicesAllowed(std::string_view lib_registration_name) { + constexpr std::string_view suffix{".virtual"}; + + return lib_registration_name.size() >= suffix.size() && + lib_registration_name.compare(lib_registration_name.size() - suffix.size(), + suffix.size(), suffix) == 0; +} + +Status SetEpFactoryEnvironmentOptions(OrtEpFactory& factory, std::string_view lib_registration_name) { + // OrtEpFactory::SetEnvironmentOptions was added in ORT 1.24 + if (factory.ort_version_supported < 24 || factory.SetEnvironmentOptions == nullptr) { + return Status::OK(); + } + + // We only set one option now but this can be generalized if necessary. + OrtKeyValuePairs options; + options.Add("allow_virtual_devices", AreVirtualDevicesAllowed(lib_registration_name) ? "1" : "0"); + + ORT_RETURN_IF_ERROR(ToStatusAndRelease(factory.SetEnvironmentOptions(&factory, &options))); + + return Status::OK(); +} +} // namespace + +const std::vector& Environment::GetSortedOrtHardwareDevices() const +{ + std::lock_guard lock{mutex_}; + if (ort_hardware_devices_.empty()) { + ort_hardware_devices_ = SortDevicesByType(); + } + return ort_hardware_devices_; +} + Status Environment::CreateSharedAllocator(const OrtEpDevice& ep_device, OrtDeviceMemoryType mem_type, OrtAllocatorType allocator_type, const OrtKeyValuePairs* allocator_options, @@ -728,51 +835,6 @@ Status Environment::ReleaseSharedAllocator(const OrtEpDevice& ep_device, OrtDevi return status; } -namespace { -std::vector SortDevicesByType() { - auto& devices = DeviceDiscovery::GetDevices(); - std::vector sorted_devices; - sorted_devices.reserve(devices.size()); - - const auto select_by_type = [&](OrtHardwareDeviceType type) { - for (const auto& device : devices) { - if (device.type == type) { - sorted_devices.push_back(&device); - } - } - }; - - select_by_type(OrtHardwareDeviceType_NPU); - select_by_type(OrtHardwareDeviceType_GPU); - select_by_type(OrtHardwareDeviceType_CPU); - - return sorted_devices; -} - -bool AreVirtualDevicesAllowed(std::string_view lib_registration_name) { - constexpr std::string_view suffix{".virtual"}; - - return lib_registration_name.size() >= suffix.size() && - lib_registration_name.compare(lib_registration_name.size() - suffix.size(), - suffix.size(), suffix) == 0; -} - -Status SetEpFactoryEnvironmentOptions(OrtEpFactory& factory, std::string_view lib_registration_name) { - // OrtEpFactory::SetEnvironmentOptions was added in ORT 1.24 - if (factory.ort_version_supported < 24 || factory.SetEnvironmentOptions == nullptr) { - return Status::OK(); - } - - // We only set one option now but this can be generalized if necessary. - OrtKeyValuePairs options; - options.Add("allow_virtual_devices", AreVirtualDevicesAllowed(lib_registration_name) ? "1" : "0"); - - ORT_RETURN_IF_ERROR(ToStatusAndRelease(factory.SetEnvironmentOptions(&factory, &options))); - - return Status::OK(); -} -} // namespace - Status Environment::EpInfo::Create(std::unique_ptr library_in, std::unique_ptr& out, const std::vector& internal_factories) { if (!library_in) { diff --git a/onnxruntime/core/session/onnxruntime_c_api.cc b/onnxruntime/core/session/onnxruntime_c_api.cc index c3bf74a4607e8..3b3846775201b 100644 --- a/onnxruntime/core/session/onnxruntime_c_api.cc +++ b/onnxruntime/core/session/onnxruntime_c_api.cc @@ -4291,6 +4291,12 @@ static constexpr OrtApi ort_api_1_to_24 = { &OrtApis::GetInteropApi, &OrtApis::SessionGetEpDeviceForOutputs, + &OrtApis::GetOrtHardwareDevices, + &OrtApis::GetHardwareDeviceEPIncompatibilityReasons, + &OrtApis::DeviceEpIncompatibilityDetails_GetReasonsBitmask, + &OrtApis::DeviceEpIncompatibilityDetails_GetNotes, + &OrtApis::DeviceEpIncompatibilityDetails_GetErrorCode, + &OrtApis::ReleaseDeviceEpIncompatibilityDetails, }; // OrtApiBase can never change as there is no way to know what version of OrtApiBase is returned by OrtGetApiBase. @@ -4368,3 +4374,97 @@ DEFINE_RELEASE_ORT_OBJECT_FUNCTION(Value, OrtValue) DEFINE_RELEASE_ORT_OBJECT_FUNCTION(RunOptions, OrtRunOptions) DEFINE_RELEASE_ORT_OBJECT_FUNCTION(Session, ::onnxruntime::InferenceSession) DEFINE_RELEASE_ORT_OBJECT_FUNCTION(ModelMetadata, ::onnxruntime::ModelMetadata) + +ORT_API_STATUS_IMPL(OrtApis::GetOrtHardwareDevices, _In_ const OrtEnv* env, _Outptr_ const OrtHardwareDevice* const** devices, _Out_ size_t* num_devices) { + API_IMPL_BEGIN + if (env == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "env must not be null"); + } + if (devices == nullptr || num_devices == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "devices and num_devices must not be null"); + } + + const auto& device_vector = env->GetEnvironment().GetSortedOrtHardwareDevices(); + *devices = device_vector.data(); + *num_devices = device_vector.size(); + return nullptr; + API_IMPL_END +} + +ORT_API_STATUS_IMPL(OrtApis::GetHardwareDeviceEPIncompatibilityReasons, _In_ const OrtEnv* env, _In_ const char* ep_name, _In_ const OrtHardwareDevice* hw, _Outptr_ OrtDeviceEpIncompatibilityDetails** details) { + API_IMPL_BEGIN +#if !defined(ORT_MINIMAL_BUILD) + // Validate all input parameters + if (env == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "env is required and cannot be null"); + } + if (ep_name == nullptr || ep_name[0] == '\0') { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "ep_name is required and cannot be null or empty"); + } + if (hw == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "hw is required and cannot be null"); + } + if (details == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "details output parameter cannot be null"); + } + + std::unique_ptr compat_details; + auto status = env->GetEnvironment().GetHardwareDeviceEPIncompatibilityReasons(ep_name, hw, compat_details); + if (!status.IsOK()) { + return ToOrtStatus(status); + } + + *details = compat_details.release(); + return nullptr; +#else + ORT_UNUSED_PARAMETER(env); + ORT_UNUSED_PARAMETER(ep_name); + ORT_UNUSED_PARAMETER(hw); + ORT_UNUSED_PARAMETER(details); + return OrtApis::CreateStatus(ORT_NOT_IMPLEMENTED, "GetHardwareDeviceEPIncompatibilityReasons is not available in minimal build"); +#endif + API_IMPL_END +} + +ORT_API_STATUS_IMPL(OrtApis::DeviceEpIncompatibilityDetails_GetReasonsBitmask, _In_ const OrtDeviceEpIncompatibilityDetails* details, _Out_ uint32_t* reasons_bitmask) { + API_IMPL_BEGIN + if (details == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "details cannot be null"); + } + if (reasons_bitmask == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "reasons_bitmask output parameter cannot be null"); + } + *reasons_bitmask = details->reasons_bitmask; + return nullptr; + API_IMPL_END +} + +ORT_API_STATUS_IMPL(OrtApis::DeviceEpIncompatibilityDetails_GetNotes, _In_ const OrtDeviceEpIncompatibilityDetails* details, _Outptr_result_maybenull_ const char** notes) { + API_IMPL_BEGIN + if (details == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "details cannot be null"); + } + if (notes == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "notes output parameter cannot be null"); + } + *notes = details->notes.empty() ? nullptr : details->notes.c_str(); + return nullptr; + API_IMPL_END +} + +ORT_API_STATUS_IMPL(OrtApis::DeviceEpIncompatibilityDetails_GetErrorCode, _In_ const OrtDeviceEpIncompatibilityDetails* details, _Out_ int32_t* error_code) { + API_IMPL_BEGIN + if (details == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "details cannot be null"); + } + if (error_code == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "error_code output parameter cannot be null"); + } + *error_code = details->error_code; + return nullptr; + API_IMPL_END +} + +void ORT_API_CALL OrtApis::ReleaseDeviceEpIncompatibilityDetails(OrtDeviceEpIncompatibilityDetails* details) noexcept { + delete details; +} diff --git a/onnxruntime/core/session/ort_apis.h b/onnxruntime/core/session/ort_apis.h index 7aa09adfd32d1..712c11a8cbd72 100644 --- a/onnxruntime/core/session/ort_apis.h +++ b/onnxruntime/core/session/ort_apis.h @@ -480,6 +480,18 @@ ORT_API_STATUS_IMPL(KernelContext_GetAllocator, _In_ const OrtKernelContext* con ORT_API(const char*, GetBuildInfoString); +ORT_API_STATUS_IMPL(GetOrtHardwareDevices, _In_ const OrtEnv* env, _Outptr_ const OrtHardwareDevice* const** devices, _Out_ size_t* num_devices); + +ORT_API_STATUS_IMPL(GetHardwareDeviceEPIncompatibilityReasons, _In_ const OrtEnv* env, _In_ const char* ep_name, _In_ const OrtHardwareDevice* hw, _Outptr_ OrtDeviceEpIncompatibilityDetails** details); + +ORT_API_STATUS_IMPL(DeviceEpIncompatibilityDetails_GetReasonsBitmask, _In_ const OrtDeviceEpIncompatibilityDetails* details, _Out_ uint32_t* reasons_bitmask); + +ORT_API_STATUS_IMPL(DeviceEpIncompatibilityDetails_GetNotes, _In_ const OrtDeviceEpIncompatibilityDetails* details, _Outptr_result_maybenull_ const char** notes); + +ORT_API_STATUS_IMPL(DeviceEpIncompatibilityDetails_GetErrorCode, _In_ const OrtDeviceEpIncompatibilityDetails* details, _Out_ int32_t* error_code); + +ORT_API(void, ReleaseDeviceEpIncompatibilityDetails, _Frees_ptr_opt_ OrtDeviceEpIncompatibilityDetails*); + ORT_API_STATUS_IMPL(CreateROCMProviderOptions, _Outptr_ OrtROCMProviderOptions** out); ORT_API_STATUS_IMPL(UpdateROCMProviderOptions, _Inout_ OrtROCMProviderOptions* rocm_options, _In_reads_(num_keys) const char* const* provider_options_keys, diff --git a/onnxruntime/core/session/plugin_ep/ep_api.cc b/onnxruntime/core/session/plugin_ep/ep_api.cc index a598de3053133..068617a446a87 100644 --- a/onnxruntime/core/session/plugin_ep/ep_api.cc +++ b/onnxruntime/core/session/plugin_ep/ep_api.cc @@ -677,6 +677,27 @@ ORT_API_STATUS_IMPL(CreateIfKernel, _In_ const OrtKernelInfo* kernel_info, _Outp API_IMPL_END } +ORT_API_STATUS_IMPL(CreateDeviceEpIncompatibilityDetails, _In_ uint32_t reasons_bitmask, + _In_ int32_t error_code, + _In_opt_z_ const char* notes, + _Outptr_ OrtDeviceEpIncompatibilityDetails** details) { + API_IMPL_BEGIN + if (details == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "details output parameter must not be null"); + } + + auto compat_details = std::make_unique(); + compat_details->reasons_bitmask = reasons_bitmask; + compat_details->error_code = error_code; + if (notes != nullptr) { + compat_details->notes = notes; + } + + *details = compat_details.release(); + return nullptr; + API_IMPL_END +} + ORT_API_STATUS_IMPL(CreateLoopKernel, _In_ const OrtKernelInfo* kernel_info, _In_ OrtLoopKernelHelper* helper, _Outptr_ OrtKernelImpl** kernel_out) { API_IMPL_BEGIN @@ -823,6 +844,7 @@ static constexpr OrtEpApi ort_ep_api = { &OrtExecutionProviderApi::CreateLoopKernel, &OrtExecutionProviderApi::CreateScanKernel, &OrtExecutionProviderApi::ReleaseKernelImpl, + &OrtExecutionProviderApi::CreateDeviceEpIncompatibilityDetails, }; // checks that we don't violate the rule that the functions must remain in the slots they were originally assigned @@ -830,7 +852,6 @@ static_assert(offsetof(OrtEpApi, ReleaseEpDevice) / sizeof(void*) == 1, "Size of version 22 API cannot change"); // initial version in ORT 1.22 static_assert(offsetof(OrtEpApi, GetSyncIdForLastWaitOnSyncStream) / sizeof(void*) == 15, "Size of version 23 API cannot change"); - } // namespace OrtExecutionProviderApi ORT_API(const OrtEpApi*, OrtExecutionProviderApi::GetEpApi) { diff --git a/onnxruntime/core/session/plugin_ep/ep_factory_internal.cc b/onnxruntime/core/session/plugin_ep/ep_factory_internal.cc index 8dc92802aa84b..abc056d5899e7 100644 --- a/onnxruntime/core/session/plugin_ep/ep_factory_internal.cc +++ b/onnxruntime/core/session/plugin_ep/ep_factory_internal.cc @@ -34,6 +34,7 @@ EpFactoryInternal::EpFactoryInternal(std::unique_ptr impl OrtEpFactory::CreateSyncStreamForDevice = Forward::CreateSyncStreamForDevice; OrtEpFactory::SetEnvironmentOptions = Forward::SetEnvironmentOptions; OrtEpFactory::CreateExternalResourceImporterForDevice = Forward::CreateExternalResourceImporterForDevice; + OrtEpFactory::GetHardwareDeviceIncompatibilityReasons = Forward::GetHardwareDeviceIncompatibilityReasons; } InternalExecutionProviderFactory::InternalExecutionProviderFactory(EpFactoryInternal& ep_factory, diff --git a/onnxruntime/core/session/plugin_ep/ep_factory_internal.h b/onnxruntime/core/session/plugin_ep/ep_factory_internal.h index ae98f2c0ac589..35a2d657448e7 100644 --- a/onnxruntime/core/session/plugin_ep/ep_factory_internal.h +++ b/onnxruntime/core/session/plugin_ep/ep_factory_internal.h @@ -96,6 +96,11 @@ class EpFactoryInternal : public OrtEpFactory { return impl_->CreateExternalResourceImporterForDevice(ep_device, importer); } + OrtStatus* GetHardwareDeviceIncompatibilityReasons(_In_ const OrtHardwareDevice* hw, + _Outptr_ OrtDeviceEpIncompatibilityDetails** details) noexcept { + return impl_->GetHardwareDeviceIncompatibilityReasons(hw, details); + } + // Function ORT calls to release an EP instance. void ReleaseEp(OrtEp* /*ep*/) noexcept { // we never create an OrtEp so we should never be trying to release one diff --git a/onnxruntime/core/session/plugin_ep/ep_factory_internal_impl.h b/onnxruntime/core/session/plugin_ep/ep_factory_internal_impl.h index 20a47715df2b8..1f72b303f3894 100644 --- a/onnxruntime/core/session/plugin_ep/ep_factory_internal_impl.h +++ b/onnxruntime/core/session/plugin_ep/ep_factory_internal_impl.h @@ -96,6 +96,13 @@ class EpFactoryInternalImpl { return nullptr; } + virtual OrtStatus* GetHardwareDeviceIncompatibilityReasons(_In_ const OrtHardwareDevice* /*hw*/, + _Outptr_ OrtDeviceEpIncompatibilityDetails** details) noexcept { + // Default implementation: return nullptr to indicate no incompatibility info provided (device assumed compatible) + *details = nullptr; + return nullptr; + } + // Function ORT calls to release an EP instance. void ReleaseEp(OrtEp* ep); diff --git a/onnxruntime/core/session/plugin_ep/ep_factory_provider_bridge.h b/onnxruntime/core/session/plugin_ep/ep_factory_provider_bridge.h index 26173f0055ed7..531cbf019af24 100644 --- a/onnxruntime/core/session/plugin_ep/ep_factory_provider_bridge.h +++ b/onnxruntime/core/session/plugin_ep/ep_factory_provider_bridge.h @@ -77,6 +77,16 @@ class ProviderBridgeEpFactory : public EpFactoryInternalImpl { return ep_factory_.CreateExternalResourceImporterForDevice(&ep_factory_, ep_device, importer); } + OrtStatus* GetHardwareDeviceIncompatibilityReasons(_In_ const OrtHardwareDevice* hw, + _Outptr_ OrtDeviceEpIncompatibilityDetails** details) noexcept override { + if (ep_factory_.GetHardwareDeviceIncompatibilityReasons == nullptr) { + // Factory doesn't implement this hook, return nullptr (no incompatibility) + *details = nullptr; + return nullptr; + } + return ep_factory_.GetHardwareDeviceIncompatibilityReasons(&ep_factory_, hw, details); + } + OrtEpFactory& ep_factory_; ProviderLibrary& provider_library_; std::optional library_path_; diff --git a/onnxruntime/core/session/plugin_ep/forward_to_factory_impl.h b/onnxruntime/core/session/plugin_ep/forward_to_factory_impl.h index 2530ae8eb3c2b..1e822c811fa3c 100644 --- a/onnxruntime/core/session/plugin_ep/forward_to_factory_impl.h +++ b/onnxruntime/core/session/plugin_ep/forward_to_factory_impl.h @@ -94,6 +94,12 @@ struct ForwardToFactoryImpl { return static_cast(this_ptr)->CreateExternalResourceImporterForDevice(ep_device, importer); } + static OrtStatus* ORT_API_CALL GetHardwareDeviceIncompatibilityReasons(_In_ OrtEpFactory* this_ptr, + _In_ const OrtHardwareDevice* hw, + _Outptr_ OrtDeviceEpIncompatibilityDetails** details) noexcept { + return static_cast(this_ptr)->GetHardwareDeviceIncompatibilityReasons(hw, details); + } + static void ORT_API_CALL ReleaseEp(OrtEpFactory* this_ptr, OrtEp* ep) noexcept { static_cast(this_ptr)->ReleaseEp(ep); } diff --git a/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc b/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc index 7c2b8e59ade89..33ffef2855009 100644 --- a/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc +++ b/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc @@ -36,6 +36,7 @@ ExampleEpFactory::ExampleEpFactory(const char* ep_name, ApiPtrs apis, const OrtL IsStreamAware = IsStreamAwareImpl; CreateSyncStreamForDevice = CreateSyncStreamForDeviceImpl; + GetHardwareDeviceIncompatibilityReasons = GetHardwareDeviceIncompatibilityReasonsImpl; CreateExternalResourceImporterForDevice = CreateExternalResourceImporterForDeviceImpl; @@ -332,3 +333,27 @@ OrtStatus* ORT_API_CALL ExampleEpFactory::CreateExternalResourceImporterForDevic return nullptr; } + +OrtStatus* ORT_API_CALL ExampleEpFactory::GetHardwareDeviceIncompatibilityReasonsImpl( + OrtEpFactory* this_ptr, + const OrtHardwareDevice* hw, + OrtDeviceEpIncompatibilityDetails** details) noexcept { + auto& factory = *static_cast(this_ptr); + *details = nullptr; + + // Example: This EP only supports CPU devices. Report incompatibility for non-CPU devices. + OrtHardwareDeviceType device_type = factory.ort_api.HardwareDevice_Type(hw); + + if (device_type != OrtHardwareDeviceType_CPU) { + // Report that the device type is not supported + uint32_t reasons = OrtDeviceEpIncompatibility_DEVICE_INCOMPATIBLE; + return factory.ep_api.CreateDeviceEpIncompatibilityDetails( + reasons, + static_cast(device_type), // Use device type as the error code for testing + "ExampleEP only supports CPU devices", + details); + } + + // Device is compatible - return empty details (no incompatibility reasons) + return factory.ep_api.CreateDeviceEpIncompatibilityDetails(0, 0, nullptr, details); +} diff --git a/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.h b/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.h index 230fdef772e2f..0a25d4d3a097f 100644 --- a/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.h +++ b/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.h @@ -73,6 +73,11 @@ class ExampleEpFactory : public OrtEpFactory, public ApiPtrs { const OrtEpDevice* ep_device, OrtExternalResourceImporterImpl** out_importer) noexcept; + static OrtStatus* ORT_API_CALL GetHardwareDeviceIncompatibilityReasonsImpl( + OrtEpFactory* this_ptr, + const OrtHardwareDevice* hw, + OrtDeviceEpIncompatibilityDetails** details) noexcept; + const OrtLogger& default_logger_; // default logger for the EP factory const std::string ep_name_; // EP name const std::string vendor_{"Contoso"}; // EP vendor name diff --git a/onnxruntime/test/autoep/test_execution.cc b/onnxruntime/test/autoep/test_execution.cc index c20a5455e5eae..f35ad91eaac80 100644 --- a/onnxruntime/test/autoep/test_execution.cc +++ b/onnxruntime/test/autoep/test_execution.cc @@ -549,5 +549,102 @@ TEST(OrtEpLibrary, KernelPluginEp_ControlFlow_Scan) { ASSERT_NO_FATAL_FAILURE(RunScanMulModel(session_options)); } } + +// Tests the GetHardwareDeviceEPIncompatibilityReasons C API with the example plugin EP. +// The example plugin EP supports CPU devices, so this test verifies that a CPU device +// is reported as compatible (reasons_bitmask == 0). +TEST(OrtEpLibrary, PluginEp_CpuDevice_ReturnsCompatible) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = static_cast(*ort_env); + + // Register the example plugin EP + RegisteredEpDeviceUniquePtr example_ep; + ASSERT_NO_FATAL_FAILURE(Utils::RegisterAndGetExampleEp(*ort_env, Utils::example_ep_info, example_ep)); + + // Get all hardware devices + const OrtHardwareDevice* const* hw_devices = nullptr; + size_t num_hw_devices = 0; + ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); + ASSERT_GT(num_hw_devices, 0u); + + // Find a CPU device using the public accessor + const OrtHardwareDevice* cpu_device = nullptr; + for (size_t i = 0; i < num_hw_devices; ++i) { + if (api->HardwareDevice_Type(hw_devices[i]) == OrtHardwareDeviceType_CPU) { + cpu_device = hw_devices[i]; + break; + } + } + ASSERT_NE(cpu_device, nullptr) << "No CPU device found"; + + // Check compatibility - ExampleEP supports CPU, so should return no incompatibility reasons + OrtDeviceEpIncompatibilityDetails* details = nullptr; + ASSERT_ORTSTATUS_OK(api->GetHardwareDeviceEPIncompatibilityReasons(env, Utils::example_ep_info.registration_name.c_str(), + cpu_device, &details)); + ASSERT_NE(details, nullptr); + + // Verify compatible (no incompatibility reasons) + uint32_t reasons_bitmask = 0xFFFFFFFF; + ASSERT_ORTSTATUS_OK(api->DeviceEpIncompatibilityDetails_GetReasonsBitmask(details, &reasons_bitmask)); + EXPECT_EQ(reasons_bitmask, 0u) << "CPU device should be compatible with example_plugin_ep"; + + int32_t error_code = -1; + ASSERT_ORTSTATUS_OK(api->DeviceEpIncompatibilityDetails_GetErrorCode(details, &error_code)); + EXPECT_EQ(error_code, 0); + + api->ReleaseDeviceEpIncompatibilityDetails(details); +} + +// Tests the GetHardwareDeviceEPIncompatibilityReasons C API with the example plugin EP. +// The example plugin EP only supports CPU devices, so this test verifies that a GPU device +// is reported as incompatible (reasons_bitmask != 0). +// Note: This test uses the virtual GPU EP to register a GPU device, then checks if it's +// compatible with the regular example_plugin_ep (which only supports CPU). +TEST(OrtEpLibrary, PluginEp_GpuDevice_ReturnsInCompatible) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = static_cast(*ort_env); + + // Register the regular example plugin EP (CPU-only) + RegisteredEpDeviceUniquePtr example_ep; + ASSERT_NO_FATAL_FAILURE(Utils::RegisterAndGetExampleEp(*ort_env, Utils::example_ep_info, example_ep)); + + // Register the virtual GPU EP to get a GPU device + RegisteredEpDeviceUniquePtr virt_gpu_ep; + ASSERT_NO_FATAL_FAILURE(Utils::RegisterAndGetExampleEp(*ort_env, Utils::example_ep_virt_gpu_info, virt_gpu_ep)); + + // Get all hardware devices + const OrtHardwareDevice* const* hw_devices = nullptr; + size_t num_hw_devices = 0; + ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); + ASSERT_GT(num_hw_devices, 0u); + + // Find a GPU device using the public accessor + const OrtHardwareDevice* gpu_device = nullptr; + for (size_t i = 0; i < num_hw_devices; ++i) { + if (api->HardwareDevice_Type(hw_devices[i]) == OrtHardwareDeviceType_GPU) { + gpu_device = hw_devices[i]; + break; + } + } + ASSERT_NE(gpu_device, nullptr) << "No GPU device found (virtual GPU EP should have added one)"; + + // Check compatibility - ExampleEP only supports CPU, so GPU should return incompatibility reasons + OrtDeviceEpIncompatibilityDetails* details = nullptr; + ASSERT_ORTSTATUS_OK(api->GetHardwareDeviceEPIncompatibilityReasons(env, Utils::example_ep_info.registration_name.c_str(), + gpu_device, &details)); + ASSERT_NE(details, nullptr); + + // Verify incompatible (should have incompatibility reasons) + uint32_t reasons_bitmask = 0; + ASSERT_ORTSTATUS_OK(api->DeviceEpIncompatibilityDetails_GetReasonsBitmask(details, &reasons_bitmask)); + EXPECT_NE(reasons_bitmask, 0u) << "GPU device should be incompatible with example_plugin_ep (CPU-only)"; + + api->ReleaseDeviceEpIncompatibilityDetails(details); +} + } // namespace test } // namespace onnxruntime diff --git a/onnxruntime/test/framework/hardware_device_compatibility_test.cc b/onnxruntime/test/framework/hardware_device_compatibility_test.cc new file mode 100644 index 0000000000000..ce24215d2c9ea --- /dev/null +++ b/onnxruntime/test/framework/hardware_device_compatibility_test.cc @@ -0,0 +1,329 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "core/session/onnxruntime_c_api.h" +#include "core/session/onnxruntime_cxx_api.h" +#include "test/test_environment.h" +#include "test/util/include/api_asserts.h" + +using namespace onnxruntime::test; + +// ----------------------------- +// GetHardwareDeviceEPIncompatibilityReasons C API unit tests +// ----------------------------- + +TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullEnv) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + // Create env for GetOrtHardwareDevices + OrtEnv* env = nullptr; + EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); + EXPECT_NE(env, nullptr); + + // Get a valid hardware device first + const OrtHardwareDevice* const* hw_devices = nullptr; + size_t num_hw_devices = 0; + ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); + ASSERT_GT(num_hw_devices, 0u); + ASSERT_NE(hw_devices, nullptr); + + // env == nullptr for GetHardwareDeviceEPIncompatibilityReasons + OrtDeviceEpIncompatibilityDetails* details = nullptr; + OrtStatus* st = api->GetHardwareDeviceEPIncompatibilityReasons(nullptr, "CPUExecutionProvider", hw_devices[0], &details); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + api->ReleaseEnv(env); +} + +TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullEpName) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = nullptr; + EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); + EXPECT_NE(env, nullptr); + + const OrtHardwareDevice* const* hw_devices = nullptr; + size_t num_hw_devices = 0; + ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); + ASSERT_GT(num_hw_devices, 0u); + + // ep_name == nullptr + OrtDeviceEpIncompatibilityDetails* details = nullptr; + OrtStatus* st = api->GetHardwareDeviceEPIncompatibilityReasons(env, nullptr, hw_devices[0], &details); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + api->ReleaseEnv(env); +} + +TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_EmptyEpName) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = nullptr; + EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); + EXPECT_NE(env, nullptr); + + const OrtHardwareDevice* const* hw_devices = nullptr; + size_t num_hw_devices = 0; + ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); + ASSERT_GT(num_hw_devices, 0u); + + // ep_name == "" + OrtDeviceEpIncompatibilityDetails* details = nullptr; + OrtStatus* st = api->GetHardwareDeviceEPIncompatibilityReasons(env, "", hw_devices[0], &details); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + api->ReleaseEnv(env); +} + +TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullHardwareDevice) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = nullptr; + EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); + EXPECT_NE(env, nullptr); + + // hw == nullptr + OrtDeviceEpIncompatibilityDetails* details = nullptr; + OrtStatus* st = api->GetHardwareDeviceEPIncompatibilityReasons(env, "CPUExecutionProvider", nullptr, &details); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + api->ReleaseEnv(env); +} + +TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullDetailsOutput) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = nullptr; + EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); + EXPECT_NE(env, nullptr); + + const OrtHardwareDevice* const* hw_devices = nullptr; + size_t num_hw_devices = 0; + ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); + ASSERT_GT(num_hw_devices, 0u); + + // details == nullptr + OrtStatus* st = api->GetHardwareDeviceEPIncompatibilityReasons(env, "CPUExecutionProvider", hw_devices[0], nullptr); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + api->ReleaseEnv(env); +} + +TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, UnregisteredEp_ReturnsInvalidArgument) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = nullptr; + EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); + EXPECT_NE(env, nullptr); + + const OrtHardwareDevice* const* hw_devices = nullptr; + size_t num_hw_devices = 0; + ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); + ASSERT_GT(num_hw_devices, 0u); + + // Non-existent EP name should return INVALID_ARGUMENT + OrtDeviceEpIncompatibilityDetails* details = nullptr; + OrtStatus* st = api->GetHardwareDeviceEPIncompatibilityReasons(env, "NonExistentExecutionProvider", hw_devices[0], &details); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + EXPECT_THAT(api->GetErrorMessage(st), testing::HasSubstr("No valid factory found")); + api->ReleaseStatus(st); + + api->ReleaseEnv(env); +} + +TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, CpuEp_ReturnsEmptyDetails) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = nullptr; + EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); + EXPECT_NE(env, nullptr); + + const OrtHardwareDevice* const* hw_devices = nullptr; + size_t num_hw_devices = 0; + ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); + ASSERT_GT(num_hw_devices, 0u); + + // CPU EP doesn't implement GetHardwareDeviceIncompatibilityReasons, so should return empty details + OrtDeviceEpIncompatibilityDetails* details = nullptr; + ASSERT_ORTSTATUS_OK(api->GetHardwareDeviceEPIncompatibilityReasons(env, "CPUExecutionProvider", hw_devices[0], &details)); + ASSERT_NE(details, nullptr); + + // Verify empty details + uint32_t reasons_bitmask = 0xFFFFFFFF; // Initialize to non-zero to verify it gets set + ASSERT_ORTSTATUS_OK(api->DeviceEpIncompatibilityDetails_GetReasonsBitmask(details, &reasons_bitmask)); + EXPECT_EQ(reasons_bitmask, 0u); + + int32_t error_code = -1; // Initialize to non-zero to verify it gets set + ASSERT_ORTSTATUS_OK(api->DeviceEpIncompatibilityDetails_GetErrorCode(details, &error_code)); + EXPECT_EQ(error_code, 0); + + const char* notes = reinterpret_cast(0xDEADBEEF); // Initialize to non-null + ASSERT_ORTSTATUS_OK(api->DeviceEpIncompatibilityDetails_GetNotes(details, ¬es)); + EXPECT_TRUE(notes == nullptr || strlen(notes) == 0); + + api->ReleaseDeviceEpIncompatibilityDetails(details); + api->ReleaseEnv(env); +} + +TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, AccessorFunctions_NullDetails) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + // Test accessor functions with null details + uint32_t reasons_bitmask = 0; + OrtStatus* st = api->DeviceEpIncompatibilityDetails_GetReasonsBitmask(nullptr, &reasons_bitmask); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + int32_t error_code = 0; + st = api->DeviceEpIncompatibilityDetails_GetErrorCode(nullptr, &error_code); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + const char* notes = nullptr; + st = api->DeviceEpIncompatibilityDetails_GetNotes(nullptr, ¬es); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); +} + +TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, AccessorFunctions_NullOutputPtr) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = nullptr; + EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); + EXPECT_NE(env, nullptr); + + const OrtHardwareDevice* const* hw_devices = nullptr; + size_t num_hw_devices = 0; + ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); + ASSERT_GT(num_hw_devices, 0u); + + // Get a valid details object first + OrtDeviceEpIncompatibilityDetails* details = nullptr; + ASSERT_ORTSTATUS_OK(api->GetHardwareDeviceEPIncompatibilityReasons(env, "CPUExecutionProvider", hw_devices[0], &details)); + ASSERT_NE(details, nullptr); + + // Test accessor functions with null output pointers + OrtStatus* st = api->DeviceEpIncompatibilityDetails_GetReasonsBitmask(details, nullptr); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + st = api->DeviceEpIncompatibilityDetails_GetErrorCode(details, nullptr); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + st = api->DeviceEpIncompatibilityDetails_GetNotes(details, nullptr); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + api->ReleaseDeviceEpIncompatibilityDetails(details); + api->ReleaseEnv(env); +} + +// ----------------------------- +// GetOrtHardwareDevices C API unit tests +// ----------------------------- + +TEST(GetOrtHardwareDevicesCapiTest, InvalidArguments_NullEnv) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + const OrtHardwareDevice* const* devices = nullptr; + size_t num_devices = 0; + OrtStatus* st = api->GetOrtHardwareDevices(nullptr, &devices, &num_devices); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); +} + +TEST(GetOrtHardwareDevicesCapiTest, InvalidArguments_NullDevices) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = nullptr; + EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "HwDevicesTest", &env)); + EXPECT_NE(env, nullptr); + + size_t num_devices = 0; + OrtStatus* st = api->GetOrtHardwareDevices(env, nullptr, &num_devices); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + api->ReleaseEnv(env); +} + +TEST(GetOrtHardwareDevicesCapiTest, InvalidArguments_NullNumDevices) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = nullptr; + EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "HwDevicesTest", &env)); + EXPECT_NE(env, nullptr); + + const OrtHardwareDevice* const* devices = nullptr; + OrtStatus* st = api->GetOrtHardwareDevices(env, &devices, nullptr); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + api->ReleaseEnv(env); +} + +TEST(GetOrtHardwareDevicesCapiTest, ReturnsDevices) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = nullptr; + EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "HwDevicesTest", &env)); + EXPECT_NE(env, nullptr); + + const OrtHardwareDevice* const* devices = nullptr; + size_t num_devices = 0; + ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &devices, &num_devices)); + + // Should return at least one device (CPU) + EXPECT_GT(num_devices, 0u); + EXPECT_NE(devices, nullptr); + + // Verify we can access device properties + for (size_t i = 0; i < num_devices; ++i) { + const OrtHardwareDevice* device = devices[i]; + // Device type should be valid (CPU, GPU, or NPU) + EXPECT_TRUE(device->type == OrtHardwareDeviceType_CPU || + device->type == OrtHardwareDeviceType_GPU || + device->type == OrtHardwareDeviceType_NPU); + // Vendor should not be empty + EXPECT_FALSE(device->vendor.empty()); + } + + api->ReleaseEnv(env); +} From 4228c9620c1ff4aef0e08d76a72e49f71b87976d Mon Sep 17 00:00:00 2001 From: Xiaoxi Han Date: Tue, 6 Jan 2026 14:58:08 -0800 Subject: [PATCH 02/11] fix build error --- onnxruntime/core/session/plugin_ep/ep_api.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/onnxruntime/core/session/plugin_ep/ep_api.cc b/onnxruntime/core/session/plugin_ep/ep_api.cc index 068617a446a87..9228a53dacf76 100644 --- a/onnxruntime/core/session/plugin_ep/ep_api.cc +++ b/onnxruntime/core/session/plugin_ep/ep_api.cc @@ -852,6 +852,7 @@ static_assert(offsetof(OrtEpApi, ReleaseEpDevice) / sizeof(void*) == 1, "Size of version 22 API cannot change"); // initial version in ORT 1.22 static_assert(offsetof(OrtEpApi, GetSyncIdForLastWaitOnSyncStream) / sizeof(void*) == 15, "Size of version 23 API cannot change"); + } // namespace OrtExecutionProviderApi ORT_API(const OrtEpApi*, OrtExecutionProviderApi::GetEpApi) { From 01bd22ba4b1a17c40e4acb3820f86d0358ff5021 Mon Sep 17 00:00:00 2001 From: Xiaoxi Han Date: Tue, 6 Jan 2026 16:38:56 -0800 Subject: [PATCH 03/11] fix build error and style error --- .../onnxruntime/core/session/onnxruntime_c_api.h | 6 +++--- onnxruntime/core/session/abi_devices.h | 6 +++--- onnxruntime/core/session/environment.cc | 3 +-- .../framework/hardware_device_compatibility_test.cc | 13 ++++++++----- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/include/onnxruntime/core/session/onnxruntime_c_api.h b/include/onnxruntime/core/session/onnxruntime_c_api.h index 06c8c55a195e1..9363d33a847d8 100644 --- a/include/onnxruntime/core/session/onnxruntime_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_c_api.h @@ -514,11 +514,11 @@ typedef enum OrtExecutionProviderDevicePolicy { /** \brief Reasons why an execution provider might not be compatible with a device */ typedef enum OrtDeviceEpIncompatibilityReason { - OrtDeviceEpIncompatibility_NONE = 0, - OrtDeviceEpIncompatibility_DRIVER_INCOMPATIBLE = 1u << 0, + OrtDeviceEpIncompatibility_NONE = 0, + OrtDeviceEpIncompatibility_DRIVER_INCOMPATIBLE = 1u << 0, OrtDeviceEpIncompatibility_DEVICE_INCOMPATIBLE = 1u << 1, OrtDeviceEpIncompatibility_MISSING_DEPENDENCY = 1u << 2, - OrtDeviceEpIncompatibility_UNKNOWN = 1u << 31 + OrtDeviceEpIncompatibility_UNKNOWN = 1u << 31 } OrtDeviceEpIncompatibilityReason; /** \brief Delegate to allow providing custom OrtEpDevice selection logic diff --git a/onnxruntime/core/session/abi_devices.h b/onnxruntime/core/session/abi_devices.h index 12107cc4ff8ab..aafe2ab114ab9 100644 --- a/onnxruntime/core/session/abi_devices.h +++ b/onnxruntime/core/session/abi_devices.h @@ -77,7 +77,7 @@ struct OrtEpDevice { }; struct OrtDeviceEpIncompatibilityDetails { - uint32_t reasons_bitmask{0}; // Bitmask of OrtDeviceEpIncompatibilityReason values - int32_t error_code{0}; // EP-specific error code (0 = no error) - std::string notes; // Additional human-readable notes + uint32_t reasons_bitmask{0}; // Bitmask of OrtDeviceEpIncompatibilityReason values + int32_t error_code{0}; // EP-specific error code (0 = no error) + std::string notes; // Additional human-readable notes }; diff --git a/onnxruntime/core/session/environment.cc b/onnxruntime/core/session/environment.cc index 118dec073a210..21a640603796e 100644 --- a/onnxruntime/core/session/environment.cc +++ b/onnxruntime/core/session/environment.cc @@ -735,8 +735,7 @@ Status SetEpFactoryEnvironmentOptions(OrtEpFactory& factory, std::string_view li } } // namespace -const std::vector& Environment::GetSortedOrtHardwareDevices() const -{ +const std::vector& Environment::GetSortedOrtHardwareDevices() const { std::lock_guard lock{mutex_}; if (ort_hardware_devices_.empty()) { ort_hardware_devices_ = SortDevicesByType(); diff --git a/onnxruntime/test/framework/hardware_device_compatibility_test.cc b/onnxruntime/test/framework/hardware_device_compatibility_test.cc index ce24215d2c9ea..4323b1dda6e74 100644 --- a/onnxruntime/test/framework/hardware_device_compatibility_test.cc +++ b/onnxruntime/test/framework/hardware_device_compatibility_test.cc @@ -314,15 +314,18 @@ TEST(GetOrtHardwareDevicesCapiTest, ReturnsDevices) { EXPECT_GT(num_devices, 0u); EXPECT_NE(devices, nullptr); - // Verify we can access device properties + // Verify we can access device properties via C API accessor functions for (size_t i = 0; i < num_devices; ++i) { const OrtHardwareDevice* device = devices[i]; // Device type should be valid (CPU, GPU, or NPU) - EXPECT_TRUE(device->type == OrtHardwareDeviceType_CPU || - device->type == OrtHardwareDeviceType_GPU || - device->type == OrtHardwareDeviceType_NPU); + OrtHardwareDeviceType device_type = api->HardwareDevice_Type(device); + EXPECT_TRUE(device_type == OrtHardwareDeviceType_CPU || + device_type == OrtHardwareDeviceType_GPU || + device_type == OrtHardwareDeviceType_NPU); // Vendor should not be empty - EXPECT_FALSE(device->vendor.empty()); + const char* vendor = api->HardwareDevice_Vendor(device); + EXPECT_NE(vendor, nullptr); + EXPECT_GT(strlen(vendor), 0u); } api->ReleaseEnv(env); From fe2eb41bf0952a74b23873f761a26af237c95805 Mon Sep 17 00:00:00 2001 From: Xiaoxi Han Date: Tue, 6 Jan 2026 18:05:10 -0800 Subject: [PATCH 04/11] fix build issue --- onnxruntime/core/session/onnxruntime_c_api.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/onnxruntime/core/session/onnxruntime_c_api.cc b/onnxruntime/core/session/onnxruntime_c_api.cc index 3b3846775201b..c6487d5e7a04e 100644 --- a/onnxruntime/core/session/onnxruntime_c_api.cc +++ b/onnxruntime/core/session/onnxruntime_c_api.cc @@ -4377,6 +4377,7 @@ DEFINE_RELEASE_ORT_OBJECT_FUNCTION(ModelMetadata, ::onnxruntime::ModelMetadata) ORT_API_STATUS_IMPL(OrtApis::GetOrtHardwareDevices, _In_ const OrtEnv* env, _Outptr_ const OrtHardwareDevice* const** devices, _Out_ size_t* num_devices) { API_IMPL_BEGIN +#if !defined(ORT_MINIMAL_BUILD) if (env == nullptr) { return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "env must not be null"); } @@ -4388,6 +4389,12 @@ ORT_API_STATUS_IMPL(OrtApis::GetOrtHardwareDevices, _In_ const OrtEnv* env, _Out *devices = device_vector.data(); *num_devices = device_vector.size(); return nullptr; +#else + ORT_UNUSED_PARAMETER(env); + ORT_UNUSED_PARAMETER(devices); + ORT_UNUSED_PARAMETER(num_devices); + return OrtApis::CreateStatus(ORT_NOT_IMPLEMENTED, "GetOrtHardwareDevices is not available in minimal build"); +#endif API_IMPL_END } From 28346acd8f32daddbf037c7b5b151798356793c8 Mon Sep 17 00:00:00 2001 From: Xiaoxi Han Date: Tue, 6 Jan 2026 21:35:48 -0800 Subject: [PATCH 05/11] fix broken tests --- include/onnxruntime/core/session/onnxruntime_c_api.h | 8 ++++---- onnxruntime/test/autoep/test_execution.cc | 12 +++++------- .../framework/hardware_device_compatibility_test.cc | 3 +-- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/include/onnxruntime/core/session/onnxruntime_c_api.h b/include/onnxruntime/core/session/onnxruntime_c_api.h index 9363d33a847d8..05db0ee8a7aac 100644 --- a/include/onnxruntime/core/session/onnxruntime_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_c_api.h @@ -515,10 +515,10 @@ typedef enum OrtExecutionProviderDevicePolicy { */ typedef enum OrtDeviceEpIncompatibilityReason { OrtDeviceEpIncompatibility_NONE = 0, - OrtDeviceEpIncompatibility_DRIVER_INCOMPATIBLE = 1u << 0, - OrtDeviceEpIncompatibility_DEVICE_INCOMPATIBLE = 1u << 1, - OrtDeviceEpIncompatibility_MISSING_DEPENDENCY = 1u << 2, - OrtDeviceEpIncompatibility_UNKNOWN = 1u << 31 + OrtDeviceEpIncompatibility_DRIVER_INCOMPATIBLE = 1 << 0, + OrtDeviceEpIncompatibility_DEVICE_INCOMPATIBLE = 1 << 1, + OrtDeviceEpIncompatibility_MISSING_DEPENDENCY = 1 << 2, + OrtDeviceEpIncompatibility_UNKNOWN = 1 << 30 } OrtDeviceEpIncompatibilityReason; /** \brief Delegate to allow providing custom OrtEpDevice selection logic diff --git a/onnxruntime/test/autoep/test_execution.cc b/onnxruntime/test/autoep/test_execution.cc index f35ad91eaac80..8fb49d67279a2 100644 --- a/onnxruntime/test/autoep/test_execution.cc +++ b/onnxruntime/test/autoep/test_execution.cc @@ -600,8 +600,6 @@ TEST(OrtEpLibrary, PluginEp_CpuDevice_ReturnsCompatible) { // Tests the GetHardwareDeviceEPIncompatibilityReasons C API with the example plugin EP. // The example plugin EP only supports CPU devices, so this test verifies that a GPU device // is reported as incompatible (reasons_bitmask != 0). -// Note: This test uses the virtual GPU EP to register a GPU device, then checks if it's -// compatible with the regular example_plugin_ep (which only supports CPU). TEST(OrtEpLibrary, PluginEp_GpuDevice_ReturnsInCompatible) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); @@ -612,10 +610,6 @@ TEST(OrtEpLibrary, PluginEp_GpuDevice_ReturnsInCompatible) { RegisteredEpDeviceUniquePtr example_ep; ASSERT_NO_FATAL_FAILURE(Utils::RegisterAndGetExampleEp(*ort_env, Utils::example_ep_info, example_ep)); - // Register the virtual GPU EP to get a GPU device - RegisteredEpDeviceUniquePtr virt_gpu_ep; - ASSERT_NO_FATAL_FAILURE(Utils::RegisterAndGetExampleEp(*ort_env, Utils::example_ep_virt_gpu_info, virt_gpu_ep)); - // Get all hardware devices const OrtHardwareDevice* const* hw_devices = nullptr; size_t num_hw_devices = 0; @@ -630,7 +624,11 @@ TEST(OrtEpLibrary, PluginEp_GpuDevice_ReturnsInCompatible) { break; } } - ASSERT_NE(gpu_device, nullptr) << "No GPU device found (virtual GPU EP should have added one)"; + + if (gpu_device == nullptr) { + // GPU device not found, early exit + GTEST_SKIP() << "No GPU device found"; + } // Check compatibility - ExampleEP only supports CPU, so GPU should return incompatibility reasons OrtDeviceEpIncompatibilityDetails* details = nullptr; diff --git a/onnxruntime/test/framework/hardware_device_compatibility_test.cc b/onnxruntime/test/framework/hardware_device_compatibility_test.cc index 4323b1dda6e74..43f3bdc429ca7 100644 --- a/onnxruntime/test/framework/hardware_device_compatibility_test.cc +++ b/onnxruntime/test/framework/hardware_device_compatibility_test.cc @@ -322,10 +322,9 @@ TEST(GetOrtHardwareDevicesCapiTest, ReturnsDevices) { EXPECT_TRUE(device_type == OrtHardwareDeviceType_CPU || device_type == OrtHardwareDeviceType_GPU || device_type == OrtHardwareDeviceType_NPU); - // Vendor should not be empty + // Vendor should not be null const char* vendor = api->HardwareDevice_Vendor(device); EXPECT_NE(vendor, nullptr); - EXPECT_GT(strlen(vendor), 0u); } api->ReleaseEnv(env); From 3e0247fefdfbfc37513368d26f81882dd7d0e6dc Mon Sep 17 00:00:00 2001 From: Xiaoxi Han Date: Sun, 11 Jan 2026 17:49:23 -0800 Subject: [PATCH 06/11] resolve comments --- .../onnxruntime/core/session/environment.h | 4 +- .../core/session/onnxruntime_c_api.h | 46 ++++-- .../core/session/onnxruntime_ep_c_api.h | 35 +++-- onnxruntime/core/session/environment.cc | 45 +++--- onnxruntime/core/session/onnxruntime_c_api.cc | 49 ++++-- onnxruntime/core/session/ort_apis.h | 6 +- onnxruntime/core/session/plugin_ep/ep_api.cc | 20 +-- .../session/plugin_ep/ep_factory_internal.cc | 2 +- .../session/plugin_ep/ep_factory_internal.h | 6 +- .../plugin_ep/ep_factory_internal_impl.h | 7 +- .../plugin_ep/ep_factory_provider_bridge.h | 11 +- .../plugin_ep/forward_to_factory_impl.h | 6 +- .../library/example_plugin_ep/ep_factory.cc | 17 +- .../library/example_plugin_ep/ep_factory.h | 4 +- onnxruntime/test/autoep/test_execution.cc | 19 ++- .../hardware_device_compatibility_test.cc | 147 ++++++++++-------- 16 files changed, 245 insertions(+), 179 deletions(-) diff --git a/include/onnxruntime/core/session/environment.h b/include/onnxruntime/core/session/environment.h index 08516f6fdf7f9..7e3b3687a5856 100644 --- a/include/onnxruntime/core/session/environment.h +++ b/include/onnxruntime/core/session/environment.h @@ -151,7 +151,7 @@ class Environment { /// @param hw The hardware device to check for incompatibility. /// @param details Output: Incompatibility details including reasons for incompatibility if any. /// @returns Status indicating success or failure. - Status GetHardwareDeviceEPIncompatibilityReasons(const std::string& ep_name, + Status GetHardwareDeviceEpIncompatibilityDetails(const std::string& ep_name, const OrtHardwareDevice* hw, std::unique_ptr& details) const; @@ -253,8 +253,6 @@ class Environment { DataTransferManager data_transfer_mgr_; // plugin EP IDataTransfer instances - mutable std::vector ort_hardware_devices_; - #endif // !defined(ORT_MINIMAL_BUILD) }; diff --git a/include/onnxruntime/core/session/onnxruntime_c_api.h b/include/onnxruntime/core/session/onnxruntime_c_api.h index 05db0ee8a7aac..84654dd5ea433 100644 --- a/include/onnxruntime/core/session/onnxruntime_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_c_api.h @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // See docs\c_cxx\README.md on generating the Doxygen documentation from this file @@ -6795,29 +6795,49 @@ struct OrtApi { ORT_API2_STATUS(SessionGetEpDeviceForOutputs, _In_ const OrtSession* session, _Out_writes_(num_outputs) const OrtEpDevice** outputs_ep_devices, _In_ size_t num_outputs); + /** \brief Get the number of available hardware devices. + * + * Returns the count of hardware devices discovered on the system. + * Use this to allocate an array before calling GetHardwareDevices(). + * + * \param[in] env The OrtEnv instance where device discovery results are stored. + * \param[out] num_devices The number of OrtHardwareDevice instances available. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24. + */ + ORT_API2_STATUS(GetNumHardwareDevices, _In_ const OrtEnv* env, _Out_ size_t* num_devices); + /** \brief Get the list of available hardware devices. * - * Enumerates hardware devices available on the system. Device discovery results - * are stored in the ORT environment. + * Enumerates hardware devices available on the system. + * Populates a user-provided array with pointers to OrtHardwareDevice instances. The caller is responsible + * for allocating the array with sufficient space (use GetNumHardwareDevices() to get the count). + * + * The returned pointers reference internal ORT data structures that are discovered once at process + * startup and remain valid for the lifetime of the OrtEnv. The caller does not need to release these + * pointers, but should not use them after calling ReleaseEnv(). * * \param[in] env The OrtEnv instance where device discovery results are stored. - * \param[out] devices The OrtHardwareDevice instances that are available. - * \param[out] num_devices The number of OrtHardwareDevice instances returned. + * \param[out] devices User-allocated array to receive pointers to OrtHardwareDevice instances. + * The array must have space for at least num_devices elements. + * \param[in] num_devices The size of the user-allocated devices array. * * \snippet{doc} snippets.dox OrtStatus Return Value * * \since Version 1.24. */ - ORT_API2_STATUS(GetOrtHardwareDevices, _In_ const OrtEnv* env, - _Outptr_ const OrtHardwareDevice* const** devices, - _Out_ size_t* num_devices); + ORT_API2_STATUS(GetHardwareDevices, _In_ const OrtEnv* env, + _Out_writes_(num_devices) const OrtHardwareDevice** devices, + _In_ size_t num_devices); - /** \brief Check for known incompatibility reasons between hardware device and a specific execution provider. + /** \brief Check for known incompatibility issues between hardware device and a specific execution provider. * - * This function checks for known incompatibility reasons between the specified hardware device + * This function checks for known incompatibility issues between the specified hardware device * and a specific execution provider. - * If incompatibility reasons are returned, it indicates the device is not compatible. - * However, if no reasons are returned, it doesn't guarantee 100% compatibility for all models, + * If returned incompatibility details have non-zero reasons, it indicates the device is not compatible. + * However, if returned detail have reason == 0, it doesn't guarantee 100% compatibility for all models, * as models may have specific requirements. * * Note: This method should only be called when the OrtEnv has been initialized with execution @@ -6833,7 +6853,7 @@ struct OrtApi { * * \since Version 1.24. */ - ORT_API2_STATUS(GetHardwareDeviceEPIncompatibilityReasons, _In_ const OrtEnv* env, + ORT_API2_STATUS(GetHardwareDeviceEpIncompatibilityDetails, _In_ const OrtEnv* env, _In_ const char* ep_name, _In_ const OrtHardwareDevice* hw, _Outptr_ OrtDeviceEpIncompatibilityDetails** details); diff --git a/include/onnxruntime/core/session/onnxruntime_ep_c_api.h b/include/onnxruntime/core/session/onnxruntime_ep_c_api.h index ad0141bb426c8..932e57567f744 100644 --- a/include/onnxruntime/core/session/onnxruntime_ep_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_ep_c_api.h @@ -1408,25 +1408,25 @@ struct OrtEpApi { _Outptr_ OrtKernelImpl** kernel_out); ORT_CLASS_RELEASE(KernelImpl); - /** \brief Create an OrtDeviceEpIncompatibilityDetails instance. + /** \brief Initialize an OrtDeviceEpIncompatibilityDetails instance. * - * Used by execution provider factories to create incompatibility details in their - * GetHardwareDeviceIncompatibilityReasons implementation. + * Used by execution provider factories to initialize incompatibility details in their + * GetHardwareDeviceIncompatibilityDetails implementation. ORT creates the object and passes it + * to the EP, which uses this function to set the incompatibility information. * - * \param[in] reasons_bitmask Bitmask of OrtDeviceEpIncompatibilityReason values. - * \param[in] error_code EP-specific error code (0 = no error). + * \param[in,out] details The OrtDeviceEpIncompatibilityDetails instance to initialize. + * \param[in] reasons_bitmask Bitmask of OrtDeviceEpIncompatibilityReason values. (0 = no incompatibility). + * \param[in] error_code Optional EP-specific error code (0 = no error). * \param[in] notes Optional human-readable notes. Can be null. - * \param[out] details Output parameter set to the new OrtDeviceEpIncompatibilityDetails instance. - * Must be released by ORT using ReleaseDeviceEpIncompatibilityDetails. * * \snippet{doc} snippets.dox OrtStatus Return Value * * \since Version 1.24. */ - ORT_API2_STATUS(CreateDeviceEpIncompatibilityDetails, _In_ uint32_t reasons_bitmask, + ORT_API2_STATUS(DeviceEpIncompatibilityDetails_Initialize, _Inout_ OrtDeviceEpIncompatibilityDetails* details, + _In_ uint32_t reasons_bitmask, _In_ int32_t error_code, - _In_opt_z_ const char* notes, - _Outptr_ OrtDeviceEpIncompatibilityDetails** details); + _In_opt_z_ const char* notes); }; /** @@ -2037,14 +2037,15 @@ struct OrtEpFactory { /** \brief Check for known incompatibility reasons between a hardware device and this execution provider. * * This function allows an execution provider to check if a specific hardware device is compatible - * with the execution provider. The EP can return specific incompatibility reasons via the - * OrtDeviceEpIncompatibilityDetails output parameter. + * with the execution provider. The EP can set specific incompatibility reasons via the + * OrtDeviceEpIncompatibilityDetails parameter using OrtEpApi::DeviceEpIncompatibilityDetails_Initialize. * * \param[in] this_ptr The OrtEpFactory instance. * \param[in] hw The hardware device to check for incompatibility. - * \param[out] details Incompatibility details including reasons for incompatibility if any. - * The EP should allocate this using OrtEpApi::CreateDeviceEpIncompatibilityDetails. - * ORT will take ownership and call ReleaseDeviceEpIncompatibilityDetails. + * \param[in,out] details Pre-allocated incompatibility details object created by ORT. + * The EP should initialize this using OrtEpApi::DeviceEpIncompatibilityDetails_Initialize + * to set any incompatibility information. If the device is compatible, the EP can + * leave the object uninitialized (it defaults to no incompatibility). * * \note Implementation of this function is optional. * If not implemented, ORT will assume the device is compatible with this EP. @@ -2053,9 +2054,9 @@ struct OrtEpFactory { * * \since Version 1.24. */ - ORT_API2_STATUS(GetHardwareDeviceIncompatibilityReasons, _In_ OrtEpFactory* this_ptr, + ORT_API2_STATUS(GetHardwareDeviceIncompatibilityDetails, _In_ OrtEpFactory* this_ptr, _In_ const OrtHardwareDevice* hw, - _Outptr_ OrtDeviceEpIncompatibilityDetails** details); + _Inout_ OrtDeviceEpIncompatibilityDetails* details); }; #ifdef __cplusplus diff --git a/onnxruntime/core/session/environment.cc b/onnxruntime/core/session/environment.cc index 21a640603796e..1bbfdefaf5b4e 100644 --- a/onnxruntime/core/session/environment.cc +++ b/onnxruntime/core/session/environment.cc @@ -637,7 +637,7 @@ Status Environment::UnregisterExecutionProviderLibrary(const std::string& ep_nam return status; } -Status Environment::GetHardwareDeviceEPIncompatibilityReasons( +Status Environment::GetHardwareDeviceEpIncompatibilityDetails( const std::string& ep_name, const OrtHardwareDevice* hw, std::unique_ptr& details) const { @@ -666,27 +666,22 @@ Status Environment::GetHardwareDeviceEPIncompatibilityReasons( "No valid factory found for execution provider '", ep_name, "'."); } - // Check if the factory implements GetHardwareDeviceIncompatibilityReasons - if (matched_factory->GetHardwareDeviceIncompatibilityReasons != nullptr) { - OrtDeviceEpIncompatibilityDetails* factory_details = nullptr; - OrtStatusPtr status = matched_factory->GetHardwareDeviceIncompatibilityReasons(matched_factory, hw, &factory_details); + // ORT creates the details object with default values (compatible) + details = std::make_unique(); + details->reasons_bitmask = 0; + details->error_code = 0; + details->notes = ""; + + // If the factory implements GetHardwareDeviceIncompatibilityDetails, let it initialize the details + if (matched_factory->GetHardwareDeviceIncompatibilityDetails != nullptr) { + OrtStatusPtr status = matched_factory->GetHardwareDeviceIncompatibilityDetails(matched_factory, hw, details.get()); if (status != nullptr) { return ToStatusAndRelease(status); } - - if (factory_details != nullptr) { - details.reset(factory_details); - return Status::OK(); - } } - // Factory doesn't implement the hook or returned nullptr - create empty details (compatible) - details = std::make_unique(); - details->reasons_bitmask = 0; - details->error_code = 0; - details->notes = ""; - + // Factory doesn't implement the hook - details remain with default values (compatible) return Status::OK(); } @@ -711,6 +706,13 @@ std::vector SortDevicesByType() { return sorted_devices; } +// Returns a static reference to sorted hardware devices. +// Hardware devices are discovered once at startup and don't change. +const std::vector& GetSortedHardwareDevices() { + static const auto sorted_devices = SortDevicesByType(); + return sorted_devices; +} + bool AreVirtualDevicesAllowed(std::string_view lib_registration_name) { constexpr std::string_view suffix{".virtual"}; @@ -736,11 +738,7 @@ Status SetEpFactoryEnvironmentOptions(OrtEpFactory& factory, std::string_view li } // namespace const std::vector& Environment::GetSortedOrtHardwareDevices() const { - std::lock_guard lock{mutex_}; - if (ort_hardware_devices_.empty()) { - ort_hardware_devices_ = SortDevicesByType(); - } - return ort_hardware_devices_; + return GetSortedHardwareDevices(); } Status Environment::CreateSharedAllocator(const OrtEpDevice& ep_device, @@ -848,9 +846,8 @@ Status Environment::EpInfo::Create(std::unique_ptr library_in, std::u ORT_RETURN_IF_ERROR(instance.library->Load()); instance.factories = instance.library->GetFactories(); - // OrtHardwareDevice instances to pass to GetSupportedDevices. sorted by type to be slightly more structured. - // the set of hardware devices is static so this can also be static. - const static std::vector sorted_devices = SortDevicesByType(); + // OrtHardwareDevice instances to pass to GetSupportedDevices. + const auto& sorted_devices = GetSortedHardwareDevices(); for (auto* factory_ptr : instance.factories) { ORT_ENFORCE(factory_ptr != nullptr, "Factory pointer was null. EpLibrary should prevent this. Library:", diff --git a/onnxruntime/core/session/onnxruntime_c_api.cc b/onnxruntime/core/session/onnxruntime_c_api.cc index c6487d5e7a04e..88b6afb385c94 100644 --- a/onnxruntime/core/session/onnxruntime_c_api.cc +++ b/onnxruntime/core/session/onnxruntime_c_api.cc @@ -4291,8 +4291,9 @@ static constexpr OrtApi ort_api_1_to_24 = { &OrtApis::GetInteropApi, &OrtApis::SessionGetEpDeviceForOutputs, - &OrtApis::GetOrtHardwareDevices, - &OrtApis::GetHardwareDeviceEPIncompatibilityReasons, + &OrtApis::GetNumHardwareDevices, + &OrtApis::GetHardwareDevices, + &OrtApis::GetHardwareDeviceEpIncompatibilityDetails, &OrtApis::DeviceEpIncompatibilityDetails_GetReasonsBitmask, &OrtApis::DeviceEpIncompatibilityDetails_GetNotes, &OrtApis::DeviceEpIncompatibilityDetails_GetErrorCode, @@ -4375,30 +4376,58 @@ DEFINE_RELEASE_ORT_OBJECT_FUNCTION(RunOptions, OrtRunOptions) DEFINE_RELEASE_ORT_OBJECT_FUNCTION(Session, ::onnxruntime::InferenceSession) DEFINE_RELEASE_ORT_OBJECT_FUNCTION(ModelMetadata, ::onnxruntime::ModelMetadata) -ORT_API_STATUS_IMPL(OrtApis::GetOrtHardwareDevices, _In_ const OrtEnv* env, _Outptr_ const OrtHardwareDevice* const** devices, _Out_ size_t* num_devices) { +ORT_API_STATUS_IMPL(OrtApis::GetNumHardwareDevices, _In_ const OrtEnv* env, _Out_ size_t* num_devices) { API_IMPL_BEGIN #if !defined(ORT_MINIMAL_BUILD) if (env == nullptr) { return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "env must not be null"); } - if (devices == nullptr || num_devices == nullptr) { - return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "devices and num_devices must not be null"); + if (num_devices == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "num_devices must not be null"); } const auto& device_vector = env->GetEnvironment().GetSortedOrtHardwareDevices(); - *devices = device_vector.data(); *num_devices = device_vector.size(); + return nullptr; +#else + ORT_UNUSED_PARAMETER(env); + ORT_UNUSED_PARAMETER(num_devices); + return OrtApis::CreateStatus(ORT_NOT_IMPLEMENTED, "GetNumHardwareDevices is not available in minimal build"); +#endif + API_IMPL_END +} + +ORT_API_STATUS_IMPL(OrtApis::GetHardwareDevices, _In_ const OrtEnv* env, + _Out_writes_(num_devices) const OrtHardwareDevice** devices, + _In_ size_t num_devices) { + API_IMPL_BEGIN +#if !defined(ORT_MINIMAL_BUILD) + if (env == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "env must not be null"); + } + if (devices == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "devices must not be null"); + } + + const auto& device_vector = env->GetEnvironment().GetSortedOrtHardwareDevices(); + size_t available_devices = device_vector.size(); + size_t copy_count = std::min(num_devices, available_devices); + + for (size_t i = 0; i < copy_count; ++i) { + devices[i] = device_vector[i]; + } + return nullptr; #else ORT_UNUSED_PARAMETER(env); ORT_UNUSED_PARAMETER(devices); ORT_UNUSED_PARAMETER(num_devices); - return OrtApis::CreateStatus(ORT_NOT_IMPLEMENTED, "GetOrtHardwareDevices is not available in minimal build"); + return OrtApis::CreateStatus(ORT_NOT_IMPLEMENTED, "GetHardwareDevices is not available in minimal build"); #endif API_IMPL_END } -ORT_API_STATUS_IMPL(OrtApis::GetHardwareDeviceEPIncompatibilityReasons, _In_ const OrtEnv* env, _In_ const char* ep_name, _In_ const OrtHardwareDevice* hw, _Outptr_ OrtDeviceEpIncompatibilityDetails** details) { +ORT_API_STATUS_IMPL(OrtApis::GetHardwareDeviceEpIncompatibilityDetails, _In_ const OrtEnv* env, _In_ const char* ep_name, _In_ const OrtHardwareDevice* hw, _Outptr_ OrtDeviceEpIncompatibilityDetails** details) { API_IMPL_BEGIN #if !defined(ORT_MINIMAL_BUILD) // Validate all input parameters @@ -4416,7 +4445,7 @@ ORT_API_STATUS_IMPL(OrtApis::GetHardwareDeviceEPIncompatibilityReasons, _In_ con } std::unique_ptr compat_details; - auto status = env->GetEnvironment().GetHardwareDeviceEPIncompatibilityReasons(ep_name, hw, compat_details); + auto status = env->GetEnvironment().GetHardwareDeviceEpIncompatibilityDetails(ep_name, hw, compat_details); if (!status.IsOK()) { return ToOrtStatus(status); } @@ -4428,7 +4457,7 @@ ORT_API_STATUS_IMPL(OrtApis::GetHardwareDeviceEPIncompatibilityReasons, _In_ con ORT_UNUSED_PARAMETER(ep_name); ORT_UNUSED_PARAMETER(hw); ORT_UNUSED_PARAMETER(details); - return OrtApis::CreateStatus(ORT_NOT_IMPLEMENTED, "GetHardwareDeviceEPIncompatibilityReasons is not available in minimal build"); + return OrtApis::CreateStatus(ORT_NOT_IMPLEMENTED, "GetHardwareDeviceEpIncompatibilityDetails is not available in minimal build"); #endif API_IMPL_END } diff --git a/onnxruntime/core/session/ort_apis.h b/onnxruntime/core/session/ort_apis.h index 712c11a8cbd72..a93d853592dea 100644 --- a/onnxruntime/core/session/ort_apis.h +++ b/onnxruntime/core/session/ort_apis.h @@ -480,9 +480,11 @@ ORT_API_STATUS_IMPL(KernelContext_GetAllocator, _In_ const OrtKernelContext* con ORT_API(const char*, GetBuildInfoString); -ORT_API_STATUS_IMPL(GetOrtHardwareDevices, _In_ const OrtEnv* env, _Outptr_ const OrtHardwareDevice* const** devices, _Out_ size_t* num_devices); +ORT_API_STATUS_IMPL(GetNumHardwareDevices, _In_ const OrtEnv* env, _Out_ size_t* num_devices); -ORT_API_STATUS_IMPL(GetHardwareDeviceEPIncompatibilityReasons, _In_ const OrtEnv* env, _In_ const char* ep_name, _In_ const OrtHardwareDevice* hw, _Outptr_ OrtDeviceEpIncompatibilityDetails** details); +ORT_API_STATUS_IMPL(GetHardwareDevices, _In_ const OrtEnv* env, _Out_writes_(num_devices) const OrtHardwareDevice** devices, _In_ size_t num_devices); + +ORT_API_STATUS_IMPL(GetHardwareDeviceEpIncompatibilityDetails, _In_ const OrtEnv* env, _In_ const char* ep_name, _In_ const OrtHardwareDevice* hw, _Outptr_ OrtDeviceEpIncompatibilityDetails** details); ORT_API_STATUS_IMPL(DeviceEpIncompatibilityDetails_GetReasonsBitmask, _In_ const OrtDeviceEpIncompatibilityDetails* details, _Out_ uint32_t* reasons_bitmask); diff --git a/onnxruntime/core/session/plugin_ep/ep_api.cc b/onnxruntime/core/session/plugin_ep/ep_api.cc index 9228a53dacf76..8a9f33be7d428 100644 --- a/onnxruntime/core/session/plugin_ep/ep_api.cc +++ b/onnxruntime/core/session/plugin_ep/ep_api.cc @@ -677,23 +677,23 @@ ORT_API_STATUS_IMPL(CreateIfKernel, _In_ const OrtKernelInfo* kernel_info, _Outp API_IMPL_END } -ORT_API_STATUS_IMPL(CreateDeviceEpIncompatibilityDetails, _In_ uint32_t reasons_bitmask, +ORT_API_STATUS_IMPL(DeviceEpIncompatibilityDetails_Initialize, _Inout_ OrtDeviceEpIncompatibilityDetails* details, + _In_ uint32_t reasons_bitmask, _In_ int32_t error_code, - _In_opt_z_ const char* notes, - _Outptr_ OrtDeviceEpIncompatibilityDetails** details) { + _In_opt_z_ const char* notes) { API_IMPL_BEGIN if (details == nullptr) { - return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "details output parameter must not be null"); + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "details parameter must not be null"); } - auto compat_details = std::make_unique(); - compat_details->reasons_bitmask = reasons_bitmask; - compat_details->error_code = error_code; + details->reasons_bitmask = reasons_bitmask; + details->error_code = error_code; if (notes != nullptr) { - compat_details->notes = notes; + details->notes = notes; + } else { + details->notes.clear(); } - *details = compat_details.release(); return nullptr; API_IMPL_END } @@ -844,7 +844,7 @@ static constexpr OrtEpApi ort_ep_api = { &OrtExecutionProviderApi::CreateLoopKernel, &OrtExecutionProviderApi::CreateScanKernel, &OrtExecutionProviderApi::ReleaseKernelImpl, - &OrtExecutionProviderApi::CreateDeviceEpIncompatibilityDetails, + &OrtExecutionProviderApi::DeviceEpIncompatibilityDetails_Initialize, }; // checks that we don't violate the rule that the functions must remain in the slots they were originally assigned diff --git a/onnxruntime/core/session/plugin_ep/ep_factory_internal.cc b/onnxruntime/core/session/plugin_ep/ep_factory_internal.cc index abc056d5899e7..a13645e293844 100644 --- a/onnxruntime/core/session/plugin_ep/ep_factory_internal.cc +++ b/onnxruntime/core/session/plugin_ep/ep_factory_internal.cc @@ -34,7 +34,7 @@ EpFactoryInternal::EpFactoryInternal(std::unique_ptr impl OrtEpFactory::CreateSyncStreamForDevice = Forward::CreateSyncStreamForDevice; OrtEpFactory::SetEnvironmentOptions = Forward::SetEnvironmentOptions; OrtEpFactory::CreateExternalResourceImporterForDevice = Forward::CreateExternalResourceImporterForDevice; - OrtEpFactory::GetHardwareDeviceIncompatibilityReasons = Forward::GetHardwareDeviceIncompatibilityReasons; + OrtEpFactory::GetHardwareDeviceIncompatibilityDetails = Forward::GetHardwareDeviceIncompatibilityDetails; } InternalExecutionProviderFactory::InternalExecutionProviderFactory(EpFactoryInternal& ep_factory, diff --git a/onnxruntime/core/session/plugin_ep/ep_factory_internal.h b/onnxruntime/core/session/plugin_ep/ep_factory_internal.h index 35a2d657448e7..6f4a37f44fb44 100644 --- a/onnxruntime/core/session/plugin_ep/ep_factory_internal.h +++ b/onnxruntime/core/session/plugin_ep/ep_factory_internal.h @@ -96,9 +96,9 @@ class EpFactoryInternal : public OrtEpFactory { return impl_->CreateExternalResourceImporterForDevice(ep_device, importer); } - OrtStatus* GetHardwareDeviceIncompatibilityReasons(_In_ const OrtHardwareDevice* hw, - _Outptr_ OrtDeviceEpIncompatibilityDetails** details) noexcept { - return impl_->GetHardwareDeviceIncompatibilityReasons(hw, details); + OrtStatus* GetHardwareDeviceIncompatibilityDetails(_In_ const OrtHardwareDevice* hw, + _Inout_ OrtDeviceEpIncompatibilityDetails* details) noexcept { + return impl_->GetHardwareDeviceIncompatibilityDetails(hw, details); } // Function ORT calls to release an EP instance. diff --git a/onnxruntime/core/session/plugin_ep/ep_factory_internal_impl.h b/onnxruntime/core/session/plugin_ep/ep_factory_internal_impl.h index 1f72b303f3894..7f42cdda33a96 100644 --- a/onnxruntime/core/session/plugin_ep/ep_factory_internal_impl.h +++ b/onnxruntime/core/session/plugin_ep/ep_factory_internal_impl.h @@ -96,10 +96,9 @@ class EpFactoryInternalImpl { return nullptr; } - virtual OrtStatus* GetHardwareDeviceIncompatibilityReasons(_In_ const OrtHardwareDevice* /*hw*/, - _Outptr_ OrtDeviceEpIncompatibilityDetails** details) noexcept { - // Default implementation: return nullptr to indicate no incompatibility info provided (device assumed compatible) - *details = nullptr; + virtual OrtStatus* GetHardwareDeviceIncompatibilityDetails(_In_ const OrtHardwareDevice* /*hw*/, + _Inout_ OrtDeviceEpIncompatibilityDetails* /*details*/) noexcept { + // Default implementation: leave details unchanged (device assumed compatible) return nullptr; } diff --git a/onnxruntime/core/session/plugin_ep/ep_factory_provider_bridge.h b/onnxruntime/core/session/plugin_ep/ep_factory_provider_bridge.h index 531cbf019af24..3a7a1b6504d12 100644 --- a/onnxruntime/core/session/plugin_ep/ep_factory_provider_bridge.h +++ b/onnxruntime/core/session/plugin_ep/ep_factory_provider_bridge.h @@ -77,14 +77,13 @@ class ProviderBridgeEpFactory : public EpFactoryInternalImpl { return ep_factory_.CreateExternalResourceImporterForDevice(&ep_factory_, ep_device, importer); } - OrtStatus* GetHardwareDeviceIncompatibilityReasons(_In_ const OrtHardwareDevice* hw, - _Outptr_ OrtDeviceEpIncompatibilityDetails** details) noexcept override { - if (ep_factory_.GetHardwareDeviceIncompatibilityReasons == nullptr) { - // Factory doesn't implement this hook, return nullptr (no incompatibility) - *details = nullptr; + OrtStatus* GetHardwareDeviceIncompatibilityDetails(_In_ const OrtHardwareDevice* hw, + _Inout_ OrtDeviceEpIncompatibilityDetails* details) noexcept override { + if (ep_factory_.GetHardwareDeviceIncompatibilityDetails == nullptr) { + // Factory doesn't implement this hook, leave details unchanged (device assumed compatible) return nullptr; } - return ep_factory_.GetHardwareDeviceIncompatibilityReasons(&ep_factory_, hw, details); + return ep_factory_.GetHardwareDeviceIncompatibilityDetails(&ep_factory_, hw, details); } OrtEpFactory& ep_factory_; diff --git a/onnxruntime/core/session/plugin_ep/forward_to_factory_impl.h b/onnxruntime/core/session/plugin_ep/forward_to_factory_impl.h index 1e822c811fa3c..27c453b500017 100644 --- a/onnxruntime/core/session/plugin_ep/forward_to_factory_impl.h +++ b/onnxruntime/core/session/plugin_ep/forward_to_factory_impl.h @@ -94,10 +94,10 @@ struct ForwardToFactoryImpl { return static_cast(this_ptr)->CreateExternalResourceImporterForDevice(ep_device, importer); } - static OrtStatus* ORT_API_CALL GetHardwareDeviceIncompatibilityReasons(_In_ OrtEpFactory* this_ptr, + static OrtStatus* ORT_API_CALL GetHardwareDeviceIncompatibilityDetails(_In_ OrtEpFactory* this_ptr, _In_ const OrtHardwareDevice* hw, - _Outptr_ OrtDeviceEpIncompatibilityDetails** details) noexcept { - return static_cast(this_ptr)->GetHardwareDeviceIncompatibilityReasons(hw, details); + _Inout_ OrtDeviceEpIncompatibilityDetails* details) noexcept { + return static_cast(this_ptr)->GetHardwareDeviceIncompatibilityDetails(hw, details); } static void ORT_API_CALL ReleaseEp(OrtEpFactory* this_ptr, OrtEp* ep) noexcept { diff --git a/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc b/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc index 33ffef2855009..cb15d9e6e6889 100644 --- a/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc +++ b/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc @@ -36,7 +36,7 @@ ExampleEpFactory::ExampleEpFactory(const char* ep_name, ApiPtrs apis, const OrtL IsStreamAware = IsStreamAwareImpl; CreateSyncStreamForDevice = CreateSyncStreamForDeviceImpl; - GetHardwareDeviceIncompatibilityReasons = GetHardwareDeviceIncompatibilityReasonsImpl; + GetHardwareDeviceIncompatibilityDetails = GetHardwareDeviceIncompatibilityDetailsImpl; CreateExternalResourceImporterForDevice = CreateExternalResourceImporterForDeviceImpl; @@ -334,12 +334,11 @@ OrtStatus* ORT_API_CALL ExampleEpFactory::CreateExternalResourceImporterForDevic return nullptr; } -OrtStatus* ORT_API_CALL ExampleEpFactory::GetHardwareDeviceIncompatibilityReasonsImpl( +OrtStatus* ORT_API_CALL ExampleEpFactory::GetHardwareDeviceIncompatibilityDetailsImpl( OrtEpFactory* this_ptr, const OrtHardwareDevice* hw, - OrtDeviceEpIncompatibilityDetails** details) noexcept { + OrtDeviceEpIncompatibilityDetails* details) noexcept { auto& factory = *static_cast(this_ptr); - *details = nullptr; // Example: This EP only supports CPU devices. Report incompatibility for non-CPU devices. OrtHardwareDeviceType device_type = factory.ort_api.HardwareDevice_Type(hw); @@ -347,13 +346,13 @@ OrtStatus* ORT_API_CALL ExampleEpFactory::GetHardwareDeviceIncompatibilityReason if (device_type != OrtHardwareDeviceType_CPU) { // Report that the device type is not supported uint32_t reasons = OrtDeviceEpIncompatibility_DEVICE_INCOMPATIBLE; - return factory.ep_api.CreateDeviceEpIncompatibilityDetails( + return factory.ep_api.DeviceEpIncompatibilityDetails_Initialize( + details, reasons, static_cast(device_type), // Use device type as the error code for testing - "ExampleEP only supports CPU devices", - details); + "ExampleEP only supports CPU devices"); } - // Device is compatible - return empty details (no incompatibility reasons) - return factory.ep_api.CreateDeviceEpIncompatibilityDetails(0, 0, nullptr, details); + // Device is compatible - details are already initialized with default values by ORT + return nullptr; } diff --git a/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.h b/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.h index 0a25d4d3a097f..9306b0fc88ec9 100644 --- a/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.h +++ b/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.h @@ -73,10 +73,10 @@ class ExampleEpFactory : public OrtEpFactory, public ApiPtrs { const OrtEpDevice* ep_device, OrtExternalResourceImporterImpl** out_importer) noexcept; - static OrtStatus* ORT_API_CALL GetHardwareDeviceIncompatibilityReasonsImpl( + static OrtStatus* ORT_API_CALL GetHardwareDeviceIncompatibilityDetailsImpl( OrtEpFactory* this_ptr, const OrtHardwareDevice* hw, - OrtDeviceEpIncompatibilityDetails** details) noexcept; + OrtDeviceEpIncompatibilityDetails* details) noexcept; const OrtLogger& default_logger_; // default logger for the EP factory const std::string ep_name_; // EP name diff --git a/onnxruntime/test/autoep/test_execution.cc b/onnxruntime/test/autoep/test_execution.cc index 8fb49d67279a2..0970654b48ca1 100644 --- a/onnxruntime/test/autoep/test_execution.cc +++ b/onnxruntime/test/autoep/test_execution.cc @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include +#include // #include #include #include @@ -550,7 +551,7 @@ TEST(OrtEpLibrary, KernelPluginEp_ControlFlow_Scan) { } } -// Tests the GetHardwareDeviceEPIncompatibilityReasons C API with the example plugin EP. +// Tests the GetHardwareDeviceEpIncompatibilityDetails C API with the example plugin EP. // The example plugin EP supports CPU devices, so this test verifies that a CPU device // is reported as compatible (reasons_bitmask == 0). TEST(OrtEpLibrary, PluginEp_CpuDevice_ReturnsCompatible) { @@ -564,10 +565,11 @@ TEST(OrtEpLibrary, PluginEp_CpuDevice_ReturnsCompatible) { ASSERT_NO_FATAL_FAILURE(Utils::RegisterAndGetExampleEp(*ort_env, Utils::example_ep_info, example_ep)); // Get all hardware devices - const OrtHardwareDevice* const* hw_devices = nullptr; size_t num_hw_devices = 0; - ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); + ASSERT_ORTSTATUS_OK(api->GetNumHardwareDevices(env, &num_hw_devices)); ASSERT_GT(num_hw_devices, 0u); + std::vector hw_devices(num_hw_devices); + ASSERT_ORTSTATUS_OK(api->GetHardwareDevices(env, hw_devices.data(), num_hw_devices)); // Find a CPU device using the public accessor const OrtHardwareDevice* cpu_device = nullptr; @@ -581,7 +583,7 @@ TEST(OrtEpLibrary, PluginEp_CpuDevice_ReturnsCompatible) { // Check compatibility - ExampleEP supports CPU, so should return no incompatibility reasons OrtDeviceEpIncompatibilityDetails* details = nullptr; - ASSERT_ORTSTATUS_OK(api->GetHardwareDeviceEPIncompatibilityReasons(env, Utils::example_ep_info.registration_name.c_str(), + ASSERT_ORTSTATUS_OK(api->GetHardwareDeviceEpIncompatibilityDetails(env, Utils::example_ep_info.registration_name.c_str(), cpu_device, &details)); ASSERT_NE(details, nullptr); @@ -597,7 +599,7 @@ TEST(OrtEpLibrary, PluginEp_CpuDevice_ReturnsCompatible) { api->ReleaseDeviceEpIncompatibilityDetails(details); } -// Tests the GetHardwareDeviceEPIncompatibilityReasons C API with the example plugin EP. +// Tests the GetHardwareDeviceEpIncompatibilityDetails C API with the example plugin EP. // The example plugin EP only supports CPU devices, so this test verifies that a GPU device // is reported as incompatible (reasons_bitmask != 0). TEST(OrtEpLibrary, PluginEp_GpuDevice_ReturnsInCompatible) { @@ -611,10 +613,11 @@ TEST(OrtEpLibrary, PluginEp_GpuDevice_ReturnsInCompatible) { ASSERT_NO_FATAL_FAILURE(Utils::RegisterAndGetExampleEp(*ort_env, Utils::example_ep_info, example_ep)); // Get all hardware devices - const OrtHardwareDevice* const* hw_devices = nullptr; size_t num_hw_devices = 0; - ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); + ASSERT_ORTSTATUS_OK(api->GetNumHardwareDevices(env, &num_hw_devices)); ASSERT_GT(num_hw_devices, 0u); + std::vector hw_devices(num_hw_devices); + ASSERT_ORTSTATUS_OK(api->GetHardwareDevices(env, hw_devices.data(), num_hw_devices)); // Find a GPU device using the public accessor const OrtHardwareDevice* gpu_device = nullptr; @@ -632,7 +635,7 @@ TEST(OrtEpLibrary, PluginEp_GpuDevice_ReturnsInCompatible) { // Check compatibility - ExampleEP only supports CPU, so GPU should return incompatibility reasons OrtDeviceEpIncompatibilityDetails* details = nullptr; - ASSERT_ORTSTATUS_OK(api->GetHardwareDeviceEPIncompatibilityReasons(env, Utils::example_ep_info.registration_name.c_str(), + ASSERT_ORTSTATUS_OK(api->GetHardwareDeviceEpIncompatibilityDetails(env, Utils::example_ep_info.registration_name.c_str(), gpu_device, &details)); ASSERT_NE(details, nullptr); diff --git a/onnxruntime/test/framework/hardware_device_compatibility_test.cc b/onnxruntime/test/framework/hardware_device_compatibility_test.cc index 43f3bdc429ca7..03ec7939e67ff 100644 --- a/onnxruntime/test/framework/hardware_device_compatibility_test.cc +++ b/onnxruntime/test/framework/hardware_device_compatibility_test.cc @@ -9,31 +9,44 @@ #include "test/test_environment.h" #include "test/util/include/api_asserts.h" +#include + using namespace onnxruntime::test; +namespace { +// Helper to get hardware devices using the two-step API pattern +void GetHardwareDevicesHelper(const OrtApi* api, OrtEnv* env, + std::vector& devices) { + size_t num_devices = 0; + ASSERT_ORTSTATUS_OK(api->GetNumHardwareDevices(env, &num_devices)); + devices.resize(num_devices); + if (num_devices > 0) { + ASSERT_ORTSTATUS_OK(api->GetHardwareDevices(env, devices.data(), num_devices)); + } +} +} // namespace + // ----------------------------- -// GetHardwareDeviceEPIncompatibilityReasons C API unit tests +// GetHardwareDeviceEpIncompatibilityDetails C API unit tests // ----------------------------- -TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullEnv) { +TEST(GetHardwareDeviceEpIncompatibilityDetailsCapiTest, InvalidArguments_NullEnv) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); - // Create env for GetOrtHardwareDevices + // Create env for GetHardwareDevices OrtEnv* env = nullptr; EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); EXPECT_NE(env, nullptr); // Get a valid hardware device first - const OrtHardwareDevice* const* hw_devices = nullptr; - size_t num_hw_devices = 0; - ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); - ASSERT_GT(num_hw_devices, 0u); - ASSERT_NE(hw_devices, nullptr); + std::vector hw_devices; + ASSERT_NO_FATAL_FAILURE(GetHardwareDevicesHelper(api, env, hw_devices)); + ASSERT_GT(hw_devices.size(), 0u); - // env == nullptr for GetHardwareDeviceEPIncompatibilityReasons + // env == nullptr for GetHardwareDeviceEpIncompatibilityDetails OrtDeviceEpIncompatibilityDetails* details = nullptr; - OrtStatus* st = api->GetHardwareDeviceEPIncompatibilityReasons(nullptr, "CPUExecutionProvider", hw_devices[0], &details); + OrtStatus* st = api->GetHardwareDeviceEpIncompatibilityDetails(nullptr, "CPUExecutionProvider", hw_devices[0], &details); ASSERT_NE(st, nullptr); EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); api->ReleaseStatus(st); @@ -41,7 +54,7 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullEnv api->ReleaseEnv(env); } -TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullEpName) { +TEST(GetHardwareDeviceEpIncompatibilityDetailsCapiTest, InvalidArguments_NullEpName) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); @@ -49,14 +62,13 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullEpN EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); EXPECT_NE(env, nullptr); - const OrtHardwareDevice* const* hw_devices = nullptr; - size_t num_hw_devices = 0; - ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); - ASSERT_GT(num_hw_devices, 0u); + std::vector hw_devices; + ASSERT_NO_FATAL_FAILURE(GetHardwareDevicesHelper(api, env, hw_devices)); + ASSERT_GT(hw_devices.size(), 0u); // ep_name == nullptr OrtDeviceEpIncompatibilityDetails* details = nullptr; - OrtStatus* st = api->GetHardwareDeviceEPIncompatibilityReasons(env, nullptr, hw_devices[0], &details); + OrtStatus* st = api->GetHardwareDeviceEpIncompatibilityDetails(env, nullptr, hw_devices[0], &details); ASSERT_NE(st, nullptr); EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); api->ReleaseStatus(st); @@ -64,7 +76,7 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullEpN api->ReleaseEnv(env); } -TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_EmptyEpName) { +TEST(GetHardwareDeviceEpIncompatibilityDetailsCapiTest, InvalidArguments_EmptyEpName) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); @@ -72,14 +84,13 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_EmptyEp EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); EXPECT_NE(env, nullptr); - const OrtHardwareDevice* const* hw_devices = nullptr; - size_t num_hw_devices = 0; - ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); - ASSERT_GT(num_hw_devices, 0u); + std::vector hw_devices; + ASSERT_NO_FATAL_FAILURE(GetHardwareDevicesHelper(api, env, hw_devices)); + ASSERT_GT(hw_devices.size(), 0u); // ep_name == "" OrtDeviceEpIncompatibilityDetails* details = nullptr; - OrtStatus* st = api->GetHardwareDeviceEPIncompatibilityReasons(env, "", hw_devices[0], &details); + OrtStatus* st = api->GetHardwareDeviceEpIncompatibilityDetails(env, "", hw_devices[0], &details); ASSERT_NE(st, nullptr); EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); api->ReleaseStatus(st); @@ -87,7 +98,7 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_EmptyEp api->ReleaseEnv(env); } -TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullHardwareDevice) { +TEST(GetHardwareDeviceEpIncompatibilityDetailsCapiTest, InvalidArguments_NullHardwareDevice) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); @@ -97,7 +108,7 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullHar // hw == nullptr OrtDeviceEpIncompatibilityDetails* details = nullptr; - OrtStatus* st = api->GetHardwareDeviceEPIncompatibilityReasons(env, "CPUExecutionProvider", nullptr, &details); + OrtStatus* st = api->GetHardwareDeviceEpIncompatibilityDetails(env, "CPUExecutionProvider", nullptr, &details); ASSERT_NE(st, nullptr); EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); api->ReleaseStatus(st); @@ -105,7 +116,7 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullHar api->ReleaseEnv(env); } -TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullDetailsOutput) { +TEST(GetHardwareDeviceEpIncompatibilityDetailsCapiTest, InvalidArguments_NullDetailsOutput) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); @@ -113,13 +124,12 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullDet EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); EXPECT_NE(env, nullptr); - const OrtHardwareDevice* const* hw_devices = nullptr; - size_t num_hw_devices = 0; - ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); - ASSERT_GT(num_hw_devices, 0u); + std::vector hw_devices; + ASSERT_NO_FATAL_FAILURE(GetHardwareDevicesHelper(api, env, hw_devices)); + ASSERT_GT(hw_devices.size(), 0u); // details == nullptr - OrtStatus* st = api->GetHardwareDeviceEPIncompatibilityReasons(env, "CPUExecutionProvider", hw_devices[0], nullptr); + OrtStatus* st = api->GetHardwareDeviceEpIncompatibilityDetails(env, "CPUExecutionProvider", hw_devices[0], nullptr); ASSERT_NE(st, nullptr); EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); api->ReleaseStatus(st); @@ -127,7 +137,7 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, InvalidArguments_NullDet api->ReleaseEnv(env); } -TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, UnregisteredEp_ReturnsInvalidArgument) { +TEST(GetHardwareDeviceEpIncompatibilityDetailsCapiTest, UnregisteredEp_ReturnsInvalidArgument) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); @@ -135,14 +145,13 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, UnregisteredEp_ReturnsIn EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); EXPECT_NE(env, nullptr); - const OrtHardwareDevice* const* hw_devices = nullptr; - size_t num_hw_devices = 0; - ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); - ASSERT_GT(num_hw_devices, 0u); + std::vector hw_devices; + ASSERT_NO_FATAL_FAILURE(GetHardwareDevicesHelper(api, env, hw_devices)); + ASSERT_GT(hw_devices.size(), 0u); // Non-existent EP name should return INVALID_ARGUMENT OrtDeviceEpIncompatibilityDetails* details = nullptr; - OrtStatus* st = api->GetHardwareDeviceEPIncompatibilityReasons(env, "NonExistentExecutionProvider", hw_devices[0], &details); + OrtStatus* st = api->GetHardwareDeviceEpIncompatibilityDetails(env, "NonExistentExecutionProvider", hw_devices[0], &details); ASSERT_NE(st, nullptr); EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); EXPECT_THAT(api->GetErrorMessage(st), testing::HasSubstr("No valid factory found")); @@ -151,7 +160,7 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, UnregisteredEp_ReturnsIn api->ReleaseEnv(env); } -TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, CpuEp_ReturnsEmptyDetails) { +TEST(GetHardwareDeviceEpIncompatibilityDetailsCapiTest, CpuEp_ReturnsEmptyDetails) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); @@ -159,14 +168,13 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, CpuEp_ReturnsEmptyDetail EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); EXPECT_NE(env, nullptr); - const OrtHardwareDevice* const* hw_devices = nullptr; - size_t num_hw_devices = 0; - ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); - ASSERT_GT(num_hw_devices, 0u); + std::vector hw_devices; + ASSERT_NO_FATAL_FAILURE(GetHardwareDevicesHelper(api, env, hw_devices)); + ASSERT_GT(hw_devices.size(), 0u); - // CPU EP doesn't implement GetHardwareDeviceIncompatibilityReasons, so should return empty details + // CPU EP doesn't implement GetHardwareDeviceIncompatibilityDetails, so should return empty details OrtDeviceEpIncompatibilityDetails* details = nullptr; - ASSERT_ORTSTATUS_OK(api->GetHardwareDeviceEPIncompatibilityReasons(env, "CPUExecutionProvider", hw_devices[0], &details)); + ASSERT_ORTSTATUS_OK(api->GetHardwareDeviceEpIncompatibilityDetails(env, "CPUExecutionProvider", hw_devices[0], &details)); ASSERT_NE(details, nullptr); // Verify empty details @@ -186,7 +194,7 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, CpuEp_ReturnsEmptyDetail api->ReleaseEnv(env); } -TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, AccessorFunctions_NullDetails) { +TEST(GetHardwareDeviceEpIncompatibilityDetailsCapiTest, AccessorFunctions_NullDetails) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); @@ -210,7 +218,7 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, AccessorFunctions_NullDe api->ReleaseStatus(st); } -TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, AccessorFunctions_NullOutputPtr) { +TEST(GetHardwareDeviceEpIncompatibilityDetailsCapiTest, AccessorFunctions_NullOutputPtr) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); @@ -218,14 +226,13 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, AccessorFunctions_NullOu EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "EpIncompatTest", &env)); EXPECT_NE(env, nullptr); - const OrtHardwareDevice* const* hw_devices = nullptr; - size_t num_hw_devices = 0; - ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &hw_devices, &num_hw_devices)); - ASSERT_GT(num_hw_devices, 0u); + std::vector hw_devices; + ASSERT_NO_FATAL_FAILURE(GetHardwareDevicesHelper(api, env, hw_devices)); + ASSERT_GT(hw_devices.size(), 0u); // Get a valid details object first OrtDeviceEpIncompatibilityDetails* details = nullptr; - ASSERT_ORTSTATUS_OK(api->GetHardwareDeviceEPIncompatibilityReasons(env, "CPUExecutionProvider", hw_devices[0], &details)); + ASSERT_ORTSTATUS_OK(api->GetHardwareDeviceEpIncompatibilityDetails(env, "CPUExecutionProvider", hw_devices[0], &details)); ASSERT_NE(details, nullptr); // Test accessor functions with null output pointers @@ -249,22 +256,21 @@ TEST(GetHardwareDeviceEPIncompatibilityReasonsCapiTest, AccessorFunctions_NullOu } // ----------------------------- -// GetOrtHardwareDevices C API unit tests +// GetNumHardwareDevices / GetHardwareDevices C API unit tests // ----------------------------- -TEST(GetOrtHardwareDevicesCapiTest, InvalidArguments_NullEnv) { +TEST(GetHardwareDevicesCapiTest, GetNumHardwareDevices_InvalidArguments_NullEnv) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); - const OrtHardwareDevice* const* devices = nullptr; size_t num_devices = 0; - OrtStatus* st = api->GetOrtHardwareDevices(nullptr, &devices, &num_devices); + OrtStatus* st = api->GetNumHardwareDevices(nullptr, &num_devices); ASSERT_NE(st, nullptr); EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); api->ReleaseStatus(st); } -TEST(GetOrtHardwareDevicesCapiTest, InvalidArguments_NullDevices) { +TEST(GetHardwareDevicesCapiTest, GetNumHardwareDevices_InvalidArguments_NullNumDevices) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); @@ -272,8 +278,7 @@ TEST(GetOrtHardwareDevicesCapiTest, InvalidArguments_NullDevices) { EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "HwDevicesTest", &env)); EXPECT_NE(env, nullptr); - size_t num_devices = 0; - OrtStatus* st = api->GetOrtHardwareDevices(env, nullptr, &num_devices); + OrtStatus* st = api->GetNumHardwareDevices(env, nullptr); ASSERT_NE(st, nullptr); EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); api->ReleaseStatus(st); @@ -281,7 +286,18 @@ TEST(GetOrtHardwareDevicesCapiTest, InvalidArguments_NullDevices) { api->ReleaseEnv(env); } -TEST(GetOrtHardwareDevicesCapiTest, InvalidArguments_NullNumDevices) { +TEST(GetHardwareDevicesCapiTest, GetHardwareDevices_InvalidArguments_NullEnv) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + const OrtHardwareDevice* devices[1] = {nullptr}; + OrtStatus* st = api->GetHardwareDevices(nullptr, devices, 1); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); +} + +TEST(GetHardwareDevicesCapiTest, GetHardwareDevices_InvalidArguments_NullDevices) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); @@ -289,8 +305,7 @@ TEST(GetOrtHardwareDevicesCapiTest, InvalidArguments_NullNumDevices) { EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "HwDevicesTest", &env)); EXPECT_NE(env, nullptr); - const OrtHardwareDevice* const* devices = nullptr; - OrtStatus* st = api->GetOrtHardwareDevices(env, &devices, nullptr); + OrtStatus* st = api->GetHardwareDevices(env, nullptr, 1); ASSERT_NE(st, nullptr); EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); api->ReleaseStatus(st); @@ -298,7 +313,7 @@ TEST(GetOrtHardwareDevicesCapiTest, InvalidArguments_NullNumDevices) { api->ReleaseEnv(env); } -TEST(GetOrtHardwareDevicesCapiTest, ReturnsDevices) { +TEST(GetHardwareDevicesCapiTest, ReturnsDevices) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); @@ -306,17 +321,21 @@ TEST(GetOrtHardwareDevicesCapiTest, ReturnsDevices) { EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "HwDevicesTest", &env)); EXPECT_NE(env, nullptr); - const OrtHardwareDevice* const* devices = nullptr; + // Get number of devices first size_t num_devices = 0; - ASSERT_ORTSTATUS_OK(api->GetOrtHardwareDevices(env, &devices, &num_devices)); + ASSERT_ORTSTATUS_OK(api->GetNumHardwareDevices(env, &num_devices)); // Should return at least one device (CPU) EXPECT_GT(num_devices, 0u); - EXPECT_NE(devices, nullptr); + + // Allocate array and get devices + std::vector devices(num_devices); + ASSERT_ORTSTATUS_OK(api->GetHardwareDevices(env, devices.data(), num_devices)); // Verify we can access device properties via C API accessor functions for (size_t i = 0; i < num_devices; ++i) { const OrtHardwareDevice* device = devices[i]; + ASSERT_NE(device, nullptr); // Device type should be valid (CPU, GPU, or NPU) OrtHardwareDeviceType device_type = api->HardwareDevice_Type(device); EXPECT_TRUE(device_type == OrtHardwareDeviceType_CPU || From d491b99321c2895a79fb2ba05e773bb5be217d70 Mon Sep 17 00:00:00 2001 From: Xiaoxi Han Date: Sun, 11 Jan 2026 18:29:33 -0800 Subject: [PATCH 07/11] fix clang --- .../core/session/onnxruntime_c_api.h | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/include/onnxruntime/core/session/onnxruntime_c_api.h b/include/onnxruntime/core/session/onnxruntime_c_api.h index 84654dd5ea433..804121d03f9ac 100644 --- a/include/onnxruntime/core/session/onnxruntime_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_c_api.h @@ -6912,6 +6912,43 @@ struct OrtApi { ORT_CLASS_RELEASE(DeviceEpIncompatibilityDetails); /// @} + + /** \brief Get the EP Interop API instance. + * + * Get the Interop API instance to work with external resources. This API provides functions + * for importing external GPU memory and semaphores for zero-copy sharing between ORT inference + * and other GPU workloads. + * + * \return Interop API struct instance. + * + * \since Version 1.24. + */ + const OrtInteropApi*(ORT_API_CALL* GetInteropApi)(void); + + /** \brief Get the EP device assigned to each session output. + * + * Returns the OrtEpDevice assigned to each output of the session after graph partitioning. + * This allows validation that outputs are placed on the expected device for external resource sharing. + * + * The EP device for each output is determined by which execution provider will produce that output + * during inferencing. This information is useful for: + * - Validating that outputs will be placed on the expected device for external resource sharing + * - Deciding whether to use external memory handles for outputs + * + * \param[in] session The OrtSession instance to query. + * \param[out] outputs_ep_devices An array to be filled with the EP device for each output. + * The array must be allocated by the caller with space for + * OrtEpDevice* values for each output. + * The order is the same as returned by SessionGetOutputName. + * \param[in] num_outputs The number of outputs in the session. Must match SessionGetOutputCount. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24 + */ + ORT_API2_STATUS(SessionGetEpDeviceForOutputs, _In_ const OrtSession* session, + _Out_writes_(num_outputs) const OrtEpDevice** outputs_ep_devices, + _In_ size_t num_outputs); }; /* From fd2075b020591cf6b268009fc8195fec4d884cac Mon Sep 17 00:00:00 2001 From: Xiaoxi Han Date: Mon, 12 Jan 2026 20:53:52 -0800 Subject: [PATCH 08/11] resolve feedbacks --- .../onnxruntime/core/session/environment.h | 2 +- .../core/session/onnxruntime_c_api.h | 2 +- .../core/session/onnxruntime_ep_c_api.h | 23 +++++++++--------- onnxruntime/core/session/environment.cc | 4 ---- onnxruntime/core/session/onnxruntime_c_api.cc | 9 +++++-- onnxruntime/core/session/plugin_ep/ep_api.cc | 4 ++-- .../library/example_plugin_ep/ep_factory.cc | 2 +- .../hardware_device_compatibility_test.cc | 24 +++++++++++++++++++ 8 files changed, 48 insertions(+), 22 deletions(-) diff --git a/include/onnxruntime/core/session/environment.h b/include/onnxruntime/core/session/environment.h index 7e3b3687a5856..3a423a64b9047 100644 --- a/include/onnxruntime/core/session/environment.h +++ b/include/onnxruntime/core/session/environment.h @@ -146,7 +146,7 @@ class Environment { return execution_devices_; } - /// Get hardware device incompatibility reasons for a specific EP. + /// Get hardware device incompatibility details for a specific EP. /// @param ep_name The name of the execution provider to check. /// @param hw The hardware device to check for incompatibility. /// @param details Output: Incompatibility details including reasons for incompatibility if any. diff --git a/include/onnxruntime/core/session/onnxruntime_c_api.h b/include/onnxruntime/core/session/onnxruntime_c_api.h index 804121d03f9ac..319ef5ebf8d8f 100644 --- a/include/onnxruntime/core/session/onnxruntime_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_c_api.h @@ -518,7 +518,7 @@ typedef enum OrtDeviceEpIncompatibilityReason { OrtDeviceEpIncompatibility_DRIVER_INCOMPATIBLE = 1 << 0, OrtDeviceEpIncompatibility_DEVICE_INCOMPATIBLE = 1 << 1, OrtDeviceEpIncompatibility_MISSING_DEPENDENCY = 1 << 2, - OrtDeviceEpIncompatibility_UNKNOWN = 1 << 30 + OrtDeviceEpIncompatibility_UNKNOWN = 1 << 31 } OrtDeviceEpIncompatibilityReason; /** \brief Delegate to allow providing custom OrtEpDevice selection logic diff --git a/include/onnxruntime/core/session/onnxruntime_ep_c_api.h b/include/onnxruntime/core/session/onnxruntime_ep_c_api.h index 932e57567f744..e084b00d80088 100644 --- a/include/onnxruntime/core/session/onnxruntime_ep_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_ep_c_api.h @@ -1408,13 +1408,14 @@ struct OrtEpApi { _Outptr_ OrtKernelImpl** kernel_out); ORT_CLASS_RELEASE(KernelImpl); - /** \brief Initialize an OrtDeviceEpIncompatibilityDetails instance. + /** \brief Set the details of an OrtDeviceEpIncompatibilityDetails instance. * - * Used by execution provider factories to initialize incompatibility details in their - * GetHardwareDeviceIncompatibilityDetails implementation. ORT creates the object and passes it - * to the EP, which uses this function to set the incompatibility information. + * Used by execution provider factories to set incompatibility details in their + * GetHardwareDeviceIncompatibilityDetails implementation. ORT creates and initializes the object + * before passing it to the EP, so calling this function is optional. The EP uses this function + * to set incompatibility information when the device is not compatible. * - * \param[in,out] details The OrtDeviceEpIncompatibilityDetails instance to initialize. + * \param[in,out] details The OrtDeviceEpIncompatibilityDetails instance to update. * \param[in] reasons_bitmask Bitmask of OrtDeviceEpIncompatibilityReason values. (0 = no incompatibility). * \param[in] error_code Optional EP-specific error code (0 = no error). * \param[in] notes Optional human-readable notes. Can be null. @@ -1423,7 +1424,7 @@ struct OrtEpApi { * * \since Version 1.24. */ - ORT_API2_STATUS(DeviceEpIncompatibilityDetails_Initialize, _Inout_ OrtDeviceEpIncompatibilityDetails* details, + ORT_API2_STATUS(DeviceEpIncompatibilityDetails_SetDetails, _Inout_ OrtDeviceEpIncompatibilityDetails* details, _In_ uint32_t reasons_bitmask, _In_ int32_t error_code, _In_opt_z_ const char* notes); @@ -2038,14 +2039,14 @@ struct OrtEpFactory { * * This function allows an execution provider to check if a specific hardware device is compatible * with the execution provider. The EP can set specific incompatibility reasons via the - * OrtDeviceEpIncompatibilityDetails parameter using OrtEpApi::DeviceEpIncompatibilityDetails_Initialize. + * OrtDeviceEpIncompatibilityDetails parameter using OrtEpApi::DeviceEpIncompatibilityDetails_SetDetails. * * \param[in] this_ptr The OrtEpFactory instance. * \param[in] hw The hardware device to check for incompatibility. - * \param[in,out] details Pre-allocated incompatibility details object created by ORT. - * The EP should initialize this using OrtEpApi::DeviceEpIncompatibilityDetails_Initialize - * to set any incompatibility information. If the device is compatible, the EP can - * leave the object uninitialized (it defaults to no incompatibility). + * \param[in,out] details Pre-allocated incompatibility details object created and initialized by ORT. + * The EP can use OrtEpApi::DeviceEpIncompatibilityDetails_SetDetails to set + * incompatibility information. If the device is compatible, the EP can + * leave the object unchanged (it defaults to no incompatibility). * * \note Implementation of this function is optional. * If not implemented, ORT will assume the device is compatible with this EP. diff --git a/onnxruntime/core/session/environment.cc b/onnxruntime/core/session/environment.cc index 1bbfdefaf5b4e..cd8a799115ce6 100644 --- a/onnxruntime/core/session/environment.cc +++ b/onnxruntime/core/session/environment.cc @@ -668,10 +668,6 @@ Status Environment::GetHardwareDeviceEpIncompatibilityDetails( // ORT creates the details object with default values (compatible) details = std::make_unique(); - details->reasons_bitmask = 0; - details->error_code = 0; - details->notes = ""; - // If the factory implements GetHardwareDeviceIncompatibilityDetails, let it initialize the details if (matched_factory->GetHardwareDeviceIncompatibilityDetails != nullptr) { OrtStatusPtr status = matched_factory->GetHardwareDeviceIncompatibilityDetails(matched_factory, hw, details.get()); diff --git a/onnxruntime/core/session/onnxruntime_c_api.cc b/onnxruntime/core/session/onnxruntime_c_api.cc index 88b6afb385c94..8cca7f2872c44 100644 --- a/onnxruntime/core/session/onnxruntime_c_api.cc +++ b/onnxruntime/core/session/onnxruntime_c_api.cc @@ -4411,9 +4411,14 @@ ORT_API_STATUS_IMPL(OrtApis::GetHardwareDevices, _In_ const OrtEnv* env, const auto& device_vector = env->GetEnvironment().GetSortedOrtHardwareDevices(); size_t available_devices = device_vector.size(); - size_t copy_count = std::min(num_devices, available_devices); - for (size_t i = 0; i < copy_count; ++i) { + if (num_devices < available_devices) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, + "num_devices is less than the number of available hardware devices. " + "Use GetNumHardwareDevices() to get the required array size."); + } + + for (size_t i = 0; i < available_devices; ++i) { devices[i] = device_vector[i]; } diff --git a/onnxruntime/core/session/plugin_ep/ep_api.cc b/onnxruntime/core/session/plugin_ep/ep_api.cc index 8a9f33be7d428..d4765f0531a3a 100644 --- a/onnxruntime/core/session/plugin_ep/ep_api.cc +++ b/onnxruntime/core/session/plugin_ep/ep_api.cc @@ -677,7 +677,7 @@ ORT_API_STATUS_IMPL(CreateIfKernel, _In_ const OrtKernelInfo* kernel_info, _Outp API_IMPL_END } -ORT_API_STATUS_IMPL(DeviceEpIncompatibilityDetails_Initialize, _Inout_ OrtDeviceEpIncompatibilityDetails* details, +ORT_API_STATUS_IMPL(DeviceEpIncompatibilityDetails_SetDetails, _Inout_ OrtDeviceEpIncompatibilityDetails* details, _In_ uint32_t reasons_bitmask, _In_ int32_t error_code, _In_opt_z_ const char* notes) { @@ -844,7 +844,7 @@ static constexpr OrtEpApi ort_ep_api = { &OrtExecutionProviderApi::CreateLoopKernel, &OrtExecutionProviderApi::CreateScanKernel, &OrtExecutionProviderApi::ReleaseKernelImpl, - &OrtExecutionProviderApi::DeviceEpIncompatibilityDetails_Initialize, + &OrtExecutionProviderApi::DeviceEpIncompatibilityDetails_SetDetails, }; // checks that we don't violate the rule that the functions must remain in the slots they were originally assigned diff --git a/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc b/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc index cb15d9e6e6889..32dcef2014bd2 100644 --- a/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc +++ b/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc @@ -346,7 +346,7 @@ OrtStatus* ORT_API_CALL ExampleEpFactory::GetHardwareDeviceIncompatibilityDetail if (device_type != OrtHardwareDeviceType_CPU) { // Report that the device type is not supported uint32_t reasons = OrtDeviceEpIncompatibility_DEVICE_INCOMPATIBLE; - return factory.ep_api.DeviceEpIncompatibilityDetails_Initialize( + return factory.ep_api.DeviceEpIncompatibilityDetails_SetDetails( details, reasons, static_cast(device_type), // Use device type as the error code for testing diff --git a/onnxruntime/test/framework/hardware_device_compatibility_test.cc b/onnxruntime/test/framework/hardware_device_compatibility_test.cc index 03ec7939e67ff..9faeff4d466ad 100644 --- a/onnxruntime/test/framework/hardware_device_compatibility_test.cc +++ b/onnxruntime/test/framework/hardware_device_compatibility_test.cc @@ -313,6 +313,30 @@ TEST(GetHardwareDevicesCapiTest, GetHardwareDevices_InvalidArguments_NullDevices api->ReleaseEnv(env); } +TEST(GetHardwareDevicesCapiTest, GetHardwareDevices_InvalidArguments_ArrayTooSmall) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtEnv* env = nullptr; + EXPECT_EQ(nullptr, api->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "HwDevicesTest", &env)); + EXPECT_NE(env, nullptr); + + // Get number of devices first + size_t num_devices = 0; + ASSERT_ORTSTATUS_OK(api->GetNumHardwareDevices(env, &num_devices)); + ASSERT_GT(num_devices, 0u); + + // Try to get devices with an undersized array (pass a valid pointer but claim size is 0) + std::vector devices(1); // Allocate at least 1 element to avoid nullptr + OrtStatus* st = api->GetHardwareDevices(env, devices.data(), 0); // But claim size is 0 + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + EXPECT_THAT(api->GetErrorMessage(st), testing::HasSubstr("num_devices is less than")); + api->ReleaseStatus(st); + + api->ReleaseEnv(env); +} + TEST(GetHardwareDevicesCapiTest, ReturnsDevices) { const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); ASSERT_NE(api, nullptr); From 3faee38d8a699d2e9d401e24dbf25a3a343f6a22 Mon Sep 17 00:00:00 2001 From: Xiaoxi Han Date: Mon, 12 Jan 2026 20:58:52 -0800 Subject: [PATCH 09/11] resolve clang issue --- .../test/framework/hardware_device_compatibility_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/onnxruntime/test/framework/hardware_device_compatibility_test.cc b/onnxruntime/test/framework/hardware_device_compatibility_test.cc index 9faeff4d466ad..d5f0188cb683f 100644 --- a/onnxruntime/test/framework/hardware_device_compatibility_test.cc +++ b/onnxruntime/test/framework/hardware_device_compatibility_test.cc @@ -327,7 +327,7 @@ TEST(GetHardwareDevicesCapiTest, GetHardwareDevices_InvalidArguments_ArrayTooSma ASSERT_GT(num_devices, 0u); // Try to get devices with an undersized array (pass a valid pointer but claim size is 0) - std::vector devices(1); // Allocate at least 1 element to avoid nullptr + std::vector devices(1); // Allocate at least 1 element to avoid nullptr OrtStatus* st = api->GetHardwareDevices(env, devices.data(), 0); // But claim size is 0 ASSERT_NE(st, nullptr); EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); From de1312072a7b6a157247db1c66d3c54a36a68adc Mon Sep 17 00:00:00 2001 From: Xiaoxi Han Date: Mon, 12 Jan 2026 20:53:52 -0800 Subject: [PATCH 10/11] resolve feedbacks --- .../core/session/onnxruntime_ep_c_api.h | 88 ++++++++++--------- onnxruntime/core/session/plugin_ep/ep_api.cc | 44 +++++----- .../library/example_plugin_ep/ep_factory.cc | 42 ++++----- 3 files changed, 88 insertions(+), 86 deletions(-) diff --git a/include/onnxruntime/core/session/onnxruntime_ep_c_api.h b/include/onnxruntime/core/session/onnxruntime_ep_c_api.h index e084b00d80088..6bb454cd47623 100644 --- a/include/onnxruntime/core/session/onnxruntime_ep_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_ep_c_api.h @@ -1308,6 +1308,27 @@ struct OrtEpApi { */ ORT_API2_STATUS(KernelInfo_GetEp, _In_ const OrtKernelInfo* info, _Outptr_ const OrtEp** ep); + /** \brief Set the details of an OrtDeviceEpIncompatibilityDetails instance. + * + * Used by execution provider factories to set incompatibility details in their + * GetHardwareDeviceIncompatibilityDetails implementation. ORT creates and initializes the object + * before passing it to the EP, so calling this function is optional. The EP uses this function + * to set incompatibility information when the device is not compatible. + * + * \param[in,out] details The OrtDeviceEpIncompatibilityDetails instance to update. + * \param[in] reasons_bitmask Bitmask of OrtDeviceEpIncompatibilityReason values. (0 = no incompatibility). + * \param[in] error_code Optional EP-specific error code (0 = no error). + * \param[in] notes Optional human-readable notes. Can be null. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24. + */ + ORT_API2_STATUS(DeviceEpIncompatibilityDetails_SetDetails, _Inout_ OrtDeviceEpIncompatibilityDetails* details, + _In_ uint32_t reasons_bitmask, + _In_ int32_t error_code, + _In_opt_z_ const char* notes); + /** \brief Creates an OrtKernelImpl instance for an If operator. * * Control flow operators require access to ORT session internals to orchestrate subgraph operations. @@ -1408,26 +1429,6 @@ struct OrtEpApi { _Outptr_ OrtKernelImpl** kernel_out); ORT_CLASS_RELEASE(KernelImpl); - /** \brief Set the details of an OrtDeviceEpIncompatibilityDetails instance. - * - * Used by execution provider factories to set incompatibility details in their - * GetHardwareDeviceIncompatibilityDetails implementation. ORT creates and initializes the object - * before passing it to the EP, so calling this function is optional. The EP uses this function - * to set incompatibility information when the device is not compatible. - * - * \param[in,out] details The OrtDeviceEpIncompatibilityDetails instance to update. - * \param[in] reasons_bitmask Bitmask of OrtDeviceEpIncompatibilityReason values. (0 = no incompatibility). - * \param[in] error_code Optional EP-specific error code (0 = no error). - * \param[in] notes Optional human-readable notes. Can be null. - * - * \snippet{doc} snippets.dox OrtStatus Return Value - * - * \since Version 1.24. - */ - ORT_API2_STATUS(DeviceEpIncompatibilityDetails_SetDetails, _Inout_ OrtDeviceEpIncompatibilityDetails* details, - _In_ uint32_t reasons_bitmask, - _In_ int32_t error_code, - _In_opt_z_ const char* notes); }; /** @@ -2010,6 +2011,30 @@ struct OrtEpFactory { */ ORT_API2_STATUS(SetEnvironmentOptions, _In_ OrtEpFactory* this_ptr, _In_ const OrtKeyValuePairs* options); + /** \brief Check for known incompatibility reasons between a hardware device and this execution provider. + * + * This function allows an execution provider to check if a specific hardware device is compatible + * with the execution provider. The EP can set specific incompatibility reasons via the + * OrtDeviceEpIncompatibilityDetails parameter using OrtEpApi::DeviceEpIncompatibilityDetails_SetDetails. + * + * \param[in] this_ptr The OrtEpFactory instance. + * \param[in] hw The hardware device to check for incompatibility. + * \param[in,out] details Pre-allocated incompatibility details object created and initialized by ORT. + * The EP can use OrtEpApi::DeviceEpIncompatibilityDetails_SetDetails to set + * incompatibility information. If the device is compatible, the EP can + * leave the object unchanged (it defaults to no incompatibility). + * + * \note Implementation of this function is optional. + * If not implemented, ORT will assume the device is compatible with this EP. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24. + */ + ORT_API2_STATUS(GetHardwareDeviceIncompatibilityDetails, _In_ OrtEpFactory* this_ptr, + _In_ const OrtHardwareDevice* hw, + _Inout_ OrtDeviceEpIncompatibilityDetails* details); + /** \brief Create an OrtExternalResourceImporterImpl for external resource import. * * This is used to create an external resource importer that enables zero-copy import of @@ -2035,29 +2060,6 @@ struct OrtEpFactory { ORT_API2_STATUS(CreateExternalResourceImporterForDevice, _In_ OrtEpFactory* this_ptr, _In_ const OrtEpDevice* ep_device, _Outptr_result_maybenull_ OrtExternalResourceImporterImpl** out_importer); - /** \brief Check for known incompatibility reasons between a hardware device and this execution provider. - * - * This function allows an execution provider to check if a specific hardware device is compatible - * with the execution provider. The EP can set specific incompatibility reasons via the - * OrtDeviceEpIncompatibilityDetails parameter using OrtEpApi::DeviceEpIncompatibilityDetails_SetDetails. - * - * \param[in] this_ptr The OrtEpFactory instance. - * \param[in] hw The hardware device to check for incompatibility. - * \param[in,out] details Pre-allocated incompatibility details object created and initialized by ORT. - * The EP can use OrtEpApi::DeviceEpIncompatibilityDetails_SetDetails to set - * incompatibility information. If the device is compatible, the EP can - * leave the object unchanged (it defaults to no incompatibility). - * - * \note Implementation of this function is optional. - * If not implemented, ORT will assume the device is compatible with this EP. - * - * \snippet{doc} snippets.dox OrtStatus Return Value - * - * \since Version 1.24. - */ - ORT_API2_STATUS(GetHardwareDeviceIncompatibilityDetails, _In_ OrtEpFactory* this_ptr, - _In_ const OrtHardwareDevice* hw, - _Inout_ OrtDeviceEpIncompatibilityDetails* details); }; #ifdef __cplusplus diff --git a/onnxruntime/core/session/plugin_ep/ep_api.cc b/onnxruntime/core/session/plugin_ep/ep_api.cc index d4765f0531a3a..21e6ae1525838 100644 --- a/onnxruntime/core/session/plugin_ep/ep_api.cc +++ b/onnxruntime/core/session/plugin_ep/ep_api.cc @@ -656,27 +656,6 @@ ORT_API_STATUS_IMPL(KernelInfo_GetEp, _In_ const OrtKernelInfo* info, _Outptr_ c API_IMPL_END } -// Control flow kernel APIs -ORT_API_STATUS_IMPL(CreateIfKernel, _In_ const OrtKernelInfo* kernel_info, _Outptr_ OrtKernelImpl** kernel_out) { - API_IMPL_BEGIN - if (kernel_info == nullptr) { - return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, - "Must specify a non-null OrtKernelInfo instance to create an If OrtKernelImpl"); - } - - if (kernel_out == nullptr) { - return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, - "Must specify a non-null output parameter to hold the OrtKernelImpl for If"); - } - - const auto* op_kernel_info = reinterpret_cast(kernel_info); - auto kernel_unique_ptr = std::make_unique(*op_kernel_info); - - *kernel_out = kernel_unique_ptr.release(); - return nullptr; - API_IMPL_END -} - ORT_API_STATUS_IMPL(DeviceEpIncompatibilityDetails_SetDetails, _Inout_ OrtDeviceEpIncompatibilityDetails* details, _In_ uint32_t reasons_bitmask, _In_ int32_t error_code, @@ -698,6 +677,27 @@ ORT_API_STATUS_IMPL(DeviceEpIncompatibilityDetails_SetDetails, _Inout_ OrtDevice API_IMPL_END } +// Control flow kernel APIs +ORT_API_STATUS_IMPL(CreateIfKernel, _In_ const OrtKernelInfo* kernel_info, _Outptr_ OrtKernelImpl** kernel_out) { + API_IMPL_BEGIN + if (kernel_info == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, + "Must specify a non-null OrtKernelInfo instance to create an If OrtKernelImpl"); + } + + if (kernel_out == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, + "Must specify a non-null output parameter to hold the OrtKernelImpl for If"); + } + + const auto* op_kernel_info = reinterpret_cast(kernel_info); + auto kernel_unique_ptr = std::make_unique(*op_kernel_info); + + *kernel_out = kernel_unique_ptr.release(); + return nullptr; + API_IMPL_END +} + ORT_API_STATUS_IMPL(CreateLoopKernel, _In_ const OrtKernelInfo* kernel_info, _In_ OrtLoopKernelHelper* helper, _Outptr_ OrtKernelImpl** kernel_out) { API_IMPL_BEGIN @@ -840,11 +840,11 @@ static constexpr OrtEpApi ort_ep_api = { &OrtExecutionProviderApi::EpGraphSupportInfo_LookUpKernel, &OrtExecutionProviderApi::SharedPrePackedWeightCache_StoreWeightData, &OrtExecutionProviderApi::KernelInfo_GetEp, + &OrtExecutionProviderApi::DeviceEpIncompatibilityDetails_SetDetails, &OrtExecutionProviderApi::CreateIfKernel, &OrtExecutionProviderApi::CreateLoopKernel, &OrtExecutionProviderApi::CreateScanKernel, &OrtExecutionProviderApi::ReleaseKernelImpl, - &OrtExecutionProviderApi::DeviceEpIncompatibilityDetails_SetDetails, }; // checks that we don't violate the rule that the functions must remain in the slots they were originally assigned diff --git a/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc b/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc index 32dcef2014bd2..79ec3fe3a3780 100644 --- a/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc +++ b/onnxruntime/test/autoep/library/example_plugin_ep/ep_factory.cc @@ -313,27 +313,6 @@ OrtStatus* ORT_API_CALL ExampleEpFactory::CreateSyncStreamForDeviceImpl(OrtEpFac } /*static*/ -OrtStatus* ORT_API_CALL ExampleEpFactory::CreateExternalResourceImporterForDeviceImpl( - OrtEpFactory* this_ptr, - const OrtEpDevice* /*ep_device*/, - OrtExternalResourceImporterImpl** out_importer) noexcept { - auto& factory = *static_cast(this_ptr); - - if (out_importer == nullptr) { - return factory.ort_api.CreateStatus(ORT_INVALID_ARGUMENT, - "out_importer cannot be nullptr"); - } - - // Create the external resource importer - // NOTE: For production multi-GPU EPs, you should capture ep_device in the importer - // to enable proper device validation and support multiple physical devices. - // This example EP only supports a single device, so we don't store it. - auto importer = std::make_unique(factory); - *out_importer = importer.release(); - - return nullptr; -} - OrtStatus* ORT_API_CALL ExampleEpFactory::GetHardwareDeviceIncompatibilityDetailsImpl( OrtEpFactory* this_ptr, const OrtHardwareDevice* hw, @@ -356,3 +335,24 @@ OrtStatus* ORT_API_CALL ExampleEpFactory::GetHardwareDeviceIncompatibilityDetail // Device is compatible - details are already initialized with default values by ORT return nullptr; } + +OrtStatus* ORT_API_CALL ExampleEpFactory::CreateExternalResourceImporterForDeviceImpl( + OrtEpFactory* this_ptr, + const OrtEpDevice* /*ep_device*/, + OrtExternalResourceImporterImpl** out_importer) noexcept { + auto& factory = *static_cast(this_ptr); + + if (out_importer == nullptr) { + return factory.ort_api.CreateStatus(ORT_INVALID_ARGUMENT, + "out_importer cannot be nullptr"); + } + + // Create the external resource importer + // NOTE: For production multi-GPU EPs, you should capture ep_device in the importer + // to enable proper device validation and support multiple physical devices. + // This example EP only supports a single device, so we don't store it. + auto importer = std::make_unique(factory); + *out_importer = importer.release(); + + return nullptr; +} From 62fdfe301409f09b0b52188191d9cd400aa64f17 Mon Sep 17 00:00:00 2001 From: Xiaoxi Han Date: Tue, 13 Jan 2026 21:38:21 -0800 Subject: [PATCH 11/11] rebase to main --- .../core/session/onnxruntime_c_api.h | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/include/onnxruntime/core/session/onnxruntime_c_api.h b/include/onnxruntime/core/session/onnxruntime_c_api.h index 319ef5ebf8d8f..5acac571f3f3b 100644 --- a/include/onnxruntime/core/session/onnxruntime_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_c_api.h @@ -6912,43 +6912,6 @@ struct OrtApi { ORT_CLASS_RELEASE(DeviceEpIncompatibilityDetails); /// @} - - /** \brief Get the EP Interop API instance. - * - * Get the Interop API instance to work with external resources. This API provides functions - * for importing external GPU memory and semaphores for zero-copy sharing between ORT inference - * and other GPU workloads. - * - * \return Interop API struct instance. - * - * \since Version 1.24. - */ - const OrtInteropApi*(ORT_API_CALL* GetInteropApi)(void); - - /** \brief Get the EP device assigned to each session output. - * - * Returns the OrtEpDevice assigned to each output of the session after graph partitioning. - * This allows validation that outputs are placed on the expected device for external resource sharing. - * - * The EP device for each output is determined by which execution provider will produce that output - * during inferencing. This information is useful for: - * - Validating that outputs will be placed on the expected device for external resource sharing - * - Deciding whether to use external memory handles for outputs - * - * \param[in] session The OrtSession instance to query. - * \param[out] outputs_ep_devices An array to be filled with the EP device for each output. - * The array must be allocated by the caller with space for - * OrtEpDevice* values for each output. - * The order is the same as returned by SessionGetOutputName. - * \param[in] num_outputs The number of outputs in the session. Must match SessionGetOutputCount. - * - * \snippet{doc} snippets.dox OrtStatus Return Value - * - * \since Version 1.24 - */ - ORT_API2_STATUS(SessionGetEpDeviceForOutputs, _In_ const OrtSession* session, - _Out_writes_(num_outputs) const OrtEpDevice** outputs_ep_devices, - _In_ size_t num_outputs); }; /*