def residual_block_quant(self, quantizable_op_type, skip_pattern=None, for_ci=True): main = fluid.Program() startup = fluid.Program() with fluid.program_guard(main, startup): loss = quant_dequant_residual_block(2, skip_pattern) opt = fluid.optimizer.Adam(learning_rate=0.001) opt.minimize(loss) place = fluid.CPUPlace() graph = IrGraph(core.Graph(main.desc), for_test=False) add_quant_dequant_pass = AddQuantDequantPass( scope=fluid.global_scope(), place=place, skip_pattern=skip_pattern, quantizable_op_type=quantizable_op_type) add_quant_dequant_pass.apply(graph) if not for_ci: marked_nodes = set() for op in graph.all_op_nodes(): if op.name().find('quant') > -1: marked_nodes.add(op) graph.draw('.', 'add_quant_dequant_graph', marked_nodes) self.check_graph(graph, skip_pattern) program = graph.to_program() val_graph = IrGraph(core.Graph(program.desc), for_test=False) if not for_ci: val_marked_nodes = set() for op in val_graph.all_op_nodes(): if op.name().find('quant') > -1: val_marked_nodes.add(op) val_graph.draw('.', 'val_add_quant_dequant_graph', val_marked_nodes)
def linear_fc_quant(self, activation_quant_type, weight_quantize_type, for_ci=True): main = fluid.Program() startup = fluid.Program() with fluid.program_guard(main, startup): loss = linear_fc(3) opt = fluid.optimizer.Adam(learning_rate=0.001) opt.minimize(loss) place = fluid.CPUPlace() graph = IrGraph(core.Graph(main.desc), for_test=False) transform_pass = QuantizationTransformPass( scope=fluid.global_scope(), place=place, activation_quantize_type=activation_quant_type, weight_quantize_type=weight_quantize_type) transform_pass.apply(graph) if not for_ci: marked_nodes = set() for op in graph.all_op_nodes(): if op.name().find('quantize') > -1: marked_nodes.add(op) graph.draw('.', 'quantize_fc_' + activation_quant_type, marked_nodes) program = graph.to_program() self.check_program(program) val_graph = IrGraph(core.Graph(program.desc), for_test=False) if not for_ci: val_marked_nodes = set() for op in val_graph.all_op_nodes(): if op.name().find('quantize') > -1: val_marked_nodes.add(op) val_graph.draw('.', 'val_fc_' + activation_quant_type, val_marked_nodes)
def residual_block_quant(self, quant_type): main = fluid.Program() startup = fluid.Program() with fluid.program_guard(main, startup): loss = residual_block(2) opt = fluid.optimizer.Adam(learning_rate=0.001) opt.minimize(loss) place = fluid.CPUPlace() exe = fluid.Executor(place) graph = IrGraph(core.Graph(main.desc), for_test=False) transform_pass = QuantizationTransformPass( scope=fluid.global_scope(), place=place, activation_quantize_type=quant_type) transform_pass.apply(graph) marked_nodes = set() for op in graph.all_op_nodes(): if op.name().find('quantize') > -1: marked_nodes.add(op) graph.draw('.', 'quantize_residual_' + quant_type, marked_nodes) program = graph.to_program() self.check_program(transform_pass, program) val_graph = IrGraph(core.Graph(program.desc), for_test=False) val_marked_nodes = set() for op in val_graph.all_op_nodes(): if op.name().find('quantize') > -1: val_marked_nodes.add(op) val_graph.draw('.', 'val_residual_' + quant_type, val_marked_nodes)
def quant_embedding(program, place, config, scope=None): """quantize lookup_table op parameters Args: program(fluid.Program): infer program scope(fluid.Scope): Scope records the mapping between variable names and variables, similar to brackets in programming languages. Usually users can use `fluid.global_scope() <https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api_cn/executor_cn/global_scope_cn.html>`_ . When ``None`` will use `fluid.global_scope() <https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api_cn/executor_cn/global_scope_cn.html>`_. Default : ``None``. place(fluid.CPUPlace or fluid.CUDAPlace): This parameter represents the executor run on which device. config(dict): config to quantize. The keys are 'params_name', 'quantize_type', \ 'quantize_bits', 'dtype', 'threshold'. \ ``params_name`` is parameter name to quantize, must be set. ``quantize_type`` is quantize type, supported types are ['abs_max'], default is "abs_max". ``quantize_bits`` supported bits are [8] and default is 8. ``dtype`` is quantize dtype, supported dtype are ['int8'], default is 'int8'. ``threshold`` is threshold to clip tensor before quant. When threshold is not set, \ tensor will not be clipped. Returns: None """ assert isinstance(config, dict), "config must be dict" config = _merge_config(copy.deepcopy(default_config), config) scope = fluid.global_scope() if scope is None else scope graph = IrGraph(core.Graph(program.desc), for_test=True) if config['quantize_type'] == 'abs_max': _quant_embedding_abs_max(graph, scope, place, config) return graph.to_program()
def test_fuse_resenet_unit(self): place = paddle.CUDAPlace(0) program = paddle.static.Program() startup_program = paddle.static.Program() with paddle.static.amp.fp16_guard(): with paddle.static.program_guard(program, startup_program): x = paddle.static.data("x", [1, 64, 64, 8]) conv2d = paddle.nn.Conv2D( 8, 32, 1, bias_attr=False, data_format='NHWC') batch_norm = paddle.nn.BatchNorm( 32, act='relu', data_layout='NHWC') out = batch_norm(conv2d(x)) graph = core.Graph(program.desc) core.get_pass("fuse_resnet_unit").apply(graph) after_program = paddle.fluid.framework.IrGraph(graph).to_program() params = paddle.static.amp.cast_model_to_fp16(program) after_params = paddle.static.amp.cast_model_to_fp16(after_program) exe = paddle.static.Executor(place) exe.run(startup_program) paddle.static.amp.cast_parameters_to_fp16( place, program, to_fp16_var_names=params) paddle.static.amp.cast_parameters_to_fp16( place, after_program, to_fp16_var_names=after_params) feed = {"x": np.random.randn(1, 64, 64, 8).astype("float16")} before_out = exe.run(program, feed=feed, fetch_list=[out.name]) after_out = exe.run(after_program, feed=feed, fetch_list=[out.name]) self.assertTrue(np.allclose(before_out[0], after_out[0], atol=5e-3))
def transform_and_save_model(original_path, save_path, save_type): place = fluid.CPUPlace() exe = fluid.Executor(place) inference_scope = fluid.executor.global_scope() with fluid.scope_guard(inference_scope): if os.path.exists(os.path.join(original_path, '__model__')): [inference_program, feed_target_names, fetch_targets ] = fluid.io.load_inference_model(original_path, exe) else: [inference_program, feed_target_names, fetch_targets ] = fluid.io.load_inference_model(original_path, exe, 'model', 'params') quantized_ops = set(test_args.quantized_ops.split(',')) transform_to_mkldnn_int8_pass = Qat2Int8MkldnnPass( quantized_ops, _scope=inference_scope, _place=place, _core=core) graph = IrGraph(core.Graph(inference_program.desc), for_test=True) if save_type == 'FP32': graph = transform_to_mkldnn_int8_pass.apply_fp32(graph) elif save_type == 'INT8': graph = transform_to_mkldnn_int8_pass.apply(graph) inference_program = graph.to_program() with fluid.scope_guard(inference_scope): fluid.io.save_inference_model(save_path, feed_target_names, fetch_targets, exe, inference_program) print( "Success! Transformed QAT_{0} model can be found at {1}\n".format( save_type, save_path))
def quant_embedding(program, place, config, scope=None): """ quant lookup_table op parameters Args: program(fluid.Program): infer program scope(fluid.Scope): the scope to store var, when is None will use fluid.global_scope() place(fluid.CPUPlace or fluid.CUDAPlace): place config(dict): config to quant. The keys are 'params_name', 'quantize_type', \ 'quantize_bits', 'dtype', 'threshold'. \ 'params_name': parameter name to quant, must be set. 'quantize_type': quantize type, supported types are ['abs_max']. default is "abs_max". 'quantize_bits': quantize bits, supported bits are [8]. default is 8. 'dtype': quantize dtype, supported dtype are ['int8']. default is 'int8'. 'threshold': threshold to clip tensor before quant. When threshold is not set, \ tensor will not be clipped. """ assert isinstance(config, dict), "config must be dict" config = _merge_config(copy.deepcopy(default_config), config) scope = fluid.global_scope() if scope is None else scope graph = IrGraph(core.Graph(program.desc), for_test=True) if config['quantize_type'] == 'abs_max': _quant_embedding_abs_max(graph, scope, place, config) return graph.to_program()
def test_dequantize_op_weights(self): program = fluid.Program() with fluid.program_guard(program): self.prepare_program_mul(program) graph = IrGraph(core.Graph(program.desc), for_test=True) for op in graph.all_op_nodes(): if op.op().type() == "mul": op_node = op break qpass = Quant2Int8MkldnnPass(self.quantized_ops, _scope=self.scope, _place=self.place, _core=core, _debug=False) qpass._weight_scales["mul_output"] = self.mul_output_scale param = self.scope.var("mul_weights").get_tensor() param.set(self.variables_mul["mul_weights"], self.place) qpass._dequantize_op_weights(graph, op_node, "Y", "Out") assert np.allclose( self.scope.find_var("mul_weights").get_tensor(), [[127, 63.5, 42.3333, 31.75, 25.4], [127, 63.5, 42.3333, 31.75, 25.4], [127, 63.5, 42.3333, 31.75, 25.4]]) param = self.scope.var("mul_weights").get_tensor() param.set(self.variables_mul["mul_weights_bad"], self.place) with self.assertRaises(ValueError): qpass._dequantize_op_weights(graph, op_node, "Y", "Out")
def test_generate_layer_norm_fuse_pass(self): paddle.enable_static() program = paddle.static.Program() startup_program = paddle.static.Program() with paddle.static.program_guard(program, startup_program): x = paddle.static.data("x", [3, 64, 120], "float32") gamma = paddle.static.create_parameter(shape=[120], dtype="float32", is_bias=True) beta = paddle.static.create_parameter(shape=[120], dtype="float32", is_bias=True) x_sub_mean = x - paddle.mean(x, axis=-1, keepdim=True) std_dev = paddle.mean(x_sub_mean.pow(2), axis=-1, keepdim=True) lnorm = x_sub_mean - (std_dev + 1e-5).sqrt() out = lnorm * gamma + beta graph = core.Graph(program.desc) before_node_nums = len(graph.nodes()) core.get_pass("generate_layer_norm_fuse_pass").apply(graph) after_node_nums = len(graph.nodes()) self.assertEqual(after_node_nums, before_node_nums - 14) after_program = paddle.fluid.framework.IrGraph(graph).to_program() executor = paddle.static.Executor(paddle.CPUPlace()) executor.run(startup_program) feed = {"x": np.random.random([3, 64, 120]).astype("float32")} before_out = executor.run(program, feed=feed, fetch_list=[out.name]) after_out = executor.run(after_program, feed=feed, fetch_list=[out.name]) self.assertTrue(np.allclose(before_out, after_out))
def transform_and_save_int8_model(original_path, save_path, ops_to_quantize='', op_ids_to_skip='', debug=False, quant_model_filename='', quant_params_filename='', save_model_filename="__model__", save_params_filename=None): place = fluid.CPUPlace() exe = fluid.Executor(place) inference_scope = fluid.executor.global_scope() with fluid.scope_guard(inference_scope): if not quant_model_filename: if os.path.exists(os.path.join(original_path, '__model__')): [inference_program, feed_target_names, fetch_targets ] = fluid.io.load_inference_model(original_path, exe) else: [inference_program, feed_target_names, fetch_targets ] = fluid.io.load_inference_model(original_path, exe, 'model', 'params') else: [inference_program, feed_target_names, fetch_targets ] = fluid.io.load_inference_model(original_path, exe, quant_model_filename, quant_params_filename) ops_to_quantize_set = set() print(ops_to_quantize) if len(ops_to_quantize) > 0: ops_to_quantize_set = set(ops_to_quantize.split(',')) op_ids_to_skip_set = set([-1]) print(op_ids_to_skip) if len(op_ids_to_skip) > 0: op_ids_to_skip_set = set(map(int, op_ids_to_skip.split(','))) graph = IrGraph(core.Graph(inference_program.desc), for_test=True) if (debug): graph.draw('.', 'quant_orig', graph.all_op_nodes()) transform_to_mkldnn_int8_pass = Quant2Int8MkldnnPass( ops_to_quantize_set, _op_ids_to_skip=op_ids_to_skip_set, _scope=inference_scope, _place=place, _core=core, _debug=debug) graph = transform_to_mkldnn_int8_pass.apply(graph) inference_program = graph.to_program() with fluid.scope_guard(inference_scope): fluid.io.save_inference_model(save_path, feed_target_names, fetch_targets, exe, inference_program, model_filename=save_model_filename, params_filename=save_params_filename) print( "Success! INT8 model obtained from the Quant model can be found at {}\n" .format(save_path))
def _apply_ir_passes(self): graph = core.Graph(self.main_program.desc) graph.set_not_owned("__param_scope__", fluid.global_scope()) if not isinstance(self.pass_names, list): self.pass_names = [self.pass_names] pass_builder = core.PassBuilder() for name in self.pass_names: ir_pass = pass_builder.append_pass(name) # Set attr for pass if self.pass_attrs.get(name, None) is not None: attrs = self.pass_attrs[name] for key in attrs: ir_pass.set(key, attrs[key]) trans_pass = pass_builder.append_pass("graph_to_program_pass") opt_program = fluid.Program() trans_pass.set_not_owned("program", opt_program.desc) for p in pass_builder.all_passes(): p.apply(graph) opt_program.blocks = [ Block(opt_program, i) for i in six.moves.range(opt_program.desc.num_blocks()) ] opt_program._sync_with_cpp() return opt_program
def test_dequantize_op_weights(self): program = fluid.Program() with fluid.program_guard(program): self.prepare_program_mul(program) graph = IrGraph(core.Graph(program.desc), for_test=True) op_node = "" for op in graph.all_op_nodes(): if op.op().type() == self.op_name(): op_node = op break assert op_node != "", "op of type %s not found" % self.op_name() qpass = Quant2Int8MkldnnPass(self.quantized_ops, _scope=self.scope, _place=self.place, _core=core, _debug=False) qpass._weight_thresholds["mul_output"] = self.mul_output_scale param = self.scope.var("mul_weights").get_tensor() param.set(self.variables_mul["mul_weights"], self.place) qpass._dequantize_op_weights(graph, op_node, "Y", "Out") assert np.allclose( self.scope.find_var("mul_weights").get_tensor(), [[1. / 127., 2. / 127., 3. / 127., 4. / 127., 5. / 127.], [1. / 127., 2. / 127., 3. / 127., 4. / 127., 5. / 127.], [1. / 127., 2. / 127., 3. / 127., 4. / 127., 5. / 127.]]) param = self.scope.var("mul_weights").get_tensor() param.set(self.variables_mul["mul_weights_bad"], self.place) with self.assertRaises(ValueError): qpass._dequantize_op_weights(graph, op_node, "Y", "Out")
def test_quant_update_activation(self): program = fluid.Program() with fluid.program_guard(program): self.prepare_program(program) graph = IrGraph(core.Graph(program.desc), for_test=True) quant2_int8_mkldnn_pass = Quant2Int8MkldnnPass( self.quantized_ops, _scope=self.scope, _place=self.place, _core=core, _debug=False) input_scale_tensor = quant2_int8_mkldnn_pass._convert_scale2tensor( np.array(self.scale).astype(np.float64)) output_scale_tensor = quant2_int8_mkldnn_pass._convert_scale2tensor( np.array(1. / self.scale * self.scale).astype(np.float64)) var_scale = { "input": (False, input_scale_tensor), "filter": (False, input_scale_tensor), "conv_output": (False, output_scale_tensor), } if core.avx_supported(): quant2_int8_mkldnn_pass._var_quant_scales = var_scale graph = quant2_int8_mkldnn_pass._propagate_scales(graph) graph = quant2_int8_mkldnn_pass._quantize_fp32_graph(graph) self.check_graph_after_pass(graph)
def check_multi_add_to_sum(self, pass_type): program = paddle.static.Program() startup_program = paddle.static.Program() with paddle.static.program_guard(program, startup_program): x = paddle.static.data("x", [10, 10, 10], "float32") y = paddle.static.data("y", [10, 10, 10], "float32") z = paddle.static.data("z", [10, 10, 10], "float32") add_1 = paddle.add(paddle.add(x, y), z) matmul_1 = paddle.matmul(add_1, z) add_tmp = paddle.add(x, y) add_2 = paddle.add(add_tmp, z) matmul_2 = paddle.matmul(add_2, add_tmp) out = paddle.add(matmul_1, matmul_2) graph = core.Graph(program.desc) before_node_nums = len(graph.nodes()) core.get_pass(pass_type).apply(graph) after_node_nums = len(graph.nodes()) self.assertEqual(after_node_nums, before_node_nums - 2) after_program = paddle.fluid.framework.IrGraph(graph).to_program() executor = paddle.static.Executor(paddle.CPUPlace()) executor.run(startup_program) feed = { "x": np.random.random([10, 10, 10]).astype("float32"), "y": np.random.random([10, 10, 10]).astype("float32"), "z": np.random.random([10, 10, 10]).astype("float32") } before_out = executor.run(program, feed=feed, fetch_list=[out.name]) after_out = executor.run(after_program, feed=feed, fetch_list=[out.name]) self.assertTrue(np.allclose(before_out, after_out))
def test_generate_combine_mul_v1(self): paddle.enable_static() program = paddle.static.Program() startup_program = paddle.static.Program() with paddle.static.program_guard(program, startup_program): x = paddle.static.data("x", [16, 32]) y = paddle.static.data("y", [32, 12]) z = paddle.static.data("z", [32, 48]) out1 = paddle.matmul(x, y) out2 = paddle.matmul(x, z) graph = core.Graph(program.desc) before_node_nums = len(graph.nodes()) core.get_pass("generate_combine_mul_v1").apply(graph) after_node_nums = len(graph.nodes()) self.assertEqual(after_node_nums, before_node_nums + 4) after_program = paddle.fluid.framework.IrGraph(graph).to_program() executor = paddle.static.Executor(paddle.CPUPlace()) executor.run(startup_program) feed = { "x": np.random.random([16, 32]).astype("float32"), "y": np.random.random([32, 12]).astype("float32"), "z": np.random.random([32, 48]).astype("float32") } before_out1, before_out2 = executor.run( program, feed=feed, fetch_list=[out1.name, out2.name]) after_out1, after_out2 = executor.run( after_program, feed=feed, fetch_list=[out1.name, out2.name]) self.assertTrue(np.allclose(before_out1, after_out1)) self.assertTrue(np.allclose(before_out2, after_out2))
def convert(program, place, config=None, scope=None, save_int8=False): """ convert quantized and well-trained ``program`` to final quantized ``program`` that can be used to save ``inference model``. Args: program(fluid.Program): quantized and well-trained ``test program``. place(fluid.CPUPlace or fluid.CUDAPlace): This parameter represents the executor run on which device. config(dict, optional): configs for convert. if set None, will use default config. It must be same with config that used in 'quant_aware'. Default: None. scope(fluid.Scope, optional): Scope records the mapping between variable names and variables, similar to brackets in programming languages. Usually users can use `fluid.global_scope <https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api_cn/executor_cn/global_scope_cn.html>`_. When ``None`` will use `fluid.global_scope() <https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api_cn/executor_cn/global_scope_cn.html>`_ . Default: ``None``. save_int8: Whether to return ``program`` which model parameters' dtype is ``int8``. This parameter can only be used to get model size. Default: ``False``. Returns: Tuple : freezed program which can be used for inference. when ``save_int8`` is False, return ``freezed_program(fluid.Program)``. when ``save_int8`` is True, return ``freezed_program(fluid.Program)`` and ``freezed_program_int8(fluid.Program)`` """ scope = fluid.global_scope() if not scope else scope if config is None: config = _quant_config_default else: assert isinstance(config, dict), "config must be dict" config = _parse_configs(config) _logger.info("convert config {}".format(config)) test_graph = IrGraph(core.Graph(program.desc), for_test=True) support_op_types = [] for op in config['quantize_op_types']: if op in QuantizationFreezePass._supported_quantizable_op_type: support_op_types.append(op) # Freeze the graph after training by adjusting the quantize # operators' order for the inference. freeze_pass = QuantizationFreezePass( scope=scope, place=place, weight_bits=config['weight_bits'], activation_bits=config['activation_bits'], weight_quantize_type=config['weight_quantize_type'], quantizable_op_type=support_op_types) freeze_pass.apply(test_graph) freezed_program = test_graph.to_program() if save_int8: convert_int8_pass = ConvertToInt8Pass( scope=fluid.global_scope(), place=place, quantizable_op_type=support_op_types) convert_int8_pass.apply(test_graph) freezed_program_int8 = test_graph.to_program() return freezed_program, freezed_program_int8 else: return freezed_program
def test_get_valid_program_error(self): # case 1: CompiledProgram no program graph = core.Graph(core.ProgramDesc()) compiled_program = fluid.CompiledProgram(graph) with self.assertRaises(TypeError): fluid.io._get_valid_program(compiled_program) # case 2: main_program type error with self.assertRaises(TypeError): fluid.io._get_valid_program("program")
def get_op_number(self, prog): graph = IrGraph(core.Graph(prog.desc), for_test=False) quant_op_nums = 0 op_nums = 0 for op in graph.all_op_nodes(): if op.name() in ['conv2d', 'depthwise_conv2d', 'mul']: op_nums += 1 elif 'fake_' in op.name(): quant_op_nums += 1 return op_nums, quant_op_nums
def test_exception(self): paddle.enable_static() program = paddle.static.Program() startup_program = paddle.static.Program() with paddle.static.program_guard(program, startup_program): x = paddle.static.data("x", [10, 10], "float32") y = paddle.static.data("y", [10, 10], "float32") paddle.add(x, y) graph = core.Graph(program.desc) with self.assertRaises(NotImplementedError): core.get_pass("unimplemented_operand_exception").apply(graph) with self.assertRaises(NotImplementedError): core.get_pass("unimplemented_operation_exception").apply(graph)
def test_quant_update_activation(self): program = fluid.Program() with fluid.program_guard(program): self.prepare_program(program) graph = IrGraph(core.Graph(program.desc), for_test=True) graph = self.remove_fuse_activation_attribute(graph) self.check_graph_before_pass(graph) quant2_int8_mkldnn_pass = Quant2Int8MkldnnPass(self.quantized_ops, _scope=self.scope, _place=self.place, _core=core, _debug=False) graph = quant2_int8_mkldnn_pass._update_activations(graph) self.check_graph_after_pass(graph)
def quant_embedding(program, place, config=None, scope=None): """quantize lookup_table op parameters Args: program(paddle.static.Program): infer program scope(paddle.static.Scope, optional): Scope records the mapping between variable names and variables, similar to brackets in programming languages. Usually users can use `paddle.static.global_scope() <https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api_cn/executor_cn/global_scope_cn.html>`_ . When ``None`` will use `paddle.static.global_scope() <https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api_cn/executor_cn/global_scope_cn.html>`_. Default : ``None``. place(paddle.CPUPlace or paddle.CUDAPlace): This parameter represents the executor run on which device. config(dict, optional): config to quantize. The keys are 'quantize_op_types'. For op in quantize_op_types, you can define 'quantize_type', \ 'quantize_bits', 'dtype', 'threshold'. \ ``quantize_type`` is quantize type, supported types are ['abs_max'], default is "abs_max". ``quantize_bits`` supported bits are [8] and default is 8. ``dtype`` is quantize dtype, supported dtype are ['int8'], default is 'int8'. ``threshold`` is threshold to clip tensor before quant. When threshold is not set, \ tensor will not be clipped. Returns: None """ config = config or {} config = _merge_config(copy.deepcopy(_default_config), config) scope = paddle.static.global_scope() if scope is None else scope graph = IrGraph(core.Graph(program.desc), for_test=True) quantize_params_map = {} all_op = graph.all_op_nodes() for op in all_op: if op.inputs == [] and op.outputs == []: continue op_type = op.name() if op_type in config['quantize_op_types']: weight_name = op.input('W')[0] if weight_name in quantize_params_map.values(): continue embedding_node = graph._find_node_by_name(op.inputs, op.input('W')[0]) for op_node in embedding_node.outputs: if op_node.name() == 'fused_embedding_seq_pool': _split_embedding_seq_pool(graph, op_node) if config[op_type]['quantize_type'] == 'abs_max': _quant_embedding_abs_max(graph, scope, place, config[op_type], weight_name, embedding_node) elif config[op_type]['quantize_type'] == 'log': _quant_embedding_log(graph, scope, place, config[op_type], weight_name, embedding_node) quantize_params_map[weight_name] = _get_quant_var_name(weight_name) for op in all_op: if op.name() == 'fused_embedding_seq_pool': graph.safe_remove_nodes(op) return graph.to_program()
def _predict(self, test_reader=None, model_path=None): place = fluid.CPUPlace() exe = fluid.Executor(place) inference_scope = fluid.executor.global_scope() with fluid.scope_guard(inference_scope): if os.path.exists(os.path.join(model_path, '__model__')): [inference_program, feed_target_names, fetch_targets ] = fluid.io.load_inference_model(model_path, exe) else: [inference_program, feed_target_names, fetch_targets ] = fluid.io.load_inference_model(model_path, exe, 'model', 'params') use_mkldnn = fluid.core.globals()["FLAGS_use_mkldnn"] if (use_mkldnn): graph = IrGraph(core.Graph(inference_program.desc), for_test=True) graph = self._transform_depthwise_conv(graph) inference_program = graph.to_program() dshape = [3, 224, 224] top1 = 0.0 top5 = 0.0 total_samples = 0 for batch_id, data in enumerate(test_reader()): if six.PY2: images = map(lambda x: x[0].reshape(dshape), data) if six.PY3: images = list(map(lambda x: x[0].reshape(dshape), data)) images = np.array(images).astype('float32') labels = np.array([x[1] for x in data]).astype("int64") labels = labels.reshape([-1, 1]) fluid.core.set_num_threads(int(os.environ['CPU_NUM_THREADS'])) out = exe.run(inference_program, feed={ feed_target_names[0]: images, feed_target_names[1]: labels }, fetch_list=fetch_targets) fluid.core.set_num_threads(1) top1 += np.sum(out[1]) * len(data) top5 += np.sum(out[2]) * len(data) total_samples += len(data) if (batch_id + 1) % 100 == 0: _logger.info( '{} images have been predicted'.format(total_samples)) return top1 / total_samples, top5 / total_samples
def init_dist_attr_for_graph(self): assert self._is_initialized_for_program, \ "The program must be initialized before initializing the distributed attributes for its graph." if self._is_initialized_for_graph: return # Convert program to graph self._serial_graph = framework.IrGraph( core.Graph(self._serial_program.desc)) all_nodes = self._serial_graph.all_nodes() self.order_nodes_by_program_order() for node in self.serial_ordered_nodes: if node.is_var() and node.var() is not None: dist_tensor = None tensor_id = node.node.original_desc_id() for cur_tensor_id, cur_dist_tensor in self._dist_tensors_for_program.items( ): if tensor_id == cur_tensor_id \ or tensor_id == cur_dist_tensor.serial_tensor.desc.original_id(): dist_tensor = cur_dist_tensor self._node_id_to_tensor_id[node.id()] = cur_tensor_id assert dist_tensor is not None, \ "Tensor must have a distributed tensor after the initialization for program." serial_tensor_node_id = node.id() new_dist_tensor = DistributedTensor(dist_tensor.serial_tensor, dist_tensor.dist_attr) self._dist_tensors_for_graph[ serial_tensor_node_id] = new_dist_tensor if node.is_op() and node.op() is not None: dist_op = None op_id = node.node.original_desc_id() for cur_op_id, cur_dist_op in self._dist_ops_for_program.items( ): if op_id == cur_op_id \ or op_id == cur_dist_op.serial_op.desc.original_id(): dist_op = cur_dist_op self._node_id_to_op_id[node.id()] = cur_op_id assert dist_op is not None, \ "Operator must have a distributed operator after the initialization for program." serial_op_node_id = node.id() new_dist_op = DistributedOperator(dist_op.serial_op, dist_op.dist_attr) self._dist_ops_for_graph[serial_op_node_id] = new_dist_op self._is_initialized_for_graph = True
def test_graph_functions(self): main = fluid.Program() startup = fluid.Program() with fluid.program_guard(main, startup): loss = residual_block(2) opt = fluid.optimizer.Adam(learning_rate=0.001) opt.minimize(loss) graph = IrGraph(core.Graph(main.desc), for_test=False) marked_nodes = set() for op in graph.all_op_nodes(): if op.name().find('conv2d') > -1: marked_nodes.add(op) graph.draw('.', 'residual', marked_nodes) self.assertFalse(graph.has_circle()) self.assertEqual(graph.graph_num(), 1) nodes = graph.topology_sort() self.assertEqual(len(nodes), len(graph.all_op_nodes())) nodes_map = graph.build_adjacency_list() self.assertEqual(len(nodes_map), len(graph.all_op_nodes())) nodes_num = len(graph.all_nodes()) graph.safe_remove_nodes(marked_nodes) self.assertEqual(len(graph.all_nodes()), nodes_num - len(marked_nodes))
def generate_dot_for_model(model_path, save_graph_dir, save_graph_name): place = fluid.CPUPlace() exe = fluid.Executor(place) inference_scope = fluid.executor.global_scope() with fluid.scope_guard(inference_scope): if os.path.exists(os.path.join(model_path, '__model__')): [inference_program, feed_target_names, fetch_targets] = fluid.io.load_inference_model(model_path, exe) else: [inference_program, feed_target_names, fetch_targets] = fluid.io.load_inference_model(model_path, exe, 'model', 'params') graph = IrGraph(core.Graph(inference_program.desc), for_test=True) if not os.path.exists(save_graph_dir): os.makedirs(save_graph_dir) model_name = os.path.basename(os.path.normpath(save_graph_dir)) if save_graph_name is '': save_graph_name = model_name graph.draw(save_graph_dir, save_graph_name, graph.all_op_nodes()) print( "Success! Generated dot and pdf files for {0} model, that can be found at {1} named {2}.\n". format(model_name, save_graph_dir, save_graph_name))
def build_graph_with_sub_graph(self): def linear_fc(num): data = fluid.layers.data(name='image', shape=[1, 32, 32], dtype='float32') label = fluid.layers.data(name='label', shape=[1], dtype='int64') hidden = data for _ in six.moves.xrange(num): hidden = fluid.layers.fc(hidden, size=128, act='relu') loss = fluid.layers.cross_entropy(input=hidden, label=label) loss = fluid.layers.mean(loss) return loss main_program = Program() startup_program = Program() def true_func(): return linear_fc(3) def false_func(): return linear_fc(5) with program_guard(main_program, startup_program): x = layers.fill_constant(shape=[1], dtype='float32', value=0.1) y = layers.fill_constant(shape=[1], dtype='float32', value=0.23) pred = layers.less_than(y, x) out = layers.cond(pred, true_func, false_func) core_graph = core.Graph(main_program.desc) # We should create graph for test, otherwise it will throw a # error that it cannot find the node of "STEP_COUNTER" graph = IrGraph(core_graph, for_test=True) sub_graph = graph.get_sub_graph(0) all_sub_graphs = graph.all_sub_graphs( for_test=True) # same reason for subgraph # Should return graph and sub_graphs at the same time. If only return sub_graph, the graph will # be destructed and the sub_graphs will be empty. return graph, all_sub_graphs
def check_generate_simplify_inference(self, pass_type): paddle.enable_static() program = paddle.static.Program() startup_program = paddle.static.Program() with paddle.static.program_guard(program, startup_program): x = paddle.static.data("x", [10, 16, 16], "float32") x1 = paddle.transpose(paddle.transpose(x, [0, 2, 1]), [0, 2, 1]) tmp = paddle.transpose(x, [0, 2, 1]) x2 = paddle.transpose(tmp, [0, 2, 1]) out = paddle.add(x1, paddle.matmul(x2, tmp)) graph = core.Graph(program.desc) before_node_nums = len(graph.nodes()) core.get_pass(pass_type).apply(graph) after_node_nums = len(graph.nodes()) self.assertEqual(after_node_nums, before_node_nums - 6) after_program = paddle.fluid.framework.IrGraph(graph).to_program() executor = paddle.static.Executor(paddle.CPUPlace()) executor.run(startup_program) feed = {"x": np.random.random([10, 16, 16]).astype("float32")} before_out = executor.run(program, feed=feed, fetch_list=[out.name]) after_out = executor.run(after_program, feed=feed, fetch_list=[out.name]) self.assertTrue(np.allclose(before_out, after_out))
def _predict(self, test_reader=None, model_path=None, batch_size=1, batch_num=1, skip_batch_num=0, transform_to_int8=False): place = fluid.CPUPlace() exe = fluid.Executor(place) inference_scope = fluid.executor.global_scope() with fluid.scope_guard(inference_scope): if os.path.exists(os.path.join(model_path, '__model__')): [inference_program, feed_target_names, fetch_targets ] = fluid.io.load_inference_model(model_path, exe) else: [inference_program, feed_target_names, fetch_targets ] = fluid.io.load_inference_model(model_path, exe, 'model', 'params') graph = IrGraph(core.Graph(inference_program.desc), for_test=True) if (self._debug): graph.draw('.', 'qat_orig', graph.all_op_nodes()) if (transform_to_int8): transform_to_mkldnn_int8_pass = Qat2Int8MkldnnPass( self._quantized_ops, _scope=inference_scope, _place=place, _core=core, _debug=self._debug) graph = transform_to_mkldnn_int8_pass.apply(graph) inference_program = graph.to_program() total_correct = 0 total_samples = 0 batch_times = [] ppses = [] # predictions per second iters = 0 infer_start_time = time.time() for data in test_reader(): if batch_num > 0 and iters >= batch_num: break if iters == skip_batch_num: total_samples = 0 infer_start_time = time.time() input0 = np.array([x[0] for x in data]).astype('int64') input1 = np.array([x[1] for x in data]).astype('int64') labels = np.array([x[2] for x in data]).astype('int64') start = time.time() out = exe.run(inference_program, feed={ feed_target_names[0]: input0, feed_target_names[1]: input1 }, fetch_list=fetch_targets) batch_time = (time.time() - start) * 1000 # in miliseconds batch_times.append(batch_time) batch_correct = self._get_batch_correct(out, labels) batch_len = len(data) total_samples += batch_len total_correct += batch_correct batch_acc = float(batch_correct) / float(batch_len) pps = batch_len / batch_time * 1000 ppses.append(pps) latency = batch_time / batch_len iters += 1 appx = ' (warm-up)' if iters <= skip_batch_num else '' _logger.info( 'batch {0}{4}, acc: {1:.4f}, latency: {2:.4f} ms, predictions per sec: {3:.2f}' .format(iters, batch_acc, latency, pps, appx)) # Postprocess benchmark data infer_total_time = time.time() - infer_start_time batch_latencies = batch_times[skip_batch_num:] batch_latency_avg = np.average(batch_latencies) latency_avg = batch_latency_avg / batch_size ppses = ppses[skip_batch_num:] pps_avg = np.average(ppses) acc_avg = float(np.sum(total_correct)) / float(total_samples) _logger.info( 'Total inference run time: {:.2f} s'.format(infer_total_time)) return acc_avg, pps_avg, latency_avg
def test_qat_acc(self): def _build_static_lenet(main, startup, is_test=False, seed=1000): with fluid.unique_name.guard(): with fluid.program_guard(main, startup): main.random_seed = seed startup.random_seed = seed img = fluid.layers.data( name='image', shape=[1, 28, 28], dtype='float32') label = fluid.layers.data( name='label', shape=[1], dtype='int64') prediction = StaticLenet(img) if not is_test: loss = fluid.layers.cross_entropy( input=prediction, label=label) avg_loss = fluid.layers.mean(loss) else: avg_loss = prediction return img, label, avg_loss reader = paddle.batch( paddle.dataset.mnist.test(), batch_size=32, drop_last=True) weight_quantize_type = 'abs_max' activation_quant_type = 'moving_average_abs_max' param_init_map = {} seed = 1000 lr = 0.001 # imperative train _logger.info( "--------------------------dynamic graph qat--------------------------" ) imperative_qat = ImperativeQuantAware( weight_quantize_type=weight_quantize_type, activation_quantize_type=activation_quant_type, quantizable_layer_type=[ 'Conv2D', 'Linear', 'ReLU', 'LeakyReLU', 'ReLU6', 'Tanh', 'Swish' ]) with fluid.dygraph.guard(): np.random.seed(seed) fluid.default_main_program().random_seed = seed fluid.default_startup_program().random_seed = seed lenet = ImperativeLenet() fixed_state = {} for name, param in lenet.named_parameters(): p_shape = param.numpy().shape p_value = param.numpy() if name.endswith("bias"): value = np.zeros_like(p_value).astype('float32') else: value = np.random.normal( loc=0.0, scale=0.01, size=np.product(p_shape)).reshape( p_shape).astype('float32') fixed_state[name] = value param_init_map[param.name] = value lenet.set_dict(fixed_state) imperative_qat.quantize(lenet) adam = AdamOptimizer( learning_rate=lr, parameter_list=lenet.parameters()) dynamic_loss_rec = [] lenet.train() for batch_id, data in enumerate(reader()): x_data = np.array([x[0].reshape(1, 28, 28) for x in data]).astype('float32') y_data = np.array( [x[1] for x in data]).astype('int64').reshape(-1, 1) img = fluid.dygraph.to_variable(x_data) label = fluid.dygraph.to_variable(y_data) out = lenet(img) loss = fluid.layers.cross_entropy(out, label) avg_loss = fluid.layers.mean(loss) avg_loss.backward() adam.minimize(avg_loss) lenet.clear_gradients() dynamic_loss_rec.append(avg_loss.numpy()[0]) if batch_id % 100 == 0: _logger.info('{}: {}'.format('loss', avg_loss.numpy())) if batch_id > 500: break lenet.eval() paddle.jit.save( layer=lenet, path="./dynamic_mnist/model", input_spec=[ paddle.static.InputSpec( shape=[None, 1, 28, 28], dtype='float32') ]) # static graph train _logger.info( "--------------------------static graph qat--------------------------" ) static_loss_rec = [] if core.is_compiled_with_cuda(): place = core.CUDAPlace(0) else: place = core.CPUPlace() exe = fluid.Executor(place) main = fluid.Program() infer = fluid.Program() startup = fluid.Program() static_img, static_label, static_loss = _build_static_lenet( main, startup, False, seed) infer_img, _, infer_pre = _build_static_lenet(infer, startup, True, seed) with fluid.unique_name.guard(): with fluid.program_guard(main, startup): opt = AdamOptimizer(learning_rate=lr) opt.minimize(static_loss) scope = core.Scope() with fluid.scope_guard(scope): exe.run(startup) for param in main.all_parameters(): param_tensor = scope.var(param.name).get_tensor() param_tensor.set(param_init_map[param.name], place) main_graph = IrGraph(core.Graph(main.desc), for_test=False) infer_graph = IrGraph(core.Graph(infer.desc), for_test=True) transform_pass = QuantizationTransformPass( scope=scope, place=place, activation_quantize_type=activation_quant_type, weight_quantize_type=weight_quantize_type, quantizable_op_type=['conv2d', 'depthwise_conv2d', 'mul']) add_quant_dequant_pass = AddQuantDequantPass( scope=scope, place=place, quantizable_op_type=[ 'relu', 'leaky_relu', 'relu6', 'tanh', 'swish' ]) transform_pass.apply(main_graph) transform_pass.apply(infer_graph) add_quant_dequant_pass.apply(main_graph) add_quant_dequant_pass.apply(infer_graph) build_strategy = fluid.BuildStrategy() build_strategy.fuse_all_reduce_ops = False binary = fluid.CompiledProgram(main_graph.graph).with_data_parallel( loss_name=static_loss.name, build_strategy=build_strategy) feeder = fluid.DataFeeder( feed_list=[static_img, static_label], place=place) with fluid.scope_guard(scope): for batch_id, data in enumerate(reader()): loss_v, = exe.run(binary, feed=feeder.feed(data), fetch_list=[static_loss]) static_loss_rec.append(loss_v[0]) if batch_id % 100 == 0: _logger.info('{}: {}'.format('loss', loss_v)) save_program = infer_graph.to_program() with fluid.scope_guard(scope): fluid.io.save_inference_model("./static_mnist", [infer_img.name], [infer_pre], exe, save_program) rtol = 1e-08 atol = 1e-10 for i, (loss_d, loss_s) in enumerate(zip(dynamic_loss_rec, static_loss_rec)): diff = np.abs(loss_d - loss_s) if diff > (atol + rtol * np.abs(loss_s)): _logger.info( "diff({}) at {}, dynamic loss = {}, static loss = {}". format(diff, i, loss_d, loss_s)) break self.assertTrue( np.allclose( np.array(dynamic_loss_rec), np.array(static_loss_rec), rtol=rtol, atol=atol, equal_nan=True), msg='Failed to do the imperative qat.')
def quant_aware(program, place, config=None, scope=None, for_test=False, weight_quantize_func=None, act_quantize_func=None, weight_preprocess_func=None, act_preprocess_func=None, optimizer_func=None, executor=None, return_program=False): """Add quantization and dequantization operators to "program" for quantization training or testing. Args: program(paddle.static.Program): training or testing ``program``. place(paddle.CPUPlace or paddle.CUDAPlace): This parameter represents the executor run on which device. config(dict, optional): configs for quantization. if None, will use default config. Default: None. scope(paddle.static.Scope): Scope records the mapping between variable names and variables, similar to brackets in programming languages. Usually users can use `paddle.static.global_scope <https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api_cn/executor_cn/global_scope_cn.html>`_. When ``None`` will use `paddle.static.global_scope() <https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/api_cn/executor_cn/global_scope_cn.html>`_ . Default: ``None``. for_test(bool): If the 'program' parameter is a test program, this parameter should be set to ``True``. Otherwise, set to ``False``.Default: False weight_quantize_func(function): Function that defines how to quantize weight. Using this can quickly test if user's quantization method works or not. In this function, user should both define quantization function and dequantization function, that is, the function's input is non-quantized weight and function returns dequantized weight. If None, will use quantization op defined by 'weight_quantize_type'. Default is None. act_quantize_func(function): Function that defines how to quantize activation. Using this can quickly test if user's quantization method works or not. In this function, user should both define quantization and dequantization process, that is, the function's input is non-quantized activation and function returns dequantized activation. If None, will use quantization op defined by 'activation_quantize_type'. Default is None. weight_preprocess_func(function): Function that defines how to preprocess weight before quantization. Using this can quickly test if user's preprocess method works or not. The function's input is non-quantized weight and function returns processed weight to be quantized. If None, the weight will be quantized directly. Default is None. act_preprocess_func(function): Function that defines how to preprocess activation before quantization. Using this can quickly test if user's preprocess method works or not. The function's input is non-quantized activation and function returns processed activation to be quantized. If None, the activation will be quantized directly. Default is None. optimizer_func(function): Fuction return a optimizer. When 'is_test' is False and user want to use self-defined quantization function and preprocess function, this function must be set. Default is None. exe(paddle.static.Executor): If user want to use self-defined quantization function and preprocess function, exe must be set for initialization. Default is None. return_program(bool): If user want return value is a Program rather than Compiled Program, This argument should be set True. Default is False. Returns: paddle.static.CompiledProgram | paddle.static.Program: Program with quantization and dequantization ``operators`` """ scope = paddle.static.global_scope() if not scope else scope if config is None: config = _quant_config_default else: assert isinstance(config, dict), "config must be dict" config = _parse_configs(config) _logger.info("quant_aware config {}".format(config)) main_graph = IrGraph(core.Graph(program.desc), for_test=for_test) transform_pass_ops = [] quant_dequant_ops = [] for op_type in config['quantize_op_types']: if op_type in TRANSFORM_PASS_OP_TYPES: transform_pass_ops.append(op_type) elif op_type in QUANT_DEQUANT_PASS_OP_TYPES: quant_dequant_ops.append(op_type) if len(transform_pass_ops) > 0: transform_pass = QuantizationTransformPass( scope=scope, place=place, weight_bits=config['weight_bits'], activation_bits=config['activation_bits'], activation_quantize_type=config['activation_quantize_type'], weight_quantize_type=config['weight_quantize_type'], window_size=config['window_size'], moving_rate=config['moving_rate'], quantizable_op_type=transform_pass_ops, skip_pattern=config['not_quant_pattern'], weight_quantize_func=weight_quantize_func, act_quantize_func=act_quantize_func, weight_preprocess_func=weight_preprocess_func, act_preprocess_func=act_preprocess_func, optimizer_func=optimizer_func, executor=executor) transform_pass.apply(main_graph) if len(quant_dequant_ops) > 0: quant_dequant_pass = AddQuantDequantPass( scope=scope, place=place, moving_rate=config['moving_rate'], quant_bits=config['activation_bits'], skip_pattern=config['not_quant_pattern'], quantizable_op_type=quant_dequant_ops) quant_dequant_pass.apply(main_graph) out_scale_training_pass = OutScaleForTrainingPass( scope=scope, place=place, moving_rate=config['moving_rate']) out_scale_training_pass.apply(main_graph) if (weight_preprocess_func is not None or act_preprocess_func is not None) and not for_test: _logger.info( "When a preprocess_func is used in quant_aware, Need to save a mapping table to match variable names in the convert phase." ) _logger.info( "The mapping table is saved as '{}'.".format(VARS_MAPPING_TABLE)) save_dict(main_graph.out_node_mapping_table) if for_test or return_program: quant_program = main_graph.to_program() else: quant_program = paddle.static.CompiledProgram(main_graph.graph) return quant_program