深度学习之神经网络量化理解

news2024/11/25 16:48:25

深度学习系列文章目录


文章目录

  • 深度学习系列文章目录
  • 前言
  • 一、什么是量化
    • 量化现状
    • Google
    • TensorRT
    • TVM
    • 量化基本知识
    • 基于线性量化的对称量化和非对称量化
  • 总结


前言

刚开始接触神经网络,对量化是2年前那会,用NCNN和TVM在树莓派上部署一个简单的SSD网络。那个时候使用的量化脚本是参考于TensorRT和NCNN的PTQ量化(训练后量化)模式,使用交叉熵的方式对模型进行量化,最终在树莓派3B+上部署一个简单的分类模型(识别剪刀石头布静态手势)。

这是那会的一篇文章,略显稚嫩哈哈:

一步一步解读神经网络编译器TVM(二)——利用TVM完成C++端的部署
转眼间过了这么久啦,神经网络量化应用已经完全实现大面积落地了、相比之前成熟多了!

我工作的时候虽然也简单接触过量化,但感觉还远远不够,趁着最近项目需要,重新再学习一下,也打算把重新学习的路线写成一篇系列文,分享给大家。

本篇系列文的主要内容计划从头开始梳理一遍量化的基础知识以及代码实践。因为老潘对TensorRT比较熟悉,会主要以TensorRT的量化方式进行描述以及讲解。不过TensorRT由于是闭源工具,内部的实现看不到,咱们也不能两眼一抹黑。所以也打算参考Pytorch、NCNN、TVM、TFLITE的量化op的现象方式学习和实践一下。

当然这只是学习计划,之后可能也会变动。对于量化我也是学习者,既然要用到这个技术,必须要先理解其内部原理。而且接触了挺长时间量化,感觉这里面学问还是不少。好记性不如烂笔头,写点东西记录下,也希望这系列文章在能够帮助大家的同时,抛砖引玉,一起讨论、共同进步。

当然在学习途中,也认识了很多在量化领域经验丰富的大佬(田子宸、JermmyXu等等),嗯,这样前进路上也就不孤单了。

OK,废话不多说开始吧。

一、什么是量化

我们都知道,训练好的模型的权重一般来说都是FP32也就是单精度浮点型,在深度学习训练和推理的过程中,最常用的精度就是FP32。当然也会有FP64、FP16、BF16、TF32等更多的精度:
在这里插入图片描述
FP32 是单精度浮点数,用8bit 表示指数,23bit 表示小数;
FP16半精度浮点数,用5bit 表示指数,10bit 表示小数;
BF16是对FP32单精度浮点数截断数据,即用8bit 表示指数,7bit 表示小数。
TF32 是一种截短的 Float32 数据格式,将 FP32 中 23 个尾数位截短为 10 bits,而指数位仍为 8 bits,总长度为 19 (=1 + 8 + 10) bits。

对于浮点数来说,指数位表示该精度可达的动态范围,而尾数位表示精度。之前老潘的一篇文章中提到,FP16的普遍精度是~5.96e−8 (6.10e−5) … 65504,而我们模型中的FP32权重有部分数值是1e-10级别。这样从FP32->FP16会导致部分精度丢失,从而模型的精度也会下降一些。
在这里插入图片描述
其实从FP32->FP16也是一种量化,只不过因为FP32->FP16几乎是无损的(CUDA中使用__float2half直接进行转换),不需要calibrator去校正、更不需要retrain。

而且FP16的精度下降对于大部分任务影响不是很大,甚至有些任务会提升。NVIDIA对于FP16有专门的Tensor Cores可以进行矩阵运算,相比FP32来说吞吐量提升一倍。
在这里插入图片描述
实际点来说,量化就是将我们训练好的模型,不论是权重、还是计算op,都转换为低精度去计算。因为FP16的量化很简单,所以实际中我们谈论的量化更多的是INT8的量化,当然也有3-bit、4-bit的量化,不过目前来说比较常见比较实用的,也就是INT8量化了,之后老潘的重点也是INT8量化。

