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

PPQ Graph(PPQ计算图)

PPQ在Onnx IR的基础上构建了自己的计算图结构。在这里,一个计算图的定义由图(BaseGraph),算子(Operation)和变量(Variable)三者组成。PPQ的计算图对象不仅保存了网络的信息结构与参数信息,同时也保存了图上的量化信息与量化参数,所有的量化过程都是围绕着这样的底层数据结构展开的。接下来将介绍PPQ IR的基本定义与使用方法:

1.Variable(变量)

该类型定义于ppq\IR\base\graph.py。

在Onnx中,Variable描述了图中的连接关系,可以理解它是图中的“边”。在Onnx的定义中,图中的边是单向且一对多的。这意味着Onnx中的边总会一个或零个输入算子,二可以多余于一个输出算子。当一个Variable的输入算子是None,它表示Onnx中的常量,通常而言这些常量用来表示网络中的权重以及参数。

成员属性:

  • is_parameter(self) -> bool:返回当前变量是否是一个参数,如果变量是一个参数,通常而言它是有常数值的。参数变量会出现在onnx文件的initializer中。这个属性允许用户进行修改,你可以通过variable.is_parameter = True的方式对其赋值。
  • name(self) -> str:返回当前算子的名字,可以使用variable.name = 'New Var Name'的方式为变量指定新的名字。但值得注意的是,不能在一个图中有两个命名相同的变量。
  • value(self) -> torch.Tensor:返回当前variable的值,对于那些连接了算子两端的非参数变量而言,它们通常是无值的,仅当运行时,它们的值才会被赋予,而对于常数变量而言,variable.value将直接返回变量的常数值。可以通过variable.value = xxx的方式对变量的值直接进行修改。
  • dest_ops(self) -> List[Operation]:返回当前变量连接的所有下游节点。在Onnx与PPQ的定义中,任何Variable都有且只有一个源算子,但可以有多个目标算子,该函数将返回与当前Variable相连接的所有目标算子,这个列表是无序的。可以直接修改这个列表从而为Variable添加新的目标算子,如此修改时,需要同步修改目标算子的inputs属性。
  • source_op(self) -> Operation:返回当前变量的源算子。在Onnx与PPQ的定义中,任何Variable都有且只有一个源算子,但可以有多个目标算子,该函数将返回与当前Variable的源算子。可以直接修改该属性从而替换变量的源算子,如此修改时,需要同步修改源算子的outputs属性。
  • shape(self) -> List[Union[Text,int,None]]:返回当前变量的形状信息。当variable.value不为空时,该函数直接返回variable.value.shape,当variable.value为空时,该函数返回自身的self.shape属性。非参数变量的形状信息将在执行时由ppq.executor.TorchExecutor.tracing_operation_mata创建,若用户没有调用此函数,则图中所有非参数变量的形状信息均为空值。该属性会在导出时写入Onnx文件。可以通过直接赋值的方式修改非参数变量的形状信息,PPQ接受int,Text,None三种类型指定的形状,如['Batchsize',3,224,224],[1,3,224,224],[None,3,224,224]均是合理有效的形状信息。
  • dtype(self) -> DataType:返回当前变量的数据类型。当variable.value不为空时,该函数直接返回variable.value.dtype,当variable.value为空时,该函数返回自身的self.dtype属性。非参数变量的数据类型信息将在执行时由ppq.executor.TorchExecutor.tracing_operation_mata方法创建,若没有调用此函数,则图中所有非参数变量的数据类型默认为FP32,。该属性会在导出时写入Onnx文件。可以直接通过复制的方式修改非参数变量的数据类型。
  • copy(self,copy_value:bool=False):返回当前变量的克隆,参数copy_value指明了是否进行深克隆。

2.Operation(算子) 

 类型定义于ppq\IR\base\graph.py.

Operation是一个计算图中的节点,在Onnx中一个节点可以有多个输入和输出变量,任何一个算子的输入和输出都是有序的。

