[POT] BC update for transformers (#13869)

* Update node_utils

* HBC update

* Updated HBC & added test

* Apply comments
This commit is contained in:
Nikita Malinin 2022-11-11 08:33:11 +01:00 committed by GitHub
parent 7ab81d0950
commit 8e9a6d3457
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 182 additions and 53 deletions

View File

@ -88,7 +88,7 @@ class BiasCorrection(Algorithm):
logger.update_progress(self._batch_stat_size)
bias = nu.get_bias_for_node(node)
bias_copy = nu.get_node_input(node_copy_bias_add, 1)
bias_copy = nu.get_bias_for_node(node_copy)
current_bias_value = nu.get_node_value(bias)
bias_is_updated = False
@ -184,6 +184,7 @@ class BiasCorrection(Algorithm):
def walk_to_children(node, is_this_branch_node=False):
node_parents = self.get_node_parents(node)
node_input_0 = nu.get_node_input(node, 0)
node_input_0 = nu.get_node_input(node, 1) if node_input_0.type == 'Const' else node_input_0
if is_this_branch_node:
# Jump over Split nodes
if node_input_0.type in self._split_types:
@ -496,4 +497,10 @@ class BiasCorrection(Algorithm):
@staticmethod
def get_node_children(node):
return [n for n in nu.get_all_node_outputs(node) if n is not None and nu.get_input_data_value(n, 0) is None]
child_nodes = []
for output_node in nu.get_all_node_outputs(node):
for input_port_id, _ in enumerate(nu.get_node_input_ports(output_node)):
if nu.get_input_data_value(output_node, input_port_id) is None \
and output_node not in child_nodes:
child_nodes.append(output_node)
return child_nodes

View File

@ -111,6 +111,7 @@ class FastBiasCorrection(Algorithm):
continue
if bias_shift_magnitude < self._threshold:
logger.debug('Setting bias for %s. Magnitude: %f', op_node.fullname, bias_shift_magnitude)
op_node['original_bias'] = current_bias_value
nu.set_node_value(bias_node, bias_shift)
else:

View File

@ -140,9 +140,9 @@ def get_bias_for_node(node: Node):
if len(node_outputs) == 1:
potential_bias = node_outputs[0]
if potential_bias.type == 'Add' and len(get_node_inputs(potential_bias)) > 1:
potential_bias_const = get_node_input(potential_bias, 1)
if potential_bias_const.type == 'Const':
return potential_bias_const
for potential_bias_const in get_node_inputs(potential_bias):
if potential_bias_const.type == 'Const':
return potential_bias_const
return None
@ -191,7 +191,12 @@ def get_quantized_input_key(quantized_node):
If input node of quantized node have one output port -> key is name of fq_input node.
Otherwise, key is tuple (fq_input name, output port number)
"""
quantized_input = get_node_input(quantized_node, 0)
if quantized_node.type == 'Add':
for quantized_node_input in get_node_inputs(quantized_node):
if quantized_node_input.type != 'Const':
quantized_input = quantized_node_input
else:
quantized_input = get_node_input(quantized_node, 0)
key = quantized_input.fullname
if len(quantized_input.out_ports()) > 1:
port_number = quantized_node.in_port(0).get_source().out

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6938940f2c47151dc5dd2d139eda106cce2864700d71dcbae8d058dc38e3cd59
size 173

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1333e866390cb28aa46be9a4a1911fd1e0e06a72ecb17f7d257ca264ca9ff6ab
size 297505

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -16,6 +16,7 @@ from openvino.tools.pot.algorithms.quantization.fake_quantize_configuration impo
from .utils.config import provide_dataset_path
from .utils.path import HARDWARE_CONFIG_PATH, HARDWARE_CONFIG_REFERENCE_PATH, \
TOOL_CONFIG_PATH, INTERMEDIATE_CONFIG_PATH
from .utils.data_helper import load_json
def check_hardware_config(config, config_name):
@ -24,8 +25,7 @@ def check_hardware_config(config, config_name):
with open(path_to_ref_json.as_posix(), 'w') as f:
json.dump(config, f)
with open(path_to_ref_json.as_posix(), 'r') as f:
ref_config = json.load(f)
ref_config = load_json(path_to_ref_json.as_posix())
assert config == ref_config
@ -86,8 +86,7 @@ def test_load_tool_config(config_name, tmp_path, models):
def test_configurations_by_preset(preset):
def _load_config(name):
path_to_conf = INTERMEDIATE_CONFIG_PATH.joinpath(name).as_posix()
with open(path_to_conf, 'r') as f:
return json.load(f)
return load_json(path_to_conf)
config = Dict({
'preset': preset,

View File

@ -0,0 +1,106 @@
# Copyright (C) 2020-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import os
import numpy as np
import pytest
from addict import Dict
from openvino.tools.pot import create_pipeline, load_model
from openvino.tools.pot.engines.simplified_engine import SimplifiedEngine
from openvino.tools.pot.graph.model_utils import get_nodes_by_type, get_node_by_name
from openvino.tools.pot.graph.node_utils import get_bias_for_node, get_node_value
from openvino.tools.pot.graph.special_operations import OPERATIONS_WITH_BIAS
from .utils.config import merge_configs
from .utils.data_helper import dump_intermediate_data, load_json
from .test_scales import RandomDataLoader
EPS = 1e-6
TEST_TRANSFORMERS_MODELS = [
(
'transformer_example',
'pytorch',
'DefaultQuantization',
{
'preset': 'performance',
'stat_subset_size': 10,
'threshold': 1000,
'target_device': 'CPU'
},
'transformer_example_fbc'
),
(
'transformer_example',
'pytorch',
'DefaultQuantization',
{
'preset': 'performance',
'stat_subset_size': 10,
'threshold': 1000,
'target_device': 'CPU',
'use_fast_bias': False
},
'transformer_example_hbc'
)
]
@pytest.fixture(scope='module', params=TEST_TRANSFORMERS_MODELS,
ids=['{}_{}_{}'.format(*m) for m in TEST_TRANSFORMERS_MODELS])
def _params(request):
return request.param
def test_transformer_biases_after_correction(_params, tmp_path, models):
model_name, framework, algorithm, algorithm_params, test_name = _params
references_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'./data/reference_biases')
reference_path = os.path.join(references_dir, f'{test_name}.json')
reference_exists = os.path.isfile(reference_path)
local_path = os.path.join(tmp_path, f'{test_name}.json')
model = models.get(model_name, framework, tmp_path)
algorithm_config = Dict({
'algorithms': [{
'name': algorithm,
'params': algorithm_params
}]
})
engine_config = {'device': 'CPU'}
config = merge_configs(model.model_params, engine_config, algorithm_config)
data_loader = RandomDataLoader(shapes={'input': (1, 128)}, seed=0)
engine = SimplifiedEngine(config.engine, data_loader=data_loader)
pipeline = create_pipeline(config.compression.algorithms, engine)
model = load_model(config.model)
compressed_model = pipeline.run(model)
values = {}
nodes_with_bias = get_nodes_by_type(
compressed_model, [op['type'] for op in OPERATIONS_WITH_BIAS])
for node_with_bias in nodes_with_bias:
bias = get_bias_for_node(node_with_bias)
if bias is not None:
values[node_with_bias.fullname] = get_node_value(bias)
if not reference_exists:
dump_intermediate_data(reference_path, values)
return
dump_intermediate_data(local_path, values)
references = load_json(reference_path)
assert len(references) == len(values)
for node_name, ref_val in references.items():
node = get_node_by_name(compressed_model, node_name)
bias = get_bias_for_node(node)
cur_val = get_node_value(bias)
np.testing.assert_almost_equal(cur_val, ref_val)

View File

@ -4,7 +4,6 @@
import json
import os
from copy import deepcopy
import numpy as np
import pytest
from addict import Dict
@ -17,22 +16,11 @@ from openvino.tools.pot.graph import load_model
from openvino.tools.pot.graph.node_utils import get_node_inputs, get_node_input, get_node_value
from openvino.tools.pot.graph import model_utils as mu
from openvino.tools.pot.statistics.collector import StatisticsCollector
from .utils.data_helper import dump_intermediate_data, load_json
EPS = 1e-6
class NumpyEncoder(json.JSONEncoder):
""" Special json encoder for numpy types """
# pylint: disable=W0221, E0202
def default(self, o):
if isinstance(o, np.integer):
return int(o)
if isinstance(o, np.floating):
return float(o)
if isinstance(o, np.ndarray):
return o.tolist()
return json.JSONEncoder.default(self, o)
def get_fq_nodes_stats_algo(model, preset, bits, is_weights, clipping_value=None):
test_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
@ -89,11 +77,6 @@ def get_fq_nodes_stats_algo(model, preset, bits, is_weights, clipping_value=None
return out
def get_ref_stats(stats_path):
with open(stats_path) as json_file:
return json.load(json_file)
CONFIGURATIONS = [('performance', 8,
os.path.join(os.path.dirname(os.path.realpath(__file__)),
'./data/reference_scale/mobilenet-v2-pytorch_performance_activations.json'), None),
@ -127,11 +110,11 @@ def test_activation_scales(tmp_path, models, preset, bits, stats_path, clipping_
model = models.get('mobilenet-v2-pytorch', 'pytorch', tmp_path)
ref_nodes = get_ref_stats(stats_path)
ref_nodes = load_json(stats_path)
nodes = normalize(get_fq_nodes_stats_algo(model, preset, bits, False,
clipping_value=clipping_value))
local_path = os.path.join(tmp_path, '{}.json'.format(stats_path.split("_")[-2]))
dump_intermediate_scales(local_path, nodes)
dump_intermediate_data(local_path, nodes)
assert len(ref_nodes) == len(nodes)
processed_nodes = []
@ -154,10 +137,10 @@ def test_weights_scales(tmp_path, models):
'./data/reference_scale/mobilenet-v2-pytorch_weights.json')
model = models.get('mobilenet-v2-pytorch', 'pytorch', tmp_path)
ref_weights = get_ref_stats(path_to_weights)
ref_weights = load_json(path_to_weights)
weights = get_fq_nodes_stats_algo(model, False, 8, True)
local_path = os.path.join(tmp_path, '{}.json'.format('mv2_weights'))
dump_intermediate_scales(local_path, weights)
dump_intermediate_data(local_path, weights)
for fq_name in weights:
item_min, item_max = weights[fq_name]['low_level'], weights[fq_name]['high_level']
@ -178,11 +161,6 @@ def test_weights_scales(tmp_path, models):
assert assert_flag
def load_refs(path_to_refs):
with open(path_to_refs) as json_file:
return json.load(json_file)
def make_list(x):
if isinstance(x, np.ndarray):
x = x.tolist()
@ -298,7 +276,7 @@ def test_fake_quantize_configurations(tmp_path, models, model_name, model_framew
refs_path = os.path.join(REFERENCES_DIR, '{}_{}.json'.format(model_name, algo_mode))
local_path = os.path.join(tmp_path, '{}.json'.format(model_name))
ref_exists = os.path.isfile(refs_path)
refs = load_refs(refs_path) if ref_exists else {}
refs = load_json(refs_path) if ref_exists else {}
local_file = open(local_path, 'w')
if not ref_exists:
@ -362,7 +340,7 @@ def test_matmul_scale_unification(tmp_path, models, model_name, model_framework,
refs_path = os.path.join(REFERENCES_DIR, f'{model_name}.json')
local_path = os.path.join(tmp_path, f'{model_name}.json')
ref_exists = os.path.isfile(refs_path)
refs = load_refs(refs_path) if ref_exists else {}
refs = load_json(refs_path) if ref_exists else {}
local_file = open(local_path, 'w')
if not ref_exists:
@ -435,9 +413,3 @@ def _get_tf_accuracy_checker_config(path_to_dataset):
}]
}
]}]})
def dump_intermediate_scales(local_path, data):
data = json.dumps(deepcopy(data), cls=NumpyEncoder)
local_file = open(local_path, 'w')
json.dump(data, local_file)

View File

@ -16,6 +16,7 @@ from openvino.tools.pot.statistics.collector import StatisticsCollector
from openvino.tools.pot.algorithms.quantization.minmax.algorithm import MinMaxQuantization
from openvino.tools.pot.algorithms.quantization.bias_correction.algorithm import BiasCorrection
from .utils.config import PATHS2DATASETS_CONFIG
from .utils.data_helper import load_json
TEST_MODELS = [('mobilenet-v2-pytorch', 'pytorch'), ('lstm_outs_quantization', 'tf')]
@ -60,8 +61,7 @@ def test_statistics_collector_subsets(tmp_path, models, model_name, model_framew
local_path = os.path.join(tmp_path, '{}_{}.json'.format(model_name, 'statistics_data'))
local_file = open(local_path, 'w')
with open(refs_file.as_posix()) as file:
refs = json.load(file)
refs = load_json(refs_file.as_posix())
eps = 1e-6
local_out = {}

View File

@ -2,7 +2,6 @@
# SPDX-License-Identifier: Apache-2.0
import os
import json
from unittest.mock import patch
import pytest
try:
@ -22,6 +21,7 @@ from openvino.tools.pot.utils.ac_imports import ConfigReader
from openvino.tools.pot.configs.config import Config
from .utils.config import provide_dataset_path
from .utils.path import TELEMETRY_CONFIG_PATH
from .utils.data_helper import load_json
TEST_MODEL = ('mobilenet-v2-pytorch', 'pytorch')
TOOL_CONFIG_NAME = ['mobilenet-v2-pytorch.json', 'mobilenet-v2-pytorch_aa.json', 'mobilenet-v2-pytorch_sparsity.json']
@ -44,8 +44,7 @@ class TelemetryTest(Telemetry):
)
def test_telemetry(config_name, tmp_path, models):
telemetry = TelemetryTest()
with open(os.path.join(TELEMETRY_CONFIG_PATH, 'expected_values.txt')) as file:
expected = json.load(file)
expected = load_json(os.path.join(TELEMETRY_CONFIG_PATH, 'expected_values.txt'))
@patch(func, new=telemetry.send_event)
def compress_model():

View File

@ -15,6 +15,7 @@ from openvino.tools.pot.pipeline.initializer import create_pipeline
from openvino.tools.pot.engines.ac_engine import ACEngine
from .utils.config import get_engine_config, merge_configs
from .utils.path import TEST_ROOT
from .utils.data_helper import load_json
TEST_MODELS = [
('mobilenet-v2-pytorch', 'pytorch', 'MinMaxQuantization', 'performance', 'VPU'),
@ -69,9 +70,8 @@ def test_unify_scales(_params, tmp_path, models):
ref_path = REFERENCES_PATH.joinpath(model_name + '_to_unify.json')
if ref_path.exists():
with open(ref_path.as_posix(), 'r') as f:
to_unify_ref = json.load(f)
assert to_unify == to_unify_ref
to_unify_ref = load_json(ref_path.as_posix())
assert to_unify == to_unify_ref
else:
with open(ref_path.as_posix(), 'w+') as f:
json.dump(to_unify, f, indent=4)

View File

@ -0,0 +1,32 @@
# Copyright (C) 2020-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import json
from copy import deepcopy
import numpy as np
class NumpyEncoder(json.JSONEncoder):
""" Special json encoder for numpy types """
# pylint: disable=W0221, E0202
def default(self, o):
if isinstance(o, np.integer):
return int(o)
if isinstance(o, np.floating):
return float(o)
if isinstance(o, np.ndarray):
return o.tolist()
return json.JSONEncoder.default(self, o)
def dump_intermediate_data(local_path, data):
data = json.dumps(deepcopy(data), cls=NumpyEncoder)
local_file = open(local_path, 'w')
json.dump(data, local_file)
def load_json(stats_path):
with open(stats_path) as json_file:
return json.load(json_file)