那么经过INT8量化后的模型:

模型容量变小了,这个很好理解,FP32的权重变成INT8,大小直接缩了4倍
模型运行速度可以提升,实际卷积计算的op是INT8类型,在特定硬件下可以利用INT8的指令集去实现高吞吐,不论是GPU还是INTEL、ARM等平台都有INT8的指令集优化
对于某些设备,使用INT8的模型耗电量更少,对于嵌入式侧端设备来说提升是巨大的
所以说,随着我们模型越来越大,需求越来越高,模型的量化自然是少不了的一项技术。

如果你担心INT8量化对于精度的影响,我们可以看下NVIDIA量化研究的一些结论:
在这里插入图片描述
出自《INTEGER QUANTIZATION FOR DEEP LEARNING INFERENCE: PRINCIPLES AND EMPIRICAL EVALUATION》,文末有下载链接。

量化现状

量化技术已经广泛应用于实际生产环境了,也有很多大厂开源了其量化方法。不过比较遗憾的是目前这些方法比较琐碎,没有一套比较成熟比较完善的量化方案,使用起来稍微有点难度。不过我们仍可以从这些框架中学习到很多。

Google

谷歌是比较早进行量化尝试的大厂了,感兴趣的可以看下Google的白皮书Quantizing deep convolutional networks for efficient inference: A whitepaper以及Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference。

TensorFlow很早就支持了量化训练,而TFLite也很早就支持了后训练量化,感兴趣的可以看下TFLite的量化规范,目前TensorRT支持TensorFlow训练后量化的导出的模型。

TensorRT

TensorRT在2017年公布了自己的后训练量化方法,不过没有开源,NCNN按照这个思想实现了一个,也特别好用。不过目前TensorRT8也支持直接导入通过ONNX导出的QTA好的模型,使用上方便了不少,之后老潘会重点讲下。
在这里插入图片描述

TVM

TVM有自己的INT8量化操作,可以跑量化,我们也可以添加自己的算子。不过TVM目前只支持PTQ,可以通过交叉熵或者percentile的方式进行校准。不过如果动手能力强的话,应该可以拿自己计算出来的scale值传入TVM去跑,应该也有人这样做过了。

比较有参考意义的一篇:

ViT-int8 on TVM:提速4.6倍,比TRT快1.5倍

当然还有很多优秀的量化框架,想看详细的可以看这篇,后续如果涉及到具体知识点老潘也会再提到。

量化基本知识

进入主题前需要提两个概念,也就是量化的两个重要过程,一个是量化(Quantize),另一个是反量化(Dequantize):

量化就是将浮点型实数量化为整型数(FP32->INT8)
反量化就是将整型数转换为浮点型实数(INT8->FP32)

在这里插入图片描述
量化和反量化操作在最终的模型推理中都会用到,接下来我们就具体说下。

之后实数就代表我们的FP32浮点数,而整数就代表INT8整型数。

量化操作
比如有一个FP32的浮点型数字x = 5.234 x=5.234x=5.234,然后我们需要把这个数变为整型,也就是要量化它,怎么搞。我们可以把这个数字乘上一个量化系数s ss,比如s = 100 s=100s=100,那么量化后的值x q = x ∗ s = 5.234 ∗ 100 = 523.4 x_q = xs=5.234100=523.4x
q

=x∗s=5.234∗100=523.4,然后我们对这个数字进行四舍五入(也就是round操作)最终为

x q = r o u n d ( x ∗ s ) = r o u n d ( 5.234 ∗ 100 ) = 523 x_q = round(xs)=round(5.234100)=523
x
q

=round(x∗s)=round(5.234∗100)=523

这样就行了吗,523有点大啊,我们的整型INT8的范围是[-128,127],无符号INT8的范围也才[0-255],这个量化后的值有点放不下呀。

