Created
August 8, 2021 13:24
-
-
Save Enoch2090/f0605ae887e3617e92b28320cd895d50 to your computer and use it in GitHub Desktop.
YOLOv4 Pytorch quantization using Vitis-ai
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import re | |
import sys | |
import argparse | |
import time | |
import pdb | |
import random | |
from pytorch_nndct.apis import torch_quantizer, dump_xmodel | |
import torch | |
import torchvision | |
import torchvision.transforms as transforms | |
# from torchvision.models.resnet import resnet18 | |
# XXX: import yolov4 | |
from yolov4_models import Yolov4 | |
from torch import jit | |
from tqdm import tqdm | |
#device = torch.device("cuda") | |
#device = torch.device("cpu") | |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
parser = argparse.ArgumentParser() | |
parser.add_argument( | |
'--data_dir', | |
default="test_pics", | |
help='Data set directory, when quant_mode=calib, it is for calibration, while quant_mode=test it is for evaluation') | |
parser.add_argument( | |
'--model_dir', | |
default="models", | |
help='Trained model file path. Download pretrained model from the following url and put it in model_dir specified path: https://download.pytorch.org/models/resnet18-5c106cde.pth' | |
) | |
parser.add_argument( | |
'--subset_len', | |
default=None, | |
type=int, | |
help='subset_len to evaluate model, using the whole validation dataset if it is not set') | |
parser.add_argument( | |
'--batch_size', | |
default=32, | |
type=int, | |
help='input data batch size to evaluate model') | |
parser.add_argument('--quant_mode', | |
default='calib', | |
choices=['float', 'calib', 'test'], | |
help='quantization mode. 0: no quantization, evaluate float model, calib: quantize, test: evaluate quantized model') | |
parser.add_argument('--fast_finetune', | |
dest='fast_finetune', | |
action='store_true', | |
help='fast finetune model before calibration') | |
parser.add_argument('--deploy', | |
dest='deploy', | |
action='store_true', | |
help='export xmodel for deployment') | |
args, _ = parser.parse_known_args() | |
def load_data(train=True, | |
data_dir='dataset/imagenet', | |
batch_size=128, | |
subset_len=None, | |
sample_method='random', | |
distributed=False, | |
model_name='Yolov4', | |
**kwargs): | |
#prepare data | |
# random.seed(12345) | |
traindir = data_dir + '/train' | |
valdir = data_dir + '/val' | |
train_sampler = None | |
# normalize = transforms.Normalize( | |
# mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) | |
if model_name == 'inception_v3': | |
size = 299 | |
resize = 299 | |
else: | |
size = 416 | |
resize = 256 | |
# resize = 416 | |
# TODO: 这个resize需要吗 | |
if train: | |
dataset = torchvision.datasets.ImageFolder( | |
traindir, | |
transforms.Compose([ | |
transforms.RandomResizedCrop(size), | |
transforms.RandomHorizontalFlip(), | |
transforms.ToTensor(), | |
# normalize, | |
])) | |
if subset_len: | |
assert subset_len <= len(dataset) # subset_len长度要小于dataset长度 | |
if sample_method == 'random': | |
dataset = torch.utils.data.Subset( | |
dataset, random.sample(range(0, len(dataset)), subset_len)) | |
else: | |
dataset = torch.utils.data.Subset(dataset, list(range(subset_len))) | |
if distributed: | |
train_sampler = torch.utils.data.distributed.DistributedSampler(dataset) | |
data_loader = torch.utils.data.DataLoader( | |
dataset, | |
batch_size=batch_size, | |
shuffle=(train_sampler is None), | |
sampler=train_sampler, | |
**kwargs) | |
else: | |
dataset = torchvision.datasets.ImageFolder( | |
valdir, | |
transforms.Compose([ | |
transforms.Resize(resize), | |
transforms.CenterCrop(size), | |
transforms.ToTensor(), | |
# normalize, | |
])) | |
if subset_len: | |
assert subset_len <= len(dataset) | |
if sample_method == 'random': | |
dataset = torch.utils.data.Subset( | |
dataset, random.sample(range(0, len(dataset)), subset_len)) | |
else: | |
dataset = torch.utils.data.Subset(dataset, list(range(subset_len))) | |
data_loader = torch.utils.data.DataLoader( | |
dataset, batch_size=batch_size, shuffle=False, **kwargs) | |
return data_loader, train_sampler | |
class AverageMeter(object): | |
"""Computes and stores the average and current value""" | |
def __init__(self, name, fmt=':f'): | |
self.name = name | |
self.fmt = fmt | |
self.reset() | |
def reset(self): | |
self.val = 0 | |
self.avg = 0 | |
self.sum = 0 | |
self.count = 0 | |
def update(self, val, n=1): | |
self.val = val | |
self.sum += val * n | |
self.count += n | |
self.avg = self.sum / self.count | |
def __str__(self): | |
fmtstr = '{name} {val' + self.fmt + '} ({avg' + self.fmt + '})' | |
return fmtstr.format(**self.__dict__) | |
def accuracy(output, target, topk=(1,)): | |
"""Computes the accuracy over the k top predictions | |
for the specified values of k""" | |
with torch.no_grad(): | |
maxk = max(topk) | |
batch_size = target.size(0) | |
_, pred = output.topk(maxk, 1, True, True) | |
pred = pred.t() | |
correct = pred.eq(target.view(1, -1).expand_as(pred)) | |
res = [] | |
for k in topk: | |
correct_k = correct[:k].view(-1).float().sum(0, keepdim=True) | |
res.append(correct_k.mul_(100.0 / batch_size)) | |
return res | |
def evaluate(model, val_loader, loss_fn): | |
model.eval() | |
model = model.to(device) | |
# model.aux_logits = False | |
top1 = AverageMeter('Acc@1', ':6.2f') | |
top5 = AverageMeter('Acc@5', ':6.2f') | |
total = 0 | |
Loss = 0 | |
for iteraction, (images, labels) in tqdm( | |
enumerate(val_loader), total=len(val_loader)): | |
images = images.to(device) | |
labels = labels.to(device) | |
#pdb.set_trace() | |
outputs = model(images) | |
#outputs = torch.Tensor(outputs) | |
#print("outputs:",type(outputs),outputs.shape) | |
loss = loss_fn(outputs, labels) | |
Loss += loss.item() | |
total += images.size(0) | |
acc1, acc5 = accuracy(outputs, labels, topk=(1, 5)) | |
top1.update(acc1[0], images.size(0)) | |
top5.update(acc5[0], images.size(0)) | |
return top1.avg, top5.avg, Loss / total | |
def quantization(title='optimize', | |
model_name='', | |
file_path=''): | |
data_dir = args.data_dir | |
quant_mode = args.quant_mode | |
finetune = args.fast_finetune | |
deploy = args.deploy | |
batch_size = args.batch_size | |
subset_len = args.subset_len | |
if quant_mode != 'test' and deploy: | |
deploy = False | |
print(r'Warning: Exporting xmodel needs to be done in quantization test mode, turn off it in this running!') | |
if deploy and (batch_size != 1 or subset_len != 1): | |
print(r'Warning: Exporting xmodel needs batch size to be 1 and only 1 iteration of inference, change them automatically!') | |
batch_size = 1 | |
subset_len = 1 | |
model = Yolov4(yolov4conv137weight=None, n_classes=80, inference=True).cpu() | |
model.load_state_dict(torch.load(file_path)) | |
model.aux_logits = False | |
example_input = torch.randn([batch_size, 3, 416, 416]) | |
# FIXME: 先做 jit.trace | |
# module = jit.trace(model, example_input) | |
if quant_mode == 'float': | |
quant_model = model | |
else: | |
## new api | |
#################################################################################### | |
quantizer = torch_quantizer( | |
quant_mode, model, (example_input), device=device, custom_quant_ops=['aten::softplus']) | |
print("finish quantizer!") # add | |
quant_model = quantizer.quant_model | |
# print(dir(quantizer)) | |
##################################################################################### | |
# to get loss value after evaluation | |
# loss_fn = torch.nn.CrossEntropyLoss().to(device) | |
val_loader, _ = load_data( | |
subset_len=subset_len, | |
train=False, | |
batch_size=batch_size, | |
sample_method='random', | |
data_dir=data_dir, | |
model_name=model_name) | |
# fast finetune model or load finetuned parameter before test | |
# if finetune == True: | |
# ft_loader, _ = load_data( | |
# subset_len=1024, | |
# train=False, | |
# batch_size=batch_size, | |
# sample_method=None, | |
# data_dir=data_dir, | |
# model_name=model_name) | |
# if quant_mode == 'calib': | |
# quantizer.fast_finetune(evaluate, (quant_model, ft_loader, loss_fn)) | |
# elif quant_mode == 'test': | |
# quantizer.load_ft_param() | |
# record modules float model accuracy | |
# add modules float model accuracy here | |
acc_org1 = 0.0 | |
acc_org5 = 0.0 | |
loss_org = 0.0 | |
#register_modification_hooks(model_gen, train=False) | |
# acc1_gen, acc5_gen, loss_gen = evaluate(quant_model, val_loader, loss_fn) | |
# logging accuracy | |
# print('loss: %g' % (loss_gen)) | |
# print('top-1 / top-5 accuracy: %g / %g' % (acc1_gen, acc5_gen)) | |
# handle quantization result | |
if quant_mode == 'calib': | |
quantizer.export_quant_config() | |
if deploy: | |
quantizer.export_xmodel(deploy_check=False) | |
if __name__ == '__main__': | |
model_name = 'Yolov4' | |
file_path = os.path.join(args.model_dir, model_name + '.pth') | |
feature_test = ' float model evaluation' | |
if args.quant_mode != 'float': | |
feature_test = ' quantization' | |
# force to merge BN with CONV for better quantization accuracy | |
args.optimize = 1 | |
feature_test += ' with optimization' | |
else: | |
feature_test = ' float model evaluation' | |
title = model_name + feature_test | |
print("-------- Start {} test ".format(model_name)) | |
# calibration or evaluation | |
quantization( | |
title=title, | |
model_name=model_name, | |
file_path=file_path) | |
print("-------- End of {} test ".format(model_name)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment