PPQ.executor中的内容(来自PPQ官网)

PPQ Graph Executor(PPQ执行引擎)

为了量化并优化神经网络模型,PPQ实现了基于Pytorch的执行引擎,该执行引擎能够执行Onnx与Caffe的模型文件,目前支持90余种常见Onnx算子,涵盖1d,2d,3d视觉、语音、文本模型。PPQ的执行引擎位于ppq.executor目录下,有两个主要部分组成:ppq.executor.torch.py文件中包含了执行引擎自身;PPQ.executor.op文件夹中则包含了不同后端的算子库。

PPQ Backend Functions(PPQ算子库)

核心算子库位于ppq.executor.op.torch.default文件中,该文件中包含了所有算子默认的执行逻辑。

对于一个量化算子而言,由于硬件的不同其执行逻辑也可能发生变化。例如LeakyRelu算子的负数部分在GPU上会采用x*alpha的方法完成计算,而在FPGA则会采用x=x>>3完成计算。正是因为这种差异的存在,PPQ允许相同的算子在不同的平台(TargetPlatform)上拥有不同的执行逻辑。这也意味着针对每一个平台,都将实现一个平台独特的算子库文件,这些算子库都继承于ppq.executor.op.torch.default。

def Mul_forward(op:Operation,values:List[torch.Tensor],ctx:TensorBackendContext=None,**kwargs) -> torch.Tensor:
    """
    Performs element-wise binary multiplication(with Numpy-style broadcasting support).
    This oparator supports multidirectional(i.e.,Numpy-style) broadcasting;for more details please check the doc.
    (Opset 14 change): Extend supported types to include uint8, int8, uint16, and int16.
    """
    ASSERT_NUM_OF_INPUT(op=op,values=values,min_num_of_input=2,max_num_of_input=2)
    values = VALUE_TO_EXECUTING_DEVICE(op=op,ctx=ctx,values=values)
    multiplicand,multiplier = values
    return multiplicand * multiplier

 上文中的内容即ppq.executor.op.torch.defualt中Mul算子的执行逻辑,在PPQ中,所有算子在执行时都将接受一系列torch.Tensor作为输入,而后调用pytorch完成算子的计算逻辑。可以打开PPQ的算子库文件查看其它算子的执行逻辑,并且PPQ也提供了register_operation_handler函数,借助该函数可以注册自定义算子的执行逻辑;或是覆盖现有算子的执行逻辑。

def register_operation_handler(handler:Callable,operation_type:str,platform:TargetPlatform):
    """
    Register a custimized function as operation handler.
    Function should accept at least 3 input parameters,return one or more tensor as result:
    func(op:Operation,values:List[torch.Tensor],ctx:TorchBackendContext=None,**kwargs) -> torch.Tensor:
    If there is already another operation handler for given operation_type,
        new handler will replace the old one without warring.
    """
    if platform not in GLOBAL_DISPATCHING_TABLE:
        raise ValueError('Unknown Platform detected,Please check your platform setting.')
    GLOBAL_DISPATCHING_TABLE[platform][operation_type] = handler

该函数位于ppq.api,你可以使用语句from ppq.api import register_operation_handler来引入它。

PPQ Executor(PPQ执行引擎) 

接下来将介绍PPQ执行引擎TorchExecutor,可以使用语句from ppq import TorchExecutor导入执行引擎。初始化执行引擎则需要传入一个PPQ计算图实例对象,在这里假设已经获取到了一个量化后的计算图对象ppq_quant_ir,并使用下面的语句初始化计算引擎: 

executor = TorchExecutor(graph=ppq_quant_ir)
executor.forward(inputs=...,output_names=...,hooks=...)

使用executor.forward接口获取图的执行结果,它将可以传入三个参数:

  • inputs:inputs(Union[dict,list,torch.Tensor]):[input tensors or somewhat]
  • output_names(List[str],optional):output variable names.default is None.
  • hooks(Dict[str,RuntimeHook],optional):A hook table for customizing operation bahaviour and collate data during executing. 

当执行引擎获取到推理请求时,它将按拓扑顺序依次执行图中的算子:下面展示了一个简单的示例:

在这里,图中包含三个算子Conv,Relu,Softmax,他们将按照拓扑次序被依次执行。PPQ的执行引擎会在执行完Conv算子后,将Conv算子的结果暂存于Var1中,供Relu算子取用。而在执行完Relu算子后,PPQ执行引擎则会及时地释放Var1中暂存的数据,因为它们不会被其他算子取用,而且也不是网络的输出Variable。在每一次推理过后,PPQ还会清空网络中所有的暂存变量以释放显存。下面代码段展示了一个非量化算子的执行逻辑:

for operation in executing_order:
    outputs = operation_forward_func(operation,inputs,self._executing_context)
    outputs = outputs if isinstance(outputs,(list,tuple)) else [outputs]
    fp_outputs = outputs
    for output_idx,output_var in enumerate(operation.outputs):
        output_var = operation.outputs[ouput_idx]
        output_var.value = outputs[output_idx]
# clear all variable(static clear).
for var in self._graph.variables.values():
    if not var.is_parameter:
        var.value = None

PPQ的执行引擎是专为量化计算图的设计执行的-接下来深入到量化算子的执行过程中去。对于一个量化算子而言,其每一个输入和输出变量都会有一个Tensor Quantization Config(TQC)控制结构体对量化过程进行描述。

对于一个量化Conv算子而言,PPQ将为它创建2-3个Input TQC,以及一个Output TQC。分别对其输入变量以及输出变量的量化行为进行描述。下面的代码展示了如何为量化算子创建特定的TQC描述量化逻辑。