怎么办,当然是要截断了,假设我们的INT8范围是[ − 2 b − 1 , 2 b − 1 − 1 ] [−2^{b−1}, 2^{b−1} − 1][−2
b−1
,2
b−1
−1],因为我们使用的是INT8,所以这里的b = 8 b=8b=8,那么上述的式子又可以变为:

x q = c l i p ( r o u n d ( x ∗ s ) , − 2 b − 1 , 2 b − 1 − 1 ) = c l i p ( r o u n d ( 5.234 ∗ 100 ) , − 128 , 127 ) = 127 x_q = clip(round(xs),−2^{b−1}, 2^{b−1} − 1)=clip(round(5.234100),-128,127)=127
x
q

=clip(round(x∗s),−2
b−1
,2
b−1
−1)=clip(round(5.234∗100),−128,127)=127

这样就结束了么?

当然没有,刚才的这个数字x = 5.234 x=5.234x=5.234,被映射到了127,那么如果是x = 0 x=0x=0呢?貌似直接带入算出来也是0,但是这样做对么?

基于线性量化的对称量化和非对称量化

对不对的关键在于我们是否是采用对称量化,什么是对称量化呢?这里的对称指的是以0为中心进行量化(还有另一种说法,这里老潘先略过),然后0两边的动态范围都是一样的。
在这里插入图片描述
可以看上图,左边是非对称量化,右边是对称量化(也称为Affine quantization和Scale quantization)。可以观察到:

对称量化的实数0也对应着整数的0,而非对称量化的实数0不一定对应着整数0,而是z。
对称量化实数的范围是对称的([ − α , α ] [-\alpha,\alpha][−α,α]),而非对称量化的则不对称([ − β , α ] [-\beta,\alpha][−β,α])
对称量化整数的范围是对称的([-127,127]),而非对称量化的则不对称([-128,127])
所以上述的非对称量化过程可以简述为f ( x ) = s ⋅ x + z f (x) = s · x + zf(x)=s⋅x+z,其中z zz是zero-point,这个数字就代表实数0映射到整数是多少,而对称量化则是f ( x ) = s ⋅ x f (x) = s · xf(x)=s⋅x。

这样就明白了刚才的问题:如果是x = 0 x=0x=0呢?貌似直接带入算出来也是0,如果我们采用的是对称量化,那就没问题!

需要说明一点,不论是非对称还是对称量化,是基于线性量化(也可以称作均匀量化)的一种。线性量化将FP32映射到INT8数据类型,每个间隔是相等的,而不相等的就称为非线性量化。非线性量化因为对部署并不是很友好,虽然能够更好地捕捉到权重分布的密集点,但感觉用的并不多,这里也就先不多说了。

关于详细的非对称量化,对称量化对比可以参考这篇文章:

Affine Quantization vs Scale Quantization
对称量化
接下来的重点是对称量化,也就是TensorRT中使用的量化方式,这里的范围也就是[-127,127],因为只比[-128,127]少了一个范围,所以实际量化中并没有太大的影响。

话说回来,上文量化操作中,量化系数随便说了个s = 100 s=100s=100,这个当然是不对的,这个s ss需要根据我们的实际数据分布来计算。

如上式,α \alphaα代表当前输入数据分布中的实数最大值,因为是对称,因此实际范围是[ − α , α ] [-\alpha,\alpha][−α,α]。而b = 8 b=8b=8代表INT8量化,那么上述的量化公式就是之前提到的对称量化公式。

可以对比下非对称和对称的量化公式,对称量化因为z = 0 z=0z=0,所以公式简化了很多。

对于对称量化,假设当前根据权重分布,选取的α \alphaα为4,那么s = 127 / α = 127 / 4 = 31.75 s=127/{\alpha}=127/4=31.75s=127/α=127/4=31.75。

如下式子,在反量化的时候我们需要将反向操作一番,将量化后的结果乘以1 / s 1/s1/s重新变为浮点型。这里其实也就相当于乘以α / 127 \alpha/127α/127,因为有1 / s = 1 / ( 127 / α ) = α / 127 1/s=1/({127/{\alpha}})=\alpha/1271/s=1/(127/α)=α/127。

