首页 > 其他分享 >量化训练及精度调优经验分享

量化训练及精度调优经验分享

时间:2024-11-15 14:57:54浏览次数:1  
标签:qat conv cfg self 调优 calib 量化 model 精度

本文提纲:

  1. fx 和 eager 两种量化训练方式介绍
  2. 量化训练的流程介绍:以 mmdet 的 yolov3 为例
  3. 常用的精度调优 debug 工具介绍
  4. 案例分析:模型精度调优经验分享

第一部分:fx 和 eager 两种量化训练方式介绍

首先介绍一下量化训练的原理。

上图为单个神经元的计算,计算形式是加权求和,再经过非线性激活后得到输出,这个输出又可以作为下一个神经元的输入继续运输,所以神经网络的基础运算是矩阵的乘法。如果神经元的计算全部采用 float32 的形式,模型的内存占用和数据搬运都会很占资源。如果用 int8 替换 float32,内存的搬运效率能提高 75%,充分展示了量化的有效性。由于两个 int8 相乘会超出 int8 的表示范围,为了防止溢出,累加器使用 int32 类型的,累加后的结果会再次 requantized 到 int8;

量化的目标就是在尽可能不影响模型精度的情况下降低模型的功耗,实现模型压缩效果,常见的量化方式有后量化训练 PTQ 和量化感知训练 QAT。

量化感知训练其实是一种伪量化的过程,即在训练过程中模拟浮点转定点的量化过程,数据虽然都是表示为 float32,但实际的值会间隔地受到量化参数的限制。具体方法是在某些 op 前插入伪量化节点(fake quantization nodes),伪量化节点有两个作用:

1.在训练时,用以统计流经该 op 的数据的最大最小值,便于在部署量化模型时对节点进行量化

2.伪量化节点参与模型训练的前向推理过程,因此会模型训练中导入了量化损失,但伪量化节点是不参与梯度更新过程的。

上图是模型学习量化损失的示意图, 正常的量化流程是 quantize->mul(int)->dequantize,而伪量化是对原先的 float 先 quantize 到 int,再 dequantize 到 float,这个步骤用于模拟量化过程中 round 操作所带来的误差,用这个误差再去进行前向运算。上图可以比较直观的表示引起误差的原因,从左到右数第 4 个黑点表示一个浮点数,quantize 后映射到 253,dequantize 后取到了第 5 个黑点,这就引起了误差。

地平线基于 PyTorch 开发的 horizon_plugin_pytorch 量化训练工具,同时支持 Eager 和 fx 两种模式。

eager 模式的使用方式建议参考用户手册 -4.2 量化感知训练章节(4.2.2。 快速上手中有完整的快速上手示例,各使用阶段注意事项建议参考 4.2.3。 使用指南)。fx 模式的相关 API 介绍请参考用户手册 -4.2.3.4.2。 主要接口参数说明章节

第二部分:量化训练的流程介绍:以 mmdet 的 yolov3 为例

QAT 流程介绍

准备好浮点模型,加载训好的浮点权重

    model = build_detector(
        cfg.model,
        train_cfg=cfg.get('train_cfg'),
        test_cfg=cfg.get('test_cfg'))
    model.init_weights()# 加载config里的 init_cfg

设置 BPU 架构

set_march(March.BAYES)

算子融合(eager 模式需要,fx 可省略)

    # qat: run fuse_module to fuse conv+bn/relu/add op
    model.backbone.fuse_modules()
    model.neck.fuse_modules()
    model.bbox_head.fuse_modules()

设置量化配置

  • 整个 model 使用默认的 qconfig
  • 模型的输出,配置高精度输出
  • det 模型 head 输出的 loss 损失函数的 qconfig 设置为 None
    # qat: set qconfig for float model
    model.qconfig = get_default_qat_qconfig()
    # qat: set default_qat_out_qconfig for last conv
    for m in model.bbox_head.convs_pred:
        m.qconfig = get_default_qat_out_qconfig()
    # qat: set None for loss qconfig, loss should be quantized
    model.bbox_head.loss_cls.qconfig = None
    model.bbox_head.loss_conf.qconfig = None
    model.bbox_head.loss_xy.qconfig = None
    model.bbox_head.loss_wh.qconfig = None

