在本次教程中将展示使用ppq TensorQuantizationConfig量化一个torch Tensor。首先,想给你看一个例子,说明如何使用pyyotch自己的函数量化tensor:
import torch
v_fp32 = torch.Tensor([1,2,3,4,5,6,7,8,9,10])
scale,clip_min,clip_max = 2,-128,127
v_int8 = torch.clip(torch.round(v_fp32 / scale),min=clip_min,max=clip_max)
print(f'Torch Quantized v_int8: {v_int8}')
# tensor([0.,1.,2.,2.,2.,3.,4.,4.,4.,5.])
# let's dequant v_int8
dqv_fp32 = v_int8 * scale
print(f'Torch Dequantized dqv_fp32: {dqv_fp32}')
# tensor([0.,2.,4.,4.,4.,6.,8.,8.,8.,10.])
这里使用对称量化,其中scale=2,,截断值为-128-127。使用的量化公式为:
y = torch.clip(torch.round(x / scale),min=clip_min,max=clip_max)
同样地,使用的反量化公式为:
y = x * scale
卷积量化
为了在pytorch中量化卷积层,一个合理的方式是在卷积之前量化卷积输入和权重,得到卷积结果后量化输出。下面这个例子展示了一个卷积层是如何被量化的:
import torch
import torch.nn.functional as F
def quant(tensor: torch.Tensor, scale = 0.1, clip_min = -128, clip_max = 127):
y = torch.clip(torch.rpund(tensor / scale),min_clip_min,max=clip_max)
y = y.char() # convert y to int8 dtype
def dequant(tensor: torch.Tensor,scale = 0.1):
y = tensor * scale
y = y.float() # convert y to fp32 dtype
return y
# fp32 version
v_fp32 = torch.rand(size=[1,3,96,96])
w_fp32 = torch.rand(size=[3,3,3,3])
b_fp32 = torch.rand(size=[3])
o_fp32 = F.conv2d(v_fp32,w_fp32,b_fp32)
print(o_fp32)
# int8 version
v_fp32 = torch.rand(size=[1,3,96,96])
w_fp32 = torch.rand(size=[3,3,3,3])
b_fp32 = torch.rand(size=[3])
dqv_fp32 = dequant(quant(v_fp32))
dqw_fp32 = dequant(quant(w_fp32))
o_fp32 = F.conv2d(dqv_fp32,dqw_fp32,b_fp32)
dqo_fp32 = dequant(quant(o_fp32))
print(dqo_fp32)
注意到F.conv2d使用的是dqv_fp32,dqw_fp32而不是v_int8,w_int8,即使pytorch在最新版本中确实有一个int8卷积的实现。事实上使用反量化值和使用int8量化值将会有相同的结果,然而应用int8版本将会在执行过程中阻止梯度方向传播。在PTQ中梯度是必须的,它确保我们可以微调你的网络,以获得更好的量化性能,所以我们我们更愿意在前向传播中使用反量化的值。
PPQ QLinear函数
现在是使用PPQ QLinear函数的时候了,让我们先从PPQ库中导入它们:
from ppq import TensorQuantizationConfig
from ppq.quantization.qfunction.linear import PPQLinearQuant_toInt,PPQLinearQuantFunction
PPQLinearQuantFunction和PPQLinearQuant_toInt是PPQ执行器中的量化(反量化)函数:PPQLinearQuant_toInt是把fp32 tensor量化到int8,PPQLinearQuantFunction可以量化和反量化一个fp32 tensor。TensorQuantizationConfig是描述量化参数(scale,offset,...)的数据结构。换言之,TensorQuantizationConfig告诉你如何量化你的tensor。
import torch
from ppq import TensorQuantizationConfig
from ppq.core import *
from ppq.quantization.qfunction.linear import PPQLinearQuant_toInt,PPQLinearQuantFunction
v_fp32 = torch.tensor([1,2,3,4,5,6,7,8,9,10])
tqc = TensorQuantizationConfig(
policy = (QuantizationPolicy(
QuantizationProperty.SYMMETRICAL +
QuantizationProperty.LINEAR +
QuantizationProperty.PER_TENSOR
)),
rounding = RoundingPolicy.ROUND_HALF_EVEN,
num_of_bits = 8,
quant_min = -128, quant_max = 128,
scale = torch.tensor([2.0]),
offset = torch.tensor([0.0]),
observer_algorithm = None,
state = QuantizationStates.ACTIVATED)
v_int8 = PPQLinearQuant_toInt(tensor=v_fp32,config=tqc)
dqv_fp32 = PPQLinearQuantFunction(tensor=v_fp32,config=tqc)
print(f'PPQ Quantized v_int8: {v_int8}') # tensor([0,1,2,2,2,3,4,4,4,5],dtype=torch.int32)
print(f'PPQ Dequant dqv_fp32: {dqv_fp32}') # tensor([0.,2.,4.,4.,4.,6.,8.,8.,8.,10.])
在这里一个TensorQuantizationConfig对象被初始化,TensorQuantizationConfig的属性是QuantizationPolicy,RoundingPolicy,num_of_bits,quant_min,quant_max,scale,offset和QuantizationStates。
QuantizationPolicy
一个QuantizationPolicy是一些QuantizationPropertys的组合,在PPQ(与其他配置单独使用)中QuantizationPolicy被使用去描述一个tensor是如何被量化的。PPQ现在支持7中不同的量化属性。
PER_TENSOR:所有Tensor中共享Scale和Offset(对于有偏置的Convulution层和Gemm层,偏置层将被动量化)
PER_CHANNEL:一个Tensor中允许Scale不共享,有很多Scale(不能每一个元素都有一个scale,硬件无法计算),可以每一个channel一个Scale
LINEAR:线性量化,遵循公式quant(x)=clip(round(x/scale))
EXPONENTIAL:表示指数量化,还没有使用。
SYMMETRICAL:对称量化,在这种模式下offset被停用
ASYMMETRICAL:非对称量化,在这种模式下offset被使用
POWER_OF_2:表示整数量化,在这种模式下scale必须是pow(2,k)
RoundingPolicy
RoundingPolicy 是PPQ量化加速的核心设置。它定义了量化计算中的取整行为。
公式:quant(x) = clip(round(x/scale,RoundingPolicy),-128,127)
PPQ现在支持7中不同的取整策略。
ROUND_HALF_EVEN = 0
ROUND_HALF_UP = 1
ROUND_HALF_DOWN = 2
ROUND_HALF_TOEARDS_ZERO = 3
ROUND_HALF_FAR_FROM_ZERO = 4
ROUND_TO_NEAR_INT = 5
ROUND_UP = 6
QParams
Scale,offset,quant_min,quant_max都是参数,它们是量化函数中使用的参数。
quant(x) = clip(round(x/scale,RoundingPolicy),-128,127)
在对称量化中offset将被忽略,在PER_TENSOR量化中 scale和offset是单元素tensor。对于PER_CHANNEL策略,每一个channel都有一个scale和offset。在PPQ中,Offset总是一个浮点tensor,PPQ执行器将在执行前将它取整为int。
QuantizationStates
QuantizationStates是PPQ量化中的核心数据结构。QuantizationStates表示量化配置是否被激活。对于一个TensorQuantizationConfig实例,现在有11个可用的量化状态。
现在我们对每一种量化状态给一个简短的描述:
INITIAL:在创建TensorQuantizationConfig时给出,是所有量化配置的初始状态。
PASSIVE_INITIAL:对于特定的参数,如GEMM(卷积)的偏置和Pad的填充值。通常情况下,它没有独立的量化scale和偏移量,而与其他tensor的配置一起被量化。对于GEMM和卷积,它的偏置将与输入scale*权重scale一起被量化,对于填充值和clip值,它与输入sale相同。如果有任何量化配置是INITIAL或PASSIVE_INIT状态,PPQ将拒绝部署你的模型,并出现错误。部署你的模型,并且会抛出一个错误。 当PPQ.core.config.DEBUG设置为True时,该检查将被忽略。
DEACTIVATED:DEACTIVATED状态与 "反量化 "功能有关,一旦一个操作被反量化、 所有相关的tensor配置将被替换为DEACTIVATED,这样就跳过了执行过程中的所有量化。
SOI:只要tensor量化配置持有SOI状态、 它将永远不会被量化,也不会被包括在任何优化算法中。 这意味着底层tensor是与SOI相关的tensor,它不能被量化。
ACTIVATE:意味着相应的tensor已经准备好用其配置进行量化。
PASSIVE:意味着相应的tensor已经准备好用其配置进行量化。(然而,它的配置并不是独立的,它仍然取决于其他的人)。
BAKED:意味着相应的张量已经被预先量化,它的值可以直接前向传播而不需要量化。