PPQ 自定义量化平台

PPQ已经支持许多后端,本教程说明如何在PPQ中添加你的量化后端支持。为简单起见,这里将以TargetPlatform.ACADEMIC_INT8平台为例进行说明。这里提供了可执行的例子:

https://kgithub.com/openppl-public/ppq/blob/master/ppq/quantization/quantizer/MyQuantizer.py

创建你的平台

首先,你需要在TargetPlatform中命名并创建你的平台,这里有所有PPQ支持的平台。不要忘记在is_quantized_platform集合中添加你创建的平台。

@classmethod
def is_quantized_platform(cls,platform) -> bool:
    return platform in {
        cls.PPL_DSP_INT8,cls.PPL_DSP_TI_INT8,cls.QNN_DSP_INT8,cls.TRT_INT8,cls.NCNN_INT8,cls.NXP_INT8,
        cls.SNPE_INT8,cls.PPL_CUDA_INT8,cls.PPL_CUDA_INT4,cls.EXTENSION,cls.PPL_CUDA_MIX,cls.ORT_OOS_INT8,
        cls.ACADEMIC_INT4,cls.ACADEMIC_INT8,cls.ACADEMIC_MIX,cls.METAX_INT8_C,cls.METAX_INT8_T
}

继承BaseQuantizer

 正如你在quantizer中看到的,这里有许多quantizers,各自对应一个后端平台,它们都是从基类BaseQuantizer继承的,基本quantizer类规定了基本量化工作流程和应用量化passes的过程,这是由用户事先指定的量化设置所引导的,所以你的quantizer也应该继承基类。以ACADEMICQuantizer为例

class ACADEMICQuantizer(BaseQuantizer):
    """
    ACADEMICQuantizer采用宽松的量化方案,只针对计算算子的输入变量进行量化(权重采用对称的、per-                                        tensor量化,激活采用非对称的、per-tensor量化。这个设置目前还没有与任何类型的平台一致,它的设计只是为了复制文件和验证算法。
    """
    def _init_(self,graph:BaseGraph,verbose:bool=True) -> None:
        self._num_of_bits = 8
        self._quant_min = 0
        self._quant_max = 255
        super()._init_(graph,verbose) 

 TargetPlatform.ACADEMIC_INT8平台采用8位非对称量化方案进行激活,因此_num_of_bits=8,_quant_min=0,_quant_max=255,注意,如果你的平台采用对称量化,你应该修改如下:

self._num_of_bits = 8
self._quant_min = -128 # 或者-127,这取决于你平台的截断边界
self._quant_max = 127

确认量化方案

你需要指定你的目标平台和quantizer的默认平台,当PPQ加载和调度你的模型时,这些平台将会被PPQ图形调度器调度到不同的操作。目标平台应该是你创建的平台。

@ property
def target_platform(self) -> TargetPlatform:
    return TargetPlatform.ACADEMIC_INT8
"""
有时你还需要指定你的平台可量化的算子类型,例如,在大多数学术场合,只有计算算子(Conv,Gemm,ConvTranspose)需要量化,你只需要在*quant_operation_types*中说明需要量化的算子类型。
"""
@ property
def quant_operation_types(self) -> set:
    return {
        'Conv','Gemm','ConvTranspose'
    }

为了实现整个quantizer,你应该确认你的后端平台的量化方案(per tensor/per channel,symmetric/asymmetric),在许多平台上,权重参数和激活可能采取不同的量化方案。所有支持的量化方案请参见QuantizationProperty。你的quantizer类应该实现quantize_policy以识别激活的量化方案。

@ property
def quantize_policy(self) -> QuantizationPolicy:
    return QuantizationPolicy(
        QuantizationProperty.ASYMMETRICAL +
        QuantizationProperty.LINEAR +
        QuantizationProperty.PER_TENSOR
    )

从上面可以看出,ACADEMICQuantizer采取非对称、线性、per-tensor方案进行激活量化,这是学术论文中最常见的量化设置。

同样,你需要确认你的后端平台的取整策略,例如,TargetPlatform.PPL_CUDA_INT8平台采用的是round-to-nearest-even策略,而TargetPlatform.NCNN_INT8平台采用round-half-away-from-zero策略,为了更好的与后端保持一致,你应该确保你的quantizer和你真实后端应采用相同的rounding策略。

@ property
def rounding_policy(self) -> RoundingPolicy:
    return RoundingPolicy.ROUND_HALF_EVEN

