diff --git a/cmake/onnxruntime_unittests.cmake b/cmake/onnxruntime_unittests.cmake index 81bb0878f5017..a7344d05bf0fd 100644 --- a/cmake/onnxruntime_unittests.cmake +++ b/cmake/onnxruntime_unittests.cmake @@ -1148,6 +1148,15 @@ endif() partition_provider_test_srcs(all_tests onnxruntime_provider_test_srcs onnxruntime_test_all_srcs) +if (onnxruntime_USE_OPENVINO) + # ov_protobuf_utils.cpp lives under core/providers (not test/), so partition_provider_test_srcs + # would route it to onnxruntime_test_all. Append it here after the partition so it is compiled into + # onnxruntime_provider_test alongside openvino_ov_protobuf_utils_test.cc, because the OpenVINO EP + # is a dynamically-loaded module and is not statically linked into the test binary. + list(APPEND onnxruntime_provider_test_srcs + ${ONNXRUNTIME_ROOT}/core/providers/openvino/ov_protobuf_utils.cpp) +endif() + # Workarounds for onnxruntime test targets. function(onnxruntime_apply_test_target_workarounds target) if (MSVC) diff --git a/onnxruntime/core/providers/openvino/ov_protobuf_utils.cpp b/onnxruntime/core/providers/openvino/ov_protobuf_utils.cpp index e28330e0bd433..8e4704ab0fb22 100644 --- a/onnxruntime/core/providers/openvino/ov_protobuf_utils.cpp +++ b/onnxruntime/core/providers/openvino/ov_protobuf_utils.cpp @@ -3,6 +3,8 @@ #include "ov_protobuf_utils.h" +#include + #include "core/graph/onnx_protobuf.h" #include "core/common/common.h" @@ -11,14 +13,35 @@ namespace openvino_ep { float get_float_initializer_data(const void* initializer) { const auto* tp = reinterpret_cast(initializer); ORT_ENFORCE((tp->has_data_type() && (tp->data_type() == ONNX_NAMESPACE::TensorProto_DataType_FLOAT))); - // ORT_ENFORCE(initializer.dims_size() == 1); - return tp->float_data(0); + + // A FLOAT scalar/tensor may store its value either in the typed float_data + // field or in raw_data. Indexing float_data(0) when it is empty is undefined + // behavior, so pick the field that actually holds the data. + if (tp->float_data_size() > 0) { + return tp->float_data(0); + } + + ORT_ENFORCE(tp->has_raw_data() && tp->raw_data().size() >= sizeof(float), + "FLOAT initializer has neither float_data nor sufficient raw_data to read a value"); + float value; + std::memcpy(&value, tp->raw_data().data(), sizeof(float)); + return value; } void set_float_initializer_data(const void* initializer, float data) { auto* tp = (ONNX_NAMESPACE::TensorProto*)(initializer); ORT_ENFORCE((tp->has_data_type() && (tp->data_type() == ONNX_NAMESPACE::TensorProto_DataType_FLOAT))); - // ORT_ENFORCE(initializer.dims_size() == 1); - tp->set_float_data(0, data); + + // Mirror get_float_initializer_data: write back into whichever storage the + // initializer actually uses. set_float_data(0, data) on an empty float_data + // field is an out-of-bounds write. + if (tp->float_data_size() > 0) { + tp->set_float_data(0, data); + return; + } + + ORT_ENFORCE(tp->has_raw_data() && tp->raw_data().size() >= sizeof(float), + "FLOAT initializer has neither float_data nor sufficient raw_data to write a value"); + tp->set_raw_data(&data, sizeof(float)); } } // namespace openvino_ep } // namespace onnxruntime diff --git a/onnxruntime/test/providers/openvino/openvino_ov_protobuf_utils_test.cc b/onnxruntime/test/providers/openvino/openvino_ov_protobuf_utils_test.cc new file mode 100644 index 0000000000000..67a5ea7b30cd4 --- /dev/null +++ b/onnxruntime/test/providers/openvino/openvino_ov_protobuf_utils_test.cc @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include + +#include "core/graph/onnx_protobuf.h" +#include "core/providers/openvino/ov_protobuf_utils.h" + +#include "gtest/gtest.h" + +using namespace ONNX_NAMESPACE; + +namespace onnxruntime { +namespace test { + +// Builds a FLOAT scalar whose value lives in raw_data, with an empty float_data +// field. +static TensorProto MakeRawDataFloatScalar(float value) { + TensorProto tp; + tp.set_data_type(TensorProto_DataType_FLOAT); + tp.set_raw_data(&value, sizeof(float)); + return tp; +} + +// Builds a FLOAT scalar whose value lives in the typed float_data field. +static TensorProto MakeFloatDataScalar(float value) { + TensorProto tp; + tp.set_data_type(TensorProto_DataType_FLOAT); + tp.add_float_data(value); + return tp; +} + +TEST(OpenVINO_OvProtobufUtils, GetFromRawData) { + TensorProto tp = MakeRawDataFloatScalar(4.0f); + ASSERT_EQ(tp.float_data_size(), 0); // value is only in raw_data + + EXPECT_FLOAT_EQ(openvino_ep::get_float_initializer_data(&tp), 4.0f); +} + +TEST(OpenVINO_OvProtobufUtils, SetIntoRawData) { + TensorProto tp = MakeRawDataFloatScalar(4.0f); + ASSERT_EQ(tp.float_data_size(), 0); + + openvino_ep::set_float_initializer_data(&tp, 0.5f); + + // The write must land in raw_data (the field that actually holds the value), + // and must be readable back through the getter. + ASSERT_GE(tp.raw_data().size(), sizeof(float)); + float stored; + std::memcpy(&stored, tp.raw_data().data(), sizeof(float)); + EXPECT_FLOAT_EQ(stored, 0.5f); + EXPECT_FLOAT_EQ(openvino_ep::get_float_initializer_data(&tp), 0.5f); +} + +TEST(OpenVINO_OvProtobufUtils, GetFromFloatData) { + TensorProto tp = MakeFloatDataScalar(3.0f); + EXPECT_FLOAT_EQ(openvino_ep::get_float_initializer_data(&tp), 3.0f); +} + +TEST(OpenVINO_OvProtobufUtils, SetIntoFloatData) { + TensorProto tp = MakeFloatDataScalar(3.0f); + openvino_ep::set_float_initializer_data(&tp, 7.0f); + EXPECT_FLOAT_EQ(tp.float_data(0), 7.0f); + EXPECT_FLOAT_EQ(openvino_ep::get_float_initializer_data(&tp), 7.0f); +} + +} // namespace test +} // namespace onnxruntime