模型量化初始知识

原文网址:知乎原文-量化基础知识

背景

一些基本知识
PyTorch对量化的支持目前有如下三种方式

Post Training Dynamic Quantization,模型训练完毕后的动态量化;
Post Training Static Quantization,模型训练完毕后的静态量化;
QAT(Quantization Aware Training),模型训练中开启量化。

Post Training Dynamic Quantization

训练完成后,再量化模型的权重参数,动态量化(下面有解释)
在这里插入图片描述
Pytorch有着成熟的api:

torch.quantization.quantize_dynamic(model, qconfig_spec=None, dtype=torch.qint8, mapping=None, inplace=False)

torch.quantization.quantize_dynamic可以将一个float model转化为dynamic quantized model(fp16或者qint8)。一般只转换以下op:

Linear
LSTM
LSTMCell
RNNCell
GRUCell
原因在于动态量化只讲权重参数进行量化,而这些layer的一般参数量比较大,因此边际效应高。
下面来解释参数

  1. model(模型)
  2. qconfig_spec关键

用我自己的话来说:
QConfig又封装了两个observer(activation激活函数,weight权重)
observer就是根据(min_val,max_val,qmin,qmax)来计算量化参数scale和zero_point.(qmin和qmax是算法提前就确定好的,min_val和max_val是输入数据中得到的,因此叫observer。)

QConfig的子类QConfigDynamic,只封装了weight的observer;动态量化的qconfig使用的就是这个QConfigDynamic的实例 (因此只对weight进行量化),
qconfig_spec指定一组qconfig和op,op和qconfig互相对应

若qconfig_spec为None即为默认行为,即:

 qconfig_spec = {
                nn.Linear : default_dynamic_qconfig,  
                nn.LSTM : default_dynamic_qconfig,
                nn.GRU : default_dynamic_qconfig,
                nn.LSTMCell : default_dynamic_qconfig,
                nn.RNNCell : default_dynamic_qconfig,
                nn.GRUCell : default_dynamic_qconfig,
            }

而default_dynamic_qconfig也就是QConfigDynamic的一个实例,如下:

default_dynamic_qconfig = QConfigDynamic(activation=default_dynamic_quant_observer, weight=default_weight_observer)

default_dynamic_quant_observer = PlaceholderObserver.with_args(dtype=torch.float, compute_dtype=torch.quint8)

default_weight_observer = MinMaxObserver.with_args(dtype=torch.qint8, qscheme=torch.per_tensor_symmetric)

其中activation的PlaceholderObserver就是个占位符,什么也不做
其中weight的MinMaxObserver就是记录输入tensor中的最大值和最小值,用来计算scale和zp

举例:

class CivilNet(nn.Module):
    def __init__(self):
        super(CivilNet, self).__init__()
        gemfieldin = 1
        gemfieldout = 1
        self.conv = nn.Conv2d(gemfieldin, gemfieldout, kernel_size=1, stride=1, padding=0, groups=1, bias=False)
        self.fc = nn.Linear(3, 2,bias=False)
        self.relu = nn.ReLU(inplace=False)

    def forward(self, x):
        x = self.conv(x)
        x = self.fc(x)
        x = self.relu(x)
        return x
        