正确的量化细节

 在大多数情况下,权重参数可能和激活采用不同的量化方案,因此我们需要在init_quantize_config中纠正权重参数和特殊算子的量化方案,注意这个函数会为图中的每个可量化算子生成量化配置,你需要首先为普通激活生成量化配置。

def init_quantize_config(self,operation:Operation) -> OperationQuantizationConfig:
    # 创建一个基本的量化配置
    config = self.create_default_quant_config(
        operation_meta=operation.meta_data,num_of_bits=self._num_of_bits,
        quant_max=self._quant_max,quant_min=self._quant_min,
        observer_algorithm='percentile',policy=self.quantize_policy,
        rounding=self.rounding_policy,
    )

create_default_quant_config为每个变量(包括权重参数)创建了一个默认的非对称、per-tensor配置,所以下一步你需要把它改成学术平台的对称配置,因为在学术设置中,权重参数采用per-tensor、对称量化方案而不是默认的per-tensor、非对称方案。

# 实际上,通常只支持计算输入的量化
# 学术设置中的ops
if operation.type in {'Conv','Gemm','ConvTranspose'}:
    W_config = config.input_quantization_config[1]
    output_config = config.output_quantization_config[0]
    W_config.quant_max = int(pow(2,self._num_of_bits - 1) - 1)
    W_config.quant_min = - int(pow(2,self._num_of_bits - 1)
    W_config.policy = QuantizationPolicy(
        QuantizationProperty.SYMMETRICAL +
        QuantizationProperty.LINEAR +
        QuantizationProperty.PER_Tensor
    )
    output_config.state = QuantizationStates.FP32
    if operation.num_of_input == 3:
        bias_config = config.input_quantization_config[-1]
        # bias应该被量化为32位
        # 在 python3 中,int 表示 C++ 中的 long long
        # 这样它就有足够的精度来表示一个像2^32的数字
        # 然而,这可能会导致scale下溢
        # 在这里,我们给bias一个30比特的精度,这在任何情况下都是足够的。
        bias_config.num_of_bits = 30
        bias_config.quant_max = int(pow(2,30 - 1) - 1)
        bias_config.quant_min = -int(pow(2,30 - 1))
        bias_config.policy = QuantizationPolicy(
            QuantizationProperty.SYMMETRICAL +
            QuantizationProperty.LINEAR +
            QuantizationProperty.PER_TENSOR)
        bias_config.state = QuantizationStates.PASSIVE_INIT
for tensor_config in config.input_quantization_config[1: ]:
    tensor_config.observer_algorithm = 'minmax'

正如你所看到的,你需要做的就是找到特殊的算子(这些算子的变量采取不同的量化方案),并找到相应的配置(本例中为W_config),然后根据你的后台需要修改quant_max,quant_min和policy属性(本例中为权重参数的对称、per-tensor量化)。还要注意的是,在大多数学术设置中,只有输入变量需要量化,所以我们挑选出输出配置output_config,把它的状态设置为QuantizationStates.FP32,这意味着PPQ将跳过每个可量化算子中的输出激活的量化,以fp32模式执行。

注册你的Quantizer和Platform

现在你已经创建了你的platform和相应的quantizer,之后你需要在ppq.lib.common中注册。

# 示例代码
from ppq.IR import Operation
from ppq.core import OperationQuantizationConfig
from ppq.quantization.quantizer import BaseQuantizer
from ppq.core import TargetPlatform
class MyQuantizer(BaseQuantizer):
    def init_quantize_config(self,operation:Operation) -> OperationQuantizationConfig:
        return super().init_quantize_config(operation)
    def quant_operation_types(self) -> set:
        return {'Conv','Gemm'}
register_network_quantizer(quantizer=MyQuantizer,platform=TargetPlatform.PPL_CUDA_INT8)

之后你可以通过ppq.api函数调用你的quantizer:

from ppq.lib import Quantizer
from ppq.executor import TorchExecutor
from ppq.api import load_onnx_graph,load_caffe_graph
from ppq.api.interface import dispatch_graph
target_platform = TargetPlatform.ACADEMIC_INT8
EXECUTING_DEVICE = 'cuda'
ppq_graph_ir = load_onnx_graph(model_path)
ppq_graph_ir = dispatch_graph(ppq_graph_ir,target_platform,setting)
executor = TorchExecutor(ppq_graph_ir,device=EXECUTING_DEVICE)
quantizer = Quantizer(target_platform,ppq_graph_ir)
quantizer.quantize(
    inputs=dummy_input,
    calib_dataloader=dataloader,
    executor=executor,
    setting=setting,
    calib_steps=calib_steps,
    collate_fn=lambda x: x.to(executing_device)
)

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值