Files
paddlepaddle--paddle/test/quantization/test_post_training_quantization_mobilenetv1.py
2026-07-13 12:40:42 +08:00

912 lines
28 KiB
Python

# 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 functools
import logging
import os
import random
import sys
import tempfile
import time
import unittest
import numpy as np
from PIL import Image
import paddle
from paddle.dataset.common import download
from paddle.io import Dataset
from paddle.static.log_helper import get_logger
from paddle.static.quantization import PostTrainingQuantization
paddle.enable_static()
random.seed(0)
np.random.seed(0)
DATA_DIM = 224
THREAD = 1
BUF_SIZE = 102400
DATA_DIR = 'data/ILSVRC2012'
img_mean = np.array([0.485, 0.456, 0.406]).reshape((3, 1, 1))
img_std = np.array([0.229, 0.224, 0.225]).reshape((3, 1, 1))
_logger = get_logger(
__name__, logging.INFO, fmt='%(asctime)s-%(levelname)s: %(message)s'
)
def resize_short(img, target_size):
percent = float(target_size) / min(img.size[0], img.size[1])
resized_width = int(round(img.size[0] * percent))
resized_height = int(round(img.size[1] * percent))
img = img.resize((resized_width, resized_height), Image.LANCZOS)
return img
def crop_image(img, target_size, center):
width, height = img.size
size = target_size
if center is True:
w_start = (width - size) / 2
h_start = (height - size) / 2
else:
w_start = np.random.randint(0, width - size + 1)
h_start = np.random.randint(0, height - size + 1)
w_end = w_start + size
h_end = h_start + size
img = img.crop((w_start, h_start, w_end, h_end))
return img
def process_image(sample, mode, color_jitter, rotate):
img_path = sample[0]
img = Image.open(img_path)
img = resize_short(img, target_size=256)
img = crop_image(img, target_size=DATA_DIM, center=True)
if img.mode != 'RGB':
img = img.convert('RGB')
img = np.array(img).astype('float32').transpose((2, 0, 1)) / 255
img -= img_mean
img /= img_std
return img, sample[1]
def _reader_creator(
file_list,
mode,
shuffle=False,
color_jitter=False,
rotate=False,
data_dir=DATA_DIR,
):
def reader():
with open(file_list) as flist:
full_lines = [line.strip() for line in flist]
if shuffle:
np.random.shuffle(full_lines)
lines = full_lines
for line in lines:
img_path, label = line.split()
img_path = os.path.join(data_dir, img_path)
if not os.path.exists(img_path):
continue
yield img_path, int(label)
mapper = functools.partial(
process_image, mode=mode, color_jitter=color_jitter, rotate=rotate
)
return paddle.reader.xmap_readers(mapper, reader, THREAD, BUF_SIZE)
def val(data_dir=DATA_DIR):
file_list = os.path.join(data_dir, 'val_list.txt')
return _reader_creator(file_list, 'val', shuffle=False, data_dir=data_dir)
class ImageNetDataset(Dataset):
def __init__(self, data_dir=DATA_DIR, shuffle=False, need_label=False):
super().__init__()
self.need_label = need_label
self.data_dir = data_dir
val_file_list = os.path.join(data_dir, 'val_list.txt')
with open(val_file_list) as flist:
lines = [line.strip() for line in flist]
if shuffle:
np.random.shuffle(lines)
self.data = [line.split() for line in lines]
def __getitem__(self, index):
sample = self.data[index]
data_path = os.path.join(self.data_dir, sample[0])
data, label = process_image(
[data_path, sample[1]], mode='val', color_jitter=False, rotate=False
)
if self.need_label:
return data, np.array([label]).astype('int64')
else:
return data
def __len__(self):
return len(self.data)
class TestPostTrainingQuantization(unittest.TestCase):
def setUp(self):
self.int8_download = 'int8/download'
self.cache_folder = os.path.expanduser(
'~/.cache/paddle/dataset/' + self.int8_download
)
self.data_cache_folder = ''
data_urls = []
data_md5s = []
if os.environ.get('DATASET') == 'full':
data_urls.append(
'https://paddle-inference-dist.bj.bcebos.com/int8/ILSVRC2012_img_val.tar.gz.partaa'
)
data_md5s.append('60f6525b0e1d127f345641d75d41f0a8')
data_urls.append(
'https://paddle-inference-dist.bj.bcebos.com/int8/ILSVRC2012_img_val.tar.gz.partab'
)
data_md5s.append('1e9f15f64e015e58d6f9ec3210ed18b5')
self.data_cache_folder = self.download_data(
data_urls, data_md5s, "full_data", False
)
else:
data_urls.append(
'http://paddle-inference-dist.bj.bcebos.com/int8/calibration_test_data.tar.gz'
)
data_md5s.append('1b6c1c434172cca1bf9ba1e4d7a3157d')
self.data_cache_folder = self.download_data(
data_urls, data_md5s, "small_data", False
)
# reader/decorator.py requires the relative path to the data folder
if not os.path.exists("./data/ILSVRC2012"):
cmd = 'rm -rf {0} && ln -s {1} {0}'.format(
"data", self.data_cache_folder
)
os.system(cmd)
self.batch_size = 1 if os.environ.get('DATASET') == 'full' else 50
self.infer_iterations = (
50000 if os.environ.get('DATASET') == 'full' else 2
)
self.root_path = tempfile.TemporaryDirectory()
self.int8_model = os.path.join(
self.root_path.name, "post_training_quantization"
)
def tearDown(self):
self.root_path.cleanup()
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_data(self, data_urls, data_md5s, folder_name, is_model=True):
data_cache_folder = os.path.join(self.cache_folder, folder_name)
zip_path = ''
if os.environ.get('DATASET') == 'full':
file_names = []
for i in range(0, len(data_urls)):
download(data_urls[i], self.int8_download, data_md5s[i])
file_names.append(data_urls[i].split('/')[-1])
zip_path = os.path.join(
self.cache_folder, 'full_imagenet_val.tar.gz'
)
if not os.path.exists(zip_path):
cat_command = 'cat'
for file_name in file_names:
cat_command += ' ' + os.path.join(
self.cache_folder, file_name
)
cat_command += ' > ' + zip_path
os.system(cat_command)
if os.environ.get('DATASET') != 'full' or is_model:
download(data_urls[0], self.int8_download, data_md5s[0])
file_name = data_urls[0].split('/')[-1]
zip_path = os.path.join(self.cache_folder, file_name)
_logger.info(f'Data is downloaded at {zip_path}')
self.cache_unzipping(data_cache_folder, zip_path)
return data_cache_folder
def download_model(self):
pass
def run_program(
self,
model_path,
model_filename,
params_filename,
batch_size,
infer_iterations,
):
image_shape = [3, 224, 224]
place = paddle.CPUPlace()
exe = paddle.static.Executor(place)
[
infer_program,
feed_dict,
fetch_targets,
] = paddle.static.load_inference_model(
model_path,
exe,
model_filename=model_filename,
params_filename=params_filename,
)
val_reader = paddle.batch(val(), batch_size)
iterations = infer_iterations
test_info = []
cnt = 0
periods = []
for batch_id, data in enumerate(val_reader()):
image = np.array([x[0].reshape(image_shape) for x in data]).astype(
"float32"
)
label = np.array([x[1] for x in data]).astype("int64")
label = label.reshape([-1, 1])
t1 = time.time()
pred = exe.run(
infer_program,
feed={feed_dict[0]: image},
fetch_list=fetch_targets,
)
t2 = time.time()
period = t2 - t1
periods.append(period)
pred = np.array(pred[0])
sort_array = pred.argsort(axis=1)
top_1_pred = sort_array[:, -1:][:, ::-1]
top_1 = np.mean(label == top_1_pred)
test_info.append(np.mean(top_1) * len(data))
cnt += len(data)
if (batch_id + 1) % 100 == 0:
_logger.info(f"{batch_id + 1} images,")
sys.stdout.flush()
if (batch_id + 1) == iterations:
break
throughput = cnt / np.sum(periods)
latency = np.average(periods)
acc1 = np.sum(test_info) / cnt
return (throughput, latency, acc1, feed_dict)
def generate_quantized_model(
self,
model_path,
model_filename,
params_filename,
quantizable_op_type,
batch_size,
algo="KL",
round_type="round",
is_full_quantize=False,
is_use_cache_file=False,
is_optimize_model=False,
batch_nums=1,
onnx_format=False,
deploy_backend=None,
feed_name="inputs",
):
try:
os.system("mkdir " + self.int8_model)
except Exception as e:
_logger.info(f"Failed to create {self.int8_model} due to {e}")
sys.exit(-1)
place = paddle.CPUPlace()
exe = paddle.static.Executor(place)
image = paddle.static.data(
name=feed_name[0], shape=[None, 3, 224, 224], dtype='float32'
)
feed_list = [image]
if len(feed_name) == 2:
label = paddle.static.data(
name='label', shape=[None, 1], dtype='int64'
)
feed_list.append(label)
val_dataset = ImageNetDataset(need_label=len(feed_list) == 2)
data_loader = paddle.io.DataLoader(
val_dataset,
places=place,
feed_list=feed_list,
drop_last=False,
return_list=False,
batch_size=2,
shuffle=False,
)
ptq = PostTrainingQuantization(
executor=exe,
data_loader=data_loader,
model_dir=model_path,
model_filename=model_filename,
params_filename=params_filename,
batch_size=batch_size,
batch_nums=batch_nums,
algo=algo,
quantizable_op_type=quantizable_op_type,
round_type=round_type,
is_full_quantize=is_full_quantize,
optimize_model=is_optimize_model,
onnx_format=onnx_format,
is_use_cache_file=is_use_cache_file,
deploy_backend=deploy_backend,
)
ptq.quantize()
ptq.save_quantized_model(
self.int8_model,
model_filename=model_filename,
params_filename=params_filename,
)
def run_test(
self,
model,
model_filename,
params_filename,
algo,
round_type,
data_urls,
data_md5s,
data_name,
quantizable_op_type,
is_full_quantize,
is_use_cache_file,
is_optimize_model,
diff_threshold,
onnx_format=False,
batch_nums=1,
deploy_backend=None,
):
infer_iterations = self.infer_iterations
batch_size = self.batch_size
model_cache_folder = self.download_data(data_urls, data_md5s, model)
model_path = os.path.join(model_cache_folder, data_name)
_logger.info(
f"Start FP32 inference for {model} on {infer_iterations * batch_size} images ..."
)
(
fp32_throughput,
fp32_latency,
fp32_acc1,
feed_name,
) = self.run_program(
model_path,
model_filename,
params_filename,
batch_size,
infer_iterations,
)
self.generate_quantized_model(
model_path,
model_filename,
params_filename,
quantizable_op_type,
batch_size,
algo,
round_type,
is_full_quantize,
is_use_cache_file,
is_optimize_model,
batch_nums,
onnx_format,
deploy_backend,
feed_name,
)
_logger.info(
f"Start INT8 inference for {model} on {infer_iterations * batch_size} images ..."
)
(int8_throughput, int8_latency, int8_acc1, _) = self.run_program(
self.int8_model,
model_filename,
params_filename,
batch_size,
infer_iterations,
)
_logger.info(f"---Post training quantization of {algo} method---")
_logger.info(
f"FP32 {model}: batch_size {batch_size}, throughput {fp32_throughput} images/second, latency {fp32_latency} second, accuracy {fp32_acc1}."
)
_logger.info(
f"INT8 {model}: batch_size {batch_size}, throughput {int8_throughput} images/second, latency {int8_latency} second, accuracy {int8_acc1}.\n"
)
sys.stdout.flush()
delta_value = fp32_acc1 - int8_acc1
self.assertLess(delta_value, diff_threshold)
class TestPostTrainingKLForMobilenetv1(TestPostTrainingQuantization):
def test_post_training_kl_mobilenetv1(self):
model = "MobileNet-V1"
algo = "KL"
round_type = "round"
data_urls = [
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/MobileNetV1_infer.tar'
]
data_md5s = ['5ee2b1775b11dc233079236cdc216c2e']
quantizable_op_type = [
"conv2d",
"depthwise_conv2d",
"mul",
"pool2d",
]
is_full_quantize = False
is_use_cache_file = False
is_optimize_model = True
diff_threshold = 0.025
batch_nums = 2
self.run_test(
model,
'inference.pdmodel',
'inference.pdiparams',
algo,
round_type,
data_urls,
data_md5s,
"MobileNetV1_infer",
quantizable_op_type,
is_full_quantize,
is_use_cache_file,
is_optimize_model,
diff_threshold,
)
class TestPostTrainingavgForMobilenetv1(TestPostTrainingQuantization):
def test_post_training_avg_mobilenetv1(self):
model = "MobileNet-V1"
algo = "avg"
round_type = "round"
data_urls = [
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/MobileNetV1_infer.tar'
]
data_md5s = ['5ee2b1775b11dc233079236cdc216c2e']
quantizable_op_type = [
"conv2d",
"depthwise_conv2d",
"mul",
]
is_full_quantize = False
is_use_cache_file = False
is_optimize_model = True
diff_threshold = 0.025
self.run_test(
model,
'inference.pdmodel',
'inference.pdiparams',
algo,
round_type,
data_urls,
data_md5s,
"MobileNetV1_infer",
quantizable_op_type,
is_full_quantize,
is_use_cache_file,
is_optimize_model,
diff_threshold,
batch_nums=2,
)
class TestPostTrainingavgForPwganCsmsc(TestPostTrainingQuantization):
def setUp(self):
self.int8_download = 'int8/download'
self.cache_folder = os.path.expanduser(
'~/.cache/paddle/dataset/' + self.int8_download
)
self.data_cache_folder = ''
data_urls = []
data_md5s = []
if os.environ.get('DATASET') == 'full':
data_urls.append(
'https://paddlespeech.bj.bcebos.com/tmp/csmsc_voc1.npy'
)
data_md5s.append('47950146167ca8d885a78d71e74f1a2b')
self.data_cache_folder = self.download_data(
data_urls, data_md5s, "full_data", False
)
else:
data_urls.append(
'https://paddlespeech.bj.bcebos.com/tmp/csmsc_voc1.npy'
)
data_md5s.append('47950146167ca8d885a78d71e74f1a2b')
self.data_cache_folder = self.download_data(
data_urls, data_md5s, "small_data", False
)
# reader/decorator.py requires the relative path to the data folder
if not os.path.exists("./data/BZNSYP"):
cmd = 'rm -rf {0} && ln -s {1} {0}'.format(
"data", self.data_cache_folder
)
os.system(cmd)
self.batch_size = 1 if os.environ.get('DATASET') == 'full' else 50
self.infer_iterations = (
50000 if os.environ.get('DATASET') == 'full' else 2
)
self.root_path = tempfile.TemporaryDirectory()
self.int8_model = os.path.join(
self.root_path.name, "post_training_quantization"
)
def cache_unzipping(self, target_folder, zip_path):
if not os.path.exists(target_folder):
if zip_path.endswith('.tar.gz'):
cmd = f'mkdir {target_folder} && tar xf {zip_path} -C {target_folder}'
elif zip_path.endswith('.zip'):
cmd = f'mkdir {target_folder} && unzip -o {zip_path} -d {target_folder}'
else:
cmd = f'mkdir {target_folder}'
os.system(cmd)
def generate_quantized_model(
self,
model_path,
model_filename,
params_filename,
quantizable_op_type,
batch_size,
algo="avg",
round_type="round",
is_full_quantize=False,
is_use_cache_file=False,
is_optimize_model=False,
batch_nums=1,
onnx_format=False,
deploy_backend=None,
feed_name="inputs",
):
try:
os.system("mkdir " + self.int8_model)
except Exception as e:
_logger.info(f"Failed to create {self.int8_model} due to {e}")
sys.exit(-1)
place = paddle.CPUPlace()
exe = paddle.static.Executor(place)
val_dataset = "~/.cache/paddle/dataset/int8/download/csmsc_voc1.npy"
data_loader = paddle.io.DataLoader(
val_dataset,
places=place,
drop_last=False,
batch_size=2,
)
ptq = PostTrainingQuantization(
executor=exe,
data_loader=data_loader,
model_dir=model_path,
model_filename=model_filename,
params_filename=params_filename,
batch_size=batch_size,
batch_nums=batch_nums,
algo=algo,
quantizable_op_type=quantizable_op_type,
round_type=round_type,
is_full_quantize=is_full_quantize,
optimize_model=is_optimize_model,
onnx_format=onnx_format,
is_use_cache_file=is_use_cache_file,
deploy_backend=deploy_backend,
)
ptq.quantize()
ptq.save_quantized_model(
self.int8_model,
model_filename=model_filename,
params_filename=params_filename,
)
class TestPostTraininghistForNoneShape(TestPostTrainingavgForPwganCsmsc):
def test_post_training_avg_pwgancsmsc(self):
model = "pwg_baker_static_0.4"
algo = "avg"
round_type = "round"
data_urls = [
'https://paddlespeech.bj.bcebos.com/Parakeet/released_models/pwgan/pwg_baker_static_0.4.zip'
]
data_md5s = ['e3504aed9c5a290be12d1347836d2742']
quantizable_op_type = [
"conv2d",
"depthwise_conv2d",
"mul",
]
is_full_quantize = False
is_use_cache_file = False
is_optimize_model = True
diff_threshold = 0.05
self.run_test(
model,
'pwgan_csmsc.pdmodel',
'pwgan_csmsc.pdiparams',
algo,
round_type,
data_urls,
data_md5s,
"pwg_baker_static_0.4",
quantizable_op_type,
is_full_quantize,
is_use_cache_file,
is_optimize_model,
diff_threshold,
batch_nums=2,
)
class TestPostTraininghistForMobilenetv1(TestPostTrainingQuantization):
def test_post_training_hist_mobilenetv1(self):
model = "MobileNet-V1"
algo = "hist"
round_type = "round"
data_urls = [
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/MobileNetV1_infer.tar'
]
data_md5s = ['5ee2b1775b11dc233079236cdc216c2e']
quantizable_op_type = [
"conv2d",
"depthwise_conv2d",
"mul",
]
is_full_quantize = False
is_use_cache_file = False
is_optimize_model = True
diff_threshold = 0.03
batch_nums = 1
self.run_test(
model,
'inference.pdmodel',
'inference.pdiparams',
algo,
round_type,
data_urls,
data_md5s,
"MobileNetV1_infer",
quantizable_op_type,
is_full_quantize,
is_use_cache_file,
is_optimize_model,
diff_threshold,
batch_nums=batch_nums,
)
class TestPostTrainingAbsMaxForMobilenetv1(TestPostTrainingQuantization):
def test_post_training_abs_max_mobilenetv1(self):
model = "MobileNet-V1"
algo = "abs_max"
round_type = "round"
data_urls = [
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/MobileNetV1_infer.tar'
]
data_md5s = ['5ee2b1775b11dc233079236cdc216c2e']
quantizable_op_type = [
"conv2d",
"mul",
]
is_full_quantize = False
is_use_cache_file = False
is_optimize_model = False
# The accuracy diff of post-training quantization (abs_max) maybe bigger
diff_threshold = 0.05
self.run_test(
model,
'inference.pdmodel',
'inference.pdiparams',
algo,
round_type,
data_urls,
data_md5s,
"MobileNetV1_infer",
quantizable_op_type,
is_full_quantize,
is_use_cache_file,
is_optimize_model,
diff_threshold,
)
class TestPostTrainingAvgONNXFormatForMobilenetv1(TestPostTrainingQuantization):
def test_post_training_onnx_format_mobilenetv1(self):
model = "MobileNet-V1"
algo = "emd"
round_type = "round"
data_urls = [
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/MobileNetV1_infer.tar'
]
data_md5s = ['5ee2b1775b11dc233079236cdc216c2e']
quantizable_op_type = [
"conv2d",
"depthwise_conv2d",
"mul",
]
is_full_quantize = False
is_use_cache_file = False
is_optimize_model = True
onnx_format = True
diff_threshold = 0.05
batch_nums = 1
self.run_test(
model,
'inference.pdmodel',
'inference.pdiparams',
algo,
round_type,
data_urls,
data_md5s,
"MobileNetV1_infer",
quantizable_op_type,
is_full_quantize,
is_use_cache_file,
is_optimize_model,
diff_threshold,
onnx_format=onnx_format,
batch_nums=batch_nums,
)
class TestPostTrainingAvgONNXFormatForMobilenetv1TensorRT(
TestPostTrainingQuantization
):
def test_post_training_onnx_format_mobilenetv1_tensorrt(self):
model = "MobileNet-V1"
algo = "KL"
round_type = "round"
data_urls = [
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/MobileNetV1_infer.tar'
]
data_md5s = ['5ee2b1775b11dc233079236cdc216c2e']
quantizable_op_type = [
"conv2d",
"depthwise_conv2d",
"mul",
]
is_full_quantize = False
is_use_cache_file = False
is_optimize_model = False
onnx_format = True
diff_threshold = 0.05
batch_nums = 12
deploy_backend = "tensorrt"
self.run_test(
model,
'inference.pdmodel',
'inference.pdiparams',
algo,
round_type,
data_urls,
data_md5s,
"MobileNetV1_infer",
quantizable_op_type,
is_full_quantize,
is_use_cache_file,
is_optimize_model,
diff_threshold,
onnx_format=onnx_format,
batch_nums=batch_nums,
deploy_backend=deploy_backend,
)
class TestPostTrainingKLONNXFormatForMobilenetv1ONEDNN(
TestPostTrainingQuantization
):
def test_post_training_onnx_format_mobilenetv1_onednn(self):
model = "MobileNet-V1"
algo = "ptf"
round_type = "round"
data_urls = [
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/MobileNetV1_infer.tar'
]
data_md5s = ['5ee2b1775b11dc233079236cdc216c2e']
quantizable_op_type = [
"conv2d",
"depthwise_conv2d",
"mul",
]
is_full_quantize = False
is_use_cache_file = False
is_optimize_model = False
onnx_format = True
diff_threshold = 0.05
batch_nums = 12
deploy_backend = "onednn"
self.run_test(
model,
'inference.pdmodel',
'inference.pdiparams',
algo,
round_type,
data_urls,
data_md5s,
"MobileNetV1_infer",
quantizable_op_type,
is_full_quantize,
is_use_cache_file,
is_optimize_model,
diff_threshold,
onnx_format=onnx_format,
batch_nums=batch_nums,
deploy_backend=deploy_backend,
)
class TestPostTrainingAvgONNXFormatForMobilenetv1ARMCPU(
TestPostTrainingQuantization
):
def test_post_training_onnx_format_mobilenetv1_armcpu(self):
model = "MobileNet-V1"
algo = "avg"
round_type = "round"
data_urls = [
'https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/inference/MobileNetV1_infer.tar'
]
data_md5s = ['5ee2b1775b11dc233079236cdc216c2e']
quantizable_op_type = [
"conv2d",
"depthwise_conv2d",
"mul",
]
is_full_quantize = False
is_use_cache_file = False
is_optimize_model = True
onnx_format = True
diff_threshold = 0.05
batch_nums = 1
deploy_backend = "arm"
self.run_test(
model,
'inference.pdmodel',
'inference.pdiparams',
algo,
round_type,
data_urls,
data_md5s,
"MobileNetV1_infer",
quantizable_op_type,
is_full_quantize,
is_use_cache_file,
is_optimize_model,
diff_threshold,
onnx_format=onnx_format,
batch_nums=batch_nums,
deploy_backend=deploy_backend,
)
if __name__ == '__main__':
unittest.main()