diff --git a/inference-engine/tests/functional/inference_engine/serialization/single_layer/convert.cpp b/inference-engine/tests/functional/inference_engine/serialization/single_layer/convert.cpp index c56d444dcdc94b..e6d6bc19ed7d6d 100644 --- a/inference-engine/tests/functional/inference_engine/serialization/single_layer/convert.cpp +++ b/inference-engine/tests/functional/inference_engine/serialization/single_layer/convert.cpp @@ -12,13 +12,14 @@ namespace { const std::vector> inShape = {{1, 2, 3, 4}}; const std::vector precisions = { - InferenceEngine::Precision::BOOL, InferenceEngine::Precision::U8, - InferenceEngine::Precision::I8, InferenceEngine::Precision::U16, - InferenceEngine::Precision::I16, InferenceEngine::Precision::U32, - InferenceEngine::Precision::I32, InferenceEngine::Precision::U64, - InferenceEngine::Precision::I64, InferenceEngine::Precision::BF16, - InferenceEngine::Precision::FP16, InferenceEngine::Precision::FP32, - InferenceEngine::Precision::FP64}; + InferenceEngine::Precision::BOOL, InferenceEngine::Precision::BIN, + InferenceEngine::Precision::U4, InferenceEngine::Precision::U8, + InferenceEngine::Precision::I4, InferenceEngine::Precision::I8, + InferenceEngine::Precision::U16, InferenceEngine::Precision::I16, + InferenceEngine::Precision::U32, InferenceEngine::Precision::I32, + InferenceEngine::Precision::U64, InferenceEngine::Precision::I64, + InferenceEngine::Precision::BF16, InferenceEngine::Precision::FP16, + InferenceEngine::Precision::FP32, InferenceEngine::Precision::FP64}; TEST_P(ConvertLayerTest, Serialize) { Serialize(); diff --git a/inference-engine/tests/ie_test_utils/functional_test_utils/layer_tests_summary/utils/constants.py b/inference-engine/tests/ie_test_utils/functional_test_utils/layer_tests_summary/utils/constants.py index 59df352e9d5506..e0b2e3c23e9039 100644 --- a/inference-engine/tests/ie_test_utils/functional_test_utils/layer_tests_summary/utils/constants.py +++ b/inference-engine/tests/ie_test_utils/functional_test_utils/layer_tests_summary/utils/constants.py @@ -16,6 +16,7 @@ 'CTCGreedyDecoder-1', 'CTCGreedyDecoderSeqLen-6', 'Concat-1', + 'Convert-1', 'ConvertLike-1', 'Convolution-1', 'Constant-1', diff --git a/ngraph/core/reference/include/ngraph/runtime/reference/convert.hpp b/ngraph/core/reference/include/ngraph/runtime/reference/convert.hpp index ecaf92ffd6184a..2fce199f7bbf7d 100644 --- a/ngraph/core/reference/include/ngraph/runtime/reference/convert.hpp +++ b/ngraph/core/reference/include/ngraph/runtime/reference/convert.hpp @@ -6,6 +6,7 @@ #include +#include "ngraph/type/element_type.hpp" #include "ngraph/type/float16.hpp" namespace ngraph @@ -14,6 +15,118 @@ namespace ngraph { namespace reference { + namespace detail + { + inline void set_u1(uint8_t* buf, size_t idx, uint8_t val) + { + const int byte_idx = idx / 8; + const int bit_idx = 7 - (idx % 8); + if (val) + { + buf[byte_idx] |= (1 << bit_idx); + } + else + { + buf[byte_idx] &= ~(1 << bit_idx); + } + } + + inline uint8_t get_u1(const uint8_t* buf, size_t idx) + { + const int byte_idx = idx / 8; + const int bit_idx = 7 - (idx % 8); + return (buf[byte_idx] & (1 << bit_idx)) ? 1 : 0; + } + + inline void set_u4(uint8_t* buf, size_t idx, uint8_t val) + { + const int byte_idx = idx / 2; + const int bit_shift = 4 * (++idx % 2); + buf[byte_idx] &= ~(0xF << bit_shift); // half byte zeroed + buf[byte_idx] |= (val << bit_shift); // set 1's + } + + inline uint8_t get_u4(const uint8_t* buf, size_t idx) + { + const int byte_idx = idx / 2; + const int bit_shift = 4 * (++idx % 2); + return (buf[byte_idx] >> bit_shift) & 0xF; + } + + inline void set_i4(uint8_t* buf, size_t idx, int8_t val) + { + const int byte_idx = idx / 2; + const int bit_shift = 4 * (++idx % 2); + buf[byte_idx] &= ~(0xF << bit_shift); // half byte zeroed + buf[byte_idx] |= (val << bit_shift); // set 1's + } + + inline int8_t get_i4(const uint8_t* buf, size_t idx) + { + const int byte_idx = idx / 2; + const int bit_shift = 4 * (++idx % 2); + uint8_t val = (buf[byte_idx] >> bit_shift) & 0xF; + if (val & 0x08) + { // negative number + val |= 0xF0; + } + return val; + } + template + T get_value(const uint8_t* buf, size_t idx, element::Type type) + { + if (type == element::u1) + { + return detail::get_u1(buf, idx); + } + + if (type == element::u4) + { + return detail::get_u4(buf, idx); + } + + if (type == element::i4) + { + return detail::get_i4(buf, idx); + } + + return static_cast(buf[idx]); + } + + template + void lp_convert(const TI* arg, + TO* out, + size_t count, + element::Type_t src_type, + element::Type_t dst_type) + { + const uint8_t* input = reinterpret_cast(arg); + uint8_t* output = reinterpret_cast(out); + for (size_t i = 0; i < count; ++i) + { + if (dst_type == element::u1) + { + detail::set_u1( + output, i, detail::get_value(input, i, src_type)); + } + else if (dst_type == element::u4) + { + detail::set_u4( + output, i, detail::get_value(input, i, src_type)); + } + else if (dst_type == element::i4) + { + detail::set_i4( + output, i, detail::get_value(input, i, src_type)); + } + else + { + out[i] = detail::get_value(input, i, src_type); + } + } + } + } // namespace detail + template typename std::enable_if::value>::type convert(const TI* arg, TO* out, size_t count) diff --git a/ngraph/core/src/op/convert.cpp b/ngraph/core/src/op/convert.cpp index 8999113ba08133..4f585f30572b72 100644 --- a/ngraph/core/src/op/convert.cpp +++ b/ngraph/core/src/op/convert.cpp @@ -26,22 +26,6 @@ op::Convert::Convert(const Output& arg, const element::Type& destination_t void op::Convert::validate_and_infer_types() { NGRAPH_OP_SCOPE(v0_Convert_validate_and_infer_types); - const element::Type data_et = get_input_element_type(0); - const element::Type destination_et = m_destination_type; - - NODE_VALIDATION_CHECK(this, - data_et != element::u1 && data_et != element::u4 && - data_et != element::i4, - "Input element type '", - data_et, - "' is not supported."); - - NODE_VALIDATION_CHECK(this, - destination_et != element::u1 && destination_et != element::u4 && - destination_et != element::i4, - "Destination element type '", - destination_et, - "' is not supported."); set_output_type(0, m_destination_type, get_input_partial_shape(0)); } @@ -68,10 +52,27 @@ namespace convert { out->set_shape(arg->get_shape()); size_t element_count = shape_size(out->get_shape()); - return (INPUT_ET == arg->get_element_type()) && OUTPUT_ET == out->get_element_type() && - (runtime::reference::convert( - arg->get_data_ptr(), out->get_data_ptr(), element_count), - true); + + if ((INPUT_ET != arg->get_element_type()) || OUTPUT_ET != out->get_element_type()) + { + return false; + } + if (((INPUT_ET == element::u1) || (OUTPUT_ET == element::u1)) || + ((INPUT_ET == element::u4) || (OUTPUT_ET == element::u4)) || + ((INPUT_ET == element::i4) || (OUTPUT_ET == element::i4))) + { + runtime::reference::detail::lp_convert(arg->get_data_ptr(), + out->get_data_ptr(), + element_count, + INPUT_ET, + OUTPUT_ET); + } + else + { + runtime::reference::convert( + arg->get_data_ptr(), out->get_data_ptr(), element_count); + } + return true; } #define TYPE_OUT_CASE(a, ...) \ @@ -89,10 +90,13 @@ namespace convert switch (out->get_element_type()) { + TYPE_OUT_CASE(i4, arg, out); TYPE_OUT_CASE(i8, arg, out); TYPE_OUT_CASE(i16, arg, out); TYPE_OUT_CASE(i32, arg, out); TYPE_OUT_CASE(i64, arg, out); + TYPE_OUT_CASE(u1, arg, out); + TYPE_OUT_CASE(u4, arg, out); TYPE_OUT_CASE(u8, arg, out); TYPE_OUT_CASE(u16, arg, out); TYPE_OUT_CASE(u32, arg, out); @@ -112,7 +116,10 @@ namespace convert bool rc = true; switch (arg->get_element_type()) { + NGRAPH_TYPE_CASE(evaluate_convert, u1, arg, out); + NGRAPH_TYPE_CASE(evaluate_convert, u4, arg, out); NGRAPH_TYPE_CASE(evaluate_convert, u8, arg, out); + NGRAPH_TYPE_CASE(evaluate_convert, i4, arg, out); NGRAPH_TYPE_CASE(evaluate_convert, i8, arg, out); NGRAPH_TYPE_CASE(evaluate_convert, i32, arg, out); NGRAPH_TYPE_CASE(evaluate_convert, i16, arg, out); diff --git a/ngraph/test/backend/convert.in.cpp b/ngraph/test/backend/convert.in.cpp index 730fc13cd50f47..a3abdd87af311b 100644 --- a/ngraph/test/backend/convert.in.cpp +++ b/ngraph/test/backend/convert.in.cpp @@ -23,6 +23,15 @@ static string s_manifest = "${MANIFEST}"; using TestEngine = test::ENGINE_CLASS_NAME(${BACKEND_NAME}); namespace { + std::shared_ptr CreateFunction(const Shape& input_shape, + const element::Type& input_type, + const element::Type& expected_output_type) + { + const auto in = make_shared(input_type, input_shape); + const auto convert = make_shared(in, expected_output_type); + return make_shared(NodeVector{convert}, ParameterVector{in}); + } + template void ConvertTest(const std::vector& input, const Shape& input_shape, @@ -30,16 +39,34 @@ namespace const std::vector& expected_output, const ngraph::element::Type& expected_output_type) { - const auto in = make_shared(input_type, input_shape); - const auto convert = make_shared(in, expected_output_type); - const auto f = make_shared(NodeVector{convert}, ParameterVector{in}); - + const auto f = CreateFunction(input_shape, input_type, expected_output_type); auto test_case = test::TestCase(f); test_case.add_input(input); test_case.add_expected_output(expected_output); test_case.run(); } + + // TestCase doesn't support LP types + template + void LPConvertTest(const std::vector& input, + const Shape& input_shape, + const ngraph::element::Type& input_type, + const std::vector& expected_output, + const ngraph::element::Type& expected_output_type) + { + const auto f = CreateFunction(input_shape, input_type, expected_output_type); + auto backend = runtime::Backend::create("${BACKEND_NAME}"); + auto input_tesnor = backend->create_tensor(input_type, input_shape); + copy_data(input_tesnor, input); + auto output = backend->create_tensor(expected_output_type, input_shape); + auto handle = backend->compile(f); + handle->call_with_validate({output}, {input_tesnor}); + + std::vector result(expected_output.size()); + output->read(result.data(), result.size() * sizeof(uint8_t)); + EXPECT_TRUE(test::all_close(expected_output, result)); + } } // namespace // destination: boolean @@ -119,17 +146,28 @@ NGRAPH_TEST(${BACKEND_NAME}, convert_u8_to_f16) } // destination: f32 -NGRAPH_TEST(${BACKEND_NAME}, convert_i4_to_f32_is_not_supported_yet) +NGRAPH_TEST(${BACKEND_NAME}, convert_i4_to_f32) { - const std::vector input{0x00, 0x00}; + const std::vector input{0xFE, 0xF2}; const Shape input_shape{2, 2}; const element::Type input_type = ngraph::element::i4; - const std::vector expected_output{0.0f, 0.0f, 0.0f, 0.0f}; + const std::vector expected_output{-1.0f, -2.0f, -1.0f, 2.0f}; const element::Type expected_output_type = ngraph::element::f32; - ASSERT_THROW(ConvertTest(input, input_shape, input_type, expected_output, expected_output_type), - ngraph::NodeValidationFailure); + { + const auto f = CreateFunction(input_shape, input_type, expected_output_type); + auto backend = runtime::Backend::create("${BACKEND_NAME}"); + auto input_tesnor = backend->create_tensor(input_type, input_shape); + copy_data(input_tesnor, input); + auto output = backend->create_tensor(expected_output_type, input_shape); + auto handle = backend->compile(f); + handle->call_with_validate({output}, {input_tesnor}); + + std::vector result(expected_output.size()); + output->read(result.data(), result.size() * sizeof(float)); + EXPECT_TRUE(test::all_close_f(expected_output, result)); + } } NGRAPH_TEST(${BACKEND_NAME}, convert_i8_to_f32) @@ -180,30 +218,52 @@ NGRAPH_TEST(${BACKEND_NAME}, convert_i64_to_f32) ConvertTest(input, input_shape, input_type, expected_output, expected_output_type); } -NGRAPH_TEST(${BACKEND_NAME}, convert_u1_to_f32_is_not_supported_yet) +NGRAPH_TEST(${BACKEND_NAME}, convert_u1_to_f32) { - const std::vector input{0x00}; + const std::vector input{0xA0}; const Shape input_shape{2, 2}; const element::Type input_type = ngraph::element::u1; - const std::vector expected_output{0.0f, 0.0f, 0.0f, 0.0f}; + const std::vector expected_output{1.0f, 0.0f, 1.0f, 0.0f}; const element::Type expected_output_type = ngraph::element::f32; - ASSERT_THROW(ConvertTest(input, input_shape, input_type, expected_output, expected_output_type), - ngraph::NodeValidationFailure); + { + const auto f = CreateFunction(input_shape, input_type, expected_output_type); + auto backend = runtime::Backend::create("${BACKEND_NAME}"); + auto input_tesnor = backend->create_tensor(input_type, input_shape); + copy_data(input_tesnor, input); + auto output = backend->create_tensor(expected_output_type, input_shape); + auto handle = backend->compile(f); + handle->call_with_validate({output}, {input_tesnor}); + + std::vector result(expected_output.size()); + output->read(result.data(), result.size() * sizeof(float)); + EXPECT_TRUE(test::all_close_f(expected_output, result)); + } } -NGRAPH_TEST(${BACKEND_NAME}, convert_u4_to_f32_is_not_supported_yet) +NGRAPH_TEST(${BACKEND_NAME}, convert_u4_to_f32) { - const std::vector input{0x00, 0x00}; + const std::vector input{0xFB, 0x0A}; const Shape input_shape{2, 2}; const element::Type input_type = ngraph::element::u4; - const std::vector expected_output{0.0f, 0.0f, 0.0f, 0.0f}; + const std::vector expected_output{15.0f, 11.0f, 0.0f, 10.0f}; const element::Type expected_output_type = ngraph::element::f32; - ASSERT_THROW(ConvertTest(input, input_shape, input_type, expected_output, expected_output_type), - ngraph::NodeValidationFailure); + { + const auto f = CreateFunction(input_shape, input_type, expected_output_type); + auto backend = runtime::Backend::create("${BACKEND_NAME}"); + auto input_tesnor = backend->create_tensor(input_type, input_shape); + copy_data(input_tesnor, input); + auto output = backend->create_tensor(expected_output_type, input_shape); + auto handle = backend->compile(f); + handle->call_with_validate({output}, {input_tesnor}); + + std::vector result(expected_output.size()); + output->read(result.data(), result.size() * sizeof(float)); + EXPECT_TRUE(test::all_close_f(expected_output, result)); + } } NGRAPH_TEST(${BACKEND_NAME}, convert_u8_to_f32) @@ -295,17 +355,28 @@ NGRAPH_TEST(${BACKEND_NAME}, convert_f32_to_f32) // not supported by IE, hence no tests // destination: i4 -NGRAPH_TEST(${BACKEND_NAME}, convert_u8_to_i4_is_not_supported_yet) +NGRAPH_TEST(${BACKEND_NAME}, convert_u8_to_i4) { - const std::vector input{0, 0, 0, 0}; + const std::vector input{1, 2, 0, 3}; const Shape input_shape{4}; const element::Type input_type = ngraph::element::u8; - const std::vector expected_output{0x00, 0x00}; + const std::vector expected_output{0x12, 0x03}; const element::Type expected_output_type = ngraph::element::i4; - ASSERT_THROW(ConvertTest(input, input_shape, input_type, expected_output, expected_output_type), - ngraph::NodeValidationFailure); + { + const auto f = CreateFunction(input_shape, input_type, expected_output_type); + auto backend = runtime::Backend::create("${BACKEND_NAME}"); + auto input_tesnor = backend->create_tensor(input_type, input_shape); + copy_data(input_tesnor, input); + auto output = backend->create_tensor(expected_output_type, input_shape); + auto handle = backend->compile(f); + handle->call_with_validate({output}, {input_tesnor}); + + std::vector result(expected_output.size()); + output->read(result.data(), result.size() * sizeof(uint8_t)); + EXPECT_TRUE(test::all_close(expected_output, result)); + } } // destination: i8 @@ -361,31 +432,102 @@ NGRAPH_TEST(${BACKEND_NAME}, convert_u8_to_i64) } // destination: u1 -NGRAPH_TEST(${BACKEND_NAME}, convert_u8_to_u1_is_not_supported_yet) +NGRAPH_TEST(${BACKEND_NAME}, convert_u1_to_u1) { - const std::vector input{0, 0, 0, 0}; - const Shape input_shape{4}; + const std::vector input{0xF0}; + const Shape input_shape{8}; + const element::Type input_type = ngraph::element::u1; + + const std::vector expected_output{0xF0}; + const element::Type expected_output_type = ngraph::element::u1; + + LPConvertTest(input, input_shape, input_type, expected_output, expected_output_type); +} + +NGRAPH_TEST(${BACKEND_NAME}, convert_u4_to_u1) +{ + const std::vector input{0x10, 0x01, 0x00, 0x00}; + const Shape input_shape{8}; + const element::Type input_type = ngraph::element::u4; + + const std::vector expected_output{0x90}; + const element::Type expected_output_type = ngraph::element::u1; + + LPConvertTest(input, input_shape, input_type, expected_output, expected_output_type); +} + +NGRAPH_TEST(${BACKEND_NAME}, convert_u8_to_u1) +{ + const std::vector input{1, 0, 1, 0, 0, 0, 0, 1}; + const Shape input_shape{8}; const element::Type input_type = ngraph::element::u8; - const std::vector expected_output{0x00}; + const std::vector expected_output{0xA1}; const element::Type expected_output_type = ngraph::element::u1; - ASSERT_THROW(ConvertTest(input, input_shape, input_type, expected_output, expected_output_type), - ngraph::NodeValidationFailure); + LPConvertTest(input, input_shape, input_type, expected_output, expected_output_type); +} + +NGRAPH_TEST(${BACKEND_NAME}, convert_i4_to_u1) +{ + const std::vector input{0x10, 0x01, 0x00, 0x00}; + const Shape input_shape{8}; + const element::Type input_type = ngraph::element::u4; + + const std::vector expected_output{0x90}; + const element::Type expected_output_type = ngraph::element::u1; + + LPConvertTest(input, input_shape, input_type, expected_output, expected_output_type); } // destination: u4 -NGRAPH_TEST(${BACKEND_NAME}, convert_u8_to_u4_is_not_supported_yet) +NGRAPH_TEST(${BACKEND_NAME}, convert_u1_to_u4) { - const std::vector input{0, 0, 0, 0}; + const std::vector input{0xF0}; + const Shape input_shape{4}; + const element::Type input_type = ngraph::element::u1; + + const std::vector expected_output{0x11, 0x11}; + const element::Type expected_output_type = ngraph::element::u4; + + LPConvertTest(input, input_shape, input_type, expected_output, expected_output_type); +} + +NGRAPH_TEST(${BACKEND_NAME}, convert_u4_to_u4) +{ + const std::vector input{0x22, 0x33}; + const Shape input_shape{4}; + const element::Type input_type = ngraph::element::u4; + + const std::vector expected_output{0x22, 0x33}; + const element::Type expected_output_type = ngraph::element::u4; + + LPConvertTest(input, input_shape, input_type, expected_output, expected_output_type); +} + + +NGRAPH_TEST(${BACKEND_NAME}, convert_u8_to_u4) +{ + const std::vector input{7, 0, 1, 15}; const Shape input_shape{4}; const element::Type input_type = ngraph::element::u8; - const std::vector expected_output{0x00, 0x00}; + const std::vector expected_output{0x70, 0x1F}; + const element::Type expected_output_type = ngraph::element::u4; + + LPConvertTest(input, input_shape, input_type, expected_output, expected_output_type); +} + +NGRAPH_TEST(${BACKEND_NAME}, convert_i4_to_u4) +{ + const std::vector input{0x22, 0x33}; + const Shape input_shape{4}; + const element::Type input_type = ngraph::element::i4; + + const std::vector expected_output{0x22, 0x33}; const element::Type expected_output_type = ngraph::element::u4; - ASSERT_THROW(ConvertTest(input, input_shape, input_type, expected_output, expected_output_type), - ngraph::NodeValidationFailure); + LPConvertTest(input, input_shape, input_type, expected_output, expected_output_type); } // destination: u8 diff --git a/ngraph/test/runtime/ie/unit_test.manifest b/ngraph/test/runtime/ie/unit_test.manifest index 9e9fe8ee49e075..dcb0b6cd288c39 100644 --- a/ngraph/test/runtime/ie/unit_test.manifest +++ b/ngraph/test/runtime/ie/unit_test.manifest @@ -1050,8 +1050,11 @@ roll_negative_axes # convert operation IE_CPU.convert_f16_to_f32 IE_CPU.convert_u8_to_f16 +IE_CPU.convert_u8_to_i4 IE_CPU.convert_u8_to_i16 IE_CPU.convert_u8_to_i64 +IE_CPU.convert_u8_to_u1 +IE_CPU.convert_u8_to_u4 IE_CPU.convert_u8_to_u16 IE_CPU.convert_u8_to_u32 IE_CPU.convert_u8_to_u64 @@ -1062,6 +1065,12 @@ IE_CPU.convert_u32_to_f32 # NOT_IMPLEMENTED IE_CPU.convert_i4_to_f32 # NOT_IMPLEMENTED IE_CPU.convert_u1_to_f32 # NOT_IMPLEMENTED IE_CPU.convert_u4_to_f32 # NOT_IMPLEMENTED +IE_CPU.convert_u1_to_u1 +IE_CPU.convert_u4_to_u1 +IE_CPU.convert_i4_to_u1 +IE_CPU.convert_u1_to_u4 +IE_CPU.convert_u4_to_u4 +IE_CPU.convert_i4_to_u4 #------------------------------------------------------------------------------- #