当前位置: 首页 > news >正文

Vitis AI 进阶认知(Torch量化基础+映射+量化参数+对称性+每通道+PTQ+QAT+敏感性)

目录

1. 介绍

2. 基本概念

2.1 映射函数

2.2 量化参数

2.3 校准

2.4 对称与非对称量化

2.5 Per-Tensor and Per-Channel

2.6 PTQ

2.7 QAT

2.8 敏感性分析

2.6 退火学习率

3. 几点建议

4. 总结


1. 介绍

Practical Quantization in PyTorch | PyTorchQuantization is a cheap and easy way to make your DNN run faster and with lower memory requirements. PyTorch offers a few different approaches to quantize your model. In this blog post, we’ll lay a (quick) foundation of quantization in deep learning, and then take a look at how each technique looks like in practice. Finally we’ll end with recommendations from the literature for using quantization in your workflows.icon-default.png?t=N7T8https://pytorch.org/blog/quantization-in-practice/#fundamentals-of-quantization

本文介绍了量化的基本概念和实践应用:

  • 映射函数:将浮点值映射到整数空间,常用的映射函数是线性变换。
  • 量化参数:包括比例因子 ( S ) 和零点 ( Z ),用于确定输入数据的范围和偏移。
  • 校准:确定量化过程中所需的缩放因子和零点,常用方法包括 MaxMin、Percentile、Entropy、MSE 和 Diffs。
  • 对称与非对称量化:对称量化无需计算零点偏移,而非对称量化则需要。
  • Per-Tensor 和 Per-Channel:Per-Tensor 使用相同的量化参数,而 Per-Channel 则为每个通道使用不同的量化参数。
  • PTQ(训练后静态量化):适用于大型模型,通过模块融合和定期校准来减少量化误差。
  • QAT(量化感知训练):通过在训练过程中模拟量化误差来提高小型模型的量化精度。
  • 敏感性分析:确定哪些层对量化最敏感,并保留这些层的 FP32 精度。
  • 退火学习率:通过动态调整学习率来帮助模型更好地收敛。

2. 基本概念

2.1 映射函数

映射函数是一个将值从浮点映射到整数空间的函数。

常用的映射函数是线性变换,由下式给出:

Q_{r}=round(r/S+Z)

其中,r是输入,S和Z是量化参数。

可以将量化后的模型重新转换为浮点空间,反函数由下式给出:

\widetilde{r}=(Q_{r}-Z)\cdot S

\widetilde{r}\neq r,它们的差值构成了量化误差。

2.2 量化参数

映射函数有两个参数:比例因子 S 和零点 Z。

比例因子 S

S 就是输入范围与输出范围的比率:

S=(\beta_{U}-\alpha _{L})/(\beta_{q}-\alpha _{q})

\beta_{U}\alpha _{L}:输入的裁剪范围,即允许输入数据的边界值。这个范围定义了输入数据中哪些值会被保留并映射到量化后的输出空间,而超出这个范围的值会被剪切(截断)。

例如,对于一个输入范围 [-5.0, 10.0],所有小于-5.0的值会被剪切为-5.0,所有大于10.0的值会被剪切为10.0,以确保输入数据在量化过程中不会因为极端值而导致量化精度的显著下降

零点 Z

Z 为偏差量,以确保输入空间中的 0 完美映射到量化空间中的 0。

1). 偏移调整:零点作为偏移量,将缩放后的数据移动到量化范围内。例如,在无符号量化中,零点可以将负值移动到正值范围内。

2). 对称量化:在对称量化中,零点通常为零,因为浮点范围和量化范围是对称的。

3). 非对称量化:在非对称量化中,零点用于调整量化范围,使其适应非对称的浮点数据分布。

2.3 校准

校准(Calibration)的目的是为了确定量化过程中所需的缩放因子(scale factor)和零点(zero point),以便将浮点数转换为整数表示。

Vitis AI 中,3.2.3 局部量化设置 2.Method,提供了不同的校准方法:maxmin、percentile、entropy、mse、diffs。

  • MaxMin:使用校准数据的最大值和最小值来确定范围。这是最简单的方法,但容易受到异常值的影响。
  • Percentile:基于数据的分位数来确定范围,通常使用 99.9% 分位数来避免异常值的影响。
  • Entropy:使用信息熵(如 KL 散度)来最小化原始浮点值和量化值之间的信息损失。这种方法可以最大化保留信息,但计算复杂度较高。
  • MSE(Mean Squared Error):通过最小化原始值和量化值之间的均方误差来确定范围。这种方法在保留模型精度方面表现良好。
  • Diffs:基于数据的差异来确定范围,具体实现可能因工具而异。

