Skip to content

Commit 2b7d228

Browse files
authored
[OV CPU] Convert Node converting integer to unsigned one, it did clamp (#32389)
As Convert Op's [Specification ](https://docs.openvino.ai/2025/documentation/openvino-ir-format/operation-sets/operation-specs/type/convert-1.html) said. > Conversion of negative signed integer to unsigned integer value happens in accordance with c++ standard. Notably, result is the unique value of the destination unsigned type that is congruent to the source integer modulo 2^N (where N is the bit width of the destination type). **For example, when an int32 value -1 is converted to uint32 the result will be uint32 max which is 4,294,967,295.** ### Reproduce: Test when int32 value -1 convert to uint8 - OV GPU/NPU, got 255 (match the spec) - OV CPU, it uses the boundary to clamp, got 0, not 255 (did't match the spec) ``` import openvino as ov import numpy as np input_data = np.full((2, 2), -1, dtype=np.int32) expected_output_data = np.full((2, 2), 2**8 - 1, dtype=np.uint8) input_param = ov.op.Parameter(ov.runtime.Type.i32, ov.Shape([2, 2])) convert_op = ov.opset1.convert(input_param, destination_type=ov.runtime.Type.u8) model = ov.Model(results=[convert_op], parameters=[input_param], name="int32_to_uint8_convert") core = ov.Core() compiled_model = core.compile_model(model, "CPU") results = compiled_model(input_data) actual_output_data = results[compiled_model.outputs[0]] try: print("--- Verification ---") np.testing.assert_array_equal(actual_output_data, expected_output_data) print("SUCCESS: Actual output matches expected output.") except AssertionError as e: print(f"FAILED: Output mismatch!\n{e}") ``` ``` --- Verification --- FAILED: Output mismatch! Arrays are not equal ... ACTUAL: array([[0, 0],[0, 0]], dtype=uint8) DESIRED: array([[255, 255],[255, 255]], dtype=uint8) ``` ### Tickets: - *CVS-172796*
1 parent 4c4f1f2 commit 2b7d228

File tree

8 files changed

+42
-10
lines changed

8 files changed

+42
-10
lines changed

src/frontends/onnx/tests/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ def xfail_test(reason="Mark the test as expected to fail", strict=True):
5151
xfail_issue_38708 = xfail_test(reason="RuntimeError: While validating ONNX node '<Node(Slice): y>': "
5252
"Axes input must be constant")
5353
skip_bitwise_ui64 = pytest.mark.skip(reason="AssertionError: Not equal to tolerance rtol=0.001, atol=1e-07")
54-
xfail_issue_99949 = xfail_test(reason="Bitwise operators are not supported")
5554
xfail_issue_99950 = xfail_test(reason="CenterCropPad func is not supported")
5655
xfail_issue_99952 = xfail_test(reason="Col2Im operator is not supported")
5756
xfail_issue_99954 = xfail_test(reason="Constant Pad - RuntimeError: Shape inference of Reference node with name y failed")

