diff --git a/demo/auto-compression/configs/PaddleDet/README.md b/demo/auto-compression/configs/PaddleDet/README.md new file mode 100644 index 0000000000000..2df7072167f3f --- /dev/null +++ b/demo/auto-compression/configs/PaddleDet/README.md @@ -0,0 +1,63 @@ +# 使用预测模型进行量化训练示例 + +预测模型保存接口: +动态图使用``paddle.jit.save``保存; +静态图使用``paddle.static.save_inference_model``保存。 + +本示例将介绍如何使用PaddleDetection中预测模型进行蒸馏量化训练。 + +## 模型量化蒸馏训练流程 + +### 1. 准备COCO格式数据 + +参考[COCO数据准备文档](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.4/docs/tutorials/PrepareDataSet.md#coco%E6%95%B0%E6%8D%AE) + +### 2. 准备需要量化的环境 + +- PaddlePaddle >= 2.2 +- PaddleDet >= 2.3 + +```shell +pip install paddledet +``` + +#### 3 准备待量化模型 +- 下载代码 +``` +git clone https://github.com/PaddlePaddle/PaddleDetection.git +``` +- 导出预测模型 +```shell +python tools/export_model.py -c configs/yolov3/yolov3_mobilenet_v1_270e_coco.yml -o weights=https://paddledet.bj.bcebos.com/models/yolov3_mobilenet_v1_270e_coco.pdparams +``` +或直接下载: +```shell +wget https://paddledet.bj.bcebos.com/models/slim/yolov3_mobilenet_v1_270e_coco.tar +tar -xf yolov3_mobilenet_v1_270e_coco.tar +``` + +#### 2.4 测试模型精度 +拷贝``yolov3_mobilenet_v1_270e_coco``文件夹到``PaddleSlim/demo/auto-compression/``文件夹。 +``` +cd PaddleSlim/demo/auto-compression/ +``` +使用[demo_coco.py](../demo_coco.py)脚本得到模型的分类精度: +``` +python3.7 ../demo_coco.py --model_dir=../yolov3_mobilenet_v1_270e_coco/ --model_filename=model.pdmodel --params_filename=model.pdiparams --eval=True +``` + +### 3. 进行多策略融合压缩 + +每一个小章节代表一种多策略融合压缩,不代表需要串行执行。 + +### 3.1 进行量化蒸馏压缩 +蒸馏量化训练示例脚本为[demo_coco.py](../demo_coco.py),使用接口``paddleslim.auto_compression.AutoCompression``对模型进行量化训练。运行命令为: +``` +python ../demo_coco.py \ + --model_dir='infermodel_mobilenetv2' \ + --model_filename='model.pdmodel' \ + --params_filename='./model.pdiparams' \ + --save_dir='./output/' \ + --devices='gpu' \ + --config_path='./yolov3_mbv1_qat_dis.yaml' +``` diff --git a/demo/auto-compression/configs/PaddleDet/coco_dataset.yml b/demo/auto-compression/configs/PaddleDet/coco_dataset.yml index 7a62c3b0b57a5..2dc2d8f09b955 100644 --- a/demo/auto-compression/configs/PaddleDet/coco_dataset.yml +++ b/demo/auto-compression/configs/PaddleDet/coco_dataset.yml @@ -5,14 +5,13 @@ TrainDataset: !COCODataSet image_dir: train2017 anno_path: annotations/instances_train2017.json - dataset_dir: dataset/coco - data_fields: ['image', 'gt_bbox', 'gt_class', 'is_crowd'] + dataset_dir: dataset/coco/ EvalDataset: !COCODataSet image_dir: val2017 anno_path: annotations/instances_val2017.json - dataset_dir: dataset/coco + dataset_dir: dataset/coco/ TestDataset: !ImageFolder diff --git a/demo/auto-compression/configs/PaddleDet/ppyoloe_reader.yml b/demo/auto-compression/configs/PaddleDet/ppyoloe_reader.yml deleted file mode 100644 index a6a1ce6b22fa3..0000000000000 --- a/demo/auto-compression/configs/PaddleDet/ppyoloe_reader.yml +++ /dev/null @@ -1,40 +0,0 @@ -_BASE_: [ - './coco_dataset.yml', -] - -worker_num: 8 -TrainReader: - sample_transforms: - - Decode: {} - - RandomDistort: {} - - RandomExpand: {fill_value: [123.675, 116.28, 103.53]} - - RandomCrop: {} - - RandomFlip: {} - batch_transforms: - - BatchRandomResize: {target_size: [320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 672, 704, 736, 768], random_size: True, random_interp: True, keep_ratio: False} - - NormalizeImage: {mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225], is_scale: True} - - Permute: {} - - PadGT: {} - batch_size: 24 - shuffle: true - drop_last: true - use_shared_memory: true - collate_batch: true - -EvalReader: - sample_transforms: - - Decode: {} - - Resize: {target_size: [640, 640], keep_ratio: False, interp: 2} - - NormalizeImage: {mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225], is_scale: True} - - Permute: {} - batch_size: 4 - -TestReader: - inputs_def: - image_shape: [3, 640, 640] - sample_transforms: - - Decode: {} - - Resize: {target_size: [640, 640], keep_ratio: False, interp: 2} - - NormalizeImage: {mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225], is_scale: True} - - Permute: {} - batch_size: 1 diff --git a/demo/auto-compression/configs/PaddleDet/yolo_reader.yml b/demo/auto-compression/configs/PaddleDet/yolo_reader.yml new file mode 100644 index 0000000000000..7d87a14438428 --- /dev/null +++ b/demo/auto-compression/configs/PaddleDet/yolo_reader.yml @@ -0,0 +1,15 @@ +_BASE_: [ + './coco_dataset.yml', +] + +worker_num: 8 + +TestReader: + inputs_def: + image_shape: [3, 640, 640] + sample_transforms: + - Decode: {} + - Resize: {target_size: [640, 640], keep_ratio: False, interp: 2} + - NormalizeImage: {mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225], is_scale: True} + - Permute: {} + batch_size: 4 diff --git a/demo/auto-compression/configs/PaddleDet/yolov3_mbv1_qat_dis.yaml b/demo/auto-compression/configs/PaddleDet/yolov3_mbv1_qat_dis.yaml new file mode 100644 index 0000000000000..26cab81bc431f --- /dev/null +++ b/demo/auto-compression/configs/PaddleDet/yolov3_mbv1_qat_dis.yaml @@ -0,0 +1,32 @@ +Distillation: + distill_lambda: 1.0 + distill_loss: l2_loss + distill_node_pair: + - teacher_conv2d_84.tmp_0 + - conv2d_84.tmp_0 + - teacher_conv2d_85.tmp_0 + - conv2d_85.tmp_0 + - teacher_conv2d_86.tmp_0 + - conv2d_86.tmp_0 + merge_feed: true + teacher_model_dir: ./yolov3_mobilenet_v1_270e_coco/ + teacher_model_filename: model.pdmodel + teacher_params_filename: model.pdiparams +Quantization: + activation_bits: 8 + is_full_quantize: false + not_quant_pattern: + - skip_quant + quantize_op_types: + - conv2d + - depthwise_conv2d + weight_bits: 8 +TrainConfig: + epochs: 1 + eval_iter: 1000 + learning_rate: 0.0001 + optimizer: SGD + optim_args: + weight_decay: 4.0e-05 + #origin_metric: 0.289 + diff --git a/demo/auto-compression/demo_coco.py b/demo/auto-compression/demo_coco.py index 0164b28cdd55b..9661e993616cd 100644 --- a/demo/auto-compression/demo_coco.py +++ b/demo/auto-compression/demo_coco.py @@ -2,15 +2,156 @@ import sys sys.path[0] = os.path.join(os.path.dirname("__file__"), os.path.pardir) import argparse - +import functools +from functools import partial +import numpy as np +import paddle from ppdet.core.workspace import load_config, merge_config from ppdet.core.workspace import create +from ppdet.metrics import COCOMetric +from paddleslim.auto_compression.config_helpers import load_config as load_slim_config +from paddleslim.auto_compression import AutoCompression + +paddle.enable_static() + +from utility import add_arguments, print_arguments + +parser = argparse.ArgumentParser(description=__doc__) +add_arg = functools.partial(add_arguments, argparser=parser) + +# yapf: disable +add_arg('model_dir', str, None, "inference model directory.") +add_arg('model_filename', str, None, "inference model filename.") +add_arg('params_filename', str, None, "inference params filename.") +add_arg('save_dir', str, 'output', "directory to save compressed model.") +add_arg('devices', str, 'gpu', "which device used to compress.") +add_arg('batch_size', int, 1, "train batch size.") +add_arg('config_path', str, None, "path of compression strategy config.") +add_arg('eval', bool, False, "whether to run evaluation.") +# yapf: enable + + +def reader_wrapper(reader): + def gen(): + for data in reader: + yield { + "image": data['image'], + 'im_shape': data['im_shape'], + 'scale_factor': data['scale_factor'] + } + + return gen + + +def eval(): + dataset = reader_cfg['EvalDataset'] + val_loader = create('TestReader')(dataset, + reader_cfg['worker_num'], + return_list=True) + + place = paddle.CUDAPlace(0) if args.devices == 'gpu' else paddle.CPUPlace() + exe = paddle.static.Executor(place) + + val_program, feed_target_names, fetch_targets = paddle.fluid.io.load_inference_model( + args.model_dir, + exe, + model_filename=args.model_filename, + params_filename=args.params_filename) + clsid2catid = {v: k for k, v in dataset.catid2clsid.items()} + + anno_file = dataset.get_anno() + metric = COCOMetric( + anno_file=anno_file, clsid2catid=clsid2catid, bias=0, IouType='bbox') + for batch_id, data in enumerate(val_loader): + data_new = {k: np.array(v) for k, v in data.items()} + outs = exe.run(val_program, + feed={ + 'image': data['image'], + 'im_shape': data['im_shape'], + 'scale_factor': data['scale_factor'] + }, + fetch_list=fetch_targets, + return_numpy=False) + res = {} + for out in outs: + v = np.array(out) + if len(v.shape) > 1: + res['bbox'] = v + else: + res['bbox_num'] = v + + metric.update(data_new, res) + if batch_id % 100 == 0: + print('Eval iter:', batch_id) + metric.accumulate() + metric.log() + metric.reset() + + +def eval_function(exe, compiled_test_program, test_feed_names, test_fetch_list): + clsid2catid = {v: k for k, v in dataset.catid2clsid.items()} + + anno_file = dataset.get_anno() + metric = COCOMetric( + anno_file=anno_file, clsid2catid=clsid2catid, bias=1, IouType='bbox') + for batch_id, data in enumerate(val_loader): + data_new = {k: np.array(v) for k, v in data.items()} + outs = exe.run(compiled_test_program, + feed={ + 'image': data['image'], + 'im_shape': data['im_shape'], + 'scale_factor': data['scale_factor'] + }, + fetch_list=test_fetch_list, + return_numpy=False) + res = {} + for out in outs: + v = np.array(out) + if len(v.shape) > 1: + res['bbox'] = v + else: + res['bbox_num'] = v + + metric.update(data_new, res) + if batch_id % 100 == 0: + print('Eval iter:', batch_id) + metric.accumulate() + metric.log() + map_res = metric.get_results() + metric.reset() + return map_res['bbox'][0] + + +if __name__ == '__main__': + args = parser.parse_args() + print_arguments(args) + paddle.enable_static() + reader_cfg = load_config('./configs/PaddleDet/yolo_reader.yml') + if args.eval: + eval() + sys.exit(0) + + compress_config, train_config = load_slim_config(args.config_path) -cfg = load_config('./configs/PaddleDet/ppyoloe_reader.yml') + train_loader = create('TestReader')(reader_cfg['TrainDataset'], + reader_cfg['worker_num'], + return_list=True) + dataset = reader_cfg['EvalDataset'] + val_loader = create('TestReader')(reader_cfg['EvalDataset'], + reader_cfg['worker_num'], + return_list=True) -print(cfg) + train_dataloader = reader_wrapper(train_loader) -coco_loader = create('TestReader')(cfg['TrainDataset'], cfg['worker_num']) + ac = AutoCompression( + model_dir=args.model_dir, + model_filename=args.model_filename, + params_filename=args.params_filename, + save_dir=args.save_dir, + strategy_config=compress_config, + train_config=train_config, + train_dataloader=train_dataloader, + eval_callback=eval_function, + devices=args.devices) -for data in coco_loader: - print(data.keys()) + ac.compress() diff --git a/paddleslim/auto_compression/compressor.py b/paddleslim/auto_compression/compressor.py index 1a0447f56560b..48515d61e1636 100644 --- a/paddleslim/auto_compression/compressor.py +++ b/paddleslim/auto_compression/compressor.py @@ -315,7 +315,8 @@ def _start_train(self, train_program_info, test_program_info): _logger.info("epoch: {}, batch: {}, loss: {}".format( epoch_id, batch_id, np_probs_float)) - if batch_id % int(self.train_config.eval_iter) == 0: + if batch_id % int( + self.train_config.eval_iter) == 0 and batch_id != 0: if self.eval_function is not None: # GMP pruner step 3: update params before summrizing sparsity, saving model or evaluation. diff --git a/paddleslim/quant/quanter.py b/paddleslim/quant/quanter.py index a982529b91ebd..9668ed34e2587 100755 --- a/paddleslim/quant/quanter.py +++ b/paddleslim/quant/quanter.py @@ -198,7 +198,8 @@ def quant_aware(program, optimizer_func=None, executor=None, onnx_format=False, - return_program=False): + return_program=False, + draw_graph=False): """Add quantization and dequantization operators to "program" for quantization training or testing. @@ -241,6 +242,8 @@ def quant_aware(program, 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. + draw_graph(bool): whether to draw graph when quantization is initialized. In order to prevent cycle, + the ERNIE model needs to be set to True. Default is False. Returns: paddle.static.CompiledProgram | paddle.static.Program: Program with quantization and dequantization ``operators`` """ @@ -308,15 +311,10 @@ def quant_aware(program, VARS_MAPPING_TABLE)) save_dict(main_graph.out_node_mapping_table) - main_graph.draw('./', 'graph.pdf') - #remove_ctr_vars = set() - #from paddle.fluid.framework import IrVarNode - #all_var_nodes = {IrVarNode(node) for node in main_graph.nodes() if node.is_var()} - #for node in all_var_nodes: - # print("node: ", node) - # if node.is_ctrl_var(): - # remove_ctr_vars.add(node) - #self.safe_remove_nodes(remove_ctr_vars) + # TDOD: remove it. + if draw_graph: + main_graph.draw('./', 'graph.pdf') + if for_test or return_program: quant_program = main_graph.to_program() else: