静态量化是否使模型能够将前一层的输出提供给下一层,而无需转换为 fp(并返回 int)?

问题描述 投票:0回答:1

我正在阅读有关量化的内容(特别是关于 int8),并试图弄清楚是否有一种方法可以避免在将节点的输出馈送到下一个节点之前对其进行反量化和重新量化。所以我最终找到了静态和动态量化的定义。根据onnxruntime

动态量化动态计算激活的量化参数(比例和零点)。 [...]静态量化方法首先使用一组称为校准数据的输入运行模型。在这些运行期间,我们计算每个激活的量化参数。这些量化参数作为常量写入量化模型并用于所有输入。

对我来说,这似乎很清楚,这两种方法之间的区别在于何时计算(反)量化参数(在推理时动态执行,而在推理之前静态执行并将其硬编码到模型中),而不是关于实际的(去)量化过程。

但是我接触到了一些文章/论坛的答案,它们似乎指向了不同的方向。这篇文章讲述了静态量化:

[...] 重要的是,这个额外的步骤允许我们在操作之间传递量化值,而不是在每个操作之间将这些值转换为浮点数,然后再转换回整数,从而显着提高速度。

似乎在争论静态量化不需要在将节点的输出作为输入提供给下一个节点之前对节点的输出应用反量化和量化操作。 我还发现了一个讨论也有同样的争论:

问:[...] 但是,我们的硬件同事告诉我,因为它有 FP 尺度和通道中的零点,所以硬件仍然应该支持 FP 才能实现它。 他们还认为,在每个内部阶段,值(通道内)应该被去量化并转换为 FP,并在下一层再次量化。 [...]

A:对于第一个论点,你是对的,因为标度和零点都是 FP,硬件需要支持 FP 进行计算。 第二个参数可能不正确,对于静态量化,前一层的输出可以输入到下一层,而无需反量化为 FP。也许他们正在考虑动态量化,即在 FP 中将张量保持在两层之间。

其他人也提出了同样的问题。

所以我尝试使用

onnxruntime.quantization.quantize_static
手动量化模型。在继续之前我必须先做一个前提:我不是在人工智能领域,我学习这个话题是为了另一个目的。所以我在谷歌上搜索如何做到这一点,并设法使用以下代码完成它:

import torch
import torchvision as tv
import onnxruntime
from onnxruntime import quantization


MODEL_PATH = "best480x640.onnx"
MODEL_OPTIMIZED_PATH = "best480x640_optimized.onnx"
QUANTIZED_MODEL_PATH = "best480x640_quantized.onnx"


class QuntizationDataReader(quantization.CalibrationDataReader):
    def __init__(self, torch_ds, batch_size, input_name):

        self.torch_dl = torch.utils.data.DataLoader(
            torch_ds, batch_size=batch_size, shuffle=False)

        self.input_name = input_name
        self.datasize = len(self.torch_dl)

        self.enum_data = iter(self.torch_dl)

    def to_numpy(self, pt_tensor):
        return (pt_tensor.detach().cpu().numpy() if pt_tensor.requires_grad
                else pt_tensor.cpu().numpy())

    def get_next(self):
        batch = next(self.enum_data, None)
        if batch is not None:
            return {self.input_name: self.to_numpy(batch[0])}
        else:
            return None

    def rewind(self):
        self.enum_data = iter(self.torch_dl)


preprocess = tv.transforms.Compose([
    tv.transforms.Resize((480, 640)),
    tv.transforms.ToTensor(),
    tv.transforms.Normalize(
        mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

ds = tv.datasets.ImageFolder(root="./calib/", transform=preprocess)

# optimisations
quantization.shape_inference.quant_pre_process(
    MODEL_PATH, MODEL_OPTIMIZED_PATH, skip_symbolic_shape=False)

quant_ops = {"ActivationSymmetric": False, "WeightSymmetric": True}
ort_sess = onnxruntime.InferenceSession(
    MODEL_PATH, providers=["CPUExecutionProvider"])
qdr = QuntizationDataReader(
    ds, batch_size=1, input_name=ort_sess.get_inputs()[0].name)
quantized_model = quantization.quantize_static(
    model_input=MODEL_OPTIMIZED_PATH,
    model_output=QUANTIZED_MODEL_PATH,
    calibration_data_reader=qdr,
    extra_options=quant_ops
)

然而结果让我更困惑。下图显示了 netron 上的两个模型图(“原始”模型和量化模型)的一部分。 这是非量化模型图。

enter image description here

虽然这是量化的。 enter image description here

它添加了 QuantizeLinear/DequantizeLinear 节点的事实可能表明了我正在寻找的答案。然而,这些节点的放置方式对我来说没有任何意义:它在量化后立即计算反量化,因此各种 Conv、Mul 等节点的输入类型仍然是 float32 张量。 我确信我在这里遗漏(或误解)了一些东西,所以我无法弄清楚我最初在寻找什么:静态量化是否允许为节点提供前一个节点的仍然量化的输出?我上面的量化过程出了什么问题?

neural-network artificial-intelligence onnx quantization static-quantization
1个回答
0
投票

硬件人工智能人员在这里,简而言之:如果你愿意,你也可以在层之间传递 int 值。考虑矩阵乘法,

$Y = Wx+b$

这可以表示为量化乘法,

$Y_q = rac{S_xS_w}{S_Y}(X_q-Z_x)(W_q-Z_w) + $

© www.soinside.com 2019 - 2024. All rights reserved.