Skip to content

Commit 138c6e0

Browse files
[OVEP] feat: Integrate new ABI with Legacy OVEP Plugin (#747)
* update: Implement OV Plugin using factories * fix: refactor plugin code * fix: map ep_metadata to device type using "ov_device" key * fix: block provider options for AppendExecutionProvider_V2 pass * minor fix for linux * Add OrtEpLibraryOv tests * ovep: Support multiple devices (i.e. AUTO) passed to CreateIExecutionProvider * CreateIExecutionProvider: comment out unused devices parameter * ovep factory: Implement CreateDataTransfer to avoid crash in RegisterExecutionProviderLibrary * update: Enable shared libs linker flags for linux & macos * CreateIExecutionProvider: For some disallowed provider options, give better guidance * Add PluginEp_CheckV2DisallowedProviderOptions test * ovep: Add CreateProvider_V2 & call it from CreateIExecutionProvider * disable data transfer for ovep * minor fix for linux * openvino_provider_factory: Add 'num_of_threads' to block_and_advise_entries --------- Co-authored-by: Ryan Metcalfe <[email protected]>
1 parent 217f285 commit 138c6e0

File tree

8 files changed

+770
-2
lines changed

8 files changed

+770
-2
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
_GetProvider
2+
_CreateEpFactories
3+
_ReleaseEpFactory

onnxruntime/core/providers/openvino/openvino_provider_factory.cc

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,18 @@ struct OpenVINOProviderFactory : IExecutionProviderFactory {
403403
return ov_ep;
404404
}
405405

406+
// This is called during session creation when AppendExecutionProvider_V2 is used.
407+
// This one is called because ParseProviderInfo / ParseConfigOptions, etc. are already
408+
// performed in CreateIExecutionProvider, and so provider_info_ has already been populated.
409+
std::unique_ptr<IExecutionProvider> CreateProvider_V2(const OrtSessionOptions& /*session_options*/,
410+
const OrtLogger& session_logger) {
411+
ProviderInfo provider_info = provider_info_;
412+
auto ov_ep = std::make_unique<OpenVINOExecutionProvider>(provider_info, shared_context_);
413+
ov_ep->SetLogger(reinterpret_cast<const logging::Logger*>(&session_logger));
414+
return ov_ep;
415+
}
416+
417+
406418
private:
407419
ProviderInfo provider_info_;
408420
std::shared_ptr<SharedContext> shared_context_;
@@ -433,6 +445,116 @@ struct OpenVINO_Provider : Provider {
433445
return std::make_shared<OpenVINOProviderFactory>(pi, SharedContext::Get());
434446
}
435447

448+
Status CreateIExecutionProvider(const OrtHardwareDevice* const* /*devices*/,
449+
const OrtKeyValuePairs* const* ep_metadata,
450+
size_t num_devices,
451+
ProviderOptions& provider_options,
452+
const OrtSessionOptions& session_options,
453+
const OrtLogger& logger,
454+
std::unique_ptr<IExecutionProvider>& ep) override {
455+
// Check if no devices are provided
456+
if (num_devices == 0) {
457+
return Status(common::ONNXRUNTIME, ORT_EP_FAIL, "No devices provided to CreateIExecutionProvider");
458+
}
459+
460+
// For provider options that we don't support directly but are still supported through load_config,
461+
// give some specific guidance & example about how to make use of the option through load_config.
462+
const std::vector<std::pair<std::string, std::string>> block_and_advise_entries = {
463+
{"cache_dir", "\"CACHE_DIR\": \"<filesystem_path>\""},
464+
{"precision", "\"INFERENCE_PRECISION_HINT\": \"F32\""},
465+
{"num_of_threads", "\"INFERENCE_NUM_THREADS\": \"1\""},
466+
{"num_streams", "\"NUM_STREAMS\": \"1\""},
467+
{"model_priority", "\"MODEL_PRIORITY\": \"LOW\""},
468+
{"enable_opencl_throttling", "\"GPU\": {\"PLUGIN_THROTTLE\": \"1\"}"},
469+
{"enable_qdq_optimizer", "\"NPU\": {\"NPU_QDQ_OPTIMIZATION\": \"YES\"}"}
470+
};
471+
472+
for (auto& block_and_advise_entry : block_and_advise_entries) {
473+
if (provider_options.find(block_and_advise_entry.first) != provider_options.end()) {
474+
std::string message = "OpenVINO EP: Option '" + block_and_advise_entry.first +
475+
"' cannot be set when using AppendExecutionProvider_V2. " +
476+
"It can instead be enabled by a load_config key / value pair. For example: " +
477+
block_and_advise_entry.second;
478+
return Status(common::ONNXRUNTIME, ORT_INVALID_ARGUMENT, message);
479+
}
480+
}
481+
482+
// For the rest of the disallowed provider options, give a generic error message.
483+
const std::vector<std::string> blocked_provider_keys = {
484+
"device_type", "device_id", "device_luid", "context", "disable_dynamic_shapes"};
485+
486+
for (const auto& key : blocked_provider_keys) {
487+
if (provider_options.find(key) != provider_options.end()) {
488+
return Status(common::ONNXRUNTIME, ORT_INVALID_ARGUMENT,
489+
"OpenVINO EP: Option '" + key + "' cannot be set when using AppendExecutionProvider_V2.");
490+
}
491+
}
492+
493+
const char* ov_device_key = "ov_device";
494+
const char* ov_meta_device_key = "ov_meta_device";
495+
496+
// Create a unique list of ov_devices that were passed in.
497+
std::unordered_set<std::string_view> unique_ov_devices;
498+
std::vector<std::string_view> ordered_unique_ov_devices;
499+
for (size_t i = 0; i < num_devices; ++i) {
500+
const auto& device_meta_data = ep_metadata[i];
501+
auto ov_device_it = device_meta_data->Entries().find(ov_device_key);
502+
if (ov_device_it == device_meta_data->Entries().end()) {
503+
return Status(common::ONNXRUNTIME, ORT_INVALID_ARGUMENT, "OpenVINO EP device metadata not found.");
504+
}
505+
auto &ov_device = ov_device_it->second;
506+
507+
// Add to ordered_unique only if not already present
508+
if (unique_ov_devices.insert(ov_device).second) {
509+
ordered_unique_ov_devices.push_back(ov_device);
510+
}
511+
}
512+
513+
std::string ov_meta_device_type = "NONE";
514+
{
515+
auto ov_meta_device_it = ep_metadata[0]->Entries().find(ov_meta_device_key);
516+
if (ov_meta_device_it != ep_metadata[0]->Entries().end()) {
517+
ov_meta_device_type = ov_meta_device_it->second;
518+
}
519+
}
520+
521+
bool is_meta_device_factory = (ov_meta_device_type != "NONE");
522+
523+
if (ordered_unique_ov_devices.size() > 1 && !is_meta_device_factory) {
524+
LOGS_DEFAULT(WARNING) << "[OpenVINO EP] Multiple devices were specified that are not OpenVINO meta devices. Using first ov_device only: " << ordered_unique_ov_devices.at(0);
525+
ordered_unique_ov_devices.resize(1); // Use only the first device if not a meta device factory
526+
}
527+
528+
std::string ov_device_string;
529+
if (is_meta_device_factory) {
530+
// Build up a meta device string based on the devices that are passed in. E.g. AUTO:NPU,GPU.0,CPU
531+
ov_device_string = ov_meta_device_type;
532+
ov_device_string += ":";
533+
}
534+
535+
bool prepend_comma = false;
536+
for (const auto& ov_device : ordered_unique_ov_devices) {
537+
if (prepend_comma) {
538+
ov_device_string += ",";
539+
}
540+
ov_device_string += ov_device;
541+
prepend_comma = true;
542+
}
543+
544+
provider_options["device_type"] = ov_device_string;
545+
546+
// Parse provider info with the device type
547+
ProviderInfo pi;
548+
const auto& config_options = session_options.GetConfigOptions();
549+
ParseProviderInfo(provider_options, &config_options, pi);
550+
ParseConfigOptions(pi);
551+
552+
// Create and return the execution provider
553+
auto factory = std::make_unique<OpenVINOProviderFactory>(pi, SharedContext::Get());
554+
ep = factory->CreateProvider_V2(session_options, logger);
555+
return Status::OK();
556+
}
557+
436558
void Initialize() override {
437559
}
438560

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// Copyright (C) Intel Corporation
2+
// Licensed under the MIT License
3+
4+
#include <memory>
5+
#include <map>
6+
#include <string>
7+
#include <algorithm>
8+
#include <vector>
9+
#include <ranges>
10+
#include <format>
11+
12+
#define ORT_API_MANUAL_INIT
13+
#include "onnxruntime_cxx_api.h"
14+
#undef ORT_API_MANUAL_INIT
15+
16+
#include "onnxruntime_c_api.h"
17+
#include "ov_factory.h"
18+
#include "openvino/openvino.hpp"
19+
#include "ov_interface.h"
20+
21+
using namespace onnxruntime::openvino_ep;
22+
using ov_core_singleton = onnxruntime::openvino_ep::WeakSingleton<ov::Core>;
23+
24+
static void InitCxxApi(const OrtApiBase& ort_api_base) {
25+
static std::once_flag init_api;
26+
std::call_once(init_api, [&]() {
27+
const OrtApi* ort_api = ort_api_base.GetApi(ORT_API_VERSION);
28+
Ort::InitApi(ort_api);
29+
});
30+
}
31+
32+
OpenVINOEpPluginFactory::OpenVINOEpPluginFactory(ApiPtrs apis, const std::string& ov_metadevice_name, std::shared_ptr<ov::Core> core)
33+
: ApiPtrs{apis},
34+
ep_name_(ov_metadevice_name.empty() ? provider_name_ : std::string(provider_name_) + "." + ov_metadevice_name),
35+
device_type_(ov_metadevice_name),
36+
ov_core_(std::move(core)) {
37+
OrtEpFactory::GetName = GetNameImpl;
38+
OrtEpFactory::GetVendor = GetVendorImpl;
39+
OrtEpFactory::GetVendorId = GetVendorIdImpl;
40+
OrtEpFactory::GetSupportedDevices = GetSupportedDevicesImpl;
41+
OrtEpFactory::GetVersion = GetVersionImpl;
42+
OrtEpFactory::CreateDataTransfer = CreateDataTransferImpl;
43+
44+
ort_version_supported = ORT_API_VERSION; // Set to the ORT version we were compiled with.
45+
}
46+
47+
const std::vector<std::string>& OpenVINOEpPluginFactory::GetOvDevices() {
48+
static std::vector<std::string> devices = ov_core_singleton::Get()->get_available_devices();
49+
return devices;
50+
}
51+
52+
const std::vector<std::string>& OpenVINOEpPluginFactory::GetOvMetaDevices() {
53+
static std::vector<std::string> virtual_devices = [ov_core = ov_core_singleton::Get()] {
54+
std::vector<std::string> supported_virtual_devices{};
55+
for (const auto& meta_device : known_meta_devices_) {
56+
try {
57+
ov_core->get_property(meta_device, ov::supported_properties);
58+
supported_virtual_devices.push_back(meta_device);
59+
} catch (ov::Exception&) {
60+
// meta device isn't supported.
61+
}
62+
}
63+
return supported_virtual_devices;
64+
}();
65+
66+
return virtual_devices;
67+
}
68+
69+
OrtStatus* OpenVINOEpPluginFactory::GetSupportedDevices(const OrtHardwareDevice* const* devices,
70+
size_t num_devices,
71+
OrtEpDevice** ep_devices,
72+
size_t max_ep_devices,
73+
size_t* p_num_ep_devices) {
74+
size_t& num_ep_devices = *p_num_ep_devices;
75+
76+
// Create a map for device type mapping
77+
static const std::map<OrtHardwareDeviceType, std::string> ort_to_ov_device_name = {
78+
{OrtHardwareDeviceType::OrtHardwareDeviceType_CPU, "CPU"},
79+
{OrtHardwareDeviceType::OrtHardwareDeviceType_GPU, "GPU"},
80+
{OrtHardwareDeviceType::OrtHardwareDeviceType_NPU, "NPU"},
81+
};
82+
83+
for (size_t i = 0; i < num_devices && num_ep_devices < max_ep_devices; ++i) {
84+
const OrtHardwareDevice& device = *devices[i];
85+
if (ort_api.HardwareDevice_VendorId(&device) != vendor_id_) {
86+
// Not an Intel Device.
87+
continue;
88+
}
89+
90+
auto device_type = ort_api.HardwareDevice_Type(&device);
91+
auto device_it = ort_to_ov_device_name.find(device_type);
92+
if (device_it == ort_to_ov_device_name.end()) {
93+
// We don't know about this device type
94+
continue;
95+
}
96+
97+
const auto& ov_device_type = device_it->second;
98+
std::string ov_device_name;
99+
auto get_pci_device_id = [&](const std::string& ov_device) {
100+
try {
101+
ov::device::PCIInfo pci_info = ov_core_->get_property(ov_device, ov::device::pci_info);
102+
return pci_info.device;
103+
} catch (ov::Exception&) {
104+
return 0u; // If we can't get the PCI info, we won't have a device ID.
105+
}
106+
};
107+
108+
auto filtered_devices = GetOvDevices(ov_device_type);
109+
auto matched_device = filtered_devices.begin();
110+
if (filtered_devices.size() > 1 && device_type == OrtHardwareDeviceType::OrtHardwareDeviceType_GPU) {
111+
// If there are multiple devices of the same type, we need to match by device ID.
112+
matched_device = std::find_if(filtered_devices.begin(), filtered_devices.end(), [&](const std::string& ov_device) {
113+
uint32_t ort_device_id = ort_api.HardwareDevice_DeviceId(&device);
114+
return ort_device_id == get_pci_device_id(ov_device);
115+
});
116+
}
117+
118+
if (matched_device == filtered_devices.end()) {
119+
// We didn't find a matching OpenVINO device for the OrtHardwareDevice.
120+
continue;
121+
}
122+
123+
// these can be returned as nullptr if you have nothing to add.
124+
OrtKeyValuePairs* ep_metadata = nullptr;
125+
OrtKeyValuePairs* ep_options = nullptr;
126+
ort_api.CreateKeyValuePairs(&ep_metadata);
127+
ort_api.AddKeyValuePair(ep_metadata, ov_device_key_, matched_device->c_str());
128+
129+
if (IsMetaDeviceFactory()) {
130+
ort_api.AddKeyValuePair(ep_metadata, ov_meta_device_key_, device_type_.c_str());
131+
}
132+
133+
// Create EP device
134+
auto* status = ort_api.GetEpApi()->CreateEpDevice(this, &device, ep_metadata, ep_options,
135+
&ep_devices[num_ep_devices++]);
136+
137+
ort_api.ReleaseKeyValuePairs(ep_metadata);
138+
ort_api.ReleaseKeyValuePairs(ep_options);
139+
140+
if (status != nullptr) {
141+
return status;
142+
}
143+
}
144+
145+
return nullptr;
146+
}
147+
148+
extern "C" {
149+
//
150+
// Public symbols
151+
//
152+
OrtStatus* CreateEpFactories(const char* /*registration_name*/, const OrtApiBase* ort_api_base,
153+
OrtEpFactory** factories, size_t max_factories, size_t* num_factories) {
154+
InitCxxApi(*ort_api_base);
155+
const ApiPtrs api_ptrs{Ort::GetApi(), Ort::GetEpApi(), Ort::GetModelEditorApi()};
156+
157+
// Get available devices from OpenVINO
158+
auto ov_core = ov_core_singleton::Get();
159+
std::vector<std::string> supported_factories = {""};
160+
const auto& meta_devices = OpenVINOEpPluginFactory::GetOvMetaDevices();
161+
supported_factories.insert(supported_factories.end(), meta_devices.begin(), meta_devices.end());
162+
163+
const size_t required_factories = supported_factories.size();
164+
if (max_factories < required_factories) {
165+
return Ort::Status(std::format("Not enough space to return EP factories. Need at least {} factories.", required_factories).c_str(), ORT_INVALID_ARGUMENT);
166+
}
167+
168+
size_t factory_index = 0;
169+
for (const auto& device_name : supported_factories) {
170+
// Create a factory for this specific device
171+
factories[factory_index++] = new OpenVINOEpPluginFactory(api_ptrs, device_name, ov_core);
172+
}
173+
174+
*num_factories = factory_index;
175+
return nullptr;
176+
}
177+
178+
OrtStatus* ReleaseEpFactory(OrtEpFactory* factory) {
179+
delete static_cast<OpenVINOEpPluginFactory*>(factory);
180+
return nullptr;
181+
}
182+
}

0 commit comments

Comments
 (0)