量化观察器:

import torch
from torch.quantization.observer import MinMaxObserver, MovingAverageMinMaxObserver, HistogramObserver
C, L = 3, 4
# 使用正态分布生成两个随机张量作为输入数据
normal = torch.distributions.normal.Normal(0,1)
inputs = [normal.sample((C, L)), normal.sample((C, L))]
print(inputs)observers = [MinMaxObserver(), MovingAverageMinMaxObserver(), HistogramObserver()]
for obs in observers:for x in inputs: obs(x) print(obs.__class__.__name__, obs.calculate_qparams())

执行结果:

[tensor([[-0.1551,  0.4171,  0.0281,  0.8844],[-0.0766,  1.4027,  0.1924,  0.8369],[ 0.7786,  1.0915,  0.4398, -1.8102]]),tensor([[-1.2902, -1.3943, -1.6080,  0.1695],[-0.9307, -0.5508,  0.6164, -2.2461],[-1.1094, -0.3126,  0.5751,  0.6137]])]MinMaxObserver (tensor([0.0143]), tensor([157], dtype=torch.int32))
MovingAverageMinMaxObserver (tensor([0.0126]), tensor([144], dtype=torch.int32))
HistogramObserver (tensor([0.0125]), tensor([141], dtype=torch.int32))

上述结果中,含缩放因子(tensor([0.0143])),零点(tensor([157], dtype=torch.int32))。

通过这个例子,可以看到不同的观察器在处理相同的数据时,可能会生成不同的量化参数。这有助于理解不同观察器的行为和它们在量化过程中可能产生的影响。 

2.4 对称与非对称量化

对称量化方案

对称量化方案(Symmetric quantization schemes),将输入范围集中在 0 附近,无需计算零点偏移。范围计算如下:

-\alpha _{L}=\beta _{U}=max(\left | max(r) \right |,\left | min(r) \right |)

对于倾斜信号(如非负激活),这可能会导致量化分辨率不佳,因为剪切范围包含永远不会出现在输入中的值。

非对称量化方案

非对称量化方案(Asymmetric quantization schemes),将输入空间的最小和最大观测值分配给(\beta_{U},\alpha _{L})。范围计算如下:

\beta_{U}=max(r), \alpha _{L}=min(r)

对于量化非负激活非常有用(如果输入张量从不为负,则不需要输入范围包含负值)。

import torch
import matplotlib.pyplot as plt
import numpy as np# 从帕累托分布中生成的激活值样本
act = torch.distributions.pareto.Pareto(1, 10).sample((1, 1024))
# 从正态分布中生成的权重样本,并将其展平
weights = torch.distributions.normal.Normal(0, 0.12).sample((3, 64, 7, 7)).flatten()def get_range(x, scheme):if scheme == 'asymmetric':return x.min().item(), x.max().item()elif scheme == 'symmetric':beta = torch.max(x.max(), x.min().abs())return -beta.item(), beta.item()# 计算直方图、边界、以及直方图中非零部分的25%和95%分位数。
def prepare_data(data, scheme):boundaries = get_range(data, scheme)hist, bin_edges = np.histogram(data.numpy(), bins=100, density=True)ymin, ymax = np.quantile(hist[hist > 0], [0.25, 0.95])return hist, bin_edges, boundaries, ymin, ymax# 准备激活和权重数据
act_asymmetric = prepare_data(act, 'asymmetric')
act_symmetric  = prepare_data(act, 'symmetric')
weights_asymmetric = prepare_data(weights, 'asymmetric')
weights_symmetric  = prepare_data(weights, 'symmetric')# 绘图循环
fig, axs = plt.subplots(2, 2, figsize=(12, 8))
titles = ["Activation, Asymmetric-Quantized", "Activation, Symmetric-Quantized", "Weights, Asymmetric-Quantized", "Weights, Symmetric-Quantized"]
data_list = [act_asymmetric, act_symmetric, weights_asymmetric, weights_symmetric]for ax, data, title in zip(axs.flatten(), data_list, titles):hist, bin_edges, boundaries, ymin, ymax = dataax.hist(bin_edges[:-1], bin_edges, weights=hist)ax.vlines(x=boundaries, ls='--', colors='purple', ymin=ymin, ymax=ymax)ax.set_title(title)plt.tight_layout()
plt.show()