if operation.type == 'Conv':
    config = self.create_default_quant_config(
        op = operation,
        num_of_bits = 8,
        quant_max = 127,
        quant_min = -128,
        observer_algorithm = 'percentile',
        policy = QuantizationPolicy(
            QuantizationProperty.PER_TENSOR + 
            QuantizationProperty.LINEAR +
            QuantizationProperty.SYMMETRICAL),
        rounding = RoundingPolicy.ROUND_HALF_EVEN)
    # 关闭所有输入量化,状态设置为fp32
    for tensor_quant_config in config.input_quantization_config:
        tensor_quant_config,state = QuantizationStates.FP32
    operation.config = config

在下图中,展示了PPQ执行引擎对于量化算子的执行逻辑:

 在PPQ中,算子的执行被分为四个过程:

  • 首先PPQ将根据算子上的TQC信息量化算子的输入。量化过程并非是原地的,量化后的数据会是一个新的torch.Tensor。
  • 随后PPQ在算子库中寻找算子的执行逻辑,已经提到对于每一个平台,都可以拥有自己的一套独立的算子库。PPQ将按照算子的平台找到特定的计算逻辑,并调用他们完成计算得到结果。
  • PPQ将根据算子上的TQC信息量化算子的输出。同样地,输出的量化也不是原地的。
  • 最后将量化好的结果写入到计算图的Variable上,从而供后续的算子取用。

对于一个非量化算子而言,上述步骤中的1,3是可以省略的。

下图展示了一个量化卷积算子与TQC之间的关系:

 Quantize Delegate(量化代理函数)

 PPQ允许为网络中特定的TQC注册量化代理函数。这样就可以注册自定义的量化处理逻辑,而非使用PPQLinearQuantFunction完成量化。

def register_quantize_delegate(
    self,config:TensorQuantizationConfig,
    delegator:TorchQuantizeDelegator):

使用executor.register_quantize_delegate(config,function)完成函数注册,被注册的函数必须满足TorchQuantizeDelegator所定义的接口。下面给出一个简单的量化代理函数例子:

class MyQuantDelegator(TorchQuantizeDelegator):
    """
    Use This class to realize your quantization logic
    Inherit class TorchQuantizeDelegate,implement interface _call_,then
    register your delegator with executor.register_quantize_delegate
    """
    def _call_(self,tensor:torch.Tensor,config:TensorQuanttizationConfig) -> torch.Tensor:
        if config.policy.has_property(QuantizationProperty.ASYMMETRICAL):
            raise ValueError('Sorry,this delegator handles only Symmetrical Quantizations.')
        print('You are invoking custimized quant function now.')
        return torch.round(tensor / config.scale) * config.scale

在执行器遇到TQC时,将会调用executor.quantize_function执行量化,其逻辑为:

def quantize_function(self,tensor:torch.Tensor,config:TensorQuantizationConfig=None) -> torch.Tensor:
    if config is None or not QuantizationStates.is_activated(config.state): return tensor
    elif config in self._delegates: return self._delegates[config](tensor,config)
    else:
        if config.policy.has_property(QuantizationProperty.DYNAMIC):
        else:
            return self._default_quant_fn(tensor,config)

 Usage(用法示例)

PPQ的执行器初始化需要一个计算图示例作为参数:

executor = TorchExecutor(graph=ppq_quant_ir)
executor.forward(inputs=...,output_names=...,hooks=...)

 这一计算图可以是量化过后的,也可以是没有量化的。但PPQ希望传入的计算图经过正确的调度,传入没有调度的计算图将会触发警报:

if not graph.extension_attrib.get(IS_DISPATCHED,False):
    ppq_warning('Can not create executor with your graph,graph is not correctly dispatched,'
        'use dispatch_graph(graph=ir,platform=platform,setting=setting) first.') 

executor.forward需要三个参数,下面举例对其进行说明:

# 传入三个变量a,b,c作为输入
executor.forward(inputs=[a,b,c],output_names=...,hooks=...)
# 分别对图中input,var1两个变量传入a,b作为输入
executor.forward(inputs={'input':a,'var1':b},output_names=...,hooks=...)
# 传入一个完整的tensor作为输入
executor.forward(inputs=torch.zeros(shape=[1,3,224,224]),output_names=...,hooks=...)
# 要求网络输出output,Var1的值
executor.forward(inputs=...,output_names=['output1','Var1'],hooks=...)

executor.forward函数默认不需要梯度,如果希望执行带有梯度的网络,需要使用executor.forward_with_geadient函数。forward函数的返回值永远是一个torch.Tensor数组,其中元素的顺序由output_names参数决定。

Hook(执行钩子函数)

在调用executor.forward函数时可以传入hooks参数。钩子函数是注册在op上的,可以传入一个字典用来说明调用的钩子函数:字典{‘Conv1’:myhook}说明了希望在算子Conv1的执行器件调用钩子函数myhook。

钩子函数必须继承于RuntimeHook类,必须实现成员函数pre_forward_hook,post_forward_hook。在这两个函数中,可以制定特定的逻辑修改算子输入输出的值。

class RuntimeHook(metaclass=ABCMeta):
    """
    RuntimeHook is an abstract class designed for executor customizing.
    """
    def _init_(self,operation:Operation) -> None:
        self._hook_to = operation
    def pre_forward_hook(self,inputs:list,**kwargs) -> list:
        return inputs
    def post_forward_hook(self,outputs:list,**kwargs) -> list:
        return outputs

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值