在深度学习领域,模型通常采用float32这种数据格式进行训练和存储,每个参数占据32比特(即4字节)的空间。例如,一个大小为7b的模型会需求28b的显存或内存资源。但如果能够将参数所需的存储空间压缩至16比特、8比特甚至4比特,那么就能显著减少所需的存储容量,并可能加快模型的推理速度。量化可以应用在模型的参数(即权重)、激活值,甚至是在训练过程中更新的梯度上。参数的量化通常相对容易实施,因为模型的参数分布通常比较稳定。
量化可以按照处理时机或技术方法进行分类。
(1)量化感知训练(QAT) - 在模型训练阶段,即在反向传播过程中更新梯度时,进行量化处理。
(2)后量化(PTQ) - 在模型训练完成后进行量化,这种方法又细分为两种:
训练后动态量化 - 无需校准数据集,直接按照量化算法转换每一层的参数。QLoRA就是采用这种方法的一个例子。
训练后校正量化 - 需要输入具有代表性的数据集,据此调整每层的量化策略来优化权重。GPTQ是这一方法的一个实践。
线性量化 - 如果量化前后数值保持线性关系,则这种量化方式被称为线性量化。
非线性量化 - 如果量化过程中数值关系不是线性的,则称之为非线性量化。
对称量化 - 在这种方式中,目标是让为0的参数在量化后仍保持为0。
非对称量化 - 如果量化过程中0的表示发生改变,则此方式被称为非对称量化。
Int8量化是一种优化技术,它将32位的浮点数(float32)模型参数转换为8位整数(int8)。这有助于减少模型的内存占用,加快其执行速度,并有可能减少能耗,特别是在边缘设备上。以下是完成Int8量化所采用的基本计算公式:
(1)确定缩放因子(Scale)和零点(Zero Point):
首先,你需要计算量化过程使用的两个重要值:缩放因子(scale)和零点(zero point)。缩放因子用于将浮点数映射到整数范围,零点则确保浮点数0可以被准确地表示为整数。
计算scale和zero_point通常需要最大值(max_val)和最小值(min_val):
scale = (max_val - min_val) / (2^7 - 1) # 127是int8的最大值 zero_point = round(-min_val / scale)
这里,zero_point需要在-128到127的范围内,因为这是8位有符号整数的范围。
(2)应用量化:
使用scale和zero_point,量化一个浮点数(float_val)成一个8位整数(int_val):
int_val = round(float_val / scale) + zero_point
(3)整数裁剪:
如果量化后的整数超出了-128到127的范围,它需要被裁剪以保持在有效范围内。
int_val = min(127, max(-128, int_val))
这样,量化过程就可以将32位浮点数数组转换为能够在8位整数范围内表示的数组,这减少了内存占用,同时可能提高了执行速度。
这里举一个常见的int8量化例子,在这个例子中,使用了线性、非对称、后量化的方法。
(注意,以下例子只是为了理解。许多深度学习框架(如TensorFlow和PyTorch)提供了现成的量化工具,以便自动完成这些转换。)
量化的计算公式为:
q = (w / scale) + zero_point
其中w是未量化的值,q是量化后的值。 scale和zero_point在下面有介绍。
假设我们有下面这个列表的模型权重作为浮点数:
weights = [0.15, -0.8, 0.2, -0.5]
量化到8位整数涉及以下步骤:
(1)确定量化范围:
在Int8量化中,值的范围通常是[-128, 127](对于有符号整数)。
找到浮点列表中的最小和最大值:
min_val = min(weights) # -0.8
max_val = max(weights) # 0.2
(2)计算缩放系数(scale)和零点(zero point):
计算缩放系数,将浮点数范围映射到整数范围。举例来说,可以简单选择max(abs(min_val), abs(max_val))作为分母,然后以[-128, 127]为分子进行缩放计算。
因为0可能不在浮点数的范围内,我们需要一个零点来表示量化值0对应的浮点数。通常,零点是通过缩放原始的浮点数零值来确定的。
scale = (max_val - min_val) / (127 - (-128))
# 缩放系数 (0.2 - (-0.8)) / (127 - (-128))
# scale = 1.0 / 255
zero_point = 127-max_val / scale
# zero_point = 76
(3)应用量化转换:
将每个浮点数权重转换为整数表示。
quantized_weights = [round((w / scale) + zero_point) for w in weights]
应用上面的公式,具体计算:
quantized_weights = [
round(0.15 / scale) + zero_point,
round(-0.8 / scale) + zero_point,
round(0.2 / scale) + zero_point,
round(-0.5 / scale) + zero_point
]
# 根据scale,四舍五入并加上zero_point
quantized_weights = [
round(38.4) + 76,
round(-204.8) + 76,
round(51.2) + 76,
round(-128.0) + 76
]
得到的quantized_weights是: [114, -129, 127, -52]
(4)做截断,使得数据落在[-128,127]范围
量化后的权重如下所示:
quantized_weights = [114, -128, 127, -52]
QLoRA 创新地将模型量化技术(Quant)与精细的LoRA参数微调策略相结合,使得在仅有48GB内存的单个GPU上就能对高达65B参数的大型模型进行微调成为可能。QLoRA所采用的量化方法—由bitsandbytes库提供支持—已成为Transformers模型量化的官方实施方案。
截至2023年7月14日,采用QLoRA微调技术训练的模型Guanaco在多个任务上均展现出卓越性能,其65B版本的模型在Open LLM Leaderboard上名列第二,显著优于初代的llama-65B模型。得益于QLoRA高效的训练方法以及在下游任务中的出色表现,Guanaco模型自公布以来,QLoRA的方法便受到了业界的广泛关注。
QLoRA对模型权重进行量化时使用的是对称量化算法,其过程与前述的线性量化方法基本一致。QLoRA的解决方案主要包括三个部分:
(1)NF4 Quantization(4-bit量化):这是一种基于信息论启发的全新int4量化技术。NF4量化能保障在量化过程中数据分布的一致性,换句话说,经NF4量化的权重信息损失较小,从而保证模型整体精度的最小损失。
(2)Double Quantization:对初次完成量化的常量进行二次量化,进一步缩减模型存储体积。
(3)Paged Optimizers:利用NVIDIA的统一内存管理功能,该技术可以在CPU和GPU之间自动进行页对页的传输,使得即便在GPU偶发地内存溢出(OOM)时仍能够继续进行训练。这可以理解为在训练过程中出现临时OOM时的自动故障处理机制。
上面我举了一个int8量化的例子。int8量化是一种常见的线性量化过程,其计算公式是线性的:
q = (w / scale) + zero_point。
但这存在一个问题:若数据分布不均匀,量化后的值有可能“粘连”堆叠在一起。例如,数组weights_fp32 = [0.001, 0.0015, 0.0016, 0.002, 55.0]在经量化处理后,变为:
quantized_weights = [-128,-128,-128,-128,127]。
这四个原本不同的权重经量化后全数转化为相同的数值,导致模型出现较大误差。一般的模型参数通常呈正态分布,而非均匀分布。若依照线性方式进行量化,极可能导致多个不同的值被量化到相同的数值上。如参数符合标准正态分布,(0,1)区间内的值差异性将远大于(10,11),造成相同值概率的不均衡。
相较于此,nf4量化则采取一种非对称量化方式,它基于分位数来执行量化映射。在标准正态分布里,由于靠近中心0点的取值较多,非对称量化能为这些取值提供更多的“格子”,以维持数据的精细度。
在刚才的讲解中,我们提到了这样一个例子:浮点数数组weights_fp32 = [0.001, 0.0015, 0.0016, 0.002, 55.0]通过量化处理后,转变为了quantized_weights = [-128, -128, -128, -128, 127]。 之所以会出现这种情况,是因为我们在量化过程中设定的scale约等于55/255,大约是0.21。
这意味着,若两个不同的浮点数之间的差值小于或等于0.21,它们在量化过程中有很大几率被映射到同一个整数值上。
你可以把量化理解成装格子的过程。int8量化有256个不同的选值,相当于有256个不同的格子——即,最大值和最小值中间,等间隔地摆放256个不同的格子,然后每个参数值都跳进离它最近的格子里。
如果原始数值相当接近,它们就极有可能最终跳入同一个"格子"。如此一来,这些数值在量化后就会归并为一个,从而引起误差。
再举个例子,假设我们有另一个浮点数数组weights_fp32 = [-128, 0, 0.1, 0.2, 0.3, 127]。在这种情况下,scale正好等于1, 量化公式简化为q = int(x),那么量化后的结果就是[-128, 0, 0, 0, 0, 127]。
在这个具体的案例中,我们看到参数值相邻且接近0的情况比较多。原本不同的数值最终都被归入同一个格子,这就产生了误差。如果我们按照分位数进行映射,那么相同参数范围内的量化结果将会更加离散。例如,使用分位数映射,量化后的数值有可能是[-128, 0, 2, 4, 6, 127]。请注意,这里的数值只是为了演示而随意设定的,实际的量化映射细节将更加复杂,是需要根据数据分布的分位数来确定的。
NF4量化策略基于一项假设:参数服从标准正态分布,并以此为依据,更科学地将32位浮点数映射到4位整数,以尽可能减少精度损失。4位表示意味着我们只有16个取值可用,可以想象成仅有16个"格子"。如果参数被规整的方式均匀地分布到这些有限的"格子"中,显然模型的精度可能会遭受损害。
典型的量化做法是为了削弱异常值的影响,将输入张量划分为若干块(block),并对每个块分别进行量化,让每个块都有独立的缩放因子(scale)和零点(zero)。这种策略可以显著降低由于量化引起的精度损失(如下图中显示的橙色误差区域,图片来自于互联网)。
在QLoRA框架中,采用64个参数构成一个block进行量化,即block_size=64,每个块计算出一个对称量化中用到的Scale值。因为是对称量化,我们不必存储零点这个值。(值得一提的是,如果block的大小等于所有参数的总量,这就相当于没有划分任何block)
如果以32位浮点数存储Scale,那么每个block将会额外存储一个32位数字,分摊到每个参数上,这意味着每个参数实际上需要额外的32/64=0.5bit存储空间。因此,每个参数实际占用的存储空间变成了4+0.5=4.5bits。
为了优化这一存储需求,研究人员提出了Double Quant策略,即对Scale本身再进行一次量化;不过这里使用的是线性量化方法,量化后的格式为FP8,其中block_size=256。
注:之前提到,参数的分布大致服从正态分布,而非均匀分布,因此采用非线性量化。对于scale,研究人员采用线性量化的方式,其背后的考量还有待研究者揭示。随机生成64个服从标准正态分布的数,最大值和最小值之差的分布实际上并不服从均匀分布,如下图所示:
Double Quant 后,每个参数做量化只需要额外的 8/64 + 32 / (64*256) = 0.127 bits 显存。
Double Quant策略通过对量化系数Scale再次进行量化,有效地降低了每个参数所需的额外存储开销。在这一策略的应用之下,每个参数的总量化开销降至大约0.127 bits的额外显存,极大程度上节约了资源。这种存储优化的方法让大规模模型能够在资源有限的设备上高效运行,同时保持了模型的性能和精度。
代码:
model = AutoModelForCausalLM.from_pretrained("Llama-2-7b-ms",
low_cpu_mem_usage=True,
load_in_8bit=True,
device_map="auto")
模型参数类型:
模型参数量:
sum(param.numel() for param in model.parameters())
output: 6738415616
=(409640964+1100840963)32+2320004096+24096*32+4096(最后有一个norm层)
模型占用内存:
开启训练后的参数增量:1G
代码:
model = AutoModelForCausalLM.from_pretrained("Llama-2-7b-ms",
low_cpu_mem_usage=True,
load_in_4bit=True,
device_map="auto")
模型参数类型:
有几个需要注意的点:
1.embedding层和layernorm层是float16格式
2.剩余层竟然是uint8格式,这一点比较奇怪,而且参数矩阵的shape也比较奇怪。
llama-7b的hiddensize=4096,ffn_dim=11008
注意到22544384=4096*11008/2
8388608=4096*4096/2
我的猜测是,两个fp4相当于组成了一个uint8。
3.模型参数量变成了3504607232,
(409640964+1100840963)16+2320004096+24096*32+4096=3500412928
还相差一个40961024=4096(8*4)*32是lora层,lora的隐藏参数是8,然后QV两个地方用到了lora
这样加起来等于3504607232
模型占用内存:
开启训练后的参数增量:1.3G
代码:
from transformers import BitsAndBytesConfig
import torch
nf4_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.bfloat16
)
model = AutoModelForCausalLM.from_pretrained("Llama-2-7b-ms",
low_cpu_mem_usage=True,
quantization_config=nf4_config,
device_map="auto")
参数解释:
load_in_4bit:替换Linear层为FP4/NF4层,启用4位量化
bnb_4bit_compute_dtype:设置计算类型,它可能与输入时的类型不同。例如,输入可能是fp32,但计算可以设置为bf16以获得速度提升。
bnb_4bit_use_double_quant:是否开启double_quant
bnb_4bit_quant_type:有两个参数可以选fp4和nf4,默认是fp4
注意,在后面的TrainingArguments中,应设置optim="paged_adamw_32bit",否则训练阶段会报错
模型参数类型:
量化的层仍然是uint8格式
参数量:3500412928+4194304,和FP4的情况一样
模型占用内存:
开启训练后的参数增量:1G
1.NF4格式能否被直接用于运算?
答:NF4是一种专有的数据格式。当前的Tensor Core或CUDA Core并不支持直接对NF4格式数据进行运算。
2.在QLORA中,什么是存储数据类型和计算数据类型分别是什么?在前向和后向传播中,我们是如何将存储数据类型转化为计算数据类型的?在参数更新过程中,我们只计算哪些权重的梯度?
在QLORA中,存储数据类型通常是4位的NormalFloat(NF4),而计算数据类型通常是16位的BrainFloat(BFloat16)。
在前向传播和后向传播过程中,我们将存储数据类型转化为计算数据类型的方法是通过进行反量化(dequantization)操作。具体来说,在权重张量被使用时,我们将其从存储数据类型(NF4)反量化为计算数据类型(BFloat16),然后进行16位的矩阵乘法运算。
在参数更新过程中,我们只计算适配器(adapter)权重的梯度,而不计算4位权重的梯度。这意味着我们只对适配器的参数进行更新,而对4位权重不进行更新。
3.QLoRA的效果怎么样?
通过实施NF4量化加上二次量化的策略,对模型进行微调后的精度能够与BFloat16相媲美。
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
# 参数: 均值(mu)和标准偏差(sigma)
mu, sigma = 0, 1
# 生成x轴上均匀的值,覆盖正态分布的大部分区域
x = np.linspace(mu - 4*sigma, mu + 4*sigma, 1000)
# 计算正态分布的概率密度函数值
pdf = norm.pdf(x, mu, sigma)
plt.figure(figsize=(10, 6))
plt.plot(x, pdf, label='正态分布')
# 量化级别的示例,比如8个均匀间隔的级别
quant_levels = 8
min_quant = mu - 3*sigma
max_quant = mu + 3*sigma
quant_values = np.linspace(min_quant, max_quant, quant_levels)
for value in quant_values:
plt.axvline(value, color='r', linestyle='--')
# 高亮显示可能产生粘连的区域
for i in range(quant_levels - 1):
plt.fill_betweenx(pdf, quant_values[i], quant_values[i + 1], color='y', alpha=0.3)
# plt.title('正态分布与量化级别')
# plt.legend()
# plt.xlabel('值')
# plt.ylabel('概率密度')
plt.grid(True)
plt.show()
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm
# Parameters: mean (mu) and standard deviation (sigma)
mu, sigma = 0, 1
# Generate some x values evenly over the range of the distribution
x = np.linspace(mu - 4*sigma, mu + 4*sigma, 1000)
# Calculate the probability density function (pdf) for the normal distribution
pdf = norm.pdf(x, mu, sigma)
plt.figure(figsize=(10, 6))
plt.plot(x, pdf, label='Normal Distribution')
# Calculate quantiles using the cumulative distribution function (CDF)
quantiles = norm.ppf([i/8 for i in range(1, 8)], mu, sigma)
# Draw vertical lines at quantile positions
for quantile in quantiles:
plt.axvline(quantile, color='r', linestyle='--')
# Highlight regions between quantiles to show potential 'clamping' effect
for i in range(len(quantiles) - 1):
plt.fill_betweenx(pdf, quantiles[i], quantiles[i + 1], color='y', alpha=0.5)
# Highlight the first and last region
plt.fill_betweenx(pdf, mu - 4*sigma, quantiles[0], color='y', alpha=0.5)
plt.fill_betweenx(pdf, quantiles[-1], mu + 4*sigma, color='y', alpha=0.5)
# Annotate quantiles on the plot
for i, quantile in enumerate(quantiles):
plt.text(quantile, 0.02, f'{(i+1)*12.5}%', ha='center', va='bottom', color='black')
plt.title('Normal Distribution with Equal Area Quantiles')
plt.legend()
plt.xlabel('Values')
plt.ylabel('Probability Density')
plt.grid(True)
plt.show()
import numpy as np
import matplotlib.pyplot as plt
# 设置随机种子,用于结果复现
np.random.seed(0)
# 指定生成的数据组数
num_samples = 100000
# 创建一个空列表来存储每组数值的最大值和最小值之差
max_min_diffs = []
# 对于每组64个标准正态分布的随机数,找到最大值和最小值之差,并添加到列表中
for _ in range(num_samples):
values = np.random.randn(64)
max_val = np.max(values)
min_val = np.min(values)
max_min_diff = max_val - min_val
max_min_diffs.append(max_min_diff)
# 可视化结果
plt.figure(figsize=(10, 6))
plt.hist(max_min_diffs, bins=50, alpha=0.7, color='blue')
plt.title('Distribution of the Difference Between Max and Min Values (64 Normal Samples)')
plt.xlabel('Max - Min Value')
plt.ylabel('Frequency')
plt.grid(True)
plt.show()
https://huggingface.co/blog/zh/4bit-transformers-bitsandbytes
https://huggingface.co/blog/zh/hf-bitsandbytes-integration
QLoRA、GPTQ:模型量化概述 - 杨远航的文章 - 知乎
https://zhuanlan.zhihu.com/p/646210009
大模型微调实战(八)-使用INT8/FP4/NF4微调大模型 - 吃果冻不吐果冻皮的文章 - 知乎
https://zhuanlan.zhihu.com/p/670116171
QLoRA——技术方案总结篇 - 52AI的文章 - 知乎
https://zhuanlan.zhihu.com/p/632717605
【【手把手带你实战HuggingFace Transformers-低精度训练篇】4bit量化与QLoRA模型训练】
【手把手带你实战HuggingFace Transformers-低精度训练篇】4bit量化与QLoRA模型训练_哔哩哔哩_bilibili
大模型高效微调-QLoRA代码实战 - 老苏聊AI的文章 - 知乎
通信工程专业毕业,7年开发经验
精通c/c++
精通golang
熟悉常见的脚本,js,lua,python,php
熟悉电路基础,嵌入式,单片机
服务端开发
嵌入式开发
>gin接口代码CURD生成工具
sql ddl to struct and markdown,将sql表自动化生成代码内对应的结构体和markdown表格格式,节省宝贵的时间。
qt .ui文件转css文件
duilib xml 自动生成绑定控件代码
协议调试器
基于lua虚拟机的的协议调试器软件 支持的协议有:
串口
tcp客户端/服务端
udp 组播/udp节点
tcp websocket 客户端/服务端
软件界面
使用例子: 通过脚本来获得接收到的数据并写入文件和展示在界面上
下载地址和源码
webrtc easy demo
webrtc c++ native 库 demo 实现功能:
基于QT
webrtc摄像头/桌面捕获功能
opengl渲染/多播放窗格管理
janus meeting room
下载地址和源码
wifi,蓝牙 - 无线开关
实现功能:
通过wifi/蓝牙实现远程开关电器或者其他电子设备
电路原理图:
实物图:
深度学习验证工具
虚拟示波器
硬件实物图:
实现原理
基本性能
采集频率: 取决于外部adc模块和ebaz4205矿板的以太网接口速率,最高可以达到100M/8 约为12.5MPS
上位机实现功能: 采集,显示波形,存储wave文件。
参数可运行时配置
上位机:
显示缓冲区大小可调
刷新率可调节
触发显示刷新可调节
又一个modbus调试工具
最近混迹物联网企业,发现目前缺少一个简易可用的modbus调试工具,本软件旨在为开发者提供一个简单modbus测试工具。
主打一个代码简单易修改。
特点:
1. 基于QT5
2. 基于libmodbus
3. 三方库完全跨平台,linux/windows。
开源plutosdr 板卡
1. 完全开源
2. 提高固件定制服务
3. 硬件售价450 手焊产量有线
测试数据
内部DDS回环测试
接收测试
外部发送500MHZ FM波形
matlab测试
2TRX版本
大部分plutosdr应用场景都是讲plutosdr板卡作为射频收发器来使用。
实际上plutosdr板卡本身运行linux 操作系统。是具有一定脱机运算的能力。
对于一些微型频谱检测,简单射频信号收发等应用完全可以将应用层直接实现在板卡上
相较于通过网卡或者USB口传输具有更稳定,带宽更高等优点。
本开源板卡由于了SD卡启动,较原版pluto支持了自定义启动应用的功能。
提供了应用层开发SDK(编译器,buildroot文件系统)。
通过usb连接电脑,经过RNDIS驱动可以近似为通过网卡连接
(支持固件的开发定制)。
二次开发例子
```
all:
arm-linux-gnueabihf-gcc -mfloat-abi=hard --sysroot=/root/v0.32_2trx/buildroot/output/staging -std=gnu99 -g -o pluto_stream ad9361-iiostream.c -lpthread -liio -lm -Wall -Wextra -lrt
clean:
rm pluto_stream
版面分析即分析出图片内的具体文件元素,如文档标题,文档内容,文档页码等,本工具基于cnstd模型