那么实际操作过程中,scale系数是怎么用呢?或者说s ss这个量化系数是怎么作用于所有的输入、所有的权重呢?

一般量化过程中,有pre-tensor和pre-channel两种方式,pre-tensor显而易见,就是对于同一块输入(比如某个卷积前的输入tensor)我们采用一个scale,该层所有的输入数据共享一个scale值;而pre-channel呢一般是作用于权重,比如一个卷积的权重维度是[64,3,3,3](输入通道为3输出通道为64,卷积核为3x3),pre-channel就是会产生64个scale值,分别作用于该卷积权重参数的64个通道。

为什么权重不能是pre-tensor呢?这个对精度的影响太大了,所以一般不用。输入就可以pre-tensor?当然可以,也经过测试了,对精度的影响不是很大,完全可以用。

那为什么权重必须是pre-channel呢?不能是每个权重值都有自己的scale么?呃,这个问题嘛,首先可以想到,这个计算量,应该挺大,其次嘛,让我们分析一下。

卷积操作量化
铺垫了这么多,那么接下来说下量化最核心的操作吧,量化过程中最核心的操作当然是卷积量化。

我们都知道卷积操作可以拆分为im2col+sgemm,而大部分的计算都在矩阵运算也就是sgemm中,我们量化的重点也就是这个操作。以前是FP32计算,而现在变成INT8去计算,这是怎么转换的呢?

接下来重点分析一下量化公式!注意!这个很重要!

首先,矩阵相乘可以表示为Y = X W Y = X WY=XW,X为输入W为权重,Y为输出。偏置bias一般可以去掉,对精度影响也不大,所以就先不考虑了。
在这里插入图片描述
注意看上图输入X的维度为[m,p]而W的维度为[p,n],因此i的范围为[0,m),k的范围为[0,p)。W和Y同理。这里的输入和权重都是FP32精度,也就是实数。

而对应的INT8精度的输入和权重为,q下标就代表quantize也就是量化:

接下来,我们把矩阵公式细粒度拆成一个一个计算,也就是行和列每个元素相乘然后求和:

首先是最左边,x i k x_{ik}x
ik

和w k j w_{kj}w
kj

分别代表浮点型的输入和权重,i ii代表第i ii行,k kk代表第k kk列,因此x i k x_{ik}x
ik

代表第i ii行,第k kk列的元素,w k i w_{ki}w
ki

同理。两者相乘求和就可以得到y i j y_{ij}y
ij

,可以看到这里求和的范围是p pp,k kk从1到p pp变化。

进一步,两个浮点型的运算可以被近似为INT8反量化后的运算,进一步等于量化后的运算:

可以看到上式每个元素都有自己的scale值,也就是s ss,而我们也必须把x和w的scale值提取到前面才能让x和w实现INT8类型的矩阵运算:

这里可以发现,如果想要把这两个scale元素,也就是s x , i s_{x,i}s
x,i

和s w , j s_{w,j}s
w,j

提出来,那么这个k kk必须干掉,这里可以暂停一下想下为什么?

当把k去除将s取出来之后,我们发现s x , i s_{x,i}s
x,i

和s w , j s_{w,j}s
w,j

分别代表输入的第i ii行的scale和权重的第j jj列的scale值,这样输入的每一行必须共享scale,而权重的每一列也必须共享scale!

在这里插入图片描述

那么pre-channel又是怎么来的呢?

还记得老潘之前说过的im2col+sgemm操作吗(如果不记得强烈建议去看看),其中的sgemm是这样的,需要注意,下图左边的kernel矩阵,每一行代表一个输出通道的kernel集合(这里因为输入图像是三通道的,因此kernel有三个,不同颜色代表一个kernel):

