# 该脚本展示如何自由调度算子,实现混合精度推理
import torch
import torchvision
from ppq import *
from ppq.api import *
# 在开始之前,首先设计一个支持混合精度的量化器
# BaseQuantizer在ppq/quantization/quantizer/base.py中
class MyQuantizer(BaseQuantizer):
# quant_operation_types是一个类型枚举,在这里需要写下所有该量化器需要所需要量化的算子
@ property
def quant_operation_types(self) -> set:
return {'Conv'}
# 一旦确定了哪些算子需要量化,则需要在init_quantize_config为他们初始化量化信息
# 对于一个Onnx算子,总会有几个输入和输出Variable,需要为每一个相连的Variable初始化量化信息
# 麻烦的问题是:对于很多onnx算子而言,他们的部分输入是不需要量化的
# 如clip算子的三个输入value,min,max,大部分框架不要求量化min,max
# 如reshape算子的两个输入value,shape,其中shape不能够被量化
# PPQ的算子接线器中记录了这些信息
# 算子接线器中记录了所有标注onnx的默认量化策略
# 该函数将使用预定义的算子量化策略初始化量化信息
# 然而需要注意的是,由于手动调度的存在,用户可以强制调度一个类型不在quant_operation_types中的算子来到量化平台
# 建议针对这种情况进行回应。或者,在探测到算子类型并非可量化后进行报错
def init_quantize_config(self,operation: Operation) -> OperationQuantizationConfig:
# 为卷积算子初始化量化信息,只量化卷积算子的输入(input & weight),bias不被量化
if operation.type == 'Conv':
config = self.create_defualt_quant_config(
op = operation,
num_of_bits = 4,
quant_max = 15,
quant_min = -16,
observer_algorithm = 'percentile',
# 量化策略:QuantizationProperty.PER_TENSOR->以tensor为单位完成量化,每个tensor使用一个scale和offset信息
# QuantizationProperty.LINEAR:线性量化,通常INT8,INT16皆属于线性量化,在线性量化的表示中不存在指数位
# QuantizationProperty.SYMMETRICAL:对称量化,在量化计算中不启用offset
policy = QuantizationPolicy(
QuantizationProperty.PER_TENSOR +
QuantizationProperty.LINEAR +
QuantizationProperty.SYMMETRICAL),
rounding = RoundingPolicy.ROUND_HALF_EVEN)
# 关闭所有输出量化,状态设置为fp32
for tensor_quant_config in config.output_quantization_config:
# QuantizationStates.FP32:表示这一路输入不量化
tensor_quant_config.state = QuantizationStates.FP32
# 关闭bias量化,状态设置为fp32
if operation.num_of_input == 3:
config.input_quantization_config[-1].state = QuantizationStates.FP32
# 如果算子调度到INT8平台上,执行INT8量化
if operation.platform == TargetPlatform.ACADEMIC_INT8:
print('f{operation.name} has been dispatched to INT8')
config.input_quantization_config[0].num_of_bits = 8
# 获取TQC最大量化值
config.input_quantization_config[0].quant_max = 127
# # 获取TQC最小量化值
config.input_quantization_config[0].quant_min = -128
config.input_quantization_config[1].num_of_bits = 8
config.input_quantization_config[1].quant_max = 127
config.input_quantization_config[1].quant_min = -128
return config
else:
raise TypeError(f'Unsupported Op Type: {operation.type}')
# 当前量化器进行量化的算子都将被发往一个指定的目标平台
# 这里选择TargetPlatform.ACADEMIC_INT4作为目标平台
@ property
def target_platform(self) -> TargetPlatform:
return TargetPlatform.ACADEMIC_INT4
#注册量化器
register_network_quantizer(MyQuantizer,platform=TargetPlatform.ACADEMIC_INT4)
# 下面展示PPQ手动调度逻辑
# 以Mobilenetv2举例,展示如何完成混合精度调度
BATCHSIZE = 32
INPUT_SHAPE = [BATCHSIZE,3,224,224]
DEVICE = 'cuda'
PLATFORM = TargetPlatform.ACADEMIC_INT4
CALIBRATION = [torch.rand(size=INPUT_SHAPE) for _ in range(32)]
def collate_fn(batch: torch.Tensor) -> torch.Tensor: return batch.to(DEVICE)
model = torchvision.models.mobilenet.mobilenet_v2(pretrained=True)
model = model.to(DEVICE)
# 为了执行手动调度,必须首先创建QuantizationSetting
# 使用QuantizationSetting.dispatch_table属性来传递调度方案
# 大多数预制量化器没有针对INT4写过量化方案,因此只支持FP32-INT8的相互调度
QS = QuantizationSettingFactory.default_setting()
# 示例语句:下面的语句把Op1调度到FP32平台
# QS.dispatching_table.append(operation='Op1',platform=TargetPlatform.FP32)
# 我如何知道调度哪些层到高精度会得到最优的性能表现?
# layerwise_error_analyse函数正是为此设计,调用该方法
# 选择那些误差较高的层调度到高精度平台
# 首先将所有层送往INT4,然后执行误差分析
with ENABLE_CUDA_KERNEL:
dump_torch_to_onnx(model=model,onnx_export_file='Output/model.onnx',
input_shape=INPUT_SHAPE,input_dtype=torch.float32)
graph = load_onnx_graph(onnx_import_file='Output/model.onnx')
quantized = quantize_native_model(
model=graph,calib_dataloader=CALIBRATION,
calib_steps=32,input_shape=INPUT_SHAPE,
collate_fn=collate_fn,platform=PLATFORM,
device=DEVICE,verbose=0,setting=QS)
# 使用graphwise_analyse衡量调度前的量化误差
reports = graphwise_error_analyse(
graph=quantized,running_device=DEVICE,collate_fn=collate_fn,
dataloader=CALIBRATION)
# 执行逐层分析,结果是一个字典,该字典内写入了所有算子的单层量化误差
reports = layerwise_error_analyse(
graph=quantized,running_device=DEVICE,collate_fn=collate_fn,
dataloader=CALIBRATION,verbose=False)
# 从小到大排序单层误差
sensitivity = [(op_name,error) for op_name,error in reports.items()]
sensitivity = sorted(sensitivity,key=lambda x: x[1], reverse=True)
# 将前十个误差最大的层送上INT8,并重新量化
for op_name, _ in sensitivity[: 10]:
QS.dispatching_table.append(operation=op_name,platform=TargetPlatform.ACADEMIC_INT8)
graph = load_onnx_graph(onnx_import_file='Output/model.onnx')
quantized = quantize_native_model(
model=graph,calib_dataloader=CALIBRATION,
calib_steps=32,input_shape=INPUT_SHAPE,
collate_fn=collate_fn,platform=PLATFORM,
device=DEVICE,verbose=0,setting=QS)
# 使用graphwise_analyse衡量最终的量化误差
reports = graphwise_error_analyse(
graph=quantized,running_device=DEVICE,collate_fn=collate_fn,
dataloader=CALIBRATION)
这个demo在注册量化器这行代码报错,现在还没有找到原因,如果有知道原因的朋友希望可以解答一下,感谢!、
更换platform之后demo可以运行,且在官方platform中没有找到ACADEMIC_INT4或者ACADEMIC_INT8。猜测是这个patform的原因。