结果:

2.5 Per-Tensor and Per-Channel

Per-Tensor:整个张量使用相同的比例因子 S 和零点 Z。

Per-Channel:每个通道使用一组比例因子 S 和零点 Z。

Per-Channel 可以减少量化误差,因为异常值只会影响它所在的通道,而不是整个张量。 

2.6 PTQ

训练后静态量化(Post-Training Static Quantization)。

PTQ 方法非常适合大型模型(>10M),但在较小的模型中准确性会受到影响。

模块融合将多个顺序模块(例如: [Conv2d, BatchNorm, ReLU] )合并为一个。融合模块意味着编译器只需要运行一个内核而不是多个;这可以通过减少量化误差来加快速度并提高准确性。

静态量化模型可能需要定期重新校准,以保持对分布漂移的鲁棒性。

2.7 QAT

量化感知训练(Quantization-aware Training)。

QAT 通过将量化误差包含在训练损失中来解决小型模型(<10M)量化数值精度的损失。

所有权重和偏差都存储在 FP32 中,正常进行反向传播。然而,在前向传播中,量化是通过 FakeQuantize 模块进行内部模拟的。FakeQuantize 对数据进行量化并立即反量化,从而添加类似于量化推理期间可能遇到的量化噪声。因此,最终的损失考虑了任何预期的量化误差。

QAT 比 PTQ 具有更高的准确度。

在 QAT 中重新训练模型的计算成本可能是数百个 epoch。

import torch
from torch import nnbackend = "fbgemm"  # 在x86 CPU上运行。如果在ARM上运行,请使用 "qnnpack"。m = nn.Sequential(nn.Conv2d(2,64,8),nn.ReLU(),nn.Conv2d(64, 128, 8),nn.ReLU()
)"""融合模块"""
torch.quantization.fuse_modules(m, ['0','1'], inplace=True) # 融合第一对Conv-ReLU
torch.quantization.fuse_modules(m, ['2','3'], inplace=True) # 融合第二对Conv-ReLU"""插入量化和去量化节点"""
m = nn.Sequential(torch.quantization.QuantStub(), *m, torch.quantization.DeQuantStub())"""准备进行量化感知训练"""
m.train()
m.qconfig = torch.quantization.get_default_qconfig(backend)
torch.quantization.prepare_qat(m, inplace=True)"""训练循环"""
n_epochs = 10
opt = torch.optim.SGD(m.parameters(), lr=0.1)
loss_fn = lambda out, tgt: torch.pow(tgt-out, 2).mean()
for epoch in range(n_epochs):x = torch.rand(10,2,24,24)  # 生成随机数据out = m(x)  # 通过模型传递数据loss = loss_fn(out, torch.rand_like(out))  # 计算损失opt.zero_grad()  # 清除梯度loss.backward()  # 反向传播opt.step()  # 更新参数"""转换为量化模型"""
m.eval()  # 设置为评估模式
torch.quantization.convert(m, inplace=True)  # 转换模型为量化版本

2.8 敏感性分析

并非所有层对量化的响应都相同,有些层对精度下降比其他层更敏感。

确定最小化精度下降的最佳层组合非常耗时。

进行一次一个的敏感性分析,可以确定哪些层最敏感,并保留这些层的 FP32 精度。

实验中,仅跳过 2 个卷积层(MobileNet v1 中总共 28 个)即可获得接近 FP32 的精度。

2.6 退火学习率

退火学习率(Annealing learning rate)是一种动态调整学习率的方法,灵感来自于物理中的退火过程。退火是指通过逐渐降低温度来减少系统的能量,从而达到更稳定的状态。在机器学习中,退火学习率计划(Annealing learning rate schedule)通过逐渐降低学习率来帮助模型更好地收敛,避免陷入局部最优解。

常见的 Annealing learning rate schedule 方法包括:

  • 指数衰减(Exponential Decay):学习率按指数函数逐渐减小。
  • 余弦退火(Cosine Annealing):学习率按余弦函数周期性减小。
  • 分段衰减(Step Decay):学习率在特定的训练轮次后按固定比例减小。