在这里插入图片描述
这就是pre-channel或者详细点就是per-output-channel也就是卷积输出通道,我们对每一个卷积权重的输出通道那一维进行量化,然后共享一个scale,这也就呼应了上述的公式!

后记
到此我们已经讲述了量化的基本概念以及卷积量化的实际操作是什么样的,当然想说的还有很多…就是现在实在写不动了,关于非对称量化的公式以及为什么非对称量化计算量比较大,就放到第二期再说吧。文中提到的一些资料,号内回复”量化“即可获取。

后续文章会继续说明其他量化的操作细节以及实际部署中的代码细节,涉及到TensorRT以及Pytorch和TVM,感兴趣的不妨持续关注老潘~

也欢迎大家一起讨论,如有错误也欢迎指正。

总结

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

知识点滴 - 世界化工企业百强

2022年7月25日,美国《化学与工程新闻》(C&EN)发布2022年全球化工企业50强名单。 2022-09-21日附近,国际石化市场信息服务商安迅思ICIS发布了最新世界化工企业100强排行榜(Top 100 Chemical Companies)&…

基于微信小程序的垃圾分类系统的研究与实现(附源码和教程)

1. 简介 本文介绍的事基于微信小程序的垃圾分类系统,主要实现的功能有登录、注册、垃圾分类查询、垃圾预约回收、垃圾分类功能。 2.系统设计与实现 本章节是论文的重点,基于上一章介绍的总体设计框架的搭建,详细对小程序的页面布局、流程设…

Mask2Former来了!用于通用图像分割的 Masked-attention Mask Transformer

原理https://blog.csdn.net/bikahuli/article/details/121991697 源码解析 论文地址:http://arxiv.org/abs/2112.01527 项目地址:https://bowenc0221.github.io/mask2former Mask2Former的整体架构由三个组件组成: 主干特征提取器&#xff…

GPT在医疗健康领域:应用、价值与展望

目录 发展背景 应用场景 价值分析 未来发展趋势 发展痛点 市场规模预测 结论 全文精要提炼: 作者:ChatgptMidjourneyFOTORXmind 随着科技的不断发展,人工智能(AI)在各行各业的应用越来越广泛,尤其…

unity NGUI使用方法

基本用法 很多基本模块比如按钮、slider等都能从Prefab中直接拖拽到场景中实现,但都需要有一个Collider(Prefab已经自带) 因为不仅是UI,所有带有Collider的游戏物体都能接收到OnClick, OnPress这样的事件——前提是需…

sed编辑器基础命令

shell脚本编程系列 学习sed编辑器 sed编辑器被称作流编辑器(stream editor),与普通的交互式文本编辑器不同,在交互式文本编辑器可以用键盘命令交互式插入、删除或替换文本数据。流编辑器则是根据事先设计好的一组规则编辑数据流。 sed编辑器…

人脸检测--传统方法

人脸检测与识别综述 人脸检测与识别是计算机视觉和生物识别领域中最受关注的研究内容。 如何从包含人脸内容的图像、视频等多媒体数据中,找到人脸,并对其身份进行判定,是人脸检测与识别中的主要问题。 应用场景 - 安全监控 - 访问控制 …

【Java校招面试】基础知识(一)——Java常用类库

目录 前言一、编程时常用的Java类库1. 异常捕获模块(try-catch-finally, Error, Exception)2. boolean / short / int / long / float / double / char / byte及其对应的引用类型 二、面试时常考的Java类库1. 一切类型的父类Object及其equals / hashCode / toString方法2. 常用…

anaconda安装pytorch的流程

