From 96d309a7765bf0ba97771f618c988c491e31ffa0 Mon Sep 17 00:00:00 2001 From: Guanghua Yu <742925032@qq.com> Date: Fri, 19 Aug 2022 19:50:59 +0800 Subject: [PATCH] Cherry pick some PR (#1354) --- README.md | 52 ++- demo/quant/pact_quant_aware/train.py | 30 +- demo/quant/quant_aware/train.py | 7 +- demo/quant/quant_post/eval.py | 7 +- .../auto-compression/auto_compression_api.rst | 10 +- .../api_cn/static/quant/quantization_api.rst | 10 +- example/auto_compression/README.md | 10 +- .../detection/configs/ppyoloe_l_qat_dis.yaml | 1 + .../detection/configs/ppyoloe_s_qat_dis.yaml | 34 ++ example/auto_compression/detection/eval.py | 2 +- example/auto_compression/detection/run.py | 5 +- .../hyperparameter_tutorial.md | 186 +++++++-- .../image_classification/README.md | 46 ++- .../image_classification/eval.py | 2 +- .../image_classification/infer.py | 2 +- .../image_classification/run.py | 14 +- example/auto_compression/nlp/run.py | 2 +- .../pytorch_huggingface/run.py | 2 +- .../pytorch_yolo_series/README.md | 170 ++++++++ .../configs/yolov5s_qat_dis.yaml | 15 +- .../configs/yolov6s_qat_dis.yaml | 15 +- .../configs/yolov7_qat_dis.yaml | 18 +- .../configs/yolov7_tiny_qat_dis.yaml | 32 ++ .../cpp_infer/CMakeLists.txt | 0 .../cpp_infer/README.md | 37 +- .../cpp_infer/compile.sh | 0 .../cpp_infer/trt_run.cc | 9 +- .../pytorch_yolo_series/dataset.py | 115 ++++++ .../pytorch_yolo_series/eval.py | 102 +++++ .../images/000000570688.jpg | Bin .../pytorch_yolo_series/onnx_trt_infer.py | 378 ++++++++++++++++++ .../paddle_trt_infer.py | 10 +- .../post_process.py | 66 ++- .../pytorch_yolo_series/run.py | 127 ++++++ .../auto_compression/pytorch_yolov5/README.md | 147 ------- .../pytorch_yolov5/configs/yolov5_reader.yml | 27 -- .../pytorch_yolov5/cpp_infer/trt_run.cc | 116 ------ .../pytorch_yolov5/paddle_trt_infer.py | 322 --------------- .../auto_compression/pytorch_yolov5/run.py | 179 --------- .../auto_compression/pytorch_yolov6/README.md | 143 ------- .../pytorch_yolov6/configs/yolov6_reader.yml | 27 -- .../pytorch_yolov6/cpp_infer/CMakeLists.txt | 263 ------------ .../pytorch_yolov6/cpp_infer/README.md | 50 --- .../pytorch_yolov6/cpp_infer/compile.sh | 37 -- .../pytorch_yolov6/cpp_infer/trt_run.cc | 116 ------ .../auto_compression/pytorch_yolov6/eval.py | 159 -------- .../pytorch_yolov6/images/000000570688.jpg | Bin 138365 -> 0 bytes .../pytorch_yolov6/paddle_trt_infer.py | 322 --------------- .../pytorch_yolov6/post_quant.py | 106 ----- .../auto_compression/pytorch_yolov6/run.py | 181 --------- .../auto_compression/pytorch_yolov7/README.md | 152 ------- .../pytorch_yolov7/configs/yolov7_reader.yaml | 27 -- .../pytorch_yolov7/cpp_infer/CMakeLists.txt | 263 ------------ .../pytorch_yolov7/cpp_infer/README.md | 51 --- .../pytorch_yolov7/cpp_infer/compile.sh | 37 -- .../auto_compression/pytorch_yolov7/eval.py | 151 ------- .../pytorch_yolov7/images/000000570688.jpg | Bin 138365 -> 0 bytes .../pytorch_yolov7/post_process.py | 173 -------- .../auto_compression/pytorch_yolov7/run.py | 172 -------- .../semantic_segmentation/README.md | 4 +- .../semantic_segmentation/run.py | 14 +- .../tensorflow_mobilenet/eval.py | 5 +- .../tensorflow_mobilenet/run.py | 5 +- .../post_training_quantization/analysis.md | 49 +++ .../detection/analysis.py | 179 +++++++++ .../detection/configs/picodet_s_analysis.yaml | 47 +++ .../detection/configs/picodet_s_ptq.yaml | 38 ++ .../detection}/eval.py | 24 +- .../detection/keypoint_utils.py | 307 ++++++++++++++ .../detection/post_process.py | 157 ++++++++ .../detection}/post_quant.py | 29 +- .../pytorch_yolo_series/README.md | 150 +++++++ .../pytorch_yolo_series/analysis.py | 115 ++++++ .../configs/yolov5s_ptq.yaml | 8 + .../configs/yolov6s_analysis.yaml | 15 + .../configs/yolov6s_analyzed_ptq.yaml | 8 + .../configs/yolov6s_ptq.yaml | 8 + .../configs/yolov7s_ptq.yaml | 7 + .../pytorch_yolo_series/dataset.py | 115 ++++++ .../pytorch_yolo_series/eval.py | 101 +++++ .../images/hist_compare.png | Bin 0 -> 159445 bytes .../images/sensitivity_rank.png | Bin 0 -> 25389 bytes .../pytorch_yolo_series}/post_process.py | 66 ++- .../pytorch_yolo_series}/post_quant.py | 60 ++- paddleslim/analysis/_utils.py | 4 +- paddleslim/analysis/latency_predictor.py | 4 +- paddleslim/auto_compression/__init__.py | 14 +- paddleslim/auto_compression/auto_strategy.py | 6 +- paddleslim/auto_compression/compressor.py | 124 ++++-- paddleslim/auto_compression/config_helpers.py | 46 +-- .../create_compressed_program.py | 10 +- .../auto_compression/strategy_config.py | 6 +- paddleslim/auto_compression/utils/__init__.py | 6 - paddleslim/auto_compression/utils/fake_ptq.py | 2 +- .../auto_compression/utils/load_model.py | 45 --- paddleslim/auto_compression/utils/predict.py | 4 +- .../auto_compression/utils/prune_model.py | 7 +- paddleslim/common/__init__.py | 7 +- paddleslim/common/config_helper.py | 60 +++ .../utils => common}/dataloader.py | 3 +- paddleslim/common/load_model.py | 222 ++++++++++ paddleslim/dygraph/prune/pruning_plan.py | 3 +- paddleslim/prune/prune_worker.py | 5 +- paddleslim/prune/pruner.py | 4 +- paddleslim/quant/__init__.py | 4 +- paddleslim/quant/analysis.py | 312 +++++++++++++++ paddleslim/quant/post_quant_hpo.py | 20 +- paddleslim/quant/quanter.py | 128 ++++-- requirements.txt | 2 - tests/act/test_act_api.py | 34 +- tests/test_prune_walker.py | 6 +- tests/test_quant_post.py | 4 +- 112 files changed, 3674 insertions(+), 3666 deletions(-) create mode 100644 example/auto_compression/detection/configs/ppyoloe_s_qat_dis.yaml create mode 100644 example/auto_compression/pytorch_yolo_series/README.md rename example/auto_compression/{pytorch_yolov5 => pytorch_yolo_series}/configs/yolov5s_qat_dis.yaml (60%) rename example/auto_compression/{pytorch_yolov6 => pytorch_yolo_series}/configs/yolov6s_qat_dis.yaml (62%) rename example/auto_compression/{pytorch_yolov7 => pytorch_yolo_series}/configs/yolov7_qat_dis.yaml (56%) create mode 100644 example/auto_compression/pytorch_yolo_series/configs/yolov7_tiny_qat_dis.yaml rename example/auto_compression/{pytorch_yolov5 => pytorch_yolo_series}/cpp_infer/CMakeLists.txt (100%) rename example/auto_compression/{pytorch_yolov5 => pytorch_yolo_series}/cpp_infer/README.md (64%) rename example/auto_compression/{pytorch_yolov5 => pytorch_yolo_series}/cpp_infer/compile.sh (100%) rename example/auto_compression/{pytorch_yolov7 => pytorch_yolo_series}/cpp_infer/trt_run.cc (93%) create mode 100644 example/auto_compression/pytorch_yolo_series/dataset.py create mode 100644 example/auto_compression/pytorch_yolo_series/eval.py rename example/auto_compression/{pytorch_yolov5 => pytorch_yolo_series}/images/000000570688.jpg (100%) create mode 100644 example/auto_compression/pytorch_yolo_series/onnx_trt_infer.py rename example/auto_compression/{pytorch_yolov7 => pytorch_yolo_series}/paddle_trt_infer.py (98%) rename example/auto_compression/{pytorch_yolov6 => pytorch_yolo_series}/post_process.py (75%) create mode 100644 example/auto_compression/pytorch_yolo_series/run.py delete mode 100644 example/auto_compression/pytorch_yolov5/README.md delete mode 100644 example/auto_compression/pytorch_yolov5/configs/yolov5_reader.yml delete mode 100644 example/auto_compression/pytorch_yolov5/cpp_infer/trt_run.cc delete mode 100644 example/auto_compression/pytorch_yolov5/paddle_trt_infer.py delete mode 100644 example/auto_compression/pytorch_yolov5/run.py delete mode 100644 example/auto_compression/pytorch_yolov6/README.md delete mode 100644 example/auto_compression/pytorch_yolov6/configs/yolov6_reader.yml delete mode 100644 example/auto_compression/pytorch_yolov6/cpp_infer/CMakeLists.txt delete mode 100644 example/auto_compression/pytorch_yolov6/cpp_infer/README.md delete mode 100644 example/auto_compression/pytorch_yolov6/cpp_infer/compile.sh delete mode 100644 example/auto_compression/pytorch_yolov6/cpp_infer/trt_run.cc delete mode 100644 example/auto_compression/pytorch_yolov6/eval.py delete mode 100644 example/auto_compression/pytorch_yolov6/images/000000570688.jpg delete mode 100644 example/auto_compression/pytorch_yolov6/paddle_trt_infer.py delete mode 100644 example/auto_compression/pytorch_yolov6/post_quant.py delete mode 100644 example/auto_compression/pytorch_yolov6/run.py delete mode 100644 example/auto_compression/pytorch_yolov7/README.md delete mode 100644 example/auto_compression/pytorch_yolov7/configs/yolov7_reader.yaml delete mode 100644 example/auto_compression/pytorch_yolov7/cpp_infer/CMakeLists.txt delete mode 100644 example/auto_compression/pytorch_yolov7/cpp_infer/README.md delete mode 100644 example/auto_compression/pytorch_yolov7/cpp_infer/compile.sh delete mode 100644 example/auto_compression/pytorch_yolov7/eval.py delete mode 100644 example/auto_compression/pytorch_yolov7/images/000000570688.jpg delete mode 100644 example/auto_compression/pytorch_yolov7/post_process.py delete mode 100644 example/auto_compression/pytorch_yolov7/run.py create mode 100644 example/post_training_quantization/analysis.md create mode 100644 example/post_training_quantization/detection/analysis.py create mode 100644 example/post_training_quantization/detection/configs/picodet_s_analysis.yaml create mode 100644 example/post_training_quantization/detection/configs/picodet_s_ptq.yaml rename example/{auto_compression/pytorch_yolov5 => post_training_quantization/detection}/eval.py (86%) create mode 100644 example/post_training_quantization/detection/keypoint_utils.py create mode 100644 example/post_training_quantization/detection/post_process.py rename example/{auto_compression/pytorch_yolov5 => post_training_quantization/detection}/post_quant.py (75%) create mode 100644 example/post_training_quantization/pytorch_yolo_series/README.md create mode 100644 example/post_training_quantization/pytorch_yolo_series/analysis.py create mode 100644 example/post_training_quantization/pytorch_yolo_series/configs/yolov5s_ptq.yaml create mode 100644 example/post_training_quantization/pytorch_yolo_series/configs/yolov6s_analysis.yaml create mode 100644 example/post_training_quantization/pytorch_yolo_series/configs/yolov6s_analyzed_ptq.yaml create mode 100644 example/post_training_quantization/pytorch_yolo_series/configs/yolov6s_ptq.yaml create mode 100644 example/post_training_quantization/pytorch_yolo_series/configs/yolov7s_ptq.yaml create mode 100644 example/post_training_quantization/pytorch_yolo_series/dataset.py create mode 100644 example/post_training_quantization/pytorch_yolo_series/eval.py create mode 100644 example/post_training_quantization/pytorch_yolo_series/images/hist_compare.png create mode 100644 example/post_training_quantization/pytorch_yolo_series/images/sensitivity_rank.png rename example/{auto_compression/pytorch_yolov5 => post_training_quantization/pytorch_yolo_series}/post_process.py (75%) rename example/{auto_compression/pytorch_yolov7 => post_training_quantization/pytorch_yolo_series}/post_quant.py (56%) delete mode 100644 paddleslim/auto_compression/utils/load_model.py create mode 100644 paddleslim/common/config_helper.py rename paddleslim/{auto_compression/utils => common}/dataloader.py (95%) create mode 100644 paddleslim/common/load_model.py create mode 100644 paddleslim/quant/analysis.py diff --git a/README.md b/README.md index 5289fb2e..063ae08a 100755 --- a/README.md +++ b/README.md @@ -4,40 +4,51 @@

- - - + + + + + + + +

-PaddleSlim是一个专注于深度学习模型压缩的工具库,提供**低比特量化、知识蒸馏、稀疏化和模型结构搜索**等模型压缩策略,帮助用户快速实现模型的小型化。 +PaddleSlim是一个专注于深度学习模型压缩的工具库,提供**低比特量化、知识蒸馏、稀疏化和模型结构搜索**等模型压缩策略,帮助开发者快速实现模型的小型化。 ## 产品动态 +- 🔥 **2022.08.16:自动化压缩功能升级** + - 支持直接加载ONNX模型和Paddle模型导出至ONNX + - 发布量化分析工具试用版,发布[YOLO系列离线量化工具](example/post_training_quantization/pytorch_yolo_series/) + - 更新[YOLO-Series自动化压缩模型库](example/auto_compression/pytorch_yolo_series) + + | 模型 | Base mAPval
0.5:0.95 | ACT量化mAPval
0.5:0.95 | 模型体积压缩比 | 预测时延FP32
| 预测时延INT8
| 预测加速比 | + | :-------- |:-------- |:--------: | :--------: | :---------------------: | :----------------: | :----------------: | + | PPYOLOE-s | 43.1 | 42.6 | 3.9倍 | 6.51ms | 2.12ms | 3.1倍 | + | YOLOv5s | 37.4 | 36.9 | 3.8倍 | 5.95ms | 1.87ms | 3.2倍 | + | YOLOv6s | 42.4 | 41.3 | 3.9倍 | 9.06ms | 1.83ms | 5.0倍 | + | YOLOv7 | 51.1 | 50.9 | 3.9倍 | 26.84ms | 4.55ms | 5.9倍 | + | YOLOv7-Tiny | 37.3 | 37.0 | 3.9倍 | 5.06ms | 1.68ms | 3.0倍 | + + - 🔥 **2022.07.01: 发布[v2.3.0版本](https://github.com/PaddlePaddle/PaddleSlim/releases/tag/v2.3.0)** - 发布[自动化压缩功能](example/auto_compression) - - - 支持代码无感知压缩:用户只需提供推理模型文件和数据,既可进行离线量化(PTQ)、量化训练(QAT)、稀疏训练等压缩任务。 + - 支持代码无感知压缩:开发者只需提供推理模型文件和数据,既可进行离线量化(PTQ)、量化训练(QAT)、稀疏训练等压缩任务。 - 支持自动策略选择,根据任务特点和部署环境特性:自动搜索合适的离线量化方法,自动搜索最佳的压缩策略组合方式。 - 发布[自然语言处理](example/auto_compression/nlp)、[图像语义分割](example/auto_compression/semantic_segmentation)、[图像目标检测](example/auto_compression/detection)三个方向的自动化压缩示例。 - - 发布`X2Paddle`模型自动化压缩方案:[YOLOv5](example/auto_compression/pytorch_yolov5)、[YOLOv6](example/auto_compression/pytorch_yolov6)、[YOLOv7](example/auto_compression/pytorch_yolov7)、[HuggingFace](example/auto_compression/pytorch_huggingface)、[MobileNet](example/auto_compression/tensorflow_mobilenet)。 - + - 发布`X2Paddle`模型自动化压缩方案:[YOLOv5](example/auto_compression/pytorch_yolo_series)、[YOLOv6](example/auto_compression/pytorch_yolo_series)、[YOLOv7](example/auto_compression/pytorch_yolo_series)、[HuggingFace](example/auto_compression/pytorch_huggingface)、[MobileNet](example/auto_compression/tensorflow_mobilenet)。 - 升级量化功能 - - - 统一量化模型格式 - - 离线量化支持while op - - 新增7种[离线量化方法](docs/zh_cn/tutorials/quant/post_training_quantization.md), 包括HIST, AVG, EMD, Bias Correction, AdaRound等 - - 修复BERT大模型量化训练过慢的问题 - + - 统一量化模型格式;离线量化支持while op;修复BERT大模型量化训练过慢的问题。 + - 新增7种[离线量化方法](docs/zh_cn/tutorials/quant/post_training_quantization.md), 包括HIST, AVG, EMD, Bias Correction, AdaRound等。 - 支持半结构化稀疏训练 - - 新增延时预估工具 + - 支持对稀疏化模型、低比特量化模型的性能预估;支持预估指定模型在特定部署环境下 (ARM CPU + Paddle Lite) 的推理性能;提供 SD625、SD710、RK3288 芯片 + Paddle Lite 的预估接口。 + - 提供部署环境自动扩展工具,可以自动增加在更多 ARM CPU 设备上的预估工具。 - - 支持预估指定模型在特定部署环境下 (ARM CPU + Paddle Lite) 的推理性能 - - 提供部署环境自动扩展工具,可以自动增加在更多 ARM CPU 设备上的预估工具 - - 支持对稀疏化模型、低比特量化模型的性能预估 - - 提供 SD625、SD710、RK3288 芯片 + Paddle Lite 的预估接口 - +
+历史更新 - **2021.11.15: 发布v2.2.0版本** @@ -52,6 +63,7 @@ PaddleSlim是一个专注于深度学习模型压缩的工具库,提供**低 更多信息请参考:[release note](https://github.com/PaddlePaddle/PaddleSlim/releases) +
## 基础压缩功能概览 diff --git a/demo/quant/pact_quant_aware/train.py b/demo/quant/pact_quant_aware/train.py index fb70c0fc..67945a45 100644 --- a/demo/quant/pact_quant_aware/train.py +++ b/demo/quant/pact_quant_aware/train.py @@ -65,6 +65,8 @@ add_arg('use_pact', bool, True, "Whether to use PACT or not.") add_arg('analysis', bool, False, "Whether analysis variables distribution.") +add_arg('onnx_format', bool, False, + "Whether use onnx format or not.") add_arg('ce_test', bool, False, "Whether to CE test.") # yapf: enable @@ -257,6 +259,8 @@ def compress(args): 'window_size': 10000, # The decay coefficient of moving average, default is 0.9 'moving_rate': 0.9, + # Whether use onnx format or not + 'onnx_format': args.onnx_format, } # 2. quantization transform programs (training aware) @@ -298,9 +302,9 @@ def compress(args): places, quant_config, scope=None, - act_preprocess_func=act_preprocess_func, - optimizer_func=optimizer_func, - executor=executor, + act_preprocess_func=None, + optimizer_func=None, + executor=None, for_test=True) compiled_train_prog = quant_aware( train_prog, @@ -425,29 +429,23 @@ def compress(args): # 3. Freeze the graph after training by adjusting the quantize # operators' order for the inference. # The dtype of float_program's weights is float32, but in int8 range. - float_program, int8_program = convert(val_program, places, quant_config, \ - scope=None, \ - save_int8=True) + model_path = os.path.join(quantization_model_save_dir, args.model) + if not os.path.isdir(model_path): + os.makedirs(model_path) + float_program = convert(val_program, places, quant_config) _logger.info("eval best_model after convert") final_acc1 = test(best_epoch, float_program) _logger.info("final acc:{}".format(final_acc1)) # 4. Save inference model - model_path = os.path.join(quantization_model_save_dir, args.model, - 'act_' + quant_config['activation_quantize_type'] - + '_w_' + quant_config['weight_quantize_type']) - float_path = os.path.join(model_path, 'float') - if not os.path.isdir(model_path): - os.makedirs(model_path) - paddle.fluid.io.save_inference_model( - dirname=float_path, + dirname=model_path, feeded_var_names=[image.name], target_vars=[out], executor=exe, main_program=float_program, - model_filename=float_path + '/model', - params_filename=float_path + '/params') + model_filename=model_path + '/model.pdmodel', + params_filename=model_path + '/model.pdiparams') def main(): diff --git a/demo/quant/quant_aware/train.py b/demo/quant/quant_aware/train.py index abf6073e..7fc133a4 100644 --- a/demo/quant/quant_aware/train.py +++ b/demo/quant/quant_aware/train.py @@ -126,6 +126,8 @@ def compress(args): 'window_size': 10000, # The decay coefficient of moving average, default is 0.9 'moving_rate': 0.9, + # Whether use onnx format or not + 'onnx_format': args.onnx_format, } pretrain = True @@ -294,10 +296,7 @@ def compress(args): # operators' order for the inference. # The dtype of float_program's weights is float32, but in int8 range. ############################################################################################################ - float_program, int8_program = convert(val_program, places, quant_config, \ - scope=None, \ - save_int8=True, - onnx_format=args.onnx_format) + float_program = convert(val_program, places, quant_config) print("eval best_model after convert") final_acc1 = test(best_epoch, float_program) ############################################################################################################ diff --git a/demo/quant/quant_post/eval.py b/demo/quant/quant_post/eval.py index 310eacd0..e8184e84 100755 --- a/demo/quant/quant_post/eval.py +++ b/demo/quant/quant_post/eval.py @@ -21,8 +21,7 @@ import functools import paddle sys.path[0] = os.path.join( os.path.dirname("__file__"), os.path.pardir, os.path.pardir) -sys.path[1] = os.path.join( - os.path.dirname("__file__"), os.path.pardir) +sys.path[1] = os.path.join(os.path.dirname("__file__"), os.path.pardir) import imagenet_reader as reader from utility import add_arguments, print_arguments @@ -31,8 +30,8 @@ parser = argparse.ArgumentParser(description=__doc__) add_arg = functools.partial(add_arguments, argparser=parser) add_arg('use_gpu', bool, True, "Whether to use GPU or not.") add_arg('model_path', str, "./pruning/checkpoints/resnet50/2/eval_model/", "Whether to use pretrained model.") -add_arg('model_name', str, '__model__', "model filename for inference model") -add_arg('params_name', str, '__params__', "params filename for inference model") +add_arg('model_name', str, 'model.pdmodel', "model filename for inference model") +add_arg('params_name', str, 'model.pdiparams', "params filename for inference model") add_arg('batch_size', int, 64, "Minibatch size.") # yapf: enable diff --git a/docs/zh_cn/api_cn/static/auto-compression/auto_compression_api.rst b/docs/zh_cn/api_cn/static/auto-compression/auto_compression_api.rst index f5731df4..c308413d 100644 --- a/docs/zh_cn/api_cn/static/auto-compression/auto_compression_api.rst +++ b/docs/zh_cn/api_cn/static/auto-compression/auto_compression_api.rst @@ -3,19 +3,19 @@ AutoCompression自动压缩功能 AutoCompression --------------- -.. py:class:: paddleslim.auto_compression.AutoCompression(model_dir, model_filename, params_filename, save_dir, strategy_config, train_config, train_dataloader, eval_callback, devices='gpu') +.. py:class:: paddleslim.auto_compression.AutoCompression(model_dir, train_dataloader, model_filename, params_filename, save_dir, strategy_config, train_config, eval_callback, devices='gpu') -`源代码 `_ +`源代码 `_ 根据指定的配置对使用 ``paddle.jit.save`` 接口或者 ``paddle.static.save_inference_model`` 接口保存的推理模型进行压缩。 **参数: ** - **model_dir(str)** - 需要压缩的推理模型所在的目录。 +- **train_dataloader(paddle.io.DataLoader)** - 训练数据迭代器。注意:如果选择离线量化超参搜索策略的话, ``train_dataloader`` 和 ``eval_callback`` 设置相同的数据读取即可。 - **model_filename(str)** - 需要压缩的推理模型文件名称。 - **params_filename(str)** - 需要压缩的推理模型参数文件名称。 - **save_dir(str)** - 压缩后模型的所保存的目录。 -- **train_dataloader(paddle.io.DataLoader)** - 训练数据迭代器。注意:如果选择离线量化超参搜索策略的话, ``train_dataloader`` 和 ``eval_callback`` 设置相同的数据读取即可。 - **train_config(dict)** - 训练配置。可以配置的参数请参考: ``_ 。注意:如果选择离线量化超参搜索策略的话, ``train_config`` 直接设置为 ``None`` 即可。 - **strategy_config(dict, list(dict), 可选)** - 使用的压缩策略,可以通过设置多个单种策略来并行使用这些压缩方式。字典的关键字必须在: ``Quantization`` (量化配置, 可配置的参数参考 ``_ ), @@ -82,13 +82,13 @@ AutoCompression eval_dataloader = Cifar10(mode='eval') - ac = AutoCompression(model_path, model_filename, params_filename, save_dir, \ + ac = AutoCompression(model_path, train_dataloader, model_filename, params_filename, save_dir, \ strategy_config="Quantization": Quantization(**default_ptq_config), "Distillation": HyperParameterOptimization(**default_distill_config)}, \ - train_config=None, train_dataloader=train_dataloader, eval_callback=eval_dataloader,devices='gpu') + train_config=None, eval_callback=eval_dataloader,devices='gpu') ``` diff --git a/docs/zh_cn/api_cn/static/quant/quantization_api.rst b/docs/zh_cn/api_cn/static/quant/quantization_api.rst index a12e4e9b..f2d7b77d 100644 --- a/docs/zh_cn/api_cn/static/quant/quantization_api.rst +++ b/docs/zh_cn/api_cn/static/quant/quantization_api.rst @@ -118,7 +118,7 @@ quant_post_dynamic quant_post_static --------------- -.. py:function:: paddleslim.quant.quant_post_static(executor,model_dir, quantize_model_path, batch_generator=None, sample_generator=None, model_filename=None, params_filename=None, save_model_filename='__model__', save_params_filename='__params__', batch_size=16, batch_nums=None, scope=None, algo='KL', round_type='round', quantizable_op_type=["conv2d","depthwise_conv2d","mul"], is_full_quantize=False, weight_bits=8, activation_bits=8, activation_quantize_type='range_abs_max', weight_quantize_type='channel_wise_abs_max', onnx_format=False, skip_tensor_list=None, optimize_model=False) +.. py:function:: paddleslim.quant.quant_post_static(executor,model_dir, quantize_model_path, batch_generator=None, sample_generator=None, model_filename=None, params_filename=None, save_model_filename='model.pdmodel', save_params_filename='model.pdiparams', batch_size=16, batch_nums=None, scope=None, algo='KL', round_type='round', quantizable_op_type=["conv2d","depthwise_conv2d","mul"], is_full_quantize=False, weight_bits=8, activation_bits=8, activation_quantize_type='range_abs_max', weight_quantize_type='channel_wise_abs_max', onnx_format=False, skip_tensor_list=None, optimize_model=False) `源代码 `_ @@ -217,15 +217,15 @@ quant_post_static target_vars=[out], main_program=val_prog, executor=exe, - model_filename='__model__', - params_filename='__params__') + model_filename='model.pdmodel', + params_filename='model.pdiparams') quant_post_static( executor=exe, model_dir='./model_path', quantize_model_path='./save_path', sample_generator=val_reader, - model_filename='__model__', - params_filename='__params__', + model_filename='model.pdmodel', + params_filename='model.pdiparams', batch_size=16, batch_nums=10) diff --git a/example/auto_compression/README.md b/example/auto_compression/README.md index e907908b..cb3b9277 100644 --- a/example/auto_compression/README.md +++ b/example/auto_compression/README.md @@ -82,15 +82,15 @@ ACT相比传统的模型压缩方法, | [语义分割](./semantic_segmentation) | UNet | 65.00 | 64.93 | 15.29 | 10.23 | **1.49** | NVIDIA Tesla T4 | | NLP | PP-MiniLM | 72.81 | 72.44 | 128.01 | 17.97 | **7.12** | NVIDIA Tesla T4 | | NLP | ERNIE 3.0-Medium | 73.09 | 72.40 | 29.25(fp16) | 19.61 | **1.49** | NVIDIA Tesla T4 | -| [目标检测](./pytorch_yolov5) | YOLOv5s
(PyTorch) | 37.40 | 36.9 | 5.95 | 1.87 | **3.18** | NVIDIA Tesla T4 | -| [目标检测](./pytorch_yolov6) | YOLOv6s
(PyTorch) | 42.4 | 41.3 | 9.06 | 1.83 | **4.95** | NVIDIA Tesla T4 | -| [目标检测](./pytorch_yolov7) | YOLOv7
(PyTorch) | 51.1 | 50.8 | 26.84 | 4.55 | **5.89** | NVIDIA Tesla T4 | -| [目标检测](./detection) | PP-YOLOE-l | 50.9 | 50.6 | 11.2 | 6.7 | **1.67** | NVIDIA Tesla V100 | +| [目标检测](./pytorch_yolo_series) | YOLOv5s
(PyTorch) | 37.40 | 36.9 | 5.95 | 1.87 | **3.18** | NVIDIA Tesla T4 | +| [目标检测](./pytorch_yolo_series) | YOLOv6s
(PyTorch) | 42.4 | 41.3 | 9.06 | 1.83 | **4.95** | NVIDIA Tesla T4 | +| [目标检测](./pytorch_yolo_series) | YOLOv7
(PyTorch) | 51.1 | 50.8 | 26.84 | 4.55 | **5.89** | NVIDIA Tesla T4 | +| [目标检测](./detection) | PP-YOLOE-s | 43.1 | 42.6 | 6.51 | 2.12 | **3.07** | NVIDIA Tesla T4 | | [图像分类](./image_classification) | MobileNetV1
(TensorFlow) | 71.0 | 70.22 | 30.45 | 15.86 | **1.92** | SDMM865(骁龙865) | - 备注:目标检测精度指标为mAP(0.5:0.95)精度测量结果。图像分割精度指标为IoU精度测量结果。 - 更多飞桨模型应用示例及Benchmark可以参考:[图像分类](./image_classification),[目标检测](./detection),[语义分割](./semantic_segmentation),[自然语言处理](./nlp) -- 更多其它框架应用示例及Benchmark可以参考:[YOLOv5(PyTorch)](./pytorch_yolov5),[YOLOv6(PyTorch)](./pytorch_yolov6),[YOLOv7(PyTorch)](./pytorch_yolov7),[HuggingFace(PyTorch)](./pytorch_huggingface),[MobileNet(TensorFlow)](./tensorflow_mobilenet)。 +- 更多其它框架应用示例及Benchmark可以参考:[YOLOv5(PyTorch)](./pytorch_yolo_series),[YOLOv6(PyTorch)](./pytorch_yolo_series),[YOLOv7(PyTorch)](./pytorch_yolo_series),[HuggingFace(PyTorch)](./pytorch_huggingface),[MobileNet(TensorFlow)](./tensorflow_mobilenet)。 ## **环境准备** diff --git a/example/auto_compression/detection/configs/ppyoloe_l_qat_dis.yaml b/example/auto_compression/detection/configs/ppyoloe_l_qat_dis.yaml index 0b28ef89..1727e533 100644 --- a/example/auto_compression/detection/configs/ppyoloe_l_qat_dis.yaml +++ b/example/auto_compression/detection/configs/ppyoloe_l_qat_dis.yaml @@ -12,6 +12,7 @@ Distillation: loss: soft_label Quantization: + onnx_format: true use_pact: true activation_quantize_type: 'moving_average_abs_max' quantize_op_types: diff --git a/example/auto_compression/detection/configs/ppyoloe_s_qat_dis.yaml b/example/auto_compression/detection/configs/ppyoloe_s_qat_dis.yaml new file mode 100644 index 00000000..466c9c2b --- /dev/null +++ b/example/auto_compression/detection/configs/ppyoloe_s_qat_dis.yaml @@ -0,0 +1,34 @@ + +Global: + reader_config: configs/yolo_reader.yml + input_list: ['image'] + arch: PPYOLOE # When export exclude_nms=True, need set arch: PPYOLOE + Evaluation: True + model_dir: ./ppyoloe_crn_s_300e_coco + model_filename: model.pdmodel + params_filename: model.pdiparams + +Distillation: + alpha: 1.0 + loss: soft_label + +Quantization: + onnx_format: true + use_pact: true + activation_quantize_type: 'moving_average_abs_max' + quantize_op_types: + - conv2d + - depthwise_conv2d + +TrainConfig: + train_iter: 5000 + eval_iter: 1000 + learning_rate: + type: CosineAnnealingDecay + learning_rate: 0.00003 + T_max: 6000 + optimizer_builder: + optimizer: + type: SGD + weight_decay: 4.0e-05 + diff --git a/example/auto_compression/detection/eval.py b/example/auto_compression/detection/eval.py index 3a723653..a4ea554c 100644 --- a/example/auto_compression/detection/eval.py +++ b/example/auto_compression/detection/eval.py @@ -20,7 +20,7 @@ import paddle from ppdet.core.workspace import load_config, merge_config from ppdet.core.workspace import create from ppdet.metrics import COCOMetric, VOCMetric, KeyPointTopDownCOCOEval -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config +from paddleslim.common import load_config as load_slim_config from keypoint_utils import keypoint_post_process diff --git a/example/auto_compression/detection/run.py b/example/auto_compression/detection/run.py index b7cc7505..523f2439 100644 --- a/example/auto_compression/detection/run.py +++ b/example/auto_compression/detection/run.py @@ -20,7 +20,7 @@ import paddle from ppdet.core.workspace import load_config, merge_config from ppdet.core.workspace import create from ppdet.metrics import COCOMetric, VOCMetric, KeyPointTopDownCOCOEval -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config +from paddleslim.common import load_config as load_slim_config from paddleslim.auto_compression import AutoCompression from keypoint_utils import keypoint_post_process @@ -121,7 +121,8 @@ def eval_function(exe, compiled_test_program, test_feed_names, test_fetch_list): def main(): global global_config all_config = load_slim_config(FLAGS.config_path) - assert "Global" in all_config, f"Key 'Global' not found in config file. \n{all_config}" + assert "Global" in all_config, "Key 'Global' not found in config file. \n{}".format( + all_config) global_config = all_config["Global"] reader_cfg = load_config(global_config['reader_config']) diff --git a/example/auto_compression/hyperparameter_tutorial.md b/example/auto_compression/hyperparameter_tutorial.md index 6d423a9f..29ce9fef 100644 --- a/example/auto_compression/hyperparameter_tutorial.md +++ b/example/auto_compression/hyperparameter_tutorial.md @@ -1,9 +1,9 @@ -# ACT超参详细教程 +# 1. ACT超参详细教程 -## 各压缩方法超参解析 +## 1.1 各压缩方法超参解析 -#### 配置定制量化方案 +### 1.1.1 量化(quantization) 量化参数主要设置量化比特数和量化op类型,其中量化op包含卷积层(conv2d, depthwise_conv2d)和全连接层(mul, matmul_v2)。以下为只量化卷积层的示例: ```yaml @@ -20,69 +20,148 @@ Quantization: moving_rate: 0.9 # 'moving_average_abs_max' 量化方式的衰减系数,默认 0.9。 for_tensorrt: false # 量化后的模型是否使用 TensorRT 进行预测。如果是的话,量化op类型为: TENSORRT_OP_TYPES 。默认值为False. is_full_quantize: false # 是否全量化 + onnx_format: false # 是否采用ONNX量化标准格式 ``` -#### 配置定制蒸馏策略 +以上配置项说明如下: + + +- use_pact: 是否开启PACT。一般情况下,开启PACT后,量化产出的模型精度会更高。算法原理请参考:[PACT: Parameterized Clipping Activation for Quantized Neural Networks](https://arxiv.org/abs/1805.06085) +- activation_bits: 激活量化bit数,可选1~8。默认为8。 +- weight_bits: 参数量化bit数,可选1~8。默认为8。 +- activation_quantize_type: 激活量化方式,可选 'abs_max' , 'range_abs_max' , 'moving_average_abs_max' 。如果使用 TensorRT 加载量化后的模型来预测,请使用 'range_abs_max' 或 'moving_average_abs_max' 。默认为 'moving_average_abs_max'。 +- weight_quantize_type: 参数量化方式。可选 'abs_max' , 'channel_wise_abs_max' , 'range_abs_max' , 'moving_average_abs_max' 。如果使用 TensorRT 加载量化后的模型来预测,请使用 'channel_wise_abs_max' 。 默认 'channel_wise_abs_max' 。 +- not_quant_pattern: 所有 `name_scope` 包含 'not_quant_pattern' 字符串的 op ,都不量化。 `name_scope` 设置方式请参考 [paddle.static.name_scope](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/static/name_scope_cn.html#name-scope)。 +- quantize_op_types:需要进行量化的OP类型。通过以下代码输出所有支持量化的OP类型: +``` +from paddleslim.quant.quanter import TRANSFORM_PASS_OP_TYPES,QUANT_DEQUANT_PASS_OP_TYPES +print(TRANSFORM_PASS_OP_TYPES + QUANT_DEQUANT_PASS_OP_TYPES) +``` +- dtype: 量化后的参数类型,默认 int8 , 目前仅支持 int8 +- window_size: 'range_abs_max' 量化方式的 window size ,默认10000。 +- moving_rate: 'moving_average_abs_max' 量化方式的衰减系数,默认 0.9。 +- for_tensorrt: 量化后的模型是否使用 TensorRT 进行预测。默认值为False. 通过以下代码,输出for_tensorrt=True时会量化到的OP: +``` +from paddleslim.quant.quanter import TENSORRT_OP_TYPES +print(TENSORRT_OP_TYPES) +``` + +- is_full_quantize: 是否量化所有可支持op类型。默认值为False. + + +### 1.1.2 知识蒸馏(knowledge distillation) 蒸馏参数主要设置蒸馏节点(`node`)和教师预测模型路径,如下所示: ```yaml Distillation: - # alpha: 蒸馏loss所占权重;可输入多个数值,支持不同节点之间使用不同的ahpha值 alpha: 1.0 - # loss: 蒸馏loss算法;可输入多个loss,支持不同节点之间使用不同的loss算法 loss: l2 - # node: 蒸馏节点,即某层输出的变量名称,可以选择: - # 1. 使用自蒸馏的话,蒸馏结点仅包含学生网络节点即可, 支持多节点蒸馏; - # 2. 使用其他蒸馏的话,蒸馏节点需要包含教师网络节点和对应的学生网络节点, - # 每两个节点组成一对,分别属于教师模型和学生模型,支持多节点蒸馏。 node: - relu_30.tmp_0 - # teacher_model_dir: 保存预测模型文件和预测模型参数文件的文件夹名称 + teacher_model_dir: ./inference_model # teacher_model_filename: 预测模型文件,格式为 *.pdmodel 或 __model__ teacher_model_filename: model.pdmodel # teacher_params_filename: 预测模型参数文件,格式为 *.pdiparams 或 __params__ teacher_params_filename: model.pdiparams ``` +以上配置项说明如下: + +- alpha: 蒸馏loss所占权重;可输入多个数值,支持不同节点之间使用不同的alpha值。 +- loss: 蒸馏loss算法;可输入多个loss,支持不同节点之间使用不同的loss算法。 可选"soft_label"、“l2”或“fsp”。也可自定义loss。具体定义和使用可参考[知识蒸馏API文档](https://paddleslim.readthedocs.io/zh_CN/latest/api_cn/static/dist/single_distiller_api.html)。 +- node: 蒸馏节点,即某层输出的变量名称。该选项设置方式分两种情况: -- 蒸馏loss目前支持的有:fsp,l2,soft_label,也可自定义loss。具体定义和使用可参考[知识蒸馏API文档](https://paddleslim.readthedocs.io/zh_CN/latest/api_cn/static/dist/single_distiller_api.html)。 + - 自蒸馏:教师模型为压缩前的推理模型,学生模型为压缩后的推理模型。‘node’ 可设置为变量名称的列表,ACT会自动在该列表中的变量上依次添加知识蒸馏loss。示例如下: + ``` + node: + - relu_30.tmp_0 + - relu_31.tmp_0 + ``` + 上述示例,会添加两个知识蒸馏loss。第一个loss的输入为教师模型和学生模型的 'relu_30.tmp_0',第二个loss的输入为教师模型和学生模型的'relu_31.tmp_0'。 + - 普通蒸馏:教师模型为任意模型,学生模型为压缩后的推理模型。‘node’ 可设置为变量名称的列表,列表中元素数量必须为偶数。示例如下: + ``` + node: + - teacher_relu_0.tmp_0 + - student_relu_0.tmp_0 + - teacher_relu_1.tmp_0 + - student_relu_1.tmp_0 + ``` -#### 配置定制结构化稀疏策略 + 上述示例,会添加两个知识蒸馏loss。第一个loss的输入为教师模型的变量“teacher_relu_0.tmp_0”和学生模型的变量“student_relu_0.tmp_0”,第二个loss的输入为教师模型的变量“teacher_relu_1.tmp_0”和学生模型的“student_relu_1.tmp_0”。 + + 如果不设置`node`,则分别取教师模型和学生模型的最后一个带参数的层的输出,组成知识蒸馏loss. + +- teacher_model_dir: 用于监督压缩后模型训练的教师模型所在的路径。如果不设置该选项,则使用压缩前的模型做为教师模型。 +- teacher_model_filename: 教师模型的模型文件名称,格式为 *.pdmodel 或 __model__。仅当设置`teacher_model_dir`后生效。 +- teacher_params_filename: 教师模型的参数文件名称,格式为 *.pdiparams 或 __params__。仅当设置`teacher_model_dir`后生效。 + + +### 1.1.3 结构化稀疏(sparsity) 结构化稀疏参数设置如下所示: ```yaml ChannelPrune: - # pruned_ratio: 裁剪比例 pruned_ratio: 0.25 - # prune_params_name: 需要裁剪的参数名字 prune_params_name: - conv1_weights - # criterion: 评估一个卷积层内通道重要性所参考的指标 criterion: l1_norm ``` -- criterion目前支持的有:l1_norm , bn_scale , geometry_median。具体定义和使用可参考[结构化稀疏API文档](https://paddleslim.readthedocs.io/zh_CN/latest/api_cn/static/prune/prune_api.html)。 -#### 配置定制ASP半结构化稀疏策略 +- pruned_ratio: 每个卷积层的通道数被剪裁的比例。 +- prune_params_name: 待剪裁的卷积层的权重名称。通过以下脚本获得推理模型中所有卷积层的权重名称: + +``` +import paddle +paddle.enable_static() +model_dir="./inference_model" +exe = paddle.static.Executor(paddle.CPUPlace()) +[inference_program, feed_target_names, fetch_targets] = ( + paddle.static.load_inference_model(model_dir, exe)) +for var_ in inference_program.list_vars(): + if var_.persistable and "conv2d" in var_.name: + print(f"{var_.name}") +``` + +或者,使用[Netron工具](https://netron.app/) 可视化`*.pdmodel`模型文件,选择合适的卷积层进行剪裁。 + +- criterion: 评估卷积通道重要性的指标。可选 “l1_norm” , “bn_scale” , “geometry_median”。具体定义和使用可参考[结构化稀疏API文档](https://paddleslim.readthedocs.io/zh_CN/latest/api_cn/static/prune/prune_api.html)。 + +### 1.1.4 ASP半结构化稀疏 半结构化稀疏参数设置如下所示: ```yaml ASPPrune: - # prune_params_name: 需要裁剪的参数名字 prune_params_name: - conv1_weights ``` -#### 配置定制针对Transformer结构的结构化剪枝策略 +- prune_params_name: 待剪裁的卷积层的权重名称。通过以下脚本获得推理模型中所有卷积层的权重名称: + +``` +import paddle +paddle.enable_static() +model_dir="./inference_model" +exe = paddle.static.Executor(paddle.CPUPlace()) +[inference_program, feed_target_names, fetch_targets] = ( + paddle.static.load_inference_model(model_dir, exe)) +for var_ in inference_program.list_vars(): + if var_.persistable and "conv2d" in var_.name: + print(f"{var_.name}") +``` + +或者,使用[Netron工具](https://netron.app/) 可视化`*.pdmodel`模型文件,选择合适的卷积层进行剪裁。 + +### 1.1.5 Transformer结构化剪枝 针对Transformer结构的结构化剪枝参数设置如下所示: ```yaml TransformerPrune: - # pruned_ratio: 每个全链接层的裁剪比例 pruned_ratio: 0.25 ``` +- pruned_ratio: 每个全链接层的被剪裁的比例。 -#### 配置定制非结构化稀疏策略 +### 1.1.6 非结构化稀疏策略 非结构化稀疏参数设置如下所示: ```yaml @@ -122,7 +201,7 @@ UnstructurePrune: - local_sparsity 表示剪裁比例(ratio)应用的范围,仅在 'ratio' 模式生效。local_sparsity 开启时意味着每个参与剪裁的参数矩阵稀疏度均为 'ratio', 关闭时表示只保证模型整体稀疏度达到'ratio',但是每个参数矩阵的稀疏度可能存在差异。各个矩阵稀疏度保持一致时,稀疏加速更显著。 - 更多非结构化稀疏的参数含义详见[非结构化稀疏API文档](https://github.com/PaddlePaddle/PaddleSlim/blob/develop/docs/zh_cn/api_cn/dygraph/pruners/unstructured_pruner.rst) -#### 配置训练超参 +### 1.1.7 训练超参 训练参数主要设置学习率、训练次数(epochs)和优化器等。 ```yaml @@ -143,12 +222,69 @@ TrainConfig: boundaries: [4500] # 设置策略参数 values: [0.005, 0.0005] # 设置策略参数 ``` -## 其他参数配置 +## 1.2 FAQ -#### 1.自动蒸馏效果不理想,怎么自主选择蒸馏节点? +### 1.自动蒸馏效果不理想,怎么自主选择蒸馏节点? 首先使用[Netron工具](https://netron.app/) 可视化`model.pdmodel`模型文件,选择模型中某些层输出Tensor名称,对蒸馏节点进行配置。(一般选择Backbone或网络的输出等层进行蒸馏)
+ +### 2.如何获得推理模型中的OP类型 + +执行以下代码获取推理模型中的OP类型,其中`model_dir`为推理模型存储路径。 + +``` +import paddle +paddle.enable_static() +model_dir="./inference_model" +exe = paddle.static.Executor(paddle.CPUPlace()) +inference_program, _, _ = ( + paddle.static.load_inference_model(model_dir, exe)) +op_types = {} +for block in inference_program.blocks: + for op in block.ops: + op_types[op.type] = 1 +print(f"Operators in inference model:\n{op_types.keys()}") +``` + +所用飞桨框架接口: + +- [load_inference_model](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/static/load_inference_model_cn.html#load-inference-model) +- [Program](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/static/Program_cn.html#program) +- [Executor](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/static/Executor_cn.html#executor) + +### 3. 量化支持对哪些OP进行量化 + +执行以下代码,查看当前PaddlePaddle版本的量化功能所支持的OP类型: +``` +from paddle.fluid.contrib.slim.quantization.utils import _weight_supported_quantizable_op_type, _act_supported_quantizable_op_type +print(f"_supported_quantizable_op_type:\n{_weight_supported_quantizable_op_type}") +print(f"_supported_quantizable_op_type:\n{_act_supported_quantizable_op_type}") +``` + +### 4. 如何设置推理模型中OP的‘name_scope’属性 + +以下代码,将输出变量为`conv2d_52.tmp_0`的OP的`name_scope`设置为'skip_quant': +``` +import paddle +paddle.enable_static() +model_dir="./original_model" +exe = paddle.static.Executor(paddle.CPUPlace()) +[inference_program, feed_target_names, fetch_targets] = ( + paddle.static.load_inference_model(model_dir, exe)) +skips = ['conv2d_52.tmp_0'] +for block in inference_program.blocks: + for op in block.ops: + if op.output_arg_names[0] in skips: + op._set_attr("name_scope", "skip_quant") + +feed_vars = [] +for var_ in inference_program.list_vars(): + if var_.name in feed_target_names: + feed_vars.append(var_) +paddle.static.save_inference_model("./infer_model", feed_vars, fetch_targets, exe, program=inference_program) + +``` diff --git a/example/auto_compression/image_classification/README.md b/example/auto_compression/image_classification/README.md index c11e51c2..14c70fbd 100644 --- a/example/auto_compression/image_classification/README.md +++ b/example/auto_compression/image_classification/README.md @@ -21,28 +21,30 @@ ### PaddleClas模型 -| 模型 | 策略 | Top-1 Acc | GPU 耗时(ms) | ARM CPU 耗时(ms) | -|:------:|:------:|:------:|:------:|:------:| -| MobileNetV1 | Baseline | 70.90 | - | 33.15 | -| MobileNetV1 | 量化+蒸馏 | 70.57 | - | 13.64 | -| ResNet50_vd | Baseline | 79.12 | 3.19 | - | -| ResNet50_vd | 量化+蒸馏 | 78.74 | 0.92 | - | -| ShuffleNetV2_x1_0 | Baseline | 68.65 | - | 10.43 | -| ShuffleNetV2_x1_0 | 量化+蒸馏 | 68.32 | - | 5.51 | -| SqueezeNet1_0_infer | Baseline | 59.60 | - | 35.98 | -| SqueezeNet1_0_infer | 量化+蒸馏 | 59.45 | - | 16.96 | -| PPLCNetV2_base | Baseline | 76.86 | - | 36.50 | -| PPLCNetV2_base | 量化+蒸馏 | 76.43 | - | 15.79 | -| PPHGNet_tiny | Baseline | 79.59 | 2.82 | - | -| PPHGNet_tiny | 量化+蒸馏 | 79.20 | 0.98 | - | -| InceptionV3 | Baseline | 79.14 | 4.79 | - | -| InceptionV3 | 量化+蒸馏 | 78.32 | 1.47 | - | -| EfficientNetB0 | Baseline | 77.02 | 1.95 | - | -| EfficientNetB0 | 量化+蒸馏 | 75.39 | 1.44 | - | -| GhostNet_x1_0 | Baseline | 74.02 | 2.93 | - | -| GhostNet_x1_0 | 量化+蒸馏 | 72.62 | 1.03 | - | -| MobileNetV3_large_x1_0 | Baseline | 75.32 | - | 16.62 | -| MobileNetV3_large_x1_0 | 量化+蒸馏 | 70.93 | - | 9.85 | +| 模型 | 策略 | Top-1 Acc | GPU 耗时(ms) | ARM CPU 耗时(ms) | 配置文件 | Inference模型 | +|:------:|:------:|:------:|:------:|:------:|:------:|:------:| +| MobileNetV1 | Baseline | 70.90 | - | 33.15 | - | [Model](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/MobileNetV1_infer.tar) | +| MobileNetV1 | 量化+蒸馏 | 70.57 | - | 13.64 | [Config](./configs/MobileNetV1/qat_dis.yaml) | [Model](https://paddle-slim-models.bj.bcebos.com/act/MobileNetV1_QAT.tar) | +| ResNet50_vd | Baseline | 79.12 | 3.19 | - | - | [Model](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ResNet50_vd_infer.tar) | +| ResNet50_vd | 量化+蒸馏 | 78.74 | 0.92 | - | [Config](./configs/ResNet50_vd/qat_dis.yaml) | [Model](https://paddle-slim-models.bj.bcebos.com/act/ResNet50_vd_QAT.tar) | +| ShuffleNetV2_x1_0 | Baseline | 68.65 | - | 10.43 | - | [Model](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/ShuffleNetV2_x1_0_infer.tar) | +| ShuffleNetV2_x1_0 | 量化+蒸馏 | 68.32 | - | 5.51 | [Config](./configs/ShuffleNetV2_x1_0/qat_dis.yaml) | [Model](https://paddle-slim-models.bj.bcebos.com/act/ShuffleNetV2_x1_0_QAT.tar) | +| SqueezeNet1_0 | Baseline | 59.60 | - | 35.98 | - | [Model](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/SqueezeNet1_0_infer.tar) | +| SqueezeNet1_0 | 量化+蒸馏 | 59.45 | - | 16.96 | [Config](./configs/SqueezeNet1_0/qat_dis.yaml) | [Model](https://paddle-slim-models.bj.bcebos.com/act/SqueezeNet1_0_QAT.tar) | +| PPLCNetV2_base | Baseline | 76.86 | - | 36.50 | - | [Model](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PPLCNetV2_base_infer.tar) | +| PPLCNetV2_base | 量化+蒸馏 | 76.43 | - | 15.79 | [Config](./configs/PPLCNetV2_base/qat_dis.yaml) | [Model](https://paddle-slim-models.bj.bcebos.com/act/PPLCNetV2_base_QAT.tar) | +| PPHGNet_tiny | Baseline | 79.59 | 2.82 | - | - |[Model](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/PPHGNet_tiny_infer.tar) | +| PPHGNet_tiny | 量化+蒸馏 | 79.20 | 0.98 | - | [Config](./configs/PPHGNet_tiny/qat_dis.yaml) | [Model](https://paddle-slim-models.bj.bcebos.com/act/PPHGNet_tiny_QAT.tar) | +| InceptionV3 | Baseline | 79.14 | 4.79 | - | - | [Model](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/InceptionV3_infer.tar) | +| InceptionV3 | 量化+蒸馏 | 78.32 | 1.47 | - | [Config](./configs/InceptionV3/qat_dis.yaml) | [Model](https://paddle-slim-models.bj.bcebos.com/act/InceptionV3_QAT.tar) | +| EfficientNetB0 | Baseline | 77.02 | 1.95 | - | - | [Model](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/EfficientNetB0_infer.tar) | +| EfficientNetB0 | 量化+蒸馏 | 75.39 | 1.44 | - | [Config](./configs/EfficientNetB0/qat_dis.yaml) | [Model](https://paddle-slim-models.bj.bcebos.com/act/EfficientNetB0_QAT.tar) | +| GhostNet_x1_0 | Baseline | 74.02 | 2.93 | - | - | [Model](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/GhostNet_x1_0_infer.tar) | +| GhostNet_x1_0 | 量化+蒸馏 | 72.62 | 1.03 | - | [Config](./configs/GhostNet_x1_0/qat_dis.yaml) | [Model](https://paddle-slim-models.bj.bcebos.com/act/GhostNet_x1_0_QAT.tar) | +| MobileNetV3_large_x1_0 | Baseline | 75.32 | - | 16.62 | - | [Model](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/MobileNetV3_large_x1_0_infer.tar) | +| MobileNetV3_large_x1_0 | 量化+蒸馏 | 74.41 | - | 9.85 | [Config](./configs/MobileNetV3_large_x1_0/qat_dis.yaml) | [Model](https://paddle-slim-models.bj.bcebos.com/act/MobileNetV3_large_x1_0_QAT.tar) | +| MobileNetV3_large_x1_0_ssld | Baseline | 78.96 | - | 16.62 | - | [Model](https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/MobileNetV3_large_x1_0_ssld_infer.tar) | +| MobileNetV3_large_x1_0_ssld | 量化+蒸馏 | 77.17 | - | 9.85 | [Config](./configs/MobileNetV3_large_x1_0/qat_dis.yaml) | [Model](https://paddle-slim-models.bj.bcebos.com/act/MobileNetV3_large_x1_0_ssld_QAT.tar) | - ARM CPU 测试环境:`SDM865(4xA77+4xA55)` - Nvidia GPU 测试环境: diff --git a/example/auto_compression/image_classification/eval.py b/example/auto_compression/image_classification/eval.py index d0e0c3d1..9cd9b4a3 100644 --- a/example/auto_compression/image_classification/eval.py +++ b/example/auto_compression/image_classification/eval.py @@ -23,7 +23,7 @@ import paddle import paddle.nn as nn from paddle.io import DataLoader from imagenet_reader import ImageNetDataset -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config +from paddleslim.common import load_config as load_slim_config def argsparser(): diff --git a/example/auto_compression/image_classification/infer.py b/example/auto_compression/image_classification/infer.py index 5060115c..46eb7115 100644 --- a/example/auto_compression/image_classification/infer.py +++ b/example/auto_compression/image_classification/infer.py @@ -22,7 +22,7 @@ import yaml from utils import preprocess, postprocess import paddle from paddle.inference import create_predictor -from paddleslim.auto_compression.config_helpers import load_config +from paddleslim.common import load_config def argsparser(): diff --git a/example/auto_compression/image_classification/run.py b/example/auto_compression/image_classification/run.py index d8da1a9f..dee25a17 100644 --- a/example/auto_compression/image_classification/run.py +++ b/example/auto_compression/image_classification/run.py @@ -24,7 +24,7 @@ import paddle import paddle.nn as nn from paddle.io import DataLoader from imagenet_reader import ImageNetDataset -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config +from paddleslim.common import load_config as load_slim_config from paddleslim.auto_compression import AutoCompression @@ -46,6 +46,11 @@ def argsparser(): type=int, default=1281167, help="the number of total training images.") + parser.add_argument( + '--devices', + type=str, + default='gpu', + help="which device used to compress.") return parser @@ -122,7 +127,12 @@ def eval_function(exe, compiled_test_program, test_feed_names, test_fetch_list): def main(): rank_id = paddle.distributed.get_rank() - place = paddle.CUDAPlace(rank_id) + if args.devices == 'gpu': + place = paddle.CUDAPlace(rank_id) + paddle.set_device('gpu') + else: + place = paddle.CPUPlace() + paddle.set_device('cpu') global global_config all_config = load_slim_config(args.config_path) diff --git a/example/auto_compression/nlp/run.py b/example/auto_compression/nlp/run.py index 013b5826..e1bf4f25 100644 --- a/example/auto_compression/nlp/run.py +++ b/example/auto_compression/nlp/run.py @@ -15,7 +15,7 @@ from paddlenlp.datasets import load_dataset from paddlenlp.data import Stack, Tuple, Pad from paddlenlp.data.sampler import SamplerHelper from paddlenlp.metrics import Mcc, PearsonAndSpearman -from paddleslim.auto_compression.config_helpers import load_config +from paddleslim.common import load_config from paddleslim.auto_compression.compressor import AutoCompression diff --git a/example/auto_compression/pytorch_huggingface/run.py b/example/auto_compression/pytorch_huggingface/run.py index 4da4e703..0c730dff 100644 --- a/example/auto_compression/pytorch_huggingface/run.py +++ b/example/auto_compression/pytorch_huggingface/run.py @@ -27,7 +27,7 @@ from paddlenlp.transformers import AutoModelForTokenClassification, AutoTokenize from paddlenlp.datasets import load_dataset from paddlenlp.data import Stack, Tuple, Pad from paddlenlp.metrics import AccuracyAndF1, Mcc, PearsonAndSpearman -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config +from paddleslim.common import load_config as load_slim_config from paddleslim.auto_compression.compressor import AutoCompression diff --git a/example/auto_compression/pytorch_yolo_series/README.md b/example/auto_compression/pytorch_yolo_series/README.md new file mode 100644 index 00000000..3f80ad6f --- /dev/null +++ b/example/auto_compression/pytorch_yolo_series/README.md @@ -0,0 +1,170 @@ +# YOLO系列模型自动压缩示例 + +目录: +- [1.简介](#1简介) +- [2.Benchmark](#2Benchmark) +- [3.开始自动压缩](#自动压缩流程) + - [3.1 环境准备](#31-准备环境) + - [3.2 准备数据集](#32-准备数据集) + - [3.3 准备预测模型](#33-准备预测模型) + - [3.4 测试模型精度](#34-测试模型精度) + - [3.5 自动压缩并产出模型](#35-自动压缩并产出模型) +- [4.预测部署](#4预测部署) +- [5.FAQ](5FAQ) + +## 1. 简介 + +本示例将以以[ultralytics/yolov5](https://github.com/ultralytics/yolov5),[meituan/YOLOv6](https://github.com/meituan/YOLOv6) 和 [WongKinYiu/yolov7](https://github.com/WongKinYiu/yolov7) 目标检测模型为例,借助[X2Paddle](https://github.com/PaddlePaddle/X2Paddle)的能力,将PyTorch框架模型转换为Paddle框架模型,再使用ACT自动压缩功能进行模型压缩,压缩后的模型可使用Paddle Inference或者导出至ONNX,利用TensorRT部署。 + +## 2.Benchmark + +| 模型 | 策略 | 输入尺寸 | mAPval
0.5:0.95 | 模型体积 | 预测时延FP32
|预测时延FP16
| 预测时延INT8
| 配置文件 | Inference模型 | +| :-------- |:-------- |:--------: | :--------: | :---------------------: | :----------------: | :----------------: | :---------------: | :-----------------------------: | :-----------------------------: | +| YOLOv5s | Base模型 | 640*640 | 37.4 | 28.1MB | 5.95ms | 2.44ms | - | - | [Model](https://paddle-slim-models.bj.bcebos.com/act/yolov5s.onnx) | +| YOLOv5s | 离线量化 | 640*640 | 36.0 | 7.4MB | - | - | 1.87ms | [config](https://github.com/PaddlePaddle/PaddleSlim/tree/develop/example/post_training_quantization/pytorch_yolo_series) | - | +| YOLOv5s | ACT量化训练 | 640*640 | **36.9** | 7.4MB | - | - | **1.87ms** | [config](./configs/yolov5s_qat_dis.yaml) | [Infer Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov5s_quant.tar) | [ONNX Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov5s_quant.onnx) | +| | | | | | | | | | +| YOLOv6s | Base模型 | 640*640 | 42.4 | 65.9MB | 9.06ms | 2.90ms | - | - | [Model](https://paddle-slim-models.bj.bcebos.com/act/yolov6s.onnx) | +| YOLOv6s | KL离线量化 | 640*640 | 30.3 | 16.8MB | - | - | 1.83ms | [config](https://github.com/PaddlePaddle/PaddleSlim/tree/develop/example/post_training_quantization/pytorch_yolo_series) | - | +| YOLOv6s | 量化蒸馏训练 | 640*640 | **41.3** | 16.8MB | - | - | **1.83ms** | [config](./configs/yolov6s_qat_dis.yaml) | [Infer Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov6s_quant.tar) | [ONNX Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov6s_quant.onnx) | +| | | | | | | | | | +| YOLOv7 | Base模型 | 640*640 | 51.1 | 141MB | 26.84ms | 7.44ms | - | - | [Model](https://paddle-slim-models.bj.bcebos.com/act/yolov7.onnx) | +| YOLOv7 | 离线量化 | 640*640 | 50.2 | 36MB | - | - | 4.55ms | [config](https://github.com/PaddlePaddle/PaddleSlim/tree/develop/example/post_training_quantization/pytorch_yolo_series) | - | +| YOLOv7 | ACT量化训练 | 640*640 | **50.9** | 36MB | - | - | **4.55ms** | [config](./configs/yolov7_qat_dis.yaml) | [Infer Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov7_quant.tar) | [ONNX Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov7_quant.onnx) | +| | | | | | | | | | +| YOLOv7-Tiny | Base模型 | 640*640 | 37.3 | 24MB | 5.06ms | 2.32ms | - | - | [Model](https://paddle-slim-models.bj.bcebos.com/act/yolov7-tiny.onnx) | +| YOLOv7-Tiny | 离线量化 | 640*640 | 35.8 | 6.1MB | - | - | 1.68ms | - | - | +| YOLOv7-Tiny | ACT量化训练 | 640*640 | **37.0** | 6.1MB | - | - | **1.68ms** | [config](./configs/yolov7_tiny_qat_dis.yaml) | [Infer Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov7_tiny_quant.tar) | [ONNX Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov7_tiny_quant.onnx) | + +说明: +- mAP的指标均在COCO val2017数据集中评测得到。 +- YOLOv7模型在Tesla T4的GPU环境下开启TensorRT 8.4.1,batch_size=1, 测试脚本是[cpp_infer](./cpp_infer)。 + +## 3. 自动压缩流程 + +#### 3.1 准备环境 +- PaddlePaddle >= 2.3.2版本 (可从[Paddle官网](https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/linux-pip.html)根据相应环境的安装指令进行安装) +- PaddleSlim develop 版本 + +(1)安装paddlepaddle +``` +# CPU +pip install paddlepaddle==2.3.2 +# GPU +pip install paddlepaddle-gpu==2.3.2 +``` + +(2)安装paddleslim: +```shell +git clone https://github.com/PaddlePaddle/PaddleSlim.git & cd PaddleSlim +python setup.py install +``` + + +#### 3.2 准备数据集 + +本示例默认以COCO数据进行自动压缩实验,可以从[MS COCO官网](https://cocodataset.org)下载[Train](http://images.cocodataset.org/zips/train2017.zip)、[Val](http://images.cocodataset.org/zips/val2017.zip)、[annotation](http://images.cocodataset.org/annotations/annotations_trainval2017.zip)。 + +目录格式如下: +``` +dataset/coco/ +├── annotations +│ ├── instances_train2017.json +│ ├── instances_val2017.json +│ | ... +├── train2017 +│ ├── 000000000009.jpg +│ ├── 000000580008.jpg +│ | ... +├── val2017 +│ ├── 000000000139.jpg +│ ├── 000000000285.jpg +``` + +如果是自定义数据集,请按照如上COCO数据格式准备数据。 + + +#### 3.3 准备预测模型 + +(1)准备ONNX模型: + +- YOLOv5: + + 本示例模型使用[ultralytics/yolov5](https://github.com/ultralytics/yolov5)的master分支导出,要求v6.1之后的ONNX模型,可以根据官方的[导出教程](https://github.com/ultralytics/yolov5/issues/251)来准备ONNX模型。也可以下载准备好的[yolov5s.onnx](https://paddle-slim-models.bj.bcebos.com/act/yolov5s.onnx)。 + ```shell + python export.py --weights yolov5s.pt --include onnx + ``` + +- YOLOv6: + + 可通过[meituan/YOLOv6](https://github.com/meituan/YOLOv6)官方的[导出教程](https://github.com/meituan/YOLOv6/blob/main/deploy/ONNX/README.md)来准备ONNX模型。也可以下载已经准备好的[yolov6s.onnx](https://paddle-slim-models.bj.bcebos.com/act/yolov6s.onnx)。 + +- YOLOv7: 可通过[WongKinYiu/yolov7](https://github.com/WongKinYiu/yolov7)的导出脚本来准备ONNX模型,具体步骤如下: + ```shell + git clone https://github.com/WongKinYiu/yolov7.git + python export.py --weights yolov7-tiny.pt --grid + ``` + + **注意**:目前ACT支持**不带NMS**模型,使用如上命令导出即可。也可以直接下载我们已经准备好的[yolov7.onnx](https://paddle-slim-models.bj.bcebos.com/act/yolov7-tiny.onnx)。 + +#### 3.4 自动压缩并产出模型 + +蒸馏量化自动压缩示例通过run.py脚本启动,会使用接口```paddleslim.auto_compression.AutoCompression```对模型进行自动压缩。配置config文件中模型路径、蒸馏、量化、和训练等部分的参数,配置完成后便可对模型进行量化和蒸馏。 + +本示例启动自动压缩以YOLOv7-Tiny为例,如果想要更换模型,可修改`--config_path`路径即可,具体运行命令为: + +- 单卡训练: +``` +export CUDA_VISIBLE_DEVICES=0 +python run.py --config_path=./configs/yolov7_tiny_qat_dis.yaml --save_dir='./output/' +``` + +- 多卡训练: +``` +CUDA_VISIBLE_DEVICES=0,1,2,3 python -m paddle.distributed.launch --log_dir=log --gpus 0,1,2,3 run.py \ + --config_path=./configs/yolov7_tiny_qat_dis.yaml --save_dir='./output/' +``` + +#### 3.5 测试模型精度 + +修改[yolov7_qat_dis.yaml](./configs/yolov7_qat_dis.yaml)中`model_dir`字段为模型存储路径,然后使用eval.py脚本得到模型的mAP: +``` +export CUDA_VISIBLE_DEVICES=0 +python eval.py --config_path=./configs/yolov7_tiny_qat_dis.yaml +``` + + +## 4.预测部署 + +#### 导出至ONNX使用TensorRT部署 + +执行完自动压缩后会默认在`save_dir`中生成`quant_model.onnx`的ONNX模型文件,可以直接使用TensorRT测试脚本进行验证。 + +- 进行测试: +```shell +python yolov7_onnx_trt.py --model_path=output/quant_model.onnx --image_file=images/000000570688.jpg --precision=int8 +``` + +#### Paddle-TensorRT部署 +- C++部署 + +进入[cpp_infer](./cpp_infer)文件夹内,请按照[C++ TensorRT Benchmark测试教程](./cpp_infer/README.md)进行准备环境及编译,然后开始测试: +```shell +# 编译 +bash complie.sh +# 执行 +./build/trt_run --model_file yolov7_quant/model.pdmodel --params_file yolov7_quant/model.pdiparams --run_mode=trt_int8 +``` + +- Python部署: + +首先安装带有TensorRT的[Paddle安装包](https://www.paddlepaddle.org.cn/inference/v2.3/user_guides/download_lib.html#python)。 + +然后使用[paddle_trt_infer.py](./paddle_trt_infer.py)进行部署: +```shell +python paddle_trt_infer.py --model_path=output --image_file=images/000000570688.jpg --benchmark=True --run_mode=trt_int8 +``` + +## 5.FAQ + +- 如果想对模型进行离线量化,可进入[YOLO系列模型离线量化示例](https://github.com/PaddlePaddle/PaddleSlim/tree/develop/example/post_training_quantization/pytorch_yolo_series)中进行实验。 diff --git a/example/auto_compression/pytorch_yolov5/configs/yolov5s_qat_dis.yaml b/example/auto_compression/pytorch_yolo_series/configs/yolov5s_qat_dis.yaml similarity index 60% rename from example/auto_compression/pytorch_yolov5/configs/yolov5s_qat_dis.yaml rename to example/auto_compression/pytorch_yolo_series/configs/yolov5s_qat_dis.yaml index ef9bf8b7..d5c853be 100644 --- a/example/auto_compression/pytorch_yolov5/configs/yolov5s_qat_dis.yaml +++ b/example/auto_compression/pytorch_yolo_series/configs/yolov5s_qat_dis.yaml @@ -1,18 +1,19 @@ - Global: - reader_config: configs/yolov5_reader.yml - input_list: {'image': 'x2paddle_images'} + model_dir: ./yolov5s.onnx + dataset_dir: dataset/coco/ + train_image_dir: train2017 + val_image_dir: val2017 + train_anno_path: annotations/instances_train2017.json + val_anno_path: annotations/instances_val2017.json Evaluation: True - arch: 'YOLOv5' - model_dir: ./yolov5s_infer - model_filename: model.pdmodel - params_filename: model.pdiparams + arch: YOLOv5 Distillation: alpha: 1.0 loss: soft_label Quantization: + onnx_format: true use_pact: true activation_quantize_type: 'moving_average_abs_max' quantize_op_types: diff --git a/example/auto_compression/pytorch_yolov6/configs/yolov6s_qat_dis.yaml b/example/auto_compression/pytorch_yolo_series/configs/yolov6s_qat_dis.yaml similarity index 62% rename from example/auto_compression/pytorch_yolov6/configs/yolov6s_qat_dis.yaml rename to example/auto_compression/pytorch_yolo_series/configs/yolov6s_qat_dis.yaml index 4fcf4777..e14a6b65 100644 --- a/example/auto_compression/pytorch_yolov6/configs/yolov6s_qat_dis.yaml +++ b/example/auto_compression/pytorch_yolo_series/configs/yolov6s_qat_dis.yaml @@ -1,18 +1,19 @@ - Global: - reader_config: configs/yolov6_reader.yml - input_list: {'image': 'x2paddle_image_arrays'} + model_dir: ./yolov6s.onnx + dataset_dir: dataset/coco/ + train_image_dir: train2017 + val_image_dir: val2017 + train_anno_path: annotations/instances_train2017.json + val_anno_path: annotations/instances_val2017.json Evaluation: True - arch: 'YOLOv6' - model_dir: ./yolov6s_infer - model_filename: model.pdmodel - params_filename: model.pdiparams + arch: YOLOv6 Distillation: alpha: 1.0 loss: soft_label Quantization: + onnx_format: true activation_quantize_type: 'moving_average_abs_max' quantize_op_types: - conv2d diff --git a/example/auto_compression/pytorch_yolov7/configs/yolov7_qat_dis.yaml b/example/auto_compression/pytorch_yolo_series/configs/yolov7_qat_dis.yaml similarity index 56% rename from example/auto_compression/pytorch_yolov7/configs/yolov7_qat_dis.yaml rename to example/auto_compression/pytorch_yolo_series/configs/yolov7_qat_dis.yaml index 6607e361..437ceea9 100644 --- a/example/auto_compression/pytorch_yolov7/configs/yolov7_qat_dis.yaml +++ b/example/auto_compression/pytorch_yolo_series/configs/yolov7_qat_dis.yaml @@ -1,26 +1,28 @@ - Global: - reader_config: configs/yolov7_reader.yaml - input_list: {'image': 'x2paddle_images'} + model_dir: ./yolov7.onnx + dataset_dir: dataset/coco/ + train_image_dir: train2017 + val_image_dir: val2017 + train_anno_path: annotations/instances_train2017.json + val_anno_path: annotations/instances_val2017.json Evaluation: True - model_dir: ./yolov7_infer - model_filename: model.pdmodel - params_filename: model.pdiparams + arch: YOLOv7 Distillation: alpha: 1.0 loss: soft_label Quantization: + onnx_format: true activation_quantize_type: 'moving_average_abs_max' quantize_op_types: - conv2d - depthwise_conv2d TrainConfig: - train_iter: 8000 + train_iter: 5000 eval_iter: 1000 - learning_rate: + learning_rate: type: CosineAnnealingDecay learning_rate: 0.00003 T_max: 8000 diff --git a/example/auto_compression/pytorch_yolo_series/configs/yolov7_tiny_qat_dis.yaml b/example/auto_compression/pytorch_yolo_series/configs/yolov7_tiny_qat_dis.yaml new file mode 100644 index 00000000..958182f6 --- /dev/null +++ b/example/auto_compression/pytorch_yolo_series/configs/yolov7_tiny_qat_dis.yaml @@ -0,0 +1,32 @@ +Global: + model_dir: ./yolov7-tiny.onnx + dataset_dir: dataset/coco/ + train_image_dir: train2017 + val_image_dir: val2017 + train_anno_path: annotations/instances_train2017.json + val_anno_path: annotations/instances_val2017.json + Evaluation: True + arch: YOLOv7 + +Distillation: + alpha: 1.0 + loss: soft_label + +Quantization: + onnx_format: true + activation_quantize_type: 'moving_average_abs_max' + quantize_op_types: + - conv2d + - depthwise_conv2d + +TrainConfig: + train_iter: 5000 + eval_iter: 1000 + learning_rate: + type: CosineAnnealingDecay + learning_rate: 0.00003 + T_max: 8000 + optimizer_builder: + optimizer: + type: SGD + weight_decay: 0.00004 diff --git a/example/auto_compression/pytorch_yolov5/cpp_infer/CMakeLists.txt b/example/auto_compression/pytorch_yolo_series/cpp_infer/CMakeLists.txt similarity index 100% rename from example/auto_compression/pytorch_yolov5/cpp_infer/CMakeLists.txt rename to example/auto_compression/pytorch_yolo_series/cpp_infer/CMakeLists.txt diff --git a/example/auto_compression/pytorch_yolov5/cpp_infer/README.md b/example/auto_compression/pytorch_yolo_series/cpp_infer/README.md similarity index 64% rename from example/auto_compression/pytorch_yolov5/cpp_infer/README.md rename to example/auto_compression/pytorch_yolo_series/cpp_infer/README.md index 9566728a..0286c26d 100644 --- a/example/auto_compression/pytorch_yolov5/cpp_infer/README.md +++ b/example/auto_compression/pytorch_yolo_series/cpp_infer/README.md @@ -1,4 +1,4 @@ -# YOLOv5 TensorRT Benchmark测试(Linux) +# YOLOv7 TensorRT Benchmark测试(Linux) ## 环境准备 @@ -22,21 +22,37 @@ CUDA_LIB=/usr/local/cuda/lib64 TENSORRT_ROOT=/root/auto_compress/trt/trt8.4/ ``` -## Paddle TensorRT测试 +## Paddle tensorRT测试 -- FP32 +- YOLOv5 ``` +# FP32 ./build/trt_run --model_file yolov5s_infer/model.pdmodel --params_file yolov5s_infer/model.pdiparams --run_mode=trt_fp32 +# FP16 +./build/trt_run --model_file yolov5s_infer/model.pdmodel --params_file yolov5s_infer/model.pdiparams --run_mode=trt_fp16 +# INT8 +./build/trt_run --model_file yolov5s_quant/model.pdmodel --params_file yolov5s_quant/model.pdiparams --run_mode=trt_int8 ``` -- FP16 +- YOLOv6 ``` -./build/trt_run --model_file yolov5s_infer/model.pdmodel --params_file yolov5s_infer/model.pdiparams --run_mode=trt_fp16 +# FP32 +./build/trt_run --arch=YOLOv6 --model_file yolov6s_infer/model.pdmodel --params_file yolov6s_infer/model.pdiparams --run_mode=trt_fp32 +# FP16 +./build/trt_run --arch=YOLOv6 --model_file yolov6s_infer/model.pdmodel --params_file yolov6s_infer/model.pdiparams --run_mode=trt_fp16 +# INT8 +./build/trt_run --arch=YOLOv6 --model_file yolov6s_quant/model.pdmodel --params_file yolov6s_quant/model.pdiparams --run_mode=trt_int8 ``` -- INT8 + +- YOLOv7 ``` -./build/trt_run --model_file yolov5s_quant/model.pdmodel --params_file yolov5s_quant/model.pdiparams --run_mode=trt_int8 +# FP32 +./build/trt_run --model_file yolov7_infer/model.pdmodel --params_file yolov7_infer/model.pdiparams --run_mode=trt_fp32 +# FP16 +./build/trt_run --model_file yolov7_infer/model.pdmodel --params_file yolov7_infer/model.pdiparams --run_mode=trt_fp16 +# INT8 +./build/trt_run --model_file yolov7_quant/model.pdmodel --params_file yolov7_quant/model.pdiparams --run_mode=trt_int8 ``` ## 原生TensorRT测试 @@ -49,6 +65,7 @@ trtexec --onnx=yolov5s.onnx --workspace=1024 --avgRuns=1000 --inputIOFormats=fp1 # INT8 trtexec --onnx=yolov5s.onnx --workspace=1024 --avgRuns=1000 --inputIOFormats=fp16:chw --outputIOFormats=fp16:chw --int8 ``` +- 注:可把--onnx=yolov5s.onnx替换成yolov6s.onnx和yolov7.onnx模型 ## 性能对比 @@ -56,6 +73,12 @@ trtexec --onnx=yolov5s.onnx --workspace=1024 --avgRuns=1000 --inputIOFormats=fp1 | :--------: | :--------: |:-------- |:--------: | :---------------------: | | Paddle TensorRT | yolov5s | 5.95ms | 2.44ms | 1.87ms | | TensorRT | yolov5s | 6.16ms | 2.58ms | 2.07ms | +| | | | | | +| Paddle TensorRT | YOLOv6s | 9.06ms | 2.90ms | 1.83ms | +| TensorRT | YOLOv6s | 8.59ms | 2.83ms | 1.87ms | +| | | | | | +| Paddle TensorRT | YOLOv7 | 26.84ms | 7.44ms | 4.55ms | +| TensorRT | YOLOv7 | 28.25ms | 7.23ms | 4.67ms | 环境: - Tesla T4,TensorRT 8.4.1,CUDA 11.2 diff --git a/example/auto_compression/pytorch_yolov5/cpp_infer/compile.sh b/example/auto_compression/pytorch_yolo_series/cpp_infer/compile.sh similarity index 100% rename from example/auto_compression/pytorch_yolov5/cpp_infer/compile.sh rename to example/auto_compression/pytorch_yolo_series/cpp_infer/compile.sh diff --git a/example/auto_compression/pytorch_yolov7/cpp_infer/trt_run.cc b/example/auto_compression/pytorch_yolo_series/cpp_infer/trt_run.cc similarity index 93% rename from example/auto_compression/pytorch_yolov7/cpp_infer/trt_run.cc rename to example/auto_compression/pytorch_yolo_series/cpp_infer/trt_run.cc index 0ae055ac..22095b39 100644 --- a/example/auto_compression/pytorch_yolov7/cpp_infer/trt_run.cc +++ b/example/auto_compression/pytorch_yolo_series/cpp_infer/trt_run.cc @@ -19,6 +19,7 @@ using phi::dtype::float16; DEFINE_string(model_dir, "", "Directory of the inference model."); DEFINE_string(model_file, "", "Path of the inference model file."); DEFINE_string(params_file, "", "Path of the inference params file."); +DEFINE_string(arch, "YOLOv5", "Architectures name, can be: YOLOv5, YOLOv6, YOLOv7."); DEFINE_string(run_mode, "trt_fp32", "run_mode which can be: trt_fp32, trt_fp16 and trt_int8"); DEFINE_int32(batch_size, 1, "Batch size."); DEFINE_int32(gpu_id, 0, "GPU card ID num."); @@ -106,11 +107,15 @@ int main(int argc, char *argv[]) { using dtype = float16; std::vector input_data(FLAGS_batch_size * 3 * 640 * 640, dtype(1.0)); + int out_box_shape = 25200; + if (FLAGS_arch == "YOLOv6"){ + out_box_shape = 8400; + } dtype *out_data; - int out_data_size = FLAGS_batch_size * 25200 * 85; + int out_data_size = FLAGS_batch_size * out_box_shape * 85; cudaHostAlloc((void**)&out_data, sizeof(float) * out_data_size, cudaHostAllocMapped); - std::vector out_shape{ FLAGS_batch_size, 1, 25200, 85}; + std::vector out_shape{ FLAGS_batch_size, 1, out_box_shape, 85}; run(predictor.get(), input_data, input_shape, out_data, out_shape); return 0; } diff --git a/example/auto_compression/pytorch_yolo_series/dataset.py b/example/auto_compression/pytorch_yolo_series/dataset.py new file mode 100644 index 00000000..0250b936 --- /dev/null +++ b/example/auto_compression/pytorch_yolo_series/dataset.py @@ -0,0 +1,115 @@ +from pycocotools.coco import COCO +import cv2 +import os +import numpy as np +import paddle + + +class COCOValDataset(paddle.io.Dataset): + def __init__(self, + dataset_dir=None, + image_dir=None, + anno_path=None, + img_size=[640, 640], + input_name='x2paddle_images'): + self.dataset_dir = dataset_dir + self.image_dir = image_dir + self.img_size = img_size + self.input_name = input_name + self.ann_file = os.path.join(dataset_dir, anno_path) + self.coco = COCO(self.ann_file) + ori_ids = list(sorted(self.coco.imgs.keys())) + # check gt bbox + clean_ids = [] + for idx in ori_ids: + ins_anno_ids = self.coco.getAnnIds(imgIds=[idx], iscrowd=False) + instances = self.coco.loadAnns(ins_anno_ids) + num_bbox = 0 + for inst in instances: + if inst.get('ignore', False): + continue + if 'bbox' not in inst.keys(): + continue + elif not any(np.array(inst['bbox'])): + continue + else: + num_bbox += 1 + if num_bbox > 0: + clean_ids.append(idx) + self.ids = clean_ids + + def __getitem__(self, idx): + img_id = self.ids[idx] + img = self._get_img_data_from_img_id(img_id) + img, scale_factor = self.image_preprocess(img, self.img_size) + return { + 'image': img, + 'im_id': np.array([img_id]), + 'scale_factor': scale_factor + } + + def __len__(self): + return len(self.ids) + + def _get_img_data_from_img_id(self, img_id): + img_info = self.coco.loadImgs(img_id)[0] + img_path = os.path.join(self.dataset_dir, self.image_dir, + img_info['file_name']) + img = cv2.imread(img_path) + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + return img + + def _generate_scale(self, im, target_shape, keep_ratio=True): + """ + Args: + im (np.ndarray): image (np.ndarray) + Returns: + im_scale_x: the resize ratio of X + im_scale_y: the resize ratio of Y + """ + origin_shape = im.shape[:2] + if keep_ratio: + im_size_min = np.min(origin_shape) + im_size_max = np.max(origin_shape) + target_size_min = np.min(target_shape) + target_size_max = np.max(target_shape) + im_scale = float(target_size_min) / float(im_size_min) + if np.round(im_scale * im_size_max) > target_size_max: + im_scale = float(target_size_max) / float(im_size_max) + im_scale_x = im_scale + im_scale_y = im_scale + else: + resize_h, resize_w = target_shape + im_scale_y = resize_h / float(origin_shape[0]) + im_scale_x = resize_w / float(origin_shape[1]) + return im_scale_y, im_scale_x + + def image_preprocess(self, img, target_shape): + # Resize image + im_scale_y, im_scale_x = self._generate_scale(img, target_shape) + img = cv2.resize( + img, + None, + None, + fx=im_scale_x, + fy=im_scale_y, + interpolation=cv2.INTER_LINEAR) + # Pad + im_h, im_w = img.shape[:2] + h, w = target_shape[:] + if h != im_h or w != im_w: + canvas = np.ones((h, w, 3), dtype=np.float32) + canvas *= np.array([114.0, 114.0, 114.0], dtype=np.float32) + canvas[0:im_h, 0:im_w, :] = img.astype(np.float32) + img = canvas + img = np.transpose(img / 255, [2, 0, 1]) + scale_factor = np.array([im_scale_y, im_scale_x]) + return img.astype(np.float32), scale_factor + + +class COCOTrainDataset(COCOValDataset): + def __getitem__(self, idx): + img_id = self.ids[idx] + img = self._get_img_data_from_img_id(img_id) + img, scale_factor = self.image_preprocess(img, self.img_size) + return {self.input_name: img} diff --git a/example/auto_compression/pytorch_yolo_series/eval.py b/example/auto_compression/pytorch_yolo_series/eval.py new file mode 100644 index 00000000..de11989e --- /dev/null +++ b/example/auto_compression/pytorch_yolo_series/eval.py @@ -0,0 +1,102 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import numpy as np +import argparse +from tqdm import tqdm +import paddle +from paddleslim.common import load_config +from paddleslim.common import load_inference_model +from post_process import YOLOPostProcess, coco_metric +from dataset import COCOValDataset + + +def argsparser(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--config_path', + type=str, + default=None, + help="path of compression strategy config.", + required=True) + parser.add_argument( + '--batch_size', type=int, default=1, help="Batch size of model input.") + parser.add_argument( + '--devices', + type=str, + default='gpu', + help="which device used to compress.") + + return parser + + +def eval(): + + place = paddle.CUDAPlace(0) if FLAGS.devices == 'gpu' else paddle.CPUPlace() + exe = paddle.static.Executor(place) + + val_program, feed_target_names, fetch_targets = load_inference_model( + global_config["model_dir"], exe) + + bboxes_list, bbox_nums_list, image_id_list = [], [], [] + with tqdm( + total=len(val_loader), + bar_format='Evaluation stage, Run batch:|{bar}| {n_fmt}/{total_fmt}', + ncols=80) as t: + for data in val_loader: + data_all = {k: np.array(v) for k, v in data.items()} + outs = exe.run(val_program, + feed={feed_target_names[0]: data_all['image']}, + fetch_list=fetch_targets, + return_numpy=False) + postprocess = YOLOPostProcess( + score_threshold=0.001, nms_threshold=0.65, multi_label=True) + res = postprocess(np.array(outs[0]), data_all['scale_factor']) + bboxes_list.append(res['bbox']) + bbox_nums_list.append(res['bbox_num']) + image_id_list.append(np.array(data_all['im_id'])) + t.update() + + coco_metric(anno_file, bboxes_list, bbox_nums_list, image_id_list) + + +def main(): + global global_config + all_config = load_config(FLAGS.config_path) + global_config = all_config["Global"] + + global val_loader + dataset = COCOValDataset( + dataset_dir=global_config['dataset_dir'], + image_dir=global_config['val_image_dir'], + anno_path=global_config['val_anno_path']) + global anno_file + anno_file = dataset.ann_file + val_loader = paddle.io.DataLoader( + dataset, batch_size=FLAGS.batch_size, drop_last=True) + + eval() + + +if __name__ == '__main__': + paddle.enable_static() + parser = argsparser() + FLAGS = parser.parse_args() + + assert FLAGS.devices in ['cpu', 'gpu', 'xpu', 'npu'] + paddle.set_device(FLAGS.devices) + + main() diff --git a/example/auto_compression/pytorch_yolov5/images/000000570688.jpg b/example/auto_compression/pytorch_yolo_series/images/000000570688.jpg similarity index 100% rename from example/auto_compression/pytorch_yolov5/images/000000570688.jpg rename to example/auto_compression/pytorch_yolo_series/images/000000570688.jpg diff --git a/example/auto_compression/pytorch_yolo_series/onnx_trt_infer.py b/example/auto_compression/pytorch_yolo_series/onnx_trt_infer.py new file mode 100644 index 00000000..3540c33d --- /dev/null +++ b/example/auto_compression/pytorch_yolo_series/onnx_trt_infer.py @@ -0,0 +1,378 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +import cv2 +import tensorrt as trt +import pycuda.driver as cuda +import pycuda.autoinit +import os +import time +import random +import argparse + +EXPLICIT_BATCH = 1 << (int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH) +EXPLICIT_PRECISION = 1 << ( + int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_PRECISION) + +# load coco labels +CLASS_LABEL = [ + "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", + "truck", "boat", "traffic light", "fire hydrant", "stop sign", + "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", + "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", + "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite", + "baseball bat", "baseball glove", "skateboard", "surfboard", + "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", + "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", + "hot dog", "pizza", "donut", "cake", "chair", "couch", "potted plant", + "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", + "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", + "refrigerator", "book", "clock", "vase", "scissors", "teddy bear", + "hair drier", "toothbrush" +] + + +def preprocess(image, input_size, mean=None, std=None, swap=(2, 0, 1)): + if len(image.shape) == 3: + padded_img = np.ones((input_size[0], input_size[1], 3)) * 114.0 + else: + padded_img = np.ones(input_size) * 114.0 + img = np.array(image) + r = min(input_size[0] / img.shape[0], input_size[1] / img.shape[1]) + resized_img = cv2.resize( + img, + (int(img.shape[1] * r), int(img.shape[0] * r)), + interpolation=cv2.INTER_LINEAR, ).astype(np.float32) + padded_img[:int(img.shape[0] * r), :int(img.shape[1] * r)] = resized_img + + padded_img = padded_img[:, :, ::-1] + padded_img /= 255.0 + if mean is not None: + padded_img -= mean + if std is not None: + padded_img /= std + padded_img = padded_img.transpose(swap) + padded_img = np.ascontiguousarray(padded_img, dtype=np.float32) + return padded_img, r + + +def postprocess(predictions, ratio): + boxes = predictions[:, :4] + scores = predictions[:, 4:5] * predictions[:, 5:] + boxes_xyxy = np.ones_like(boxes) + boxes_xyxy[:, 0] = boxes[:, 0] - boxes[:, 2] / 2. + boxes_xyxy[:, 1] = boxes[:, 1] - boxes[:, 3] / 2. + boxes_xyxy[:, 2] = boxes[:, 0] + boxes[:, 2] / 2. + boxes_xyxy[:, 3] = boxes[:, 1] + boxes[:, 3] / 2. + boxes_xyxy /= ratio + dets = multiclass_nms(boxes_xyxy, scores, nms_thr=0.45, score_thr=0.1) + return dets + + +def nms(boxes, scores, nms_thr): + """Single class NMS implemented in Numpy.""" + x1 = boxes[:, 0] + y1 = boxes[:, 1] + x2 = boxes[:, 2] + y2 = boxes[:, 3] + + areas = (x2 - x1 + 1) * (y2 - y1 + 1) + order = scores.argsort()[::-1] + + keep = [] + while order.size > 0: + i = order[0] + keep.append(i) + xx1 = np.maximum(x1[i], x1[order[1:]]) + yy1 = np.maximum(y1[i], y1[order[1:]]) + xx2 = np.minimum(x2[i], x2[order[1:]]) + yy2 = np.minimum(y2[i], y2[order[1:]]) + + w = np.maximum(0.0, xx2 - xx1 + 1) + h = np.maximum(0.0, yy2 - yy1 + 1) + inter = w * h + ovr = inter / (areas[i] + areas[order[1:]] - inter) + + inds = np.where(ovr <= nms_thr)[0] + order = order[inds + 1] + + return keep + + +def multiclass_nms(boxes, scores, nms_thr, score_thr): + """Multiclass NMS implemented in Numpy""" + final_dets = [] + num_classes = scores.shape[1] + for cls_ind in range(num_classes): + cls_scores = scores[:, cls_ind] + valid_score_mask = cls_scores > score_thr + if valid_score_mask.sum() == 0: + continue + else: + valid_scores = cls_scores[valid_score_mask] + valid_boxes = boxes[valid_score_mask] + keep = nms(valid_boxes, valid_scores, nms_thr) + if len(keep) > 0: + cls_inds = np.ones((len(keep), 1)) * cls_ind + dets = np.concatenate( + [valid_boxes[keep], valid_scores[keep, None], cls_inds], 1) + final_dets.append(dets) + if len(final_dets) == 0: + return None + return np.concatenate(final_dets, 0) + + +def get_color_map_list(num_classes): + color_map = num_classes * [0, 0, 0] + for i in range(0, num_classes): + j = 0 + lab = i + while lab: + color_map[i * 3] |= (((lab >> 0) & 1) << (7 - j)) + color_map[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j)) + color_map[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j)) + j += 1 + lab >>= 3 + color_map = [color_map[i:i + 3] for i in range(0, len(color_map), 3)] + return color_map + + +def draw_box(img, boxes, scores, cls_ids, conf=0.5, class_names=None): + color_list = get_color_map_list(len(class_names)) + for i in range(len(boxes)): + box = boxes[i] + cls_id = int(cls_ids[i]) + color = tuple(color_list[cls_id]) + score = scores[i] + if score < conf: + continue + x0 = int(box[0]) + y0 = int(box[1]) + x1 = int(box[2]) + y1 = int(box[3]) + + text = '{}:{:.1f}%'.format(class_names[cls_id], score * 100) + font = cv2.FONT_HERSHEY_SIMPLEX + + txt_size = cv2.getTextSize(text, font, 0.4, 1)[0] + cv2.rectangle(img, (x0, y0), (x1, y1), color, 2) + cv2.rectangle(img, (x0, y0 + 1), + (x0 + txt_size[0] + 1, y0 + int(1.5 * txt_size[1])), + color, -1) + cv2.putText( + img, + text, (x0, y0 + txt_size[1]), + font, + 0.8, (0, 255, 0), + thickness=2) + + return img + + +def get_engine(precision, model_file_path): + # TRT_LOGGER = trt.Logger(trt.Logger.VERBOSE) + TRT_LOGGER = trt.Logger() + builder = trt.Builder(TRT_LOGGER) + config = builder.create_builder_config() + if precision == 'int8': + network = builder.create_network(EXPLICIT_BATCH | EXPLICIT_PRECISION) + else: + network = builder.create_network(EXPLICIT_BATCH) + parser = trt.OnnxParser(network, TRT_LOGGER) + + runtime = trt.Runtime(TRT_LOGGER) + if model_file_path.endswith('.trt'): + # If a serialized engine exists, use it instead of building an engine. + print("Reading engine from file {}".format(model_file_path)) + with open(model_file_path, + "rb") as f, trt.Runtime(TRT_LOGGER) as runtime: + engine = runtime.deserialize_cuda_engine(f.read()) + for i in range(network.num_layers): + layer = network.get_layer(i) + print(i, layer.name) + return engine + else: + config.max_workspace_size = 1 << 30 + + if precision == "fp16": + if not builder.platform_has_fast_fp16: + print("FP16 is not supported natively on this platform/device") + else: + config.set_flag(trt.BuilderFlag.FP16) + elif precision == "int8": + if not builder.platform_has_fast_int8: + print("INT8 is not supported natively on this platform/device") + else: + if builder.platform_has_fast_fp16: + # Also enable fp16, as some layers may be even more efficient in fp16 than int8 + config.set_flag(trt.BuilderFlag.FP16) + config.set_flag(trt.BuilderFlag.INT8) + + builder.max_batch_size = 1 + print('Loading ONNX file from path {}...'.format(model_file_path)) + with open(model_file_path, 'rb') as model: + print('Beginning ONNX file parsing') + if not parser.parse(model.read()): + print('ERROR: Failed to parse the ONNX file.') + for error in range(parser.num_errors): + print(parser.get_error(error)) + return None + + print('Completed parsing of ONNX file') + print('Building an engine from file {}; this may take a while...'. + format(model_file_path)) + plan = builder.build_serialized_network(network, config) + engine = runtime.deserialize_cuda_engine(plan) + print("Completed creating Engine") + with open(model_file_path, "wb") as f: + f.write(engine.serialize()) + for i in range(network.num_layers): + layer = network.get_layer(i) + print(i, layer.name) + return engine + + +# Simple helper data class that's a little nicer to use than a 2-tuple. +class HostDeviceMem(object): + def __init__(self, host_mem, device_mem): + self.host = host_mem + self.device = device_mem + + def __str__(self): + return "Host:\n" + str(self.host) + "\nDevice:\n" + str(self.device) + + def __repr__(self): + return self.__str__() + + +def allocate_buffers(engine): + inputs = [] + outputs = [] + bindings = [] + stream = cuda.Stream() + for binding in engine: + size = trt.volume(engine.get_binding_shape( + binding)) * engine.max_batch_size + dtype = trt.nptype(engine.get_binding_dtype(binding)) + # Allocate host and device buffers + host_mem = cuda.pagelocked_empty(size, dtype) + device_mem = cuda.mem_alloc(host_mem.nbytes) + # Append the device buffer to device bindings. + bindings.append(int(device_mem)) + # Append to the appropriate list. + if engine.binding_is_input(binding): + inputs.append(HostDeviceMem(host_mem, device_mem)) + else: + outputs.append(HostDeviceMem(host_mem, device_mem)) + return inputs, outputs, bindings, stream + + +def run_inference(context, bindings, inputs, outputs, stream): + # Transfer input data to the GPU. + [cuda.memcpy_htod_async(inp.device, inp.host, stream) for inp in inputs] + # Run inference. + context.execute_async_v2(bindings=bindings, stream_handle=stream.handle) + # Transfer predictions back from the GPU. + [cuda.memcpy_dtoh_async(out.host, out.device, stream) for out in outputs] + # Synchronize the stream + stream.synchronize() + # Return only the host outputs. + return [out.host for out in outputs] + + +def main(args): + onnx_model = args.model_path + img_path = args.image_file + num_class = len(CLASS_LABEL) + repeat = 1000 + engine = get_engine(args.precision, onnx_model) + + model_all_names = [] + for idx in range(engine.num_bindings): + is_input = engine.binding_is_input(idx) + name = engine.get_binding_name(idx) + op_type = engine.get_binding_dtype(idx) + model_all_names.append(name) + shape = engine.get_binding_shape(idx) + print('input id:', idx, ' is input: ', is_input, ' binding name:', + name, ' shape:', shape, 'type: ', op_type) + + context = engine.create_execution_context() + print('Allocate buffers ...') + inputs, outputs, bindings, stream = allocate_buffers(engine) + print("TRT set input ...") + + origin_img = cv2.imread(img_path) + input_shape = [args.img_shape, args.img_shape] + input_image, ratio = preprocess(origin_img, input_shape) + + inputs[0].host = np.expand_dims(input_image, axis=0) + + for _ in range(0, 50): + trt_outputs = run_inference( + context, + bindings=bindings, + inputs=inputs, + outputs=outputs, + stream=stream) + + time1 = time.time() + for _ in range(0, repeat): + trt_outputs = run_inference( + context, + bindings=bindings, + inputs=inputs, + outputs=outputs, + stream=stream) + time2 = time.time() + # total time cost(ms) + total_inference_cost = (time2 - time1) * 1000 + print("model path: ", onnx_model, " precision: ", args.precision) + print("In TensorRT, ", + "average latency is : {} ms".format(total_inference_cost / repeat)) + # Do postprocess + output = trt_outputs[0] + predictions = np.reshape(output, (1, -1, int(5 + num_class)))[0] + dets = postprocess(predictions, ratio) + # Draw rectangles and labels on the original image + if dets is not None: + final_boxes, final_scores, final_cls_inds = dets[:, : + 4], dets[:, 4], dets[:, + 5] + origin_img = draw_box( + origin_img, + final_boxes, + final_scores, + final_cls_inds, + conf=0.5, + class_names=CLASS_LABEL) + cv2.imwrite('output.jpg', origin_img) + print('The prediction results are saved in output.jpg.') + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + '--model_path', + type=str, + default="quant_model.onnx", + help="inference model filepath") + parser.add_argument( + '--image_file', type=str, default="bus.jpg", help="image path") + parser.add_argument( + '--precision', type=str, default='fp32', help="support fp32/fp16/int8.") + parser.add_argument('--img_shape', type=int, default=640, help="input_size") + args = parser.parse_args() + main(args) diff --git a/example/auto_compression/pytorch_yolov7/paddle_trt_infer.py b/example/auto_compression/pytorch_yolo_series/paddle_trt_infer.py similarity index 98% rename from example/auto_compression/pytorch_yolov7/paddle_trt_infer.py rename to example/auto_compression/pytorch_yolo_series/paddle_trt_infer.py index fedc9cc1..eacb67fb 100644 --- a/example/auto_compression/pytorch_yolov7/paddle_trt_infer.py +++ b/example/auto_compression/pytorch_yolo_series/paddle_trt_infer.py @@ -244,8 +244,9 @@ def predict_image(predictor, threshold=0.5, arch='YOLOv5'): img, scale_factor = image_preprocess(image_file, image_shape) - inputs = {} - if arch == 'YOLOv5': + if arch == 'YOLOv6': + inputs['x2paddle_image_arrays'] = img + else: inputs['x2paddle_images'] = img input_names = predictor.get_input_names() for i in range(len(input_names)): @@ -306,6 +307,8 @@ if __name__ == '__main__': default='GPU', help="Choose the device you want to run, it can be: CPU/GPU/XPU, default is GPU" ) + parser.add_argument( + '--arch', type=str, default='YOLOv5', help="architectures name.") parser.add_argument('--img_shape', type=int, default=640, help="input_size") args = parser.parse_args() @@ -319,4 +322,5 @@ if __name__ == '__main__': args.image_file, image_shape=[args.img_shape, args.img_shape], warmup=warmup, - repeats=repeats) + repeats=repeats, + arch=args.arch) diff --git a/example/auto_compression/pytorch_yolov6/post_process.py b/example/auto_compression/pytorch_yolo_series/post_process.py similarity index 75% rename from example/auto_compression/pytorch_yolov6/post_process.py rename to example/auto_compression/pytorch_yolo_series/post_process.py index 37bd2c95..644c24b8 100644 --- a/example/auto_compression/pytorch_yolov6/post_process.py +++ b/example/auto_compression/pytorch_yolo_series/post_process.py @@ -14,6 +14,8 @@ import numpy as np import cv2 +import json +import sys def box_area(boxes): @@ -68,9 +70,9 @@ def nms(boxes, scores, iou_threshold): return keep -class YOLOv6PostProcess(object): +class YOLOPostProcess(object): """ - Post process of YOLOv6 network. + Post process of YOLO-series network. args: score_threshold(float): Threshold to filter out bounding boxes with low confidence score. If not provided, consider all boxes. @@ -157,8 +159,8 @@ class YOLOv6PostProcess(object): if len(pred.shape) == 1: pred = pred[np.newaxis, :] pred_bboxes = pred[:, :4] - scale_factor = np.tile(scale_factor[i][::-1], (1, 2)) - pred_bboxes /= scale_factor + scale = np.tile(scale_factor[i][::-1], (2)) + pred_bboxes /= scale bbox = np.concatenate( [ pred[:, -1][:, np.newaxis], pred[:, -2][:, np.newaxis], @@ -171,3 +173,59 @@ class YOLOv6PostProcess(object): bboxs = np.concatenate(bboxs, axis=0) box_nums = np.array(box_nums) return {'bbox': bboxs, 'bbox_num': box_nums} + + +def coco_metric(anno_file, bboxes_list, bbox_nums_list, image_id_list): + try: + from pycocotools.coco import COCO + from pycocotools.cocoeval import COCOeval + except: + print( + "[ERROR] Not found pycocotools, please install by `pip install pycocotools`" + ) + sys.exit(1) + + coco_gt = COCO(anno_file) + cats = coco_gt.loadCats(coco_gt.getCatIds()) + clsid2catid = {i: cat['id'] for i, cat in enumerate(cats)} + results = [] + for bboxes, bbox_nums, image_id in zip(bboxes_list, bbox_nums_list, + image_id_list): + results += _get_det_res(bboxes, bbox_nums, image_id, clsid2catid) + + output = "bbox.json" + with open(output, 'w') as f: + json.dump(results, f) + + coco_dt = coco_gt.loadRes(output) + coco_eval = COCOeval(coco_gt, coco_dt, 'bbox') + coco_eval.evaluate() + coco_eval.accumulate() + coco_eval.summarize() + return coco_eval.stats + + +def _get_det_res(bboxes, bbox_nums, image_id, label_to_cat_id_map): + det_res = [] + k = 0 + for i in range(len(bbox_nums)): + cur_image_id = int(image_id[i][0]) + det_nums = bbox_nums[i] + for j in range(det_nums): + dt = bboxes[k] + k = k + 1 + num_id, score, xmin, ymin, xmax, ymax = dt.tolist() + if int(num_id) < 0: + continue + category_id = label_to_cat_id_map[int(num_id)] + w = xmax - xmin + h = ymax - ymin + bbox = [xmin, ymin, w, h] + dt_res = { + 'image_id': cur_image_id, + 'category_id': category_id, + 'bbox': bbox, + 'score': score + } + det_res.append(dt_res) + return det_res diff --git a/example/auto_compression/pytorch_yolo_series/run.py b/example/auto_compression/pytorch_yolo_series/run.py new file mode 100644 index 00000000..1a22d822 --- /dev/null +++ b/example/auto_compression/pytorch_yolo_series/run.py @@ -0,0 +1,127 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import numpy as np +import argparse +from tqdm import tqdm +import paddle +from paddleslim.common import load_config +from paddleslim.auto_compression import AutoCompression +from dataset import COCOValDataset, COCOTrainDataset +from post_process import YOLOPostProcess, coco_metric + + +def argsparser(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--config_path', + type=str, + default=None, + help="path of compression strategy config.", + required=True) + parser.add_argument( + '--save_dir', + type=str, + default='output', + help="directory to save compressed model.") + parser.add_argument( + '--devices', + type=str, + default='gpu', + help="which device used to compress.") + parser.add_argument( + '--eval', type=bool, default=False, help="whether to run evaluation.") + + return parser + + +def eval_function(exe, compiled_test_program, test_feed_names, test_fetch_list): + bboxes_list, bbox_nums_list, image_id_list = [], [], [] + with tqdm( + total=len(val_loader), + bar_format='Evaluation stage, Run batch:|{bar}| {n_fmt}/{total_fmt}', + ncols=80) as t: + for data in val_loader: + data_all = {k: np.array(v) for k, v in data.items()} + outs = exe.run(compiled_test_program, + feed={test_feed_names[0]: data_all['image']}, + fetch_list=test_fetch_list, + return_numpy=False) + res = {} + postprocess = YOLOPostProcess( + score_threshold=0.001, nms_threshold=0.65, multi_label=True) + res = postprocess(np.array(outs[0]), data_all['scale_factor']) + bboxes_list.append(res['bbox']) + bbox_nums_list.append(res['bbox_num']) + image_id_list.append(np.array(data_all['im_id'])) + t.update() + map_res = coco_metric(anno_file, bboxes_list, bbox_nums_list, image_id_list) + return map_res[0] + + +def main(): + global global_config + all_config = load_config(FLAGS.config_path) + assert "Global" in all_config, f"Key 'Global' not found in config file. \n{all_config}" + global_config = all_config["Global"] + input_name = 'x2paddle_image_arrays' if global_config[ + 'arch'] == 'YOLOv6' else 'x2paddle_images' + dataset = COCOTrainDataset( + dataset_dir=global_config['dataset_dir'], + image_dir=global_config['train_image_dir'], + anno_path=global_config['train_anno_path'], + input_name=input_name) + train_loader = paddle.io.DataLoader( + dataset, batch_size=1, shuffle=True, drop_last=True, num_workers=0) + + if 'Evaluation' in global_config.keys() and global_config[ + 'Evaluation'] and paddle.distributed.get_rank() == 0: + eval_func = eval_function + global val_loader + dataset = COCOValDataset( + dataset_dir=global_config['dataset_dir'], + image_dir=global_config['val_image_dir'], + anno_path=global_config['val_anno_path']) + global anno_file + anno_file = dataset.ann_file + val_loader = paddle.io.DataLoader( + dataset, + batch_size=1, + shuffle=False, + drop_last=False, + num_workers=0) + else: + eval_func = None + + ac = AutoCompression( + model_dir=global_config["model_dir"], + train_dataloader=train_loader, + save_dir=FLAGS.save_dir, + config=all_config, + eval_callback=eval_func) + ac.compress() + ac.export_onnx() + + +if __name__ == '__main__': + paddle.enable_static() + parser = argsparser() + FLAGS = parser.parse_args() + + assert FLAGS.devices in ['cpu', 'gpu', 'xpu', 'npu'] + paddle.set_device(FLAGS.devices) + + main() diff --git a/example/auto_compression/pytorch_yolov5/README.md b/example/auto_compression/pytorch_yolov5/README.md deleted file mode 100644 index 16709408..00000000 --- a/example/auto_compression/pytorch_yolov5/README.md +++ /dev/null @@ -1,147 +0,0 @@ -# YOLOv5目标检测模型自动压缩示例 - -目录: -- [1.简介](#1简介) -- [2.Benchmark](#2Benchmark) -- [3.开始自动压缩](#自动压缩流程) - - [3.1 环境准备](#31-准备环境) - - [3.2 准备数据集](#32-准备数据集) - - [3.3 准备预测模型](#33-准备预测模型) - - [3.4 测试模型精度](#34-测试模型精度) - - [3.5 自动压缩并产出模型](#35-自动压缩并产出模型) -- [4.预测部署](#4预测部署) -- [5.FAQ](5FAQ) - -## 1. 简介 - -飞桨模型转换工具[X2Paddle](https://github.com/PaddlePaddle/X2Paddle)支持将```Caffe/TensorFlow/ONNX/PyTorch```的模型一键转为飞桨(PaddlePaddle)的预测模型。借助X2Paddle的能力,各种框架的推理模型可以很方便的使用PaddleSlim的自动化压缩功能。 - -本示例将以[ultralytics/yolov5](https://github.com/ultralytics/yolov5)目标检测模型为例,将PyTorch框架模型转换为Paddle框架模型,再使用ACT自动压缩功能进行自动压缩。本示例使用的自动压缩策略为量化训练。 - -## 2.Benchmark - -| 模型 | 策略 | 输入尺寸 | mAPval
0.5:0.95 | 预测时延FP32
(ms) |预测时延FP16
(ms) | 预测时延INT8
(ms) | 配置文件 | Inference模型 | -| :-------- |:-------- |:--------: | :---------------------: | :----------------: | :----------------: | :---------------: | :-----------------------------: | :-----------------------------: | -| YOLOv5s | Base模型 | 640*640 | 37.4 | 5.95ms | 2.44ms | - | - | [Model](https://bj.bcebos.com/v1/paddle-slim-models/detection/yolov5s_infer.tar) | -| YOLOv5s | KL离线量化 | 640*640 | 36.0 | - | - | 1.87ms | - | - | -| YOLOv5s | 量化蒸馏训练 | 640*640 | **36.9** | - | - | **1.87ms** | [config](./configs/yolov5s_qat_dis.yaml) | [Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov5s_quant.tar) | - - -说明: -- mAP的指标均在COCO val2017数据集中评测得到。 -- YOLOv5s模型在Tesla T4的GPU环境下开启TensorRT 8.4.1,batch_size=1, 测试脚本是[cpp_infer](./cpp_infer)。 - -## 3. 自动压缩流程 - -#### 3.1 准备环境 -- PaddlePaddle >= 2.3 (可从[Paddle官网](https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/linux-pip.html)下载安装) -- PaddleSlim >= 2.3 -- PaddleDet >= 2.4 -- [X2Paddle](https://github.com/PaddlePaddle/X2Paddle) >= 1.3.6 -- opencv-python - -(1)安装paddlepaddle: -```shell -# CPU -pip install paddlepaddle -# GPU -pip install paddlepaddle-gpu -``` - -(2)安装paddleslim: -```shell -pip install paddleslim -``` - -(3)安装paddledet: -```shell -pip install paddledet -``` - -注:安装PaddleDet的目的是为了直接使用PaddleDetection中的Dataloader组件。 - -(4)安装X2Paddle的1.3.6以上版本: -```shell -pip install x2paddle sympy onnx -``` - -#### 3.2 准备数据集 - -本案例默认以COCO数据进行自动压缩实验,并且依赖PaddleDetection中数据读取模块,如果自定义COCO数据,或者其他格式数据,请参考[PaddleDetection数据准备文档](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.4/docs/tutorials/PrepareDataSet.md) 来准备数据。 - -如果已经准备好数据集,请直接修改[./configs/yolov6_reader.yml]中`EvalDataset`的`dataset_dir`字段为自己数据集路径即可。 - -#### 3.3 准备预测模型 - -(1)准备ONNX模型: - -可通过[ultralytics/yolov5](https://github.com/ultralytics/yolov5) 官方的[导出教程](https://github.com/ultralytics/yolov5/issues/251)来准备ONNX模型。也可以下载准备好的[yolov5s.onnx](https://paddle-slim-models.bj.bcebos.com/act/yolov5s.onnx)。 -```shell -python export.py --weights yolov5s.pt --include onnx -``` - -(2) 转换模型: -```shell -x2paddle --framework=onnx --model=yolov5s.onnx --save_dir=pd_model -cp -r pd_model/inference_model/ yolov5s_infer -``` -即可得到YOLOv5s模型的预测模型(`model.pdmodel` 和 `model.pdiparams`)。如想快速体验,可直接下载上方表格中YOLOv5s的[Paddle预测模型](https://bj.bcebos.com/v1/paddle-slim-models/detection/yolov5s_infer.tar)。 - - -预测模型的格式为:`model.pdmodel` 和 `model.pdiparams`两个,带`pdmodel`的是模型文件,带`pdiparams`后缀的是权重文件。 - - -#### 3.4 自动压缩并产出模型 - -蒸馏量化自动压缩示例通过run.py脚本启动,会使用接口```paddleslim.auto_compression.AutoCompression```对模型进行自动压缩。配置config文件中模型路径、蒸馏、量化、和训练等部分的参数,配置完成后便可对模型进行量化和蒸馏。具体运行命令为: - -- 单卡训练: -``` -export CUDA_VISIBLE_DEVICES=0 -python run.py --config_path=./configs/yolov5s_qat_dis.yaml --save_dir='./output/' -``` - -- 多卡训练: -``` -CUDA_VISIBLE_DEVICES=0,1,2,3 python -m paddle.distributed.launch --log_dir=log --gpus 0,1,2,3 run.py \ - --config_path=./configs/yolov5s_qat_dis.yaml --save_dir='./output/' -``` - -#### 3.5 测试模型精度 - -使用eval.py脚本得到模型的mAP: -``` -export CUDA_VISIBLE_DEVICES=0 -python eval.py --config_path=./configs/yolov5s_qat_dis.yaml -``` - -**注意**:如果要测试量化后的模型,模型路径需要在配置文件中`model_dir`字段下进行修改指定。 - - -## 4.预测部署 - -#### Paddle-TensorRT C++部署 - -进入[cpp_infer](./cpp_infer)文件夹内,请按照[C++ TensorRT Benchmark测试教程](./cpp_infer/README.md)进行准备环境及编译,然后开始测试: -```shell -# 编译 -bash complie.sh -# 执行 -./build/trt_run --model_file yolov5s_quant/model.pdmodel --params_file yolov5s_quant/model.pdiparams --run_mode=trt_int8 -``` - -#### Paddle-TensorRT Python部署: - -首先安装带有TensorRT的[Paddle安装包](https://www.paddlepaddle.org.cn/inference/v2.3/user_guides/download_lib.html#python)。 - -然后使用[paddle_trt_infer.py](./paddle_trt_infer.py)进行部署: -```shell -python paddle_trt_infer.py --model_path=output --image_file=images/000000570688.jpg --benchmark=True --run_mode=trt_int8 -``` - -## 5.FAQ - -- 如果想测试离线量化模型精度,可执行: -```shell -python post_quant.py --config_path=./configs/yolov5s_qat_dis.yaml -``` diff --git a/example/auto_compression/pytorch_yolov5/configs/yolov5_reader.yml b/example/auto_compression/pytorch_yolov5/configs/yolov5_reader.yml deleted file mode 100644 index cb87c3f8..00000000 --- a/example/auto_compression/pytorch_yolov5/configs/yolov5_reader.yml +++ /dev/null @@ -1,27 +0,0 @@ -metric: COCO -num_classes: 80 - -# Datset configuration -TrainDataset: - !COCODataSet - image_dir: train2017 - anno_path: annotations/instances_train2017.json - dataset_dir: dataset/coco/ - -EvalDataset: - !COCODataSet - image_dir: val2017 - anno_path: annotations/instances_val2017.json - dataset_dir: dataset/coco/ - -worker_num: 0 - -# preprocess reader in test -EvalReader: - sample_transforms: - - Decode: {} - - Resize: {target_size: [640, 640], keep_ratio: True} - - Pad: {size: [640, 640], fill_value: [114., 114., 114.]} - - NormalizeImage: {mean: [0, 0, 0], std: [1, 1, 1], is_scale: True} - - Permute: {} - batch_size: 1 diff --git a/example/auto_compression/pytorch_yolov5/cpp_infer/trt_run.cc b/example/auto_compression/pytorch_yolov5/cpp_infer/trt_run.cc deleted file mode 100644 index 0ae055ac..00000000 --- a/example/auto_compression/pytorch_yolov5/cpp_infer/trt_run.cc +++ /dev/null @@ -1,116 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -#include "paddle/include/paddle_inference_api.h" -#include "paddle/include/experimental/phi/common/float16.h" - -using paddle_infer::Config; -using paddle_infer::Predictor; -using paddle_infer::CreatePredictor; -using paddle_infer::PrecisionType; -using phi::dtype::float16; - -DEFINE_string(model_dir, "", "Directory of the inference model."); -DEFINE_string(model_file, "", "Path of the inference model file."); -DEFINE_string(params_file, "", "Path of the inference params file."); -DEFINE_string(run_mode, "trt_fp32", "run_mode which can be: trt_fp32, trt_fp16 and trt_int8"); -DEFINE_int32(batch_size, 1, "Batch size."); -DEFINE_int32(gpu_id, 0, "GPU card ID num."); -DEFINE_int32(trt_min_subgraph_size, 3, "tensorrt min_subgraph_size"); -DEFINE_int32(warmup, 50, "warmup"); -DEFINE_int32(repeats, 1000, "repeats"); - -using Time = decltype(std::chrono::high_resolution_clock::now()); -Time time() { return std::chrono::high_resolution_clock::now(); }; -double time_diff(Time t1, Time t2) { - typedef std::chrono::microseconds ms; - auto diff = t2 - t1; - ms counter = std::chrono::duration_cast(diff); - return counter.count() / 1000.0; -} - -std::shared_ptr InitPredictor() { - Config config; - std::string model_path; - if (FLAGS_model_dir != "") { - config.SetModel(FLAGS_model_dir); - model_path = FLAGS_model_dir.substr(0, FLAGS_model_dir.find_last_of("/")); - } else { - config.SetModel(FLAGS_model_file, FLAGS_params_file); - model_path = FLAGS_model_file.substr(0, FLAGS_model_file.find_last_of("/")); - } - // enable tune - std::cout << "model_path: " << model_path << std::endl; - config.EnableUseGpu(256, FLAGS_gpu_id); - if (FLAGS_run_mode == "trt_fp32") { - config.EnableTensorRtEngine(1 << 30, FLAGS_batch_size, FLAGS_trt_min_subgraph_size, - PrecisionType::kFloat32, false, false); - } else if (FLAGS_run_mode == "trt_fp16") { - config.EnableTensorRtEngine(1 << 30, FLAGS_batch_size, FLAGS_trt_min_subgraph_size, - PrecisionType::kHalf, false, false); - } else if (FLAGS_run_mode == "trt_int8") { - config.EnableTensorRtEngine(1 << 30, FLAGS_batch_size, FLAGS_trt_min_subgraph_size, - PrecisionType::kInt8, false, false); - } - config.EnableMemoryOptim(); - config.SwitchIrOptim(true); - return CreatePredictor(config); -} - -template -void run(Predictor *predictor, const std::vector &input, - const std::vector &input_shape, type* out_data, std::vector out_shape) { - - // prepare input - int input_num = std::accumulate(input_shape.begin(), input_shape.end(), 1, - std::multiplies()); - - auto input_names = predictor->GetInputNames(); - auto input_t = predictor->GetInputHandle(input_names[0]); - input_t->Reshape(input_shape); - input_t->CopyFromCpu(input.data()); - - for (int i = 0; i < FLAGS_warmup; ++i) - CHECK(predictor->Run()); - - auto st = time(); - for (int i = 0; i < FLAGS_repeats; ++i) { - auto input_names = predictor->GetInputNames(); - auto input_t = predictor->GetInputHandle(input_names[0]); - input_t->Reshape(input_shape); - input_t->CopyFromCpu(input.data()); - - CHECK(predictor->Run()); - - auto output_names = predictor->GetOutputNames(); - auto output_t = predictor->GetOutputHandle(output_names[0]); - std::vector output_shape = output_t->shape(); - output_t -> ShareExternalData(out_data, out_shape, paddle_infer::PlaceType::kGPU); - } - - LOG(INFO) << "[" << FLAGS_run_mode << " bs-" << FLAGS_batch_size << " ] run avg time is " << time_diff(st, time()) / FLAGS_repeats - << " ms"; -} - -int main(int argc, char *argv[]) { - google::ParseCommandLineFlags(&argc, &argv, true); - auto predictor = InitPredictor(); - std::vector input_shape = {FLAGS_batch_size, 3, 640, 640}; - // float16 - using dtype = float16; - std::vector input_data(FLAGS_batch_size * 3 * 640 * 640, dtype(1.0)); - - dtype *out_data; - int out_data_size = FLAGS_batch_size * 25200 * 85; - cudaHostAlloc((void**)&out_data, sizeof(float) * out_data_size, cudaHostAllocMapped); - - std::vector out_shape{ FLAGS_batch_size, 1, 25200, 85}; - run(predictor.get(), input_data, input_shape, out_data, out_shape); - return 0; -} diff --git a/example/auto_compression/pytorch_yolov5/paddle_trt_infer.py b/example/auto_compression/pytorch_yolov5/paddle_trt_infer.py deleted file mode 100644 index 62c2c89b..00000000 --- a/example/auto_compression/pytorch_yolov5/paddle_trt_infer.py +++ /dev/null @@ -1,322 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import cv2 -import numpy as np -import argparse -import time - -from paddle.inference import Config -from paddle.inference import create_predictor - -from post_process import YOLOv5PostProcess - -CLASS_LABEL = [ - 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', - 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', - 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', - 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', - 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', - 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', - 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', - 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', - 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', - 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', - 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', - 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', - 'hair drier', 'toothbrush' -] - - -def generate_scale(im, target_shape, keep_ratio=True): - """ - Args: - im (np.ndarray): image (np.ndarray) - Returns: - im_scale_x: the resize ratio of X - im_scale_y: the resize ratio of Y - """ - origin_shape = im.shape[:2] - if keep_ratio: - im_size_min = np.min(origin_shape) - im_size_max = np.max(origin_shape) - target_size_min = np.min(target_shape) - target_size_max = np.max(target_shape) - im_scale = float(target_size_min) / float(im_size_min) - if np.round(im_scale * im_size_max) > target_size_max: - im_scale = float(target_size_max) / float(im_size_max) - im_scale_x = im_scale - im_scale_y = im_scale - else: - resize_h, resize_w = target_shape - im_scale_y = resize_h / float(origin_shape[0]) - im_scale_x = resize_w / float(origin_shape[1]) - return im_scale_y, im_scale_x - - -def image_preprocess(img_path, target_shape): - img = cv2.imread(img_path) - # Resize - im_scale_y, im_scale_x = generate_scale(img, target_shape) - img = cv2.resize( - img, - None, - None, - fx=im_scale_x, - fy=im_scale_y, - interpolation=cv2.INTER_LINEAR) - # Pad - im_h, im_w = img.shape[:2] - h, w = target_shape[:] - if h != im_h or w != im_w: - canvas = np.ones((h, w, 3), dtype=np.float32) - canvas *= np.array([114.0, 114.0, 114.0], dtype=np.float32) - canvas[0:im_h, 0:im_w, :] = img.astype(np.float32) - img = canvas - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - img = np.transpose(img, [2, 0, 1]) / 255 - img = np.expand_dims(img, 0) - scale_factor = np.array([[im_scale_y, im_scale_x]]) - return img.astype(np.float32), scale_factor - - -def get_color_map_list(num_classes): - color_map = num_classes * [0, 0, 0] - for i in range(0, num_classes): - j = 0 - lab = i - while lab: - color_map[i * 3] |= (((lab >> 0) & 1) << (7 - j)) - color_map[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j)) - color_map[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j)) - j += 1 - lab >>= 3 - color_map = [color_map[i:i + 3] for i in range(0, len(color_map), 3)] - return color_map - - -def draw_box(image_file, results, class_label, threshold=0.5): - srcimg = cv2.imread(image_file, 1) - for i in range(len(results)): - color_list = get_color_map_list(len(class_label)) - clsid2color = {} - classid, conf = int(results[i, 0]), results[i, 1] - if conf < threshold: - continue - xmin, ymin, xmax, ymax = int(results[i, 2]), int(results[i, 3]), int( - results[i, 4]), int(results[i, 5]) - - if classid not in clsid2color: - clsid2color[classid] = color_list[classid] - color = tuple(clsid2color[classid]) - - cv2.rectangle(srcimg, (xmin, ymin), (xmax, ymax), color, thickness=2) - print(class_label[classid] + ': ' + str(round(conf, 3))) - cv2.putText( - srcimg, - class_label[classid] + ':' + str(round(conf, 3)), (xmin, ymin - 10), - cv2.FONT_HERSHEY_SIMPLEX, - 0.8, (0, 255, 0), - thickness=2) - return srcimg - - -def load_predictor(model_dir, - run_mode='paddle', - batch_size=1, - device='CPU', - min_subgraph_size=3, - use_dynamic_shape=False, - trt_min_shape=1, - trt_max_shape=1280, - trt_opt_shape=640, - trt_calib_mode=False, - cpu_threads=1, - enable_mkldnn=False, - enable_mkldnn_bfloat16=False, - delete_shuffle_pass=False): - """set AnalysisConfig, generate AnalysisPredictor - Args: - model_dir (str): root path of __model__ and __params__ - device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU - run_mode (str): mode of running(paddle/trt_fp32/trt_fp16/trt_int8) - use_dynamic_shape (bool): use dynamic shape or not - trt_min_shape (int): min shape for dynamic shape in trt - trt_max_shape (int): max shape for dynamic shape in trt - trt_opt_shape (int): opt shape for dynamic shape in trt - trt_calib_mode (bool): If the model is produced by TRT offline quantitative - calibration, trt_calib_mode need to set True - delete_shuffle_pass (bool): whether to remove shuffle_channel_detect_pass in TensorRT. - Used by action model. - Returns: - predictor (PaddlePredictor): AnalysisPredictor - Raises: - ValueError: predict by TensorRT need device == 'GPU'. - """ - if device != 'GPU' and run_mode != 'paddle': - raise ValueError( - "Predict by TensorRT mode: {}, expect device=='GPU', but device == {}" - .format(run_mode, device)) - config = Config( - os.path.join(model_dir, 'model.pdmodel'), - os.path.join(model_dir, 'model.pdiparams')) - if device == 'GPU': - # initial GPU memory(M), device ID - config.enable_use_gpu(200, 0) - # optimize graph and fuse op - config.switch_ir_optim(True) - elif device == 'XPU': - config.enable_lite_engine() - config.enable_xpu(10 * 1024 * 1024) - else: - config.disable_gpu() - config.set_cpu_math_library_num_threads(cpu_threads) - if enable_mkldnn: - try: - # cache 10 different shapes for mkldnn to avoid memory leak - config.set_mkldnn_cache_capacity(10) - config.enable_mkldnn() - if enable_mkldnn_bfloat16: - config.enable_mkldnn_bfloat16() - except Exception as e: - print( - "The current environment does not support `mkldnn`, so disable mkldnn." - ) - pass - - precision_map = { - 'trt_int8': Config.Precision.Int8, - 'trt_fp32': Config.Precision.Float32, - 'trt_fp16': Config.Precision.Half - } - if run_mode in precision_map.keys(): - config.enable_tensorrt_engine( - workspace_size=(1 << 25) * batch_size, - max_batch_size=batch_size, - min_subgraph_size=min_subgraph_size, - precision_mode=precision_map[run_mode], - use_static=False, - use_calib_mode=trt_calib_mode) - - if use_dynamic_shape: - min_input_shape = { - 'image': [batch_size, 3, trt_min_shape, trt_min_shape] - } - max_input_shape = { - 'image': [batch_size, 3, trt_max_shape, trt_max_shape] - } - opt_input_shape = { - 'image': [batch_size, 3, trt_opt_shape, trt_opt_shape] - } - config.set_trt_dynamic_shape_info(min_input_shape, max_input_shape, - opt_input_shape) - print('trt set dynamic shape done!') - - # disable print log when predict - config.disable_glog_info() - # enable shared memory - config.enable_memory_optim() - # disable feed, fetch OP, needed by zero_copy_run - config.switch_use_feed_fetch_ops(False) - if delete_shuffle_pass: - config.delete_pass("shuffle_channel_detect_pass") - predictor = create_predictor(config) - return predictor - - -def predict_image(predictor, - image_file, - image_shape=[640, 640], - warmup=1, - repeats=1, - threshold=0.5, - arch='YOLOv5'): - img, scale_factor = image_preprocess(image_file, image_shape) - inputs = {} - if arch == 'YOLOv5': - inputs['x2paddle_images'] = img - input_names = predictor.get_input_names() - for i in range(len(input_names)): - input_tensor = predictor.get_input_handle(input_names[i]) - input_tensor.copy_from_cpu(inputs[input_names[i]]) - - for i in range(warmup): - predictor.run() - - np_boxes = None - predict_time = 0. - time_min = float("inf") - time_max = float('-inf') - for i in range(repeats): - start_time = time.time() - predictor.run() - output_names = predictor.get_output_names() - boxes_tensor = predictor.get_output_handle(output_names[0]) - np_boxes = boxes_tensor.copy_to_cpu() - end_time = time.time() - timed = end_time - start_time - time_min = min(time_min, timed) - time_max = max(time_max, timed) - predict_time += timed - - time_avg = predict_time / repeats - print('Inference time(ms): min={}, max={}, avg={}'.format( - round(time_min * 1000, 2), - round(time_max * 1000, 1), round(time_avg * 1000, 1))) - postprocess = YOLOv5PostProcess( - score_threshold=0.001, nms_threshold=0.6, multi_label=True) - res = postprocess(np_boxes, scale_factor) - res_img = draw_box( - image_file, res['bbox'], CLASS_LABEL, threshold=threshold) - cv2.imwrite('result.jpg', res_img) - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument( - '--image_file', type=str, default=None, help="image path") - parser.add_argument( - '--model_path', type=str, help="inference model filepath") - parser.add_argument( - '--benchmark', - type=bool, - default=False, - help="Whether run benchmark or not.") - parser.add_argument( - '--run_mode', - type=str, - default='paddle', - help="mode of running(paddle/trt_fp32/trt_fp16/trt_int8)") - parser.add_argument( - '--device', - type=str, - default='GPU', - help="Choose the device you want to run, it can be: CPU/GPU/XPU, default is GPU" - ) - parser.add_argument('--img_shape', type=int, default=640, help="input_size") - args = parser.parse_args() - - predictor = load_predictor( - args.model_path, run_mode=args.run_mode, device=args.device) - warmup, repeats = 1, 1 - if args.benchmark: - warmup, repeats = 50, 100 - predict_image( - predictor, - args.image_file, - image_shape=[args.img_shape, args.img_shape], - warmup=warmup, - repeats=repeats) diff --git a/example/auto_compression/pytorch_yolov5/run.py b/example/auto_compression/pytorch_yolov5/run.py deleted file mode 100644 index 965a546f..00000000 --- a/example/auto_compression/pytorch_yolov5/run.py +++ /dev/null @@ -1,179 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys -import numpy as np -import argparse -import paddle -from ppdet.core.workspace import load_config, merge_config -from ppdet.core.workspace import create -from ppdet.metrics import COCOMetric, VOCMetric -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config -from paddleslim.auto_compression import AutoCompression - -from post_process import YOLOv5PostProcess - - -def argsparser(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - '--config_path', - type=str, - default=None, - help="path of compression strategy config.", - required=True) - parser.add_argument( - '--save_dir', - type=str, - default='output', - help="directory to save compressed model.") - parser.add_argument( - '--devices', - type=str, - default='gpu', - help="which device used to compress.") - - return parser - - -def reader_wrapper(reader, input_list): - def gen(): - for data in reader: - in_dict = {} - if isinstance(input_list, list): - for input_name in input_list: - in_dict[input_name] = data[input_name] - elif isinstance(input_list, dict): - for input_name in input_list.keys(): - in_dict[input_list[input_name]] = data[input_name] - yield in_dict - - return gen - - -def convert_numpy_data(data, metric): - data_all = {} - data_all = {k: np.array(v) for k, v in data.items()} - if isinstance(metric, VOCMetric): - for k, v in data_all.items(): - if not isinstance(v[0], np.ndarray): - tmp_list = [] - for t in v: - tmp_list.append(np.array(t)) - data_all[k] = np.array(tmp_list) - else: - data_all = {k: np.array(v) for k, v in data.items()} - return data_all - - -def eval_function(exe, compiled_test_program, test_feed_names, test_fetch_list): - metric = global_config['metric'] - for batch_id, data in enumerate(val_loader): - data_all = convert_numpy_data(data, metric) - data_input = {} - for k, v in data.items(): - if isinstance(global_config['input_list'], list): - if k in test_feed_names: - data_input[k] = np.array(v) - elif isinstance(global_config['input_list'], dict): - if k in global_config['input_list'].keys(): - data_input[global_config['input_list'][k]] = np.array(v) - outs = exe.run(compiled_test_program, - feed=data_input, - fetch_list=test_fetch_list, - return_numpy=False) - res = {} - if 'arch' in global_config and global_config['arch'] == 'YOLOv5': - postprocess = YOLOv5PostProcess( - score_threshold=0.001, nms_threshold=0.6, multi_label=True) - res = postprocess(np.array(outs[0]), data_all['scale_factor']) - else: - for out in outs: - v = np.array(out) - if len(v.shape) > 1: - res['bbox'] = v - else: - res['bbox_num'] = v - - metric.update(data_all, 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] - - -def main(): - global global_config - all_config = load_slim_config(FLAGS.config_path) - assert "Global" in all_config, f"Key 'Global' not found in config file. \n{all_config}" - global_config = all_config["Global"] - reader_cfg = load_config(global_config['reader_config']) - - train_loader = create('EvalReader')(reader_cfg['TrainDataset'], - reader_cfg['worker_num'], - return_list=True) - train_loader = reader_wrapper(train_loader, global_config['input_list']) - - if 'Evaluation' in global_config.keys() and global_config[ - 'Evaluation'] and paddle.distributed.get_rank() == 0: - eval_func = eval_function - dataset = reader_cfg['EvalDataset'] - global val_loader - _eval_batch_sampler = paddle.io.BatchSampler( - dataset, batch_size=reader_cfg['EvalReader']['batch_size']) - val_loader = create('EvalReader')(dataset, - reader_cfg['worker_num'], - batch_sampler=_eval_batch_sampler, - return_list=True) - metric = None - if reader_cfg['metric'] == 'COCO': - clsid2catid = {v: k for k, v in dataset.catid2clsid.items()} - anno_file = dataset.get_anno() - metric = COCOMetric( - anno_file=anno_file, clsid2catid=clsid2catid, IouType='bbox') - elif reader_cfg['metric'] == 'VOC': - metric = VOCMetric( - label_list=dataset.get_label_list(), - class_num=reader_cfg['num_classes'], - map_type=reader_cfg['map_type']) - else: - raise ValueError("metric currently only supports COCO and VOC.") - global_config['metric'] = metric - else: - eval_func = None - - ac = AutoCompression( - model_dir=global_config["model_dir"], - model_filename=global_config["model_filename"], - params_filename=global_config["params_filename"], - save_dir=FLAGS.save_dir, - config=all_config, - train_dataloader=train_loader, - eval_callback=eval_func) - ac.compress() - - -if __name__ == '__main__': - paddle.enable_static() - parser = argsparser() - FLAGS = parser.parse_args() - - assert FLAGS.devices in ['cpu', 'gpu', 'xpu', 'npu'] - paddle.set_device(FLAGS.devices) - - main() diff --git a/example/auto_compression/pytorch_yolov6/README.md b/example/auto_compression/pytorch_yolov6/README.md deleted file mode 100644 index 7cdb5464..00000000 --- a/example/auto_compression/pytorch_yolov6/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# YOLOv6自动压缩示例 - -目录: -- [1.简介](#1简介) -- [2.Benchmark](#2Benchmark) -- [3.开始自动压缩](#自动压缩流程) - - [3.1 环境准备](#31-准备环境) - - [3.2 准备数据集](#32-准备数据集) - - [3.3 准备预测模型](#33-准备预测模型) - - [3.4 测试模型精度](#34-测试模型精度) - - [3.5 自动压缩并产出模型](#35-自动压缩并产出模型) -- [4.预测部署](#4预测部署) -- [5.FAQ](5FAQ) - -## 1. 简介 - -飞桨模型转换工具[X2Paddle](https://github.com/PaddlePaddle/X2Paddle)支持将```Caffe/TensorFlow/ONNX/PyTorch```的模型一键转为飞桨(PaddlePaddle)的预测模型。借助X2Paddle的能力,各种框架的推理模型可以很方便的使用PaddleSlim的自动化压缩功能。 - -本示例将以[meituan/YOLOv6](https://github.com/meituan/YOLOv6)目标检测模型为例,将PyTorch框架模型转换为Paddle框架模型,再使用ACT自动压缩功能进行自动压缩。本示例使用的自动压缩策略为量化训练。 - -## 2.Benchmark - -| 模型 | 策略 | 输入尺寸 | mAPval
0.5:0.95 | 预测时延FP32
(ms) |预测时延FP16
(ms) | 预测时延INT8
(ms) | 配置文件 | Inference模型 | -| :-------- |:-------- |:--------: | :---------------------: | :----------------: | :----------------: | :---------------: | :-----------------------------: | :-----------------------------: | -| YOLOv6s | Base模型 | 640*640 | 42.4 | 9.06ms | 2.90ms | - | - | [Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov6s_infer.tar) | -| YOLOv6s | KL离线量化 | 640*640 | 30.3 | - | - | 1.83ms | - | - | -| YOLOv6s | 量化蒸馏训练 | 640*640 | **41.3** | - | - | **1.83ms** | [config](./configs/yolov6s_qat_dis.yaml) | [Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov6s_quant.tar) | - -说明: -- mAP的指标均在COCO val2017数据集中评测得到。 -- YOLOv6s模型在Tesla T4的GPU环境下开启TensorRT 8.4.1,batch_size=1, 测试脚本是[cpp_infer](./cpp_infer)。 - -## 3. 自动压缩流程 - -#### 3.1 准备环境 -- PaddlePaddle >= 2.3 (可从[Paddle官网](https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/linux-pip.html)下载安装) -- PaddleSlim > 2.3版本 -- PaddleDet >= 2.4 -- [X2Paddle](https://github.com/PaddlePaddle/X2Paddle) >= 1.3.6 -- opencv-python - -(1)安装paddlepaddle: -```shell -# CPU -pip install paddlepaddle -# GPU -pip install paddlepaddle-gpu -``` - -(2)安装paddleslim: -```shell -pip install paddleslim -``` - -(3)安装paddledet: -```shell -pip install paddledet -``` - -注:安装PaddleDet的目的只是为了直接使用PaddleDetection中的Dataloader组件。 - -(4)安装X2Paddle的1.3.6以上版本: -```shell -pip install x2paddle sympy onnx -``` - -#### 3.2 准备数据集 - -本案例默认以COCO数据进行自动压缩实验,并且依赖PaddleDetection中数据读取模块,如果自定义COCO数据,或者其他格式数据,请参考[PaddleDetection数据准备文档](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.4/docs/tutorials/PrepareDataSet.md) 来准备数据。 - -如果已经准备好数据集,请直接修改[./configs/yolov6_reader.yml]中`EvalDataset`的`dataset_dir`字段为自己数据集路径即可。 - - -#### 3.3 准备预测模型 - -(1)准备ONNX模型: - -可通过[meituan/YOLOv6](https://github.com/meituan/YOLOv6)官方的[导出教程](https://github.com/meituan/YOLOv6/blob/main/deploy/ONNX/README.md)来准备ONNX模型。也可以下载已经准备好的[yolov6s.onnx](https://paddle-slim-models.bj.bcebos.com/act/yolov6s.onnx)。 - - -(2) 转换模型: -``` -x2paddle --framework=onnx --model=yolov6s.onnx --save_dir=pd_model -cp -r pd_model/inference_model/ yolov6s_infer -``` -即可得到YOLOv6s模型的预测模型(`model.pdmodel` 和 `model.pdiparams`)。如想快速体验,可直接下载上方表格中YOLOv6s的[Paddle预测模型](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov6s_infer.tar)。 - - -预测模型的格式为:`model.pdmodel` 和 `model.pdiparams`两个,带`pdmodel`的是模型文件,带`pdiparams`后缀的是权重文件。 - - -#### 3.4 自动压缩并产出模型 - -蒸馏量化自动压缩示例通过run.py脚本启动,会使用接口```paddleslim.auto_compression.AutoCompression```对模型进行自动压缩。配置config文件中模型路径、蒸馏、量化、和训练等部分的参数,配置完成后便可对模型进行量化和蒸馏。具体运行命令为: - -- 单卡训练: -``` -export CUDA_VISIBLE_DEVICES=0 -python run.py --config_path=./configs/yolov6s_qat_dis.yaml --save_dir='./output/' -``` - -- 多卡训练: -``` -CUDA_VISIBLE_DEVICES=0,1,2,3 python -m paddle.distributed.launch --log_dir=log --gpus 0,1,2,3 run.py \ - --config_path=./configs/yolov6s_qat_dis.yaml --save_dir='./output/' -``` - -#### 3.5 测试模型精度 - -修改[yolov6s_qat_dis.yaml](./configs/yolov6s_qat_dis.yaml)中`model_dir`字段为模型存储路径,然后使用eval.py脚本得到模型的mAP: -``` -export CUDA_VISIBLE_DEVICES=0 -python eval.py --config_path=./configs/yolov6s_qat_dis.yaml -``` - - -## 4.预测部署 - -#### Paddle-TensorRT C++部署 - -进入[cpp_infer](./cpp_infer)文件夹内,请按照[C++ TensorRT Benchmark测试教程](./cpp_infer/README.md)进行准备环境及编译,然后开始测试: -```shell -# 编译 -bash complie.sh -# 执行 -./build/trt_run --model_file yolov6s_quant/model.pdmodel --params_file yolov6s_quant/model.pdiparams --run_mode=trt_int8 -``` - -#### Paddle-TensorRT Python部署: - -首先安装带有TensorRT的[Paddle安装包](https://www.paddlepaddle.org.cn/inference/v2.3/user_guides/download_lib.html#python)。 - -然后使用[paddle_trt_infer.py](./paddle_trt_infer.py)进行部署: -```shell -python paddle_trt_infer.py --model_path=output --image_file=images/000000570688.jpg --benchmark=True --run_mode=trt_int8 -``` - -## 5.FAQ - -- 如果想测试离线量化模型精度,可执行: -```shell -python post_quant.py --config_path=./configs/yolov6s_qat_dis.yaml -``` diff --git a/example/auto_compression/pytorch_yolov6/configs/yolov6_reader.yml b/example/auto_compression/pytorch_yolov6/configs/yolov6_reader.yml deleted file mode 100644 index cb87c3f8..00000000 --- a/example/auto_compression/pytorch_yolov6/configs/yolov6_reader.yml +++ /dev/null @@ -1,27 +0,0 @@ -metric: COCO -num_classes: 80 - -# Datset configuration -TrainDataset: - !COCODataSet - image_dir: train2017 - anno_path: annotations/instances_train2017.json - dataset_dir: dataset/coco/ - -EvalDataset: - !COCODataSet - image_dir: val2017 - anno_path: annotations/instances_val2017.json - dataset_dir: dataset/coco/ - -worker_num: 0 - -# preprocess reader in test -EvalReader: - sample_transforms: - - Decode: {} - - Resize: {target_size: [640, 640], keep_ratio: True} - - Pad: {size: [640, 640], fill_value: [114., 114., 114.]} - - NormalizeImage: {mean: [0, 0, 0], std: [1, 1, 1], is_scale: True} - - Permute: {} - batch_size: 1 diff --git a/example/auto_compression/pytorch_yolov6/cpp_infer/CMakeLists.txt b/example/auto_compression/pytorch_yolov6/cpp_infer/CMakeLists.txt deleted file mode 100644 index d5307c65..00000000 --- a/example/auto_compression/pytorch_yolov6/cpp_infer/CMakeLists.txt +++ /dev/null @@ -1,263 +0,0 @@ -cmake_minimum_required(VERSION 3.0) -project(cpp_inference_demo CXX C) -option(WITH_MKL "Compile demo with MKL/OpenBlas support, default use MKL." ON) -option(WITH_GPU "Compile demo with GPU/CPU, default use CPU." OFF) -option(WITH_STATIC_LIB "Compile demo with static/shared library, default use static." ON) -option(USE_TENSORRT "Compile demo with TensorRT." OFF) -option(WITH_ROCM "Compile demo with rocm." OFF) -option(WITH_ONNXRUNTIME "Compile demo with ONNXRuntime" OFF) -option(WITH_ARM "Compile demo with ARM" OFF) -option(WITH_MIPS "Compile demo with MIPS" OFF) -option(WITH_SW "Compile demo with SW" OFF) -option(WITH_XPU "Compile demow ith xpu" OFF) -option(WITH_NPU "Compile demow ith npu" OFF) - -if(NOT WITH_STATIC_LIB) - add_definitions("-DPADDLE_WITH_SHARED_LIB") -else() - # PD_INFER_DECL is mainly used to set the dllimport/dllexport attribute in dynamic library mode. - # Set it to empty in static library mode to avoid compilation issues. - add_definitions("/DPD_INFER_DECL=") -endif() - -macro(safe_set_static_flag) - foreach(flag_var - CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif(${flag_var} MATCHES "/MD") - endforeach(flag_var) -endmacro() - -if(NOT DEFINED PADDLE_LIB) - message(FATAL_ERROR "please set PADDLE_LIB with -DPADDLE_LIB=/path/paddle/lib") -endif() -if(NOT DEFINED DEMO_NAME) - message(FATAL_ERROR "please set DEMO_NAME with -DDEMO_NAME=demo_name") -endif() - -include_directories("${PADDLE_LIB}/") -set(PADDLE_LIB_THIRD_PARTY_PATH "${PADDLE_LIB}/third_party/install/") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}protobuf/include") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}glog/include") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}gflags/include") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}xxhash/include") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}cryptopp/include") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}onnxruntime/include") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}paddle2onnx/include") - -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}protobuf/lib") -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}glog/lib") -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}gflags/lib") -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}xxhash/lib") -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}cryptopp/lib") -link_directories("${PADDLE_LIB}/paddle/lib") -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}onnxruntime/lib") -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}paddle2onnx/lib") - -if (WIN32) - add_definitions("/DGOOGLE_GLOG_DLL_DECL=") - option(MSVC_STATIC_CRT "use static C Runtime library by default" ON) - if (MSVC_STATIC_CRT) - if (WITH_MKL) - set(FLAG_OPENMP "/openmp") - endif() - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /bigobj /MTd ${FLAG_OPENMP}") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /bigobj /MT ${FLAG_OPENMP}") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj /MTd ${FLAG_OPENMP}") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /bigobj /MT ${FLAG_OPENMP}") - safe_set_static_flag() - if (WITH_STATIC_LIB) - add_definitions(-DSTATIC_LIB) - endif() - endif() -else() - if(WITH_MKL) - set(FLAG_OPENMP "-fopenmp") - endif() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ${FLAG_OPENMP}") -endif() - -if(WITH_GPU) - if(NOT WIN32) - include_directories("/usr/local/cuda/include") - if(CUDA_LIB STREQUAL "") - set(CUDA_LIB "/usr/local/cuda/lib64/" CACHE STRING "CUDA Library") - endif() - else() - include_directories("C:\\Program\ Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v8.0\\include") - if(CUDA_LIB STREQUAL "") - set(CUDA_LIB "C:\\Program\ Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v8.0\\lib\\x64") - endif() - endif(NOT WIN32) -endif() - -if (USE_TENSORRT AND WITH_GPU) - set(TENSORRT_ROOT "" CACHE STRING "The root directory of TensorRT library") - if("${TENSORRT_ROOT}" STREQUAL "") - message(FATAL_ERROR "The TENSORRT_ROOT is empty, you must assign it a value with CMake command. Such as: -DTENSORRT_ROOT=TENSORRT_ROOT_PATH ") - endif() - set(TENSORRT_INCLUDE_DIR ${TENSORRT_ROOT}/include) - set(TENSORRT_LIB_DIR ${TENSORRT_ROOT}/lib) - file(READ ${TENSORRT_INCLUDE_DIR}/NvInfer.h TENSORRT_VERSION_FILE_CONTENTS) - string(REGEX MATCH "define NV_TENSORRT_MAJOR +([0-9]+)" TENSORRT_MAJOR_VERSION - "${TENSORRT_VERSION_FILE_CONTENTS}") - if("${TENSORRT_MAJOR_VERSION}" STREQUAL "") - file(READ ${TENSORRT_INCLUDE_DIR}/NvInferVersion.h TENSORRT_VERSION_FILE_CONTENTS) - string(REGEX MATCH "define NV_TENSORRT_MAJOR +([0-9]+)" TENSORRT_MAJOR_VERSION - "${TENSORRT_VERSION_FILE_CONTENTS}") - endif() - if("${TENSORRT_MAJOR_VERSION}" STREQUAL "") - message(SEND_ERROR "Failed to detect TensorRT version.") - endif() - string(REGEX REPLACE "define NV_TENSORRT_MAJOR +([0-9]+)" "\\1" - TENSORRT_MAJOR_VERSION "${TENSORRT_MAJOR_VERSION}") - message(STATUS "Current TensorRT header is ${TENSORRT_INCLUDE_DIR}/NvInfer.h. " - "Current TensorRT version is v${TENSORRT_MAJOR_VERSION}. ") - include_directories("${TENSORRT_INCLUDE_DIR}") - link_directories("${TENSORRT_LIB_DIR}") -endif() - -if(WITH_MKL) - set(MATH_LIB_PATH "${PADDLE_LIB_THIRD_PARTY_PATH}mklml") - include_directories("${MATH_LIB_PATH}/include") - if(WIN32) - set(MATH_LIB ${MATH_LIB_PATH}/lib/mklml${CMAKE_STATIC_LIBRARY_SUFFIX} - ${MATH_LIB_PATH}/lib/libiomp5md${CMAKE_STATIC_LIBRARY_SUFFIX}) - else() - set(MATH_LIB ${MATH_LIB_PATH}/lib/libmklml_intel${CMAKE_SHARED_LIBRARY_SUFFIX} - ${MATH_LIB_PATH}/lib/libiomp5${CMAKE_SHARED_LIBRARY_SUFFIX}) - endif() - set(MKLDNN_PATH "${PADDLE_LIB_THIRD_PARTY_PATH}mkldnn") - if(EXISTS ${MKLDNN_PATH}) - include_directories("${MKLDNN_PATH}/include") - if(WIN32) - set(MKLDNN_LIB ${MKLDNN_PATH}/lib/mkldnn.lib) - else(WIN32) - set(MKLDNN_LIB ${MKLDNN_PATH}/lib/libmkldnn.so.0) - endif(WIN32) - endif() -elseif((NOT WITH_MIPS) AND (NOT WITH_SW)) - set(OPENBLAS_LIB_PATH "${PADDLE_LIB_THIRD_PARTY_PATH}openblas") - include_directories("${OPENBLAS_LIB_PATH}/include/openblas") - if(WIN32) - set(MATH_LIB ${OPENBLAS_LIB_PATH}/lib/openblas${CMAKE_STATIC_LIBRARY_SUFFIX}) - else() - set(MATH_LIB ${OPENBLAS_LIB_PATH}/lib/libopenblas${CMAKE_STATIC_LIBRARY_SUFFIX}) - endif() -endif() - -if(WITH_STATIC_LIB) - set(DEPS ${PADDLE_LIB}/paddle/lib/libpaddle_inference${CMAKE_STATIC_LIBRARY_SUFFIX}) -else() - if(WIN32) - set(DEPS ${PADDLE_LIB}/paddle/lib/paddle_inference${CMAKE_STATIC_LIBRARY_SUFFIX}) - else() - set(DEPS ${PADDLE_LIB}/paddle/lib/libpaddle_inference${CMAKE_SHARED_LIBRARY_SUFFIX}) - endif() -endif() - -if (WITH_ONNXRUNTIME) - if(WIN32) - set(DEPS ${DEPS} ${PADDLE_LIB_THIRD_PARTY_PATH}onnxruntime/lib/onnxruntime.lib paddle2onnx) - elseif(APPLE) - set(DEPS ${DEPS} ${PADDLE_LIB_THIRD_PARTY_PATH}onnxruntime/lib/libonnxruntime.1.10.0.dylib paddle2onnx) - else() - set(DEPS ${DEPS} ${PADDLE_LIB_THIRD_PARTY_PATH}onnxruntime/lib/libonnxruntime.so.1.10.0 paddle2onnx) - endif() -endif() - -if (NOT WIN32) - set(EXTERNAL_LIB "-lrt -ldl -lpthread") - set(DEPS ${DEPS} - ${MATH_LIB} ${MKLDNN_LIB} - glog gflags protobuf xxhash cryptopp - ${EXTERNAL_LIB}) -else() - set(DEPS ${DEPS} - ${MATH_LIB} ${MKLDNN_LIB} - glog gflags_static libprotobuf xxhash cryptopp-static ${EXTERNAL_LIB}) - set(DEPS ${DEPS} shlwapi.lib) -endif(NOT WIN32) - -if(WITH_GPU) - if(NOT WIN32) - if (USE_TENSORRT) - set(DEPS ${DEPS} ${TENSORRT_LIB_DIR}/libnvinfer${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${TENSORRT_LIB_DIR}/libnvinfer_plugin${CMAKE_SHARED_LIBRARY_SUFFIX}) - endif() - set(DEPS ${DEPS} ${CUDA_LIB}/libcudart${CMAKE_SHARED_LIBRARY_SUFFIX}) - else() - if(USE_TENSORRT) - set(DEPS ${DEPS} ${TENSORRT_LIB_DIR}/nvinfer${CMAKE_STATIC_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${TENSORRT_LIB_DIR}/nvinfer_plugin${CMAKE_STATIC_LIBRARY_SUFFIX}) - if(${TENSORRT_MAJOR_VERSION} GREATER_EQUAL 7) - set(DEPS ${DEPS} ${TENSORRT_LIB_DIR}/myelin64_1${CMAKE_STATIC_LIBRARY_SUFFIX}) - endif() - endif() - set(DEPS ${DEPS} ${CUDA_LIB}/cudart${CMAKE_STATIC_LIBRARY_SUFFIX} ) - set(DEPS ${DEPS} ${CUDA_LIB}/cublas${CMAKE_STATIC_LIBRARY_SUFFIX} ) - set(DEPS ${DEPS} ${CUDA_LIB}/cudnn${CMAKE_STATIC_LIBRARY_SUFFIX} ) - endif() -endif() - -if(WITH_ROCM AND NOT WIN32) - set(DEPS ${DEPS} ${ROCM_LIB}/libamdhip64${CMAKE_SHARED_LIBRARY_SUFFIX}) -endif() - -if(WITH_XPU AND NOT WIN32) - set(XPU_INSTALL_PATH "${PADDLE_LIB_THIRD_PARTY_PATH}xpu") - set(DEPS ${DEPS} ${XPU_INSTALL_PATH}/lib/libxpuapi${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${XPU_INSTALL_PATH}/lib/libxpurt${CMAKE_SHARED_LIBRARY_SUFFIX}) -endif() - -if(WITH_NPU AND NOT WIN32) - set(DEPS ${DEPS} ${ASCEND_DIR}/ascend-toolkit/latest/fwkacllib/lib64/libgraph${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${ASCEND_DIR}/ascend-toolkit/latest/fwkacllib/lib64/libge_runner${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${ASCEND_DIR}/ascend-toolkit/latest/fwkacllib/lib64/libascendcl${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${ASCEND_DIR}/ascend-toolkit/latest/fwkacllib/lib64/libascendcl${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${ASCEND_DIR}/ascend-toolkit/latest/fwkacllib/lib64/libacl_op_compiler${CMAKE_SHARED_LIBRARY_SUFFIX}) -endif() - -add_executable(${DEMO_NAME} ${DEMO_NAME}.cc) -target_link_libraries(${DEMO_NAME} ${DEPS}) -if(WIN32) - if(USE_TENSORRT) - add_custom_command(TARGET ${DEMO_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${TENSORRT_LIB_DIR}/nvinfer${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE} - COMMAND ${CMAKE_COMMAND} -E copy ${TENSORRT_LIB_DIR}/nvinfer_plugin${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE} - ) - if(${TENSORRT_MAJOR_VERSION} GREATER_EQUAL 7) - add_custom_command(TARGET ${DEMO_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${TENSORRT_LIB_DIR}/myelin64_1${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}) - endif() - endif() - if(WITH_MKL) - add_custom_command(TARGET ${DEMO_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${MATH_LIB_PATH}/lib/mklml.dll ${CMAKE_BINARY_DIR}/Release - COMMAND ${CMAKE_COMMAND} -E copy ${MATH_LIB_PATH}/lib/libiomp5md.dll ${CMAKE_BINARY_DIR}/Release - COMMAND ${CMAKE_COMMAND} -E copy ${MKLDNN_PATH}/lib/mkldnn.dll ${CMAKE_BINARY_DIR}/Release - ) - else() - add_custom_command(TARGET ${DEMO_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${OPENBLAS_LIB_PATH}/lib/openblas.dll ${CMAKE_BINARY_DIR}/Release - ) - endif() - if(WITH_ONNXRUNTIME) - add_custom_command(TARGET ${DEMO_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${PADDLE_LIB_THIRD_PARTY_PATH}onnxruntime/lib/onnxruntime.dll - ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE} - COMMAND ${CMAKE_COMMAND} -E copy ${PADDLE_LIB_THIRD_PARTY_PATH}paddle2onnx/lib/paddle2onnx.dll - ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE} - ) - endif() - if(NOT WITH_STATIC_LIB) - add_custom_command(TARGET ${DEMO_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy "${PADDLE_LIB}/paddle/lib/paddle_inference.dll" ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE} - ) - endif() -endif() diff --git a/example/auto_compression/pytorch_yolov6/cpp_infer/README.md b/example/auto_compression/pytorch_yolov6/cpp_infer/README.md deleted file mode 100644 index 2f220486..00000000 --- a/example/auto_compression/pytorch_yolov6/cpp_infer/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# YOLOv6 TensorRT Benchmark测试(Linux) - -## 环境准备 - -- CUDA、CUDNN:确认环境中已经安装CUDA和CUDNN,并且提前获取其安装路径。 - -- TensorRT:可通过NVIDIA官网下载[TensorRT 8.4.1.5](https://developer.nvidia.com/compute/machine-learning/tensorrt/secure/8.4.1/tars/tensorrt-8.4.1.5.linux.x86_64-gnu.cuda-11.6.cudnn8.4.tar.gz)或其他版本安装包。 - -- Paddle Inference C++预测库:编译develop版本请参考[编译文档](https://www.paddlepaddle.org.cn/inference/user_guides/source_compile.html)。编译完成后,会在build目录下生成`paddle_inference_install_dir`文件夹,这个就是我们需要的C++预测库文件。 - -## 编译可执行程序 - -- (1)修改`compile.sh`中依赖库路径,主要是以下内容: -```shell -# Paddle Inference预测库路径 -LIB_DIR=/root/auto_compress/Paddle/build/paddle_inference_install_dir/ -# CUDNN路径 -CUDNN_LIB=/usr/lib/x86_64-linux-gnu/ -# CUDA路径 -CUDA_LIB=/usr/local/cuda/lib64 -# TensorRT安装包路径,为TRT资源包解压完成后的绝对路径,其中包含`lib`和`include`文件夹 -TENSORRT_ROOT=/root/auto_compress/trt/trt8.4/ -``` - -## 测试 - -- FP32 -``` -./build/trt_run --model_file yolov6s_infer/model.pdmodel --params_file yolov6s_infer/model.pdiparams --run_mode=trt_fp32 -``` - -- FP16 -``` -./build/trt_run --model_file yolov6s_infer/model.pdmodel --params_file yolov6s_infer/model.pdiparams --run_mode=trt_fp16 -``` - -- INT8 -``` -./build/trt_run --model_file yolov6s_quant/model.pdmodel --params_file yolov6s_quant/model.pdiparams --run_mode=trt_int8 -``` - -## 性能对比 - -| 模型 | 预测时延FP32
(ms) |预测时延FP16
(ms) | 预测时延INT8
(ms) | -| :-------- |:-------- |:--------: | :---------------------: | -| YOLOv6s | 9.06ms | 2.90ms | 1.83ms | - -环境: -- Tesla T4,TensorRT 8.4.1,CUDA 11.2 -- batch_size=1 diff --git a/example/auto_compression/pytorch_yolov6/cpp_infer/compile.sh b/example/auto_compression/pytorch_yolov6/cpp_infer/compile.sh deleted file mode 100644 index afff924b..00000000 --- a/example/auto_compression/pytorch_yolov6/cpp_infer/compile.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -set +x -set -e - -work_path=$(dirname $(readlink -f $0)) - -mkdir -p build -cd build -rm -rf * - -DEMO_NAME=trt_run - -WITH_MKL=ON -WITH_GPU=ON -USE_TENSORRT=ON - -LIB_DIR=/root/auto_compress/Paddle/build/paddle_inference_install_dir/ -CUDNN_LIB=/usr/lib/x86_64-linux-gnu/ -CUDA_LIB=/usr/local/cuda/lib64 -TENSORRT_ROOT=/root/auto_compress/trt/trt8.4/ - -WITH_ROCM=OFF -ROCM_LIB=/opt/rocm/lib - -cmake .. -DPADDLE_LIB=${LIB_DIR} \ - -DWITH_MKL=${WITH_MKL} \ - -DDEMO_NAME=${DEMO_NAME} \ - -DWITH_GPU=${WITH_GPU} \ - -DWITH_STATIC_LIB=OFF \ - -DUSE_TENSORRT=${USE_TENSORRT} \ - -DWITH_ROCM=${WITH_ROCM} \ - -DROCM_LIB=${ROCM_LIB} \ - -DCUDNN_LIB=${CUDNN_LIB} \ - -DCUDA_LIB=${CUDA_LIB} \ - -DTENSORRT_ROOT=${TENSORRT_ROOT} - -make -j diff --git a/example/auto_compression/pytorch_yolov6/cpp_infer/trt_run.cc b/example/auto_compression/pytorch_yolov6/cpp_infer/trt_run.cc deleted file mode 100644 index 9c14baf7..00000000 --- a/example/auto_compression/pytorch_yolov6/cpp_infer/trt_run.cc +++ /dev/null @@ -1,116 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -#include "paddle/include/paddle_inference_api.h" -#include "paddle/include/experimental/phi/common/float16.h" - -using paddle_infer::Config; -using paddle_infer::Predictor; -using paddle_infer::CreatePredictor; -using paddle_infer::PrecisionType; -using phi::dtype::float16; - -DEFINE_string(model_dir, "", "Directory of the inference model."); -DEFINE_string(model_file, "", "Path of the inference model file."); -DEFINE_string(params_file, "", "Path of the inference params file."); -DEFINE_string(run_mode, "trt_fp32", "run_mode which can be: trt_fp32, trt_fp16 and trt_int8"); -DEFINE_int32(batch_size, 1, "Batch size."); -DEFINE_int32(gpu_id, 0, "GPU card ID num."); -DEFINE_int32(trt_min_subgraph_size, 3, "tensorrt min_subgraph_size"); -DEFINE_int32(warmup, 50, "warmup"); -DEFINE_int32(repeats, 1000, "repeats"); - -using Time = decltype(std::chrono::high_resolution_clock::now()); -Time time() { return std::chrono::high_resolution_clock::now(); }; -double time_diff(Time t1, Time t2) { - typedef std::chrono::microseconds ms; - auto diff = t2 - t1; - ms counter = std::chrono::duration_cast(diff); - return counter.count() / 1000.0; -} - -std::shared_ptr InitPredictor() { - Config config; - std::string model_path; - if (FLAGS_model_dir != "") { - config.SetModel(FLAGS_model_dir); - model_path = FLAGS_model_dir.substr(0, FLAGS_model_dir.find_last_of("/")); - } else { - config.SetModel(FLAGS_model_file, FLAGS_params_file); - model_path = FLAGS_model_file.substr(0, FLAGS_model_file.find_last_of("/")); - } - // enable tune - std::cout << "model_path: " << model_path << std::endl; - config.EnableUseGpu(256, FLAGS_gpu_id); - if (FLAGS_run_mode == "trt_fp32") { - config.EnableTensorRtEngine(1 << 30, FLAGS_batch_size, FLAGS_trt_min_subgraph_size, - PrecisionType::kFloat32, false, false); - } else if (FLAGS_run_mode == "trt_fp16") { - config.EnableTensorRtEngine(1 << 30, FLAGS_batch_size, FLAGS_trt_min_subgraph_size, - PrecisionType::kHalf, false, false); - } else if (FLAGS_run_mode == "trt_int8") { - config.EnableTensorRtEngine(1 << 30, FLAGS_batch_size, FLAGS_trt_min_subgraph_size, - PrecisionType::kInt8, false, false); - } - config.EnableMemoryOptim(); - config.SwitchIrOptim(true); - return CreatePredictor(config); -} - -template -void run(Predictor *predictor, const std::vector &input, - const std::vector &input_shape, type* out_data, std::vector out_shape) { - - // prepare input - int input_num = std::accumulate(input_shape.begin(), input_shape.end(), 1, - std::multiplies()); - - auto input_names = predictor->GetInputNames(); - auto input_t = predictor->GetInputHandle(input_names[0]); - input_t->Reshape(input_shape); - input_t->CopyFromCpu(input.data()); - - for (int i = 0; i < FLAGS_warmup; ++i) - CHECK(predictor->Run()); - - auto st = time(); - for (int i = 0; i < FLAGS_repeats; ++i) { - auto input_names = predictor->GetInputNames(); - auto input_t = predictor->GetInputHandle(input_names[0]); - input_t->Reshape(input_shape); - input_t->CopyFromCpu(input.data()); - - CHECK(predictor->Run()); - - auto output_names = predictor->GetOutputNames(); - auto output_t = predictor->GetOutputHandle(output_names[0]); - std::vector output_shape = output_t->shape(); - output_t -> ShareExternalData(out_data, out_shape, paddle_infer::PlaceType::kGPU); - } - - LOG(INFO) << "[" << FLAGS_run_mode << " bs-" << FLAGS_batch_size << " ] run avg time is " << time_diff(st, time()) / FLAGS_repeats - << " ms"; -} - -int main(int argc, char *argv[]) { - google::ParseCommandLineFlags(&argc, &argv, true); - auto predictor = InitPredictor(); - std::vector input_shape = {FLAGS_batch_size, 3, 640, 640}; - // float16 - using dtype = float16; - std::vector input_data(FLAGS_batch_size * 3 * 640 * 640, dtype(1.0)); - - dtype *out_data; - int out_data_size = FLAGS_batch_size * 8400 * 85; - cudaHostAlloc((void**)&out_data, sizeof(float) * out_data_size, cudaHostAllocMapped); - - std::vector out_shape{ FLAGS_batch_size, 1, 8400, 85}; - run(predictor.get(), input_data, input_shape, out_data, out_shape); - return 0; -} diff --git a/example/auto_compression/pytorch_yolov6/eval.py b/example/auto_compression/pytorch_yolov6/eval.py deleted file mode 100644 index 62127b51..00000000 --- a/example/auto_compression/pytorch_yolov6/eval.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys -import numpy as np -import argparse -import paddle -from ppdet.core.workspace import load_config, merge_config -from ppdet.core.workspace import create -from ppdet.metrics import COCOMetric, VOCMetric -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config - -from post_process import YOLOv6PostProcess - - -def argsparser(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - '--config_path', - type=str, - default=None, - help="path of compression strategy config.", - required=True) - parser.add_argument( - '--devices', - type=str, - default='gpu', - help="which device used to compress.") - - return parser - - -def reader_wrapper(reader, input_list): - def gen(): - for data in reader: - in_dict = {} - if isinstance(input_list, list): - for input_name in input_list: - in_dict[input_name] = data[input_name] - elif isinstance(input_list, dict): - for input_name in input_list.keys(): - in_dict[input_list[input_name]] = data[input_name] - yield in_dict - - return gen - - -def convert_numpy_data(data, metric): - data_all = {} - data_all = {k: np.array(v) for k, v in data.items()} - if isinstance(metric, VOCMetric): - for k, v in data_all.items(): - if not isinstance(v[0], np.ndarray): - tmp_list = [] - for t in v: - tmp_list.append(np.array(t)) - data_all[k] = np.array(tmp_list) - else: - data_all = {k: np.array(v) for k, v in data.items()} - return data_all - - -def eval(): - - place = paddle.CUDAPlace(0) if FLAGS.devices == 'gpu' else paddle.CPUPlace() - exe = paddle.static.Executor(place) - - val_program, feed_target_names, fetch_targets = paddle.static.load_inference_model( - global_config["model_dir"], - exe, - model_filename=global_config["model_filename"], - params_filename=global_config["params_filename"]) - print('Loaded model from: {}'.format(global_config["model_dir"])) - - metric = global_config['metric'] - for batch_id, data in enumerate(val_loader): - data_all = convert_numpy_data(data, metric) - data_input = {} - for k, v in data.items(): - if isinstance(global_config['input_list'], list): - if k in global_config['input_list']: - data_input[k] = np.array(v) - elif isinstance(global_config['input_list'], dict): - if k in global_config['input_list'].keys(): - data_input[global_config['input_list'][k]] = np.array(v) - outs = exe.run(val_program, - feed=data_input, - fetch_list=fetch_targets, - return_numpy=False) - res = {} - if 'arch' in global_config and global_config['arch'] == 'YOLOv6': - postprocess = YOLOv6PostProcess( - score_threshold=0.001, nms_threshold=0.65, multi_label=True) - res = postprocess(np.array(outs[0]), data_all['scale_factor']) - else: - for out in outs: - v = np.array(out) - if len(v.shape) > 1: - res['bbox'] = v - else: - res['bbox_num'] = v - metric.update(data_all, res) - if batch_id % 100 == 0: - print('Eval iter:', batch_id) - metric.accumulate() - metric.log() - metric.reset() - - -def main(): - global global_config - all_config = load_slim_config(FLAGS.config_path) - global_config = all_config["Global"] - reader_cfg = load_config(global_config['reader_config']) - - dataset = reader_cfg['EvalDataset'] - global val_loader - val_loader = create('EvalReader')(reader_cfg['EvalDataset'], - reader_cfg['worker_num'], - return_list=True) - metric = None - if reader_cfg['metric'] == 'COCO': - clsid2catid = {v: k for k, v in dataset.catid2clsid.items()} - anno_file = dataset.get_anno() - metric = COCOMetric( - anno_file=anno_file, clsid2catid=clsid2catid, IouType='bbox') - elif reader_cfg['metric'] == 'VOC': - metric = VOCMetric( - label_list=dataset.get_label_list(), - class_num=reader_cfg['num_classes'], - map_type=reader_cfg['map_type']) - else: - raise ValueError("metric currently only supports COCO and VOC.") - global_config['metric'] = metric - - eval() - - -if __name__ == '__main__': - paddle.enable_static() - parser = argsparser() - FLAGS = parser.parse_args() - - assert FLAGS.devices in ['cpu', 'gpu', 'xpu', 'npu'] - paddle.set_device(FLAGS.devices) - - main() diff --git a/example/auto_compression/pytorch_yolov6/images/000000570688.jpg b/example/auto_compression/pytorch_yolov6/images/000000570688.jpg deleted file mode 100644 index cb304bd56c4010c08611a30dcca58ea9140cea54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138365 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�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%0D4VMGW#Js4Aj+!>|GYQ+%PHsS*#KMEBnG$Kv zeYBe|RHu~_KGeH1i^<((2T8@IxCrrn^4@vmTu*|#RfvgMw5OcbnO$We$&0iu&*i>-khE6Hy^%Hm&q>*SC zCOh6a4Yb02R1^`!WSeMIU<;seJ8Sx@iIPnVNM27q6+~P48 zpua;#7Ee(Hcme3d#JL2dV1PAqM8{t^Nb-ipM*~B>F9Y)t0x&CKodfEk#A$Z@Y<`yp zsR5QD%-hj~AH4Fg65db3Gk~HDMDyaj6{X$q} zM*P&8f4rF?cEVLAjM2J0xE({YFd3$izICo$&$+BoTbi7%1`{OTC=&$cv9K^cra44Q z1iVRxZG$|9aCjkRIa#4`x~2#I1{q%?>!Zqa@kOtR>eAPQaSSv!z$1JcKSr?hIL*WH zDqF_|5@efitGiL>URhzJFc@uX&c~Z%ru?}B7hSS7{<=G79s=Q-)`2~U2F{lhQ)34{ z;z5-k9QWL6A6b5ys@hISH2T

#oy!`N);SsOiWwvC-xPQkZb#0s1vjtn2!IgMJ5c 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

KgbW0626^0HI!q^+J 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

xGbeq`b*lQpge4|)Ru{Kft<-09=sS z1UU`~$pgp($?ib^01=_Yi{+ApXyk%=#Qb|q@spQ$*?FHH?c3|;LxY;wrwCqI5D6VY zzy#oAAAhLrsf`e&TQ6nQUTWzorlF{=wi4yih2t_FAd(1X97kc3><+5&xYkf>nAZlr z(K8qFzfP)65^kmU8=N+p0ZUU=Euy|^MgreSMibO&4>)q z#A`#^59ThIix~N2%yuV83u)C12AcuVMmIIm=MClm024hu-Z>M~jlM>TOfCUcL&xRM z%0D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 RmToMuVX&ENIq_+-|JejjX*B=< diff --git a/example/auto_compression/pytorch_yolov6/paddle_trt_infer.py b/example/auto_compression/pytorch_yolov6/paddle_trt_infer.py deleted file mode 100644 index 5d88643f..00000000 --- a/example/auto_compression/pytorch_yolov6/paddle_trt_infer.py +++ /dev/null @@ -1,322 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import cv2 -import numpy as np -import argparse -import time - -from paddle.inference import Config -from paddle.inference import create_predictor - -from post_process import YOLOv6PostProcess - -CLASS_LABEL = [ - 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', - 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', - 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', - 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', - 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', - 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', - 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', - 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', - 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', - 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', - 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', - 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', - 'hair drier', 'toothbrush' -] - - -def generate_scale(im, target_shape, keep_ratio=True): - """ - Args: - im (np.ndarray): image (np.ndarray) - Returns: - im_scale_x: the resize ratio of X - im_scale_y: the resize ratio of Y - """ - origin_shape = im.shape[:2] - if keep_ratio: - im_size_min = np.min(origin_shape) - im_size_max = np.max(origin_shape) - target_size_min = np.min(target_shape) - target_size_max = np.max(target_shape) - im_scale = float(target_size_min) / float(im_size_min) - if np.round(im_scale * im_size_max) > target_size_max: - im_scale = float(target_size_max) / float(im_size_max) - im_scale_x = im_scale - im_scale_y = im_scale - else: - resize_h, resize_w = target_shape - im_scale_y = resize_h / float(origin_shape[0]) - im_scale_x = resize_w / float(origin_shape[1]) - return im_scale_y, im_scale_x - - -def image_preprocess(img_path, target_shape): - img = cv2.imread(img_path) - # Resize - im_scale_y, im_scale_x = generate_scale(img, target_shape) - img = cv2.resize( - img, - None, - None, - fx=im_scale_x, - fy=im_scale_y, - interpolation=cv2.INTER_LINEAR) - # Pad - im_h, im_w = img.shape[:2] - h, w = target_shape[:] - if h != im_h or w != im_w: - canvas = np.ones((h, w, 3), dtype=np.float32) - canvas *= np.array([114.0, 114.0, 114.0], dtype=np.float32) - canvas[0:im_h, 0:im_w, :] = img.astype(np.float32) - img = canvas - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - img = np.transpose(img, [2, 0, 1]) / 255 - img = np.expand_dims(img, 0) - scale_factor = np.array([[im_scale_y, im_scale_x]]) - return img.astype(np.float32), scale_factor - - -def get_color_map_list(num_classes): - color_map = num_classes * [0, 0, 0] - for i in range(0, num_classes): - j = 0 - lab = i - while lab: - color_map[i * 3] |= (((lab >> 0) & 1) << (7 - j)) - color_map[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j)) - color_map[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j)) - j += 1 - lab >>= 3 - color_map = [color_map[i:i + 3] for i in range(0, len(color_map), 3)] - return color_map - - -def draw_box(image_file, results, class_label, threshold=0.5): - srcimg = cv2.imread(image_file, 1) - for i in range(len(results)): - color_list = get_color_map_list(len(class_label)) - clsid2color = {} - classid, conf = int(results[i, 0]), results[i, 1] - if conf < threshold: - continue - xmin, ymin, xmax, ymax = int(results[i, 2]), int(results[i, 3]), int( - results[i, 4]), int(results[i, 5]) - - if classid not in clsid2color: - clsid2color[classid] = color_list[classid] - color = tuple(clsid2color[classid]) - - cv2.rectangle(srcimg, (xmin, ymin), (xmax, ymax), color, thickness=2) - print(class_label[classid] + ': ' + str(round(conf, 3))) - cv2.putText( - srcimg, - class_label[classid] + ':' + str(round(conf, 3)), (xmin, ymin - 10), - cv2.FONT_HERSHEY_SIMPLEX, - 0.8, (0, 255, 0), - thickness=2) - return srcimg - - -def load_predictor(model_dir, - run_mode='paddle', - batch_size=1, - device='CPU', - min_subgraph_size=3, - use_dynamic_shape=False, - trt_min_shape=1, - trt_max_shape=1280, - trt_opt_shape=640, - trt_calib_mode=False, - cpu_threads=1, - enable_mkldnn=False, - enable_mkldnn_bfloat16=False, - delete_shuffle_pass=False): - """set AnalysisConfig, generate AnalysisPredictor - Args: - model_dir (str): root path of __model__ and __params__ - device (str): Choose the device you want to run, it can be: CPU/GPU/XPU, default is CPU - run_mode (str): mode of running(paddle/trt_fp32/trt_fp16/trt_int8) - use_dynamic_shape (bool): use dynamic shape or not - trt_min_shape (int): min shape for dynamic shape in trt - trt_max_shape (int): max shape for dynamic shape in trt - trt_opt_shape (int): opt shape for dynamic shape in trt - trt_calib_mode (bool): If the model is produced by TRT offline quantitative - calibration, trt_calib_mode need to set True - delete_shuffle_pass (bool): whether to remove shuffle_channel_detect_pass in TensorRT. - Used by action model. - Returns: - predictor (PaddlePredictor): AnalysisPredictor - Raises: - ValueError: predict by TensorRT need device == 'GPU'. - """ - if device != 'GPU' and run_mode != 'paddle': - raise ValueError( - "Predict by TensorRT mode: {}, expect device=='GPU', but device == {}" - .format(run_mode, device)) - config = Config( - os.path.join(model_dir, 'model.pdmodel'), - os.path.join(model_dir, 'model.pdiparams')) - if device == 'GPU': - # initial GPU memory(M), device ID - config.enable_use_gpu(200, 0) - # optimize graph and fuse op - config.switch_ir_optim(True) - elif device == 'XPU': - config.enable_lite_engine() - config.enable_xpu(10 * 1024 * 1024) - else: - config.disable_gpu() - config.set_cpu_math_library_num_threads(cpu_threads) - if enable_mkldnn: - try: - # cache 10 different shapes for mkldnn to avoid memory leak - config.set_mkldnn_cache_capacity(10) - config.enable_mkldnn() - if enable_mkldnn_bfloat16: - config.enable_mkldnn_bfloat16() - except Exception as e: - print( - "The current environment does not support `mkldnn`, so disable mkldnn." - ) - pass - - precision_map = { - 'trt_int8': Config.Precision.Int8, - 'trt_fp32': Config.Precision.Float32, - 'trt_fp16': Config.Precision.Half - } - if run_mode in precision_map.keys(): - config.enable_tensorrt_engine( - workspace_size=(1 << 25) * batch_size, - max_batch_size=batch_size, - min_subgraph_size=min_subgraph_size, - precision_mode=precision_map[run_mode], - use_static=False, - use_calib_mode=trt_calib_mode) - - if use_dynamic_shape: - min_input_shape = { - 'image': [batch_size, 3, trt_min_shape, trt_min_shape] - } - max_input_shape = { - 'image': [batch_size, 3, trt_max_shape, trt_max_shape] - } - opt_input_shape = { - 'image': [batch_size, 3, trt_opt_shape, trt_opt_shape] - } - config.set_trt_dynamic_shape_info(min_input_shape, max_input_shape, - opt_input_shape) - print('trt set dynamic shape done!') - - # disable print log when predict - config.disable_glog_info() - # enable shared memory - config.enable_memory_optim() - # disable feed, fetch OP, needed by zero_copy_run - config.switch_use_feed_fetch_ops(False) - if delete_shuffle_pass: - config.delete_pass("shuffle_channel_detect_pass") - predictor = create_predictor(config) - return predictor - - -def predict_image(predictor, - image_file, - image_shape=[640, 640], - warmup=1, - repeats=1, - threshold=0.5, - arch='YOLOv5'): - img, scale_factor = image_preprocess(image_file, image_shape) - inputs = {} - if arch == 'YOLOv5': - inputs['x2paddle_images'] = img - input_names = predictor.get_input_names() - for i in range(len(input_names)): - input_tensor = predictor.get_input_handle(input_names[i]) - input_tensor.copy_from_cpu(inputs[input_names[i]]) - - for i in range(warmup): - predictor.run() - - np_boxes = None - predict_time = 0. - time_min = float("inf") - time_max = float('-inf') - for i in range(repeats): - start_time = time.time() - predictor.run() - output_names = predictor.get_output_names() - boxes_tensor = predictor.get_output_handle(output_names[0]) - np_boxes = boxes_tensor.copy_to_cpu() - end_time = time.time() - timed = end_time - start_time - time_min = min(time_min, timed) - time_max = max(time_max, timed) - predict_time += timed - - time_avg = predict_time / repeats - print('Inference time(ms): min={}, max={}, avg={}'.format( - round(time_min * 1000, 2), - round(time_max * 1000, 1), round(time_avg * 1000, 1))) - postprocess = YOLOv6PostProcess( - score_threshold=0.001, nms_threshold=0.65, multi_label=True) - res = postprocess(np_boxes, scale_factor) - res_img = draw_box( - image_file, res['bbox'], CLASS_LABEL, threshold=threshold) - cv2.imwrite('result.jpg', res_img) - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - parser.add_argument( - '--image_file', type=str, default=None, help="image path") - parser.add_argument( - '--model_path', type=str, help="inference model filepath") - parser.add_argument( - '--benchmark', - type=bool, - default=False, - help="Whether run benchmark or not.") - parser.add_argument( - '--run_mode', - type=str, - default='paddle', - help="mode of running(paddle/trt_fp32/trt_fp16/trt_int8)") - parser.add_argument( - '--device', - type=str, - default='GPU', - help="Choose the device you want to run, it can be: CPU/GPU/XPU, default is GPU" - ) - parser.add_argument('--img_shape', type=int, default=640, help="input_size") - args = parser.parse_args() - - predictor = load_predictor( - args.model_path, run_mode=args.run_mode, device=args.device) - warmup, repeats = 1, 1 - if args.benchmark: - warmup, repeats = 50, 100 - predict_image( - predictor, - args.image_file, - image_shape=[args.img_shape, args.img_shape], - warmup=warmup, - repeats=repeats) diff --git a/example/auto_compression/pytorch_yolov6/post_quant.py b/example/auto_compression/pytorch_yolov6/post_quant.py deleted file mode 100644 index aa4f5d8f..00000000 --- a/example/auto_compression/pytorch_yolov6/post_quant.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys -import numpy as np -import argparse -import paddle -from ppdet.core.workspace import load_config, merge_config -from ppdet.core.workspace import create -from ppdet.metrics import COCOMetric, VOCMetric -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config -from paddleslim.quant import quant_post_static - -from post_process import YOLOv6PostProcess - - -def argsparser(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - '--config_path', - type=str, - default=None, - help="path of compression strategy config.", - required=True) - parser.add_argument( - '--save_dir', - type=str, - default='ptq_out', - help="directory to save compressed model.") - parser.add_argument( - '--devices', - type=str, - default='gpu', - help="which device used to compress.") - parser.add_argument( - '--algo', type=str, default='KL', help="post quant algo.") - - return parser - - -def reader_wrapper(reader, input_list): - def gen(): - for data in reader: - in_dict = {} - if isinstance(input_list, list): - for input_name in input_list: - in_dict[input_name] = data[input_name] - elif isinstance(input_list, dict): - for input_name in input_list.keys(): - in_dict[input_list[input_name]] = data[input_name] - yield in_dict - - return gen - - -def main(): - global global_config - all_config = load_slim_config(FLAGS.config_path) - assert "Global" in all_config, f"Key 'Global' not found in config file. \n{all_config}" - global_config = all_config["Global"] - reader_cfg = load_config(global_config['reader_config']) - - train_loader = create('EvalReader')(reader_cfg['TrainDataset'], - reader_cfg['worker_num'], - return_list=True) - train_loader = reader_wrapper(train_loader, global_config['input_list']) - - place = paddle.CUDAPlace(0) if FLAGS.devices == 'gpu' else paddle.CPUPlace() - exe = paddle.static.Executor(place) - quant_post_static( - executor=exe, - model_dir=global_config["model_dir"], - quantize_model_path=FLAGS.save_dir, - data_loader=train_loader, - model_filename=global_config["model_filename"], - params_filename=global_config["params_filename"], - batch_size=32, - batch_nums=10, - algo=FLAGS.algo, - hist_percent=0.999, - is_full_quantize=False, - bias_correction=False, - onnx_format=False) - - -if __name__ == '__main__': - paddle.enable_static() - parser = argsparser() - FLAGS = parser.parse_args() - - assert FLAGS.devices in ['cpu', 'gpu', 'xpu', 'npu'] - paddle.set_device(FLAGS.devices) - - main() diff --git a/example/auto_compression/pytorch_yolov6/run.py b/example/auto_compression/pytorch_yolov6/run.py deleted file mode 100644 index 05fe7fdd..00000000 --- a/example/auto_compression/pytorch_yolov6/run.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys -import numpy as np -import argparse -import paddle -from ppdet.core.workspace import load_config, merge_config -from ppdet.core.workspace import create -from ppdet.metrics import COCOMetric, VOCMetric -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config -from paddleslim.auto_compression import AutoCompression - -from post_process import YOLOv6PostProcess - - -def argsparser(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - '--config_path', - type=str, - default=None, - help="path of compression strategy config.", - required=True) - parser.add_argument( - '--save_dir', - type=str, - default='output', - help="directory to save compressed model.") - parser.add_argument( - '--devices', - type=str, - default='gpu', - help="which device used to compress.") - parser.add_argument( - '--eval', type=bool, default=False, help="whether to run evaluation.") - - return parser - - -def reader_wrapper(reader, input_list): - def gen(): - for data in reader: - in_dict = {} - if isinstance(input_list, list): - for input_name in input_list: - in_dict[input_name] = data[input_name] - elif isinstance(input_list, dict): - for input_name in input_list.keys(): - in_dict[input_list[input_name]] = data[input_name] - yield in_dict - - return gen - - -def convert_numpy_data(data, metric): - data_all = {} - data_all = {k: np.array(v) for k, v in data.items()} - if isinstance(metric, VOCMetric): - for k, v in data_all.items(): - if not isinstance(v[0], np.ndarray): - tmp_list = [] - for t in v: - tmp_list.append(np.array(t)) - data_all[k] = np.array(tmp_list) - else: - data_all = {k: np.array(v) for k, v in data.items()} - return data_all - - -def eval_function(exe, compiled_test_program, test_feed_names, test_fetch_list): - metric = global_config['metric'] - for batch_id, data in enumerate(val_loader): - data_all = convert_numpy_data(data, metric) - data_input = {} - for k, v in data.items(): - if isinstance(global_config['input_list'], list): - if k in test_feed_names: - data_input[k] = np.array(v) - elif isinstance(global_config['input_list'], dict): - if k in global_config['input_list'].keys(): - data_input[global_config['input_list'][k]] = np.array(v) - outs = exe.run(compiled_test_program, - feed=data_input, - fetch_list=test_fetch_list, - return_numpy=False) - res = {} - if 'arch' in global_config and global_config['arch'] == 'YOLOv6': - postprocess = YOLOv6PostProcess( - score_threshold=0.001, nms_threshold=0.65, multi_label=True) - res = postprocess(np.array(outs[0]), data_all['scale_factor']) - else: - for out in outs: - v = np.array(out) - if len(v.shape) > 1: - res['bbox'] = v - else: - res['bbox_num'] = v - - metric.update(data_all, 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] - - -def main(): - global global_config - all_config = load_slim_config(FLAGS.config_path) - assert "Global" in all_config, f"Key 'Global' not found in config file. \n{all_config}" - global_config = all_config["Global"] - reader_cfg = load_config(global_config['reader_config']) - - train_loader = create('EvalReader')(reader_cfg['TrainDataset'], - reader_cfg['worker_num'], - return_list=True) - train_loader = reader_wrapper(train_loader, global_config['input_list']) - - if 'Evaluation' in global_config.keys() and global_config[ - 'Evaluation'] and paddle.distributed.get_rank() == 0: - eval_func = eval_function - dataset = reader_cfg['EvalDataset'] - global val_loader - _eval_batch_sampler = paddle.io.BatchSampler( - dataset, batch_size=reader_cfg['EvalReader']['batch_size']) - val_loader = create('EvalReader')(dataset, - reader_cfg['worker_num'], - batch_sampler=_eval_batch_sampler, - return_list=True) - metric = None - if reader_cfg['metric'] == 'COCO': - clsid2catid = {v: k for k, v in dataset.catid2clsid.items()} - anno_file = dataset.get_anno() - metric = COCOMetric( - anno_file=anno_file, clsid2catid=clsid2catid, IouType='bbox') - elif reader_cfg['metric'] == 'VOC': - metric = VOCMetric( - label_list=dataset.get_label_list(), - class_num=reader_cfg['num_classes'], - map_type=reader_cfg['map_type']) - else: - raise ValueError("metric currently only supports COCO and VOC.") - global_config['metric'] = metric - else: - eval_func = None - - ac = AutoCompression( - model_dir=global_config["model_dir"], - model_filename=global_config["model_filename"], - params_filename=global_config["params_filename"], - save_dir=FLAGS.save_dir, - config=all_config, - train_dataloader=train_loader, - eval_callback=eval_func) - ac.compress() - - -if __name__ == '__main__': - paddle.enable_static() - parser = argsparser() - FLAGS = parser.parse_args() - - assert FLAGS.devices in ['cpu', 'gpu', 'xpu', 'npu'] - paddle.set_device(FLAGS.devices) - - main() diff --git a/example/auto_compression/pytorch_yolov7/README.md b/example/auto_compression/pytorch_yolov7/README.md deleted file mode 100644 index 7dddf99b..00000000 --- a/example/auto_compression/pytorch_yolov7/README.md +++ /dev/null @@ -1,152 +0,0 @@ -# YOLOv7自动压缩示例 - -目录: -- [1.简介](#1简介) -- [2.Benchmark](#2Benchmark) -- [3.开始自动压缩](#自动压缩流程) - - [3.1 环境准备](#31-准备环境) - - [3.2 准备数据集](#32-准备数据集) - - [3.3 准备预测模型](#33-准备预测模型) - - [3.4 测试模型精度](#34-测试模型精度) - - [3.5 自动压缩并产出模型](#35-自动压缩并产出模型) -- [4.预测部署](#4预测部署) -- [5.FAQ](5FAQ) - -## 1. 简介 - -飞桨模型转换工具[X2Paddle](https://github.com/PaddlePaddle/X2Paddle)支持将```Caffe/TensorFlow/ONNX/PyTorch```的模型一键转为飞桨(PaddlePaddle)的预测模型。借助X2Paddle的能力,各种框架的推理模型可以很方便的使用PaddleSlim的自动化压缩功能。 - -本示例将以[WongKinYiu/yolov7](https://github.com/WongKinYiu/yolov7)目标检测模型为例,将PyTorch框架模型转换为Paddle框架模型,再使用ACT自动压缩功能进行自动压缩。本示例使用的自动压缩策略为量化训练。 - -## 2.Benchmark - -| 模型 | 策略 | 输入尺寸 | mAPval
0.5:0.95 | 预测时延FP32
(ms) |预测时延FP16
(ms) | 预测时延INT8
(ms) | 配置文件 | Inference模型 | -| :-------- |:-------- |:--------: | :---------------------: | :----------------: | :----------------: | :---------------: | :-----------------------------: | :-----------------------------: | -| YOLOv7 | Base模型 | 640*640 | 51.1 | 26.84ms | 7.44ms | - | - | [Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov7_infer.tar) | -| YOLOv7 | KL离线量化 | 640*640 | 50.2 | - | - | 4.55ms | - | - | -| YOLOv7 | 量化蒸馏训练 | 640*640 | **50.8** | - | - | **4.55ms** | [config](./configs/yolov7_qat_dis.yaml) | [Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov7_quant.tar) | - -说明: -- mAP的指标均在COCO val2017数据集中评测得到。 -- YOLOv7模型在Tesla T4的GPU环境下开启TensorRT 8.4.1,batch_size=1, 测试脚本是[cpp_infer](./cpp_infer)。 - -## 3. 自动压缩流程 - -#### 3.1 准备环境 -- PaddlePaddle >= 2.3 (可从[Paddle官网](https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/linux-pip.html)下载安装) -- PaddleSlim > 2.3版本 -- PaddleDet >= 2.4 -- [X2Paddle](https://github.com/PaddlePaddle/X2Paddle) >= 1.3.6 -- opencv-python - -(1)安装paddlepaddle: -```shell -# CPU -pip install paddlepaddle -# GPU -pip install paddlepaddle-gpu -``` - -(2)安装paddleslim: -```shell -pip install paddleslim -``` - -(3)安装paddledet: -```shell -pip install paddledet -``` - -注:安装PaddleDet的目的只是为了直接使用PaddleDetection中的Dataloader组件。 - -(4)安装X2Paddle的1.3.6以上版本: -```shell -pip install x2paddle sympy onnx -``` - -#### 3.2 准备数据集 - -本案例默认以COCO数据进行自动压缩实验,并且依赖PaddleDetection中数据读取模块,如果自定义COCO数据,或者其他格式数据,请参考[PaddleDetection数据准备文档](https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.4/docs/tutorials/PrepareDataSet.md) 来准备数据。 - -如果已经准备好数据集,请直接修改[./configs/yolov7_reader.yml]中`EvalDataset`的`dataset_dir`字段为自己数据集路径即可。 - - -#### 3.3 准备预测模型 - -(1)准备ONNX模型: - -可通过[WongKinYiu/yolov7](https://github.com/WongKinYiu/yolov7)的导出脚本来准备ONNX模型,具体步骤如下: -```shell -git clone https://github.com/WongKinYiu/yolov7.git -# 切换分支到u5分支,保持导出的ONNX模型后处理和YOLOv5一致 -git checkout u5 -# 下载好yolov7.pt权重后执行: -python export.py --weights yolov7.pt --include onnx -``` - -也可以直接下载我们已经准备好的[yolov7.onnx](https://paddle-slim-models.bj.bcebos.com/act/yolov7.onnx)。 - - -(2) 转换模型: -``` -x2paddle --framework=onnx --model=yolov7.onnx --save_dir=pd_model -cp -r pd_model/inference_model/ yolov7_infer -``` -即可得到YOLOv7模型的预测模型(`model.pdmodel` 和 `model.pdiparams`)。如想快速体验,可直接下载上方表格中YOLOv7的[Paddle预测模型](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov7_infer.tar)。 - - -预测模型的格式为:`model.pdmodel` 和 `model.pdiparams`两个,带`pdmodel`的是模型文件,带`pdiparams`后缀的是权重文件。 - - -#### 3.4 自动压缩并产出模型 - -蒸馏量化自动压缩示例通过run.py脚本启动,会使用接口```paddleslim.auto_compression.AutoCompression```对模型进行自动压缩。配置config文件中模型路径、蒸馏、量化、和训练等部分的参数,配置完成后便可对模型进行量化和蒸馏。具体运行命令为: - -- 单卡训练: -``` -export CUDA_VISIBLE_DEVICES=0 -python run.py --config_path=./configs/yolov7_qat_dis.yaml --save_dir='./output/' -``` - -- 多卡训练: -``` -CUDA_VISIBLE_DEVICES=0,1,2,3 python -m paddle.distributed.launch --log_dir=log --gpus 0,1,2,3 run.py \ - --config_path=./configs/yolov7_qat_dis.yaml --save_dir='./output/' -``` - -#### 3.5 测试模型精度 - -修改[yolov7_qat_dis.yaml](./configs/yolov7_qat_dis.yaml)中`model_dir`字段为模型存储路径,然后使用eval.py脚本得到模型的mAP: -``` -export CUDA_VISIBLE_DEVICES=0 -python eval.py --config_path=./configs/yolov7_qat_dis.yaml -``` - - -## 4.预测部署 - -#### Paddle-TensorRT C++部署 - -进入[cpp_infer](./cpp_infer)文件夹内,请按照[C++ TensorRT Benchmark测试教程](./cpp_infer/README.md)进行准备环境及编译,然后开始测试: -```shell -# 编译 -bash complie.sh -# 执行 -./build/trt_run --model_file yolov7_quant/model.pdmodel --params_file yolov7_quant/model.pdiparams --run_mode=trt_int8 -``` - -#### Paddle-TensorRT Python部署: - -首先安装带有TensorRT的[Paddle安装包](https://www.paddlepaddle.org.cn/inference/v2.3/user_guides/download_lib.html#python)。 - -然后使用[paddle_trt_infer.py](./paddle_trt_infer.py)进行部署: -```shell -python paddle_trt_infer.py --model_path=output --image_file=images/000000570688.jpg --benchmark=True --run_mode=trt_int8 -``` - -## 5.FAQ - -- 如果想测试离线量化模型精度,可执行: -```shell -python post_quant.py --config_path=./configs/yolov7_qat_dis.yaml -``` diff --git a/example/auto_compression/pytorch_yolov7/configs/yolov7_reader.yaml b/example/auto_compression/pytorch_yolov7/configs/yolov7_reader.yaml deleted file mode 100644 index cb87c3f8..00000000 --- a/example/auto_compression/pytorch_yolov7/configs/yolov7_reader.yaml +++ /dev/null @@ -1,27 +0,0 @@ -metric: COCO -num_classes: 80 - -# Datset configuration -TrainDataset: - !COCODataSet - image_dir: train2017 - anno_path: annotations/instances_train2017.json - dataset_dir: dataset/coco/ - -EvalDataset: - !COCODataSet - image_dir: val2017 - anno_path: annotations/instances_val2017.json - dataset_dir: dataset/coco/ - -worker_num: 0 - -# preprocess reader in test -EvalReader: - sample_transforms: - - Decode: {} - - Resize: {target_size: [640, 640], keep_ratio: True} - - Pad: {size: [640, 640], fill_value: [114., 114., 114.]} - - NormalizeImage: {mean: [0, 0, 0], std: [1, 1, 1], is_scale: True} - - Permute: {} - batch_size: 1 diff --git a/example/auto_compression/pytorch_yolov7/cpp_infer/CMakeLists.txt b/example/auto_compression/pytorch_yolov7/cpp_infer/CMakeLists.txt deleted file mode 100644 index d5307c65..00000000 --- a/example/auto_compression/pytorch_yolov7/cpp_infer/CMakeLists.txt +++ /dev/null @@ -1,263 +0,0 @@ -cmake_minimum_required(VERSION 3.0) -project(cpp_inference_demo CXX C) -option(WITH_MKL "Compile demo with MKL/OpenBlas support, default use MKL." ON) -option(WITH_GPU "Compile demo with GPU/CPU, default use CPU." OFF) -option(WITH_STATIC_LIB "Compile demo with static/shared library, default use static." ON) -option(USE_TENSORRT "Compile demo with TensorRT." OFF) -option(WITH_ROCM "Compile demo with rocm." OFF) -option(WITH_ONNXRUNTIME "Compile demo with ONNXRuntime" OFF) -option(WITH_ARM "Compile demo with ARM" OFF) -option(WITH_MIPS "Compile demo with MIPS" OFF) -option(WITH_SW "Compile demo with SW" OFF) -option(WITH_XPU "Compile demow ith xpu" OFF) -option(WITH_NPU "Compile demow ith npu" OFF) - -if(NOT WITH_STATIC_LIB) - add_definitions("-DPADDLE_WITH_SHARED_LIB") -else() - # PD_INFER_DECL is mainly used to set the dllimport/dllexport attribute in dynamic library mode. - # Set it to empty in static library mode to avoid compilation issues. - add_definitions("/DPD_INFER_DECL=") -endif() - -macro(safe_set_static_flag) - foreach(flag_var - CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif(${flag_var} MATCHES "/MD") - endforeach(flag_var) -endmacro() - -if(NOT DEFINED PADDLE_LIB) - message(FATAL_ERROR "please set PADDLE_LIB with -DPADDLE_LIB=/path/paddle/lib") -endif() -if(NOT DEFINED DEMO_NAME) - message(FATAL_ERROR "please set DEMO_NAME with -DDEMO_NAME=demo_name") -endif() - -include_directories("${PADDLE_LIB}/") -set(PADDLE_LIB_THIRD_PARTY_PATH "${PADDLE_LIB}/third_party/install/") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}protobuf/include") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}glog/include") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}gflags/include") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}xxhash/include") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}cryptopp/include") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}onnxruntime/include") -include_directories("${PADDLE_LIB_THIRD_PARTY_PATH}paddle2onnx/include") - -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}protobuf/lib") -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}glog/lib") -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}gflags/lib") -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}xxhash/lib") -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}cryptopp/lib") -link_directories("${PADDLE_LIB}/paddle/lib") -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}onnxruntime/lib") -link_directories("${PADDLE_LIB_THIRD_PARTY_PATH}paddle2onnx/lib") - -if (WIN32) - add_definitions("/DGOOGLE_GLOG_DLL_DECL=") - option(MSVC_STATIC_CRT "use static C Runtime library by default" ON) - if (MSVC_STATIC_CRT) - if (WITH_MKL) - set(FLAG_OPENMP "/openmp") - endif() - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /bigobj /MTd ${FLAG_OPENMP}") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /bigobj /MT ${FLAG_OPENMP}") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj /MTd ${FLAG_OPENMP}") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /bigobj /MT ${FLAG_OPENMP}") - safe_set_static_flag() - if (WITH_STATIC_LIB) - add_definitions(-DSTATIC_LIB) - endif() - endif() -else() - if(WITH_MKL) - set(FLAG_OPENMP "-fopenmp") - endif() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ${FLAG_OPENMP}") -endif() - -if(WITH_GPU) - if(NOT WIN32) - include_directories("/usr/local/cuda/include") - if(CUDA_LIB STREQUAL "") - set(CUDA_LIB "/usr/local/cuda/lib64/" CACHE STRING "CUDA Library") - endif() - else() - include_directories("C:\\Program\ Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v8.0\\include") - if(CUDA_LIB STREQUAL "") - set(CUDA_LIB "C:\\Program\ Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v8.0\\lib\\x64") - endif() - endif(NOT WIN32) -endif() - -if (USE_TENSORRT AND WITH_GPU) - set(TENSORRT_ROOT "" CACHE STRING "The root directory of TensorRT library") - if("${TENSORRT_ROOT}" STREQUAL "") - message(FATAL_ERROR "The TENSORRT_ROOT is empty, you must assign it a value with CMake command. Such as: -DTENSORRT_ROOT=TENSORRT_ROOT_PATH ") - endif() - set(TENSORRT_INCLUDE_DIR ${TENSORRT_ROOT}/include) - set(TENSORRT_LIB_DIR ${TENSORRT_ROOT}/lib) - file(READ ${TENSORRT_INCLUDE_DIR}/NvInfer.h TENSORRT_VERSION_FILE_CONTENTS) - string(REGEX MATCH "define NV_TENSORRT_MAJOR +([0-9]+)" TENSORRT_MAJOR_VERSION - "${TENSORRT_VERSION_FILE_CONTENTS}") - if("${TENSORRT_MAJOR_VERSION}" STREQUAL "") - file(READ ${TENSORRT_INCLUDE_DIR}/NvInferVersion.h TENSORRT_VERSION_FILE_CONTENTS) - string(REGEX MATCH "define NV_TENSORRT_MAJOR +([0-9]+)" TENSORRT_MAJOR_VERSION - "${TENSORRT_VERSION_FILE_CONTENTS}") - endif() - if("${TENSORRT_MAJOR_VERSION}" STREQUAL "") - message(SEND_ERROR "Failed to detect TensorRT version.") - endif() - string(REGEX REPLACE "define NV_TENSORRT_MAJOR +([0-9]+)" "\\1" - TENSORRT_MAJOR_VERSION "${TENSORRT_MAJOR_VERSION}") - message(STATUS "Current TensorRT header is ${TENSORRT_INCLUDE_DIR}/NvInfer.h. " - "Current TensorRT version is v${TENSORRT_MAJOR_VERSION}. ") - include_directories("${TENSORRT_INCLUDE_DIR}") - link_directories("${TENSORRT_LIB_DIR}") -endif() - -if(WITH_MKL) - set(MATH_LIB_PATH "${PADDLE_LIB_THIRD_PARTY_PATH}mklml") - include_directories("${MATH_LIB_PATH}/include") - if(WIN32) - set(MATH_LIB ${MATH_LIB_PATH}/lib/mklml${CMAKE_STATIC_LIBRARY_SUFFIX} - ${MATH_LIB_PATH}/lib/libiomp5md${CMAKE_STATIC_LIBRARY_SUFFIX}) - else() - set(MATH_LIB ${MATH_LIB_PATH}/lib/libmklml_intel${CMAKE_SHARED_LIBRARY_SUFFIX} - ${MATH_LIB_PATH}/lib/libiomp5${CMAKE_SHARED_LIBRARY_SUFFIX}) - endif() - set(MKLDNN_PATH "${PADDLE_LIB_THIRD_PARTY_PATH}mkldnn") - if(EXISTS ${MKLDNN_PATH}) - include_directories("${MKLDNN_PATH}/include") - if(WIN32) - set(MKLDNN_LIB ${MKLDNN_PATH}/lib/mkldnn.lib) - else(WIN32) - set(MKLDNN_LIB ${MKLDNN_PATH}/lib/libmkldnn.so.0) - endif(WIN32) - endif() -elseif((NOT WITH_MIPS) AND (NOT WITH_SW)) - set(OPENBLAS_LIB_PATH "${PADDLE_LIB_THIRD_PARTY_PATH}openblas") - include_directories("${OPENBLAS_LIB_PATH}/include/openblas") - if(WIN32) - set(MATH_LIB ${OPENBLAS_LIB_PATH}/lib/openblas${CMAKE_STATIC_LIBRARY_SUFFIX}) - else() - set(MATH_LIB ${OPENBLAS_LIB_PATH}/lib/libopenblas${CMAKE_STATIC_LIBRARY_SUFFIX}) - endif() -endif() - -if(WITH_STATIC_LIB) - set(DEPS ${PADDLE_LIB}/paddle/lib/libpaddle_inference${CMAKE_STATIC_LIBRARY_SUFFIX}) -else() - if(WIN32) - set(DEPS ${PADDLE_LIB}/paddle/lib/paddle_inference${CMAKE_STATIC_LIBRARY_SUFFIX}) - else() - set(DEPS ${PADDLE_LIB}/paddle/lib/libpaddle_inference${CMAKE_SHARED_LIBRARY_SUFFIX}) - endif() -endif() - -if (WITH_ONNXRUNTIME) - if(WIN32) - set(DEPS ${DEPS} ${PADDLE_LIB_THIRD_PARTY_PATH}onnxruntime/lib/onnxruntime.lib paddle2onnx) - elseif(APPLE) - set(DEPS ${DEPS} ${PADDLE_LIB_THIRD_PARTY_PATH}onnxruntime/lib/libonnxruntime.1.10.0.dylib paddle2onnx) - else() - set(DEPS ${DEPS} ${PADDLE_LIB_THIRD_PARTY_PATH}onnxruntime/lib/libonnxruntime.so.1.10.0 paddle2onnx) - endif() -endif() - -if (NOT WIN32) - set(EXTERNAL_LIB "-lrt -ldl -lpthread") - set(DEPS ${DEPS} - ${MATH_LIB} ${MKLDNN_LIB} - glog gflags protobuf xxhash cryptopp - ${EXTERNAL_LIB}) -else() - set(DEPS ${DEPS} - ${MATH_LIB} ${MKLDNN_LIB} - glog gflags_static libprotobuf xxhash cryptopp-static ${EXTERNAL_LIB}) - set(DEPS ${DEPS} shlwapi.lib) -endif(NOT WIN32) - -if(WITH_GPU) - if(NOT WIN32) - if (USE_TENSORRT) - set(DEPS ${DEPS} ${TENSORRT_LIB_DIR}/libnvinfer${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${TENSORRT_LIB_DIR}/libnvinfer_plugin${CMAKE_SHARED_LIBRARY_SUFFIX}) - endif() - set(DEPS ${DEPS} ${CUDA_LIB}/libcudart${CMAKE_SHARED_LIBRARY_SUFFIX}) - else() - if(USE_TENSORRT) - set(DEPS ${DEPS} ${TENSORRT_LIB_DIR}/nvinfer${CMAKE_STATIC_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${TENSORRT_LIB_DIR}/nvinfer_plugin${CMAKE_STATIC_LIBRARY_SUFFIX}) - if(${TENSORRT_MAJOR_VERSION} GREATER_EQUAL 7) - set(DEPS ${DEPS} ${TENSORRT_LIB_DIR}/myelin64_1${CMAKE_STATIC_LIBRARY_SUFFIX}) - endif() - endif() - set(DEPS ${DEPS} ${CUDA_LIB}/cudart${CMAKE_STATIC_LIBRARY_SUFFIX} ) - set(DEPS ${DEPS} ${CUDA_LIB}/cublas${CMAKE_STATIC_LIBRARY_SUFFIX} ) - set(DEPS ${DEPS} ${CUDA_LIB}/cudnn${CMAKE_STATIC_LIBRARY_SUFFIX} ) - endif() -endif() - -if(WITH_ROCM AND NOT WIN32) - set(DEPS ${DEPS} ${ROCM_LIB}/libamdhip64${CMAKE_SHARED_LIBRARY_SUFFIX}) -endif() - -if(WITH_XPU AND NOT WIN32) - set(XPU_INSTALL_PATH "${PADDLE_LIB_THIRD_PARTY_PATH}xpu") - set(DEPS ${DEPS} ${XPU_INSTALL_PATH}/lib/libxpuapi${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${XPU_INSTALL_PATH}/lib/libxpurt${CMAKE_SHARED_LIBRARY_SUFFIX}) -endif() - -if(WITH_NPU AND NOT WIN32) - set(DEPS ${DEPS} ${ASCEND_DIR}/ascend-toolkit/latest/fwkacllib/lib64/libgraph${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${ASCEND_DIR}/ascend-toolkit/latest/fwkacllib/lib64/libge_runner${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${ASCEND_DIR}/ascend-toolkit/latest/fwkacllib/lib64/libascendcl${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${ASCEND_DIR}/ascend-toolkit/latest/fwkacllib/lib64/libascendcl${CMAKE_SHARED_LIBRARY_SUFFIX}) - set(DEPS ${DEPS} ${ASCEND_DIR}/ascend-toolkit/latest/fwkacllib/lib64/libacl_op_compiler${CMAKE_SHARED_LIBRARY_SUFFIX}) -endif() - -add_executable(${DEMO_NAME} ${DEMO_NAME}.cc) -target_link_libraries(${DEMO_NAME} ${DEPS}) -if(WIN32) - if(USE_TENSORRT) - add_custom_command(TARGET ${DEMO_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${TENSORRT_LIB_DIR}/nvinfer${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE} - COMMAND ${CMAKE_COMMAND} -E copy ${TENSORRT_LIB_DIR}/nvinfer_plugin${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE} - ) - if(${TENSORRT_MAJOR_VERSION} GREATER_EQUAL 7) - add_custom_command(TARGET ${DEMO_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${TENSORRT_LIB_DIR}/myelin64_1${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}) - endif() - endif() - if(WITH_MKL) - add_custom_command(TARGET ${DEMO_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${MATH_LIB_PATH}/lib/mklml.dll ${CMAKE_BINARY_DIR}/Release - COMMAND ${CMAKE_COMMAND} -E copy ${MATH_LIB_PATH}/lib/libiomp5md.dll ${CMAKE_BINARY_DIR}/Release - COMMAND ${CMAKE_COMMAND} -E copy ${MKLDNN_PATH}/lib/mkldnn.dll ${CMAKE_BINARY_DIR}/Release - ) - else() - add_custom_command(TARGET ${DEMO_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${OPENBLAS_LIB_PATH}/lib/openblas.dll ${CMAKE_BINARY_DIR}/Release - ) - endif() - if(WITH_ONNXRUNTIME) - add_custom_command(TARGET ${DEMO_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${PADDLE_LIB_THIRD_PARTY_PATH}onnxruntime/lib/onnxruntime.dll - ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE} - COMMAND ${CMAKE_COMMAND} -E copy ${PADDLE_LIB_THIRD_PARTY_PATH}paddle2onnx/lib/paddle2onnx.dll - ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE} - ) - endif() - if(NOT WITH_STATIC_LIB) - add_custom_command(TARGET ${DEMO_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy "${PADDLE_LIB}/paddle/lib/paddle_inference.dll" ${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE} - ) - endif() -endif() diff --git a/example/auto_compression/pytorch_yolov7/cpp_infer/README.md b/example/auto_compression/pytorch_yolov7/cpp_infer/README.md deleted file mode 100644 index 04c1c23b..00000000 --- a/example/auto_compression/pytorch_yolov7/cpp_infer/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# YOLOv7 TensorRT Benchmark测试(Linux) - -## 环境准备 - -- CUDA、CUDNN:确认环境中已经安装CUDA和CUDNN,并且提前获取其安装路径。 - -- TensorRT:可通过NVIDIA官网下载[TensorRT 8.4.1.5](https://developer.nvidia.com/compute/machine-learning/tensorrt/secure/8.4.1/tars/tensorrt-8.4.1.5.linux.x86_64-gnu.cuda-11.6.cudnn8.4.tar.gz)或其他版本安装包。 - -- Paddle Inference C++预测库:编译develop版本请参考[编译文档](https://www.paddlepaddle.org.cn/inference/user_guides/source_compile.html)。编译完成后,会在build目录下生成`paddle_inference_install_dir`文件夹,这个就是我们需要的C++预测库文件。 - -## 编译可执行程序 - -- (1)修改`compile.sh`中依赖库路径,主要是以下内容: -```shell -# Paddle Inference预测库路径 -LIB_DIR=/root/auto_compress/Paddle/build/paddle_inference_install_dir/ -# CUDNN路径 -CUDNN_LIB=/usr/lib/x86_64-linux-gnu/ -# CUDA路径 -CUDA_LIB=/usr/local/cuda/lib64 -# TensorRT安装包路径,为TRT资源包解压完成后的绝对路径,其中包含`lib`和`include`文件夹 -TENSORRT_ROOT=/root/auto_compress/trt/trt8.4/ -``` - -## 测试 - -- FP32 -``` -./build/trt_run --model_file yolov7_infer/model.pdmodel --params_file yolov7_infer/model.pdiparams --run_mode=trt_fp32 -``` - -- FP16 -``` -./build/trt_run --model_file yolov7_infer/model.pdmodel --params_file yolov7_infer/model.pdiparams --run_mode=trt_fp16 -``` - -- INT8 -``` -./build/trt_run --model_file yolov7_quant/model.pdmodel --params_file yolov7_quant/model.pdiparams --run_mode=trt_int8 -``` - -## 性能对比 - -| 预测库 | 模型 | 预测时延FP32
(ms) |预测时延FP16
(ms) | 预测时延INT8
(ms) | -| :--------: | :--------: |:-------- |:--------: | :---------------------: | -| Paddle TensorRT | YOLOv7 | 26.84ms | 7.44ms | 4.55ms | -| TensorRT | YOLOv7 | 28.25ms | 7.23ms | 4.67ms | - -环境: -- Tesla T4,TensorRT 8.4.1,CUDA 11.2 -- batch_size=1 diff --git a/example/auto_compression/pytorch_yolov7/cpp_infer/compile.sh b/example/auto_compression/pytorch_yolov7/cpp_infer/compile.sh deleted file mode 100644 index afff924b..00000000 --- a/example/auto_compression/pytorch_yolov7/cpp_infer/compile.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -set +x -set -e - -work_path=$(dirname $(readlink -f $0)) - -mkdir -p build -cd build -rm -rf * - -DEMO_NAME=trt_run - -WITH_MKL=ON -WITH_GPU=ON -USE_TENSORRT=ON - -LIB_DIR=/root/auto_compress/Paddle/build/paddle_inference_install_dir/ -CUDNN_LIB=/usr/lib/x86_64-linux-gnu/ -CUDA_LIB=/usr/local/cuda/lib64 -TENSORRT_ROOT=/root/auto_compress/trt/trt8.4/ - -WITH_ROCM=OFF -ROCM_LIB=/opt/rocm/lib - -cmake .. -DPADDLE_LIB=${LIB_DIR} \ - -DWITH_MKL=${WITH_MKL} \ - -DDEMO_NAME=${DEMO_NAME} \ - -DWITH_GPU=${WITH_GPU} \ - -DWITH_STATIC_LIB=OFF \ - -DUSE_TENSORRT=${USE_TENSORRT} \ - -DWITH_ROCM=${WITH_ROCM} \ - -DROCM_LIB=${ROCM_LIB} \ - -DCUDNN_LIB=${CUDNN_LIB} \ - -DCUDA_LIB=${CUDA_LIB} \ - -DTENSORRT_ROOT=${TENSORRT_ROOT} - -make -j diff --git a/example/auto_compression/pytorch_yolov7/eval.py b/example/auto_compression/pytorch_yolov7/eval.py deleted file mode 100644 index 478c4e1a..00000000 --- a/example/auto_compression/pytorch_yolov7/eval.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys -import numpy as np -import argparse -import paddle -from ppdet.core.workspace import load_config, merge_config -from ppdet.core.workspace import create -from ppdet.metrics import COCOMetric, VOCMetric -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config - -from post_process import YOLOv7PostProcess - - -def argsparser(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - '--config_path', - type=str, - default=None, - help="path of compression strategy config.", - required=True) - parser.add_argument( - '--devices', - type=str, - default='gpu', - help="which device used to compress.") - - return parser - - -def reader_wrapper(reader, input_list): - def gen(): - for data in reader: - in_dict = {} - if isinstance(input_list, list): - for input_name in input_list: - in_dict[input_name] = data[input_name] - elif isinstance(input_list, dict): - for input_name in input_list.keys(): - in_dict[input_list[input_name]] = data[input_name] - yield in_dict - - return gen - - -def convert_numpy_data(data, metric): - data_all = {} - data_all = {k: np.array(v) for k, v in data.items()} - if isinstance(metric, VOCMetric): - for k, v in data_all.items(): - if not isinstance(v[0], np.ndarray): - tmp_list = [] - for t in v: - tmp_list.append(np.array(t)) - data_all[k] = np.array(tmp_list) - else: - data_all = {k: np.array(v) for k, v in data.items()} - return data_all - - -def eval(): - - place = paddle.CUDAPlace(0) if FLAGS.devices == 'gpu' else paddle.CPUPlace() - exe = paddle.static.Executor(place) - - val_program, feed_target_names, fetch_targets = paddle.static.load_inference_model( - global_config["model_dir"], - exe, - model_filename=global_config["model_filename"], - params_filename=global_config["params_filename"]) - print('Loaded model from: {}'.format(global_config["model_dir"])) - - metric = global_config['metric'] - for batch_id, data in enumerate(val_loader): - data_all = convert_numpy_data(data, metric) - data_input = {} - for k, v in data.items(): - if isinstance(global_config['input_list'], list): - if k in global_config['input_list']: - data_input[k] = np.array(v) - elif isinstance(global_config['input_list'], dict): - if k in global_config['input_list'].keys(): - data_input[global_config['input_list'][k]] = np.array(v) - outs = exe.run(val_program, - feed=data_input, - fetch_list=fetch_targets, - return_numpy=False) - res = {} - postprocess = YOLOv7PostProcess( - score_threshold=0.001, nms_threshold=0.65, multi_label=True) - res = postprocess(np.array(outs[0]), data_all['scale_factor']) - metric.update(data_all, res) - if batch_id % 100 == 0: - print('Eval iter:', batch_id) - metric.accumulate() - metric.log() - metric.reset() - - -def main(): - global global_config - all_config = load_slim_config(FLAGS.config_path) - global_config = all_config["Global"] - reader_cfg = load_config(global_config['reader_config']) - - dataset = reader_cfg['EvalDataset'] - global val_loader - val_loader = create('EvalReader')(reader_cfg['EvalDataset'], - reader_cfg['worker_num'], - return_list=True) - metric = None - if reader_cfg['metric'] == 'COCO': - clsid2catid = {v: k for k, v in dataset.catid2clsid.items()} - anno_file = dataset.get_anno() - metric = COCOMetric( - anno_file=anno_file, clsid2catid=clsid2catid, IouType='bbox') - elif reader_cfg['metric'] == 'VOC': - metric = VOCMetric( - label_list=dataset.get_label_list(), - class_num=reader_cfg['num_classes'], - map_type=reader_cfg['map_type']) - else: - raise ValueError("metric currently only supports COCO and VOC.") - global_config['metric'] = metric - - eval() - - -if __name__ == '__main__': - paddle.enable_static() - parser = argsparser() - FLAGS = parser.parse_args() - - assert FLAGS.devices in ['cpu', 'gpu', 'xpu', 'npu'] - paddle.set_device(FLAGS.devices) - - main() diff --git a/example/auto_compression/pytorch_yolov7/images/000000570688.jpg b/example/auto_compression/pytorch_yolov7/images/000000570688.jpg deleted file mode 100644 index cb304bd56c4010c08611a30dcca58ea9140cea54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138365 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�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%0D4VMGW#Js4Aj+!>|GYQ+%PHsS*#KMEBnG$Kv zeYBe|RHu~_KGeH1i^<((2T8@IxCrrn^4@vmTu*|#RfvgMw5OcbnO$We$&0iu&*i>-khE6Hy^%Hm&q>*SC zCOh6a4Yb02R1^`!WSeMIU<;seJ8Sx@iIPnVNM27q6+~P48 zpua;#7Ee(Hcme3d#JL2dV1PAqM8{t^Nb-ipM*~B>F9Y)t0x&CKodfEk#A$Z@Y<`yp zsR5QD%-hj~AH4Fg65db3Gk~HDMDyaj6{X$q} zM*P&8f4rF?cEVLAjM2J0xE({YFd3$izICo$&$+BoTbi7%1`{OTC=&$cv9K^cra44Q z1iVRxZG$|9aCjkRIa#4`x~2#I1{q%?>!Zqa@kOtR>eAPQaSSv!z$1JcKSr?hIL*WH zDqF_|5@efitGiL>URhzJFc@uX&c~Z%ru?}B7hSS7{<=G79s=Q-)`2~U2F{lhQ)34{ z;z5-k9QWL6A6b5ys@hISH2T

#oy!`N);SsOiWwvC-xPQkZb#0s1vjtn2!IgMJ5c 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

KgbW0626^0HI!q^+J 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

xGbeq`b*lQpge4|)Ru{Kft<-09=sS z1UU`~$pgp($?ib^01=_Yi{+ApXyk%=#Qb|q@spQ$*?FHH?c3|;LxY;wrwCqI5D6VY zzy#oAAAhLrsf`e&TQ6nQUTWzorlF{=wi4yih2t_FAd(1X97kc3><+5&xYkf>nAZlr z(K8qFzfP)65^kmU8=N+p0ZUU=Euy|^MgreSMibO&4>)q z#A`#^59ThIix~N2%yuV83u)C12AcuVMmIIm=MClm024hu-Z>M~jlM>TOfCUcL&xRM z%0D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 RmToMuVX&ENIq_+-|JejjX*B=< diff --git a/example/auto_compression/pytorch_yolov7/post_process.py b/example/auto_compression/pytorch_yolov7/post_process.py deleted file mode 100644 index 853693da..00000000 --- a/example/auto_compression/pytorch_yolov7/post_process.py +++ /dev/null @@ -1,173 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import numpy as np -import cv2 - - -def box_area(boxes): - """ - Args: - boxes(np.ndarray): [N, 4] - return: [N] - """ - return (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1]) - - -def box_iou(box1, box2): - """ - Args: - box1(np.ndarray): [N, 4] - box2(np.ndarray): [M, 4] - return: [N, M] - """ - area1 = box_area(box1) - area2 = box_area(box2) - lt = np.maximum(box1[:, np.newaxis, :2], box2[:, :2]) - rb = np.minimum(box1[:, np.newaxis, 2:], box2[:, 2:]) - wh = rb - lt - wh = np.maximum(0, wh) - inter = wh[:, :, 0] * wh[:, :, 1] - iou = inter / (area1[:, np.newaxis] + area2 - inter) - return iou - - -def nms(boxes, scores, iou_threshold): - """ - Non Max Suppression numpy implementation. - args: - boxes(np.ndarray): [N, 4] - scores(np.ndarray): [N, 1] - iou_threshold(float): Threshold of IoU. - """ - idxs = scores.argsort() - keep = [] - while idxs.size > 0: - max_score_index = idxs[-1] - max_score_box = boxes[max_score_index][None, :] - keep.append(max_score_index) - if idxs.size == 1: - break - idxs = idxs[:-1] - other_boxes = boxes[idxs] - ious = box_iou(max_score_box, other_boxes) - idxs = idxs[ious[0] <= iou_threshold] - - keep = np.array(keep) - return keep - - -class YOLOv7PostProcess(object): - """ - Post process of YOLOv6 network. - args: - score_threshold(float): Threshold to filter out bounding boxes with low - confidence score. If not provided, consider all boxes. - nms_threshold(float): The threshold to be used in NMS. - multi_label(bool): Whether keep multi label in boxes. - keep_top_k(int): Number of total bboxes to be kept per image after NMS - step. -1 means keeping all bboxes after NMS step. - """ - - def __init__(self, - score_threshold=0.25, - nms_threshold=0.5, - multi_label=False, - keep_top_k=300): - self.score_threshold = score_threshold - self.nms_threshold = nms_threshold - self.multi_label = multi_label - self.keep_top_k = keep_top_k - - def _xywh2xyxy(self, x): - # Convert from [x, y, w, h] to [x1, y1, x2, y2] - y = np.copy(x) - y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x - y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y - y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x - y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y - return y - - def _non_max_suppression(self, prediction): - max_wh = 4096 # (pixels) minimum and maximum box width and height - nms_top_k = 30000 - - cand_boxes = prediction[..., 4] > self.score_threshold # candidates - output = [np.zeros((0, 6))] * prediction.shape[0] - - for batch_id, boxes in enumerate(prediction): - # Apply constraints - boxes = boxes[cand_boxes[batch_id]] - if not boxes.shape[0]: - continue - # Compute conf (conf = obj_conf * cls_conf) - boxes[:, 5:] *= boxes[:, 4:5] - - # Box (center x, center y, width, height) to (x1, y1, x2, y2) - convert_box = self._xywh2xyxy(boxes[:, :4]) - - # Detections matrix nx6 (xyxy, conf, cls) - if self.multi_label: - i, j = (boxes[:, 5:] > self.score_threshold).nonzero() - boxes = np.concatenate( - (convert_box[i], boxes[i, j + 5, None], - j[:, None].astype(np.float32)), - axis=1) - else: - conf = np.max(boxes[:, 5:], axis=1) - j = np.argmax(boxes[:, 5:], axis=1) - re = np.array(conf.reshape(-1) > self.score_threshold) - conf = conf.reshape(-1, 1) - j = j.reshape(-1, 1) - boxes = np.concatenate((convert_box, conf, j), axis=1)[re] - - num_box = boxes.shape[0] - if not num_box: - continue - elif num_box > nms_top_k: - boxes = boxes[boxes[:, 4].argsort()[::-1][:nms_top_k]] - - # Batched NMS - c = boxes[:, 5:6] * max_wh - clean_boxes, scores = boxes[:, :4] + c, boxes[:, 4] - keep = nms(clean_boxes, scores, self.nms_threshold) - # limit detection box num - if keep.shape[0] > self.keep_top_k: - keep = keep[:self.keep_top_k] - output[batch_id] = boxes[keep] - return output - - def __call__(self, outs, scale_factor): - preds = self._non_max_suppression(outs) - bboxs, box_nums = [], [] - for i, pred in enumerate(preds): - if len(pred.shape) > 2: - pred = np.squeeze(pred) - if len(pred.shape) == 1: - pred = pred[np.newaxis, :] - pred_bboxes = pred[:, :4] - scale_factor = np.tile(scale_factor[i][::-1], (1, 2)) - pred_bboxes /= scale_factor - bbox = np.concatenate( - [ - pred[:, -1][:, np.newaxis], pred[:, -2][:, np.newaxis], - pred_bboxes - ], - axis=-1) - bboxs.append(bbox) - box_num = bbox.shape[0] - box_nums.append(box_num) - bboxs = np.concatenate(bboxs, axis=0) - box_nums = np.array(box_nums) - return {'bbox': bboxs, 'bbox_num': box_nums} diff --git a/example/auto_compression/pytorch_yolov7/run.py b/example/auto_compression/pytorch_yolov7/run.py deleted file mode 100644 index ed73d81a..00000000 --- a/example/auto_compression/pytorch_yolov7/run.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import sys -import numpy as np -import argparse -import paddle -from ppdet.core.workspace import load_config, merge_config -from ppdet.core.workspace import create -from ppdet.metrics import COCOMetric, VOCMetric -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config -from paddleslim.auto_compression import AutoCompression - -from post_process import YOLOv7PostProcess - - -def argsparser(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - '--config_path', - type=str, - default=None, - help="path of compression strategy config.", - required=True) - parser.add_argument( - '--save_dir', - type=str, - default='output', - help="directory to save compressed model.") - parser.add_argument( - '--devices', - type=str, - default='gpu', - help="which device used to compress.") - parser.add_argument( - '--eval', type=bool, default=False, help="whether to run evaluation.") - - return parser - - -def reader_wrapper(reader, input_list): - def gen(): - for data in reader: - in_dict = {} - if isinstance(input_list, list): - for input_name in input_list: - in_dict[input_name] = data[input_name] - elif isinstance(input_list, dict): - for input_name in input_list.keys(): - in_dict[input_list[input_name]] = data[input_name] - yield in_dict - - return gen - - -def convert_numpy_data(data, metric): - data_all = {} - data_all = {k: np.array(v) for k, v in data.items()} - if isinstance(metric, VOCMetric): - for k, v in data_all.items(): - if not isinstance(v[0], np.ndarray): - tmp_list = [] - for t in v: - tmp_list.append(np.array(t)) - data_all[k] = np.array(tmp_list) - else: - data_all = {k: np.array(v) for k, v in data.items()} - return data_all - - -def eval_function(exe, compiled_test_program, test_feed_names, test_fetch_list): - metric = global_config['metric'] - for batch_id, data in enumerate(val_loader): - data_all = convert_numpy_data(data, metric) - data_input = {} - for k, v in data.items(): - if isinstance(global_config['input_list'], list): - if k in test_feed_names: - data_input[k] = np.array(v) - elif isinstance(global_config['input_list'], dict): - if k in global_config['input_list'].keys(): - data_input[global_config['input_list'][k]] = np.array(v) - outs = exe.run(compiled_test_program, - feed=data_input, - fetch_list=test_fetch_list, - return_numpy=False) - res = {} - postprocess = YOLOv7PostProcess( - score_threshold=0.001, nms_threshold=0.65, multi_label=True) - res = postprocess(np.array(outs[0]), data_all['scale_factor']) - metric.update(data_all, 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] - - -def main(): - global global_config - all_config = load_slim_config(FLAGS.config_path) - assert "Global" in all_config, f"Key 'Global' not found in config file. \n{all_config}" - global_config = all_config["Global"] - reader_cfg = load_config(global_config['reader_config']) - - train_loader = create('EvalReader')(reader_cfg['TrainDataset'], - reader_cfg['worker_num'], - return_list=True) - train_loader = reader_wrapper(train_loader, global_config['input_list']) - - if 'Evaluation' in global_config.keys() and global_config[ - 'Evaluation'] and paddle.distributed.get_rank() == 0: - eval_func = eval_function - dataset = reader_cfg['EvalDataset'] - global val_loader - _eval_batch_sampler = paddle.io.BatchSampler( - dataset, batch_size=reader_cfg['EvalReader']['batch_size']) - val_loader = create('EvalReader')(dataset, - reader_cfg['worker_num'], - batch_sampler=_eval_batch_sampler, - return_list=True) - metric = None - if reader_cfg['metric'] == 'COCO': - clsid2catid = {v: k for k, v in dataset.catid2clsid.items()} - anno_file = dataset.get_anno() - metric = COCOMetric( - anno_file=anno_file, clsid2catid=clsid2catid, IouType='bbox') - elif reader_cfg['metric'] == 'VOC': - metric = VOCMetric( - label_list=dataset.get_label_list(), - class_num=reader_cfg['num_classes'], - map_type=reader_cfg['map_type']) - else: - raise ValueError("metric currently only supports COCO and VOC.") - global_config['metric'] = metric - else: - eval_func = None - - ac = AutoCompression( - model_dir=global_config["model_dir"], - model_filename=global_config["model_filename"], - params_filename=global_config["params_filename"], - save_dir=FLAGS.save_dir, - config=all_config, - train_dataloader=train_loader, - eval_callback=eval_func) - ac.compress() - - -if __name__ == '__main__': - paddle.enable_static() - parser = argsparser() - FLAGS = parser.parse_args() - - assert FLAGS.devices in ['cpu', 'gpu', 'xpu', 'npu'] - paddle.set_device(FLAGS.devices) - - main() diff --git a/example/auto_compression/semantic_segmentation/README.md b/example/auto_compression/semantic_segmentation/README.md index f01bb904..a923ec35 100644 --- a/example/auto_compression/semantic_segmentation/README.md +++ b/example/auto_compression/semantic_segmentation/README.md @@ -71,10 +71,10 @@ git clone https://github.com/PaddlePaddle/PaddleSlim.git 安装paddleseg ```shell -pip install paddleseg +pip install paddleseg==2.5.0 ``` -注:安装[PaddleSeg](https://github.com/PaddlePaddle/PaddleSeg)的目的只是为了直接使用PaddleSeg中的Dataloader组件,不涉及模型组网等。 +注:安装[PaddleSeg](https://github.com/PaddlePaddle/PaddleSeg)的目的只是为了直接使用PaddleSeg中的Dataloader组件,不涉及模型组网等。推荐安装PaddleSeg 2.5.0, 不同版本的PaddleSeg的Dataloader返回数据的格式略有不同. #### 3.2 准备数据集 diff --git a/example/auto_compression/semantic_segmentation/run.py b/example/auto_compression/semantic_segmentation/run.py index 4f4d4c56..a8a486c2 100644 --- a/example/auto_compression/semantic_segmentation/run.py +++ b/example/auto_compression/semantic_segmentation/run.py @@ -21,7 +21,7 @@ from paddleseg.cvlibs import Config as PaddleSegDataConfig from paddleseg.utils import worker_init_fn from paddleslim.auto_compression import AutoCompression -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config +from paddleslim.common import load_config as load_slim_config from paddleseg.core.infer import reverse_transform from paddleseg.utils import metrics @@ -38,6 +38,11 @@ def argsparser(): type=str, default=None, help="directory to save compressed model.") + parser.add_argument( + '--devices', + type=str, + default='gpu', + help="which device used to compress.") return parser @@ -123,7 +128,12 @@ def main(args): config = all_config["Global"] rank_id = paddle.distributed.get_rank() - place = paddle.CUDAPlace(rank_id) + if args.devices == 'gpu': + place = paddle.CUDAPlace(rank_id) + paddle.set_device('gpu') + else: + place = paddle.CPUPlace() + paddle.set_device('cpu') # step1: load dataset config and create dataloader data_cfg = PaddleSegDataConfig(config['reader_config']) train_dataset = data_cfg.train_dataset diff --git a/example/auto_compression/tensorflow_mobilenet/eval.py b/example/auto_compression/tensorflow_mobilenet/eval.py index 85e5fdaf..bf0987e3 100644 --- a/example/auto_compression/tensorflow_mobilenet/eval.py +++ b/example/auto_compression/tensorflow_mobilenet/eval.py @@ -23,7 +23,7 @@ import paddle import paddle.nn as nn from paddle.io import DataLoader from imagenet_reader import ImageNetDataset -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config +from paddleslim.common import load_config as load_slim_config def argsparser(): @@ -93,7 +93,8 @@ def eval(): def main(): global global_config all_config = load_slim_config(args.config_path) - assert "Global" in all_config, f"Key 'Global' not found in config file. \n{all_config}" + assert "Global" in all_config, "Key 'Global' not found in config file. \n{}".format( + all_config) global_config = all_config["Global"] global data_dir data_dir = global_config['data_dir'] diff --git a/example/auto_compression/tensorflow_mobilenet/run.py b/example/auto_compression/tensorflow_mobilenet/run.py index 86345ec2..aefd2941 100644 --- a/example/auto_compression/tensorflow_mobilenet/run.py +++ b/example/auto_compression/tensorflow_mobilenet/run.py @@ -23,7 +23,7 @@ import paddle import paddle.nn as nn from paddle.io import DataLoader from imagenet_reader import ImageNetDataset -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config +from paddleslim.common import load_config as load_slim_config from paddleslim.auto_compression import AutoCompression @@ -107,7 +107,8 @@ def eval_function(exe, compiled_test_program, test_feed_names, test_fetch_list): def main(): global global_config all_config = load_slim_config(args.config_path) - assert "Global" in all_config, f"Key 'Global' not found in config file. \n{all_config}" + assert "Global" in all_config, "Key 'Global' not found in config file. \n{}".format( + all_config) global_config = all_config["Global"] global data_dir data_dir = global_config['data_dir'] diff --git a/example/post_training_quantization/analysis.md b/example/post_training_quantization/analysis.md new file mode 100644 index 00000000..ce5e7a75 --- /dev/null +++ b/example/post_training_quantization/analysis.md @@ -0,0 +1,49 @@ +# 量化分析工具详细教程 + +## 1. 量化分析工具功能 +1. 遍历模型所有层,依次量化该层,计算量化后精度。为所有只量化一层的模型精度排序,可视化不适合量化的层,以供量化时可选择性跳过不适合量化的层。 +2. 可视化量化效果最好和最差的层的权重和激活分布图,以供分析模型量化效果的原因。 +3. 【敬请期待】输入预期精度,直接产出符合预期精度的量化模型。 + +## 2. paddleslim.quant.AnalysisQuant 可传入参数解析 +```yaml +model_dir +model_filename: None +params_filename: None +eval_function: None +data_loader: None +save_dir: 'analysis_results' +checkpoint_name: 'analysis_checkpoint.pkl' +num_histogram_plots: 10 +ptq_config +``` +- model_dir: 必须传入的模型文件路径,可为文件夹名;若模型为ONNX类型,直接输入'.onnx'模型文件名称即可。 +- model_filename: 默认为None,若model_dir为文件夹名,则必须传入以'.pdmodel'结尾的模型名称,若model_dir为'.onnx'模型文件名称,则不需要传入。 +- params_filename: 默认为None,若model_dir为文件夹名,则必须传入以'.pdiparams'结尾的模型名称,若model_dir为'.onnx'模型文件名称,则不需要传入。 +- eval_function:目前不支持为None,需要传入自定义的验证函数。 +- data_loader:模型校准时使用的数据,DataLoader继承自`paddle.io.DataLoader`。可以直接使用模型套件中的DataLoader,或者根据[paddle.io.DataLoader](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/io/DataLoader_cn.html#dataloader)自定义所需要的DataLoader。 +- save_dir:分析后保存模型精度或pdf等文件的文件夹,默认为`analysis_results`。 +- checkpoint_name:由于模型可能存在大量层需要分析,因此分析过程中会中间保存结果,如果程序中断会自动加载已经分析好的结果,默认为`analysis_checkpoint.pkl`。 +- num_histogram_plots:需要可视化的直方分布图数量。可视化量化效果最好和最坏的该数量个权重和激活的分布图。默认为10。若不需要可视化直方图,设置为0即可。 +- ptq_config:可传入的离线量化中的参数,详细可参考[离线量化文档](https://github.com/PaddlePaddle/PaddleSlim/tree/develop/demo/quant/quant_post)。 + + + + +## 3. 量化分析工具产出内容 + +量化分析工具会默认会产出以下目录: +``` +analysis_results/ +├── analysis.txt +├── best_weight_hist_result.pdf +├── best_act_hist_result.pdf +├── worst_weight_hist_result.pdf +├── worst_act_hist_result.pdf +``` +- 所有只量化一层的模型精度排序,将默认保存在 `./analysis_results/analysis.txt` 中。 +- 通过设置参数`num_histogram_plots`,可选择绘出该数量个量化效果最好和最差层的weight和activation的直方分布图,将以PDF形式保存在 `./analysis_results` 文件夹下, 分别保存为 `best_weight_hist_result.pdf`,`best_act_hist_result.pdf`,`worst_weight_hist_result.pdf` 和 `worst_act_hist_result.pdf` 中以供对比分析。 + + +## 3. 根据分析结果执行离线量化 +执行完量化分析工具后,可根据 `analysis.txt` 中的精度排序,在量化中去掉效果较差的层,具体操作为:在调用 `paddleslim.quant.quant_post_static` 时加入参数 `skip_tensor_list`,将需要去掉的层传入即可。 diff --git a/example/post_training_quantization/detection/analysis.py b/example/post_training_quantization/detection/analysis.py new file mode 100644 index 00000000..ba65d729 --- /dev/null +++ b/example/post_training_quantization/detection/analysis.py @@ -0,0 +1,179 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import numpy as np +import argparse +from tqdm import tqdm +import paddle +from ppdet.core.workspace import load_config, merge_config +from ppdet.core.workspace import create +from ppdet.metrics import COCOMetric, VOCMetric, KeyPointTopDownCOCOEval +from keypoint_utils import keypoint_post_process +from post_process import PPYOLOEPostProcess +from paddleslim.quant.analysis import AnalysisQuant + + +def argsparser(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--config_path', + type=str, + default=None, + help="path of analysis config.", + required=True) + parser.add_argument( + '--devices', + type=str, + default='gpu', + help="which device used to compress.") + return parser + + +def reader_wrapper(reader, input_list): + def gen(): + for data in reader: + in_dict = {} + if isinstance(input_list, list): + for input_name in input_list: + in_dict[input_name] = data[input_name] + elif isinstance(input_list, dict): + for input_name in input_list.keys(): + in_dict[input_list[input_name]] = data[input_name] + yield in_dict + + return gen + + +def convert_numpy_data(data, metric): + data_all = {} + data_all = {k: np.array(v) for k, v in data.items()} + if isinstance(metric, VOCMetric): + for k, v in data_all.items(): + if not isinstance(v[0], np.ndarray): + tmp_list = [] + for t in v: + tmp_list.append(np.array(t)) + data_all[k] = np.array(tmp_list) + else: + data_all = {k: np.array(v) for k, v in data.items()} + return data_all + + +def eval_function(exe, compiled_test_program, test_feed_names, test_fetch_list): + with tqdm( + total=len(val_loader), + bar_format='Evaluation stage, Run batch:|{bar}| {n_fmt}/{total_fmt}', + ncols=80) as t: + for batch_id, data in enumerate(val_loader): + data_all = convert_numpy_data(data, metric) + data_input = {} + for k, v in data.items(): + if isinstance(config['input_list'], list): + if k in test_feed_names: + data_input[k] = np.array(v) + elif isinstance(config['input_list'], dict): + if k in config['input_list'].keys(): + data_input[config['input_list'][k]] = np.array(v) + outs = exe.run(compiled_test_program, + feed=data_input, + fetch_list=test_fetch_list, + return_numpy=False) + res = {} + if 'arch' in config and config['arch'] == 'keypoint': + res = keypoint_post_process(data, data_input, exe, + compiled_test_program, + test_fetch_list, outs) + if 'arch' in config and config['arch'] == 'PPYOLOE': + postprocess = PPYOLOEPostProcess( + score_threshold=0.01, nms_threshold=0.6) + res = postprocess(np.array(outs[0]), data_all['scale_factor']) + else: + for out in outs: + v = np.array(out) + if len(v.shape) > 1: + res['bbox'] = v + else: + res['bbox_num'] = v + + metric.update(data_all, res) + t.update() + + metric.accumulate() + metric.log() + map_res = metric.get_results() + metric.reset() + map_key = 'keypoint' if 'arch' in config and config[ + 'arch'] == 'keypoint' else 'bbox' + return map_res[map_key][0] + + +def main(): + + global config + config = load_config(FLAGS.config_path) + ptq_config = config['PTQ'] + + data_loader = create('EvalReader')(config['EvalDataset'], + config['worker_num'], + return_list=True) + data_loader = reader_wrapper(data_loader, config['input_list']) + + dataset = config['EvalDataset'] + global val_loader + _eval_batch_sampler = paddle.io.BatchSampler( + dataset, batch_size=config['EvalReader']['batch_size']) + val_loader = create('EvalReader')(dataset, + config['worker_num'], + batch_sampler=_eval_batch_sampler, + return_list=True) + global metric + if config['metric'] == 'COCO': + clsid2catid = {v: k for k, v in dataset.catid2clsid.items()} + anno_file = dataset.get_anno() + metric = COCOMetric( + anno_file=anno_file, clsid2catid=clsid2catid, IouType='bbox') + elif config['metric'] == 'VOC': + metric = VOCMetric( + label_list=dataset.get_label_list(), + class_num=config['num_classes'], + map_type=config['map_type']) + elif config['metric'] == 'KeyPointTopDownCOCOEval': + anno_file = dataset.get_anno() + metric = KeyPointTopDownCOCOEval(anno_file, + len(dataset), 17, 'output_eval') + else: + raise ValueError("metric currently only supports COCO and VOC.") + + analyzer = AnalysisQuant( + model_dir=config["model_dir"], + model_filename=config["model_filename"], + params_filename=config["params_filename"], + eval_function=eval_function, + data_loader=data_loader, + save_dir=config['save_dir'], + ptq_config=ptq_config) + analyzer.analysis() + + +if __name__ == '__main__': + paddle.enable_static() + parser = argsparser() + FLAGS = parser.parse_args() + + assert FLAGS.devices in ['cpu', 'gpu', 'xpu', 'npu'] + paddle.set_device(FLAGS.devices) + + main() diff --git a/example/post_training_quantization/detection/configs/picodet_s_analysis.yaml b/example/post_training_quantization/detection/configs/picodet_s_analysis.yaml new file mode 100644 index 00000000..6c640795 --- /dev/null +++ b/example/post_training_quantization/detection/configs/picodet_s_analysis.yaml @@ -0,0 +1,47 @@ +input_list: ['image', 'scale_factor'] +model_dir: ./picodet_s_416_coco_lcnet/ +model_filename: model.pdmodel +params_filename: model.pdiparams +save_dir: ./analysis_results +metric: COCO +num_classes: 80 + +PTQ: + quantizable_op_type: ["conv2d", "depthwise_conv2d"] + weight_quantize_type: 'abs_max' + activation_quantize_type: 'moving_average_abs_max' + is_full_quantize: False + batch_size: 10 + batch_nums: 10 + +# Datset configuration +TrainDataset: + !COCODataSet + image_dir: train2017 + anno_path: annotations/instances_train2017.json + dataset_dir: /dataset/coco/ + +EvalDataset: + !COCODataSet + image_dir: val2017 + anno_path: annotations/instances_val2017.json + dataset_dir: /dataset/coco/ + +eval_height: &eval_height 416 +eval_width: &eval_width 416 +eval_size: &eval_size [*eval_height, *eval_width] + +worker_num: 0 + +EvalReader: + inputs_def: + image_shape: [1, 3, *eval_height, *eval_width] + sample_transforms: + - Decode: {} + - Resize: {interp: 2, target_size: *eval_size, keep_ratio: False} + - NormalizeImage: {is_scale: true, mean: [0.485,0.456,0.406], std: [0.229, 0.224,0.225]} + - Permute: {} + batch_size: 32 + + + diff --git a/example/post_training_quantization/detection/configs/picodet_s_ptq.yaml b/example/post_training_quantization/detection/configs/picodet_s_ptq.yaml new file mode 100644 index 00000000..005c0d46 --- /dev/null +++ b/example/post_training_quantization/detection/configs/picodet_s_ptq.yaml @@ -0,0 +1,38 @@ +input_list: ['image', 'scale_factor'] +model_dir: ./picodet_s_416_coco_lcnet/ +model_filename: model.pdmodel +params_filename: model.pdiparams +skip_tensor_list: None + +metric: COCO +num_classes: 80 + +# Datset configuration +TrainDataset: + !COCODataSet + image_dir: train2017 + anno_path: annotations/instances_train2017.json + dataset_dir: /dataset/coco/ + +EvalDataset: + !COCODataSet + image_dir: val2017 + anno_path: annotations/instances_val2017.json + dataset_dir: /dataset/coco/ + +eval_height: &eval_height 416 +eval_width: &eval_width 416 +eval_size: &eval_size [*eval_height, *eval_width] + +worker_num: 0 + +EvalReader: + inputs_def: + image_shape: [1, 3, *eval_height, *eval_width] + sample_transforms: + - Decode: {} + - Resize: {interp: 2, target_size: *eval_size, keep_ratio: False} + - NormalizeImage: {is_scale: true, mean: [0.485,0.456,0.406], std: [0.229, 0.224,0.225]} + - Permute: {} + batch_size: 32 + diff --git a/example/auto_compression/pytorch_yolov5/eval.py b/example/post_training_quantization/detection/eval.py similarity index 86% rename from example/auto_compression/pytorch_yolov5/eval.py rename to example/post_training_quantization/detection/eval.py index 55be2feb..fc0c09ae 100644 --- a/example/auto_compression/pytorch_yolov5/eval.py +++ b/example/post_training_quantization/detection/eval.py @@ -19,10 +19,10 @@ import argparse import paddle from ppdet.core.workspace import load_config, merge_config from ppdet.core.workspace import create -from ppdet.metrics import COCOMetric, VOCMetric -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config - -from post_process import YOLOv5PostProcess +from ppdet.metrics import COCOMetric, VOCMetric, KeyPointTopDownCOCOEval +from paddleslim.common import load_config as load_slim_config +from keypoint_utils import keypoint_post_process +from post_process import PPYOLOEPostProcess def argsparser(): @@ -78,7 +78,7 @@ def eval(): exe = paddle.static.Executor(place) val_program, feed_target_names, fetch_targets = paddle.static.load_inference_model( - global_config["model_dir"], + global_config["model_dir"].rstrip('/'), exe, model_filename=global_config["model_filename"], params_filename=global_config["params_filename"]) @@ -101,9 +101,12 @@ def eval(): fetch_list=fetch_targets, return_numpy=False) res = {} - if 'arch' in global_config and global_config['arch'] == 'YOLOv5': - postprocess = YOLOv5PostProcess( - score_threshold=0.001, nms_threshold=0.6, multi_label=True) + if 'arch' in global_config and global_config['arch'] == 'keypoint': + res = keypoint_post_process(data, data_input, exe, val_program, + fetch_targets, outs) + if 'arch' in global_config and global_config['arch'] == 'PPYOLOE': + postprocess = PPYOLOEPostProcess( + score_threshold=0.01, nms_threshold=0.6) res = postprocess(np.array(outs[0]), data_all['scale_factor']) else: for out in outs: @@ -142,6 +145,10 @@ def main(): label_list=dataset.get_label_list(), class_num=reader_cfg['num_classes'], map_type=reader_cfg['map_type']) + elif reader_cfg['metric'] == 'KeyPointTopDownCOCOEval': + anno_file = dataset.get_anno() + metric = KeyPointTopDownCOCOEval(anno_file, + len(dataset), 17, 'output_eval') else: raise ValueError("metric currently only supports COCO and VOC.") global_config['metric'] = metric @@ -153,7 +160,6 @@ if __name__ == '__main__': paddle.enable_static() parser = argsparser() FLAGS = parser.parse_args() - assert FLAGS.devices in ['cpu', 'gpu', 'xpu', 'npu'] paddle.set_device(FLAGS.devices) diff --git a/example/post_training_quantization/detection/keypoint_utils.py b/example/post_training_quantization/detection/keypoint_utils.py new file mode 100644 index 00000000..d17095f4 --- /dev/null +++ b/example/post_training_quantization/detection/keypoint_utils.py @@ -0,0 +1,307 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import numpy as np +import cv2 +import copy +from paddleslim.common import get_logger + +logger = get_logger(__name__, level=logging.INFO) + +__all__ = ['keypoint_post_process'] + + +def flip_back(output_flipped, matched_parts): + assert output_flipped.ndim == 4,\ + 'output_flipped should be [batch_size, num_joints, height, width]' + + output_flipped = output_flipped[:, :, :, ::-1] + + for pair in matched_parts: + tmp = output_flipped[:, pair[0], :, :].copy() + output_flipped[:, pair[0], :, :] = output_flipped[:, pair[1], :, :] + output_flipped[:, pair[1], :, :] = tmp + + return output_flipped + + +def get_affine_transform(center, + input_size, + rot, + output_size, + shift=(0., 0.), + inv=False): + """Get the affine transform matrix, given the center/scale/rot/output_size. + Args: + center (np.ndarray[2, ]): Center of the bounding box (x, y). + input_size (np.ndarray[2, ]): Size of input feature (width, height). + rot (float): Rotation angle (degree). + output_size (np.ndarray[2, ]): Size of the destination heatmaps. + shift (0-100%): Shift translation ratio wrt the width/height. + Default (0., 0.). + inv (bool): Option to inverse the affine transform direction. + (inv=False: src->dst or inv=True: dst->src) + Returns: + np.ndarray: The transform matrix. + """ + assert len(center) == 2 + assert len(output_size) == 2 + assert len(shift) == 2 + + if not isinstance(input_size, (np.ndarray, list)): + input_size = np.array([input_size, input_size], dtype=np.float32) + scale_tmp = input_size + + shift = np.array(shift) + src_w = scale_tmp[0] + dst_w = output_size[0] + dst_h = output_size[1] + + rot_rad = np.pi * rot / 180 + src_dir = rotate_point([0., src_w * -0.5], rot_rad) + dst_dir = np.array([0., dst_w * -0.5]) + + src = np.zeros((3, 2), dtype=np.float32) + + src[0, :] = center + scale_tmp * shift + src[1, :] = center + src_dir + scale_tmp * shift + src[2, :] = _get_3rd_point(src[0, :], src[1, :]) + + dst = np.zeros((3, 2), dtype=np.float32) + dst[0, :] = [dst_w * 0.5, dst_h * 0.5] + dst[1, :] = np.array([dst_w * 0.5, dst_h * 0.5]) + dst_dir + dst[2, :] = _get_3rd_point(dst[0, :], dst[1, :]) + + if inv: + trans = cv2.getAffineTransform(np.float32(dst), np.float32(src)) + else: + trans = cv2.getAffineTransform(np.float32(src), np.float32(dst)) + + return trans + + +def _get_3rd_point(a, b): + """To calculate the affine matrix, three pairs of points are required. This + function is used to get the 3rd point, given 2D points a & b. + The 3rd point is defined by rotating vector `a - b` by 90 degrees + anticlockwise, using b as the rotation center. + Args: + a (np.ndarray): point(x,y) + b (np.ndarray): point(x,y) + Returns: + np.ndarray: The 3rd point. + """ + assert len( + a) == 2, 'input of _get_3rd_point should be point with length of 2' + assert len( + b) == 2, 'input of _get_3rd_point should be point with length of 2' + direction = a - b + third_pt = b + np.array([-direction[1], direction[0]], dtype=np.float32) + + return third_pt + + +def rotate_point(pt, angle_rad): + """Rotate a point by an angle. + Args: + pt (list[float]): 2 dimensional point to be rotated + angle_rad (float): rotation angle by radian + Returns: + list[float]: Rotated point. + """ + assert len(pt) == 2 + sn, cs = np.sin(angle_rad), np.cos(angle_rad) + new_x = pt[0] * cs - pt[1] * sn + new_y = pt[0] * sn + pt[1] * cs + rotated_pt = [new_x, new_y] + + return rotated_pt + + +def affine_transform(pt, t): + new_pt = np.array([pt[0], pt[1], 1.]).T + new_pt = np.dot(t, new_pt) + return new_pt[:2] + + +def transform_preds(coords, center, scale, output_size): + target_coords = np.zeros(coords.shape) + trans = get_affine_transform(center, scale * 200, 0, output_size, inv=1) + for p in range(coords.shape[0]): + target_coords[p, 0:2] = affine_transform(coords[p, 0:2], trans) + return target_coords + + +class HRNetPostProcess(object): + def __init__(self, use_dark=True): + self.use_dark = use_dark + + def get_max_preds(self, heatmaps): + '''get predictions from score maps + Args: + heatmaps: numpy.ndarray([batch_size, num_joints, height, width]) + Returns: + preds: numpy.ndarray([batch_size, num_joints, 2]), keypoints coords + maxvals: numpy.ndarray([batch_size, num_joints, 2]), the maximum confidence of the keypoints + ''' + assert isinstance(heatmaps, + np.ndarray), 'heatmaps should be numpy.ndarray' + assert heatmaps.ndim == 4, 'batch_images should be 4-ndim' + + batch_size = heatmaps.shape[0] + num_joints = heatmaps.shape[1] + width = heatmaps.shape[3] + heatmaps_reshaped = heatmaps.reshape((batch_size, num_joints, -1)) + idx = np.argmax(heatmaps_reshaped, 2) + maxvals = np.amax(heatmaps_reshaped, 2) + + maxvals = maxvals.reshape((batch_size, num_joints, 1)) + idx = idx.reshape((batch_size, num_joints, 1)) + + preds = np.tile(idx, (1, 1, 2)).astype(np.float32) + + preds[:, :, 0] = (preds[:, :, 0]) % width + preds[:, :, 1] = np.floor((preds[:, :, 1]) / width) + + pred_mask = np.tile(np.greater(maxvals, 0.0), (1, 1, 2)) + pred_mask = pred_mask.astype(np.float32) + + preds *= pred_mask + + return preds, maxvals + + def gaussian_blur(self, heatmap, kernel): + border = (kernel - 1) // 2 + batch_size = heatmap.shape[0] + num_joints = heatmap.shape[1] + height = heatmap.shape[2] + width = heatmap.shape[3] + for i in range(batch_size): + for j in range(num_joints): + origin_max = np.max(heatmap[i, j]) + dr = np.zeros((height + 2 * border, width + 2 * border)) + dr[border:-border, border:-border] = heatmap[i, j].copy() + dr = cv2.GaussianBlur(dr, (kernel, kernel), 0) + heatmap[i, j] = dr[border:-border, border:-border].copy() + heatmap[i, j] *= origin_max / np.max(heatmap[i, j]) + return heatmap + + def dark_parse(self, hm, coord): + heatmap_height = hm.shape[0] + heatmap_width = hm.shape[1] + px = int(coord[0]) + py = int(coord[1]) + if 1 < px < heatmap_width - 2 and 1 < py < heatmap_height - 2: + dx = 0.5 * (hm[py][px + 1] - hm[py][px - 1]) + dy = 0.5 * (hm[py + 1][px] - hm[py - 1][px]) + dxx = 0.25 * (hm[py][px + 2] - 2 * hm[py][px] + hm[py][px - 2]) + dxy = 0.25 * (hm[py+1][px+1] - hm[py-1][px+1] - hm[py+1][px-1] \ + + hm[py-1][px-1]) + dyy = 0.25 * ( + hm[py + 2 * 1][px] - 2 * hm[py][px] + hm[py - 2 * 1][px]) + derivative = np.matrix([[dx], [dy]]) + hessian = np.matrix([[dxx, dxy], [dxy, dyy]]) + if dxx * dyy - dxy**2 != 0: + hessianinv = hessian.I + offset = -hessianinv * derivative + offset = np.squeeze(np.array(offset.T), axis=0) + coord += offset + return coord + + def dark_postprocess(self, hm, coords, kernelsize): + ''' + DARK postpocessing, Zhang et al. Distribution-Aware Coordinate + Representation for Human Pose Estimation (CVPR 2020). + ''' + hm = self.gaussian_blur(hm, kernelsize) + hm = np.maximum(hm, 1e-10) + hm = np.log(hm) + for n in range(coords.shape[0]): + for p in range(coords.shape[1]): + coords[n, p] = self.dark_parse(hm[n][p], coords[n][p]) + return coords + + def get_final_preds(self, heatmaps, center, scale, kernelsize=3): + """ + The highest heatvalue location with a quarter offset in the + direction from the highest response to the second highest response. + Args: + heatmaps (numpy.ndarray): The predicted heatmaps + center (numpy.ndarray): The boxes center + scale (numpy.ndarray): The scale factor + Returns: + preds: numpy.ndarray([batch_size, num_joints, 2]), keypoints coords + maxvals: numpy.ndarray([batch_size, num_joints, 1]), the maximum confidence of the keypoints + """ + coords, maxvals = self.get_max_preds(heatmaps) + + heatmap_height = heatmaps.shape[2] + heatmap_width = heatmaps.shape[3] + + if self.use_dark: + coords = self.dark_postprocess(heatmaps, coords, kernelsize) + else: + for n in range(coords.shape[0]): + for p in range(coords.shape[1]): + hm = heatmaps[n][p] + px = int(math.floor(coords[n][p][0] + 0.5)) + py = int(math.floor(coords[n][p][1] + 0.5)) + if 1 < px < heatmap_width - 1 and 1 < py < heatmap_height - 1: + diff = np.array([ + hm[py][px + 1] - hm[py][px - 1], + hm[py + 1][px] - hm[py - 1][px] + ]) + coords[n][p] += np.sign(diff) * .25 + preds = coords.copy() + + # Transform back + for i in range(coords.shape[0]): + preds[i] = transform_preds(coords[i], center[i], scale[i], + [heatmap_width, heatmap_height]) + + return preds, maxvals + + def __call__(self, output, center, scale): + preds, maxvals = self.get_final_preds(np.array(output), center, scale) + outputs = [[ + np.concatenate( + (preds, maxvals), axis=-1), np.mean( + maxvals, axis=1) + ]] + return outputs + + +def keypoint_post_process(data, data_input, exe, val_program, fetch_targets, + outs): + data_input['image'] = np.flip(data_input['image'], [3]) + output_flipped = exe.run(val_program, + feed=data_input, + fetch_list=fetch_targets, + return_numpy=False) + + output_flipped = np.array(output_flipped[0]) + flip_perm = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14], + [15, 16]] + output_flipped = flip_back(output_flipped, flip_perm) + output_flipped[:, :, :, 1:] = copy.copy(output_flipped)[:, :, :, 0:-1] + hrnet_outputs = (np.array(outs[0]) + output_flipped) * 0.5 + imshape = ( + np.array(data['im_shape']))[:, ::-1] if 'im_shape' in data else None + center = np.array(data['center']) if 'center' in data else np.round( + imshape / 2.) + scale = np.array(data['scale']) if 'scale' in data else imshape / 200. + post_process = HRNetPostProcess() + outputs = post_process(hrnet_outputs, center, scale) + return {'keypoint': outputs} diff --git a/example/post_training_quantization/detection/post_process.py b/example/post_training_quantization/detection/post_process.py new file mode 100644 index 00000000..eea2f019 --- /dev/null +++ b/example/post_training_quantization/detection/post_process.py @@ -0,0 +1,157 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +import cv2 + + +def hard_nms(box_scores, iou_threshold, top_k=-1, candidate_size=200): + """ + Args: + box_scores (N, 5): boxes in corner-form and probabilities. + iou_threshold: intersection over union threshold. + top_k: keep top_k results. If k <= 0, keep all the results. + candidate_size: only consider the candidates with the highest scores. + Returns: + picked: a list of indexes of the kept boxes + """ + scores = box_scores[:, -1] + boxes = box_scores[:, :-1] + picked = [] + indexes = np.argsort(scores) + indexes = indexes[-candidate_size:] + while len(indexes) > 0: + current = indexes[-1] + picked.append(current) + if 0 < top_k == len(picked) or len(indexes) == 1: + break + current_box = boxes[current, :] + indexes = indexes[:-1] + rest_boxes = boxes[indexes, :] + iou = iou_of( + rest_boxes, + np.expand_dims( + current_box, axis=0), ) + indexes = indexes[iou <= iou_threshold] + + return box_scores[picked, :] + + +def iou_of(boxes0, boxes1, eps=1e-5): + """Return intersection-over-union (Jaccard index) of boxes. + Args: + boxes0 (N, 4): ground truth boxes. + boxes1 (N or 1, 4): predicted boxes. + eps: a small number to avoid 0 as denominator. + Returns: + iou (N): IoU values. + """ + overlap_left_top = np.maximum(boxes0[..., :2], boxes1[..., :2]) + overlap_right_bottom = np.minimum(boxes0[..., 2:], boxes1[..., 2:]) + + overlap_area = area_of(overlap_left_top, overlap_right_bottom) + area0 = area_of(boxes0[..., :2], boxes0[..., 2:]) + area1 = area_of(boxes1[..., :2], boxes1[..., 2:]) + return overlap_area / (area0 + area1 - overlap_area + eps) + + +def area_of(left_top, right_bottom): + """Compute the areas of rectangles given two corners. + Args: + left_top (N, 2): left top corner. + right_bottom (N, 2): right bottom corner. + Returns: + area (N): return the area. + """ + hw = np.clip(right_bottom - left_top, 0.0, None) + return hw[..., 0] * hw[..., 1] + + +class PPYOLOEPostProcess(object): + """ + Args: + input_shape (int): network input image size + scale_factor (float): scale factor of ori image + """ + + def __init__(self, + score_threshold=0.4, + nms_threshold=0.5, + nms_top_k=10000, + keep_top_k=300): + self.score_threshold = score_threshold + self.nms_threshold = nms_threshold + self.nms_top_k = nms_top_k + self.keep_top_k = keep_top_k + + def _non_max_suppression(self, prediction, scale_factor): + batch_size = prediction.shape[0] + out_boxes_list = [] + box_num_list = [] + for batch_id in range(batch_size): + bboxes, confidences = prediction[batch_id][..., :4], prediction[ + batch_id][..., 4:] + # nms + picked_box_probs = [] + picked_labels = [] + for class_index in range(0, confidences.shape[1]): + probs = confidences[:, class_index] + mask = probs > self.score_threshold + probs = probs[mask] + if probs.shape[0] == 0: + continue + subset_boxes = bboxes[mask, :] + box_probs = np.concatenate( + [subset_boxes, probs.reshape(-1, 1)], axis=1) + box_probs = hard_nms( + box_probs, + iou_threshold=self.nms_threshold, + top_k=self.nms_top_k) + picked_box_probs.append(box_probs) + picked_labels.extend([class_index] * box_probs.shape[0]) + + if len(picked_box_probs) == 0: + out_boxes_list.append(np.empty((0, 4))) + + else: + picked_box_probs = np.concatenate(picked_box_probs) + # resize output boxes + picked_box_probs[:, 0] /= scale_factor[batch_id][1] + picked_box_probs[:, 2] /= scale_factor[batch_id][1] + picked_box_probs[:, 1] /= scale_factor[batch_id][0] + picked_box_probs[:, 3] /= scale_factor[batch_id][0] + + # clas score box + out_box = np.concatenate( + [ + np.expand_dims( + np.array(picked_labels), axis=-1), np.expand_dims( + picked_box_probs[:, 4], axis=-1), + picked_box_probs[:, :4] + ], + axis=1) + if out_box.shape[0] > self.keep_top_k: + out_box = out_box[out_box[:, 1].argsort()[::-1] + [:self.keep_top_k]] + out_boxes_list.append(out_box) + box_num_list.append(out_box.shape[0]) + + out_boxes_list = np.concatenate(out_boxes_list, axis=0) + box_num_list = np.array(box_num_list) + return out_boxes_list, box_num_list + + def __call__(self, outs, scale_factor): + out_boxes_list, box_num_list = self._non_max_suppression(outs, + scale_factor) + return {'bbox': out_boxes_list, 'bbox_num': box_num_list} diff --git a/example/auto_compression/pytorch_yolov5/post_quant.py b/example/post_training_quantization/detection/post_quant.py similarity index 75% rename from example/auto_compression/pytorch_yolov5/post_quant.py rename to example/post_training_quantization/detection/post_quant.py index 8c866727..a0c01036 100644 --- a/example/auto_compression/pytorch_yolov5/post_quant.py +++ b/example/post_training_quantization/detection/post_quant.py @@ -19,8 +19,6 @@ import argparse import paddle from ppdet.core.workspace import load_config, merge_config from ppdet.core.workspace import create -from ppdet.metrics import COCOMetric, VOCMetric -from paddleslim.auto_compression.config_helpers import load_config as load_slim_config from paddleslim.quant import quant_post_static @@ -64,33 +62,32 @@ def reader_wrapper(reader, input_list): def main(): - global global_config - all_config = load_slim_config(FLAGS.config_path) - assert "Global" in all_config, f"Key 'Global' not found in config file. \n{all_config}" - global_config = all_config["Global"] - reader_cfg = load_config(global_config['reader_config']) + global config + config = load_config(FLAGS.config_path) - train_loader = create('EvalReader')(reader_cfg['TrainDataset'], - reader_cfg['worker_num'], + train_loader = create('EvalReader')(config['TrainDataset'], + config['worker_num'], return_list=True) - train_loader = reader_wrapper(train_loader, global_config['input_list']) + train_loader = reader_wrapper(train_loader, config['input_list']) place = paddle.CUDAPlace(0) if FLAGS.devices == 'gpu' else paddle.CPUPlace() exe = paddle.static.Executor(place) quant_post_static( executor=exe, - model_dir=global_config["model_dir"], + model_dir=config["model_dir"], quantize_model_path=FLAGS.save_dir, data_loader=train_loader, - model_filename=global_config["model_filename"], - params_filename=global_config["params_filename"], - batch_size=32, - batch_nums=10, + model_filename=config["model_filename"], + params_filename=config["params_filename"], + batch_size=4, + batch_nums=64, algo=FLAGS.algo, hist_percent=0.999, is_full_quantize=False, bias_correction=False, - onnx_format=False) + onnx_format=False, + skip_tensor_list=config['skip_tensor_list'] + if 'skip_tensor_list' in config else None) if __name__ == '__main__': diff --git a/example/post_training_quantization/pytorch_yolo_series/README.md b/example/post_training_quantization/pytorch_yolo_series/README.md new file mode 100644 index 00000000..1df18c4a --- /dev/null +++ b/example/post_training_quantization/pytorch_yolo_series/README.md @@ -0,0 +1,150 @@ +# YOLO系列离线量化示例 + +目录: +- [1.简介](#1简介) +- [2.Benchmark](#2Benchmark) +- [3.离线量化流程](#离线量化流程) + - [3.1 准备环境](#31-准备环境) + - [3.2 准备数据集](#32-准备数据集) + - [3.3 准备预测模型](#33-准备预测模型) + - [3.4 离线量化并产出模型](#34-离线量化并产出模型) + - [3.5 测试模型精度](#35-测试模型精度) + - [3.6 提高离线量化精度](#36-提高离线量化精度) +- [4.预测部署](#4预测部署) +- [5.FAQ](5FAQ) + + +本示例将以[ultralytics/yolov5](https://github.com/ultralytics/yolov5),[meituan/YOLOv6](https://github.com/meituan/YOLOv6) 和 [WongKinYiu/yolov7](https://github.com/WongKinYiu/yolov7) YOLO系列目标检测模型为例,将PyTorch框架产出的推理模型转换为Paddle推理模型,使用离线量化功能进行压缩,并使用敏感度分析功能提升离线量化精度。离线量化产出的模型可以用PaddleInference部署,也可以导出为ONNX格式模型文件,并用TensorRT部署。 + + +## 2.Benchmark +| 模型 | 策略 | 输入尺寸 | mAPval
0.5:0.95 | 预测时延FP32
(ms) |预测时延FP16
(ms) | 预测时延INT8
(ms) | 配置文件 | Inference模型 | +| :-------- |:-------- |:--------: | :---------------------: | :----------------: | :----------------: | :---------------: | :-----------------------------: | :-----------------------------: | +| YOLOv5s | Base模型 | 640*640 | 37.4 | 5.95ms | 2.44ms | - | - | [Model](https://paddle-slim-models.bj.bcebos.com/act/yolov5s.onnx) | +| YOLOv5s | KL离线量化 | 640*640 | 36.0 | - | - | 1.87ms | - | - | +| | | | | | | | | | +| YOLOv6s | Base模型 | 640*640 | 42.4 | 9.06ms | 2.90ms | - | - | [Model](https://paddle-slim-models.bj.bcebos.com/act/yolov6s.onnx) | +| YOLOv6s | KL离线量化(量化分析前) | 640*640 | 30.3 | - | - | 1.83ms | - | - | +| YOLOv6s | KL离线量化(量化分析后) | 640*640 | 39.7 | - | - | - | - | [Infer Model](https://bj.bcebos.com/v1/paddle-slim-models/act/yolov6s_analyzed_ptq.tar) | +| | | | | | | | | | +| YOLOv7 | Base模型 | 640*640 | 51.1 | 26.84ms | 7.44ms | - | - | [Model](https://paddle-slim-models.bj.bcebos.com/act/yolov7.onnx) | +| YOLOv7 | KL离线量化 | 640*640 | 50.2 | - | - | 4.55ms | - | - | + +说明: +- mAP的指标均在COCO val2017数据集中评测得到。 + +## 3. 离线量化流程 + +#### 3.1 准备环境 +- PaddlePaddle >= 2.3 (可从[Paddle官网](https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/install/pip/linux-pip.html)下载安装) +- PaddleSlim > 2.3版本 +- opencv-python + +(1)安装paddlepaddle: +```shell +# CPU +pip install paddlepaddle +# GPU +pip install paddlepaddle-gpu +``` + +(2)安装paddleslim: +```shell +pip install paddleslim +``` + +#### 3.2 准备数据集 +本示例默认以COCO数据进行自动压缩实验,可以从 [MS COCO官网](https://cocodataset.org) 下载 [Train](http://images.cocodataset.org/zips/train2017.zip)、[Val](http://images.cocodataset.org/zips/val2017.zip)、[annotation](http://images.cocodataset.org/annotations/annotations_trainval2017.zip)。 + +目录格式如下: +``` +dataset/coco/ +├── annotations +│ ├── instances_train2017.json +│ ├── instances_val2017.json +│ | ... +├── train2017 +│ ├── 000000000009.jpg +│ ├── 000000580008.jpg +│ | ... +├── val2017 +│ ├── 000000000139.jpg +│ ├── 000000000285.jpg +``` + +#### 3.3 准备预测模型 +(1)准备ONNX模型: + +- YOLOv5:可通过[ultralytics/yolov5](https://github.com/ultralytics/yolov5) 官方的[导出教程](https://github.com/ultralytics/yolov5/issues/251)来准备ONNX模型,也可以下载准备好的[yolov5s.onnx](https://paddle-slim-models.bj.bcebos.com/act/yolov5s.onnx)。 + +- YOLOv6:可通过[WongKinYiu/yolov7](https://github.com/WongKinYiu/yolov7)的导出脚本来准备ONNX模型,也可以直接下载我们已经准备好的[yolov7.onnx](https://paddle-slim-models.bj.bcebos.com/act/yolov7.onnx)。 + +- YOLOv7:可通过[meituan/YOLOv6](https://github.com/meituan/YOLOv6)官方的[导出教程](https://github.com/meituan/YOLOv6/blob/main/deploy/ONNX/README.md)来准备ONNX模型,也可以下载已经准备好的[yolov6s.onnx](https://paddle-slim-models.bj.bcebos.com/act/yolov6s.onnx)。 + + +#### 3.4 离线量化并产出模型 +离线量化示例通过post_quant.py脚本启动,会使用接口```paddleslim.quant.quant_post_static```对模型进行量化。配置config文件中模型路径、数据路径和量化相关的参数,配置完成后便可对模型进行离线量化。具体运行命令为: +- YOLOv5 + +```shell +python post_quant.py --config_path=./configs/yolov5s_ptq.yaml --save_dir=./yolov5s_ptq_out +``` + +- YOLOv6 + +```shell +python post_quant.py --config_path=./configs/yolov6s_ptq.yaml --save_dir=./yolov6s_ptq_out +``` + +- YOLOv7 + +```shell +python post_quant.py --config_path=./configs/yolov7s_ptq.yaml --save_dir=./yolov7s_ptq_out +``` + + +#### 3.5 测试模型精度 + +修改 [yolov5s_ptq.yaml](./configs/yolov5s_ptq.yaml) 中`model_dir`字段为模型存储路径,然后使用eval.py脚本得到模型的mAP: + +```shell +export CUDA_VISIBLE_DEVICES=0 +python eval.py --config_path=./configs/yolov5s_ptq.yaml +``` + + +#### 3.6 提高离线量化精度 +本节介绍如何使用量化分析工具提升离线量化精度。离线量化功能仅需使用少量数据,且使用简单、能快速得到量化模型,但往往会造成较大的精度损失。PaddleSlim提供量化分析工具,会使用接口```paddleslim.quant.AnalysisQuant```,可视化展示出不适合量化的层,通过跳过这些层,提高离线量化模型精度。 + +由于YOLOv6离线量化效果较差,以YOLOv6为例,量化分析工具具体使用方法如下: + +```shell +python analysis.py --config_path=./configs/yolov6s_analysis.yaml +``` + +如下图,经过量化分析之后,可以发现`conv2d_2.w_0`, `conv2d_11.w_0`,`conv2d_15.w_0`, `conv2d_46.w_0`, `conv2d_49.w_0` 这些层会导致较大的精度损失。 + +

+
+

+ + + +对比权重直方分布图后,可以发现量化损失较小的层数值分布相对平稳,数值处于-0.25到0.25之间,而量化损失较大的层数值分布非常极端,绝大部分值趋近于0,且数值处于-0.1到0.1之间,尽管看上去都是正太分布,但大量值为0是不利于量化统计scale值的。 + +

+
+

+ + +经此分析,在进行离线量化时,可以跳过这些导致精度下降较多的层,可使用 [yolov6s_analyzed_ptq.yaml](./configs/yolov6s_analyzed_ptq.yaml),然后再次进行离线量化。跳过这些层后,离线量化精度上升9.4个点。 + +```shell +python post_quant.py --config_path=./configs/yolov6s_analyzed_ptq.yaml --save_dir=./yolov6s_analyzed_ptq_out +``` + +## 4.预测部署 + + +## 5.FAQ +- 如果想对模型进行自动压缩,可进入[YOLO系列模型自动压缩示例](https://github.com/PaddlePaddle/PaddleSlim/tree/develop/example/auto_compression/pytorch_yolo_series)中进行实验。 diff --git a/example/post_training_quantization/pytorch_yolo_series/analysis.py b/example/post_training_quantization/pytorch_yolo_series/analysis.py new file mode 100644 index 00000000..1c8cbcb5 --- /dev/null +++ b/example/post_training_quantization/pytorch_yolo_series/analysis.py @@ -0,0 +1,115 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import numpy as np +import argparse +import paddle +from tqdm import tqdm +from post_process import YOLOv6PostProcess, coco_metric +from dataset import COCOValDataset, COCOTrainDataset +from paddleslim.common import load_config, load_onnx_model +from paddleslim.quant.analysis import AnalysisQuant + + +def argsparser(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--config_path', + type=str, + default=None, + help="path of analysis config.", + required=True) + parser.add_argument( + '--devices', + type=str, + default='gpu', + help="which device used to compress.") + return parser + + +def eval_function(exe, compiled_test_program, test_feed_names, test_fetch_list): + bboxes_list, bbox_nums_list, image_id_list = [], [], [] + with tqdm( + total=len(val_loader), + bar_format='Evaluation stage, Run batch:|{bar}| {n_fmt}/{total_fmt}', + ncols=80) as t: + for data in val_loader: + data_all = {k: np.array(v) for k, v in data.items()} + outs = exe.run(compiled_test_program, + feed={test_feed_names[0]: data_all['image']}, + fetch_list=test_fetch_list, + return_numpy=False) + res = {} + postprocess = YOLOv6PostProcess( + score_threshold=0.001, nms_threshold=0.65, multi_label=True) + res = postprocess(np.array(outs[0]), data_all['scale_factor']) + bboxes_list.append(res['bbox']) + bbox_nums_list.append(res['bbox_num']) + image_id_list.append(np.array(data_all['im_id'])) + t.update() + map_res = coco_metric(anno_file, bboxes_list, bbox_nums_list, image_id_list) + return map_res[0] + + +def main(): + + global config + config = load_config(FLAGS.config_path) + ptq_config = config['PTQ'] + + input_name = 'x2paddle_image_arrays' if config[ + 'arch'] == 'YOLOv6' else 'x2paddle_images' + dataset = COCOTrainDataset( + dataset_dir=config['dataset_dir'], + image_dir=config['val_image_dir'], + anno_path=config['val_anno_path'], + input_name=input_name) + data_loader = paddle.io.DataLoader( + dataset, batch_size=1, shuffle=True, drop_last=True, num_workers=0) + + global val_loader + dataset = COCOValDataset( + dataset_dir=config['dataset_dir'], + image_dir=config['val_image_dir'], + anno_path=config['val_anno_path']) + global anno_file + anno_file = dataset.ann_file + val_loader = paddle.io.DataLoader( + dataset, batch_size=1, shuffle=False, drop_last=False, num_workers=0) + + load_onnx_model(config["model_dir"]) + inference_model_path = config["model_dir"].rstrip().rstrip( + '.onnx') + '_infer' + analyzer = AnalysisQuant( + model_dir=inference_model_path, + model_filename='model.pdmodel', + params_filename='model.pdiparams', + eval_function=eval_function, + data_loader=data_loader, + save_dir=config['save_dir'], + ptq_config=ptq_config) + analyzer.analysis() + + +if __name__ == '__main__': + paddle.enable_static() + parser = argsparser() + FLAGS = parser.parse_args() + + assert FLAGS.devices in ['cpu', 'gpu', 'xpu', 'npu'] + paddle.set_device(FLAGS.devices) + + main() diff --git a/example/post_training_quantization/pytorch_yolo_series/configs/yolov5s_ptq.yaml b/example/post_training_quantization/pytorch_yolo_series/configs/yolov5s_ptq.yaml new file mode 100644 index 00000000..eb9f792b --- /dev/null +++ b/example/post_training_quantization/pytorch_yolo_series/configs/yolov5s_ptq.yaml @@ -0,0 +1,8 @@ +arch: YOLOv5 +model_dir: ./yolov5s.onnx +dataset_dir: dataset/coco/ +train_image_dir: train2017 +val_image_dir: val2017 +train_anno_path: annotations/instances_train2017.json +val_anno_path: annotations/instances_val2017.json +skip_tensors: None # you can set it after analysis diff --git a/example/post_training_quantization/pytorch_yolo_series/configs/yolov6s_analysis.yaml b/example/post_training_quantization/pytorch_yolo_series/configs/yolov6s_analysis.yaml new file mode 100644 index 00000000..a99198a4 --- /dev/null +++ b/example/post_training_quantization/pytorch_yolo_series/configs/yolov6s_analysis.yaml @@ -0,0 +1,15 @@ +arch: YOLOv6 +model_dir: ./yolov6s.onnx +save_dir: ./analysis_results +dataset_dir: /dataset/coco/ +val_image_dir: val2017 +val_anno_path: annotations/instances_val2017.json + +PTQ: + quantizable_op_type: ["conv2d", "depthwise_conv2d"] + weight_quantize_type: 'abs_max' + activation_quantize_type: 'moving_average_abs_max' + is_full_quantize: False + batch_size: 10 + batch_nums: 10 + diff --git a/example/post_training_quantization/pytorch_yolo_series/configs/yolov6s_analyzed_ptq.yaml b/example/post_training_quantization/pytorch_yolo_series/configs/yolov6s_analyzed_ptq.yaml new file mode 100644 index 00000000..fa9585d3 --- /dev/null +++ b/example/post_training_quantization/pytorch_yolo_series/configs/yolov6s_analyzed_ptq.yaml @@ -0,0 +1,8 @@ +arch: YOLOv6 +model_dir: ./yolov6s.onnx +dataset_dir: /dataset/coco/ +train_image_dir: train2017 +val_image_dir: val2017 +train_anno_path: annotations/instances_train2017.json +val_anno_path: annotations/instances_val2017.json +skip_tensor_list: ['conv2d_2.w_0', 'conv2d_15.w_0', 'conv2d_46.w_0', 'conv2d_11.w_0', 'conv2d_49.w_0'] diff --git a/example/post_training_quantization/pytorch_yolo_series/configs/yolov6s_ptq.yaml b/example/post_training_quantization/pytorch_yolo_series/configs/yolov6s_ptq.yaml new file mode 100644 index 00000000..9ec6b6a6 --- /dev/null +++ b/example/post_training_quantization/pytorch_yolo_series/configs/yolov6s_ptq.yaml @@ -0,0 +1,8 @@ +arch: YOLOv6 +model_dir: ./yolov6s.onnx +dataset_dir: /dataset/coco/ +train_image_dir: train2017 +val_image_dir: val2017 +train_anno_path: annotations/instances_train2017.json +val_anno_path: annotations/instances_val2017.json +skip_tensor_list: None diff --git a/example/post_training_quantization/pytorch_yolo_series/configs/yolov7s_ptq.yaml b/example/post_training_quantization/pytorch_yolo_series/configs/yolov7s_ptq.yaml new file mode 100644 index 00000000..4d110b4e --- /dev/null +++ b/example/post_training_quantization/pytorch_yolo_series/configs/yolov7s_ptq.yaml @@ -0,0 +1,7 @@ +arch: YOLOv7 +model_dir: ./yolov7s.onnx +dataset_dir: /dataset/coco/ +train_image_dir: train2017 +val_image_dir: val2017 +train_anno_path: annotations/instances_train2017.json +val_anno_path: annotations/instances_val2017.json diff --git a/example/post_training_quantization/pytorch_yolo_series/dataset.py b/example/post_training_quantization/pytorch_yolo_series/dataset.py new file mode 100644 index 00000000..326c5ecf --- /dev/null +++ b/example/post_training_quantization/pytorch_yolo_series/dataset.py @@ -0,0 +1,115 @@ +from pycocotools.coco import COCO +import cv2 +import os +import numpy as np +import paddle + + +class COCOValDataset(paddle.io.Dataset): + def __init__(self, + dataset_dir=None, + image_dir=None, + anno_path=None, + img_size=[640, 640], + input_name='x2paddle_images'): + self.dataset_dir = dataset_dir + self.image_dir = image_dir + self.img_size = img_size + self.input_name = input_name + self.ann_file = os.path.join(dataset_dir, anno_path) + self.coco = COCO(self.ann_file) + ori_ids = list(sorted(self.coco.imgs.keys())) + # check gt bbox + clean_ids = [] + for idx in ori_ids: + ins_anno_ids = self.coco.getAnnIds(imgIds=[idx], iscrowd=False) + instances = self.coco.loadAnns(ins_anno_ids) + num_bbox = 0 + for inst in instances: + if inst.get('ignore', False): + continue + if 'bbox' not in inst.keys(): + continue + elif not any(np.array(inst['bbox'])): + continue + else: + num_bbox += 1 + if num_bbox > 0: + clean_ids.append(idx) + self.ids = clean_ids + + def __getitem__(self, idx): + img_id = self.ids[idx] + img = self._get_img_data_from_img_id(img_id) + img, scale_factor = self.image_preprocess(img, self.img_size) + return { + 'image': img, + 'im_id': np.array([img_id]), + 'scale_factor': scale_factor + } + + def __len__(self): + return len(self.ids) + + def _get_img_data_from_img_id(self, img_id): + img_info = self.coco.loadImgs(img_id)[0] + img_path = os.path.join(self.dataset_dir, self.image_dir, + img_info['file_name']) + img = cv2.imread(img_path) + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + return img + + def _generate_scale(self, im, target_shape, keep_ratio=True): + """ + Args: + im (np.ndarray): image (np.ndarray) + Returns: + im_scale_x: the resize ratio of X + im_scale_y: the resize ratio of Y + """ + origin_shape = im.shape[:2] + if keep_ratio: + im_size_min = np.min(origin_shape) + im_size_max = np.max(origin_shape) + target_size_min = np.min(target_shape) + target_size_max = np.max(target_shape) + im_scale = float(target_size_min) / float(im_size_min) + if np.round(im_scale * im_size_max) > target_size_max: + im_scale = float(target_size_max) / float(im_size_max) + im_scale_x = im_scale + im_scale_y = im_scale + else: + resize_h, resize_w = target_shape + im_scale_y = resize_h / float(origin_shape[0]) + im_scale_x = resize_w / float(origin_shape[1]) + return im_scale_y, im_scale_x + + def image_preprocess(self, img, target_shape): + # Resize image + im_scale_y, im_scale_x = self._generate_scale(img, target_shape) + img = cv2.resize( + img, + None, + None, + fx=im_scale_x, + fy=im_scale_y, + interpolation=cv2.INTER_LINEAR) + # Pad + im_h, im_w = img.shape[:2] + h, w = target_shape[:] + if h != im_h or w != im_w: + canvas = np.ones((h, w, 3), dtype=np.float32) + canvas *= np.array([114.0, 114.0, 114.0], dtype=np.float32) + canvas[0:im_h, 0:im_w, :] = img.astype(np.float32) + img = canvas + img = np.transpose(img / 255, [2, 0, 1]) + scale_factor = np.array([im_scale_y, im_scale_x]) + return img.astype(np.float32), scale_factor + + +class COCOTrainDataset(COCOValDataset): + def __getitem__(self, idx): + img_id = self.ids[idx] + img = self._get_img_data_from_img_id(img_id) + img, scale_factor = self.image_preprocess(img, self.img_size) + return {self.input_name: img} \ No newline at end of file diff --git a/example/post_training_quantization/pytorch_yolo_series/eval.py b/example/post_training_quantization/pytorch_yolo_series/eval.py new file mode 100644 index 00000000..e105bb78 --- /dev/null +++ b/example/post_training_quantization/pytorch_yolo_series/eval.py @@ -0,0 +1,101 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import numpy as np +import argparse +from tqdm import tqdm +import paddle +from paddleslim.common import load_config as load_slim_config +from paddleslim.common import load_inference_model +from post_process import YOLOPostProcess, coco_metric +from dataset import COCOValDataset + + +def argsparser(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--config_path', + type=str, + default=None, + help="path of compression strategy config.", + required=True) + parser.add_argument( + '--batch_size', type=int, default=1, help="Batch size of model input.") + parser.add_argument( + '--devices', + type=str, + default='gpu', + help="which device used to compress.") + + return parser + + +def eval(): + + place = paddle.CUDAPlace(0) if FLAGS.devices == 'gpu' else paddle.CPUPlace() + exe = paddle.static.Executor(place) + + val_program, feed_target_names, fetch_targets = load_inference_model( + config["model_dir"], exe, "model.pdmodel", "model.pdiparams") + + bboxes_list, bbox_nums_list, image_id_list = [], [], [] + with tqdm( + total=len(val_loader), + bar_format='Evaluation stage, Run batch:|{bar}| {n_fmt}/{total_fmt}', + ncols=80) as t: + for data in val_loader: + data_all = {k: np.array(v) for k, v in data.items()} + outs = exe.run(val_program, + feed={feed_target_names[0]: data_all['image']}, + fetch_list=fetch_targets, + return_numpy=False) + postprocess = YOLOPostProcess( + score_threshold=0.001, nms_threshold=0.65, multi_label=True) + res = postprocess(np.array(outs[0]), data_all['scale_factor']) + bboxes_list.append(res['bbox']) + bbox_nums_list.append(res['bbox_num']) + image_id_list.append(np.array(data_all['im_id'])) + t.update() + + coco_metric(anno_file, bboxes_list, bbox_nums_list, image_id_list) + + +def main(): + global config + config = load_slim_config(FLAGS.config_path) + + global val_loader + dataset = COCOValDataset( + dataset_dir=config['dataset_dir'], + image_dir=config['val_image_dir'], + anno_path=config['val_anno_path']) + global anno_file + anno_file = dataset.ann_file + val_loader = paddle.io.DataLoader( + dataset, batch_size=FLAGS.batch_size, drop_last=True) + + eval() + + +if __name__ == '__main__': + paddle.enable_static() + parser = argsparser() + FLAGS = parser.parse_args() + + assert FLAGS.devices in ['cpu', 'gpu', 'xpu', 'npu'] + paddle.set_device(FLAGS.devices) + + main() diff --git a/example/post_training_quantization/pytorch_yolo_series/images/hist_compare.png b/example/post_training_quantization/pytorch_yolo_series/images/hist_compare.png new file mode 100644 index 0000000000000000000000000000000000000000..e895bd261126050a14254856c411b9b2cf0b7c44 GIT binary patch literal 159445 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�XS6e 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 float(train_config.target_metric): + stop_training = True + _logger.info( + "The metric of compressed model has reached the target metric. The training process ends." + ) break else: _logger.warning( "Not set eval function, so unable to test accuracy performance." ) - if train_config.train_iter and total_train_iter >= train_config.train_iter: - epoch_id = total_epochs + if (train_config.train_iter and total_train_iter >= + train_config.train_iter) or stop_training: break if 'unstructure' in self._strategy or train_config.sparse_model: @@ -787,15 +805,19 @@ class AutoCompression: os.remove(os.path.join(self.tmp_dir, 'best_model.pdopt')) os.remove(os.path.join(self.tmp_dir, 'best_model.pdparams')) - if 'qat' in strategy: - test_program, int8_program = convert(test_program, self._places, self._quant_config, \ - scope=paddle.static.global_scope(), \ - save_int8=True) - model_dir = os.path.join(self.tmp_dir, 'strategy_{}'.format(str(strategy_idx + 1))) if not os.path.exists(model_dir): os.makedirs(model_dir) + + if 'qat' in strategy: + test_program = convert( + test_program, + self._places, + self._quant_config, + scope=paddle.static.global_scope(), + save_clip_ranges_path=self.final_dir) + feed_vars = [ test_program.global_block().var(name) for name in test_program_info.feed_target_names @@ -816,3 +838,17 @@ class AutoCompression: fetch_vars=test_program_info.fetch_targets, executor=self._exe, program=test_program) + + def export_onnx(self, + model_name='quant_model.onnx', + deploy_backend='tensorrt'): + infer_model_path = os.path.join(self.final_dir, self.model_filename) + assert os.path.exists( + infer_model_path), 'Not found {}, please check it.'.format( + infer_model_path) + export_onnx( + self.final_dir, + model_filename=self.model_filename, + params_filename=self.params_filename, + save_file_path=os.path.join(self.final_dir, model_name), + deploy_backend=deploy_backend) diff --git a/paddleslim/auto_compression/config_helpers.py b/paddleslim/auto_compression/config_helpers.py index ebc5b45c..b1e426cc 100644 --- a/paddleslim/auto_compression/config_helpers.py +++ b/paddleslim/auto_compression/config_helpers.py @@ -14,42 +14,7 @@ import yaml import os from paddleslim.auto_compression.strategy_config import * - -__all__ = ['save_config', 'load_config'] - - -def print_arguments(args, level=0): - if level == 0: - print('----------- Running Arguments -----------') - for arg, value in sorted(args.items()): - if isinstance(value, dict): - print('\t' * level, '%s:' % arg) - print_arguments(value, level + 1) - else: - print('\t' * level, '%s: %s' % (arg, value)) - if level == 0: - print('------------------------------------------') - - -def load_config(config): - """Load configurations from yaml file into dict. - Fields validation is skipped for loading some custom information. - Args: - config(str): The path of configuration file. - Returns: - dict: A dict storing configuration information. - """ - if config is None: - return None - assert isinstance( - config, - str), f"config should be str but got type(config)={type(config)}" - assert os.path.exists(config) and os.path.isfile( - config), f"{config} not found or it is not a file." - with open(config) as f: - cfg = yaml.load(f, Loader=yaml.FullLoader) - print_arguments(cfg) - return cfg +from ..common.config_helper import load_config def extract_strategy_config(config): @@ -101,12 +66,3 @@ def extract_train_config(config): **value) if value is not None else TrainConfig() # return default training config when it is not set return TrainConfig() - - -def save_config(config, config_path): - """ - convert dict config to yaml. - """ - f = open(config_path, "w") - yaml.dump(config, f) - f.close() diff --git a/paddleslim/auto_compression/create_compressed_program.py b/paddleslim/auto_compression/create_compressed_program.py index 30276bbf..8a6c7db2 100644 --- a/paddleslim/auto_compression/create_compressed_program.py +++ b/paddleslim/auto_compression/create_compressed_program.py @@ -23,7 +23,7 @@ from ..dist import * from ..common.recover_program import recover_inference_program, _remove_fetch_node from ..common import get_logger from .strategy_config import ProgramInfo -from .utils import load_inference_model +from ..common.load_model import load_inference_model _logger = get_logger(__name__, level=logging.INFO) __all__ = [ @@ -52,7 +52,8 @@ def _create_optimizer(train_config): optimizer_builder = train_config['optimizer_builder'] assert isinstance( optimizer_builder, dict - ), f"Value of 'optimizer_builder' in train_config should be dict but got {type(optimizer_builder)}" + ), "Value of 'optimizer_builder' in train_config should be dict but got {}".format( + type(optimizer_builder)) if 'grad_clip' in optimizer_builder: g_clip_params = optimizer_builder['grad_clip'] g_clip_type = g_clip_params.pop('type') @@ -444,9 +445,8 @@ def build_prune_program(executor, "####################channel pruning##########################") for param in pruned_program.global_block().all_parameters(): if param.name in original_shapes: - _logger.info( - f"{param.name}, from {original_shapes[param.name]} to {param.shape}" - ) + _logger.info("{}, from {} to {}".format( + param.name, original_shapes[param.name], param.shape)) _logger.info( "####################channel pruning end##########################") train_program_info.program = pruned_program diff --git a/paddleslim/auto_compression/strategy_config.py b/paddleslim/auto_compression/strategy_config.py index 5226a7c8..d8b3e90c 100644 --- a/paddleslim/auto_compression/strategy_config.py +++ b/paddleslim/auto_compression/strategy_config.py @@ -53,7 +53,8 @@ class BaseStrategy: class Quantization(BaseStrategy): def __init__(self, quantize_op_types=[ - 'conv2d', 'depthwise_conv2d', 'mul', 'matmul', 'matmul_v2' + 'conv2d', 'depthwise_conv2d', 'conv2d_transpose', 'mul', + 'matmul', 'matmul_v2' ], weight_bits=8, activation_bits=8, @@ -65,6 +66,7 @@ class Quantization(BaseStrategy): window_size=10000, moving_rate=0.9, for_tensorrt=False, + onnx_format=False, is_full_quantize=False): """ Quantization Config. @@ -80,6 +82,7 @@ class Quantization(BaseStrategy): window_size(int): Window size for 'range_abs_max' quantization. Default: 10000. moving_rate(float): The decay coefficient of moving average. Default: 0.9. for_tensorrt(bool): If True, 'quantize_op_types' will be TENSORRT_OP_TYPES. Default: False. + onnx_format(bool): Whether to export the quantized model with format of ONNX. Default is False. is_full_quantize(bool): If True, 'quantoze_op_types' will be TRANSFORM_PASS_OP_TYPES + QUANT_DEQUANT_PASS_OP_TYPES. Default: False. """ super(Quantization, self).__init__("Quantization") @@ -95,6 +98,7 @@ class Quantization(BaseStrategy): self.window_size = window_size self.moving_rate = moving_rate self.for_tensorrt = for_tensorrt + self.onnx_format = onnx_format self.is_full_quantize = is_full_quantize diff --git a/paddleslim/auto_compression/utils/__init__.py b/paddleslim/auto_compression/utils/__init__.py index aa4f3ec0..e3c3a49d 100644 --- a/paddleslim/auto_compression/utils/__init__.py +++ b/paddleslim/auto_compression/utils/__init__.py @@ -14,11 +14,5 @@ from __future__ import absolute_import from .predict import predict_compressed_model -from .dataloader import * -from . import dataloader -from .load_model import * -from . import load_model __all__ = ["predict_compressed_model"] -__all__ += dataloader.__all__ -__all__ += load_model.__all__ diff --git a/paddleslim/auto_compression/utils/fake_ptq.py b/paddleslim/auto_compression/utils/fake_ptq.py index fbecc224..e86dd848 100644 --- a/paddleslim/auto_compression/utils/fake_ptq.py +++ b/paddleslim/auto_compression/utils/fake_ptq.py @@ -12,7 +12,7 @@ except: TRANSFORM_PASS_OP_TYPES = QuantizationTransformPass._supported_quantizable_op_type QUANT_DEQUANT_PASS_OP_TYPES = AddQuantDequantPass._supported_quantizable_op_type -from .load_model import load_inference_model +from ...common.load_model import load_inference_model def post_quant_fake(executor, diff --git a/paddleslim/auto_compression/utils/load_model.py b/paddleslim/auto_compression/utils/load_model.py deleted file mode 100644 index bb61ab56..00000000 --- a/paddleslim/auto_compression/utils/load_model.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import paddle - -__all__ = ['load_inference_model'] - - -def load_inference_model(path_prefix, - executor, - model_filename=None, - params_filename=None): - if model_filename is not None and params_filename is not None: - [inference_program, feed_target_names, fetch_targets] = ( - paddle.static.load_inference_model( - path_prefix=path_prefix, - executor=executor, - model_filename=model_filename, - params_filename=params_filename)) - else: - model_name = '.'.join(model_filename.split('.') - [:-1]) if model_filename is not None else 'model' - if os.path.exists(os.path.join(path_prefix, model_name + '.pdmodel')): - model_path_prefix = os.path.join(path_prefix, model_name) - [inference_program, feed_target_names, fetch_targets] = ( - paddle.static.load_inference_model( - path_prefix=model_path_prefix, executor=executor)) - else: - [inference_program, feed_target_names, fetch_targets] = ( - paddle.static.load_inference_model( - path_prefix=path_prefix, executor=executor)) - - return [inference_program, feed_target_names, fetch_targets] diff --git a/paddleslim/auto_compression/utils/predict.py b/paddleslim/auto_compression/utils/predict.py index 01ef6a90..5b8c6adb 100644 --- a/paddleslim/auto_compression/utils/predict.py +++ b/paddleslim/auto_compression/utils/predict.py @@ -4,7 +4,7 @@ import paddle from ...analysis import TableLatencyPredictor from .prune_model import get_sparse_model, get_prune_model from .fake_ptq import post_quant_fake -from .load_model import load_inference_model +from ...common.load_model import load_inference_model def with_variable_shape(model_dir, model_filename=None, params_filename=None): @@ -53,7 +53,7 @@ def predict_compressed_model(executor, latency_dict(dict): The latency latency of the model under various compression strategies. """ local_rank = paddle.distributed.get_rank() - quant_model_path = f'quant_model_rank_{local_rank}_tmp' + quant_model_path = 'quant_model_rank_{}_tmp'.format(local_rank) prune_model_path = f'prune_model_rank_{local_rank}_tmp' sparse_model_path = f'sparse_model_rank_{local_rank}_tmp' diff --git a/paddleslim/auto_compression/utils/prune_model.py b/paddleslim/auto_compression/utils/prune_model.py index 426a1859..c0da14ca 100644 --- a/paddleslim/auto_compression/utils/prune_model.py +++ b/paddleslim/auto_compression/utils/prune_model.py @@ -5,7 +5,7 @@ import paddle import paddle.static as static from ...prune import Pruner from ...core import GraphWrapper -from .load_model import load_inference_model +from ...common.load_model import load_inference_model __all__ = ["get_sparse_model", "get_prune_model"] @@ -19,9 +19,10 @@ def get_sparse_model(executor, places, model_file, param_file, ratio, ratio(float): The ratio to prune the model. save_path(str): The save path of pruned model. """ - assert os.path.exists(model_file), f'{model_file} does not exist.' + assert os.path.exists(model_file), '{} does not exist.'.format(model_file) assert os.path.exists( - param_file) or param_file is None, f'{param_file} does not exist.' + param_file) or param_file is None, '{} does not exist.'.format( + param_file) paddle.enable_static() SKIP = ['image', 'feed', 'pool2d_0.tmp_0'] diff --git a/paddleslim/common/__init__.py b/paddleslim/common/__init__.py index c3e40415..8b1ffc02 100644 --- a/paddleslim/common/__init__.py +++ b/paddleslim/common/__init__.py @@ -25,11 +25,16 @@ from .analyze_helper import VarCollector from . import wrapper_function from . import recover_program from . import patterns +from .load_model import load_inference_model, get_model_dir, load_onnx_model, export_onnx +from .dataloader import wrap_dataloader, get_feed_vars +from .config_helper import load_config, save_config __all__ = [ 'EvolutionaryController', 'SAController', 'get_logger', 'ControllerServer', 'ControllerClient', 'lock', 'unlock', 'cached_reader', 'AvgrageMeter', - 'Server', 'Client', 'RLBaseController', 'VarCollector' + 'Server', 'Client', 'RLBaseController', 'VarCollector', 'load_onnx_model', + 'load_inference_model', 'get_model_dir', 'wrap_dataloader', 'get_feed_vars', + 'load_config', 'save_config' ] __all__ += wrapper_function.__all__ diff --git a/paddleslim/common/config_helper.py b/paddleslim/common/config_helper.py new file mode 100644 index 00000000..486fa9b4 --- /dev/null +++ b/paddleslim/common/config_helper.py @@ -0,0 +1,60 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License" +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import yaml +import os + +__all__ = ['load_config', 'save_config'] + + +def print_arguments(args, level=0): + if level == 0: + print('----------- Running Arguments -----------') + for arg, value in sorted(args.items()): + if isinstance(value, dict): + print('\t' * level, '%s:' % arg) + print_arguments(value, level + 1) + else: + print('\t' * level, '%s: %s' % (arg, value)) + if level == 0: + print('------------------------------------------') + + +def load_config(config): + """Load configurations from yaml file into dict. + Fields validation is skipped for loading some custom information. + Args: + config(str): The path of configuration file. + Returns: + dict: A dict storing configuration information. + """ + if config is None: + return None + assert isinstance( + config, + str), f"config should be str but got type(config)={type(config)}" + assert os.path.exists(config) and os.path.isfile( + config), f"{config} not found or it is not a file." + with open(config) as f: + cfg = yaml.load(f, Loader=yaml.FullLoader) + print_arguments(cfg) + return cfg + + +def save_config(config, config_path): + """ + convert dict config to yaml. + """ + f = open(config_path, "w") + yaml.dump(config, f) + f.close() diff --git a/paddleslim/auto_compression/utils/dataloader.py b/paddleslim/common/dataloader.py similarity index 95% rename from paddleslim/auto_compression/utils/dataloader.py rename to paddleslim/common/dataloader.py index f0f36716..31e375de 100644 --- a/paddleslim/auto_compression/utils/dataloader.py +++ b/paddleslim/common/dataloader.py @@ -3,6 +3,7 @@ import time import numpy as np import paddle from collections.abc import Iterable +from .load_model import load_inference_model __all__ = ["wrap_dataloader", "get_feed_vars"] @@ -13,7 +14,7 @@ def get_feed_vars(model_dir, model_filename, params_filename): paddle.enable_static() exe = paddle.static.Executor(paddle.CPUPlace()) [inference_program, feed_target_names, fetch_targets] = ( - paddle.static.load_inference_model( + load_inference_model( model_dir, exe, model_filename=model_filename, diff --git a/paddleslim/common/load_model.py b/paddleslim/common/load_model.py new file mode 100644 index 00000000..cc545b31 --- /dev/null +++ b/paddleslim/common/load_model.py @@ -0,0 +1,222 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +import logging +import os +import shutil +import sys +import pkg_resources as pkg +import paddle + +from . import get_logger +_logger = get_logger(__name__, level=logging.INFO) + +__all__ = [ + 'load_inference_model', 'get_model_dir', 'load_onnx_model', 'export_onnx' +] + + +def load_inference_model(path_prefix, + executor, + model_filename=None, + params_filename=None): + # Load onnx model to Inference model. + if path_prefix.endswith('.onnx'): + inference_program, feed_target_names, fetch_targets = load_onnx_model( + path_prefix) + return [inference_program, feed_target_names, fetch_targets] + # Load Inference model. + # TODO: clean code + if model_filename is not None and model_filename.endswith('.pdmodel'): + model_name = '.'.join(model_filename.split('.')[:-1]) + assert os.path.exists( + os.path.join(path_prefix, model_name + '.pdmodel') + ), 'Please check {}, or fix model_filename parameter.'.format( + os.path.join(path_prefix, model_name + '.pdmodel')) + assert os.path.exists( + os.path.join(path_prefix, model_name + '.pdiparams') + ), 'Please check {}, or fix params_filename parameter.'.format( + os.path.join(path_prefix, model_name + '.pdiparams')) + model_path_prefix = os.path.join(path_prefix, model_name) + [inference_program, feed_target_names, fetch_targets] = ( + paddle.static.load_inference_model( + path_prefix=model_path_prefix, executor=executor)) + elif model_filename is not None and params_filename is not None: + [inference_program, feed_target_names, fetch_targets] = ( + paddle.static.load_inference_model( + path_prefix=path_prefix, + executor=executor, + model_filename=model_filename, + params_filename=params_filename)) + else: + model_name = '.'.join(model_filename.split('.') + [:-1]) if model_filename is not None else 'model' + if os.path.exists(os.path.join(path_prefix, model_name + '.pdmodel')): + model_path_prefix = os.path.join(path_prefix, model_name) + [inference_program, feed_target_names, fetch_targets] = ( + paddle.static.load_inference_model( + path_prefix=model_path_prefix, executor=executor)) + else: + [inference_program, feed_target_names, fetch_targets] = ( + paddle.static.load_inference_model( + path_prefix=path_prefix, executor=executor)) + + return [inference_program, feed_target_names, fetch_targets] + + +def get_model_dir(model_dir, model_filename, params_filename): + if model_dir.endswith('.onnx'): + updated_model_dir = model_dir.rstrip().rstrip('.onnx') + '_infer' + else: + updated_model_dir = model_dir.rstrip('/') + + if model_filename == None: + updated_model_filename = 'model.pdmodel' + else: + updated_model_filename = model_filename + + if params_filename == None: + updated_params_filename = 'model.pdiparams' + else: + updated_params_filename = params_filename + + if params_filename is None and model_filename is not None: + raise NotImplementedError( + "NOT SUPPORT parameters saved in separate files. Please convert it to single binary file first." + ) + return updated_model_dir, updated_model_filename, updated_params_filename + + +def load_onnx_model(model_path, disable_feedback=False): + assert model_path.endswith( + '.onnx' + ), '{} does not end with .onnx suffix and cannot be loaded.'.format( + model_path) + inference_model_path = model_path.rstrip().rstrip('.onnx') + '_infer' + exe = paddle.static.Executor(paddle.CPUPlace()) + if os.path.exists(os.path.join( + inference_model_path, 'model.pdmodel')) and os.path.exists( + os.path.join(inference_model_path, 'model.pdiparams')): + val_program, feed_target_names, fetch_targets = paddle.static.load_inference_model( + os.path.join(inference_model_path, 'model'), exe) + _logger.info('Loaded model from: {}'.format(inference_model_path)) + return val_program, feed_target_names, fetch_targets + else: + # onnx to paddle inference model. + assert os.path.exists( + model_path), 'Not found `{}`, please check model path.'.format( + model_path) + try: + pkg.require('x2paddle') + except: + from pip._internal import main + main(['install', 'x2paddle']) + # check onnx installation and version + try: + pkg.require('onnx') + import onnx + version = onnx.version.version + v0, v1, v2 = version.split('.') + version_sum = int(v0) * 100 + int(v1) * 10 + int(v2) + if version_sum < 160: + _logger.error( + "onnx>=1.6.0 is required, please use \"pip install onnx\".") + except: + from pip._internal import main + main(['install', 'onnx==1.12.0']) + + from x2paddle.decoder.onnx_decoder import ONNXDecoder + from x2paddle.op_mapper.onnx2paddle.onnx_op_mapper import ONNXOpMapper + from x2paddle.optimizer.optimizer import GraphOptimizer + from x2paddle.utils import ConverterCheck + time_info = int(time.time()) + if not disable_feedback: + ConverterCheck( + task="ONNX", time_info=time_info, convert_state="Start").start() + # support distributed convert model + model_idx = paddle.distributed.get_rank( + ) if paddle.distributed.get_world_size() > 1 else 0 + try: + _logger.info("Now translating model from onnx to paddle.") + model = ONNXDecoder(model_path) + mapper = ONNXOpMapper(model) + mapper.paddle_graph.build() + graph_opt = GraphOptimizer(source_frame="onnx") + graph_opt.optimize(mapper.paddle_graph) + _logger.info("Model optimized.") + onnx2paddle_out_dir = os.path.join( + inference_model_path, 'onnx2paddle_{}'.format(model_idx)) + mapper.paddle_graph.gen_model(onnx2paddle_out_dir) + _logger.info("Successfully exported Paddle static graph model!") + if not disable_feedback: + ConverterCheck( + task="ONNX", time_info=time_info, + convert_state="Success").start() + except Exception as e: + _logger.warning(e) + _logger.error( + "x2paddle threw an exception, you can ask for help at: https://github.com/PaddlePaddle/X2Paddle/issues" + ) + sys.exit(1) + + if paddle.distributed.get_rank() == 0: + shutil.move( + os.path.join(onnx2paddle_out_dir, 'inference_model', + 'model.pdmodel'), + os.path.join(inference_model_path, 'model.pdmodel')) + shutil.move( + os.path.join(onnx2paddle_out_dir, 'inference_model', + 'model.pdiparams'), + os.path.join(inference_model_path, 'model.pdiparams')) + load_model_path = inference_model_path + else: + load_model_path = os.path.join(onnx2paddle_out_dir, + 'inference_model') + + paddle.enable_static() + val_program, feed_target_names, fetch_targets = paddle.static.load_inference_model( + os.path.join(load_model_path, 'model'), exe) + _logger.info('Loaded model from: {}'.format(load_model_path)) + # Clean up the file storage directory + shutil.rmtree( + os.path.join(inference_model_path, 'onnx2paddle_{}'.format( + model_idx))) + return val_program, feed_target_names, fetch_targets + + +def export_onnx(model_dir, + model_filename=None, + params_filename=None, + save_file_path='output.onnx', + opset_version=13, + deploy_backend='tensorrt'): + if not model_filename: + model_filename = 'model.pdmodel' + if not params_filename: + params_filename = 'model.pdiparams' + try: + pkg.require('paddle2onnx') + except: + from pip._internal import main + main(['install', 'paddle2onnx==1.0.0rc3']) + import paddle2onnx + paddle2onnx.command.c_paddle_to_onnx( + model_file=os.path.join(model_dir, model_filename), + params_file=os.path.join(model_dir, params_filename), + save_file=save_file_path, + opset_version=opset_version, + enable_onnx_checker=True, + deploy_backend=deploy_backend) + _logger.info('Convert model to ONNX: {}'.format(save_file_path)) diff --git a/paddleslim/dygraph/prune/pruning_plan.py b/paddleslim/dygraph/prune/pruning_plan.py index d9cd8e4a..cd669ffd 100644 --- a/paddleslim/dygraph/prune/pruning_plan.py +++ b/paddleslim/dygraph/prune/pruning_plan.py @@ -220,7 +220,7 @@ class PruningPlan(): t_value = param.value().get_tensor() value = np.array(t_value).astype("float32") groups = _mask._op.attr('groups') - if dims == 1 and groups is not None and groups > 1 and len( + if groups is not None and groups > 1 and len( value.shape) == 4: filter_size = value.shape[1] except_num = np.sum(bool_mask) @@ -230,7 +230,6 @@ class PruningPlan(): sub_layer._groups = new_groups _logger.info("change groups from {} to {} for {}.". format(groups, new_groups, param.name)) - continue # The name of buffer can not contains "." backup_name = param.name.replace(".", "_") + "_backup" diff --git a/paddleslim/prune/prune_worker.py b/paddleslim/prune/prune_worker.py index 25703c66..c9c69f62 100644 --- a/paddleslim/prune/prune_worker.py +++ b/paddleslim/prune/prune_worker.py @@ -522,7 +522,6 @@ class depthwise_conv2d(PruneWorker): channel_axis = 1 if data_format == "NHWC": channel_axis = 3 - if var == _in_var: assert pruned_axis == channel_axis, "The Input of conv2d can only be pruned at channel axis, but got {}".format( pruned_axis) @@ -533,7 +532,6 @@ class depthwise_conv2d(PruneWorker): "repeat": repeat }]) # kernel_number * groups will be pruned by reducing groups - self.append_pruned_vars(_filter, 1, transforms) self._visit_and_search(_filter, 0, transforms + [{ "repeat": repeat }]) @@ -546,14 +544,13 @@ class depthwise_conv2d(PruneWorker): }]) elif var == _filter: assert pruned_axis == 0, "The filter of depthwise conv2d can only be pruned at axis 0." - self.append_pruned_vars(_filter, 1, transforms) + self.append_pruned_vars(_filter, 0, transforms) self._visit_and_search(_in_var, channel_axis, transforms) self._visit_and_search(_out, channel_axis, transforms) elif var == _out: assert pruned_axis == channel_axis, "The Input of conv2d can only be pruned at channel axis, but got {}".format( pruned_axis) self.append_pruned_vars(_filter, 0, transforms) - self.append_pruned_vars(_filter, 1, transforms) self._visit_and_search(_filter, 0, transforms) # It will not pruning number of kernels in depthwise conv2d, # so it is not neccesary to search succeed operators. diff --git a/paddleslim/prune/pruner.py b/paddleslim/prune/pruner.py index 1926c8cb..4c58c2e1 100644 --- a/paddleslim/prune/pruner.py +++ b/paddleslim/prune/pruner.py @@ -117,7 +117,7 @@ class Pruner(): _groups = 1 if not lazy: # update groups of conv2d - if pruned_axis == 1: + if pruned_axis == 1 or pruned_axis == 0: for op in param.outputs(): if op.type() in [ "conv2d", "conv2d_grad", "depthwise_conv2d", @@ -132,7 +132,7 @@ class Pruner(): f"change groups of {op.type()}({param.name()}) from {op.attr('groups')} to {new_groups};" ) op.set_attr("groups", new_groups) - if _groups == 1: + origin_shape = copy.deepcopy(param.shape()) if param_shape_backup is not None: param_shape_backup[param.name()] = origin_shape diff --git a/paddleslim/quant/__init__.py b/paddleslim/quant/__init__.py index 8b472e72..69e54296 100644 --- a/paddleslim/quant/__init__.py +++ b/paddleslim/quant/__init__.py @@ -35,7 +35,7 @@ try: except Exception as e: _logger.warning(e) _logger.warning( - f"If you want to use training-aware and post-training quantization, " - "please use Paddle >= {min_paddle_version} or develop version") + "If you want to use training-aware and post-training quantization, " + "please use Paddle >= {} or develop version".format(min_paddle_version)) from .quant_embedding import quant_embedding diff --git a/paddleslim/quant/analysis.py b/paddleslim/quant/analysis.py new file mode 100644 index 00000000..d81e80f8 --- /dev/null +++ b/paddleslim/quant/analysis.py @@ -0,0 +1,312 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import pickle +import copy +import logging +import matplotlib.pyplot as plt +from matplotlib.backends.backend_pdf import PdfPages +import numpy as np + +import paddle +from paddle.fluid import core +from paddle.fluid import framework +from paddle.fluid.framework import IrGraph +from paddle.fluid.executor import global_scope +from paddle.fluid.contrib.slim.quantization import PostTrainingQuantization +from paddle.fluid.contrib.slim.quantization.utils import _get_op_input_var_names, load_variable_data +from .quanter import quant_post +from ..core import GraphWrapper +from ..common import get_logger +from ..common import get_feed_vars, wrap_dataloader, load_inference_model, get_model_dir + +_logger = get_logger(__name__, level=logging.INFO) + +__all__ = ["AnalysisQuant"] + + +class AnalysisQuant(object): + def __init__(self, + model_dir, + model_filename=None, + params_filename=None, + eval_function=None, + data_loader=None, + save_dir='analysis_results', + checkpoint_name='analysis_checkpoint.pkl', + num_histogram_plots=10, + ptq_config=None): + """ + AnalysisQuant provides to analysis the sensitivity of each op in the model. + + Args: + model_dir(str): the path of fp32 model that will be quantized, it can also be '.onnx' + model_filename(str, optional): the model file name of the fp32 model + params_filename(str, optional): the parameter file name of the fp32 model + eval_function(function): eval function, define by yourself to return the metric of the inference program, can be used to judge the metric of quantized model. (TODO: optional) + data_loader(Python Generator, Paddle.io.DataLoader, optional): the + Generator or Dataloader provides calibrate data, and it could + return a batch every time + save_dir(str, optional): the output dir that stores the analyzed information + checkpoint_name(str, optional): the name of checkpoint file that saves analyzed information and avoids break off while ananlyzing + ptq_config(dict, optional): the args that can initialize PostTrainingQuantization + + """ + if model_filename is None: + model_filename = 'model.pdmodel' + if params_filename is None: + params_filename = 'model.pdiparams' + self.model_dir = model_dir + self.model_filename = model_filename + self.params_filename = params_filename + self.histogram_bins = 1000 + self.save_dir = save_dir + self.eval_function = eval_function + self.quant_layer_names = [] + self.checkpoint_name = os.path.join(save_dir, checkpoint_name) + self.quant_layer_metrics = {} + self.num_histogram_plots = num_histogram_plots + self.ptq_config = ptq_config + self.batch_nums = ptq_config[ + 'batch_nums'] if 'batch_nums' in ptq_config else 10 + + if not os.path.exists(self.save_dir): + os.mkdir(self.save_dir) + + devices = paddle.device.get_device().split(':')[0] + self.places = paddle.device._convert_to_place(devices) + executor = paddle.static.Executor(self.places) + + # load model + [program, self.feed_list, self.fetch_list]= load_inference_model( \ + self.model_dir, \ + executor=executor, \ + model_filename=self.model_filename, \ + params_filename=self.params_filename) + + # create data_loader + self.data_loader = wrap_dataloader(data_loader, self.feed_list) + + # evaluate before quant + # TODO: self.eval_function can be None + if self.eval_function is not None: + self.base_metric = self.eval_function( + executor, program, self.feed_list, self.fetch_list) + _logger.info('before quantized, the accuracy of the model is: {}'. + format(self.base_metric)) + + # quant and evaluate after quant (skip_list = None) + post_training_quantization = PostTrainingQuantization( + executor=executor, + data_loader=self.data_loader, + model_dir=self.model_dir, + model_filename=self.model_filename, + params_filename=self.params_filename, + skip_tensor_list=None, + algo='avg', #fastest + **self.ptq_config) + program = post_training_quantization.quantize() + self.quant_metric = self.eval_function(executor, program, + self.feed_list, self.fetch_list) + _logger.info('after quantized, the accuracy of the model is: {}'.format( + self.quant_metric)) + + # get quantized weight and act var name + self.quantized_weight_var_name = post_training_quantization._quantized_weight_var_name + self.quantized_act_var_name = post_training_quantization._quantized_act_var_name + executor.close() + + # load tobe_analyized_layer from checkpoint + self.load_checkpoint() + self.tobe_analyized_layer = self.quantized_weight_var_name - set( + list(self.quant_layer_metrics.keys())) + self.tobe_analyized_layer = sorted(list(self.tobe_analyized_layer)) + + def analysis(self): + self.compute_quant_sensitivity() + self.sensitivity_ranklist = sorted( + self.quant_layer_metrics, + key=self.quant_layer_metrics.get, + reverse=False) + + _logger.info('Finished computing the sensitivity of the model.') + for name in self.sensitivity_ranklist: + _logger.info("quant layer name: {}, eval metric: {}".format( + name, self.quant_layer_metrics[name])) + + analysis_file = os.path.join(self.save_dir, "analysis.txt") + with open(analysis_file, "w") as analysis_ret_f: + for name in self.sensitivity_ranklist: + analysis_ret_f.write( + "quant layer name: {}, eval metric: {}\n".format( + name, self.quant_layer_metrics[name])) + _logger.info('Analysis file is saved in {}'.format(analysis_file)) + self.calculate_histogram() + + def save_checkpoint(self): + if not os.path.exists(self.save_dir): + os.makedirs(self.save_dir) + with open(self.checkpoint_name, 'wb') as f: + pickle.dump(self.quant_layer_metrics, f) + _logger.info('save checkpoint to {}'.format(self.checkpoint_name)) + + def load_checkpoint(self): + if not os.path.exists(self.checkpoint_name): + return False + with open(self.checkpoint_name, 'rb') as f: + self.quant_layer_metrics = pickle.load(f) + _logger.info('load checkpoint from {}'.format(self.checkpoint_name)) + return True + + def compute_quant_sensitivity(self): + ''' + For each layer, quantize the weight op and evaluate the quantized model. + ''' + for i, layer_name in enumerate(self.tobe_analyized_layer): + _logger.info('checking {}/{} quant model: quant layer {}'.format( + i + 1, len(self.tobe_analyized_layer), layer_name)) + skip_list = copy.copy(list(self.quantized_weight_var_name)) + skip_list.remove(layer_name) + + executor = paddle.static.Executor(self.places) + post_training_quantization = PostTrainingQuantization( + executor=executor, + data_loader=self.data_loader, + model_dir=self.model_dir, + model_filename=self.model_filename, + params_filename=self.params_filename, + skip_tensor_list=skip_list, + algo='avg', #fastest + **self.ptq_config) + program = post_training_quantization.quantize() + + _logger.info('Evaluating...') + quant_metric = self.eval_function(executor, program, self.feed_list, + self.fetch_list) + executor.close() + _logger.info( + "quant layer name: {}, eval metric: {}, the loss caused by this layer: {}". + format(layer_name, quant_metric, self.base_metric - + quant_metric)) + self.quant_layer_metrics[layer_name] = quant_metric + self.save_checkpoint() + + def get_act_name_by_weight(self, program, weight_names, + persistable_var_names): + act_ops_names = [] + for op_name in weight_names: + for block_id in range(len(program.blocks)): + for op in program.blocks[block_id].ops: + var_name_list = _get_op_input_var_names(op) + if op_name in var_name_list: + for var_name in var_name_list: + if var_name not in persistable_var_names: + act_ops_names.append(var_name) + return act_ops_names + + def get_hist_ops_name(self, graph, program): + if self.num_histogram_plots <= 0: + return [] + + best_weight_ops = self.sensitivity_ranklist[::-1][:self. + num_histogram_plots] + worst_weight_ops = self.sensitivity_ranklist[:self.num_histogram_plots] + + persistable_var_names = [] + for var in program.list_vars(): + if var.persistable: + persistable_var_names.append(var.name) + + best_act_ops = self.get_act_name_by_weight(program, best_weight_ops, + persistable_var_names) + worst_act_ops = self.get_act_name_by_weight(program, worst_weight_ops, + persistable_var_names) + return [best_weight_ops, best_act_ops, worst_weight_ops, worst_act_ops] + + def collect_ops_histogram(self, scope, ops): + hist = {} + for var_name in ops: + var_tensor = load_variable_data(scope, var_name) + var_tensor = np.array(var_tensor) + min_v = float(np.min(var_tensor)) + max_v = float(np.max(var_tensor)) + var_tensor = var_tensor.flatten() + _, hist_edges = np.histogram( + var_tensor.copy(), + bins=self.histogram_bins, + range=(min_v, max_v)) + hist[var_name] = [var_tensor, hist_edges] + return hist + + def calculate_histogram(self): + ''' + Sample histograms for the weight and corresponding act tensors + ''' + devices = paddle.device.get_device().split(':')[0] + places = paddle.device._convert_to_place(devices) + executor = paddle.static.Executor(places) + + [program, feed_list, fetch_list]= load_inference_model( \ + self.model_dir, \ + executor=executor, \ + model_filename=self.model_filename, \ + params_filename=self.params_filename) + + scope = global_scope() + + graph = IrGraph(core.Graph(program.desc), for_test=False) + ops_tobe_draw_hist = self.get_hist_ops_name(graph, program) + if not ops_tobe_draw_hist: + return + + for var in program.list_vars(): + if var.name in self.quantized_act_var_name: + var.persistable = True + + # sample before collect histogram + batch_id = 0 + for data in self.data_loader(): + executor.run(program=program, + feed=data, + fetch_list=fetch_list, + return_numpy=False, + scope=scope) + batch_id += 1 + if batch_id >= self.batch_nums: + break + + pdf_names = [ + 'best_weight_hist_result.pdf', + 'best_act_hist_result.pdf', + 'worst_weight_hist_result.pdf', + 'worst_act_hist_result.pdf', + ] + for ops, save_pdf_name in zip(ops_tobe_draw_hist, pdf_names): + hist_data = self.collect_ops_histogram(scope, ops) + self.draw_pdf(hist_data, save_pdf_name) + + def draw_pdf(self, hist_data, save_pdf_name): + pdf_path = os.path.join(self.save_dir, save_pdf_name) + with PdfPages(pdf_path) as pdf: + for name in hist_data: + plt.hist(hist_data[name][0], bins=hist_data[name][1]) + plt.xlabel(name) + plt.ylabel("frequency") + plt.title("Hist of variable {}".format(name)) + plt.show() + pdf.savefig() + plt.close() + _logger.info('Histogram plot is saved in {}'.format(pdf_path)) diff --git a/paddleslim/quant/post_quant_hpo.py b/paddleslim/quant/post_quant_hpo.py index e6bc1d9f..dc41e48d 100755 --- a/paddleslim/quant/post_quant_hpo.py +++ b/paddleslim/quant/post_quant_hpo.py @@ -29,13 +29,7 @@ import shutil import glob from scipy.stats import wasserstein_distance -# smac -from ConfigSpace.hyperparameters import CategoricalHyperparameter, \ - UniformFloatHyperparameter, UniformIntegerHyperparameter -from smac.configspace import ConfigurationSpace -from smac.facade.smac_hpo_facade import SMAC4HPO -from smac.scenario.scenario import Scenario - +import pkg_resources as pkg from paddleslim.common import get_logger from paddleslim.quant import quant_post @@ -417,6 +411,18 @@ def quant_post_hpo( None """ + try: + pkg.require('smac') + except: + from pip._internal import main + main(['install', 'smac']) + # smac + from ConfigSpace.hyperparameters import CategoricalHyperparameter, \ + UniformFloatHyperparameter, UniformIntegerHyperparameter + from smac.configspace import ConfigurationSpace + from smac.facade.smac_hpo_facade import SMAC4HPO + from smac.scenario.scenario import Scenario + global g_quant_config g_quant_config = QuantConfig( executor, place, model_dir, quantize_model_path, algo, hist_percent, diff --git a/paddleslim/quant/quanter.py b/paddleslim/quant/quanter.py index 9e07c03c..9f8ed323 100755 --- a/paddleslim/quant/quanter.py +++ b/paddleslim/quant/quanter.py @@ -16,9 +16,14 @@ import os import copy import json import logging +import collections +import numpy as np import paddle +from paddle.fluid import core +from paddle.fluid.layer_helper import LayerHelper from paddle.fluid.framework import IrGraph +from paddle.fluid.contrib.slim.quantization import WeightQuantization from paddle.fluid.contrib.slim.quantization import QuantizationTransformPass from paddle.fluid.contrib.slim.quantization import QuantizationFreezePass from paddle.fluid.contrib.slim.quantization import ConvertToInt8Pass @@ -27,18 +32,17 @@ from paddle.fluid.contrib.slim.quantization import PostTrainingQuantization from paddle.fluid.contrib.slim.quantization import AddQuantDequantPass from paddle.fluid.contrib.slim.quantization import OutScaleForTrainingPass from paddle.fluid.contrib.slim.quantization import OutScaleForInferencePass +from ..common import get_logger +_logger = get_logger(__name__, level=logging.INFO) + try: from paddle.fluid.contrib.slim.quantization import QuantizationTransformPassV2 from paddle.fluid.contrib.slim.quantization import QuantWeightPass from paddle.fluid.contrib.slim.quantization import AddQuantDequantPassV2 except: - pass -from paddle.fluid import core -from paddle.fluid.contrib.slim.quantization import WeightQuantization -from paddle.fluid.layer_helper import LayerHelper - -from ..common import get_logger -_logger = get_logger(__name__, level=logging.INFO) + _logger.warning( + "Some functions fail to import, please update PaddlePaddle version to 2.3+" + ) WEIGHT_QUANTIZATION_TYPES = [ 'abs_max', 'channel_wise_abs_max', 'range_abs_max', 'moving_average_abs_max' @@ -91,27 +95,54 @@ _quant_config_default = { # if True, 'quantize_op_types' will be TENSORRT_OP_TYPES 'for_tensorrt': False, # if True, 'quantoze_op_types' will be TRANSFORM_PASS_OP_TYPES + QUANT_DEQUANT_PASS_OP_TYPES - 'is_full_quantize': False + 'is_full_quantize': False, + # if True, use onnx format to quant. + 'onnx_format': False, } -# TODO: Hard-code, remove it when Paddle 2.3.1 -class OutScaleForTrainingPassV2(OutScaleForTrainingPass): - def __init__(self, scope=None, place=None, moving_rate=0.9): - OutScaleForTrainingPass.__init__( - self, scope=scope, place=place, moving_rate=moving_rate) - - def _scale_name(self, var_name): +class OutScaleForInferencePassV2(object): + def __init__(self, scope=None): """ - Return the scale name for the var named `var_name`. + This pass is used for setting output scales of some operators. + These output scales may be used by tensorRT or some other inference engines. + + Args: + scope(fluid.Scope): The scope is used to initialize these new parameters. """ - return "%s@scale" % (var_name) + self._scope = scope + self._teller_set = utils._out_scale_op_list + def apply(self, graph): + """ + Get output scales from the scope and set these scales in op_descs + of operators in the teller_set. -# TODO: Hard-code, remove it when Paddle 2.3.1 -class OutScaleForInferencePassV2(OutScaleForInferencePass): - def __init__(self, scope=None): - OutScaleForInferencePass.__init__(self, scope=scope) + Args: + graph(IrGraph): the target graph. + """ + assert isinstance(graph, + IrGraph), 'graph must be the instance of IrGraph.' + collect_dict = collections.OrderedDict() + op_nodes = graph.all_op_nodes() + for op_node in op_nodes: + if op_node.name() in self._teller_set: + var_names = utils._get_op_output_var_names(op_node) + for var_name in var_names: + in_node = graph._find_node_by_name(op_node.outputs, + var_name) + if in_node.dtype() not in \ + [core.VarDesc.VarType.FP64, core.VarDesc.VarType.FP32]: + continue + + collect_dict[var_name] = {} + scale_name = self._scale_name(var_name) + scale_var = self._scope.find_var(scale_name) + assert scale_var is not None, \ + "Can not find {} variable in the scope".format(scale_name) + scale_value = np.array(scale_var.get_tensor())[0] + collect_dict[var_name]['scale'] = float(scale_value) + return graph, collect_dict def _scale_name(self, var_name): """ @@ -222,7 +253,6 @@ def quant_aware(program, act_preprocess_func=None, optimizer_func=None, executor=None, - onnx_format=False, return_program=False, draw_graph=False): """Add quantization and dequantization operators to "program" @@ -236,7 +266,9 @@ def quant_aware(program, 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 `_. When ``None`` will use `paddle.static.global_scope() `_ . Default: ``None``. + `paddle.static.global_scope `_. + When ``None`` will use `paddle.static.global_scope() `_ . + 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 @@ -291,7 +323,8 @@ def quant_aware(program, elif op_type in QUANT_DEQUANT_PASS_OP_TYPES: quant_dequant_ops.append(op_type) if len(transform_pass_ops) > 0: - trannsform_func = 'QuantizationTransformPassV2' if onnx_format else 'QuantizationTransformPass' + trannsform_func = 'QuantizationTransformPassV2' if config[ + 'onnx_format'] else 'QuantizationTransformPass' transform_pass = eval(trannsform_func)( scope=scope, place=place, @@ -313,7 +346,8 @@ def quant_aware(program, transform_pass.apply(main_graph) if len(quant_dequant_ops) > 0: - qdq_func = 'AddQuantDequantPassV2' if onnx_format else 'AddQuantDequantPass' + qdq_func = 'AddQuantDequantPassV2' if config[ + 'onnx_format'] else 'AddQuantDequantPass' quant_dequant_pass = eval(qdq_func)( scope=scope, place=place, @@ -323,7 +357,7 @@ def quant_aware(program, quantizable_op_type=quant_dequant_ops) quant_dequant_pass.apply(main_graph) - out_scale_training_pass = OutScaleForTrainingPassV2( + out_scale_training_pass = OutScaleForTrainingPass( scope=scope, place=place, moving_rate=config['moving_rate']) out_scale_training_pass.apply(main_graph) @@ -356,8 +390,8 @@ def quant_post_static( data_loader=None, model_filename=None, params_filename=None, - save_model_filename='__model__', - save_params_filename='__params__', + save_model_filename='model.pdmodel', + save_params_filename='model.pdiparams', batch_size=1, batch_nums=None, scope=None, @@ -405,9 +439,9 @@ def quant_post_static( When all parameters are saved in a single file, set it as filename. If parameters are saved in separate files, set it as 'None'. Default : 'None'. - save_model_filename(str): The name of model file to save the quantized inference program. Default: '__model__'. + save_model_filename(str): The name of model file to save the quantized inference program. Default: 'model.pdmodel'. save_params_filename(str): The name of file to save all related parameters. - If it is set None, parameters will be saved in separate files. Default: '__params__'. + If it is set None, parameters will be saved in separate files. Default: 'model.pdiparams'. batch_size(int, optional): The batch size of DataLoader, default is 1. batch_nums(int, optional): If batch_nums is not None, the number of calibrate data is 'batch_size*batch_nums'. If batch_nums is None, use all data @@ -508,6 +542,22 @@ def quant_post_static( quantize_model_path, model_filename=save_model_filename, params_filename=save_params_filename) + if onnx_format: + try: + collect_dict = post_training_quantization._calibration_scales + save_quant_table_path = os.path.join(quantize_model_path, + 'calibration_table.txt') + with open(save_quant_table_path, 'w') as txt_file: + for tensor_name in collect_dict.keys(): + write_line = '{} {}'.format( + tensor_name, collect_dict[tensor_name]['scale']) + '\n' + txt_file.write(write_line) + _logger.info("Quantization clip ranges of tensors is save in: {}". + format(save_quant_table_path)) + except: + _logger.warning( + "Unable to generate `calibration_table.txt`, please update PaddlePaddle >= 2.3.3" + ) # We have changed the quant_post to quant_post_static. @@ -521,7 +571,7 @@ def convert(program, config=None, scope=None, save_int8=False, - onnx_format=False): + save_clip_ranges_path='./'): """ convert quantized and well-trained ``program`` to final quantized ``program``that can be used to save ``inference model``. @@ -543,6 +593,7 @@ def convert(program, save_int8: Whether to return ``program`` which model parameters' dtype is ``int8``. This parameter can only be used to get model size. Default: ``False``. + save_clip_ranges_path: If config.onnx_format=True, quantization clip ranges will be saved locally. Returns: Tuple : freezed program which can be used for inference. @@ -560,11 +611,22 @@ def convert(program, _logger.info("convert config {}".format(config)) test_graph = IrGraph(core.Graph(program.desc), for_test=True) - if onnx_format: + if config['onnx_format']: quant_weight_pass = QuantWeightPass(scope, place) quant_weight_pass.apply(test_graph) - else: out_scale_infer_pass = OutScaleForInferencePassV2(scope=scope) + _, collect_dict = out_scale_infer_pass.apply(test_graph) + save_quant_table_path = os.path.join(save_clip_ranges_path, + 'calibration_table.txt') + with open(save_quant_table_path, 'w') as txt_file: + for tensor_name in collect_dict.keys(): + write_line = '{} {}'.format( + tensor_name, collect_dict[tensor_name]['scale']) + '\n' + txt_file.write(write_line) + _logger.info("Quantization clip ranges of tensors is save in: {}". + format(save_quant_table_path)) + else: + out_scale_infer_pass = OutScaleForInferencePass(scope=scope) out_scale_infer_pass.apply(test_graph) # Freeze the graph after training by adjusting the quantize # operators' order for the inference. diff --git a/requirements.txt b/requirements.txt index 8ed081ba..d558b221 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,6 @@ -#paddlepaddle == 1.6.0rc0 tqdm pyzmq matplotlib pillow pyyaml scikit-learn -smac diff --git a/tests/act/test_act_api.py b/tests/act/test_act_api.py index ec73d46e..2b2a8b6c 100644 --- a/tests/act/test_act_api.py +++ b/tests/act/test_act_api.py @@ -8,7 +8,8 @@ import unittest import numpy as np from paddle.io import Dataset from paddleslim.auto_compression import AutoCompression -from paddleslim.auto_compression.config_helpers import load_config +from paddleslim.common import load_config +from paddleslim.common import load_inference_model, export_onnx class RandomEvalDataset(Dataset): @@ -120,5 +121,36 @@ class TestDictQATDist(ACTBase): ac.compress() +class TestLoadONNXModel(ACTBase): + def __init__(self, *args, **kwargs): + super(TestLoadONNXModel, self).__init__(*args, **kwargs) + os.system( + 'wget https://paddle-slim-models.bj.bcebos.com/act/yolov5s.onnx') + self.model_dir = 'yolov5s.onnx' + + def test_compress(self): + place = paddle.CPUPlace() + exe = paddle.static.Executor(place) + _, _, _ = load_inference_model( + self.model_dir, + executor=exe, + model_filename='model.pdmodel', + params_filename='model.paiparams') + # reload model + _, _, _ = load_inference_model( + self.model_dir, + executor=exe, + model_filename='model.pdmodel', + params_filename='model.paiparams') + # convert onnx + export_onnx( + self.model_dir, + model_filename='model.pdmodel', + params_filename='model.paiparams', + save_file_path='output.onnx', + opset_version=13, + deploy_backend='tensorrt') + + if __name__ == '__main__': unittest.main() diff --git a/tests/test_prune_walker.py b/tests/test_prune_walker.py index e0375c8b..f2f2d2fc 100644 --- a/tests/test_prune_walker.py +++ b/tests/test_prune_walker.py @@ -473,9 +473,9 @@ class TestDepthwiseConv2d(TestPruneWorker): def set_cases(self): weight_var = self.graph.var('conv1.w_0') - self.cases.append((self.in_var, 1, {'conv1.w_0': [0, 1]})) - self.cases.append((self.out_var, 1, {'conv1.w_0': [0, 1]})) - self.cases.append((weight_var, 0, {'conv1.w_0': [1]})) + self.cases.append((self.in_var, 1, {'conv1.w_0': [0]})) + self.cases.append((self.out_var, 1, {'conv1.w_0': [0]})) + self.cases.append((weight_var, 0, {'conv1.w_0': [0]})) def test_prune(self): self.check_in_out() diff --git a/tests/test_quant_post.py b/tests/test_quant_post.py index 39c3a2fe..31eed36e 100644 --- a/tests/test_quant_post.py +++ b/tests/test_quant_post.py @@ -132,8 +132,8 @@ class TestQuantAwareCase1(StaticCase): quant_post_prog, feed_target_names, fetch_targets = paddle.fluid.io.load_inference_model( dirname='./test_quant_post_inference', executor=exe, - model_filename='__model__', - params_filename='__params__') + model_filename='model.pdmodel', + params_filename='model.pdiparams') top1_2, top5_2 = test(quant_post_prog, fetch_targets) print("before quantization: top1: {}, top5: {}".format(top1_1, top5_1)) print("after quantization: top1: {}, top5: {}".format(top1_2, top5_2)) -- GitLab