src/frontends/onnx/tests/tests_python/test_backend.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
xfail_issue_82039,
4141
xfail_issue_90649,
4242
skip_bitwise_ui64,
43-
xfail_issue_99949,
4443
xfail_issue_99950,
4544
xfail_issue_99952,
4645
xfail_issue_99954,
@@ -381,10 +380,6 @@ def expect_fail(test_case_path, xfail): # type: (str) -> None
381380
"OnnxBackendNodeModelTest.test_bitwise_or_ui64_bcast_3v1d_cpu",
382381
"OnnxBackendNodeModelTest.test_bitwise_xor_ui64_bcast_3v1d_cpu",
383382
),
384-
(
385-
xfail_issue_99949,
386-
"OnnxBackendNodeModelTest.test_bitwise_not_3d_cpu",
387-
),
388383
(
389384
xfail_issue_99950,
390385
"OnnxBackendNodeModelTest.test_center_crop_pad_crop_axes_chw_expanded_cpu",

src/plugins/intel_cpu/src/nodes/common/cpu_convert.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,8 +550,9 @@ struct ConvertPrecision<std::tuple<src_t, dst_t>> {
550550
// Align with the behavior of ngraph ref and jit implementation. Conversion from f8e4m3-inf
551551
// to float should output float-inf instead of f8e4m3-max. Proper handling of special values
552552
// (nan, inf, overflow) has already been assured by the conversion process.
553-
if (std::is_same_v<src_t, ov::float8_e4m3> || std::is_same_v<src_t, ov::float8_e5m2> ||
554-
std::is_same_v<dst_t, ov::float8_e4m3> || std::is_same_v<dst_t, ov::float8_e5m2>) {
553+
if (ov::intel_cpu::any_of_v<src_t, ov::float8_e4m3, ov::float8_e5m2> ||
554+
ov::intel_cpu::any_of_v<dst_t, ov::float8_e4m3, ov::float8_e5m2> ||
555+
(std::is_integral_v<src_t> && std::is_integral_v<dst_t>)) {
555556
parallel_for(ctx.size, [&](size_t i) {
556557
dst[i] = static_cast<dst_t>(src[i]);
557558
});

src/plugins/intel_cpu/src/nodes/executors/acl/acl_convert.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ bool ACLConvertExecutor::init(const ConvertParams& convertParams,
5656
return false;
5757
}
5858
} else {
59-
Status s = NECast::validate(&srcTensorInfo, &dstTensorInfo, ConvertPolicy::SATURATE);
59+
Status s = NECast::validate(&srcTensorInfo, &dstTensorInfo, ConvertPolicy::WRAP);
6060
if (!s) {
6161
DEBUG_LOG("NECast validation failed: ", s.error_description());
6262
return false;
@@ -74,7 +74,7 @@ bool ACLConvertExecutor::init(const ConvertParams& convertParams,
7474
} else {
7575
acl_cast = std::make_unique<NECast>();
7676
configureThreadSafe([&] {
77-
acl_cast->configure(&srcTensor, &dstTensor, ConvertPolicy::SATURATE);
77+
acl_cast->configure(&srcTensor, &dstTensor, ConvertPolicy::WRAP);
7878
});
7979
}
8080
return true;

src/plugins/intel_cpu/tests/functional/shared_tests_instances/single_layer_tests/conversion.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
namespace {
1111
using ov::test::ConversionLayerTest;
12+
using ov::test::ConversionSpecifyInputLayerTest;
1213

1314
const std::vector<ov::test::utils::ConversionTypes> conversionOpTypes = {
1415
ov::test::utils::ConversionTypes::CONVERT,
@@ -79,4 +80,13 @@ INSTANTIATE_TEST_SUITE_P(smoke_ConversionFromF8LayerTest,
7980
::testing::Values(ov::test::utils::DEVICE_CPU)),
8081
ConversionLayerTest::getTestCaseName);
8182

83+
INSTANTIATE_TEST_SUITE_P(smoke_ConversionI32ToU8LayerTest,
84+
ConversionSpecifyInputLayerTest,
85+
::testing::Combine(::testing::Values(conversionOpTypes[0]),
86+
::testing::ValuesIn(ov::test::static_shapes_to_test_representation(shapes)),
87+
::testing::Values(ov::element::i32),
88+
::testing::Values(ov::element::u8),
89+
::testing::Values(ov::test::utils::DEVICE_CPU)),
90+
ConversionSpecifyInputLayerTest::getTestCaseName);
91+
8292
} // namespace

src/tests/functional/plugin/shared/include/shared_test_classes/single_op/conversion.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,11 @@ class ConversionLayerTest : public testing::WithParamInterface<ConversionParamsT
2626
protected:
2727
void SetUp() override;
2828
};
29+
30+
class ConversionSpecifyInputLayerTest : public ConversionLayerTest {
31+
protected:
32+
void generate_inputs(const std::vector<ov::Shape>& targetInputStaticShapes) override;
33+
};
34+
2935
} // namespace test
3036
} // namespace ov

src/tests/functional/plugin/shared/include/single_op_tests/conversion.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@ namespace test {
1111
TEST_P(ConversionLayerTest, Inference) {
1212
run();
1313
};
14+
TEST_P(ConversionSpecifyInputLayerTest, Inference) {
15+
run();
16+
};
1417
} // namespace test
1518
} // namespace ov

src/tests/functional/plugin/shared/src/single_op/conversion.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
//
44

55
#include "shared_test_classes/single_op/conversion.hpp"
6+
7+
#include "common_test_utils/data_utils.hpp"
68
#include "openvino/op/convert.hpp"
79
#include "openvino/op/convert_like.hpp"
810

@@ -57,5 +59,21 @@ void ConversionLayerTest::SetUp() {
5759
auto result = std::make_shared<ov::op::v0::Result>(conversion);
5860
function = std::make_shared<ov::Model>(result, params, "Conversion");
5961
}
62+
63+
void ConversionSpecifyInputLayerTest::generate_inputs(const std::vector<ov::Shape>& targetInputStaticShapes) {
64+
const auto& [conversion_type, shapes, input_type, convert_type, _targetDevice] = GetParam();
65+
if (input_type != ov::element::i32 || convert_type != ov::element::u8) {
66+
SubgraphBaseTest::generate_inputs(targetInputStaticShapes);
67+
return;
68+
}
69+
70+
inputs.clear();
71+
const auto& funcInputs = function->inputs();
72+
const auto& funcInput = funcInputs[0];
73+
ov::Tensor tensor(funcInput.get_element_type(), targetInputStaticShapes[0]);
74+
ov::test::utils::fill_data_random(tensor.data<int32_t>(), tensor.get_size(), 1024, -512);
75+
inputs.insert({funcInput.get_node_shared_ptr(), tensor});
76+
}
77+
6078
} // namespace test
6179
} // namespace ov

0 commit comments

Comments
 (0)