1.查看本机支持的CUDA版本 cmd:nvidia-smi 2.安装本机支持的CUDA版本 CUDA安装教程 GPU, CUDA,cuDNN三者的关系总结 3.使用anaconda创建虚拟环境 3.在anaconda对应的虚拟环境中安装CUDA对应的pytorch版本 anaconda安装pytorch(anaconda3,Windows10&am…

idea使用 ( 二 ) 创建java项目

3.创建java项目 3.1.创建普通java项目 3.1.1.打开创建向导 接 2.3.1.创建新的项目 也可以 从菜单选择建立项目 会打开下面的选择界面 3.1.2.不使用模板 3.1.3.设置项目名 Project name : 项目名 Project location : 项目存放的位置 确认创建 3.1.4.关闭tips 将 Dont s…

二叉搜索树【Java】

文章目录 二叉搜索树的性质二叉搜索树的操作遍历查找插入删除 二叉搜索树又称为二叉排序树,是一种具有一定性质的特殊的二叉树; 二叉搜索树的性质 若它的左子树不为空,则左子树上结点的值均小于根节点的值; 若它的右子树不为空&a…

08 Kubernetes应用配置管理

课件 在 Kubernetes 中,secret 是一种用于存储敏感信息的对象。Kubernetes 支持以下三种类型的 secret: Opaque:这是默认的 secret 类型,可以用于存储任何类型的数据,包括字符串、二进制数据等。 Service Account&…

【P2】Jmeter 线程组的并行与串行

一、串行与并行规则 (1)、测试计划中的执行顺序遵循:setUp 线程组 -> 线程组 -> tearDown 线程组 (2)、如果将测试计划中的独立运行每个线程组勾选上,则多个线程组串行执行,否则并发执行…

吴恩达 Chatgpt prompt 工程--5.Transforming

探索如何将大型语言模型用于文本转换任务,如语言翻译、拼写和语法检查、音调调整和格式转换。 Setup import openai import osfrom dotenv import load_dotenv, find_dotenv _ load_dotenv(find_dotenv()) # read local .env fileopenai.api_key os.getenv(OPE…

2.6 浮点运算方法和浮点运算器

学习目标: 以下是一些具体的学习目标: 理解浮点数的基本概念和表示方法,包括符号位、指数和尾数。学习浮点数的运算规则和舍入规则,包括加、减、乘、除、开方等。了解浮点数的常见问题和误差,例如舍入误差、溢出、下…

Unity一般打包流程

Unity一般打包流程 通常打包流程主要是通过 Building setting来选择需要打包的场景后出包到指定文件夹位置,也可以采用 [MenuItem("MyMenu/Do Something")]中使用static函数来选择打包路径和打包方式——需要将该脚本放置在 Editor文件夹下 [MenuItem(&…

Vue3源码 第六篇-JavaScript AST transform函数

系列文章目录 Vue3源码 第一篇-总览 Vue3源码 第二篇-Reactive API Vue3源码 第三篇-Vue3是如何实现响应性 Vue3源码 第四篇-Vue3 setup Vue3源码 第五篇-Vue3 模版compile AST生成篇 文章目录 系列文章目录前言一、transform 转换二、traverseNode 遍历节点,trave…

B/S结构系统的会话机制(session)

B/S结构系统的会话机制(session) 文章目录 B/S结构系统的会话机制(session)每博一文案1. session 会话机制的概述2. 什么是 session 的会话3. session 的作用4. session 的实现原理解释5. 补充: Cookie禁用了,session还能找到吗 ?6. 总结一下…

PCA学习

前置知识 统计 假设数据集 X ∈ R n m \mathbf{X}\in\mathbb{R}^{n\times m} X∈Rnm,其中 n n n表示样本数量, m m m表示特征个数 均值 X ˉ 1 n e T X 1 n ∑ i 1 n X i \bar{\mathbf{X}} \frac{1}{n}\mathbf{e}^T\mathbf{X} \frac{1}{n} \sum_{i1}^{n}\mat…

[架构之路-178]-《软考-系统分析师》- 分区操作系统(Partition Operating System)概述

目录: 本文概述: 1.1 什么是分区操作系统 1.2 分区操作系统出现背景 1. 前后台系统(Foreground/Background System) 2. 实时操作系统(RTOS) 本文概述: 随着嵌入式系统日趋复杂化以及对安全性要求的不断提高,采用空间隔离、时…