将浮点模型转换为 qat 模型(示例使用 eager 模式)

    qat_model = prepare_qat(model)
    qat_model.to(torch.device("cuda:1"))

开始 qat 训练

  1. 可以复用浮点的 train_detector,替换 model 即可
train_detector(
        qat_model,
        datasets,
        cfg,
        distributed=distributed,
        validate=(not args.no_validate),
        timestamp=timestamp,
        meta=meta)

qat 模型转定点(需要 load 训练好的 qat 模型权重)

quantized_model = convert(qat_model.eval())

deploy_model 和 example_input 准备

    deploy_model = DeployModel(
        quantized_model.backbone, quantized_model.neck,
        quantized_model.bbox_head).to(torch.device("cuda:1"))
    example_input = torch.randn(size=(24, 3, 320, 320), device=torch.device("cuda:1"))

Trace 模型构建静态 graph,进行编译

  • eval()使 bn、dropout 等处于正确的状态
  • 编译只能在 cpu 上做
  • check_model 用于检查算子是否能全部跑在 bpu 上,建议提前检查
    traced_model = torch.jit.trace(deploy_model.eval(), example_input)
    traced_model.to(torch.device("cpu"))
    example_input.to(torch.device("cpu"))
    check_model(traced_model, example_input, advice=1)
    compile_model(traced_model, [example_input], opt=0, hbm="model.hbm")

如果 qat 精度不达标,如何插入 calibration?

1. 准备好浮点模型,加载训好的浮点权重
2. 设置BPU架构
3. 算子融合(eager模式需要,fx可省略)
4. 设置model的量化配置
-----------------calib_model-------------------
calib_model = prepare_qat(float_model)
calib_model.eval() # 使bn、dropout等处于正确的状态
set_fake_quantize(calib_model, FakeQuantState.CALIBRATION) # 不进行伪量化操作,仅观测算子输入输出统计量,更新scale
#校准训练(可复用浮点的train_detector,替换model即可)
train_detector(
        calib_model,
        datasets,
        cfg,
        distributed=distributed,
        validate=(not args.no_validate),
        timestamp=timestamp,
        meta=meta)
 #校准精度验证
calib_model.eval()
set_fake_quantize(calib_model, FakeQuantState.VALIDATION)
val(calib_model,val_dataloader,device) 
-----------此时calib_model里的scale已经更新了-------------------------
qat_model = prepare_qat(float_model)
-----------qat_model加载calib训练好的模型权重,开始qat训练-----------------------------------------------
train_detector(
        qat_model,
        datasets,
        cfg,
        distributed=distributed,
        validate=(not args.no_validate),
        timestamp=timestamp,
        meta=meta)

伪量化节点(fake quantize)的三种状态:

  • CALIBRATION 模式:即不进行伪量化操作,仅观测算子输入输出统计量,更新 scale
  • QAT 模式:观测统计量并进行伪量化操作。
  • VALIDATION 模式:不会观测统计量,仅进行伪量化操作。

以下常见误操作会导致一些异常现象:

  1. calibration 之前模型设置为 train()的状态,且未使用set_fake_quantize,等于是在跑 QAT 训练;
  2. calibration 之前模型设置为 eval()的状态,且未使用set_fake_quantize,会导致 scale 一直处于初始状态,全为 1,calib 不起作用。
  3. calibration 之前模型设置为 eval()的状态,且正确使用了set_fake_quantize,但是在这之后又设置了一遍 model.eval(),这将导致 fake_quant 未处于训练状态,scale 一直处于初始状态,全为 1;

对 mobilenet_v2 模型做 qat 训练的设置

量化节点设置

关键代码:

from horizon_plugin_pytorch.quantization import QuantStub

self.quant = QuantStub(scale=1/128) # 一般 pyramid 输入的 Quant 层,需要手动设置 scale=1/128def fuse_modules(self):

x = self.quant(x)
# Copyright (c) OpenMMLab. All rights reserved.
import warnings

import torch.nn as nn
from mmcv.cnn import ConvModule
from mmcv.runner import BaseModule
from torch.nn.modules.batchnorm import _BatchNorm
from horizon_plugin_pytorch.quantization import QuantStub

