本文为对目前线性量化优点、原理、方法和实战内容的总结,主要参考 神经网络量化简介 并加以自己的理解和总结,适合初学者阅读和自身复习用。
模型量化是指将神经网络的浮点算法转换为定点。量化有一些相似的术语,低精度(Low precision)可能是常见的。
FP16(半精度浮点)或者 INT8(8位的定点整数),但是目前低精度往往就指代 INT8。FP32(32位浮点,单精度)。FP32 和 FP16 的权重数值格式。 FP16 减少了一半的内存大小,但有些参数或操作符必须采用 FP32 格式才能保持准确度。模型量化有以下好处:
int8 量化可减少 75% 的模型大小,int8 量化模型大小一般为 32 位浮点模型大小的 1/4:
32 位浮点型可以访问四次 int8 整型,整型运算比浮点型运算更快;CPU 用 int8 计算的速度更快8 位的,低功耗运行浮点运算速度慢,需要进行 8bit 量化。总结:模型量化主要意义就是加快模型端侧的推理速度,并降低设备功耗和减少存储空间,
工业界一般只使用 INT8 量化模型,如 NCNN、TNN 等移动端模型推理框架都支持模型的 INT8 量化和量化模型的推理功能。
通常,可以根据 FP32 和 INT8 的转换机制对量化模型推理方案进行分类。一些框架简单地引入了 Quantize 和 Dequantize 层,当从卷积或全链接层送入或取出时,它将 FP32 转换为 INT8 或相反。在这种情况下,如下图的上半部分所示,模型本身和输入/输出采用 FP32 格式。深度学习推理框架加载模型时,重写网络以插入 Quantize 和 Dequantize 层,并将权重转换为 INT8 格式。
注意,之所以要插入反量化层(
Dequantize),是因为量化技术的早期,只有卷积算子支持量化,但实际网络中还包含其他算子,而其他算子又只支持FP32计算,因此需要把 INT8 转换成 FP32。但随着技术的迭代,后期估计会逐步改善乃至消除Dequantize操作,达成全网络的量化运行,而不是部分算子量化运行。

