77#include " core/providers/qnn/qnn_provider_factory_creator.h"
88#include " core/providers/qnn/qnn_execution_provider.h"
99#include " core/providers/qnn/builder/qnn_utils.h"
10+ #include " core/session/abi_devices.h"
11+
12+ static const std::unordered_map<OrtHardwareDeviceType, std::string> kDefaultBackends = {
13+ #if defined(_WIN32)
14+ {OrtHardwareDeviceType_NPU, " QnnHtp.dll" },
15+ {OrtHardwareDeviceType_GPU, " QnnGpu.dll" },
16+ #else
17+ {OrtHardwareDeviceType_NPU, " libQnnHtp.so" },
18+ {OrtHardwareDeviceType_GPU, " libQnnGpu.so" },
19+ #endif
20+ };
1021
1122namespace onnxruntime {
1223struct QNNProviderFactory : IExecutionProviderFactory {
@@ -61,6 +72,35 @@ std::shared_ptr<IExecutionProviderFactory> QNNProviderFactoryCreator::Create(con
6172 return std::make_shared<onnxruntime::QNNProviderFactory>(provider_options_map, config_options);
6273}
6374#else
75+ // / @brief Gets the path of directory containing the dynamic library that contains the address.
76+ // / @param address An address of a function or variable in the dynamic library.
77+ // / @return The path of the directory containing the dynamic library, or an empty string if the path cannot be determined.
78+ static onnxruntime::PathString GetDynamicLibraryLocationByAddress (const void * address) {
79+ #ifdef _WIN32
80+ HMODULE moduleHandle;
81+ if (!::GetModuleHandleExW (GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT ,
82+ reinterpret_cast <LPCWSTR >(address), &moduleHandle)) {
83+ return {};
84+ }
85+ std::wstring buffer;
86+ for (std::uint32_t size{70 }; size < 4096 ; size *= 2 ) {
87+ buffer.resize (size, L' \0 ' );
88+ const std::uint32_t requiredSize = ::GetModuleFileNameW (moduleHandle, buffer.data (), size);
89+ if (requiredSize == 0 ) {
90+ break ;
91+ }
92+ if (requiredSize == size) {
93+ continue ;
94+ }
95+ buffer.resize (requiredSize);
96+ return {std::move (buffer)};
97+ }
98+ #else
99+ std::ignore = address;
100+ #endif
101+ return {};
102+ }
103+
64104struct QNN_Provider : Provider {
65105 std::shared_ptr<IExecutionProviderFactory> CreateExecutionProviderFactory (const void * param) override {
66106 if (param == nullptr ) {
@@ -80,13 +120,61 @@ struct QNN_Provider : Provider {
80120 return std::make_shared<onnxruntime::QNNProviderFactory>(*provider_options, config_options);
81121 }
82122
83- Status CreateIExecutionProvider (const OrtHardwareDevice* const * /* devices*/ ,
123+ Status CreateIExecutionProvider (const OrtHardwareDevice* const * devices,
84124 const OrtKeyValuePairs* const * /* ep_metadata*/ ,
85- size_t /* num_devices*/ ,
125+ size_t num_devices,
86126 ProviderOptions& provider_options,
87127 const OrtSessionOptions& session_options,
88128 const OrtLogger& logger,
89129 std::unique_ptr<IExecutionProvider>& ep) override {
130+ if (provider_options.find (" backend_path" ) == provider_options.end () &&
131+ provider_options.find (" backend_type" ) == provider_options.end ()) {
132+ // If neither "backend_path" nor "backend_type" has been given in the provider options, then determine the backend based
133+ // on the provided devices. As QNN EP does not support partitioning across backends, if multiple devices are provided,
134+ // default to HTP (if present) or else to the GPU.
135+ const OrtHardwareDevice* device_to_use = nullptr ;
136+ if (num_devices == 0 ) {
137+ return Status (common::ONNXRUNTIME , ORT_EP_FAIL , " No devices were provided to QNN EP." );
138+ } else if (num_devices == 1 ) {
139+ device_to_use = devices[0 ];
140+ } else {
141+ const auto is_npu = [](const OrtHardwareDevice* device) { return device->type == OrtHardwareDeviceType_NPU; };
142+ const auto is_gpu = [](const OrtHardwareDevice* device) { return device->type == OrtHardwareDeviceType_GPU; };
143+
144+ auto device_it = std::find_if (devices, devices + num_devices, is_npu);
145+ if (device_it != devices + num_devices) {
146+ LOGS_DEFAULT (WARNING ) << " QNN EP only supports one device. Only the NPU device will be used." ;
147+ device_to_use = *device_it;
148+ } else {
149+ device_it = std::find_if (devices, devices + num_devices, is_gpu);
150+ if (device_it != devices + num_devices) {
151+ LOGS_DEFAULT (WARNING )
152+ << " QNN EP only supports one device. An NPU device was not provided, so only the GPU device will be used." ;
153+ device_to_use = *device_it;
154+ } else {
155+ return Status (common::ONNXRUNTIME , ORT_EP_FAIL ,
156+ " Multiple devices were provided to QNN EP, but neither an NPU nor a GPU was included." );
157+ }
158+ }
159+ }
160+ ORT_RETURN_IF (device_to_use == nullptr , " Failed to select device for QNN EP!" );
161+
162+ auto default_backends_it = kDefaultBackends .find (device_to_use->type );
163+ ORT_RETURN_IF (default_backends_it == kDefaultBackends .end (),
164+ " Could not determine default backend path for device of type: " , device_to_use->type );
165+
166+ // Identify the path of the current dynamic library, and expect that the backend library is in the same directory.
167+ onnxruntime::PathString current_path = GetDynamicLibraryLocationByAddress (
168+ reinterpret_cast <const void *>(&kDefaultBackends ));
169+
170+ std::filesystem::path parent_path;
171+ if (!current_path.empty ()) {
172+ parent_path = std::filesystem::path{std::move (current_path)}.parent_path ();
173+ }
174+
175+ provider_options[" backend_path" ] = (parent_path / default_backends_it->second ).string ();
176+ }
177+
90178 const ConfigOptions* config_options = &session_options.GetConfigOptions ();
91179
92180 std::array<const void *, 2 > configs_array = {&provider_options, config_options};
@@ -115,45 +203,14 @@ ORT_API(onnxruntime::Provider*, GetProvider) {
115203#include " core/framework/error_code_helper.h"
116204#include " onnxruntime_config.h" // for ORT_VERSION
117205
118- // / @brief Gets the path of directory containing the dynamic library that contains the address.
119- // / @param address An address of a function or variable in the dynamic library.
120- // / @return The path of the directory containing the dynamic library, or an empty string if the path cannot be determined.
121- static onnxruntime::PathString GetDynamicLibraryLocationByAddress (const void * address) {
122- #ifdef _WIN32
123- HMODULE moduleHandle;
124- if (!::GetModuleHandleExW (GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT ,
125- reinterpret_cast <LPCWSTR >(address), &moduleHandle)) {
126- return {};
127- }
128- std::wstring buffer;
129- for (std::uint32_t size{70 }; size < 4096 ; size *= 2 ) {
130- buffer.resize (size, L' \0 ' );
131- const std::uint32_t requiredSize = ::GetModuleFileNameW (moduleHandle, buffer.data (), size);
132- if (requiredSize == 0 ) {
133- break ;
134- }
135- if (requiredSize == size) {
136- continue ;
137- }
138- buffer.resize (requiredSize);
139- return {std::move (buffer)};
140- }
141- #else
142- std::ignore = address;
143- #endif
144- return {};
145- }
146-
147206// OrtEpApi infrastructure to be able to use the QNN EP as an OrtEpFactory for auto EP selection.
148207struct QnnEpFactory : OrtEpFactory {
149208 QnnEpFactory (const OrtApi& ort_api_in,
150209 const OrtLogger& default_logger_in,
151- const char * ep_name,
152- std::unordered_map<OrtHardwareDeviceType, std::string> supported_backends)
210+ const char * ep_name)
153211 : ort_api{ort_api_in},
154212 default_logger{default_logger_in},
155- ep_name{ep_name},
156- supported_backends{std::move (supported_backends)} {
213+ ep_name{ep_name} {
157214 ort_version_supported = ORT_API_VERSION ;
158215 GetName = GetNameImpl;
159216 GetVendor = GetVendorImpl;
@@ -195,27 +252,24 @@ struct QnnEpFactory : OrtEpFactory {
195252 // An EP created with this factory is expected to be able to execute a model with *all* supported
196253 // hardware devices at once. A single instance of QNN EP is not currently setup to partition a model among
197254 // multiple different QNN backends at once (e.g, npu, cpu, gpu), so currently this factory instance is set
198- // to pick the last specified backend only .
255+ // to default to npu .
199256 static OrtStatus* GetSupportedDevicesImpl (OrtEpFactory* this_ptr,
200257 const OrtHardwareDevice* const * devices,
201258 size_t num_devices,
202259 OrtEpDevice** ep_devices,
203260 size_t max_ep_devices,
204261 size_t * p_num_ep_devices) noexcept {
205262 size_t & num_ep_devices = *p_num_ep_devices;
263+ num_ep_devices = 0 ;
264+
206265 auto * factory = static_cast <QnnEpFactory*>(this_ptr);
207266
208267 for (size_t i = 0 ; i < num_devices && num_ep_devices < max_ep_devices; ++i) {
209268 const OrtHardwareDevice& device = *devices[i];
210- auto supported_backend_it = factory->supported_backends .find (factory->ort_api .HardwareDevice_Type (&device));
211- if (supported_backend_it != factory->supported_backends .end () &&
269+ if (kDefaultBackends .find (factory->ort_api .HardwareDevice_Type (&device)) != kDefaultBackends .end () &&
212270 factory->ort_api .HardwareDevice_VendorId (&device) == factory->vendor_id ) {
213- OrtKeyValuePairs* ep_options = nullptr ;
214- factory->ort_api .CreateKeyValuePairs (&ep_options);
215- factory->ort_api .AddKeyValuePair (ep_options, " backend_path" , supported_backend_it->second .c_str ());
216- OrtStatus* status = factory->ort_api .GetEpApi ()->CreateEpDevice (factory, &device, nullptr , ep_options,
271+ OrtStatus* status = factory->ort_api .GetEpApi ()->CreateEpDevice (factory, &device, nullptr , nullptr ,
217272 &ep_devices[num_ep_devices++]);
218- factory->ort_api .ReleaseKeyValuePairs (ep_options);
219273 ORT_API_RETURN_IF_ERROR (status);
220274 }
221275 }
@@ -282,8 +336,6 @@ struct QnnEpFactory : OrtEpFactory {
282336
283337 // Qualcomm vendor ID. Refer to the ACPI ID registry (search Qualcomm): https://uefi.org/ACPI_ID_List
284338 const uint32_t vendor_id{' Q' | (' C' << 8 ) | (' O' << 16 ) | (' M' << 24 )};
285- const std::unordered_map<OrtHardwareDeviceType, std::string> supported_backends; // Supported OrtHardwareDeviceTypes
286- // and their QNN backend paths
287339};
288340
289341extern " C" {
@@ -295,35 +347,13 @@ OrtStatus* CreateEpFactories(const char* /*registration_name*/, const OrtApiBase
295347 OrtEpFactory** factories, size_t max_factories, size_t * num_factories) {
296348 const OrtApi* ort_api = ort_api_base->GetApi (ORT_API_VERSION );
297349
298- std::unordered_map<OrtHardwareDeviceType, std::string> supported_backends = {
299- #if defined(_WIN32)
300- {OrtHardwareDeviceType_NPU, " QnnHtp.dll" },
301- {OrtHardwareDeviceType_GPU, " QnnGpu.dll" },
302- #else
303- {OrtHardwareDeviceType_NPU, " libQnnHtp.so" },
304- {OrtHardwareDeviceType_GPU, " libQnnGpu.so" },
305- #endif
306- };
307-
308- for (auto & [_, backend_path] : supported_backends) {
309- // Identify the path of the current dynamic library, and expect that backend_path is in the same directory.
310- onnxruntime::PathString current_path = GetDynamicLibraryLocationByAddress (
311- reinterpret_cast <const void *>(&CreateEpFactories));
312-
313- if (!current_path.empty ()) {
314- const std::filesystem::path parent_path = std::filesystem::path{std::move (current_path)}.parent_path ();
315- backend_path = (parent_path / backend_path).string ();
316- }
317- }
318-
319350 if (max_factories < 1 ) {
320351 return ort_api->CreateStatus (ORT_INVALID_ARGUMENT ,
321352 " Not enough space to return EP factory. Need at least one." );
322353 }
323354
324355 auto factory = std::make_unique<QnnEpFactory>(*ort_api, *default_logger,
325- onnxruntime::kQnnExecutionProvider ,
326- supported_backends);
356+ onnxruntime::kQnnExecutionProvider );
327357 factories[0 ] = factory.release ();
328358 *num_factories = 1 ;
329359
0 commit comments