from ..builder import BACKBONES
from ..utils import InvertedResidual, make_divisible
import torch

@BACKBONES.register_module()
class MobileNetV2(BaseModule):
    arch_settings = [[1, 16, 1, 1], [6, 24, 2, 2], [6, 32, 3, 2],
                     [6, 64, 4, 2], [6, 96, 3, 1], [6, 160, 3, 2],
                     [6, 320, 1, 1]]

    def __init__(self,
                 widen_factor=1.,
                 out_indices=(1, 2, 4, 7),
                 frozen_stages=-1,
                 conv_cfg=None,
                 norm_cfg=dict(type='BN'),
                 act_cfg=dict(type='ReLU6'),
                 norm_eval=False,
                 with_cp=False,
                 pretrained=None,
                 init_cfg=None):
        super(MobileNetV2, self).__init__(init_cfg)
        # qat: model start with Quantization node
        # and set scale=1/128
        self.quant = QuantStub(scale=1/128) # 一般pyramid输入的Quant层,需要手动设置scale=1/128
        self.pretrained = pretrained
        assert not (init_cfg and pretrained), \
            'init_cfg and pretrained cannot be specified at the same time'
        if isinstance(pretrained, str):
            warnings.warn('DeprecationWarning: pretrained is deprecated, '
                          'please use "init_cfg" instead')
            self.init_cfg = dict(type='Pretrained', checkpoint=pretrained)
        elif pretrained is None:
            if init_cfg is None:
                self.init_cfg = [
                    dict(type='Kaiming', layer='Conv2d'),
                    dict(
                        type='Constant',
                        val=1,
                        layer=['_BatchNorm', 'GroupNorm'])
                ]
        else:
            raise TypeError('pretrained must be a str or None')

        self.widen_factor = widen_factor
        self.out_indices = out_indices
        if not set(out_indices).issubset(set(range(0, 8))):
            raise ValueError('out_indices must be a subset of range'
                             f'(0, 8). But received {out_indices}')

        if frozen_stages not in range(-1, 8):
            raise ValueError('frozen_stages must be in range(-1, 8). '
                             f'But received {frozen_stages}')
        self.out_indices = out_indices
        self.frozen_stages = frozen_stages
        self.conv_cfg = conv_cfg
        self.norm_cfg = norm_cfg
        self.act_cfg = act_cfg
        self.norm_eval = norm_eval
        self.with_cp = with_cp

        self.in_channels = make_divisible(32 * widen_factor, 8)

        self.conv1 = ConvModule(
            in_channels=3,
            out_channels=self.in_channels,
            kernel_size=3,
            stride=2,
            padding=1,
            conv_cfg=self.conv_cfg,
            norm_cfg=self.norm_cfg,
            act_cfg=self.act_cfg)

        self.layers = []

        for i, layer_cfg in enumerate(self.arch_settings):
            expand_ratio, channel, num_blocks, stride = layer_cfg
            out_channels = make_divisible(channel * widen_factor, 8)
            inverted_res_layer = self.make_layer(
                out_channels=out_channels,
                num_blocks=num_blocks,
                stride=stride,
                expand_ratio=expand_ratio)
            layer_name = f'layer{i + 1}'
            self.add_module(layer_name, inverted_res_layer)
            self.layers.append(layer_name)

        if widen_factor > 1.0:
            self.out_channel = int(1280 * widen_factor)
        else:
            self.out_channel = 1280

        layer = ConvModule(
            in_channels=self.in_channels,
            out_channels=self.out_channel,
            kernel_size=1,
            stride=1,
            padding=0,
            conv_cfg=self.conv_cfg,
            norm_cfg=self.norm_cfg,
            act_cfg=self.act_cfg)
        self.add_module('conv2', layer)
        self.layers.append('conv2')

    def make_layer(self, out_channels, num_blocks, stride, expand_ratio):
        """Stack InvertedResidual blocks to build a layer for MobileNetV2.

        Args:
            out_channels (int): out_channels of block.
            num_blocks (int): number of blocks.
            stride (int): stride of the first block. Default: 1
            expand_ratio (int): Expand the number of channels of the
                hidden layer in InvertedResidual by this ratio. Default: 6.
        """
        layers = []
        for i in range(num_blocks):
            if i >= 1:
                stride = 1
            layers.append(
                InvertedResidual(
                    self.in_channels,
                    out_channels,
                    mid_channels=int(round(self.in_channels * expand_ratio)),
                    stride=stride,
                    with_expand_conv=expand_ratio != 1,
                    conv_cfg=self.conv_cfg,
                    norm_cfg=self.norm_cfg,
                    act_cfg=self.act_cfg,
                    with_cp=self.with_cp))
            self.in_channels = out_channels

        return nn.Sequential(*layers)

    def _freeze_stages(self):
        if self.frozen_stages >= 0:
            for param in self.conv1.parameters():
                param.requires_grad = False
        for i in range(1, self.frozen_stages + 1):
            layer = getattr(self, f'layer{i}')
            layer.eval()
            for param in layer.parameters():
                param.requires_grad = False
    
    # qat: do fuse model
    def fuse_modules(self):
        self.conv1.fuse_modules()
        for layer_name in self.layers:
            layer = getattr(self, layer_name)
            if hasattr(layer, "fuse_modules"):
                layer.fuse_modules()
            elif isinstance(layer, nn.Sequential):
                for m in layer:
                    if hasattr(m, "fuse_modules"):
                        m.fuse_modules()

    def forward(self, x):
        """Forward function."""
        # qat: qat model start with QuantStub
        x = self.quant(x)
        x = self.conv1(x)
        outs = []
        for i, layer_name in enumerate(self.layers):
            layer = getattr(self, layer_name)
            x = layer(x)
            if i in self.out_indices:
                outs.append(x)
        return tuple(outs)

    def train(self, mode=True):
        """Convert the model into training mode while keep normalization layer
        frozen."""
        super(MobileNetV2, self).train(mode)
        self._freeze_stages()
        if mode and self.norm_eval:
            for m in self.modules():
                # trick: eval have effect on BatchNorm only
                if isinstance(m, _BatchNorm):
                    m.eval()