#原始网络
CivilNet(
  (conv): Conv2d(1, 1, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (fc): Linear(in_features=3, out_features=2, bias=False)
  (relu): ReLU()
)

#quantize_dynamic 后
CivilNet(
  (conv): Conv2d(1, 1, kernel_size=(1, 1), stride=(1, 1), bias=False)
  (fc): DynamicQuantizedLinear(in_features=3, out_features=2, dtype=torch.qint8, qscheme=torch.per_tensor_affine)
  (relu): ReLU()
)

可以看到,除了Linear,其它op都没有变动。而Linear被转换成了DynamicQuantizedLinear,DynamicQuantizedLinear就是torch.nn.quantized.dynamic.modules.linear.Linear类。没错,quantize_dynamic API的本质就是检索模型中op的type,如果某个op的type属于字典DEFAULT_DYNAMIC_QUANT_MODULE_MAPPINGS的key,那么,这个op将被替换为key对应的value:op和key对应value关系如下:

# Default map for swapping dynamic modules
DEFAULT_DYNAMIC_QUANT_MODULE_MAPPINGS = {
    nn.GRUCell: nnqd.GRUCell,
    nn.Linear: nnqd.Linear, ## nnqd.Linear就是DynamicQuantizedLinear就是torch.nn.quantized.dynamic.modules.linear.Linear。
    nn.LSTM: nnqd.LSTM,
    nn.LSTMCell: nnqd.LSTMCell,
    nn.RNNCell: nnqd.RNNCell,
}

从key换到value,也就是从Linear换成DynamicQuantizedLinear,那么这个DQLinear该如何实例化?通过以下方式:

new_mod = mapping[type(mod)].from_float(mod)

from_float做的事就是:
1、使用MinMaxObserver计算模型中op权重参数中tensor的最大值最小值(这个例子中只有Linear op),缩小量化时原始值的取值范围,提高量化的精度;
2、通过上述步骤中得到四元组中的min_val和max_val,再结合算法确定的qmin, qmax计算出scale和zp,参考前文“Tensor的量化”小节,计算得到量化后的weight,这个量化过程有torch.quantize_per_tensor和torch.quantize_per_channel两种,默认是前者(因为qchema默认是torch.per_tensor_affine);
3、实例化nnqd.Linear,然后使用qlinear.set_weight_bias将量化后的weight和原始的bias设置到新的layer上。其中最后一步还涉及到weight和bias的打包,在源代码中是这样的:

#ifdef USE_FBGEMM
    if (ctx.qEngine() == at::QEngine::FBGEMM) {
      return PackedLinearWeight::prepack(std::move(weight), std::move(bias));
    }
#endif

#ifdef USE_PYTORCH_QNNPACK
    if (ctx.qEngine() == at::QEngine::QNNPACK) {
      return PackedLinearWeightsQnnp::prepack(std::move(weight), std::move(bias));
    }
#endif
    TORCH_CHECK(false,"Didn't find engine for operation quantized::linear_prepack ",toString(ctx.qEngine()));

量化完后的模型在推理的时候有什么不一样的呢?在原始网络中,从输入到最终输出是这么计算的:

#input
torch.Tensor([[[[-1,-2,-3],[1,2,3]]]])

#经过卷积后(权重为torch.Tensor([[[[-0.7867]]]]))
torch.Tensor([[[[ 0.7867,  1.5734,  2.3601],[-0.7867, -1.5734, -2.3601]]]])

#经过fc后(权重为torch.Tensor([[ 0.4097, -0.2896, -0.4931], [-0.3738, -0.5541,  0.3243]]) )
torch.Tensor([[[[-1.2972, -0.4004], [1.2972,  0.4004]]]])

#经过relu后
torch.Tensor([[[[0.0000, 0.0000],[1.2972, 0.4004]]]])

### 在动态量化模型中,上述过程就变成了

#input
torch.Tensor([[[[-1,-2,-3],[1,2,3]]]])

#经过卷积后(权重为torch.Tensor([[[[-0.7867]]]]))
torch.Tensor([[[[ 0.7867,  1.5734,  2.3601],[-0.7867, -1.5734, -2.3601]]]])

#经过fc后(权重为torch.Tensor([[ 0.4085, -0.2912, -0.4911],[-0.3737, -0.5563,  0.3259]], dtype=torch.qint8,scale=0.0043458822183310986,zero_point=0) )
torch.Tensor([[[[-1.3038, -0.3847], [1.2856,  0.3969]]]])

#经过relu后
torch.Tensor([[[[0.0000, 0.0000], [1.2856, 0.3969]]]])

这里关键就是Linear op,scale和zero_point如何得来?
由其使用的observer计算得到的,具体来说就是默认的MinMaxObserver,
在各种observer中,计算权重的scale和zp离不开这四个变量:min_val,max_val代表op权重数据/input tensor数据分布的最小值和最大值,qmin和qmax代表量化后的取值范围的最小、最大值。
举例:
Linear op的权重为torch.Tensor([[ 0.4097, -0.2896, -0.4931], [-0.3738, -0.5541, 0.3243]]),因此其min_val和max_val分别为-0.5541 和 0.4097,在这个上下文中,max_val将进一步取这俩绝对值的最大值。由此我们就可以得到:

  • scale = max_val / (float(qmax - qmin) / 2) = 0.5541 / ((127 + 128) /
    2) = 0.004345882…
  • zp = 0
    从上面我们可以得知,权重部分的量化是“静态”的,是提前就转换完毕的,而之所以叫做“动态”量化,就在于前向推理的时候动态的把input的float tensor转换为量化tensor。

在forward的时候,nnqd.Linear会调用torch.ops.quantized.linear_dynamic函数,输入正是上面(pack好后的)量化后的权重和float的bias,而torch.ops.quantized.linear_dynamic函数最终会被PyTorch分发到C++中的apply_dynamic_impl函数,在这里,或者使用FBGEMM的实现(x86-64设备),或者使用QNNPACK的实现(ARM设备上):

#ifdef USE_FBGEMM
at::Tensor PackedLinearWeight::apply_dynamic_impl(at::Tensor input, bool reduce_range) {
  ...
  fbgemm::xxxx
  ...
}
#endif // USE_FBGEMM

#ifdef USE_PYTORCH_QNNPACK
at::Tensor PackedLinearWeightsQnnp::apply_dynamic_impl(at::Tensor input) {
  ...
  qnnpack::qnnpackLinearDynamic(xxxx)
  ...
}
#endif // USE_PYTORCH_QNNPACK

apply_dynamic_impl函数中,使用如下逻辑进行量化:

Tensor q_input = at::quantize_per_tensor(input_contig, q_params.scale, q_params.zero_point, c10::kQUInt8);

动态量化的本质就藏身于此:基于运行时对数据范围的观察,来动态确定对输入进行量化时的scale值。这就确保 input tensor的scale因子能够基于输入数据进行优化,从而获得颗粒度更细的信息。

而模型的参数则是提前就转换为了INT8的格式(在使用quantize_dynamic API的时候)。这样,当输入也被量化后,网络中的运算就使用向量化的INT8指令来完成。 而在当前layer输出的时候,我们还需要把结果再重新转换为float32——re-quantization的scale值是依据input、 weight和output scale来确定的,定义如下:
requant_scale = input_scale_fp32 * weight_scale_fp32 / output_scale_fp32

总结下来,我们可以这么说:Post Training Dynamic Quantization,简称为Dynamic Quantization,也就是动态量化,或者叫作Weight-only的量化,是提前把模型中某些op的参数量化为INT8,然后在运行的时候动态的把输入量化为INT8,然后在当前op输出的时候再把结果requantization回到float32类型。动态量化默认只适用于Linear以及RNN的变种。

Post Training Static Quantization

先介绍statci和dynamic的区别和相同点
相同点:都是把网络的权重从float32转换为int8
不同点:需要把(训练集)或者(训练集类似的数据)喂给模型(没有反向传播),然后通过每个op的分布特点来计算activation的量化参数(scale和zp)——Calibrate(定标)。也就是静态量化包含activation,而动态量化只有weight的量化。静态量化需要activation的原因是静态量化的前向推理始终是INT计算,activation需要确保一个op的输入符合下一个op的输入。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/972596.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在项目中调用本地Deepseek(接入本地Deepseek)

前言 之前发表的文章已经讲了如何本地部署Deepseek模型,并且如何给Deepseek模型投喂数据、搭建本地知识库,但大部分人不知道怎么应用,让自己的项目接入AI模型。 文末有彩蛋哦!!! 要接入本地部署的deepsee…

Redis7——基础篇(五)

前言:此篇文章系本人学习过程中记录下来的笔记,里面难免会有不少欠缺的地方,诚心期待大家多多给予指教。 基础篇: Redis(一)Redis(二)Redis(三)Redis&#x…

【爬虫基础】第一部分 网络通讯 P1/3

前言 1.知识点碎片化:每个网站实现的技术相似但是有区别,要求我们根据不同的网站使用不同的应对手段。主要是常用的一些网站爬取技术。 2.学习难度:入门比web简单,但后期难度要比web难,在于爬虫工程师与网站开发及运维…

揭秘区块链隐私黑科技:零知识证明如何改变未来

文章目录 1. 引言:什么是零知识证明?2. 零知识证明的核心概念与三大属性2.1 完备性(Completeness)2.2 可靠性(Soundness)2.3 零知识性(Zero-Knowledge) 3. 零知识证明的工作原理4. 零…

R软件用潜在类别混合模型LCM分析老年人抑郁数据轨迹多变量建模研究

全文链接: tecdat.cn/?p40283 潜在类别混合模型假设总体具有异质性,由 GG 个潜在类别组成。在多变量的情况下,潜在类别是根据 KK 个纵向结果来定义的,从而形成 GG 个组,每个组的特征由 KK 个轨迹均值轮廓集表示&#…

【Rust中级教程】1.11. 生命周期(进阶) Pt.1:回顾、借用检查器、泛型生命周期

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 这篇文章在Rust初级教程的基础上对生命周期这一概念进行了补充,建议先看【Rust自…

【DeepSeek服务器部署全攻略】Linux服务器部署DeepSeek R1模型、实现API调用、搭建Web页面以及专属知识库

DeepSeek R1模型的Linux服务器搭建、API访问及Web页面搭建 1,引言2,安装Ollama工具3,下载DeepSeek R1 模型4,DeepSeek命令行对话5,DeepSeek API接口远程调用6,DeepSeek结合Web-ui实现图形化界面远程访问6.1…

【免费软件分享】Typor1.9.5-x64-CN免费版

到处找pojie软件的朋友,这里给大家提供一个版本,之前也是废了老大的劲才找到,这里分享给大家,希望帮助到需要的朋友! Typor1.9.5-x64-CN: 我用夸克网盘分享了「Typor1.9.5-x64-CN.7z」,点击链接…

Python天梯赛刷题-五分题(上)

蓝桥杯题刷的好累,感觉零帧起手、以题带学真的会很吃力,打算重新刷一点天梯的题目巩固一下,我本人在算法非常不精通的情况下,自认为天梯的L1的题是会相对容易一些的,可能有一些没有脑子光靠力气的“硬推”hhhh。 从头…

Python编程之数据分组

有哪些方式可以进行数据分组利用Pandas库进行分组使用itertools库的groupby分组操作构建Python字典方式实现(小规模数据,不适用数量特别大的情况,不需要依赖其它python库)利用NumPy的groupby函数分组操作利用Python的Dask库提供的函数进行分组下面看一个如何去实现坐标数据…

激光雷达YDLIDAR X2 SDK安装

激光雷达YDLIDAR X2 SDK安装 陈拓 2024/12/15-2024/12/19 1. 简介 YDLIDAR X2官方网址https://ydlidar.cn/index.html‌YDLIDAR X2 YDLIDAR X2是一款高性能的激光雷达传感器,具有以下主要特点和规格参数‌: ‌测距频率‌:3000Hz ‌扫描频…

大数据组件(四)快速入门实时数据湖存储系统Apache Paimon(2)

大数据组件(四)快速入门实时数据湖存储系统Apache Paimon(2) 我们上次已经了解了Paimon的下载及安装,并且了解了主键表的引擎以及changelog-producer的含义 大数据组件(四)快速入门实时数据湖存储系统Apache Paimon(1) 今天,我们继续快速了解下最近比…

⭐ Unity 横向滑动列表 首尾相连 轮转图

效果如下: 场景挂载: 代码部分: using DG.Tweening; using System; using System.Collections; using System.Collections.Generic; using System.Drawing.Printing; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine…

大白话实战Sentinel

Sentinel是SpringCloudAlibaba提供的用来做服务保护的框架,而服务保护的常见手段就是限流和熔断降级。在大型分布式系统里面,由于微服务众多,所以服务之间的稳定性需要做特别关注,Sentinel的核心包就提供了从多个维度去保护服务稳定的策略,而且这些保护策略都可以连接上Se…

【C语言】C语言 哈夫曼编码传输(源码+数据文件)【独一无二】

👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉专__注👈:专注主流机器人、人工智能等相关领域的开发、测试技术。 C语言 哈夫曼编码传输(源码数据文件&am…

用命令模式设计一个JSBridge用于JavaScript与Android交互通信

用命令模式设计一个JSBridge用于JavaScript与Android交互通信 在开发APP的过程中,通常会遇到Android需要与H5页面互相传递数据的情况,而Android与H5交互的容器就是WebView。 因此要想设计一个高可用的 J S B r i d g e JSBridge JSBridge,不…

3月营销日历:开启春日盛宴,绽放生活魅力

关键营销节点∶惊蛰、女生节、妇女节、 植树节、315消费者权益日、春分 营销关键词 养生、女生魅力、感恩女性、环保、品质 01.重点关注品类 春季服饰:如轻薄外套、春装等,适合惊蛰后的市场需求; 美妆护肤:妇女节期间&#xf…

GPT-SoVITS更新V3 win整合包

GPT-SoVITS 是由社区开发者联合打造的开源语音生成框架,其创新性地融合了GPT语言模型与SoVITS(Singing Voice Inference and Timbre Synthesis)语音合成技术,实现了仅需5秒语音样本即可生成高保真目标音色的突破。该项目凭借其开箱…

AI芯片:科技变革的核心驱动力

近年来,人工智能(AI)的飞速发展对众多行业产生了深远影响,芯片领域也不例外。AI在芯片设计、制造及应用等方面带来了革新性的改变,成为推动芯片行业发展的关键力量。 AI助力芯片设计效率飞升 传统芯片设计极为复杂&am…

【phpstudy】关于实现两个不同版本的mysql并存。

1.首先是先安装好两个版本的mysql mysql5.7用默认的就行 2.更改mysql8.0的配置,如图 3.找到mysql8.0的路径,看着个里面就可以知道了 4.进入后,可以把data里面的数据情况,就是把data文件夹里的东西删除(我是先备份好了一…