# 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 os import tempfile import time import unittest import numpy as np from imperative_test_utils import ( ImperativeLenet, ImperativeLinearBn, ImperativeLinearBn_hook, ) import paddle from paddle import nn from paddle.dataset.common import download from paddle.quantization import ( AbsmaxQuantizer, HistQuantizer, ImperativePTQ, KLQuantizer, PerChannelAbsmaxQuantizer, PTQConfig, ) from paddle.static.log_helper import get_logger _logger = get_logger( __name__, logging.INFO, fmt='%(asctime)s-%(levelname)s: %(message)s' ) class TestFuseLinearBn(unittest.TestCase): """ Fuse the linear and bn layers, and then quantize the model. """ def test_fuse(self): model = ImperativeLinearBn() model_h = ImperativeLinearBn_hook() inputs = paddle.randn((3, 10), dtype="float32") config = PTQConfig(AbsmaxQuantizer(), AbsmaxQuantizer()) ptq = ImperativePTQ(config) f_l = [['linear', 'bn']] quant_model = ptq.quantize(model, fuse=True, fuse_list=f_l) quant_h = ptq.quantize(model_h, fuse=True, fuse_list=f_l) for name, layer in quant_model.named_sublayers(): if name in f_l: assert not (isinstance(layer, (nn.BatchNorm1D, nn.BatchNorm2D))) out = model(inputs) out_h = model_h(inputs) out_quant = quant_model(inputs) out_quant_h = quant_h(inputs) cos_sim_func = nn.CosineSimilarity(axis=0) print( 'fuse linear+bn', cos_sim_func(out.flatten(), out_quant.flatten()) ) print(cos_sim_func(out_h.flatten(), out_quant_h.flatten())) class TestImperativePTQ(unittest.TestCase): """ """ @classmethod def setUpClass(cls): cls.download_path = 'dygraph_int8/download' cls.cache_folder = os.path.expanduser( '~/.cache/paddle/dataset/' + cls.download_path ) cls.lenet_url = "https://paddle-inference-dist.cdn.bcebos.com/int8/unittest_model_data/lenet_pretrained.tar.gz" cls.lenet_md5 = "953b802fb73b52fae42896e3c24f0afb" seed = 1 np.random.seed(seed) paddle.seed(seed) def cache_unzipping(self, target_folder, zip_path): if not os.path.exists(target_folder): cmd = ( f'mkdir {target_folder} && tar xf {zip_path} -C {target_folder}' ) os.system(cmd) def download_model(self, data_url, data_md5, folder_name): download(data_url, self.download_path, data_md5) file_name = data_url.split('/')[-1] zip_path = os.path.join(self.cache_folder, file_name) print(f'Data is downloaded at {zip_path}') data_cache_folder = os.path.join(self.cache_folder, folder_name) self.cache_unzipping(data_cache_folder, zip_path) return data_cache_folder def set_vars(self): config = PTQConfig(AbsmaxQuantizer(), AbsmaxQuantizer()) self.ptq = ImperativePTQ(config) self.batch_num = 10 self.batch_size = 10 self.eval_acc_top1 = 0.95 # the input, output and weight thresholds of quantized op self.gt_thresholds = { 'conv2d_0': [[1.0], [0.37673383951187134], [0.10933732241392136]], 'batch_norm2d_0': [[0.37673383951187134], [0.44249194860458374]], 're_lu_0': [[0.44249194860458374], [0.25804123282432556]], 'max_pool2d_0': [[0.25804123282432556], [0.25804123282432556]], 'linear_0': [ [1.7058950662612915], [14.405526161193848], [0.4373355209827423], ], 'add_0': [[1.7058950662612915, 0.0], [1.7058950662612915]], } def model_test(self, model, batch_num=-1, batch_size=8): model.eval() test_reader = paddle.batch( paddle.dataset.mnist.test(), batch_size=batch_size ) eval_acc_top1_list = [] for batch_id, data in enumerate(test_reader()): x_data = np.array([x[0].reshape(1, 28, 28) for x in data]).astype( 'float32' ) y_data = ( np.array([x[1] for x in data]).astype('int64').reshape(-1, 1) ) img = paddle.to_tensor(x_data) label = paddle.to_tensor(y_data) out = model(img) acc_top1 = paddle.metric.accuracy(input=out, label=label, k=1) acc_top5 = paddle.metric.accuracy(input=out, label=label, k=5) eval_acc_top1_list.append(float(acc_top1.numpy())) if batch_id % 50 == 0: _logger.info( f"Test | At step {batch_id}: acc1 = {acc_top1.numpy()}, acc5 = {acc_top5.numpy()}" ) if batch_num > 0 and batch_id + 1 >= batch_num: break eval_acc_top1 = sum(eval_acc_top1_list) / len(eval_acc_top1_list) return eval_acc_top1 def program_test(self, program_path, batch_num=-1, batch_size=8): exe = paddle.static.Executor(paddle.CPUPlace()) [ inference_program, feed_target_names, fetch_targets, ] = paddle.static.load_inference_model(program_path, exe) test_reader = paddle.batch( paddle.dataset.mnist.test(), batch_size=batch_size ) top1_correct_num = 0.0 total_num = 0.0 for batch_id, data in enumerate(test_reader()): img = np.array([x[0].reshape(1, 28, 28) for x in data]).astype( 'float32' ) label = np.array([x[1] for x in data]).astype('int64') feed = {feed_target_names[0]: img} results = exe.run( inference_program, feed=feed, fetch_list=fetch_targets ) pred = np.argmax(results[0], axis=1) top1_correct_num += np.sum(np.equal(pred, label)) total_num += len(img) if total_num % 50 == 49: _logger.info( f"Test | Test num {total_num}: acc1 = {top1_correct_num / total_num}" ) if batch_num > 0 and batch_id + 1 >= batch_num: break return top1_correct_num / total_num def func_ptq(self): start_time = time.time() self.set_vars() # Load model params_path = self.download_model( self.lenet_url, self.lenet_md5, "lenet" ) params_path += "/lenet_pretrained/lenet.pdparams" model = ImperativeLenet() model_state_dict = paddle.load(params_path) model.set_state_dict(model_state_dict) # Quantize, calibrate and save quant_model = self.ptq.quantize(model) before_acc_top1 = self.model_test( quant_model, self.batch_num, self.batch_size ) input_spec = [ paddle.static.InputSpec(shape=[None, 1, 28, 28], dtype='float32') ] with tempfile.TemporaryDirectory(prefix="imperative_ptq_") as tmpdir: save_path = os.path.join(tmpdir, "model") self.ptq.save_quantized_model( model=quant_model, path=save_path, input_spec=input_spec ) print(f'Quantized model saved in {{{save_path}}}') after_acc_top1 = self.model_test( quant_model, self.batch_num, self.batch_size ) paddle.enable_static() infer_acc_top1 = self.program_test( save_path, self.batch_num, self.batch_size ) paddle.disable_static() # Check print(f'Before converted acc_top1: {before_acc_top1}') print(f'After converted acc_top1: {after_acc_top1}') print(f'Infer acc_top1: {infer_acc_top1}') self.assertTrue( after_acc_top1 >= self.eval_acc_top1, msg=f"The test acc {{{after_acc_top1:f}}} is less than {{{self.eval_acc_top1:f}}}.", ) self.assertTrue( infer_acc_top1 >= after_acc_top1, msg='The acc is lower after converting model.', ) end_time = time.time() print("total time: %ss \n" % (end_time - start_time)) def test_ptq(self): self.func_ptq() class TestImperativePTQfuse(TestImperativePTQ): def func_ptq(self): start_time = time.time() self.set_vars() # Load model params_path = self.download_model( self.lenet_url, self.lenet_md5, "lenet" ) params_path += "/lenet_pretrained/lenet.pdparams" model = ImperativeLenet() model_state_dict = paddle.load(params_path) model.set_state_dict(model_state_dict) # Quantize, calibrate and save f_l = [['features.0', 'features.1'], ['features.4', 'features.5']] quant_model = self.ptq.quantize(model, fuse=True, fuse_list=f_l) for name, layer in quant_model.named_sublayers(): if name in f_l: assert not (isinstance(layer, (nn.BatchNorm1D, nn.BatchNorm2D))) before_acc_top1 = self.model_test( quant_model, self.batch_num, self.batch_size ) input_spec = [ paddle.static.InputSpec(shape=[None, 1, 28, 28], dtype='float32') ] with tempfile.TemporaryDirectory(prefix="imperative_ptq_") as tmpdir: save_path = os.path.join(tmpdir, "model") self.ptq.save_quantized_model( model=quant_model, path=save_path, input_spec=input_spec ) print(f'Quantized model saved in {{{save_path}}}') after_acc_top1 = self.model_test( quant_model, self.batch_num, self.batch_size ) paddle.enable_static() infer_acc_top1 = self.program_test( save_path, self.batch_num, self.batch_size ) paddle.disable_static() # Check print(f'Before converted acc_top1: {before_acc_top1}') print(f'After converted acc_top1: {after_acc_top1}') print(f'Infer acc_top1: {infer_acc_top1}') # Check whether the quant_model is correct after converting. # The acc of quantized model should be higher than 0.95. self.assertTrue( after_acc_top1 >= self.eval_acc_top1, msg=f"The test acc {{{after_acc_top1:f}}} is less than {{{self.eval_acc_top1:f}}}.", ) # Check the saved infer_model.The acc of infer model # should not be lower than the one of dygraph model. self.assertTrue( infer_acc_top1 >= after_acc_top1, msg='The acc is lower after converting model.', ) end_time = time.time() print("total time: %ss \n" % (end_time - start_time)) def test_ptq(self): self.func_ptq() class TestImperativePTQHist(TestImperativePTQ): def set_vars(self): config = PTQConfig(HistQuantizer(), AbsmaxQuantizer()) self.ptq = ImperativePTQ(config) self.batch_num = 10 self.batch_size = 10 self.eval_acc_top1 = 0.98 self.gt_thresholds = { 'conv2d_0': [ [0.99853515625], [0.35732391771364225], [0.10933732241392136], ], 'batch_norm2d_0': [[0.35732391771364225], [0.4291427868761275]], 're_lu_0': [[0.4291427868761275], [0.2359918110742001]], 'max_pool2d_0': [[0.2359918110742001], [0.25665526917146053]], 'linear_0': [ [1.7037603475152991], [14.395224522473026], [0.4373355209827423], ], 'add_0': [[1.7037603475152991, 0.0], [1.7037603475152991]], } class TestImperativePTQKL(TestImperativePTQ): def set_vars(self): config = PTQConfig(KLQuantizer(), PerChannelAbsmaxQuantizer()) self.ptq = ImperativePTQ(config) self.batch_num = 10 self.batch_size = 10 self.eval_acc_top1 = 0.98 conv2d_1_wt_thresholds = [ 0.18116560578346252, 0.17079241573810577, 0.1702047884464264, 0.179476797580719, 0.1454375684261322, 0.22981858253479004, ] self.gt_thresholds = { 'conv2d_0': [[0.99267578125], [0.37695913558696836]], 'conv2d_1': [ [0.19189296757394914], [0.24514256547263358], [conv2d_1_wt_thresholds], ], 'batch_norm2d_0': [[0.37695913558696836], [0.27462541429440535]], 're_lu_0': [[0.27462541429440535], [0.19189296757394914]], 'max_pool2d_0': [[0.19189296757394914], [0.19189296757394914]], 'linear_0': [[1.2839322163611087], [8.957185942414352]], 'add_0': [[1.2839322163611087, 0.0], [1.2839322163611087]], } if __name__ == '__main__': unittest.main()