成员属性:

  •  socker(self) -> OpSocker:返回当前算子的接线器(OpSocker),在PPQ当中使用接线器描述算子应当被如何量化以及网络中的数据传递情况。算子接线器描述了该算子的每一个输入与输出变量是否能够被量化,描述了算自重数据从哪一个输入流动到哪一个输出。
  • inputs(self) -> List[Variable]:返回当前算子的所有输入变量,若没有输入则返回空列表,该列表是有序的。该属性直接返回op.inputs对象,可以直接修改返回的列表从而为算子添加新的输入,如此修改时,需要同步修改输入变量的dest_ops属性。
  • outputs(self) -> List[Variable]:返回当前算子的所有输出变量,若没有输出则返回空列表没该列表是有序的。该属性直接返回op._outputs对象,可以直接修改返回的列表从而为算子添加新的输出,如此修改时,需要同步修改输入变量的dest_ops属性。
  • parameters(self) -> List(Variable):返回当前算子的所有参数,这将返回一个视图,并不能修改这个视图来为算子添加新的参数。
  • num_of_parameter(self) -> int:返回当前算子的参数个数。
  • name(self) -> str:返回当前算子的名字。可以使用op.name=‘New Op Name’的方式为算子指定新的名字,但值得注意的是,不能在一个图中有两个命名相同的算子。
  • type(self) -> str:返回当前算子的类型。可以使用op.type='New Op Type'的方式为算子指定新的类型。
  • opset(self) -> Opset:返回当前算子的Opset。
  • attributes(self) -> Dict[str,Any]:返回当前算子的属性字典。可以对其进行修改从而添加、修改、删除算子上的属性,任何添加的属性都会在导出时被写入Onnx文件中。
  • platform(self) -> TargetPlatform:返回当前算子的平台。可以使用op.platform=xxx来修改算子的调度情况。
  • num_of_input(self) -> int:返回当前算子的输入个数。
  • num_of_output(self) -> int:返回当前算子的输出个数。
  • extension_attrib(self) -> dict:返回扩展属性集合,该属性与attributes类似,但写入此处的属性并不会写入到Onnx中。可以使用extension_attrib为算子添加自定义的信息,从而完成信息在不同Optim Pass之间的传递。

成员函数: 

  • copy(self) -> Operation:返回一个当前算子的克隆对象。

3.QuantableOperation(量化算子) 

类型定义于ppq\IR\quantize.py

QuantableOperation是PPQ在Onnx Operation基础上追加定义的内容,它表示一个被量化过的算子,它是Operation的子类,这表示它具有普通算子的一切功能,以此同时追加地定义了一些用于量化的内容:

成员属性:

  • config(self) -> OperationQuantizationConfig:返回当前量化算子的量化信息,OperationQuantizationConfig结构体包含了量化算子的所有输入、输出量化信息。
  • input_quant_config(self) -> List[TensorQuantizationConfig]:以有序列表的形式返回当前算子的输入量化信息,可以修改量化信息中的内容,但不推荐修改列表本身。
  • output_quant_config(self) -> List[TensorQuantizationConfig]:以有序列表的形式返回当前算子的输出量化信息。可以修改量化信息中的内容,但不推荐修改列表本身。

成员函数: 

  • baking_parameters(self,quant_func:BaseQuantFunction):执行量化烘焙,这将使得该算子的所有参数量化静态化,这些参数的值将被量化后的值所替换,其对应TQC的状态被切换为Baked。在神经网络的前向传播中,我们总是需要反复计算算子参数的量化过程,但当参数已经确定并不再发生变化时,可以通过缓存这些参数量化结果的方式加速程序运行。
  • store_parameter_value(self):备份算子当前的状态,该函数与dequantize相互呼应,使得算子能够在量化与非量化状态中相互切换。
  • dequantize(self,parameter_only:bool=False,expire_device:str='cpu'):解除当前算子的量化,如果在量化过程中算子的参数并未发生改变,则可以通过直接切换TQC状态的方式完成dequantize操作。但如果在量化过程中算子的参数被修改,则该方法不仅切换TQC的状态,同时从expire_device上去去的算子参数的备份数据并替换现有的。 
  • restore_quantize_state(self,expire_device:str='cpu'):还原算子的量化,如果在量化过程中算子的参数并未发生改变,则可以通过直接切换TQC状态的方式完成restore_quantize_state操作。但如果在量化过程中算子的参数被修改,则该方法不仅切换TQC的状态,同时从expire_device上取得量化后的算子参数并替换现有的。

