(1)4位NormalFloat,一种信息理论上最优的正态分布数据量化数据类型,比4位整数和4位浮点数产生更好的经验结果。
(2)双量化。
(3)Paged Optimizers,使用NVIDIA统一内存,以避免处理具有长序列长度的小批量时发生的梯度检查点内存峰值。
将预训练模型量化为4位,然后添加一组可学习的Low-rank Adapter权重[28],这些权重通过量化权重的反向传播梯度进行调整


为了防止异常值问题,一种常见的方法是将输入张量分成独立量化的块,每个块都有自己的量化常数c。避免因为离群值导致量化效果不佳

NormalFloat (NF)数据类型建立在Quantile Quantization的基础上,这是一种信息理论上最优的数据类型,可确保每个量化bin具有从输入张量分配的相同数量的值。
(1)估计理论的
分位数N(0, 1)获得k位分布为正态分布分位数量化数据类型,
(2)把这个数据类型和其数值正则化到(−1,1)范围内,
(3)量化输入权重张量通过改变绝对最大尺度范围来实现正则化(−1,1)。
其中
是标准正态分布N(0,1)的分位数函数。对称k位量化的一个问题是,这种方法没有零的精确表示,这是量化填充和其他零值元素而没有错误的重要性质。
解决对称量化问题:
负部分为
,正部分为
,然后我们统一这些qi集合并删除两个集合中出现的两个零中的一个。我们将结果数据类型命名为在每个量化中具有相同预期值的k位NormalFloat (NFk),因为该数据类型对于零中心正态分布数据在信息理论上是最优的
虽然精确的4位量化需要很小的块大小,但它也有相当大的内存开销。
e.g. 使用32位常量,W的块大小为64,量化常量平均为每个参数添加32/64 = 0.5位。
作为第二次量化的输入。这第二步产生量子化的量化常数
和第二级量化常数
。
在GPU偶尔内存不足的情况下,在CPU和GPU之间进行自动页对页传输,以实现无错误的GPU处理。
使用单个LoRA适配器在量化基本模型中定义单个线性层的QLORA如下
其中doubleDequant(·)定义为:

冗长的代码中其中这么几句便可以在transforms中使用NF4类型和双量化

- from transformers import BitsAndBytesConfig
- import torch
- model_name =""
- 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_nf4 = AutoModelForCausalLM.from_pretrained(model_name, quantization_config=nf4_config)
在transformers\utils\bitsandbytes.py的_replace_with_bnb_linear中

在bitsandbytes这个库中
- class LinearNF4(Linear4bit):
- ''' Implements the NF4 data type.
- Constructs a quantization data type where each bin has equal area under a standard normal distribution N(0, 1) that
- is normalized into the range [-1, 1].
- For more information read the paper: QLoRA: Efficient Finetuning of Quantized LLMs (https://arxiv.org/abs/2305.14314)
- Implementation of the NF4 data type in bitsandbytes can be found in the `create_normal_map` function in
- the `functional.py` file: https://github.com/TimDettmers/bitsandbytes/blob/main/bitsandbytes/functional.py#L236.
- '''
- def __init__(self, input_features, output_features, bias=True, compute_dtype=None, compress_statistics=True,device=None):
- super().__init__(input_features, output_features, bias, compute_dtype, compress_statistics, 'nf4', device)
调用了父类的构造方法
- class Linear4bit(nn.Linear):
- def __init__(self, input_features, output_features, bias=True, compute_dtype=None, compress_statistics=True, quant_type='fp4',device=None):
- super().__init__(input_features, output_features, bias, device)
- self.weight = Params4bit(self.weight.data, requires_grad=False, compress_statistics=compress_statistics, quant_type=quant_type)
- self.compute_dtype = compute_dtype
- self.compute_type_is_set = False
权重通过Params4bit进行压缩
- class Params4bit(torch.nn.Parameter):
- def __new__(cls, data=None, requires_grad=True, quant_state=None, blocksize=64, compress_statistics=True, quant_type='fp4'):
- if data is None:
- data = torch.empty(0)
-
- self = torch.Tensor._make_subclass(cls, data, requires_grad)
- self.blocksize = blocksize
- self.compress_statistics = compress_statistics
- self.quant_type = quant_type
- self.quant_state = quant_state
- self.data = data
- return self
-
- def cuda(self, device):
- w = self.data.contiguous().half().cuda(device)
- w_4bit, quant_state = bnb.functional.quantize_4bit(w, blocksize=self.blocksize, compress_statistics=self.compress_statistics, quant_type=self.quant_type)
- self.data = w_4bit
- self.quant_state = quant_state
-
- return self
quantize_4bit最终也是通过调取二进制文件实现若干不同类型之间的变化