图四:混合 FP32/INT8 和纯 INT8 推理。红色为 FP32,绿色为 INT8 或量化。
其他一些框架将网络整体转换为 INT8 格式,因此在推理期间没有格式转换,如上图的下半部分。该方法要求算子(Operator)都支持量化,因为运算符之间的数据流是 INT8。对于尚未支持的那些,它可能会回落到 Quantize/Dequantize 方案。
在实践中将浮点模型转为量化模型的方法有以下三种方法:
data free:不使用校准集,传统的方法直接将浮点参数转化成量化数,使用上非常简单,但是一般会带来很大的精度损失,但是高通最新的论文 DFQ 不使用校准集也得到了很高的精度。calibration:基于校准集方案,通过输入少量真实数据进行统计分析。很多芯片厂商都提供这样的功能,如 tensorRT、高通、海思、地平线、寒武纪finetune:基于训练 finetune 的方案,将量化误差在训练时仿真建模,调整权重使其更适合量化。好处是能带来更大的精度提升,缺点是要修改模型训练代码,开发周期较长。TensorFlow 框架按照量化阶段的不同,其模型量化功能分为以下两种:
PTQ(训练后量化、离线量化);QAT(训练时量化,伪量化,在线量化)。PTQ Post Training Quantization 是训练后量化,也叫做离线量化,根据量化零点 \(x_{zero\_point}\) 是否为 0,训练后量化分为对称量化和非对称量化;根据数据通道顺序 NHWC(TensorFlow) 这一维度区分,训练后量化又分为逐层量化和逐通道量化。目前 nvidia 的 TensorRT 框架中使用了逐层量化的方法,每一层采用同一个阈值来进行量化。逐通道量化就是对每一层每个通道都有各自的阈值,对精度可以有一个很好的提升。
目前已知的加快推理速度概率较大的量化方法主要有:
ristretto 几种。在 nvdia gpu,x86、arm 和 部分 AI 芯片平台上,均支持 8bit 的计算,效率提升从 1 倍到 16 倍不等,其中 tensor core 甚至支持 4bit计算,这也是非常有潜力的方向。线性量化引入的额外量化/反量化计算都是标准的向量操作,因此也可以使用 SIMD 进行加速,带来的额外计算耗时不大。nvdia gpu,x86、arm 三大平台上没有实现对数量化的加速库,但是目前已知海思 351X 系列芯片上使用了对数量化。与非线性量化不同,线性量化采用均匀分布的聚类中心,原始浮点数据和量化后的定点数据存在一个简单的线性变换关系,因为卷积、全连接等网络层本身只是简单的线性计算,因此线性量化中可以直接用量化后的数据进行直接计算。
模型量化过程可以分为两部分:将模型从 FP32 转换为 INT8,以及使用 INT8 进行推理。本节说明这两部分背后的算术原理。如果不了解基础算术原理,在考虑量化细节时通常会感到困惑。
定点和浮点都是数值的表示(representation),它们区别在于,将整数(integer)部分和小数(fractional)部分分开的点,点在哪里。定点保留特定位数整数和小数,而浮点保留特定位数的有效数字(significand)和指数(exponent)。
绝大多数现代的计算机系统采纳了浮点数表示方式,这种表达方式利用科学计数法来表达实数。即用一个尾数(Mantissa,尾数有时也称为有效数字,它实际上是有效数字的非正式说法),一个基数(Base),一个指数(Exponent)以及一个表示正负的符号来表达实数。具体组成如下:
sign 符号位 \(s\),占 1 bit,用来表示正负号;exponent 指数偏移值 \(k\),占 8 bits,用来表示其是 2 的多少次幂;fraction 分数值(有效数字) \(M\),占 23 bits,用来表示该浮点数的数值大小。基于上述表示,浮点数的值可以用以下公式计算:
值得注意是,上述公式隐藏了一些细节,如指数偏移值 \(k\) 使用的时候需要加上一个固定的偏移值。
比如 123.45 用十进制科学计数法可以表示为 \(1.2345\times 10^2\),其中 1.2345 为尾数,10 为基数,2 为指数。
单精度浮点类型 float 占用 32bit,所以也称作 FP32;双精度浮点类型 double 占用 64bit。

图五:定点和浮点的格式和示例。
32-bit 浮点数和 8-bit 定点数的表示范围如下表所示:
| 数据类型 | 最小值 | 最大值 |
|---|---|---|
FP32 |
-3.4e38 | 3.4e38 |
int8 |
-128 | 128 |
uint8 |
0 | 255 |
神经网络的推理由浮点运算构成。FP32 和 INT8 的值域是 \([(2−2^{23})×2^{127},(2^{23}−2)\times 2^{127}]\) 和 \([−128,127]\),而取值数量大约分别为 \(2^{32}\) 和 \(2^8\) 。FP32 取值范围非常广,因此,将网络从 FP32 转换为 INT8 并不像数据类型转换截断那样简单。但是,一般神经网络权重的值分布范围很窄,非常接近零。图八给出了 MobileNetV1 中十层(拥有最多值的层)的权重分布。

图八:十层 MobileNetV1 的权重分布。
根据偏移量 \(Z\) 是否为 0,可以将浮点数的线性量化分为两类-对称量化和非对称量化。
当浮点值域落在 \((-1,1)\) 之间,权重浮点数据的量化运算可使用下式的方法将 FP32 映射到 INT8,这是对称量化。其中 \(x_{float}\) 表示 FP32 权重, \(x_{quantized}\) 表示量化的 INT8 权重,\(x_{scale}\) 是缩放因子(映射因子、量化尺度(范围)/ float32 的缩放因子)。
对称量化的浮点值和 8 位定点值的映射关系如下图,从图中可以看出,对称量化就是将一个 tensor 中的 \([-max(|\mathrm{x}|),max(|\mathrm{x}|)]\) 内的 FP32 值分别映射到 8 bit 数据的 [-128, 127] 的范围内,中间值按照线性关系进行映射,称这种映射关系是对称量化。可以看出,对称量化的浮点值和量化值范围都是相对于零对称的。

