def test_can_load_quant_algo__with_defaults(): model = BasicConvTestModel() config = get_quantization_config_without_range_init() register_bn_adaptation_init_args(config) builder = create_compression_algorithm_builder(config) assert isinstance(builder, QuantizationBuilder) quant_model, _ = create_compressed_model_and_algo_for_test( deepcopy(model), config) model_conv = get_all_modules_by_type(model, 'Conv2d') quant_model_conv = get_all_modules_by_type( quant_model.get_nncf_wrapped_model(), 'NNCFConv2d') assert len(model_conv) == len(quant_model_conv) for module_scope, _ in model_conv.items(): quant_scope = deepcopy(module_scope) # type: Scope quant_scope.pop() quant_scope.push(ScopeElement('NNCFConv2d', 'conv')) assert quant_scope in quant_model_conv.keys() store = [] for op in quant_model_conv[quant_scope].pre_ops.values(): if isinstance(op, (UpdateInputs, UpdateWeight)) and isinstance( op.operand, SymmetricQuantizer): assert op.__class__.__name__ not in store store.append(op.__class__.__name__) assert UpdateWeight.__name__ in store
def test_quantization_configs__with_defaults(): model = BasicConvTestModel() config = get_quantization_config_without_range_init() register_bn_adaptation_init_args(config) _, compression_ctrl = create_compressed_model_and_algo_for_test( model, config) assert isinstance(compression_ctrl, QuantizationController) weight_quantizers = compression_ctrl.weight_quantizers activation_quantizer_infos = compression_ctrl.non_weight_quantizers ref_weight_qspec = PTQuantizerSpec( num_bits=8, mode=QuantizationMode.SYMMETRIC, signedness_to_force=True, narrow_range=True, half_range=False, scale_shape=model.wq_scale_shape_per_channel, logarithm_scale=False) for wq_info in weight_quantizers.values(): compare_qspecs(ref_weight_qspec, wq_info.quantizer_module_ref) ref_activation_qspec = PTQuantizerSpec(num_bits=8, mode=QuantizationMode.SYMMETRIC, signedness_to_force=None, narrow_range=False, half_range=False, scale_shape=(1, ), logarithm_scale=False) for aq_info in activation_quantizer_infos.values(): compare_qspecs(ref_activation_qspec, aq_info.quantizer_module_ref)
def test_quantize_outputs_with_scope_overrides(): config = get_quantization_config_without_range_init() config["input_info"] = [{ "sample_size": [2, 3, 32, 32], }] model = QuantizeOutputsTestModel() config['compression']['quantize_outputs'] = True config['target_device'] = "TRIAL" config['compression']['scope_overrides'] = { "activations": { "/nncf_model_output_0": { "bits": 4, "mode": "asymmetric", } } } register_bn_adaptation_init_args(config) model, ctrl = create_compressed_model_and_algo_for_test(model, config) output_quantizers =\ [q for qid, q in ctrl.all_quantizations.items() if isinstance(qid, NonWeightQuantizerId)] for q in output_quantizers[1:]: assert q.num_bits == 8 assert isinstance(q, SymmetricQuantizer) assert output_quantizers[0].num_bits == 4 assert isinstance(output_quantizers[0], AsymmetricQuantizer)
def create_autoq_test_config(batch_size=10, image_size=10, num_channels=3, num_init_samples=1): config = get_quantization_config_without_range_init() config['input_info'] = { "sample_size": [batch_size, num_channels, image_size, image_size], } config['batch_size'] = batch_size config['compression'].update({ 'initializer': { 'precision': { "type": "autoq", "bits": [2, 4, 8], "iter_number": 3, "compression_ratio": 0.15, "eval_subset_ratio": 1.0, "warmup_iter_number": 2 }, 'range': { 'num_init_samples': num_init_samples }, 'batchnorm_adaptation': { 'num_bn_adaptation_samples': 0 } } }) return config
def create_hawq_test_config(batch_size=10, num_data_points=100, image_size=10): config = get_quantization_config_without_range_init() config['input_info'] = { "sample_size": [batch_size, 3, image_size, image_size], } config['batch_size'] = batch_size config['compression'].update({ 'initializer': { 'precision': { "type": "hawq", "bits": [4, 8, 6], "num_data_points": num_data_points, "iter_number": 1, "tolerance": 1e-2 }, 'range': { 'num_init_samples': 1 }, 'batchnorm_adaptation': { 'num_bn_adaptation_samples': 0 } } }) return config
def test_quantize_outputs(): config = get_quantization_config_without_range_init() config["input_info"] = [{ "sample_size": [2, 3, 32, 32], }] model = QuantizeOutputsTestModel() config['compression']['quantize_outputs'] = True register_bn_adaptation_init_args(config) model, qctrl = create_compressed_model_and_algo_for_test(model, config) REF_QUANTIZED_OUTPUT_MODULE_SCOPES = [ 'QuantizeOutputsTestModel/NNCFConv2d[conv1]/conv2d_0|OUTPUT', 'QuantizeOutputsTestModel/NNCFConv2d[conv2]/conv2d_0|OUTPUT', 'QuantizeOutputsTestModel/NNCFConv2d[conv3]/conv2d_0|OUTPUT', 'QuantizeOutputsTestModel/NNCFConv2d[conv4]/conv2d_0|OUTPUT' ] actual_output_quantizer_str_scopes =\ [str(aq_id) for aq_id in qctrl.non_weight_quantizers if 'nncf_model_input' not in str(aq_id)] assert len(REF_QUANTIZED_OUTPUT_MODULE_SCOPES) == len( actual_output_quantizer_str_scopes) for ref_qinput_scope_str in REF_QUANTIZED_OUTPUT_MODULE_SCOPES: matches = [] for aq_id in qctrl.non_weight_quantizers: if str(aq_id) == ref_qinput_scope_str: matches.append(aq_id) assert len(matches) == 1 quantizer = qctrl.non_weight_quantizers[ matches[0]].quantizer_module_ref assert isinstance(quantizer, SymmetricQuantizer)
def __init__(self): self.model_creator = AddTwoConv config = get_quantization_config_without_range_init() config['compression']['initializer'].update({ "precision": { "bitwidth_per_scope": [[2, 'AddTwoConv/NNCFConv2d[conv1]/conv2d_0|WEIGHT'], [4, 'AddTwoConv/NNCFConv2d[conv2]/conv2d_0|WEIGHT']] } }) config['target_device'] = 'TRIAL' config['compression']["activations"] = {"bits": 6} self.config = config self.ref_bits = [ (WeightQuantizerId( target_node_name='AddTwoConv/NNCFConv2d[conv1]/conv2d_0'), 2), (WeightQuantizerId( target_node_name='AddTwoConv/NNCFConv2d[conv2]/conv2d_0'), 4), (NonWeightQuantizerId( target_node_name='AddTwoConv/NNCFConv2d[conv2]/conv2d_0'), 6), (NonWeightQuantizerId( target_node_name='AddTwoConv/NNCFConv2d[conv1]/conv2d_0'), 6), (NonWeightQuantizerId('/nncf_model_input_0'), 6) ] self.expected_stats = BitwidthDistributionStatistics( num_wq_per_bitwidth={ 4: 1, 2: 1 }, num_aq_per_bitwidth={6: 3}) self.config_to_resume = None
def test_unified_scales_are_identical_in_onnx(self, tmp_path): # pylint:disable=no-member nncf_config = get_quantization_config_without_range_init(model_size=1) nncf_config["compression"]["quantize_outputs"] = True nncf_config["input_info"] = [ { "sample_size": [1, 1, 1, 2], }, ] nncf_config["target_device"] = "VPU" register_bn_adaptation_init_args(nncf_config) compressed_model, compression_ctrl = create_compressed_model_and_algo_for_test( SimplerModelForUnifiedScalesTesting(), nncf_config) with torch.no_grad(): for quant_info in compression_ctrl.non_weight_quantizers.values(): if isinstance(quant_info.quantizer_module_ref, AsymmetricQuantizer): quant_info.quantizer_module_ref.input_range *= torch.abs( torch.rand_like( quant_info.quantizer_module_ref.input_range)) else: quant_info.quantizer_module_ref.scale *= torch.abs( torch.rand_like(quant_info.quantizer_module_ref.scale)) test_input1 = torch.ones([1, 1, 1, 2]) compressed_model.forward(test_input1) onnx_path = str(tmp_path / "model.onnx") compression_ctrl.export_model(onnx_path) onnx_model = onnx.load(onnx_path) fq_nodes = TestsWithONNXInspection.get_fq_nodes(onnx_model) eltwise_dominator_predicate = partial( TestsWithONNXInspection.immediately_dominates_add_or_mul, graph=onnx_model.graph) eltwise_fq_nodes = list(filter(eltwise_dominator_predicate, fq_nodes)) cat_dominator_predicate = partial( TestsWithONNXInspection.immediately_dominates_cat, graph=onnx_model.graph) cat_fq_nodes = list(filter(cat_dominator_predicate, fq_nodes)) fq_nodes_grouped_by_output = TestsWithONNXInspection.group_nodes_by_output_target( eltwise_fq_nodes + cat_fq_nodes, onnx_model.graph) for unified_scale_group in fq_nodes_grouped_by_output: inputs = [ resolve_constant_node_inputs_to_values(fq_node, onnx_model.graph) for fq_node in unified_scale_group ] for inputs_dict in inputs[1:]: curr_values = list(inputs_dict.values()) ref_values = list(inputs[0].values()) assert curr_values == ref_values # All inputs for unified scale quantizers must be equal
def test_debug_mode(): config = get_quantization_config_without_range_init() register_bn_adaptation_init_args(config) model = BasicConvTestModel() with nncf_debug(): model, _ = create_compressed_model_and_algo_for_test(model, config) model.forward( torch.zeros(BasicConvTestModel.INPUT_SIZE, device=next(model.parameters()).device))
def test_weight_and_act_quantizer_scale_unification(self, tmp_path): # pylint:disable=no-member nncf_config = get_quantization_config_without_range_init(model_size=1) nncf_config["input_info"] = [ { "sample_size": [1, 5], "type": "long", "filler": "zeros" }, ] nncf_config["target_device"] = "VPU" register_bn_adaptation_init_args(nncf_config) compressed_model, compression_ctrl = create_compressed_model_and_algo_for_test( TwoEmbeddingAddModel(), nncf_config) with torch.no_grad(): for quant_module in compression_ctrl.all_quantizations.values(): if isinstance(quant_module, AsymmetricQuantizer): quant_module.input_range *= torch.abs( torch.rand_like(quant_module.input_range)) else: quant_module.scale *= torch.abs( torch.rand_like(quant_module.scale)) test_input1 = torch.ones([1, 5], dtype=torch.long) compressed_model.forward(test_input1) onnx_path = str(tmp_path / "model.onnx") compression_ctrl.export_model(onnx_path) onnx_model = onnx.load(onnx_path) fq_nodes = TestsWithONNXInspection.get_fq_nodes(onnx_model) eltwise_dominator_predicate = partial( TestsWithONNXInspection.immediately_dominates_add_or_mul, graph=onnx_model.graph) embedding_dominator_predicate = partial( TestsWithONNXInspection.immediately_dominates_embedding, graph=onnx_model.graph) eltwise_fq_nodes = list(filter(eltwise_dominator_predicate, fq_nodes)) embedding_weight_fq_nodes = list( filter(embedding_dominator_predicate, fq_nodes)) fq_nodes_with_expected_unified_scales = embedding_weight_fq_nodes + eltwise_fq_nodes unified_fq_node_inputs = [ resolve_constant_node_inputs_to_values(fq_node, onnx_model.graph) for fq_node in fq_nodes_with_expected_unified_scales ] for inputs_dict in unified_fq_node_inputs[1:]: curr_values = list(inputs_dict.values()) ref_values = list(unified_fq_node_inputs[0].values()) assert curr_values == ref_values # All inputs for unified scale quantizers must be equal
def test_can_create_quant_loss_and_scheduler(): config = get_quantization_config_without_range_init() register_bn_adaptation_init_args(config) _, compression_ctrl = create_compressed_model_and_algo_for_test( BasicConvTestModel(), config) loss = compression_ctrl.loss assert isinstance(loss, PTCompressionLoss) scheduler = compression_ctrl.scheduler assert isinstance(scheduler, CompressionScheduler)
def test_mock_dump_checkpoint(aa_config): is_called_dump_checkpoint_fn = False def mock_dump_checkpoint_fn(model, compression_controller, accuracy_aware_runner, aa_log_dir): from nncf.api.compression import CompressionAlgorithmController from nncf.common.accuracy_aware_training.runner import TrainingRunner assert isinstance(model, torch.nn.Module) assert isinstance(compression_controller, CompressionAlgorithmController) assert isinstance(accuracy_aware_runner, TrainingRunner) assert isinstance(aa_log_dir, str) nonlocal is_called_dump_checkpoint_fn is_called_dump_checkpoint_fn = True config = get_quantization_config_without_range_init(LeNet.INPUT_SIZE[-1]) train_loader = create_ones_mock_dataloader(aa_config, num_samples=10) model = LeNet() config.update(aa_config) def train_fn(compression_ctrl, model, epoch, optimizer, lr_scheduler, train_loader=train_loader): pass def mock_validate_fn(model, init_step=False, epoch=0): return 80 def configure_optimizers_fn(): optimizer = SGD(model.parameters(), lr=0.001) return optimizer, None config = register_default_init_args(config, train_loader=train_loader, model_eval_fn=partial(mock_validate_fn, init_step=True)) model, compression_ctrl = create_compressed_model_and_algo_for_test( model, config) early_stopping_training_loop = EarlyExitCompressionTrainingLoop( config, compression_ctrl, dump_checkpoints=True) model = early_stopping_training_loop.run( model, train_epoch_fn=train_fn, validate_fn=partial(mock_validate_fn), configure_optimizers_fn=configure_optimizers_fn, dump_checkpoint_fn=mock_dump_checkpoint_fn) assert is_called_dump_checkpoint_fn
def test_quantizer_scale_linking(mocker): nncf_config = get_quantization_config_without_range_init(model_size=1) nncf_config["input_info"] = [{ "sample_size": [1, 1, 1, 1], }, { "sample_size": [1, 1, 1, 1], }] nncf_config["compression"]["activations"] = { "unified_scale_ops": [[ # Note: Assuming that quantizers are attached as a post-op to the specified operation "EltwiseQuantizerLinkingTestModel/Path[path2]/__mul___0", "EltwiseQuantizerLinkingTestModel/Path[path2]/__add___0", ]], "ignored_scopes": [ # Ignore path output averaging operations "EltwiseQuantizerLinkingTestModel/__add___0", "EltwiseQuantizerLinkingTestModel/__add___1", "EltwiseQuantizerLinkingTestModel/__add___2", ] } register_bn_adaptation_init_args(nncf_config) compressed_model, compression_ctrl = create_compressed_model_and_algo_for_test( EltwiseQuantizerLinkingTestModel(), nncf_config) # 18 inputs to quantize (14 regular + 4 linked), # 8 quantization points left after propagation, out of these 3 are linked assert len(compression_ctrl.non_weight_quantizers) == 6 shared_quantizer_id = NonWeightQuantizerId( target_node_name="/nncf_model_input_0") non_shared_spies = [] for aq_id, aq_info in compression_ctrl.non_weight_quantizers.items(): quantizer = aq_info.quantizer_module_ref spy = mocker.spy(quantizer, 'forward') if aq_id == shared_quantizer_id: shared_spy = spy else: non_shared_spies.append(spy) test_input1 = torch.ones([1, 1, 1, 1]) test_input2 = 2 * test_input1 compressed_model(test_input1, test_input2) assert shared_spy.call_count == 3 for non_shared_spy in non_shared_spies: assert non_shared_spy.call_count == 1
def get_model_and_ctrl_with_applied_hw_config_quantization( model: torch.nn.Module, hw_config_dict: dict, should_be_quantize_inputs: bool = True): nncf_config = get_quantization_config_without_range_init(model_size=1) nncf_config["compression"].update( {"quantize_inputs": should_be_quantize_inputs}) nncf_config["target_device"] = "ANY" # for compatibility net = NNCFNetwork(model, input_infos=[ModelInputInfo([1, 2, 1, 1])]) hw_config = PTHWConfig.from_dict(hw_config_dict) qbuilder = QuantizationBuilder(nncf_config, should_init=False) qbuilder.hw_config = hw_config net = qbuilder.apply_to(net) ctrl = qbuilder.build_controller(net) return net, ctrl
def test_quantization_configs__custom(): model = BasicConvTestModel() config = get_quantization_config_without_range_init() config['compression'].update({ "weights": { "mode": "asymmetric", "per_channel": True, "bits": 4 }, "activations": { "mode": "asymmetric", "bits": 4, "signed": True, }, }) config['target_device'] = 'TRIAL' register_bn_adaptation_init_args(config) _, compression_ctrl = create_compressed_model_and_algo_for_test( model, config) assert isinstance(compression_ctrl, QuantizationController) weight_quantizers = compression_ctrl.weight_quantizers activation_quantizer_infos = compression_ctrl.non_weight_quantizers ref_weight_qspec = PTQuantizerSpec( num_bits=4, mode=QuantizationMode.ASYMMETRIC, signedness_to_force=None, scale_shape=model.wq_scale_shape_per_channel, narrow_range=True, half_range=False, logarithm_scale=False) for wq_info in weight_quantizers.values(): compare_qspecs(ref_weight_qspec, wq_info.quantizer_module_ref) ref_activation_qspec = PTQuantizerSpec(num_bits=4, mode=QuantizationMode.ASYMMETRIC, signedness_to_force=True, scale_shape=(1, ), narrow_range=False, half_range=False, logarithm_scale=False) for aq_info in activation_quantizer_infos.values(): compare_qspecs(ref_activation_qspec, aq_info.quantizer_module_ref)
def test_quantize_inputs(): model = QuantizeInputsTestModel() config = get_quantization_config_without_range_init() config["input_info"] = [{ "sample_size": [2, 3, 32, 32], }, { "sample_size": [2, 3, 32, 32], }, { "sample_size": [2, 3, 32, 32], }, { "sample_size": [2, 3, 32, 32], }, { "sample_size": [2, 3, 32, 32], }] register_bn_adaptation_init_args(config) model, qctrl = create_compressed_model_and_algo_for_test(model, config) REF_QUANTIZED_INPUT_MODULE_SCOPES = [ '/nncf_model_input_0|OUTPUT', '/nncf_model_input_1|OUTPUT', '/nncf_model_input_2|OUTPUT', '/nncf_model_input_3|OUTPUT', '/nncf_model_input_4|OUTPUT' ] actual_input_quantizer_str_scopes = [] for aq_id, aq_info in qctrl.non_weight_quantizers.items(): for target_point in aq_info.affected_insertions: quantizer_target_node_name = str(target_point.target_node_name) if 'nncf_model_input' in quantizer_target_node_name: actual_input_quantizer_str_scopes.append( quantizer_target_node_name) assert len(REF_QUANTIZED_INPUT_MODULE_SCOPES) == len( actual_input_quantizer_str_scopes) for qinput_scope_str in actual_input_quantizer_str_scopes: matches = set() for aq_id, aq_info in qctrl.non_weight_quantizers.items(): for target_point in aq_info.affected_insertions: if qinput_scope_str in str(target_point.target_node_name): matches.add(aq_id) assert len(matches) == 1 input_aq_id = next(iter(matches)) quantizer = qctrl.non_weight_quantizers[ input_aq_id].quantizer_module_ref assert isinstance(quantizer, SymmetricQuantizer)
def test_staged_quantization_saves_enabled_quantizers_in_state_dict(tmp_path): config = get_quantization_config_without_range_init() config["compression"]["params"] = { "activations_quant_start_epoch": 2, "weights_quant_start_epoch": 1 } register_bn_adaptation_init_args(config) _, ctrl_save = create_compressed_model_and_algo_for_test( BasicConvTestModel(), config) ctrl_save.scheduler.epoch_step() ctrl_save.scheduler.epoch_step() compression_state = ctrl_save.get_compression_state() _, ctrl_load = create_compressed_model_and_algo_for_test( BasicConvTestModel(), config, compression_state=compression_state) for quantizer_info in ctrl_load.non_weight_quantizers.values(): assert not quantizer_info.quantizer_module_ref.is_enabled_quantization( ) for quantizer_info in ctrl_load.weight_quantizers.values(): assert quantizer_info.quantizer_module_ref.is_enabled_quantization()
def disable_quantizer_gradients(): config = get_quantization_config_without_range_init() config['input_info'] = { "sample_size": [2, 3, 10, 10], } register_bn_adaptation_init_args(config) model = MobileNetV2(num_classes=10) model, compression_ctrl = create_compressed_model_and_algo_for_test( model, config) original_requires_grad_per_param = get_requires_grad_per_param(model) quantization_types = [ class_type.__name__ for class_type in QUANTIZATION_MODULES.registry_dict.values() ] all_quantizations = get_all_modules_by_type(model, quantization_types) quantizers_switcher = QuantizersSwitcher(list(all_quantizations.values())) params_to_restore = HAWQPrecisionInitializer.disable_all_gradients_except_weights_of_quantized_modules( quantizers_switcher, compression_ctrl.weight_quantizers, model, get_skipped_quantized_weight_node_names()) return quantizers_switcher, params_to_restore, model, compression_ctrl, original_requires_grad_per_param
def test_eltwise_unified_scales_for_vpu(): nncf_config = get_quantization_config_without_range_init(model_size=1) nncf_config["input_info"] = [{ "sample_size": [1, 1, 1, 1], }, { "sample_size": [1, 1, 1, 1], }] nncf_config["target_device"] = "VPU" register_bn_adaptation_init_args(nncf_config) _, compression_ctrl = create_compressed_model_and_algo_for_test( EltwiseQuantizerLinkingTestModel(), nncf_config) assert len(compression_ctrl.non_weight_quantizers) == 2 total_quantizations = sum([ len(info.affected_insertions) for info in compression_ctrl.non_weight_quantizers.values() ]) assert total_quantizations == 8
def test_unified_scales_with_shared_nodes(): nncf_config = get_quantization_config_without_range_init(model_size=1) nncf_config["input_info"] = [ { "sample_size": [1, 5], "type": "long", "filler": "zeros" }, ] nncf_config["target_device"] = "VPU" register_bn_adaptation_init_args(nncf_config) _, compression_ctrl = create_compressed_model_and_algo_for_test( SharedEmbeddingAddModel(), nncf_config) # type: NNCFNetwork, QuantizationController assert len(compression_ctrl.weight_quantizers ) == 1 # The two embedding nodes point to a single shared layer assert len(compression_ctrl.non_weight_quantizers ) == 0 # The "add" operation has its inputs already quantized
def test_unified_scales_with_concat(target_device, model_creator, ref_aq_module_count, ref_quantizations): nncf_config = get_quantization_config_without_range_init(model_size=1) nncf_config["input_info"] = [{ "sample_size": [1, 4, 1, 1], }, { "sample_size": [1, 4, 1, 1], }] nncf_config["target_device"] = target_device register_bn_adaptation_init_args(nncf_config) _, compression_ctrl = create_compressed_model_and_algo_for_test( model_creator(), nncf_config) assert len(compression_ctrl.non_weight_quantizers) == ref_aq_module_count total_quantizations = sum([ len(info.affected_insertions) for info in compression_ctrl.non_weight_quantizers.values() ]) assert total_quantizations == ref_quantizations
def test_load_state_sets_initialized_flag(): config = get_quantization_config_without_range_init() register_bn_adaptation_init_args(config) model = TwoConvTestModel() quant_model, qctrl = create_compressed_model_and_algo_for_test( model, config) load_state( quant_model, { 'module.features.0.0.pre_ops.0.op.signed_tensor': torch.tensor( [1.0]), # quantizer of 1st conv's weights 'module.features.1.0.pre_ops.0.op.scale': torch.ones( 1, 1, 1, 1) # quantizer of 2nd conv's weights }) for wq_info in qctrl.weight_quantizers.values(): assert wq_info.quantizer_module_ref.initialized for aq_info in qctrl.non_weight_quantizers.values(): assert not aq_info.quantizer_module_ref.initialized
def test_quantizers_have_proper_narrow_range_set(): class Model(nn.Module): def __init__(self, size=1): super().__init__() self.size = size self.conv = nn.Conv2d(size, size, size) def forward(self, x): return self.conv(x) model = Model() config = get_quantization_config_without_range_init(model_size=2) register_bn_adaptation_init_args(config) quant_model, _ = create_compressed_model_and_algo_for_test(model, config) for module in quant_model.modules(): if isinstance(module, NNCFConv2d): for op in module.pre_ops.values(): assert isinstance(op, (UpdateWeight, UpdateInputs)) assert op.operand.narrow_range == isinstance(op, UpdateWeight) for _, aq in quant_model.get_compression_modules_by_type( ExtraCompressionModuleType.EXTERNAL_QUANTIZER).items(): assert aq.narrow_range is False
def test_accuracy_aware_config(aa_config, must_raise): def mock_validate_fn(model): pass config = get_quantization_config_without_range_init(LeNet.INPUT_SIZE[-1]) config.update({ "accuracy_aware_training": { "mode": "adaptive_compression_level", "params": { "maximal_relative_accuracy_degradation": 1, "initial_training_phase_epochs": 1, "patience_epochs": 10 } } }) config.update(aa_config) train_loader = create_ones_mock_dataloader(config, num_samples=10) model = LeNet() config = register_default_init_args(config, train_loader=train_loader, model_eval_fn=mock_validate_fn) model, compression_ctrl = create_compressed_model_and_algo_for_test( model, config) if must_raise: with pytest.raises(RuntimeError): _ = create_accuracy_aware_training_loop(config, compression_ctrl, dump_checkpoints=False) else: _ = create_accuracy_aware_training_loop(config, compression_ctrl, dump_checkpoints=False)
def test_early_exit_with_mock_validation(max_accuracy_degradation, exit_epoch_number, maximal_total_epochs=100): epoch_counter = 0 def mock_validate_fn(model, init_step=False, epoch=0): original_metric = 0.85 if init_step: return original_metric nonlocal epoch_counter epoch_counter = epoch if "maximal_relative_accuracy_degradation" in max_accuracy_degradation: return original_metric * (1 - 0.01 * max_accuracy_degradation[ 'maximal_relative_accuracy_degradation']) * (epoch / exit_epoch_number) return (original_metric - max_accuracy_degradation['maximal_absolute_accuracy_degradation']) * \ epoch / exit_epoch_number config = get_quantization_config_without_range_init(LeNet.INPUT_SIZE[-1]) params = {"maximal_total_epochs": maximal_total_epochs} params.update(max_accuracy_degradation) accuracy_aware_config = { "accuracy_aware_training": { "mode": "early_exit", "params": params } } config.update(accuracy_aware_config) train_loader = create_ones_mock_dataloader(config, num_samples=10) model = LeNet() config = register_default_init_args(config, train_loader=train_loader, model_eval_fn=partial(mock_validate_fn, init_step=True)) model, compression_ctrl = create_compressed_model_and_algo_for_test( model, config) def train_fn(compression_ctrl, model, epoch, optimizer, lr_scheduler, train_loader=train_loader): pass def configure_optimizers_fn(): return None, None early_stopping_training_loop = EarlyExitCompressionTrainingLoop( config, compression_ctrl, dump_checkpoints=False) model = early_stopping_training_loop.run( model, train_epoch_fn=train_fn, validate_fn=partial(mock_validate_fn), configure_optimizers_fn=configure_optimizers_fn) # Epoch number starts from 0 assert epoch_counter == exit_epoch_number
def get_quantization_config_with_ignored_scope(): config = get_quantization_config_without_range_init() config['compression']['ignored_scopes'] = 'ConvLinear/NNCFLinear[fc]' return config
def test_early_exit_training_loop(max_accuracy_degradation, num_steps=10, learning_rate=1e-3, maximal_total_epochs=100, init_finetuning_steps=10): def validate_fn(model, epoch=0, train_loader=None): with set_torch_seed(): train_loader = iter(train_loader) loss = torch.FloatTensor([0]) with torch.no_grad(): for _ in range(num_steps): x, y_gt = next(train_loader) y = model(x) loss += F.mse_loss(y.sum(), y_gt) return 1 - loss.item() config = get_quantization_config_without_range_init(LeNet.INPUT_SIZE[-1]) params = { "maximal_total_epochs": maximal_total_epochs, } params.update(max_accuracy_degradation) accuracy_aware_config = { "accuracy_aware_training": { "mode": "early_exit", "params": params } } config.update(accuracy_aware_config) model, train_loader, compression_ctrl = create_finetuned_lenet_model_and_dataloader( config, validate_fn, init_finetuning_steps) def train_fn(compression_ctrl, model, epoch, optimizer, lr_scheduler, train_loader=train_loader): with set_torch_seed(): train_loader = iter(train_loader) for _ in range(num_steps): compression_ctrl.scheduler.step() optimizer.zero_grad() x, y_gt = next(train_loader) y = model(x) loss = F.mse_loss(y.sum(), y_gt) loss.backward() optimizer.step() def configure_optimizers_fn(): optimizer = SGD(model.parameters(), lr=learning_rate) return optimizer, None early_stopping_training_loop = EarlyExitCompressionTrainingLoop( config, compression_ctrl) model = early_stopping_training_loop.run( model, train_epoch_fn=train_fn, validate_fn=partial(validate_fn, train_loader=train_loader), configure_optimizers_fn=configure_optimizers_fn) original_model_accuracy = model.original_model_accuracy compressed_model_accuracy = validate_fn(model, train_loader=train_loader) if "maximal_absolute_accuracy_degradation" in max_accuracy_degradation: assert (original_model_accuracy - compressed_model_accuracy) <= \ max_accuracy_degradation["maximal_absolute_accuracy_degradation"] else: assert (original_model_accuracy - compressed_model_accuracy) / original_model_accuracy * 100 <= \ max_accuracy_degradation["maximal_relative_accuracy_degradation"]
def __init__(self, name: str): self.name = name self.nncf_config = get_quantization_config_without_range_init() self.expects_error = False self.model = BasicConvTestModel()
def test_shared_layers_are_weight_quantized_only_once(): model = SharedLayersModel() config = get_quantization_config_without_range_init(model_size=1) register_bn_adaptation_init_args(config) model, qctrl = create_compressed_model_and_algo_for_test(model, config) assert len(qctrl.weight_quantizers) == 1
def get_basic_asym_quantization_config(model_size=4): config = get_quantization_config_without_range_init(model_size) config['compression']['activations'] = {"mode": "asymmetric"} config['compression']['initializer']['range'] = {"num_init_samples": 0} return config