From 66f530f9baa962724f588fc97cbeb02b2afe2589 Mon Sep 17 00:00:00 2001 From: Aditya Rastogi Date: Wed, 14 Jan 2026 15:47:47 -0800 Subject: [PATCH 1/4] Initial draft --- .../core/session/onnxruntime_c_api.h | 71 +++++ .../core/session/onnxruntime_cxx_api.h | 33 +++ .../core/session/onnxruntime_cxx_inline.h | 14 + onnxruntime/core/session/onnxruntime_c_api.cc | 118 ++++++++- onnxruntime/core/session/ort_apis.h | 11 + .../test/framework/ep_compatibility_test.cc | 245 ++++++++++++++++++ 6 files changed, 491 insertions(+), 1 deletion(-) diff --git a/include/onnxruntime/core/session/onnxruntime_c_api.h b/include/onnxruntime/core/session/onnxruntime_c_api.h index 5acac571f3f3b..2381c982ef10b 100644 --- a/include/onnxruntime/core/session/onnxruntime_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_c_api.h @@ -6912,6 +6912,77 @@ struct OrtApi { ORT_CLASS_RELEASE(DeviceEpIncompatibilityDetails); /// @} + + /// \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); + + /// @} }; /* diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_api.h b/include/onnxruntime/core/session/onnxruntime_cxx_api.h index d24594f590619..0300b3232bc62 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); + /** \brief The Env (Environment) * * The Env holds the logging state used by all other objects. diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_inline.h b/include/onnxruntime/core/session/onnxruntime_cxx_inline.h index 18299d2e49343..4499a93336943 100644 --- a/include/onnxruntime/core/session/onnxruntime_cxx_inline.h +++ b/include/onnxruntime/core/session/onnxruntime_cxx_inline.h @@ -919,6 +919,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 8cca7f2872c44..48b8d6edd6874 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" @@ -39,6 +42,7 @@ #include "core/session/compile_api.h" #include "core/session/environment.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" @@ -3565,6 +3569,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(); @@ -3603,6 +3694,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*/, @@ -4298,6 +4412,8 @@ static constexpr OrtApi ort_api_1_to_24 = { &OrtApis::DeviceEpIncompatibilityDetails_GetNotes, &OrtApis::DeviceEpIncompatibilityDetails_GetErrorCode, &OrtApis::ReleaseDeviceEpIncompatibilityDetails, + &OrtApis::GetCompatibilityInfoFromModel, + &OrtApis::GetCompatibilityInfoFromModelBytes, }; // OrtApiBase can never change as there is no way to know what version of OrtApiBase is returned by OrtGetApiBase. diff --git a/onnxruntime/core/session/ort_apis.h b/onnxruntime/core/session/ort_apis.h index a93d853592dea..d6a16847e4959 100644 --- a/onnxruntime/core/session/ort_apis.h +++ b/onnxruntime/core/session/ort_apis.h @@ -659,6 +659,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..677978ba00a2b 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; + api->GetAllocatorWithDefaultOptions(&allocator); + 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; + api->GetAllocatorWithDefaultOptions(&allocator); + 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; + api->GetAllocatorWithDefaultOptions(&allocator); + 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; + api->GetAllocatorWithDefaultOptions(&allocator); + 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; + api->GetAllocatorWithDefaultOptions(&allocator); + 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()); + api->AllocatorFree(allocator, compat_info); +} + +// 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; + api->GetAllocatorWithDefaultOptions(&allocator); + 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); +} From cef679e636414c592aa26dab53309ea3b96d4443 Mon Sep 17 00:00:00 2001 From: adrastogi Date: Thu, 15 Jan 2026 17:33:04 -0800 Subject: [PATCH 2/4] Apply linter fixes from Copilot code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- include/onnxruntime/core/session/onnxruntime_cxx_inline.h | 4 ++-- onnxruntime/test/framework/ep_compatibility_test.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_inline.h b/include/onnxruntime/core/session/onnxruntime_cxx_inline.h index 4b63b2d021316..07caed3d73bd0 100644 --- a/include/onnxruntime/core/session/onnxruntime_cxx_inline.h +++ b/include/onnxruntime/core/session/onnxruntime_cxx_inline.h @@ -929,14 +929,14 @@ inline OrtCompiledModelCompatibility GetModelCompatibilityForEpDevices( } inline AllocatedStringPtr GetCompatibilityInfoFromModelAllocated(const ORTCHAR_T* model_path, const char* ep_type, - OrtAllocator* allocator) { + 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) { + 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)); diff --git a/onnxruntime/test/framework/ep_compatibility_test.cc b/onnxruntime/test/framework/ep_compatibility_test.cc index 677978ba00a2b..131871b05bb54 100644 --- a/onnxruntime/test/framework/ep_compatibility_test.cc +++ b/onnxruntime/test/framework/ep_compatibility_test.cc @@ -735,7 +735,7 @@ TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModelBytes_NotFound) { 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 + ASSERT_EQ(st, nullptr); // Not an error - just not found EXPECT_EQ(compat_info, nullptr); // Should be nullptr when not found } From 355082b1b4745fd9d87b32b4ac92455223707209 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 15 Jan 2026 17:33:34 -0800 Subject: [PATCH 3/4] Fix Android CI build: check return values in ep_compatibility_test.cc (#27032) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description Android CI failed due to `-Werror,-Wunused-result` treating ignored return values as errors. Fixed 7 instances in `ep_compatibility_test.cc` where C API functions marked with `warn_unused_result` were called without checking their `OrtStatus*` returns: - 6 calls to `GetAllocatorWithDefaultOptions` - 1 call to `AllocatorFree` Changed from: ```cpp api->GetAllocatorWithDefaultOptions(&allocator); api->AllocatorFree(allocator, compat_info); ``` To: ```cpp ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); ASSERT_EQ(api->AllocatorFree(allocator, compat_info), nullptr); ``` ### Motivation and Context The Android build uses Clang with strict warnings. These C API functions return `OrtStatus*` to indicate success/failure, and ignoring them violates the `warn_unused_result` attribute. The fix follows the pattern used in other test files (e.g., `test_inference.cc`) and improves test robustness by catching API errors.
Original prompt > Fix the failing GitHub Actions workflow Android CI Pipeline > > Analyze the workflow logs, identify the root cause of the failure, and implement a fix. > > Job ID: 60532176150 > > Job URL: https://github.com/microsoft/onnxruntime/actions/runs/21049485371/job/60532176150
--- ✨ Let Copilot coding agent [set things up for you](https://github.com/microsoft/onnxruntime/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: adrastogi <8368026+adrastogi@users.noreply.github.com> --- .../test/framework/ep_compatibility_test.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/onnxruntime/test/framework/ep_compatibility_test.cc b/onnxruntime/test/framework/ep_compatibility_test.cc index 131871b05bb54..e4cf8754fb85f 100644 --- a/onnxruntime/test/framework/ep_compatibility_test.cc +++ b/onnxruntime/test/framework/ep_compatibility_test.cc @@ -540,7 +540,7 @@ TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModel_InvalidArgs) { ASSERT_NE(api, nullptr); OrtAllocator* allocator = nullptr; - api->GetAllocatorWithDefaultOptions(&allocator); + ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); ASSERT_NE(allocator, nullptr); char* compat_info = nullptr; @@ -581,7 +581,7 @@ TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModel_FileNotFound) { ASSERT_NE(api, nullptr); OrtAllocator* allocator = nullptr; - api->GetAllocatorWithDefaultOptions(&allocator); + ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); ASSERT_NE(allocator, nullptr); char* compat_info = nullptr; @@ -596,7 +596,7 @@ TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModelBytes_InvalidArgs) { ASSERT_NE(api, nullptr); OrtAllocator* allocator = nullptr; - api->GetAllocatorWithDefaultOptions(&allocator); + ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); ASSERT_NE(allocator, nullptr); char* compat_info = nullptr; @@ -653,7 +653,7 @@ TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModelBytes_InvalidModelDat ASSERT_NE(api, nullptr); OrtAllocator* allocator = nullptr; - api->GetAllocatorWithDefaultOptions(&allocator); + ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); ASSERT_NE(allocator, nullptr); char* compat_info = nullptr; @@ -671,7 +671,7 @@ TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModelBytes_WithMetadata) { ASSERT_NE(api, nullptr); OrtAllocator* allocator = nullptr; - api->GetAllocatorWithDefaultOptions(&allocator); + ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); ASSERT_NE(allocator, nullptr); // Create a minimal ModelProto with compatibility metadata @@ -702,7 +702,7 @@ TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModelBytes_WithMetadata) { ASSERT_EQ(st, nullptr) << (st ? api->GetErrorMessage(st) : ""); ASSERT_NE(compat_info, nullptr); EXPECT_STREQ(compat_info, expected_compat_info.c_str()); - api->AllocatorFree(allocator, compat_info); + ASSERT_EQ(api->AllocatorFree(allocator, compat_info), nullptr); } // Test when compatibility info is not found for the EP @@ -711,7 +711,7 @@ TEST(EpCompatibilityCapiTest, GetCompatibilityInfoFromModelBytes_NotFound) { ASSERT_NE(api, nullptr); OrtAllocator* allocator = nullptr; - api->GetAllocatorWithDefaultOptions(&allocator); + ASSERT_EQ(api->GetAllocatorWithDefaultOptions(&allocator), nullptr); ASSERT_NE(allocator, nullptr); // Create a minimal ModelProto without compatibility metadata for our EP From 58a949e810cfa10998fde99064502dbaaef49439 Mon Sep 17 00:00:00 2001 From: Aditya Rastogi Date: Fri, 16 Jan 2026 17:47:12 -0800 Subject: [PATCH 4/4] Fix Doxygen comments --- .../core/session/onnxruntime_cxx_api.h | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_api.h b/include/onnxruntime/core/session/onnxruntime_cxx_api.h index b40c54480bcd3..6c18b265a4f11 100644 --- a/include/onnxruntime/core/session/onnxruntime_cxx_api.h +++ b/include/onnxruntime/core/session/onnxruntime_cxx_api.h @@ -1164,35 +1164,35 @@ OrtCompiledModelCompatibility GetModelCompatibilityForEpDevices( const std::vector& ep_devices, const char* compatibility_info); -/** \\brief Extract EP compatibility info from a precompiled model file. +/** \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. + * \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. + * \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. + * \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. +/** \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. + * \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);