diff --git a/src/bindings/python/src/openvino/frontend/pytorch/fx_decoder.py b/src/bindings/python/src/openvino/frontend/pytorch/fx_decoder.py index 45a662e4e45..a79892b3e4d 100644 --- a/src/bindings/python/src/openvino/frontend/pytorch/fx_decoder.py +++ b/src/bindings/python/src/openvino/frontend/pytorch/fx_decoder.py @@ -370,7 +370,7 @@ class TorchFXPythonDecoder (Decoder): return result def may_produce_alias(self, in_index: int, out_index: int) -> bool: - if self.get_op_type() in ["aten::conv1d", "aten::conv2d", "aten::conv3d"]: + if self.get_op_type() in ["aten::conv1d", "aten::conv2d", "aten::conv3d", "aten::matmul"]: # AliasDB::may_contain_alias sometimes return True for tensors produced by convnd, we have to workaround that return False try: diff --git a/src/bindings/python/src/openvino/frontend/pytorch/gptq.py b/src/bindings/python/src/openvino/frontend/pytorch/gptq.py new file mode 100644 index 00000000000..b4bd06552b2 --- /dev/null +++ b/src/bindings/python/src/openvino/frontend/pytorch/gptq.py @@ -0,0 +1,140 @@ + +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# flake8: noqa +# mypy: ignore-errors + +import torch +from functools import partial + +# Wraps a single tensor to a module to prevent it from jit.freezing +# It depends on a tensor dtype whether it will be preserved from freezing. Refer to the decoder code to learn which types will be preserved. +class KeepWeight(torch.nn.Module): + + def __init__(self, weight): + super().__init__() + self.weight = torch.nn.Parameter(weight, requires_grad=False) + + def forward(self): + return self.weight + + +# Produces a pattern that can be captured later and represented as a single u4 constant node +def decompression_pattern(weights): + mask = torch.tensor(15, dtype=torch.uint8).to(weights.device) + return torch.stack((torch.bitwise_and(weights, mask), torch.bitwise_right_shift(weights, 4)), dim=-1) + + +def patched_forward(self, *args, **kwargs): + if hasattr(self, '_hf_hook'): + args, kwargs = self._hf_hook.pre_forward(self, *args, **kwargs) + + x = args[0] + dtype = x.dtype + outshape = x.shape[:-1] + (self.width,) + x = x.view(-1, x.shape[-1]) + groups = self.qzeros.shape[0] + height = self.qweight.shape[0] + + unpacked_weights = decompression_pattern( + self._openvino_u4_compression_submodule_qweights()).contiguous().view(height, -1, 8) + unpacked_weights = torch.transpose( + unpacked_weights, 1, 2).contiguous().view(-1, self.group_size, self.width) + unpacked_zp = decompression_pattern( + self._openvino_u4_compression_submodule_qzeros()).contiguous().view(groups, 1, -1) + + unpacked_zp = unpacked_zp.to(dtype) + 1 + + unpacked_weights = (unpacked_weights.to(dtype) - unpacked_zp) * self.scales + unpacked_weights = unpacked_weights.view(-1, self.width) + + out = x @ unpacked_weights + + out = out.view(outshape) + if self.bias is not None: + out.add_(self.bias) + + if hasattr(self, '_hf_hook'): + out = self._hf_hook.post_forward(self, out) + return out + + +# All the following AutoGPTQ's quant types are supposed to have the same weights packing schema +supported_quant_types = ['triton', 'exllama', 'cuda', 'exllamav2', 'cuda-old'] + + +def patch_model(model): + for name, m in model.named_modules(): + if hasattr(m, '_openvino_patch_orig_forward'): + # already patched, skipping + continue + # TODO: Check module type + is_quantized = getattr(m, 'is_quantized', None) + if is_quantized is not None: + m.is_quantized = False + m.float() # enables tracing on CPU, applied for all modules + if hasattr(m, 'QUANT_TYPE'): + if m.QUANT_TYPE not in supported_quant_types: + raise ValueError( + f'Unsupported QUANT_TYPE == {m.QUANT_TYPE} is discovered for AutoGPTQ model, only the following types are supported: {supported_quant_types}') + if m.bits != 4: + raise ValueError( + f'Unsupported bits == {m.bits} is discovered in module {name} in AutoGPTQ model, only bits == 4 is supported.') + + int4_in_int32 = 8 + groups = m.qzeros.shape[0] + m.width = m.qweight.shape[1] + assert m.group_size == m.qweight.shape[0] * int4_in_int32 // groups + + m._openvino_patch_orig_forward = m.forward + m.forward = partial(patched_forward, m) + + # Keep original field properties to be used when model is returned back to its original state + m._openvino_patch_orig_qweights_type = m.qweight.dtype + m._openvino_patch_orig_qzeros_type = m.qzeros.dtype + m._openvino_patch_orig_scale_shape = m.scales.shape + + m.qweight = m.qweight.view(dtype=torch.uint8) + m.qzeros = m.qzeros.view(dtype=torch.uint8) + + # TODO: Redundant tensor copy? Try to remove m.qweigh and m.qzeros after keeping modified values as submodules + m.add_module( + '_openvino_u4_compression_submodule_qweights', KeepWeight(m.qweight)) + m.add_module('_openvino_u4_compression_submodule_qzeros', + KeepWeight(m.qzeros)) + + m.scales = m.scales.view(-1, 1, m.width) + + +def unpatch_model(model): + for _, m in model.named_modules(): + if hasattr(m, '_openvino_patch_orig_forward'): + try: + m.forward = m._openvino_patch_orig_forward + del m._openvino_patch_orig_forward + + m.qweight = m.qweight.view( + dtype=m._openvino_patch_orig_qweights_type) + del m._openvino_patch_orig_qweights_type + + m.qzeros = m.qzeros.view( + dtype=m._openvino_patch_orig_qzeros_type) + del m._openvino_patch_orig_qzeros_type + + m.scales = m.scales.view(m._openvino_patch_orig_scale_shape) + del m._openvino_patch_orig_scale_shape + + del m._openvino_u4_compression_submodule_qweights + del m._openvino_u4_compression_submodule_qzeros + except Exception as error: + print('[ WARNING ] Exception raised during GPTQ model unpatching. Depending on the exact issue it may lead to broken original model') + print(error) + + +def detect_gptq_model_raw(model): + return model and getattr(model, 'config', None) and getattr(model.config, 'quantization_config', None) and model.config.quantization_config.quant_method == 'gptq' + + +def detect_gptq_model(model): + return detect_gptq_model_raw(model) or getattr(model, 'model', None) and detect_gptq_model_raw(model.model) diff --git a/src/bindings/python/src/openvino/frontend/pytorch/ts_decoder.py b/src/bindings/python/src/openvino/frontend/pytorch/ts_decoder.py index b6caf22cfc7..11d5991e700 100644 --- a/src/bindings/python/src/openvino/frontend/pytorch/ts_decoder.py +++ b/src/bindings/python/src/openvino/frontend/pytorch/ts_decoder.py @@ -9,6 +9,7 @@ from openvino.frontend.pytorch.py_pytorch_frontend import _Type as DecoderType from openvino.runtime import op, PartialShape, Type as OVType, OVAny from openvino.frontend.pytorch.utils import ivalue_to_constant, get_value_from_getattr, pt_to_ov_type_map, prepare_example_inputs_and_model, convert_quantized_tensor from openvino.runtime import opset11 as ops +from openvino.frontend.pytorch import gptq import typing import torch @@ -84,8 +85,27 @@ class TorchScriptPythonDecoder (Decoder): if example_inputs is None: scripted = torch.jit.script(pt_module) else: - input_parameters, input_signature, pt_module, self._input_is_list = prepare_example_inputs_and_model(example_inputs, input_params, pt_module) - scripted = torch.jit.trace(pt_module, **input_parameters, strict=False) + input_parameters, input_signature, pt_module, self._input_is_list = prepare_example_inputs_and_model( + example_inputs, input_params, pt_module) + gptq_patched = False + + if gptq.detect_gptq_model(pt_module): + try: + gptq.patch_model(pt_module) + gptq_patched = True + except Exception as error: + print('[ WARNING ] Failed patching of AutoGPTQ model. Error message:\n', error) + print('[ WARNING ] Tracing of the model will likely be unsuccesfull or incorrect') + gptq.unpatch_model(pt_module) + gptq_patched = False + + try: + scripted = torch.jit.trace( + pt_module, **input_parameters, strict=False) + finally: + if gptq_patched: + gptq.unpatch_model(pt_module) + if not skip_freeze: for n in scripted.inlined_graph.nodes(): # TODO: switch off freezing for all traced models @@ -341,7 +361,7 @@ class TorchScriptPythonDecoder (Decoder): return False def may_produce_alias(self, in_index: int, out_index: int) -> bool: - if self.get_op_type() in ["aten::conv1d", "aten::conv2d", "aten::conv3d"]: + if self.get_op_type() in ["aten::conv1d", "aten::conv2d", "aten::conv3d", "aten::matmul"]: # AliasDB::may_contain_alias sometimes return True for tensors produced by convnd, we have to workaround that return False try: diff --git a/src/common/transformations/tests/utils/convert_precision.cpp b/src/common/transformations/tests/utils/convert_precision.cpp index 6c0da965f9b..1dac080461d 100644 --- a/src/common/transformations/tests/utils/convert_precision.cpp +++ b/src/common/transformations/tests/utils/convert_precision.cpp @@ -1188,7 +1188,7 @@ void constant_convert_test(element::Type type_from, } ASSERT_TRUE(actual.size() >= expected.size()); for (size_t i = 0; i < expected.size(); i++) { - ASSERT_EQ(expected[i], actual[i]); + EXPECT_EQ(expected[i], actual[i]) << "Elements with index " << i << " are not equal."; } } @@ -1378,7 +1378,7 @@ TEST(TransformationTests, ConvertPrecision_ConstantConversion_U1ToU4) { constant_convert_test(element::u1, element::u4, std::vector{171}, - {1, 0, 1, 0, 1, 0, 1, 1}); + {0, 1, 0, 1, 0, 1, 1, 1}); } TEST(TransformationTests, ConvertPrecision_keep_precission_sensitive_fp32_with_exp) { diff --git a/src/core/include/openvino/op/constant.hpp b/src/core/include/openvino/op/constant.hpp index 14ee7b33134..e122d36a822 100644 --- a/src/core/include/openvino/op/constant.hpp +++ b/src/core/include/openvino/op/constant.hpp @@ -426,7 +426,7 @@ private: typename StorageDataType = fundamental_type_for, typename std::enable_if::type = true> StorageDataType get_element_value(size_t index) const { - return (get_data_ptr()[index / 2] >> (index % 2 ? 0 : 4)) & 0x0F; + return (get_data_ptr()[index / 2] >> (index % 2 ? 4 : 0)) & 0x0F; } template , typename std::enable_if::type = true> StorageDataType get_element_value(size_t index) const { - const uint8_t i4data = (get_data_ptr()[index / 2] >> (index % 2 ? 0 : 4)) & 0x0F; + const uint8_t i4data = (get_data_ptr()[index / 2] >> (index % 2 ? 4 : 0)) & 0x0F; const bool is_negative_number = (i4data >> 3) & 0x01; const int8_t data = is_negative_number ? i4data | 0xF0 : i4data; return data; @@ -530,7 +530,7 @@ private: const auto round_element_no = element_number % 2 ? element_number + 1 : element_number; output.reserve(round_element_no); // adds 1 more elements here? std::for_each(source_begin, source_end, [&](IN_T c) { - for (const auto i : {4, 0}) { + for (const auto i : {0, 4}) { const uint8_t data = (c >> i) & 0x0F; output.push_back(data); } @@ -548,7 +548,7 @@ private: const auto round_element_no = element_number % 2 ? element_number + 1 : element_number; output.reserve(round_element_no); // adds 1 more elements here? std::for_each(source_begin, source_end, [&](IN_T c) { - for (const auto i : {4, 0}) { + for (const auto i : {0, 4}) { const uint8_t i4data = (c >> i) & 0x0F; const bool is_negative_number = (i4data >> 3) & 0x01; const int8_t data = is_negative_number ? i4data | 0xF0 : i4data; @@ -663,27 +663,9 @@ private: template , - typename std::enable_if::type = true> - void write_buffer(const std::vector& source) { - auto p = get_data_ptr_nc(); - size_t i = 0; - for (; i < source.size() / 2; i++) { - const auto v1 = value_in_range(source[i * 2]) & 0x0F; - const auto v2 = value_in_range(source[i * 2 + 1]) & 0x0F; - const auto v = (v1 << 4) | v2; - p[i] = static_cast(v); - } - if (source.size() % 2) { - const auto v1 = value_in_range(source[i * 2]) & 0x0F; - const auto v = v1 << 4; - p[i] = static_cast(v); - } - } - - template , - typename std::enable_if::value, bool>::type = true> + typename std::enable_if::value), + bool>::type = true> void write_buffer(const std::vector& source) { auto p = get_data_ptr_nc(); size_t i = 0; diff --git a/src/core/reference/include/openvino/reference/convert.hpp b/src/core/reference/include/openvino/reference/convert.hpp index e943e548a8f..bd36b50b033 100644 --- a/src/core/reference/include/openvino/reference/convert.hpp +++ b/src/core/reference/include/openvino/reference/convert.hpp @@ -14,7 +14,7 @@ namespace reference { namespace detail { inline void set_u1(uint8_t* buf, size_t idx, uint8_t val) { const size_t byte_idx = idx / 8; - const uint8_t bit_idx = 7 - (idx % 8); + const uint8_t bit_idx = 7 - (idx % 8); // Reversed order of bits if (val) { buf[byte_idx] |= (1 << bit_idx); } else { @@ -24,33 +24,33 @@ inline void set_u1(uint8_t* buf, size_t idx, uint8_t val) { inline uint8_t get_u1(const uint8_t* buf, size_t idx) { const size_t byte_idx = idx / 8; - const uint8_t bit_idx = 7 - (idx % 8); + const uint8_t bit_idx = 7 - (idx % 8); // Reversed order of bits return (buf[byte_idx] & (1 << bit_idx)) ? 1 : 0; } inline void set_u4(uint8_t* buf, size_t idx, uint8_t val) { const size_t byte_idx = idx / 2; - const uint8_t bit_shift = 4 * (++idx % 2); + const uint8_t bit_shift = 4 * (idx % 2); buf[byte_idx] &= ~(0xF << bit_shift); // half byte zeroed buf[byte_idx] |= ((val & 0xF) << bit_shift); // set 1's } inline uint8_t get_u4(const uint8_t* buf, size_t idx) { const size_t byte_idx = idx / 2; - const uint8_t bit_shift = 4 * (++idx % 2); + const uint8_t 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 size_t byte_idx = idx / 2; - const uint8_t bit_shift = 4 * (++idx % 2); + const uint8_t bit_shift = 4 * (idx % 2); buf[byte_idx] &= ~(0xF << bit_shift); // half byte zeroed buf[byte_idx] |= ((val & 0xF) << bit_shift); // set 1's } inline int8_t get_i4(const uint8_t* buf, size_t idx) { const size_t byte_idx = idx / 2; - const uint8_t bit_shift = 4 * (++idx % 2); + const uint8_t bit_shift = 4 * (idx % 2); uint8_t val = (buf[byte_idx] >> bit_shift) & 0xF; if (val & 0x08) { // negative number val |= 0xF0; diff --git a/src/core/tests/constant.cpp b/src/core/tests/constant.cpp index 45ad60d1536..0feefb84bed 100644 --- a/src/core/tests/constant.cpp +++ b/src/core/tests/constant.cpp @@ -266,8 +266,8 @@ TEST(constant, int4_string) { EXPECT_EQ(v[2], -1); const auto p = c.get_data_ptr(); - EXPECT_EQ(0x10, p[0]); - EXPECT_EQ(0xF0, p[1] & 0xF0); + EXPECT_EQ(0x01, p[0]); + EXPECT_EQ(0x0F, p[1] & 0x0F); EXPECT_EQ(input, c.get_value_strings()); @@ -318,8 +318,8 @@ TEST(constant, int4_vector_negative_number) { EXPECT_EQ(v[2], int8_t(-1)); const auto p = c.get_data_ptr(); - EXPECT_EQ(0xFE, p[0]); - EXPECT_EQ(0xF0, p[1] & 0xF0); + EXPECT_EQ(0xEF, p[0]); + EXPECT_EQ(0x0F, p[1] & 0x0F); } TEST(constant, int4_vector_positive_number) { @@ -332,8 +332,8 @@ TEST(constant, int4_vector_positive_number) { EXPECT_EQ(v[2], int8_t(5)); const auto p = c.get_data_ptr(); - EXPECT_EQ(0x12, p[0]); - EXPECT_EQ(0x50, p[1] & 0xF0); + EXPECT_EQ(0x21, p[0]); + EXPECT_EQ(0x05, p[1] & 0x0F); } TEST(constant, int4_vector_broadcast_negative_number) { @@ -795,8 +795,8 @@ TEST(constant, uint4_string) { EXPECT_EQ(v[3], 0); const auto p = c.get_data_ptr(); - EXPECT_EQ(p[0], 0x10); - EXPECT_EQ(p[1], 0x10); + EXPECT_EQ(p[0], 0x01); + EXPECT_EQ(p[1], 0x01); EXPECT_EQ(input, c.get_value_strings()); @@ -831,8 +831,8 @@ TEST(constant, uint4_vector) { EXPECT_EQ(v[3], 0); const auto p = c.get_data_ptr(); - EXPECT_EQ(p[0], 0x10); - EXPECT_EQ(p[1], 0x10); + EXPECT_EQ(p[0], 0x01); + EXPECT_EQ(p[1], 0x01); } TEST(constant, uint4_vector_broadcast) { diff --git a/src/core/tests/int4.cpp b/src/core/tests/int4.cpp index 2edb82dda01..d9a20fbf364 100644 --- a/src/core/tests/int4.cpp +++ b/src/core/tests/int4.cpp @@ -15,9 +15,9 @@ TEST(int4, convert_i4_to_string) { vector values{171, 16}; auto constant = make_shared(element::i4, Shape{3}, &values[0]); - vector ref{"-6", "-5", "1"}; + vector ref{"-5", "-6", "0"}; for (size_t i = 0; i < 3; ++i) { - ASSERT_EQ(constant->convert_value_to_string(i), ref[i]); + EXPECT_EQ(constant->convert_value_to_string(i), ref[i]); } } diff --git a/src/core/tests/uint4.cpp b/src/core/tests/uint4.cpp index 5c3b0a5e06a..8285fdb3cd5 100644 --- a/src/core/tests/uint4.cpp +++ b/src/core/tests/uint4.cpp @@ -13,9 +13,9 @@ TEST(uint4, convert_u4_to_string) { vector values{171, 16}; auto constant = make_shared(element::u4, Shape{3}, &values[0]); - vector ref{"10", "11", "1"}; + vector ref{"11", "10", "0"}; for (size_t i = 0; i < 3; ++i) { - ASSERT_EQ(constant->convert_value_to_string(i), ref[i]); + EXPECT_EQ(constant->convert_value_to_string(i), ref[i]); } } diff --git a/src/frontends/pytorch/src/frontend.cpp b/src/frontends/pytorch/src/frontend.cpp index 0910aa3e057..14c1094b3d0 100644 --- a/src/frontends/pytorch/src/frontend.cpp +++ b/src/frontends/pytorch/src/frontend.cpp @@ -41,6 +41,7 @@ #include "transforms/softmax_reshape_elimination.hpp" #include "transforms/string_equality_replacer.hpp" #include "transforms/tuple_unpack_replacer.hpp" +#include "transforms/u4_block_repack.hpp" #include "translate_session.hpp" namespace ov { @@ -200,6 +201,7 @@ void FrontEnd::normalize(const std::shared_ptr& model) const { manager.register_pass(); manager.register_pass(); manager.register_pass(); + manager.register_pass(); manager.register_pass(); manager.register_pass(); manager.register_pass(); diff --git a/src/frontends/pytorch/src/transforms/aten_stack_list_construct_replacer.cpp b/src/frontends/pytorch/src/transforms/aten_stack_list_construct_replacer.cpp index f8de5275b69..67ea5f4f9e1 100644 --- a/src/frontends/pytorch/src/transforms/aten_stack_list_construct_replacer.cpp +++ b/src/frontends/pytorch/src/transforms/aten_stack_list_construct_replacer.cpp @@ -12,6 +12,7 @@ #include "openvino/pass/pattern/matcher.hpp" #include "openvino/pass/pattern/op/wrap_type.hpp" #include "utils.hpp" +#include "utils_quantize.hpp" namespace ov { namespace frontend { @@ -38,22 +39,31 @@ AtenStackListConstructReplacer::AtenStackListConstructReplacer() { auto axis_node = pattern_map.at(axis).get_node_shared_ptr(); auto axis_const = std::dynamic_pointer_cast(axis_node); auto axis = axis_const->cast_vector(); + if (axis.size() != 1) { + add_exception_to_fw_node(stack, "aten::stack has multiple axes, only one is supported."); + return false; + } // Check if ListConstruct is an input if (auto list_construct_node = cast_fw_node(input_node, "prim::ListConstruct")) { const auto& list_inputs = list_construct_node->input_values(); - OutputVector node_vector; - auto zero = v0::Constant::create(element::i32, Shape{}, {0}); - // Iterate over values in ListConstruct - for (const auto& list_input : list_inputs) { - auto node = concat_list_construct(list_input); - auto unsqueezed_node = std::make_shared(node, axis_const); - node_vector.push_back(unsqueezed_node); + std::shared_ptr node; + if (auto compression = u4_compression_stack(list_inputs, axis[0])) { + node = compression; + } else { + OutputVector node_vector; + auto zero = v0::Constant::create(element::i32, Shape{}, {0}); + // Iterate over values in ListConstruct + for (const auto& list_input : list_inputs) { + auto node = concat_list_construct(list_input); + auto unsqueezed_node = std::make_shared(node, axis_const); + node_vector.push_back(unsqueezed_node); + } + // Concat vectors on provided axis + node = std::make_shared(node_vector, axis[0]); } - // Concat vectors on provided axis - auto concat = std::make_shared(node_vector, axis[0]); - copy_runtime_info_and_name(stack, {concat}, {input_node}); - replace_node(stack, concat); + copy_runtime_info_and_name(stack, {node}, {input_node}); + replace_node(stack, node); return true; } add_exception_to_fw_node(stack, "Unsupported case of aten::stack."); diff --git a/src/frontends/pytorch/src/transforms/u4_block_repack.cpp b/src/frontends/pytorch/src/transforms/u4_block_repack.cpp new file mode 100644 index 00000000000..e08ebd728b0 --- /dev/null +++ b/src/frontends/pytorch/src/transforms/u4_block_repack.cpp @@ -0,0 +1,98 @@ +// Copyright (C) 2018-2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "u4_block_repack.hpp" + +#include "openvino/core/rt_info.hpp" +#include "openvino/op/constant.hpp" +#include "openvino/op/reshape.hpp" +#include "openvino/op/transpose.hpp" +#include "openvino/pass/pattern/matcher.hpp" +#include "openvino/pass/pattern/op/wrap_type.hpp" +#include "utils.hpp" +#include "utils_quantize.hpp" + +namespace ov { +namespace frontend { +namespace pytorch { +namespace pass { + +using namespace ov::op; +using namespace ov::pass::pattern; + +U4BlockRepack::U4BlockRepack() { + const auto m_constant = ov::pass::pattern::wrap_type(); + const auto m_reshape1 = ov::pass::pattern::wrap_type({m_constant, any_input()}); + const auto m_transpose = ov::pass::pattern::wrap_type({m_reshape1, any_input()}); + const auto m_reshape2 = ov::pass::pattern::wrap_type({m_transpose, any_input()}); + + auto pack_byte = [](uint8_t lo, uint8_t hi) { + return (hi << 4) | (lo & 0x0F); + }; // swap halfs because Convert op assumes this layout + + auto get_u4 = [](const uint8_t* src, size_t idx) { + const size_t byte_idx = idx / 2; + const uint8_t bit_shift = 4 * (idx % 2); + return (src[byte_idx] >> bit_shift) & 0xF; + }; + + register_matcher( + std::make_shared(m_reshape2, "ov::frontend::pytorch::pass::U4BlockRepack"), + [=](ov::pass::pattern::Matcher& m) { + auto& pattern_to_output = m.get_pattern_value_map(); + auto constant = + std::dynamic_pointer_cast(pattern_to_output[m_constant].get_node_shared_ptr()); + if (!constant) + return false; + auto reshape1 = pattern_to_output[m_reshape1].get_node_shared_ptr(); + auto transpose = pattern_to_output[m_transpose].get_node_shared_ptr(); + auto reshape2 = pattern_to_output[m_reshape2].get_node_shared_ptr(); + + if (constant->get_element_type() != element::u4) + return false; + + // FIXME: Check reshape/transpose/reshape target shapes and axes permutation; now they are supposed to be + // always in expected form + + auto source_shape = reshape1->get_output_shape(0); + + if (source_shape.size() != 3) + return false; + + auto destination_shape = reshape2->get_output_shape(0); + + size_t n_blocks = source_shape[0]; + size_t block_height = source_shape[1]; + size_t lane_size = source_shape[2]; // size in u4 units + size_t block_size = block_height * lane_size / 2; // size in bytes + + auto src = constant->get_data_ptr(); + + auto new_const = std::make_shared(element::u4, destination_shape); + auto dst = const_cast( // const_cast? + reinterpret_cast(new_const->get_data_ptr())); // TODO: How to better accees u4 data? + + for (size_t iblock = 0; iblock < n_blocks; ++iblock) { + auto src_block = src + iblock * block_size; + auto dst_block = dst + iblock * block_size; + for (size_t i = 0; i < lane_size; ++i) { + for (size_t j = 0; j < block_height / 2; ++j) { // /2 because we handle two bytes at once + uint8_t lo = get_u4(src_block, 2 * j * lane_size + i); + uint8_t hi = get_u4(src_block, (2 * j + 1) * lane_size + i); + dst_block[i * block_height / 2 + j] = pack_byte(lo, hi); + } + } + } + + copy_runtime_info(NodeVector{constant, reshape1, transpose, reshape2}, new_const); + replace_node(reshape2, new_const); + + return true; + }); +}; + +} // namespace pass +} // namespace pytorch +} // namespace frontend +} // namespace ov diff --git a/src/frontends/pytorch/src/transforms/u4_block_repack.hpp b/src/frontends/pytorch/src/transforms/u4_block_repack.hpp new file mode 100644 index 00000000000..aa6e00f70e5 --- /dev/null +++ b/src/frontends/pytorch/src/transforms/u4_block_repack.hpp @@ -0,0 +1,24 @@ +// Copyright (C) 2018-2023 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "openvino/pass/graph_rewrite.hpp" +#include "openvino/pass/pass.hpp" + +namespace ov { +namespace frontend { +namespace pytorch { +namespace pass { + +class U4BlockRepack : public ov::pass::MatcherPass { +public: + OPENVINO_RTTI("ov::frontend::pytorch::pass::U4BlockRepack"); + U4BlockRepack(); +}; + +} // namespace pass +} // namespace pytorch +} // namespace frontend +} // namespace ov diff --git a/src/frontends/pytorch/src/utils_quantize.cpp b/src/frontends/pytorch/src/utils_quantize.cpp index 5af546f3d5b..1346fd76971 100644 --- a/src/frontends/pytorch/src/utils_quantize.cpp +++ b/src/frontends/pytorch/src/utils_quantize.cpp @@ -6,6 +6,7 @@ #include "openvino/frontend/pytorch/node_context.hpp" #include "openvino/op/broadcast.hpp" +#include "openvino/op/constant.hpp" #include "openvino/op/convert.hpp" #include "openvino/op/convert_like.hpp" #include "openvino/op/fake_quantize.hpp" @@ -13,6 +14,7 @@ #include "openvino/op/reshape.hpp" #include "openvino/op/scatter_elements_update.hpp" #include "openvino/op/subtract.hpp" +#include "transformations/utils/utils.hpp" namespace ov { namespace frontend { @@ -168,6 +170,53 @@ std::shared_ptr cast_quantized_fw_node(std::shared_ptr no return quant_node; } +std::shared_ptr u4_compression_stack(const OutputVector& list_elems, int64_t axis) { + // Part 1: Detect pattern + + if (list_elems.size() != 2) + return nullptr; + auto bitwise_and = cast_fw_node(list_elems[0].get_node_shared_ptr(), "aten::bitwise_and"); + if (!bitwise_and) + return nullptr; + auto bitwise_shift = cast_fw_node(list_elems[1].get_node_shared_ptr(), "aten::bitwise_right_shift"); + if (!bitwise_shift) + return nullptr; + + auto weights_u8 = std::dynamic_pointer_cast(bitwise_and->get_input_node_shared_ptr(0)); + if (weights_u8 != std::dynamic_pointer_cast(bitwise_shift->get_input_node_shared_ptr(0))) + return nullptr; + + if (weights_u8->get_output_element_type(0) != element::u8) + return nullptr; + + if (axis != -1 && static_cast(axis) != weights_u8->get_shape().size() - 1) + return nullptr; + + if (!ov::op::util::has_constant_value(bitwise_and->get_input_node_shared_ptr(1), 0x0F)) + return nullptr; + + if (!ov::op::util::has_constant_value(bitwise_shift->get_input_node_shared_ptr(1), 4)) + return nullptr; + + // Pattern detected, weights_u8 is target u8 packed constant with weights + + // Part 2: Form u4 constant by repacking of the original weights_u8 + // Repacking transformes half of lanes to interleaved representation. + + auto u8_shape = weights_u8->get_shape(); + size_t full_size = shape_size(u8_shape); + auto src = weights_u8->get_data_ptr(); + + auto u4_shape = u8_shape; + u4_shape.push_back(2); + auto new_const = std::make_shared(element::u4, u4_shape); + auto dst = const_cast(reinterpret_cast(new_const->get_data_ptr())); + + std::copy(src, src + full_size, dst); // TODO: Avoid copying, reuse the same constant + copy_runtime_info_and_name(weights_u8, {new_const}, {weights_u8, bitwise_and, bitwise_shift}); + return new_const; +} + } // namespace pytorch } // namespace frontend } // namespace ov diff --git a/src/frontends/pytorch/src/utils_quantize.hpp b/src/frontends/pytorch/src/utils_quantize.hpp index 69917e7b8bc..e02bce880d2 100644 --- a/src/frontends/pytorch/src/utils_quantize.hpp +++ b/src/frontends/pytorch/src/utils_quantize.hpp @@ -166,6 +166,12 @@ OutputVector quantizable_op(const NodeContext& context) { } } // namespace op +/** + * Captures aten::stack([aten::bitwise_and(Constant(u8)), aten::bitwise_right_shift(Constant(u8))], dim=-1). + * This pattern is transformed to a single Constant with element_type=u4. + */ +std::shared_ptr u4_compression_stack(const OutputVector& list_elems, int64_t axis); + } // namespace pytorch } // namespace frontend } // namespace ov diff --git a/src/plugins/template/tests/functional/op_reference/convert.cpp b/src/plugins/template/tests/functional/op_reference/convert.cpp index 0f3e4714879..b6195744c9c 100644 --- a/src/plugins/template/tests/functional/op_reference/convert.cpp +++ b/src/plugins/template/tests/functional/op_reference/convert.cpp @@ -103,7 +103,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{2, 2}, ov::element::u4, ov::element::f32, - std::vector{0xFB, 0x0A}, + std::vector{0xBF, 0xA0}, std::vector{15.0f, 11.0f, 0.0f, 10.0f}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -134,7 +134,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{2, 2}, ov::element::i4, ov::element::f32, - std::vector{0xFE, 0xF2}, + std::vector{0xEF, 0x2F}, std::vector{-1.0f, -2.0f, -1.0f, 2.0f}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -245,7 +245,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u1, ov::element::i4, std::vector{0xA0}, - std::vector{0x10, 0x10}, + std::vector{0x01, 0x01}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -261,7 +261,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u8, ov::element::i4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -269,7 +269,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u16, ov::element::i4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -277,7 +277,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u32, ov::element::i4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -285,7 +285,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u64, ov::element::i4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -301,7 +301,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i8, ov::element::i4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -309,7 +309,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i16, ov::element::i4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -317,7 +317,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i32, ov::element::i4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -325,7 +325,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i64, ov::element::i4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -333,7 +333,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::f16, ov::element::i4, std::vector{-1, -2, 0, 3}, - std::vector{0xFE, 0x03}, + std::vector{0xEF, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -341,7 +341,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::bf16, ov::element::i4, std::vector{-1, -2, 0, 3}, - std::vector{0xFE, 0x03}, + std::vector{0xEF, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -349,7 +349,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::f32, ov::element::i4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), // destination i8 @@ -364,7 +364,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::i8, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -395,7 +395,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::i8, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -452,7 +452,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::i16, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -483,7 +483,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::i16, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -540,7 +540,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::i32, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -571,7 +571,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::i32, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -628,7 +628,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::i64, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -659,7 +659,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::i64, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -718,7 +718,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{8}, ov::element::u4, ov::element::u1, - std::vector{0x10, 0x01, 0x00, 0x00}, + std::vector{0x01, 0x10, 0x00, 0x00}, std::vector{0x90}, 8, 8), @@ -758,7 +758,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{8}, ov::element::i4, ov::element::u1, - std::vector{0x10, 0x01, 0x00, 0x00}, + std::vector{0x01, 0x10, 0x00, 0x00}, std::vector{0x90}, 8, 8), @@ -825,7 +825,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u1, ov::element::u4, std::vector{0xA0}, - std::vector{0x10, 0x10}, + std::vector{0x01, 0x01}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -841,7 +841,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u8, ov::element::u4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -849,7 +849,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u16, ov::element::u4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -857,7 +857,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u32, ov::element::u4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -865,7 +865,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u64, ov::element::u4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -881,7 +881,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i8, ov::element::u4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -889,7 +889,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i16, ov::element::u4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -897,7 +897,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i32, ov::element::u4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -905,7 +905,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i64, ov::element::u4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -913,7 +913,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::f16, ov::element::u4, std::vector{-1, -2, 0, 3}, - std::vector{0xFE, 0x03}, + std::vector{0xEF, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -921,7 +921,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::bf16, ov::element::u4, std::vector{-1, -2, 0, 3}, - std::vector{0xFE, 0x03}, + std::vector{0xEF, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT, @@ -929,7 +929,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::f32, ov::element::u4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), @@ -945,7 +945,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::u8, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -976,7 +976,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::u8, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -1034,7 +1034,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::u16, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -1065,7 +1065,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::u16, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -1123,7 +1123,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::u32, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -1154,7 +1154,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::u32, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -1211,7 +1211,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::u64, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, @@ -1242,7 +1242,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::u64, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT, diff --git a/src/plugins/template/tests/functional/op_reference/convert_like.cpp b/src/plugins/template/tests/functional/op_reference/convert_like.cpp index b46fe98af03..4ddf3dda276 100644 --- a/src/plugins/template/tests/functional/op_reference/convert_like.cpp +++ b/src/plugins/template/tests/functional/op_reference/convert_like.cpp @@ -6,6 +6,8 @@ #include +#include + #include "conversion.hpp" using namespace ov; @@ -101,7 +103,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{2, 2}, ov::element::u4, ov::element::f32, - std::vector{0xFB, 0x0A}, + std::vector{0xBF, 0xA0}, std::vector{15.0f, 11.0f, 0.0f, 10.0f}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -132,7 +134,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{2, 2}, ov::element::i4, ov::element::f32, - std::vector{0xFE, 0xF2}, + std::vector{0xEF, 0x2F}, std::vector{-1.0f, -2.0f, -1.0f, 2.0f}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -243,7 +245,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u1, ov::element::i4, std::vector{0xA0}, - std::vector{0x10, 0x10}, + std::vector{0x01, 0x01}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -259,7 +261,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u8, ov::element::i4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -267,7 +269,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u16, ov::element::i4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -275,7 +277,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u32, ov::element::i4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -283,7 +285,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u64, ov::element::i4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -299,7 +301,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i8, ov::element::i4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -307,7 +309,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i16, ov::element::i4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -315,7 +317,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i32, ov::element::i4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -323,7 +325,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i64, ov::element::i4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -331,7 +333,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::f16, ov::element::i4, std::vector{-1, -2, 0, 3}, - std::vector{0xFE, 0x03}, + std::vector{0xEF, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -339,7 +341,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::bf16, ov::element::i4, std::vector{-1, -2, 0, 3}, - std::vector{0xFE, 0x03}, + std::vector{0xEF, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -347,7 +349,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::f32, ov::element::i4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), // destination i8 @@ -362,7 +364,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::i8, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -393,7 +395,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::i8, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -436,7 +438,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::f32, ov::element::i8, - std::vector{-1, -2, 2, 3}, + std::vector{-1, -2, 2.2, 3.8}, std::vector{-1, -2, 2, 3}), // destination i16 ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -450,7 +452,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::i16, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -481,7 +483,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::i16, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -524,7 +526,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::f32, ov::element::i16, - std::vector{-1, -2, 2, 3}, + std::vector{-1, -2, 2.2, 3.8}, std::vector{-1, -2, 2, 3}), // destination i32 ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -538,7 +540,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::i32, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -569,7 +571,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::i32, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -612,7 +614,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::f32, ov::element::i32, - std::vector{-1, -2, 2, 3}, + std::vector{-1, -2, 2.2, 3.8}, std::vector{-1, -2, 2, 3}), // destination i64 ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -626,7 +628,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::i64, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -657,7 +659,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::i64, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -700,7 +702,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::f32, ov::element::i64, - std::vector{-1, -2, 2, 3}, + std::vector{-1, -2, 2.2, 3.8}, std::vector{-1, -2, 2, 3}), // destination u1 @@ -716,7 +718,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{8}, ov::element::u4, ov::element::u1, - std::vector{0x10, 0x01, 0x00, 0x00}, + std::vector{0x01, 0x10, 0x00, 0x00}, std::vector{0x90}, 8, 8), @@ -756,7 +758,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{8}, ov::element::i4, ov::element::u1, - std::vector{0x10, 0x01, 0x00, 0x00}, + std::vector{0x01, 0x10, 0x00, 0x00}, std::vector{0x90}, 8, 8), @@ -823,7 +825,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u1, ov::element::u4, std::vector{0xA0}, - std::vector{0x10, 0x10}, + std::vector{0x01, 0x01}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -839,7 +841,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u8, ov::element::u4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -847,7 +849,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u16, ov::element::u4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -855,7 +857,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u32, ov::element::u4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -863,7 +865,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::u64, ov::element::u4, std::vector{1, 2, 0, 3}, - std::vector{0x12, 0x03}, + std::vector{0x21, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -879,7 +881,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i8, ov::element::u4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -887,7 +889,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i16, ov::element::u4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -895,7 +897,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i32, ov::element::u4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -903,7 +905,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::i64, ov::element::u4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -911,7 +913,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::f16, ov::element::u4, std::vector{-1, -2, 0, 3}, - std::vector{0xFE, 0x03}, + std::vector{0xEF, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -919,7 +921,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::bf16, ov::element::u4, std::vector{-1, -2, 0, 3}, - std::vector{0xFE, 0x03}, + std::vector{0xEF, 0x30}, 4, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -927,7 +929,7 @@ INSTANTIATE_TEST_SUITE_P( ov::element::f32, ov::element::u4, std::vector{-1, -2, 2, 3}, - std::vector{0xFE, 0x23}, + std::vector{0xEF, 0x32}, 4, 4), @@ -943,7 +945,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::u8, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -974,7 +976,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::u8, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -1017,7 +1019,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::f32, ov::element::u8, - std::vector{1, 2, 2, 3}, + std::vector{1, 2, 2.2, 3.8}, std::vector{1, 2, 2, 3}), // destination u16 @@ -1032,7 +1034,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::u16, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -1063,7 +1065,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::u16, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -1106,7 +1108,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::f32, ov::element::u16, - std::vector{1, 2, 2, 3}, + std::vector{1, 2, 2.2, 3.8}, std::vector{1, 2, 2, 3}), // destination u32 @@ -1121,7 +1123,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::u32, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -1152,7 +1154,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::u32, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -1195,7 +1197,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::f32, ov::element::u32, - std::vector{1, 2, 2, 3}, + std::vector{1, 2, 2.2, 3.8}, std::vector{1, 2, 2, 3}), // destination u64 ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -1209,7 +1211,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::u4, ov::element::u64, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -1240,7 +1242,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::i4, ov::element::u64, - std::vector{0x21, 0x43}, + std::vector{0x12, 0x34}, std::vector{2, 1, 4, 3}, 4), ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -1283,7 +1285,7 @@ INSTANTIATE_TEST_SUITE_P( ov::PartialShape{4}, ov::element::f32, ov::element::u64, - std::vector{1, 2, 2, 3}, + std::vector{1, 2, 2.2, 3.8}, std::vector{1, 2, 2, 3})), ReferenceConversionLayerTest::getTestCaseName); } // namespace diff --git a/tools/mo/openvino/tools/mo/ops/Cast.py b/tools/mo/openvino/tools/mo/ops/Cast.py index 77beb07c741..24409912429 100644 --- a/tools/mo/openvino/tools/mo/ops/Cast.py +++ b/tools/mo/openvino/tools/mo/ops/Cast.py @@ -36,12 +36,14 @@ class Cast(Op): @staticmethod def type_infer(node: Node): - assert node.has_valid('dst_type'), 'Destination type of "Cast" operation should be extracted earlier' + assert node.has_valid( + 'dst_type'), 'Destination type of "Cast" operation should be extracted earlier' node.out_port(0).set_data_type(node.dst_type) @staticmethod def helper_value_propagation(node_name, value, dst_type): - new_blob, finite_match_count, zero_match_count = convert_blob(value, dst_type) + new_blob, finite_match_count, zero_match_count = convert_blob( + value, dst_type) if finite_match_count: log.error("{} elements of {} were clipped to infinity while converting an input blob for node '{}' to {}." @@ -63,6 +65,10 @@ class Cast(Op): we would pad them to 6 element with the last element as zero and we would pack them into 3 uint8 values """ assert dst_type in [packed_U4, packed_I4] + # TODO: Remove this comment when it's clear that we can fix it easily + # raise Exception("Packing of u4/i4 data is no longer supported in mo because it is now incompatible with the new " + # "order of the halfs of a byte that was introduced in OpenVINO runtime recently. Use ovc " + # "command line tool or openvino.convert_model python function instead.") minimum_regular_dtype = np.uint8 if dst_type == packed_U4 else np.int8 # initial casing from the source type to the numpy-friendly type which could absorb all the values of dst_type @@ -83,10 +89,12 @@ class Cast(Op): padded = np.concatenate((flattened, np.zeros([pad], dtype=minimum_regular_dtype))) assert np.prod(padded.shape) % num_values_fitting_into_uint8 == 0 - bit_order_little = (padded[:, None] & (1 << np.arange(num_bits)) > 0).astype(np.uint8) - bit_order_big = np.flip(bit_order_little, axis=1) - bit_order_big_flattened = bit_order_big.flatten() - packed = np.packbits(bit_order_big_flattened) + bit_order_little = (padded[:, None] & ( + 1 << np.arange(num_bits)) > 0).astype(np.uint8) + bit_order_big_flattened = bit_order_little.flatten() + # u1 still has reversed bit order: + packed = np.packbits(bit_order_big_flattened, + bitorder='little' if num_bits > 1 else 'big') node.out_node(0)['force_shape'] = data_shape.copy() node.out_node(0)['force_type'] = np_data_type_to_precision(dst_type) diff --git a/tools/mo/unit_tests/mo/ops/cast_test.py b/tools/mo/unit_tests/mo/ops/cast_test.py index 985a7276514..73a468e9fb8 100644 --- a/tools/mo/unit_tests/mo/ops/cast_test.py +++ b/tools/mo/unit_tests/mo/ops/cast_test.py @@ -21,81 +21,20 @@ nodes = lambda value, dst_type: { class TestCastTest(): """ Example of checking: - 7 == 0111, padded to 0111 0000, results in 112 - 7 == 0111, 8 == 1000 packed to 0111 1000, results in 120 + 7 == 0111, padded to 00000111, results in 7 + 7 == 0111, 8 == 1000 packed to 10000111, results in 7+16 - -8 == 1000, padded to 1000 0000, results in 128 + -8 == 1000, padded to 00001000, results in 8 """ - @pytest.mark.parametrize("value, expected, custom_dtype",[ - ([0], [0], packed_U4), - ([1], [16], packed_U4), - ([2], [32], packed_U4), - ([3], [48], packed_U4), - ([4], [64], packed_U4), - ([5], [80], packed_U4), - ([6], [96], packed_U4), - ([7], [112], packed_U4), - ([8], [128], packed_U4), - ([9], [144], packed_U4), - ([10], [160], packed_U4), - ([11], [176], packed_U4), - ([12], [192], packed_U4), - ([13], [208], packed_U4), - ([14], [224], packed_U4), - ([15], [240], packed_U4), - - ([0, 15], [15], packed_U4), - ([1, 14], [30], packed_U4), - ([2, 13], [45], packed_U4), - ([3, 12], [60], packed_U4), - ([4, 11], [75], packed_U4), - ([5, 10], [90], packed_U4), - ([6, 9], [105], packed_U4), - ([7, 8], [120], packed_U4), - ([8, 7], [135], packed_U4), - ([9, 6], [150], packed_U4), - ([10, 5], [165], packed_U4), - ([11, 4], [180], packed_U4), - ([12, 3], [195], packed_U4), - ([13, 2], [210], packed_U4), - ([14, 1], [225], packed_U4), - ([15, 0], [240], packed_U4), - - ([-8], [128], packed_I4), - ([-7], [144], packed_I4), - ([-6], [160], packed_I4), - ([-5], [176], packed_I4), - ([-4], [192], packed_I4), - ([-3], [208], packed_I4), - ([-2], [224], packed_I4), - ([-1], [240], packed_I4), - ([0], [0], packed_I4), - ([1], [16], packed_I4), - ([2], [32], packed_I4), - ([3], [48], packed_I4), - ([4], [64], packed_I4), - ([5], [80], packed_I4), - ([6], [96], packed_I4), - ([7], [112], packed_I4), - - ([-8, 7], [135], packed_I4), - ([-7, 6], [150], packed_I4), - ([-6, 5], [165], packed_I4), - ([-5, 4], [180], packed_I4), - ([-4, 3], [195], packed_I4), - ([-3, 2], [210], packed_I4), - ([-2, 1], [225], packed_I4), - ([-1, 0], [240], packed_I4), - ([0, -1], [15], packed_I4), - ([1, -2], [30], packed_I4), - ([2, -3], [45], packed_I4), - ([3, -4], [60], packed_I4), - ([4, -5], [75], packed_I4), - ([5, -6], [90], packed_I4), - ([6, -7], [105], packed_I4), - ([7, -8], [120], packed_I4), - ]) + @pytest.mark.parametrize("value, expected, custom_dtype", + [([i], [i], packed_U4) for i in range(16)] + + [([i, 15-i], [i + (15-i)*16], packed_U4) for i in range(16)] + + [([-i], [16-i], packed_I4) for i in range(1, 8+1)] + + [([i], [i], packed_I4) for i in range(8)] + + [([-i-1, i], [16-i-1 + 16*i], packed_I4) for i in range(8)] + + [([i, -i-1], [i + 16*(16-i-1)], packed_I4) for i in range(8)] + ) def test_custom_value_propagation(self, value, expected, custom_dtype): graph = build_graph(nodes(value, custom_dtype), [ *connect('value', 'convert'), *connect('convert', 'output'),