算子融合

[7.5.5. 算子融合 — Horizon Open Explorer](https://developer.horizon.ai/api/v1/fileData/horizon_j5_open_explorer_cn_doc/plugin/source/advanced_content/op_fusion.html?highlight=算子融合 算子 融合#)

举个例子:mmcv/cnn/bricks/conv_module.py

class ConvModule(nn.Module):
...
# qat: fuse conv + bn/relu
    def fuse_modules(self):
        fuse_list = None
        if self.with_norm:
            if self.with_activation:
                fuse_list = ["conv", self.norm_name, "activate"] # conv+bn+relu
            else:
                fuse_list = ["conv", self.norm_name] # conv+bn
        else:
            if self.with_activation:
                fuse_list = ["conv", "activate"] # conv+relu
        if fuse_list is not None:
            torch.quantization.fuse_modules(
                self,
                fuse_list,
                inplace=True,
                fuser_func=quantization.fuse_known_modules,
            )

eager 方案麻烦的是,基本每个模块都要手动去设置算子融合

反量化节点设置

mmdetection-master/mmdet/models/dense_heads/yolo_head.py

关键代码:

self.dequant = nn.ModuleList() # 不止1个反量化节点,用list包起来

self.dequant.append(DeQuantStub())

def fuse_modules(self):

pred_map = self.dequant[i](self.convs_pred[i](x))
class YOLOV3Head(BaseDenseHead, BBoxTestMixin):

    def __init__(self,
                 num_classes,
                 in_channels,
                 out_channels=(1024, 512, 256),
                 anchor_generator=dict(
                     type='YOLOAnchorGenerator',
                     base_sizes=[[(116, 90), (156, 198), (373, 326)],
                                 [(30, 61), (62, 45), (59, 119)],
                                 [(10, 13), (16, 30), (33, 23)]],
                     strides=[32, 16, 8]),
                 bbox_coder=dict(type='YOLOBBoxCoder'),
                 featmap_strides=[32, 16, 8],
                 one_hot_smoother=0.,
                 conv_cfg=None,
                 norm_cfg=dict(type='BN', requires_grad=True),
                 # qat
                 # act_cfg=dict(type='LeakyReLU', negative_slope=0.1),
                 act_cfg=dict(type='ReLU'),
                 loss_cls=dict(
                     type='CrossEntropyLoss',
                     use_sigmoid=True,
                     loss_weight=1.0),
                 loss_conf=dict(
                     type='CrossEntropyLoss',
                     use_sigmoid=True,
                     loss_weight=1.0),
                 loss_xy=dict(
                     type='CrossEntropyLoss',
                     use_sigmoid=True,
                     loss_weight=1.0),
                 loss_wh=dict(type='MSELoss', loss_weight=1.0),
                 train_cfg=None,
                 test_cfg=None,
                 init_cfg=dict(
                     type='Normal', std=0.01,
                     override=dict(name='convs_pred'))):
        super(YOLOV3Head, self).__init__(init_cfg)
        # Check params
        assert (len(in_channels) == len(out_channels) == len(featmap_strides))

        self.num_classes = num_classes
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.featmap_strides = featmap_strides
        self.train_cfg = train_cfg
        self.test_cfg = test_cfg
        if self.train_cfg:
            self.assigner = build_assigner(self.train_cfg.assigner)
            if hasattr(self.train_cfg, 'sampler'):
                sampler_cfg = self.train_cfg.sampler
            else:
                sampler_cfg = dict(type='PseudoSampler')
            self.sampler = build_sampler(sampler_cfg, context=self)
        self.fp16_enabled = False

        self.one_hot_smoother = one_hot_smoother

        self.conv_cfg = conv_cfg
        self.norm_cfg = norm_cfg
        self.act_cfg = act_cfg

        self.bbox_coder = build_bbox_coder(bbox_coder)

        self.prior_generator = build_prior_generator(anchor_generator)

        self.loss_cls = build_loss(loss_cls)
        self.loss_conf = build_loss(loss_conf)
        self.loss_xy = build_loss(loss_xy)
        self.loss_wh = build_loss(loss_wh)

        self.num_base_priors = self.prior_generator.num_base_priors[0]
        assert len(
            self.prior_generator.num_base_priors) == len(featmap_strides)
        self._init_layers()

    def _init_layers(self):
        self.convs_bridge = nn.ModuleList()
        self.convs_pred = nn.ModuleList()
        self.dequant = nn.ModuleList() # 不止1个反量化节点,用list包起来
        for i in range(self.num_levels):
            conv_bridge = ConvModule(
                self.in_channels[i],
                self.out_channels[i],
                3,
                padding=1,
                conv_cfg=self.conv_cfg,
                norm_cfg=self.norm_cfg,
                act_cfg=self.act_cfg)
            conv_pred = nn.Conv2d(self.out_channels[i],
                                  self.num_base_priors * self.num_attrib, 1)

            self.convs_bridge.append(conv_bridge)
            self.convs_pred.append(conv_pred)
            self.dequant.append(DeQuantStub())

    def fuse_modules(self):
        for m in self.convs_bridge:
            m.fuse_modules()

    def forward(self, feats):
        """Forward features from the upstream network.

        Args:
            feats (tuple[Tensor]): Features from the upstream network, each is
                a 4D-tensor.

        Returns:
            tuple[Tensor]: A tuple of multi-level predication map, each is a
                4D-tensor of shape (batch_size, 5+num_classes, height, width).
        """

        assert len(feats) == self.num_levels
        pred_maps = []
        for i in range(self.num_levels):
            x = feats[i]
            x = self.convs_bridge[i](x)
            pred_map = self.dequant[i](self.convs_pred[i](x))
            pred_maps.append(pred_map)

        return tuple(pred_maps),

第三部分:常用的精度调优 debug 工具介绍

工具:集成接口量化配置检查模型可视化相似度对比统计量分步量化异构模型部署 device 检查

第四部分:模型精度调优分享

模型精度调优时常遇到的问题:

  1. calib 模型的精度和 float 对齐,quantized 模型的精度损失较大

正常情况下,calib/qat 模型的精度和 quantized 模型的精度损失很小(1%), 如果偏差过大,可能是 calib/qat 的流程不对。

原因:calib 模型伪量化节点的状态不正确,导致 calib 阶段,测试的是 float 模型的精度,而 quantized 阶段,测试的是 calib 模型的精度,所以精度损失本质上还是量化精度的损失。

如何避免:

  • 正确设置 calib 训练和评测时的伪量化节点状态。
  • 让客户在 calib 的基础上,做 qat, 评测 qat 模型的精度。(客户的数据量大,qat 时间太长,一直没有选择 qat,导致这个问题被暴露出来了)

如何设置正确的 calib 伪量化节点的状态?(fx 和 eager 都是一样的)

http://model.aidi.hobot.cc/api/docs/horizon_plugin_pytorch/latest/html/user_guide/calibration.html

#加载浮点模型权重
    model.load_state_dict(torch.load("output/toy_experiments/model_moderate_best_soft_float_131892.pth"))
    set_march(March.BAYES)
 #校准配置
    calib_model = prepare_qat_fx(
        model,
        {"":default_calib_8bit_fake_quant_qconfig,
        "module_name":
            ...
        }).to(device)
    calib_model.to(device)
    #校准需要全程开启eval()状态
    calib_model.eval()
    #校准的训练阶段,设置伪量化节点模式为 CALIBRATION
    set_fake_quantize(calib_model, FakeQuantState.CALIBRATION)
    train(cfg, calib_model, device, distributed)
    #校准的评测阶段,设置伪量化节点的模式为 VALIDATION
    set_fake_quantize(calib_model, FakeQuantState.VALIDATION)
    #加载校准的模型权重
    calib_model.load_state_dict(torch.load("output/toy_experiments/model_moderate_best_soft_calib_118633.pth"))
    #测试校准的精度
    run_test(cfg, calib_model, vis=args.vis, eval_score_iou=args.eval_score_iou, eval_all_depths=args.eval_all_depths) # 11.8650

注意:16 行的 train 在评测时,也要设置 FakeQuantState.VALIDATION,不然 scale 不生效,评测的指标也不对

常见问题:

  1. 数据校准之前模型设置为 train()的状态,且未使用set_fake_quantize,等于 caib 阶段是在跑 QAT 训练;
  2. 校准的评测阶段,未设置伪量化节点的模式为 VALIDATION, 实际评测的是 float 模型;

总结 2: 如果做 calib,一定要仔细检查伪量化节点状态和模型状态是否正确,避免不符合预期的结果

2.当量化精度损失超过大,如何调优?

  1. 使用 model_profiler() 这个集成接口,生成压缩包。
  2. 检查是否配置高精度输出、是否存在未融合的算子、是否共享 op、是否算子分布过大 int8 兜不住?
  • 注意:使用 debug 集成接口时,要保证浮点模型训练到位,并传入真实数据

3.多任务模型的精度调优建议

  1. qat 调优策略和常规模型一样,ptq+qat
  2. 如果只有一个 head 精度有损失,可以固定其他部分,单独使用这个 head 的数据做 calib

4.calib 和 qat 流程的正确衔接

calib:

    #加载浮点模型权重
    model.load_state_dict(torch.load("output/toy_experiments/model_moderate_best_soft_float_131892.pth"))
    set_march(March.BAYES)
    #校准配置
    calib_model = prepare_qat_fx(
        model,
        {"":default_calib_8bit_fake_quant_qconfig,
        "module_name":
           ...
        }).to(device)
    calib_model.to(device)
    #校准需要全程开启eval()状态
    calib_model.eval()
    #校准的训练阶段,设置伪量化节点模式为 CALIBRATION
    set_fake_quantize(calib_model, FakeQuantState.CALIBRATION)
    train(cfg, calib_model, device, distributed)
    #校准的评测阶段,设置伪量化节点的模式为 VALIDATION
    set_fake_quantize(calib_model, FakeQuantState.VALIDATION)
    #加载校准的模型权重
    calib_model.load_state_dict(torch.load("output/toy_experiments/model_moderate_best_soft_calib_118633.pth"))
    #测试校准的精度
    run_test(cfg, calib_model, vis=args.vis, eval_score_iou=args.eval_score_iou, eval_all_depths=args.eval_all_depths) # 11.8650

qat:

set_march(March.BAYES)
    qat_model = prepare_qat_fx(
        model,
        {"":default_qat_8bit_fake_quant_qconfig,
        "module_name":
            '''
        }).to(device)
    qat_model.to(device)
    #加载校准模型权重
    qat_model.load_state_dict(torch.load("output/toy_experiments/model_moderate_best_soft_calib_118633.pth"))
    #训练阶段,保证模型处于model.train()状态,这样伪量化节点也处于qat模式
    train(cfg, qat_model, device, distributed)

5.检查 conv 高精度输出

方式 1:查看 qconfig_info.txt,重点关注 DeQuantStub 附近的 conv 是不是 float32 输出

qconfig_info.txt

方式 2:打印 qat_model 的最后一层,查看该层是否有 (activation_post_process): FakeQuantize

高精度的 conv:

  (1): ConvModule2d(
    (0): Conv2d(
      64, 3, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)
      (weight_fake_quant): FakeQuantize(
        fake_quant_enabled=tensor([1], dtype=torch.uint8), observer_enabled=tensor([1], dtype=torch.uint8),            quant_min=-128, quant_max=127, dtype=qint8, qscheme=torch.per_channel_symmetric, ch_axis=0,         scale=tensor([1., 1., 1.]), zero_point=tensor([0, 0, 0])
        (activation_post_process): MovingAveragePerChannelMinMaxObserver(min_val=tensor([]), max_val=tensor([]))
      )
    )
  )
)

int8 的 conv

  (0): ConvModule2d(
    (0): ConvReLU2d(
      64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)
      (weight_fake_quant): FakeQuantize(
        fake_quant_enabled=tensor([1], dtype=torch.uint8), observer_enabled=tensor([1], dtype=torch.uint8),            quant_min=-128, quant_max=127, dtype=qint8, qscheme=torch.per_channel_symmetric, ch_axis=0,         scale=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
                1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
                1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
                1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]), zero_point=tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
        (activation_post_process): MovingAveragePerChannelMinMaxObserver(min_val=tensor([]), max_val=tensor([]))
      )
      (activation_post_process): FakeQuantize(
        fake_quant_enabled=tensor([1], dtype=torch.uint8), observer_enabled=tensor([1], dtype=torch.uint8),            quant_min=-128, quant_max=127, dtype=qint8, qscheme=torch.per_tensor_symmetric, ch_axis=-1,         scale=tensor([1.]), zero_point=tensor([0])
        (activation_post_process): MovingAverageMinMaxObserver(min_val=tensor([]), max_val=tensor([]))
      )
    )

6.检查共享 op

打开 qconfig_info.txt,后面标有(n)的就是共享的

特殊情况:layernorm 在 QAT 阶段是多个小量化算子拼接而成,module 的重复调用,也会产生大量 op 共享的问题

解决办法: 将 layernorm 替换为 batchnorm,测试了 float 精度,没有下降。

7.检查未融合的算子

打开 qconfig_info.txt,全局搜 BatchNorm2d 和 ReLU,如果前面有 conv,那就是没做算子融合

可以融合的算子:

  • conv+bn
  • conv+relu
  • conv+add
  • conv+bn+relu
  • conv+bn+add
  • conv+bn+relu+add

8.检查数据分布特别大的算子

打开 float 模型的统计量分布,一般是 model0_statistic.txt

有两个表,第一个表是按模型结构排列的;第二个表是按数据分布范围排列的

拖到第二个表,看前几行是那些 op

可以看到很多 conv 的分布很异常,使用的是 int8 量化

解决办法:

  1. 检查这些 conv 后面是否有 bn,添加 bn 后,数据能收敛一些
  2. 如果结构上已经加了 bn,数据分布还大,可以配置 int16 量化
  • int16 调这两个接口,default_qat_16bit_fake_quant_qconfig 和 default_calib_16bit_fake_quant_qconfig
  • 中间算子的写法和高精度输出类似 model.xx.qconfig = default_qat_16bit_fake_quant_qconfig ()

标签:qat,conv,cfg,self,调优,calib,量化,model,精度
From: https://www.cnblogs.com/horizondeveloper/p/18548005

相关文章

  • python的decimal默认精度为28
    python的decimal的精度可以修改的计算pi使用莱布尼茨级数计算圆周率fromdecimalimportDecimal,getcontext#设置全局精度为100位getcontext().prec=100#使用莱布尼茨级数计算圆周率pi=Decimal(0)forkinrange(1000000):pi+=(Decimal(-1)**k)/(......
  • 高精度 加减乘, 除法只提供商
    //bigNum.cpp:此文件包含"main"函数。程序执行将在此处开始并结束。//#include<iostream>#include<cstring>usingnamespacestd;#defineMAXDIGITS100110#definePLUS1#defineMINUS-1typedefstruct{chardigits[MAXDIGITS];......
  • 模型轻量化中的模型剪枝(Pruning)方法——结构化剪枝
    模型轻量化中的模型剪枝(Pruning)方法——结构化剪枝详解目录简介结构化剪枝的基本概念结构化剪枝的数学基础结构化剪枝的步骤结构化剪枝的方法5.1通道剪枝5.2神经元剪枝5.3层剪枝结构化剪枝的优缺点结构化剪枝的应用实例代码示例8.1代码说明总结简介随着深度学......
  • 模型轻量化中的模型剪枝(Pruning)方法——动态剪枝详解
    模型轻量化中的模型剪枝(Pruning)方法——动态剪枝详解目录简介动态剪枝的基本概念动态剪枝的数学基础动态剪枝的步骤动态剪枝的方法5.1基于门控机制的动态剪枝5.2基于稀疏化的动态剪枝5.3基于强化学习的动态剪枝动态剪枝的优缺点动态剪枝的应用实例代码示例8.1代码......
  • 向量动态量化
    本文介绍向量检索服务DashVector动态量化功能和作用。背景介绍量化(Quantization)是向量检索技术中一种常用的优化方法,通过一定程度的精度(召回率)损失,来换取性能的大幅度提升,以及内存占用(索引文件大小)大幅度降低。向量检索服务DashVector支持向量的动态量化,用户仅需在新建Collecti......
  • 11.13机器学习_KNN和模型选择调优
    7特征降维实际数据中,有时候特征很多,会增加计算量,降维就是去掉一些特征,或者转化多个特征为少量个特征特征降维其目的:是减少数据集的维度,同时尽可能保留数据的重要信息。特征降维的好处:减少计算成本:在高维空间中处理数据可能非常耗时且计算密集。降维可以简化模型,......
  • Flink调优之前,必须先看懂的TaskManager内存模型
    Flink调优之前,必须先看懂的TaskManager内存模型TaskManager内存模型Flink的程序运行在内存中。不管是我们在学习C语言、Java语言的时候,我们都很想知道程序到底是如何管理内存的。Flink程序也一样,当我们写完Flink程序,我们需要为Flink程序分配运行的资源,那针对什么样的数据量,需要......
  • 【轻量化】YOLOv8 更换骨干网络之 MobileNetv4 | 模块化加法!非 timm 包!
    之前咱们在这个文章中讲了timm包的加法,不少同学反馈要模块化的加法,那么这篇就讲解下模块化的加法,值得注意的是,这样改加载不了mobilebnetv4官方开源的权重了~论文地址:https://arxiv.org/pdf/2404.10518代码地址:https://github.com/tensorflow/models/blob/master/offic......
  • 阵列式自动雷达波测流监测系统:以毫米级精度,掌控每一滴水流
    在水资源管理日益重要的今天,精确监测水流的动态变化成为关键任务。阵列式自动雷达波测流监测系统的出现,为我们带来了全新的解决方案,以毫米级精度掌控每一滴水流,为水利事业的发展注入强大动力。一、阵列式自动雷达波测流监测系统概述阵列式自动雷达波测流监测系统是一种先进的......
  • ES调优建议
    查询模块保证ES节点有充足的内存ES默认分配节点一半的内存给JVM(最多会分配30GB),剩余的内存用于向量索引和pagecache,节点内存不足会导致频繁触发向量索引的驱逐和加载,导致查询延迟较高。因此需要确保数据节点有足够的内存,使向量索引常驻内存。可以通过以下方式获取节点向量数据......