CompressQuantizeWeights - use f32 precision when computing scale and zero point (#16794)
Ticket: 101825
This commit is contained in:
@@ -95,13 +95,13 @@ ngraph::pass::CompressQuantizeWeights::CompressQuantizeWeights() {
|
||||
output_high = levels - 1 + output_low
|
||||
The FakeQuantize result is converted to low precision type and then constant folded
|
||||
*/
|
||||
std::shared_ptr<Node> new_input_low;
|
||||
auto new_output_low = op::Constant::create(input_type, Shape{}, {-static_cast<float>(levels / 2)});
|
||||
auto new_output_high =
|
||||
std::shared_ptr<Node> new_output_low =
|
||||
op::Constant::create(input_type, Shape{}, {-static_cast<float>(levels / 2)});
|
||||
std::shared_ptr<Node> new_output_high =
|
||||
std::make_shared<opset8::Add>(new_output_low, op::Constant::create(input_type, Shape{}, {levels - 1}));
|
||||
const auto& weights_const = pattern_value_map.at(weights_const_pattern);
|
||||
const auto& input_low = pattern_value_map.at(input_low_pattern);
|
||||
const auto& input_high = pattern_value_map.at(input_high_pattern);
|
||||
Output<Node> input_low = pattern_value_map.at(input_low_pattern);
|
||||
Output<Node> input_high = pattern_value_map.at(input_high_pattern);
|
||||
const auto& fq_data_input = pattern_value_map.count(weigths_convert_pattern)
|
||||
? pattern_value_map.at(weigths_convert_pattern)
|
||||
: weights_const;
|
||||
@@ -143,8 +143,18 @@ ngraph::pass::CompressQuantizeWeights::CompressQuantizeWeights() {
|
||||
scale = (output_high - output_low) / (new_output_high - new_output_low)
|
||||
zero_point = new_output_low - output_low / scale
|
||||
*/
|
||||
const auto& output_low = pattern_value_map.at(output_low_pattern);
|
||||
const auto& output_high = pattern_value_map.at(output_high_pattern);
|
||||
Output<Node> output_low = pattern_value_map.at(output_low_pattern);
|
||||
Output<Node> output_high = pattern_value_map.at(output_high_pattern);
|
||||
const auto& fq_type = fq->get_output_element_type(0);
|
||||
const bool should_convert = fq_type.is_real() && fq_type.size() < element::f32.size();
|
||||
if (should_convert) {
|
||||
input_low = std::make_shared<opset8::Convert>(input_low, element::f32);
|
||||
input_high = std::make_shared<opset8::Convert>(input_high, element::f32);
|
||||
output_low = std::make_shared<opset8::Convert>(output_low, element::f32);
|
||||
output_high = std::make_shared<opset8::Convert>(output_high, element::f32);
|
||||
new_output_low = std::make_shared<opset8::Convert>(new_output_low, element::f32);
|
||||
new_output_high = std::make_shared<opset8::Convert>(new_output_high, element::f32);
|
||||
}
|
||||
auto output_range = std::make_shared<opset8::Subtract>(output_high, output_low);
|
||||
auto input_range = std::make_shared<opset8::Subtract>(new_output_high, new_output_low);
|
||||
std::shared_ptr<Node> scale = std::make_shared<opset8::Divide>(output_range, input_range);
|
||||
@@ -155,11 +165,17 @@ ngraph::pass::CompressQuantizeWeights::CompressQuantizeWeights() {
|
||||
OPENVINO_SUPPRESS_DEPRECATED_END
|
||||
scale = constant;
|
||||
}
|
||||
auto zero = op::Constant::create(input_type, Shape{}, {0});
|
||||
auto zero = op::Constant::create(scale->get_output_element_type(0), Shape{}, {0});
|
||||
auto scale_eq_zero = std::make_shared<opset8::Equal>(scale, zero);
|
||||
// shift equals to input_low - output_low / scale
|
||||
// for positions where scale == 0, we put zero as shift
|
||||
std::shared_ptr<Node> zero_point = std::make_shared<opset8::Select>(scale_eq_zero, zero, shift);
|
||||
|
||||
if (should_convert) {
|
||||
scale = std::make_shared<opset8::Convert>(scale, fq_type);
|
||||
zero_point = std::make_shared<opset8::Convert>(zero_point, fq_type);
|
||||
}
|
||||
|
||||
OPENVINO_SUPPRESS_DEPRECATED_START
|
||||
if (auto constant = ov::get_constant_from_source(zero_point)) {
|
||||
OPENVINO_SUPPRESS_DEPRECATED_END
|
||||
|
||||
@@ -184,6 +184,36 @@ TEST_F(TransformationTestsF, CompressQuantizeWeightsWithZeroPointOptimizer) {
|
||||
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, CompressQuantizeWeightsWithZeroPointOptimizerFP16) {
|
||||
{
|
||||
auto data = opset8::Constant::create(element::f16, Shape{3, 1, 1, 1}, {0.2, 1.2, 1.2});
|
||||
auto input_low =
|
||||
opset8::Constant::create(element::f16, Shape{3, 1, 1, 1}, {0.59033203125, 1.4833984375, 1.2900390625});
|
||||
auto input_high =
|
||||
opset8::Constant::create(element::f16, Shape{3, 1, 1, 1}, {-0.59033203125, -1.4833984375, -1.2900390625});
|
||||
auto output_low =
|
||||
opset8::Constant::create(element::f16, Shape{3, 1, 1, 1}, {0.295166015625, 0.74169921875, 0.64501953125});
|
||||
auto output_high = opset8::Constant::create(element::f16,
|
||||
Shape{3, 1, 1, 1},
|
||||
{-0.295166015625, -0.74169921875, -0.64501953125});
|
||||
auto fq = std::make_shared<opset8::FakeQuantize>(data, input_low, input_high, output_low, output_high, 255);
|
||||
function = std::make_shared<Function>(NodeVector{fq}, ParameterVector{});
|
||||
|
||||
manager.register_pass<pass::CompressQuantizeWeights>();
|
||||
manager.register_pass<pass::ZeroPointOptimizer>();
|
||||
}
|
||||
|
||||
{
|
||||
auto data = opset8::Constant::create(element::i8, Shape{3, 1, 1, 1}, {-43, -103, -118});
|
||||
auto convert = std::make_shared<opset8::Convert>(data, element::f16);
|
||||
auto scale = opset8::Constant::create(element::f16, Shape{3, 1, 1, 1}, {-0.002325, -0.00584, -0.005077});
|
||||
auto mul = std::make_shared<opset8::Multiply>(convert, scale);
|
||||
function_ref = std::make_shared<Function>(NodeVector{mul}, ParameterVector{});
|
||||
}
|
||||
comparator.enable(FunctionsComparator::CmpValues::CONST_VALUES);
|
||||
comparator.enable(FunctionsComparator::CmpValues::ACCURACY);
|
||||
}
|
||||
|
||||
TEST_F(TransformationTestsF, NegativeCompressQuantizeWeightsWithZeroPointOptimizer) {
|
||||
{
|
||||
auto data = opset8::Constant::create(element::f32, Shape{2, 4, 1, 1}, {-1, 0, 1, 2, 3, 4, 5, 11});
|
||||
|
||||
@@ -7,6 +7,7 @@ import numpy as np
|
||||
|
||||
from openvino.tools.mo.ops.Cast import Cast
|
||||
from openvino.tools.mo.ops.elementwise import Sub, Div, Mul, Equal
|
||||
from openvino.tools.mo.ops.Cast import Cast
|
||||
from openvino.tools.mo.ops.select import Select
|
||||
from openvino.tools.mo.back.replacement import BackReplacementPattern
|
||||
from openvino.tools.mo.front.common.partial_infer.utils import mo_array
|
||||
@@ -183,6 +184,24 @@ class CompressQuantizeWeights(BackReplacementPattern):
|
||||
out_low = fake_quantize.in_port(3).get_source()
|
||||
out_high = fake_quantize.in_port(4).get_source()
|
||||
|
||||
need_cast_to_f32 = fake_quantize.out_port(0).is_data_type_defined() and fake_quantize.out_port(0).get_data_type() < np.float32
|
||||
if need_cast_to_f32:
|
||||
in_low_cast = Cast(graph, {'name': name + '/in_low/convert_to_f32', 'dst_type': np.float32}).create_node()
|
||||
in_low_cast.in_port(0).connect(in_low)
|
||||
in_low = in_low_cast.out_port(0)
|
||||
|
||||
in_high_cast = Cast(graph, {'name': name + '/in_high/convert_to_f32', 'dst_type': np.float32}).create_node()
|
||||
in_high_cast.in_port(0).connect(in_high)
|
||||
in_high = in_high_cast.out_port(0)
|
||||
|
||||
out_low_cast = Cast(graph, {'name': name + '/out_low/convert_to_f32', 'dst_type': np.float32}).create_node()
|
||||
out_low_cast.in_port(0).connect(out_low)
|
||||
out_low = out_low_cast.out_port(0)
|
||||
|
||||
out_high_cast = Cast(graph, {'name': name + '/out_high/convert_to_f32', 'dst_type': np.float32}).create_node()
|
||||
out_high_cast.in_port(0).connect(out_high)
|
||||
out_high = out_high_cast.out_port(0)
|
||||
|
||||
# scale calculation
|
||||
output_range = Sub(graph, {'name': name + '/output_range'}).create_node()
|
||||
output_range.in_port(0).connect(out_high)
|
||||
@@ -215,6 +234,15 @@ class CompressQuantizeWeights(BackReplacementPattern):
|
||||
zero_point.in_port(1).connect(zero.out_port(0))
|
||||
zero_point.in_port(2).connect(shift.out_port(0))
|
||||
|
||||
if need_cast_to_f32:
|
||||
fq_dtype = fake_quantize.out_port(0).get_data_type()
|
||||
scale_cast = Cast(graph, {'name': name + '/scale/convert_back', 'dst_type': fq_dtype}).create_node()
|
||||
scale_cast.in_port(0).connect(scale.out_port(0))
|
||||
scale = scale_cast
|
||||
zero_point_cast = Cast(graph, {'name': name + '/zero_point/convert_back', 'dst_type': fq_dtype}).create_node()
|
||||
zero_point_cast.in_port(0).connect(zero_point.out_port(0))
|
||||
zero_point = zero_point_cast
|
||||
|
||||
# DeQuantize(x) == Mul(Sub(x, zero_point), scale)
|
||||
sub_zp = Sub(graph, {'name': name + '/minus_zp'}).create_node()
|
||||
sub_zp.in_port(0).connect(dequantizing_cast.out_port(0))
|
||||
|
||||
@@ -13,14 +13,18 @@ from openvino.tools.mo.ops.elementwise import Sub, Mul
|
||||
from openvino.tools.mo.ops.fakequantize import FakeQuantize
|
||||
from openvino.tools.mo.front.common.partial_infer.eltwise import eltwise_infer
|
||||
from openvino.tools.mo.utils.ir_engine.compare_graphs import compare_graphs
|
||||
from openvino.tools.mo.middle.passes.infer import type_infer
|
||||
from unit_tests.utils.graph import build_graph, regular_op_with_shaped_data, valued_const_with_data, result, connect, \
|
||||
shaped_const_with_data
|
||||
|
||||
|
||||
def nodes_dict(original, transformed=None, levels=255, data=None, il=[-127], ih=[127], ol=[-127], oh=[127]):
|
||||
def nodes_dict(original, transformed=None, levels=255, data=None,
|
||||
il=[-127], ih=[127], ol=[-127], oh=[127],
|
||||
scale=np.array([1]), zp=np.array([0]), int_data=None):
|
||||
shape = [1, 2, 3, 4] if data is None else np.array(data).shape
|
||||
data = np.ones(shape, dtype=original) if data is None else np.array(data, dtype=original)
|
||||
int_data = data.astype(dtype=np.int8)
|
||||
if int_data is None:
|
||||
int_data = data.astype(dtype=np.int8)
|
||||
transformed = transformed if transformed is not None else original
|
||||
|
||||
return {
|
||||
@@ -42,8 +46,8 @@ def nodes_dict(original, transformed=None, levels=255, data=None, il=[-127], ih=
|
||||
'FQ', shape, {'type': 'FakeQuantize', 'infer': FakeQuantize.infer, 'stop_value_propagation': True,
|
||||
'levels': levels, 'op': 'FakeQuantize'}),
|
||||
|
||||
**valued_const_with_data('zp', np.array([0])),
|
||||
**valued_const_with_data('scale', np.array([1])),
|
||||
**valued_const_with_data('zp', zp),
|
||||
**valued_const_with_data('scale', scale),
|
||||
|
||||
**regular_op_with_shaped_data(
|
||||
'sub', shape, {'type': 'Subtract', 'op': 'Sub', 'infer': lambda node: eltwise_infer(node, Sub.operation)}),
|
||||
@@ -233,6 +237,51 @@ class CompressionDataTypeTest(unittest.TestCase):
|
||||
self.assertTrue(flag, resp)
|
||||
|
||||
|
||||
def test_fp16_fake_quantize(self):
|
||||
original_type = np.float16
|
||||
input_low = np.array([-0.59033203125, -1.4833984375, -1.2900390625], dtype=np.float16)
|
||||
input_high = np.array([0.59033203125, 1.4833984375, 1.2900390625], dtype=np.float16)
|
||||
output_low = np.array([0.295166015625, 0.74169921875, 0.64501953125], dtype=np.float16)
|
||||
output_high = np.array([-0.295166015625, -0.74169921875, -0.64501953125], dtype=np.float16)
|
||||
scale = np.array([-0.002325, -0.00584, -0.005077], dtype=np.float16)
|
||||
int_data = np.array([43, 103, 118], dtype=np.int8)
|
||||
nodes = nodes_dict(original_type, transformed=np.int8,
|
||||
levels=255, data=np.array([0.2, 1.2, 1.2], dtype=np.float16),
|
||||
il=input_low, ih=input_high, ol=output_low, oh=output_high, scale=scale, int_data=int_data)
|
||||
|
||||
graph = build_graph(nodes, [
|
||||
*connect('weights:0', '0:FQ'),
|
||||
*connect('il:0', '1:FQ'),
|
||||
*connect('ih:0', '2:FQ'),
|
||||
*connect('ol:0', '3:FQ'),
|
||||
*connect('oh:0', '4:FQ'),
|
||||
*connect('FQ:0', 'output'),
|
||||
], nodes_with_edges_only=True)
|
||||
type_infer(graph)
|
||||
|
||||
error_message = 'Unexpected number of {} nodes {} CompressQuantizeWeights.dequantize_data call `{}`'
|
||||
fq_nodes = graph.get_op_nodes(type='FakeQuantize')
|
||||
self.assertEqual(len(fq_nodes), 1, error_message.format('FakeQuantize', 'before', len(fq_nodes)))
|
||||
|
||||
CompressQuantizeWeights().find_and_replace_pattern(graph)
|
||||
graph.clean_up()
|
||||
ZeroPointOptimizer().find_and_replace_pattern(graph)
|
||||
graph.clean_up()
|
||||
|
||||
fq_nodes = graph.get_op_nodes(type='FakeQuantize')
|
||||
self.assertEqual(len(fq_nodes), 0, error_message.format('FakeQuantize', 'after', len(fq_nodes)))
|
||||
|
||||
graph_ref = build_graph(nodes, [
|
||||
*connect('int_weights:0', '0:cast'),
|
||||
*connect('cast:0', '0:mul'),
|
||||
*connect('scale:0', '1:mul'),
|
||||
*connect('mul:0', 'output'),
|
||||
], {'cast': {'dst_type': original_type}}, nodes_with_edges_only=True)
|
||||
|
||||
(flag, resp) = compare_graphs(graph, graph_ref, 'output', check_op_attrs=True)
|
||||
self.assertTrue(flag, resp)
|
||||
|
||||
|
||||
@generator
|
||||
class AccuracyCheckFP32Test(unittest.TestCase):
|
||||
eps = np.finfo(np.float32).eps
|
||||
|
||||
Reference in New Issue
Block a user