3. 几点建议

需要注意的几点:

1. 大型模型(参数超过1000万)对量化误差更具鲁棒性。

2. 从预训练的32位量化模型比从头训练INT8模型提供更好的准确性。

3. 通过运行时剖析模型,可以帮助识别在推理中造成瓶颈的层。

4. 动态量化是一个简单的第一步,特别是如果你的模型有很多线性或递归层。

5. 对于权重量化,使用带有MinMax观察器的对称通道量化。对于激活量化,使用带有移动平均MinMax观察器的仿射张量量化。

6. 使用 SQNR 等指标来识别哪些层最容易受到量化误差的影响。关闭这些层的量化。

7. 使用量化感知训练(QAT)进行微调,训练时间约为原始训练计划的10%,并采用从初始训练学习率的1%开始的退火学习率计划。

4. 总结

量化技术在深度学习模型优化中具有重要作用,通过合理选择量化方法和参数,可以在不显著降低模型精度的情况下,显著提高模型的推理速度和内存效率。

本文记录的概念和建议能帮助在今后的实际应用中更好地利用量化技术。

相关文章:

  • PHP进阶篇(奇怪的知识又增加了)
  • 局部整体(三)利用python绘制饼图
  • PostgreSQL-04-入门篇-连接多张表
  • 【微服务】Nacos配置中心和客户端数据同步模式
  • Vue3项目开发——新闻发布管理系统(一)
  • 《黑神话:悟空》总销量已破 450 万份,总销售额超过15亿元,对于单机游戏来说,这一成绩意味着什么?
  • 博途PLC手自动控制功能块(FB和FC完整SCL源代码)
  • 干货:2024必备的四大PDF编辑器推荐!
  • ​​​​​​​STM32通过SPI硬件读写W25Q64
  • Hadoop入门基础(三):Hadoop启动踩坑记录
  • 数学基础 -- 定积分之估算积分
  • [Qt][Qt 文件]详细讲解
  • PHP网页下的注入原理
  • 【时时三省】(C语言基础)指针进阶
  • 亦菲喊你来学习之机器学习(6)--逻辑回归算法
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • canvas实际项目操作,包含:线条,圆形,扇形,图片绘制,图片圆角遮罩,矩形,弧形文字...
  • iOS帅气加载动画、通知视图、红包助手、引导页、导航栏、朋友圈、小游戏等效果源码...
  • Java小白进阶笔记(3)-初级面向对象
  • Lsb图片隐写
  • markdown编辑器简评
  • React Native移动开发实战-3-实现页面间的数据传递
  • SpiderData 2019年2月13日 DApp数据排行榜
  • 前端学习笔记之观察者模式
  • 入手阿里云新服务器的部署NODE
  • 使用SAX解析XML
  • 我这样减少了26.5M Java内存!
  • 新书推荐|Windows黑客编程技术详解
  • 学习笔记TF060:图像语音结合,看图说话
  • 怎么把视频里的音乐提取出来
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • 【干货分享】dos命令大全
  • Android开发者必备:推荐一款助力开发的开源APP
  • ​【数据结构与算法】冒泡排序:简单易懂的排序算法解析
  • ###51单片机学习(2)-----如何通过C语言运用延时函数设计LED流水灯
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • #知识分享#笔记#学习方法
  • (12)目标检测_SSD基于pytorch搭建代码
  • (20)docke容器
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (免费领源码)Java#ssm#MySQL 创意商城03663-计算机毕业设计项目选题推荐
  • (牛客腾讯思维编程题)编码编码分组打印下标(java 版本+ C版本)
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (十八)devops持续集成开发——使用docker安装部署jenkins流水线服务
  • (四)Controller接口控制器详解(三)
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (转)关于如何学好游戏3D引擎编程的一些经验
  • (最新)华为 2024 届秋招-硬件技术工程师-单板硬件开发—机试题—(共12套)(每套四十题)
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • .form文件_一篇文章学会文件上传
  • .gitignore文件设置了忽略但不生效
  • .NET 2.0中新增的一些TryGet,TryParse等方法
  • .NET 8.0 中有哪些新的变化?
  • .NET Core MongoDB数据仓储和工作单元模式封装
  • .NET Standard / dotnet-core / net472 —— .NET 究竟应该如何大小写?