4.Graph(计算图) 

类型定义于ppq\IR\base\graph.py

成员属性: 

  • operations -> Dict[str,Operation]:返回一个字典,包含当前计算图中的所有算子。如需添加或删除一个算子,用户需要使用成员函数create_operation,remove_operation。可以使用graph.operations.values()方法来获得一个算子列表从而进行算子遍历
  • variable -> Dict[str,Variable]:返回一个字典,包含当前计算图中的所有变量。如需添加或删除一个算子,需要使用成员函数create_variable,remove_variable。可以使用graph.variables.values()方法来获得一个算子列表从而进行算子遍历
  • inputs -> Dict[str,Varibale]:返回一个字典,包含当前计算图中的所有输入变量。如需添加或删除输入变量,可以直接操作该字典对象,或者通过成员函数mark_variable_as_graph_input完成。理论上可以将任何图中的变量标记为图的输入变量,但这可能导致错误,请不要将参数与中间变量标记为输入
  • outputs -> Dict[str,Variable]:返回一个字典,包含当前计算图中的所有输出变量。如需添加或删除一个算子,可以直接操作该字典对象,或者通过成员函数mark_variable_as_graph_output完成。可以将任何变量标记为图的输出,但这可能导致错误,并干扰图的融合逻辑。当你把图中的中间变量标记为输出时,请格外注意该操作是否会对图融合产生影响,PPQ与推理框架都难以保证此时图融合的正确性

成员函数: 

  • get_downstream_operations(self,operation:Operation) -> List[Operation]:获取一个给定算子的所有下游算子,如算子不在图中,则抛出异常。如果给定的算子没有下游算子,则返回空列表。
  • get_upstream_operations(self,operation:Operation) -> List[Operation]:获取一个给定算子的所有上游算子,如算子不在图中,将抛出异常。如果给定的算子没有上游算子,则返回空的列表。
  • topological_sort(self) -> List[Operation]:返回一个算子列表,其中的算子按照图内拓扑序先后进行排序。对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。当无法完成上述操作时,则应任务图是由多个联通部分组成的,或图中有环。PPQ会针对上述异常情况抛出错误,并指出哪些算子无法完成排序。
  • insert_op_before(self,A:Operation,B:Operation,input_idx:int=0):在给定的算子B之前插入一个算子A,被插入的算子必须不含任何输入输出变量。如果算子B中存在多个输入变量,用户需要依靠参数input_idx指明插入在哪一个变量上。如果送入的算子A含有任何相连的输入输出变量,该函数将抛出异常。
  • insert_op_after(self,A:Operation,B:Operation,output_idx:int=0):在给定的算子B之后插入一个算子A,被插入的算子必须不含任何输入输出变量。如果算子B存在多个输出变量,用户需要依靠参数output_idx指明插入在哪一个变量上。如果送入的算子A中含有任何相连的输入输出·变量,该函数将抛出异常。
  • remove_operation(self,removing_op:Operation,keep_coherence:bool=False):从图中移除一个给定的算子,被移除的算子可以包含任意多个输入输出变量。移除这个算子时,这个算子的参数变量将一并从图中移除。通常而言,移除一个算子将导致图不再连续,用户可以手动连接不连通的部分,也可以使用参数keep_coherence=True要求PPQ自动完成上述操作。当被移除的算子只有一个非参数输入和输出变量时,可以指定keep_coherence=True,PPQ将保持算子被移除后图的连贯性。当给定的算子不在图中,该函数将抛出异常。
  • def remove_variable(self,removing_var:Variable):从图中移除一个给定的变量,被移除的变量可以有source_op与dest_ops。这个函数操作将一并将removing_var与source_op中的输出中移除,这个函数操作将一并将removing_var从dest_ops中的输入移除。
  • create_link_with_op(self,A:Operation,B:Operation,variable:Variable=None):连接给定的A,B算子,如果参数variable不为空,则使用给定的variable完成连接,否则将创建一个variable完成连接。如果给定了一个variable,则给定的变量必须以算子A为作为起点,或者没有source_op(此时PPQ将修改它的source_op=A),否则将抛出异常。对于这个函数而言,算子A可以为空,此时这个函数将使得variable.sorce_op=None,并将variable加入B的输入变量中。如果算子A,B不在图中,将抛出异常。
  • create_link_with_var(self,A:Variable,B:Variable):连接给定的A,B两个变量,这个操作将使用变量A连接A.source_op与B.dest_ops,变量B将被移除执行这个操作时,PPQ将检查B.source_op是否为空,若不为空则抛出异常。如果变量A,B不在图中,将抛出异常。
  • mark_variable_as_graph_input(self,var:Variable):将一个变量标记为图的输入。
  • mark_variable_as_graph_output(self,var:Variable):将一个变量标记为图的输出。
  • copy(self,copy_value:bool=False) -> BaseGraph:返回一个图的克隆对象,参数copy_value决定了是仅拷贝图的结构,还是执行深拷贝。