因为对称量化的缩放方法可能会将 FP32 零映射到 INT8 零,但我们不希望这种情况出现,于是出现了数字信号处理中的均一量化,即非对称量化。数学表达式如下所示,其中 \(x_{zero\_point}\) 表示量化零点(量化偏移)。
大多数情况下量化是选用无符号整数,即 INT8 的值域就为 \([0,255]\) ,这种情况,显然要用非对称量化。非对称量化的浮点值和 8 位定点值的映射关系如下图:

总的来说,权重量化浮点值可以分为两个步骤:
注意,当浮点运算结果不等于整数时,需要额外的舍入步骤。例如将 FP32 值域 [−1,1] 映射到 INT8 值域 [0,255],有 \(x_{scale}=\frac{2}{255}\),而\(x_{zero\_point}= 255−\frac{255}{2}≈127\)。
注意,量化过程中存在误差是不可避免的,就像数字信号处理中量化一样。非对称算法一般能够较好地处理数据分布不均匀的情况。
量化的一个重要议题是用量化算术表示非量化算术,即量化神经网络中的 INT8 计算是描述常规神经网络的 FP32 计算,对应的就是反量化过程,也就是如何将 INT8 的定点数据反量化成 FP32 的浮点数据。
下面的等式 5-10 是反量化乘法 \(x_{float} \cdot y_{float}\) 的过程。对于给定神经网络,输入 \(x\)、权重 \(y\) 和输出 \(z\) 的缩放因子肯定是已知的,因此等式 14 的 \(Multiplier_{x,y,z} = \frac{x_{scale}y_{scale}}{z_{scale}}\) 也是已知的,在反量化过程之前可预先计算。因此,除了 \(Multiplier_{x,y,z}\) 和 \((x_{quantized} - x_{zero\_point})\cdot (y_{quantized} - y_{zero\_point})\) 之间的乘法外,等式 16 中的运算都是整数运算。
等式:反量化算术过程。
对于等式 10 可以应用的大多数情况,\(quantized\) 和 \(zero\_point\) 变量 (x,y) 都是 INT8 类型,\(scale\) 是 FP32。实际上两个 INT8 之间的算术运算会累加到 INT16 或 INT32,这时 INT8 的值域可能无法保存运算结果。例如,对于 \(x_{quantized}=20\)、\(x_{zero\_point} = 50\) 的情况,有 \((x_{quantized} − x_{zero_point}) = −30\) 超出 INT8 值范围 \([0,255]\)。
数据类型转换可能将 \(Multiplier_{x,y,z} \cdot (x_{quantized} - x_{zero\_point}) \cdot (y_{quantized} - y_{zero\_point})\) 转换为 INT32 或 INT16,和 \(z_{zero\_point}\) 一起确保计算结果几乎全部落入 INT8 值域 [0,255] 中。
对于以上情况,在工程中,比如对于卷积算子的计算,sum(x*y) 的结果需要用 INT32 保存,同时,b 值一般也是 INT32 格式的,之后再 requantize (重新量化)成 INT8。
量化浮点部分中描述权重浮点量化方法是非常简单的。在深度学习框架的早期开发中,这种简单的方法能快速跑通 INT8 推理功能,然而采用这种方法的网络的预测准确度通常会出现明显的下降。
虽然 FP32 权重的值域很窄,在这值域中数值点数量却很大。以上文的缩放为例,\([−1,1]\) 值域中 \(2^{31}\)(是的,基本上是总得可表示数值的一半)个 FP32 值被映射到 \(256\) 个 INT8 值。
SYMMETRIC) 对称量化和 (NON-SYMMETRIC) 非对称量化;MINMAX、KL 散度、ADMM;per-channel per-layer;采用普通量化方法时,靠近零的浮点值在量化时没有精确地用定点值表示。因此,与原始网络相比,量化网络一般会有明显的精度损失。对于线性(均匀)量化,这个问题是不可避免的。
同时值映射的精度是受由 \(x_{float}^{min}\) 和 \(x_{float}^{max}\) 得到的 \(x_{scale}\) 显著影响的。并且,如图十所示,权重中邻近 \(x_{float}^{min}\) 和 \(x_{float}^{max}\) 附近的值通常是可忽略的,其实就等同于映射关系中浮点值的 min 和 max 值是可以通过算法选择的。

