diff --git a/include/onnxruntime/core/session/onnxruntime_c_api.h b/include/onnxruntime/core/session/onnxruntime_c_api.h index 3168a8423a6d2..397312cc41c02 100644 --- a/include/onnxruntime/core/session/onnxruntime_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_c_api.h @@ -7009,6 +7009,77 @@ struct OrtApi { /// @} + /// \name Model Compatibility APIs + /// @{ + + /** \brief Extract EP compatibility info from a precompiled model file. + * + * Parses the model file to extract the compatibility info string for a specific execution provider + * from the model's metadata properties. This is only applicable to models that have been precompiled + * for an EP (e.g., via OrtCompileApi). Standard ONNX models do not contain this information. + * + * The compatibility info string must be valid UTF-8 without embedded NUL characters. + * + * \note This API performs standalone model parsing, separate from session creation. This means + * the protobuf parsing cost is incurred here and again during session creation. It is intended + * for scenarios where applications need to check compatibility before deciding whether to proceed + * with session creation, such as providing early user feedback. + * + * \note This operation parses the full ONNX ModelProto from disk. For very large models, consider + * using GetCompatibilityInfoFromModelBytes with a pre-loaded buffer if the model is already in memory. + * + * The compatibility info can then be passed to GetModelCompatibilityForEpDevices to check if a + * precompiled model is compatible with the current system. + * + * \param[in] model_path Path to the ONNX model file. + * \param[in] ep_type The execution provider type string. Must be non-empty. + * Use OrtApi::EpDevice_EpName to get this value from an OrtEpDevice. + * \param[in] allocator Allocator to use for the output string. Use OrtApi::GetAllocatorWithDefaultOptions. + * \param[out] compatibility_info Output pointer to the compatibility info string. + * Returns nullptr if no compatibility info exists for the specified EP. + * Caller must free with OrtApi::AllocatorFree when non-null. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24. + */ + ORT_API2_STATUS(GetCompatibilityInfoFromModel, + _In_ const ORTCHAR_T* model_path, + _In_ const char* ep_type, + _Inout_ OrtAllocator* allocator, + _Outptr_result_maybenull_ char** compatibility_info); + + /** \brief Extract EP compatibility info from precompiled model bytes in memory. + * + * Same as GetCompatibilityInfoFromModel but reads from a memory buffer instead of a file. + * Useful when precompiled models are loaded from encrypted storage, network, or other non-file sources. + * + * \note This API performs standalone model parsing, separate from session creation. This means + * the protobuf parsing cost is incurred here and again during session creation. It is intended + * for scenarios where applications need to check compatibility before deciding whether to proceed + * with session creation, such as providing early user feedback. + * + * \param[in] model_data Pointer to the model data in memory. + * \param[in] model_data_length Size of the model data in bytes. + * \param[in] ep_type The execution provider type string. Must be non-empty. + * \param[in] allocator Allocator to use for the output string. Use OrtApi::GetAllocatorWithDefaultOptions. + * \param[out] compatibility_info Output pointer to the compatibility info string. + * Returns nullptr if no compatibility info exists for the specified EP. + * Caller must free with OrtApi::AllocatorFree when non-null. + * + * \snippet{doc} snippets.dox OrtStatus Return Value + * + * \since Version 1.24. + */ + ORT_API2_STATUS(GetCompatibilityInfoFromModelBytes, + _In_reads_(model_data_length) const void* model_data, + _In_ size_t model_data_length, + _In_ const char* ep_type, + _Inout_ OrtAllocator* allocator, + _Outptr_result_maybenull_ char** compatibility_info); + + /// @} + /** \brief Create an OrtEnv instance with the given options. * * \note Invoking this function will return the same instance of the environment as that returned by a previous call diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_api.h b/include/onnxruntime/core/session/onnxruntime_cxx_api.h index 0efca9eaa928e..0dbf206bea992 100644 --- a/include/onnxruntime/core/session/onnxruntime_cxx_api.h +++ b/include/onnxruntime/core/session/onnxruntime_cxx_api.h @@ -1164,6 +1164,39 @@ OrtCompiledModelCompatibility GetModelCompatibilityForEpDevices( const std::vector& ep_devices, const char* compatibility_info); +/** \brief Extract EP compatibility info from a precompiled model file. + * + * Parses the model file to extract the compatibility info string for a specific execution provider + * from the model's metadata properties. This is only applicable to models that have been precompiled + * for an EP. Standard ONNX models do not contain this information. + * + * \note This operation parses the full ONNX ModelProto from disk. + * + * \param model_path Path to the ONNX model file. + * \param ep_type The execution provider type string. Must be non-empty. + * Use ConstEpDevice::EpName() to get this value. + * \param allocator Allocator to use for the output string. + * \return The compatibility info string, or nullptr if not found for this EP. Caller must free via allocator. + * \throws Ort::Exception on error. + */ +AllocatedStringPtr GetCompatibilityInfoFromModelAllocated(const ORTCHAR_T* model_path, const char* ep_type, + OrtAllocator* allocator); + +/** \brief Extract EP compatibility info from precompiled model bytes in memory. + * + * Same as GetCompatibilityInfoFromModelAllocated but reads from a memory buffer. + * Useful when precompiled models are loaded from encrypted storage, network, or other non-file sources. + * + * \param model_data Pointer to the model data in memory. + * \param model_data_length Size of the model data in bytes. + * \param ep_type The execution provider type string. Must be non-empty. + * \param allocator Allocator to use for the output string. + * \return The compatibility info string, or nullptr if not found for this EP. Caller must free via allocator. + * \throws Ort::Exception on error. + */ +AllocatedStringPtr GetCompatibilityInfoFromModelBytesAllocated(const void* model_data, size_t model_data_length, + const char* ep_type, OrtAllocator* allocator); + namespace detail { template struct EpAssignedNodeImpl : Ort::detail::Base { diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_inline.h b/include/onnxruntime/core/session/onnxruntime_cxx_inline.h index 0249a2bd8e0c9..9d95c6a880467 100644 --- a/include/onnxruntime/core/session/onnxruntime_cxx_inline.h +++ b/include/onnxruntime/core/session/onnxruntime_cxx_inline.h @@ -983,6 +983,20 @@ inline OrtCompiledModelCompatibility GetModelCompatibilityForEpDevices( return status; } +inline AllocatedStringPtr GetCompatibilityInfoFromModelAllocated(const ORTCHAR_T* model_path, const char* ep_type, + OrtAllocator* allocator) { + char* compat_info = nullptr; + ThrowOnError(GetApi().GetCompatibilityInfoFromModel(model_path, ep_type, allocator, &compat_info)); + return AllocatedStringPtr(compat_info, detail::AllocatedFree(allocator)); +} + +inline AllocatedStringPtr GetCompatibilityInfoFromModelBytesAllocated(const void* model_data, size_t model_data_length, + const char* ep_type, OrtAllocator* allocator) { + char* compat_info = nullptr; + ThrowOnError(GetApi().GetCompatibilityInfoFromModelBytes(model_data, model_data_length, ep_type, allocator, &compat_info)); + return AllocatedStringPtr(compat_info, detail::AllocatedFree(allocator)); +} + inline LoraAdapter LoraAdapter::CreateLoraAdapter(const std::basic_string& adapter_path, OrtAllocator* allocator) { OrtLoraAdapter* p; diff --git a/onnxruntime/core/session/onnxruntime_c_api.cc b/onnxruntime/core/session/onnxruntime_c_api.cc index 9bbd4ed19ca29..0e60a7c5c39ae 100644 --- a/onnxruntime/core/session/onnxruntime_c_api.cc +++ b/onnxruntime/core/session/onnxruntime_c_api.cc @@ -3,11 +3,12 @@ #include #include +#include #include #include #include -#include #include +#include #include "core/common/common.h" #include "core/common/logging/logging.h" @@ -30,8 +31,10 @@ #include "core/framework/utils.h" #include "core/graph/constants.h" #include "core/graph/graph.h" +#include "core/graph/model.h" #include "core/graph/model_editor_api_types.h" #include "core/graph/ep_api_types.h" +#include "core/graph/onnx_protobuf.h" #include "core/providers/get_execution_providers.h" #include "core/session/abi_devices.h" #include "core/session/abi_session_options_impl.h" @@ -40,6 +43,7 @@ #include "core/session/environment.h" #include "core/session/ep_graph_assignment_info.h" #include "core/session/interop_api.h" +#include "core/session/onnxruntime_ep_device_ep_metadata_keys.h" #include "core/session/plugin_ep/ep_api.h" #include "core/session/plugin_ep/ep_library_internal.h" #include "core/session/inference_session.h" @@ -3943,6 +3947,93 @@ ORT_API_STATUS_IMPL(OrtApis::GetModelCompatibilityForEpDevices, API_IMPL_END } +// Helper function to extract compatibility info from model metadata +static OrtStatus* ExtractCompatibilityInfoFromModelProto( + const ONNX_NAMESPACE::ModelProto& model_proto, + const char* ep_type, + OrtAllocator* allocator, + char** compatibility_info) { + // Build the key we're looking for + std::string target_key = std::string(kOrtModelMetadata_EpCompatibilityInfoPrefix) + ep_type; + + // Search through metadata_props for the matching key + for (const auto& prop : model_proto.metadata_props()) { + if (prop.key() == target_key) { + // Found it - allocate and copy the value using the provided allocator + *compatibility_info = onnxruntime::StrDup(prop.value(), allocator); + if (*compatibility_info == nullptr) { + return OrtApis::CreateStatus(ORT_FAIL, "Failed to allocate memory for compatibility info."); + } + return nullptr; + } + } + + // Key not found - return nullptr (not an error, just means no compat info for this EP) + *compatibility_info = nullptr; + return nullptr; +} + +// Extract EP compatibility info from a model file +ORT_API_STATUS_IMPL(OrtApis::GetCompatibilityInfoFromModel, + _In_ const ORTCHAR_T* model_path, + _In_ const char* ep_type, + _Inout_ OrtAllocator* allocator, + _Outptr_result_maybenull_ char** compatibility_info) { + API_IMPL_BEGIN + if (model_path == nullptr || ep_type == nullptr || ep_type[0] == '\0' || + allocator == nullptr || compatibility_info == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, + "Invalid argument provided to GetCompatibilityInfoFromModel."); + } + + *compatibility_info = nullptr; + + // Use Model::Load for proper cross-platform path handling via file descriptor + ONNX_NAMESPACE::ModelProto model_proto; + auto status = Model::Load(PathString(model_path), model_proto); + if (!status.IsOK()) { + if (status.Code() == common::NO_SUCHFILE) { + return OrtApis::CreateStatus(ORT_NO_SUCHFILE, status.ErrorMessage().c_str()); + } + return OrtApis::CreateStatus(ORT_INVALID_GRAPH, status.ErrorMessage().c_str()); + } + + return ExtractCompatibilityInfoFromModelProto(model_proto, ep_type, allocator, compatibility_info); + API_IMPL_END +} + +// Extract EP compatibility info from model bytes in memory +ORT_API_STATUS_IMPL(OrtApis::GetCompatibilityInfoFromModelBytes, + _In_reads_(model_data_length) const void* model_data, + _In_ size_t model_data_length, + _In_ const char* ep_type, + _Inout_ OrtAllocator* allocator, + _Outptr_result_maybenull_ char** compatibility_info) { + API_IMPL_BEGIN + if (model_data == nullptr || model_data_length == 0 || ep_type == nullptr || ep_type[0] == '\0' || + allocator == nullptr || compatibility_info == nullptr) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, + "Invalid argument provided to GetCompatibilityInfoFromModelBytes."); + } + + *compatibility_info = nullptr; + + // Explicit check for size limit - Model::LoadFromBytes uses int for size due to protobuf API + if (model_data_length > static_cast(INT_MAX)) { + return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, + "Model data size exceeds maximum supported size (2GB). Use GetCompatibilityInfoFromModel with a file path instead."); + } + + ONNX_NAMESPACE::ModelProto model_proto; + auto status = Model::LoadFromBytes(static_cast(model_data_length), model_data, model_proto); + if (!status.IsOK()) { + return OrtApis::CreateStatus(ORT_INVALID_GRAPH, status.ErrorMessage().c_str()); + } + + return ExtractCompatibilityInfoFromModelProto(model_proto, ep_type, allocator, compatibility_info); + API_IMPL_END +} + // GetInteropApi - returns the Interop API struct ORT_API(const OrtInteropApi*, OrtApis::GetInteropApi) { return OrtInteropAPI::GetInteropApi(); @@ -3981,6 +4072,29 @@ ORT_API_STATUS_IMPL(OrtApis::GetModelCompatibilityForEpDevices, API_IMPL_END } +// Minimal build stub for GetCompatibilityInfoFromModel +ORT_API_STATUS_IMPL(OrtApis::GetCompatibilityInfoFromModel, + _In_ const ORTCHAR_T* /*model_path*/, + _In_ const char* /*ep_type*/, + _Inout_ OrtAllocator* /*allocator*/, + _Outptr_result_maybenull_ char** /*compatibility_info*/) { + API_IMPL_BEGIN + return OrtApis::CreateStatus(ORT_NOT_IMPLEMENTED, "GetCompatibilityInfoFromModel is not supported in a minimal build."); + API_IMPL_END +} + +// Minimal build stub for GetCompatibilityInfoFromModelBytes +ORT_API_STATUS_IMPL(OrtApis::GetCompatibilityInfoFromModelBytes, + _In_reads_(model_data_length) const void* /*model_data*/, + _In_ size_t /*model_data_length*/, + _In_ const char* /*ep_type*/, + _Inout_ OrtAllocator* /*allocator*/, + _Outptr_result_maybenull_ char** /*compatibility_info*/) { + API_IMPL_BEGIN + return OrtApis::CreateStatus(ORT_NOT_IMPLEMENTED, "GetCompatibilityInfoFromModelBytes is not supported in a minimal build."); + API_IMPL_END +} + ORT_API_STATUS_IMPL(OrtApis::SessionOptionsAppendExecutionProvider_V2, _In_ OrtSessionOptions* /*session_options*/, _In_ OrtEnv* /*env*/, _In_reads_(num_ep_devices) const OrtEpDevice* const* /*ep_devices*/, @@ -4677,6 +4791,9 @@ static constexpr OrtApi ort_api_1_to_24 = { &OrtApis::DeviceEpIncompatibilityDetails_GetNotes, &OrtApis::DeviceEpIncompatibilityDetails_GetErrorCode, &OrtApis::ReleaseDeviceEpIncompatibilityDetails, + &OrtApis::GetCompatibilityInfoFromModel, + &OrtApis::GetCompatibilityInfoFromModelBytes, + &OrtApis::CreateEnvWithOptions, &OrtApis::Session_GetEpGraphAssignmentInfo, &OrtApis::EpAssignedSubgraph_GetEpName, diff --git a/onnxruntime/core/session/ort_apis.h b/onnxruntime/core/session/ort_apis.h index bbe5f3db388b5..efea582e3f798 100644 --- a/onnxruntime/core/session/ort_apis.h +++ b/onnxruntime/core/session/ort_apis.h @@ -660,6 +660,17 @@ ORT_API_STATUS_IMPL(GetModelCompatibilityForEpDevices, _In_ size_t num_ep_devices, _In_ const char* compatibility_info, _Out_ OrtCompiledModelCompatibility* out_status); +ORT_API_STATUS_IMPL(GetCompatibilityInfoFromModel, + _In_ const ORTCHAR_T* model_path, + _In_ const char* ep_type, + _Inout_ OrtAllocator* allocator, + _Outptr_result_maybenull_ char** compatibility_info); +ORT_API_STATUS_IMPL(GetCompatibilityInfoFromModelBytes, + _In_reads_(model_data_length) const void* model_data, + _In_ size_t model_data_length, + _In_ const char* ep_type, + _Inout_ OrtAllocator* allocator, + _Outptr_result_maybenull_ char** compatibility_info); ORT_API_STATUS_IMPL(Graph_GetModelPath, _In_ const OrtGraph* graph, _Outptr_ const ORTCHAR_T** model_path); ORT_API_STATUS_IMPL(Graph_GetOnnxIRVersion, _In_ const OrtGraph* graph, _Out_ int64_t* onnx_ir_version); ORT_API_STATUS_IMPL(Graph_GetNumOperatorSets, _In_ const OrtGraph* graph, _Out_ size_t* num_operator_sets); diff --git a/onnxruntime/test/framework/ep_compatibility_test.cc b/onnxruntime/test/framework/ep_compatibility_test.cc index 0ae3fb746dd24..e4cf8754fb85f 100644 --- a/onnxruntime/test/framework/ep_compatibility_test.cc +++ b/onnxruntime/test/framework/ep_compatibility_test.cc @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +#include + #include "gtest/gtest.h" #include "gmock/gmock.h" @@ -528,3 +530,246 @@ TEST(EpCompatibilityCxxApiTest, SingleDeviceCpuProvider) { ASSERT_TRUE(status == OrtCompiledModelCompatibility_EP_NOT_APPLICABLE); } + +// ----------------------------- +// GetCompatibilityInfoFromModel Tests +// ----------------------------- + +TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModel_InvalidArgs) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtAllocator* allocator = nullptr; + ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); + ASSERT_NE(allocator, nullptr); + + char* compat_info = nullptr; + + // model_path == nullptr + OrtStatus* st = api->GetCompatibilityInfoFromModel(nullptr, "TestEP", allocator, &compat_info); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + // ep_type == nullptr + st = api->GetCompatibilityInfoFromModel(ORT_TSTR("test.onnx"), nullptr, allocator, &compat_info); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + // ep_type == empty string + st = api->GetCompatibilityInfoFromModel(ORT_TSTR("test.onnx"), "", allocator, &compat_info); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + // allocator == nullptr + st = api->GetCompatibilityInfoFromModel(ORT_TSTR("test.onnx"), "TestEP", nullptr, &compat_info); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + // compatibility_info == nullptr + st = api->GetCompatibilityInfoFromModel(ORT_TSTR("test.onnx"), "TestEP", allocator, nullptr); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); +} + +TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModel_FileNotFound) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtAllocator* allocator = nullptr; + ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); + ASSERT_NE(allocator, nullptr); + + char* compat_info = nullptr; + OrtStatus* st = api->GetCompatibilityInfoFromModel(ORT_TSTR("nonexistent_model.onnx"), "TestEP", allocator, &compat_info); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_NO_SUCHFILE); + api->ReleaseStatus(st); +} + +TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModelBytes_InvalidArgs) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtAllocator* allocator = nullptr; + ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); + ASSERT_NE(allocator, nullptr); + + char* compat_info = nullptr; + const char dummy_data[] = "dummy"; + + // model_data == nullptr + OrtStatus* st = api->GetCompatibilityInfoFromModelBytes(nullptr, 10, "TestEP", allocator, &compat_info); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + // model_data_length == 0 + st = api->GetCompatibilityInfoFromModelBytes(dummy_data, 0, "TestEP", allocator, &compat_info); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + // ep_type == nullptr + st = api->GetCompatibilityInfoFromModelBytes(dummy_data, sizeof(dummy_data), nullptr, allocator, &compat_info); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + // ep_type == empty string + st = api->GetCompatibilityInfoFromModelBytes(dummy_data, sizeof(dummy_data), "", allocator, &compat_info); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + // allocator == nullptr + st = api->GetCompatibilityInfoFromModelBytes(dummy_data, sizeof(dummy_data), "TestEP", nullptr, &compat_info); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + // compatibility_info == nullptr + st = api->GetCompatibilityInfoFromModelBytes(dummy_data, sizeof(dummy_data), "TestEP", allocator, nullptr); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); + + // model_data_length > INT_MAX (should return error, not crash) + // We can't actually allocate this much memory, but we can pass the size + // The API should validate the size before attempting to use the data + size_t oversized_length = static_cast(INT_MAX) + 1; + st = api->GetCompatibilityInfoFromModelBytes(dummy_data, oversized_length, "TestEP", allocator, &compat_info); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_ARGUMENT); + api->ReleaseStatus(st); +} + +TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModelBytes_InvalidModelData) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtAllocator* allocator = nullptr; + ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); + ASSERT_NE(allocator, nullptr); + + char* compat_info = nullptr; + const char invalid_data[] = "this is not a valid ONNX model"; + + OrtStatus* st = api->GetCompatibilityInfoFromModelBytes(invalid_data, sizeof(invalid_data), "TestEP", allocator, &compat_info); + ASSERT_NE(st, nullptr); + EXPECT_EQ(api->GetErrorCode(st), ORT_INVALID_GRAPH); + api->ReleaseStatus(st); +} + +// Test extracting compatibility info from a model with metadata +TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModelBytes_WithMetadata) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtAllocator* allocator = nullptr; + ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); + ASSERT_NE(allocator, nullptr); + + // Create a minimal ModelProto with compatibility metadata + ONNX_NAMESPACE::ModelProto model_proto; + model_proto.set_ir_version(ONNX_NAMESPACE::Version::IR_VERSION); + model_proto.mutable_graph()->set_name("test_graph"); + + // Add an opset import (required) + auto* opset = model_proto.add_opset_import(); + opset->set_domain(""); + opset->set_version(13); + + // Add compatibility metadata + const std::string ep_type = "TestCompatEP"; + const std::string expected_compat_info = "test_compat_v1.0_driver_123"; + auto* prop = model_proto.add_metadata_props(); + prop->set_key(std::string("ep_compatibility_info.") + ep_type); + prop->set_value(expected_compat_info); + + // Serialize the model + std::string model_data; + ASSERT_TRUE(model_proto.SerializeToString(&model_data)); + + // Extract compatibility info + char* compat_info = nullptr; + OrtStatus* st = api->GetCompatibilityInfoFromModelBytes( + model_data.data(), model_data.size(), ep_type.c_str(), allocator, &compat_info); + ASSERT_EQ(st, nullptr) << (st ? api->GetErrorMessage(st) : ""); + ASSERT_NE(compat_info, nullptr); + EXPECT_STREQ(compat_info, expected_compat_info.c_str()); + ASSERT_EQ(api->AllocatorFree(allocator, compat_info), nullptr); +} + +// Test when compatibility info is not found for the EP +TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModelBytes_NotFound) { + const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + ASSERT_NE(api, nullptr); + + OrtAllocator* allocator = nullptr; + ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); + ASSERT_NE(allocator, nullptr); + + // Create a minimal ModelProto without compatibility metadata for our EP + ONNX_NAMESPACE::ModelProto model_proto; + model_proto.set_ir_version(ONNX_NAMESPACE::Version::IR_VERSION); + model_proto.mutable_graph()->set_name("test_graph"); + + auto* opset = model_proto.add_opset_import(); + opset->set_domain(""); + opset->set_version(13); + + // Add metadata for a different EP + auto* prop = model_proto.add_metadata_props(); + prop->set_key("ep_compatibility_info.DifferentEP"); + prop->set_value("some_value"); + + std::string model_data; + ASSERT_TRUE(model_proto.SerializeToString(&model_data)); + + // Try to get compatibility info for an EP that doesn't have it + char* compat_info = nullptr; + OrtStatus* st = api->GetCompatibilityInfoFromModelBytes( + model_data.data(), model_data.size(), "NonExistentEP", allocator, &compat_info); + ASSERT_EQ(st, nullptr); // Not an error - just not found + EXPECT_EQ(compat_info, nullptr); // Should be nullptr when not found +} + +// C++ API test +TEST(EpCompatibilityCxxApiTest, GetCompatibilityInfoFromModelBytes) { + // Create a minimal ModelProto with compatibility metadata + ONNX_NAMESPACE::ModelProto model_proto; + model_proto.set_ir_version(ONNX_NAMESPACE::Version::IR_VERSION); + model_proto.mutable_graph()->set_name("test_graph"); + + auto* opset = model_proto.add_opset_import(); + opset->set_domain(""); + opset->set_version(13); + + const std::string ep_type = "CxxTestEP"; + const std::string expected_compat_info = "cxx_compat_v2.0"; + auto* prop = model_proto.add_metadata_props(); + prop->set_key(std::string("ep_compatibility_info.") + ep_type); + prop->set_value(expected_compat_info); + + std::string model_data; + ASSERT_TRUE(model_proto.SerializeToString(&model_data)); + + // Get allocator + Ort::AllocatorWithDefaultOptions allocator; + + // Test C++ API - found case + Ort::AllocatedStringPtr result = Ort::GetCompatibilityInfoFromModelBytesAllocated( + model_data.data(), model_data.size(), ep_type.c_str(), allocator); + ASSERT_NE(result.get(), nullptr); + EXPECT_STREQ(result.get(), expected_compat_info.c_str()); + + // Test when not found - should return nullptr + Ort::AllocatedStringPtr not_found = Ort::GetCompatibilityInfoFromModelBytesAllocated( + model_data.data(), model_data.size(), "NonExistentEP", allocator); + EXPECT_EQ(not_found.get(), nullptr); +}