PPQ Graph Extension(PPQ计算图扩展) 

在PPQ中,设计了一类用于扩充BaseGraph的功能,定义在文件夹ppq\IR中。

这些类涵盖常见的算子合并、算子拆分、算子规范化、图模式匹配等功能。可以在优化过程以及图的预处理过程中使用这些类完成特定的任务,下面简要介绍其中常用的功能。

5.SearchableGraph(图模式匹配引擎)

定义于ppq/IR/search.py。

该类用于在计算图中检索特定模式,匹配满足特定规则的算子。

成员方法

  • path_matching(self,sp_expr:Callable,rp_expr:Callable,ep_expr:Callable,direction:str) -> List[Path]:按给定模式匹配图中的路径,在网络中,路径指的是从起点到终点的全程路由。参数sp_expr,rp_expr_ep_expr用于定义匹配模式,它们分别对应起点、中继点、终点的匹配模式。direction用于确定匹配方向。

如:

        sp_expr = lamdba x: x.type == 'Conv'

        rp_expr = lamdba x, y: y.type == 'Relu'

        ep_expr = lamdba x: x.type == 'Conv'

        direction = 'down'

该指令检索出从任意Conv出发,到任意Conv的所有可行路径

其中路径上含有任意多个Relu节点,并且只能包含Relu

又如:

        sp_expr = lamdba x: x.type in {'Conv','Gemm'}

        rp_expr = lamdba x, y: x.type == 'Gemm' and y.type == 'Relu'

        ep_expr = lamdba x: x.type in {'Conv','Gemm'}

        direction = 'down'

该指令检索出从任意Conv或Gemm出发,到任意Conv或Gemm的所有可行路径

其中如果输入算子是Gemm,则允许路径上存在一个Relu