图十将浮点量化为定点时调整最小值-最大值。
上图展示了可以调整 min/max 来选择一个值域,使得值域的值更准确地量化,而范围外的值则直接映射到定点的 min/max。例如,当从原始值范围 \([−1,1]\) 中选定\(x_{min}^{float} = −0.9\) 和 \(x_{max}^{float} = 0.8\) ,\([−0.9,0.8]\) 中的值将能更准确地映射到 \([0,255]\) 中,而 \([−1,−0.9]\) 和 \([0.8,1]\) 中的值分别映射为 \(0\) 和 \(255\)。
通过前文对量化算数的理解和上面两种量化算法的介绍我们不难发现,为了计算 scale 和 zero_point,我们需要知道 FP32 weight/activation 的实际动态范围。对于推理过程来说,weight 是一个常量张量,动态范围是固定的,activation 的动态范围是变化的,它的实际动态范围必须经过采样获取(一般把这个过程称为数据校准(calibration))。
将浮点量化转为定点时调整最小值/最大值(值域调整),也就是浮点数动态范围的选择,动态范围的选取直接决定了量化数据的分布情况,处于动态范围之外的数据将被映射成量化数据的边界点,即值域的选择直接决定了量化的误差。
目前各大深度学习框架和三大平台的推理框架使用最多的有最大最小值(MinMax)、滑动平均最大最小值(MovingAverageMinMax)和 KL 距离(Kullback-Leibler divergence)三种方法,去确定浮点数的动态范围。如果量化过程中的每一个 FP32 数值都在这个实际动态范围内,我们一般称这种为不饱和状态;反之如果出现某些 FP32 数值不在这个实际动态范围之内我们称之为饱和状态。
MinMax 是使用最简单也是较为常用的一种采样方法。基本思想是直接从 FP32 张量中选取最大值和最小值来确定实际的动态范围,如下公式所示。
\(x_{min} = \left\{\begin{matrix}min(X) & if\ x_{min} = None \\ min(x_{min}, min(X)) & otherwise\end{matrix}\right.\)
\(x_{max} = \left\{\begin{matrix}max(X) & if\ x_{max} = None \\ max(x_{max}, max(X)) & otherwise\end{matrix}\right.\)
对 weights 而言,这种采样方法是不饱和的,但是对于 activation 而言,如果采样数据中出现离群点,则可能明显扩大实际的动态范围,比如实际计算时 99% 的数据都均匀分布在 [-100, 100] 之间,但是在采样时有一个离群点的数值为 10000,这时候采样获得的动态范围就变成 [-100, 10000]。
与 MinMax 算法直接替换不同,MovingAverageMinMax 会采用一个超参数 c (Pytorch 默认值为0.01)逐步更新动态范围。
\(x_{min} = \left\{\begin{matrix}min(X) & if x_{min} = None \\ (1-c)x_{min}+c \; min(X) & otherwise\end{matrix}\right.\)
\(x_{max} = \left\{\begin{matrix}max(X) & if x_{max} = None \\ (1-c)x_{max}+c \; max(X) & otherwise\end{matrix}\right.\)
这种方法获得的动态范围一般要小于实际的动态范围。对于 weights 而言,由于不存在采样的迭代,因此 MovingAverageMinMax 与 MinMax 的效果是一样的。
理解 KL 散度方法之前,我们先看下 TensorRT 关于值域范围阈值选择的一张图:

这张图展示的是不同网络结构的不同 layer 的激活值分布统计图,横坐标是激活值,纵坐标是统计数量的归一化表示,而不是绝对数值统计;图中有卷积层和池化层,它们之间分布很不相同,因此合理的量化方法应该是适用于不同的激活值分布,并且减小信息损失,因为从 FP32 到 INT8 其实也是一种信息再编码的过程。
简单的将一个 tensor 中的 -|max| 和 |max| FP32 value 映射为 -127 和 127 ,中间值按照线性关系进行映射,这种映射关系为不饱和的(No saturation),即对称的。对于这种简单的量化浮点方法,试验结果显示会导致比较大的精度损失。
通过上图可以分析出,线性量化中使用简单的量化浮点方法导致精度损失较大的原因是:
一般认为量化之后的数据分布与量化前的数据分布越相似,量化对原始数据信息的损失也就越小,即量化算法精度越高。KL 距离(也叫 KL 散度)一般被用来度量两个分布之间的相似性。这里的数据分布都是离散形式的,其离散数据的 KL 散度公式如下:
式中 P 和 Q 分布表示量化前 FP32 的数据分布和量化后的 INT8 数据分布。注意公式要求 P、Q 两个统计直方图长度一样(也就是 bins 的数量一样)。
TensorRT 使用 KL 散度算法进行量化校准的过程:首先在校准集上运行 FP32 推理,然后对于网络每一层执行以下步骤:
T,并确定 Scale。以上使用校准集的模型量化过程通常只需几分钟时间。
KL 散度来选择 饱和量化中的 阈值 |T|;1,量化是一种已经获得了工业界认可和使用的方法,在训练 (Training) 中使用 FP32 精度,在推理 (Inference) 期间使用 INT8 精度的这套量化体系已经被包括 TensorFlow,TensorRT,PyTorch,MxNet 等众多深度学习框架和启用,地平线机器人、海思、安霸等众多 AI 芯片厂商也在深度学习工具链中提供了各自版本的模型量化功能。
2,量化是一个大部分硬件平台都会支持的,因此比较常用;知识蒸馏有利于获得小模型,还可以进一步提升量化模型的精度,所以这个技巧也会使用,尤其是在有已经训好比较强的大模型的基础上会非常有用。剪枝用的会相对较少,因为可以被网络结构搜索覆盖。
我正在使用这个:4.times{|i|assert_not_equal("content#{i+2}".constantize,object.first_content)}我之前声明过局部变量content1content2content3content4content5我得到的错误NameError:wrongconstantnamecontent2这个错误是什么意思?我很确定我想要content2=\ 最佳答案 你必须用一个大字母来调用ruby常量:Content2而不是content2。Aconstantnamestart
我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b
网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功
ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear
是否可以在不实际下载文件的情况下检查文件是否存在?我有这么大的(~40mb)文件,例如:http://mirrors.sohu.com/mysql/MySQL-6.0/MySQL-6.0.11-0.glibc23.src.rpm这与ruby不严格相关,但如果发件人可以设置内容长度就好了。RestClient.get"http://mirrors.sohu.com/mysql/MySQL-6.0/MySQL-6.0.11-0.glibc23.src.rpm",headers:{"Content-Length"=>100} 最佳答案
我在这方面尝试了很多URL,在我遇到这个特定的之前,它们似乎都很好:require'rubygems'require'nokogiri'require'open-uri'doc=Nokogiri::HTML(open("http://www.moxyst.com/fashion/men-clothing/underwear.html"))putsdoc这是结果:/Users/macbookair/.rvm/rubies/ruby-2.0.0-p481/lib/ruby/2.0.0/open-uri.rb:353:in`open_http':404NotFound(OpenURI::HT
深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG
(本文是网络的宏观的概念铺垫)目录计算机网络背景网络发展认识"协议"网络协议初识协议分层OSI七层模型TCP/IP五层(或四层)模型报头以太网碰撞路由器IP地址和MAC地址IP地址与MAC地址总结IP地址MAC地址计算机网络背景网络发展 是最开始先有的计算机,计算机后来因为多项技术的水平升高,逐渐的计算机变的小型化、高效化。后来因为计算机其本身的计算能力比较的快速:独立模式:计算机之间相互独立。 如:有三个人,每个人做的不同的事物,但是是需要协作的完成。 而这三个人所做的事是需要进行协作的,然而刚开始因为每一台计算机之间都是互相独立的。所以前面的人处理完了就需要将数据