sp_expr,ep_expr均是一元表达式,rp_expr则需要接受两个输入做出判断。该函数返回所有图中满足条件的路径,路径是有序的。

  • opset_matching(self,sp_expr:Callable,rp_expr:Callable,ep_expr:Callable,direction:str) -> OperationSet:与path_matching匹配规则类似,但该函数不关注算子的顺序,因此它将更加高效。函数返回匹配到的所有算子(无序)。
  • pattern_matching(self,patterns:List[Callable],edges:List[List[int]],exclusive:bool=True) -> List[List[Operation]]:按给定模式匹配网络中的子图结构。暴力子图模式匹配,这是PPQ0.6.6更新的内容,在0.6.6之前,使用具有不确定性的贪心匹配算法,但是考虑到实际应用中的问题。在0.6.6版本之后,将其修改为枚举匹配。子图匹配问题是一个NP-Hard的问题,不存在多项式时间复杂度的解法。需要给出一个模式子图,match_burte_force方法将在graph中对模式子图进行匹配。PPQ使用了非递归的算法完成上述匹配,其最坏时间和空间复杂度大概都是O(NM^k)其中N是母图节点个数,M是子图节点个数,k是母图的最大出度。对于存在二义性子图模式,匹配复杂度将指数级增长;为了限制算法执行时间,当匹配到多于max_candidates个模式子图时,算法强制停机,并报错返回。实际使用中的时间复杂度更加接近于O(NM)。参数exclusive指定了是否需要进行精确匹配。在精确匹配模式下:不允许模式子图中除根节点外的其他节点有来自模式子图以外节点的输入;不允许模式子图中除叶节点外的其他节点有朝向模式子图以外节点的输出。 

使用例子:

        pt = PatternTree(

                patterns = [lambda x: x.is_computing_op,'Softplus','Tanh','Mul']

                edges = [[0,1],[1,2],[2,3],[0,3]])

        pt will match pattern like that:

                                                        ---'Softplus'  ---  'Tanh'  --

        lamdba x: x.is_computing_op  ---  +

                                                        ---  ---  ---  ---  -- 

该函数按模式指定的顺序返回匹配到的算子

6.GraphFormatter(图规范化) 

定义于ppq\IR\morph.py

这个类中包含大量成员方法,这些方法用于规范化图中算子,并移除孤立的变量,将重点介绍几个成员函数。

成员方法

  • truncate_on_var(self,var:Variable,mark_as_output:bool):从一个指定的变量处截断计算图,该变量后续的所有算子都将被移除。参数mark_as_output决定了是否将当前变量指定为图的输出变量。
  • delete_isolated(self):移除图中所有悬而未决的孤立变量与孤立算子,这将删除那些没有连接到图的输出的算子与变量。
  • format_parameter(self) -> None:分裂图中所有的参数,确保它们是一对一的。Onnx允许变量存在一对多的关系,即一个参数可以被多个算子所共同使用,这对于PPQ的后续处理逻辑而言会出现错误。因此该函数用于分裂图中所有一对多的参数,它们将被复制多份,并保证处理过后的图中不存在同时连接在多个算子上的参数。

7.GraphMerge(算子融合器) 

定义于ppq\IR\morph.py

这个类中包含大量成员方法,这些方法用于融合图中算子。

成员方法 

  • fuse_bn(self):合并图中的Conv+bn,Gemm+bn,MatMul+bn,ConvTranspose+bn
  • fuse_gemm(self):合并图中的MatMul+Add,将其替换成Gemm。该函数不会检查替换的正确性,在Onnx定义中Gemm仅能处理二维输入,但MatMul可以处理多维输入,因此融合之后的算子可能出现错误。
  • fuse_layernorm(self):合并图中的layernorm。Onnx标准中不包含Layernorm算子,因此它们将被拆分成ReduceMean(1)---Sub(2)---Pow(3)---ReduceMean(4)---Add(5)--Sqrt(6)---Div(7)---Mul(8)---Add(9)。该函数将它们进行合并
  • fuse_skiplayernorm(self):合并图中的Add+Layernorm
  • fuse_gelu(self):合并图中的gelu激活函数。Onnx标准中不包含Gelu算子,因此它们将被拆分成‘Div’,‘Erf’,‘Add’,‘Mul’,‘Mul’5个算子。该函数将它们进行合并
  • fuse_bias_add(self):合并图中的bias add。在一些情况下,Onnx导出的计算图会出现Conv bias形成独立算子的情况,即Conv之后存在单独的Add算子。该函数用于合并上述情况。

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值